diff options
Diffstat (limited to 'java/src/com')
86 files changed, 3983 insertions, 2545 deletions
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java index b9b6362fc..0ab84f7b5 100644 --- a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java +++ b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java @@ -36,6 +36,7 @@ import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardView; import com.android.inputmethod.latin.CollectionUtils; +import com.android.inputmethod.latin.CoordinateUtils; /** * Exposes a virtual view sub-tree for {@link KeyboardView} and generates @@ -62,7 +63,7 @@ public final class AccessibilityEntityProvider extends AccessibilityNodeProvider private final Rect mTempBoundsInScreen = new Rect(); /** The parent view's cached on-screen location. */ - private final int[] mParentLocation = new int[2]; + private final int[] mParentLocation = CoordinateUtils.newInstance(); /** The virtual view identifier for the focused node. */ private int mAccessibilityFocusedView = UNDEFINED; @@ -180,7 +181,8 @@ public final class AccessibilityEntityProvider extends AccessibilityNodeProvider // Calculate the key's in-screen bounds. mTempBoundsInScreen.set(boundsInParent); - mTempBoundsInScreen.offset(mParentLocation[0], mParentLocation[1]); + mTempBoundsInScreen.offset( + CoordinateUtils.x(mParentLocation), CoordinateUtils.y(mParentLocation)); final Rect boundsInScreen = mTempBoundsInScreen; diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java index 32618ad85..b87ae3a15 100644 --- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java +++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java @@ -26,6 +26,7 @@ import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardId; import com.android.inputmethod.latin.CollectionUtils; +import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.R; import java.util.HashMap; @@ -61,17 +62,19 @@ public final class KeyCodeDescriptionMapper { mKeyLabelMap.put(":-)", R.string.spoken_description_smiley); // Special non-character codes defined in Keyboard - mKeyCodeMap.put(Keyboard.CODE_SPACE, R.string.spoken_description_space); - mKeyCodeMap.put(Keyboard.CODE_DELETE, R.string.spoken_description_delete); - mKeyCodeMap.put(Keyboard.CODE_ENTER, R.string.spoken_description_return); - mKeyCodeMap.put(Keyboard.CODE_SETTINGS, R.string.spoken_description_settings); - mKeyCodeMap.put(Keyboard.CODE_SHIFT, R.string.spoken_description_shift); - mKeyCodeMap.put(Keyboard.CODE_SHORTCUT, R.string.spoken_description_mic); - mKeyCodeMap.put(Keyboard.CODE_SWITCH_ALPHA_SYMBOL, R.string.spoken_description_to_symbol); - mKeyCodeMap.put(Keyboard.CODE_TAB, R.string.spoken_description_tab); - mKeyCodeMap.put(Keyboard.CODE_LANGUAGE_SWITCH, R.string.spoken_description_language_switch); - mKeyCodeMap.put(Keyboard.CODE_ACTION_NEXT, R.string.spoken_description_action_next); - mKeyCodeMap.put(Keyboard.CODE_ACTION_PREVIOUS, R.string.spoken_description_action_previous); + mKeyCodeMap.put(Constants.CODE_SPACE, R.string.spoken_description_space); + mKeyCodeMap.put(Constants.CODE_DELETE, R.string.spoken_description_delete); + mKeyCodeMap.put(Constants.CODE_ENTER, R.string.spoken_description_return); + mKeyCodeMap.put(Constants.CODE_SETTINGS, R.string.spoken_description_settings); + mKeyCodeMap.put(Constants.CODE_SHIFT, R.string.spoken_description_shift); + mKeyCodeMap.put(Constants.CODE_SHORTCUT, R.string.spoken_description_mic); + mKeyCodeMap.put(Constants.CODE_SWITCH_ALPHA_SYMBOL, R.string.spoken_description_to_symbol); + mKeyCodeMap.put(Constants.CODE_TAB, R.string.spoken_description_tab); + mKeyCodeMap.put(Constants.CODE_LANGUAGE_SWITCH, + R.string.spoken_description_language_switch); + mKeyCodeMap.put(Constants.CODE_ACTION_NEXT, R.string.spoken_description_action_next); + mKeyCodeMap.put(Constants.CODE_ACTION_PREVIOUS, + R.string.spoken_description_action_previous); } /** @@ -97,17 +100,17 @@ public final class KeyCodeDescriptionMapper { boolean shouldObscure) { final int code = key.mCode; - if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) { + if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) { final String description = getDescriptionForSwitchAlphaSymbol(context, keyboard); if (description != null) return description; } - if (code == Keyboard.CODE_SHIFT) { + if (code == Constants.CODE_SHIFT) { return getDescriptionForShiftKey(context, keyboard); } - if (code == Keyboard.CODE_ACTION_ENTER) { + if (code == Constants.CODE_ACTION_ENTER) { return getDescriptionForActionKey(context, keyboard, key); } @@ -121,7 +124,7 @@ public final class KeyCodeDescriptionMapper { } // Just attempt to speak the description. - if (key.mCode != Keyboard.CODE_UNSPECIFIED) { + if (key.mCode != Constants.CODE_UNSPECIFIED) { return getDescriptionForKeyCode(context, keyboard, key, shouldObscure); } diff --git a/java/src/com/android/inputmethod/annotations/ExternallyReferenced.java b/java/src/com/android/inputmethod/annotations/ExternallyReferenced.java new file mode 100644 index 000000000..ea5f12ce2 --- /dev/null +++ b/java/src/com/android/inputmethod/annotations/ExternallyReferenced.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.annotations; + +/** + * Denotes that the class, method or field should not be eliminated by ProGuard, + * because it is externally referenced. (See proguard.flags) + */ +public @interface ExternallyReferenced { +} diff --git a/java/src/com/android/inputmethod/annotations/UsedForTesting.java b/java/src/com/android/inputmethod/annotations/UsedForTesting.java new file mode 100644 index 000000000..2ada091e4 --- /dev/null +++ b/java/src/com/android/inputmethod/annotations/UsedForTesting.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.annotations; + +/** + * Denotes that the class, method or field should not be eliminated by ProGuard, + * so that unit tests can access it. (See proguard.flags) + */ +public @interface UsedForTesting { +} diff --git a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java index ab7bd4914..8bd1e5208 100644 --- a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java +++ b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java @@ -19,61 +19,21 @@ package com.android.inputmethod.compat; import android.content.Context; import android.os.IBinder; import android.view.inputmethod.InputMethodManager; -import android.view.inputmethod.InputMethodSubtype; - -import com.android.inputmethod.latin.ImfUtils; import java.lang.reflect.Method; -// TODO: Override this class with the concrete implementation if we need to take care of the -// performance. public final class InputMethodManagerCompatWrapper { - private static final String TAG = InputMethodManagerCompatWrapper.class.getSimpleName(); private static final Method METHOD_switchToNextInputMethod = CompatUtils.getMethod( InputMethodManager.class, "switchToNextInputMethod", IBinder.class, Boolean.TYPE); - private static final InputMethodManagerCompatWrapper sInstance = - new InputMethodManagerCompatWrapper(); - - private InputMethodManager mImm; - - private InputMethodManagerCompatWrapper() { - // This wrapper class is not publicly instantiable. - } - - public static InputMethodManagerCompatWrapper getInstance() { - if (sInstance.mImm == null) { - throw new RuntimeException(TAG + ".getInstance() is called before initialization"); - } - return sInstance; - } - - public static void init(Context context) { - sInstance.mImm = ImfUtils.getInputMethodManager(context); - } - - public InputMethodSubtype getCurrentInputMethodSubtype() { - return mImm.getCurrentInputMethodSubtype(); - } - - public InputMethodSubtype getLastInputMethodSubtype() { - return mImm.getLastInputMethodSubtype(); - } + public final InputMethodManager mImm; - public boolean switchToLastInputMethod(IBinder token) { - return mImm.switchToLastInputMethod(token); + public InputMethodManagerCompatWrapper(final Context context) { + mImm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE); } - public boolean switchToNextInputMethod(IBinder token, boolean onlyCurrentIme) { + public boolean switchToNextInputMethod(final IBinder token, final boolean onlyCurrentIme) { return (Boolean)CompatUtils.invoke(mImm, false, METHOD_switchToNextInputMethod, token, onlyCurrentIme); } - - public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) { - mImm.setInputMethodAndSubtype(token, id, subtype); - } - - public void showInputMethodPicker() { - mImm.showInputMethodPicker(); - } } diff --git a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java index 159f43650..c1609ea28 100644 --- a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java +++ b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java @@ -83,14 +83,13 @@ public final class SuggestionSpanUtils { } public static CharSequence getTextWithAutoCorrectionIndicatorUnderline( - Context context, CharSequence text) { + final Context context, final String text) { if (TextUtils.isEmpty(text) || CONSTRUCTOR_SuggestionSpan == null || OBJ_FLAG_AUTO_CORRECTION == null || OBJ_SUGGESTIONS_MAX_SIZE == null || OBJ_FLAG_MISSPELLED == null || OBJ_FLAG_EASY_CORRECT == null) { return text; } - final Spannable spannable = text instanceof Spannable - ? (Spannable) text : new SpannableString(text); + final Spannable spannable = new SpannableString(text); final Object[] args = { context, null, new String[] {}, (int)OBJ_FLAG_AUTO_CORRECTION, (Class<?>) SuggestionSpanPickedNotificationReceiver.class }; @@ -104,28 +103,24 @@ public final class SuggestionSpanUtils { return spannable; } - public static CharSequence getTextWithSuggestionSpan(Context context, - CharSequence pickedWord, SuggestedWords suggestedWords, boolean dictionaryAvailable) { + public static CharSequence getTextWithSuggestionSpan(final Context context, + final String pickedWord, final SuggestedWords suggestedWords, + final boolean dictionaryAvailable) { if (!dictionaryAvailable || TextUtils.isEmpty(pickedWord) || CONSTRUCTOR_SuggestionSpan == null - || suggestedWords == null || suggestedWords.size() == 0 - || suggestedWords.mIsPrediction || suggestedWords.mIsPunctuationSuggestions + || suggestedWords.isEmpty() || suggestedWords.mIsPrediction + || suggestedWords.mIsPunctuationSuggestions || OBJ_SUGGESTIONS_MAX_SIZE == null) { return pickedWord; } - final Spannable spannable; - if (pickedWord instanceof Spannable) { - spannable = (Spannable) pickedWord; - } else { - spannable = new SpannableString(pickedWord); - } + final Spannable spannable = new SpannableString(pickedWord); final ArrayList<String> suggestionsList = CollectionUtils.newArrayList(); for (int i = 0; i < suggestedWords.size(); ++i) { if (suggestionsList.size() >= OBJ_SUGGESTIONS_MAX_SIZE) { break; } - final CharSequence word = suggestedWords.getWord(i); + final String word = suggestedWords.getWord(i); if (!TextUtils.equals(pickedWord, word)) { suggestionsList.add(word.toString()); } diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java index 30812e8c3..7346a9c38 100644 --- a/java/src/com/android/inputmethod/keyboard/Key.java +++ b/java/src/com/android/inputmethod/keyboard/Key.java @@ -16,11 +16,11 @@ package com.android.inputmethod.keyboard; -import static com.android.inputmethod.keyboard.Keyboard.CODE_OUTPUT_TEXT; -import static com.android.inputmethod.keyboard.Keyboard.CODE_SHIFT; -import static com.android.inputmethod.keyboard.Keyboard.CODE_SWITCH_ALPHA_SYMBOL; -import static com.android.inputmethod.keyboard.Keyboard.CODE_UNSPECIFIED; import static com.android.inputmethod.keyboard.internal.KeyboardIconsSet.ICON_UNDEFINED; +import static com.android.inputmethod.latin.Constants.CODE_OUTPUT_TEXT; +import static com.android.inputmethod.latin.Constants.CODE_SHIFT; +import static com.android.inputmethod.latin.Constants.CODE_SWITCH_ALPHA_SYMBOL; +import static com.android.inputmethod.latin.Constants.CODE_UNSPECIFIED; import android.content.res.Resources; import android.content.res.TypedArray; @@ -39,6 +39,7 @@ import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; import com.android.inputmethod.keyboard.internal.KeyboardParams; import com.android.inputmethod.keyboard.internal.KeyboardRow; import com.android.inputmethod.keyboard.internal.MoreKeySpec; +import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.ResourceUtils; import com.android.inputmethod.latin.StringUtils; @@ -453,7 +454,7 @@ public class Key implements Comparable<Key> { label = "/" + mLabel; } return String.format("%s%s %d,%d %dx%d %s/%s/%s", - Keyboard.printableCode(mCode), label, mX, mY, mWidth, mHeight, mHintLabel, + Constants.printableCode(mCode), label, mX, mY, mWidth, mHeight, mHintLabel, KeyboardIconsSet.getIconName(mIconId), backgroundName(mBackgroundType)); } diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java index aa683c1d7..0a91284d0 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java +++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java @@ -118,7 +118,7 @@ public class KeyDetector { } public static String printableCode(Key key) { - return key != null ? Keyboard.printableCode(key.mCode) : "none"; + return key != null ? Constants.printableCode(key.mCode) : "none"; } public static String printableCodes(int[] codes) { @@ -127,7 +127,7 @@ public class KeyDetector { for (final int code : codes) { if (code == Constants.NOT_A_CODE) break; if (addDelimiter) sb.append(", "); - sb.append(Keyboard.printableCode(code)); + sb.append(Constants.printableCode(code)); addDelimiter = true; } return "[" + sb + "]"; diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java index b7c7f415d..a1b1f5dad 100644 --- a/java/src/com/android/inputmethod/keyboard/Keyboard.java +++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java @@ -16,15 +16,13 @@ package com.android.inputmethod.keyboard; -import android.util.Log; import android.util.SparseArray; import com.android.inputmethod.keyboard.internal.KeyVisualAttributes; import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; import com.android.inputmethod.keyboard.internal.KeyboardParams; import com.android.inputmethod.latin.CollectionUtils; - - +import com.android.inputmethod.latin.Constants; /** * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard @@ -45,46 +43,6 @@ import com.android.inputmethod.latin.CollectionUtils; * </pre> */ public class Keyboard { - private static final String TAG = Keyboard.class.getSimpleName(); - - /** Some common keys code. Must be positive. - * These should be aligned with values/keycodes.xml - */ - public static final int CODE_ENTER = '\n'; - public static final int CODE_TAB = '\t'; - public static final int CODE_SPACE = ' '; - public static final int CODE_PERIOD = '.'; - public static final int CODE_DASH = '-'; - public static final int CODE_SINGLE_QUOTE = '\''; - public static final int CODE_DOUBLE_QUOTE = '"'; - public static final int CODE_QUESTION_MARK = '?'; - public static final int CODE_EXCLAMATION_MARK = '!'; - // TODO: Check how this should work for right-to-left languages. It seems to stand - // that for rtl languages, a closing parenthesis is a left parenthesis. Is this - // managed by the font? Or is it a different char? - public static final int CODE_CLOSING_PARENTHESIS = ')'; - public static final int CODE_CLOSING_SQUARE_BRACKET = ']'; - public static final int CODE_CLOSING_CURLY_BRACKET = '}'; - public static final int CODE_CLOSING_ANGLE_BRACKET = '>'; - - /** Special keys code. Must be negative. - * These should be aligned with KeyboardCodesSet.ID_TO_NAME[], - * KeyboardCodesSet.DEFAULT[] and KeyboardCodesSet.RTL[] - */ - public static final int CODE_SHIFT = -1; - public static final int CODE_SWITCH_ALPHA_SYMBOL = -2; - public static final int CODE_OUTPUT_TEXT = -3; - public static final int CODE_DELETE = -4; - public static final int CODE_SETTINGS = -5; - public static final int CODE_SHORTCUT = -6; - public static final int CODE_ACTION_ENTER = -7; - public static final int CODE_ACTION_NEXT = -8; - public static final int CODE_ACTION_PREVIOUS = -9; - public static final int CODE_LANGUAGE_SWITCH = -10; - public static final int CODE_RESEARCH = -11; - // Code value representing the code is not specified. - public static final int CODE_UNSPECIFIED = -12; - public final KeyboardId mId; public final int mThemeId; @@ -163,7 +121,7 @@ public class Keyboard { } public Key getKey(final int code) { - if (code == CODE_UNSPECIFIED) { + if (code == Constants.CODE_UNSPECIFIED) { return null; } synchronized (mKeyCache) { @@ -197,10 +155,6 @@ public class Keyboard { return false; } - public static boolean isLetterCode(final int code) { - return code >= CODE_SPACE; - } - @Override public String toString() { return mId.toString(); @@ -219,27 +173,4 @@ public class Keyboard { final int adjustedY = Math.max(0, Math.min(y, mOccupiedHeight - 1)); return mProximityInfo.getNearestKeys(adjustedX, adjustedY); } - - public static String printableCode(final int code) { - switch (code) { - case CODE_SHIFT: return "shift"; - case CODE_SWITCH_ALPHA_SYMBOL: return "symbol"; - case CODE_OUTPUT_TEXT: return "text"; - case CODE_DELETE: return "delete"; - case CODE_SETTINGS: return "settings"; - case CODE_SHORTCUT: return "shortcut"; - case CODE_ACTION_ENTER: return "actionEnter"; - case CODE_ACTION_NEXT: return "actionNext"; - case CODE_ACTION_PREVIOUS: return "actionPrevious"; - case CODE_LANGUAGE_SWITCH: return "languageSwitch"; - case CODE_UNSPECIFIED: return "unspec"; - case CODE_TAB: return "tab"; - case CODE_ENTER: return "enter"; - default: - if (code <= 0) Log.w(TAG, "Unknown non-positive key code=" + code); - if (code < CODE_SPACE) return String.format("'\\u%02x'", code); - if (code < 0x100) return String.format("'%c'", code); - return String.format("'\\u%04x'", code); - } - } } diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java index 5c8f78f5e..14da9ebe6 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java @@ -56,11 +56,11 @@ public interface KeyboardActionListener { public void onCodeInput(int primaryCode, int x, int y); /** - * Sends a sequence of characters to the listener. + * Sends a string of characters to the listener. * - * @param text the sequence of characters to be displayed. + * @param text the string of characters to be registered. */ - public void onTextInput(CharSequence text); + public void onTextInput(String text); /** * Called when user started batch input. @@ -80,6 +80,8 @@ public interface KeyboardActionListener { */ public void onEndBatchInput(InputPointers batchPointers); + public void onCancelBatchInput(); + /** * Called when user released a finger outside any key. */ @@ -99,7 +101,7 @@ public interface KeyboardActionListener { @Override public void onCodeInput(int primaryCode, int x, int y) {} @Override - public void onTextInput(CharSequence text) {} + public void onTextInput(String text) {} @Override public void onStartBatchInput() {} @Override @@ -107,18 +109,12 @@ public interface KeyboardActionListener { @Override public void onEndBatchInput(InputPointers batchPointers) {} @Override + public void onCancelBatchInput() {} + @Override public void onCancelInput() {} @Override public boolean onCustomRequest(int requestCode) { return false; } - - // TODO: Remove this method when the vertical correction is removed. - public static boolean isInvalidCoordinate(int coordinate) { - // Detect {@link Constants#NOT_A_COORDINATE}, - // {@link Constants#SUGGESTION_STRIP_COORDINATE}, and - // {@link Constants#SPELL_CHECKER_COORDINATE}. - return coordinate < 0; - } } } diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java index 5e8a8f6bb..b41361515 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java @@ -71,34 +71,39 @@ public final class KeyboardId { private final EditorInfo mEditorInfo; public final boolean mClobberSettingsKey; public final boolean mShortcutKeyEnabled; - public final boolean mHasShortcutKey; + public final boolean mShortcutKeyOnSymbols; public final boolean mLanguageSwitchKeyEnabled; public final String mCustomActionLabel; + public final boolean mHasShortcutKey; private final int mHashCode; - public KeyboardId(int elementId, InputMethodSubtype subtype, int deviceFormFactor, - int orientation, int width, int mode, EditorInfo editorInfo, boolean clobberSettingsKey, - boolean shortcutKeyEnabled, boolean hasShortcutKey, boolean languageSwitchKeyEnabled) { - mSubtype = subtype; - mLocale = SubtypeLocale.getSubtypeLocale(subtype); - mDeviceFormFactor = deviceFormFactor; - mOrientation = orientation; - mWidth = width; - mMode = mode; + public KeyboardId(final int elementId, final KeyboardLayoutSet.Params params) { + mSubtype = params.mSubtype; + mLocale = SubtypeLocale.getSubtypeLocale(mSubtype); + mDeviceFormFactor = params.mDeviceFormFactor; + mOrientation = params.mOrientation; + mWidth = params.mWidth; + mMode = params.mMode; mElementId = elementId; - mEditorInfo = editorInfo; - mClobberSettingsKey = clobberSettingsKey; - mShortcutKeyEnabled = shortcutKeyEnabled; - mHasShortcutKey = hasShortcutKey; - mLanguageSwitchKeyEnabled = languageSwitchKeyEnabled; - mCustomActionLabel = (editorInfo.actionLabel != null) - ? editorInfo.actionLabel.toString() : null; + mEditorInfo = params.mEditorInfo; + mClobberSettingsKey = params.mNoSettingsKey; + mShortcutKeyEnabled = params.mVoiceKeyEnabled; + mShortcutKeyOnSymbols = mShortcutKeyEnabled && !params.mVoiceKeyOnMain; + mLanguageSwitchKeyEnabled = params.mLanguageSwitchKeyEnabled; + mCustomActionLabel = (mEditorInfo.actionLabel != null) + ? mEditorInfo.actionLabel.toString() : null; + final boolean alphabetMayHaveShortcutKey = isAlphabetKeyboard(elementId) + && !mShortcutKeyOnSymbols; + final boolean symbolsMayHaveShortcutKey = (elementId == KeyboardId.ELEMENT_SYMBOLS) + && mShortcutKeyOnSymbols; + mHasShortcutKey = mShortcutKeyEnabled + && (alphabetMayHaveShortcutKey || symbolsMayHaveShortcutKey); mHashCode = computeHashCode(this); } - private static int computeHashCode(KeyboardId id) { + private static int computeHashCode(final KeyboardId id) { return Arrays.hashCode(new Object[] { id.mDeviceFormFactor, id.mOrientation, @@ -108,7 +113,7 @@ public final class KeyboardId { id.passwordInput(), id.mClobberSettingsKey, id.mShortcutKeyEnabled, - id.mHasShortcutKey, + id.mShortcutKeyOnSymbols, id.mLanguageSwitchKeyEnabled, id.isMultiLine(), id.imeAction(), @@ -119,7 +124,7 @@ public final class KeyboardId { }); } - private boolean equals(KeyboardId other) { + private boolean equals(final KeyboardId other) { if (other == this) return true; return other.mDeviceFormFactor == mDeviceFormFactor @@ -130,7 +135,7 @@ public final class KeyboardId { && other.passwordInput() == passwordInput() && other.mClobberSettingsKey == mClobberSettingsKey && other.mShortcutKeyEnabled == mShortcutKeyEnabled - && other.mHasShortcutKey == mHasShortcutKey + && other.mShortcutKeyOnSymbols == mShortcutKeyOnSymbols && other.mLanguageSwitchKeyEnabled == mLanguageSwitchKeyEnabled && other.isMultiLine() == isMultiLine() && other.imeAction() == imeAction() @@ -140,8 +145,12 @@ public final class KeyboardId { && other.mSubtype.equals(mSubtype); } + private static boolean isAlphabetKeyboard(final int elementId) { + return elementId < ELEMENT_SYMBOLS; + } + public boolean isAlphabetKeyboard() { - return mElementId < ELEMENT_SYMBOLS; + return isAlphabetKeyboard(mElementId); } public boolean navigateNext() { @@ -181,7 +190,7 @@ public final class KeyboardId { } @Override - public boolean equals(Object other) { + public boolean equals(final Object other) { return other instanceof KeyboardId && equals((KeyboardId) other); } @@ -192,7 +201,7 @@ public final class KeyboardId { @Override public String toString() { - return String.format("[%s %s:%s %s-%s:%d %s %s %s%s%s%s%s%s%s%s]", + return String.format("[%s %s:%s %s-%s:%d %s %s %s%s%s%s%s%s%s%s%s]", elementIdToName(mElementId), mLocale, mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET), @@ -204,13 +213,14 @@ public final class KeyboardId { (mClobberSettingsKey ? " clobberSettingsKey" : ""), (passwordInput() ? " passwordInput" : ""), (mShortcutKeyEnabled ? " shortcutKeyEnabled" : ""), + (mShortcutKeyOnSymbols ? " shortcutKeyOnSymbols" : ""), (mHasShortcutKey ? " hasShortcutKey" : ""), (mLanguageSwitchKeyEnabled ? " languageSwitchKeyEnabled" : ""), (isMultiLine() ? "isMultiLine" : "") ); } - public static boolean equivalentEditorInfoForKeyboard(EditorInfo a, EditorInfo b) { + public static boolean equivalentEditorInfoForKeyboard(final EditorInfo a, final EditorInfo b) { if (a == null && b == null) return true; if (a == null || b == null) return false; return a.inputType == b.inputType @@ -218,7 +228,7 @@ public final class KeyboardId { && TextUtils.equals(a.privateImeOptions, b.privateImeOptions); } - public static String elementIdToName(int elementId) { + public static String elementIdToName(final int elementId) { switch (elementId) { case ELEMENT_ALPHABET: return "alphabet"; case ELEMENT_ALPHABET_MANUAL_SHIFTED: return "alphabetManualShifted"; @@ -234,8 +244,8 @@ public final class KeyboardId { } } - public static String deviceFormFactor(int devoceFormFactor) { - switch (devoceFormFactor) { + public static String deviceFormFactor(final int deviceFormFactor) { + switch (deviceFormFactor) { case FORM_FACTOR_PHONE: return "phone"; case FORM_FACTOR_TABLET7: return "tablet7"; case FORM_FACTOR_TABLET10: return "tablet10"; @@ -243,7 +253,7 @@ public final class KeyboardId { } } - public static String modeName(int mode) { + public static String modeName(final int mode) { switch (mode) { case MODE_TEXT: return "text"; case MODE_URL: return "url"; @@ -258,7 +268,7 @@ public final class KeyboardId { } } - public static String actionName(int actionId) { + public static String actionName(final int actionId) { return (actionId == IME_ACTION_CUSTOM_LABEL) ? "actionCustomLabel" : EditorInfoCompatUtils.imeActionName(actionId); } diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java index c7813ab02..295047530 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java @@ -34,6 +34,7 @@ import android.util.Xml; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodSubtype; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.compat.EditorInfoCompatUtils; import com.android.inputmethod.keyboard.internal.KeyboardBuilder; import com.android.inputmethod.keyboard.internal.KeyboardParams; @@ -77,6 +78,7 @@ public final class KeyboardLayoutSet { CollectionUtils.newHashMap(); private static final KeysCache sKeysCache = new KeysCache(); + @SuppressWarnings("serial") public static final class KeyboardLayoutSetException extends RuntimeException { public final KeyboardId mKeyboardId; @@ -92,7 +94,7 @@ public final class KeyboardLayoutSet { public ElementParams() {} } - private static final class Params { + public static final class Params { String mKeyboardLayoutSetName; int mMode; EditorInfo mEditorInfo; @@ -108,7 +110,6 @@ public final class KeyboardLayoutSet { // Sparse array of KeyboardLayoutSet element parameters indexed by element's id. final SparseArray<ElementParams> mKeyboardLayoutSetElementIdToParamsMap = CollectionUtils.newSparseArray(); - public Params() {} } public static void clearKeyboardCache() { @@ -148,7 +149,11 @@ public final class KeyboardLayoutSet { elementParams = mParams.mKeyboardLayoutSetElementIdToParamsMap.get( KeyboardId.ELEMENT_ALPHABET); } - final KeyboardId id = getKeyboardId(keyboardLayoutSetElementId); + // Note: The keyboard for each shift state, and mode are represented as an elementName + // attribute in a keyboard_layout_set XML file. Also each keyboard layout XML resource is + // specified as an elementKeyboard attribute in the file. + // The KeyboardId is an internal key for a Keyboard object. + final KeyboardId id = new KeyboardId(keyboardLayoutSetElementId, mParams); try { return getKeyboard(elementParams, id); } catch (RuntimeException e) { @@ -186,23 +191,6 @@ public final class KeyboardLayoutSet { return keyboard; } - // Note: The keyboard for each locale, shift state, and mode are represented as - // KeyboardLayoutSet element id that is a key in keyboard_set.xml. Also that file specifies - // which XML layout should be used for each keyboard. The KeyboardId is an internal key for - // Keyboard object. - private KeyboardId getKeyboardId(final int keyboardLayoutSetElementId) { - final Params params = mParams; - final boolean isSymbols = (keyboardLayoutSetElementId == KeyboardId.ELEMENT_SYMBOLS - || keyboardLayoutSetElementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED); - final boolean noLanguage = SubtypeLocale.isNoLanguage(params.mSubtype); - final boolean voiceKeyEnabled = params.mVoiceKeyEnabled && !noLanguage; - final boolean hasShortcutKey = voiceKeyEnabled && (isSymbols != params.mVoiceKeyOnMain); - return new KeyboardId(keyboardLayoutSetElementId, params.mSubtype, params.mDeviceFormFactor, - params.mOrientation, params.mWidth, params.mMode, params.mEditorInfo, - params.mNoSettingsKey, voiceKeyEnabled, hasShortcutKey, - params.mLanguageSwitchKeyEnabled); - } - public static final class Builder { private final Context mContext; private final String mPackageName; @@ -266,7 +254,7 @@ public final class KeyboardLayoutSet { return this; } - // For test only + @UsedForTesting public void disableTouchPositionCorrectionDataForTest() { mParams.mDisableTouchPositionCorrectionDataForTest = true; } diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index de8097b55..c1c105e8d 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -29,12 +29,12 @@ import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy; import com.android.inputmethod.keyboard.KeyboardLayoutSet.KeyboardLayoutSetException; import com.android.inputmethod.keyboard.PointerTracker.TimerProxy; import com.android.inputmethod.keyboard.internal.KeyboardState; -import com.android.inputmethod.latin.DebugSettings; -import com.android.inputmethod.latin.ImfUtils; +import com.android.inputmethod.latin.AudioAndHapticFeedbackManager; import com.android.inputmethod.latin.InputView; import com.android.inputmethod.latin.LatinIME; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.RichInputMethodManager; import com.android.inputmethod.latin.SettingsValues; import com.android.inputmethod.latin.SubtypeSwitcher; import com.android.inputmethod.latin.WordComposer; @@ -65,9 +65,9 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { new KeyboardTheme(5, R.style.KeyboardTheme_IceCreamSandwich), }; + private AudioAndHapticFeedbackManager mFeedbackManager; private SubtypeSwitcher mSubtypeSwitcher; private SharedPreferences mPrefs; - private boolean mForceNonDistinctMultitouch; private InputView mCurrentInputView; private MainKeyboardView mKeyboardView; @@ -102,12 +102,11 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { private void initInternal(LatinIME latinIme, SharedPreferences prefs) { mLatinIME = latinIme; mResources = latinIme.getResources(); + mFeedbackManager = new AudioAndHapticFeedbackManager(latinIme); mPrefs = prefs; mSubtypeSwitcher = SubtypeSwitcher.getInstance(); mState = new KeyboardState(this); setContextThemeWrapper(latinIme, getKeyboardTheme(latinIme, prefs)); - mForceNonDistinctMultitouch = prefs.getBoolean( - DebugSettings.FORCE_NON_DISTINCT_MULTITOUCH_KEY, false); } private static KeyboardTheme getKeyboardTheme(Context context, SharedPreferences prefs) { @@ -126,7 +125,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { } private void setContextThemeWrapper(Context context, KeyboardTheme keyboardTheme) { - if (mKeyboardTheme.mThemeId != keyboardTheme.mThemeId) { + if (mThemeContext == null || mKeyboardTheme.mThemeId != keyboardTheme.mThemeId) { mKeyboardTheme = keyboardTheme; mThemeContext = new ContextThemeWrapper(context, keyboardTheme.mStyleId); KeyboardLayoutSet.clearKeyboardCache(); @@ -143,10 +142,11 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { builder.setOptions( settingsValues.isVoiceKeyEnabled(editorInfo), settingsValues.isVoiceKeyOnMain(), - settingsValues.isLanguageSwitchKeyEnabled(mThemeContext)); + settingsValues.isLanguageSwitchKeyEnabled()); mKeyboardLayoutSet = builder.build(); try { mState.onLoadKeyboard(mResources.getString(R.string.layout_switch_back_symbols)); + mFeedbackManager.onSettingsChanged(settingsValues); } catch (KeyboardLayoutSetException e) { Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause()); LatinImeLogger.logOnException(e.mKeyboardId.toString(), e.getCause()); @@ -154,6 +154,10 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { } } + public void onRingerModeChanged() { + mFeedbackManager.onRingerModeChanged(); + } + public void saveKeyboardState() { if (getKeyboard() != null) { mState.onSaveKeyboardState(); @@ -183,7 +187,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { final boolean needsToDisplayLanguage = mSubtypeSwitcher.needsToDisplayLanguage( keyboard.mId.mLocale); keyboardView.startDisplayLanguageOnSpacebar(subtypeChanged, needsToDisplayLanguage, - ImfUtils.hasMultipleEnabledIMEsOrSubtypes(mLatinIME, true)); + RichInputMethodManager.getInstance().hasMultipleEnabledIMEsOrSubtypes(true)); } public Keyboard getKeyboard() { @@ -208,7 +212,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { public void onPressKey(int code) { if (isVibrateAndSoundFeedbackRequired()) { - mLatinIME.hapticAndAudioFeedback(code); + mFeedbackManager.hapticAndAudioFeedback(code, mKeyboardView); } mState.onPressKey(code, isSinglePointer(), mLatinIME.getCurrentAutoCapsState()); } @@ -320,7 +324,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { // Implements {@link KeyboardState.SwitchActions}. @Override public void hapticAndAudioFeedback(int code) { - mLatinIME.hapticAndAudioFeedback(code); + mFeedbackManager.hapticAndAudioFeedback(code, mKeyboardView); } public void onLongPressTimeout(int code) { @@ -339,10 +343,6 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { return mKeyboardView != null && mKeyboardView.getPointerCount() == 1; } - public boolean hasDistinctMultitouch() { - return mKeyboardView != null && mKeyboardView.hasDistinctMultitouch(); - } - /** * Updates state machine to figure out when to automatically switch back to the previous mode. */ @@ -369,9 +369,6 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { // TODO: Should use LAYER_TYPE_SOFTWARE when hardware acceleration is off? } mKeyboardView.setKeyboardActionListener(mLatinIME); - if (mForceNonDistinctMultitouch) { - mKeyboardView.setDistinctMultitouch(false); - } // This always needs to be set since the accessibility state can // potentially change without the input view being re-created. diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java index 472f74b12..61d38745e 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java @@ -45,6 +45,7 @@ import com.android.inputmethod.keyboard.internal.KeyVisualAttributes; import com.android.inputmethod.keyboard.internal.PreviewPlacerView; import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.CoordinateUtils; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.StaticInnerHandlerWrapper; @@ -101,7 +102,8 @@ import java.util.HashSet; * @attr ref R.styleable#Keyboard_Key_keyShiftedLetterHintActivatedColor * @attr ref R.styleable#Keyboard_Key_keyPreviewTextColor */ -public class KeyboardView extends View implements PointerTracker.DrawingProxy { +public class KeyboardView extends View implements PointerTracker.DrawingProxy, + MoreKeysPanel.Controller { private static final String TAG = KeyboardView.class.getSimpleName(); // XML attributes @@ -134,7 +136,11 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { // Preview placer view private final PreviewPlacerView mPreviewPlacerView; - private final int[] mCoordinates = new int[2]; + private final int[] mOriginCoords = CoordinateUtils.newInstance(); + + // More keys panel (used by both more keys keyboard and more suggestions view) + // TODO: Consider extending to support multiple more keys panels + protected MoreKeysPanel mMoreKeysPanel; // Key preview private static final int PREVIEW_ALPHA = 240; @@ -832,10 +838,9 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { // In transient state. return; } - final int[] viewOrigin = new int[2]; - getLocationInWindow(viewOrigin); + getLocationInWindow(mOriginCoords); final DisplayMetrics dm = getResources().getDisplayMetrics(); - if (viewOrigin[1] < dm.heightPixels / 4) { + if (CoordinateUtils.y(mOriginCoords) < dm.heightPixels / 4) { // In transient state. return; } @@ -850,10 +855,21 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { Log.w(TAG, "Cannot find android.R.id.content view to add PreviewPlacerView"); } else { windowContentView.addView(mPreviewPlacerView); - mPreviewPlacerView.setKeyboardViewGeometry(viewOrigin[0], viewOrigin[1], width, height); + mPreviewPlacerView.setKeyboardViewGeometry(mOriginCoords, width, height); } } + @Override + public void showSlidingKeyInputPreview(final PointerTracker tracker) { + locatePreviewPlacerView(); + mPreviewPlacerView.showSlidingKeyInputPreview(tracker); + } + + @Override + public void dismissSlidingKeyInputPreview() { + mPreviewPlacerView.dismissSlidingKeyInputPreview(); + } + public void showGestureFloatingPreviewText(final String gestureFloatingPreviewText) { locatePreviewPlacerView(); mPreviewPlacerView.setGestureFloatingPreviewText(gestureFloatingPreviewText); @@ -935,12 +951,13 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { // The distance between the top edge of the parent key and the bottom of the visible part // of the key preview background. previewParams.mPreviewVisibleOffset = mPreviewOffset - previewText.getPaddingBottom(); - getLocationInWindow(mCoordinates); + getLocationInWindow(mOriginCoords); // The key preview is horizontally aligned with the center of the visible part of the // parent key. If it doesn't fit in this {@link KeyboardView}, it is moved inward to fit and // the left/right background is used if such background is specified. final int statePosition; - int previewX = key.getDrawX() - (previewWidth - keyDrawWidth) / 2 + mCoordinates[0]; + int previewX = key.getDrawX() - (previewWidth - keyDrawWidth) / 2 + + CoordinateUtils.x(mOriginCoords); if (previewX < 0) { previewX = 0; statePosition = STATE_LEFT; @@ -952,7 +969,8 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { } // The key preview is placed vertically above the top edge of the parent key with an // arbitrary offset. - final int previewY = key.mY - previewHeight + mPreviewOffset + mCoordinates[1]; + final int previewY = key.mY - previewHeight + mPreviewOffset + + CoordinateUtils.y(mOriginCoords); if (background != null) { final int hasMoreKeys = (key.mMoreKeys != null) ? STATE_HAS_MOREKEYS : STATE_NORMAL; @@ -995,13 +1013,38 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { public void closing() { dismissAllKeyPreviews(); cancelAllMessages(); - + onCancelMoreKeysPanel(); mInvalidateAllKeys = true; requestLayout(); } @Override - public boolean dismissMoreKeysPanel() { + public void onShowMoreKeysPanel(final MoreKeysPanel panel) { + if (isShowingMoreKeysPanel()) { + onDismissMoreKeysPanel(); + } + mMoreKeysPanel = panel; + mPreviewPlacerView.addView(mMoreKeysPanel.getContainerView()); + } + + public boolean isShowingMoreKeysPanel() { + return (mMoreKeysPanel != null); + } + + @Override + public void onCancelMoreKeysPanel() { + if (isShowingMoreKeysPanel()) { + mMoreKeysPanel.dismissMoreKeysPanel(); + } + } + + @Override + public boolean onDismissMoreKeysPanel() { + if (isShowingMoreKeysPanel()) { + mPreviewPlacerView.removeView(mMoreKeysPanel.getContainerView()); + mMoreKeysPanel = null; + return true; + } return false; } diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java index 3e6f92c2a..584d2fe46 100644 --- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java @@ -19,6 +19,7 @@ package com.android.inputmethod.keyboard; import android.animation.AnimatorInflater; import android.animation.ObjectAnimator; import android.content.Context; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.Resources; import android.content.res.TypedArray; @@ -28,7 +29,8 @@ import android.graphics.Paint.Align; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Message; -import android.text.TextUtils; +import android.os.SystemClock; +import android.preference.PreferenceManager; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; @@ -37,15 +39,17 @@ import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.inputmethod.InputMethodSubtype; -import android.widget.PopupWindow; import com.android.inputmethod.accessibility.AccessibilityUtils; import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy; +import com.android.inputmethod.annotations.ExternallyReferenced; import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy; import com.android.inputmethod.keyboard.PointerTracker.TimerProxy; import com.android.inputmethod.keyboard.internal.KeyDrawParams; -import com.android.inputmethod.keyboard.internal.SuddenJumpingTouchEventHandler; +import com.android.inputmethod.keyboard.internal.TouchScreenRegulator; import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.CoordinateUtils; +import com.android.inputmethod.latin.DebugSettings; import com.android.inputmethod.latin.LatinIME; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; @@ -95,7 +99,7 @@ import java.util.WeakHashMap; * @attr ref R.styleable#MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration */ public final class MainKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler, - SuddenJumpingTouchEventHandler.ProcessMotionEvent { + TouchScreenRegulator.ProcessMotionEvent { private static final String TAG = MainKeyboardView.class.getSimpleName(); // TODO: Kill process when the usability study mode was changed. @@ -131,17 +135,14 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack private int mAltCodeKeyWhileTypingAnimAlpha = Constants.Color.ALPHA_OPAQUE; // More keys keyboard - private PopupWindow mMoreKeysWindow; - private MoreKeysPanel mMoreKeysPanel; - private int mMoreKeysPanelPointerTrackerId; private final WeakHashMap<Key, MoreKeysPanel> mMoreKeysPanelCache = new WeakHashMap<Key, MoreKeysPanel>(); private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint; - private final SuddenJumpingTouchEventHandler mTouchScreenRegulator; + private final TouchScreenRegulator mTouchScreenRegulator; protected KeyDetector mKeyDetector; - private boolean mHasDistinctMultitouch; + private final boolean mHasDistinctMultitouch; private int mOldPointerCount = 1; private Key mOldKey; @@ -153,12 +154,14 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack private static final int MSG_REPEAT_KEY = 1; private static final int MSG_LONGPRESS_KEY = 2; private static final int MSG_DOUBLE_TAP = 3; + private static final int MSG_UPDATE_BATCH_INPUT = 4; private final int mKeyRepeatStartTimeout; private final int mKeyRepeatInterval; private final int mLongPressKeyTimeout; private final int mLongPressShiftKeyTimeout; private final int mIgnoreAltCodeKeyTimeout; + private final int mGestureRecognitionUpdateTime; public KeyTimerHandler(final MainKeyboardView outerInstance, final TypedArray mainKeyboardViewAttr) { @@ -174,6 +177,8 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack R.styleable.MainKeyboardView_longPressShiftKeyTimeout, 0); mIgnoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt( R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0); + mGestureRecognitionUpdateTime = mainKeyboardViewAttr.getInt( + R.styleable.MainKeyboardView_gestureRecognitionUpdateTime, 0); } @Override @@ -198,12 +203,18 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack KeyboardSwitcher.getInstance().onLongPressTimeout(msg.arg1); } break; + case MSG_UPDATE_BATCH_INPUT: + tracker.updateBatchInputByTimer(SystemClock.uptimeMillis()); + startUpdateBatchInputTimer(tracker); + break; } } private void startKeyRepeatTimer(final PointerTracker tracker, final long delay) { final Key key = tracker.getKey(); - if (key == null) return; + if (key == null) { + return; + } sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, key.mCode, 0, tracker), delay); } @@ -226,7 +237,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack cancelLongPressTimer(); final int delay; switch (code) { - case Keyboard.CODE_SHIFT: + case Constants.CODE_SHIFT: delay = mLongPressShiftKeyTimeout; break; default: @@ -247,7 +258,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack final Key key = tracker.getKey(); final int delay; switch (key.mCode) { - case Keyboard.CODE_SHIFT: + case Constants.CODE_SHIFT: delay = mLongPressShiftKeyTimeout; break; default: @@ -304,7 +315,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack // When user hits the space or the enter key, just cancel the while-typing timer. final int typedCode = typedKey.mCode; - if (typedCode == Keyboard.CODE_SPACE || typedCode == Keyboard.CODE_ENTER) { + if (typedCode == Constants.CODE_SPACE || typedCode == Constants.CODE_ENTER) { startWhileTypingFadeinAnimation(keyboardView); return; } @@ -344,8 +355,24 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack cancelLongPressTimer(); } + @Override + public void startUpdateBatchInputTimer(final PointerTracker tracker) { + if (mGestureRecognitionUpdateTime <= 0) { + return; + } + removeMessages(MSG_UPDATE_BATCH_INPUT, tracker); + sendMessageDelayed(obtainMessage(MSG_UPDATE_BATCH_INPUT, tracker), + mGestureRecognitionUpdateTime); + } + + @Override + public void cancelAllUpdateBatchInputTimers() { + removeMessages(MSG_UPDATE_BATCH_INPUT); + } + public void cancelAllMessages() { cancelKeyTimers(); + cancelAllUpdateBatchInputTimers(); } } @@ -356,15 +383,19 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack public MainKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) { super(context, attrs, defStyle); - mTouchScreenRegulator = new SuddenJumpingTouchEventHandler(getContext(), this); + mTouchScreenRegulator = new TouchScreenRegulator(context, this); - mHasDistinctMultitouch = context.getPackageManager() + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + final boolean forceNonDistinctMultitouch = prefs.getBoolean( + DebugSettings.FORCE_NON_DISTINCT_MULTITOUCH_KEY, false); + final boolean hasDistinctMultitouch = context.getPackageManager() .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT); + mHasDistinctMultitouch = hasDistinctMultitouch && !forceNonDistinctMultitouch; final Resources res = getResources(); final boolean needsPhantomSuddenMoveEventHack = Boolean.parseBoolean( ResourceUtils.getDeviceOverrideValue(res, R.array.phantom_sudden_move_event_device_list, "false")); - PointerTracker.init(mHasDistinctMultitouch, needsPhantomSuddenMoveEventHack); + PointerTracker.init(needsPhantomSuddenMoveEventHack); final TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.MainKeyboardView, defStyle, R.style.MainKeyboardView); @@ -408,7 +439,9 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack } private ObjectAnimator loadObjectAnimator(final int resId, final Object target) { - if (resId == 0) return null; + if (resId == 0) { + return null; + } final ObjectAnimator animator = (ObjectAnimator)AnimatorInflater.loadAnimator( getContext(), resId); if (animator != null) { @@ -417,20 +450,23 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack return animator; } - // Getter/setter methods for {@link ObjectAnimator}. + @ExternallyReferenced public int getLanguageOnSpacebarAnimAlpha() { return mLanguageOnSpacebarAnimAlpha; } + @ExternallyReferenced public void setLanguageOnSpacebarAnimAlpha(final int alpha) { mLanguageOnSpacebarAnimAlpha = alpha; invalidateKey(mSpaceKey); } + @ExternallyReferenced public int getAltCodeKeyWhileTypingAnimAlpha() { return mAltCodeKeyWhileTypingAnimAlpha; } + @ExternallyReferenced public void setAltCodeKeyWhileTypingAnimAlpha(final int alpha) { mAltCodeKeyWhileTypingAnimAlpha = alpha; updateAltCodeKeyWhileTyping(); @@ -480,10 +516,10 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack mKeyDetector.setKeyboard( keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection); PointerTracker.setKeyDetector(mKeyDetector); - mTouchScreenRegulator.setKeyboard(keyboard); + mTouchScreenRegulator.setKeyboardGeometry(keyboard.mOccupiedWidth); mMoreKeysPanelCache.clear(); - mSpaceKey = keyboard.getKey(Keyboard.CODE_SPACE); + mSpaceKey = keyboard.getKey(Constants.CODE_SPACE); mSpaceIcon = (mSpaceKey != null) ? mSpaceKey.getIcon(keyboard.mIconsSet, Constants.Color.ALPHA_OPAQUE) : null; final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap; @@ -506,18 +542,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack PointerTracker.setGestureHandlingEnabledByUser(gestureHandlingEnabledByUser); } - /** - * Returns whether the device has distinct multi-touch panel. - * @return true if the device has distinct multi-touch panel. - */ - public boolean hasDistinctMultitouch() { - return mHasDistinctMultitouch; - } - - public void setDistinctMultitouch(final boolean hasDistinctMultitouch) { - mHasDistinctMultitouch = hasDistinctMultitouch; - } - @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); @@ -553,21 +577,25 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack } // Check if we are already displaying popup panel. - if (mMoreKeysPanel != null) + if (mMoreKeysPanel != null) { return false; - if (parentKey == null) + } + if (parentKey == null) { return false; + } return onLongPress(parentKey, tracker); } // This default implementation returns a more keys panel. protected MoreKeysPanel onCreateMoreKeysPanel(final Key parentKey) { - if (parentKey.mMoreKeys == null) + if (parentKey.mMoreKeys == null) { return null; + } final View container = LayoutInflater.from(getContext()).inflate(mMoreKeysLayout, null); - if (container == null) + if (container == null) { throw new NullPointerException(); + } final MoreKeysKeyboardView moreKeysKeyboardView = (MoreKeysKeyboardView)container.findViewById(R.id.more_keys_keyboard_view); @@ -600,7 +628,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack KeyboardSwitcher.getInstance().hapticAndAudioFeedback(primaryCode); return true; } - if (primaryCode == Keyboard.CODE_SPACE || primaryCode == Keyboard.CODE_LANGUAGE_SWITCH) { + if (primaryCode == Constants.CODE_SPACE || primaryCode == Constants.CODE_LANGUAGE_SWITCH) { // Long pressing the space key invokes IME switcher dialog. if (invokeCustomRequest(LatinIME.CODE_SHOW_INPUT_METHOD_PICKER)) { tracker.onLongPressed(); @@ -628,24 +656,20 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack MoreKeysPanel moreKeysPanel = mMoreKeysPanelCache.get(parentKey); if (moreKeysPanel == null) { moreKeysPanel = onCreateMoreKeysPanel(parentKey); - if (moreKeysPanel == null) + if (moreKeysPanel == null) { return false; + } mMoreKeysPanelCache.put(parentKey, moreKeysPanel); } - if (mMoreKeysWindow == null) { - mMoreKeysWindow = new PopupWindow(getContext()); - mMoreKeysWindow.setBackgroundDrawable(null); - mMoreKeysWindow.setAnimationStyle(R.style.MoreKeysKeyboardAnimation); - } - mMoreKeysPanel = moreKeysPanel; - mMoreKeysPanelPointerTrackerId = tracker.mPointerId; + final int[] lastCoords = CoordinateUtils.newInstance(); + tracker.getLastCoordinates(lastCoords); final boolean keyPreviewEnabled = isKeyPreviewPopupEnabled() && !parentKey.noKeyPreview(); // The more keys keyboard is usually horizontally aligned with the center of the parent key. // If showMoreKeysKeyboardAtTouchedPoint is true and the key preview is disabled, the more // keys keyboard is placed at the touch point of the parent key. final int pointX = (mConfigShowMoreKeysKeyboardAtTouchedPoint && !keyPreviewEnabled) - ? tracker.getLastX() + ? CoordinateUtils.x(lastCoords) : parentKey.mX + parentKey.mWidth / 2; // The more keys keyboard is usually vertically aligned with the top edge of the parent key // (plus vertical gap). If the key preview is enabled, the more keys keyboard is vertically @@ -653,21 +677,19 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack // {@code mPreviewVisibleOffset} has been set appropriately in // {@link KeyboardView#showKeyPreview(PointerTracker)}. final int pointY = parentKey.mY + mKeyPreviewDrawParams.mPreviewVisibleOffset; - moreKeysPanel.showMoreKeysPanel( - this, this, pointX, pointY, mMoreKeysWindow, mKeyboardActionListener); - final int translatedX = moreKeysPanel.translateX(tracker.getLastX()); - final int translatedY = moreKeysPanel.translateY(tracker.getLastY()); + moreKeysPanel.showMoreKeysPanel(this, this, pointX, pointY, mKeyboardActionListener); + final int translatedX = moreKeysPanel.translateX(CoordinateUtils.x(lastCoords)); + final int translatedY = moreKeysPanel.translateY(CoordinateUtils.y(lastCoords)); tracker.onShowMoreKeysPanel(translatedX, translatedY, moreKeysPanel); - dimEntireKeyboard(true); + dimEntireKeyboard(true /* dimmed */); return true; } public boolean isInSlidingKeyInput() { if (mMoreKeysPanel != null) { return true; - } else { - return PointerTracker.isAnyInSlidingKeyInput(); } + return PointerTracker.isAnyInSlidingKeyInput(); } public int getPointerCount() { @@ -708,47 +730,18 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack final long eventTime = me.getEventTime(); final int index = me.getActionIndex(); final int id = me.getPointerId(index); - final int x, y; - if (mMoreKeysPanel != null && id == mMoreKeysPanelPointerTrackerId) { - x = mMoreKeysPanel.translateX((int)me.getX(index)); - y = mMoreKeysPanel.translateY((int)me.getY(index)); - } else { - x = (int)me.getX(index); - y = (int)me.getY(index); - } - if (ENABLE_USABILITY_STUDY_LOG) { - final String eventTag; - switch (action) { - case MotionEvent.ACTION_UP: - eventTag = "[Up]"; - break; - case MotionEvent.ACTION_DOWN: - eventTag = "[Down]"; - break; - case MotionEvent.ACTION_POINTER_UP: - eventTag = "[PointerUp]"; - break; - case MotionEvent.ACTION_POINTER_DOWN: - eventTag = "[PointerDown]"; - break; - case MotionEvent.ACTION_MOVE: // Skip this as being logged below - eventTag = ""; - break; - default: - eventTag = "[Action" + action + "]"; - break; - } - if (!TextUtils.isEmpty(eventTag)) { - final float size = me.getSize(index); - final float pressure = me.getPressure(index); - UsabilityStudyLogUtils.getInstance().write( - eventTag + eventTime + "," + id + "," + x + "," + y + "," - + size + "," + pressure); - } + final int x = (int)me.getX(index); + final int y = (int)me.getY(index); + + // TODO: This might be moved to the tracker.processMotionEvent() call below. + if (ENABLE_USABILITY_STUDY_LOG && action != MotionEvent.ACTION_MOVE) { + writeUsabilityStudyLog(me, action, eventTime, index, id, x, y); } + // TODO: This should be moved to the tracker.processMotionEvent() call below. + // Currently the same "move" event is being logged twice. if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.mainKeyboardView_processMotionEvent(me, action, eventTime, index, id, - x, y); + ResearchLogger.mainKeyboardView_processMotionEvent( + me, action, eventTime, index, id, x, y); } if (mKeyTimerHandler.isInKeyRepeat()) { @@ -774,16 +767,18 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack final Key newKey = tracker.getKeyOn(x, y); if (mOldKey != newKey) { tracker.onDownEvent(x, y, eventTime, this); - if (action == MotionEvent.ACTION_UP) + if (action == MotionEvent.ACTION_UP) { tracker.onUpEvent(x, y, eventTime); + } } } else if (pointerCount == 2 && oldPointerCount == 1) { // Single-touch to multi-touch transition. // Send an up event for the last pointer. - final int lastX = tracker.getLastX(); - final int lastY = tracker.getLastY(); - mOldKey = tracker.getKeyOn(lastX, lastY); - tracker.onUpEvent(lastX, lastY, eventTime); + final int[] lastCoords = CoordinateUtils.newInstance(); + mOldKey = tracker.getKeyOn( + CoordinateUtils.x(lastCoords), CoordinateUtils.y(lastCoords)); + tracker.onUpEvent( + CoordinateUtils.x(lastCoords), CoordinateUtils.y(lastCoords), eventTime); } else if (pointerCount == 1 && oldPointerCount == 1) { tracker.processMotionEvent(action, x, y, eventTime, this); } else { @@ -798,29 +793,15 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack final int pointerId = me.getPointerId(i); final PointerTracker tracker = PointerTracker.getPointerTracker( pointerId, this); - final int px, py; - final MotionEvent motionEvent; - if (mMoreKeysPanel != null - && tracker.mPointerId == mMoreKeysPanelPointerTrackerId) { - px = mMoreKeysPanel.translateX((int)me.getX(i)); - py = mMoreKeysPanel.translateY((int)me.getY(i)); - motionEvent = null; - } else { - px = (int)me.getX(i); - py = (int)me.getY(i); - motionEvent = me; - } - tracker.onMoveEvent(px, py, eventTime, motionEvent); + final int px = (int)me.getX(i); + final int py = (int)me.getY(i); + tracker.onMoveEvent(px, py, eventTime, me); if (ENABLE_USABILITY_STUDY_LOG) { - final float pointerSize = me.getSize(i); - final float pointerPressure = me.getPressure(i); - UsabilityStudyLogUtils.getInstance().write("[Move]" + eventTime + "," - + pointerId + "," + px + "," + py + "," - + pointerSize + "," + pointerPressure); + writeUsabilityStudyLog(me, action, eventTime, i, pointerId, px, py); } if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.mainKeyboardView_processMotionEvent(me, action, eventTime, - i, pointerId, px, py); + ResearchLogger.mainKeyboardView_processMotionEvent( + me, action, eventTime, i, pointerId, px, py); } } } else { @@ -831,23 +812,52 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack return true; } + private static void writeUsabilityStudyLog(final MotionEvent me, final int action, + final long eventTime, final int index, final int id, final int x, final int y) { + final String eventTag; + switch (action) { + case MotionEvent.ACTION_UP: + eventTag = "[Up]"; + break; + case MotionEvent.ACTION_DOWN: + eventTag = "[Down]"; + break; + case MotionEvent.ACTION_POINTER_UP: + eventTag = "[PointerUp]"; + break; + case MotionEvent.ACTION_POINTER_DOWN: + eventTag = "[PointerDown]"; + break; + case MotionEvent.ACTION_MOVE: + eventTag = "[Move]"; + break; + default: + eventTag = "[Action" + action + "]"; + break; + } + final float size = me.getSize(index); + final float pressure = me.getPressure(index); + UsabilityStudyLogUtils.getInstance().write( + eventTag + eventTime + "," + id + "," + x + "," + y + "," + size + "," + pressure); + } + @Override public void closing() { super.closing(); - dismissMoreKeysPanel(); + onCancelMoreKeysPanel(); mMoreKeysPanelCache.clear(); } @Override - public boolean dismissMoreKeysPanel() { - if (mMoreKeysWindow != null && mMoreKeysWindow.isShowing()) { - mMoreKeysWindow.dismiss(); - mMoreKeysPanel = null; - mMoreKeysPanelPointerTrackerId = -1; - dimEntireKeyboard(false); - return true; - } - return false; + public void onCancelMoreKeysPanel() { + super.onCancelMoreKeysPanel(); + PointerTracker.dismissAllMoreKeysPanels(); + } + + @Override + public boolean onDismissMoreKeysPanel() { + dimEntireKeyboard(false /* dimmed */); + return super.onDismissMoreKeysPanel(); } /** @@ -870,16 +880,22 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack public void updateShortcutKey(final boolean available) { final Keyboard keyboard = getKeyboard(); - if (keyboard == null) return; - final Key shortcutKey = keyboard.getKey(Keyboard.CODE_SHORTCUT); - if (shortcutKey == null) return; + if (keyboard == null) { + return; + } + final Key shortcutKey = keyboard.getKey(Constants.CODE_SHORTCUT); + if (shortcutKey == null) { + return; + } shortcutKey.setEnabled(available); invalidateKey(shortcutKey); } private void updateAltCodeKeyWhileTyping() { final Keyboard keyboard = getKeyboard(); - if (keyboard == null) return; + if (keyboard == null) { + return; + } for (final Key key : keyboard.mAltCodeKeysWhileTyping) { invalidateKey(key); } @@ -909,7 +925,9 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack } public void updateAutoCorrectionState(final boolean isAutoCorrection) { - if (!mAutoCorrectionSpacebarLedEnabled) return; + if (!mAutoCorrectionSpacebarLedEnabled) { + return; + } mAutoCorrectionSpacebarLedOn = isAutoCorrection; invalidateKey(mSpaceKey); } @@ -920,13 +938,13 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack if (key.altCodeWhileTyping() && key.isEnabled()) { params.mAnimAlpha = mAltCodeKeyWhileTypingAnimAlpha; } - if (key.mCode == Keyboard.CODE_SPACE) { + if (key.mCode == Constants.CODE_SPACE) { drawSpacebar(key, canvas, paint); // Whether space key needs to show the "..." popup hint for special purposes if (key.isLongPressEnabled() && mHasMultipleEnabledIMEsOrSubtypes) { drawKeyPopupHint(key, canvas, paint, params); } - } else if (key.mCode == Keyboard.CODE_LANGUAGE_SWITCH) { + } else if (key.mCode == Constants.CODE_LANGUAGE_SWITCH) { super.onDrawKeyTopVisuals(key, canvas, paint, params); drawKeyPopupHint(key, canvas, paint, params); } else { @@ -937,10 +955,14 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack private boolean fitsTextIntoWidth(final int width, final String text, final Paint paint) { paint.setTextScaleX(1.0f); final float textWidth = getLabelWidth(text, paint); - if (textWidth < width) return true; + if (textWidth < width) { + return true; + } final float scaleX = width / textWidth; - if (scaleX < MINIMUM_XSCALE_OF_LANGUAGE_NAME) return false; + if (scaleX < MINIMUM_XSCALE_OF_LANGUAGE_NAME) { + return false; + } paint.setTextScaleX(scaleX); return getLabelWidth(text, paint) < width; @@ -950,19 +972,19 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack private String layoutLanguageOnSpacebar(final Paint paint, final InputMethodSubtype subtype, final int width) { // Choose appropriate language name to fit into the width. - String text = getFullDisplayName(subtype, getResources()); - if (fitsTextIntoWidth(width, text, paint)) { - return text; + final String fullText = getFullDisplayName(subtype, getResources()); + if (fitsTextIntoWidth(width, fullText, paint)) { + return fullText; } - text = getMiddleDisplayName(subtype); - if (fitsTextIntoWidth(width, text, paint)) { - return text; + final String middleText = getMiddleDisplayName(subtype); + if (fitsTextIntoWidth(width, middleText, paint)) { + return middleText; } - text = getShortDisplayName(subtype); - if (fitsTextIntoWidth(width, text, paint)) { - return text; + final String shortText = getShortDisplayName(subtype); + if (fitsTextIntoWidth(width, shortText, paint)) { + return shortText; } return ""; @@ -1009,18 +1031,19 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack // InputMethodSubtype's display name for spacebar text in its locale. // isAdditionalSubtype (T=true, F=false) - // locale layout | Short Middle Full - // ------ ------ - ---- --------- ---------------------- - // en_US qwerty F En English English (US) exception - // en_GB qwerty F En English English (UK) exception - // fr azerty F Fr Français Français - // fr_CA qwerty F Fr Français Français (Canada) - // de qwertz F De Deutsch Deutsch - // zz qwerty F QWERTY QWERTY - // fr qwertz T Fr Français Français (QWERTZ) - // de qwerty T De Deutsch Deutsch (QWERTY) - // en_US azerty T En English English (US) (AZERTY) - // zz azerty T AZERTY AZERTY + // locale layout | Short Middle Full + // ------ ------- - ---- --------- ---------------------- + // en_US qwerty F En English English (US) exception + // en_GB qwerty F En English English (UK) exception + // es_US spanish F Es Español Español (EE.UU.) exception + // fr azerty F Fr Français Français + // fr_CA qwerty F Fr Français Français (Canada) + // de qwertz F De Deutsch Deutsch + // zz qwerty F QWERTY QWERTY + // fr qwertz T Fr Français Français (QWERTZ) + // de qwerty T De Deutsch Deutsch (QWERTY) + // en_US azerty T En English English (US) (AZERTY) + // zz azerty T AZERTY AZERTY // Get InputMethodSubtype's full display name in its locale. static String getFullDisplayName(final InputMethodSubtype subtype, final Resources res) { diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java index d7d4be40b..3826a39a4 100644 --- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java +++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java @@ -20,6 +20,7 @@ import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.view.View; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.keyboard.internal.KeyboardBuilder; import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; import com.android.inputmethod.keyboard.internal.KeyboardParams; @@ -39,7 +40,7 @@ public final class MoreKeysKeyboard extends Keyboard { return mDefaultKeyCoordX; } - /* package for test */ + @UsedForTesting static class MoreKeysKeyboardParams extends KeyboardParams { public boolean mIsFixedOrder; /* package */int mTopRowAdjustment; diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java index a50617693..8a5b7dad5 100644 --- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java @@ -19,83 +19,35 @@ package com.android.inputmethod.keyboard; import android.content.Context; import android.content.res.Resources; import android.util.AttributeSet; -import android.view.Gravity; +import android.view.MotionEvent; import android.view.View; -import android.widget.PopupWindow; -import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy; -import com.android.inputmethod.keyboard.PointerTracker.TimerProxy; import com.android.inputmethod.latin.Constants; -import com.android.inputmethod.latin.InputPointers; +import com.android.inputmethod.latin.CoordinateUtils; import com.android.inputmethod.latin.R; /** * A view that renders a virtual {@link MoreKeysKeyboard}. It handles rendering of keys and * detecting key presses and touch movements. */ -public final class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel { - private final int[] mCoordinates = new int[2]; +public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel { + private final int[] mCoordinates = CoordinateUtils.newInstance(); private final KeyDetector mKeyDetector; - private Controller mController; - private KeyboardActionListener mListener; + protected KeyboardActionListener mListener; private int mOriginX; private int mOriginY; + private Key mCurrentKey; - private static final TimerProxy EMPTY_TIMER_PROXY = new TimerProxy.Adapter(); - - private final KeyboardActionListener mMoreKeysKeyboardListener = - new KeyboardActionListener.Adapter() { - @Override - public void onCodeInput(int primaryCode, int x, int y) { - // Because a more keys keyboard doesn't need proximity characters correction, we don't - // send touch event coordinates. - mListener.onCodeInput( - primaryCode, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); - } - - @Override - public void onTextInput(CharSequence text) { - mListener.onTextInput(text); - } - - @Override - public void onStartBatchInput() { - mListener.onStartBatchInput(); - } - - @Override - public void onUpdateBatchInput(InputPointers batchPointers) { - mListener.onUpdateBatchInput(batchPointers); - } - - @Override - public void onEndBatchInput(InputPointers batchPointers) { - mListener.onEndBatchInput(batchPointers); - } - - @Override - public void onCancelInput() { - mListener.onCancelInput(); - } - - @Override - public void onPressKey(int primaryCode) { - mListener.onPressKey(primaryCode); - } - - @Override - public void onReleaseKey(int primaryCode, boolean withSliding) { - mListener.onReleaseKey(primaryCode, withSliding); - } - }; + private int mActivePointerId; - public MoreKeysKeyboardView(Context context, AttributeSet attrs) { + public MoreKeysKeyboardView(final Context context, final AttributeSet attrs) { this(context, attrs, R.attr.moreKeysKeyboardViewStyle); } - public MoreKeysKeyboardView(Context context, AttributeSet attrs, int defStyle) { + public MoreKeysKeyboardView(final Context context, final AttributeSet attrs, + final int defStyle) { super(context, attrs, defStyle); final Resources res = context.getResources(); @@ -105,7 +57,7 @@ public final class MoreKeysKeyboardView extends KeyboardView implements MoreKeys } @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { final Keyboard keyboard = getKeyboard(); if (keyboard != null) { final int width = keyboard.mOccupiedWidth + getPaddingLeft() + getPaddingRight(); @@ -117,80 +69,169 @@ public final class MoreKeysKeyboardView extends KeyboardView implements MoreKeys } @Override - public void setKeyboard(Keyboard keyboard) { + public void setKeyboard(final Keyboard keyboard) { super.setKeyboard(keyboard); mKeyDetector.setKeyboard(keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection); } @Override - public KeyDetector getKeyDetector() { - return mKeyDetector; + public void setKeyPreviewPopupEnabled(final boolean previewEnabled, final int delay) { + // More keys keyboard needs no pop-up key preview displayed, so we pass always false with a + // delay of 0. The delay does not matter actually since the popup is not shown anyway. + super.setKeyPreviewPopupEnabled(false, 0); } @Override - public KeyboardActionListener getKeyboardActionListener() { - return mMoreKeysKeyboardListener; + public void showMoreKeysPanel(final View parentView, final Controller controller, + final int pointX, final int pointY, final KeyboardActionListener listener) { + mController = controller; + mListener = listener; + final View container = getContainerView(); + // The coordinates of panel's left-top corner in parentView's coordinate system. + final int x = pointX - getDefaultCoordX() - container.getPaddingLeft(); + final int y = pointY - container.getMeasuredHeight() + container.getPaddingBottom(); + + parentView.getLocationInWindow(mCoordinates); + // Ensure the horizontal position of the panel does not extend past the screen edges. + final int maxX = parentView.getMeasuredWidth() - container.getMeasuredWidth(); + final int panelX = Math.max(0, Math.min(maxX, x)) + CoordinateUtils.x(mCoordinates); + final int panelY = y + CoordinateUtils.y(mCoordinates); + container.setX(panelX); + container.setY(panelY); + + mOriginX = x + container.getPaddingLeft(); + mOriginY = y + container.getPaddingTop(); + controller.onShowMoreKeysPanel(this); } - @Override - public DrawingProxy getDrawingProxy() { - return this; + /** + * Returns the default x coordinate for showing this panel. + */ + protected int getDefaultCoordX() { + return ((MoreKeysKeyboard)getKeyboard()).getDefaultCoordX(); } @Override - public TimerProxy getTimerProxy() { - return EMPTY_TIMER_PROXY; + public void onDownEvent(final int x, final int y, final int pointerId, final long eventTime) { + mActivePointerId = pointerId; + onMoveKeyInternal(x, y, pointerId); } @Override - public void setKeyPreviewPopupEnabled(boolean previewEnabled, int delay) { - // More keys keyboard needs no pop-up key preview displayed, so we pass always false with a - // delay of 0. The delay does not matter actually since the popup is not shown anyway. - super.setKeyPreviewPopupEnabled(false, 0); + public void onMoveEvent(int x, int y, final int pointerId, long eventTime) { + if (mActivePointerId != pointerId) { + return; + } + final boolean hasOldKey = (mCurrentKey != null); + onMoveKeyInternal(x, y, pointerId); + if (hasOldKey && mCurrentKey == null) { + // If the pointer has moved too far away from any target then cancel the panel. + mController.onCancelMoreKeysPanel(); + } } @Override - public void showMoreKeysPanel(View parentView, Controller controller, int pointX, int pointY, - PopupWindow window, KeyboardActionListener listener) { - mController = controller; - mListener = listener; - final View container = (View)getParent(); - final MoreKeysKeyboard pane = (MoreKeysKeyboard)getKeyboard(); - final int defaultCoordX = pane.getDefaultCoordX(); - // The coordinates of panel's left-top corner in parentView's coordinate system. - final int x = pointX - defaultCoordX - container.getPaddingLeft(); - final int y = pointY - container.getMeasuredHeight() + container.getPaddingBottom(); + public void onUpEvent(final int x, final int y, final int pointerId, final long eventTime) { + if (mCurrentKey != null && mActivePointerId == pointerId) { + updateReleaseKeyGraphics(mCurrentKey); + onCodeInput(mCurrentKey.mCode, x, y); + mCurrentKey = null; + } + } - window.setContentView(container); - window.setWidth(container.getMeasuredWidth()); - window.setHeight(container.getMeasuredHeight()); - parentView.getLocationInWindow(mCoordinates); - window.showAtLocation(parentView, Gravity.NO_GRAVITY, - x + mCoordinates[0], y + mCoordinates[1]); + /** + * Performs the specific action for this panel when the user presses a key on the panel. + */ + protected void onCodeInput(final int code, final int x, final int y) { + if (code == Constants.CODE_OUTPUT_TEXT) { + mListener.onTextInput(mCurrentKey.getOutputText()); + } else if (code != Constants.CODE_UNSPECIFIED) { + mListener.onCodeInput(code, x, y); + } + } - mOriginX = x + container.getPaddingLeft(); - mOriginY = y + container.getPaddingTop(); + private void onMoveKeyInternal(int x, int y, int pointerId) { + if (mActivePointerId != pointerId) { + // Ignore old pointers when newer pointer is active. + return; + } + final Key oldKey = mCurrentKey; + final Key newKey = mKeyDetector.detectHitKey(x, y); + if (newKey != oldKey) { + mCurrentKey = newKey; + invalidateKey(mCurrentKey); + if (oldKey != null) { + updateReleaseKeyGraphics(oldKey); + } + if (newKey != null) { + updatePressKeyGraphics(newKey); + } + } } - private boolean mIsDismissing; + private void updateReleaseKeyGraphics(final Key key) { + key.onReleased(); + invalidateKey(key); + } + + private void updatePressKeyGraphics(final Key key) { + key.onPressed(); + invalidateKey(key); + } @Override public boolean dismissMoreKeysPanel() { - if (mIsDismissing || mController == null) return false; - mIsDismissing = true; - final boolean dismissed = mController.dismissMoreKeysPanel(); - mIsDismissing = false; - return dismissed; + if (mController == null) return false; + return mController.onDismissMoreKeysPanel(); } @Override - public int translateX(int x) { + public int translateX(final int x) { return x - mOriginX; } @Override - public int translateY(int y) { + public int translateY(final int y) { return y - mOriginY; } + + @Override + public boolean onTouchEvent(final MotionEvent me) { + final int action = me.getActionMasked(); + final long eventTime = me.getEventTime(); + final int index = me.getActionIndex(); + final int x = (int)me.getX(index); + final int y = (int)me.getY(index); + final int pointerId = me.getPointerId(index); + processMotionEvent(action, x, y, pointerId, eventTime); + return true; + } + + public void processMotionEvent(final int action, final int x, final int y, + final int pointerId, final long eventTime) { + switch (action) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_POINTER_DOWN: + onDownEvent(x, y, pointerId, eventTime); + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_POINTER_UP: + onUpEvent(x, y, pointerId, eventTime); + break; + case MotionEvent.ACTION_MOVE: + onMoveEvent(x, y, pointerId, eventTime); + break; + } + } + + @Override + public View getContainerView() { + return (View)getParent(); + } + + @Override + public boolean isShowingInParent() { + return (getContainerView().getParent() != null); + } } diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java b/java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java index f9a196d24..9c677e5c8 100644 --- a/java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java +++ b/java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java @@ -17,25 +17,77 @@ package com.android.inputmethod.keyboard; import android.view.View; -import android.widget.PopupWindow; -public interface MoreKeysPanel extends PointerTracker.KeyEventHandler { +public interface MoreKeysPanel { public interface Controller { - public boolean dismissMoreKeysPanel(); + /** + * Add the {@link MoreKeysPanel} to the target view. + * @param panel + */ + public void onShowMoreKeysPanel(final MoreKeysPanel panel); + + /** + * Remove the current {@link MoreKeysPanel} from the target view. + */ + public boolean onDismissMoreKeysPanel(); + + /** + * Instructs the parent to cancel the panel (e.g., when entering a different input mode). + */ + public void onCancelMoreKeysPanel(); } /** - * Show more keys panel. + * Initializes the layout and event handling of this {@link MoreKeysPanel} and calls the + * controller's onShowMoreKeysPanel to add the panel's container view. + * + * @param parentView the parent view of this {@link MoreKeysPanel} + * @param controller the controller that can dismiss this {@link MoreKeysPanel} + * @param pointX x coordinate of this {@link MoreKeysPanel} + * @param pointY y coordinate of this {@link MoreKeysPanel} + * @param listener the listener that will receive keyboard action from this + * {@link MoreKeysPanel}. + */ + // TODO: Currently the MoreKeysPanel is inside a container view that is added to the parent. + // Consider the simpler approach of placing the MoreKeysPanel itself into the parent view. + public void showMoreKeysPanel(View parentView, Controller controller, int pointX, + int pointY, KeyboardActionListener listener); + + /** + * Dismisses the more keys panel and calls the controller's onDismissMoreKeysPanel to remove + * the panel's container view. + */ + public boolean dismissMoreKeysPanel(); + + /** + * Process a move event on the more keys panel. + * + * @param x translated x coordinate of the touch point + * @param y translated y coordinate of the touch point + * @param pointerId pointer id touch point + * @param eventTime timestamp of touch point + */ + public void onMoveEvent(final int x, final int y, final int pointerId, final long eventTime); + + /** + * Process a down event on the more keys panel. + * + * @param x translated x coordinate of the touch point + * @param y translated y coordinate of the touch point + * @param pointerId pointer id touch point + * @param eventTime timestamp of touch point + */ + public void onDownEvent(final int x, final int y, final int pointerId, final long eventTime); + + /** + * Process an up event on the more keys panel. * - * @param parentView the parent view of this more keys panel - * @param controller the controller that can dismiss this more keys panel - * @param pointX x coordinate of this more keys panel - * @param pointY y coordinate of this more keys panel - * @param window PopupWindow to be used to show this more keys panel - * @param listener the listener that will receive keyboard action from this more keys panel. + * @param x translated x coordinate of the touch point + * @param y translated y coordinate of the touch point + * @param pointerId pointer id touch point + * @param eventTime timestamp of touch point */ - public void showMoreKeysPanel(View parentView, Controller controller, int pointX, int pointY, - PopupWindow window, KeyboardActionListener listener); + public void onUpEvent(final int x, final int y, final int pointerId, final long eventTime); /** * Translate X-coordinate of touch event to the local X-coordinate of this @@ -54,4 +106,14 @@ public interface MoreKeysPanel extends PointerTracker.KeyEventHandler { * @return the local Y-coordinate to this {@link MoreKeysPanel} */ public int translateY(int y); + + /** + * Return the view containing the more keys panel. + */ + public View getContainerView(); + + /** + * Return whether the panel is currently being shown. + */ + public boolean isShowingInParent(); } diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index d0f7cb276..0f55607a0 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -27,6 +27,8 @@ import com.android.inputmethod.keyboard.internal.GestureStroke.GestureStrokePara import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewPoints; import com.android.inputmethod.keyboard.internal.PointerTrackerQueue; import com.android.inputmethod.latin.CollectionUtils; +import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.CoordinateUtils; import com.android.inputmethod.latin.InputPointers; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; @@ -75,10 +77,12 @@ public final class PointerTracker implements PointerTrackerQueue.Element { public TimerProxy getTimerProxy(); } - public interface DrawingProxy extends MoreKeysPanel.Controller { + public interface DrawingProxy { public void invalidateKey(Key key); public void showKeyPreview(PointerTracker tracker); public void dismissKeyPreview(PointerTracker tracker); + public void showSlidingKeyInputPreview(PointerTracker tracker); + public void dismissSlidingKeyInputPreview(); public void showGesturePreviewTrail(PointerTracker tracker, boolean isOldestTracker); } @@ -93,6 +97,8 @@ public final class PointerTracker implements PointerTrackerQueue.Element { public void cancelDoubleTapTimer(); public boolean isInDoubleTapTimeout(); public void cancelKeyTimers(); + public void startUpdateBatchInputTimer(PointerTracker tracker); + public void cancelAllUpdateBatchInputTimers(); public static class Adapter implements TimerProxy { @Override @@ -115,6 +121,10 @@ public final class PointerTracker implements PointerTrackerQueue.Element { public boolean isInDoubleTapTimeout() { return false; } @Override public void cancelKeyTimers() {} + @Override + public void startUpdateBatchInputTimer(PointerTracker tracker) {} + @Override + public void cancelAllUpdateBatchInputTimers() {} } } @@ -156,7 +166,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { private static final boolean sNeedsProximateBogusDownMoveUpEventHack = true; private static final ArrayList<PointerTracker> sTrackers = CollectionUtils.newArrayList(); - private static PointerTrackerQueue sPointerTrackerQueue; + private static final PointerTrackerQueue sPointerTrackerQueue = new PointerTrackerQueue(); public final int mPointerId; @@ -289,6 +299,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { // The position and time at which first down event occurred. private long mDownTime; + private int[] mDownCoordinates = CoordinateUtils.newInstance(); private long mUpTime; // The current key where this pointer is. @@ -304,11 +315,11 @@ public final class PointerTracker implements PointerTrackerQueue.Element { // true if keyboard layout has been changed. private boolean mKeyboardLayoutHasBeenChanged; - // true if event is already translated to a key action. - private boolean mKeyAlreadyProcessed; + // true if this pointer is no longer tracking touch event. + private boolean mIsTrackingCanceled; - // true if this pointer has been long-pressed and is showing a more keys panel. - private boolean mIsShowingMoreKeysPanel; + // the more keys panel currently being shown. equals null if no panel is active. + private MoreKeysPanel mMoreKeysPanel; // true if this pointer is in a sliding key input. boolean mIsInSlidingKeyInput; @@ -325,13 +336,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { private final GestureStrokeWithPreviewPoints mGestureStrokeWithPreviewPoints; - public static void init(boolean hasDistinctMultitouch, - boolean needsPhantomSuddenMoveEventHack) { - if (hasDistinctMultitouch) { - sPointerTrackerQueue = new PointerTrackerQueue(); - } else { - sPointerTrackerQueue = null; - } + public static void init(final boolean needsPhantomSuddenMoveEventHack) { sNeedsPhantomSuddenMoveEventHack = needsPhantomSuddenMoveEventHack; sParams = PointerTrackerParams.DEFAULT; sGestureStrokeParams = GestureStrokeParams.DEFAULT; @@ -375,7 +380,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { } public static boolean isAnyInSlidingKeyInput() { - return sPointerTrackerQueue != null ? sPointerTrackerQueue.isAnyInSlidingKeyInput() : false; + return sPointerTrackerQueue.isAnyInSlidingKeyInput(); } public static void setKeyboardActionListener(final KeyboardActionListener listener) { @@ -407,6 +412,17 @@ public final class PointerTracker implements PointerTrackerQueue.Element { } } + public static void dismissAllMoreKeysPanels() { + final int trackersSize = sTrackers.size(); + for (int i = 0; i < trackersSize; ++i) { + final PointerTracker tracker = sTrackers.get(i); + if (tracker.isShowingMoreKeysPanel()) { + tracker.mMoreKeysPanel.dismissMoreKeysPanel(); + tracker.mMoreKeysPanel = null; + } + } + } + private PointerTracker(final int id, final KeyEventHandler handler) { if (handler == null) { throw new NullPointerException(); @@ -453,8 +469,8 @@ public final class PointerTracker implements PointerTrackerQueue.Element { final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState(); final int code = altersCode ? key.getAltCode() : primaryCode; if (DEBUG_LISTENER) { - final String output = code == Keyboard.CODE_OUTPUT_TEXT - ? key.getOutputText() : Keyboard.printableCode(code); + final String output = code == Constants.CODE_OUTPUT_TEXT + ? key.getOutputText() : Constants.printableCode(code); Log.d(TAG, String.format("[%d] onCodeInput: %4d %4d %s%s%s", mPointerId, x, y, output, ignoreModifierKey ? " ignoreModifier" : "", altersCode ? " altersCode" : "", key.isEnabled() ? "" : " disabled")); @@ -469,9 +485,9 @@ public final class PointerTracker implements PointerTrackerQueue.Element { // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state. if (key.isEnabled() || altersCode) { sTimeRecorder.onCodeInput(code, eventTime); - if (code == Keyboard.CODE_OUTPUT_TEXT) { + if (code == Constants.CODE_OUTPUT_TEXT) { mListener.onTextInput(key.getOutputText()); - } else if (code != Keyboard.CODE_UNSPECIFIED) { + } else if (code != Constants.CODE_UNSPECIFIED) { mListener.onCodeInput(code, x, y); } } @@ -487,7 +503,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { final boolean ignoreModifierKey = mIsInSlidingKeyInputFromModifier && key.isModifier(); if (DEBUG_LISTENER) { Log.d(TAG, String.format("[%d] onRelease : %s%s%s%s", mPointerId, - Keyboard.printableCode(primaryCode), + Constants.printableCode(primaryCode), withSliding ? " sliding" : "", ignoreModifierKey ? " ignoreModifier" : "", key.isEnabled() ? "": " disabled")); } @@ -522,7 +538,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { mKeyboard = keyDetector.getKeyboard(); final int keyWidth = mKeyboard.mMostCommonKeyWidth; final int keyHeight = mKeyboard.mMostCommonKeyHeight; - mGestureStrokeWithPreviewPoints.setKeyboardGeometry(keyWidth); + mGestureStrokeWithPreviewPoints.setKeyboardGeometry(keyWidth, mKeyboard.mOccupiedHeight); final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY); if (newKey != mCurrentKey) { if (mDrawingProxy != null) { @@ -539,6 +555,10 @@ public final class PointerTracker implements PointerTrackerQueue.Element { return mIsInSlidingKeyInput; } + public boolean isInSlidingKeyInputFromModifier() { + return mIsInSlidingKeyInputFromModifier; + } + public Key getKey() { return mCurrentKey; } @@ -641,20 +661,21 @@ public final class PointerTracker implements PointerTrackerQueue.Element { return mGestureStrokeWithPreviewPoints; } - public int getLastX() { - return mLastX; - } - - public int getLastY() { - return mLastY; + public void getLastCoordinates(final int[] outCoords) { + CoordinateUtils.set(outCoords, mLastX, mLastY); } public long getDownTime() { return mDownTime; } + public void getDownCoordinates(final int[] outCoords) { + CoordinateUtils.copy(outCoords, mDownCoordinates); + } + private Key onDownKey(final int x, final int y, final long eventTime) { mDownTime = eventTime; + CoordinateUtils.set(mDownCoordinates, x, y); mBogusMoveEventDetector.onDownKey(); return onMoveToNewKey(onMoveKeyInternal(x, y), x, y); } @@ -682,7 +703,11 @@ public final class PointerTracker implements PointerTrackerQueue.Element { } private static int getActivePointerTrackerCount() { - return (sPointerTrackerQueue == null) ? 1 : sPointerTrackerQueue.size(); + return sPointerTrackerQueue.size(); + } + + private static boolean isOldestTrackerInQueue(final PointerTracker tracker) { + return sPointerTrackerQueue.getOldestElement() == tracker; } private void mayStartBatchInput(final Key key) { @@ -701,48 +726,76 @@ public final class PointerTracker implements PointerTrackerQueue.Element { sLastRecognitionPointSize = 0; sLastRecognitionTime = 0; mListener.onStartBatchInput(); + dismissAllMoreKeysPanels(); } - final boolean isOldestTracker = sPointerTrackerQueue.getOldestElement() == this; - mDrawingProxy.showGesturePreviewTrail(this, isOldestTracker); + mTimerProxy.cancelLongPressTimer(); + mDrawingProxy.showGesturePreviewTrail(this, isOldestTrackerInQueue(this)); + } + + public void updateBatchInputByTimer(final long eventTime) { + final int gestureTime = (int)(eventTime - sGestureFirstDownTime); + mGestureStrokeWithPreviewPoints.duplicateLastPointWith(gestureTime); + updateBatchInput(eventTime); } private void mayUpdateBatchInput(final long eventTime, final Key key) { if (key != null) { - synchronized (sAggregratedPointers) { - final GestureStroke stroke = mGestureStrokeWithPreviewPoints; - stroke.appendIncrementalBatchPoints(sAggregratedPointers); - final int size = sAggregratedPointers.getPointerSize(); - if (size > sLastRecognitionPointSize - && stroke.hasRecognitionTimePast(eventTime, sLastRecognitionTime)) { - sLastRecognitionPointSize = size; - sLastRecognitionTime = eventTime; - if (DEBUG_LISTENER) { - Log.d(TAG, String.format("[%d] onUpdateBatchInput: batchPoints=%d", - mPointerId, size)); - } - mListener.onUpdateBatchInput(sAggregratedPointers); + updateBatchInput(eventTime); + } + if (mIsTrackingCanceled) { + return; + } + mDrawingProxy.showGesturePreviewTrail(this, isOldestTrackerInQueue(this)); + } + + private void updateBatchInput(final long eventTime) { + synchronized (sAggregratedPointers) { + final GestureStroke stroke = mGestureStrokeWithPreviewPoints; + stroke.appendIncrementalBatchPoints(sAggregratedPointers); + final int size = sAggregratedPointers.getPointerSize(); + if (size > sLastRecognitionPointSize + && stroke.hasRecognitionTimePast(eventTime, sLastRecognitionTime)) { + sLastRecognitionPointSize = size; + sLastRecognitionTime = eventTime; + if (DEBUG_LISTENER) { + Log.d(TAG, String.format("[%d] onUpdateBatchInput: batchPoints=%d", mPointerId, + size)); } + mTimerProxy.startUpdateBatchInputTimer(this); + mListener.onUpdateBatchInput(sAggregratedPointers); } } - final boolean isOldestTracker = sPointerTrackerQueue.getOldestElement() == this; - mDrawingProxy.showGesturePreviewTrail(this, isOldestTracker); } private void mayEndBatchInput(final long eventTime) { synchronized (sAggregratedPointers) { mGestureStrokeWithPreviewPoints.appendAllBatchPoints(sAggregratedPointers); if (getActivePointerTrackerCount() == 1) { - if (DEBUG_LISTENER) { - Log.d(TAG, String.format("[%d] onEndBatchInput : batchPoints=%d", - mPointerId, sAggregratedPointers.getPointerSize())); - } sInGesture = false; sTimeRecorder.onEndBatchInput(eventTime); - mListener.onEndBatchInput(sAggregratedPointers); + mTimerProxy.cancelAllUpdateBatchInputTimers(); + if (!mIsTrackingCanceled) { + if (DEBUG_LISTENER) { + Log.d(TAG, String.format("[%d] onEndBatchInput : batchPoints=%d", + mPointerId, sAggregratedPointers.getPointerSize())); + } + mListener.onEndBatchInput(sAggregratedPointers); + } } } - final boolean isOldestTracker = sPointerTrackerQueue.getOldestElement() == this; - mDrawingProxy.showGesturePreviewTrail(this, isOldestTracker); + if (mIsTrackingCanceled) { + return; + } + mDrawingProxy.showGesturePreviewTrail(this, isOldestTrackerInQueue(this)); + } + + private void cancelBatchInput() { + sPointerTrackerQueue.cancelAllPointerTracker(); + sInGesture = false; + if (DEBUG_LISTENER) { + Log.d(TAG, String.format("[%d] onCancelBatchInput", mPointerId)); + } + mListener.onCancelBatchInput(); } public void processMotionEvent(final int action, final int x, final int y, final long eventTime, @@ -770,7 +823,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element { if (DEBUG_EVENT) { printTouchEvent("onDownEvent:", x, y, eventTime); } - mDrawingProxy = handler.getDrawingProxy(); mTimerProxy = handler.getTimerProxy(); setKeyboardActionListener(handler.getKeyboardActionListener()); @@ -787,29 +839,26 @@ public final class PointerTracker implements PointerTrackerQueue.Element { if (ProductionFlag.IS_EXPERIMENTAL) { ResearchLogger.pointerTracker_onDownEvent(deltaT, distance * distance); } - mKeyAlreadyProcessed = true; + cancelTracking(); return; } } final Key key = getKeyOn(x, y); mBogusMoveEventDetector.onActualDownEvent(x, y); - final PointerTrackerQueue queue = sPointerTrackerQueue; - if (queue != null) { - if (key != null && key.isModifier()) { - // Before processing a down event of modifier key, all pointers already being - // tracked should be released. - queue.releaseAllPointers(eventTime); - } - queue.add(this); + if (key != null && key.isModifier()) { + // Before processing a down event of modifier key, all pointers already being + // tracked should be released. + sPointerTrackerQueue.releaseAllPointers(eventTime); } + sPointerTrackerQueue.add(this); onDownEventInternal(x, y, eventTime); if (!sShouldHandleGesture) { return; } - // A gesture should start only from the letter key. + // A gesture should start only from a non-modifier key. mIsDetectingGesture = (mKeyboard != null) && mKeyboard.mId.isAlphabetKeyboard() - && !mIsShowingMoreKeysPanel && key != null && Keyboard.isLetterCode(key.mCode); + && key != null && !key.isModifier(); if (mIsDetectingGesture) { if (getActivePointerTrackerCount() == 1) { sGestureFirstDownTime = eventTime; @@ -819,6 +868,10 @@ public final class PointerTracker implements PointerTrackerQueue.Element { } } + private boolean isShowingMoreKeysPanel() { + return (mMoreKeysPanel != null); + } + private void onDownEventInternal(final int x, final int y, final long eventTime) { Key key = onDownKey(x, y, eventTime); // Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding @@ -827,7 +880,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { || (key != null && key.isModifier()) || mKeyDetector.alwaysAllowsSlidingInput(); mKeyboardLayoutHasBeenChanged = false; - mKeyAlreadyProcessed = false; + mIsTrackingCanceled = false; resetSlidingKeyInput(); if (key != null) { // This onPress call may have changed keyboard layout. Those cases are detected at @@ -853,13 +906,24 @@ public final class PointerTracker implements PointerTrackerQueue.Element { private void resetSlidingKeyInput() { mIsInSlidingKeyInput = false; mIsInSlidingKeyInputFromModifier = false; + mDrawingProxy.dismissSlidingKeyInputPreview(); } private void onGestureMoveEvent(final int x, final int y, final long eventTime, final boolean isMajorEvent, final Key key) { final int gestureTime = (int)(eventTime - sGestureFirstDownTime); if (mIsDetectingGesture) { - mGestureStrokeWithPreviewPoints.addPoint(x, y, gestureTime, isMajorEvent); + final boolean onValidArea = mGestureStrokeWithPreviewPoints.addPointOnKeyboard( + x, y, gestureTime, isMajorEvent); + if (!onValidArea) { + cancelBatchInput(); + return; + } + // If the MoreKeysPanel is showing then do not attempt to enter gesture mode. However, + // the gestured touch points are still being recorded in case the panel is dismissed. + if (isShowingMoreKeysPanel()) { + return; + } mayStartBatchInput(key); if (sInGesture) { mayUpdateBatchInput(eventTime, key); @@ -871,10 +935,16 @@ public final class PointerTracker implements PointerTrackerQueue.Element { if (DEBUG_MOVE_EVENT) { printTouchEvent("onMoveEvent:", x, y, eventTime); } - if (mKeyAlreadyProcessed) { + if (mIsTrackingCanceled) { return; } + if (isShowingMoreKeysPanel()) { + final int translatedX = mMoreKeysPanel.translateX(x); + final int translatedY = mMoreKeysPanel.translateY(y); + mMoreKeysPanel.onMoveEvent(translatedX, translatedY, mPointerId, eventTime); + } + if (sShouldHandleGesture && me != null) { // Add historical points to gesture path. final int pointerIndex = me.findPointerIndex(mPointerId); @@ -888,141 +958,160 @@ public final class PointerTracker implements PointerTrackerQueue.Element { } } + if (isShowingMoreKeysPanel()) { + // Do not handle sliding keys (or show key pop-ups) when the MoreKeysPanel is visible. + return; + } onMoveEventInternal(x, y, eventTime); } + private void processSlidingKeyInput(final Key newKey, final int x, final int y, + final long eventTime) { + // This onPress call may have changed keyboard layout. Those cases are detected + // at {@link #setKeyboard}. In those cases, we should update key according + // to the new keyboard layout. + Key key = newKey; + if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) { + key = onMoveKey(x, y); + } + onMoveToNewKey(key, x, y); + startLongPressTimer(key); + setPressedKeyGraphics(key, eventTime); + } + + private void processPhantomSuddenMoveHack(final Key key, final int x, final int y, + final long eventTime, final Key oldKey, final int lastX, final int lastY) { + if (DEBUG_MODE) { + Log.w(TAG, String.format("[%d] onMoveEvent:" + + " phantom sudden move event (distance=%d) is translated to " + + "up[%d,%d,%s]/down[%d,%d,%s] events", mPointerId, + getDistance(x, y, lastX, lastY), + lastX, lastY, Constants.printableCode(oldKey.mCode), + x, y, Constants.printableCode(key.mCode))); + } + // TODO: This should be moved to outside of this nested if-clause? + if (ProductionFlag.IS_EXPERIMENTAL) { + ResearchLogger.pointerTracker_onMoveEvent(x, y, lastX, lastY); + } + onUpEventInternal(x, y, eventTime); + onDownEventInternal(x, y, eventTime); + } + + private void processProximateBogusDownMoveUpEventHack(final Key key, final int x, final int y, + final long eventTime, final Key oldKey, final int lastX, final int lastY) { + if (DEBUG_MODE) { + final float keyDiagonal = (float)Math.hypot( + mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight); + final float radiusRatio = + mBogusMoveEventDetector.getDistanceFromDownEvent(x, y) + / keyDiagonal; + Log.w(TAG, String.format("[%d] onMoveEvent:" + + " bogus down-move-up event (raidus=%.2f key diagonal) is " + + " translated to up[%d,%d,%s]/down[%d,%d,%s] events", + mPointerId, radiusRatio, + lastX, lastY, Constants.printableCode(oldKey.mCode), + x, y, Constants.printableCode(key.mCode))); + } + onUpEventInternal(x, y, eventTime); + onDownEventInternal(x, y, eventTime); + } + + private void processSildeOutFromOldKey(final Key oldKey) { + setReleasedKeyGraphics(oldKey); + callListenerOnRelease(oldKey, oldKey.mCode, true); + startSlidingKeyInput(oldKey); + mTimerProxy.cancelKeyTimers(); + } + + private void slideFromOldKeyToNewKey(final Key key, final int x, final int y, + final long eventTime, final Key oldKey, final int lastX, final int lastY) { + // The pointer has been slid in to the new key from the previous key, we must call + // onRelease() first to notify that the previous key has been released, then call + // onPress() to notify that the new key is being pressed. + processSildeOutFromOldKey(oldKey); + startRepeatKey(key); + if (mIsAllowedSlidingKeyInput) { + processSlidingKeyInput(key, x, y, eventTime); + } + // HACK: On some devices, quick successive touches may be reported as a sudden move by + // touch panel firmware. This hack detects such cases and translates the move event to + // successive up and down events. + // TODO: Should find a way to balance gesture detection and this hack. + else if (sNeedsPhantomSuddenMoveEventHack + && getDistance(x, y, lastX, lastY) >= mPhantonSuddenMoveThreshold) { + processPhantomSuddenMoveHack(key, x, y, eventTime, oldKey, lastX, lastY); + } + // HACK: On some devices, quick successive proximate touches may be reported as a bogus + // down-move-up event by touch panel firmware. This hack detects such cases and breaks + // these events into separate up and down events. + else if (sNeedsProximateBogusDownMoveUpEventHack && sTimeRecorder.isInFastTyping(eventTime) + && mBogusMoveEventDetector.isCloseToActualDownEvent(x, y)) { + processProximateBogusDownMoveUpEventHack(key, x, y, eventTime, oldKey, lastX, lastY); + } + // HACK: If there are currently multiple touches, register the key even if the finger + // slides off the key. This defends against noise from some touch panels when there are + // close multiple touches. + // Caveat: When in chording input mode with a modifier key, we don't use this hack. + else if (getActivePointerTrackerCount() > 1 + && !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) { + if (DEBUG_MODE) { + Log.w(TAG, String.format("[%d] onMoveEvent:" + + " detected sliding finger while multi touching", mPointerId)); + } + onUpEvent(x, y, eventTime); + cancelTracking(); + setReleasedKeyGraphics(oldKey); + } else { + if (!mIsDetectingGesture) { + cancelTracking(); + } + setReleasedKeyGraphics(oldKey); + } + } + + private void slideOutFromOldKey(final Key oldKey, final int x, final int y) { + // The pointer has been slid out from the previous key, we must call onRelease() to + // notify that the previous key has been released. + processSildeOutFromOldKey(oldKey); + if (mIsAllowedSlidingKeyInput) { + onMoveToNewKey(null, x, y); + } else { + if (!mIsDetectingGesture) { + cancelTracking(); + } + } + } + private void onMoveEventInternal(final int x, final int y, final long eventTime) { final int lastX = mLastX; final int lastY = mLastY; final Key oldKey = mCurrentKey; - Key key = onMoveKey(x, y); + final Key newKey = onMoveKey(x, y); if (sShouldHandleGesture) { // Register move event on gesture tracker. - onGestureMoveEvent(x, y, eventTime, true /* isMajorEvent */, key); + onGestureMoveEvent(x, y, eventTime, true /* isMajorEvent */, newKey); if (sInGesture) { - mTimerProxy.cancelLongPressTimer(); mCurrentKey = null; setReleasedKeyGraphics(oldKey); return; } } - if (key != null) { - if (oldKey == null) { + if (newKey != null) { + if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, eventTime, newKey)) { + slideFromOldKeyToNewKey(newKey, x, y, eventTime, oldKey, lastX, lastY); + } else if (oldKey == null) { // The pointer has been slid in to the new key, but the finger was not on any keys. // In this case, we must call onPress() to notify that the new key is being pressed. - // This onPress call may have changed keyboard layout. Those cases are detected at - // {@link #setKeyboard}. In those cases, we should update key according to the - // new keyboard layout. - if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) { - key = onMoveKey(x, y); - } - onMoveToNewKey(key, x, y); - startLongPressTimer(key); - setPressedKeyGraphics(key, eventTime); - } else if (isMajorEnoughMoveToBeOnNewKey(x, y, eventTime, key)) { - // The pointer has been slid in to the new key from the previous key, we must call - // onRelease() first to notify that the previous key has been released, then call - // onPress() to notify that the new key is being pressed. - setReleasedKeyGraphics(oldKey); - callListenerOnRelease(oldKey, oldKey.mCode, true); - startSlidingKeyInput(oldKey); - mTimerProxy.cancelKeyTimers(); - startRepeatKey(key); - if (mIsAllowedSlidingKeyInput) { - // This onPress call may have changed keyboard layout. Those cases are detected - // at {@link #setKeyboard}. In those cases, we should update key according - // to the new keyboard layout. - if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) { - key = onMoveKey(x, y); - } - onMoveToNewKey(key, x, y); - startLongPressTimer(key); - setPressedKeyGraphics(key, eventTime); - } else { - // HACK: On some devices, quick successive touches may be reported as a sudden - // move by touch panel firmware. This hack detects such cases and translates the - // move event to successive up and down events. - // TODO: Should find a way to balance gesture detection and this hack. - if (sNeedsPhantomSuddenMoveEventHack - && getDistance(x, y, lastX, lastY) >= mPhantonSuddenMoveThreshold) { - if (DEBUG_MODE) { - Log.w(TAG, String.format("[%d] onMoveEvent:" - + " phantom sudden move event (distance=%d) is translated to " - + "up[%d,%d,%s]/down[%d,%d,%s] events", mPointerId, - getDistance(x, y, lastX, lastY), - lastX, lastY, Keyboard.printableCode(oldKey.mCode), - x, y, Keyboard.printableCode(key.mCode))); - } - // TODO: This should be moved to outside of this nested if-clause? - if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.pointerTracker_onMoveEvent(x, y, lastX, lastY); - } - onUpEventInternal(eventTime); - onDownEventInternal(x, y, eventTime); - } - // HACK: On some devices, quick successive proximate touches may be reported as - // a bogus down-move-up event by touch panel firmware. This hack detects such - // cases and breaks these events into separate up and down events. - else if (sNeedsProximateBogusDownMoveUpEventHack - && sTimeRecorder.isInFastTyping(eventTime) - && mBogusMoveEventDetector.isCloseToActualDownEvent(x, y)) { - if (DEBUG_MODE) { - final float keyDiagonal = (float)Math.hypot( - mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight); - final float radiusRatio = - mBogusMoveEventDetector.getDistanceFromDownEvent(x, y) - / keyDiagonal; - Log.w(TAG, String.format("[%d] onMoveEvent:" - + " bogus down-move-up event (raidus=%.2f key diagonal) is " - + " translated to up[%d,%d,%s]/down[%d,%d,%s] events", - mPointerId, radiusRatio, - lastX, lastY, Keyboard.printableCode(oldKey.mCode), - x, y, Keyboard.printableCode(key.mCode))); - } - onUpEventInternal(eventTime); - onDownEventInternal(x, y, eventTime); - } else { - // HACK: If there are currently multiple touches, register the key even if - // the finger slides off the key. This defends against noise from some - // touch panels when there are close multiple touches. - // Caveat: When in chording input mode with a modifier key, we don't use - // this hack. - if (getActivePointerTrackerCount() > 1 && sPointerTrackerQueue != null - && !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) { - if (DEBUG_MODE) { - Log.w(TAG, String.format("[%d] onMoveEvent:" - + " detected sliding finger while multi touching", - mPointerId)); - } - onUpEvent(x, y, eventTime); - mKeyAlreadyProcessed = true; - } - if (!mIsDetectingGesture) { - mKeyAlreadyProcessed = true; - } - setReleasedKeyGraphics(oldKey); - } - } + processSlidingKeyInput(newKey, x, y, eventTime); } - } else { - if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, eventTime, key)) { - // The pointer has been slid out from the previous key, we must call onRelease() to - // notify that the previous key has been released. - setReleasedKeyGraphics(oldKey); - callListenerOnRelease(oldKey, oldKey.mCode, true); - startSlidingKeyInput(oldKey); - mTimerProxy.cancelLongPressTimer(); - if (mIsAllowedSlidingKeyInput) { - onMoveToNewKey(key, x, y); - } else { - if (!mIsDetectingGesture) { - mKeyAlreadyProcessed = true; - } - } + } else { // newKey == null + if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, eventTime, newKey)) { + slideOutFromOldKey(oldKey, x, y); } } + mDrawingProxy.showSlidingKeyInputPreview(this); } public void onUpEvent(final int x, final int y, final long eventTime) { @@ -1030,22 +1119,17 @@ public final class PointerTracker implements PointerTrackerQueue.Element { printTouchEvent("onUpEvent :", x, y, eventTime); } - final PointerTrackerQueue queue = sPointerTrackerQueue; - if (queue != null) { - if (!sInGesture) { - if (mCurrentKey != null && mCurrentKey.isModifier()) { - // Before processing an up event of modifier key, all pointers already being - // tracked should be released. - queue.releaseAllPointersExcept(this, eventTime); - } else { - queue.releaseAllPointersOlderThan(this, eventTime); - } + if (!sInGesture) { + if (mCurrentKey != null && mCurrentKey.isModifier()) { + // Before processing an up event of modifier key, all pointers already being + // tracked should be released. + sPointerTrackerQueue.releaseAllPointersExcept(this, eventTime); + } else { + sPointerTrackerQueue.releaseAllPointersOlderThan(this, eventTime); } } - onUpEventInternal(eventTime); - if (queue != null) { - queue.remove(this); - } + onUpEventInternal(x, y, eventTime); + sPointerTrackerQueue.remove(this); } // Let this pointer tracker know that one of newer-than-this pointer trackers got an up event. @@ -1054,13 +1138,16 @@ public final class PointerTracker implements PointerTrackerQueue.Element { @Override public void onPhantomUpEvent(final long eventTime) { if (DEBUG_EVENT) { - printTouchEvent("onPhntEvent:", getLastX(), getLastY(), eventTime); + printTouchEvent("onPhntEvent:", mLastX, mLastY, eventTime); } - onUpEventInternal(eventTime); - mKeyAlreadyProcessed = true; + if (isShowingMoreKeysPanel()) { + return; + } + onUpEventInternal(mLastX, mLastY, eventTime); + cancelTracking(); } - private void onUpEventInternal(final long eventTime) { + private void onUpEventInternal(final int x, final int y, final long eventTime) { mTimerProxy.cancelKeyTimers(); resetSlidingKeyInput(); mIsDetectingGesture = false; @@ -1068,9 +1155,16 @@ public final class PointerTracker implements PointerTrackerQueue.Element { mCurrentKey = null; // Release the last pressed key. setReleasedKeyGraphics(currentKey); - if (mIsShowingMoreKeysPanel) { - mDrawingProxy.dismissMoreKeysPanel(); - mIsShowingMoreKeysPanel = false; + + if (isShowingMoreKeysPanel()) { + if (!mIsTrackingCanceled) { + final int translatedX = mMoreKeysPanel.translateX(x); + final int translatedY = mMoreKeysPanel.translateY(y); + mMoreKeysPanel.onUpEvent(translatedX, translatedY, mPointerId, eventTime); + } + mMoreKeysPanel.dismissMoreKeysPanel(); + mMoreKeysPanel = null; + return; } if (sInGesture) { @@ -1081,7 +1175,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { return; } - if (mKeyAlreadyProcessed) { + if (mIsTrackingCanceled) { return; } if (currentKey != null && !currentKey.isRepeatable()) { @@ -1089,19 +1183,24 @@ public final class PointerTracker implements PointerTrackerQueue.Element { } } - public void onShowMoreKeysPanel(final int x, final int y, final KeyEventHandler handler) { - onLongPressed(); - mIsShowingMoreKeysPanel = true; - onDownEvent(x, y, SystemClock.uptimeMillis(), handler); + public void onShowMoreKeysPanel(final int translatedX, final int translatedY, + final MoreKeysPanel panel) { + setReleasedKeyGraphics(mCurrentKey); + final long eventTime = SystemClock.uptimeMillis(); + mMoreKeysPanel = panel; + mMoreKeysPanel.onDownEvent(translatedX, translatedY, mPointerId, eventTime); + } + + @Override + public void cancelTracking() { + mIsTrackingCanceled = true; } public void onLongPressed() { - mKeyAlreadyProcessed = true; + resetSlidingKeyInput(); + cancelTracking(); setReleasedKeyGraphics(mCurrentKey); - final PointerTrackerQueue queue = sPointerTrackerQueue; - if (queue != null) { - queue.remove(this); - } + sPointerTrackerQueue.remove(this); } public void onCancelEvent(final int x, final int y, final long eventTime) { @@ -1109,11 +1208,11 @@ public final class PointerTracker implements PointerTrackerQueue.Element { printTouchEvent("onCancelEvt:", x, y, eventTime); } - final PointerTrackerQueue queue = sPointerTrackerQueue; - if (queue != null) { - queue.releaseAllPointersExcept(this, eventTime); - queue.remove(this); + if (sInGesture) { + cancelBatchInput(); } + sPointerTrackerQueue.cancelAllPointerTracker(); + sPointerTrackerQueue.releaseAllPointers(eventTime); onCancelEventInternal(); } @@ -1121,9 +1220,9 @@ public final class PointerTracker implements PointerTrackerQueue.Element { mTimerProxy.cancelKeyTimers(); setReleasedKeyGraphics(mCurrentKey); resetSlidingKeyInput(); - if (mIsShowingMoreKeysPanel) { - mDrawingProxy.dismissMoreKeysPanel(); - mIsShowingMoreKeysPanel = false; + if (isShowingMoreKeysPanel()) { + mMoreKeysPanel.dismissMoreKeysPanel(); + mMoreKeysPanel = null; } } @@ -1149,38 +1248,38 @@ public final class PointerTracker implements PointerTrackerQueue.Element { final Key curKey = mCurrentKey; if (newKey == curKey) { return false; - } else if (curKey != null) { - final int keyHysteresisDistanceSquared = mKeyDetector.getKeyHysteresisDistanceSquared( - mIsInSlidingKeyInputFromModifier); - final int distanceFromKeyEdgeSquared = curKey.squaredDistanceToEdge(x, y); - if (distanceFromKeyEdgeSquared >= keyHysteresisDistanceSquared) { - if (DEBUG_MODE) { - final float distanceToEdgeRatio = (float)Math.sqrt(distanceFromKeyEdgeSquared) - / mKeyboard.mMostCommonKeyWidth; - Log.d(TAG, String.format("[%d] isMajorEnoughMoveToBeOnNewKey:" - +" %.2f key width from key edge", - mPointerId, distanceToEdgeRatio)); - } - return true; + } + if (curKey == null /* && newKey != null */) { + return true; + } + // Here curKey points to the different key from newKey. + final int keyHysteresisDistanceSquared = mKeyDetector.getKeyHysteresisDistanceSquared( + mIsInSlidingKeyInputFromModifier); + final int distanceFromKeyEdgeSquared = curKey.squaredDistanceToEdge(x, y); + if (distanceFromKeyEdgeSquared >= keyHysteresisDistanceSquared) { + if (DEBUG_MODE) { + final float distanceToEdgeRatio = (float)Math.sqrt(distanceFromKeyEdgeSquared) + / mKeyboard.mMostCommonKeyWidth; + Log.d(TAG, String.format("[%d] isMajorEnoughMoveToBeOnNewKey:" + +" %.2f key width from key edge", mPointerId, distanceToEdgeRatio)); } - if (sNeedsProximateBogusDownMoveUpEventHack && !mIsAllowedSlidingKeyInput - && sTimeRecorder.isInFastTyping(eventTime) - && mBogusMoveEventDetector.hasTraveledLongDistance(x, y)) { - if (DEBUG_MODE) { - final float keyDiagonal = (float)Math.hypot( - mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight); - final float lengthFromDownRatio = - mBogusMoveEventDetector.mAccumulatedDistanceFromDownKey / keyDiagonal; - Log.d(TAG, String.format("[%d] isMajorEnoughMoveToBeOnNewKey:" - + " %.2f key diagonal from virtual down point", - mPointerId, lengthFromDownRatio)); - } - return true; + return true; + } + if (sNeedsProximateBogusDownMoveUpEventHack && !mIsAllowedSlidingKeyInput + && sTimeRecorder.isInFastTyping(eventTime) + && mBogusMoveEventDetector.hasTraveledLongDistance(x, y)) { + if (DEBUG_MODE) { + final float keyDiagonal = (float)Math.hypot( + mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight); + final float lengthFromDownRatio = + mBogusMoveEventDetector.mAccumulatedDistanceFromDownKey / keyDiagonal; + Log.d(TAG, String.format("[%d] isMajorEnoughMoveToBeOnNewKey:" + + " %.2f key diagonal from virtual down point", + mPointerId, lengthFromDownRatio)); } - return false; - } else { // curKey == null && newKey != null return true; } + return false; } private void startLongPressTimer(final Key key) { @@ -1205,6 +1304,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element { final Key key = mKeyDetector.detectHitKey(x, y); final String code = KeyDetector.printableCode(key); Log.d(TAG, String.format("[%d]%s%s %4d %4d %5d %s", mPointerId, - (mKeyAlreadyProcessed ? "-" : " "), title, x, y, eventTime, code)); + (mIsTrackingCanceled ? "-" : " "), title, x, y, eventTime, code)); } } diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java index 06a9e9252..b5ba98d85 100644 --- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java +++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java @@ -18,6 +18,7 @@ package com.android.inputmethod.keyboard; import android.graphics.Rect; import android.text.TextUtils; +import android.util.Log; import com.android.inputmethod.keyboard.internal.TouchPositionCorrection; import com.android.inputmethod.latin.Constants; @@ -26,11 +27,14 @@ import com.android.inputmethod.latin.JniUtils; import java.util.Arrays; public final class ProximityInfo { + private static final String TAG = ProximityInfo.class.getSimpleName(); + private static final boolean DEBUG = false; + /** MAX_PROXIMITY_CHARS_SIZE must be the same as MAX_PROXIMITY_CHARS_SIZE_INTERNAL * in defines.h */ public static final int MAX_PROXIMITY_CHARS_SIZE = 16; /** Number of key widths from current touch point to search for nearest keys. */ - private static float SEARCH_DISTANCE = 1.2f; + private static final float SEARCH_DISTANCE = 1.2f; private static final Key[] EMPTY_KEY_ARRAY = new Key[0]; private static final float DEFAULT_TOUCH_POSITION_CORRECTION_RADIUS = 0.15f; @@ -95,6 +99,7 @@ public final class ProximityInfo { JniUtils.loadNativeLibrary(); } + // TODO: Stop passing proximityCharsArray private native long setProximityInfoNative( String locale, int maxProximityCharsSize, int displayWidth, int displayHeight, int gridWidth, int gridHeight, @@ -105,22 +110,56 @@ public final class ProximityInfo { private native void releaseProximityInfoNative(long nativeProximityInfo); - private final long createNativeProximityInfo( - final TouchPositionCorrection touchPositionCorrection) { + private static boolean needsProximityInfo(final Key key) { + // Don't include special keys into ProximityInfo. + return key.mCode >= Constants.CODE_SPACE; + } + + private static int getProximityInfoKeysCount(final Key[] keys) { + int count = 0; + for (final Key key : keys) { + if (needsProximityInfo(key)) { + count++; + } + } + return count; + } + + private long createNativeProximityInfo(final TouchPositionCorrection touchPositionCorrection) { final Key[][] gridNeighborKeys = mGridNeighbors; - final int keyboardWidth = mKeyboardMinWidth; - final int keyboardHeight = mKeyboardHeight; - final Key[] keys = mKeys; final int[] proximityCharsArray = new int[mGridSize * MAX_PROXIMITY_CHARS_SIZE]; Arrays.fill(proximityCharsArray, Constants.NOT_A_CODE); for (int i = 0; i < mGridSize; ++i) { final int proximityCharsLength = gridNeighborKeys[i].length; + int infoIndex = i * MAX_PROXIMITY_CHARS_SIZE; for (int j = 0; j < proximityCharsLength; ++j) { - proximityCharsArray[i * MAX_PROXIMITY_CHARS_SIZE + j] = - gridNeighborKeys[i][j].mCode; + final Key neighborKey = gridNeighborKeys[i][j]; + // Excluding from proximityCharsArray + if (!needsProximityInfo(neighborKey)) { + continue; + } + proximityCharsArray[infoIndex] = neighborKey.mCode; + infoIndex++; } } - final int keyCount = keys.length; + if (DEBUG) { + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < mGridSize; i++) { + sb.setLength(0); + for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE; j++) { + final int code = proximityCharsArray[i * MAX_PROXIMITY_CHARS_SIZE + j]; + if (code == Constants.NOT_A_CODE) { + break; + } + if (sb.length() > 0) sb.append(" "); + sb.append(Constants.printableCode(code)); + } + Log.d(TAG, "proxmityChars["+i+"]: " + sb); + } + } + + final Key[] keys = mKeys; + final int keyCount = getProximityInfoKeysCount(keys); final int[] keyXCoordinates = new int[keyCount]; final int[] keyYCoordinates = new int[keyCount]; final int[] keyWidths = new int[keyCount]; @@ -130,46 +169,73 @@ public final class ProximityInfo { final float[] sweetSpotCenterYs; final float[] sweetSpotRadii; - for (int i = 0; i < keyCount; ++i) { - final Key key = keys[i]; - keyXCoordinates[i] = key.mX; - keyYCoordinates[i] = key.mY; - keyWidths[i] = key.mWidth; - keyHeights[i] = key.mHeight; - keyCharCodes[i] = key.mCode; + for (int infoIndex = 0, keyIndex = 0; keyIndex < keys.length; keyIndex++) { + final Key key = keys[keyIndex]; + // Excluding from key coordinate arrays + if (!needsProximityInfo(key)) { + continue; + } + keyXCoordinates[infoIndex] = key.mX; + keyYCoordinates[infoIndex] = key.mY; + keyWidths[infoIndex] = key.mWidth; + keyHeights[infoIndex] = key.mHeight; + keyCharCodes[infoIndex] = key.mCode; + infoIndex++; } if (touchPositionCorrection != null && touchPositionCorrection.isValid()) { + if (DEBUG) { + Log.d(TAG, "touchPositionCorrection: ON"); + } sweetSpotCenterXs = new float[keyCount]; sweetSpotCenterYs = new float[keyCount]; sweetSpotRadii = new float[keyCount]; + final int rows = touchPositionCorrection.getRows(); final float defaultRadius = DEFAULT_TOUCH_POSITION_CORRECTION_RADIUS * (float)Math.hypot(mMostCommonKeyWidth, mMostCommonKeyHeight); - for (int i = 0; i < keyCount; i++) { - final Key key = keys[i]; + for (int infoIndex = 0, keyIndex = 0; keyIndex < keys.length; keyIndex++) { + final Key key = keys[keyIndex]; + // Excluding from touch position correction arrays + if (!needsProximityInfo(key)) { + continue; + } final Rect hitBox = key.mHitBox; - sweetSpotCenterXs[i] = hitBox.exactCenterX(); - sweetSpotCenterYs[i] = hitBox.exactCenterY(); - sweetSpotRadii[i] = defaultRadius; + sweetSpotCenterXs[infoIndex] = hitBox.exactCenterX(); + sweetSpotCenterYs[infoIndex] = hitBox.exactCenterY(); + sweetSpotRadii[infoIndex] = defaultRadius; final int row = hitBox.top / mMostCommonKeyHeight; - if (row < touchPositionCorrection.getRows()) { + if (row < rows) { final int hitBoxWidth = hitBox.width(); final int hitBoxHeight = hitBox.height(); final float hitBoxDiagonal = (float)Math.hypot(hitBoxWidth, hitBoxHeight); - sweetSpotCenterXs[i] += touchPositionCorrection.getX(row) * hitBoxWidth; - sweetSpotCenterYs[i] += touchPositionCorrection.getY(row) * hitBoxHeight; - sweetSpotRadii[i] = touchPositionCorrection.getRadius(row) * hitBoxDiagonal; + sweetSpotCenterXs[infoIndex] += + touchPositionCorrection.getX(row) * hitBoxWidth; + sweetSpotCenterYs[infoIndex] += + touchPositionCorrection.getY(row) * hitBoxHeight; + sweetSpotRadii[infoIndex] = + touchPositionCorrection.getRadius(row) * hitBoxDiagonal; } + if (DEBUG) { + Log.d(TAG, String.format( + " [%2d] row=%d x/y/r=%7.2f/%7.2f/%5.2f %s code=%s", infoIndex, row, + sweetSpotCenterXs[infoIndex], sweetSpotCenterYs[infoIndex], + sweetSpotRadii[infoIndex], (row < rows ? "correct" : "default"), + Constants.printableCode(key.mCode))); + } + infoIndex++; } } else { sweetSpotCenterXs = sweetSpotCenterYs = sweetSpotRadii = null; + if (DEBUG) { + Log.d(TAG, "touchPositionCorrection: OFF"); + } } + // TODO: Stop passing proximityCharsArray return setProximityInfoNative(mLocaleStr, MAX_PROXIMITY_CHARS_SIZE, - keyboardWidth, keyboardHeight, mGridWidth, mGridHeight, mMostCommonKeyWidth, - proximityCharsArray, - keyCount, keyXCoordinates, keyYCoordinates, keyWidths, keyHeights, keyCharCodes, - sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii); + mKeyboardMinWidth, mKeyboardHeight, mGridWidth, mGridHeight, mMostCommonKeyWidth, + proximityCharsArray, keyCount, keyXCoordinates, keyYCoordinates, keyWidths, + keyHeights, keyCharCodes, sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii); } public long getNativeProximityInfo() { @@ -221,7 +287,7 @@ public final class ProximityInfo { return; } int index = 0; - if (primaryKeyCode > Keyboard.CODE_SPACE) { + if (primaryKeyCode > Constants.CODE_SPACE) { dest[index++] = primaryKeyCode; } final Key[] nearestKeys = getNearestKeys(x, y); @@ -230,7 +296,7 @@ public final class ProximityInfo { break; } final int code = key.mCode; - if (code <= Keyboard.CODE_SPACE) { + if (code <= Constants.CODE_SPACE) { break; } dest[index++] = code; diff --git a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java index 699aaeaef..3a57f673a 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java @@ -19,7 +19,6 @@ import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Rect; -import android.graphics.RectF; import android.os.SystemClock; import com.android.inputmethod.latin.Constants; @@ -118,98 +117,7 @@ final class GesturePreviewTrail { / params.mTrailLingerDuration, 0.0f); } - static final class WorkingSet { - // Input - // Previous point (P1) coordinates and trail radius. - public float p1x, p1y; - public float r1; - // Current point (P2) coordinates and trail radius. - public float p2x, p2y; - public float r2; - - // Output - // Closing point of arc at P1. - public float p1ax, p1ay; - // Opening point of arc at P1. - public float p1bx, p1by; - // Opening point of arc at P2. - public float p2ax, p2ay; - // Closing point of arc at P2. - public float p2bx, p2by; - // Start angle of the trail arcs. - public float aa; - // Sweep angle of the trail arc at P1. - public float a1; - public RectF arc1 = new RectF(); - // Sweep angle of the trail arc at P2. - public float a2; - public RectF arc2 = new RectF(); - } - - private static final float RIGHT_ANGLE = (float)(Math.PI / 2.0d); - private static final float RADIAN_TO_DEGREE = (float)(180.0d / Math.PI); - - private static boolean calculatePathPoints(final WorkingSet w) { - final float dx = w.p2x - w.p1x; - final float dy = w.p2y - w.p1y; - // Distance of the points. - final double l = Math.hypot(dx, dy); - if (Double.compare(0.0d, l) == 0) { - return false; - } - // Angle of the line p1-p2 - final float a = (float)Math.atan2(dy, dx); - // Difference of trail cap radius. - final float dr = w.r2 - w.r1; - // Variation of angle at trail cap. - final float ar = (float)Math.asin(dr / l); - // The start angle of trail cap arc at P1. - final float aa = a - (RIGHT_ANGLE + ar); - // The end angle of trail cap arc at P2. - final float ab = a + (RIGHT_ANGLE + ar); - final float cosa = (float)Math.cos(aa); - final float sina = (float)Math.sin(aa); - final float cosb = (float)Math.cos(ab); - final float sinb = (float)Math.sin(ab); - w.p1ax = w.p1x + w.r1 * cosa; - w.p1ay = w.p1y + w.r1 * sina; - w.p1bx = w.p1x + w.r1 * cosb; - w.p1by = w.p1y + w.r1 * sinb; - w.p2ax = w.p2x + w.r2 * cosa; - w.p2ay = w.p2y + w.r2 * sina; - w.p2bx = w.p2x + w.r2 * cosb; - w.p2by = w.p2y + w.r2 * sinb; - w.aa = aa * RADIAN_TO_DEGREE; - final float ar2degree = ar * 2.0f * RADIAN_TO_DEGREE; - w.a1 = -180.0f + ar2degree; - w.a2 = 180.0f + ar2degree; - w.arc1.set(w.p1x, w.p1y, w.p1x, w.p1y); - w.arc1.inset(-w.r1, -w.r1); - w.arc2.set(w.p2x, w.p2y, w.p2x, w.p2y); - w.arc2.inset(-w.r2, -w.r2); - return true; - } - - private static void createPath(final Path path, final WorkingSet w) { - path.rewind(); - // Trail cap at P1. - path.moveTo(w.p1x, w.p1y); - path.arcTo(w.arc1, w.aa, w.a1); - // Trail cap at P2. - path.moveTo(w.p2x, w.p2y); - path.arcTo(w.arc2, w.aa, w.a2); - // Two trapezoids connecting P1 and P2. - path.moveTo(w.p1ax, w.p1ay); - path.lineTo(w.p1x, w.p1y); - path.lineTo(w.p1bx, w.p1by); - path.lineTo(w.p2bx, w.p2by); - path.lineTo(w.p2x, w.p2y); - path.lineTo(w.p2ax, w.p2ay); - path.close(); - } - - private final WorkingSet mWorkingSet = new WorkingSet(); - private final Path mPath = new Path(); + private final RoundedLine mRoundedLine = new RoundedLine(); /** * Draw gesture preview trail @@ -243,36 +151,35 @@ final class GesturePreviewTrail { if (startIndex < trailSize) { paint.setColor(params.mTrailColor); paint.setStyle(Paint.Style.FILL); - final Path path = mPath; - final WorkingSet w = mWorkingSet; - w.p1x = getXCoordValue(xCoords[startIndex]); - w.p1y = yCoords[startIndex]; + final RoundedLine line = mRoundedLine; + int p1x = getXCoordValue(xCoords[startIndex]); + int p1y = yCoords[startIndex]; int lastTime = sinceDown - eventTimes[startIndex]; float maxWidth = getWidth(lastTime, params); - w.r1 = maxWidth / 2.0f; + float r1 = maxWidth / 2.0f; // Initialize bounds rectangle. - outBoundsRect.set((int)w.p1x, (int)w.p1y, (int)w.p1x, (int)w.p1y); - for (int i = startIndex + 1; i < trailSize - 1; i++) { + outBoundsRect.set(p1x, p1y, p1x, p1y); + for (int i = startIndex + 1; i < trailSize; i++) { final int elapsedTime = sinceDown - eventTimes[i]; - w.p2x = getXCoordValue(xCoords[i]); - w.p2y = yCoords[i]; + final int p2x = getXCoordValue(xCoords[i]); + final int p2y = yCoords[i]; + final float width = getWidth(elapsedTime, params); + final float r2 = width / 2.0f; // Draw trail line only when the current point isn't a down point. if (!isDownEventXCoord(xCoords[i])) { final int alpha = getAlpha(elapsedTime, params); paint.setAlpha(alpha); - final float width = getWidth(elapsedTime, params); - w.r2 = width / 2.0f; - if (calculatePathPoints(w)) { - createPath(path, w); + final Path path = line.makePath(p1x, p1y, r1, p2x, p2y, r2); + if (path != null) { canvas.drawPath(path, paint); - outBoundsRect.union((int)w.p2x, (int)w.p2y); + outBoundsRect.union(p2x, p2y); } // Take union for the bounds. maxWidth = Math.max(maxWidth, width); } - w.p1x = w.p2x; - w.p1y = w.p2y; - w.r1 = w.r2; + p1x = p2x; + p1y = p2y; + r1 = r2; lastTime = elapsedTime; } // Take care of trail line width. diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java index f8244dd5b..a43e94a75 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java @@ -27,6 +27,10 @@ public class GestureStroke { private static final boolean DEBUG = false; private static final boolean DEBUG_SPEED = false; + // The height of extra area above the keyboard to draw gesture trails. + // Proportional to the keyboard height. + public static final float EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO = 0.25f; + public static final int DEFAULT_CAPACITY = 128; private final int mPointerId; @@ -37,6 +41,8 @@ public class GestureStroke { private final GestureStrokeParams mParams; private int mKeyWidth; // pixel + private int mMinYCoordinate; // pixel + private int mMaxYCoordinate; // pixel // Static threshold for starting gesture detection private int mDetectFastMoveSpeedThreshold; // pixel /sec private int mDetectFastMoveTime; @@ -135,8 +141,10 @@ public class GestureStroke { mParams = params; } - public void setKeyboardGeometry(final int keyWidth) { + public void setKeyboardGeometry(final int keyWidth, final int keyboardHeight) { mKeyWidth = keyWidth; + mMinYCoordinate = -(int)(keyboardHeight * EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO); + mMaxYCoordinate = keyboardHeight - 1; // TODO: Find an appropriate base metric for these length. Maybe diagonal length of the key? mDetectFastMoveSpeedThreshold = (int)(keyWidth * mParams.mDetectFastMoveSpeedThreshold); mGestureDynamicDistanceThresholdFrom = @@ -167,7 +175,7 @@ public class GestureStroke { elapsedTimeAfterTyping, mAfterFastTyping ? " afterFastTyping" : "")); } final int elapsedTimeFromFirstDown = (int)(downTime - gestureFirstDownTime); - addPoint(x, y, elapsedTimeFromFirstDown, true /* isMajorEvent */); + addPointOnKeyboard(x, y, elapsedTimeFromFirstDown, true /* isMajorEvent */); } private int getGestureDynamicDistanceThreshold(final int deltaTime) { @@ -220,6 +228,17 @@ public class GestureStroke { return isStartOfAGesture; } + public void duplicateLastPointWith(final int time) { + final int lastIndex = mEventTimes.getLength() - 1; + if (lastIndex >= 0) { + final int x = mXCoordinates.get(lastIndex); + final int y = mYCoordinates.get(lastIndex); + // TODO: Have appendMajorPoint() + appendPoint(x, y, time); + updateIncrementalRecognitionSize(x, y, time); + } + } + protected void reset() { mIncrementalRecognitionSize = 0; mLastIncrementalBatchSize = 0; @@ -277,7 +296,17 @@ public class GestureStroke { return dist; } - public void addPoint(final int x, final int y, final int time, final boolean isMajorEvent) { + /** + * Add a touch event as a gesture point. Returns true if the touch event is on the valid + * gesture area. + * @param x the x-coordinate of the touch event + * @param y the y-coordinate of the touch event + * @param time the elapsed time in millisecond from the first gesture down + * @param isMajorEvent false if this is a historical move event + * @return true if the touch event is on the valid gesture area + */ + public boolean addPointOnKeyboard(final int x, final int y, final int time, + final boolean isMajorEvent) { final int size = mEventTimes.getLength(); if (size <= 0) { // Down event @@ -293,6 +322,7 @@ public class GestureStroke { updateIncrementalRecognitionSize(x, y, time); updateMajorEvent(x, y, time); } + return y >= mMinYCoordinate && y < mMaxYCoordinate; } private void updateIncrementalRecognitionSize(final int x, final int y, final int time) { diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java index 8192c9076..7ab7e9aad 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java @@ -56,8 +56,8 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { } @Override - public void setKeyboardGeometry(final int keyWidth) { - super.setKeyboardGeometry(keyWidth); + public void setKeyboardGeometry(final int keyWidth, final int keyboardHeight) { + super.setKeyboardGeometry(keyWidth, keyboardHeight); final float sampleLength = keyWidth * MIN_PREVIEW_SAMPLE_LENGTH_RATIO_TO_KEY_WIDTH; mMinPreviewSampleLengthSquare = (int)(sampleLength * sampleLength); } @@ -69,8 +69,9 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { } @Override - public void addPoint(final int x, final int y, final int time, final boolean isMajorEvent) { - super.addPoint(x, y, time, isMajorEvent); + public boolean addPointOnKeyboard(final int x, final int y, final int time, + final boolean isMajorEvent) { + final boolean onValidArea = super.addPointOnKeyboard(x, y, time, isMajorEvent); if (isMajorEvent || needsSampling(x, y)) { mPreviewEventTimes.add(time); mPreviewXCoordinates.add(x); @@ -78,6 +79,7 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { mLastX = x; mLastY = y; } + return onValidArea; } public void appendPreviewStroke(final ResizableIntArray eventTimes, diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java index 2caa5eb02..9f6e2f37e 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java @@ -16,12 +16,13 @@ package com.android.inputmethod.keyboard.internal; -import static com.android.inputmethod.keyboard.Keyboard.CODE_UNSPECIFIED; +import static com.android.inputmethod.latin.Constants.CODE_OUTPUT_TEXT; +import static com.android.inputmethod.latin.Constants.CODE_UNSPECIFIED; import android.text.TextUtils; -import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.latin.CollectionUtils; +import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.StringUtils; @@ -172,7 +173,7 @@ public final class KeySpecParser { if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) { throw new KeySpecParserError("Multiple " + LABEL_END + ": " + moreKeySpec); } - return parseCode(moreKeySpec.substring(end + 1), codesSet, Keyboard.CODE_UNSPECIFIED); + return parseCode(moreKeySpec.substring(end + 1), codesSet, CODE_UNSPECIFIED); } final String outputText = getOutputTextInternal(moreKeySpec); if (outputText != null) { @@ -181,14 +182,14 @@ public final class KeySpecParser { if (StringUtils.codePointCount(outputText) == 1) { return outputText.codePointAt(0); } - return Keyboard.CODE_OUTPUT_TEXT; + return CODE_OUTPUT_TEXT; } final String label = getLabel(moreKeySpec); // Code is automatically generated for one letter label. if (StringUtils.codePointCount(label) == 1) { return label.codePointAt(0); } - return Keyboard.CODE_OUTPUT_TEXT; + return CODE_OUTPUT_TEXT; } public static int parseCode(final String text, final KeyboardCodesSet codesSet, @@ -468,7 +469,7 @@ public final class KeySpecParser { public static int toUpperCaseOfCodeForLocale(final int code, final boolean needsToUpperCase, final Locale locale) { - if (!Keyboard.isLetterCode(code) || !needsToUpperCase) return code; + if (!Constants.isLetterCode(code) || !needsToUpperCase) return code; final String text = new String(new int[] { code } , 0, 1); final String casedText = KeySpecParser.toUpperCaseOfStringForLocale( text, needsToUpperCase, locale); diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java index b314a3795..da418f41a 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java @@ -27,6 +27,7 @@ import android.util.TypedValue; import android.util.Xml; import android.view.InflateException; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardId; @@ -177,7 +178,7 @@ public class KeyboardBuilder<KP extends KeyboardParams> { return this; } - // For test only + @UsedForTesting public void disableTouchPositionCorrectionDataForTest() { mParams.mTouchPositionCorrection.setEnabled(false); } @@ -621,6 +622,8 @@ public class KeyboardBuilder<KP extends KeyboardParams> { R.styleable.Keyboard_Case_clobberSettingsKey, id.mClobberSettingsKey); final boolean shortcutKeyEnabledMatched = matchBoolean(a, R.styleable.Keyboard_Case_shortcutKeyEnabled, id.mShortcutKeyEnabled); + final boolean shortcutKeyOnSymbolsMatched = matchBoolean(a, + R.styleable.Keyboard_Case_shortcutKeyOnSymbols, id.mShortcutKeyOnSymbols); final boolean hasShortcutKeyMatched = matchBoolean(a, R.styleable.Keyboard_Case_hasShortcutKey, id.mHasShortcutKey); final boolean languageSwitchKeyEnabledMatched = matchBoolean(a, @@ -639,12 +642,12 @@ public class KeyboardBuilder<KP extends KeyboardParams> { final boolean selected = keyboardLayoutSetElementMatched && modeMatched && navigateNextMatched && navigatePreviousMatched && passwordInputMatched && clobberSettingsKeyMatched && shortcutKeyEnabledMatched - && hasShortcutKeyMatched && languageSwitchKeyEnabledMatched - && isMultiLineMatched && imeActionMatched && localeCodeMatched - && languageCodeMatched && countryCodeMatched; + && shortcutKeyOnSymbolsMatched && hasShortcutKeyMatched + && languageSwitchKeyEnabledMatched && isMultiLineMatched && imeActionMatched + && localeCodeMatched && languageCodeMatched && countryCodeMatched; if (DEBUG) { - startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE, + startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE, textAttr(a.getString( R.styleable.Keyboard_Case_keyboardLayoutSetElement), "keyboardLayoutSetElement"), @@ -661,6 +664,8 @@ public class KeyboardBuilder<KP extends KeyboardParams> { "passwordInput"), booleanAttr(a, R.styleable.Keyboard_Case_shortcutKeyEnabled, "shortcutKeyEnabled"), + booleanAttr(a, R.styleable.Keyboard_Case_shortcutKeyOnSymbols, + "shortcutKeyOnSymbols"), booleanAttr(a, R.styleable.Keyboard_Case_hasShortcutKey, "hasShortcutKey"), booleanAttr(a, R.styleable.Keyboard_Case_languageSwitchKeyEnabled, diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java index 840d7133d..428e31ccd 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java @@ -16,8 +16,8 @@ package com.android.inputmethod.keyboard.internal; -import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.latin.CollectionUtils; +import com.android.inputmethod.latin.Constants; import java.util.HashMap; @@ -74,21 +74,21 @@ public final class KeyboardCodesSet { private static final int CODE_RIGHT_CURLY_BRACKET = '}'; private static final int[] DEFAULT = { - Keyboard.CODE_TAB, - Keyboard.CODE_ENTER, - Keyboard.CODE_SPACE, - Keyboard.CODE_SHIFT, - Keyboard.CODE_SWITCH_ALPHA_SYMBOL, - Keyboard.CODE_OUTPUT_TEXT, - Keyboard.CODE_DELETE, - Keyboard.CODE_SETTINGS, - Keyboard.CODE_SHORTCUT, - Keyboard.CODE_ACTION_ENTER, - Keyboard.CODE_ACTION_NEXT, - Keyboard.CODE_ACTION_PREVIOUS, - Keyboard.CODE_LANGUAGE_SWITCH, - Keyboard.CODE_RESEARCH, - Keyboard.CODE_UNSPECIFIED, + Constants.CODE_TAB, + Constants.CODE_ENTER, + Constants.CODE_SPACE, + Constants.CODE_SHIFT, + Constants.CODE_SWITCH_ALPHA_SYMBOL, + Constants.CODE_OUTPUT_TEXT, + Constants.CODE_DELETE, + Constants.CODE_SETTINGS, + Constants.CODE_SHORTCUT, + Constants.CODE_ACTION_ENTER, + Constants.CODE_ACTION_NEXT, + Constants.CODE_ACTION_PREVIOUS, + Constants.CODE_LANGUAGE_SWITCH, + Constants.CODE_RESEARCH, + Constants.CODE_UNSPECIFIED, CODE_LEFT_PARENTHESIS, CODE_RIGHT_PARENTHESIS, CODE_LESS_THAN_SIGN, diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java index e6fe50e02..642e1a18c 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java @@ -19,9 +19,9 @@ package com.android.inputmethod.keyboard.internal; import android.util.SparseIntArray; import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardId; import com.android.inputmethod.latin.CollectionUtils; +import com.android.inputmethod.latin.Constants; import java.util.ArrayList; import java.util.TreeSet; @@ -89,7 +89,7 @@ public class KeyboardParams { mKeys.add(key); updateHistogram(key); } - if (key.mCode == Keyboard.CODE_SHIFT) { + if (key.mCode == Constants.CODE_SHIFT) { mShiftKeys.add(key); } if (key.altCodeWhileTyping()) { diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java index 631e647e8..25a1c6a00 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java @@ -19,7 +19,6 @@ package com.android.inputmethod.keyboard.internal; import android.text.TextUtils; import android.util.Log; -import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.latin.Constants; /** @@ -316,12 +315,12 @@ public final class KeyboardState { public void onPressKey(int code, boolean isSinglePointer, int autoCaps) { if (DEBUG_EVENT) { - Log.d(TAG, "onPressKey: code=" + Keyboard.printableCode(code) + Log.d(TAG, "onPressKey: code=" + Constants.printableCode(code) + " single=" + isSinglePointer + " autoCaps=" + autoCaps + " " + this); } - if (code == Keyboard.CODE_SHIFT) { + if (code == Constants.CODE_SHIFT) { onPressShift(); - } else if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) { + } else if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) { onPressSymbol(); } else { mSwitchActions.cancelDoubleTapTimer(); @@ -349,12 +348,12 @@ public final class KeyboardState { public void onReleaseKey(int code, boolean withSliding) { if (DEBUG_EVENT) { - Log.d(TAG, "onReleaseKey: code=" + Keyboard.printableCode(code) + Log.d(TAG, "onReleaseKey: code=" + Constants.printableCode(code) + " sliding=" + withSliding + " " + this); } - if (code == Keyboard.CODE_SHIFT) { + if (code == Constants.CODE_SHIFT) { onReleaseShift(withSliding); - } else if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) { + } else if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) { onReleaseSymbol(withSliding); } } @@ -381,9 +380,9 @@ public final class KeyboardState { public void onLongPressTimeout(int code) { if (DEBUG_EVENT) { - Log.d(TAG, "onLongPressTimeout: code=" + Keyboard.printableCode(code) + " " + this); + Log.d(TAG, "onLongPressTimeout: code=" + Constants.printableCode(code) + " " + this); } - if (mIsAlphabetMode && code == Keyboard.CODE_SHIFT) { + if (mIsAlphabetMode && code == Constants.CODE_SHIFT) { mLongPressShiftLockFired = true; mSwitchActions.hapticAndAudioFeedback(code); } @@ -459,7 +458,7 @@ public final class KeyboardState { setShifted(MANUAL_SHIFT); mShiftKeyState.onPress(); } - mSwitchActions.startLongPressTimer(Keyboard.CODE_SHIFT); + mSwitchActions.startLongPressTimer(Constants.CODE_SHIFT); } } else { // In symbol mode, just toggle symbol and symbol more keyboard. @@ -544,7 +543,7 @@ public final class KeyboardState { } private static boolean isSpaceCharacter(int c) { - return c == Keyboard.CODE_SPACE || c == Keyboard.CODE_ENTER; + return c == Constants.CODE_SPACE || c == Constants.CODE_ENTER; } private boolean isLayoutSwitchBackCharacter(int c) { @@ -555,14 +554,14 @@ public final class KeyboardState { public void onCodeInput(int code, boolean isSinglePointer, int autoCaps) { if (DEBUG_EVENT) { - Log.d(TAG, "onCodeInput: code=" + Keyboard.printableCode(code) + Log.d(TAG, "onCodeInput: code=" + Constants.printableCode(code) + " single=" + isSinglePointer + " autoCaps=" + autoCaps + " " + this); } switch (mSwitchState) { case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL: - if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) { + if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) { // Detected only the mode change key has been pressed, and then released. if (mIsAlphabetMode) { mSwitchState = SWITCH_STATE_ALPHA; @@ -578,7 +577,7 @@ public final class KeyboardState { } break; case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE: - if (code == Keyboard.CODE_SHIFT) { + if (code == Constants.CODE_SHIFT) { // Detected only the shift key has been pressed on symbol layout, and then released. mSwitchState = SWITCH_STATE_SYMBOL_BEGIN; } else if (isSinglePointer) { @@ -589,8 +588,8 @@ public final class KeyboardState { } break; case SWITCH_STATE_SYMBOL_BEGIN: - if (!isSpaceCharacter(code) && (Keyboard.isLetterCode(code) - || code == Keyboard.CODE_OUTPUT_TEXT)) { + if (!isSpaceCharacter(code) && (Constants.isLetterCode(code) + || code == Constants.CODE_OUTPUT_TEXT)) { mSwitchState = SWITCH_STATE_SYMBOL; } // Switch back to alpha keyboard mode immediately if user types one of the switch back @@ -611,7 +610,7 @@ public final class KeyboardState { } // If the code is a letter, update keyboard shift state. - if (Keyboard.isLetterCode(code)) { + if (Constants.isLetterCode(code)) { updateAlphabetShiftState(autoCaps); } } @@ -639,7 +638,7 @@ public final class KeyboardState { @Override public String toString() { return "[keyboard=" + (mIsAlphabetMode ? mAlphabetShiftState.toString() - : (mIsSymbolShifted ? "SYMBOLS_SHIFTED" : "SYMBOLS")) + : (mIsSymbolShifted ? "SYMBOLS_SHIFTED" : "SYMBOLS")) + " shift=" + mShiftKeyState + " symbol=" + mSymbolKeyState + " switch=" + switchStateToString(mSwitchState) + "]"; diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java index 3bb272f8c..6fefb809b 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java @@ -19,6 +19,7 @@ package com.android.inputmethod.keyboard.internal; import android.content.Context; import android.content.res.Resources; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.R; @@ -64,7 +65,7 @@ public final class KeyboardTextsSet { loadStringResourcesInternal(context, RESOURCE_NAMES, R.string.english_ime_name); } - /* package for test */ + @UsedForTesting void loadStringResourcesInternal(Context context, final String[] resourceNames, int referenceId) { final Resources res = context.getResources(); @@ -96,9 +97,6 @@ public final class KeyboardTextsSet { "label_done_key", "label_previous_key", // Other labels. - "label_to_alpha_key", - "label_to_symbol_key", - "label_to_symbol_with_microphone_key", "label_pause_key", "label_wait_key", }; @@ -146,13 +144,13 @@ public final class KeyboardTextsSet { /* 39 */ "keylabel_for_south_slavic_row3_8", /* 40 */ "more_keys_for_cyrillic_ie", /* 41 */ "more_keys_for_cyrillic_i", - /* 42 */ "more_keys_for_single_quote", - /* 43 */ "more_keys_for_double_quote", - /* 44 */ "more_keys_for_tablet_double_quote", - /* 45 */ "more_keys_for_currency_dollar", - /* 46 */ "more_keys_for_currency_euro", - /* 47 */ "more_keys_for_currency_pound", - /* 48 */ "more_keys_for_currency_general", + /* 42 */ "label_to_alpha_key", + /* 43 */ "more_keys_for_single_quote", + /* 44 */ "more_keys_for_double_quote", + /* 45 */ "more_keys_for_tablet_double_quote", + /* 46 */ "more_keys_for_currency_dollar", + /* 47 */ "keylabel_for_currency_generic", + /* 48 */ "more_keys_for_currency_generic", /* 49 */ "more_keys_for_punctuation", /* 50 */ "more_keys_for_star", /* 51 */ "more_keys_for_bullet", @@ -173,66 +171,68 @@ public final class KeyboardTextsSet { /* 66 */ "keylabel_for_symbols_8", /* 67 */ "keylabel_for_symbols_9", /* 68 */ "keylabel_for_symbols_0", - /* 69 */ "additional_more_keys_for_symbols_1", - /* 70 */ "additional_more_keys_for_symbols_2", - /* 71 */ "additional_more_keys_for_symbols_3", - /* 72 */ "additional_more_keys_for_symbols_4", - /* 73 */ "additional_more_keys_for_symbols_5", - /* 74 */ "additional_more_keys_for_symbols_6", - /* 75 */ "additional_more_keys_for_symbols_7", - /* 76 */ "additional_more_keys_for_symbols_8", - /* 77 */ "additional_more_keys_for_symbols_9", - /* 78 */ "additional_more_keys_for_symbols_0", - /* 79 */ "more_keys_for_symbols_1", - /* 80 */ "more_keys_for_symbols_2", - /* 81 */ "more_keys_for_symbols_3", - /* 82 */ "more_keys_for_symbols_4", - /* 83 */ "more_keys_for_symbols_5", - /* 84 */ "more_keys_for_symbols_6", - /* 85 */ "more_keys_for_symbols_7", - /* 86 */ "more_keys_for_symbols_8", - /* 87 */ "more_keys_for_symbols_9", - /* 88 */ "more_keys_for_symbols_0", - /* 89 */ "keylabel_for_comma", - /* 90 */ "more_keys_for_comma", - /* 91 */ "keylabel_for_symbols_question", - /* 92 */ "keylabel_for_symbols_semicolon", - /* 93 */ "keylabel_for_symbols_percent", - /* 94 */ "more_keys_for_symbols_exclamation", - /* 95 */ "more_keys_for_symbols_question", - /* 96 */ "more_keys_for_symbols_semicolon", - /* 97 */ "more_keys_for_symbols_percent", - /* 98 */ "keylabel_for_tablet_comma", - /* 99 */ "keyhintlabel_for_tablet_comma", - /* 100 */ "more_keys_for_tablet_comma", - /* 101 */ "keyhintlabel_for_tablet_period", - /* 102 */ "more_keys_for_tablet_period", - /* 103 */ "keylabel_for_apostrophe", - /* 104 */ "keyhintlabel_for_apostrophe", - /* 105 */ "more_keys_for_apostrophe", - /* 106 */ "more_keys_for_q", - /* 107 */ "more_keys_for_x", - /* 108 */ "keylabel_for_q", - /* 109 */ "keylabel_for_w", - /* 110 */ "keylabel_for_y", - /* 111 */ "keylabel_for_x", - /* 112 */ "keylabel_for_spanish_row2_10", - /* 113 */ "more_keys_for_am_pm", - /* 114 */ "settings_as_more_key", - /* 115 */ "shortcut_as_more_key", - /* 116 */ "action_next_as_more_key", - /* 117 */ "action_previous_as_more_key", - /* 118 */ "label_to_more_symbol_key", - /* 119 */ "label_to_more_symbol_for_tablet_key", - /* 120 */ "label_tab_key", - /* 121 */ "label_to_phone_numeric_key", - /* 122 */ "label_to_phone_symbols_key", - /* 123 */ "label_time_am", - /* 124 */ "label_time_pm", - /* 125 */ "label_to_symbol_key_pcqwerty", - /* 126 */ "keylabel_for_popular_domain", - /* 127 */ "more_keys_for_popular_domain", - /* 128 */ "more_keys_for_smiley", + /* 69 */ "label_to_symbol_key", + /* 70 */ "label_to_symbol_with_microphone_key", + /* 71 */ "additional_more_keys_for_symbols_1", + /* 72 */ "additional_more_keys_for_symbols_2", + /* 73 */ "additional_more_keys_for_symbols_3", + /* 74 */ "additional_more_keys_for_symbols_4", + /* 75 */ "additional_more_keys_for_symbols_5", + /* 76 */ "additional_more_keys_for_symbols_6", + /* 77 */ "additional_more_keys_for_symbols_7", + /* 78 */ "additional_more_keys_for_symbols_8", + /* 79 */ "additional_more_keys_for_symbols_9", + /* 80 */ "additional_more_keys_for_symbols_0", + /* 81 */ "more_keys_for_symbols_1", + /* 82 */ "more_keys_for_symbols_2", + /* 83 */ "more_keys_for_symbols_3", + /* 84 */ "more_keys_for_symbols_4", + /* 85 */ "more_keys_for_symbols_5", + /* 86 */ "more_keys_for_symbols_6", + /* 87 */ "more_keys_for_symbols_7", + /* 88 */ "more_keys_for_symbols_8", + /* 89 */ "more_keys_for_symbols_9", + /* 90 */ "more_keys_for_symbols_0", + /* 91 */ "keylabel_for_comma", + /* 92 */ "more_keys_for_comma", + /* 93 */ "keylabel_for_symbols_question", + /* 94 */ "keylabel_for_symbols_semicolon", + /* 95 */ "keylabel_for_symbols_percent", + /* 96 */ "more_keys_for_symbols_exclamation", + /* 97 */ "more_keys_for_symbols_question", + /* 98 */ "more_keys_for_symbols_semicolon", + /* 99 */ "more_keys_for_symbols_percent", + /* 100 */ "keylabel_for_tablet_comma", + /* 101 */ "keyhintlabel_for_tablet_comma", + /* 102 */ "more_keys_for_tablet_comma", + /* 103 */ "keyhintlabel_for_tablet_period", + /* 104 */ "more_keys_for_tablet_period", + /* 105 */ "keylabel_for_apostrophe", + /* 106 */ "keyhintlabel_for_apostrophe", + /* 107 */ "more_keys_for_apostrophe", + /* 108 */ "more_keys_for_q", + /* 109 */ "more_keys_for_x", + /* 110 */ "keylabel_for_q", + /* 111 */ "keylabel_for_w", + /* 112 */ "keylabel_for_y", + /* 113 */ "keylabel_for_x", + /* 114 */ "keylabel_for_spanish_row2_10", + /* 115 */ "more_keys_for_am_pm", + /* 116 */ "settings_as_more_key", + /* 117 */ "shortcut_as_more_key", + /* 118 */ "action_next_as_more_key", + /* 119 */ "action_previous_as_more_key", + /* 120 */ "label_to_more_symbol_key", + /* 121 */ "label_to_more_symbol_for_tablet_key", + /* 122 */ "label_tab_key", + /* 123 */ "label_to_phone_numeric_key", + /* 124 */ "label_to_phone_symbols_key", + /* 125 */ "label_time_am", + /* 126 */ "label_time_pm", + /* 127 */ "label_to_symbol_key_pcqwerty", + /* 128 */ "keylabel_for_popular_domain", + /* 129 */ "more_keys_for_popular_domain", + /* 130 */ "more_keys_for_smiley", }; private static final String EMPTY = ""; @@ -245,22 +245,23 @@ public final class KeyboardTextsSet { EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, /* ~41 */ - /* 42 */ "!fixedColumnOrder!4,\u2018,\u2019,\u201A,\u201B", + // Label for "switch to alphabetic" key. + /* 42 */ "ABC", + /* 43 */ "!fixedColumnOrder!4,\u2018,\u2019,\u201A,\u201B", // TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK. // <string name="more_keys_for_double_quote">!fixedColumnOrder!6,“,”,„,‟,«,»</string> - /* 43 */ "!fixedColumnOrder!4,\u201C,\u201D,\u00AB,\u00BB", + /* 44 */ "!fixedColumnOrder!4,\u201C,\u201D,\u00AB,\u00BB", // TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK. // <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,“,”,„,‟,«,»,‘,’,‚,‛</string> - /* 44 */ "!fixedColumnOrder!4,\u201C,\u201D,\u00AB,\u00BB,\u2018,\u2019,\u201A,\u201B", + /* 45 */ "!fixedColumnOrder!4,\u201C,\u201D,\u00AB,\u00BB,\u2018,\u2019,\u201A,\u201B", // U+00A2: "¢" CENT SIGN // U+00A3: "£" POUND SIGN // U+20AC: "€" EURO SIGN // U+00A5: "¥" YEN SIGN // U+20B1: "₱" PESO SIGN - /* 45 */ "\u00A2,\u00A3,\u20AC,\u00A5,\u20B1", - /* 46 */ "\u00A2,\u00A3,$,\u00A5,\u20B1", - /* 47 */ "\u00A2,$,\u20AC,\u00A5,\u20B1", - /* 48 */ "\u00A2,$,\u20AC,\u00A3,\u00A5,\u20B1", + /* 46 */ "\u00A2,\u00A3,\u20AC,\u00A5,\u20B1", + /* 47 */ "$", + /* 48 */ "$,\u00A2,\u20AC,\u00A3,\u00A5,\u20B1", /* 49 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\\,,?,@,&,\\%,+,;,/,(,)", // U+2020: "†" DAGGER // U+2021: "‡" DOUBLE DAGGER @@ -307,89 +308,94 @@ public final class KeyboardTextsSet { /* 66 */ "8", /* 67 */ "9", /* 68 */ "0", - /* 69~ */ + // Label for "switch to symbols" key. + /* 69 */ "?123", + // Label for "switch to symbols with microphone" key. This string shouldn't include the "mic" + // part because it'll be appended by the code. + /* 70 */ "123", + /* 71~ */ EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, - /* ~78 */ + /* ~80 */ // U+00B9: "¹" SUPERSCRIPT ONE // U+00BD: "½" VULGAR FRACTION ONE HALF // U+2153: "⅓" VULGAR FRACTION ONE THIRD // U+00BC: "¼" VULGAR FRACTION ONE QUARTER // U+215B: "⅛" VULGAR FRACTION ONE EIGHTH - /* 79 */ "\u00B9,\u00BD,\u2153,\u00BC,\u215B", + /* 81 */ "\u00B9,\u00BD,\u2153,\u00BC,\u215B", // U+00B2: "²" SUPERSCRIPT TWO // U+2154: "⅔" VULGAR FRACTION TWO THIRDS - /* 80 */ "\u00B2,\u2154", + /* 82 */ "\u00B2,\u2154", // U+00B3: "³" SUPERSCRIPT THREE // U+00BE: "¾" VULGAR FRACTION THREE QUARTERS // U+215C: "⅜" VULGAR FRACTION THREE EIGHTHS - /* 81 */ "\u00B3,\u00BE,\u215C", + /* 83 */ "\u00B3,\u00BE,\u215C", // U+2074: "⁴" SUPERSCRIPT FOUR - /* 82 */ "\u2074", + /* 84 */ "\u2074", // U+215D: "⅝" VULGAR FRACTION FIVE EIGHTHS - /* 83 */ "\u215D", - /* 84 */ EMPTY, - // U+215E: "⅞" VULGAR FRACTION SEVEN EIGHTHS - /* 85 */ "\u215E", + /* 85 */ "\u215D", /* 86 */ EMPTY, - /* 87 */ EMPTY, + // U+215E: "⅞" VULGAR FRACTION SEVEN EIGHTHS + /* 87 */ "\u215E", + /* 88 */ EMPTY, + /* 89 */ EMPTY, // U+207F: "ⁿ" SUPERSCRIPT LATIN SMALL LETTER N // U+2205: "∅" EMPTY SET - /* 88 */ "\u207F,\u2205", - /* 89 */ ",", - /* 90 */ EMPTY, - /* 91 */ "?", - /* 92 */ ";", - /* 93 */ "%", + /* 90 */ "\u207F,\u2205", + /* 91 */ ",", + /* 92 */ EMPTY, + /* 93 */ "?", + /* 94 */ ";", + /* 95 */ "%", // U+00A1: "¡" INVERTED EXCLAMATION MARK - /* 94 */ "\u00A1", + /* 96 */ "\u00A1", // U+00BF: "¿" INVERTED QUESTION MARK - /* 95 */ "\u00BF", - /* 96 */ EMPTY, + /* 97 */ "\u00BF", + /* 98 */ EMPTY, // U+2030: "‰" PER MILLE SIGN - /* 97 */ "\u2030", - /* 98 */ ",", - /* 99 */ "!", - /* 100 */ "!", - /* 101 */ "?", - /* 102 */ "?", - /* 103 */ "\'", - /* 104 */ "\"", - /* 105 */ "\"", - /* 106 */ EMPTY, - /* 107 */ EMPTY, - /* 108 */ "q", - /* 109 */ "w", - /* 110 */ "y", - /* 111 */ "x", + /* 99 */ "\u2030", + /* 100 */ ",", + /* 101 */ "!", + /* 102 */ "!", + /* 103 */ "?", + /* 104 */ "?", + /* 105 */ "\'", + /* 106 */ "\"", + /* 107 */ "\"", + /* 108 */ EMPTY, + /* 109 */ EMPTY, + /* 110 */ "q", + /* 111 */ "w", + /* 112 */ "y", + /* 113 */ "x", // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE - /* 112 */ "\u00F1", - /* 113 */ "!fixedColumnOrder!2,!hasLabels!,!text/label_time_am,!text/label_time_pm", - /* 114 */ "!icon/settings_key|!code/key_settings", - /* 115 */ "!icon/shortcut_key|!code/key_shortcut", - /* 116 */ "!hasLabels!,!text/label_next_key|!code/key_action_next", - /* 117 */ "!hasLabels!,!text/label_previous_key|!code/key_action_previous", + /* 114 */ "\u00F1", + /* 115 */ "!fixedColumnOrder!2,!hasLabels!,!text/label_time_am,!text/label_time_pm", + /* 116 */ "!icon/settings_key|!code/key_settings", + /* 117 */ "!icon/shortcut_key|!code/key_shortcut", + /* 118 */ "!hasLabels!,!text/label_next_key|!code/key_action_next", + /* 119 */ "!hasLabels!,!text/label_previous_key|!code/key_action_previous", // Label for "switch to more symbol" modifier key. Must be short to fit on key! - /* 118 */ "= \\ <", + /* 120 */ "= \\ <", // Label for "switch to more symbol" modifier key on tablets. Must be short to fit on key! - /* 119 */ "~ \\ {", + /* 121 */ "~ \\ {", // Label for "Tab" key. Must be short to fit on key! - /* 120 */ "Tab", + /* 122 */ "Tab", // Label for "switch to phone numeric" key. Must be short to fit on key! - /* 121 */ "123", + /* 123 */ "123", // Label for "switch to phone symbols" key. Must be short to fit on key! // U+FF0A: "*" FULLWIDTH ASTERISK // U+FF03: "#" FULLWIDTH NUMBER SIGN - /* 122 */ "\uFF0A\uFF03", + /* 124 */ "\uFF0A\uFF03", // Key label for "ante meridiem" - /* 123 */ "AM", + /* 125 */ "AM", // Key label for "post meridiem" - /* 124 */ "PM", + /* 126 */ "PM", // Label for "switch to symbols" key on PC QWERTY layout - /* 125 */ "Sym", - /* 126 */ ".com", + /* 127 */ "Sym", + /* 128 */ ".com", // popular web domains for the locale - most popular, displayed on the keyboard - /* 127 */ "!hasLabels!,.net,.org,.gov,.edu", - /* 128 */ "!fixedColumnOrder!5,!hasLabels!,=-O|=-O ,:-P|:-P ,;-)|;-) ,:-(|:-( ,:-)|:-) ,:-!|:-! ,:-$|:-$ ,B-)|B-) ,:O|:O ,:-*|:-* ,:-D|:-D ,:\'(|:\'( ,:-\\\\|:-\\\\ ,O:-)|O:-) ,:-[|:-[ ", + /* 129 */ "!hasLabels!,.net,.org,.gov,.edu", + /* 130 */ "!fixedColumnOrder!5,!hasLabels!,=-O|=-O ,:-P|:-P ,;-)|;-) ,:-(|:-( ,:-)|:-) ,:-!|:-! ,:-$|:-$ ,B-)|B-) ,:O|:O ,:-*|:-* ,:-D|:-D ,:\'(|:\'( ,:-\\\\|:-\\\\ ,O:-)|O:-) ,:-[|:-[ ", }; /* Language af: Afrikaans */ @@ -450,17 +456,30 @@ public final class KeyboardTextsSet { /* 0~ */ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, - /* ~42 */ + null, null, null, null, null, null, null, null, null, null, null, null, + /* ~41 */ + // Label for "switch to alphabetic" key. + // U+0623: "ا" ARABIC LETTER ALEF + // U+200C: ZERO WIDTH NON-JOINER + // U+0628: "ب" ARABIC LETTER BEH + // U+062C: "پ" ARABIC LETTER PEH + /* 42 */ "\u0623\u200C\u0628\u200C\u062C", + /* 43 */ null, // TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK // <string name="more_keys_for_double_quote">“,”,„,‟,«|»,»|«</string> - /* 43 */ "!fixedColumnOrder!4,\u201C,\u201D,\u00AB|\u00BB,\u00BB|\u00AB", + /* 44 */ "!fixedColumnOrder!4,\u201C,\u201D,\u00AB|\u00BB,\u00BB|\u00AB", // TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK // <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,“,”,„,‟,«|»,»|«;,‘,’,‚,‛</string> - /* 44 */ "!fixedColumnOrder!4,\u201C,\u201D,\u00AB|\u00BB,\u00BB|\u00AB,\u2018,\u2019,\u201A,\u201B", - /* 45~ */ - null, null, null, null, - /* ~48 */ + /* 45 */ "!fixedColumnOrder!4,\u201C,\u201D,\u00AB|\u00BB,\u00BB|\u00AB,\u2018,\u2019,\u201A,\u201B", + // U+00A2: "¢" CENT SIGN + // U+00A3: "£" POUND SIGN + // U+20AC: "€" EURO SIGN + // U+00A5: "¥" YEN SIGN + // U+20B1: "₱" PESO SIGN + // U+FDFC: "﷼" RIAL SIGN + /* 46 */ "\u00A2,\u00A3,\u20AC,\u00A5,\u20B1,\uFDFC", + /* 47 */ null, + /* 48 */ null, // U+061F: "؟" ARABIC QUESTION MARK // U+060C: "،" ARABIC COMMA // U+061B: "؛" ARABIC SEMICOLON @@ -531,42 +550,48 @@ public final class KeyboardTextsSet { /* 67 */ "\u0669", // U+0660: "٠" ARABIC-INDIC DIGIT ZERO /* 68 */ "\u0660", - /* 69 */ "1", - /* 70 */ "2", - /* 71 */ "3", - /* 72 */ "4", - /* 73 */ "5", - /* 74 */ "6", - /* 75 */ "7", - /* 76 */ "8", - /* 77 */ "9", + // Label for "switch to symbols" key. + // U+061F: "؟" ARABIC QUESTION MARK + /* 69 */ "\u0663\u0662\u0661\u061F", + // Label for "switch to symbols with microphone" key. This string shouldn't include the "mic" + // part because it'll be appended by the code. + /* 70 */ "\u0663\u0662\u0661", + /* 71 */ "1", + /* 72 */ "2", + /* 73 */ "3", + /* 74 */ "4", + /* 75 */ "5", + /* 76 */ "6", + /* 77 */ "7", + /* 78 */ "8", + /* 79 */ "9", // U+066B: "٫" ARABIC DECIMAL SEPARATOR // U+066C: "٬" ARABIC THOUSANDS SEPARATOR - /* 78 */ "0,\u066B,\u066C", - /* 79~ */ + /* 80 */ "0,\u066B,\u066C", + /* 81~ */ null, null, null, null, null, null, null, null, null, null, - /* ~88 */ + /* ~90 */ // U+060C: "،" ARABIC COMMA - /* 89 */ "\u060C", - /* 90 */ "\\,", - /* 91 */ "\u061F", - /* 92 */ "\u061B", + /* 91 */ "\u060C", + /* 92 */ "\\,", + /* 93 */ "\u061F", + /* 94 */ "\u061B", // U+066A: "٪" ARABIC PERCENT SIGN - /* 93 */ "\u066A", - /* 94 */ null, - /* 95 */ "?", - /* 96 */ ";", + /* 95 */ "\u066A", + /* 96 */ null, + /* 97 */ "?", + /* 98 */ ";", // U+2030: "‰" PER MILLE SIGN - /* 97 */ "\\%,\u2030", - /* 98~ */ + /* 99 */ "\\%,\u2030", + /* 100~ */ null, null, null, null, null, - /* ~102 */ + /* ~104 */ // U+060C: "،" ARABIC COMMA // U+061B: "؛" ARABIC SEMICOLON // U+061F: "؟" ARABIC QUESTION MARK - /* 103 */ "\u060C", - /* 104 */ "\u061F", - /* 105 */ "\u061F,\u061B,!,:,-,/,\',\"", + /* 105 */ "\u060C", + /* 106 */ "\u061F", + /* 107 */ "\u061F,\u061B,!,:,-,/,\',\"", }; /* Language be: Belarusian */ @@ -595,6 +620,26 @@ public final class KeyboardTextsSet { /* ~39 */ // U+0451: "ё" CYRILLIC SMALL LETTER IO /* 40 */ "\u0451", + /* 41 */ null, + // Label for "switch to alphabetic" key. + // U+0410: "А" CYRILLIC CAPITAL LETTER A + // U+0411: "Б" CYRILLIC CAPITAL LETTER BE + // U+0412: "В" CYRILLIC CAPITAL LETTER VE + /* 42 */ "\u0410\u0411\u0412", + }; + + /* Language bg: Bulgarian */ + private static final String[] LANGUAGE_bg = { + /* 0~ */ + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, null, + /* ~41 */ + // Label for "switch to alphabetic" key. + // U+0410: "А" CYRILLIC CAPITAL LETTER A + // U+0411: "Б" CYRILLIC CAPITAL LETTER BE + // U+0412: "В" CYRILLIC CAPITAL LETTER VE + /* 42 */ "\u0410\u0411\u0412", }; /* Language ca: Catalan */ @@ -830,6 +875,20 @@ public final class KeyboardTextsSet { /* 6 */ "\u00F1,\u0144", }; + /* Language el: Greek */ + private static final String[] LANGUAGE_el = { + /* 0~ */ + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, null, + /* ~41 */ + // Label for "switch to alphabetic" key. + // U+0391: "Α" GREEK CAPITAL LETTER ALPHA + // U+0392: "Β" GREEK CAPITAL LETTER BETA + // U+0393: "Γ" GREEK CAPITAL LETTER GAMMA + /* 42 */ "\u0391\u0392\u0393", + }; + /* Language en: English */ private static final String[] LANGUAGE_en = { // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE @@ -998,20 +1057,20 @@ public final class KeyboardTextsSet { null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, - /* ~105 */ - /* 106 */ "q", - /* 107 */ "x", + null, null, null, null, null, null, null, null, null, null, null, null, null, + /* ~107 */ + /* 108 */ "q", + /* 109 */ "x", // U+015D: "ŝ" LATIN SMALL LETTER S WITH CIRCUMFLEX - /* 108 */ "\u015D", + /* 110 */ "\u015D", // U+011D: "ĝ" LATIN SMALL LETTER G WITH CIRCUMFLEX - /* 109 */ "\u011D", + /* 111 */ "\u011D", // U+016D: "ŭ" LATIN SMALL LETTER U WITH BREVE - /* 110 */ "\u016D", + /* 112 */ "\u016D", // U+0109: "ĉ" LATIN SMALL LETTER C WITH CIRCUMFLEX - /* 111 */ "\u0109", + /* 113 */ "\u0109", // U+0135: "ĵ" LATIN SMALL LETTER J WITH CIRCUMFLEX - /* 112 */ "\u0135", + /* 114 */ "\u0135", }; /* Language es: Spanish */ @@ -1078,13 +1137,16 @@ public final class KeyboardTextsSet { null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, - /* ~99 */ + null, null, null, null, null, null, null, + /* ~101 */ // U+00A1: "¡" INVERTED EXCLAMATION MARK - /* 100 */ "!,\u00A1", - /* 101 */ null, + /* 102 */ "!,\u00A1", + /* 103 */ null, // U+00BF: "¿" INVERTED QUESTION MARK - /* 102 */ "?,\u00BF", + /* 104 */ "?,\u00BF", + /* 105 */ "\"", + /* 106 */ "\'", + /* 107 */ "\'", }; /* Language et: Estonian */ @@ -1192,17 +1254,31 @@ public final class KeyboardTextsSet { /* 0~ */ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, - /* ~42 */ + null, null, null, null, null, null, null, null, null, null, null, null, + /* ~41 */ + // Label for "switch to alphabetic" key. + // U+0627: "ا" ARABIC LETTER ALEF + // U+200C: ZERO WIDTH NON-JOINER + // U+0628: "ب" ARABIC LETTER BEH + // U+067E: "پ" ARABIC LETTER PEH + /* 42 */ "\u0627\u200C\u0628\u200C\u067E", + /* 43 */ null, // TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK // <string name="more_keys_for_double_quote">“,”,„,‟,«|»,»|«</string> - /* 43 */ "!fixedColumnOrder!4,\u201C,\u201D,\",\'", + /* 44 */ "!fixedColumnOrder!4,\u201C,\u201D,\",\'", // TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK // <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,“,”,„,‟,«|»,»|«;,‘,’,‚,‛</string> - /* 44 */ "!fixedColumnOrder!4,\u201C,\u201D,\u00AB|\u00BB,\u00BB|\u00AB,\u2018,\u2019,\u201A,\u201B", - /* 45~ */ - null, null, null, null, - /* ~48 */ + /* 45 */ "!fixedColumnOrder!4,\u201C,\u201D,\u00AB|\u00BB,\u00BB|\u00AB,\u2018,\u2019,\u201A,\u201B", + /* 46 */ null, + // U+FDFC: "﷼" RIAL SIGN + // U+060B: "؋" AFGHANI SIGN + // U+00A2: "¢" CENT SIGN + // U+00A3: "£" POUND SIGN + // U+20AC: "€" EURO SIGN + // U+00A5: "¥" YEN SIGN + // U+20B1: "₱" PESO SIGN + /* 47 */ "\uFDFC", + /* 48 */ "$,\u00A2,\u20AC,\u00A3,\u00A5,\u20B1,\u060B", // U+061F: "؟" ARABIC QUESTION MARK // U+060C: "،" ARABIC COMMA // U+061B: "؛" ARABIC SEMICOLON @@ -1273,46 +1349,52 @@ public final class KeyboardTextsSet { /* 67 */ "\u06F9", // U+06F0: "۰" EXTENDED ARABIC-INDIC DIGIT ZERO /* 68 */ "\u06F0", - /* 69 */ "1", - /* 70 */ "2", - /* 71 */ "3", - /* 72 */ "4", - /* 73 */ "5", - /* 74 */ "6", - /* 75 */ "7", - /* 76 */ "8", - /* 77 */ "9", + // Label for "switch to symbols" key. + // U+061F: "؟" ARABIC QUESTION MARK + /* 69 */ "\u06F3\u06F2\u06F1\u061F", + // Label for "switch to symbols with microphone" key. This string shouldn't include the "mic" + // part because it'll be appended by the code. + /* 70 */ "\u06F3\u06F2\u06F1", + /* 71 */ "1", + /* 72 */ "2", + /* 73 */ "3", + /* 74 */ "4", + /* 75 */ "5", + /* 76 */ "6", + /* 77 */ "7", + /* 78 */ "8", + /* 79 */ "9", // U+066B: "٫" ARABIC DECIMAL SEPARATOR // U+066C: "٬" ARABIC THOUSANDS SEPARATOR - /* 78 */ "0,\u066B,\u066C", - /* 79~ */ + /* 80 */ "0,\u066B,\u066C", + /* 81~ */ null, null, null, null, null, null, null, null, null, null, - /* ~88 */ + /* ~90 */ // U+060C: "،" ARABIC COMMA - /* 89 */ "\u060C", - /* 90 */ "\\,", - /* 91 */ "\u061F", - /* 92 */ "\u061B", + /* 91 */ "\u060C", + /* 92 */ "\\,", + /* 93 */ "\u061F", + /* 94 */ "\u061B", // U+066A: "٪" ARABIC PERCENT SIGN - /* 93 */ "\u066A", - /* 94 */ null, - /* 95 */ "?", - /* 96 */ ";", + /* 95 */ "\u066A", + /* 96 */ null, + /* 97 */ "?", + /* 98 */ ";", // U+2030: "‰" PER MILLE SIGN - /* 97 */ "\\%,\u2030", + /* 99 */ "\\%,\u2030", // U+060C: "،" ARABIC COMMA // U+061B: "؛" ARABIC SEMICOLON // U+061F: "؟" ARABIC QUESTION MARK // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK - /* 98 */ "\u060C", - /* 99 */ "!", - /* 100 */ "!,\\,", - /* 101 */ "\u061F", - /* 102 */ "\u061F,?", - /* 103 */ "\u060C", - /* 104 */ "\u061F", - /* 105 */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,/,\u00AB|\u00BB,\u00BB|\u00AB", + /* 100 */ "\u060C", + /* 101 */ "!", + /* 102 */ "!,\\,", + /* 103 */ "\u061F", + /* 104 */ "\u061F,?", + /* 105 */ "\u060C", + /* 106 */ "\u061F", + /* 107 */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,/,\u00AB|\u00BB,\u00BB|\u00AB", }; /* Language fi: Finnish */ @@ -1420,8 +1502,20 @@ public final class KeyboardTextsSet { /* 0~ */ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, null, + /* ~41 */ + // Label for "switch to alphabetic" key. + // U+0915: "क" DEVANAGARI LETTER KA + // U+0916: "ख" DEVANAGARI LETTER KHA + // U+0917: "ग" DEVANAGARI LETTER GA + /* 42 */ "\u0915\u0916\u0917", + /* 43~ */ + null, null, null, null, + /* ~46 */ + // U+20B9: "₹" INDIAN RUPEE SIGN + /* 47 */ "\u20B9", + /* 48~ */ + null, null, null, null, null, null, null, null, null, null, null, /* ~58 */ // U+0967: "१" DEVANAGARI DIGIT ONE /* 59 */ "\u0967", @@ -1443,16 +1537,21 @@ public final class KeyboardTextsSet { /* 67 */ "\u096F", // U+0966: "०" DEVANAGARI DIGIT ZERO /* 68 */ "\u0966", - /* 69 */ "1", - /* 70 */ "2", - /* 71 */ "3", - /* 72 */ "4", - /* 73 */ "5", - /* 74 */ "6", - /* 75 */ "7", - /* 76 */ "8", - /* 77 */ "9", - /* 78 */ "0", + // Label for "switch to symbols" key. + /* 69 */ "?\u0967\u0968\u0969", + // Label for "switch to symbols with microphone" key. This string shouldn't include the "mic" + // part because it'll be appended by the code. + /* 70 */ "\u0967\u0968\u0969", + /* 71 */ "1", + /* 72 */ "2", + /* 73 */ "3", + /* 74 */ "4", + /* 75 */ "5", + /* 76 */ "6", + /* 77 */ "7", + /* 78 */ "8", + /* 79 */ "9", + /* 80 */ "0", }; /* Language hr: Croatian */ @@ -1640,17 +1739,25 @@ public final class KeyboardTextsSet { /* 0~ */ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, - /* ~42 */ + null, null, null, null, null, null, null, null, null, null, null, null, + /* ~41 */ + // Label for "switch to alphabetic" key. + // U+05D0: "א" HEBREW LETTER ALEF + // U+05D1: "ב" HEBREW LETTER BET + // U+05D2: "ג" HEBREW LETTER GIMEL + /* 42 */ "\u05D0\u05D1\u05D2", + /* 43 */ null, // TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK // <string name="more_keys_for_double_quote">“,”,„,‟,«|»,»|«</string> - /* 43 */ "!fixedColumnOrder!4,\u201C,\u201D,\u00AB|\u00BB,\u00BB|\u00AB", + /* 44 */ "!fixedColumnOrder!4,\u201C,\u201D,\u00AB|\u00BB,\u00BB|\u00AB", // TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK // <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,“,”,„,‟,«|»,»|«;,‘,’,‚,‛</string> - /* 44 */ "!fixedColumnOrder!4,\u201C,\u201D,\u00AB|\u00BB,\u00BB|\u00AB,\u2018,\u2019,\u201A,\u201B", - /* 45~ */ - null, null, null, null, null, - /* ~49 */ + /* 45 */ "!fixedColumnOrder!4,\u201C,\u201D,\u00AB|\u00BB,\u00BB|\u00AB,\u2018,\u2019,\u201A,\u201B", + /* 46 */ null, + // U+20AA: "₪" NEW SHEQEL SIGN + /* 47 */ "\u20AA", + /* 48 */ null, + /* 49 */ null, // U+2605: "★" BLACK STAR /* 50 */ "\u2605", /* 51 */ null, @@ -1680,6 +1787,20 @@ public final class KeyboardTextsSet { /* 56 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB", }; + /* Language ka: Georgian */ + private static final String[] LANGUAGE_ka = { + /* 0~ */ + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, null, + /* ~41 */ + // Label for "switch to alphabetic" key. + // U+10D0: "ა" GEORGIAN LETTER AN + // U+10D1: "ბ" GEORGIAN LETTER BAN + // U+10D2: "გ" GEORGIAN LETTER GAN + /* 42 */ "\u10D0\u10D1\u10D2", + }; + /* Language ky: Kirghiz */ private static final String[] LANGUAGE_ky = { /* 0~ */ @@ -1711,6 +1832,12 @@ public final class KeyboardTextsSet { /* ~39 */ // U+0451: "ё" CYRILLIC SMALL LETTER IO /* 40 */ "\u0451", + /* 41 */ null, + // Label for "switch to alphabetic" key. + // U+0410: "А" CYRILLIC CAPITAL LETTER A + // U+0411: "Б" CYRILLIC CAPITAL LETTER BE + // U+0412: "В" CYRILLIC CAPITAL LETTER VE + /* 42 */ "\u0410\u0411\u0412", }; /* Language lt: Lithuanian */ @@ -1911,7 +2038,12 @@ public final class KeyboardTextsSet { /* 40 */ "\u0450", // U+045D: "ѝ" CYRILLIC SMALL LETTER I WITH GRAVE /* 41 */ "\u045D", - /* 42 */ null, + // Label for "switch to alphabetic" key. + // U+0410: "А" CYRILLIC CAPITAL LETTER A + // U+0411: "Б" CYRILLIC CAPITAL LETTER BE + // U+0412: "В" CYRILLIC CAPITAL LETTER VE + /* 42 */ "\u0410\u0411\u0412", + /* 43 */ null, // U+2018: "‘" LEFT SINGLE QUOTATION MARK // U+2019: "’" RIGHT SINGLE QUOTATION MARK // U+201A: "‚" SINGLE LOW-9 QUOTATION MARK @@ -1922,10 +2054,29 @@ public final class KeyboardTextsSet { // U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK // TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK. // <string name="more_keys_for_double_quote">!fixedColumnOrder!6,„,“,”,‟,«,»</string> - /* 43 */ "!fixedColumnOrder!5,\u201E,\u201C,\u201D,\u00AB,\u00BB", + /* 44 */ "!fixedColumnOrder!5,\u201E,\u201C,\u201D,\u00AB,\u00BB", // TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK. // <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,“,”,„,‟,«,»,‘,’,‚,‛</string> - /* 44 */ "!fixedColumnOrder!5,\u201E,\u201C,\u201D,\u00AB,\u00BB,\u2018,\u2019,\u201A,\u201B", + /* 45 */ "!fixedColumnOrder!5,\u201E,\u201C,\u201D,\u00AB,\u00BB,\u2018,\u2019,\u201A,\u201B", + }; + + /* Language mn: Mongolian */ + private static final String[] LANGUAGE_mn = { + /* 0~ */ + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, null, + /* ~41 */ + // Label for "switch to alphabetic" key. + // U+0410: "А" CYRILLIC CAPITAL LETTER A + // U+0411: "Б" CYRILLIC CAPITAL LETTER BE + // U+0412: "В" CYRILLIC CAPITAL LETTER VE + /* 42 */ "\u0410\u0411\u0412", + /* 43~ */ + null, null, null, null, + /* ~46 */ + // U+20AE: "₮" TUGRIK SIGN + /* 47 */ "\u20AE", }; /* Language nb: Norwegian Bokmål */ @@ -2205,6 +2356,12 @@ public final class KeyboardTextsSet { /* ~39 */ // U+0451: "ё" CYRILLIC SMALL LETTER IO /* 40 */ "\u0451", + /* 41 */ null, + // Label for "switch to alphabetic" key. + // U+0410: "А" CYRILLIC CAPITAL LETTER A + // U+0411: "Б" CYRILLIC CAPITAL LETTER BE + // U+0412: "В" CYRILLIC CAPITAL LETTER VE + /* 42 */ "\u0410\u0411\u0412", }; /* Language sk: Slovak */ @@ -2354,8 +2511,13 @@ public final class KeyboardTextsSet { /* 40 */ "\u0450", // U+045D: "ѝ" CYRILLIC SMALL LETTER I WITH GRAVE /* 41 */ "\u045D", - /* 42 */ null, // END: More keys definitions for Serbian (Cyrillic) + // Label for "switch to alphabetic" key. + // U+0410: "А" CYRILLIC CAPITAL LETTER A + // U+0411: "Б" CYRILLIC CAPITAL LETTER BE + // U+0412: "В" CYRILLIC CAPITAL LETTER VE + /* 42 */ "\u0410\u0411\u0412", + /* 43 */ null, // U+2018: "‘" LEFT SINGLE QUOTATION MARK // U+2019: "’" RIGHT SINGLE QUOTATION MARK // U+201A: "‚" SINGLE LOW-9 QUOTATION MARK @@ -2366,10 +2528,10 @@ public final class KeyboardTextsSet { // U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK // TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK. // <string name="more_keys_for_double_quote">!fixedColumnOrder!6,„,“,”,‟,«,»</string> - /* 43 */ "!fixedColumnOrder!5,\u201E,\u201C,\u201D,\u00AB,\u00BB", + /* 44 */ "!fixedColumnOrder!5,\u201E,\u201C,\u201D,\u00AB,\u00BB", // TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK. // <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,“,”,„,‟,«,»,‘,’,‚,‛</string> - /* 44 */ "!fixedColumnOrder!5,\u201E,\u201C,\u201D,\u00AB,\u00BB,\u2018,\u2019,\u201A,\u201B", + /* 45 */ "!fixedColumnOrder!5,\u201E,\u201C,\u201D,\u00AB,\u00BB,\u2018,\u2019,\u201A,\u201B", }; /* Language sv: Swedish */ @@ -2465,6 +2627,25 @@ public final class KeyboardTextsSet { /* 15 */ "g\'", }; + /* Language th: Thai */ + private static final String[] LANGUAGE_th = { + /* 0~ */ + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, null, + /* ~41 */ + // Label for "switch to alphabetic" key. + // U+0E01: "ก" THAI CHARACTER KO KAI + // U+0E02: "ข" THAI CHARACTER KHO KHAI + // U+0E04: "ค" THAI CHARACTER KHO KHWAI + /* 42 */ "\u0E01\u0E02\u0E04", + /* 43~ */ + null, null, null, null, + /* ~46 */ + // U+0E3F: "฿" THAI CURRENCY SYMBOL BAHT + /* 47 */ "\u0E3F", + }; + /* Language tl: Tagalog */ private static final String[] LANGUAGE_tl = { // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE @@ -2589,6 +2770,19 @@ public final class KeyboardTextsSet { /* 34 */ null, // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN /* 35 */ "\u044A", + /* 36~ */ + null, null, null, null, null, null, + /* ~41 */ + // Label for "switch to alphabetic" key. + // U+0410: "А" CYRILLIC CAPITAL LETTER A + // U+0411: "Б" CYRILLIC CAPITAL LETTER BE + // U+0412: "В" CYRILLIC CAPITAL LETTER VE + /* 42 */ "\u0410\u0411\u0412", + /* 43~ */ + null, null, null, null, + /* ~46 */ + // U+20B4: "₴" HRYVNIA SIGN + /* 47 */ "\u20B4", }; /* Language vi: Vietnamese */ @@ -2670,6 +2864,13 @@ public final class KeyboardTextsSet { /* 8 */ "\u1EF3,\u00FD,\u1EF7,\u1EF9,\u1EF5", // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE /* 9 */ "\u0111", + /* 10~ */ + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, + /* ~46 */ + // U+20AB: "₫" DONG SIGN + /* 47 */ "\u20AB", }; /* Language zu: Zulu */ @@ -2847,10 +3048,12 @@ public final class KeyboardTextsSet { "af", LANGUAGE_af, /* Afrikaans */ "ar", LANGUAGE_ar, /* Arabic */ "be", LANGUAGE_be, /* Belarusian */ + "bg", LANGUAGE_bg, /* Bulgarian */ "ca", LANGUAGE_ca, /* Catalan */ "cs", LANGUAGE_cs, /* Czech */ "da", LANGUAGE_da, /* Danish */ "de", LANGUAGE_de, /* German */ + "el", LANGUAGE_el, /* Greek */ "en", LANGUAGE_en, /* English */ "eo", LANGUAGE_eo, /* Esperanto */ "es", LANGUAGE_es, /* Spanish */ @@ -2864,10 +3067,12 @@ public final class KeyboardTextsSet { "is", LANGUAGE_is, /* Icelandic */ "it", LANGUAGE_it, /* Italian */ "iw", LANGUAGE_iw, /* Hebrew */ + "ka", LANGUAGE_ka, /* Georgian */ "ky", LANGUAGE_ky, /* Kirghiz */ "lt", LANGUAGE_lt, /* Lithuanian */ "lv", LANGUAGE_lv, /* Latvian */ "mk", LANGUAGE_mk, /* Macedonian */ + "mn", LANGUAGE_mn, /* Mongolian */ "nb", LANGUAGE_nb, /* Norwegian Bokmål */ "nl", LANGUAGE_nl, /* Dutch */ "pl", LANGUAGE_pl, /* Polish */ @@ -2880,6 +3085,7 @@ public final class KeyboardTextsSet { "sr", LANGUAGE_sr, /* Serbian */ "sv", LANGUAGE_sv, /* Swedish */ "sw", LANGUAGE_sw, /* Swahili */ + "th", LANGUAGE_th, /* Thai */ "tl", LANGUAGE_tl, /* Tagalog */ "tr", LANGUAGE_tr, /* Turkish */ "uk", LANGUAGE_uk, /* Ukrainian */ diff --git a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java index 550391b49..ca16163d4 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java +++ b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java @@ -18,7 +18,7 @@ package com.android.inputmethod.keyboard.internal; import android.text.TextUtils; -import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.StringUtils; import java.util.Locale; @@ -35,10 +35,10 @@ public final class MoreKeySpec { KeySpecParser.getLabel(moreKeySpec), needsToUpperCase, locale); final int code = KeySpecParser.toUpperCaseOfCodeForLocale( KeySpecParser.getCode(moreKeySpec, codesSet), needsToUpperCase, locale); - if (code == Keyboard.CODE_UNSPECIFIED) { + if (code == Constants.CODE_UNSPECIFIED) { // Some letter, for example German Eszett (U+00DF: "ß"), has multiple characters // upper case representation ("SS"). - mCode = Keyboard.CODE_OUTPUT_TEXT; + mCode = Constants.CODE_OUTPUT_TEXT; mOutputText = mLabel; } else { mCode = code; @@ -75,8 +75,8 @@ public final class MoreKeySpec { public String toString() { final String label = (mIconId == KeyboardIconsSet.ICON_UNDEFINED ? mLabel : KeySpecParser.PREFIX_ICON + KeyboardIconsSet.getIconName(mIconId)); - final String output = (mCode == Keyboard.CODE_OUTPUT_TEXT ? mOutputText - : Keyboard.printableCode(mCode)); + final String output = (mCode == Constants.CODE_OUTPUT_TEXT ? mOutputText + : Constants.printableCode(mCode)); if (StringUtils.codePointCount(label) == 1 && label.codePointAt(0) == mCode) { return output; } else { diff --git a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java index a52f202aa..00fc885e8 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java +++ b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java @@ -30,6 +30,7 @@ public final class PointerTrackerQueue { public boolean isModifier(); public boolean isInSlidingKeyInput(); public void onPhantomUpEvent(long eventTime); + public void cancelTracking(); } private static final int INITIAL_CAPACITY = 10; @@ -182,6 +183,15 @@ public final class PointerTrackerQueue { return false; } + public synchronized void cancelAllPointerTracker() { + final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers; + final int arraySize = mArraySize; + for (int index = 0; index < arraySize; index++) { + final Element element = expandableArray.get(index); + element.cancelTracking(); + } + } + @Override public synchronized String toString() { final StringBuilder sb = new StringBuilder(); diff --git a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java index 776ac0204..bc734b08d 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java +++ b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java @@ -36,14 +36,11 @@ import android.widget.RelativeLayout; import com.android.inputmethod.keyboard.PointerTracker; import com.android.inputmethod.keyboard.internal.GesturePreviewTrail.Params; import com.android.inputmethod.latin.CollectionUtils; +import com.android.inputmethod.latin.CoordinateUtils; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.StaticInnerHandlerWrapper; public final class PreviewPlacerView extends RelativeLayout { - // The height of extra area above the keyboard to draw gesture trails. - // Proportional to the keyboard height. - private static final float EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO = 0.25f; - private final int mGestureFloatingPreviewTextColor; private final int mGestureFloatingPreviewTextOffset; private final int mGestureFloatingPreviewColor; @@ -51,8 +48,7 @@ public final class PreviewPlacerView extends RelativeLayout { private final float mGestureFloatingPreviewVerticalPadding; private final float mGestureFloatingPreviewRoundRadius; - private int mKeyboardViewOriginX; - private int mKeyboardViewOriginY; + private final int[] mKeyboardViewOrigin = CoordinateUtils.newInstance(); private final SparseArray<GesturePreviewTrail> mGesturePreviewTrails = CollectionUtils.newSparseArray(); @@ -72,11 +68,14 @@ public final class PreviewPlacerView extends RelativeLayout { private final int mGestureFloatingPreviewTextHeight; // {@link RectF} is needed for {@link Canvas#drawRoundRect(RectF, float, float, Paint)}. private final RectF mGestureFloatingPreviewRectangle = new RectF(); - private int mLastPointerX; - private int mLastPointerY; + private final int[] mLastPointerCoords = CoordinateUtils.newInstance(); private static final char[] TEXT_HEIGHT_REFERENCE_CHAR = { 'M' }; private boolean mDrawsGestureFloatingPreviewText; + private boolean mShowSlidingKeyInputPreview; + private final int[] mRubberBandFrom = CoordinateUtils.newInstance(); + private final int[] mRubberBandTo = CoordinateUtils.newInstance(); + private final DrawingHandler mDrawingHandler; private static final class DrawingHandler extends StaticInnerHandlerWrapper<PreviewPlacerView> { @@ -172,10 +171,9 @@ public final class PreviewPlacerView extends RelativeLayout { setLayerType(LAYER_TYPE_HARDWARE, layerPaint); } - public void setKeyboardViewGeometry(final int x, final int y, final int w, final int h) { - mKeyboardViewOriginX = x; - mKeyboardViewOriginY = y; - mOffscreenOffsetY = (int)(h * EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO); + public void setKeyboardViewGeometry(final int[] originCoords, final int w, final int h) { + CoordinateUtils.copy(mKeyboardViewOrigin, originCoords); + mOffscreenOffsetY = (int)(h * GestureStroke.EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO); mOffscreenWidth = w; mOffscreenHeight = mOffscreenOffsetY + h; } @@ -190,8 +188,7 @@ public final class PreviewPlacerView extends RelativeLayout { final boolean needsToUpdateLastPointer = isOldestTracker && mDrawsGestureFloatingPreviewText; if (needsToUpdateLastPointer) { - mLastPointerX = tracker.getLastX(); - mLastPointerY = tracker.getLastY(); + tracker.getLastCoordinates(mLastPointerCoords); } if (mDrawsGesturePreviewTrail) { @@ -212,6 +209,21 @@ public final class PreviewPlacerView extends RelativeLayout { } } + public void showSlidingKeyInputPreview(final PointerTracker tracker) { + if (!tracker.isInSlidingKeyInputFromModifier()) { + mShowSlidingKeyInputPreview = false; + return; + } + tracker.getDownCoordinates(mRubberBandFrom); + tracker.getLastCoordinates(mRubberBandTo); + mShowSlidingKeyInputPreview = true; + invalidate(); + } + + public void dismissSlidingKeyInputPreview() { + mShowSlidingKeyInputPreview = false; + } + @Override protected void onDetachedFromWindow() { freeOffscreenBuffer(); @@ -238,6 +250,8 @@ public final class PreviewPlacerView extends RelativeLayout { @Override public void onDraw(final Canvas canvas) { super.onDraw(canvas); + final int originX = CoordinateUtils.x(mKeyboardViewOrigin); + final int originY = CoordinateUtils.y(mKeyboardViewOrigin); if (mDrawsGesturePreviewTrail) { mayAllocateOffscreenBuffer(); // Draw gesture trails to offscreen buffer. @@ -245,11 +259,11 @@ public final class PreviewPlacerView extends RelativeLayout { mOffscreenCanvas, mGesturePaint, mOffscreenDirtyRect); // Transfer offscreen buffer to screen. if (!mOffscreenDirtyRect.isEmpty()) { - final int offsetY = mKeyboardViewOriginY - mOffscreenOffsetY; - canvas.translate(mKeyboardViewOriginX, offsetY); + final int offsetY = originY - mOffscreenOffsetY; + canvas.translate(originX, offsetY); canvas.drawBitmap(mOffscreenBuffer, mOffscreenDirtyRect, mOffscreenDirtyRect, mGesturePaint); - canvas.translate(-mKeyboardViewOriginX, -offsetY); + canvas.translate(-originX, -offsetY); // Note: Defer clearing the dirty rectangle here because we will get cleared // rectangle on the canvas. } @@ -258,9 +272,14 @@ public final class PreviewPlacerView extends RelativeLayout { } } if (mDrawsGestureFloatingPreviewText) { - canvas.translate(mKeyboardViewOriginX, mKeyboardViewOriginY); + canvas.translate(originX, originY); drawGestureFloatingPreviewText(canvas, mGestureFloatingPreviewText); - canvas.translate(-mKeyboardViewOriginX, -mKeyboardViewOriginY); + canvas.translate(-originX, -originY); + } + if (mShowSlidingKeyInputPreview) { + canvas.translate(originX, originY); + drawSlidingKeyInputPreview(canvas); + canvas.translate(-originX, -originY); } } @@ -321,8 +340,6 @@ public final class PreviewPlacerView extends RelativeLayout { final Paint paint = mTextPaint; final RectF rectangle = mGestureFloatingPreviewRectangle; - // TODO: Figure out how we should deal with the floating preview text with multiple moving - // fingers. // Paint the round rectangle background. final int textHeight = mGestureFloatingPreviewTextHeight; @@ -332,9 +349,11 @@ public final class PreviewPlacerView extends RelativeLayout { final float rectWidth = textWidth + hPad * 2.0f; final float rectHeight = textHeight + vPad * 2.0f; final int canvasWidth = canvas.getWidth(); - final float rectX = Math.min(Math.max(mLastPointerX - rectWidth / 2.0f, 0.0f), + final float rectX = Math.min( + Math.max(CoordinateUtils.x(mLastPointerCoords) - rectWidth / 2.0f, 0.0f), canvasWidth - rectWidth); - final float rectY = mLastPointerY - mGestureFloatingPreviewTextOffset - rectHeight; + final float rectY = CoordinateUtils.y(mLastPointerCoords) + - mGestureFloatingPreviewTextOffset - rectHeight; rectangle.set(rectX, rectY, rectX + rectWidth, rectY + rectHeight); final float round = mGestureFloatingPreviewRoundRadius; paint.setColor(mGestureFloatingPreviewColor); @@ -345,4 +364,8 @@ public final class PreviewPlacerView extends RelativeLayout { final float textY = rectY + vPad + textHeight; canvas.drawText(gestureFloatingPreviewText, textX, textY, paint); } + + private void drawSlidingKeyInputPreview(final Canvas canvas) { + // TODO: Implement rubber band preview + } } diff --git a/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java b/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java new file mode 100644 index 000000000..1f5252077 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.keyboard.internal; + +import android.graphics.Path; +import android.graphics.RectF; + +public final class RoundedLine { + private final RectF mArc1 = new RectF(); + private final RectF mArc2 = new RectF(); + private final Path mPath = new Path(); + + private static final double RADIAN_TO_DEGREE = 180.0d / Math.PI; + private static final double RIGHT_ANGLE = Math.PI / 2.0d; + + /** + * Make a rounded line path + * + * @param p1x the x-coordinate of the start point. + * @param p1y the y-coordinate of the start point. + * @param r1 the radius at the start point + * @param p2x the x-coordinate of the end point. + * @param p2y the y-coordinate of the end point. + * @param r2 the radius at the end point + * @return the path of rounded line + */ + public Path makePath(final float p1x, final float p1y, final float r1, + final float p2x, final float p2y, final float r2) { + final double dx = p2x - p1x; + final double dy = p2y - p1y; + // Distance of the points. + final double l = Math.hypot(dx, dy); + if (Double.compare(0.0d, l) == 0) { + return null; + } + // Angle of the line p1-p2 + final double a = Math.atan2(dy, dx); + // Difference of trail cap radius. + final double dr = r2 - r1; + // Variation of angle at trail cap. + final double ar = Math.asin(dr / l); + // The start angle of trail cap arc at P1. + final double aa = a - (RIGHT_ANGLE + ar); + // The end angle of trail cap arc at P2. + final double ab = a + (RIGHT_ANGLE + ar); + final float cosa = (float)Math.cos(aa); + final float sina = (float)Math.sin(aa); + final float cosb = (float)Math.cos(ab); + final float sinb = (float)Math.sin(ab); + // Closing point of arc at P1. + final float p1ax = p1x + r1 * cosa; + final float p1ay = p1y + r1 * sina; + // Opening point of arc at P1. + final float p1bx = p1x + r1 * cosb; + final float p1by = p1y + r1 * sinb; + // Opening point of arc at P2. + final float p2ax = p2x + r2 * cosa; + final float p2ay = p2y + r2 * sina; + // Closing point of arc at P2. + final float p2bx = p2x + r2 * cosb; + final float p2by = p2y + r2 * sinb; + // Start angle of the trail arcs. + final float angle = (float)(aa * RADIAN_TO_DEGREE); + final float ar2degree = (float)(ar * 2.0d * RADIAN_TO_DEGREE); + // Sweep angle of the trail arc at P1. + final float a1 = -180.0f + ar2degree; + // Sweep angle of the trail arc at P2. + final float a2 = 180.0f + ar2degree; + mArc1.set(p1x, p1y, p1x, p1y); + mArc1.inset(-r1, -r1); + mArc2.set(p2x, p2y, p2x, p2y); + mArc2.inset(-r2, -r2); + + mPath.rewind(); + // Trail cap at P1. + mPath.moveTo(p1x, p1y); + mPath.arcTo(mArc1, angle, a1); + // Trail cap at P2. + mPath.moveTo(p2x, p2y); + mPath.arcTo(mArc2, angle, a2); + // Two trapezoids connecting P1 and P2. + mPath.moveTo(p1ax, p1ay); + mPath.lineTo(p1x, p1y); + mPath.lineTo(p1bx, p1by); + mPath.lineTo(p2bx, p2by); + mPath.lineTo(p2x, p2y); + mPath.lineTo(p2ax, p2ay); + mPath.close(); + return mPath; + } +} diff --git a/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java b/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java index d8950a713..d7a2b6f39 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java +++ b/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java @@ -16,6 +16,7 @@ package com.android.inputmethod.keyboard.internal; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.LatinImeLogger; public final class TouchPositionCorrection { @@ -66,7 +67,7 @@ public final class TouchPositionCorrection { } } - // For test only + @UsedForTesting public void setEnabled(final boolean enabled) { mEnabled = enabled; } diff --git a/java/src/com/android/inputmethod/keyboard/internal/SuddenJumpingTouchEventHandler.java b/java/src/com/android/inputmethod/keyboard/internal/TouchScreenRegulator.java index c53428fe5..c795d5322 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/SuddenJumpingTouchEventHandler.java +++ b/java/src/com/android/inputmethod/keyboard/internal/TouchScreenRegulator.java @@ -20,7 +20,6 @@ import android.content.Context; import android.util.Log; import android.view.MotionEvent; -import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.MainKeyboardView; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; @@ -28,8 +27,8 @@ import com.android.inputmethod.latin.ResourceUtils; import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.research.ResearchLogger; -public final class SuddenJumpingTouchEventHandler { - private static final String TAG = SuddenJumpingTouchEventHandler.class.getSimpleName(); +public final class TouchScreenRegulator { + private static final String TAG = TouchScreenRegulator.class.getSimpleName(); private static boolean DEBUG_MODE = LatinImeLogger.sDBG; public interface ProcessMotionEvent { @@ -50,17 +49,18 @@ public final class SuddenJumpingTouchEventHandler { private int mJumpThresholdSquare = Integer.MAX_VALUE; private int mLastX; private int mLastY; + // One-seventh of the keyboard width seems like a reasonable threshold + private static final float JUMP_THRESHOLD_RATIO_TO_KEYBOARD_WIDTH = 1.0f / 7.0f; - public SuddenJumpingTouchEventHandler(Context context, ProcessMotionEvent view) { + public TouchScreenRegulator(final Context context, final ProcessMotionEvent view) { mView = view; mNeedsSuddenJumpingHack = Boolean.parseBoolean(ResourceUtils.getDeviceOverrideValue( context.getResources(), R.array.sudden_jumping_touch_event_device_list, "false")); } - public void setKeyboard(Keyboard newKeyboard) { - // One-seventh of the keyboard width seems like a reasonable threshold - final int jumpThreshold = newKeyboard.mOccupiedWidth / 7; - mJumpThresholdSquare = jumpThreshold * jumpThreshold; + public void setKeyboardGeometry(final int keyboardWidth) { + final float jumpThreshold = keyboardWidth * JUMP_THRESHOLD_RATIO_TO_KEYBOARD_WIDTH; + mJumpThresholdSquare = (int)(jumpThreshold * jumpThreshold); } /** @@ -74,7 +74,7 @@ public final class SuddenJumpingTouchEventHandler { * @return true if the event was consumed, so that it doesn't continue to be handled by * {@link MainKeyboardView}. */ - private boolean handleSuddenJumping(MotionEvent me) { + private boolean handleSuddenJumping(final MotionEvent me) { if (!mNeedsSuddenJumpingHack) return false; final int action = me.getAction(); @@ -140,7 +140,7 @@ public final class SuddenJumpingTouchEventHandler { return result; } - public boolean onTouchEvent(MotionEvent me) { + public boolean onTouchEvent(final MotionEvent me) { // If there was a sudden jump, return without processing the actual motion event. if (handleSuddenJumping(me)) { if (DEBUG_MODE) diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java index 939451899..08f08d24e 100644 --- a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java +++ b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java @@ -50,6 +50,7 @@ import java.util.ArrayList; import java.util.TreeSet; public final class AdditionalSubtypeSettings extends PreferenceFragment { + private RichInputMethodManager mRichImm; private SharedPreferences mPrefs; private SubtypeLocaleAdapter mSubtypeLocaleAdapter; private KeyboardLayoutSetAdapter mKeyboardLayoutSetAdapter; @@ -63,6 +64,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment { private static final String KEY_IS_SUBTYPE_ENABLER_NOTIFICATION_DIALOG_OPEN = "is_subtype_enabler_notification_dialog_open"; private static final String KEY_SUBTYPE_FOR_SUBTYPE_ENABLER = "subtype_for_subtype_enabler"; + static final class SubtypeLocaleItem extends Pair<String, String> implements Comparable<SubtypeLocaleItem> { public SubtypeLocaleItem(final String localeString, final String displayName) { @@ -93,7 +95,8 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment { setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); final TreeSet<SubtypeLocaleItem> items = CollectionUtils.newTreeSet(); - final InputMethodInfo imi = ImfUtils.getInputMethodInfoOfThisIme(context); + final InputMethodInfo imi = RichInputMethodManager.getInstance() + .getInputMethodInfoOfThisIme(); final int count = imi.getSubtypeCount(); for (int i = 0; i < count; i++) { final InputMethodSubtype subtype = imi.getSubtypeAt(i); @@ -383,10 +386,11 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment { public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); + mPrefs = getPreferenceManager().getSharedPreferences(); + RichInputMethodManager.init(getActivity(), mPrefs); + mRichImm = RichInputMethodManager.getInstance(); addPreferencesFromResource(R.xml.additional_subtype_settings); setHasOptionsMenu(true); - - mPrefs = getPreferenceManager().getSharedPreferences(); } @Override @@ -439,7 +443,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment { mIsAddingNewSubtype = false; final PreferenceGroup group = getPreferenceScreen(); group.removePreference(subtypePref); - ImfUtils.setAdditionalInputMethodSubtypes(getActivity(), getSubtypes()); + mRichImm.setAdditionalInputMethodSubtypes(getSubtypes()); } @Override @@ -449,7 +453,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment { return; } if (findDuplicatedSubtype(subtype) == null) { - ImfUtils.setAdditionalInputMethodSubtypes(getActivity(), getSubtypes()); + mRichImm.setAdditionalInputMethodSubtypes(getSubtypes()); return; } @@ -466,7 +470,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment { mIsAddingNewSubtype = false; final InputMethodSubtype subtype = subtypePref.getSubtype(); if (findDuplicatedSubtype(subtype) == null) { - ImfUtils.setAdditionalInputMethodSubtypes(getActivity(), getSubtypes()); + mRichImm.setAdditionalInputMethodSubtypes(getSubtypes()); mSubtypePreferenceKeyForSubtypeEnabler = subtypePref.getKey(); mSubtypeEnablerNotificationDialog = createDialog(subtypePref); mSubtypeEnablerNotificationDialog.show(); @@ -501,8 +505,8 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment { private InputMethodSubtype findDuplicatedSubtype(final InputMethodSubtype subtype) { final String localeString = subtype.getLocale(); final String keyboardLayoutSetName = SubtypeLocale.getKeyboardLayoutSetName(subtype); - return ImfUtils.findSubtypeByLocaleAndKeyboardLayoutSet( - getActivity(), localeString, keyboardLayoutSetName); + return mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + localeString, keyboardLayoutSetName); } private AlertDialog createDialog( @@ -515,7 +519,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment { @Override public void onClick(DialogInterface dialog, int which) { final Intent intent = CompatUtils.getInputLanguageSelectionIntent( - ImfUtils.getInputMethodIdOfThisIme(getActivity()), + mRichImm.getInputMethodIdOfThisIme(), Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | Intent.FLAG_ACTIVITY_CLEAR_TOP); @@ -573,7 +577,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment { } finally { editor.apply(); } - ImfUtils.setAdditionalInputMethodSubtypes(getActivity(), subtypes); + mRichImm.setAdditionalInputMethodSubtypes(subtypes); } @Override diff --git a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java index 59ef5e09f..024726391 100644 --- a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java +++ b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java @@ -21,7 +21,6 @@ import android.media.AudioManager; import android.view.HapticFeedbackConstants; import android.view.View; -import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.latin.VibratorUtils; /** @@ -31,17 +30,15 @@ import com.android.inputmethod.latin.VibratorUtils; * complexity of settings and the like. */ public final class AudioAndHapticFeedbackManager { - final private SettingsValues mSettingsValues; - final private AudioManager mAudioManager; - final private VibratorUtils mVibratorUtils; + private final AudioManager mAudioManager; + private final VibratorUtils mVibratorUtils; + + private SettingsValues mSettingsValues; private boolean mSoundOn; - public AudioAndHapticFeedbackManager(final LatinIME latinIme, - final SettingsValues settingsValues) { - mSettingsValues = settingsValues; + public AudioAndHapticFeedbackManager(final LatinIME latinIme) { mVibratorUtils = VibratorUtils.getInstance(latinIme); mAudioManager = (AudioManager) latinIme.getSystemService(Context.AUDIO_SERVICE); - mSoundOn = reevaluateIfSoundIsOn(); } public void hapticAndAudioFeedback(final int primaryCode, @@ -51,7 +48,7 @@ public final class AudioAndHapticFeedbackManager { } private boolean reevaluateIfSoundIsOn() { - if (!mSettingsValues.mSoundOn || mAudioManager == null) { + if (mSettingsValues == null || !mSettingsValues.mSoundOn || mAudioManager == null) { return false; } else { return mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_NORMAL; @@ -64,13 +61,13 @@ public final class AudioAndHapticFeedbackManager { if (mSoundOn) { final int sound; switch (primaryCode) { - case Keyboard.CODE_DELETE: + case Constants.CODE_DELETE: sound = AudioManager.FX_KEYPRESS_DELETE; break; - case Keyboard.CODE_ENTER: + case Constants.CODE_ENTER: sound = AudioManager.FX_KEYPRESS_RETURN; break; - case Keyboard.CODE_SPACE: + case Constants.CODE_SPACE: sound = AudioManager.FX_KEYPRESS_SPACEBAR; break; default: @@ -81,8 +78,7 @@ public final class AudioAndHapticFeedbackManager { } } - // TODO: make this private when LatinIME does not call it any more - public void vibrate(final View viewToPerformHapticFeedbackOn) { + private void vibrate(final View viewToPerformHapticFeedbackOn) { if (!mSettingsValues.mVibrateOn) { return; } @@ -98,6 +94,11 @@ public final class AudioAndHapticFeedbackManager { } } + public void onSettingsChanged(final SettingsValues settingsValues) { + mSettingsValues = settingsValues; + mSoundOn = reevaluateIfSoundIsOn(); + } + public void onRingerModeChanged() { mSoundOn = reevaluateIfSoundIsOn(); } diff --git a/java/src/com/android/inputmethod/latin/AutoCorrection.java b/java/src/com/android/inputmethod/latin/AutoCorrection.java index 84fad158f..fa35922b0 100644 --- a/java/src/com/android/inputmethod/latin/AutoCorrection.java +++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java @@ -33,11 +33,11 @@ public final class AutoCorrection { } public static boolean isValidWord(final ConcurrentHashMap<String, Dictionary> dictionaries, - CharSequence word, boolean ignoreCase) { + final String word, final boolean ignoreCase) { if (TextUtils.isEmpty(word)) { return false; } - final CharSequence lowerCasedWord = word.toString().toLowerCase(); + final String lowerCasedWord = word.toLowerCase(); for (final String key : dictionaries.keySet()) { final Dictionary dictionary = dictionaries.get(key); // It's unclear how realistically 'dictionary' can be null, but the monkey is somehow @@ -57,7 +57,7 @@ public final class AutoCorrection { } public static int getMaxFrequency(final ConcurrentHashMap<String, Dictionary> dictionaries, - CharSequence word) { + final String word) { if (TextUtils.isEmpty(word)) { return Dictionary.NOT_A_PROBABILITY; } @@ -76,12 +76,13 @@ public final class AutoCorrection { // Returns true if this is in any of the dictionaries. public static boolean isInTheDictionary( final ConcurrentHashMap<String, Dictionary> dictionaries, - final CharSequence word, final boolean ignoreCase) { + final String word, final boolean ignoreCase) { return isValidWord(dictionaries, word, ignoreCase); } - public static boolean suggestionExceedsAutoCorrectionThreshold(SuggestedWordInfo suggestion, - CharSequence consideredWord, float autoCorrectionThreshold) { + public static boolean suggestionExceedsAutoCorrectionThreshold( + final SuggestedWordInfo suggestion, final String consideredWord, + final float autoCorrectionThreshold) { if (null != suggestion) { // Shortlist a whitelisted word if (suggestion.mKind == SuggestedWordInfo.KIND_WHITELIST) return true; @@ -89,8 +90,7 @@ public final class AutoCorrection { // TODO: when the normalized score of the first suggestion is nearly equals to // the normalized score of the second suggestion, behave less aggressive. final float normalizedScore = BinaryDictionary.calcNormalizedScore( - consideredWord.toString(), suggestion.mWord.toString(), - autoCorrectionSuggestionScore); + consideredWord, suggestion.mWord, autoCorrectionSuggestionScore); if (DBG) { Log.d(TAG, "Normalized " + consideredWord + "," + suggestion + "," + autoCorrectionSuggestionScore + ", " + normalizedScore @@ -100,8 +100,7 @@ public final class AutoCorrection { if (DBG) { Log.d(TAG, "Auto corrected by S-threshold."); } - return !shouldBlockAutoCorrectionBySafetyNet(consideredWord.toString(), - suggestion.mWord); + return !shouldBlockAutoCorrectionBySafetyNet(consideredWord, suggestion.mWord); } } return false; @@ -110,7 +109,7 @@ public final class AutoCorrection { // TODO: Resolve the inconsistencies between the native auto correction algorithms and // this safety net public static boolean shouldBlockAutoCorrectionBySafetyNet(final String typedWord, - final CharSequence suggestion) { + final String suggestion) { // Safety net for auto correction. // Actually if we hit this safety net, it's a bug. // If user selected aggressive auto correction mode, there is no need to use the safety @@ -123,7 +122,7 @@ public final class AutoCorrection { } final int maxEditDistanceOfNativeDictionary = (typedWordLength < 5 ? 2 : typedWordLength / 2) + 1; - final int distance = BinaryDictionary.editDistance(typedWord, suggestion.toString()); + final int distance = BinaryDictionary.editDistance(typedWord, suggestion); if (DBG) { Log.d(TAG, "Autocorrected edit distance = " + distance + ", " + maxEditDistanceOfNativeDictionary); diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index 7184f1d8a..6048a3308 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -31,7 +31,7 @@ import java.util.Locale; * Implements a static, compacted, binary dictionary of standard words. */ public final class BinaryDictionary extends Dictionary { - + private static final String TAG = BinaryDictionary.class.getSimpleName(); public static final String DICTIONARY_PACK_AUTHORITY = "com.android.inputmethod.latin.dictionarypack"; @@ -45,17 +45,13 @@ public final class BinaryDictionary extends Dictionary { public static final int MAX_WORDS = 18; public static final int MAX_SPACES = 16; - private static final String TAG = BinaryDictionary.class.getSimpleName(); private static final int MAX_PREDICTIONS = 60; private static final int MAX_RESULTS = Math.max(MAX_PREDICTIONS, MAX_WORDS); - private static final int TYPED_LETTER_MULTIPLIER = 2; - private long mNativeDict; private final Locale mLocale; private final int[] mInputCodePoints = new int[MAX_WORD_LENGTH]; - // TODO: The below should be int[] mOutputCodePoints - private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_RESULTS]; + private final int[] mOutputCodePoints = new int[MAX_WORD_LENGTH * MAX_RESULTS]; private final int[] mSpaceIndices = new int[MAX_SPACES]; private final int[] mOutputScores = new int[MAX_RESULTS]; private final int[] mOutputTypes = new int[MAX_RESULTS]; @@ -67,7 +63,7 @@ public final class BinaryDictionary extends Dictionary { // TODO: There should be a way to remove used DicTraverseSession objects from // {@code mDicTraverseSessions}. - private DicTraverseSession getTraverseSession(int traverseSessionId) { + private DicTraverseSession getTraverseSession(final int traverseSessionId) { synchronized(mDicTraverseSessions) { DicTraverseSession traverseSession = mDicTraverseSessions.get(traverseSessionId); if (traverseSession == null) { @@ -84,7 +80,6 @@ public final class BinaryDictionary extends Dictionary { /** * Constructor for the binary dictionary. This is supposed to be called from the * dictionary factory. - * All implementations should pass null into flagArray, except for testing purposes. * @param context the context to access the environment from. * @param filename the name of the file to read through native code. * @param offset the offset of the dictionary data within the file. @@ -92,9 +87,9 @@ public final class BinaryDictionary extends Dictionary { * @param useFullEditDistance whether to use the full edit distance in suggestions * @param dictType the dictionary type, as a human-readable string */ - public BinaryDictionary(final Context context, - final String filename, final long offset, final long length, - final boolean useFullEditDistance, final Locale locale, final String dictType) { + public BinaryDictionary(final Context context, final String filename, final long offset, + final long length, final boolean useFullEditDistance, final Locale locale, + final String dictType) { super(dictType); mLocale = locale; mUseFullEditDistance = useFullEditDistance; @@ -106,40 +101,40 @@ public final class BinaryDictionary extends Dictionary { } private native long openNative(String sourceDir, long dictOffset, long dictSize, - int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength, int maxWords, - int maxPredictions); + int maxWordLength, int maxWords, int maxPredictions); private native void closeNative(long dict); private native int getFrequencyNative(long dict, int[] word); private native boolean isValidBigramNative(long dict, int[] word1, int[] word2); private native int getSuggestionsNative(long dict, long proximityInfo, long traverseSession, int[] xCoordinates, int[] yCoordinates, int[] times, int[] pointerIds, int[] inputCodePoints, int codesSize, int commitPoint, boolean isGesture, - int[] prevWordCodePointArray, boolean useFullEditDistance, char[] outputChars, + int[] prevWordCodePointArray, boolean useFullEditDistance, int[] outputCodePoints, int[] outputScores, int[] outputIndices, int[] outputTypes); - private static native float calcNormalizedScoreNative(char[] before, char[] after, int score); - private static native int editDistanceNative(char[] before, char[] after); + private static native float calcNormalizedScoreNative(int[] before, int[] after, int score); + private static native int editDistanceNative(int[] before, int[] after); // TODO: Move native dict into session - private final void loadDictionary(String path, long startOffset, long length) { - mNativeDict = openNative(path, startOffset, length, TYPED_LETTER_MULTIPLIER, - FULL_WORD_SCORE_MULTIPLIER, MAX_WORD_LENGTH, MAX_WORDS, MAX_PREDICTIONS); + private final void loadDictionary(final String path, final long startOffset, + final long length) { + mNativeDict = openNative(path, startOffset, length, MAX_WORD_LENGTH, MAX_WORDS, + MAX_PREDICTIONS); } @Override public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, - final CharSequence prevWord, final ProximityInfo proximityInfo) { + final String prevWord, final ProximityInfo proximityInfo) { return getSuggestionsWithSessionId(composer, prevWord, proximityInfo, 0); } @Override public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer, - final CharSequence prevWord, final ProximityInfo proximityInfo, int sessionId) { + final String prevWord, final ProximityInfo proximityInfo, int sessionId) { if (!isValidDictionary()) return null; Arrays.fill(mInputCodePoints, Constants.NOT_A_CODE); // TODO: toLowerCase in the native code final int[] prevWordCodePointArray = (null == prevWord) - ? null : StringUtils.toCodePointArray(prevWord.toString()); + ? null : StringUtils.toCodePointArray(prevWord); final int composerSize = composer.size(); final boolean isGesture = composer.isBatchMode(); @@ -157,7 +152,8 @@ public final class BinaryDictionary extends Dictionary { proximityInfo.getNativeProximityInfo(), getTraverseSession(sessionId).getSession(), ips.getXCoordinates(), ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(), mInputCodePoints, codesSize, 0 /* commitPoint */, isGesture, prevWordCodePointArray, - mUseFullEditDistance, mOutputChars, mOutputScores, mSpaceIndices, mOutputTypes); + mUseFullEditDistance, mOutputCodePoints, mOutputScores, mSpaceIndices, + mOutputTypes); final int count = Math.min(tmpCount, MAX_PREDICTIONS); final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList(); @@ -165,53 +161,56 @@ public final class BinaryDictionary extends Dictionary { if (composerSize > 0 && mOutputScores[j] < 1) break; final int start = j * MAX_WORD_LENGTH; int len = 0; - while (len < MAX_WORD_LENGTH && mOutputChars[start + len] != 0) { + while (len < MAX_WORD_LENGTH && mOutputCodePoints[start + len] != 0) { ++len; } if (len > 0) { final int score = SuggestedWordInfo.KIND_WHITELIST == mOutputTypes[j] ? SuggestedWordInfo.MAX_SCORE : mOutputScores[j]; - suggestions.add(new SuggestedWordInfo( - new String(mOutputChars, start, len), score, mOutputTypes[j], mDictType)); + suggestions.add(new SuggestedWordInfo(new String(mOutputCodePoints, start, len), + score, mOutputTypes[j], mDictType)); } } return suggestions; } - /* package for test */ boolean isValidDictionary() { + public boolean isValidDictionary() { return mNativeDict != 0; } - public static float calcNormalizedScore(String before, String after, int score) { - return calcNormalizedScoreNative(before.toCharArray(), after.toCharArray(), score); + public static float calcNormalizedScore(final String before, final String after, + final int score) { + return calcNormalizedScoreNative(StringUtils.toCodePointArray(before), + StringUtils.toCodePointArray(after), score); } - public static int editDistance(String before, String after) { + public static int editDistance(final String before, final String after) { if (before == null || after == null) { throw new IllegalArgumentException(); } - return editDistanceNative(before.toCharArray(), after.toCharArray()); + return editDistanceNative(StringUtils.toCodePointArray(before), + StringUtils.toCodePointArray(after)); } @Override - public boolean isValidWord(CharSequence word) { + public boolean isValidWord(final String word) { return getFrequency(word) >= 0; } @Override - public int getFrequency(CharSequence word) { + public int getFrequency(final String word) { if (word == null) return -1; - int[] codePoints = StringUtils.toCodePointArray(word.toString()); + int[] codePoints = StringUtils.toCodePointArray(word); return getFrequencyNative(mNativeDict, codePoints); } // TODO: Add a batch process version (isValidBigramMultiple?) to avoid excessive numbers of jni // calls when checking for changes in an entire dictionary. - public boolean isValidBigram(CharSequence word1, CharSequence word2) { + public boolean isValidBigram(final String word1, final String word2) { if (TextUtils.isEmpty(word1) || TextUtils.isEmpty(word2)) return false; - int[] chars1 = StringUtils.toCodePointArray(word1.toString()); - int[] chars2 = StringUtils.toCodePointArray(word2.toString()); - return isValidBigramNative(mNativeDict, chars1, chars2); + final int[] codePoints1 = StringUtils.toCodePointArray(word1); + final int[] codePoints2 = StringUtils.toCodePointArray(word2); + return isValidBigramNative(mNativeDict, codePoints1, codePoints2); } @Override diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index 9a3f88f52..5eab292fc 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -149,7 +149,13 @@ public final class BinaryDictionaryFileDumper { final Uri.Builder wordListUriBuilder = getProviderUriBuilder(id); final String finalFileName = BinaryDictionaryGetter.getCacheFileName(id, locale, context); - final String tempFileName = BinaryDictionaryGetter.getTempFileName(id, context); + String tempFileName; + try { + tempFileName = BinaryDictionaryGetter.getTempFileName(id, context); + } catch (IOException e) { + Log.e(TAG, "Can't open the temporary file", e); + return null; + } for (int mode = MODE_MIN; mode <= MODE_MAX; ++mode) { InputStream originalSourceStream = null; diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java index c747dc673..83dabbede 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java @@ -16,6 +16,7 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.makedict.BinaryDictInputOutput; import com.android.inputmethod.latin.makedict.FormatSpec; @@ -165,14 +166,9 @@ final class BinaryDictionaryGetter { /** * Generates a unique temporary file name in the app cache directory. - * - * This is unique as long as it doesn't get called twice in the same millisecond by the same - * thread, which should be more than enough for our purposes. */ - public static String getTempFileName(String id, Context context) { - final String fileName = replaceFileNameDangerousCharacters(id); - return context.getCacheDir() + File.separator + fileName + "." - + Thread.currentThread().getId() + "." + System.currentTimeMillis(); + public static String getTempFileName(String id, Context context) throws IOException { + return File.createTempFile(replaceFileNameDangerousCharacters(id), null).getAbsolutePath(); } /** @@ -427,8 +423,11 @@ final class BinaryDictionaryGetter { // cacheWordListsFromContentProvider returns the list of files it copied to local // storage, but we don't really care about what was copied NOW: what we want is the // list of everything we ever cached, so we ignore the return value. - BinaryDictionaryFileDumper.cacheWordListsFromContentProvider(locale, context, - hasDefaultWordList); + // TODO: The experimental version is not supported by the Dictionary Pack Service yet + if (!ProductionFlag.IS_EXPERIMENTAL) { + BinaryDictionaryFileDumper.cacheWordListsFromContentProvider(locale, context, + hasDefaultWordList); + } final File[] cachedWordLists = getCachedWordLists(locale.toString(), context); final String mainDictId = getMainDictId(locale); final DictPackSettings dictPackSettings = new DictPackSettings(context); diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java index 57e12a64f..ba932e590 100644 --- a/java/src/com/android/inputmethod/latin/Constants.java +++ b/java/src/com/android/inputmethod/latin/Constants.java @@ -136,11 +136,81 @@ public final class Constants { public static final int NOT_A_CODE = -1; - // See {@link KeyboardActionListener.Adapter#isInvalidCoordinate(int)}. public static final int NOT_A_COORDINATE = -1; public static final int SUGGESTION_STRIP_COORDINATE = -2; public static final int SPELL_CHECKER_COORDINATE = -3; + public static boolean isValidCoordinate(final int coordinate) { + // Detect {@link NOT_A_COORDINATE}, {@link SUGGESTION_STRIP_COORDINATE}, + // and {@link SPELL_CHECKER_COORDINATE}. + return coordinate >= 0; + } + + /** + * Some common keys code. Must be positive. + */ + public static final int CODE_ENTER = '\n'; + public static final int CODE_TAB = '\t'; + public static final int CODE_SPACE = ' '; + public static final int CODE_PERIOD = '.'; + public static final int CODE_DASH = '-'; + public static final int CODE_SINGLE_QUOTE = '\''; + public static final int CODE_DOUBLE_QUOTE = '"'; + public static final int CODE_QUESTION_MARK = '?'; + public static final int CODE_EXCLAMATION_MARK = '!'; + // TODO: Check how this should work for right-to-left languages. It seems to stand + // that for rtl languages, a closing parenthesis is a left parenthesis. Is this + // managed by the font? Or is it a different char? + public static final int CODE_CLOSING_PARENTHESIS = ')'; + public static final int CODE_CLOSING_SQUARE_BRACKET = ']'; + public static final int CODE_CLOSING_CURLY_BRACKET = '}'; + public static final int CODE_CLOSING_ANGLE_BRACKET = '>'; + + /** + * Special keys code. Must be negative. + * These should be aligned with KeyboardCodesSet.ID_TO_NAME[], + * KeyboardCodesSet.DEFAULT[] and KeyboardCodesSet.RTL[] + */ + public static final int CODE_SHIFT = -1; + public static final int CODE_SWITCH_ALPHA_SYMBOL = -2; + public static final int CODE_OUTPUT_TEXT = -3; + public static final int CODE_DELETE = -4; + public static final int CODE_SETTINGS = -5; + public static final int CODE_SHORTCUT = -6; + public static final int CODE_ACTION_ENTER = -7; + public static final int CODE_ACTION_NEXT = -8; + public static final int CODE_ACTION_PREVIOUS = -9; + public static final int CODE_LANGUAGE_SWITCH = -10; + public static final int CODE_RESEARCH = -11; + // Code value representing the code is not specified. + public static final int CODE_UNSPECIFIED = -12; + + public static boolean isLetterCode(final int code) { + return code >= CODE_SPACE; + } + + public static String printableCode(final int code) { + switch (code) { + case CODE_SHIFT: return "shift"; + case CODE_SWITCH_ALPHA_SYMBOL: return "symbol"; + case CODE_OUTPUT_TEXT: return "text"; + case CODE_DELETE: return "delete"; + case CODE_SETTINGS: return "settings"; + case CODE_SHORTCUT: return "shortcut"; + case CODE_ACTION_ENTER: return "actionEnter"; + case CODE_ACTION_NEXT: return "actionNext"; + case CODE_ACTION_PREVIOUS: return "actionPrevious"; + case CODE_LANGUAGE_SWITCH: return "languageSwitch"; + case CODE_UNSPECIFIED: return "unspec"; + case CODE_TAB: return "tab"; + case CODE_ENTER: return "enter"; + default: + if (code < CODE_SPACE) return String.format("'\\u%02x'", code); + if (code < 0x100) return String.format("'%c'", code); + return String.format("'\\u%04x'", code); + } + } + private Constants() { // This utility class is not publicly instantiable. } diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java index 5edc4314f..d1b32a2f3 100644 --- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java @@ -24,8 +24,6 @@ import android.provider.ContactsContract.Contacts; import android.text.TextUtils; import android.util.Log; -import com.android.inputmethod.keyboard.Keyboard; - import java.util.Locale; public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { @@ -62,7 +60,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { */ private final boolean mUseFirstLastBigrams; - public ContactsBinaryDictionary(final Context context, Locale locale) { + public ContactsBinaryDictionary(final Context context, final Locale locale) { super(context, getFilenameWithLocale(NAME, locale.toString()), Dictionary.TYPE_CONTACTS); mLocale = locale; mUseFirstLastBigrams = useFirstLastBigramsForLocale(locale); @@ -120,7 +118,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { } } - private boolean useFirstLastBigramsForLocale(Locale locale) { + private boolean useFirstLastBigramsForLocale(final Locale locale) { // TODO: Add firstname/lastname bigram rules for other languages. if (locale != null && locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) { return true; @@ -128,7 +126,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { return false; } - private void addWords(Cursor cursor) { + private void addWords(final Cursor cursor) { clearFusionDictionary(); int count = 0; while (!cursor.isAfterLast() && count < MAX_CONTACT_COUNT) { @@ -160,7 +158,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { * Adds the words in a name (e.g., firstname/lastname) to the binary dictionary along with their * bigrams depending on locale. */ - private void addName(String name) { + private void addName(final String name) { int len = StringUtils.codePointCount(name); String prevWord = null; // TODO: Better tokenization for non-Latin writing systems @@ -188,12 +186,13 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { /** * Returns the index of the last letter in the word, starting from position startIndex. */ - private static int getWordEndPosition(String string, int len, int startIndex) { + private static int getWordEndPosition(final String string, final int len, + final int startIndex) { int end; int cp = 0; for (end = startIndex + 1; end < len; end += Character.charCount(cp)) { cp = string.codePointAt(end); - if (!(cp == Keyboard.CODE_DASH || cp == Keyboard.CODE_SINGLE_QUOTE + if (!(cp == Constants.CODE_DASH || cp == Constants.CODE_SINGLE_QUOTE || Character.isLetter(cp))) { break; } @@ -249,7 +248,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { return false; } - private static boolean isValidName(String name) { + private static boolean isValidName(final String name) { if (name != null && -1 == name.indexOf('@')) { return true; } @@ -259,7 +258,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { /** * Checks if the words in a name are in the current binary dictionary. */ - private boolean isNameInDictionary(String name) { + private boolean isNameInDictionary(final String name) { int len = StringUtils.codePointCount(name); String prevWord = null; for (int i = 0; i < len; i++) { diff --git a/java/src/com/android/inputmethod/latin/CoordinateUtils.java b/java/src/com/android/inputmethod/latin/CoordinateUtils.java new file mode 100644 index 000000000..af270e1e4 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/CoordinateUtils.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin; + +public final class CoordinateUtils { + private static final int INDEX_X = 0; + private static final int INDEX_Y = 1; + private static final int ARRAY_SIZE = INDEX_Y + 1; + + private CoordinateUtils() { + // This utility class is not publicly instantiable. + } + + public static int[] newInstance() { + return new int[ARRAY_SIZE]; + } + + public static int x(final int[] coords) { + return coords[INDEX_X]; + } + + public static int y(final int[] coords) { + return coords[INDEX_Y]; + } + + public static void set(final int[] coords, final int x, final int y) { + coords[INDEX_X] = x; + coords[INDEX_Y] = y; + } + + public static void copy(final int[] destination, final int[] source) { + destination[INDEX_X] = source[INDEX_X]; + destination[INDEX_Y] = source[INDEX_Y]; + } +} diff --git a/java/src/com/android/inputmethod/latin/DebugSettings.java b/java/src/com/android/inputmethod/latin/DebugSettings.java index 3af3cab2c..f8aeb4e9f 100644 --- a/java/src/com/android/inputmethod/latin/DebugSettings.java +++ b/java/src/com/android/inputmethod/latin/DebugSettings.java @@ -37,9 +37,12 @@ public final class DebugSettings extends PreferenceFragment private static final String DEBUG_MODE_KEY = "debug_mode"; public static final String FORCE_NON_DISTINCT_MULTITOUCH_KEY = "force_non_distinct_multitouch"; public static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode"; + private static final String PREF_STATISTICS_LOGGING_KEY = "enable_logging"; + private static final boolean SHOW_STATISTICS_LOGGING = false; private boolean mServiceNeedsRestart = false; private CheckBoxPreference mDebugMode; + private CheckBoxPreference mStatisticsLoggingPref; @Override public void onCreate(Bundle icicle) { @@ -55,6 +58,13 @@ public final class DebugSettings extends PreferenceFragment ResearchLogger.DEFAULT_USABILITY_STUDY_MODE)); checkbox.setSummary(R.string.settings_warning_researcher_mode); } + final Preference statisticsLoggingPref = findPreference(PREF_STATISTICS_LOGGING_KEY); + if (statisticsLoggingPref instanceof CheckBoxPreference) { + mStatisticsLoggingPref = (CheckBoxPreference) statisticsLoggingPref; + if (!SHOW_STATISTICS_LOGGING) { + getPreferenceScreen().removePreference(statisticsLoggingPref); + } + } mServiceNeedsRestart = false; mDebugMode = (CheckBoxPreference) findPreference(DEBUG_MODE_KEY); @@ -72,6 +82,14 @@ public final class DebugSettings extends PreferenceFragment if (key.equals(DEBUG_MODE_KEY)) { if (mDebugMode != null) { mDebugMode.setChecked(prefs.getBoolean(DEBUG_MODE_KEY, false)); + final boolean checked = mDebugMode.isChecked(); + if (mStatisticsLoggingPref != null) { + if (checked) { + getPreferenceScreen().addPreference(mStatisticsLoggingPref); + } else { + getPreferenceScreen().removePreference(mStatisticsLoggingPref); + } + } updateDebugMode(); mServiceNeedsRestart = true; } diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java index 88d0c09dd..a218509f3 100644 --- a/java/src/com/android/inputmethod/latin/Dictionary.java +++ b/java/src/com/android/inputmethod/latin/Dictionary.java @@ -26,11 +26,6 @@ import java.util.ArrayList; * strokes. */ public abstract class Dictionary { - /** - * The weight to give to a word if it's length is the same as the number of typed characters. - */ - protected static final int FULL_WORD_SCORE_MULTIPLIER = 2; - public static final int NOT_A_PROBABILITY = -1; public static final String TYPE_USER_TYPED = "user_typed"; @@ -59,12 +54,12 @@ public abstract class Dictionary { // TODO: pass more context than just the previous word, to enable better suggestions (n-gram // and more) abstract public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, - final CharSequence prevWord, final ProximityInfo proximityInfo); + final String prevWord, final ProximityInfo proximityInfo); // The default implementation of this method ignores sessionId. // Subclasses that want to use sessionId need to override this method. public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer, - final CharSequence prevWord, final ProximityInfo proximityInfo, int sessionId) { + final String prevWord, final ProximityInfo proximityInfo, final int sessionId) { return getSuggestions(composer, prevWord, proximityInfo); } @@ -73,9 +68,9 @@ public abstract class Dictionary { * @param word the word to search for. The search should be case-insensitive. * @return true if the word exists, false otherwise */ - abstract public boolean isValidWord(CharSequence word); + abstract public boolean isValidWord(final String word); - public int getFrequency(CharSequence word) { + public int getFrequency(final String word) { return NOT_A_PROBABILITY; } @@ -87,7 +82,7 @@ public abstract class Dictionary { * @param typedWord the word to compare with * @return true if they are the same, false otherwise. */ - protected boolean same(final char[] word, final int length, final CharSequence typedWord) { + protected boolean same(final char[] word, final int length, final String typedWord) { if (typedWord.length() != length) { return false; } diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java index d3b120989..7f78ac8a2 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java +++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java @@ -56,7 +56,7 @@ public final class DictionaryCollection extends Dictionary { @Override public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, - final CharSequence prevWord, final ProximityInfo proximityInfo) { + final String prevWord, final ProximityInfo proximityInfo) { final CopyOnWriteArrayList<Dictionary> dictionaries = mDictionaries; if (dictionaries.isEmpty()) return null; // To avoid creating unnecessary objects, we get the list out of the first @@ -74,14 +74,14 @@ public final class DictionaryCollection extends Dictionary { } @Override - public boolean isValidWord(CharSequence word) { + public boolean isValidWord(final String word) { for (int i = mDictionaries.size() - 1; i >= 0; --i) if (mDictionaries.get(i).isValidWord(word)) return true; return false; } @Override - public int getFrequency(CharSequence word) { + public int getFrequency(final String word) { int maxFreq = -1; for (int i = mDictionaries.size() - 1; i >= 0; --i) { final int tempFreq = mDictionaries.get(i).getFrequency(word); diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java index b93c17f11..159867ade 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java @@ -198,7 +198,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { @Override public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, - final CharSequence prevWord, final ProximityInfo proximityInfo) { + final String prevWord, final ProximityInfo proximityInfo) { asyncReloadDictionaryIfRequired(); if (mLocalDictionaryController.tryLock()) { try { @@ -213,12 +213,12 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { } @Override - public boolean isValidWord(final CharSequence word) { + public boolean isValidWord(final String word) { asyncReloadDictionaryIfRequired(); return isValidWordInner(word); } - protected boolean isValidWordInner(final CharSequence word) { + protected boolean isValidWordInner(final String word) { if (mLocalDictionaryController.tryLock()) { try { return isValidWordLocked(word); @@ -229,17 +229,17 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { return false; } - protected boolean isValidWordLocked(final CharSequence word) { + protected boolean isValidWordLocked(final String word) { if (mBinaryDictionary == null) return false; return mBinaryDictionary.isValidWord(word); } - protected boolean isValidBigram(final CharSequence word1, final CharSequence word2) { + protected boolean isValidBigram(final String word1, final String word2) { if (mBinaryDictionary == null) return false; return mBinaryDictionary.isValidBigram(word1, word2); } - protected boolean isValidBigramInner(final CharSequence word1, final CharSequence word2) { + protected boolean isValidBigramInner(final String word1, final String word2) { if (mLocalDictionaryController.tryLock()) { try { return isValidBigramLocked(word1, word2); @@ -250,7 +250,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { return false; } - protected boolean isValidBigramLocked(final CharSequence word1, final CharSequence word2) { + protected boolean isValidBigramLocked(final String word1, final String word2) { if (mBinaryDictionary == null) return false; return mBinaryDictionary.isValidBigram(word1, word2); } diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java index 8cdc2a0af..fa0d5497b 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java @@ -19,7 +19,6 @@ package com.android.inputmethod.latin; import android.content.Context; import android.text.TextUtils; -import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.UserHistoryForgettingCurveUtils.ForgettingCurveParams; @@ -32,6 +31,10 @@ import java.util.LinkedList; * be searched for suggestions and valid words. */ public class ExpandableDictionary extends Dictionary { + /** + * The weight to give to a word if it's length is the same as the number of typed characters. + */ + private static final int FULL_WORD_SCORE_MULTIPLIER = 2; // Bigram frequency is a fixed point number with 1 meaning 1.2 and 255 meaning 1.8. protected static final int BIGRAM_MAX_FREQUENCY = 255; @@ -69,7 +72,7 @@ public class ExpandableDictionary extends Dictionary { mData = new Node[INCREMENT]; } - void add(Node n) { + void add(final Node n) { if (mLength + 1 > mData.length) { Node[] tempData = new Node[mLength + INCREMENT]; if (mLength > 0) { @@ -172,7 +175,7 @@ public class ExpandableDictionary extends Dictionary { } } - public void setRequiresReload(boolean reload) { + public void setRequiresReload(final boolean reload) { synchronized (mUpdatingLock) { mRequiresReload = reload; } @@ -202,8 +205,8 @@ public class ExpandableDictionary extends Dictionary { addWordRec(mRoots, word, 0, shortcutTarget, frequency, null); } - private void addWordRec(NodeArray children, final String word, final int depth, - final String shortcutTarget, final int frequency, Node parentNode) { + private void addWordRec(final NodeArray children, final String word, final int depth, + final String shortcutTarget, final int frequency, final Node parentNode) { final int wordLength = word.length(); if (wordLength <= depth) return; final char c = word.charAt(depth); @@ -248,7 +251,7 @@ public class ExpandableDictionary extends Dictionary { @Override public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, - final CharSequence prevWord, final ProximityInfo proximityInfo) { + final String prevWord, final ProximityInfo proximityInfo) { if (reloadDictionaryIfRequired()) return null; if (composer.size() > 1) { if (composer.size() >= BinaryDictionary.MAX_WORD_LENGTH) { @@ -267,8 +270,7 @@ public class ExpandableDictionary extends Dictionary { // This reloads the dictionary if required, and returns whether it's currently updating its // contents or not. - // @VisibleForTesting - boolean reloadDictionaryIfRequired() { + private boolean reloadDictionaryIfRequired() { synchronized (mUpdatingLock) { // If we need to update, start off a background task if (mRequiresReload) startDictionaryLoadingTaskLocked(); @@ -277,7 +279,7 @@ public class ExpandableDictionary extends Dictionary { } protected ArrayList<SuggestedWordInfo> getWordsInner(final WordComposer codes, - final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) { + final String prevWordForBigrams, final ProximityInfo proximityInfo) { final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList(); mInputLength = codes.size(); if (mCodes.length < mInputLength) mCodes = new int[mInputLength][]; @@ -305,7 +307,7 @@ public class ExpandableDictionary extends Dictionary { } @Override - public synchronized boolean isValidWord(CharSequence word) { + public synchronized boolean isValidWord(final String word) { synchronized (mUpdatingLock) { // If we need to update, start off a background task if (mRequiresReload) startDictionaryLoadingTaskLocked(); @@ -320,7 +322,7 @@ public class ExpandableDictionary extends Dictionary { return (node == null) ? false : !node.mShortcutOnly; } - protected boolean removeBigram(String word1, String word2) { + protected boolean removeBigram(final String word1, final String word2) { // Refer to addOrSetBigram() about word1.toLowerCase() final Node firstWord = searchWord(mRoots, word1.toLowerCase(), 0, null); final Node secondWord = searchWord(mRoots, word2, 0, null); @@ -345,13 +347,13 @@ public class ExpandableDictionary extends Dictionary { /** * Returns the word's frequency or -1 if not found */ - protected int getWordFrequency(CharSequence word) { + protected int getWordFrequency(final String word) { // Case-sensitive search final Node node = searchNode(mRoots, word, 0, word.length()); return (node == null) ? -1 : node.mFrequency; } - protected NextWord getBigramWord(String word1, String word2) { + protected NextWord getBigramWord(final String word1, final String word2) { // Refer to addOrSetBigram() about word1.toLowerCase() final Node firstWord = searchWord(mRoots, word1.toLowerCase(), 0, null); final Node secondWord = searchWord(mRoots, word2, 0, null); @@ -368,7 +370,8 @@ public class ExpandableDictionary extends Dictionary { return null; } - private static int computeSkippedWordFinalFreq(int freq, int snr, int inputLength) { + private static int computeSkippedWordFinalFreq(final int freq, final int snr, + final int inputLength) { // The computation itself makes sense for >= 2, but the == 2 case returns 0 // anyway so we may as well test against 3 instead and return the constant if (inputLength >= 3) { @@ -431,9 +434,9 @@ public class ExpandableDictionary extends Dictionary { * @param suggestions the list in which to add suggestions */ // TODO: Share this routine with the native code for BinaryDictionary - protected void getWordsRec(NodeArray roots, final WordComposer codes, final char[] word, - final int depth, final boolean completion, int snr, int inputIndex, int skipPos, - final ArrayList<SuggestedWordInfo> suggestions) { + protected void getWordsRec(final NodeArray roots, final WordComposer codes, final char[] word, + final int depth, final boolean completion, final int snr, final int inputIndex, + final int skipPos, final ArrayList<SuggestedWordInfo> suggestions) { final int count = roots.mLength; final int codeSize = mInputLength; // Optimization: Prune out words that are too long compared to how much was typed. @@ -472,8 +475,8 @@ public class ExpandableDictionary extends Dictionary { getWordsRec(children, codes, word, depth + 1, true, snr, inputIndex, skipPos, suggestions); } - } else if ((c == Keyboard.CODE_SINGLE_QUOTE - && currentChars[0] != Keyboard.CODE_SINGLE_QUOTE) || depth == skipPos) { + } else if ((c == Constants.CODE_SINGLE_QUOTE + && currentChars[0] != Constants.CODE_SINGLE_QUOTE) || depth == skipPos) { // Skip the ' and continue deeper word[depth] = c; if (children != null) { @@ -524,11 +527,13 @@ public class ExpandableDictionary extends Dictionary { } } - public int setBigramAndGetFrequency(String word1, String word2, int frequency) { + public int setBigramAndGetFrequency(final String word1, final String word2, + final int frequency) { return setBigramAndGetFrequency(word1, word2, frequency, null /* unused */); } - public int setBigramAndGetFrequency(String word1, String word2, ForgettingCurveParams fcp) { + public int setBigramAndGetFrequency(final String word1, final String word2, + final ForgettingCurveParams fcp) { return setBigramAndGetFrequency(word1, word2, 0 /* unused */, fcp); } @@ -540,8 +545,8 @@ public class ExpandableDictionary extends Dictionary { * @param fcp an instance of ForgettingCurveParams to use for decay policy * @return returns the final bigram frequency */ - private int setBigramAndGetFrequency( - String word1, String word2, int frequency, ForgettingCurveParams fcp) { + private int setBigramAndGetFrequency(final String word1, final String word2, + final int frequency, final ForgettingCurveParams fcp) { // We don't want results to be different according to case of the looked up left hand side // word. We do want however to return the correct case for the right hand side. // So we want to squash the case of the left hand side, and preserve that of the right @@ -572,7 +577,8 @@ public class ExpandableDictionary extends Dictionary { * Searches for the word and add the word if it does not exist. * @return Returns the terminal node of the word we are searching for. */ - private Node searchWord(NodeArray children, String word, int depth, Node parentNode) { + private Node searchWord(final NodeArray children, final String word, final int depth, + final Node parentNode) { final int wordLength = word.length(); final char c = word.charAt(depth); // Does children have the current character? @@ -602,36 +608,17 @@ public class ExpandableDictionary extends Dictionary { return searchWord(childNode.mChildren, word, depth + 1, childNode); } - private void runBigramReverseLookUp(final CharSequence previousWord, + private void runBigramReverseLookUp(final String previousWord, final ArrayList<SuggestedWordInfo> suggestions) { // Search for the lowercase version of the word only, because that's where bigrams // store their sons. - Node prevWord = searchNode(mRoots, previousWord.toString().toLowerCase(), 0, + final Node prevWord = searchNode(mRoots, previousWord.toLowerCase(), 0, previousWord.length()); if (prevWord != null && prevWord.mNGrams != null) { reverseLookUp(prevWord.mNGrams, suggestions); } } - /** - * Used for testing purposes and in the spell checker - * This function will wait for loading from database to be done - */ - void waitForDictionaryLoading() { - while (mUpdatingDictionary) { - try { - Thread.sleep(100); - } catch (InterruptedException e) { - // - } - } - } - - protected final void blockingReloadDictionaryIfRequired() { - reloadDictionaryIfRequired(); - waitForDictionaryLoading(); - } - // Local to reverseLookUp, but do not allocate each time. private final char[] mLookedUpString = new char[BinaryDictionary.MAX_WORD_LENGTH]; @@ -641,7 +628,7 @@ public class ExpandableDictionary extends Dictionary { * @param terminalNodes list of terminal nodes we want to add * @param suggestions the suggestion collection to add the word to */ - private void reverseLookUp(LinkedList<NextWord> terminalNodes, + private void reverseLookUp(final LinkedList<NextWord> terminalNodes, final ArrayList<SuggestedWordInfo> suggestions) { Node node; int freq; @@ -714,7 +701,7 @@ public class ExpandableDictionary extends Dictionary { } } - private static char toLowerCase(char c) { + private static char toLowerCase(final char c) { char baseChar = c; if (c < BASE_CHARS.length) { baseChar = BASE_CHARS[c]; diff --git a/java/src/com/android/inputmethod/latin/ImfUtils.java b/java/src/com/android/inputmethod/latin/ImfUtils.java deleted file mode 100644 index 2674e4575..000000000 --- a/java/src/com/android/inputmethod/latin/ImfUtils.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.latin; - -import static com.android.inputmethod.latin.Constants.Subtype.KEYBOARD_MODE; - -import android.content.Context; -import android.view.inputmethod.InputMethodInfo; -import android.view.inputmethod.InputMethodManager; -import android.view.inputmethod.InputMethodSubtype; - -import java.util.Collections; -import java.util.List; - -/** - * Utility class for Input Method Framework - */ -public final class ImfUtils { - private ImfUtils() { - // This utility class is not publicly instantiable. - } - - private static InputMethodManager sInputMethodManager; - - public static InputMethodManager getInputMethodManager(Context context) { - if (sInputMethodManager == null) { - sInputMethodManager = (InputMethodManager)context.getSystemService( - Context.INPUT_METHOD_SERVICE); - } - return sInputMethodManager; - } - - private static InputMethodInfo sInputMethodInfoOfThisIme; - - public static InputMethodInfo getInputMethodInfoOfThisIme(Context context) { - if (sInputMethodInfoOfThisIme == null) { - final InputMethodManager imm = getInputMethodManager(context); - final String packageName = context.getPackageName(); - for (final InputMethodInfo imi : imm.getInputMethodList()) { - if (imi.getPackageName().equals(packageName)) - return imi; - } - throw new RuntimeException("Can not find input method id for " + packageName); - } - return sInputMethodInfoOfThisIme; - } - - public static String getInputMethodIdOfThisIme(Context context) { - return getInputMethodInfoOfThisIme(context).getId(); - } - - public static boolean checkIfSubtypeBelongsToThisImeAndEnabled(Context context, - InputMethodSubtype ims) { - final InputMethodInfo myImi = getInputMethodInfoOfThisIme(context); - final InputMethodManager imm = getInputMethodManager(context); - // TODO: Cache all subtypes of this IME for optimization - final List<InputMethodSubtype> subtypes = imm.getEnabledInputMethodSubtypeList(myImi, true); - for (final InputMethodSubtype subtype : subtypes) { - if (subtype.equals(ims)) { - return true; - } - } - return false; - } - - public static boolean checkIfSubtypeBelongsToThisIme(Context context, - InputMethodSubtype ims) { - final InputMethodInfo myImi = getInputMethodInfoOfThisIme(context); - final int count = myImi.getSubtypeCount(); - for (int i = 0; i < count; i++) { - final InputMethodSubtype subtype = myImi.getSubtypeAt(i); - if (subtype.equals(ims)) { - return true; - } - } - return false; - } - - public static InputMethodSubtype getCurrentInputMethodSubtype(Context context, - InputMethodSubtype defaultSubtype) { - final InputMethodManager imm = getInputMethodManager(context); - final InputMethodSubtype currentSubtype = imm.getCurrentInputMethodSubtype(); - return (currentSubtype != null) ? currentSubtype : defaultSubtype; - } - - public static boolean hasMultipleEnabledIMEsOrSubtypes(Context context, - final boolean shouldIncludeAuxiliarySubtypes) { - final InputMethodManager imm = getInputMethodManager(context); - final List<InputMethodInfo> enabledImis = imm.getEnabledInputMethodList(); - return hasMultipleEnabledSubtypes(context, shouldIncludeAuxiliarySubtypes, enabledImis); - } - - public static boolean hasMultipleEnabledSubtypesInThisIme(Context context, - final boolean shouldIncludeAuxiliarySubtypes) { - final InputMethodInfo myImi = getInputMethodInfoOfThisIme(context); - final List<InputMethodInfo> imiList = Collections.singletonList(myImi); - return hasMultipleEnabledSubtypes(context, shouldIncludeAuxiliarySubtypes, imiList); - } - - private static boolean hasMultipleEnabledSubtypes(Context context, - final boolean shouldIncludeAuxiliarySubtypes, List<InputMethodInfo> imiList) { - final InputMethodManager imm = getInputMethodManager(context); - - // Number of the filtered IMEs - int filteredImisCount = 0; - - for (InputMethodInfo imi : imiList) { - // We can return true immediately after we find two or more filtered IMEs. - if (filteredImisCount > 1) return true; - final List<InputMethodSubtype> subtypes = - imm.getEnabledInputMethodSubtypeList(imi, true); - // IMEs that have no subtypes should be counted. - if (subtypes.isEmpty()) { - ++filteredImisCount; - continue; - } - - int auxCount = 0; - for (InputMethodSubtype subtype : subtypes) { - if (subtype.isAuxiliary()) { - ++auxCount; - } - } - final int nonAuxCount = subtypes.size() - auxCount; - - // IMEs that have one or more non-auxiliary subtypes should be counted. - // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary - // subtypes should be counted as well. - if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) { - ++filteredImisCount; - continue; - } - } - - if (filteredImisCount > 1) { - return true; - } - final List<InputMethodSubtype> subtypes = imm.getEnabledInputMethodSubtypeList(null, true); - int keyboardCount = 0; - // imm.getEnabledInputMethodSubtypeList(null, true) will return the current IME's - // both explicitly and implicitly enabled input method subtype. - // (The current IME should be LatinIME.) - for (InputMethodSubtype subtype : subtypes) { - if (KEYBOARD_MODE.equals(subtype.getMode())) { - ++keyboardCount; - } - } - return keyboardCount > 1; - } - - public static InputMethodSubtype findSubtypeByLocaleAndKeyboardLayoutSet( - Context context, String localeString, String keyboardLayoutSetName) { - final InputMethodInfo imi = getInputMethodInfoOfThisIme(context); - final int count = imi.getSubtypeCount(); - for (int i = 0; i < count; i++) { - final InputMethodSubtype subtype = imi.getSubtypeAt(i); - final String layoutName = SubtypeLocale.getKeyboardLayoutSetName(subtype); - if (localeString.equals(subtype.getLocale()) - && keyboardLayoutSetName.equals(layoutName)) { - return subtype; - } - } - return null; - } - - public static void setAdditionalInputMethodSubtypes(Context context, - InputMethodSubtype[] subtypes) { - final InputMethodManager imm = getInputMethodManager(context); - final String imiId = getInputMethodIdOfThisIme(context); - imm.setAdditionalInputMethodSubtypes(imiId, subtypes); - } -} diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java index 422fc72d3..1c5bff383 100644 --- a/java/src/com/android/inputmethod/latin/InputAttributes.java +++ b/java/src/com/android/inputmethod/latin/InputAttributes.java @@ -117,36 +117,56 @@ public final class InputAttributes { if (inputClass == InputType.TYPE_CLASS_DATETIME) Log.i(TAG, " TYPE_CLASS_DATETIME"); Log.i(TAG, "Variation:"); - if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS)) - Log.i(TAG, " TYPE_TEXT_VARIATION_EMAIL_ADDRESS"); - if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT)) - Log.i(TAG, " TYPE_TEXT_VARIATION_EMAIL_SUBJECT"); - if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_FILTER)) - Log.i(TAG, " TYPE_TEXT_VARIATION_FILTER"); - if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE)) - Log.i(TAG, " TYPE_TEXT_VARIATION_LONG_MESSAGE"); - if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_NORMAL)) - Log.i(TAG, " TYPE_TEXT_VARIATION_NORMAL"); - if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_PASSWORD)) - Log.i(TAG, " TYPE_TEXT_VARIATION_PASSWORD"); - if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_PERSON_NAME)) - Log.i(TAG, " TYPE_TEXT_VARIATION_PERSON_NAME"); - if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_PHONETIC)) - Log.i(TAG, " TYPE_TEXT_VARIATION_PHONETIC"); - if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS)) - Log.i(TAG, " TYPE_TEXT_VARIATION_POSTAL_ADDRESS"); - if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE)) - Log.i(TAG, " TYPE_TEXT_VARIATION_SHORT_MESSAGE"); - if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_URI)) - Log.i(TAG, " TYPE_TEXT_VARIATION_URI"); - if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD)) - Log.i(TAG, " TYPE_TEXT_VARIATION_VISIBLE_PASSWORD"); - if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT)) - Log.i(TAG, " TYPE_TEXT_VARIATION_WEB_EDIT_TEXT"); - if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS)) - Log.i(TAG, " TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS"); - if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD)) - Log.i(TAG, " TYPE_TEXT_VARIATION_WEB_PASSWORD"); + switch (InputType.TYPE_MASK_VARIATION & inputType) { + case InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS: + Log.i(TAG, " TYPE_TEXT_VARIATION_EMAIL_ADDRESS"); + break; + case InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT: + Log.i(TAG, " TYPE_TEXT_VARIATION_EMAIL_SUBJECT"); + break; + case InputType.TYPE_TEXT_VARIATION_FILTER: + Log.i(TAG, " TYPE_TEXT_VARIATION_FILTER"); + break; + case InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE: + Log.i(TAG, " TYPE_TEXT_VARIATION_LONG_MESSAGE"); + break; + case InputType.TYPE_TEXT_VARIATION_NORMAL: + Log.i(TAG, " TYPE_TEXT_VARIATION_NORMAL"); + break; + case InputType.TYPE_TEXT_VARIATION_PASSWORD: + Log.i(TAG, " TYPE_TEXT_VARIATION_PASSWORD"); + break; + case InputType.TYPE_TEXT_VARIATION_PERSON_NAME: + Log.i(TAG, " TYPE_TEXT_VARIATION_PERSON_NAME"); + break; + case InputType.TYPE_TEXT_VARIATION_PHONETIC: + Log.i(TAG, " TYPE_TEXT_VARIATION_PHONETIC"); + break; + case InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS: + Log.i(TAG, " TYPE_TEXT_VARIATION_POSTAL_ADDRESS"); + break; + case InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE: + Log.i(TAG, " TYPE_TEXT_VARIATION_SHORT_MESSAGE"); + break; + case InputType.TYPE_TEXT_VARIATION_URI: + Log.i(TAG, " TYPE_TEXT_VARIATION_URI"); + break; + case InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD: + Log.i(TAG, " TYPE_TEXT_VARIATION_VISIBLE_PASSWORD"); + break; + case InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT: + Log.i(TAG, " TYPE_TEXT_VARIATION_WEB_EDIT_TEXT"); + break; + case InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS: + Log.i(TAG, " TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS"); + break; + case InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD: + Log.i(TAG, " TYPE_TEXT_VARIATION_WEB_PASSWORD"); + break; + default: + Log.i(TAG, " Unknown variation"); + break; + } Log.i(TAG, "Flags:"); if (0 != (inputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS)) Log.i(TAG, " TYPE_TEXT_FLAG_NO_SUGGESTIONS"); diff --git a/java/src/com/android/inputmethod/latin/InputPointers.java b/java/src/com/android/inputmethod/latin/InputPointers.java index 6b48aabb3..7dffd96dd 100644 --- a/java/src/com/android/inputmethod/latin/InputPointers.java +++ b/java/src/com/android/inputmethod/latin/InputPointers.java @@ -16,6 +16,8 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.annotations.UsedForTesting; + // TODO: This class is not thread-safe. public final class InputPointers { private final int mDefaultCapacity; @@ -39,7 +41,8 @@ public final class InputPointers { mTimes.add(index, time); } - public void addPointer(int x, int y, int pointerId, int time) { + @UsedForTesting + void addPointer(int x, int y, int pointerId, int time) { mXCoordinates.add(x); mYCoordinates.add(y); mPointerIds.add(pointerId); @@ -66,7 +69,8 @@ public final class InputPointers { * @param startPos the starting index of the pointers in {@code src}. * @param length the number of pointers to be appended. */ - public void append(InputPointers src, int startPos, int length) { + @UsedForTesting + void append(InputPointers src, int startPos, int length) { if (length == 0) { return; } diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java index 94cdc9b85..44ef01204 100644 --- a/java/src/com/android/inputmethod/latin/LastComposedWord.java +++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java @@ -44,7 +44,7 @@ public final class LastComposedWord { public final String mTypedWord; public final String mCommittedWord; public final String mSeparatorString; - public final CharSequence mPrevWord; + public final String mPrevWord; public final InputPointers mInputPointers = new InputPointers(BinaryDictionary.MAX_WORD_LENGTH); private boolean mActive; @@ -56,7 +56,7 @@ public final class LastComposedWord { // immutable. Do not fiddle with their contents after you passed them to this constructor. public LastComposedWord(final int[] primaryKeyCodes, final InputPointers inputPointers, final String typedWord, final String committedWord, - final String separatorString, final CharSequence prevWord) { + final String separatorString, final String prevWord) { mPrimaryKeyCodes = primaryKeyCodes; if (inputPointers != null) { mInputPointers.copy(inputPointers); diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index e2a76a456..6a198000a 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -60,8 +60,8 @@ import android.view.inputmethod.InputMethodSubtype; import com.android.inputmethod.accessibility.AccessibilityUtils; import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.compat.CompatUtils; -import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; import com.android.inputmethod.compat.InputMethodServiceCompatUtils; import com.android.inputmethod.compat.SuggestionSpanUtils; import com.android.inputmethod.keyboard.KeyDetector; @@ -132,14 +132,14 @@ public final class LatinIME extends InputMethodService implements KeyboardAction private View mKeyPreviewBackingView; private View mSuggestionsContainer; private SuggestionStripView mSuggestionStripView; - /* package for tests */ Suggest mSuggest; + @UsedForTesting Suggest mSuggest; private CompletionInfo[] mApplicationSpecifiedCompletions; private ApplicationInfo mTargetApplicationInfo; - private InputMethodManagerCompatWrapper mImm; + private RichInputMethodManager mRichImm; private Resources mResources; private SharedPreferences mPrefs; - /* package for tests */ final KeyboardSwitcher mKeyboardSwitcher; + @UsedForTesting final KeyboardSwitcher mKeyboardSwitcher; private final SubtypeSwitcher mSubtypeSwitcher; private final SubtypeState mSubtypeState = new SubtypeState(); @@ -165,17 +165,17 @@ public final class LatinIME extends InputMethodService implements KeyboardAction private int mDeleteCount; private long mLastKeyTime; - private AudioAndHapticFeedbackManager mFeedbackManager; - // Member variables for remembering the current device orientation. private int mDisplayOrientation; // Object for reacting to adding/removing a dictionary pack. + // TODO: The experimental version is not supported by the Dictionary Pack Service yet. private BroadcastReceiver mDictionaryPackInstallReceiver = - new DictionaryPackInstallBroadcastReceiver(this); + ProductionFlag.IS_EXPERIMENTAL + ? null : new DictionaryPackInstallBroadcastReceiver(this); // Keeps track of most recently inserted text (multi-character key) for reverting - private CharSequence mEnteredText; + private String mEnteredText; private boolean mIsAutoCorrectionIndicatorOn; @@ -195,8 +195,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction private int mDelayUpdateSuggestions; private int mDelayUpdateShiftState; - private long mDoubleSpacesTurnIntoPeriodTimeout; - private long mDoubleSpaceTimerStart; + private long mDoubleSpacePeriodTimeout; + private long mDoubleSpacePeriodTimerStart; public UIHandler(final LatinIME outerInstance) { super(outerInstance); @@ -208,8 +208,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction res.getInteger(R.integer.config_delay_update_suggestions); mDelayUpdateShiftState = res.getInteger(R.integer.config_delay_update_shift_state); - mDoubleSpacesTurnIntoPeriodTimeout = res.getInteger( - R.integer.config_double_spaces_turn_into_period_timeout); + mDoubleSpacePeriodTimeout = + res.getInteger(R.integer.config_double_space_period_timeout); } @Override @@ -260,17 +260,17 @@ public final class LatinIME extends InputMethodService implements KeyboardAction .sendToTarget(); } - public void startDoubleSpacesTimer() { - mDoubleSpaceTimerStart = SystemClock.uptimeMillis(); + public void startDoubleSpacePeriodTimer() { + mDoubleSpacePeriodTimerStart = SystemClock.uptimeMillis(); } - public void cancelDoubleSpacesTimer() { - mDoubleSpaceTimerStart = 0; + public void cancelDoubleSpacePeriodTimer() { + mDoubleSpacePeriodTimerStart = 0; } - public boolean isAcceptingDoubleSpaces() { - return SystemClock.uptimeMillis() - mDoubleSpaceTimerStart - < mDoubleSpacesTurnIntoPeriodTimeout; + public boolean isAcceptingDoubleSpacePeriod() { + return SystemClock.uptimeMillis() - mDoubleSpacePeriodTimerStart + < mDoubleSpacePeriodTimeout; } // Working variables for the following methods. @@ -375,9 +375,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction mCurrentSubtypeUsed = true; } - public void switchSubtype(final IBinder token, final InputMethodManagerCompatWrapper imm, - final Context context) { - final InputMethodSubtype currentSubtype = imm.getCurrentInputMethodSubtype(); + public void switchSubtype(final IBinder token, final RichInputMethodManager richImm) { + final InputMethodSubtype currentSubtype = richImm.getInputMethodManager() + .getCurrentInputMethodSubtype(); final InputMethodSubtype lastActiveSubtype = mLastActiveSubtype; final boolean currentSubtypeUsed = mCurrentSubtypeUsed; if (currentSubtypeUsed) { @@ -385,13 +385,12 @@ public final class LatinIME extends InputMethodService implements KeyboardAction mCurrentSubtypeUsed = false; } if (currentSubtypeUsed - && ImfUtils.checkIfSubtypeBelongsToThisImeAndEnabled(context, lastActiveSubtype) + && richImm.checkIfSubtypeBelongsToThisImeAndEnabled(lastActiveSubtype) && !currentSubtype.equals(lastActiveSubtype)) { - final String id = ImfUtils.getInputMethodIdOfThisIme(context); - imm.setInputMethodAndSubtype(token, id, lastActiveSubtype); + richImm.setInputMethodAndSubtype(token, lastActiveSubtype); return; } - imm.switchToNextInputMethod(token, true /* onlyCurrentIme */); + richImm.switchToNextInputMethod(token, true /* onlyCurrentIme */); } } @@ -406,33 +405,28 @@ public final class LatinIME extends InputMethodService implements KeyboardAction @Override public void onCreate() { - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - mPrefs = prefs; - LatinImeLogger.init(this, prefs); + mPrefs = PreferenceManager.getDefaultSharedPreferences(this); + mResources = getResources(); + + LatinImeLogger.init(this, mPrefs); if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.getInstance().init(this, prefs); + ResearchLogger.getInstance().init(this, mPrefs); } - InputMethodManagerCompatWrapper.init(this); + RichInputMethodManager.init(this, mPrefs); + mRichImm = RichInputMethodManager.getInstance(); SubtypeSwitcher.init(this); - KeyboardSwitcher.init(this, prefs); + KeyboardSwitcher.init(this, mPrefs); AccessibilityUtils.init(this); super.onCreate(); - mImm = InputMethodManagerCompatWrapper.getInstance(); mHandler.onCreate(); DEBUG = LatinImeLogger.sDBG; - final Resources res = getResources(); - mResources = res; - loadSettings(); - - ImfUtils.setAdditionalInputMethodSubtypes(this, mCurrentSettings.getAdditionalSubtypes()); - initSuggest(); - mDisplayOrientation = res.getConfiguration().orientation; + mDisplayOrientation = mResources.getConfiguration().orientation; // Register to receive ringer mode change and network state change. // Also receive installation and removal of a dictionary pack. @@ -441,20 +435,23 @@ public final class LatinIME extends InputMethodService implements KeyboardAction filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); registerReceiver(mReceiver, filter); - final IntentFilter packageFilter = new IntentFilter(); - packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); - packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); - packageFilter.addDataScheme(SCHEME_PACKAGE); - registerReceiver(mDictionaryPackInstallReceiver, packageFilter); + // TODO: The experimental version is not supported by the Dictionary Pack Service yet. + if (!ProductionFlag.IS_EXPERIMENTAL) { + final IntentFilter packageFilter = new IntentFilter(); + packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + packageFilter.addDataScheme(SCHEME_PACKAGE); + registerReceiver(mDictionaryPackInstallReceiver, packageFilter); - final IntentFilter newDictFilter = new IntentFilter(); - newDictFilter.addAction( - DictionaryPackInstallBroadcastReceiver.NEW_DICTIONARY_INTENT_ACTION); - registerReceiver(mDictionaryPackInstallReceiver, newDictFilter); + final IntentFilter newDictFilter = new IntentFilter(); + newDictFilter.addAction( + DictionaryPackInstallBroadcastReceiver.NEW_DICTIONARY_INTENT_ACTION); + registerReceiver(mDictionaryPackInstallReceiver, newDictFilter); + } } // Has to be package-visible for unit tests - /* package for test */ + @UsedForTesting void loadSettings() { // Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged() // is not guaranteed. It may even be called at the same time on a different thread. @@ -468,7 +465,6 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } }; mCurrentSettings = job.runInLocale(mResources, mSubtypeSwitcher.getCurrentSubtypeLocale()); - mFeedbackManager = new AudioAndHapticFeedbackManager(this, mCurrentSettings); resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary()); } @@ -571,7 +567,10 @@ public final class LatinIME extends InputMethodService implements KeyboardAction mSuggest = null; } unregisterReceiver(mReceiver); - unregisterReceiver(mDictionaryPackInstallReceiver); + // TODO: The experimental version is not supported by the Dictionary Pack Service yet. + if (!ProductionFlag.IS_EXPERIMENTAL) { + unregisterReceiver(mDictionaryPackInstallReceiver); + } LatinImeLogger.commit(); LatinImeLogger.onDestroy(); super.onDestroy(); @@ -580,7 +579,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction @Override public void onConfigurationChanged(final Configuration conf) { // System locale has been changed. Needs to reload keyboard. - if (mSubtypeSwitcher.onConfigurationChanged(conf, this)) { + if (mSubtypeSwitcher.onConfigurationChanged(conf)) { loadKeyboard(); } // If orientation changed while predicting, commit the change @@ -720,8 +719,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction .updateParametersOnStartInputViewAndReturnIfCurrentSubtypeEnabled(); if (!currentSubtypeEnabled) { // Current subtype is disabled. Needs to update subtype and keyboard. - final InputMethodSubtype newSubtype = ImfUtils.getCurrentInputMethodSubtype( - this, mSubtypeSwitcher.getNoLanguageSubtype()); + final InputMethodSubtype newSubtype = mRichImm.getCurrentInputMethodSubtype( + mSubtypeSwitcher.getNoLanguageSubtype()); mSubtypeSwitcher.updateSubtype(newSubtype); loadKeyboard(); } @@ -773,7 +772,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction mLastSelectionEnd = editorInfo.initialSelEnd; mHandler.cancelUpdateSuggestionStrip(); - mHandler.cancelDoubleSpacesTimer(); + mHandler.cancelDoubleSpacePeriodTimer(); mainKeyboardView.setMainDictionaryAvailability(mIsMainDictionaryAvailable); mainKeyboardView.setKeyPreviewPopupEnabled(mCurrentSettings.mKeyPreviewPopupOn, @@ -840,6 +839,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } // Remove pending messages related to update suggestions mHandler.cancelUpdateSuggestionStrip(); + resetComposingState(true /* alsoResetLastComposedWord */); } @Override @@ -1080,12 +1080,13 @@ public final class LatinIME extends InputMethodService implements KeyboardAction final int suggestionsHeight = (mSuggestionsContainer.getVisibility() == View.GONE) ? 0 : mSuggestionsContainer.getHeight(); final int extraHeight = extractHeight + backingHeight + suggestionsHeight; - int touchY = extraHeight; + int visibleTopY = extraHeight; // Need to set touchable region only if input view is being shown if (mainKeyboardView.isShown()) { if (mSuggestionsContainer.getVisibility() == View.VISIBLE) { - touchY -= suggestionsHeight; + visibleTopY -= suggestionsHeight; } + final int touchY = mainKeyboardView.isShowingMoreKeysPanel() ? 0 : visibleTopY; final int touchWidth = mainKeyboardView.getWidth(); final int touchHeight = mainKeyboardView.getHeight() + extraHeight // Extend touchable region below the keyboard. @@ -1093,15 +1094,15 @@ public final class LatinIME extends InputMethodService implements KeyboardAction outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION; outInsets.touchableRegion.set(0, touchY, touchWidth, touchHeight); } - outInsets.contentTopInsets = touchY; - outInsets.visibleTopInsets = touchY; + outInsets.contentTopInsets = visibleTopY; + outInsets.visibleTopInsets = visibleTopY; } @Override public boolean onEvaluateFullscreenMode() { // Reread resource value here, because this method is called by framework anytime as needed. final boolean isFullscreenModeAllowed = - mCurrentSettings.isFullscreenModeAllowed(getResources()); + SettingsValues.isFullscreenModeAllowed(getResources()); if (super.onEvaluateFullscreenMode() && isFullscreenModeAllowed) { // TODO: Remove this hack. Actually we should not really assume NO_EXTRACT_UI // implies NO_FULLSCREEN. However, the framework mistakenly does. i.e. NO_EXTRACT_UI @@ -1144,7 +1145,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction private void commitTyped(final String separatorString) { if (!mWordComposer.isComposingWord()) return; - final CharSequence typedWord = mWordComposer.getTypedWord(); + final String typedWord = mWordComposer.getTypedWord(); if (typedWord.length() > 0) { commitChosenWord(typedWord, LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, separatorString); @@ -1181,7 +1182,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction CharSequence lastTwo = mConnection.getTextBeforeCursor(2, 0); // It is guaranteed lastTwo.charAt(1) is a swapper - else this method is not called. if (lastTwo != null && lastTwo.length() == 2 - && lastTwo.charAt(0) == Keyboard.CODE_SPACE) { + && lastTwo.charAt(0) == Constants.CODE_SPACE) { mConnection.deleteSurroundingText(2, 0); mConnection.commitText(lastTwo.charAt(1) + " ", 1); if (ProductionFlag.IS_EXPERIMENTAL) { @@ -1191,15 +1192,16 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } } - private boolean maybeDoubleSpace() { + private boolean maybeDoubleSpacePeriod() { if (!mCurrentSettings.mCorrectionEnabled) return false; - if (!mHandler.isAcceptingDoubleSpaces()) return false; + if (!mCurrentSettings.mUseDoubleSpacePeriod) return false; + if (!mHandler.isAcceptingDoubleSpacePeriod()) return false; final CharSequence lastThree = mConnection.getTextBeforeCursor(3, 0); if (lastThree != null && lastThree.length() == 3 - && canBeFollowedByPeriod(lastThree.charAt(0)) - && lastThree.charAt(1) == Keyboard.CODE_SPACE - && lastThree.charAt(2) == Keyboard.CODE_SPACE) { - mHandler.cancelDoubleSpacesTimer(); + && canBeFollowedByDoubleSpacePeriod(lastThree.charAt(0)) + && lastThree.charAt(1) == Constants.CODE_SPACE + && lastThree.charAt(2) == Constants.CODE_SPACE) { + mHandler.cancelDoubleSpacePeriodTimer(); mConnection.deleteSurroundingText(2, 0); mConnection.commitText(". ", 1); mKeyboardSwitcher.updateShiftState(); @@ -1208,16 +1210,16 @@ public final class LatinIME extends InputMethodService implements KeyboardAction return false; } - private static boolean canBeFollowedByPeriod(final int codePoint) { + private static boolean canBeFollowedByDoubleSpacePeriod(final int codePoint) { // TODO: Check again whether there really ain't a better way to check this. // TODO: This should probably be language-dependant... return Character.isLetterOrDigit(codePoint) - || codePoint == Keyboard.CODE_SINGLE_QUOTE - || codePoint == Keyboard.CODE_DOUBLE_QUOTE - || codePoint == Keyboard.CODE_CLOSING_PARENTHESIS - || codePoint == Keyboard.CODE_CLOSING_SQUARE_BRACKET - || codePoint == Keyboard.CODE_CLOSING_CURLY_BRACKET - || codePoint == Keyboard.CODE_CLOSING_ANGLE_BRACKET; + || codePoint == Constants.CODE_SINGLE_QUOTE + || codePoint == Constants.CODE_DOUBLE_QUOTE + || codePoint == Constants.CODE_CLOSING_PARENTHESIS + || codePoint == Constants.CODE_CLOSING_SQUARE_BRACKET + || codePoint == Constants.CODE_CLOSING_CURLY_BRACKET + || codePoint == Constants.CODE_CLOSING_ANGLE_BRACKET; } // Callback for the {@link SuggestionStripView}, to call when the "add to dictionary" hint is @@ -1267,9 +1269,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction if (isShowingOptionDialog()) return false; switch (requestCode) { case CODE_SHOW_INPUT_METHOD_PICKER: - if (ImfUtils.hasMultipleEnabledIMEsOrSubtypes( - this, true /* include aux subtypes */)) { - mImm.showInputMethodPicker(); + if (mRichImm.hasMultipleEnabledIMEsOrSubtypes(true /* include aux subtypes */)) { + mRichImm.getInputMethodManager().showInputMethodPicker(); return true; } return false; @@ -1293,10 +1294,10 @@ public final class LatinIME extends InputMethodService implements KeyboardAction private void handleLanguageSwitchKey() { final IBinder token = getWindow().getWindow().getAttributes().token; if (mCurrentSettings.mIncludesOtherImesInLanguageSwitchList) { - mImm.switchToNextInputMethod(token, false /* onlyCurrentIme */); + mRichImm.switchToNextInputMethod(token, false /* onlyCurrentIme */); return; } - mSubtypeState.switchSubtype(token, mImm, this); + mSubtypeState.switchSubtype(token, mRichImm); } private void sendDownUpKeyEventForBackwardCompatibility(final int code) { @@ -1322,7 +1323,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // 16 is android.os.Build.VERSION_CODES.JELLY_BEAN but we can't write it because // we want to be able to compile against the Ice Cream Sandwich SDK. - if (Keyboard.CODE_ENTER == code && mTargetApplicationInfo != null + if (Constants.CODE_ENTER == code && mTargetApplicationInfo != null && mTargetApplicationInfo.targetSdkVersion < 16) { // Backward compatibility mode. Before Jelly bean, the keyboard would simulate // a hardware keyboard event on pressing enter or delete. This is bad for many @@ -1339,7 +1340,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction @Override public void onCodeInput(final int primaryCode, final int x, final int y) { final long when = SystemClock.uptimeMillis(); - if (primaryCode != Keyboard.CODE_DELETE || when > mLastKeyTime + QUICK_PRESS) { + if (primaryCode != Constants.CODE_DELETE || when > mLastKeyTime + QUICK_PRESS) { mDeleteCount = 0; } mLastKeyTime = when; @@ -1353,43 +1354,43 @@ public final class LatinIME extends InputMethodService implements KeyboardAction final int spaceState = mSpaceState; if (!mWordComposer.isComposingWord()) mIsAutoCorrectionIndicatorOn = false; - // TODO: Consolidate the double space timer, mLastKeyTime, and the space state. - if (primaryCode != Keyboard.CODE_SPACE) { - mHandler.cancelDoubleSpacesTimer(); + // TODO: Consolidate the double-space period timer, mLastKeyTime, and the space state. + if (primaryCode != Constants.CODE_SPACE) { + mHandler.cancelDoubleSpacePeriodTimer(); } boolean didAutoCorrect = false; switch (primaryCode) { - case Keyboard.CODE_DELETE: + case Constants.CODE_DELETE: mSpaceState = SPACE_STATE_NONE; handleBackspace(spaceState); mDeleteCount++; mExpectingUpdateSelection = true; LatinImeLogger.logOnDelete(x, y); break; - case Keyboard.CODE_SHIFT: - case Keyboard.CODE_SWITCH_ALPHA_SYMBOL: + case Constants.CODE_SHIFT: + case Constants.CODE_SWITCH_ALPHA_SYMBOL: // Shift and symbol key is handled in onPressKey() and onReleaseKey(). break; - case Keyboard.CODE_SETTINGS: + case Constants.CODE_SETTINGS: onSettingsKeyPressed(); break; - case Keyboard.CODE_SHORTCUT: + case Constants.CODE_SHORTCUT: mSubtypeSwitcher.switchToShortcutIME(this); break; - case Keyboard.CODE_ACTION_ENTER: + case Constants.CODE_ACTION_ENTER: performEditorAction(getActionId(switcher.getKeyboard())); break; - case Keyboard.CODE_ACTION_NEXT: + case Constants.CODE_ACTION_NEXT: performEditorAction(EditorInfo.IME_ACTION_NEXT); break; - case Keyboard.CODE_ACTION_PREVIOUS: + case Constants.CODE_ACTION_PREVIOUS: performEditorAction(EditorInfo.IME_ACTION_PREVIOUS); break; - case Keyboard.CODE_LANGUAGE_SWITCH: + case Constants.CODE_LANGUAGE_SWITCH: handleLanguageSwitchKey(); break; - case Keyboard.CODE_RESEARCH: + case Constants.CODE_RESEARCH: if (ProductionFlag.IS_EXPERIMENTAL) { ResearchLogger.getInstance().onResearchKeySelected(this); } @@ -1424,10 +1425,10 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } switcher.onCodeInput(primaryCode); // Reset after any single keystroke, except shift and symbol-shift - if (!didAutoCorrect && primaryCode != Keyboard.CODE_SHIFT - && primaryCode != Keyboard.CODE_SWITCH_ALPHA_SYMBOL) + if (!didAutoCorrect && primaryCode != Constants.CODE_SHIFT + && primaryCode != Constants.CODE_SWITCH_ALPHA_SYMBOL) mLastComposedWord.deactivate(); - if (Keyboard.CODE_DELETE != primaryCode) { + if (Constants.CODE_DELETE != primaryCode) { mEnteredText = null; } mConnection.endBatchEdit(); @@ -1438,7 +1439,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // Called from PointerTracker through the KeyboardActionListener interface @Override - public void onTextInput(final CharSequence rawText) { + public void onTextInput(final String rawText) { mConnection.beginBatchEdit(); if (mWordComposer.isComposingWord()) { commitCurrentAutoCorrection(rawText.toString()); @@ -1446,7 +1447,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction resetComposingState(true /* alsoResetLastComposedWord */); } mHandler.postUpdateSuggestionStrip(); - final CharSequence text = specificTldProcessingOnTextInput(rawText); + final String text = specificTldProcessingOnTextInput(rawText); if (SPACE_STATE_PHANTOM == mSpaceState) { promotePhantomSpace(); } @@ -1455,7 +1456,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // Space state must be updated before calling updateShiftState mSpaceState = SPACE_STATE_NONE; mKeyboardSwitcher.updateShiftState(); - mKeyboardSwitcher.onCodeInput(Keyboard.CODE_OUTPUT_TEXT); + mKeyboardSwitcher.onCodeInput(Constants.CODE_OUTPUT_TEXT); mEnteredText = text; } @@ -1503,7 +1504,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction private static final class BatchInputUpdater implements Handler.Callback { private final Handler mHandler; private LatinIME mLatinIme; - private boolean mInBatchInput; // synchornized using "this". + private boolean mInBatchInput; // synchronized using "this". private BatchInputUpdater() { final HandlerThread handlerThread = new HandlerThread( @@ -1535,6 +1536,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // Run in the UI thread. public synchronized void onStartBatchInput() { + mHandler.removeMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP); mInBatchInput = true; } @@ -1542,7 +1544,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction private synchronized void updateBatchInput(final InputPointers batchPointers, final LatinIME latinIme) { if (!mInBatchInput) { - // Batch input has ended while the message was being delivered. + // Batch input has ended or canceled while the message was being delivered. return; } final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked( @@ -1562,6 +1564,12 @@ public final class LatinIME extends InputMethodService implements KeyboardAction .sendToTarget(); } + public synchronized void onCancelBatchInput(final LatinIME latinIme) { + mInBatchInput = false; + latinIme.mHandler.showGesturePreviewAndSuggestionStrip( + SuggestedWords.EMPTY, true /* dismissGestureFloatingPreviewText */); + } + // Run in the UI thread. public synchronized SuggestedWords onEndBatchInput(final InputPointers batchPointers, final LatinIME latinIme) { @@ -1584,13 +1592,14 @@ public final class LatinIME extends InputMethodService implements KeyboardAction private void showGesturePreviewAndSuggestionStrip(final SuggestedWords suggestedWords, final boolean dismissGestureFloatingPreviewText) { - final String batchInputText = (suggestedWords.size() > 0) - ? suggestedWords.getWord(0) : null; - final KeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); - mainKeyboardView.showGestureFloatingPreviewText(batchInputText); showSuggestionStrip(suggestedWords, null); + final KeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); if (dismissGestureFloatingPreviewText) { mainKeyboardView.dismissGestureFloatingPreviewText(); + } else { + final String batchInputText = suggestedWords.isEmpty() + ? null : suggestedWords.getWord(0); + mainKeyboardView.showGestureFloatingPreviewText(batchInputText); } } @@ -1603,8 +1612,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction public void onEndBatchInput(final InputPointers batchPointers) { final SuggestedWords suggestedWords = BatchInputUpdater.getInstance().onEndBatchInput( batchPointers, this); - final String batchInputText = (suggestedWords.size() > 0) - ? suggestedWords.getWord(0) : null; + final String batchInputText = suggestedWords.isEmpty() + ? null : suggestedWords.getWord(0); if (TextUtils.isEmpty(batchInputText)) { return; } @@ -1621,8 +1630,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction mKeyboardSwitcher.updateShiftState(); } - private CharSequence specificTldProcessingOnTextInput(final CharSequence text) { - if (text.length() <= 1 || text.charAt(0) != Keyboard.CODE_PERIOD + private String specificTldProcessingOnTextInput(final String text) { + if (text.length() <= 1 || text.charAt(0) != Constants.CODE_PERIOD || !Character.isLetter(text.charAt(1))) { // Not a tld: do nothing. return text; @@ -1633,8 +1642,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // TODO: use getCodePointBeforeCursor instead to improve performance and simplify the code final CharSequence lastOne = mConnection.getTextBeforeCursor(1, 0); if (lastOne != null && lastOne.length() == 1 - && lastOne.charAt(0) == Keyboard.CODE_PERIOD) { - return text.subSequence(1, text.length()); + && lastOne.charAt(0) == Constants.CODE_PERIOD) { + return text.substring(1); } else { return text; } @@ -1647,6 +1656,11 @@ public final class LatinIME extends InputMethodService implements KeyboardAction mKeyboardSwitcher.onCancelInput(); } + @Override + public void onCancelBatchInput() { + BatchInputUpdater.getInstance().onCancelBatchInput(this); + } + private void handleBackspace(final int spaceState) { // In many cases, we may have to put the keyboard in auto-shift state again. However // we want to wait a few milliseconds before doing it to avoid the keyboard flashing @@ -1656,8 +1670,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction if (mWordComposer.isComposingWord()) { final int length = mWordComposer.size(); if (length > 0) { - // Immediately after a batch input. - if (SPACE_STATE_PHANTOM == spaceState) { + if (mWordComposer.isBatchMode()) { mWordComposer.reset(); } else { mWordComposer.deleteLast(); @@ -1688,7 +1701,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction return; } if (SPACE_STATE_DOUBLE == spaceState) { - mHandler.cancelDoubleSpacesTimer(); + mHandler.cancelDoubleSpacePeriodTimer(); if (mConnection.revertDoubleSpace()) { // No need to reset mSpaceState, it has already be done (that's why we // receive it as a parameter) @@ -1738,7 +1751,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction private boolean maybeStripSpace(final int code, final int spaceState, final boolean isFromSuggestionStrip) { - if (Keyboard.CODE_ENTER == code && SPACE_STATE_SWAP_PUNCTUATION == spaceState) { + if (Constants.CODE_ENTER == code && SPACE_STATE_SWAP_PUNCTUATION == spaceState) { mConnection.removeTrailingSpace(); return false; } else if ((SPACE_STATE_WEAK == spaceState @@ -1781,7 +1794,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // the character is a single quote. The idea here is, single quote is not a // separator and it should be treated as a normal character, except in the first // position where it should not start composing a word. - isComposingWord = (Keyboard.CODE_SINGLE_QUOTE != primaryCode); + isComposingWord = (Constants.CODE_SINGLE_QUOTE != primaryCode); // Here we don't need to reset the last composed word. It will be reset // when we commit this one, if we ever do; if on the other hand we backspace // it entirely and resume suggestions on the previous word, we'd like to still @@ -1790,15 +1803,14 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } if (isComposingWord) { final int keyX, keyY; - if (KeyboardActionListener.Adapter.isInvalidCoordinate(x) - || KeyboardActionListener.Adapter.isInvalidCoordinate(y)) { - keyX = x; - keyY = y; - } else { + if (Constants.isValidCoordinate(x) && Constants.isValidCoordinate(y)) { final KeyDetector keyDetector = mKeyboardSwitcher.getMainKeyboardView().getKeyDetector(); keyX = keyDetector.getTouchX(x); keyY = keyDetector.getTouchY(y); + } else { + keyX = x; + keyY = y; } mWordComposer.add(primaryCode, keyX, keyY); // If it's the first letter, make note of auto-caps state @@ -1849,16 +1861,16 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } sendKeyCodePoint(primaryCode); - if (Keyboard.CODE_SPACE == primaryCode) { + if (Constants.CODE_SPACE == primaryCode) { if (mCurrentSettings.isSuggestionsRequested(mDisplayOrientation)) { - if (maybeDoubleSpace()) { + if (maybeDoubleSpacePeriod()) { mSpaceState = SPACE_STATE_DOUBLE; } else if (!isShowingPunctuationList()) { mSpaceState = SPACE_STATE_WEAK; } } - mHandler.startDoubleSpacesTimer(); + mHandler.startDoubleSpacePeriodTimer(); if (!mConnection.isCursorTouchingWord(mCurrentSettings)) { mHandler.postUpdateSuggestionStrip(); } @@ -1894,7 +1906,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction return didAutoCorrect; } - private CharSequence getTextWithUnderline(final CharSequence text) { + private CharSequence getTextWithUnderline(final String text) { return mIsAutoCorrectionIndicatorOn ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(this, text) : text; @@ -1911,7 +1923,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // TODO: make this private // Outside LatinIME, only used by the test suite. - /* package for tests */ + @UsedForTesting boolean isShowingPunctuationList() { if (mSuggestionStripView == null) return false; return mCurrentSettings.mSuggestPuncList == mSuggestionStripView.getSuggestions(); @@ -1981,7 +1993,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction private SuggestedWords getSuggestedWords(final int sessionId) { final Keyboard keyboard = mKeyboardSwitcher.getKeyboard(); - if (keyboard == null) { + if (keyboard == null || mSuggest == null) { return SuggestedWords.EMPTY; } final String typedWord = mWordComposer.getTypedWord(); @@ -1989,7 +2001,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // whatever is *before* the half-committed word in the buffer, hence 2; if we aren't, we // should just skip whitespace if any, so 1. // TODO: this is slow (2-way IPC) - we should probably cache this instead. - final CharSequence prevWord = + final String prevWord = mConnection.getNthPreviousWord(mCurrentSettings.mWordSeparators, mWordComposer.isComposingWord() ? 2 : 1); final SuggestedWords suggestedWords = mSuggest.getSuggestedWords(mWordComposer, @@ -1998,7 +2010,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction return maybeRetrieveOlderSuggestions(typedWord, suggestedWords); } - private SuggestedWords maybeRetrieveOlderSuggestions(final CharSequence typedWord, + private SuggestedWords maybeRetrieveOlderSuggestions(final String typedWord, final SuggestedWords suggestedWords) { // TODO: consolidate this into getSuggestedWords // We update the suggestion strip only when we have some suggestions to show, i.e. when @@ -2028,21 +2040,16 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } } - private void showSuggestionStrip(final SuggestedWords suggestedWords, - final CharSequence typedWord) { - if (null == suggestedWords || suggestedWords.size() <= 0) { + private void showSuggestionStrip(final SuggestedWords suggestedWords, final String typedWord) { + if (suggestedWords.isEmpty()) { clearSuggestionStrip(); return; } - final CharSequence autoCorrection; - if (suggestedWords.size() > 0) { - if (suggestedWords.mWillAutoCorrect) { - autoCorrection = suggestedWords.getWord(1); - } else { - autoCorrection = typedWord; - } + final String autoCorrection; + if (suggestedWords.mWillAutoCorrect) { + autoCorrection = suggestedWords.getWord(1); } else { - autoCorrection = null; + autoCorrection = typedWord; } mWordComposer.setAutoCorrection(autoCorrection); final boolean isAutoCorrection = suggestedWords.willAutoCorrect(); @@ -2056,9 +2063,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction if (mHandler.hasPendingUpdateSuggestions()) { updateSuggestionStrip(); } - final CharSequence typedAutoCorrection = mWordComposer.getAutoCorrectionOrNull(); + final String typedAutoCorrection = mWordComposer.getAutoCorrectionOrNull(); final String typedWord = mWordComposer.getTypedWord(); - final CharSequence autoCorrection = (typedAutoCorrection != null) + final String autoCorrection = (typedAutoCorrection != null) ? typedAutoCorrection : typedWord; if (autoCorrection != null) { if (TextUtils.isEmpty(typedWord)) { @@ -2089,7 +2096,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // Called from {@link SuggestionStripView} through the {@link SuggestionStripView#Listener} // interface @Override - public void pickSuggestionManually(final int index, final CharSequence suggestion) { + public void pickSuggestionManually(final int index, final String suggestion) { final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions(); // If this is a punctuation picked from the suggestion strip, pass it to onCodeInput if (suggestion.length() == 1 && isShowingPunctuationList()) { @@ -2159,7 +2166,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction && !AutoCorrection.isValidWord(mSuggest.getUnigramDictionaries(), suggestion, true); if (ProductionFlag.IS_INTERNAL) { - Stats.onSeparator((char)Keyboard.CODE_SPACE, + Stats.onSeparator((char)Constants.CODE_SPACE, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); } if (showingAddToDictionaryHint && mIsUserDictionaryAvailable) { @@ -2174,13 +2181,13 @@ public final class LatinIME extends InputMethodService implements KeyboardAction /** * Commits the chosen word to the text field and saves it for later retrieval. */ - private void commitChosenWord(final CharSequence chosenWord, final int commitType, + private void commitChosenWord(final String chosenWord, final int commitType, final String separatorString) { final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions(); mConnection.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan( this, chosenWord, suggestedWords, mIsMainDictionaryAvailable), 1); // Add the word to the user history dictionary - final CharSequence prevWord = addToUserHistoryDictionary(chosenWord); + final String prevWord = addToUserHistoryDictionary(chosenWord); // TODO: figure out here if this is an auto-correct or if the best word is actually // what user typed. Note: currently this is done much later in // LastComposedWord#didCommitTypedWord by string equality of the remembered @@ -2199,7 +2206,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction setSuggestionStripShown(isSuggestionsStripVisible()); } - private CharSequence addToUserHistoryDictionary(final CharSequence suggestion) { + private String addToUserHistoryDictionary(final String suggestion) { if (TextUtils.isEmpty(suggestion)) return null; if (mSuggest == null) return null; @@ -2214,19 +2221,18 @@ public final class LatinIME extends InputMethodService implements KeyboardAction = mConnection.getNthPreviousWord(mCurrentSettings.mWordSeparators, 2); final String secondWord; if (mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps()) { - secondWord = suggestion.toString().toLowerCase( - mSubtypeSwitcher.getCurrentSubtypeLocale()); + secondWord = suggestion.toLowerCase(mSubtypeSwitcher.getCurrentSubtypeLocale()); } else { - secondWord = suggestion.toString(); + secondWord = suggestion; } // We demote unrecognized words (frequency < 0, below) by specifying them as "invalid". // We don't add words with 0-frequency (assuming they would be profanity etc.). final int maxFreq = AutoCorrection.getMaxFrequency( mSuggest.getUnigramDictionaries(), suggestion); if (maxFreq == 0) return null; - userHistoryDictionary.addToUserHistory(null == prevWord ? null : prevWord.toString(), - secondWord, maxFreq > 0); - return prevWord; + final String prevWordString = (null == prevWord) ? null : prevWord.toString(); + userHistoryDictionary.addToUserHistory(prevWordString, secondWord, maxFreq > 0); + return prevWordString; } return null; } @@ -2251,9 +2257,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } private void revertCommit() { - final CharSequence previousWord = mLastComposedWord.mPrevWord; + final String previousWord = mLastComposedWord.mPrevWord; final String originallyTypedWord = mLastComposedWord.mTypedWord; - final CharSequence committedWord = mLastComposedWord.mCommittedWord; + final String committedWord = mLastComposedWord.mCommittedWord; final int cancelLength = committedWord.length(); final int separatorLength = LastComposedWord.getSeparatorLength( mLastComposedWord.mSeparatorString); @@ -2263,9 +2269,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction if (mWordComposer.isComposingWord()) { throw new RuntimeException("revertCommit, but we are composing a word"); } - final String wordBeforeCursor = + final CharSequence wordBeforeCursor = mConnection.getTextBeforeCursor(deleteLength, 0) - .subSequence(0, cancelLength).toString(); + .subSequence(0, cancelLength); if (!TextUtils.equals(committedWord, wordBeforeCursor)) { throw new RuntimeException("revertCommit check failed: we thought we were " + "reverting \"" + committedWord @@ -2295,7 +2301,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // This essentially inserts a space, and that's it. public void promotePhantomSpace() { if (mCurrentSettings.shouldInsertSpacesAutomatically()) { - sendKeyCodePoint(Keyboard.CODE_SPACE); + sendKeyCodePoint(Constants.CODE_SPACE); } } @@ -2306,7 +2312,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // TODO: Make this private // Outside LatinIME, only used by the {@link InputTestsBase} test suite. - /* package for test */ + @UsedForTesting void loadKeyboard() { // When the device locale is changed in SetupWizard etc., this method may get called via // onConfigurationChanged before SoftInputWindow is shown. @@ -2322,13 +2328,6 @@ public final class LatinIME extends InputMethodService implements KeyboardAction mHandler.postUpdateSuggestionStrip(); } - // TODO: Remove this method from {@link LatinIME} and move {@link FeedbackManager} to - // {@link KeyboardSwitcher}. Called from KeyboardSwitcher - public void hapticAndAudioFeedback(final int primaryCode) { - mFeedbackManager.hapticAndAudioFeedback( - primaryCode, mKeyboardSwitcher.getMainKeyboardView()); - } - // Callback called by PointerTracker through the KeyboardActionListener. This is called when a // key is depressed; release matching call is onReleaseKey below. @Override @@ -2345,16 +2344,16 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // If accessibility is on, ensure the user receives keyboard state updates. if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { switch (primaryCode) { - case Keyboard.CODE_SHIFT: + case Constants.CODE_SHIFT: AccessibleKeyboardViewProxy.getInstance().notifyShiftState(); break; - case Keyboard.CODE_SWITCH_ALPHA_SYMBOL: + case Constants.CODE_SWITCH_ALPHA_SYMBOL: AccessibleKeyboardViewProxy.getInstance().notifySymbolsState(); break; } } - if (Keyboard.CODE_DELETE == primaryCode) { + if (Constants.CODE_DELETE == primaryCode) { // This is a stopgap solution to avoid leaving a high surrogate alone in a text view. // In the future, we need to deprecate deteleSurroundingText() and have a surrogate // pair-friendly way of deleting characters in InputConnection. @@ -2374,7 +2373,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { mSubtypeSwitcher.onNetworkStateChanged(intent); } else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) { - mFeedbackManager.onRingerModeChanged(); + mKeyboardSwitcher.onRingerModeChanged(); } } }; @@ -2411,7 +2410,6 @@ public final class LatinIME extends InputMethodService implements KeyboardAction getString(R.string.language_selection_title), getString(R.string.english_ime_settings), }; - final Context context = this; final DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface di, int position) { @@ -2419,7 +2417,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction switch (position) { case 0: Intent intent = CompatUtils.getInputLanguageSelectionIntent( - ImfUtils.getInputMethodIdOfThisIme(context), + mRichImm.getInputMethodIdOfThisIme(), Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | Intent.FLAG_ACTIVITY_CLEAR_TOP); diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index 3b732278b..d1d920698 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -26,7 +26,6 @@ import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; -import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.research.ResearchLogger; @@ -66,12 +65,6 @@ public final class RichInputConnection { * This contains the currently composing text, as LatinIME thinks the TextView is seeing it. */ private StringBuilder mComposingText = new StringBuilder(); - /** - * This is a one-character string containing the character after the cursor. Since LatinIME - * never touches it directly, it's never modified by any means other than re-reading from the - * TextView when the cursor position is changed by the user. - */ - private CharSequence mCharAfterTheCursor = ""; // A hint on how many characters to cache from the TextView. A good value of this is given by // how many characters we need to be able to almost always find the caps mode. private static final int DEFAULT_TEXT_CACHE_SIZE = 100; @@ -147,7 +140,6 @@ public final class RichInputConnection { mCommittedTextBeforeComposingText.setLength(0); final CharSequence textBeforeCursor = getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0); if (null != textBeforeCursor) mCommittedTextBeforeComposingText.append(textBeforeCursor); - mCharAfterTheCursor = getTextAfterCursor(1, 0); if (null != mIC) { mIC.finishComposingText(); if (ProductionFlag.IS_EXPERIMENTAL) { @@ -417,7 +409,8 @@ public final class RichInputConnection { if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); } - public CharSequence getNthPreviousWord(final String sentenceSeperators, final int n) { + @SuppressWarnings("unused") + public String getNthPreviousWord(final String sentenceSeperators, final int n) { mIC = mParent.getCurrentInputConnection(); if (null == mIC) return null; final CharSequence prev = mIC.getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0); @@ -485,19 +478,22 @@ public final class RichInputConnection { // (n = 2) "abc|" -> null // (n = 2) "abc |" -> null // (n = 2) "abc. def|" -> null - public static CharSequence getNthPreviousWord(final CharSequence prev, + public static String getNthPreviousWord(final CharSequence prev, final String sentenceSeperators, final int n) { if (prev == null) return null; - String[] w = spaceRegex.split(prev); + final String[] w = spaceRegex.split(prev); // If we can't find n words, or we found an empty word, return null. - if (w.length < n || w[w.length - n].length() <= 0) return null; + if (w.length < n) return null; + final String nthPrevWord = w[w.length - n]; + final int length = nthPrevWord.length(); + if (length <= 0) return null; // If ends in a separator, return null - char lastChar = w[w.length - n].charAt(w[w.length - n].length() - 1); + final char lastChar = nthPrevWord.charAt(length - 1); if (sentenceSeperators.contains(String.valueOf(lastChar))) return null; - return w[w.length - n]; + return nthPrevWord; } /** @@ -530,19 +526,20 @@ public final class RichInputConnection { * be included in the returned range * @return a range containing the text surrounding the cursor */ - public Range getWordRangeAtCursor(String sep, int additionalPrecedingWordsCount) { + public Range getWordRangeAtCursor(final String sep, final int additionalPrecedingWordsCount) { mIC = mParent.getCurrentInputConnection(); if (mIC == null || sep == null) { return null; } - CharSequence before = mIC.getTextBeforeCursor(1000, 0); - CharSequence after = mIC.getTextAfterCursor(1000, 0); + final CharSequence before = mIC.getTextBeforeCursor(1000, 0); + final CharSequence after = mIC.getTextAfterCursor(1000, 0); if (before == null || after == null) { return null; } // Going backward, alternate skipping non-separators and separators until enough words // have been read. + int count = additionalPrecedingWordsCount; int start = before.length(); boolean isStoppingAtWhitespace = true; // toggles to indicate what to stop at while (true) { // see comments below for why this is guaranteed to halt @@ -559,7 +556,7 @@ public final class RichInputConnection { // isStoppingAtWhitespace is true every other time through the loop, // so additionalPrecedingWordsCount is guaranteed to become < 0, which // guarantees outer loop termination - if (isStoppingAtWhitespace && (--additionalPrecedingWordsCount < 0)) { + if (isStoppingAtWhitespace && (--count < 0)) { break; // outer loop } isStoppingAtWhitespace = !isStoppingAtWhitespace; @@ -577,7 +574,7 @@ public final class RichInputConnection { } } - int cursor = getCursorPosition(); + final int cursor = getCursorPosition(); if (start >= 0 && cursor + end <= after.length() + before.length()) { String word = before.toString().substring(start, before.length()) + after.toString().substring(0, end); @@ -588,8 +585,8 @@ public final class RichInputConnection { } public boolean isCursorTouchingWord(final SettingsValues settingsValues) { - CharSequence before = getTextBeforeCursor(1, 0); - CharSequence after = getTextAfterCursor(1, 0); + final CharSequence before = getTextBeforeCursor(1, 0); + final CharSequence after = getTextAfterCursor(1, 0); if (!TextUtils.isEmpty(before) && !settingsValues.isWordSeparator(before.charAt(0)) && !settingsValues.isSymbolExcludedFromWordSeparators(before.charAt(0))) { return true; @@ -605,7 +602,7 @@ public final class RichInputConnection { if (DEBUG_BATCH_NESTING) checkBatchEdit(); final CharSequence lastOne = getTextBeforeCursor(1, 0); if (lastOne != null && lastOne.length() == 1 - && lastOne.charAt(0) == Keyboard.CODE_SPACE) { + && lastOne.charAt(0) == Constants.CODE_SPACE) { deleteSurroundingText(1, 0); } } @@ -631,7 +628,7 @@ public final class RichInputConnection { CharSequence word = getWordAtCursor(settings.mWordSeparators); // We don't suggest on leading single quotes, so we have to remove them from the word if // it starts with single quotes. - while (!TextUtils.isEmpty(word) && Keyboard.CODE_SINGLE_QUOTE == word.charAt(0)) { + while (!TextUtils.isEmpty(word) && Constants.CODE_SINGLE_QUOTE == word.charAt(0)) { word = word.subSequence(1, word.length()); } if (TextUtils.isEmpty(word)) return null; @@ -683,7 +680,7 @@ public final class RichInputConnection { // NOTE: This does not work with surrogate pairs. Hopefully when the keyboard is able to // enter surrogate pairs this code will have been removed. if (TextUtils.isEmpty(textBeforeCursor) - || (Keyboard.CODE_SPACE != textBeforeCursor.charAt(1))) { + || (Constants.CODE_SPACE != textBeforeCursor.charAt(1))) { // We may only come here if the application is changing the text while we are typing. // This is quite a broken case, but not logically impossible, so we shouldn't crash, // but some debugging log may be in order. diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java new file mode 100644 index 000000000..af0d61cc7 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin; + +import static com.android.inputmethod.latin.Constants.Subtype.KEYBOARD_MODE; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.IBinder; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.InputMethodSubtype; + +import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; + +import java.util.Collections; +import java.util.List; + +/** + * Enrichment class for InputMethodManager to simplify interaction and add functionality. + */ +public final class RichInputMethodManager { + private static final String TAG = RichInputMethodManager.class.getSimpleName(); + + private RichInputMethodManager() { + // This utility class is not publicly instantiable. + } + + private static final RichInputMethodManager sInstance = new RichInputMethodManager(); + + private InputMethodManagerCompatWrapper mImmWrapper; + private InputMethodInfo mInputMethodInfoOfThisIme; + + public static RichInputMethodManager getInstance() { + sInstance.checkInitialized(); + return sInstance; + } + + public static void init(final Context context, final SharedPreferences prefs) { + sInstance.initInternal(context, prefs); + } + + private boolean isInitialized() { + return mImmWrapper != null; + } + + private void checkInitialized() { + if (!isInitialized()) { + throw new RuntimeException(TAG + " is used before initialization"); + } + } + + private void initInternal(final Context context, final SharedPreferences prefs) { + if (isInitialized()) { + return; + } + mImmWrapper = new InputMethodManagerCompatWrapper(context); + mInputMethodInfoOfThisIme = getInputMethodInfoOfThisIme(context); + + // Initialize additional subtypes. + SubtypeLocale.init(context); + final String prefAdditionalSubtypes = SettingsValues.getPrefAdditionalSubtypes( + prefs, context.getResources()); + final InputMethodSubtype[] additionalSubtypes = + AdditionalSubtype.createAdditionalSubtypesArray(prefAdditionalSubtypes); + setAdditionalInputMethodSubtypes(additionalSubtypes); + } + + public InputMethodManager getInputMethodManager() { + checkInitialized(); + return mImmWrapper.mImm; + } + + private InputMethodInfo getInputMethodInfoOfThisIme(final Context context) { + final String packageName = context.getPackageName(); + for (final InputMethodInfo imi : mImmWrapper.mImm.getInputMethodList()) { + if (imi.getPackageName().equals(packageName)) { + return imi; + } + } + throw new RuntimeException("Input method id for " + packageName + " not found."); + } + + public boolean switchToNextInputMethod(final IBinder token, final boolean onlyCurrentIme) { + final boolean result = mImmWrapper.switchToNextInputMethod(token, onlyCurrentIme); + if (!result) { + mImmWrapper.mImm.switchToLastInputMethod(token); + return false; + } + return true; + } + + public InputMethodInfo getInputMethodInfoOfThisIme() { + return mInputMethodInfoOfThisIme; + } + + public String getInputMethodIdOfThisIme() { + return mInputMethodInfoOfThisIme.getId(); + } + + public boolean checkIfSubtypeBelongsToThisImeAndEnabled(final InputMethodSubtype subtype) { + return checkIfSubtypeBelongsToImeAndEnabled(mInputMethodInfoOfThisIme, subtype); + } + + public boolean checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled( + final InputMethodSubtype subtype) { + final boolean subtypeEnabled = checkIfSubtypeBelongsToThisImeAndEnabled(subtype); + final boolean subtypeExplicitlyEnabled = checkIfSubtypeBelongsToList( + subtype, mImmWrapper.mImm.getEnabledInputMethodSubtypeList( + mInputMethodInfoOfThisIme, false /* allowsImplicitlySelectedSubtypes */)); + return subtypeEnabled && !subtypeExplicitlyEnabled; + } + + public boolean checkIfSubtypeBelongsToImeAndEnabled(final InputMethodInfo imi, + final InputMethodSubtype subtype) { + return checkIfSubtypeBelongsToList( + subtype, mImmWrapper.mImm.getEnabledInputMethodSubtypeList( + imi, true /* allowsImplicitlySelectedSubtypes */)); + } + + private static boolean checkIfSubtypeBelongsToList(final InputMethodSubtype subtype, + final List<InputMethodSubtype> subtypes) { + for (final InputMethodSubtype ims : subtypes) { + if (ims.equals(subtype)) { + return true; + } + } + return false; + } + + public boolean checkIfSubtypeBelongsToThisIme(final InputMethodSubtype subtype) { + final InputMethodInfo myImi = mInputMethodInfoOfThisIme; + final int count = myImi.getSubtypeCount(); + for (int i = 0; i < count; i++) { + final InputMethodSubtype ims = myImi.getSubtypeAt(i); + if (ims.equals(subtype)) { + return true; + } + } + return false; + } + + public InputMethodSubtype getCurrentInputMethodSubtype( + final InputMethodSubtype defaultSubtype) { + final InputMethodSubtype currentSubtype = mImmWrapper.mImm.getCurrentInputMethodSubtype(); + return (currentSubtype != null) ? currentSubtype : defaultSubtype; + } + + public boolean hasMultipleEnabledIMEsOrSubtypes(final boolean shouldIncludeAuxiliarySubtypes) { + final List<InputMethodInfo> enabledImis = mImmWrapper.mImm.getEnabledInputMethodList(); + return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, enabledImis); + } + + public boolean hasMultipleEnabledSubtypesInThisIme( + final boolean shouldIncludeAuxiliarySubtypes) { + final List<InputMethodInfo> imiList = Collections.singletonList(mInputMethodInfoOfThisIme); + return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, imiList); + } + + private boolean hasMultipleEnabledSubtypes(final boolean shouldIncludeAuxiliarySubtypes, + final List<InputMethodInfo> imiList) { + // Number of the filtered IMEs + int filteredImisCount = 0; + + for (InputMethodInfo imi : imiList) { + // We can return true immediately after we find two or more filtered IMEs. + if (filteredImisCount > 1) return true; + final List<InputMethodSubtype> subtypes = + mImmWrapper.mImm.getEnabledInputMethodSubtypeList(imi, true); + // IMEs that have no subtypes should be counted. + if (subtypes.isEmpty()) { + ++filteredImisCount; + continue; + } + + int auxCount = 0; + for (InputMethodSubtype subtype : subtypes) { + if (subtype.isAuxiliary()) { + ++auxCount; + } + } + final int nonAuxCount = subtypes.size() - auxCount; + + // IMEs that have one or more non-auxiliary subtypes should be counted. + // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary + // subtypes should be counted as well. + if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) { + ++filteredImisCount; + continue; + } + } + + if (filteredImisCount > 1) { + return true; + } + final List<InputMethodSubtype> subtypes = + mImmWrapper.mImm.getEnabledInputMethodSubtypeList(null, true); + int keyboardCount = 0; + // imm.getEnabledInputMethodSubtypeList(null, true) will return the current IME's + // both explicitly and implicitly enabled input method subtype. + // (The current IME should be LatinIME.) + for (InputMethodSubtype subtype : subtypes) { + if (KEYBOARD_MODE.equals(subtype.getMode())) { + ++keyboardCount; + } + } + return keyboardCount > 1; + } + + public InputMethodSubtype findSubtypeByLocaleAndKeyboardLayoutSet(final String localeString, + final String keyboardLayoutSetName) { + final InputMethodInfo myImi = mInputMethodInfoOfThisIme; + final int count = myImi.getSubtypeCount(); + for (int i = 0; i < count; i++) { + final InputMethodSubtype subtype = myImi.getSubtypeAt(i); + final String layoutName = SubtypeLocale.getKeyboardLayoutSetName(subtype); + if (localeString.equals(subtype.getLocale()) + && keyboardLayoutSetName.equals(layoutName)) { + return subtype; + } + } + return null; + } + + public void setInputMethodAndSubtype(final IBinder token, final InputMethodSubtype subtype) { + mImmWrapper.mImm.setInputMethodAndSubtype( + token, mInputMethodInfoOfThisIme.getId(), subtype); + } + + public void setAdditionalInputMethodSubtypes(final InputMethodSubtype[] subtypes) { + mImmWrapper.mImm.setAdditionalInputMethodSubtypes( + mInputMethodInfoOfThisIme.getId(), subtypes); + } +} diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java index 348928df8..7a73cade3 100644 --- a/java/src/com/android/inputmethod/latin/Settings.java +++ b/java/src/com/android/inputmethod/latin/Settings.java @@ -60,6 +60,8 @@ public final class Settings extends InputMethodSettingsFragment "last_user_dictionary_write_time"; public static final String PREF_ADVANCED_SETTINGS = "pref_advanced_settings"; public static final String PREF_KEY_USE_CONTACTS_DICT = "pref_key_use_contacts_dict"; + public static final String PREF_KEY_USE_DOUBLE_SPACE_PERIOD = + "pref_key_use_double_space_period"; public static final String PREF_SHOW_LANGUAGE_SWITCH_KEY = "pref_show_language_switch_key"; public static final String PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST = @@ -203,7 +205,8 @@ public final class Settings extends InputMethodSettingsFragment final Intent intent = dictionaryLink.getIntent(); final int number = context.getPackageManager().queryIntentActivities(intent, 0).size(); - if (0 >= number) { + // TODO: The experimental version is not supported by the Dictionary Pack Service yet + if (ProductionFlag.IS_EXPERIMENTAL || 0 >= number) { textCorrectionGroup.removePreference(dictionaryLink); } diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java index 2f49fe92e..a23876722 100644 --- a/java/src/com/android/inputmethod/latin/SettingsValues.java +++ b/java/src/com/android/inputmethod/latin/SettingsValues.java @@ -22,7 +22,6 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.util.Log; import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodSubtype; import com.android.inputmethod.keyboard.internal.KeySpecParser; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; @@ -76,13 +75,13 @@ public final class SettingsValues { @SuppressWarnings("unused") // TODO: Use this private final String mKeyPreviewPopupDismissDelayRawValue; public final boolean mUseContactsDict; + public final boolean mUseDoubleSpacePeriod; // Use bigrams to predict the next word when there is no input for it yet public final boolean mBigramPredictionEnabled; @SuppressWarnings("unused") // TODO: Use this private final int mVibrationDurationSettingsRawValue; @SuppressWarnings("unused") // TODO: Use this private final float mKeypressSoundVolumeRawValue; - private final InputMethodSubtype[] mAdditionalSubtypes; public final boolean mGestureInputEnabled; public final boolean mGesturePreviewTrailEnabled; public final boolean mGestureFloatingPreviewTextEnabled; @@ -156,6 +155,7 @@ public final class SettingsValues { Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY, Integer.toString(res.getInteger(R.integer.config_key_preview_linger_timeout))); mUseContactsDict = prefs.getBoolean(Settings.PREF_KEY_USE_CONTACTS_DICT, true); + mUseDoubleSpacePeriod = prefs.getBoolean(Settings.PREF_KEY_USE_DOUBLE_SPACE_PERIOD, true); mAutoCorrectEnabled = isAutoCorrectEnabled(res, mAutoCorrectionThresholdRawValue); mBigramPredictionEnabled = isBigramPredictionEnabled(prefs, res); mVibrationDurationSettingsRawValue = @@ -170,8 +170,6 @@ public final class SettingsValues { mAutoCorrectionThresholdRawValue); mVoiceKeyEnabled = mVoiceMode != null && !mVoiceMode.equals(voiceModeOff); mVoiceKeyOnMain = mVoiceMode != null && mVoiceMode.equals(voiceModeMain); - mAdditionalSubtypes = AdditionalSubtype.createAdditionalSubtypesArray( - getPrefAdditionalSubtypes(prefs, res)); final boolean gestureInputEnabledByBuildConfig = res.getBoolean( R.bool.config_gesture_input_enabled_by_build_config); mGestureInputEnabled = gestureInputEnabledByBuildConfig @@ -359,16 +357,15 @@ public final class SettingsValues { return prefs.getBoolean(Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY, true); } - public boolean isLanguageSwitchKeyEnabled(final Context context) { + public boolean isLanguageSwitchKeyEnabled() { if (!mShowsLanguageSwitchKey) { return false; } + final RichInputMethodManager imm = RichInputMethodManager.getInstance(); if (mIncludesOtherImesInLanguageSwitchList) { - return ImfUtils.hasMultipleEnabledIMEsOrSubtypes( - context, /* include aux subtypes */false); + return imm.hasMultipleEnabledIMEsOrSubtypes(false /* include aux subtypes */); } else { - return ImfUtils.hasMultipleEnabledSubtypesInThisIme( - context, /* include aux subtypes */false); + return imm.hasMultipleEnabledSubtypesInThisIme(false /* include aux subtypes */); } } @@ -376,10 +373,6 @@ public final class SettingsValues { return res.getBoolean(R.bool.config_use_fullscreen_mode); } - public InputMethodSubtype[] getAdditionalSubtypes() { - return mAdditionalSubtypes; - } - public static String getPrefAdditionalSubtypes(final SharedPreferences prefs, final Resources res) { final String predefinedPrefSubtypes = AdditionalSubtype.createPrefSubtypes( diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java index df7709892..043043cef 100644 --- a/java/src/com/android/inputmethod/latin/StringUtils.java +++ b/java/src/com/android/inputmethod/latin/StringUtils.java @@ -16,10 +16,9 @@ package com.android.inputmethod.latin; +import android.text.InputType; import android.text.TextUtils; -import com.android.inputmethod.keyboard.Keyboard; // For character constants - import java.util.ArrayList; import java.util.Locale; @@ -28,30 +27,30 @@ public final class StringUtils { // This utility class is not publicly instantiable. } - public static int codePointCount(String text) { + public static int codePointCount(final String text) { if (TextUtils.isEmpty(text)) return 0; return text.codePointCount(0, text.length()); } - public static boolean containsInArray(String key, String[] array) { + public static boolean containsInArray(final String key, final String[] array) { for (final String element : array) { if (key.equals(element)) return true; } return false; } - public static boolean containsInCsv(String key, String csv) { + public static boolean containsInCsv(final String key, final String csv) { if (TextUtils.isEmpty(csv)) return false; return containsInArray(key, csv.split(",")); } - public static String appendToCsvIfNotExists(String key, String csv) { + public static String appendToCsvIfNotExists(final String key, final String csv) { if (TextUtils.isEmpty(csv)) return key; if (containsInCsv(key, csv)) return csv; return csv + "," + key; } - public static String removeFromCsvIfExists(String key, String csv) { + public static String removeFromCsvIfExists(final String key, final String csv) { if (TextUtils.isEmpty(csv)) return ""; final String[] elements = csv.split(","); if (!containsInArray(key, elements)) return csv; @@ -63,82 +62,20 @@ public final class StringUtils { } /** - * Returns true if a and b are equal ignoring the case of the character. - * @param a first character to check - * @param b second character to check - * @return {@code true} if a and b are equal, {@code false} otherwise. - */ - public static boolean equalsIgnoreCase(char a, char b) { - // Some language, such as Turkish, need testing both cases. - return a == b - || Character.toLowerCase(a) == Character.toLowerCase(b) - || Character.toUpperCase(a) == Character.toUpperCase(b); - } - - /** - * Returns true if a and b are equal ignoring the case of the characters, including if they are - * both null. - * @param a first CharSequence to check - * @param b second CharSequence to check - * @return {@code true} if a and b are equal, {@code false} otherwise. - */ - public static boolean equalsIgnoreCase(CharSequence a, CharSequence b) { - if (a == b) - return true; // including both a and b are null. - if (a == null || b == null) - return false; - final int length = a.length(); - if (length != b.length()) - return false; - for (int i = 0; i < length; i++) { - if (!equalsIgnoreCase(a.charAt(i), b.charAt(i))) - return false; - } - return true; - } - - /** - * Returns true if a and b are equal ignoring the case of the characters, including if a is null - * and b is zero length. - * @param a CharSequence to check - * @param b character array to check - * @param offset start offset of array b - * @param length length of characters in array b - * @return {@code true} if a and b are equal, {@code false} otherwise. - * @throws IndexOutOfBoundsException - * if {@code offset < 0 || length < 0 || offset + length > data.length}. - * @throws NullPointerException if {@code b == null}. - */ - public static boolean equalsIgnoreCase(CharSequence a, char[] b, int offset, int length) { - if (offset < 0 || length < 0 || length > b.length - offset) - throw new IndexOutOfBoundsException("array.length=" + b.length + " offset=" + offset - + " length=" + length); - if (a == null) - return length == 0; // including a is null and b is zero length. - if (a.length() != length) - return false; - for (int i = 0; i < length; i++) { - if (!equalsIgnoreCase(a.charAt(i), b[offset + i])) - return false; - } - return true; - } - - /** * Remove duplicates from an array of strings. * * This method will always keep the first occurrence of all strings at their position * in the array, removing the subsequent ones. */ - public static void removeDupes(final ArrayList<CharSequence> suggestions) { + public static void removeDupes(final ArrayList<String> suggestions) { if (suggestions.size() < 2) return; int i = 1; // Don't cache suggestions.size(), since we may be removing items while (i < suggestions.size()) { - final CharSequence cur = suggestions.get(i); + final String cur = suggestions.get(i); // Compare each suggestion with each previous suggestion for (int j = 0; j < i; j++) { - CharSequence previous = suggestions.get(j); + final String previous = suggestions.get(j); if (TextUtils.equals(cur, previous)) { suggestions.remove(i); i--; @@ -149,7 +86,7 @@ public final class StringUtils { } } - public static String toTitleCase(String s, Locale locale) { + public static String toTitleCase(final String s, final Locale locale) { if (s.length() <= 1) { // TODO: is this really correct? Shouldn't this be s.toUpperCase()? return s; @@ -165,21 +102,19 @@ public final class StringUtils { return s.toUpperCase(locale).charAt(0) + s.substring(1); } + private static final int[] EMPTY_CODEPOINTS = {}; + public static int[] toCodePointArray(final String string) { - final char[] characters = string.toCharArray(); - final int length = characters.length; - final int[] codePoints = new int[Character.codePointCount(characters, 0, length)]; + final int length = string.length(); if (length <= 0) { - return new int[0]; + return EMPTY_CODEPOINTS; } - int codePoint = Character.codePointAt(characters, 0); - int dsti = 0; - for (int srci = Character.charCount(codePoint); - srci < length; srci += Character.charCount(codePoint), ++dsti) { - codePoints[dsti] = codePoint; - codePoint = Character.codePointAt(characters, srci); + final int[] codePoints = new int[string.codePointCount(0, length)]; + int destIndex = 0; + for (int index = 0; index < length; index = string.offsetByCodePoints(index, 1)) { + codePoints[destIndex] = string.codePointAt(index); + destIndex++; } - codePoints[dsti] = codePoint; return codePoints; } @@ -237,7 +172,7 @@ public final class StringUtils { } else { for (i = cs.length(); i > 0; i--) { final char c = cs.charAt(i - 1); - if (c != Keyboard.CODE_DOUBLE_QUOTE && c != Keyboard.CODE_SINGLE_QUOTE + if (c != Constants.CODE_DOUBLE_QUOTE && c != Constants.CODE_SINGLE_QUOTE && Character.getType(c) != Character.START_PUNCTUATION) { break; } @@ -253,11 +188,11 @@ public final class StringUtils { // if the first char that's not a space or tab is a start of line (as in \n, start of text, // or some other similar characters). int j = i; - char prevChar = Keyboard.CODE_SPACE; + char prevChar = Constants.CODE_SPACE; if (hasSpaceBefore) --j; while (j > 0) { prevChar = cs.charAt(j - 1); - if (!Character.isSpaceChar(prevChar) && prevChar != Keyboard.CODE_TAB) break; + if (!Character.isSpaceChar(prevChar) && prevChar != Constants.CODE_TAB) break; j--; } if (j <= 0 || Character.isWhitespace(prevChar)) { @@ -296,7 +231,7 @@ public final class StringUtils { // variants of English, the final period is placed within double quotes and maybe // other closing punctuation signs. This is generally not true in other languages. final char c = cs.charAt(j - 1); - if (c != Keyboard.CODE_DOUBLE_QUOTE && c != Keyboard.CODE_SINGLE_QUOTE + if (c != Constants.CODE_DOUBLE_QUOTE && c != Constants.CODE_SINGLE_QUOTE && Character.getType(c) != Character.END_PUNCTUATION) { break; } @@ -310,10 +245,10 @@ public final class StringUtils { // end of a sentence. If we have a question mark or an exclamation mark, it's the end of // a sentence. If it's neither, the only remaining case is the period so we get the opposite // case out of the way. - if (c == Keyboard.CODE_QUESTION_MARK || c == Keyboard.CODE_EXCLAMATION_MARK) { + if (c == Constants.CODE_QUESTION_MARK || c == Constants.CODE_EXCLAMATION_MARK) { return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_SENTENCES) & reqModes; } - if (c != Keyboard.CODE_PERIOD || j <= 0) { + if (c != Constants.CODE_PERIOD || j <= 0) { return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes; } @@ -363,7 +298,7 @@ public final class StringUtils { case WORD: if (Character.isLetter(c)) { state = WORD; - } else if (c == Keyboard.CODE_PERIOD) { + } else if (c == Constants.CODE_PERIOD) { state = PERIOD; } else { return caps; @@ -379,7 +314,7 @@ public final class StringUtils { case LETTER: if (Character.isLetter(c)) { state = LETTER; - } else if (c == Keyboard.CODE_PERIOD) { + } else if (c == Constants.CODE_PERIOD) { state = PERIOD; } else { return noCaps; diff --git a/java/src/com/android/inputmethod/latin/SubtypeLocale.java b/java/src/com/android/inputmethod/latin/SubtypeLocale.java index 579f96bb4..5d8c0b17d 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeLocale.java +++ b/java/src/com/android/inputmethod/latin/SubtypeLocale.java @@ -151,18 +151,19 @@ public final class SubtypeLocale { // InputMethodSubtype's display name in its locale. // isAdditionalSubtype (T=true, F=false) - // locale layout | display name - // ------ ------ - ---------------------- - // en_US qwerty F English (US) exception - // en_GB qwerty F English (UK) exception - // fr azerty F Français - // fr_CA qwerty F Français (Canada) - // de qwertz F Deutsch - // zz qwerty F No language (QWERTY) in system locale - // fr qwertz T Français (QWERTZ) - // de qwerty T Deutsch (QWERTY) - // en_US azerty T English (US) (AZERTY) - // zz azerty T No language (AZERTY) in system locale + // locale layout | display name + // ------ ------- - ---------------------- + // en_US qwerty F English (US) exception + // en_GB qwerty F English (UK) exception + // es_US spanish F Español (EE.UU.) exception + // fr azerty F Français + // fr_CA qwerty F Français (Canada) + // de qwertz F Deutsch + // zz qwerty F No language (QWERTY) in system locale + // fr qwertz T Français (QWERTZ) + // de qwerty T Deutsch (QWERTY) + // en_US azerty T English (US) (AZERTY) + // zz azerty T No language (AZERTY) in system locale public static String getSubtypeDisplayName(final InputMethodSubtype subtype, Resources res) { final String replacementString = (Build.VERSION.SDK_INT >= /* JELLY_BEAN */ 15 diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java index 8e51a372b..8f2e27549 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java +++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java @@ -43,7 +43,7 @@ public final class SubtypeSwitcher { private static final String TAG = SubtypeSwitcher.class.getSimpleName(); private static final SubtypeSwitcher sInstance = new SubtypeSwitcher(); - private /* final */ InputMethodManager mImm; + private /* final */ RichInputMethodManager mRichImm; private /* final */ Resources mResources; private /* final */ ConnectivityManager mConnectivityManager; @@ -84,7 +84,7 @@ public final class SubtypeSwitcher { public static void init(final Context context) { SubtypeLocale.init(context); sInstance.initialize(context); - sInstance.updateAllParameters(context); + sInstance.updateAllParameters(); } private SubtypeSwitcher() { @@ -93,13 +93,13 @@ public final class SubtypeSwitcher { private void initialize(final Context service) { mResources = service.getResources(); - mImm = ImfUtils.getInputMethodManager(service); + mRichImm = RichInputMethodManager.getInstance(); mConnectivityManager = (ConnectivityManager) service.getSystemService( Context.CONNECTIVITY_SERVICE); mCurrentSystemLocale = mResources.getConfiguration().locale; - mNoLanguageSubtype = ImfUtils.findSubtypeByLocaleAndKeyboardLayoutSet( - service, SubtypeLocale.NO_LANGUAGE, SubtypeLocale.QWERTY); - mCurrentSubtype = ImfUtils.getCurrentInputMethodSubtype(service, mNoLanguageSubtype); + mNoLanguageSubtype = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + SubtypeLocale.NO_LANGUAGE, SubtypeLocale.QWERTY); + mCurrentSubtype = mRichImm.getCurrentInputMethodSubtype(mNoLanguageSubtype); if (mNoLanguageSubtype == null) { throw new RuntimeException("Can't find no lanugage with QWERTY subtype"); } @@ -110,9 +110,9 @@ public final class SubtypeSwitcher { // Update all parameters stored in SubtypeSwitcher. // Only configuration changed event is allowed to call this because this is heavy. - private void updateAllParameters(final Context context) { + private void updateAllParameters() { mCurrentSystemLocale = mResources.getConfiguration().locale; - updateSubtype(ImfUtils.getCurrentInputMethodSubtype(context, mNoLanguageSubtype)); + updateSubtype(mRichImm.getCurrentInputMethodSubtype(mNoLanguageSubtype)); updateParametersOnStartInputViewAndReturnIfCurrentSubtypeEnabled(); } @@ -137,7 +137,7 @@ public final class SubtypeSwitcher { */ private boolean updateEnabledSubtypesAndReturnIfEnabled(final InputMethodSubtype subtype) { final List<InputMethodSubtype> enabledSubtypesOfThisIme = - mImm.getEnabledInputMethodSubtypeList(null, true); + mRichImm.getInputMethodManager().getEnabledInputMethodSubtypeList(null, true); mNeedsToDisplayLanguage.updateEnabledSubtypeCount(enabledSubtypesOfThisIme.size()); for (final InputMethodSubtype ims : enabledSubtypesOfThisIme) { @@ -162,7 +162,7 @@ public final class SubtypeSwitcher { } // TODO: Update an icon for shortcut IME final Map<InputMethodInfo, List<InputMethodSubtype>> shortcuts = - mImm.getShortcutInputMethodsAndSubtypes(); + mRichImm.getInputMethodManager().getShortcutInputMethodsAndSubtypes(); mShortcutInputMethodInfo = null; mShortcutSubtype = null; for (final InputMethodInfo imi : shortcuts.keySet()) { @@ -193,8 +193,13 @@ public final class SubtypeSwitcher { } final Locale newLocale = SubtypeLocale.getSubtypeLocale(newSubtype); + final boolean sameLocale = mCurrentSystemLocale.equals(newLocale); + final boolean sameLanguage = mCurrentSystemLocale.getLanguage().equals( + newLocale.getLanguage()); + final boolean implicitlyEnabled = + mRichImm.checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(newSubtype); mNeedsToDisplayLanguage.updateIsSystemLanguageSameAsInputLanguage( - mCurrentSystemLocale.equals(newLocale)); + sameLocale || (sameLanguage && implicitlyEnabled)); if (newSubtype.equals(mCurrentSubtype)) return; @@ -221,7 +226,7 @@ public final class SubtypeSwitcher { if (token == null) { return; } - final InputMethodManager imm = mImm; + final InputMethodManager imm = mRichImm.getInputMethodManager(); new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { @@ -238,14 +243,8 @@ public final class SubtypeSwitcher { if (mShortcutSubtype == null) { return true; } - final boolean allowsImplicitlySelectedSubtypes = true; - for (final InputMethodSubtype enabledSubtype : mImm.getEnabledInputMethodSubtypeList( - mShortcutInputMethodInfo, allowsImplicitlySelectedSubtypes)) { - if (enabledSubtype.equals(mShortcutSubtype)) { - return true; - } - } - return false; + return mRichImm.checkIfSubtypeBelongsToImeAndEnabled( + mShortcutInputMethodInfo, mShortcutSubtype); } public boolean isShortcutImeReady() { @@ -285,12 +284,12 @@ public final class SubtypeSwitcher { return SubtypeLocale.getSubtypeLocale(mCurrentSubtype); } - public boolean onConfigurationChanged(final Configuration conf, final Context context) { + public boolean onConfigurationChanged(final Configuration conf) { final Locale systemLocale = conf.locale; final boolean systemLocaleChanged = !systemLocale.equals(mCurrentSystemLocale); // If system configuration was changed, update all parameters. if (systemLocaleChanged) { - updateAllParameters(context); + updateAllParameters(); } return systemLocaleChanged; } diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index f0e3b4ebd..3dc2ba95b 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -19,7 +19,7 @@ package com.android.inputmethod.latin; import android.content.Context; import android.text.TextUtils; -import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; @@ -38,7 +38,7 @@ public final class Suggest { public static final String TAG = Suggest.class.getSimpleName(); // Session id for - // {@link #getSuggestedWords(WordComposer,CharSequence,ProximityInfo,boolean,int)}. + // {@link #getSuggestedWords(WordComposer,String,ProximityInfo,boolean,int)}. public static final int SESSION_TYPING = 0; public static final int SESSION_GESTURE = 1; @@ -71,7 +71,8 @@ public final class Suggest { mLocale = locale; } - /* package for test */ Suggest(final Context context, final File dictionary, + @UsedForTesting + Suggest(final Context context, final File dictionary, final long startOffset, final long length, final Locale locale) { final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(context, dictionary, startOffset, length /* useFullEditDistance */, false, locale); @@ -138,7 +139,7 @@ public final class Suggest { * Sets an optional user dictionary resource to be loaded. The user dictionary is consulted * before the main dictionary, if set. This refers to the system-managed user dictionary. */ - public void setUserDictionary(UserBinaryDictionary userDictionary) { + public void setUserDictionary(final UserBinaryDictionary userDictionary) { addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_USER, userDictionary); } @@ -147,12 +148,12 @@ public final class Suggest { * the contacts dictionary by passing null to this method. In this case no contacts dictionary * won't be used. */ - public void setContactsDictionary(ContactsBinaryDictionary contactsDictionary) { + public void setContactsDictionary(final ContactsBinaryDictionary contactsDictionary) { mContactsDict = contactsDictionary; addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_CONTACTS, contactsDictionary); } - public void setUserHistoryDictionary(UserHistoryDictionary userHistoryDictionary) { + public void setUserHistoryDictionary(final UserHistoryDictionary userHistoryDictionary) { addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_USER_HISTORY, userHistoryDictionary); } @@ -160,9 +161,9 @@ public final class Suggest { mAutoCorrectionThreshold = threshold; } - public SuggestedWords getSuggestedWords( - final WordComposer wordComposer, CharSequence prevWordForBigram, - final ProximityInfo proximityInfo, final boolean isCorrectionEnabled, int sessionId) { + public SuggestedWords getSuggestedWords(final WordComposer wordComposer, + final String prevWordForBigram, final ProximityInfo proximityInfo, + final boolean isCorrectionEnabled, final int sessionId) { LatinImeLogger.onStartSuggestion(prevWordForBigram); if (wordComposer.isBatchMode()) { return getSuggestedWordsForBatchInput( @@ -174,9 +175,9 @@ public final class Suggest { } // Retrieves suggestions for the typing input. - private SuggestedWords getSuggestedWordsForTypingInput( - final WordComposer wordComposer, CharSequence prevWordForBigram, - final ProximityInfo proximityInfo, final boolean isCorrectionEnabled) { + private SuggestedWords getSuggestedWordsForTypingInput(final WordComposer wordComposer, + final String prevWordForBigram, final ProximityInfo proximityInfo, + final boolean isCorrectionEnabled) { final int trailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount(); final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator, MAX_SUGGESTIONS); @@ -203,7 +204,7 @@ public final class Suggest { wordComposerForLookup, prevWordForBigram, proximityInfo)); } - final CharSequence whitelistedWord; + final String whitelistedWord; if (suggestionsSet.isEmpty()) { whitelistedWord = null; } else if (SuggestedWordInfo.KIND_WHITELIST != suggestionsSet.first().mKind) { @@ -287,9 +288,9 @@ public final class Suggest { } // Retrieves suggestions for the batch input. - private SuggestedWords getSuggestedWordsForBatchInput( - final WordComposer wordComposer, CharSequence prevWordForBigram, - final ProximityInfo proximityInfo, int sessionId) { + private SuggestedWords getSuggestedWordsForBatchInput(final WordComposer wordComposer, + final String prevWordForBigram, final ProximityInfo proximityInfo, + final int sessionId) { final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator, MAX_SUGGESTIONS); @@ -307,7 +308,7 @@ public final class Suggest { } for (SuggestedWordInfo wordInfo : suggestionsSet) { - LatinImeLogger.onAddSuggestedWord(wordInfo.mWord.toString(), wordInfo.mSourceDict); + LatinImeLogger.onAddSuggestedWord(wordInfo.mWord, wordInfo.mSourceDict); } final ArrayList<SuggestedWordInfo> suggestionsContainer = @@ -372,7 +373,7 @@ public final class Suggest { if (o1.mScore < o2.mScore) return 1; if (o1.mCodePointCount < o2.mCodePointCount) return -1; if (o1.mCodePointCount > o2.mCodePointCount) return 1; - return o1.mWord.toString().compareTo(o2.mWord.toString()); + return o1.mWord.compareTo(o2.mWord); } } private static final SuggestedWordInfoComparator sSuggestedWordInfoComparator = @@ -383,16 +384,17 @@ public final class Suggest { final boolean isFirstCharCapitalized, final int trailingSingleQuotesCount) { final StringBuilder sb = new StringBuilder(wordInfo.mWord.length()); if (isAllUpperCase) { - sb.append(wordInfo.mWord.toString().toUpperCase(locale)); + sb.append(wordInfo.mWord.toUpperCase(locale)); } else if (isFirstCharCapitalized) { - sb.append(StringUtils.toTitleCase(wordInfo.mWord.toString(), locale)); + sb.append(StringUtils.toTitleCase(wordInfo.mWord, locale)); } else { sb.append(wordInfo.mWord); } for (int i = trailingSingleQuotesCount - 1; i >= 0; --i) { - sb.appendCodePoint(Keyboard.CODE_SINGLE_QUOTE); + sb.appendCodePoint(Constants.CODE_SINGLE_QUOTE); } - return new SuggestedWordInfo(sb, wordInfo.mScore, wordInfo.mKind, wordInfo.mSourceDict); + return new SuggestedWordInfo(sb.toString(), wordInfo.mScore, wordInfo.mKind, + wordInfo.mSourceDict); } public void close() { diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java index 52e292a86..572f2906e 100644 --- a/java/src/com/android/inputmethod/latin/SuggestedWords.java +++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java @@ -53,6 +53,10 @@ public final class SuggestedWords { mIsPrediction = isPrediction; } + public boolean isEmpty() { + return mSuggestedWordInfoList.isEmpty(); + } + public int size() { return mSuggestedWordInfoList.size(); } @@ -86,11 +90,14 @@ public final class SuggestedWords { public static ArrayList<SuggestedWordInfo> getFromApplicationSpecifiedCompletions( final CompletionInfo[] infos) { final ArrayList<SuggestedWordInfo> result = CollectionUtils.newArrayList(); - for (CompletionInfo info : infos) { - if (null != info && info.getText() != null) { - result.add(new SuggestedWordInfo(info.getText(), SuggestedWordInfo.MAX_SCORE, - SuggestedWordInfo.KIND_APP_DEFINED, Dictionary.TYPE_APPLICATION_DEFINED)); - } + for (final CompletionInfo info : infos) { + if (info == null) continue; + final CharSequence text = info.getText(); + if (null == text) continue; + final SuggestedWordInfo suggestedWordInfo = new SuggestedWordInfo(text.toString(), + SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_APP_DEFINED, + Dictionary.TYPE_APPLICATION_DEFINED); + result.add(suggestedWordInfo); } return result; } @@ -98,7 +105,7 @@ public final class SuggestedWords { // Should get rid of the first one (what the user typed previously) from suggestions // and replace it with what the user currently typed. public static ArrayList<SuggestedWordInfo> getTypedWordAndPreviousSuggestions( - final CharSequence typedWord, final SuggestedWords previousSuggestions) { + final String typedWord, final SuggestedWords previousSuggestions) { final ArrayList<SuggestedWordInfo> suggestionsList = CollectionUtils.newArrayList(); final HashSet<String> alreadySeen = CollectionUtils.newHashSet(); suggestionsList.add(new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE, @@ -107,7 +114,7 @@ public final class SuggestedWords { final int previousSize = previousSuggestions.size(); for (int pos = 1; pos < previousSize; pos++) { final SuggestedWordInfo prevWordInfo = previousSuggestions.getWordInfo(pos); - final String prevWord = prevWordInfo.mWord.toString(); + final String prevWord = prevWordInfo.mWord; // Filter out duplicate suggestion. if (!alreadySeen.contains(prevWord)) { suggestionsList.add(prevWordInfo); @@ -135,9 +142,9 @@ public final class SuggestedWords { public final String mSourceDict; private String mDebugString = ""; - public SuggestedWordInfo(final CharSequence word, final int score, final int kind, + public SuggestedWordInfo(final String word, final int score, final int kind, final String sourceDict) { - mWord = word.toString(); + mWord = word; mScore = score; mKind = kind; mSourceDict = sourceDict; @@ -145,7 +152,7 @@ public final class SuggestedWords { } - public void setDebugString(String str) { + public void setDebugString(final String str) { if (null == str) throw new NullPointerException("Debug info is null"); mDebugString = str; } @@ -167,7 +174,7 @@ public final class SuggestedWords { if (TextUtils.isEmpty(mDebugString)) { return mWord; } else { - return mWord + " (" + mDebugString.toString() + ")"; + return mWord + " (" + mDebugString + ")"; } } diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java index 8f21b7b4a..ec4dc1436 100644 --- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java @@ -33,13 +33,13 @@ public final class SynchronouslyLoadedContactsBinaryDictionary extends ContactsB @Override public synchronized ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer codes, - final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) { + final String prevWordForBigrams, final ProximityInfo proximityInfo) { syncReloadDictionaryIfRequired(); return super.getSuggestions(codes, prevWordForBigrams, proximityInfo); } @Override - public synchronized boolean isValidWord(CharSequence word) { + public synchronized boolean isValidWord(final String word) { syncReloadDictionaryIfRequired(); return isValidWordInner(word); } diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java index 612f54d73..4bdaf2039 100644 --- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java @@ -36,13 +36,13 @@ public final class SynchronouslyLoadedUserBinaryDictionary extends UserBinaryDic @Override public synchronized ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer codes, - final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) { + final String prevWordForBigrams, final ProximityInfo proximityInfo) { syncReloadDictionaryIfRequired(); return super.getSuggestions(codes, prevWordForBigrams, proximityInfo); } @Override - public synchronized boolean isValidWord(CharSequence word) { + public synchronized boolean isValidWord(final String word) { syncReloadDictionaryIfRequired(); return isValidWordInner(word); } diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java index 8570746b1..ddae5ac48 100644 --- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java @@ -232,7 +232,7 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary { mContext.startActivity(intent); } - private void addWords(Cursor cursor) { + private void addWords(final Cursor cursor) { // 16 is JellyBean, but we want this to compile against ICS. final boolean hasShortcutColumn = android.os.Build.VERSION.SDK_INT >= 16; clearFusionDictionary(); diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java index e39011145..100e377f6 100644 --- a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java +++ b/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java @@ -18,6 +18,7 @@ package com.android.inputmethod.latin; import android.util.Log; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.makedict.BinaryDictIOUtils; import com.android.inputmethod.latin.makedict.BinaryDictInputOutput; import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.FusionDictionaryBufferInterface; @@ -62,7 +63,7 @@ public final class UserHistoryDictIOUtils { @Override public int readUnsignedByte() { - return ((int)mBuffer[mPosition++]) & 0xFF; + return mBuffer[mPosition++] & 0xFF; } @Override @@ -129,7 +130,8 @@ public final class UserHistoryDictIOUtils { /** * Constructs a new FusionDictionary from BigramDictionaryInterface. */ - /* packages for test */ static FusionDictionary constructFusionDictionary( + @UsedForTesting + static FusionDictionary constructFusionDictionary( final BigramDictionaryInterface dict, final UserHistoryDictionaryBigramList bigrams) { final FusionDictionary fusionDict = new FusionDictionary(new Node(), new FusionDictionary.DictionaryOptions(new HashMap<String, String>(), false, @@ -193,7 +195,8 @@ public final class UserHistoryDictIOUtils { /** * Adds all unigrams and bigrams in maps to OnAddWordListener. */ - /* package for test */ static void addWordsFromWordMap(final Map<Integer, String> unigrams, + @UsedForTesting + static void addWordsFromWordMap(final Map<Integer, String> unigrams, final Map<Integer, Integer> frequencies, final Map<Integer, ArrayList<PendingAttribute>> bigrams, final OnAddWordListener to) { for (Map.Entry<Integer, String> entry : unigrams.entrySet()) { diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java index 3615fa1fb..f30a60af2 100644 --- a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java +++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java @@ -21,6 +21,7 @@ import android.content.SharedPreferences; import android.os.AsyncTask; import android.util.Log; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.UserHistoryDictIOUtils.BigramDictionaryInterface; @@ -75,7 +76,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary { private final SharedPreferences mPrefs; // Should always be false except when we use this class for test - /* package for test */ boolean isTest = false; + @UsedForTesting boolean isTest = false; private static final ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>> sLangDictCache = CollectionUtils.newConcurrentHashMap(); @@ -122,7 +123,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary { @Override protected ArrayList<SuggestedWordInfo> getWordsInner(final WordComposer composer, - final CharSequence prevWord, final ProximityInfo proximityInfo) { + final String prevWord, final ProximityInfo proximityInfo) { // Inhibit suggestions (not predictions) for user history for now. Removing this method // is enough to use it through the standard ExpandableDictionary way. return null; @@ -132,7 +133,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary { * Return whether the passed charsequence is in the dictionary. */ @Override - public synchronized boolean isValidWord(final CharSequence word) { + public synchronized boolean isValidWord(final String word) { // TODO: figure out what is the correct thing to do here. return false; } @@ -145,7 +146,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary { * context, as in beginning of a sentence for example. * The second word may not be null (a NullPointerException would be thrown). */ - public int addToUserHistory(final String word1, String word2, boolean isValid) { + public int addToUserHistory(final String word1, final String word2, final boolean isValid) { if (word2.length() >= BinaryDictionary.MAX_WORD_LENGTH || (word1 != null && word1.length() >= BinaryDictionary.MAX_WORD_LENGTH)) { return -1; @@ -175,7 +176,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary { return -1; } - public boolean cancelAddingUserHistory(String word1, String word2) { + public boolean cancelAddingUserHistory(final String word1, final String word2) { if (mBigramListLock.tryLock()) { try { if (mBigramList.removeBigram(word1, word2)) { @@ -226,7 +227,8 @@ public final class UserHistoryDictionary extends ExpandableDictionary { final ExpandableDictionary dictionary = this; final OnAddWordListener listener = new OnAddWordListener() { @Override - public void setUnigram(String word, String shortcutTarget, int frequency) { + public void setUnigram(final String word, final String shortcutTarget, + final int frequency) { profTotal++; if (DBG_SAVE_RESTORE) { Log.d(TAG, "load unigram: " + word + "," + frequency); @@ -236,7 +238,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary { } @Override - public void setBigram(String word1, String word2, int frequency) { + public void setBigram(final String word1, final String word2, final int frequency) { if (word1.length() < BinaryDictionary.MAX_WORD_LENGTH && word2.length() < BinaryDictionary.MAX_WORD_LENGTH) { profTotal++; @@ -250,7 +252,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary { mBigramList.addBigram(word1, word2, (byte)frequency); } }; - + // Load the dictionary from binary file FileInputStream inStream = null; try { @@ -292,8 +294,9 @@ public final class UserHistoryDictionary extends ExpandableDictionary { private final SharedPreferences mPrefs; private final Context mContext; - public UpdateBinaryTask(UserHistoryDictionaryBigramList pendingWrites, String locale, - UserHistoryDictionary dict, SharedPreferences prefs, Context context) { + public UpdateBinaryTask(final UserHistoryDictionaryBigramList pendingWrites, + final String locale, final UserHistoryDictionary dict, + final SharedPreferences prefs, final Context context) { mBigramList = pendingWrites; mLocale = locale; mUserHistoryDictionary = dict; @@ -303,7 +306,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary { } @Override - protected Void doInBackground(Void... v) { + protected Void doInBackground(final Void... v) { if (mUserHistoryDictionary.isTest) { // If isTest == true, wait until the lock is released. mUserHistoryDictionary.mBigramListLock.lock(); @@ -360,7 +363,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary { } @Override - public int getFrequency(String word1, String word2) { + public int getFrequency(final String word1, final String word2) { final int freq; if (word1 == null) { // unigram freq = FREQUENCY_FOR_TYPED; @@ -373,10 +376,10 @@ public final class UserHistoryDictionary extends ExpandableDictionary { final byte fc = fcp.getFc(); final boolean isValid = fcp.isValid(); if (prevFc > 0 && prevFc == fc) { - freq = ((int)fc) & 0xFF; + freq = fc & 0xFF; } else if (UserHistoryForgettingCurveUtils. needsToSave(fc, isValid, mAddLevel0Bigrams)) { - freq = ((int)fc) & 0xFF; + freq = fc & 0xFF; } else { // Delete this entry freq = -1; @@ -390,6 +393,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary { } } + @UsedForTesting void forceAddWordForTest(final String word1, final String word2, final boolean isValid) { mBigramListLock.lock(); try { diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java index 876bc8e79..3eac6a237 100644 --- a/java/src/com/android/inputmethod/latin/Utils.java +++ b/java/src/com/android/inputmethod/latin/Utils.java @@ -41,6 +41,7 @@ import java.io.PrintWriter; import java.nio.channels.FileChannel; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.Locale; public final class Utils { private Utils() { @@ -193,7 +194,7 @@ public final class Utils { private UsabilityStudyLogUtils() { mDate = new Date(); - mDateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss.SSSZ"); + mDateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss.SSSZ", Locale.US); HandlerThread handlerThread = new HandlerThread("UsabilityStudyLogUtils logging task", Process.THREAD_PRIORITY_BACKGROUND); @@ -255,7 +256,7 @@ public final class Utils { final long currentTime = System.currentTimeMillis(); mDate.setTime(currentTime); - final String printString = String.format("%s\t%d\t%s\n", + final String printString = String.format(Locale.US, "%s\t%d\t%s\n", mDateFormat.format(mDate), currentTime, log); if (LatinImeLogger.sDBG) { Log.d(USABILITY_TAG, "Write: " + log); @@ -297,7 +298,7 @@ public final class Utils { final Date date = new Date(); date.setTime(System.currentTimeMillis()); final String currentDateTimeString = - new SimpleDateFormat("yyyyMMdd-HHmmssZ").format(date); + new SimpleDateFormat("yyyyMMdd-HHmmssZ", Locale.US).format(date); if (mFile == null) { Log.w(USABILITY_TAG, "No internal log file found."); return; @@ -313,11 +314,15 @@ public final class Utils { + "/research-" + currentDateTimeString + ".log"; final File destFile = new File(destPath); try { - final FileChannel src = (new FileInputStream(mFile)).getChannel(); - final FileChannel dest = (new FileOutputStream(destFile)).getChannel(); + final FileInputStream srcStream = new FileInputStream(mFile); + final FileOutputStream destStream = new FileOutputStream(destFile); + final FileChannel src = srcStream.getChannel(); + final FileChannel dest = destStream.getChannel(); src.transferTo(0, src.size(), dest); src.close(); + srcStream.close(); dest.close(); + destStream.close(); } catch (FileNotFoundException e1) { Log.w(USABILITY_TAG, e1); return; diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java index da0071adc..daff442f3 100644 --- a/java/src/com/android/inputmethod/latin/WordComposer.java +++ b/java/src/com/android/inputmethod/latin/WordComposer.java @@ -38,7 +38,7 @@ public final class WordComposer { private int[] mPrimaryKeyCodes; private final InputPointers mInputPointers = new InputPointers(N); private final StringBuilder mTypedWord; - private CharSequence mAutoCorrection; + private String mAutoCorrection; private boolean mIsResumed; private boolean mIsBatchMode; @@ -64,7 +64,7 @@ public final class WordComposer { refreshSize(); } - public WordComposer(WordComposer source) { + public WordComposer(final WordComposer source) { mPrimaryKeyCodes = Arrays.copyOf(source.mPrimaryKeyCodes, source.mPrimaryKeyCodes.length); mTypedWord = new StringBuilder(source.mTypedWord); mInputPointers.copy(source.mInputPointers); @@ -121,7 +121,8 @@ public final class WordComposer { return mInputPointers; } - private static boolean isFirstCharCapitalized(int index, int codePoint, boolean previous) { + private static boolean isFirstCharCapitalized(final int index, final int codePoint, + final boolean previous) { if (index == 0) return Character.isUpperCase(codePoint); return previous && !Character.isUpperCase(codePoint); } @@ -129,12 +130,12 @@ public final class WordComposer { /** * Add a new keystroke, with the pressed key's code point with the touch point coordinates. */ - public void add(int primaryCode, int keyX, int keyY) { + public void add(final int primaryCode, final int keyX, final int keyY) { final int newIndex = size(); mTypedWord.appendCodePoint(primaryCode); refreshSize(); if (newIndex < BinaryDictionary.MAX_WORD_LENGTH) { - mPrimaryKeyCodes[newIndex] = primaryCode >= Keyboard.CODE_SPACE + mPrimaryKeyCodes[newIndex] = primaryCode >= Constants.CODE_SPACE ? Character.toLowerCase(primaryCode) : primaryCode; // In the batch input mode, the {@code mInputPointers} holds batch input points and // shouldn't be overridden by the "typed key" coordinates @@ -148,7 +149,7 @@ public final class WordComposer { newIndex, primaryCode, mIsFirstCharCapitalized); if (Character.isUpperCase(primaryCode)) mCapsCount++; if (Character.isDigit(primaryCode)) mDigitsCount++; - if (Keyboard.CODE_SINGLE_QUOTE == primaryCode) { + if (Constants.CODE_SINGLE_QUOTE == primaryCode) { ++mTrailingSingleQuotesCount; } else { mTrailingSingleQuotesCount = 0; @@ -156,12 +157,12 @@ public final class WordComposer { mAutoCorrection = null; } - public void setBatchInputPointers(InputPointers batchPointers) { + public void setBatchInputPointers(final InputPointers batchPointers) { mInputPointers.set(batchPointers); mIsBatchMode = true; } - public void setBatchInputWord(CharSequence word) { + public void setBatchInputWord(final String word) { reset(); mIsBatchMode = true; final int length = word.length(); @@ -235,7 +236,7 @@ public final class WordComposer { int i = mTypedWord.length(); while (i > 0) { i = mTypedWord.offsetByCodePoints(i, -1); - if (Keyboard.CODE_SINGLE_QUOTE != mTypedWord.codePointAt(i)) break; + if (Constants.CODE_SINGLE_QUOTE != mTypedWord.codePointAt(i)) break; ++mTrailingSingleQuotesCount; } } @@ -321,14 +322,14 @@ public final class WordComposer { /** * Sets the auto-correction for this word. */ - public void setAutoCorrection(final CharSequence correction) { + public void setAutoCorrection(final String correction) { mAutoCorrection = correction; } /** * @return the auto-correction for this word, or null if none. */ - public CharSequence getAutoCorrectionOrNull() { + public String getAutoCorrectionOrNull() { return mAutoCorrection; } @@ -341,7 +342,7 @@ public final class WordComposer { // `type' should be one of the LastComposedWord.COMMIT_TYPE_* constants above. public LastComposedWord commitWord(final int type, final String committedWord, - final String separatorString, final CharSequence prevWord) { + final String separatorString, final String prevWord) { // Note: currently, we come here whenever we commit a word. If it's a MANUAL_PICK // or a DECIDED_WORD we may cancel the commit later; otherwise, we should deactivate // the last composed word to ensure this does not happen. diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java index 7b0231a6b..ee0e9cd7e 100644 --- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java +++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java @@ -16,19 +16,32 @@ package com.android.inputmethod.latin.makedict; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.CharEncoding; import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.FusionDictionaryBufferInterface; import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader; import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup; +import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString; import java.io.IOException; +import java.io.OutputStream; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; import java.util.Map; import java.util.Stack; public final class BinaryDictIOUtils { private static final boolean DBG = false; + private static final int MSB24 = 0x800000; + private static final int SINT24_MAX = 0x7FFFFF; + private static final int MAX_JUMPS = 10000; + + private BinaryDictIOUtils() { + // This utility class is not publicly instantiable. + } private static final class Position { public static final int NOT_READ_GROUPCOUNT = -1; @@ -90,7 +103,9 @@ public final class BinaryDictIOUtils { final boolean isMovedGroup = BinaryDictInputOutput.isMovedGroup(info.mFlags, formatOptions); - if (!isMovedGroup + final boolean isDeletedGroup = BinaryDictInputOutput.isDeletedGroup(info.mFlags, + formatOptions); + if (!isMovedGroup && !isDeletedGroup && info.mFrequency != FusionDictionary.CharGroup.NOT_A_TERMINAL) {// found word words.put(info.mOriginalAddress, new String(pushedChars, 0, index)); frequencies.put(info.mOriginalAddress, info.mFrequency); @@ -153,6 +168,7 @@ public final class BinaryDictIOUtils { * @throws IOException * @throws UnsupportedFormatException */ + @UsedForTesting public static int getTerminalPosition(final FusionDictionaryBufferInterface buffer, final String word) throws IOException, UnsupportedFormatException { if (word == null) return FormatSpec.NOT_VALID_WORD; @@ -165,19 +181,19 @@ public final class BinaryDictIOUtils { if (wordPos >= wordLen) return FormatSpec.NOT_VALID_WORD; do { - int groupOffset = buffer.position() - header.mHeaderSize; final int charGroupCount = BinaryDictInputOutput.readCharGroupCount(buffer); - groupOffset += BinaryDictInputOutput.getGroupCountSize(charGroupCount); - boolean foundNextCharGroup = false; for (int i = 0; i < charGroupCount; ++i) { final int charGroupPos = buffer.position(); final CharGroupInfo currentInfo = BinaryDictInputOutput.readCharGroup(buffer, buffer.position(), header.mFormatOptions); - if (BinaryDictInputOutput.isMovedGroup(currentInfo.mFlags, - header.mFormatOptions)) { - continue; - } + final boolean isMovedGroup = + BinaryDictInputOutput.isMovedGroup(currentInfo.mFlags, + header.mFormatOptions); + final boolean isDeletedGroup = + BinaryDictInputOutput.isDeletedGroup(currentInfo.mFlags, + header.mFormatOptions); + if (isMovedGroup) continue; boolean same = true; for (int p = 0, j = word.offsetByCodePoints(0, wordPos); p < currentInfo.mCharacters.length; @@ -192,7 +208,8 @@ public final class BinaryDictIOUtils { if (same) { // found the group matches the word. if (wordPos + currentInfo.mCharacters.length == wordLen) { - if (currentInfo.mFrequency == CharGroup.NOT_A_TERMINAL) { + if (currentInfo.mFrequency == CharGroup.NOT_A_TERMINAL + || isDeletedGroup) { return FormatSpec.NOT_VALID_WORD; } else { return charGroupPos; @@ -206,7 +223,6 @@ public final class BinaryDictIOUtils { buffer.position(currentInfo.mChildrenAddress); break; } - groupOffset = currentInfo.mEndAddress; } // If we found the next char group, it is under the file pointer. @@ -228,6 +244,10 @@ public final class BinaryDictIOUtils { return FormatSpec.NOT_VALID_WORD; } + private static int markAsDeleted(final int flags) { + return (flags & (~FormatSpec.MASK_GROUP_ADDRESS_TYPE)) | FormatSpec.FLAG_IS_DELETED; + } + /** * Delete the word from the binary file. * @@ -236,6 +256,7 @@ public final class BinaryDictIOUtils { * @throws IOException * @throws UnsupportedFormatException */ + @UsedForTesting public static void deleteWord(final FusionDictionaryBufferInterface buffer, final String word) throws IOException, UnsupportedFormatException { buffer.position(0); @@ -245,21 +266,58 @@ public final class BinaryDictIOUtils { buffer.position(wordPosition); final int flags = buffer.readUnsignedByte(); - final int newFlags = flags ^ FormatSpec.FLAG_IS_TERMINAL; buffer.position(wordPosition); - buffer.put((byte)newFlags); + buffer.put((byte)markAsDeleted(flags)); } - private static void putSInt24(final FusionDictionaryBufferInterface buffer, + /** + * @return the size written, in bytes. Always 3 bytes. + */ + private static int writeSInt24ToBuffer(final FusionDictionaryBufferInterface buffer, final int value) { final int absValue = Math.abs(value); buffer.put((byte)(((value < 0 ? 0x80 : 0) | (absValue >> 16)) & 0xFF)); buffer.put((byte)((absValue >> 8) & 0xFF)); buffer.put((byte)(absValue & 0xFF)); + return 3; + } + + /** + * @return the size written, in bytes. Always 3 bytes. + */ + private static int writeSInt24ToStream(final OutputStream destination, final int value) + throws IOException { + final int absValue = Math.abs(value); + destination.write((byte)(((value < 0 ? 0x80 : 0) | (absValue >> 16)) & 0xFF)); + destination.write((byte)((absValue >> 8) & 0xFF)); + destination.write((byte)(absValue & 0xFF)); + return 3; } /** - * Update a parent address in a CharGroup that is addressed by groupOriginAddress. + * @return the size written, in bytes. 1, 2, or 3 bytes. + */ + private static int writeVariableAddress(final OutputStream destination, final int value) + throws IOException { + switch (BinaryDictInputOutput.getByteSize(value)) { + case 1: + destination.write((byte)value); + break; + case 2: + destination.write((byte)(0xFF & (value >> 8))); + destination.write((byte)(0xFF & value)); + break; + case 3: + destination.write((byte)(0xFF & (value >> 16))); + destination.write((byte)(0xFF & (value >> 8))); + destination.write((byte)(0xFF & value)); + break; + } + return BinaryDictInputOutput.getByteSize(value); + } + + /** + * Update a parent address in a CharGroup that is referred to by groupOriginAddress. * * @param buffer the buffer to write. * @param groupOriginAddress the address of the group. @@ -275,8 +333,648 @@ public final class BinaryDictIOUtils { throw new RuntimeException("this file format does not support parent addresses"); } final int flags = buffer.readUnsignedByte(); + if (BinaryDictInputOutput.isMovedGroup(flags, formatOptions)) { + // if the group is moved, the parent address is stored in the destination group. + // We are guaranteed to process the destination group later, so there is no need to + // update anything here. + buffer.position(originalPosition); + return; + } + if (DBG) { + MakedictLog.d("update parent address flags=" + flags + ", " + groupOriginAddress); + } final int parentOffset = newParentAddress - groupOriginAddress; - putSInt24(buffer, parentOffset); + writeSInt24ToBuffer(buffer, parentOffset); + buffer.position(originalPosition); + } + + private static void skipCharGroup(final FusionDictionaryBufferInterface buffer, + final FormatOptions formatOptions) { + final int flags = buffer.readUnsignedByte(); + BinaryDictInputOutput.readParentAddress(buffer, formatOptions); + skipString(buffer, (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS) != 0); + BinaryDictInputOutput.readChildrenAddress(buffer, flags, formatOptions); + if ((flags & FormatSpec.FLAG_IS_TERMINAL) != 0) buffer.readUnsignedByte(); + if ((flags & FormatSpec.FLAG_HAS_SHORTCUT_TARGETS) != 0) { + final int shortcutsSize = buffer.readUnsignedShort(); + buffer.position(buffer.position() + shortcutsSize + - FormatSpec.GROUP_SHORTCUT_LIST_SIZE_SIZE); + } + if ((flags & FormatSpec.FLAG_HAS_BIGRAMS) != 0) { + int bigramCount = 0; + while (bigramCount++ < FormatSpec.MAX_BIGRAMS_IN_A_GROUP) { + final int bigramFlags = buffer.readUnsignedByte(); + switch (bigramFlags & FormatSpec.MASK_ATTRIBUTE_ADDRESS_TYPE) { + case FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE: + buffer.readUnsignedByte(); + break; + case FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES: + buffer.readUnsignedShort(); + break; + case FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES: + buffer.readUnsignedInt24(); + break; + } + if ((bigramFlags & FormatSpec.FLAG_ATTRIBUTE_HAS_NEXT) == 0) break; + } + if (bigramCount >= FormatSpec.MAX_BIGRAMS_IN_A_GROUP) { + throw new RuntimeException("Too many bigrams in a group."); + } + } + } + + /** + * Update parent addresses in a Node that is referred to by nodeOriginAddress. + * + * @param buffer the buffer to be modified. + * @param nodeOriginAddress the address of a modified Node. + * @param newParentAddress the address to be written. + * @param formatOptions file format options. + */ + public static void updateParentAddresses(final FusionDictionaryBufferInterface buffer, + final int nodeOriginAddress, final int newParentAddress, + final FormatOptions formatOptions) { + final int originalPosition = buffer.position(); + buffer.position(nodeOriginAddress); + do { + final int count = BinaryDictInputOutput.readCharGroupCount(buffer); + for (int i = 0; i < count; ++i) { + updateParentAddress(buffer, buffer.position(), newParentAddress, formatOptions); + skipCharGroup(buffer, formatOptions); + } + final int forwardLinkAddress = buffer.readUnsignedInt24(); + buffer.position(forwardLinkAddress); + } while (formatOptions.mSupportsDynamicUpdate + && buffer.position() != FormatSpec.NO_FORWARD_LINK_ADDRESS); + buffer.position(originalPosition); + } + + private static void skipString(final FusionDictionaryBufferInterface buffer, + final boolean hasMultipleChars) { + if (hasMultipleChars) { + int character = CharEncoding.readChar(buffer); + while (character != FormatSpec.INVALID_CHARACTER) { + character = CharEncoding.readChar(buffer); + } + } else { + CharEncoding.readChar(buffer); + } + } + + /** + * Write a string to a stream. + * + * @param destination the stream to write. + * @param word the string to be written. + * @return the size written, in bytes. + * @throws IOException + */ + private static int writeString(final OutputStream destination, final String word) + throws IOException { + int size = 0; + final int length = word.length(); + for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) { + final int codePoint = word.codePointAt(i); + if (CharEncoding.getCharSize(codePoint) == 1) { + destination.write((byte)codePoint); + size++; + } else { + destination.write((byte)(0xFF & (codePoint >> 16))); + destination.write((byte)(0xFF & (codePoint >> 8))); + destination.write((byte)(0xFF & codePoint)); + size += 3; + } + } + destination.write((byte)FormatSpec.GROUP_CHARACTERS_TERMINATOR); + size += FormatSpec.GROUP_TERMINATOR_SIZE; + return size; + } + + /** + * Update a children address in a CharGroup that is addressed by groupOriginAddress. + * + * @param buffer the buffer to write. + * @param groupOriginAddress the address of the group. + * @param newChildrenAddress the absolute address of the child. + * @param formatOptions file format options. + */ + public static void updateChildrenAddress(final FusionDictionaryBufferInterface buffer, + final int groupOriginAddress, final int newChildrenAddress, + final FormatOptions formatOptions) { + final int originalPosition = buffer.position(); + buffer.position(groupOriginAddress); + final int flags = buffer.readUnsignedByte(); + final int parentAddress = BinaryDictInputOutput.readParentAddress(buffer, formatOptions); + skipString(buffer, (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS) != 0); + if ((flags & FormatSpec.FLAG_IS_TERMINAL) != 0) buffer.readUnsignedByte(); + final int childrenOffset = newChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS + ? FormatSpec.NO_CHILDREN_ADDRESS : newChildrenAddress - buffer.position(); + writeSInt24ToBuffer(buffer, childrenOffset); buffer.position(originalPosition); } + + /** + * Write a char group to an output stream. + * A char group is an in-memory representation of a node in trie. + * A char group info is an on-disk representation of a node. + * + * @param destination the stream to write. + * @param info the char group info to be written. + * @return the size written, in bytes. + */ + public static int writeCharGroup(final OutputStream destination, final CharGroupInfo info) + throws IOException { + int size = FormatSpec.GROUP_FLAGS_SIZE; + destination.write((byte)info.mFlags); + final int parentOffset = info.mParentAddress == FormatSpec.NO_PARENT_ADDRESS ? + FormatSpec.NO_PARENT_ADDRESS : info.mParentAddress - info.mOriginalAddress; + size += writeSInt24ToStream(destination, parentOffset); + + for (int i = 0; i < info.mCharacters.length; ++i) { + if (CharEncoding.getCharSize(info.mCharacters[i]) == 1) { + destination.write((byte)info.mCharacters[i]); + size++; + } else { + size += writeSInt24ToStream(destination, info.mCharacters[i]); + } + } + if (info.mCharacters.length > 1) { + destination.write((byte)FormatSpec.GROUP_CHARACTERS_TERMINATOR); + size++; + } + + if ((info.mFlags & FormatSpec.FLAG_IS_TERMINAL) != 0) { + destination.write((byte)info.mFrequency); + size++; + } + + if (DBG) { + MakedictLog.d("writeCharGroup origin=" + info.mOriginalAddress + ", size=" + size + + ", child=" + info.mChildrenAddress + ", characters =" + + new String(info.mCharacters, 0, info.mCharacters.length)); + } + final int childrenOffset = info.mChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS ? + 0 : info.mChildrenAddress - (info.mOriginalAddress + size); + writeSInt24ToStream(destination, childrenOffset); + size += FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE; + + if (info.mShortcutTargets != null && info.mShortcutTargets.size() > 0) { + final int shortcutListSize = + BinaryDictInputOutput.getShortcutListSize(info.mShortcutTargets); + destination.write((byte)(shortcutListSize >> 8)); + destination.write((byte)(shortcutListSize & 0xFF)); + size += 2; + final Iterator<WeightedString> shortcutIterator = info.mShortcutTargets.iterator(); + while (shortcutIterator.hasNext()) { + final WeightedString target = shortcutIterator.next(); + destination.write((byte)BinaryDictInputOutput.makeShortcutFlags( + shortcutIterator.hasNext(), target.mFrequency)); + size++; + size += writeString(destination, target.mWord); + } + } + + if (info.mBigrams != null) { + // TODO: Consolidate this code with the code that computes the size of the bigram list + // in BinaryDictionaryInputOutput#computeActualNodeSize + for (int i = 0; i < info.mBigrams.size(); ++i) { + + final int bigramFrequency = info.mBigrams.get(i).mFrequency; + int bigramFlags = (i < info.mBigrams.size() - 1) + ? FormatSpec.FLAG_ATTRIBUTE_HAS_NEXT : 0; + size++; + final int bigramOffset = info.mBigrams.get(i).mAddress - (info.mOriginalAddress + + size); + bigramFlags |= (bigramOffset < 0) ? FormatSpec.FLAG_ATTRIBUTE_OFFSET_NEGATIVE : 0; + switch (BinaryDictInputOutput.getByteSize(bigramOffset)) { + case 1: + bigramFlags |= FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE; + break; + case 2: + bigramFlags |= FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES; + break; + case 3: + bigramFlags |= FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES; + break; + } + bigramFlags |= bigramFrequency & FormatSpec.FLAG_ATTRIBUTE_FREQUENCY; + destination.write((byte)bigramFlags); + size += writeVariableAddress(destination, Math.abs(bigramOffset)); + } + } + return size; + } + + @SuppressWarnings("unused") + private static void updateForwardLink(final FusionDictionaryBufferInterface buffer, + final int nodeOriginAddress, final int newNodeAddress, + final FormatOptions formatOptions) { + buffer.position(nodeOriginAddress); + int jumpCount = 0; + while (jumpCount++ < MAX_JUMPS) { + final int count = BinaryDictInputOutput.readCharGroupCount(buffer); + for (int i = 0; i < count; ++i) skipCharGroup(buffer, formatOptions); + final int forwardLinkAddress = buffer.readUnsignedInt24(); + if (forwardLinkAddress == FormatSpec.NO_FORWARD_LINK_ADDRESS) { + buffer.position(buffer.position() - FormatSpec.FORWARD_LINK_ADDRESS_SIZE); + writeSInt24ToBuffer(buffer, newNodeAddress); + return; + } + buffer.position(forwardLinkAddress); + } + if (DBG && jumpCount >= MAX_JUMPS) { + throw new RuntimeException("too many jumps, probably a bug."); + } + } + + /** + * Helper method to move a char group to the tail of the file. + */ + private static int moveCharGroup(final OutputStream destination, + final FusionDictionaryBufferInterface buffer, final CharGroupInfo info, + final int nodeOriginAddress, final int oldGroupAddress, + final FormatOptions formatOptions) throws IOException { + updateParentAddress(buffer, oldGroupAddress, buffer.limit() + 1, formatOptions); + buffer.position(oldGroupAddress); + final int currentFlags = buffer.readUnsignedByte(); + buffer.position(oldGroupAddress); + buffer.put((byte)(FormatSpec.FLAG_IS_MOVED | (currentFlags + & (~FormatSpec.MASK_MOVE_AND_DELETE_FLAG)))); + int size = FormatSpec.GROUP_FLAGS_SIZE; + updateForwardLink(buffer, nodeOriginAddress, buffer.limit(), formatOptions); + size += writeNode(destination, new CharGroupInfo[] { info }); + return size; + } + + /** + * Compute the size of the char group. + */ + private static int computeGroupSize(final CharGroupInfo info, + final FormatOptions formatOptions) { + int size = FormatSpec.GROUP_FLAGS_SIZE + FormatSpec.PARENT_ADDRESS_SIZE + + BinaryDictInputOutput.getGroupCharactersSize(info.mCharacters) + + BinaryDictInputOutput.getChildrenAddressSize(info.mFlags, formatOptions); + if ((info.mFlags & FormatSpec.FLAG_IS_TERMINAL) != 0) { + size += FormatSpec.GROUP_FREQUENCY_SIZE; + } + if (info.mShortcutTargets != null && !info.mShortcutTargets.isEmpty()) { + size += BinaryDictInputOutput.getShortcutListSize(info.mShortcutTargets); + } + if (info.mBigrams != null) { + for (final PendingAttribute attr : info.mBigrams) { + size += FormatSpec.GROUP_FLAGS_SIZE; + size += BinaryDictInputOutput.getByteSize(attr.mAddress); + } + } + return size; + } + + /** + * Write a node to the stream. + * + * @param destination the stream to write. + * @param infos groups to be written. + * @return the size written, in bytes. + * @throws IOException + */ + private static int writeNode(final OutputStream destination, final CharGroupInfo[] infos) + throws IOException { + int size = BinaryDictInputOutput.getGroupCountSize(infos.length); + switch (BinaryDictInputOutput.getGroupCountSize(infos.length)) { + case 1: + destination.write((byte)infos.length); + break; + case 2: + destination.write((byte)(infos.length >> 8)); + destination.write((byte)(infos.length & 0xFF)); + break; + default: + throw new RuntimeException("Invalid group count size."); + } + for (final CharGroupInfo info : infos) size += writeCharGroup(destination, info); + writeSInt24ToStream(destination, FormatSpec.NO_FORWARD_LINK_ADDRESS); + return size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE; + } + + /** + * Move a group that is referred to by oldGroupOrigin to the tail of the file. + * And set the children address to the byte after the group. + * + * @param nodeOrigin the address of the tail of the file. + * @param characters + * @param length + * @param flags + * @param frequency + * @param parentAddress + * @param shortcutTargets + * @param bigrams + * @param destination the stream representing the tail of the file. + * @param buffer the buffer representing the (constant-size) body of the file. + * @param oldNodeOrigin + * @param oldGroupOrigin + * @param formatOptions + * @return the size written, in bytes. + * @throws IOException + */ + private static int moveGroup(final int nodeOrigin, final int[] characters, final int length, + final int flags, final int frequency, final int parentAddress, + final ArrayList<WeightedString> shortcutTargets, + final ArrayList<PendingAttribute> bigrams, final OutputStream destination, + final FusionDictionaryBufferInterface buffer, final int oldNodeOrigin, + final int oldGroupOrigin, final FormatOptions formatOptions) throws IOException { + int size = 0; + final int newGroupOrigin = nodeOrigin + 1; + final int[] writtenCharacters = Arrays.copyOfRange(characters, 0, length); + final CharGroupInfo tmpInfo = new CharGroupInfo(newGroupOrigin, -1 /* endAddress */, + flags, writtenCharacters, frequency, parentAddress, FormatSpec.NO_CHILDREN_ADDRESS, + shortcutTargets, bigrams); + size = computeGroupSize(tmpInfo, formatOptions); + final CharGroupInfo newInfo = new CharGroupInfo(newGroupOrigin, newGroupOrigin + size, + flags, writtenCharacters, frequency, parentAddress, + nodeOrigin + 1 + size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE, shortcutTargets, + bigrams); + moveCharGroup(destination, buffer, newInfo, oldNodeOrigin, oldGroupOrigin, formatOptions); + return 1 + size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE; + } + + /** + * Insert a word into a binary dictionary. + * + * @param buffer + * @param destination + * @param word + * @param frequency + * @param bigramStrings + * @param shortcuts + * @throws IOException + * @throws UnsupportedFormatException + */ + // TODO: Support batch insertion. + // TODO: Remove @UsedForTesting once UserHistoryDictionary is implemented by BinaryDictionary. + @UsedForTesting + public static void insertWord(final FusionDictionaryBufferInterface buffer, + final OutputStream destination, final String word, final int frequency, + final ArrayList<WeightedString> bigramStrings, + final ArrayList<WeightedString> shortcuts, final boolean isNotAWord, + final boolean isBlackListEntry) + throws IOException, UnsupportedFormatException { + final ArrayList<PendingAttribute> bigrams = new ArrayList<PendingAttribute>(); + if (bigramStrings != null) { + for (final WeightedString bigram : bigramStrings) { + int position = getTerminalPosition(buffer, bigram.mWord); + if (position == FormatSpec.NOT_VALID_WORD) { + // TODO: figure out what is the correct thing to do here. + } else { + bigrams.add(new PendingAttribute(bigram.mFrequency, position)); + } + } + } + + final boolean isTerminal = true; + final boolean hasBigrams = !bigrams.isEmpty(); + final boolean hasShortcuts = shortcuts != null && !shortcuts.isEmpty(); + + // find the insert position of the word. + if (buffer.position() != 0) buffer.position(0); + final FileHeader header = BinaryDictInputOutput.readHeader(buffer); + + int wordPos = 0, address = buffer.position(), nodeOriginAddress = buffer.position(); + final int[] codePoints = FusionDictionary.getCodePoints(word); + final int wordLen = codePoints.length; + + for (int depth = 0; depth < Constants.Dictionary.MAX_WORD_LENGTH; ++depth) { + if (wordPos >= wordLen) break; + nodeOriginAddress = buffer.position(); + int nodeParentAddress = -1; + final int charGroupCount = BinaryDictInputOutput.readCharGroupCount(buffer); + boolean foundNextGroup = false; + + for (int i = 0; i < charGroupCount; ++i) { + address = buffer.position(); + final CharGroupInfo currentInfo = BinaryDictInputOutput.readCharGroup(buffer, + buffer.position(), header.mFormatOptions); + final boolean isMovedGroup = BinaryDictInputOutput.isMovedGroup(currentInfo.mFlags, + header.mFormatOptions); + if (isMovedGroup) continue; + nodeParentAddress = (currentInfo.mParentAddress == FormatSpec.NO_PARENT_ADDRESS) + ? FormatSpec.NO_PARENT_ADDRESS : currentInfo.mParentAddress + address; + boolean matched = true; + for (int p = 0; p < currentInfo.mCharacters.length; ++p) { + if (wordPos + p >= wordLen) { + /* + * splitting + * before + * abcd - ef + * + * insert "abc" + * + * after + * abc - d - ef + */ + final int newNodeAddress = buffer.limit(); + final int flags = BinaryDictInputOutput.makeCharGroupFlags(p > 1, + isTerminal, 0, hasShortcuts, hasBigrams, false /* isNotAWord */, + false /* isBlackListEntry */, header.mFormatOptions); + int written = moveGroup(newNodeAddress, currentInfo.mCharacters, p, flags, + frequency, nodeParentAddress, shortcuts, bigrams, destination, + buffer, nodeOriginAddress, address, header.mFormatOptions); + + final int[] characters2 = Arrays.copyOfRange(currentInfo.mCharacters, p, + currentInfo.mCharacters.length); + if (currentInfo.mChildrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) { + updateParentAddresses(buffer, currentInfo.mChildrenAddress, + newNodeAddress + written + 1, header.mFormatOptions); + } + final CharGroupInfo newInfo2 = new CharGroupInfo( + newNodeAddress + written + 1, -1 /* endAddress */, + currentInfo.mFlags, characters2, currentInfo.mFrequency, + newNodeAddress + 1, currentInfo.mChildrenAddress, + currentInfo.mShortcutTargets, currentInfo.mBigrams); + writeNode(destination, new CharGroupInfo[] { newInfo2 }); + return; + } else if (codePoints[wordPos + p] != currentInfo.mCharacters[p]) { + if (p > 0) { + /* + * splitting + * before + * ab - cd + * + * insert "ac" + * + * after + * a - b - cd + * | + * - c + */ + + final int newNodeAddress = buffer.limit(); + final int childrenAddress = currentInfo.mChildrenAddress; + + // move prefix + final int prefixFlags = BinaryDictInputOutput.makeCharGroupFlags(p > 1, + false /* isTerminal */, 0 /* childrenAddressSize*/, + false /* hasShortcut */, false /* hasBigrams */, + false /* isNotAWord */, false /* isBlackListEntry */, + header.mFormatOptions); + int written = moveGroup(newNodeAddress, currentInfo.mCharacters, p, + prefixFlags, -1 /* frequency */, nodeParentAddress, null, null, + destination, buffer, nodeOriginAddress, address, + header.mFormatOptions); + + final int[] suffixCharacters = Arrays.copyOfRange( + currentInfo.mCharacters, p, currentInfo.mCharacters.length); + if (currentInfo.mChildrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) { + updateParentAddresses(buffer, currentInfo.mChildrenAddress, + newNodeAddress + written + 1, header.mFormatOptions); + } + final int suffixFlags = BinaryDictInputOutput.makeCharGroupFlags( + suffixCharacters.length > 1, + (currentInfo.mFlags & FormatSpec.FLAG_IS_TERMINAL) != 0, + 0 /* childrenAddressSize */, + (currentInfo.mFlags & FormatSpec.FLAG_HAS_SHORTCUT_TARGETS) + != 0, + (currentInfo.mFlags & FormatSpec.FLAG_HAS_BIGRAMS) != 0, + isNotAWord, isBlackListEntry, header.mFormatOptions); + final CharGroupInfo suffixInfo = new CharGroupInfo( + newNodeAddress + written + 1, -1 /* endAddress */, suffixFlags, + suffixCharacters, currentInfo.mFrequency, newNodeAddress + 1, + currentInfo.mChildrenAddress, currentInfo.mShortcutTargets, + currentInfo.mBigrams); + written += computeGroupSize(suffixInfo, header.mFormatOptions) + 1; + + final int[] newCharacters = Arrays.copyOfRange(codePoints, wordPos + p, + codePoints.length); + final int flags = BinaryDictInputOutput.makeCharGroupFlags( + newCharacters.length > 1, isTerminal, + 0 /* childrenAddressSize */, hasShortcuts, hasBigrams, + isNotAWord, isBlackListEntry, header.mFormatOptions); + final CharGroupInfo newInfo = new CharGroupInfo( + newNodeAddress + written, -1 /* endAddress */, flags, + newCharacters, frequency, newNodeAddress + 1, + FormatSpec.NO_CHILDREN_ADDRESS, shortcuts, bigrams); + writeNode(destination, new CharGroupInfo[] { suffixInfo, newInfo }); + return; + } + matched = false; + break; + } + } + + if (matched) { + if (wordPos + currentInfo.mCharacters.length == wordLen) { + // the word exists in the dictionary. + // only update group. + final int newNodeAddress = buffer.limit(); + final boolean hasMultipleChars = currentInfo.mCharacters.length > 1; + final int flags = BinaryDictInputOutput.makeCharGroupFlags(hasMultipleChars, + isTerminal, 0 /* childrenAddressSize */, hasShortcuts, hasBigrams, + isNotAWord, isBlackListEntry, header.mFormatOptions); + final CharGroupInfo newInfo = new CharGroupInfo(newNodeAddress + 1, + -1 /* endAddress */, flags, currentInfo.mCharacters, frequency, + nodeParentAddress, currentInfo.mChildrenAddress, shortcuts, + bigrams); + moveCharGroup(destination, buffer, newInfo, nodeOriginAddress, address, + header.mFormatOptions); + return; + } + wordPos += currentInfo.mCharacters.length; + if (currentInfo.mChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS) { + /* + * found the prefix of the word. + * make new node and link to the node from this group. + * + * before + * ab - cd + * + * insert "abcde" + * + * after + * ab - cd - e + */ + final int newNodeAddress = buffer.limit(); + updateChildrenAddress(buffer, address, newNodeAddress, + header.mFormatOptions); + final int newGroupAddress = newNodeAddress + 1; + final boolean hasMultipleChars = (wordLen - wordPos) > 1; + final int flags = BinaryDictInputOutput.makeCharGroupFlags(hasMultipleChars, + isTerminal, 0 /* childrenAddressSize */, hasShortcuts, hasBigrams, + isNotAWord, isBlackListEntry, header.mFormatOptions); + final int[] characters = Arrays.copyOfRange(codePoints, wordPos, wordLen); + final CharGroupInfo newInfo = new CharGroupInfo(newGroupAddress, -1, flags, + characters, frequency, address, FormatSpec.NO_CHILDREN_ADDRESS, + shortcuts, bigrams); + writeNode(destination, new CharGroupInfo[] { newInfo }); + return; + } + buffer.position(currentInfo.mChildrenAddress); + foundNextGroup = true; + break; + } + } + + if (foundNextGroup) continue; + + // reached the end of the array. + final int linkAddressPosition = buffer.position(); + int nextLink = buffer.readUnsignedInt24(); + if ((nextLink & MSB24) != 0) { + nextLink = -(nextLink & SINT24_MAX); + } + if (nextLink == FormatSpec.NO_FORWARD_LINK_ADDRESS) { + /* + * expand this node. + * + * before + * ab - cd + * + * insert "abef" + * + * after + * ab - cd + * | + * - ef + */ + + // change the forward link address. + final int newNodeAddress = buffer.limit(); + buffer.position(linkAddressPosition); + writeSInt24ToBuffer(buffer, newNodeAddress); + + final int[] characters = Arrays.copyOfRange(codePoints, wordPos, wordLen); + final int flags = BinaryDictInputOutput.makeCharGroupFlags(characters.length > 1, + isTerminal, 0 /* childrenAddressSize */, hasShortcuts, hasBigrams, + isNotAWord, isBlackListEntry, header.mFormatOptions); + final CharGroupInfo newInfo = new CharGroupInfo(newNodeAddress + 1, + -1 /* endAddress */, flags, characters, frequency, nodeParentAddress, + FormatSpec.NO_CHILDREN_ADDRESS, shortcuts, bigrams); + writeNode(destination, new CharGroupInfo[]{ newInfo }); + return; + } else { + depth--; + buffer.position(nextLink); + } + } + } + + /** + * Find a word from the buffer. + * + * @param buffer the buffer representing the body of the dictionary file. + * @param word the word searched + * @return the found group + * @throws IOException + * @throws UnsupportedFormatException + */ + @UsedForTesting + public static CharGroupInfo findWordFromBuffer(final FusionDictionaryBufferInterface buffer, + final String word) throws IOException, UnsupportedFormatException { + int position = getTerminalPosition(buffer, word); + if (position != FormatSpec.NOT_VALID_WORD) { + buffer.position(0); + final FileHeader header = BinaryDictInputOutput.readHeader(buffer); + buffer.position(position); + return BinaryDictInputOutput.readCharGroup(buffer, position, header.mFormatOptions); + } + return null; + } } diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java index b431a4da9..f1a7e97e8 100644 --- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java +++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java @@ -16,6 +16,7 @@ package com.android.inputmethod.latin.makedict; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader; import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup; @@ -76,12 +77,12 @@ public final class BinaryDictInputOutput { @Override public int readUnsignedByte() { - return ((int)mBuffer.get()) & 0xFF; + return mBuffer.get() & 0xFF; } @Override public int readUnsignedShort() { - return ((int)mBuffer.getShort()) & 0xFFFF; + return mBuffer.getShort() & 0xFFFF; } @Override @@ -124,8 +125,7 @@ public final class BinaryDictInputOutput { /** * A class grouping utility function for our specific character encoding. */ - private static final class CharEncoding { - + static final class CharEncoding { private static final int MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20; private static final int MAXIMAL_ONE_BYTE_CHARACTER_VALUE = 0xFF; @@ -154,7 +154,7 @@ public final class BinaryDictInputOutput { * @param character the character code. * @return the size in binary encoded-form, either 1 or 3 bytes. */ - private static int getCharSize(final int character) { + static int getCharSize(final int character) { // See char encoding in FusionDictionary.java if (fitsOnOneByte(character)) return 1; if (FormatSpec.INVALID_CHARACTER == character) return 1; @@ -263,7 +263,7 @@ public final class BinaryDictInputOutput { * @param buffer the buffer, positioned over an encoded character. * @return the character code. */ - private static int readChar(final FusionDictionaryBufferInterface buffer) { + static int readChar(final FusionDictionaryBufferInterface buffer) { int character = buffer.readUnsignedByte(); if (!fitsOnOneByte(character)) { if (FormatSpec.GROUP_CHARACTERS_TERMINATOR == character) { @@ -277,6 +277,21 @@ public final class BinaryDictInputOutput { } /** + * Compute the binary size of the character array. + * + * If only one character, this is the size of this character. If many, it's the sum of their + * sizes + 1 byte for the terminator. + * + * @param characters the character array + * @return the size of the char array, including the terminator if any + */ + static int getGroupCharactersSize(final int[] characters) { + int size = CharEncoding.getCharArraySize(characters); + if (characters.length > 1) size += FormatSpec.GROUP_TERMINATOR_SIZE; + return size; + } + + /** * Compute the binary size of the character array in a group * * If only one character, this is the size of this character. If many, it's the sum of their @@ -286,9 +301,7 @@ public final class BinaryDictInputOutput { * @return the size of the char array, including the terminator if any */ private static int getGroupCharactersSize(final CharGroup group) { - int size = CharEncoding.getCharArraySize(group.mChars); - if (group.hasSeveralChars()) size += FormatSpec.GROUP_TERMINATOR_SIZE; - return size; + return getGroupCharactersSize(group.mChars); } /** @@ -338,7 +351,7 @@ public final class BinaryDictInputOutput { * This is known in advance and does not change according to position in the file * like address lists do. */ - private static int getShortcutListSize(final ArrayList<WeightedString> shortcutList) { + static int getShortcutListSize(final ArrayList<WeightedString> shortcutList) { if (null == shortcutList) return 0; int size = FormatSpec.GROUP_SHORTCUT_LIST_SIZE_SIZE; for (final WeightedString shortcut : shortcutList) { @@ -399,7 +412,16 @@ public final class BinaryDictInputOutput { * Helper method to check whether the group is moved. */ public static boolean isMovedGroup(final int flags, final FormatOptions options) { - return options.mSupportsDynamicUpdate && ((flags & FormatSpec.FLAG_IS_MOVED) == 1); + return options.mSupportsDynamicUpdate + && ((flags & FormatSpec.MASK_GROUP_ADDRESS_TYPE) == FormatSpec.FLAG_IS_MOVED); + } + + /** + * Helper method to check whether the group is deleted. + */ + public static boolean isDeletedGroup(final int flags, final FormatOptions formatOptions) { + return formatOptions.mSupportsDynamicUpdate + && ((flags & FormatSpec.MASK_GROUP_ADDRESS_TYPE) == FormatSpec.FLAG_IS_DELETED); } /** @@ -439,7 +461,7 @@ public final class BinaryDictInputOutput { * @param address the address * @return the byte size. */ - private static int getByteSize(final int address) { + static int getByteSize(final int address) { assert(address <= UINT24_MAX); if (!hasChildrenAddress(address)) { return 0; @@ -452,11 +474,8 @@ public final class BinaryDictInputOutput { } } - private static final int SINT8_MAX = 0x7F; - private static final int SINT16_MAX = 0x7FFF; private static final int SINT24_MAX = 0x7FFFFF; private static final int MSB8 = 0x80; - private static final int MSB16 = 0x8000; private static final int MSB24 = 0x800000; // End utility methods. @@ -721,53 +740,60 @@ public final class BinaryDictInputOutput { return 3; } - private static byte makeCharGroupFlags(final CharGroup group, final int groupAddress, - final int childrenOffset, final FormatOptions formatOptions) { + /** + * Makes the flag value for a char group. + * + * @param hasMultipleChars whether the group has multiple chars. + * @param isTerminal whether the group is terminal. + * @param childrenAddressSize the size of a children address. + * @param hasShortcuts whether the group has shortcuts. + * @param hasBigrams whether the group has bigrams. + * @param isNotAWord whether the group is not a word. + * @param isBlackListEntry whether the group is a blacklist entry. + * @param formatOptions file format options. + * @return the flags + */ + static int makeCharGroupFlags(final boolean hasMultipleChars, final boolean isTerminal, + final int childrenAddressSize, final boolean hasShortcuts, final boolean hasBigrams, + final boolean isNotAWord, final boolean isBlackListEntry, + final FormatOptions formatOptions) { byte flags = 0; - if (group.mChars.length > 1) flags |= FormatSpec.FLAG_HAS_MULTIPLE_CHARS; - if (group.mFrequency >= 0) { - flags |= FormatSpec.FLAG_IS_TERMINAL; - } - if (null != group.mChildren) { - final int byteSize = formatOptions.mSupportsDynamicUpdate - ? FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE : getByteSize(childrenOffset); - switch (byteSize) { - case 1: - flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_ONEBYTE; - break; - case 2: - flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_TWOBYTES; - break; - case 3: - flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_THREEBYTES; - break; - default: - throw new RuntimeException("Node with a strange address"); - } - } else if (formatOptions.mSupportsDynamicUpdate) { - flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_THREEBYTES; - } - if (null != group.mShortcutTargets) { - if (DBG && 0 == group.mShortcutTargets.size()) { - throw new RuntimeException("0-sized shortcut list must be null"); - } - flags |= FormatSpec.FLAG_HAS_SHORTCUT_TARGETS; - } - if (null != group.mBigrams) { - if (DBG && 0 == group.mBigrams.size()) { - throw new RuntimeException("0-sized bigram list must be null"); + if (hasMultipleChars) flags |= FormatSpec.FLAG_HAS_MULTIPLE_CHARS; + if (isTerminal) flags |= FormatSpec.FLAG_IS_TERMINAL; + if (formatOptions.mSupportsDynamicUpdate) { + flags |= FormatSpec.FLAG_IS_NOT_MOVED; + } else if (true) { + switch (childrenAddressSize) { + case 1: + flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_ONEBYTE; + break; + case 2: + flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_TWOBYTES; + break; + case 3: + flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_THREEBYTES; + break; + case 0: + flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_NOADDRESS; + break; + default: + throw new RuntimeException("Node with a strange address"); } - flags |= FormatSpec.FLAG_HAS_BIGRAMS; - } - if (group.mIsNotAWord) { - flags |= FormatSpec.FLAG_IS_NOT_A_WORD; - } - if (group.mIsBlacklistEntry) { - flags |= FormatSpec.FLAG_IS_BLACKLISTED; } + if (hasShortcuts) flags |= FormatSpec.FLAG_HAS_SHORTCUT_TARGETS; + if (hasBigrams) flags |= FormatSpec.FLAG_HAS_BIGRAMS; + if (isNotAWord) flags |= FormatSpec.FLAG_IS_NOT_A_WORD; + if (isBlackListEntry) flags |= FormatSpec.FLAG_IS_BLACKLISTED; return flags; } + private static byte makeCharGroupFlags(final CharGroup group, final int groupAddress, + final int childrenOffset, final FormatOptions formatOptions) { + return (byte) makeCharGroupFlags(group.mChars.length > 1, group.mFrequency >= 0, + getByteSize(childrenOffset), group.mShortcutTargets != null, group.mBigrams != null, + group.mIsNotAWord, group.mIsBlacklistEntry, formatOptions); + } + /** * Makes the flag value for a bigram. * @@ -859,7 +885,7 @@ public final class BinaryDictInputOutput { * @param frequency the frequency of the attribute, 0..15 * @return the flags */ - private static final int makeShortcutFlags(final boolean more, final int frequency) { + static final int makeShortcutFlags(final boolean more, final int frequency) { return (more ? FormatSpec.FLAG_ATTRIBUTE_HAS_NEXT : 0) + (frequency & FormatSpec.FLAG_ATTRIBUTE_FREQUENCY); } @@ -895,8 +921,10 @@ public final class BinaryDictInputOutput { * @param formatOptions file format options. * @return the address of the END of the node. */ + @SuppressWarnings("unused") private static int writePlacedNode(final FusionDictionary dict, byte[] buffer, final Node node, final FormatOptions formatOptions) { + // TODO: Make the code in common with BinaryDictIOUtils#writeCharGroup int index = node.mCachedAddress; final int groupCount = node.mData.size(); @@ -1178,7 +1206,7 @@ public final class BinaryDictInputOutput { // Input methods: Read a binary dictionary to memory. // readDictionaryBinary is the public entry point for them. - private static int getChildrenAddressSize(final int optionFlags, + static int getChildrenAddressSize(final int optionFlags, final FormatOptions formatOptions) { if (formatOptions.mSupportsDynamicUpdate) return FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE; switch (optionFlags & FormatSpec.MASK_GROUP_ADDRESS_TYPE) { @@ -1194,7 +1222,7 @@ public final class BinaryDictInputOutput { } } - private static int readChildrenAddress(final FusionDictionaryBufferInterface buffer, + static int readChildrenAddress(final FusionDictionaryBufferInterface buffer, final int optionFlags, final FormatOptions options) { if (options.mSupportsDynamicUpdate) { final int address = buffer.readUnsignedInt24(); @@ -1219,7 +1247,7 @@ public final class BinaryDictInputOutput { } } - private static int readParentAddress(final FusionDictionaryBufferInterface buffer, + static int readParentAddress(final FusionDictionaryBufferInterface buffer, final FormatOptions formatOptions) { if (supportsDynamicUpdate(formatOptions)) { final int parentAddress = buffer.readUnsignedInt24(); @@ -1290,7 +1318,8 @@ public final class BinaryDictInputOutput { ArrayList<PendingAttribute> bigrams = null; if (0 != (flags & FormatSpec.FLAG_HAS_BIGRAMS)) { bigrams = new ArrayList<PendingAttribute>(); - while (true) { + int bigramCount = 0; + while (bigramCount++ < FormatSpec.MAX_BIGRAMS_IN_A_GROUP) { final int bigramFlags = buffer.readUnsignedByte(); ++addressPointer; final int sign = 0 == (bigramFlags & FormatSpec.FLAG_ATTRIBUTE_OFFSET_NEGATIVE) @@ -1318,6 +1347,9 @@ public final class BinaryDictInputOutput { bigramAddress)); if (0 == (bigramFlags & FormatSpec.FLAG_ATTRIBUTE_HAS_NEXT)) break; } + if (bigramCount >= FormatSpec.MAX_BIGRAMS_IN_A_GROUP) { + MakedictLog.d("too many bigrams in a group."); + } } return new CharGroupInfo(originalGroupAddress, addressPointer, flags, characters, frequency, parentAddress, childrenAddress, shortcutTargets, bigrams); @@ -1340,7 +1372,8 @@ public final class BinaryDictInputOutput { // of this method. Since it performs direct, unbuffered random access to the file and // may be called hundreds of thousands of times, the resulting performance is not // reasonable without some kind of cache. Thus: - private static TreeMap<Integer, String> wordCache = new TreeMap<Integer, String>(); + private static TreeMap<Integer, WeightedString> wordCache = + new TreeMap<Integer, WeightedString>(); /** * Finds, as a string, the word at the address passed as an argument. * @@ -1348,15 +1381,15 @@ public final class BinaryDictInputOutput { * @param headerSize the size of the header. * @param address the address to seek. * @param formatOptions file format options. - * @return the word, as a string. + * @return the word with its frequency, as a weighted string. */ - /* packages for tests */ static String getWordAtAddress( + /* package for tests */ static WeightedString getWordAtAddress( final FusionDictionaryBufferInterface buffer, final int headerSize, final int address, final FormatOptions formatOptions) { - final String cachedString = wordCache.get(address); + final WeightedString cachedString = wordCache.get(address); if (null != cachedString) return cachedString; - final String result; + final WeightedString result; final int originalPointer = buffer.position(); buffer.position(address); @@ -1372,14 +1405,16 @@ public final class BinaryDictInputOutput { return result; } + // TODO: static!? This will behave erratically when used in multi-threaded code. + // We need to fix this private static int[] sGetWordBuffer = new int[FormatSpec.MAX_WORD_LENGTH]; - private static String getWordAtAddressWithParentAddress( + @SuppressWarnings("unused") + private static WeightedString getWordAtAddressWithParentAddress( final FusionDictionaryBufferInterface buffer, final int headerSize, final int address, final FormatOptions options) { - final StringBuilder builder = new StringBuilder(); - int currentAddress = address; int index = FormatSpec.MAX_WORD_LENGTH - 1; + int frequency = Integer.MIN_VALUE; // the length of the path from the root to the leaf is limited by MAX_WORD_LENGTH for (int count = 0; count < FormatSpec.MAX_WORD_LENGTH; ++count) { CharGroupInfo currentInfo; @@ -1394,6 +1429,7 @@ public final class BinaryDictInputOutput { MakedictLog.d("Too many jumps - probably a bug"); } } while (isMovedGroup(currentInfo.mFlags, options)); + if (Integer.MIN_VALUE == frequency) frequency = currentInfo.mFrequency; for (int i = 0; i < currentInfo.mCharacters.length; ++i) { sGetWordBuffer[index--] = currentInfo.mCharacters[currentInfo.mCharacters.length - i - 1]; @@ -1402,17 +1438,19 @@ public final class BinaryDictInputOutput { currentAddress = currentInfo.mParentAddress + currentInfo.mOriginalAddress; } - return new String(sGetWordBuffer, index + 1, FormatSpec.MAX_WORD_LENGTH - index - 1); + return new WeightedString( + new String(sGetWordBuffer, index + 1, FormatSpec.MAX_WORD_LENGTH - index - 1), + frequency); } - private static String getWordAtAddressWithoutParentAddress( + private static WeightedString getWordAtAddressWithoutParentAddress( final FusionDictionaryBufferInterface buffer, final int headerSize, final int address, final FormatOptions options) { buffer.position(headerSize); final int count = readCharGroupCount(buffer); int groupOffset = getGroupCountSize(count); final StringBuilder builder = new StringBuilder(); - String result = null; + WeightedString result = null; CharGroupInfo last = null; for (int i = count - 1; i >= 0; --i) { @@ -1420,7 +1458,7 @@ public final class BinaryDictInputOutput { groupOffset = info.mEndAddress; if (info.mOriginalAddress == address) { builder.append(new String(info.mCharacters, 0, info.mCharacters.length)); - result = builder.toString(); + result = new WeightedString(builder.toString(), info.mFrequency); break; // and return } if (hasChildrenAddress(info.mChildrenAddress)) { @@ -1481,9 +1519,11 @@ public final class BinaryDictInputOutput { if (null != info.mBigrams) { bigrams = new ArrayList<WeightedString>(); for (PendingAttribute bigram : info.mBigrams) { - final String word = getWordAtAddress( + final WeightedString word = getWordAtAddress( buffer, headerSize, bigram.mAddress, options); - bigrams.add(new WeightedString(word, bigram.mFrequency)); + final int reconstructedFrequency = + reconstructBigramFrequency(word.mFrequency, bigram.mFrequency); + bigrams.add(new WeightedString(word.mWord, reconstructedFrequency)); } } if (hasChildrenAddress(info.mChildrenAddress)) { @@ -1618,6 +1658,7 @@ public final class BinaryDictInputOutput { * @param dict an optional dictionary to add words to, or null. * @return the created (or merged) dictionary. */ + @UsedForTesting public static FusionDictionary readDictionaryBinary( final FusionDictionaryBufferInterface buffer, final FusionDictionary dict) throws IOException, UnsupportedFormatException { @@ -1655,17 +1696,24 @@ public final class BinaryDictInputOutput { } /** + * Helper method to pass a file name instead of a File object to isBinaryDictionary. + */ + public static boolean isBinaryDictionary(final String filename) { + final File file = new File(filename); + return isBinaryDictionary(file); + } + + /** * Basic test to find out whether the file is a binary dictionary or not. * * Concretely this only tests the magic number. * - * @param filename The name of the file to test. + * @param file The file to test. * @return true if it's a binary dictionary, false otherwise */ - public static boolean isBinaryDictionary(final String filename) { + public static boolean isBinaryDictionary(final File file) { FileInputStream inStream = null; try { - final File file = new File(filename); inStream = new FileInputStream(file); final ByteBuffer buffer = inStream.getChannel().map( FileChannel.MapMode.READ_ONLY, 0, file.length()); @@ -1700,8 +1748,7 @@ public final class BinaryDictInputOutput { final int bigramFrequency) { final float stepSize = (FormatSpec.MAX_TERMINAL_FREQUENCY - unigramFrequency) / (1.5f + FormatSpec.MAX_BIGRAM_FREQUENCY); - final float resultFreqFloat = (float)unigramFrequency - + stepSize * (bigramFrequency + 1.0f); + final float resultFreqFloat = unigramFrequency + stepSize * (bigramFrequency + 1.0f); return (int)resultFreqFloat; } } diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java index b3fbb9fb5..705f66414 100644 --- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java +++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java @@ -59,9 +59,11 @@ public final class FormatSpec { * l | 10 = 2 bytes : FLAG_GROUP_ADDRESS_TYPE_TWOBYTES * a | 11 = 3 bytes : FLAG_GROUP_ADDRESS_TYPE_THREEBYTES * g | ELSE - * s | is moved ? 2 bits, 11 = no - * | 01 = yes + * s | is moved ? 2 bits, 11 = no : FLAG_IS_NOT_MOVED + * | This must be the same as FLAG_GROUP_ADDRESS_TYPE_THREEBYTES + * | 01 = yes : FLAG_IS_MOVED * | the new address is stored in the same place as the parent address + * | is deleted? 10 = yes : FLAG_IS_DELETED * | has several chars ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_MULTIPLE_CHARS * | has a terminal ? 1 bit, 1 = yes, 0 = no : FLAG_IS_TERMINAL * | has shortcut targets ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_SHORTCUT_TARGETS @@ -170,6 +172,7 @@ public final class FormatSpec { static final int PARENT_ADDRESS_SIZE = 3; static final int FORWARD_LINK_ADDRESS_SIZE = 3; + // These flags are used only in the static dictionary. static final int MASK_GROUP_ADDRESS_TYPE = 0xC0; static final int FLAG_GROUP_ADDRESS_TYPE_NOADDRESS = 0x00; static final int FLAG_GROUP_ADDRESS_TYPE_ONEBYTE = 0x40; @@ -183,7 +186,13 @@ public final class FormatSpec { static final int FLAG_HAS_BIGRAMS = 0x04; static final int FLAG_IS_NOT_A_WORD = 0x02; static final int FLAG_IS_BLACKLISTED = 0x01; - static final int FLAG_IS_MOVED = 0x40; + + // These flags are used only in the dynamic dictionary. + static final int MASK_MOVE_AND_DELETE_FLAG = 0xC0; + static final int FIXED_BIT_OF_DYNAMIC_UPDATE_MOVE = 0x40; + static final int FLAG_IS_MOVED = 0x00 | FIXED_BIT_OF_DYNAMIC_UPDATE_MOVE; + static final int FLAG_IS_NOT_MOVED = 0x80 | FIXED_BIT_OF_DYNAMIC_UPDATE_MOVE; + static final int FLAG_IS_DELETED = 0x80; static final int FLAG_ATTRIBUTE_HAS_NEXT = 0x80; static final int FLAG_ATTRIBUTE_OFFSET_NEGATIVE = 0x40; @@ -210,10 +219,13 @@ public final class FormatSpec { static final int MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT = 0x7F; // 127 static final int MAX_CHARGROUPS_IN_A_NODE = 0x7FFF; // 32767 + static final int MAX_BIGRAMS_IN_A_GROUP = 10000; static final int MAX_TERMINAL_FREQUENCY = 255; static final int MAX_BIGRAM_FREQUENCY = 15; + public static final int SHORTCUT_WHITELIST_FREQUENCY = 15; + // This option needs to be the same numeric value as the one in binary_format.h. static final int NOT_VALID_WORD = -99; static final int SIGNED_CHILDREN_ADDRESS_SIZE = 3; diff --git a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java index 3193ef457..ee647abed 100644 --- a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java +++ b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java @@ -21,6 +21,7 @@ import com.android.inputmethod.latin.Constants; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; @@ -141,6 +142,33 @@ public final class FusionDictionary implements Iterable<Word> { return NOT_A_TERMINAL != mFrequency; } + public int getFrequency() { + return mFrequency; + } + + public boolean getIsNotAWord() { + return mIsNotAWord; + } + + public boolean getIsBlacklistEntry() { + return mIsBlacklistEntry; + } + + public ArrayList<WeightedString> getShortcutTargets() { + // We don't want write permission to escape outside the package, so we return a copy + if (null == mShortcutTargets) return null; + final ArrayList<WeightedString> copyOfShortcutTargets = + new ArrayList<WeightedString>(mShortcutTargets); + return copyOfShortcutTargets; + } + + public ArrayList<WeightedString> getBigrams() { + // We don't want write permission to escape outside the package, so we return a copy + if (null == mBigrams) return null; + final ArrayList<WeightedString> copyOfBigrams = new ArrayList<WeightedString>(mBigrams); + return copyOfBigrams; + } + public boolean hasSeveralChars() { assert(mChars.length > 0); return 1 < mChars.length; @@ -249,8 +277,6 @@ public final class FusionDictionary implements Iterable<Word> { /** * Options global to the dictionary. - * - * There are no options at the moment, so this class is empty. */ public static final class DictionaryOptions { public final boolean mGermanUmlautProcessing; @@ -262,6 +288,43 @@ public final class FusionDictionary implements Iterable<Word> { mGermanUmlautProcessing = germanUmlautProcessing; mFrenchLigatureProcessing = frenchLigatureProcessing; } + @Override + public String toString() { // Convenience method + return toString(0, false); + } + public String toString(final int indentCount, final boolean plumbing) { + final StringBuilder indent = new StringBuilder(); + if (plumbing) { + indent.append("H:"); + } else { + for (int i = 0; i < indentCount; ++i) { + indent.append(" "); + } + } + final StringBuilder s = new StringBuilder(); + for (final String optionKey : mAttributes.keySet()) { + s.append(indent); + s.append(optionKey); + s.append(" = "); + if ("date".equals(optionKey) && !plumbing) { + // Date needs a number of milliseconds, but the dictionary contains seconds + s.append(new Date( + 1000 * Long.parseLong(mAttributes.get(optionKey))).toString()); + } else { + s.append(mAttributes.get(optionKey)); + } + s.append("\n"); + } + if (mGermanUmlautProcessing) { + s.append(indent); + s.append("Needs German umlaut processing\n"); + } + if (mFrenchLigatureProcessing) { + s.append(indent); + s.append("Needs French ligature processing\n"); + } + return s.toString(); + } } public final DictionaryOptions mOptions; @@ -279,7 +342,7 @@ public final class FusionDictionary implements Iterable<Word> { /** * Helper method to convert a String to an int array. */ - static private int[] getCodePoints(final String word) { + static int[] getCodePoints(final String word) { // TODO: this is a copy-paste of the contents of StringUtils.toCodePointArray, // which is not visible from the makedict package. Factor this code. final char[] characters = word.toCharArray(); @@ -358,6 +421,10 @@ public final class FusionDictionary implements Iterable<Word> { if (charGroup2 == null) { add(getCodePoints(word2), 0, null, false /* isNotAWord */, false /* isBlacklistEntry */); + // The chargroup for the first word may have moved by the above insertion, + // if word1 and word2 share a common stem that happens not to have been + // a cutting point until now. In this case, we need to refresh charGroup. + charGroup = findWordInTree(mRoot, word1); } charGroup.addBigram(word2, frequency); } else { @@ -550,6 +617,7 @@ public final class FusionDictionary implements Iterable<Word> { /** * Helper method to find a word in a given branch. */ + @SuppressWarnings("unused") public static CharGroup findWordInTree(Node node, final String s) { int index = 0; final StringBuilder checker = DBG ? new StringBuilder() : null; diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java index 5a11ae534..89d6c9010 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java @@ -212,7 +212,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService } } - private final ArrayList<CharSequence> mSuggestions; + private final ArrayList<String> mSuggestions; private final int[] mScores; private final String mOriginalText; private final float mSuggestionThreshold; @@ -294,6 +294,8 @@ public final class AndroidSpellCheckerService extends SpellCheckerService final String[] gatheredSuggestions; final boolean hasRecommendedSuggestions; if (0 == mLength) { + // TODO: the comment below describes what is intended, but in the practice + // mBestSuggestion is only ever set to null so it doesn't work. Fix this. // Either we found no suggestions, or we found some BUT the max length was 0. // If we found some mBestSuggestion will not be null. If it is null, then // we found none, regardless of the max length. @@ -335,7 +337,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService gatheredSuggestions = mSuggestions.toArray(EMPTY_STRING_ARRAY); final int bestScore = mScores[mLength - 1]; - final CharSequence bestSuggestion = mSuggestions.get(0); + final String bestSuggestion = mSuggestions.get(0); final float normalizedScore = BinaryDictionary.calcNormalizedScore( mOriginalText, bestSuggestion.toString(), bestScore); @@ -438,15 +440,23 @@ public final class AndroidSpellCheckerService extends SpellCheckerService if (!Character.isUpperCase(text.codePointAt(0))) return CAPITALIZE_NONE; final int len = text.length(); int capsCount = 1; + int letterCount = 1; for (int i = 1; i < len; i = text.offsetByCodePoints(i, 1)) { - if (1 != capsCount && i != capsCount) break; - if (Character.isUpperCase(text.codePointAt(i))) ++capsCount; + if (1 != capsCount && letterCount != capsCount) break; + final int codePoint = text.codePointAt(i); + if (Character.isUpperCase(codePoint)) { + ++capsCount; + ++letterCount; + } else if (Character.isLetter(codePoint)) { + // We need to discount non-letters since they may not be upper-case, but may + // still be part of a word (e.g. single quote or dash, as in "IT'S" or "FULL-TIME") + ++letterCount; + } } - // We know the first char is upper case. So we want to test if either everything - // else is lower case, or if everything else is upper case. If the string is - // exactly one char long, then we will arrive here with capsCount 1, and this is - // correct, too. + // We know the first char is upper case. So we want to test if either every letter other + // than the first is lower case, or if they are all upper case. If the string is exactly + // one char long, then we will arrive here with letterCount 1, and this is correct, too. if (1 == capsCount) return CAPITALIZE_FIRST; - return (len == capsCount ? CAPITALIZE_ALL : CAPITALIZE_NONE); + return (letterCount == capsCount ? CAPITALIZE_ALL : CAPITALIZE_NONE); } } diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java index 53ed4d3c3..470943be1 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java @@ -28,9 +28,11 @@ import android.view.textservice.TextInfo; import com.android.inputmethod.compat.SuggestionsInfoCompatUtils; import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.Dictionary; import com.android.inputmethod.latin.LocaleUtils; -import com.android.inputmethod.latin.WordComposer; +import com.android.inputmethod.latin.StringUtils; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; +import com.android.inputmethod.latin.WordComposer; import com.android.inputmethod.latin.spellcheck.AndroidSpellCheckerService.SuggestionsGatherer; import java.util.ArrayList; @@ -188,6 +190,35 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { return (letterCount * 4 < length * 3); } + /** + * Helper method to test valid capitalizations of a word. + * + * If the "text" is lower-case, we test only the exact string. + * If the "Text" is capitalized, we test the exact string "Text" and the lower-cased + * version of it "text". + * If the "TEXT" is fully upper case, we test the exact string "TEXT", the lower-cased + * version of it "text" and the capitalized version of it "Text". + */ + private boolean isInDictForAnyCapitalization(final Dictionary dict, final String text, + final int capitalizeType) { + // If the word is in there as is, then it's in the dictionary. If not, we'll test lower + // case versions, but only if the word is not already all-lower case or mixed case. + if (dict.isValidWord(text)) return true; + if (AndroidSpellCheckerService.CAPITALIZE_NONE == capitalizeType) return false; + + // If we come here, we have a capitalized word (either First- or All-). + // Downcase the word and look it up again. If the word is only capitalized, we + // tested all possibilities, so if it's still negative we can return false. + final String lowerCaseText = text.toLowerCase(mLocale); + if (dict.isValidWord(lowerCaseText)) return true; + if (AndroidSpellCheckerService.CAPITALIZE_FIRST == capitalizeType) return false; + + // If the lower case version is not in the dictionary, it's still possible + // that we have an all-caps version of a word that needs to be capitalized + // according to the dictionary. E.g. "GERMANS" only exists in the dictionary as "Germans". + return dict.isValidWord(StringUtils.toTitleCase(lowerCaseText, mLocale)); + } + // Note : this must be reentrant /** * Gets a list of suggestions for a specific string. This returns a list of possible @@ -268,17 +299,11 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { dictInfo.mDictionary.getSuggestions(composer, prevWord, dictInfo.mProximityInfo); for (final SuggestedWordInfo suggestion : suggestions) { - final String suggestionStr = suggestion.mWord.toString(); + final String suggestionStr = suggestion.mWord; suggestionsGatherer.addWord(suggestionStr.toCharArray(), null, 0, suggestionStr.length(), suggestion.mScore); } - isInDict = dictInfo.mDictionary.isValidWord(text); - if (!isInDict && AndroidSpellCheckerService.CAPITALIZE_NONE != capitalizeType) { - // We want to test the word again if it's all caps or first caps only. - // If it's fully down, we already tested it, if it's mixed case, we don't - // want to test a lowercase version of it. - isInDict = dictInfo.mDictionary.isValidWord(text.toLowerCase(mLocale)); - } + isInDict = isInDictForAnyCapitalization(dictInfo.mDictionary, text, capitalizeType); } finally { if (null != dictInfo) { if (!mDictionaryPool.offer(dictInfo)) { diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java index 1fb2bbb6a..eae5d2e60 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java @@ -51,11 +51,11 @@ public final class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> new Dictionary(Dictionary.TYPE_MAIN) { @Override public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, - final CharSequence prevWord, final ProximityInfo proximityInfo) { + final String prevWord, final ProximityInfo proximityInfo) { return noSuggestions; } @Override - public boolean isValidWord(CharSequence word) { + public boolean isValidWord(final String word) { // This is never called. However if for some strange reason it ever gets // called, returning true is less destructive (it will not underline the // word in red). diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java index 11bb97031..6c0d79c2b 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java @@ -16,6 +16,7 @@ package com.android.inputmethod.latin.spellcheck; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.Constants; @@ -23,7 +24,7 @@ import com.android.inputmethod.latin.Constants; import java.util.TreeMap; public final class SpellCheckerProximityInfo { - /* public for test */ + @UsedForTesting final public static int NUL = Constants.NOT_A_CODE; // This must be the same as MAX_PROXIMITY_CHARS_SIZE else it will not work inside diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java index 4e9fd1968..35d5a0067 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java +++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java @@ -22,7 +22,6 @@ import android.graphics.drawable.Drawable; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.internal.KeyboardBuilder; import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; import com.android.inputmethod.keyboard.internal.KeyboardParams; @@ -66,7 +65,7 @@ public final class MoreSuggestions extends Keyboard { int pos = fromPos, rowStartPos = fromPos; final int size = Math.min(suggestions.size(), SuggestionStripView.MAX_SUGGESTIONS); while (pos < size) { - final String word = suggestions.getWord(pos).toString(); + final String word = suggestions.getWord(pos); // TODO: Should take care of text x-scaling. mWidths[pos] = (int)view.getLabelWidth(word, paint) + padding; final int numColumn = pos - rowStartPos + 1; @@ -176,11 +175,11 @@ public final class MoreSuggestions extends Keyboard { } public Builder layout(final SuggestedWords suggestions, final int fromPos, - final int maxWidth, final int minWidth, final int maxRow) { - final Keyboard keyboard = KeyboardSwitcher.getInstance().getKeyboard(); + final int maxWidth, final int minWidth, final int maxRow, + final Keyboard parentKeyboard) { final int xmlId = R.xml.kbd_suggestions_pane_template; - load(xmlId, keyboard.mId); - mParams.mVerticalGap = mParams.mTopPadding = keyboard.mVerticalGap / 2; + load(xmlId, parentKeyboard.mId); + mParams.mVerticalGap = mParams.mTopPadding = parentKeyboard.mVerticalGap / 2; mPaneView.updateKeyboardGeometry(mParams.mDefaultRowHeight); final int count = mParams.layout(suggestions, fromPos, maxWidth, minWidth, maxRow, diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java index 03a2e73d1..26a304ef8 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java +++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java @@ -17,84 +17,35 @@ package com.android.inputmethod.latin.suggestions; import android.content.Context; -import android.content.res.Resources; import android.util.AttributeSet; -import android.view.Gravity; -import android.view.MotionEvent; -import android.view.View; -import android.widget.PopupWindow; -import com.android.inputmethod.keyboard.KeyDetector; import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.KeyboardActionListener; -import com.android.inputmethod.keyboard.KeyboardView; -import com.android.inputmethod.keyboard.MoreKeysDetector; -import com.android.inputmethod.keyboard.MoreKeysPanel; -import com.android.inputmethod.keyboard.PointerTracker; -import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy; -import com.android.inputmethod.keyboard.PointerTracker.KeyEventHandler; -import com.android.inputmethod.keyboard.PointerTracker.TimerProxy; +import com.android.inputmethod.keyboard.MoreKeysKeyboardView; import com.android.inputmethod.latin.R; /** * A view that renders a virtual {@link MoreSuggestions}. It handles rendering of keys and detecting * key presses and touch movements. */ -public final class MoreSuggestionsView extends KeyboardView implements MoreKeysPanel { - private final int[] mCoordinates = new int[2]; +public final class MoreSuggestionsView extends MoreKeysKeyboardView { - final KeyDetector mModalPanelKeyDetector; - private final KeyDetector mSlidingPanelKeyDetector; - - private Controller mController; - KeyboardActionListener mListener; - private int mOriginX; - private int mOriginY; - - static final TimerProxy EMPTY_TIMER_PROXY = new TimerProxy.Adapter(); - - final KeyboardActionListener mSuggestionsPaneListener = - new KeyboardActionListener.Adapter() { - @Override - public void onPressKey(int primaryCode) { - mListener.onPressKey(primaryCode); - } - - @Override - public void onReleaseKey(int primaryCode, boolean withSliding) { - mListener.onReleaseKey(primaryCode, withSliding); - } - - @Override - public void onCodeInput(int primaryCode, int x, int y) { - final int index = primaryCode - MoreSuggestions.SUGGESTION_CODE_BASE; - if (index >= 0 && index < SuggestionStripView.MAX_SUGGESTIONS) { - mListener.onCustomRequest(index); - } - } - - @Override - public void onCancelInput() { - mListener.onCancelInput(); - } - }; - - public MoreSuggestionsView(Context context, AttributeSet attrs) { + public MoreSuggestionsView(final Context context, final AttributeSet attrs) { this(context, attrs, R.attr.moreSuggestionsViewStyle); } - public MoreSuggestionsView(Context context, AttributeSet attrs, int defStyle) { + public MoreSuggestionsView(final Context context, final AttributeSet attrs, + final int defStyle) { super(context, attrs, defStyle); + } - final Resources res = context.getResources(); - mModalPanelKeyDetector = new KeyDetector(/* keyHysteresisDistance */ 0); - mSlidingPanelKeyDetector = new MoreKeysDetector( - res.getDimension(R.dimen.more_suggestions_slide_allowance)); - setKeyPreviewPopupEnabled(false, 0); + @Override + protected int getDefaultCoordX() { + final MoreSuggestions pane = (MoreSuggestions)getKeyboard(); + return pane.mOccupiedWidth / 2; } @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { final Keyboard keyboard = getKeyboard(); if (keyboard != null) { final int width = keyboard.mOccupiedWidth + getPaddingLeft() + getPaddingRight(); @@ -110,116 +61,15 @@ public final class MoreSuggestionsView extends KeyboardView implements MoreKeysP } @Override - public void setKeyboard(Keyboard keyboard) { - super.setKeyboard(keyboard); - mModalPanelKeyDetector.setKeyboard(keyboard, -getPaddingLeft(), -getPaddingTop()); - mSlidingPanelKeyDetector.setKeyboard(keyboard, -getPaddingLeft(), - -getPaddingTop() + mVerticalCorrection); - } - - @Override - public KeyDetector getKeyDetector() { - return mSlidingPanelKeyDetector; - } - - @Override - public KeyboardActionListener getKeyboardActionListener() { - return mSuggestionsPaneListener; - } - - @Override - public DrawingProxy getDrawingProxy() { - return this; - } - - @Override - public TimerProxy getTimerProxy() { - return EMPTY_TIMER_PROXY; - } - - @Override - public void setKeyPreviewPopupEnabled(boolean previewEnabled, int delay) { - // Suggestions pane needs no pop-up key preview displayed, so we pass always false with a - // delay of 0. The delay does not matter actually since the popup is not shown anyway. - super.setKeyPreviewPopupEnabled(false, 0); - } - - @Override - public void showMoreKeysPanel(View parentView, Controller controller, int pointX, int pointY, - PopupWindow window, KeyboardActionListener listener) { - mController = controller; - mListener = listener; - final View container = (View)getParent(); - final MoreSuggestions pane = (MoreSuggestions)getKeyboard(); - final int defaultCoordX = pane.mOccupiedWidth / 2; - // The coordinates of panel's left-top corner in parentView's coordinate system. - final int x = pointX - defaultCoordX - container.getPaddingLeft(); - final int y = pointY - container.getMeasuredHeight() + container.getPaddingBottom(); - - window.setContentView(container); - window.setWidth(container.getMeasuredWidth()); - window.setHeight(container.getMeasuredHeight()); - parentView.getLocationInWindow(mCoordinates); - window.showAtLocation(parentView, Gravity.NO_GRAVITY, - x + mCoordinates[0], y + mCoordinates[1]); - - mOriginX = x + container.getPaddingLeft(); - mOriginY = y + container.getPaddingTop(); - } - - private boolean mIsDismissing; - - @Override - public boolean dismissMoreKeysPanel() { - if (mIsDismissing || mController == null) return false; - mIsDismissing = true; - final boolean dismissed = mController.dismissMoreKeysPanel(); - mIsDismissing = false; - return dismissed; - } - - @Override - public int translateX(int x) { - return x - mOriginX; - } - - @Override - public int translateY(int y) { - return y - mOriginY; - } - - private final KeyEventHandler mModalPanelKeyEventHandler = new KeyEventHandler() { - @Override - public KeyDetector getKeyDetector() { - return mModalPanelKeyDetector; + public void onCodeInput(final int primaryCode, final int x, final int y) { + final int index = primaryCode - MoreSuggestions.SUGGESTION_CODE_BASE; + if (index >= 0 && index < SuggestionStripView.MAX_SUGGESTIONS) { + mListener.onCustomRequest(index); } - - @Override - public KeyboardActionListener getKeyboardActionListener() { - return mSuggestionsPaneListener; - } - - @Override - public DrawingProxy getDrawingProxy() { - return MoreSuggestionsView.this; - } - - @Override - public TimerProxy getTimerProxy() { - return EMPTY_TIMER_PROXY; - } - }; + } @Override - public boolean onTouchEvent(MotionEvent me) { - final int action = me.getAction(); - final long eventTime = me.getEventTime(); - final int index = me.getActionIndex(); - final int id = me.getPointerId(index); - final PointerTracker tracker = PointerTracker.getPointerTracker(id, this); - final int x = (int)me.getX(index); - final int y = (int)me.getY(index); - tracker.processMotionEvent(action, x, y, eventTime, mModalPanelKeyEventHandler); - return true; + public boolean isShowingInParent() { + return (getContainerView().getParent() != null); } } diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java index a1c95ad8a..14bb95b3c 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java @@ -27,9 +27,7 @@ import android.graphics.Paint.Align; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; -import android.os.Message; import android.text.Spannable; import android.text.SpannableString; import android.text.Spanned; @@ -48,21 +46,21 @@ import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; import android.view.ViewGroup; import android.widget.LinearLayout; -import android.widget.PopupWindow; import android.widget.RelativeLayout; import android.widget.TextView; +import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardActionListener; +import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.KeyboardView; import com.android.inputmethod.keyboard.MoreKeysPanel; -import com.android.inputmethod.keyboard.PointerTracker; import com.android.inputmethod.keyboard.ViewLayoutUtils; import com.android.inputmethod.latin.AutoCorrection; import com.android.inputmethod.latin.CollectionUtils; +import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.ResourceUtils; -import com.android.inputmethod.latin.StaticInnerHandlerWrapper; import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.Utils; import com.android.inputmethod.latin.define.ProductionFlag; @@ -74,7 +72,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick OnLongClickListener { public interface Listener { public void addWordToUserDictionary(String word); - public void pickSuggestionManually(int index, CharSequence word); + public void pickSuggestionManually(int index, String word); } // The maximum number of suggestions available. See {@link Suggest#mPrefMaxSuggestions}. @@ -83,54 +81,22 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick static final boolean DBG = LatinImeLogger.sDBG; private final ViewGroup mSuggestionsStrip; - private KeyboardView mKeyboardView; + KeyboardView mKeyboardView; private final View mMoreSuggestionsContainer; private final MoreSuggestionsView mMoreSuggestionsView; private final MoreSuggestions.Builder mMoreSuggestionsBuilder; - private final PopupWindow mMoreSuggestionsWindow; private final ArrayList<TextView> mWords = CollectionUtils.newArrayList(); private final ArrayList<TextView> mInfos = CollectionUtils.newArrayList(); private final ArrayList<View> mDividers = CollectionUtils.newArrayList(); - private final PopupWindow mPreviewPopup; - private final TextView mPreviewText; - - private Listener mListener; - private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY; + Listener mListener; + SuggestedWords mSuggestedWords = SuggestedWords.EMPTY; private final SuggestionStripViewParams mParams; private static final float MIN_TEXT_XSCALE = 0.70f; - private final UiHandler mHandler = new UiHandler(this); - - private static final class UiHandler extends StaticInnerHandlerWrapper<SuggestionStripView> { - private static final int MSG_HIDE_PREVIEW = 0; - - public UiHandler(SuggestionStripView outerInstance) { - super(outerInstance); - } - - @Override - public void dispatchMessage(Message msg) { - final SuggestionStripView suggestionStripView = getOuterInstance(); - switch (msg.what) { - case MSG_HIDE_PREVIEW: - suggestionStripView.hidePreview(); - break; - } - } - - public void cancelHidePreview() { - removeMessages(MSG_HIDE_PREVIEW); - } - - public void cancelAllMessages() { - cancelHidePreview(); - } - } - private static final class SuggestionStripViewParams { private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3; private static final float DEFAULT_CENTER_SUGGESTION_PERCENTILE = 0.40f; @@ -177,8 +143,9 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick private final TextView mLeftwardsArrowView; private final TextView mHintToSaveView; - public SuggestionStripViewParams(Context context, AttributeSet attrs, int defStyle, - ArrayList<TextView> words, ArrayList<View> dividers, ArrayList<TextView> infos) { + public SuggestionStripViewParams(final Context context, final AttributeSet attrs, + final int defStyle, final ArrayList<TextView> words, final ArrayList<View> dividers, + final ArrayList<TextView> infos) { mWords = words; mDividers = dividers; mInfos = infos; @@ -250,7 +217,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick return mMaxMoreSuggestionsRow * mMoreSuggestionsRowHeight + mMoreSuggestionsBottomGap; } - public int setMoreSuggestionsHeight(int remainingHeight) { + public int setMoreSuggestionsHeight(final int remainingHeight) { final int currentHeight = getMoreSuggestionsHeight(); if (currentHeight <= remainingHeight) { return currentHeight; @@ -262,7 +229,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick return newHeight; } - private static Drawable getMoreSuggestionsHint(Resources res, float textSize, int color) { + private static Drawable getMoreSuggestionsHint(final Resources res, final float textSize, + final int color) { final Paint paint = new Paint(); paint.setAntiAlias(true); paint.setTextAlign(Align.CENTER); @@ -279,8 +247,9 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick return new BitmapDrawable(res, buffer); } - private CharSequence getStyledSuggestionWord(SuggestedWords suggestedWords, int pos) { - final CharSequence word = suggestedWords.getWord(pos); + private CharSequence getStyledSuggestionWord(final SuggestedWords suggestedWords, + final int pos) { + final String word = suggestedWords.getWord(pos); final boolean isAutoCorrect = pos == 1 && suggestedWords.willAutoCorrect(); final boolean isTypedWordValid = pos == 0 && suggestedWords.mTypedWordValid; if (!isAutoCorrect && !isTypedWordValid) @@ -299,7 +268,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick return spannedWord; } - private int getWordPosition(int index, SuggestedWords suggestedWords) { + private int getWordPosition(final int index, final SuggestedWords suggestedWords) { // TODO: This works for 3 suggestions. Revisit this algorithm when there are 5 or more // suggestions. final int centerPos = suggestedWords.willAutoCorrect() ? 1 : 0; @@ -312,7 +281,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick } } - private int getSuggestionTextColor(int index, SuggestedWords suggestedWords, int pos) { + private int getSuggestionTextColor(final int index, final SuggestedWords suggestedWords, + final int pos) { // TODO: Need to revisit this logic with bigram suggestions final boolean isSuggested = (pos != 0); @@ -331,7 +301,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick // is in slot 1. if (index == mCenterSuggestionIndex && AutoCorrection.shouldBlockAutoCorrectionBySafetyNet( - suggestedWords.getWord(1).toString(), suggestedWords.getWord(0))) { + suggestedWords.getWord(1), suggestedWords.getWord(0))) { return 0xFFFF0000; } } @@ -355,8 +325,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick params.gravity = Gravity.CENTER; } - public void layout(SuggestedWords suggestedWords, ViewGroup stripView, ViewGroup placer, - int stripWidth) { + public void layout(final SuggestedWords suggestedWords, final ViewGroup stripView, + final ViewGroup placer, final int stripWidth) { if (suggestedWords.mIsPunctuationSuggestions) { layoutPunctuationSuggestions(suggestedWords, stripView); return; @@ -402,7 +372,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick x += word.getMeasuredWidth(); if (DBG && pos < suggestedWords.size()) { - final CharSequence debugInfo = Utils.getDebugInfo(suggestedWords, pos); + final String debugInfo = Utils.getDebugInfo(suggestedWords, pos); if (debugInfo != null) { final TextView info = mInfos.get(pos); info.setText(debugInfo); @@ -418,14 +388,14 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick } } - private int getSuggestionWidth(int index, int maxWidth) { + private int getSuggestionWidth(final int index, final int maxWidth) { final int paddings = mPadding * mSuggestionsCountInStrip; final int dividers = mDividerWidth * (mSuggestionsCountInStrip - 1); final int availableWidth = maxWidth - paddings - dividers; return (int)(availableWidth * getSuggestionWeight(index)); } - private float getSuggestionWeight(int index) { + private float getSuggestionWeight(final int index) { if (index == mCenterSuggestionIndex) { return mCenterSuggestionWeight; } else { @@ -434,7 +404,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick } } - private void setupTexts(SuggestedWords suggestedWords, int countInStrip) { + private void setupTexts(final SuggestedWords suggestedWords, final int countInStrip) { mTexts.clear(); final int count = Math.min(suggestedWords.size(), countInStrip); for (int pos = 0; pos < count; pos++) { @@ -447,8 +417,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick } } - private void layoutPunctuationSuggestions(SuggestedWords suggestedWords, - ViewGroup stripView) { + private void layoutPunctuationSuggestions(final SuggestedWords suggestedWords, + final ViewGroup stripView) { final int countInStrip = Math.min(suggestedWords.size(), PUNCTUATIONS_IN_STRIP); for (int index = 0; index < countInStrip; index++) { if (index != 0) { @@ -459,7 +429,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick final TextView word = mWords.get(index); word.setEnabled(true); word.setTextColor(mColorAutoCorrect); - final CharSequence text = suggestedWords.getWord(index); + final String text = suggestedWords.getWord(index); word.setText(text); word.setTextScaleX(1.0f); word.setCompoundDrawables(null, null, null, null); @@ -469,8 +439,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick mMoreSuggestionsAvailable = false; } - public void layoutAddToDictionaryHint(CharSequence word, ViewGroup stripView, - int stripWidth, CharSequence hintText, OnClickListener listener) { + public void layoutAddToDictionaryHint(final String word, final ViewGroup stripView, + final int stripWidth, final CharSequence hintText, final OnClickListener listener) { final int width = stripWidth - mDividerWidth - mPadding * 2; final TextView wordView = mWordToSaveView; @@ -511,11 +481,11 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick return (CharSequence)mWordToSaveView.getTag(); } - public boolean isAddToDictionaryShowing(View v) { + public boolean isAddToDictionaryShowing(final View v) { return v == mWordToSaveView || v == mHintToSaveView || v == mLeftwardsArrowView; } - private static void setLayoutWeight(View v, float weight, int height) { + private static void setLayoutWeight(final View v, final float weight, final int height) { final ViewGroup.LayoutParams lp = v.getLayoutParams(); if (lp instanceof LinearLayout.LayoutParams) { final LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams)lp; @@ -525,7 +495,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick } } - private static float getTextScaleX(CharSequence text, int maxWidth, TextPaint paint) { + private static float getTextScaleX(final CharSequence text, final int maxWidth, + final TextPaint paint) { paint.setTextScaleX(1.0f); final int width = getTextWidth(text, paint); if (width <= maxWidth) { @@ -534,8 +505,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick return maxWidth / (float)width; } - private static CharSequence getEllipsizedText(CharSequence text, int maxWidth, - TextPaint paint) { + private static CharSequence getEllipsizedText(final CharSequence text, final int maxWidth, + final TextPaint paint) { if (text == null) return null; paint.setTextScaleX(1.0f); final int width = getTextWidth(text, paint); @@ -556,7 +527,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick return ellipsized; } - private static int getTextWidth(CharSequence text, TextPaint paint) { + private static int getTextWidth(final CharSequence text, final TextPaint paint) { if (TextUtils.isEmpty(text)) return 0; final Typeface savedTypeface = paint.getTypeface(); paint.setTypeface(getTextTypeface(text)); @@ -571,7 +542,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick return width; } - private static Typeface getTextTypeface(CharSequence text) { + private static Typeface getTextTypeface(final CharSequence text) { if (!(text instanceof SpannableString)) return Typeface.DEFAULT; @@ -593,23 +564,17 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick * @param context * @param attrs */ - public SuggestionStripView(Context context, AttributeSet attrs) { + public SuggestionStripView(final Context context, final AttributeSet attrs) { this(context, attrs, R.attr.suggestionStripViewStyle); } - public SuggestionStripView(Context context, AttributeSet attrs, int defStyle) { + public SuggestionStripView(final Context context, final AttributeSet attrs, + final int defStyle) { super(context, attrs, defStyle); final LayoutInflater inflater = LayoutInflater.from(context); inflater.inflate(R.layout.suggestions_strip, this); - mPreviewPopup = new PopupWindow(context); - mPreviewText = (TextView) inflater.inflate(R.layout.suggestion_preview, null); - mPreviewPopup.setWindowLayoutMode( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - mPreviewPopup.setContentView(mPreviewText); - mPreviewPopup.setBackgroundDrawable(null); - mSuggestionsStrip = (ViewGroup)findViewById(R.id.suggestions_strip); for (int pos = 0; pos < MAX_SUGGESTIONS; pos++) { final TextView word = (TextView)inflater.inflate(R.layout.suggestion_word, null); @@ -632,21 +597,6 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick .findViewById(R.id.more_suggestions_view); mMoreSuggestionsBuilder = new MoreSuggestions.Builder(mMoreSuggestionsView); - final PopupWindow moreWindow = new PopupWindow(context); - moreWindow.setWindowLayoutMode( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - moreWindow.setBackgroundDrawable(new ColorDrawable(android.R.color.transparent)); - moreWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); - moreWindow.setFocusable(true); - moreWindow.setOutsideTouchable(true); - moreWindow.setOnDismissListener(new PopupWindow.OnDismissListener() { - @Override - public void onDismiss() { - mKeyboardView.dimEntireKeyboard(false); - } - }); - mMoreSuggestionsWindow = moreWindow; - final Resources res = context.getResources(); mMoreSuggestionsModalTolerance = res.getDimensionPixelOffset( R.dimen.more_suggestions_modal_tolerance); @@ -658,15 +608,12 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick * A connection back to the input method. * @param listener */ - public void setListener(Listener listener, View inputView) { + public void setListener(final Listener listener, final View inputView) { mListener = listener; mKeyboardView = (KeyboardView)inputView.findViewById(R.id.keyboard_view); } - public void setSuggestions(SuggestedWords suggestedWords) { - if (suggestedWords == null) - return; - + public void setSuggestions(final SuggestedWords suggestedWords) { clear(); mSuggestedWords = suggestedWords; mParams.layout(mSuggestedWords, mSuggestionsStrip, this, getWidth()); @@ -675,7 +622,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick } } - public int setMoreSuggestionsHeight(int remainingHeight) { + public int setMoreSuggestionsHeight(final int remainingHeight) { return mParams.setMoreSuggestionsHeight(remainingHeight); } @@ -684,7 +631,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick && mParams.isAddToDictionaryShowing(mSuggestionsStrip.getChildAt(0)); } - public void showAddToDictionaryHint(CharSequence word, CharSequence hintText) { + public void showAddToDictionaryHint(final String word, final CharSequence hintText) { clear(); mParams.layoutAddToDictionaryHint(word, mSuggestionsStrip, getWidth(), hintText, this); } @@ -708,16 +655,12 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick dismissMoreSuggestions(); } - private void hidePreview() { - mPreviewPopup.dismiss(); - } - private final KeyboardActionListener mMoreSuggestionsListener = new KeyboardActionListener.Adapter() { @Override - public boolean onCustomRequest(int requestCode) { + public boolean onCustomRequest(final int requestCode) { final int index = requestCode; - final CharSequence word = mSuggestedWords.getWord(index); + final String word = mSuggestedWords.getWord(index); mListener.pickSuggestionManually(index, word); dismissMoreSuggestions(); return true; @@ -732,55 +675,64 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick private final MoreKeysPanel.Controller mMoreSuggestionsController = new MoreKeysPanel.Controller() { @Override - public boolean dismissMoreKeysPanel() { - return dismissMoreSuggestions(); + public boolean onDismissMoreKeysPanel() { + mKeyboardView.dimEntireKeyboard(false /* dimmed */); + return mKeyboardView.onDismissMoreKeysPanel(); } - }; - private boolean dismissMoreSuggestions() { - if (mMoreSuggestionsWindow.isShowing()) { - mMoreSuggestionsWindow.dismiss(); - return true; + @Override + public void onShowMoreKeysPanel(MoreKeysPanel panel) { + mKeyboardView.onShowMoreKeysPanel(panel); } - return false; + + @Override + public void onCancelMoreKeysPanel() { + dismissMoreSuggestions(); + } + }; + + boolean dismissMoreSuggestions() { + return mMoreSuggestionsView.dismissMoreKeysPanel(); } @Override - public boolean onLongClick(View view) { + public boolean onLongClick(final View view) { + KeyboardSwitcher.getInstance().hapticAndAudioFeedback(Constants.NOT_A_CODE); return showMoreSuggestions(); } - private boolean showMoreSuggestions() { + boolean showMoreSuggestions() { + final Keyboard parentKeyboard = KeyboardSwitcher.getInstance().getKeyboard(); + if (parentKeyboard == null) { + return false; + } final SuggestionStripViewParams params = mParams; - if (params.mMoreSuggestionsAvailable) { - final int stripWidth = getWidth(); - final View container = mMoreSuggestionsContainer; - final int maxWidth = stripWidth - container.getPaddingLeft() - - container.getPaddingRight(); - final MoreSuggestions.Builder builder = mMoreSuggestionsBuilder; - builder.layout(mSuggestedWords, params.mSuggestionsCountInStrip, maxWidth, - (int)(maxWidth * params.mMinMoreSuggestionsWidth), - params.getMaxMoreSuggestionsRow()); - mMoreSuggestionsView.setKeyboard(builder.build()); - container.measure( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - - final MoreKeysPanel moreKeysPanel = mMoreSuggestionsView; - final int pointX = stripWidth / 2; - final int pointY = -params.mMoreSuggestionsBottomGap; - moreKeysPanel.showMoreKeysPanel( - this, mMoreSuggestionsController, pointX, pointY, - mMoreSuggestionsWindow, mMoreSuggestionsListener); - mMoreSuggestionsMode = MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING; - mOriginX = mLastX; - mOriginY = mLastY; - mKeyboardView.dimEntireKeyboard(true); - for (int i = 0; i < params.mSuggestionsCountInStrip; i++) { - mWords.get(i).setPressed(false); - } - return true; + if (!params.mMoreSuggestionsAvailable) { + return false; } - return false; + final int stripWidth = getWidth(); + final View container = mMoreSuggestionsContainer; + final int maxWidth = stripWidth - container.getPaddingLeft() - container.getPaddingRight(); + final MoreSuggestions.Builder builder = mMoreSuggestionsBuilder; + builder.layout(mSuggestedWords, params.mSuggestionsCountInStrip, maxWidth, + (int)(maxWidth * params.mMinMoreSuggestionsWidth), + params.getMaxMoreSuggestionsRow(), parentKeyboard); + mMoreSuggestionsView.setKeyboard(builder.build()); + container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + + final MoreKeysPanel moreKeysPanel = mMoreSuggestionsView; + final int pointX = stripWidth / 2; + final int pointY = -params.mMoreSuggestionsBottomGap; + moreKeysPanel.showMoreKeysPanel(this, mMoreSuggestionsController, pointX, pointY, + mMoreSuggestionsListener); + mMoreSuggestionsMode = MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING; + mOriginX = mLastX; + mOriginY = mLastY; + mKeyboardView.dimEntireKeyboard(true /* dimmed */); + for (int i = 0; i < params.mSuggestionsCountInStrip; i++) { + mWords.get(i).setPressed(false); + } + return true; } // Working variables for onLongClick and dispatchTouchEvent. @@ -807,8 +759,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick }; @Override - public boolean dispatchTouchEvent(MotionEvent me) { - if (!mMoreSuggestionsWindow.isShowing() + public boolean dispatchTouchEvent(final MotionEvent me) { + if (!mMoreSuggestionsView.isShowingInParent() || mMoreSuggestionsMode == MORE_SUGGESTIONS_IN_MODAL_MODE) { mLastX = (int)me.getX(); mLastY = (int)me.getY(); @@ -823,7 +775,6 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick final long eventTime = me.getEventTime(); final int index = me.getActionIndex(); final int id = me.getPointerId(index); - final PointerTracker tracker = PointerTracker.getPointerTracker(id, moreKeysPanel); final int x = (int)me.getX(index); final int y = (int)me.getY(index); final int translatedX = moreKeysPanel.translateX(x); @@ -835,7 +786,6 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick // Decided to be in the sliding input mode only when the touch point has been moved // upward. mMoreSuggestionsMode = MORE_SUGGESTIONS_IN_SLIDING_MODE; - tracker.onShowMoreKeysPanel(translatedX, translatedY, moreKeysPanel); } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) { // Decided to be in the modal input mode mMoreSuggestionsMode = MORE_SUGGESTIONS_IN_MODAL_MODE; @@ -844,12 +794,12 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick } // MORE_SUGGESTIONS_IN_SLIDING_MODE - tracker.processMotionEvent(action, translatedX, translatedY, eventTime, moreKeysPanel); + mMoreSuggestionsView.processMotionEvent(action, translatedX, translatedY, id, eventTime); return true; } @Override - public void onClick(View view) { + public void onClick(final View view) { if (mParams.isAddToDictionaryShowing(view)) { mListener.addWordToUserDictionary(mParams.getAddToDictionaryWord().toString()); clear(); @@ -863,15 +813,13 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick if (index >= mSuggestedWords.size()) return; - final CharSequence word = mSuggestedWords.getWord(index); + final String word = mSuggestedWords.getWord(index); mListener.pickSuggestionManually(index, word); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - mHandler.cancelAllMessages(); - hidePreview(); dismissMoreSuggestions(); } } diff --git a/java/src/com/android/inputmethod/research/LogUnit.java b/java/src/com/android/inputmethod/research/LogUnit.java index d8b3a29ff..0aec80a40 100644 --- a/java/src/com/android/inputmethod/research/LogUnit.java +++ b/java/src/com/android/inputmethod/research/LogUnit.java @@ -18,7 +18,7 @@ package com.android.inputmethod.research; import com.android.inputmethod.latin.CollectionUtils; -import java.util.ArrayList; +import java.util.List; /** * A group of log statements related to each other. @@ -35,16 +35,39 @@ import java.util.ArrayList; * been published recently, or whether the LogUnit contains numbers, etc. */ /* package */ class LogUnit { - private final ArrayList<String[]> mKeysList = CollectionUtils.newArrayList(); - private final ArrayList<Object[]> mValuesList = CollectionUtils.newArrayList(); - private final ArrayList<Boolean> mIsPotentiallyPrivate = CollectionUtils.newArrayList(); + private final List<String[]> mKeysList; + private final List<Object[]> mValuesList; + // Assume that mTimeList is sorted in increasing order. Do not insert null values into + // mTimeList. + private final List<Long> mTimeList; + private final List<Boolean> mIsPotentiallyPrivate; private String mWord; - private boolean mContainsDigit; + private boolean mMayContainDigit; + public LogUnit() { + mKeysList = CollectionUtils.newArrayList(); + mValuesList = CollectionUtils.newArrayList(); + mTimeList = CollectionUtils.newArrayList(); + mIsPotentiallyPrivate = CollectionUtils.newArrayList(); + } + + private LogUnit(final List<String[]> keysList, final List<Object[]> valuesList, + final List<Long> timeList, final List<Boolean> isPotentiallyPrivate) { + mKeysList = keysList; + mValuesList = valuesList; + mTimeList = timeList; + mIsPotentiallyPrivate = isPotentiallyPrivate; + } + + /** + * Adds a new log statement. The time parameter in successive calls to this method must be + * monotonically increasing, or splitByTime() will not work. + */ public void addLogStatement(final String[] keys, final Object[] values, - final Boolean isPotentiallyPrivate) { + final long time, final boolean isPotentiallyPrivate) { mKeysList.add(keys); mValuesList.add(values); + mTimeList.add(time); mIsPotentiallyPrivate.add(isPotentiallyPrivate); } @@ -52,7 +75,7 @@ import java.util.ArrayList; final int size = mKeysList.size(); for (int i = 0; i < size; i++) { if (!mIsPotentiallyPrivate.get(i) || isIncludingPrivateData) { - researchLog.outputEvent(mKeysList.get(i), mValuesList.get(i)); + researchLog.outputEvent(mKeysList.get(i), mValuesList.get(i), mTimeList.get(i)); } } } @@ -69,15 +92,37 @@ import java.util.ArrayList; return mWord != null; } - public void setContainsDigit() { - mContainsDigit = true; + public void setMayContainDigit() { + mMayContainDigit = true; } - public boolean hasDigit() { - return mContainsDigit; + public boolean mayContainDigit() { + return mMayContainDigit; } public boolean isEmpty() { return mKeysList.isEmpty(); } + + /** + * Split this logUnit, with all events before maxTime staying in the current logUnit, and all + * events after maxTime going into a new LogUnit that is returned. + */ + public LogUnit splitByTime(final long maxTime) { + // Assume that mTimeList is in sorted order. + final int length = mTimeList.size(); + for (int index = 0; index < length; index++) { + if (mTimeList.get(index) >= maxTime) { + final LogUnit newLogUnit = new LogUnit( + mKeysList.subList(index, length), + mValuesList.subList(index, length), + mTimeList.subList(index, length), + mIsPotentiallyPrivate.subList(index, length)); + newLogUnit.mWord = null; + newLogUnit.mMayContainDigit = mMayContainDigit; + return newLogUnit; + } + } + return new LogUnit(); + } } diff --git a/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java index 745768d35..f665e5906 100644 --- a/java/src/com/android/inputmethod/research/MainLogBuffer.java +++ b/java/src/com/android/inputmethod/research/MainLogBuffer.java @@ -16,16 +16,22 @@ package com.android.inputmethod.research; +import android.util.Log; + import com.android.inputmethod.latin.Dictionary; import com.android.inputmethod.latin.Suggest; import java.util.Random; public class MainLogBuffer extends LogBuffer { + private static final String TAG = MainLogBuffer.class.getSimpleName(); + // For privacy reasons, be sure to set to "false" for production code. + private static final boolean DEBUG = false; + // The size of the n-grams logged. E.g. N_GRAM_SIZE = 2 means to sample bigrams. private static final int N_GRAM_SIZE = 2; // The number of words between n-grams to omit from the log. - private static final int DEFAULT_NUMBER_OF_WORDS_BETWEEN_SAMPLES = 18; + private static final int DEFAULT_NUMBER_OF_WORDS_BETWEEN_SAMPLES = DEBUG ? 2 : 18; private final ResearchLog mResearchLog; private Suggest mSuggest; @@ -61,6 +67,9 @@ public class MainLogBuffer extends LogBuffer { mWordsUntilSafeToSample--; } } + if (DEBUG) { + Log.d(TAG, "shiftedIn " + (newLogUnit.hasWord() ? newLogUnit.getWord() : "")); + } } public void resetWordCounter() { @@ -104,7 +113,7 @@ public class MainLogBuffer extends LogBuffer { final String word = logUnit.getWord(); if (word == null) { // Digits outside words are a privacy threat. - if (logUnit.hasDigit()) { + if (logUnit.mayContainDigit()) { return false; } } else { diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java index 70c38e909..96dac55ac 100644 --- a/java/src/com/android/inputmethod/research/ResearchLog.java +++ b/java/src/com/android/inputmethod/research/ResearchLog.java @@ -207,7 +207,7 @@ public class ResearchLog { private static final String UPTIME_KEY = "_ut"; private static final String EVENT_TYPE_KEY = "_ty"; - void outputEvent(final String[] keys, final Object[] values) { + void outputEvent(final String[] keys, final Object[] values, final long time) { // Not thread safe. if (keys.length == 0) { return; @@ -225,7 +225,7 @@ public class ResearchLog { } mJsonWriter.beginObject(); mJsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis()); - mJsonWriter.name(UPTIME_KEY).value(SystemClock.uptimeMillis()); + mJsonWriter.name(UPTIME_KEY).value(time); mJsonWriter.name(EVENT_TYPE_KEY).value(keys[0]); final int length = values.length; for (int i = 0; i < length; i++) { diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java index 763fd6e00..2657da285 100644 --- a/java/src/com/android/inputmethod/research/ResearchLogger.java +++ b/java/src/com/android/inputmethod/research/ResearchLogger.java @@ -57,9 +57,9 @@ import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardId; import com.android.inputmethod.keyboard.KeyboardView; import com.android.inputmethod.keyboard.MainKeyboardView; -import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.Dictionary; +import com.android.inputmethod.latin.InputTypeUtils; import com.android.inputmethod.latin.LatinIME; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.RichInputConnection; @@ -143,7 +143,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang private final Statistics mStatistics; private Intent mUploadIntent; - private PendingIntent mUploadPendingIntent; private LogUnit mCurrentLogUnit = new LogUnit(); @@ -188,7 +187,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang mInputMethodService = ims; mPrefs = prefs; mUploadIntent = new Intent(mInputMethodService, UploaderService.class); - mUploadPendingIntent = PendingIntent.getService(mInputMethodService, 0, mUploadIntent, 0); if (ProductionFlag.IS_EXPERIMENTAL) { scheduleUploadingService(mInputMethodService); @@ -379,7 +377,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang Log.d(TAG, "stop called"); } logStatistics(); - commitCurrentLogUnit(); + commitCurrentLogUnit(SystemClock.uptimeMillis()); if (mMainLogBuffer != null) { publishLogBuffer(mMainLogBuffer, mMainResearchLog, false /* isIncludingPrivateData */); @@ -427,21 +425,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } private long mResumeTime = 0L; - private void suspendLoggingUntil(long time) { - mIsLoggingSuspended = true; - mResumeTime = time; - requestIndicatorRedraw(); - } - - private void resumeLogging() { - mResumeTime = 0L; - updateSuspendedState(); - requestIndicatorRedraw(); - if (isAllowedToLog()) { - restart(); - } - } - private void updateSuspendedState() { final long time = System.currentTimeMillis(); if (time > mResumeTime) { @@ -547,7 +530,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang return; } if (includeHistory) { - commitCurrentLogUnit(); + commitCurrentLogUnit(SystemClock.uptimeMillis()); } else { mFeedbackLogBuffer.clear(); } @@ -556,7 +539,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang feedbackContents }; feedbackLogUnit.addLogStatement(EVENTKEYS_FEEDBACK, values, - false /* isPotentiallyPrivate */); + SystemClock.uptimeMillis(), false /* isPotentiallyPrivate */); mFeedbackLogBuffer.shiftIn(feedbackLogUnit); publishLogBuffer(mFeedbackLogBuffer, mFeedbackLog, true /* isIncludingPrivateData */); mFeedbackLog.close(new Runnable() { @@ -658,12 +641,13 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final Object[] values) { assert values.length + 1 == keys.length; if (isAllowedToLog()) { - mCurrentLogUnit.addLogStatement(keys, values, true /* isPotentiallyPrivate */); + final long time = SystemClock.uptimeMillis(); + mCurrentLogUnit.addLogStatement(keys, values, time, true /* isPotentiallyPrivate */); } } private void setCurrentLogUnitContainsDigitFlag() { - mCurrentLogUnit.setContainsDigit(); + mCurrentLogUnit.setMayContainDigit(); } /** @@ -681,15 +665,18 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang private synchronized void enqueueEvent(final String[] keys, final Object[] values) { assert values.length + 1 == keys.length; if (isAllowedToLog()) { - mCurrentLogUnit.addLogStatement(keys, values, false /* isPotentiallyPrivate */); + final long time = SystemClock.uptimeMillis(); + mCurrentLogUnit.addLogStatement(keys, values, time, false /* isPotentiallyPrivate */); } } - /* package for test */ void commitCurrentLogUnit() { + /* package for test */ void commitCurrentLogUnit(final long maxTime) { if (DEBUG) { - Log.d(TAG, "commitCurrentLogUnit"); + Log.d(TAG, "commitCurrentLogUnit" + (mCurrentLogUnit.hasWord() ? + ": " + mCurrentLogUnit.getWord() : "")); } if (!mCurrentLogUnit.isEmpty()) { + final LogUnit newLogUnit = mCurrentLogUnit.splitByTime(maxTime); if (mMainLogBuffer != null) { mMainLogBuffer.shiftIn(mCurrentLogUnit); if (mMainLogBuffer.isSafeToLog() && mMainResearchLog != null) { @@ -701,37 +688,54 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang if (mFeedbackLogBuffer != null) { mFeedbackLogBuffer.shiftIn(mCurrentLogUnit); } - mCurrentLogUnit = new LogUnit(); + mCurrentLogUnit = newLogUnit; Log.d(TAG, "commitCurrentLogUnit"); } } + private static final String[] EVENTKEYS_LOG_SEGMENT_START = { + "logSegmentStart", "isIncludingPrivateData" + }; + private static final String[] EVENTKEYS_LOG_SEGMENT_END = { + "logSegmentEnd" + }; /* package for test */ void publishLogBuffer(final LogBuffer logBuffer, final ResearchLog researchLog, final boolean isIncludingPrivateData) { + final LogUnit openingLogUnit = new LogUnit(); + final Object[] values = { + isIncludingPrivateData + }; + openingLogUnit.addLogStatement(EVENTKEYS_LOG_SEGMENT_START, values, + SystemClock.uptimeMillis(), false /* isPotentiallyPrivate */); + researchLog.publish(openingLogUnit, true /* isIncludingPrivateData */); LogUnit logUnit; while ((logUnit = logBuffer.shiftOut()) != null) { researchLog.publish(logUnit, isIncludingPrivateData); } + final LogUnit closingLogUnit = new LogUnit(); + closingLogUnit.addLogStatement(EVENTKEYS_LOG_SEGMENT_END, EVENTKEYS_NULLVALUES, + SystemClock.uptimeMillis(), false /* isPotentiallyPrivate */); + researchLog.publish(closingLogUnit, true /* isIncludingPrivateData */); } - private boolean hasOnlyLetters(final String word) { + private boolean hasLetters(final String word) { final int length = word.length(); for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) { final int codePoint = word.codePointAt(i); - if (!Character.isLetter(codePoint)) { - return false; + if (Character.isLetter(codePoint)) { + return true; } } - return true; + return false; } - private void onWordComplete(final String word) { + private void onWordComplete(final String word, final long maxTime) { Log.d(TAG, "onWordComplete: " + word); - if (word != null && word.length() > 0 && hasOnlyLetters(word)) { + if (word != null && word.length() > 0 && hasLetters(word)) { mCurrentLogUnit.setWord(word); mStatistics.recordWordEntered(); } - commitCurrentLogUnit(); + commitCurrentLogUnit(maxTime); } private static int scrubDigitFromCodePoint(int codePoint) { @@ -791,8 +795,11 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo, final SharedPreferences prefs) { final ResearchLogger researchLogger = getInstance(); - researchLogger.start(); if (editorInfo != null) { + final boolean isPassword = InputTypeUtils.isPasswordInputType(editorInfo.inputType) + || InputTypeUtils.isVisiblePasswordInputType(editorInfo.inputType); + getInstance().setIsPasswordView(isPassword); + researchLogger.start(); final Context context = researchLogger.mInputMethodService; try { final PackageInfo packageInfo; @@ -818,10 +825,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang stop(); } - private static final String[] EVENTKEYS_USER_FEEDBACK = { - "UserFeedback", "FeedbackContents" - }; - private static final String[] EVENTKEYS_PREFS_CHANGED = { "PrefsChanged", "prefs" }; @@ -870,7 +873,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final long time = SystemClock.uptimeMillis(); final ResearchLogger researchLogger = getInstance(); final Object[] values = { - Keyboard.printableCode(scrubDigitFromCodePoint(code)), x, y + Constants.printableCode(scrubDigitFromCodePoint(code)), x, y }; researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONCODEINPUT, values); if (Character.isDigit(code)) { @@ -903,18 +906,20 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang public static void latinIME_onWindowHidden(final int savedSelectionStart, final int savedSelectionEnd, final InputConnection ic) { if (ic != null) { - // Capture the TextView contents. This will trigger onUpdateSelection(), so we - // set sLatinIMEExpectingUpdateSelection so that when onUpdateSelection() is called, - // it can tell that it was generated by the logging code, and not by the user, and - // therefore keep user-visible state as is. - ic.beginBatchEdit(); - ic.performContextMenuAction(android.R.id.selectAll); - CharSequence charSequence = ic.getSelectedText(0); - ic.setSelection(savedSelectionStart, savedSelectionEnd); - ic.endBatchEdit(); - sLatinIMEExpectingUpdateSelection = true; final Object[] values = new Object[2]; if (OUTPUT_ENTIRE_BUFFER) { + // Capture the TextView contents. This will trigger onUpdateSelection(), so we + // set sLatinIMEExpectingUpdateSelection so that when onUpdateSelection() is called, + // it can tell that it was generated by the logging code, and not by the user, and + // therefore keep user-visible state as is. + ic.beginBatchEdit(); + ic.performContextMenuAction(android.R.id.selectAll); + CharSequence charSequence = ic.getSelectedText(0); + if (savedSelectionStart != -1 && savedSelectionEnd != -1) { + ic.setSelection(savedSelectionStart, savedSelectionEnd); + } + ic.endBatchEdit(); + sLatinIMEExpectingUpdateSelection = true; if (TextUtils.isEmpty(charSequence)) { values[0] = false; values[1] = ""; @@ -941,7 +946,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } final ResearchLogger researchLogger = getInstance(); researchLogger.enqueueEvent(EVENTKEYS_LATINIME_ONWINDOWHIDDEN, values); - researchLogger.commitCurrentLogUnit(); + researchLogger.commitCurrentLogUnit(SystemClock.uptimeMillis()); getInstance().stop(); } } @@ -1006,7 +1011,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang }; public static void latinIME_sendKeyCodePoint(final int code) { final Object[] values = { - Keyboard.printableCode(scrubDigitFromCodePoint(code)) + Constants.printableCode(scrubDigitFromCodePoint(code)) }; final ResearchLogger researchLogger = getInstance(); researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_SENDKEYCODEPOINT, values); @@ -1059,7 +1064,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang keyboard.mOccupiedHeight, keyboard.mKeys }; - getInstance().setIsPasswordView(isPasswordView); getInstance().enqueueEvent(EVENTKEYS_MAINKEYBOARDVIEW_SETKEYBOARD, values); } } @@ -1092,7 +1096,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang if (key != null) { String outputText = key.getOutputText(); final Object[] values = { - Keyboard.printableCode(scrubDigitFromCodePoint(code)), outputText == null ? null + Constants.printableCode(scrubDigitFromCodePoint(code)), outputText == null ? null : scrubDigitsFromString(outputText.toString()), x, y, ignoreModifierKey, altersCode, key.isEnabled() }; @@ -1109,7 +1113,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final boolean withSliding, final boolean ignoreModifierKey) { if (key != null) { final Object[] values = { - Keyboard.printableCode(scrubDigitFromCodePoint(primaryCode)), withSliding, + Constants.printableCode(scrubDigitFromCodePoint(primaryCode)), withSliding, ignoreModifierKey, key.isEnabled() }; getInstance().enqueuePotentiallyPrivateEvent( @@ -1188,7 +1192,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final ResearchLogger researchLogger = getInstance(); researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_RICHINPUTCONNECTION_COMMITTEXT, values); - researchLogger.onWordComplete(scrubbedWord); + // TODO: Replace Long.MAX_VALUE with timestamp of last data to include + researchLogger.onWordComplete(scrubbedWord, Long.MAX_VALUE); } private static final String[] EVENTKEYS_RICHINPUTCONNECTION_DELETESURROUNDINGTEXT = { diff --git a/java/src/com/android/inputmethod/research/Statistics.java b/java/src/com/android/inputmethod/research/Statistics.java index eab465aa2..2065ab15e 100644 --- a/java/src/com/android/inputmethod/research/Statistics.java +++ b/java/src/com/android/inputmethod/research/Statistics.java @@ -16,9 +16,14 @@ package com.android.inputmethod.research; -import com.android.inputmethod.keyboard.Keyboard; +import android.util.Log; + +import com.android.inputmethod.latin.Constants; public class Statistics { + private static final String TAG = Statistics.class.getSimpleName(); + private static final boolean DEBUG = false; + // Number of characters entered during a typing session int mCharCount; // Number of letter characters entered during a typing session @@ -103,8 +108,11 @@ public class Statistics { } public void recordChar(int codePoint, long time) { + if (DEBUG) { + Log.d(TAG, "recordChar() called"); + } final long delta = time - mLastTapTime; - if (codePoint == Keyboard.CODE_DELETE) { + if (codePoint == Constants.CODE_DELETE) { mDeleteKeyCount++; if (delta < MIN_DELETION_INTERMISSION) { if (mIsLastKeyDeleteKey) { |