aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/com/android')
-rw-r--r--java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java2
-rw-r--r--java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java2
-rw-r--r--java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java35
-rw-r--r--java/src/com/android/inputmethod/annotations/ExternallyReferenced.java24
-rw-r--r--java/src/com/android/inputmethod/annotations/UsedForTesting.java24
-rw-r--r--java/src/com/android/inputmethod/compat/AudioManagerCompatWrapper.java2
-rw-r--r--java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java2
-rw-r--r--java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java23
-rw-r--r--java/src/com/android/inputmethod/keyboard/Key.java13
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyDetector.java26
-rw-r--r--java/src/com/android/inputmethod/keyboard/Keyboard.java73
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java16
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardId.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java29
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java37
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardView.java20
-rw-r--r--java/src/com/android/inputmethod/keyboard/MainKeyboardView.java240
-rw-r--r--java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java5
-rw-r--r--java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java34
-rw-r--r--java/src/com/android/inputmethod/keyboard/PointerTracker.java714
-rw-r--r--java/src/com/android/inputmethod/keyboard/ProximityInfo.java66
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/AlphabetShiftState.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java327
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java23
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java17
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java6
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java8
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java34
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java4
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java37
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java9
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeysCache.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java10
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java135
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/ShiftKeyState.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java36
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/TouchScreenRegulator.java (renamed from java/src/com/android/inputmethod/keyboard/internal/SuddenJumpingTouchEventHandler.java)20
-rw-r--r--java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java2
-rw-r--r--java/src/com/android/inputmethod/latin/AssetFileAddress.java2
-rw-r--r--java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java31
-rw-r--r--java/src/com/android/inputmethod/latin/AutoCorrection.java25
-rw-r--r--java/src/com/android/inputmethod/latin/BackupAgent.java2
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java80
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java15
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java15
-rw-r--r--java/src/com/android/inputmethod/latin/BoundedTreeSet.java2
-rw-r--r--java/src/com/android/inputmethod/latin/Constants.java72
-rw-r--r--java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java19
-rw-r--r--java/src/com/android/inputmethod/latin/DebugSettings.java13
-rw-r--r--java/src/com/android/inputmethod/latin/DebugSettingsActivity.java2
-rw-r--r--java/src/com/android/inputmethod/latin/DicTraverseSession.java2
-rw-r--r--java/src/com/android/inputmethod/latin/Dictionary.java10
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryCollection.java8
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFactory.java2
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java2
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java14
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableDictionary.java87
-rw-r--r--java/src/com/android/inputmethod/latin/FileTransforms.java2
-rw-r--r--java/src/com/android/inputmethod/latin/InputAttributes.java2
-rw-r--r--java/src/com/android/inputmethod/latin/InputPointers.java10
-rw-r--r--java/src/com/android/inputmethod/latin/InputView.java2
-rw-r--r--java/src/com/android/inputmethod/latin/LastComposedWord.java6
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java321
-rw-r--r--java/src/com/android/inputmethod/latin/LatinImeLogger.java2
-rw-r--r--java/src/com/android/inputmethod/latin/ResizableIntArray.java2
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputConnection.java89
-rw-r--r--java/src/com/android/inputmethod/latin/Settings.java49
-rw-r--r--java/src/com/android/inputmethod/latin/SettingsActivity.java2
-rw-r--r--java/src/com/android/inputmethod/latin/SettingsValues.java6
-rw-r--r--java/src/com/android/inputmethod/latin/StringUtils.java129
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeLocale.java2
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeSwitcher.java4
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java51
-rw-r--r--java/src/com/android/inputmethod/latin/SuggestedWords.java33
-rw-r--r--java/src/com/android/inputmethod/latin/SuggestionSpanPickedNotificationReceiver.java2
-rw-r--r--java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java6
-rw-r--r--java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java6
-rw-r--r--java/src/com/android/inputmethod/latin/TargetApplicationGetter.java4
-rw-r--r--java/src/com/android/inputmethod/latin/UserBinaryDictionary.java2
-rw-r--r--java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java46
-rw-r--r--java/src/com/android/inputmethod/latin/UserHistoryDictionary.java500
-rw-r--r--java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java2
-rw-r--r--java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java4
-rw-r--r--java/src/com/android/inputmethod/latin/WordComposer.java45
-rw-r--r--java/src/com/android/inputmethod/latin/WordListInfo.java2
-rw-r--r--java/src/com/android/inputmethod/latin/XmlParseUtils.java8
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java845
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java429
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/CharGroupInfo.java2
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/FormatSpec.java88
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java91
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/MakedictLog.java2
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/PendingAttribute.java2
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/UnsupportedFormatException.java2
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Word.java2
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java10
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java2
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java6
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java2
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java6
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java9
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java2
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java2
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java23
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java34
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java172
-rw-r--r--java/src/com/android/inputmethod/research/ResearchLogger.java8
-rw-r--r--java/src/com/android/inputmethod/research/Statistics.java4
113 files changed, 3505 insertions, 2053 deletions
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
index 5af5d044f..b9b6362fc 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
@@ -47,7 +47,7 @@ import com.android.inputmethod.latin.CollectionUtils;
* virtual views, thus conveying their logical structure.
* </p>
*/
-public class AccessibilityEntityProvider extends AccessibilityNodeProviderCompat {
+public final class AccessibilityEntityProvider extends AccessibilityNodeProviderCompat {
private static final String TAG = AccessibilityEntityProvider.class.getSimpleName();
private static final int UNDEFINED = Integer.MIN_VALUE;
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
index 01220a58a..fcfa6d4e4 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
@@ -32,7 +32,7 @@ import com.android.inputmethod.keyboard.MainKeyboardView;
import com.android.inputmethod.keyboard.PointerTracker;
import com.android.inputmethod.latin.R;
-public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
+public final class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
private static final AccessibleKeyboardViewProxy sInstance = new AccessibleKeyboardViewProxy();
private InputMethodService mInputMethod;
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
index 5c45448a5..b87ae3a15 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -26,11 +26,12 @@ 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;
-public class KeyCodeDescriptionMapper {
+public final class KeyCodeDescriptionMapper {
private static final String TAG = KeyCodeDescriptionMapper.class.getSimpleName();
// The resource ID of the string spoken for obscured keys
@@ -61,17 +62,19 @@ public 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 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 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/AudioManagerCompatWrapper.java b/java/src/com/android/inputmethod/compat/AudioManagerCompatWrapper.java
index b6c3e2a88..40eed91f2 100644
--- a/java/src/com/android/inputmethod/compat/AudioManagerCompatWrapper.java
+++ b/java/src/com/android/inputmethod/compat/AudioManagerCompatWrapper.java
@@ -20,7 +20,7 @@ import android.media.AudioManager;
import java.lang.reflect.Method;
-public class AudioManagerCompatWrapper {
+public final class AudioManagerCompatWrapper {
private static final Method METHOD_isWiredHeadsetOn = CompatUtils.getMethod(
AudioManager.class, "isWiredHeadsetOn");
private static final Method METHOD_isBluetoothA2dpOn = CompatUtils.getMethod(
diff --git a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
index cc10a4ed2..a01c301ee 100644
--- a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
+++ b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
@@ -27,7 +27,7 @@ import java.lang.reflect.Method;
// TODO: Override this class with the concrete implementation if we need to take care of the
// performance.
-public class InputMethodManagerCompatWrapper {
+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);
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 cb120a33e..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;
@@ -138,7 +139,7 @@ public class Key implements Comparable<Key> {
private final OptionalAttributes mOptionalAttributes;
- private static class OptionalAttributes {
+ private static final class OptionalAttributes {
/** Text to output when pressed. This can be multiple characters, like ".com" */
public final String mOutputText;
public final int mAltCode;
@@ -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 f5686dcda..0a91284d0 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
@@ -21,6 +21,7 @@ import com.android.inputmethod.latin.Constants;
public class KeyDetector {
private final int mKeyHysteresisDistanceSquared;
+ private final int mKeyHysteresisDistanceForSlidingModifierSquared;
private Keyboard mKeyboard;
private int mCorrectionX;
@@ -30,10 +31,24 @@ public class KeyDetector {
* This class handles key detection.
*
* @param keyHysteresisDistance if the pointer movement distance is smaller than this, the
- * movement will not been handled as meaningful movement. The unit is pixel.
+ * movement will not be handled as meaningful movement. The unit is pixel.
*/
public KeyDetector(float keyHysteresisDistance) {
+ this(keyHysteresisDistance, keyHysteresisDistance);
+ }
+
+ /**
+ * This class handles key detection.
+ *
+ * @param keyHysteresisDistance if the pointer movement distance is smaller than this, the
+ * movement will not be handled as meaningful movement. The unit is pixel.
+ * @param keyHysteresisDistanceForSlidingModifier the same parameter for sliding input that
+ * starts from a modifier key such as shift and symbols key.
+ */
+ public KeyDetector(float keyHysteresisDistance, float keyHysteresisDistanceForSlidingModifier) {
mKeyHysteresisDistanceSquared = (int)(keyHysteresisDistance * keyHysteresisDistance);
+ mKeyHysteresisDistanceForSlidingModifierSquared = (int)(
+ keyHysteresisDistanceForSlidingModifier * keyHysteresisDistanceForSlidingModifier);
}
public void setKeyboard(Keyboard keyboard, float correctionX, float correctionY) {
@@ -45,8 +60,9 @@ public class KeyDetector {
mKeyboard = keyboard;
}
- public int getKeyHysteresisDistanceSquared() {
- return mKeyHysteresisDistanceSquared;
+ public int getKeyHysteresisDistanceSquared(boolean isSlidingFromModifier) {
+ return isSlidingFromModifier
+ ? mKeyHysteresisDistanceForSlidingModifierSquared : mKeyHysteresisDistanceSquared;
}
public int getTouchX(int x) {
@@ -102,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) {
@@ -111,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..b612f0927 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.
@@ -99,7 +99,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
@@ -112,13 +112,5 @@ public interface KeyboardActionListener {
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 1e5277345..5e8a8f6bb 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -33,7 +33,7 @@ import java.util.Locale;
/**
* Unique identifier for each keyboard type.
*/
-public class KeyboardId {
+public final class KeyboardId {
public static final int MODE_TEXT = 0;
public static final int MODE_URL = 1;
public static final int MODE_EMAIL = 2;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
index aaccf63ba..4d5d7e14e 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;
@@ -61,7 +62,7 @@ import java.util.HashMap;
* A {@link KeyboardLayoutSet} needs to be created for each
* {@link android.view.inputmethod.EditorInfo}.
*/
-public class KeyboardLayoutSet {
+public final class KeyboardLayoutSet {
private static final String TAG = KeyboardLayoutSet.class.getSimpleName();
private static final boolean DEBUG_CACHE = LatinImeLogger.sDBG;
@@ -77,7 +78,7 @@ public class KeyboardLayoutSet {
CollectionUtils.newHashMap();
private static final KeysCache sKeysCache = new KeysCache();
- public static class KeyboardLayoutSetException extends RuntimeException {
+ public static final class KeyboardLayoutSetException extends RuntimeException {
public final KeyboardId mKeyboardId;
public KeyboardLayoutSetException(final Throwable cause, final KeyboardId keyboardId) {
@@ -86,17 +87,17 @@ public class KeyboardLayoutSet {
}
}
- private static class ElementParams {
+ private static final class ElementParams {
int mKeyboardXmlId;
boolean mProximityCharsCorrectionEnabled;
public ElementParams() {}
}
- private static class Params {
+ private static final class Params {
String mKeyboardLayoutSetName;
int mMode;
EditorInfo mEditorInfo;
- boolean mTouchPositionCorrectionEnabled;
+ boolean mDisableTouchPositionCorrectionDataForTest;
boolean mVoiceKeyEnabled;
boolean mVoiceKeyOnMain;
boolean mNoSettingsKey;
@@ -167,7 +168,9 @@ public class KeyboardLayoutSet {
}
final int keyboardXmlId = elementParams.mKeyboardXmlId;
builder.load(keyboardXmlId, id);
- builder.setTouchPositionCorrectionEnabled(mParams.mTouchPositionCorrectionEnabled);
+ if (mParams.mDisableTouchPositionCorrectionDataForTest) {
+ builder.disableTouchPositionCorrectionDataForTest();
+ }
builder.setProximityCharsCorrectionEnabled(
elementParams.mProximityCharsCorrectionEnabled);
keyboard = builder.build();
@@ -192,16 +195,15 @@ public class KeyboardLayoutSet {
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);
+ final boolean hasShortcutKey = params.mVoiceKeyEnabled
+ && (isSymbols != params.mVoiceKeyOnMain);
return new KeyboardId(keyboardLayoutSetElementId, params.mSubtype, params.mDeviceFormFactor,
params.mOrientation, params.mWidth, params.mMode, params.mEditorInfo,
- params.mNoSettingsKey, voiceKeyEnabled, hasShortcutKey,
+ params.mNoSettingsKey, params.mVoiceKeyEnabled, hasShortcutKey,
params.mLanguageSwitchKeyEnabled);
}
- public static class Builder {
+ public static final class Builder {
private final Context mContext;
private final String mPackageName;
private final Resources mResources;
@@ -264,8 +266,9 @@ public class KeyboardLayoutSet {
return this;
}
- public void setTouchPositionCorrectionEnabled(final boolean enabled) {
- mParams.mTouchPositionCorrectionEnabled = enabled;
+ @UsedForTesting
+ public void disableTouchPositionCorrectionDataForTest() {
+ mParams.mDisableTouchPositionCorrectionDataForTest = true;
}
public KeyboardLayoutSet build() {
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index fd789f029..edcd888db 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -29,7 +29,7 @@ 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.AudioAndHapticFeedbackManager;
import com.android.inputmethod.latin.ImfUtils;
import com.android.inputmethod.latin.InputView;
import com.android.inputmethod.latin.LatinIME;
@@ -39,12 +39,12 @@ import com.android.inputmethod.latin.SettingsValues;
import com.android.inputmethod.latin.SubtypeSwitcher;
import com.android.inputmethod.latin.WordComposer;
-public class KeyboardSwitcher implements KeyboardState.SwitchActions {
+public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
private static final String TAG = KeyboardSwitcher.class.getSimpleName();
public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20110916";
- static class KeyboardTheme {
+ static final class KeyboardTheme {
public final int mThemeId;
public final int mStyleId;
@@ -65,9 +65,9 @@ public 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 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) {
@@ -147,6 +146,7 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions {
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 class KeyboardSwitcher implements KeyboardState.SwitchActions {
}
}
+ public void onRingerModeChanged() {
+ mFeedbackManager.onRingerModeChanged();
+ }
+
public void saveKeyboardState() {
if (getKeyboard() != null) {
mState.onSaveKeyboardState();
@@ -202,7 +206,7 @@ public 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());
}
@@ -314,7 +318,7 @@ public 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) {
@@ -333,10 +337,6 @@ public 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.
*/
@@ -363,9 +363,6 @@ public 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.
@@ -389,13 +386,19 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions {
}
}
- public int getManualCapsMode() {
- switch (getKeyboard().mId.mElementId) {
+ public int getKeyboardShiftMode() {
+ final Keyboard keyboard = getKeyboard();
+ if (keyboard == null) {
+ return WordComposer.CAPS_MODE_OFF;
+ }
+ switch (keyboard.mId.mElementId) {
case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
return WordComposer.CAPS_MODE_MANUAL_SHIFT_LOCKED;
case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
return WordComposer.CAPS_MODE_MANUAL_SHIFTED;
+ case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
+ return WordComposer.CAPS_MODE_AUTO_SHIFTED;
default:
return WordComposer.CAPS_MODE_OFF;
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index cf89567f8..472f74b12 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -30,6 +30,7 @@ import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Message;
import android.util.AttributeSet;
+import android.util.DisplayMetrics;
import android.util.Log;
import android.util.SparseArray;
import android.util.TypedValue;
@@ -104,7 +105,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
private static final String TAG = KeyboardView.class.getSimpleName();
// XML attributes
- private final KeyVisualAttributes mKeyVisualAttributes;
+ protected final KeyVisualAttributes mKeyVisualAttributes;
private final int mKeyLabelHorizontalPadding;
private final float mKeyHintLetterPadding;
private final float mKeyPopupHintLetterPadding;
@@ -732,6 +733,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
return width;
}
+ // TODO: Remove this method.
public float getLabelWidth(final String label, final Paint paint) {
paint.getTextBounds(label, 0, label.length(), mTextBounds);
return mTextBounds.width();
@@ -781,9 +783,6 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
public void cancelAllMessages() {
mDrawingHandler.cancelAllMessages();
- if (mPreviewPlacerView != null) {
- mPreviewPlacerView.cancelAllMessages();
- }
}
private TextView getKeyPreviewText(final int pointerId) {
@@ -827,9 +826,19 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
if (mPreviewPlacerView.getParent() != null) {
return;
}
+ final int width = getWidth();
+ final int height = getHeight();
+ if (width == 0 || height == 0) {
+ // In transient state.
+ return;
+ }
final int[] viewOrigin = new int[2];
getLocationInWindow(viewOrigin);
- mPreviewPlacerView.setOrigin(viewOrigin[0], viewOrigin[1]);
+ final DisplayMetrics dm = getResources().getDisplayMetrics();
+ if (viewOrigin[1] < dm.heightPixels / 4) {
+ // In transient state.
+ return;
+ }
final View rootView = getRootView();
if (rootView == null) {
Log.w(TAG, "Cannot find root view");
@@ -841,6 +850,7 @@ 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);
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 4ed0f58e1..e60a8a942 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,7 @@ import android.graphics.Paint.Align;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Message;
-import android.text.TextUtils;
+import android.preference.PreferenceManager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
@@ -41,11 +42,13 @@ 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.DebugSettings;
import com.android.inputmethod.latin.LatinIME;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
@@ -82,9 +85,20 @@ import java.util.WeakHashMap;
* @attr ref R.styleable#MainKeyboardView_longPressShiftKeyTimeout
* @attr ref R.styleable#MainKeyboardView_ignoreAltCodeKeyTimeout
* @attr ref R.styleable#MainKeyboardView_showMoreKeysKeyboardAtTouchPoint
+ * @attr ref R.styleable#MainKeyboardView_gestureStaticTimeThresholdAfterFastTyping
+ * @attr ref R.styleable#MainKeyboardView_gestureDetectFastMoveSpeedThreshold
+ * @attr ref R.styleable#MainKeyboardView_gestureDynamicThresholdDecayDuration
+ * @attr ref R.styleable#MainKeyboardView_gestureDynamicTimeThresholdFrom
+ * @attr ref R.styleable#MainKeyboardView_gestureDynamicTimeThresholdTo
+ * @attr ref R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdFrom
+ * @attr ref R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdTo
+ * @attr ref R.styleable#MainKeyboardView_gestureSamplingMinimumDistance
+ * @attr ref R.styleable#MainKeyboardView_gestureRecognitionMinimumTime
+ * @attr ref R.styleable#MainKeyboardView_gestureRecognitionSpeedThreshold
+ * @attr ref R.styleable#MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration
*/
-public class MainKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler,
- SuddenJumpingTouchEventHandler.ProcessMotionEvent {
+public final class MainKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler,
+ TouchScreenRegulator.ProcessMotionEvent {
private static final String TAG = MainKeyboardView.class.getSimpleName();
// TODO: Kill process when the usability study mode was changed.
@@ -127,16 +141,16 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
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;
private final KeyTimerHandler mKeyTimerHandler;
- private static class KeyTimerHandler extends StaticInnerHandlerWrapper<MainKeyboardView>
+ private static final class KeyTimerHandler extends StaticInnerHandlerWrapper<MainKeyboardView>
implements TimerProxy {
private static final int MSG_TYPING_STATE_EXPIRED = 0;
private static final int MSG_REPEAT_KEY = 1;
@@ -192,7 +206,9 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
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);
}
@@ -215,7 +231,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
cancelLongPressTimer();
final int delay;
switch (code) {
- case Keyboard.CODE_SHIFT:
+ case Constants.CODE_SHIFT:
delay = mLongPressShiftKeyTimeout;
break;
default:
@@ -236,7 +252,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
final Key key = tracker.getKey();
final int delay;
switch (key.mCode) {
- case Keyboard.CODE_SHIFT:
+ case Constants.CODE_SHIFT:
delay = mLongPressShiftKeyTimeout;
break;
default:
@@ -293,7 +309,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
// 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;
}
@@ -345,15 +361,19 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
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);
@@ -378,7 +398,10 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
final float keyHysteresisDistance = a.getDimension(
R.styleable.MainKeyboardView_keyHysteresisDistance, 0);
- mKeyDetector = new KeyDetector(keyHysteresisDistance);
+ final float keyHysteresisDistanceForSlidingModifier = a.getDimension(
+ R.styleable.MainKeyboardView_keyHysteresisDistanceForSlidingModifier, 0);
+ mKeyDetector = new KeyDetector(
+ keyHysteresisDistance, keyHysteresisDistanceForSlidingModifier);
mKeyTimerHandler = new KeyTimerHandler(this, a);
mConfigShowMoreKeysKeyboardAtTouchedPoint = a.getBoolean(
R.styleable.MainKeyboardView_showMoreKeysKeyboardAtTouchedPoint, false);
@@ -394,7 +417,9 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
}
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) {
@@ -403,20 +428,23 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
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();
@@ -466,10 +494,10 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
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;
@@ -492,18 +520,6 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
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();
@@ -539,21 +555,25 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
}
// 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);
@@ -586,7 +606,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
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();
@@ -614,8 +634,9 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
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) {
@@ -651,9 +672,8 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
public boolean isInSlidingKeyInput() {
if (mMoreKeysPanel != null) {
return true;
- } else {
- return PointerTracker.isAnyInSlidingKeyInput();
}
+ return PointerTracker.isAnyInSlidingKeyInput();
}
public int getPointerCount() {
@@ -702,39 +722,15 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
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);
- }
+ // 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()) {
@@ -760,8 +756,9 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
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.
@@ -798,15 +795,11 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
}
tracker.onMoveEvent(px, py, eventTime, motionEvent);
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 {
@@ -817,6 +810,35 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
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();
@@ -826,14 +848,14 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
@Override
public boolean dismissMoreKeysPanel() {
- if (mMoreKeysWindow != null && mMoreKeysWindow.isShowing()) {
- mMoreKeysWindow.dismiss();
- mMoreKeysPanel = null;
- mMoreKeysPanelPointerTrackerId = -1;
- dimEntireKeyboard(false);
- return true;
+ if (mMoreKeysWindow == null || !mMoreKeysWindow.isShowing()) {
+ return false;
}
- return false;
+ mMoreKeysWindow.dismiss();
+ mMoreKeysPanel = null;
+ mMoreKeysPanelPointerTrackerId = -1;
+ dimEntireKeyboard(false);
+ return true;
}
/**
@@ -856,16 +878,22 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
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);
}
@@ -895,7 +923,9 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
}
public void updateAutoCorrectionState(final boolean isAutoCorrection) {
- if (!mAutoCorrectionSpacebarLedEnabled) return;
+ if (!mAutoCorrectionSpacebarLedEnabled) {
+ return;
+ }
mAutoCorrectionSpacebarLedOn = isAutoCorrection;
invalidateKey(mSpaceKey);
}
@@ -906,13 +936,13 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
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 {
@@ -923,10 +953,14 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
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;
@@ -936,19 +970,19 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
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 "";
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java b/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
index cd4e3001e..a2001cb8f 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
@@ -16,7 +16,7 @@
package com.android.inputmethod.keyboard;
-public class MoreKeysDetector extends KeyDetector {
+public final class MoreKeysDetector extends KeyDetector {
private final int mSlideAllowanceSquare;
private final int mSlideAllowanceSquareTop;
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
index c9af888f9..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;
@@ -27,7 +28,7 @@ import com.android.inputmethod.keyboard.internal.MoreKeySpec;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.StringUtils;
-public class MoreKeysKeyboard extends Keyboard {
+public final class MoreKeysKeyboard extends Keyboard {
private final int mDefaultKeyCoordX;
MoreKeysKeyboard(final MoreKeysKeyboardParams params) {
@@ -39,7 +40,7 @@ public 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 e513a1477..9c450e994 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
@@ -33,7 +33,7 @@ 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 class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel {
+public final class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel {
private final int[] mCoordinates = new int[2];
private final KeyDetector mKeyDetector;
@@ -48,7 +48,7 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
private final KeyboardActionListener mMoreKeysKeyboardListener =
new KeyboardActionListener.Adapter() {
@Override
- public void onCodeInput(int primaryCode, int x, int y) {
+ public void onCodeInput(final int primaryCode, final int x, final int y) {
// Because a more keys keyboard doesn't need proximity characters correction, we don't
// send touch event coordinates.
mListener.onCodeInput(
@@ -56,7 +56,7 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
}
@Override
- public void onTextInput(CharSequence text) {
+ public void onTextInput(final String text) {
mListener.onTextInput(text);
}
@@ -66,12 +66,12 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
}
@Override
- public void onUpdateBatchInput(InputPointers batchPointers) {
+ public void onUpdateBatchInput(final InputPointers batchPointers) {
mListener.onUpdateBatchInput(batchPointers);
}
@Override
- public void onEndBatchInput(InputPointers batchPointers) {
+ public void onEndBatchInput(final InputPointers batchPointers) {
mListener.onEndBatchInput(batchPointers);
}
@@ -81,21 +81,22 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
}
@Override
- public void onPressKey(int primaryCode) {
+ public void onPressKey(final int primaryCode) {
mListener.onPressKey(primaryCode);
}
@Override
- public void onReleaseKey(int primaryCode, boolean withSliding) {
+ public void onReleaseKey(final int primaryCode, final boolean withSliding) {
mListener.onReleaseKey(primaryCode, withSliding);
}
};
- 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 +106,7 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
}
@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,7 +118,7 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
}
@Override
- public void setKeyboard(Keyboard keyboard) {
+ public void setKeyboard(final Keyboard keyboard) {
super.setKeyboard(keyboard);
mKeyDetector.setKeyboard(keyboard, -getPaddingLeft(),
-getPaddingTop() + mVerticalCorrection);
@@ -144,15 +145,16 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
}
@Override
- public void setKeyPreviewPopupEnabled(boolean previewEnabled, int delay) {
+ 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 void showMoreKeysPanel(View parentView, Controller controller, int pointX, int pointY,
- PopupWindow window, KeyboardActionListener listener) {
+ public void showMoreKeysPanel(final View parentView, final Controller controller,
+ final int pointX, final int pointY, final PopupWindow window,
+ final KeyboardActionListener listener) {
mController = controller;
mListener = listener;
final View container = (View)getParent();
@@ -185,12 +187,12 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
}
@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;
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 03d610a39..c86805232 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -23,9 +23,11 @@ import android.view.MotionEvent;
import com.android.inputmethod.accessibility.AccessibilityUtils;
import com.android.inputmethod.keyboard.internal.GestureStroke;
+import com.android.inputmethod.keyboard.internal.GestureStroke.GestureStrokeParams;
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.InputPointers;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
@@ -34,12 +36,12 @@ import com.android.inputmethod.research.ResearchLogger;
import java.util.ArrayList;
-public class PointerTracker implements PointerTrackerQueue.Element {
+public final class PointerTracker implements PointerTrackerQueue.Element {
private static final String TAG = PointerTracker.class.getSimpleName();
private static final boolean DEBUG_EVENT = false;
private static final boolean DEBUG_MOVE_EVENT = false;
private static final boolean DEBUG_LISTENER = false;
- private static boolean DEBUG_MODE = LatinImeLogger.sDBG;
+ private static boolean DEBUG_MODE = LatinImeLogger.sDBG || DEBUG_EVENT;
/** True if {@link PointerTracker}s should handle gesture events. */
private static boolean sShouldHandleGesture = false;
@@ -117,40 +119,45 @@ public class PointerTracker implements PointerTrackerQueue.Element {
}
}
- static class PointerTrackerParams {
+ static final class PointerTrackerParams {
public final boolean mSlidingKeyInputEnabled;
public final int mTouchNoiseThresholdTime;
- public final float mTouchNoiseThresholdDistance;
- public final int mTouchNoiseThresholdDistanceSquared;
+ public final int mTouchNoiseThresholdDistance;
+ public final int mSuppressKeyPreviewAfterBatchInputDuration;
public static final PointerTrackerParams DEFAULT = new PointerTrackerParams();
private PointerTrackerParams() {
mSlidingKeyInputEnabled = false;
mTouchNoiseThresholdTime = 0;
- mTouchNoiseThresholdDistance = 0.0f;
- mTouchNoiseThresholdDistanceSquared = 0;
+ mTouchNoiseThresholdDistance = 0;
+ mSuppressKeyPreviewAfterBatchInputDuration = 0;
}
- public PointerTrackerParams(TypedArray mainKeyboardViewAttr) {
+ public PointerTrackerParams(final TypedArray mainKeyboardViewAttr) {
mSlidingKeyInputEnabled = mainKeyboardViewAttr.getBoolean(
R.styleable.MainKeyboardView_slidingKeyInputEnable, false);
mTouchNoiseThresholdTime = mainKeyboardViewAttr.getInt(
R.styleable.MainKeyboardView_touchNoiseThresholdTime, 0);
- final float touchNouseThresholdDistance = mainKeyboardViewAttr.getDimension(
+ mTouchNoiseThresholdDistance = mainKeyboardViewAttr.getDimensionPixelSize(
R.styleable.MainKeyboardView_touchNoiseThresholdDistance, 0);
- mTouchNoiseThresholdDistance = touchNouseThresholdDistance;
- mTouchNoiseThresholdDistanceSquared =
- (int)(touchNouseThresholdDistance * touchNouseThresholdDistance);
+ mSuppressKeyPreviewAfterBatchInputDuration = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration, 0);
}
}
// Parameters for pointer handling.
private static PointerTrackerParams sParams;
+ private static GestureStrokeParams sGestureStrokeParams;
private static boolean sNeedsPhantomSuddenMoveEventHack;
+ // Move this threshold to resource.
+ // TODO: Device specific parameter would be better for device specific hack?
+ private static final float PHANTOM_SUDDEN_MOVE_THRESHOLD = 0.25f; // in keyWidth
+ // This hack might be device specific.
+ 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;
@@ -160,15 +167,126 @@ public class PointerTracker implements PointerTrackerQueue.Element {
private KeyboardActionListener mListener = EMPTY_LISTENER;
private Keyboard mKeyboard;
- private int mKeyQuarterWidthSquared;
+ private int mPhantonSuddenMoveThreshold;
+ private final BogusMoveEventDetector mBogusMoveEventDetector = new BogusMoveEventDetector();
private boolean mIsDetectingGesture = false; // per PointerTracker.
private static boolean sInGesture = false;
private static long sGestureFirstDownTime;
+ private static TimeRecorder sTimeRecorder;
private static final InputPointers sAggregratedPointers = new InputPointers(
GestureStroke.DEFAULT_CAPACITY);
- private static int sLastRecognitionPointSize = 0;
- private static long sLastRecognitionTime = 0;
+ private static int sLastRecognitionPointSize = 0; // synchronized using sAggregratedPointers
+ private static long sLastRecognitionTime = 0; // synchronized using sAggregratedPointers
+
+ static final class BogusMoveEventDetector {
+ // Move these thresholds to resource.
+ // These thresholds' unit is a diagonal length of a key.
+ private static final float BOGUS_MOVE_ACCUMULATED_DISTANCE_THRESHOLD = 0.53f;
+ private static final float BOGUS_MOVE_RADIUS_THRESHOLD = 1.14f;
+
+ private int mAccumulatedDistanceThreshold;
+ private int mRadiusThreshold;
+
+ // Accumulated distance from actual and artificial down keys.
+ /* package */ int mAccumulatedDistanceFromDownKey;
+ private int mActualDownX;
+ private int mActualDownY;
+
+ public void setKeyboardGeometry(final int keyWidth, final int keyHeight) {
+ final float keyDiagonal = (float)Math.hypot(keyWidth, keyHeight);
+ mAccumulatedDistanceThreshold = (int)(
+ keyDiagonal * BOGUS_MOVE_ACCUMULATED_DISTANCE_THRESHOLD);
+ mRadiusThreshold = (int)(keyDiagonal * BOGUS_MOVE_RADIUS_THRESHOLD);
+ }
+
+ public void onActualDownEvent(final int x, final int y) {
+ mActualDownX = x;
+ mActualDownY = y;
+ }
+
+ public void onDownKey() {
+ mAccumulatedDistanceFromDownKey = 0;
+ }
+
+ public void onMoveKey(final int distance) {
+ mAccumulatedDistanceFromDownKey += distance;
+ }
+
+ public boolean hasTraveledLongDistance(final int x, final int y) {
+ final int dx = Math.abs(x - mActualDownX);
+ final int dy = Math.abs(y - mActualDownY);
+ // A bogus move event should be a horizontal movement. A vertical movement might be
+ // a sloppy typing and should be ignored.
+ return dx >= dy && mAccumulatedDistanceFromDownKey >= mAccumulatedDistanceThreshold;
+ }
+
+ /* package */ int getDistanceFromDownEvent(final int x, final int y) {
+ return getDistance(x, y, mActualDownX, mActualDownY);
+ }
+
+ public boolean isCloseToActualDownEvent(final int x, final int y) {
+ return getDistanceFromDownEvent(x, y) < mRadiusThreshold;
+ }
+ }
+
+ static final class TimeRecorder {
+ private final int mSuppressKeyPreviewAfterBatchInputDuration;
+ private final int mStaticTimeThresholdAfterFastTyping; // msec
+ private long mLastTypingTime;
+ private long mLastLetterTypingTime;
+ private long mLastBatchInputTime;
+
+ public TimeRecorder(final PointerTrackerParams pointerTrackerParams,
+ final GestureStrokeParams gestureStrokeParams) {
+ mSuppressKeyPreviewAfterBatchInputDuration =
+ pointerTrackerParams.mSuppressKeyPreviewAfterBatchInputDuration;
+ mStaticTimeThresholdAfterFastTyping =
+ gestureStrokeParams.mStaticTimeThresholdAfterFastTyping;
+ }
+
+ public boolean isInFastTyping(final long eventTime) {
+ final long elapsedTimeSinceLastLetterTyping = eventTime - mLastLetterTypingTime;
+ return elapsedTimeSinceLastLetterTyping < mStaticTimeThresholdAfterFastTyping;
+ }
+
+ private boolean wasLastInputTyping() {
+ return mLastTypingTime >= mLastBatchInputTime;
+ }
+
+ public void onCodeInput(final int code, final long eventTime) {
+ // Record the letter typing time when
+ // 1. Letter keys are typed successively without any batch input in between.
+ // 2. A letter key is typed within the threshold time since the last any key typing.
+ // 3. A non-letter key is typed within the threshold time since the last letter key
+ // typing.
+ if (Character.isLetter(code)) {
+ if (wasLastInputTyping()
+ || eventTime - mLastTypingTime < mStaticTimeThresholdAfterFastTyping) {
+ mLastLetterTypingTime = eventTime;
+ }
+ } else {
+ if (eventTime - mLastLetterTypingTime < mStaticTimeThresholdAfterFastTyping) {
+ // This non-letter typing should be treated as a part of fast typing.
+ mLastLetterTypingTime = eventTime;
+ }
+ }
+ mLastTypingTime = eventTime;
+ }
+
+ public void onEndBatchInput(final long eventTime) {
+ mLastBatchInputTime = eventTime;
+ }
+
+ public long getLastLetterTypingTime() {
+ return mLastLetterTypingTime;
+ }
+
+ public boolean needsToSuppressKeyPreviewPopup(final long eventTime) {
+ return !wasLastInputTyping()
+ && eventTime - mLastBatchInputTime < mSuppressKeyPreviewAfterBatchInputDuration;
+ }
+ }
// The position and time at which first down event occurred.
private long mDownTime;
@@ -193,34 +311,32 @@ public class PointerTracker implements PointerTrackerQueue.Element {
// true if this pointer has been long-pressed and is showing a more keys panel.
private boolean mIsShowingMoreKeysPanel;
- // true if this pointer is in sliding key input
+ // true if this pointer is in a sliding key input.
boolean mIsInSlidingKeyInput;
+ // true if this pointer is in a sliding key input from a modifier key,
+ // so that further modifier keys should be ignored.
+ boolean mIsInSlidingKeyInputFromModifier;
- // true if sliding key is allowed.
+ // true if a sliding key input is allowed.
private boolean mIsAllowedSlidingKeyInput;
- // ignore modifier key if true
- private boolean mIgnoreModifierKey;
-
// Empty {@link KeyboardActionListener}
private static final KeyboardActionListener EMPTY_LISTENER =
new KeyboardActionListener.Adapter();
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;
+ sTimeRecorder = new TimeRecorder(sParams, sGestureStrokeParams);
}
public static void setParameters(final TypedArray mainKeyboardViewAttr) {
sParams = new PointerTrackerParams(mainKeyboardViewAttr);
+ sGestureStrokeParams = new GestureStrokeParams(mainKeyboardViewAttr);
+ sTimeRecorder = new TimeRecorder(sParams, sGestureStrokeParams);
}
private static void updateGestureHandlingMode() {
@@ -254,7 +370,7 @@ public 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) {
@@ -291,7 +407,8 @@ public class PointerTracker implements PointerTrackerQueue.Element {
throw new NullPointerException();
}
mPointerId = id;
- mGestureStrokeWithPreviewPoints = new GestureStrokeWithPreviewPoints(id);
+ mGestureStrokeWithPreviewPoints = new GestureStrokeWithPreviewPoints(
+ id, sGestureStrokeParams);
setKeyDetectorInner(handler.getKeyDetector());
mListener = handler.getKeyboardActionListener();
mDrawingProxy = handler.getDrawingProxy();
@@ -303,11 +420,12 @@ public class PointerTracker implements PointerTrackerQueue.Element {
if (sInGesture) {
return false;
}
- final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
+ final boolean ignoreModifierKey = mIsInSlidingKeyInputFromModifier && key.isModifier();
if (DEBUG_LISTENER) {
- Log.d(TAG, "onPress : " + KeyDetector.printableCode(key)
- + " ignoreModifier=" + ignoreModifierKey
- + " enabled=" + key.isEnabled());
+ Log.d(TAG, String.format("[%d] onPress : %s%s%s", mPointerId,
+ KeyDetector.printableCode(key),
+ ignoreModifierKey ? " ignoreModifier" : "",
+ key.isEnabled() ? "" : " disabled"));
}
if (ignoreModifierKey) {
return false;
@@ -325,15 +443,16 @@ public class PointerTracker implements PointerTrackerQueue.Element {
// Note that we need primaryCode argument because the keyboard may in shifted state and the
// primaryCode is different from {@link Key#mCode}.
private void callListenerOnCodeInput(final Key key, final int primaryCode, final int x,
- final int y) {
- final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
+ final int y, final long eventTime) {
+ final boolean ignoreModifierKey = mIsInSlidingKeyInputFromModifier && key.isModifier();
final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState();
final int code = altersCode ? key.getAltCode() : primaryCode;
if (DEBUG_LISTENER) {
- Log.d(TAG, "onCodeInput: " + Keyboard.printableCode(code)
- + " text=" + key.getOutputText() + " x=" + x + " y=" + y
- + " ignoreModifier=" + ignoreModifierKey + " altersCode=" + altersCode
- + " enabled=" + key.isEnabled());
+ 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"));
}
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.pointerTracker_callListenerOnCodeInput(key, x, y, ignoreModifierKey,
@@ -344,26 +463,28 @@ public 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) {
- if (code == Keyboard.CODE_OUTPUT_TEXT) {
+ sTimeRecorder.onCodeInput(code, eventTime);
+ 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);
}
}
}
- // Note that we need primaryCode argument because the keyboard may in shifted state and the
+ // Note that we need primaryCode argument because the keyboard may be in shifted state and the
// primaryCode is different from {@link Key#mCode}.
private void callListenerOnRelease(final Key key, final int primaryCode,
final boolean withSliding) {
if (sInGesture) {
return;
}
- final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
+ final boolean ignoreModifierKey = mIsInSlidingKeyInputFromModifier && key.isModifier();
if (DEBUG_LISTENER) {
- Log.d(TAG, "onRelease : " + Keyboard.printableCode(primaryCode)
- + " sliding=" + withSliding + " ignoreModifier=" + ignoreModifierKey
- + " enabled="+ key.isEnabled());
+ Log.d(TAG, String.format("[%d] onRelease : %s%s%s%s", mPointerId,
+ Constants.printableCode(primaryCode),
+ withSliding ? " sliding" : "", ignoreModifierKey ? " ignoreModifier" : "",
+ key.isEnabled() ? "": " disabled"));
}
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.pointerTracker_callListenerOnRelease(key, primaryCode, withSliding,
@@ -379,7 +500,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
private void callListenerOnCancelInput() {
if (DEBUG_LISTENER) {
- Log.d(TAG, "onCancelInput");
+ Log.d(TAG, String.format("[%d] onCancelInput", mPointerId));
}
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.pointerTracker_callListenerOnCancelInput();
@@ -388,9 +509,15 @@ public class PointerTracker implements PointerTrackerQueue.Element {
}
private void setKeyDetectorInner(final KeyDetector keyDetector) {
+ final Keyboard keyboard = keyDetector.getKeyboard();
+ if (keyDetector == mKeyDetector && keyboard == mKeyboard) {
+ return;
+ }
mKeyDetector = keyDetector;
mKeyboard = keyDetector.getKeyboard();
- mGestureStrokeWithPreviewPoints.setKeyboardGeometry(mKeyboard.mMostCommonKeyWidth);
+ final int keyWidth = mKeyboard.mMostCommonKeyWidth;
+ final int keyHeight = mKeyboard.mMostCommonKeyHeight;
+ mGestureStrokeWithPreviewPoints.setKeyboardGeometry(keyWidth);
final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY);
if (newKey != mCurrentKey) {
if (mDrawingProxy != null) {
@@ -398,8 +525,8 @@ public class PointerTracker implements PointerTrackerQueue.Element {
}
// Keep {@link #mCurrentKey} that comes from previous keyboard.
}
- final int keyQuarterWidth = mKeyboard.mMostCommonKeyWidth / 4;
- mKeyQuarterWidthSquared = keyQuarterWidth * keyQuarterWidth;
+ mPhantonSuddenMoveThreshold = (int)(keyWidth * PHANTOM_SUDDEN_MOVE_THRESHOLD);
+ mBogusMoveEventDetector.setKeyboardGeometry(keyWidth, keyHeight);
}
@Override
@@ -451,7 +578,12 @@ public class PointerTracker implements PointerTrackerQueue.Element {
}
}
- private void setPressedKeyGraphics(final Key key) {
+ private static boolean needsToSuppressKeyPreviewPopup(final long eventTime) {
+ if (!sShouldHandleGesture) return false;
+ return sTimeRecorder.needsToSuppressKeyPreviewPopup(eventTime);
+ }
+
+ private void setPressedKeyGraphics(final Key key, final long eventTime) {
if (key == null) {
return;
}
@@ -463,7 +595,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
return;
}
- if (!key.noKeyPreview() && !sInGesture) {
+ if (!key.noKeyPreview() && !sInGesture && !needsToSuppressKeyPreviewPopup(eventTime)) {
mDrawingProxy.showKeyPreview(this);
}
updatePressKeyGraphics(key);
@@ -518,10 +650,16 @@ public class PointerTracker implements PointerTrackerQueue.Element {
private Key onDownKey(final int x, final int y, final long eventTime) {
mDownTime = eventTime;
+ mBogusMoveEventDetector.onDownKey();
return onMoveToNewKey(onMoveKeyInternal(x, y), x, y);
}
+ static int getDistance(final int x1, final int y1, final int x2, final int y2) {
+ return (int)Math.hypot(x1 - x2, y1 - y2);
+ }
+
private Key onMoveKeyInternal(final int x, final int y) {
+ mBogusMoveEventDetector.onMoveKey(getDistance(x, y, mLastX, mLastY));
mLastX = x;
mLastY = y;
return mKeyDetector.detectHitKey(x, y);
@@ -539,71 +677,69 @@ public 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() {
+ private void mayStartBatchInput(final Key key) {
if (sInGesture || !mGestureStrokeWithPreviewPoints.isStartOfAGesture()) {
return;
}
+ if (key == null || !Character.isLetter(key.mCode)) {
+ return;
+ }
if (DEBUG_LISTENER) {
- Log.d(TAG, "onStartBatchInput");
+ Log.d(TAG, String.format("[%d] onStartBatchInput", mPointerId));
}
sInGesture = true;
- mListener.onStartBatchInput();
- final boolean isOldestTracker = sPointerTrackerQueue.getOldestElement() == this;
- mDrawingProxy.showGesturePreviewTrail(this, isOldestTracker);
+ synchronized (sAggregratedPointers) {
+ sAggregratedPointers.reset();
+ sLastRecognitionPointSize = 0;
+ sLastRecognitionTime = 0;
+ mListener.onStartBatchInput();
+ }
+ mTimerProxy.cancelLongPressTimer();
+ mDrawingProxy.showGesturePreviewTrail(this, isOldestTrackerInQueue(this));
}
- private void updateBatchInput(final long eventTime) {
- synchronized (sAggregratedPointers) {
- mGestureStrokeWithPreviewPoints.appendIncrementalBatchPoints(sAggregratedPointers);
- final int size = sAggregratedPointers.getPointerSize();
- if (size > sLastRecognitionPointSize
- && GestureStroke.hasRecognitionTimePast(eventTime, sLastRecognitionTime)) {
- sLastRecognitionPointSize = size;
- sLastRecognitionTime = eventTime;
- if (DEBUG_LISTENER) {
- Log.d(TAG, "onUpdateBatchInput: batchPoints=" + size);
+ 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);
}
- mListener.onUpdateBatchInput(sAggregratedPointers);
}
}
- final boolean isOldestTracker = sPointerTrackerQueue.getOldestElement() == this;
- mDrawingProxy.showGesturePreviewTrail(this, isOldestTracker);
+ mDrawingProxy.showGesturePreviewTrail(this, isOldestTrackerInQueue(this));
}
- private void mayEndBatchInput() {
+ private void mayEndBatchInput(final long eventTime) {
synchronized (sAggregratedPointers) {
mGestureStrokeWithPreviewPoints.appendAllBatchPoints(sAggregratedPointers);
- mGestureStrokeWithPreviewPoints.reset();
if (getActivePointerTrackerCount() == 1) {
if (DEBUG_LISTENER) {
- Log.d(TAG, "onEndBatchInput: batchPoints="
- + sAggregratedPointers.getPointerSize());
+ Log.d(TAG, String.format("[%d] onEndBatchInput : batchPoints=%d",
+ mPointerId, sAggregratedPointers.getPointerSize()));
}
sInGesture = false;
+ sTimeRecorder.onEndBatchInput(eventTime);
mListener.onEndBatchInput(sAggregratedPointers);
- clearBatchInputPointsOfAllPointerTrackers();
}
}
- final boolean isOldestTracker = sPointerTrackerQueue.getOldestElement() == this;
- mDrawingProxy.showGesturePreviewTrail(this, isOldestTracker);
- }
-
- private static void abortBatchInput() {
- clearBatchInputPointsOfAllPointerTrackers();
- }
-
- private static void clearBatchInputPointsOfAllPointerTrackers() {
- final int trackersSize = sTrackers.size();
- for (int i = 0; i < trackersSize; ++i) {
- final PointerTracker tracker = sTrackers.get(i);
- tracker.mGestureStrokeWithPreviewPoints.reset();
- }
- sAggregratedPointers.reset();
- sLastRecognitionPointSize = 0;
- sLastRecognitionTime = 0;
+ mDrawingProxy.showGesturePreviewTrail(this, isOldestTrackerInQueue(this));
}
public void processMotionEvent(final int action, final int x, final int y, final long eventTime,
@@ -639,15 +775,14 @@ public class PointerTracker implements PointerTrackerQueue.Element {
// Naive up-to-down noise filter.
final long deltaT = eventTime - mUpTime;
if (deltaT < sParams.mTouchNoiseThresholdTime) {
- final int dx = x - mLastX;
- final int dy = y - mLastY;
- final int distanceSquared = (dx * dx + dy * dy);
- if (distanceSquared < sParams.mTouchNoiseThresholdDistanceSquared) {
+ final int distance = getDistance(x, y, mLastX, mLastY);
+ if (distance < sParams.mTouchNoiseThresholdDistance) {
if (DEBUG_MODE)
- Log.w(TAG, "onDownEvent: ignore potential noise: time=" + deltaT
- + " distance=" + distanceSquared);
+ Log.w(TAG, String.format("[%d] onDownEvent:"
+ + " ignore potential noise: time=%d distance=%d",
+ mPointerId, deltaT, distance));
if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.pointerTracker_onDownEvent(deltaT, distanceSquared);
+ ResearchLogger.pointerTracker_onDownEvent(deltaT, distance * distance);
}
mKeyAlreadyProcessed = true;
return;
@@ -655,42 +790,29 @@ public class PointerTracker implements PointerTrackerQueue.Element {
}
final Key key = getKeyOn(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);
+ mBogusMoveEventDetector.onActualDownEvent(x, y);
+ 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;
}
- final int activePointerTrackerCount = getActivePointerTrackerCount();
- if (activePointerTrackerCount == 1) {
- mIsDetectingGesture = false;
- // A gesture should start only from the letter key.
- final boolean isAlphabetKeyboard = (mKeyboard != null)
- && mKeyboard.mId.isAlphabetKeyboard();
- if (isAlphabetKeyboard && !mIsShowingMoreKeysPanel && key != null
- && Keyboard.isLetterCode(key.mCode)) {
+ // A gesture should start only from a non-modifier key.
+ mIsDetectingGesture = (mKeyboard != null) && mKeyboard.mId.isAlphabetKeyboard()
+ && !mIsShowingMoreKeysPanel && key != null && !key.isModifier();
+ if (mIsDetectingGesture) {
+ if (getActivePointerTrackerCount() == 1) {
sGestureFirstDownTime = eventTime;
- onGestureDownEvent(x, y, eventTime);
}
- } else if (sInGesture && activePointerTrackerCount > 1) {
- onGestureDownEvent(x, y, eventTime);
+ mGestureStrokeWithPreviewPoints.onDownEvent(x, y, eventTime, sGestureFirstDownTime,
+ sTimeRecorder.getLastLetterTypingTime());
}
}
- private void onGestureDownEvent(final int x, final int y, final long eventTime) {
- mIsDetectingGesture = true;
- final int elapsedTimeFromFirstDown = (int)(eventTime - sGestureFirstDownTime);
- mGestureStrokeWithPreviewPoints.addPoint(x, y, elapsedTimeFromFirstDown,
- false /* isHistorical */);
- }
-
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
@@ -700,8 +822,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
|| mKeyDetector.alwaysAllowsSlidingInput();
mKeyboardLayoutHasBeenChanged = false;
mKeyAlreadyProcessed = false;
- mIsInSlidingKeyInput = false;
- mIgnoreModifierKey = false;
+ resetSlidingKeyInput();
if (key != null) {
// 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
@@ -712,25 +833,30 @@ public class PointerTracker implements PointerTrackerQueue.Element {
startRepeatKey(key);
startLongPressTimer(key);
- setPressedKeyGraphics(key);
+ setPressedKeyGraphics(key, eventTime);
}
}
private void startSlidingKeyInput(final Key key) {
if (!mIsInSlidingKeyInput) {
- mIgnoreModifierKey = key.isModifier();
+ mIsInSlidingKeyInputFromModifier = key.isModifier();
}
mIsInSlidingKeyInput = true;
}
+ private void resetSlidingKeyInput() {
+ mIsInSlidingKeyInput = false;
+ mIsInSlidingKeyInputFromModifier = false;
+ }
+
private void onGestureMoveEvent(final int x, final int y, final long eventTime,
- final boolean isHistorical, final Key key) {
+ final boolean isMajorEvent, final Key key) {
final int gestureTime = (int)(eventTime - sGestureFirstDownTime);
if (mIsDetectingGesture) {
- mGestureStrokeWithPreviewPoints.addPoint(x, y, gestureTime, isHistorical);
- mayStartBatchInput();
- if (sInGesture && key != null) {
- updateBatchInput(eventTime);
+ mGestureStrokeWithPreviewPoints.addPoint(x, y, gestureTime, isMajorEvent);
+ mayStartBatchInput(key);
+ if (sInGesture) {
+ mayUpdateBatchInput(eventTime, key);
}
}
}
@@ -752,118 +878,157 @@ public class PointerTracker implements PointerTrackerQueue.Element {
final int historicalY = (int)me.getHistoricalY(pointerIndex, h);
final long historicalTime = me.getHistoricalEventTime(h);
onGestureMoveEvent(historicalX, historicalY, historicalTime,
- true /* isHistorical */, null);
+ false /* isMajorEvent */, null);
}
}
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(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(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);
+ mKeyAlreadyProcessed = true;
+ setReleasedKeyGraphics(oldKey);
+ } else {
+ if (!mIsDetectingGesture) {
+ mKeyAlreadyProcessed = true;
+ }
+ 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) {
+ mKeyAlreadyProcessed = true;
+ }
+ }
+ }
+
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, false /* isHistorical */, key);
+ onGestureMoveEvent(x, y, eventTime, true /* isMajorEvent */, newKey);
if (sInGesture) {
- mIgnoreModifierKey = true;
- mTimerProxy.cancelLongPressTimer();
- mIsInSlidingKeyInput = true;
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);
- } else if (isMajorEnoughMoveToBeOnNewKey(x, y, 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);
- } else {
- // HACK: On some devices, quick successive touches may be translated to sudden
- // move by touch panel firmware. This hack detects the case and translates the
- // move event to successive up and down events.
- final int dx = x - lastX;
- final int dy = y - lastY;
- final int lastMoveSquared = dx * dx + dy * dy;
- // TODO: Should find a way to balance gesture detection and this hack.
- if (sNeedsPhantomSuddenMoveEventHack
- && lastMoveSquared >= mKeyQuarterWidthSquared
- && !mIsDetectingGesture) {
- if (DEBUG_MODE) {
- Log.w(TAG, String.format("onMoveEvent:"
- + " phantom sudden move event is translated to "
- + "up[%d,%d]/down[%d,%d] events", lastX, lastY, x, y));
- }
- // TODO: This should be moved to outside of this nested if-clause?
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.pointerTracker_onMoveEvent(x, y, lastX, lastY);
- }
- onUpEventInternal();
- 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)) {
- onUpEventInternal();
- }
- if (!mIsDetectingGesture) {
- mKeyAlreadyProcessed = true;
- }
- setReleasedKeyGraphics(oldKey);
- }
- }
+ processSlidingKeyInput(newKey, x, y, eventTime);
}
- } else {
- if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, 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);
}
}
}
@@ -873,22 +1038,17 @@ public 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();
- if (queue != null) {
- queue.remove(this);
- }
+ onUpEventInternal(eventTime);
+ sPointerTrackerQueue.remove(this);
}
// Let this pointer tracker know that one of newer-than-this pointer trackers got an up event.
@@ -899,13 +1059,13 @@ public class PointerTracker implements PointerTrackerQueue.Element {
if (DEBUG_EVENT) {
printTouchEvent("onPhntEvent:", getLastX(), getLastY(), eventTime);
}
- onUpEventInternal();
+ onUpEventInternal(eventTime);
mKeyAlreadyProcessed = true;
}
- private void onUpEventInternal() {
+ private void onUpEventInternal(final long eventTime) {
mTimerProxy.cancelKeyTimers();
- mIsInSlidingKeyInput = false;
+ resetSlidingKeyInput();
mIsDetectingGesture = false;
final Key currentKey = mCurrentKey;
mCurrentKey = null;
@@ -920,22 +1080,19 @@ public class PointerTracker implements PointerTrackerQueue.Element {
if (currentKey != null) {
callListenerOnRelease(currentKey, currentKey.mCode, true);
}
- mayEndBatchInput();
+ mayEndBatchInput(eventTime);
return;
}
- // This event will be recognized as a regular code input. Clear unused possible batch points
- // so they are not mistakenly displayed as preview.
- clearBatchInputPointsOfAllPointerTrackers();
+
if (mKeyAlreadyProcessed) {
return;
}
if (currentKey != null && !currentKey.isRepeatable()) {
- detectAndSendKey(currentKey, mKeyX, mKeyY);
+ detectAndSendKey(currentKey, mKeyX, mKeyY, eventTime);
}
}
public void onShowMoreKeysPanel(final int x, final int y, final KeyEventHandler handler) {
- abortBatchInput();
onLongPressed();
mIsShowingMoreKeysPanel = true;
onDownEvent(x, y, SystemClock.uptimeMillis(), handler);
@@ -944,10 +1101,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
public void onLongPressed() {
mKeyAlreadyProcessed = true;
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) {
@@ -955,18 +1109,15 @@ public class PointerTracker implements PointerTrackerQueue.Element {
printTouchEvent("onCancelEvt:", x, y, eventTime);
}
- final PointerTrackerQueue queue = sPointerTrackerQueue;
- if (queue != null) {
- queue.releaseAllPointersExcept(this, eventTime);
- queue.remove(this);
- }
+ sPointerTrackerQueue.releaseAllPointersExcept(this, eventTime);
+ sPointerTrackerQueue.remove(this);
onCancelEventInternal();
}
private void onCancelEventInternal() {
mTimerProxy.cancelKeyTimers();
setReleasedKeyGraphics(mCurrentKey);
- mIsInSlidingKeyInput = false;
+ resetSlidingKeyInput();
if (mIsShowingMoreKeysPanel) {
mDrawingProxy.dismissMoreKeysPanel();
mIsShowingMoreKeysPanel = false;
@@ -982,24 +1133,51 @@ public class PointerTracker implements PointerTrackerQueue.Element {
public void onRegisterKey(final Key key) {
if (key != null) {
- detectAndSendKey(key, key.mX, key.mY);
+ detectAndSendKey(key, key.mX, key.mY, SystemClock.uptimeMillis());
mTimerProxy.startTypingStateTimer(key);
}
}
- private boolean isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final Key newKey) {
+ private boolean isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final long eventTime,
+ final Key newKey) {
if (mKeyDetector == null) {
throw new NullPointerException("keyboard and/or key detector not set");
}
final Key curKey = mCurrentKey;
if (newKey == curKey) {
return false;
- } else if (curKey != null) {
- return curKey.squaredDistanceToEdge(x, y)
- >= mKeyDetector.getKeyHysteresisDistanceSquared();
- } else {
+ }
+ 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));
+ }
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 true;
+ }
+ return false;
}
private void startLongPressTimer(final Key key) {
@@ -1008,14 +1186,14 @@ public class PointerTracker implements PointerTrackerQueue.Element {
}
}
- private void detectAndSendKey(final Key key, final int x, final int y) {
+ private void detectAndSendKey(final Key key, final int x, final int y, final long eventTime) {
if (key == null) {
callListenerOnCancelInput();
return;
}
final int code = key.mCode;
- callListenerOnCodeInput(key, code, x, y);
+ callListenerOnCodeInput(key, code, x, y, eventTime);
callListenerOnRelease(key, code, false);
}
@@ -1023,7 +1201,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
final long eventTime) {
final Key key = mKeyDetector.detectHitKey(x, y);
final String code = KeyDetector.printableCode(key);
- Log.d(TAG, String.format("%s%s[%d] %4d %4d %5d %s", title,
- (mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, eventTime, code));
+ Log.d(TAG, String.format("[%d]%s%s %4d %4d %5d %s", mPointerId,
+ (mKeyAlreadyProcessed ? "-" : " "), 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 e1b082c16..ed01f3458 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;
@@ -25,15 +26,18 @@ import com.android.inputmethod.latin.JniUtils;
import java.util.Arrays;
-public class ProximityInfo {
+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;
- private final int mKeyHeight;
private final int mGridWidth;
private final int mGridHeight;
private final int mGridSize;
@@ -43,8 +47,8 @@ public class ProximityInfo {
private final int mKeyboardMinWidth;
private final int mKeyboardHeight;
private final int mMostCommonKeyWidth;
+ private final int mMostCommonKeyHeight;
private final Key[] mKeys;
- private final TouchPositionCorrection mTouchPositionCorrection;
private final Key[][] mGridNeighbors;
private final String mLocaleStr;
@@ -64,17 +68,16 @@ public class ProximityInfo {
mCellHeight = (height + mGridHeight - 1) / mGridHeight;
mKeyboardMinWidth = minWidth;
mKeyboardHeight = height;
- mKeyHeight = mostCommonKeyHeight;
+ mMostCommonKeyHeight = mostCommonKeyHeight;
mMostCommonKeyWidth = mostCommonKeyWidth;
mKeys = keys;
- mTouchPositionCorrection = touchPositionCorrection;
mGridNeighbors = new Key[mGridSize][];
if (minWidth == 0 || height == 0) {
// No proximity required. Keyboard might be more keys keyboard.
return;
}
computeNearestNeighbors();
- mNativeProximityInfo = createNativeProximityInfo();
+ mNativeProximityInfo = createNativeProximityInfo(touchPositionCorrection);
}
public static ProximityInfo createDummyProximityInfo() {
@@ -106,12 +109,12 @@ public class ProximityInfo {
private native void releaseProximityInfoNative(long nativeProximityInfo);
- private final long createNativeProximityInfo() {
+ private final long createNativeProximityInfo(
+ final TouchPositionCorrection touchPositionCorrection) {
final Key[][] gridNeighborKeys = mGridNeighbors;
final int keyboardWidth = mKeyboardMinWidth;
final int keyboardHeight = mKeyboardHeight;
final Key[] keys = mKeys;
- final TouchPositionCorrection touchPositionCorrection = mTouchPositionCorrection;
final int[] proximityCharsArray = new int[mGridSize * MAX_PROXIMITY_CHARS_SIZE];
Arrays.fill(proximityCharsArray, Constants.NOT_A_CODE);
for (int i = 0; i < mGridSize; ++i) {
@@ -141,29 +144,43 @@ public class ProximityInfo {
}
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];
final Rect hitBox = key.mHitBox;
- final int row = hitBox.top / mKeyHeight;
- if (row < touchPositionCorrection.mRadii.length) {
+ sweetSpotCenterXs[i] = hitBox.exactCenterX();
+ sweetSpotCenterYs[i] = hitBox.exactCenterY();
+ sweetSpotRadii[i] = defaultRadius;
+ final int row = hitBox.top / mMostCommonKeyHeight;
+ if (row < rows) {
final int hitBoxWidth = hitBox.width();
final int hitBoxHeight = hitBox.height();
- final float x = touchPositionCorrection.mXs[row];
- final float y = touchPositionCorrection.mYs[row];
- final float radius = touchPositionCorrection.mRadii[row];
- sweetSpotCenterXs[i] = hitBox.exactCenterX() + x * hitBoxWidth;
- sweetSpotCenterYs[i] = hitBox.exactCenterY() + y * hitBoxHeight;
- // Note that, in recent versions of Android, FloatMath is actually slower than
- // java.lang.Math due to the way the JIT optimizes java.lang.Math.
- sweetSpotRadii[i] = radius * (float)Math.sqrt(
- hitBoxWidth * hitBoxWidth + hitBoxHeight * hitBoxHeight);
+ 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;
+ }
+ if (DEBUG) {
+ Log.d(TAG, String.format(
+ " [%2d] row=%d x/y/r=%7.2f/%7.2f/%5.2f %s code=%s", i, row,
+ sweetSpotCenterXs[i], sweetSpotCenterYs[i], sweetSpotRadii[i],
+ (row < rows ? "correct" : "default"),
+ Constants.printableCode(key.mCode)));
}
}
} else {
sweetSpotCenterXs = sweetSpotCenterYs = sweetSpotRadii = null;
+ if (DEBUG) {
+ Log.d(TAG, "touchPositionCorrection: OFF");
+ }
}
return setProximityInfoNative(mLocaleStr, MAX_PROXIMITY_CHARS_SIZE,
@@ -215,13 +232,14 @@ public class ProximityInfo {
}
}
- public void fillArrayWithNearestKeyCodes(int x, int y, int primaryKeyCode, int[] dest) {
+ public void fillArrayWithNearestKeyCodes(final int x, final int y, final int primaryKeyCode,
+ final int[] dest) {
final int destLength = dest.length;
if (destLength < 1) {
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 +248,7 @@ public class ProximityInfo {
break;
}
final int code = key.mCode;
- if (code <= Keyboard.CODE_SPACE) {
+ if (code <= Constants.CODE_SPACE) {
break;
}
dest[index++] = code;
@@ -240,7 +258,7 @@ public class ProximityInfo {
}
}
- public Key[] getNearestKeys(int x, int y) {
+ public Key[] getNearestKeys(final int x, final int y) {
if (mGridNeighbors == null) {
return EMPTY_KEY_ARRAY;
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/AlphabetShiftState.java b/java/src/com/android/inputmethod/keyboard/internal/AlphabetShiftState.java
index 5712df1fc..44aa72a0a 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/AlphabetShiftState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/AlphabetShiftState.java
@@ -18,7 +18,7 @@ package com.android.inputmethod.keyboard.internal;
import android.util.Log;
-public class AlphabetShiftState {
+public final class AlphabetShiftState {
private static final String TAG = AlphabetShiftState.class.getSimpleName();
private static final boolean DEBUG = false;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
index f0be0ee94..f8244dd5b 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
@@ -14,111 +14,310 @@
package com.android.inputmethod.keyboard.internal;
+import android.content.res.TypedArray;
+import android.util.Log;
+
import com.android.inputmethod.latin.InputPointers;
+import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.ResizableIntArray;
+import com.android.inputmethod.latin.ResourceUtils;
public class GestureStroke {
+ private static final String TAG = GestureStroke.class.getSimpleName();
+ private static final boolean DEBUG = false;
+ private static final boolean DEBUG_SPEED = false;
+
public static final int DEFAULT_CAPACITY = 128;
private final int mPointerId;
private final ResizableIntArray mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY);
private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
- private float mLength;
+
+ private final GestureStrokeParams mParams;
+
+ private int mKeyWidth; // pixel
+ // Static threshold for starting gesture detection
+ private int mDetectFastMoveSpeedThreshold; // pixel /sec
+ private int mDetectFastMoveTime;
+ private int mDetectFastMoveX;
+ private int mDetectFastMoveY;
+ // Dynamic threshold for gesture after fast typing
+ private boolean mAfterFastTyping;
+ private int mGestureDynamicDistanceThresholdFrom; // pixel
+ private int mGestureDynamicDistanceThresholdTo; // pixel
+ // Variables for gesture sampling
+ private int mGestureSamplingMinimumDistance; // pixel
+ private long mLastMajorEventTime;
+ private int mLastMajorEventX;
+ private int mLastMajorEventY;
+ // Variables for gesture recognition
+ private int mGestureRecognitionSpeedThreshold; // pixel / sec
private int mIncrementalRecognitionSize;
private int mLastIncrementalBatchSize;
- private long mLastPointTime;
- private int mLastPointX;
- private int mLastPointY;
-
- private int mMinGestureLength; // pixel
- private int mMinGestureSampleLength; // pixel
- private int mGestureRecognitionThreshold; // pixel / sec
-
- // TODO: Move some of these to resource.
- private static final float MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH = 0.75f;
- private static final int MIN_GESTURE_START_DURATION = 100; // msec
- private static final int MIN_GESTURE_RECOGNITION_TIME = 100; // msec
- private static final float MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH = 1.0f / 6.0f;
- private static final float GESTURE_RECOGNITION_SPEED_THRESHOLD_RATIO_TO_KEY_WIDTH =
- 5.5f; // keyWidth / sec
- private static final int MSEC_PER_SEC = 1000;
- public static final boolean hasRecognitionTimePast(
- final long currentTime, final long lastRecognitionTime) {
- return currentTime > lastRecognitionTime + MIN_GESTURE_RECOGNITION_TIME;
+ public static final class GestureStrokeParams {
+ // Static threshold for gesture after fast typing
+ public final int mStaticTimeThresholdAfterFastTyping; // msec
+ // Static threshold for starting gesture detection
+ public final float mDetectFastMoveSpeedThreshold; // keyWidth/sec
+ // Dynamic threshold for gesture after fast typing
+ public final int mDynamicThresholdDecayDuration; // msec
+ // Time based threshold values
+ public final int mDynamicTimeThresholdFrom; // msec
+ public final int mDynamicTimeThresholdTo; // msec
+ // Distance based threshold values
+ public final float mDynamicDistanceThresholdFrom; // keyWidth
+ public final float mDynamicDistanceThresholdTo; // keyWidth
+ // Parameters for gesture sampling
+ public final float mSamplingMinimumDistance; // keyWidth
+ // Parameters for gesture recognition
+ public final int mRecognitionMinimumTime; // msec
+ public final float mRecognitionSpeedThreshold; // keyWidth/sec
+
+ // Default GestureStroke parameters for test.
+ public static final GestureStrokeParams FOR_TEST = new GestureStrokeParams();
+ public static final GestureStrokeParams DEFAULT = FOR_TEST;
+
+ private GestureStrokeParams() {
+ // These parameter values are default and intended for testing.
+ mStaticTimeThresholdAfterFastTyping = 350; // msec
+ mDetectFastMoveSpeedThreshold = 1.5f; // keyWidth / sec
+ mDynamicThresholdDecayDuration = 450; // msec
+ mDynamicTimeThresholdFrom = 300; // msec
+ mDynamicTimeThresholdTo = 20; // msec
+ mDynamicDistanceThresholdFrom = 6.0f; // keyWidth
+ mDynamicDistanceThresholdTo = 0.35f; // keyWidth
+ // The following parameters' change will affect the result of regression test.
+ mSamplingMinimumDistance = 1.0f / 6.0f; // keyWidth
+ mRecognitionMinimumTime = 100; // msec
+ mRecognitionSpeedThreshold = 5.5f; // keyWidth / sec
+ }
+
+ public GestureStrokeParams(final TypedArray mainKeyboardViewAttr) {
+ mStaticTimeThresholdAfterFastTyping = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_gestureStaticTimeThresholdAfterFastTyping,
+ DEFAULT.mStaticTimeThresholdAfterFastTyping);
+ mDetectFastMoveSpeedThreshold = ResourceUtils.getFraction(mainKeyboardViewAttr,
+ R.styleable.MainKeyboardView_gestureDetectFastMoveSpeedThreshold,
+ DEFAULT.mDetectFastMoveSpeedThreshold);
+ mDynamicThresholdDecayDuration = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_gestureDynamicThresholdDecayDuration,
+ DEFAULT.mDynamicThresholdDecayDuration);
+ mDynamicTimeThresholdFrom = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_gestureDynamicTimeThresholdFrom,
+ DEFAULT.mDynamicTimeThresholdFrom);
+ mDynamicTimeThresholdTo = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_gestureDynamicTimeThresholdTo,
+ DEFAULT.mDynamicTimeThresholdTo);
+ mDynamicDistanceThresholdFrom = ResourceUtils.getFraction(mainKeyboardViewAttr,
+ R.styleable.MainKeyboardView_gestureDynamicDistanceThresholdFrom,
+ DEFAULT.mDynamicDistanceThresholdFrom);
+ mDynamicDistanceThresholdTo = ResourceUtils.getFraction(mainKeyboardViewAttr,
+ R.styleable.MainKeyboardView_gestureDynamicDistanceThresholdTo,
+ DEFAULT.mDynamicDistanceThresholdTo);
+ mSamplingMinimumDistance = ResourceUtils.getFraction(mainKeyboardViewAttr,
+ R.styleable.MainKeyboardView_gestureSamplingMinimumDistance,
+ DEFAULT.mSamplingMinimumDistance);
+ mRecognitionMinimumTime = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_gestureRecognitionMinimumTime,
+ DEFAULT.mRecognitionMinimumTime);
+ mRecognitionSpeedThreshold = ResourceUtils.getFraction(mainKeyboardViewAttr,
+ R.styleable.MainKeyboardView_gestureRecognitionSpeedThreshold,
+ DEFAULT.mRecognitionSpeedThreshold);
+ }
}
- public GestureStroke(final int pointerId) {
+ private static final int MSEC_PER_SEC = 1000;
+
+ public GestureStroke(final int pointerId, final GestureStrokeParams params) {
mPointerId = pointerId;
+ mParams = params;
}
public void setKeyboardGeometry(final int keyWidth) {
+ mKeyWidth = keyWidth;
// TODO: Find an appropriate base metric for these length. Maybe diagonal length of the key?
- mMinGestureLength = (int)(keyWidth * MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH);
- mMinGestureSampleLength = (int)(keyWidth * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH);
- mGestureRecognitionThreshold =
- (int)(keyWidth * GESTURE_RECOGNITION_SPEED_THRESHOLD_RATIO_TO_KEY_WIDTH);
+ mDetectFastMoveSpeedThreshold = (int)(keyWidth * mParams.mDetectFastMoveSpeedThreshold);
+ mGestureDynamicDistanceThresholdFrom =
+ (int)(keyWidth * mParams.mDynamicDistanceThresholdFrom);
+ mGestureDynamicDistanceThresholdTo = (int)(keyWidth * mParams.mDynamicDistanceThresholdTo);
+ mGestureSamplingMinimumDistance = (int)(keyWidth * mParams.mSamplingMinimumDistance);
+ mGestureRecognitionSpeedThreshold = (int)(keyWidth * mParams.mRecognitionSpeedThreshold);
+ if (DEBUG) {
+ Log.d(TAG, String.format(
+ "[%d] setKeyboardGeometry: keyWidth=%3d tT=%3d >> %3d tD=%3d >> %3d",
+ mPointerId, keyWidth,
+ mParams.mDynamicTimeThresholdFrom,
+ mParams.mDynamicTimeThresholdTo,
+ mGestureDynamicDistanceThresholdFrom,
+ mGestureDynamicDistanceThresholdTo));
+ }
+ }
+
+ public void onDownEvent(final int x, final int y, final long downTime,
+ final long gestureFirstDownTime, final long lastTypingTime) {
+ reset();
+ final long elapsedTimeAfterTyping = downTime - lastTypingTime;
+ if (elapsedTimeAfterTyping < mParams.mStaticTimeThresholdAfterFastTyping) {
+ mAfterFastTyping = true;
+ }
+ if (DEBUG) {
+ Log.d(TAG, String.format("[%d] onDownEvent: dT=%3d%s", mPointerId,
+ elapsedTimeAfterTyping, mAfterFastTyping ? " afterFastTyping" : ""));
+ }
+ final int elapsedTimeFromFirstDown = (int)(downTime - gestureFirstDownTime);
+ addPoint(x, y, elapsedTimeFromFirstDown, true /* isMajorEvent */);
+ }
+
+ private int getGestureDynamicDistanceThreshold(final int deltaTime) {
+ if (!mAfterFastTyping || deltaTime >= mParams.mDynamicThresholdDecayDuration) {
+ return mGestureDynamicDistanceThresholdTo;
+ }
+ final int decayedThreshold =
+ (mGestureDynamicDistanceThresholdFrom - mGestureDynamicDistanceThresholdTo)
+ * deltaTime / mParams.mDynamicThresholdDecayDuration;
+ return mGestureDynamicDistanceThresholdFrom - decayedThreshold;
+ }
+
+ private int getGestureDynamicTimeThreshold(final int deltaTime) {
+ if (!mAfterFastTyping || deltaTime >= mParams.mDynamicThresholdDecayDuration) {
+ return mParams.mDynamicTimeThresholdTo;
+ }
+ final int decayedThreshold =
+ (mParams.mDynamicTimeThresholdFrom - mParams.mDynamicTimeThresholdTo)
+ * deltaTime / mParams.mDynamicThresholdDecayDuration;
+ return mParams.mDynamicTimeThresholdFrom - decayedThreshold;
}
- public boolean isStartOfAGesture() {
+ public final boolean isStartOfAGesture() {
+ if (!hasDetectedFastMove()) {
+ return false;
+ }
final int size = mEventTimes.getLength();
- final int downDuration = (size > 0) ? mEventTimes.get(size - 1) : 0;
- return downDuration > MIN_GESTURE_START_DURATION && mLength > mMinGestureLength;
+ if (size <= 0) {
+ return false;
+ }
+ final int lastIndex = size - 1;
+ final int deltaTime = mEventTimes.get(lastIndex) - mDetectFastMoveTime;
+ if (deltaTime < 0) {
+ return false;
+ }
+ final int deltaDistance = getDistance(
+ mXCoordinates.get(lastIndex), mYCoordinates.get(lastIndex),
+ mDetectFastMoveX, mDetectFastMoveY);
+ final int distanceThreshold = getGestureDynamicDistanceThreshold(deltaTime);
+ final int timeThreshold = getGestureDynamicTimeThreshold(deltaTime);
+ final boolean isStartOfAGesture = deltaTime >= timeThreshold
+ && deltaDistance >= distanceThreshold;
+ if (DEBUG) {
+ Log.d(TAG, String.format("[%d] isStartOfAGesture: dT=%3d tT=%3d dD=%3d tD=%3d%s%s",
+ mPointerId, deltaTime, timeThreshold,
+ deltaDistance, distanceThreshold,
+ mAfterFastTyping ? " afterFastTyping" : "",
+ isStartOfAGesture ? " startOfAGesture" : ""));
+ }
+ return isStartOfAGesture;
}
- public void reset() {
- mLength = 0;
+ protected void reset() {
mIncrementalRecognitionSize = 0;
mLastIncrementalBatchSize = 0;
- mLastPointTime = 0;
mEventTimes.setLength(0);
mXCoordinates.setLength(0);
mYCoordinates.setLength(0);
+ mLastMajorEventTime = 0;
+ mDetectFastMoveTime = 0;
+ mAfterFastTyping = false;
+ }
+
+ private void appendPoint(final int x, final int y, final int time) {
+ mEventTimes.add(time);
+ mXCoordinates.add(x);
+ mYCoordinates.add(y);
+ }
+
+ private void updateMajorEvent(final int x, final int y, final int time) {
+ mLastMajorEventTime = time;
+ mLastMajorEventX = x;
+ mLastMajorEventY = y;
}
- public void addPoint(final int x, final int y, final int time, final boolean isHistorical) {
- final boolean needsSampling;
+ private final boolean hasDetectedFastMove() {
+ return mDetectFastMoveTime > 0;
+ }
+
+ private int detectFastMove(final int x, final int y, final int time) {
final int size = mEventTimes.getLength();
- if (size == 0) {
- needsSampling = true;
+ final int lastIndex = size - 1;
+ final int lastX = mXCoordinates.get(lastIndex);
+ final int lastY = mYCoordinates.get(lastIndex);
+ final int dist = getDistance(lastX, lastY, x, y);
+ final int msecs = time - mEventTimes.get(lastIndex);
+ if (msecs > 0) {
+ final int pixels = getDistance(lastX, lastY, x, y);
+ final int pixelsPerSec = pixels * MSEC_PER_SEC;
+ if (DEBUG_SPEED) {
+ final float speed = (float)pixelsPerSec / msecs / mKeyWidth;
+ Log.d(TAG, String.format("[%d] detectFastMove: speed=%5.2f", mPointerId, speed));
+ }
+ // Equivalent to (pixels / msecs < mStartSpeedThreshold / MSEC_PER_SEC)
+ if (!hasDetectedFastMove() && pixelsPerSec > mDetectFastMoveSpeedThreshold * msecs) {
+ if (DEBUG) {
+ final float speed = (float)pixelsPerSec / msecs / mKeyWidth;
+ Log.d(TAG, String.format(
+ "[%d] detectFastMove: speed=%5.2f T=%3d points=%3d fastMove",
+ mPointerId, speed, time, size));
+ }
+ mDetectFastMoveTime = time;
+ mDetectFastMoveX = x;
+ mDetectFastMoveY = y;
+ }
+ }
+ return dist;
+ }
+
+ public void addPoint(final int x, final int y, final int time, final boolean isMajorEvent) {
+ final int size = mEventTimes.getLength();
+ if (size <= 0) {
+ // Down event
+ appendPoint(x, y, time);
+ updateMajorEvent(x, y, time);
} else {
- final int lastIndex = size - 1;
- final int lastX = mXCoordinates.get(lastIndex);
- final int lastY = mYCoordinates.get(lastIndex);
- final float dist = getDistance(lastX, lastY, x, y);
- needsSampling = dist > mMinGestureSampleLength;
- mLength += dist;
- }
- if (needsSampling) {
- mEventTimes.add(time);
- mXCoordinates.add(x);
- mYCoordinates.add(y);
- }
- if (!isHistorical) {
+ final int distance = detectFastMove(x, y, time);
+ if (distance > mGestureSamplingMinimumDistance) {
+ appendPoint(x, y, time);
+ }
+ }
+ if (isMajorEvent) {
updateIncrementalRecognitionSize(x, y, time);
+ updateMajorEvent(x, y, time);
}
}
private void updateIncrementalRecognitionSize(final int x, final int y, final int time) {
- final int msecs = (int)(time - mLastPointTime);
- if (msecs > 0) {
- final int pixels = (int)getDistance(mLastPointX, mLastPointY, x, y);
- // Equivalent to (pixels / msecs < mGestureRecognitionThreshold / MSEC_PER_SEC)
- if (pixels * MSEC_PER_SEC < mGestureRecognitionThreshold * msecs) {
- mIncrementalRecognitionSize = mEventTimes.getLength();
- }
+ final int msecs = (int)(time - mLastMajorEventTime);
+ if (msecs <= 0) {
+ return;
+ }
+ final int pixels = getDistance(mLastMajorEventX, mLastMajorEventY, x, y);
+ final int pixelsPerSec = pixels * MSEC_PER_SEC;
+ // Equivalent to (pixels / msecs < mGestureRecognitionThreshold / MSEC_PER_SEC)
+ if (pixelsPerSec < mGestureRecognitionSpeedThreshold * msecs) {
+ mIncrementalRecognitionSize = mEventTimes.getLength();
}
- mLastPointTime = time;
- mLastPointX = x;
- mLastPointY = y;
}
- public void appendAllBatchPoints(final InputPointers out) {
+ public final boolean hasRecognitionTimePast(
+ final long currentTime, final long lastRecognitionTime) {
+ return currentTime > lastRecognitionTime + mParams.mRecognitionMinimumTime;
+ }
+
+ public final void appendAllBatchPoints(final InputPointers out) {
appendBatchPoints(out, mEventTimes.getLength());
}
- public void appendIncrementalBatchPoints(final InputPointers out) {
+ public final void appendIncrementalBatchPoints(final InputPointers out) {
appendBatchPoints(out, mIncrementalRecognitionSize);
}
@@ -132,11 +331,11 @@ public class GestureStroke {
mLastIncrementalBatchSize = size;
}
- private static float getDistance(final int x1, final int y1, final int x2, final int y2) {
- final float dx = x1 - x2;
- final float dy = y1 - y2;
+ private static int getDistance(final int x1, final int y1, final int x2, final int y2) {
+ final int dx = x1 - x2;
+ final int dy = y1 - y2;
// Note that, in recent versions of Android, FloatMath is actually slower than
// java.lang.Math due to the way the JIT optimizes java.lang.Math.
- return (float)Math.sqrt(dx * dx + dy * dy);
+ return (int)Math.sqrt(dx * dx + dy * dy);
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
index ce3914076..8192c9076 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
@@ -16,7 +16,7 @@ package com.android.inputmethod.keyboard.internal;
import com.android.inputmethod.latin.ResizableIntArray;
-public class GestureStrokeWithPreviewPoints extends GestureStroke {
+public final class GestureStrokeWithPreviewPoints extends GestureStroke {
public static final int PREVIEW_CAPACITY = 256;
private final ResizableIntArray mPreviewEventTimes = new ResizableIntArray(PREVIEW_CAPACITY);
@@ -33,12 +33,12 @@ public class GestureStrokeWithPreviewPoints extends GestureStroke {
// TODO: Move this to resource.
private static final float MIN_PREVIEW_SAMPLE_LENGTH_RATIO_TO_KEY_WIDTH = 0.1f;
- public GestureStrokeWithPreviewPoints(final int pointerId) {
- super(pointerId);
+ public GestureStrokeWithPreviewPoints(final int pointerId, final GestureStrokeParams params) {
+ super(pointerId, params);
}
@Override
- public void reset() {
+ protected void reset() {
super.reset();
mStrokeId++;
mLastPreviewSize = 0;
@@ -65,21 +65,18 @@ public class GestureStrokeWithPreviewPoints extends GestureStroke {
private boolean needsSampling(final int x, final int y) {
final int dx = x - mLastX;
final int dy = y - mLastY;
- final boolean needsSampling = (dx * dx + dy * dy >= mMinPreviewSampleLengthSquare);
- if (needsSampling) {
- mLastX = x;
- mLastY = y;
- }
- return needsSampling;
+ return dx * dx + dy * dy >= mMinPreviewSampleLengthSquare;
}
@Override
- public void addPoint(final int x, final int y, final int time, final boolean isHistorical) {
- super.addPoint(x, y, time, isHistorical);
- if (mPreviewEventTimes.getLength() == 0 || isHistorical || needsSampling(x, y)) {
+ public void addPoint(final int x, final int y, final int time, final boolean isMajorEvent) {
+ super.addPoint(x, y, time, isMajorEvent);
+ if (isMajorEvent || needsSampling(x, y)) {
mPreviewEventTimes.add(time);
mPreviewXCoordinates.add(x);
mPreviewYCoordinates.add(y);
+ mLastX = x;
+ mLastY = y;
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
index 2a57caa5f..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;
@@ -46,7 +47,7 @@ import java.util.Locale;
* Note that the '\' is also parsed by XML parser and CSV parser as well.
* See {@link KeyboardIconsSet} about icon_name.
*/
-public class KeySpecParser {
+public final class KeySpecParser {
private static final boolean DEBUG = LatinImeLogger.sDBG;
private static final int MAX_STRING_REFERENCE_INDIRECTION = 10;
@@ -172,7 +173,7 @@ public 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 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,
@@ -318,7 +319,7 @@ public class KeySpecParser {
}
@SuppressWarnings("serial")
- public static class KeySpecParserError extends RuntimeException {
+ public static final class KeySpecParserError extends RuntimeException {
public KeySpecParserError(final String message) {
super(message);
}
@@ -468,7 +469,7 @@ public 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/KeyStylesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
index 71fd30563..563d22414 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
@@ -29,7 +29,7 @@ import org.xmlpull.v1.XmlPullParserException;
import java.util.HashMap;
-public class KeyStylesSet {
+public final class KeyStylesSet {
private static final String TAG = KeyStylesSet.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -45,7 +45,7 @@ public class KeyStylesSet {
mStyles.put(EMPTY_STYLE_NAME, mEmptyKeyStyle);
}
- private static class EmptyKeyStyle extends KeyStyle {
+ private static final class EmptyKeyStyle extends KeyStyle {
EmptyKeyStyle(final KeyboardTextsSet textsSet) {
super(textsSet);
}
@@ -71,7 +71,7 @@ public class KeyStylesSet {
}
}
- private static class DeclaredKeyStyle extends KeyStyle {
+ private static final class DeclaredKeyStyle extends KeyStyle {
private final HashMap<String, KeyStyle> mStyles;
private final String mParentStyleName;
private final SparseArray<Object> mStyleAttributes = CollectionUtils.newSparseArray();
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java b/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java
index 04cc152fe..6ddd2a6fb 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java
@@ -23,7 +23,7 @@ import android.util.SparseIntArray;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.ResourceUtils;
-public class KeyVisualAttributes {
+public final class KeyVisualAttributes {
public final Typeface mTypeface;
public final float mLetterRatio;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
index 31c7cb565..36342688e 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,9 +178,9 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
return this;
}
- // TODO: Remove this method.
- public void setTouchPositionCorrectionEnabled(final boolean enabled) {
- mParams.mTouchPositionCorrection.setEnabled(enabled);
+ @UsedForTesting
+ public void disableTouchPositionCorrectionDataForTest() {
+ mParams.mTouchPositionCorrection.setEnabled(false);
}
public void setProximityCharsCorrectionEnabled(final boolean enabled) {
@@ -314,7 +315,6 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
final int resourceId = keyboardAttr.getResourceId(
R.styleable.Keyboard_touchPositionCorrectionData, 0);
- params.mTouchPositionCorrection.setEnabled(resourceId != 0);
if (resourceId != 0) {
final String[] data = mResources.getStringArray(resourceId);
params.mTouchPositionCorrection.load(data);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
index f7923d0b9..428e31ccd 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
@@ -16,12 +16,12 @@
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;
-public class KeyboardCodesSet {
+public final class KeyboardCodesSet {
private static final HashMap<String, int[]> sLanguageToCodesMap = CollectionUtils.newHashMap();
private static final HashMap<String, Integer> sNameToIdMap = CollectionUtils.newHashMap();
@@ -74,21 +74,21 @@ public 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/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
index 4a98a3698..7292e8e19 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
@@ -27,7 +27,7 @@ import com.android.inputmethod.latin.R;
import java.util.HashMap;
-public class KeyboardIconsSet {
+public final class KeyboardIconsSet {
private static final String TAG = KeyboardIconsSet.class.getSimpleName();
public static final int ICON_UNDEFINED = 0;
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/KeyboardRow.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java
index eb17b0ea4..b986262d7 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java
@@ -32,7 +32,7 @@ import org.xmlpull.v1.XmlPullParser;
* Some of the key size defaults can be overridden per row from what the {@link Keyboard}
* defines.
*/
-public class KeyboardRow {
+public final class KeyboardRow {
// keyWidth enum constants
private static final int KEYWIDTH_NOT_ENUM = 0;
private static final int KEYWIDTH_FILL_RIGHT = -1;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
index 4ab6832c3..5f67ae05f 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;
/**
@@ -34,7 +33,7 @@ import com.android.inputmethod.latin.Constants;
*
* The actions are {@link SwitchActions}'s methods.
*/
-public class KeyboardState {
+public final class KeyboardState {
private static final String TAG = KeyboardState.class.getSimpleName();
private static final boolean DEBUG_EVENT = false;
private static final boolean DEBUG_ACTION = false;
@@ -92,7 +91,7 @@ public class KeyboardState {
private final SavedKeyboardState mSavedKeyboardState = new SavedKeyboardState();
- static class SavedKeyboardState {
+ static final class SavedKeyboardState {
public boolean mIsValid;
public boolean mIsAlphabetMode;
public boolean mIsAlphabetShiftLocked;
@@ -300,12 +299,12 @@ public 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();
@@ -333,12 +332,12 @@ public 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);
}
}
@@ -365,9 +364,9 @@ public 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);
}
@@ -434,7 +433,7 @@ public 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.
@@ -514,7 +513,7 @@ public 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) {
@@ -525,14 +524,14 @@ public 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;
@@ -548,7 +547,7 @@ public 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) {
@@ -559,8 +558,8 @@ public 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
@@ -581,7 +580,7 @@ public class KeyboardState {
}
// If the code is a letter, update keyboard shift state.
- if (Keyboard.isLetterCode(code)) {
+ if (Constants.isLetterCode(code)) {
updateAlphabetShiftState(autoCaps);
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
index 3b7c6ad7a..e08536b43 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();
@@ -799,8 +800,12 @@ public final class KeyboardTextsSet {
// U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
// U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
/* 0 */ "\u00E4,\u00E2,\u00E0,\u00E1,\u00E6,\u00E3,\u00E5,\u0101",
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
// U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
- /* 1 */ "\u0117",
+ /* 1 */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0117",
/* 2 */ null,
// U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
// U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java b/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java
index f54617c98..d1b4c8524 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java
@@ -21,7 +21,7 @@ import com.android.inputmethod.latin.CollectionUtils;
import java.util.HashMap;
-public class KeysCache {
+public final class KeysCache {
private final HashMap<Key, Key> mMap = CollectionUtils.newHashMap();
public void clear() {
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 c1a5cbead..a52f202aa 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
@@ -22,7 +22,7 @@ import com.android.inputmethod.latin.CollectionUtils;
import java.util.ArrayList;
-public class PointerTrackerQueue {
+public final class PointerTrackerQueue {
private static final String TAG = PointerTrackerQueue.class.getSimpleName();
private static final boolean DEBUG = false;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
index 15170e040..776ac0204 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
@@ -39,7 +39,11 @@ import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
-public class PreviewPlacerView extends RelativeLayout {
+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;
@@ -47,14 +51,17 @@ public class PreviewPlacerView extends RelativeLayout {
private final float mGestureFloatingPreviewVerticalPadding;
private final float mGestureFloatingPreviewRoundRadius;
- private int mXOrigin;
- private int mYOrigin;
+ private int mKeyboardViewOriginX;
+ private int mKeyboardViewOriginY;
private final SparseArray<GesturePreviewTrail> mGesturePreviewTrails =
CollectionUtils.newSparseArray();
private final Params mGesturePreviewTrailParams;
private final Paint mGesturePaint;
private boolean mDrawsGesturePreviewTrail;
+ private int mOffscreenWidth;
+ private int mOffscreenHeight;
+ private int mOffscreenOffsetY;
private Bitmap mOffscreenBuffer;
private final Canvas mOffscreenCanvas = new Canvas();
private final Rect mOffscreenDirtyRect = new Rect();
@@ -72,7 +79,7 @@ public class PreviewPlacerView extends RelativeLayout {
private final DrawingHandler mDrawingHandler;
- private static class DrawingHandler extends StaticInnerHandlerWrapper<PreviewPlacerView> {
+ private static final class DrawingHandler extends StaticInnerHandlerWrapper<PreviewPlacerView> {
private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 0;
private static final int MSG_UPDATE_GESTURE_PREVIEW_TRAIL = 1;
@@ -101,29 +108,17 @@ public class PreviewPlacerView extends RelativeLayout {
}
}
- private void cancelDismissGestureFloatingPreviewText() {
- removeMessages(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT);
- }
-
public void dismissGestureFloatingPreviewText() {
- cancelDismissGestureFloatingPreviewText();
+ removeMessages(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT);
sendMessageDelayed(obtainMessage(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT),
mGestureFloatingPreviewTextLingerTimeout);
}
- private void cancelUpdateGestureTrailPreview() {
- removeMessages(MSG_UPDATE_GESTURE_PREVIEW_TRAIL);
- }
-
public void postUpdateGestureTrailPreview() {
- cancelUpdateGestureTrailPreview();
+ removeMessages(MSG_UPDATE_GESTURE_PREVIEW_TRAIL);
sendMessageDelayed(obtainMessage(MSG_UPDATE_GESTURE_PREVIEW_TRAIL),
mGesturePreviewTrailParams.mUpdateInterval);
}
-
- public void cancelAllMessages() {
- cancelUpdateGestureTrailPreview();
- }
}
public PreviewPlacerView(final Context context, final AttributeSet attrs) {
@@ -177,9 +172,12 @@ public class PreviewPlacerView extends RelativeLayout {
setLayerType(LAYER_TYPE_HARDWARE, layerPaint);
}
- public void setOrigin(final int x, final int y) {
- mXOrigin = x;
- mYOrigin = y;
+ 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);
+ mOffscreenWidth = w;
+ mOffscreenHeight = mOffscreenOffsetY + h;
}
public void setGesturePreviewMode(final boolean drawsGesturePreviewTrail,
@@ -216,45 +214,42 @@ public class PreviewPlacerView extends RelativeLayout {
@Override
protected void onDetachedFromWindow() {
+ freeOffscreenBuffer();
+ }
+
+ private void freeOffscreenBuffer() {
if (mOffscreenBuffer != null) {
mOffscreenBuffer.recycle();
mOffscreenBuffer = null;
}
}
+ private void mayAllocateOffscreenBuffer() {
+ if (mOffscreenBuffer != null && mOffscreenBuffer.getWidth() == mOffscreenWidth
+ && mOffscreenBuffer.getHeight() == mOffscreenHeight) {
+ return;
+ }
+ freeOffscreenBuffer();
+ mOffscreenBuffer = Bitmap.createBitmap(
+ mOffscreenWidth, mOffscreenHeight, Bitmap.Config.ARGB_8888);
+ mOffscreenCanvas.setBitmap(mOffscreenBuffer);
+ }
+
@Override
public void onDraw(final Canvas canvas) {
super.onDraw(canvas);
- canvas.translate(mXOrigin, mYOrigin);
if (mDrawsGesturePreviewTrail) {
- if (mOffscreenBuffer == null) {
- mOffscreenBuffer = Bitmap.createBitmap(
- getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
- mOffscreenCanvas.setBitmap(mOffscreenBuffer);
- }
- if (!mOffscreenDirtyRect.isEmpty()) {
- // Clear previous dirty rectangle.
- mGesturePaint.setColor(Color.TRANSPARENT);
- mGesturePaint.setStyle(Paint.Style.FILL);
- mOffscreenCanvas.drawRect(mOffscreenDirtyRect, mGesturePaint);
- mOffscreenDirtyRect.setEmpty();
- }
- boolean needsUpdatingGesturePreviewTrail = false;
- synchronized (mGesturePreviewTrails) {
- // Trails count == fingers count that have ever been active.
- final int trailsCount = mGesturePreviewTrails.size();
- for (int index = 0; index < trailsCount; index++) {
- final GesturePreviewTrail trail = mGesturePreviewTrails.valueAt(index);
- needsUpdatingGesturePreviewTrail |=
- trail.drawGestureTrail(mOffscreenCanvas, mGesturePaint,
- mGesturePreviewTrailBoundsRect, mGesturePreviewTrailParams);
- // {@link #mGesturePreviewTrailBoundsRect} has bounding box of the trail.
- mOffscreenDirtyRect.union(mGesturePreviewTrailBoundsRect);
- }
- }
+ mayAllocateOffscreenBuffer();
+ // Draw gesture trails to offscreen buffer.
+ final boolean needsUpdatingGesturePreviewTrail = drawGestureTrails(
+ mOffscreenCanvas, mGesturePaint, mOffscreenDirtyRect);
+ // Transfer offscreen buffer to screen.
if (!mOffscreenDirtyRect.isEmpty()) {
+ final int offsetY = mKeyboardViewOriginY - mOffscreenOffsetY;
+ canvas.translate(mKeyboardViewOriginX, offsetY);
canvas.drawBitmap(mOffscreenBuffer, mOffscreenDirtyRect, mOffscreenDirtyRect,
mGesturePaint);
+ canvas.translate(-mKeyboardViewOriginX, -offsetY);
// Note: Defer clearing the dirty rectangle here because we will get cleared
// rectangle on the canvas.
}
@@ -263,9 +258,49 @@ public class PreviewPlacerView extends RelativeLayout {
}
}
if (mDrawsGestureFloatingPreviewText) {
+ canvas.translate(mKeyboardViewOriginX, mKeyboardViewOriginY);
drawGestureFloatingPreviewText(canvas, mGestureFloatingPreviewText);
+ canvas.translate(-mKeyboardViewOriginX, -mKeyboardViewOriginY);
+ }
+ }
+
+ private boolean drawGestureTrails(final Canvas offscreenCanvas, final Paint paint,
+ final Rect dirtyRect) {
+ // Clear previous dirty rectangle.
+ if (!dirtyRect.isEmpty()) {
+ paint.setColor(Color.TRANSPARENT);
+ paint.setStyle(Paint.Style.FILL);
+ offscreenCanvas.drawRect(dirtyRect, paint);
+ }
+ dirtyRect.setEmpty();
+
+ // Draw gesture trails to offscreen buffer.
+ offscreenCanvas.translate(0, mOffscreenOffsetY);
+ boolean needsUpdatingGesturePreviewTrail = false;
+ synchronized (mGesturePreviewTrails) {
+ // Trails count == fingers count that have ever been active.
+ final int trailsCount = mGesturePreviewTrails.size();
+ for (int index = 0; index < trailsCount; index++) {
+ final GesturePreviewTrail trail = mGesturePreviewTrails.valueAt(index);
+ needsUpdatingGesturePreviewTrail |=
+ trail.drawGestureTrail(offscreenCanvas, paint,
+ mGesturePreviewTrailBoundsRect, mGesturePreviewTrailParams);
+ // {@link #mGesturePreviewTrailBoundsRect} has bounding box of the trail.
+ dirtyRect.union(mGesturePreviewTrailBoundsRect);
+ }
}
- canvas.translate(-mXOrigin, -mYOrigin);
+ offscreenCanvas.translate(0, -mOffscreenOffsetY);
+
+ // Clip dirty rectangle with offscreen buffer width/height.
+ dirtyRect.offset(0, mOffscreenOffsetY);
+ clipRect(dirtyRect, 0, 0, mOffscreenWidth, mOffscreenHeight);
+ return needsUpdatingGesturePreviewTrail;
+ }
+
+ private static void clipRect(final Rect out, final int left, final int top, final int right,
+ final int bottom) {
+ out.set(Math.max(out.left, left), Math.max(out.top, top), Math.min(out.right, right),
+ Math.min(out.bottom, bottom));
}
public void setGestureFloatingPreviewText(final String gestureFloatingPreviewText) {
@@ -278,10 +313,6 @@ public class PreviewPlacerView extends RelativeLayout {
mDrawingHandler.dismissGestureFloatingPreviewText();
}
- public void cancelAllMessages() {
- mDrawingHandler.cancelAllMessages();
- }
-
private void drawGestureFloatingPreviewText(final Canvas canvas,
final String gestureFloatingPreviewText) {
if (TextUtils.isEmpty(gestureFloatingPreviewText)) {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/ShiftKeyState.java b/java/src/com/android/inputmethod/keyboard/internal/ShiftKeyState.java
index edb40c8e7..90db73dee 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/ShiftKeyState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/ShiftKeyState.java
@@ -18,7 +18,7 @@ package com.android.inputmethod.keyboard.internal;
import android.util.Log;
-/* package */ class ShiftKeyState extends ModifierKeyState {
+/* package */ final class ShiftKeyState extends ModifierKeyState {
private static final int PRESSING_ON_SHIFTED = 3; // both temporary shifted & shift locked
private static final int IGNORING = 4;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java b/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java
index 69dc01cd6..d7a2b6f39 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java
@@ -16,15 +16,16 @@
package com.android.inputmethod.keyboard.internal;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.LatinImeLogger;
-public class TouchPositionCorrection {
+public final class TouchPositionCorrection {
private static final int TOUCH_POSITION_CORRECTION_RECORD_SIZE = 3;
- public boolean mEnabled;
- public float[] mXs;
- public float[] mYs;
- public float[] mRadii;
+ private boolean mEnabled;
+ private float[] mXs;
+ private float[] mYs;
+ private float[] mRadii;
public void load(final String[] data) {
final int dataLength = data.length;
@@ -53,24 +54,43 @@ public class TouchPositionCorrection {
mRadii[index] = value;
}
}
+ mEnabled = dataLength > 0;
} catch (NumberFormatException e) {
if (LatinImeLogger.sDBG) {
throw new RuntimeException(
"the number format for touch position correction data is invalid");
}
+ mEnabled = false;
mXs = null;
mYs = null;
mRadii = null;
}
}
- // TODO: Remove this method.
+ @UsedForTesting
public void setEnabled(final boolean enabled) {
mEnabled = enabled;
}
public boolean isValid() {
- return mEnabled && mXs != null && mYs != null && mRadii != null
- && mXs.length > 0 && mYs.length > 0 && mRadii.length > 0;
+ return mEnabled;
+ }
+
+ public int getRows() {
+ return mRadii.length;
+ }
+
+ public float getX(final int row) {
+ return 0.0f;
+ // Touch position correction data for X coordinate is obsolete.
+ // return mXs[row];
+ }
+
+ public float getY(final int row) {
+ return mYs[row];
+ }
+
+ public float getRadius(final int row) {
+ return mRadii[row];
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/SuddenJumpingTouchEventHandler.java b/java/src/com/android/inputmethod/keyboard/internal/TouchScreenRegulator.java
index a591a7ac3..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 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 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 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 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 ae51d2537..d12607721 100644
--- a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
+++ b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
@@ -49,7 +49,7 @@ import com.android.inputmethod.compat.CompatUtils;
import java.util.ArrayList;
import java.util.TreeSet;
-public class AdditionalSubtypeSettings extends PreferenceFragment {
+public final class AdditionalSubtypeSettings extends PreferenceFragment {
private SharedPreferences mPrefs;
private SubtypeLocaleAdapter mSubtypeLocaleAdapter;
private KeyboardLayoutSetAdapter mKeyboardLayoutSetAdapter;
diff --git a/java/src/com/android/inputmethod/latin/AssetFileAddress.java b/java/src/com/android/inputmethod/latin/AssetFileAddress.java
index 3549a1561..29c733ba6 100644
--- a/java/src/com/android/inputmethod/latin/AssetFileAddress.java
+++ b/java/src/com/android/inputmethod/latin/AssetFileAddress.java
@@ -24,7 +24,7 @@ import java.io.File;
* the package file. Open it correctly thus requires the name of the package it is in, but
* also the offset in the file and the length of this data. This class encapsulates these three.
*/
-class AssetFileAddress {
+final class AssetFileAddress {
public final String mFilename;
public final long mOffset;
public final long mLength;
diff --git a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
index 55664d411..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;
/**
@@ -30,18 +29,16 @@ import com.android.inputmethod.latin.VibratorUtils;
* It offers a consistent and simple interface that allows LatinIME to forget about the
* complexity of settings and the like.
*/
-public class AudioAndHapticFeedbackManager {
- final private SettingsValues mSettingsValues;
- final private AudioManager mAudioManager;
- final private VibratorUtils mVibratorUtils;
+public final class AudioAndHapticFeedbackManager {
+ 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 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 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 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 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 f425e360a..fa35922b0 100644
--- a/java/src/com/android/inputmethod/latin/AutoCorrection.java
+++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java
@@ -23,7 +23,7 @@ import android.util.Log;
import java.util.concurrent.ConcurrentHashMap;
-public class AutoCorrection {
+public final class AutoCorrection {
private static final boolean DBG = LatinImeLogger.sDBG;
private static final String TAG = AutoCorrection.class.getSimpleName();
private static final int MINIMUM_SAFETY_NET_CHAR_LENGTH = 4;
@@ -33,11 +33,11 @@ public 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 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 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 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 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 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 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/BackupAgent.java b/java/src/com/android/inputmethod/latin/BackupAgent.java
index ee070af75..0beb088ac 100644
--- a/java/src/com/android/inputmethod/latin/BackupAgent.java
+++ b/java/src/com/android/inputmethod/latin/BackupAgent.java
@@ -22,7 +22,7 @@ import android.app.backup.SharedPreferencesBackupHelper;
/**
* Backs up the Latin IME shared preferences.
*/
-public class BackupAgent extends BackupAgentHelper {
+public final class BackupAgent extends BackupAgentHelper {
@Override
public void onCreate() {
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index c3ae81f3a..a7024d1d8 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -30,8 +30,8 @@ import java.util.Locale;
/**
* Implements a static, compacted, binary dictionary of standard words.
*/
-public class BinaryDictionary extends Dictionary {
-
+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 class BinaryDictionary extends Dictionary {
public static final int MAX_WORDS = 18;
public static final int MAX_SPACES = 16;
- private static final String TAG = "BinaryDictionary";
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 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 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 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 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 fullWordMultiplier, 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, FULL_WORD_SCORE_MULTIPLIER,
+ 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 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,50 +161,56 @@ public 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) {
- return editDistanceNative(before.toCharArray(), after.toCharArray());
+ public static int editDistance(final String before, final String after) {
+ if (before == null || after == null) {
+ throw new IllegalArgumentException();
+ }
+ 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 799aea8ef..bed31a7d1 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -30,7 +30,6 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -40,14 +39,14 @@ import java.util.Locale;
* Group class for static methods to help with creation and getting of the binary dictionary
* file from the dictionary provider
*/
-public class BinaryDictionaryFileDumper {
+public final class BinaryDictionaryFileDumper {
private static final String TAG = BinaryDictionaryFileDumper.class.getSimpleName();
private static final boolean DEBUG = false;
/**
* The size of the temporary buffer to copy files.
*/
- private static final int FILE_READ_BUFFER_SIZE = 1024;
+ private static final int FILE_READ_BUFFER_SIZE = 8192;
// TODO: make the following data common with the native code
private static final byte[] MAGIC_NUMBER_VERSION_1 =
new byte[] { (byte)0x78, (byte)0xB1, (byte)0x00, (byte)0x00 };
@@ -150,7 +149,13 @@ public class BinaryDictionaryFileDumper {
final Uri.Builder wordListUriBuilder = getProviderUriBuilder(id);
final String finalFileName = BinaryDictionaryGetter.getCacheFileName(id, locale, context);
- final String tempFileName = finalFileName + ".tmp";
+ 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;
@@ -201,6 +206,7 @@ public class BinaryDictionaryFileDumper {
outputStream.flush();
outputStream.close();
final File finalFile = new File(finalFileName);
+ finalFile.delete();
if (!outputFile.renameTo(finalFile)) {
throw new IOException("Can't move the file to its final name");
}
@@ -287,6 +293,7 @@ public class BinaryDictionaryFileDumper {
* @param input the stream to be copied.
* @param output an output stream to copy the data to.
*/
+ // TODO: make output a BufferedOutputStream
private static void checkMagicAndCopyFileTo(final BufferedInputStream input,
final FileOutputStream output) throws FileNotFoundException, IOException {
// Check the magic number
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index 9764df072..ecb61b46f 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -37,7 +37,7 @@ import java.util.Locale;
/**
* Helper class to get the address of a mmap'able dictionary file.
*/
-class BinaryDictionaryGetter {
+final class BinaryDictionaryGetter {
/**
* Used for Log actions from this class
@@ -164,6 +164,13 @@ class BinaryDictionaryGetter {
}
/**
+ * Generates a unique temporary file name in the app cache directory.
+ */
+ public static String getTempFileName(String id, Context context) throws IOException {
+ return File.createTempFile(replaceFileNameDangerousCharacters(id), null).getAbsolutePath();
+ }
+
+ /**
* Returns a file address from a resource, or null if it cannot be opened.
*/
private static AssetFileAddress loadFallbackResource(final Context context,
@@ -178,7 +185,7 @@ class BinaryDictionaryGetter {
context.getApplicationInfo().sourceDir, afd.getStartOffset(), afd.getLength());
}
- static private class DictPackSettings {
+ private static final class DictPackSettings {
final SharedPreferences mDictPreferences;
public DictPackSettings(final Context context) {
Context dictPackContext = null;
@@ -237,7 +244,7 @@ class BinaryDictionaryGetter {
/**
* Utility class for the {@link #getCachedWordLists} method
*/
- private static class FileAndMatchLevel {
+ private static final class FileAndMatchLevel {
final File mFile;
final int mMatchLevel;
public FileAndMatchLevel(final File file, final int matchLevel) {
@@ -350,7 +357,7 @@ class BinaryDictionaryGetter {
// of the dictionary would lose whitelist functionality.
private static boolean hackCanUseDictionaryFile(final Locale locale, final File f) {
// Only for English - other languages didn't have a whitelist, hence this
- // ad-hock ## HACK ##
+ // ad-hoc ## HACK ##
if (!Locale.ENGLISH.getLanguage().equals(locale.getLanguage())) return true;
FileInputStream inStream = null;
diff --git a/java/src/com/android/inputmethod/latin/BoundedTreeSet.java b/java/src/com/android/inputmethod/latin/BoundedTreeSet.java
index cf977617d..7f7ff31c8 100644
--- a/java/src/com/android/inputmethod/latin/BoundedTreeSet.java
+++ b/java/src/com/android/inputmethod/latin/BoundedTreeSet.java
@@ -25,7 +25,7 @@ import java.util.TreeSet;
/**
* A TreeSet that is bounded in size and throws everything that's smaller than its limit
*/
-public class BoundedTreeSet extends TreeSet<SuggestedWordInfo> {
+public final class BoundedTreeSet extends TreeSet<SuggestedWordInfo> {
private final int mCapacity;
public BoundedTreeSet(final Comparator<SuggestedWordInfo> comparator, final int capacity) {
super(comparator);
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/DebugSettings.java b/java/src/com/android/inputmethod/latin/DebugSettings.java
index af7649863..3af3cab2c 100644
--- a/java/src/com/android/inputmethod/latin/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/DebugSettings.java
@@ -23,17 +23,20 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.os.Process;
import android.preference.CheckBoxPreference;
+import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.util.Log;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
+import com.android.inputmethod.research.ResearchLogger;
-public class DebugSettings extends PreferenceFragment
+public final class DebugSettings extends PreferenceFragment
implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = DebugSettings.class.getSimpleName();
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 boolean mServiceNeedsRestart = false;
private CheckBoxPreference mDebugMode;
@@ -45,6 +48,14 @@ public class DebugSettings extends PreferenceFragment
SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
prefs.registerOnSharedPreferenceChangeListener(this);
+ final Preference usabilityStudyPref = findPreference(PREF_USABILITY_STUDY_MODE);
+ if (usabilityStudyPref instanceof CheckBoxPreference) {
+ final CheckBoxPreference checkbox = (CheckBoxPreference)usabilityStudyPref;
+ checkbox.setChecked(prefs.getBoolean(PREF_USABILITY_STUDY_MODE,
+ ResearchLogger.DEFAULT_USABILITY_STUDY_MODE));
+ checkbox.setSummary(R.string.settings_warning_researcher_mode);
+ }
+
mServiceNeedsRestart = false;
mDebugMode = (CheckBoxPreference) findPreference(DEBUG_MODE_KEY);
updateDebugMode();
diff --git a/java/src/com/android/inputmethod/latin/DebugSettingsActivity.java b/java/src/com/android/inputmethod/latin/DebugSettingsActivity.java
index cde20606a..6ef19ee82 100644
--- a/java/src/com/android/inputmethod/latin/DebugSettingsActivity.java
+++ b/java/src/com/android/inputmethod/latin/DebugSettingsActivity.java
@@ -20,7 +20,7 @@ import android.content.Intent;
import android.os.Bundle;
import android.preference.PreferenceActivity;
-public class DebugSettingsActivity extends PreferenceActivity {
+public final class DebugSettingsActivity extends PreferenceActivity {
@Override
public Intent getIntent() {
final Intent modIntent = new Intent(super.getIntent());
diff --git a/java/src/com/android/inputmethod/latin/DicTraverseSession.java b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
index 359da72cc..ce1b64660 100644
--- a/java/src/com/android/inputmethod/latin/DicTraverseSession.java
+++ b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
@@ -18,7 +18,7 @@ package com.android.inputmethod.latin;
import java.util.Locale;
-public class DicTraverseSession {
+public final class DicTraverseSession {
static {
JniUtils.loadNativeLibrary();
}
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index 88d0c09dd..8207bc47f 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -59,12 +59,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 +73,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 +87,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 4acab6b05..7f78ac8a2 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -29,7 +29,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
/**
* Class for a collection of dictionaries that behave like one dictionary.
*/
-public class DictionaryCollection extends Dictionary {
+public final class DictionaryCollection extends Dictionary {
private final String TAG = DictionaryCollection.class.getSimpleName();
protected final CopyOnWriteArrayList<Dictionary> mDictionaries;
@@ -56,7 +56,7 @@ public 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 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/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index cdd01d0c7..f381973ae 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -29,7 +29,7 @@ import java.util.Locale;
/**
* Factory for dictionary instances.
*/
-public class DictionaryFactory {
+public final class DictionaryFactory {
private static final String TAG = DictionaryFactory.class.getSimpleName();
// This class must be located in the same package as LatinIME.java.
private static final String RESOURCE_PACKAGE_NAME =
diff --git a/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
index 9c37d7673..f2f3fbded 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
@@ -27,7 +27,7 @@ import android.net.Uri;
/**
* Takes action to reload the necessary data when a dictionary pack was added/removed.
*/
-public class DictionaryPackInstallBroadcastReceiver extends BroadcastReceiver {
+public final class DictionaryPackInstallBroadcastReceiver extends BroadcastReceiver {
final LatinIME mService;
/**
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 8a38d1e1b..16cc1b35f 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;
@@ -48,7 +47,7 @@ public class ExpandableDictionary extends Dictionary {
// Use this lock before touching mUpdatingDictionary & mRequiresDownload
private Object mUpdatingLock = new Object();
- private static class Node {
+ private static final class Node {
Node() {}
char mCode;
int mFrequency;
@@ -60,7 +59,7 @@ public class ExpandableDictionary extends Dictionary {
LinkedList<NextWord> mNGrams; // Supports ngram
}
- private static class NodeArray {
+ private static final class NodeArray {
Node[] mData;
int mLength = 0;
private static final int INCREMENT = 2;
@@ -69,7 +68,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) {
@@ -88,7 +87,7 @@ public class ExpandableDictionary extends Dictionary {
public int notifyTypedAgainAndGetFrequency();
}
- private static class NextStaticWord implements NextWord {
+ private static final class NextStaticWord implements NextWord {
public final Node mWord;
private final int mFrequency;
public NextStaticWord(Node word, int frequency) {
@@ -117,7 +116,7 @@ public class ExpandableDictionary extends Dictionary {
}
}
- private static class NextHistoryWord implements NextWord {
+ private static final class NextHistoryWord implements NextWord {
public final Node mWord;
public final ForgettingCurveParams mFcp;
@@ -172,7 +171,7 @@ public class ExpandableDictionary extends Dictionary {
}
}
- public void setRequiresReload(boolean reload) {
+ public void setRequiresReload(final boolean reload) {
synchronized (mUpdatingLock) {
mRequiresReload = reload;
}
@@ -202,8 +201,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 +247,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 +266,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 +275,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 +303,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 +318,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 +343,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 +366,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 +430,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 +471,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 +523,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 +541,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 +573,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 +604,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 +624,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;
@@ -703,7 +686,7 @@ public class ExpandableDictionary extends Dictionary {
mRoots = new NodeArray();
}
- private class LoadDictionaryTask extends Thread {
+ private final class LoadDictionaryTask extends Thread {
LoadDictionaryTask() {}
@Override
public void run() {
@@ -714,7 +697,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/FileTransforms.java b/java/src/com/android/inputmethod/latin/FileTransforms.java
index 80159521c..09cf23a9b 100644
--- a/java/src/com/android/inputmethod/latin/FileTransforms.java
+++ b/java/src/com/android/inputmethod/latin/FileTransforms.java
@@ -21,7 +21,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.GZIPInputStream;
-public class FileTransforms {
+public final class FileTransforms {
public static OutputStream getCryptedStream(OutputStream out) {
// Crypt the stream.
return out;
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
index 7bcda9bc4..2f7608a03 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -23,7 +23,7 @@ import android.view.inputmethod.EditorInfo;
/**
* Class to hold attributes of the input field.
*/
-public class InputAttributes {
+public final class InputAttributes {
private final String TAG = InputAttributes.class.getSimpleName();
final public boolean mInputTypeNoAutoCorrect;
diff --git a/java/src/com/android/inputmethod/latin/InputPointers.java b/java/src/com/android/inputmethod/latin/InputPointers.java
index ff2feb51d..7dffd96dd 100644
--- a/java/src/com/android/inputmethod/latin/InputPointers.java
+++ b/java/src/com/android/inputmethod/latin/InputPointers.java
@@ -16,8 +16,10 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.annotations.UsedForTesting;
+
// TODO: This class is not thread-safe.
-public class InputPointers {
+public final class InputPointers {
private final int mDefaultCapacity;
private final ResizableIntArray mXCoordinates;
private final ResizableIntArray mYCoordinates;
@@ -39,7 +41,8 @@ public 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 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/InputView.java b/java/src/com/android/inputmethod/latin/InputView.java
index c15f45345..d7595bfbe 100644
--- a/java/src/com/android/inputmethod/latin/InputView.java
+++ b/java/src/com/android/inputmethod/latin/InputView.java
@@ -23,7 +23,7 @@ import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
-public class InputView extends LinearLayout {
+public final class InputView extends LinearLayout {
private View mSuggestionStripContainer;
private View mKeyboardView;
private int mKeyboardTopPadding;
diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java
index dd73a978c..44ef01204 100644
--- a/java/src/com/android/inputmethod/latin/LastComposedWord.java
+++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java
@@ -22,7 +22,7 @@ import android.text.TextUtils;
* This class encapsulates data about a word previously composed, but that has been
* committed already. This is used for resuming suggestion, and cancel auto-correction.
*/
-public class LastComposedWord {
+public final class LastComposedWord {
// COMMIT_TYPE_USER_TYPED_WORD is used when the word committed is the exact typed word, with
// no hinting from the IME. It happens when some external event happens (rotating the device,
// for example) or when auto-correction is off by settings or editor attributes.
@@ -44,7 +44,7 @@ public 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 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 9252b0980..2af7b1d61 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -60,6 +60,7 @@ 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;
@@ -85,7 +86,7 @@ import java.util.Locale;
/**
* Input method implementation for Qwerty'ish keyboard.
*/
-public class LatinIME extends InputMethodService implements KeyboardActionListener,
+public final class LatinIME extends InputMethodService implements KeyboardActionListener,
SuggestionStripView.Listener, TargetApplicationGetter.OnTargetApplicationKnownListener,
Suggest.SuggestInitializationListener {
private static final String TAG = LatinIME.class.getSimpleName();
@@ -132,14 +133,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
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 Resources mResources;
private SharedPreferences mPrefs;
- /* package for tests */ final KeyboardSwitcher mKeyboardSwitcher;
+ @UsedForTesting final KeyboardSwitcher mKeyboardSwitcher;
private final SubtypeSwitcher mSubtypeSwitcher;
private boolean mShouldSwitchToLastSubtype = true;
@@ -163,8 +164,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private int mDeleteCount;
private long mLastKeyTime;
- private AudioAndHapticFeedbackManager mFeedbackManager;
-
// Member variables for remembering the current device orientation.
private int mDisplayOrientation;
@@ -173,7 +172,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
new DictionaryPackInstallBroadcastReceiver(this);
// Keeps track of most recently inserted text (multi-character key) for reverting
- private CharSequence mEnteredText;
+ private String mEnteredText;
private boolean mIsAutoCorrectionIndicatorOn;
@@ -183,7 +182,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public final UIHandler mHandler = new UIHandler(this);
- public static class UIHandler extends StaticInnerHandlerWrapper<LatinIME> {
+ public static final class UIHandler extends StaticInnerHandlerWrapper<LatinIME> {
private static final int MSG_UPDATE_SHIFT_STATE = 0;
private static final int MSG_PENDING_IMS_CALLBACK = 1;
private static final int MSG_UPDATE_SUGGESTION_STRIP = 2;
@@ -424,7 +423,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
// 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.
@@ -438,7 +437,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
};
mCurrentSettings = job.runInLocale(mResources, mSubtypeSwitcher.getCurrentSubtypeLocale());
- mFeedbackManager = new AudioAndHapticFeedbackManager(this, mCurrentSettings);
resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary());
}
@@ -715,11 +713,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mSpaceState = SPACE_STATE_NONE;
if (mSuggestionStripView != null) {
- mSuggestionStripView.clear();
+ // This will set the punctuation suggestions if next word suggestion is off;
+ // otherwise it will clear the suggestion strip.
+ setPunctuationSuggestions();
}
}
- mConnection.resetCachesUponCursorMove(mLastSelectionStart);
+ mConnection.resetCachesUponCursorMove(editorInfo.initialSelStart);
if (isDifferentTextField) {
mainKeyboardView.closing();
@@ -856,16 +856,25 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// TODO: the following is probably better done in resetEntireInputState().
// it should only happen when the cursor moved, and the very purpose of the
// test below is to narrow down whether this happened or not. Likewise with
- // the call to postUpdateShiftState.
+ // the call to updateShiftState.
// We set this to NONE because after a cursor move, we don't want the space
// state-related special processing to kick in.
mSpaceState = SPACE_STATE_NONE;
if ((!mWordComposer.isComposingWord()) || selectionChanged || noComposingSpan) {
+ // If we are composing a word and moving the cursor, we would want to set a
+ // suggestion span for recorrection to work correctly. Unfortunately, that
+ // would involve the keyboard committing some new text, which would move the
+ // cursor back to where it was. Latin IME could then fix the position of the cursor
+ // again, but the asynchronous nature of the calls results in this wreaking havoc
+ // with selection on double tap and the like.
+ // Another option would be to send suggestions each time we set the composing
+ // text, but that is probably too expensive to do, so we decided to leave things
+ // as is.
resetEntireInputState(newSelStart);
}
- mHandler.postUpdateShiftState();
+ mKeyboardSwitcher.updateShiftState();
}
mExpectingUpdateSelection = false;
// TODO: Decide to call restartSuggestionsOnWordBeforeCursorIfAtEndOfWord() or not
@@ -1052,7 +1061,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Reread resource value here, because this method is called by framework anytime as needed.
final boolean isFullscreenModeAllowed =
mCurrentSettings.isFullscreenModeAllowed(getResources());
- return super.onEvaluateFullscreenMode() && isFullscreenModeAllowed;
+ 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
+ // without NO_FULLSCREEN doesn't work as expected. Because of this we need this
+ // hack for now. Let's get rid of this once the framework gets fixed.
+ final EditorInfo ei = getCurrentInputEditorInfo();
+ return !(ei != null && ((ei.imeOptions & EditorInfo.IME_FLAG_NO_EXTRACT_UI) != 0));
+ } else {
+ return false;
+ }
}
@Override
@@ -1085,13 +1103,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private void commitTyped(final String separatorString) {
if (!mWordComposer.isComposingWord()) return;
- final CharSequence typedWord = mWordComposer.getTypedWord();
+ final String typedWord = mWordComposer.getTypedWord();
if (typedWord.length() > 0) {
- mConnection.commitText(typedWord, 1);
- final CharSequence prevWord = addToUserHistoryDictionary(typedWord);
- mLastComposedWord = mWordComposer.commitWord(
- LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, typedWord.toString(),
- separatorString, prevWord);
+ commitChosenWord(typedWord, LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD,
+ separatorString);
}
}
@@ -1111,8 +1126,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Factor in auto-caps and manual caps and compute the current caps mode.
private int getActualCapsMode() {
- final int manual = mKeyboardSwitcher.getManualCapsMode();
- if (manual != WordComposer.CAPS_MODE_OFF) return manual;
+ final int keyboardShiftMode = mKeyboardSwitcher.getKeyboardShiftMode();
+ if (keyboardShiftMode != WordComposer.CAPS_MODE_AUTO_SHIFTED) return keyboardShiftMode;
final int auto = getCurrentAutoCapsState();
if (0 != (auto & TextUtils.CAP_MODE_CHARACTERS)) {
return WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED;
@@ -1125,7 +1140,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
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) {
@@ -1141,8 +1156,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
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) {
+ && lastThree.charAt(1) == Constants.CODE_SPACE
+ && lastThree.charAt(2) == Constants.CODE_SPACE) {
mHandler.cancelDoubleSpacesTimer();
mConnection.deleteSurroundingText(2, 0);
mConnection.commitText(". ", 1);
@@ -1156,12 +1171,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// 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
@@ -1211,22 +1226,25 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mConnection.performEditorAction(actionId);
}
+ // TODO: Revise the language switch key behavior to make it much smarter and more reasonable.
private void handleLanguageSwitchKey() {
- final boolean includesOtherImes = mCurrentSettings.mIncludesOtherImesInLanguageSwitchList;
final IBinder token = getWindow().getWindow().getAttributes().token;
+ if (mCurrentSettings.mIncludesOtherImesInLanguageSwitchList) {
+ mImm.switchToNextInputMethod(token, false /* onlyCurrentIme */);
+ return;
+ }
if (mShouldSwitchToLastSubtype) {
final InputMethodSubtype lastSubtype = mImm.getLastInputMethodSubtype();
final boolean lastSubtypeBelongsToThisIme =
ImfUtils.checkIfSubtypeBelongsToThisImeAndEnabled(this, lastSubtype);
- if ((includesOtherImes || lastSubtypeBelongsToThisIme)
- && mImm.switchToLastInputMethod(token)) {
+ if (lastSubtypeBelongsToThisIme && mImm.switchToLastInputMethod(token)) {
mShouldSwitchToLastSubtype = false;
} else {
- mImm.switchToNextInputMethod(token, !includesOtherImes);
+ mImm.switchToNextInputMethod(token, true /* onlyCurrentIme */);
mShouldSwitchToLastSubtype = true;
}
} else {
- mImm.switchToNextInputMethod(token, !includesOtherImes);
+ mImm.switchToNextInputMethod(token, true /* onlyCurrentIme */);
}
}
@@ -1253,7 +1271,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// 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
@@ -1270,7 +1288,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@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;
@@ -1285,13 +1303,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (!mWordComposer.isComposingWord()) mIsAutoCorrectionIndicatorOn = false;
// TODO: Consolidate the double space timer, mLastKeyTime, and the space state.
- if (primaryCode != Keyboard.CODE_SPACE) {
+ if (primaryCode != Constants.CODE_SPACE) {
mHandler.cancelDoubleSpacesTimer();
}
boolean didAutoCorrect = false;
switch (primaryCode) {
- case Keyboard.CODE_DELETE:
+ case Constants.CODE_DELETE:
mSpaceState = SPACE_STATE_NONE;
handleBackspace(spaceState);
mDeleteCount++;
@@ -1299,29 +1317,29 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mShouldSwitchToLastSubtype = 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);
}
@@ -1357,10 +1375,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
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();
@@ -1371,7 +1389,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// 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());
@@ -1379,21 +1397,22 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
resetComposingState(true /* alsoResetLastComposedWord */);
}
mHandler.postUpdateSuggestionStrip();
- final CharSequence text = specificTldProcessingOnTextInput(rawText);
+ final String text = specificTldProcessingOnTextInput(rawText);
if (SPACE_STATE_PHANTOM == mSpaceState) {
- sendKeyCodePoint(Keyboard.CODE_SPACE);
+ sendKeyCodePoint(Constants.CODE_SPACE);
}
mConnection.commitText(text, 1);
mConnection.endBatchEdit();
// 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;
}
@Override
public void onStartBatchInput() {
+ BatchInputUpdater.getInstance().onStartBatchInput();
mConnection.beginBatchEdit();
if (mWordComposer.isComposingWord()) {
if (ProductionFlag.IS_INTERNAL) {
@@ -1417,6 +1436,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// The following is necessary for the case where the user typed something but didn't
// manual pick it and didn't input any separator.
mSpaceState = SPACE_STATE_PHANTOM;
+ } else {
+ final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
+ // TODO: reverse this logic. We should have the means to determine whether a character
+ // should usually be followed by a space, and it should be more readable.
+ if (Constants.NOT_A_CODE != codePointBeforeCursor
+ && !Character.isWhitespace(codePointBeforeCursor)
+ && !mCurrentSettings.isPhantomSpacePromotingSymbol(codePointBeforeCursor)
+ && !mCurrentSettings.isWeakSpaceStripper(codePointBeforeCursor)) {
+ mSpaceState = SPACE_STATE_PHANTOM;
+ }
}
mConnection.endBatchEdit();
mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode());
@@ -1425,6 +1454,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private static final class BatchInputUpdater implements Handler.Callback {
private final Handler mHandler;
private LatinIME mLatinIme;
+ private boolean mInBatchInput; // synchornized using "this".
private BatchInputUpdater() {
final HandlerThread handlerThread = new HandlerThread(
@@ -1448,17 +1478,32 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public boolean handleMessage(final Message msg) {
switch (msg.what) {
case MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP:
- final SuggestedWords suggestedWords = getSuggestedWordsGesture(
- (InputPointers)msg.obj, mLatinIme);
- showGesturePreviewAndSuggestionStrip(
- suggestedWords, false /* dismissGestureFloatingPreviewText */, mLatinIme);
+ updateBatchInput((InputPointers)msg.obj, mLatinIme);
break;
}
return true;
}
- public void updateGesturePreviewAndSuggestionStrip(final InputPointers batchPointers,
+ // Run in the UI thread.
+ public synchronized void onStartBatchInput() {
+ mInBatchInput = true;
+ }
+
+ // Run in the Handler thread.
+ private synchronized void updateBatchInput(final InputPointers batchPointers,
final LatinIME latinIme) {
+ if (!mInBatchInput) {
+ // Batch input has ended while the message was being delivered.
+ return;
+ }
+ final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked(
+ batchPointers, latinIme);
+ latinIme.mHandler.showGesturePreviewAndSuggestionStrip(
+ suggestedWords, false /* dismissGestureFloatingPreviewText */);
+ }
+
+ // Run in the UI thread.
+ public void onUpdateBatchInput(final InputPointers batchPointers, final LatinIME latinIme) {
mLatinIme = latinIme;
if (mHandler.hasMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP)) {
return;
@@ -1468,15 +1513,20 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
.sendToTarget();
}
- public void showGesturePreviewAndSuggestionStrip(final SuggestedWords suggestedWords,
- final boolean dismissGestureFloatingPreviewText, final LatinIME latinIme) {
+ // Run in the UI thread.
+ public synchronized SuggestedWords onEndBatchInput(final InputPointers batchPointers,
+ final LatinIME latinIme) {
+ mInBatchInput = false;
+ final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked(
+ batchPointers, latinIme);
latinIme.mHandler.showGesturePreviewAndSuggestionStrip(
- suggestedWords, dismissGestureFloatingPreviewText);
+ suggestedWords, true /* dismissGestureFloatingPreviewText */);
+ return suggestedWords;
}
// {@link LatinIME#getSuggestedWords(int)} method calls with same session id have to
// be synchronized.
- public synchronized SuggestedWords getSuggestedWordsGesture(
+ private static SuggestedWords getSuggestedWordsGestureLocked(
final InputPointers batchPointers, final LatinIME latinIme) {
latinIme.mWordComposer.setBatchInputPointers(batchPointers);
return latinIme.getSuggestedWords(Suggest.SESSION_GESTURE);
@@ -1485,8 +1535,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private void showGesturePreviewAndSuggestionStrip(final SuggestedWords suggestedWords,
final boolean dismissGestureFloatingPreviewText) {
- final String batchInputText = (suggestedWords.size() > 0)
- ? suggestedWords.getWord(0) : null;
+ final String batchInputText = suggestedWords.isEmpty()
+ ? null : suggestedWords.getWord(0);
final KeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
mainKeyboardView.showGestureFloatingPreviewText(batchInputText);
showSuggestionStrip(suggestedWords, null);
@@ -1497,25 +1547,22 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public void onUpdateBatchInput(final InputPointers batchPointers) {
- BatchInputUpdater.getInstance().updateGesturePreviewAndSuggestionStrip(batchPointers, this);
+ BatchInputUpdater.getInstance().onUpdateBatchInput(batchPointers, this);
}
@Override
public void onEndBatchInput(final InputPointers batchPointers) {
- final BatchInputUpdater batchInputUpdater = BatchInputUpdater.getInstance();
- final SuggestedWords suggestedWords = batchInputUpdater.getSuggestedWordsGesture(
+ final SuggestedWords suggestedWords = BatchInputUpdater.getInstance().onEndBatchInput(
batchPointers, this);
- batchInputUpdater.showGesturePreviewAndSuggestionStrip(
- suggestedWords, true /* dismissGestureFloatingPreviewText */, 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;
}
mWordComposer.setBatchInputWord(batchInputText);
mConnection.beginBatchEdit();
if (SPACE_STATE_PHANTOM == mSpaceState) {
- sendKeyCodePoint(Keyboard.CODE_SPACE);
+ sendKeyCodePoint(Constants.CODE_SPACE);
}
mConnection.setComposingText(batchInputText, 1);
mExpectingUpdateSelection = true;
@@ -1525,8 +1572,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
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;
@@ -1534,10 +1581,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// We have a TLD (or something that looks like this): make sure we don't add
// a space even if currently in phantom mode.
mSpaceState = SPACE_STATE_NONE;
+ // 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;
}
@@ -1551,7 +1599,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
private void handleBackspace(final int spaceState) {
- // In many cases, we may have to put the keyboard in auto-shift state again.
+ // 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
+ // during key repeat.
mHandler.postUpdateShiftState();
if (mWordComposer.isComposingWord()) {
@@ -1639,7 +1689,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
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
@@ -1668,7 +1718,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Sanity check
throw new RuntimeException("Should not be composing here");
}
- sendKeyCodePoint(Keyboard.CODE_SPACE);
+ sendKeyCodePoint(Constants.CODE_SPACE);
}
// NOTE: isCursorTouchingWord() is a blocking IPC call, so it often takes several
@@ -1682,7 +1732,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// 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
@@ -1691,15 +1741,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
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
@@ -1746,11 +1795,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (SPACE_STATE_PHANTOM == spaceState &&
mCurrentSettings.isPhantomSpacePromotingSymbol(primaryCode)) {
- sendKeyCodePoint(Keyboard.CODE_SPACE);
+ sendKeyCodePoint(Constants.CODE_SPACE);
}
sendKeyCodePoint(primaryCode);
- if (Keyboard.CODE_SPACE == primaryCode) {
+ if (Constants.CODE_SPACE == primaryCode) {
if (mCurrentSettings.isSuggestionsRequested(mDisplayOrientation)) {
if (maybeDoubleSpace()) {
mSpaceState = SPACE_STATE_DOUBLE;
@@ -1791,11 +1840,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
Utils.Stats.onSeparator((char)primaryCode, x, y);
}
- mHandler.postUpdateShiftState();
+ mKeyboardSwitcher.updateShiftState();
return didAutoCorrect;
}
- private CharSequence getTextWithUnderline(final CharSequence text) {
+ private CharSequence getTextWithUnderline(final String text) {
return mIsAutoCorrectionIndicatorOn
? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(this, text)
: text;
@@ -1812,7 +1861,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// 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();
@@ -1849,6 +1898,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mIsAutoCorrectionIndicatorOn = newAutoCorrectionIndicator;
final CharSequence textWithUnderline =
getTextWithUnderline(mWordComposer.getTypedWord());
+ // TODO: when called from an updateSuggestionStrip() call that results from a posted
+ // message, this is called outside any batch edit. Potentially, this may result in some
+ // janky flickering of the screen, although the display speed makes it unlikely in
+ // the practice.
mConnection.setComposingText(textWithUnderline, 1);
}
}
@@ -1877,21 +1930,25 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
private SuggestedWords getSuggestedWords(final int sessionId) {
+ final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
+ if (keyboard == null || mSuggest == null) {
+ return SuggestedWords.EMPTY;
+ }
final String typedWord = mWordComposer.getTypedWord();
// Get the word on which we should search the bigrams. If we are composing a word, it's
// 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,
- prevWord, mKeyboardSwitcher.getKeyboard().getProximityInfo(),
- mCurrentSettings.mCorrectionEnabled, sessionId);
+ prevWord, keyboard.getProximityInfo(), mCurrentSettings.mCorrectionEnabled,
+ sessionId);
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
@@ -1921,21 +1978,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
- 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();
@@ -1949,9 +2001,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
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)) {
@@ -1982,7 +2034,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// 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()) {
@@ -2007,7 +2059,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
int firstChar = Character.codePointAt(suggestion, 0);
if ((!mCurrentSettings.isWeakSpaceStripper(firstChar))
&& (!mCurrentSettings.isWeakSpaceSwapper(firstChar))) {
- sendKeyCodePoint(Keyboard.CODE_SPACE);
+ sendKeyCodePoint(Constants.CODE_SPACE);
}
}
@@ -2052,7 +2104,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
&& !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) {
@@ -2067,13 +2119,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
/**
* 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
@@ -2092,7 +2144,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
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;
@@ -2107,19 +2159,18 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
= 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;
}
@@ -2144,9 +2195,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
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);
@@ -2156,9 +2207,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
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
@@ -2192,7 +2243,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// 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.
@@ -2208,13 +2259,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
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
@@ -2231,19 +2275,20 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// 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.
+ // TODO: use getCodePointBeforeCursor instead to improve performance
final CharSequence lastChar = mConnection.getTextBeforeCursor(1, 0);
if (!TextUtils.isEmpty(lastChar) && Character.isHighSurrogate(lastChar.charAt(0))) {
mConnection.deleteSurroundingText(1, 0);
@@ -2259,7 +2304,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
mSubtypeSwitcher.onNetworkStateChanged(intent);
} else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
- mFeedbackManager.onRingerModeChanged();
+ mKeyboardSwitcher.onRingerModeChanged();
}
}
};
diff --git a/java/src/com/android/inputmethod/latin/LatinImeLogger.java b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
index 9eab19c49..394a9c7aa 100644
--- a/java/src/com/android/inputmethod/latin/LatinImeLogger.java
+++ b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
@@ -21,7 +21,7 @@ import android.view.inputmethod.EditorInfo;
import com.android.inputmethod.keyboard.Keyboard;
-public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChangeListener {
+public final class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChangeListener {
public static boolean sDBG = false;
public static boolean sVISUALDEBUG = false;
diff --git a/java/src/com/android/inputmethod/latin/ResizableIntArray.java b/java/src/com/android/inputmethod/latin/ResizableIntArray.java
index c660f92c4..9a46f160b 100644
--- a/java/src/com/android/inputmethod/latin/ResizableIntArray.java
+++ b/java/src/com/android/inputmethod/latin/ResizableIntArray.java
@@ -19,7 +19,7 @@ package com.android.inputmethod.latin;
import java.util.Arrays;
// TODO: This class is not thread-safe.
-public class ResizableIntArray {
+public final class ResizableIntArray {
private int[] mArray;
private int mLength;
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 28c0c0f16..53aabd9e8 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;
@@ -41,10 +40,11 @@ import java.util.regex.Pattern;
* all the time to find out what text is in the buffer, when we need it to determine caps mode
* for example.
*/
-public class RichInputConnection {
+public final class RichInputConnection {
private static final String TAG = RichInputConnection.class.getSimpleName();
private static final boolean DBG = false;
private static final boolean DEBUG_PREVIOUS_TEXT = false;
+ private static final boolean DEBUG_BATCH_NESTING = false;
// Provision for a long word pair and a separator
private static final int LOOKBACK_CHARACTER_NUM = BinaryDictionary.MAX_WORD_LENGTH * 2 + 1;
private static final Pattern spaceRegex = Pattern.compile("\\s+");
@@ -128,7 +128,7 @@ public class RichInputConnection {
Log.e(TAG, "Nest level too deep : " + mNestLevel);
}
}
- checkBatchEdit();
+ if (DEBUG_BATCH_NESTING) checkBatchEdit();
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
}
@@ -163,7 +163,11 @@ public class RichInputConnection {
}
public void finishComposingText() {
+<<<<<<< HEAD
checkBatchEdit();
+=======
+ if (DEBUG_BATCH_NESTING) checkBatchEdit();
+>>>>>>> goog/master
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
mCommittedTextBeforeComposingText.append(mComposingText);
mCurrentCursorPosition += mComposingText.length();
@@ -177,7 +181,11 @@ public class RichInputConnection {
}
public void commitText(final CharSequence text, final int i) {
+<<<<<<< HEAD
checkBatchEdit();
+=======
+ if (DEBUG_BATCH_NESTING) checkBatchEdit();
+>>>>>>> goog/master
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
mCommittedTextBeforeComposingText.append(text);
mCurrentCursorPosition += text.length() - mComposingText.length();
@@ -234,7 +242,14 @@ public class RichInputConnection {
hasSpaceBefore);
}
+ public int getCodePointBeforeCursor() {
+ if (mCommittedTextBeforeComposingText.length() < 1) return Constants.NOT_A_CODE;
+ return Character.codePointBefore(mCommittedTextBeforeComposingText,
+ mCommittedTextBeforeComposingText.length());
+ }
+
public CharSequence getTextBeforeCursor(final int i, final int j) {
+ // TODO: use mCommittedTextBeforeComposingText if possible to improve performance
mIC = mParent.getCurrentInputConnection();
if (null != mIC) return mIC.getTextBeforeCursor(i, j);
return null;
@@ -247,7 +262,11 @@ public class RichInputConnection {
}
public void deleteSurroundingText(final int i, final int j) {
+<<<<<<< HEAD
checkBatchEdit();
+=======
+ if (DEBUG_BATCH_NESTING) checkBatchEdit();
+>>>>>>> goog/master
final int remainingChars = mComposingText.length() - i;
if (remainingChars >= 0) {
mComposingText.setLength(remainingChars);
@@ -283,7 +302,11 @@ public class RichInputConnection {
}
public void sendKeyEvent(final KeyEvent keyEvent) {
+<<<<<<< HEAD
checkBatchEdit();
+=======
+ if (DEBUG_BATCH_NESTING) checkBatchEdit();
+>>>>>>> goog/master
if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
// This method is only called for enter or backspace when speaking to old
@@ -331,7 +354,11 @@ public class RichInputConnection {
}
public void setComposingText(final CharSequence text, final int i) {
+<<<<<<< HEAD
checkBatchEdit();
+=======
+ if (DEBUG_BATCH_NESTING) checkBatchEdit();
+>>>>>>> goog/master
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
mCurrentCursorPosition += text.length() - mComposingText.length();
mComposingText.setLength(0);
@@ -347,7 +374,11 @@ public class RichInputConnection {
}
public void setSelection(final int from, final int to) {
+<<<<<<< HEAD
checkBatchEdit();
+=======
+ if (DEBUG_BATCH_NESTING) checkBatchEdit();
+>>>>>>> goog/master
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
if (null != mIC) {
mIC.setSelection(from, to);
@@ -361,7 +392,11 @@ public class RichInputConnection {
}
public void commitCorrection(final CorrectionInfo correctionInfo) {
+<<<<<<< HEAD
checkBatchEdit();
+=======
+ if (DEBUG_BATCH_NESTING) checkBatchEdit();
+>>>>>>> goog/master
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
// This has no effect on the text field and does not change its content. It only makes
// TextView flash the text for a second based on indices contained in the argument.
@@ -375,7 +410,11 @@ public class RichInputConnection {
}
public void commitCompletion(final CompletionInfo completionInfo) {
+<<<<<<< HEAD
checkBatchEdit();
+=======
+ if (DEBUG_BATCH_NESTING) checkBatchEdit();
+>>>>>>> goog/master
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
final CharSequence text = completionInfo.getText();
mCommittedTextBeforeComposingText.append(text);
@@ -390,7 +429,7 @@ public class RichInputConnection {
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
}
- public CharSequence getNthPreviousWord(final String sentenceSeperators, final int n) {
+ 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);
@@ -415,7 +454,7 @@ public class RichInputConnection {
/**
* Represents a range of text, relative to the current cursor position.
*/
- public static class Range {
+ public static final class Range {
/** Characters before selection start */
public final int mCharsBefore;
@@ -458,19 +497,22 @@ public 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;
}
/**
@@ -503,19 +545,20 @@ public 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
@@ -532,7 +575,7 @@ public 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;
@@ -550,7 +593,7 @@ public 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);
@@ -561,8 +604,8 @@ public 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;
@@ -575,10 +618,10 @@ public class RichInputConnection {
}
public void removeTrailingSpace() {
- checkBatchEdit();
+ 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);
}
}
@@ -604,7 +647,7 @@ public 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;
@@ -631,7 +674,7 @@ public class RichInputConnection {
}
public boolean revertDoubleSpace() {
- checkBatchEdit();
+ if (DEBUG_BATCH_NESTING) checkBatchEdit();
// Here we test whether we indeed have a period and a space before us. This should not
// be needed, but it's there just in case something went wrong.
final CharSequence textBeforeCursor = getTextBeforeCursor(2, 0);
@@ -649,14 +692,14 @@ public class RichInputConnection {
}
public boolean revertSwapPunctuation() {
- checkBatchEdit();
+ if (DEBUG_BATCH_NESTING) checkBatchEdit();
// Here we test whether we indeed have a space and something else before us. This should not
// be needed, but it's there just in case something went wrong.
final CharSequence textBeforeCursor = getTextBeforeCursor(2, 0);
// 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/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 9479a88a7..238724610 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -39,12 +39,10 @@ import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import com.android.inputmethod.latin.define.ProductionFlag;
-import com.android.inputmethod.research.ResearchLogger;
import com.android.inputmethodcommon.InputMethodSettingsFragment;
public final class Settings extends InputMethodSettingsFragment
implements SharedPreferences.OnSharedPreferenceChangeListener {
- public static final boolean ENABLE_INTERNAL_SETTINGS = ProductionFlag.IS_INTERNAL;
// In the same order as xml/prefs.xml
public static final String PREF_GENERAL_SETTINGS = "general_settings";
@@ -58,7 +56,6 @@ public final class Settings extends InputMethodSettingsFragment
public static final String PREF_AUTO_CORRECTION_THRESHOLD = "auto_correction_threshold";
public static final String PREF_SHOW_SUGGESTIONS_SETTING = "show_suggestions_setting";
public static final String PREF_MISC_SETTINGS = "misc_settings";
- public static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode";
public static final String PREF_LAST_USER_DICTIONARY_WRITE_TIME =
"last_user_dictionary_write_time";
public static final String PREF_ADVANCED_SETTINGS = "pref_advanced_settings";
@@ -77,8 +74,8 @@ public final class Settings extends InputMethodSettingsFragment
public static final String PREF_KEYPRESS_SOUND_VOLUME =
"pref_keypress_sound_volume";
public static final String PREF_GESTURE_PREVIEW_TRAIL = "pref_gesture_preview_trail";
- public static final String PREF_SHOW_GESTURE_FLOATING_PREVIEW_TEXT =
- "pref_show_gesture_floating_preview_text";
+ public static final String PREF_GESTURE_FLOATING_PREVIEW_TEXT =
+ "pref_gesture_floating_preview_text";
public static final String PREF_INPUT_LANGUAGE = "input_language";
public static final String PREF_SELECTED_LANGUAGES = "selected_languages";
@@ -133,14 +130,6 @@ public final class Settings extends InputMethodSettingsFragment
mAutoCorrectionThresholdPreference =
(ListPreference) findPreference(PREF_AUTO_CORRECTION_THRESHOLD);
mBigramPrediction = (CheckBoxPreference) findPreference(PREF_BIGRAM_PREDICTIONS);
- mDebugSettingsPreference = findPreference(PREF_DEBUG_SETTINGS);
- if (mDebugSettingsPreference != null) {
- final Intent debugSettingsIntent = new Intent(Intent.ACTION_MAIN);
- debugSettingsIntent.setClassName(
- context.getPackageName(), DebugSettings.class.getName());
- mDebugSettingsPreference.setIntent(debugSettingsIntent);
- }
-
ensureConsistencyOfAutoCorrectionSettings();
final PreferenceGroup generalSettings =
@@ -150,6 +139,18 @@ public final class Settings extends InputMethodSettingsFragment
final PreferenceGroup miscSettings =
(PreferenceGroup) findPreference(PREF_MISC_SETTINGS);
+ mDebugSettingsPreference = findPreference(PREF_DEBUG_SETTINGS);
+ if (mDebugSettingsPreference != null) {
+ if (ProductionFlag.IS_INTERNAL) {
+ final Intent debugSettingsIntent = new Intent(Intent.ACTION_MAIN);
+ debugSettingsIntent.setClassName(
+ context.getPackageName(), DebugSettingsActivity.class.getName());
+ mDebugSettingsPreference.setIntent(debugSettingsIntent);
+ } else {
+ miscSettings.removePreference(mDebugSettingsPreference);
+ }
+ }
+
final boolean showVoiceKeyOption = res.getBoolean(
R.bool.config_enable_show_voice_key_option);
if (!showVoiceKeyOption) {
@@ -207,7 +208,7 @@ public final class Settings extends InputMethodSettingsFragment
R.bool.config_gesture_input_enabled_by_build_config);
final Preference gesturePreviewTrail = findPreference(PREF_GESTURE_PREVIEW_TRAIL);
final Preference gestureFloatingPreviewText = findPreference(
- PREF_SHOW_GESTURE_FLOATING_PREVIEW_TEXT);
+ PREF_GESTURE_FLOATING_PREVIEW_TEXT);
if (!gestureInputEnabledByBuildConfig) {
miscSettings.removePreference(findPreference(PREF_GESTURE_INPUT));
miscSettings.removePreference(gesturePreviewTrail);
@@ -218,24 +219,6 @@ public final class Settings extends InputMethodSettingsFragment
setPreferenceEnabled(gestureFloatingPreviewText, gestureInputEnabledByUser);
}
- final boolean showUsabilityStudyModeOption =
- res.getBoolean(R.bool.config_enable_usability_study_mode_option)
- || ProductionFlag.IS_EXPERIMENTAL || ENABLE_INTERNAL_SETTINGS;
- final Preference usabilityStudyPref = findPreference(PREF_USABILITY_STUDY_MODE);
- if (!showUsabilityStudyModeOption) {
- if (usabilityStudyPref != null) {
- miscSettings.removePreference(usabilityStudyPref);
- }
- }
- if (ProductionFlag.IS_EXPERIMENTAL) {
- if (usabilityStudyPref instanceof CheckBoxPreference) {
- CheckBoxPreference checkbox = (CheckBoxPreference)usabilityStudyPref;
- checkbox.setChecked(prefs.getBoolean(PREF_USABILITY_STUDY_MODE,
- ResearchLogger.DEFAULT_USABILITY_STUDY_MODE));
- checkbox.setSummary(R.string.settings_warning_researcher_mode);
- }
- }
-
mKeypressVibrationDurationSettingsPref =
(PreferenceScreen) findPreference(PREF_VIBRATION_DURATION_SETTINGS);
if (mKeypressVibrationDurationSettingsPref != null) {
@@ -304,7 +287,7 @@ public final class Settings extends InputMethodSettingsFragment
PREF_GESTURE_INPUT, true);
setPreferenceEnabled(findPreference(PREF_GESTURE_PREVIEW_TRAIL),
gestureInputEnabledByUser);
- setPreferenceEnabled(findPreference(PREF_SHOW_GESTURE_FLOATING_PREVIEW_TEXT),
+ setPreferenceEnabled(findPreference(PREF_GESTURE_FLOATING_PREVIEW_TEXT),
gestureInputEnabledByUser);
}
}
diff --git a/java/src/com/android/inputmethod/latin/SettingsActivity.java b/java/src/com/android/inputmethod/latin/SettingsActivity.java
index 68f8582fc..0d3c8ebb7 100644
--- a/java/src/com/android/inputmethod/latin/SettingsActivity.java
+++ b/java/src/com/android/inputmethod/latin/SettingsActivity.java
@@ -19,7 +19,7 @@ package com.android.inputmethod.latin;
import android.content.Intent;
import android.preference.PreferenceActivity;
-public class SettingsActivity extends PreferenceActivity {
+public final class SettingsActivity extends PreferenceActivity {
private static final String DEFAULT_FRAGMENT = Settings.class.getName();
@Override
diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java
index 9d8379a7a..2a778aa0d 100644
--- a/java/src/com/android/inputmethod/latin/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -178,7 +178,7 @@ public final class SettingsValues {
&& prefs.getBoolean(Settings.PREF_GESTURE_INPUT, true);
mGesturePreviewTrailEnabled = prefs.getBoolean(Settings.PREF_GESTURE_PREVIEW_TRAIL, true);
mGestureFloatingPreviewTextEnabled = prefs.getBoolean(
- Settings.PREF_SHOW_GESTURE_FLOATING_PREVIEW_TEXT, false);
+ Settings.PREF_GESTURE_FLOATING_PREVIEW_TEXT, true);
mCorrectionEnabled = mAutoCorrectEnabled && !mInputAttributes.mInputTypeNoAutoCorrect;
mSuggestionVisibility = createSuggestionVisibility(res);
}
@@ -254,11 +254,13 @@ public final class SettingsValues {
return mSymbolsExcludedFromWordSeparators.contains(String.valueOf((char)code));
}
+ // TODO: use "Phantom" instead of "Weak" in this method name
public boolean isWeakSpaceStripper(final int code) {
// TODO: this does not work if the code does not fit in a char
return mWeakSpaceStrippers.contains(String.valueOf((char)code));
}
+ // TODO: use "Phantom" instead of "Weak" in this method name
public boolean isWeakSpaceSwapper(final int code) {
// TODO: this does not work if the code does not fit in a char
return mWeakSpaceSwappers.contains(String.valueOf((char)code));
@@ -410,7 +412,7 @@ public final class SettingsValues {
// Likewise
public static boolean getUsabilityStudyMode(final SharedPreferences prefs) {
// TODO: use mUsabilityStudyMode instead of reading it again here
- return prefs.getBoolean(Settings.PREF_USABILITY_STUDY_MODE, true);
+ return prefs.getBoolean(DebugSettings.PREF_USABILITY_STUDY_MODE, true);
}
public static long getLastUserHistoryWriteTime(final SharedPreferences prefs,
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
index 7b65b7343..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;
}
@@ -250,15 +185,19 @@ public final class StringUtils {
// Step 3 : Search for the start of a paragraph. From the starting point computed in step 2,
// we go back over any space or tab char sitting there. We find the start of a paragraph
- // if the first char that's not a space or tab is a start of line (as in, either \n or
- // start of text).
+ // 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 = Constants.CODE_SPACE;
if (hasSpaceBefore) --j;
- while (j > 0 && Character.isWhitespace(cs.charAt(j - 1))) {
+ while (j > 0) {
+ prevChar = cs.charAt(j - 1);
+ if (!Character.isSpaceChar(prevChar) && prevChar != Constants.CODE_TAB) break;
j--;
}
- if (j == 0) {
- // There is only whitespace between the start of the text and the cursor. Both
+ if (j <= 0 || Character.isWhitespace(prevChar)) {
+ // There are only spacing chars between the start of the paragraph and the cursor,
+ // defined as a isWhitespace() char that is neither a isSpaceChar() nor a tab. Both
// MODE_WORDS and MODE_SENTENCES should be active.
return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS
| TextUtils.CAP_MODE_SENTENCES) & reqModes;
@@ -292,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;
}
@@ -306,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;
}
@@ -359,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;
@@ -375,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 de5f515b0..579f96bb4 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeLocale.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeLocale.java
@@ -30,7 +30,7 @@ import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
import java.util.HashMap;
import java.util.Locale;
-public class SubtypeLocale {
+public final class SubtypeLocale {
static final String TAG = SubtypeLocale.class.getSimpleName();
// This class must be located in the same package as LatinIME.java.
private static final String RESOURCE_PACKAGE_NAME =
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index c693edcca..8e51a372b 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -38,7 +38,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
-public class SubtypeSwitcher {
+public final class SubtypeSwitcher {
private static boolean DBG = LatinImeLogger.sDBG;
private static final String TAG = SubtypeSwitcher.class.getSimpleName();
@@ -60,7 +60,7 @@ public class SubtypeSwitcher {
private boolean mIsNetworkConnected;
- static class NeedsToDisplayLanguage {
+ static final class NeedsToDisplayLanguage {
private int mEnabledSubtypeCount;
private boolean mIsSystemLanguageSameAsInputLanguage;
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 278c4b9ce..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;
@@ -34,11 +34,11 @@ import java.util.concurrent.ConcurrentHashMap;
* This class loads a dictionary and provides a list of suggestions for a given sequence of
* characters. This includes corrections and completions.
*/
-public class Suggest {
+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 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 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 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 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 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 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 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 class Suggest {
}
for (SuggestedWordInfo wordInfo : suggestionsSet) {
- LatinImeLogger.onAddSuggestedWord(wordInfo.mWord.toString(), wordInfo.mSourceDict);
+ LatinImeLogger.onAddSuggestedWord(wordInfo.mWord, wordInfo.mSourceDict);
}
final ArrayList<SuggestedWordInfo> suggestionsContainer =
@@ -362,7 +363,8 @@ public class Suggest {
return suggestionsList;
}
- private static class SuggestedWordInfoComparator implements Comparator<SuggestedWordInfo> {
+ private static final class SuggestedWordInfoComparator
+ implements Comparator<SuggestedWordInfo> {
// This comparator ranks the word info with the higher frequency first. That's because
// that's the order we want our elements in.
@Override
@@ -371,7 +373,7 @@ public 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 =
@@ -382,16 +384,17 @@ public 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 d9f48c4a4..572f2906e 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -23,7 +23,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
-public class SuggestedWords {
+public final class SuggestedWords {
private static final ArrayList<SuggestedWordInfo> EMPTY_WORD_INFO_LIST =
CollectionUtils.newArrayList(0);
public static final SuggestedWords EMPTY = new SuggestedWords(
@@ -53,6 +53,10 @@ public class SuggestedWords {
mIsPrediction = isPrediction;
}
+ public boolean isEmpty() {
+ return mSuggestedWordInfoList.isEmpty();
+ }
+
public int size() {
return mSuggestedWordInfoList.size();
}
@@ -86,11 +90,14 @@ public 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 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 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);
@@ -117,7 +124,7 @@ public class SuggestedWords {
return suggestionsList;
}
- public static class SuggestedWordInfo {
+ public static final class SuggestedWordInfo {
public static final int MAX_SCORE = Integer.MAX_VALUE;
public static final int KIND_TYPED = 0; // What user typed
public static final int KIND_CORRECTION = 1; // Simple correction/suggestion
@@ -135,9 +142,9 @@ public 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 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 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/SuggestionSpanPickedNotificationReceiver.java b/java/src/com/android/inputmethod/latin/SuggestionSpanPickedNotificationReceiver.java
index 4a3f42d5d..d188fc5ef 100644
--- a/java/src/com/android/inputmethod/latin/SuggestionSpanPickedNotificationReceiver.java
+++ b/java/src/com/android/inputmethod/latin/SuggestionSpanPickedNotificationReceiver.java
@@ -23,7 +23,7 @@ import android.content.Context;
import android.content.Intent;
import android.util.Log;
-public class SuggestionSpanPickedNotificationReceiver extends BroadcastReceiver {
+public final class SuggestionSpanPickedNotificationReceiver extends BroadcastReceiver {
private static final boolean DBG = LatinImeLogger.sDBG;
private static final String TAG =
SuggestionSpanPickedNotificationReceiver.class.getSimpleName();
diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java
index bdd988df2..ec4dc1436 100644
--- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java
@@ -24,7 +24,7 @@ import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import java.util.ArrayList;
import java.util.Locale;
-public class SynchronouslyLoadedContactsBinaryDictionary extends ContactsBinaryDictionary {
+public final class SynchronouslyLoadedContactsBinaryDictionary extends ContactsBinaryDictionary {
private boolean mClosed;
public SynchronouslyLoadedContactsBinaryDictionary(final Context context, final Locale locale) {
@@ -33,13 +33,13 @@ public class SynchronouslyLoadedContactsBinaryDictionary extends ContactsBinaryD
@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 b8cfddd4e..4bdaf2039 100644
--- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
@@ -23,7 +23,7 @@ import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import java.util.ArrayList;
-public class SynchronouslyLoadedUserBinaryDictionary extends UserBinaryDictionary {
+public final class SynchronouslyLoadedUserBinaryDictionary extends UserBinaryDictionary {
public SynchronouslyLoadedUserBinaryDictionary(final Context context, final String locale) {
this(context, locale, false);
@@ -36,13 +36,13 @@ public class SynchronouslyLoadedUserBinaryDictionary extends UserBinaryDictionar
@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/TargetApplicationGetter.java b/java/src/com/android/inputmethod/latin/TargetApplicationGetter.java
index 4265309e5..743a8c60f 100644
--- a/java/src/com/android/inputmethod/latin/TargetApplicationGetter.java
+++ b/java/src/com/android/inputmethod/latin/TargetApplicationGetter.java
@@ -22,8 +22,7 @@ import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.util.LruCache;
-public class TargetApplicationGetter extends AsyncTask<String, Void, ApplicationInfo> {
-
+public final class TargetApplicationGetter extends AsyncTask<String, Void, ApplicationInfo> {
private static final int MAX_CACHE_ENTRIES = 64; // arbitrary
private static LruCache<String, ApplicationInfo> sCache =
new LruCache<String, ApplicationInfo>(MAX_CACHE_ENTRIES);
@@ -32,6 +31,7 @@ public class TargetApplicationGetter extends AsyncTask<String, Void, Application
if (null == packageName) return null;
return sCache.get(packageName);
}
+
public static void removeApplicationInfoCache(final String packageName) {
sCache.remove(packageName);
}
diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index 60e6fa127..00c3cbe0a 100644
--- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
@@ -200,7 +200,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 4a3d11aa1..787197755 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;
@@ -38,7 +39,7 @@ import java.util.Map;
*
* All the methods in this class are static.
*/
-public class UserHistoryDictIOUtils {
+public final class UserHistoryDictIOUtils {
private static final String TAG = UserHistoryDictIOUtils.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -100,6 +101,11 @@ public class UserHistoryDictIOUtils {
@Override
public int limit() {
+ return mBuffer.length - 1;
+ }
+
+ @Override
+ public int capacity() {
return mBuffer.length;
}
}
@@ -110,11 +116,10 @@ public class UserHistoryDictIOUtils {
public static void writeDictionaryBinary(final OutputStream destination,
final BigramDictionaryInterface dict, final UserHistoryDictionaryBigramList bigrams,
final FormatOptions formatOptions) {
-
final FusionDictionary fusionDict = constructFusionDictionary(dict, bigrams);
-
try {
BinaryDictInputOutput.writeDictionaryBinary(destination, fusionDict, formatOptions);
+ Log.d(TAG, "end writing");
} catch (IOException e) {
Log.e(TAG, "IO exception while writing file: " + e);
} catch (UnsupportedFormatException e) {
@@ -125,18 +130,21 @@ public 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, false));
-
+ new FusionDictionary.DictionaryOptions(new HashMap<String, String>(), false,
+ false));
+ int profTotal = 0;
for (final String word1 : bigrams.keySet()) {
final HashMap<String, Byte> word1Bigrams = bigrams.getBigrams(word1);
for (final String word2 : word1Bigrams.keySet()) {
final int freq = dict.getFrequency(word1, word2);
-
+ if (freq == -1) {
+ // don't add this bigram.
+ continue;
+ }
if (DEBUG) {
if (word1 == null) {
Log.d(TAG, "add unigram: " + word2 + "," + Integer.toString(freq));
@@ -144,17 +152,22 @@ public class UserHistoryDictIOUtils {
Log.d(TAG, "add bigram: " + word1
+ "," + word2 + "," + Integer.toString(freq));
}
+ profTotal++;
}
-
if (word1 == null) { // unigram
fusionDict.add(word2, freq, null, false /* isNotAWord */);
} else { // bigram
+ if (FusionDictionary.findWordInTree(fusionDict.mRoot, word1) == null) {
+ fusionDict.add(word1, 2, null, false /* isNotAWord */);
+ }
fusionDict.setBigram(word1, word2, freq);
}
bigrams.updateBigram(word1, word2, (byte)freq);
}
}
-
+ if (DEBUG) {
+ Log.d(TAG, "add " + profTotal + "words");
+ }
return fusionDict;
}
@@ -166,32 +179,31 @@ public class UserHistoryDictIOUtils {
final Map<Integer, String> unigrams = CollectionUtils.newTreeMap();
final Map<Integer, Integer> frequencies = CollectionUtils.newTreeMap();
final Map<Integer, ArrayList<PendingAttribute>> bigrams = CollectionUtils.newTreeMap();
-
try {
BinaryDictIOUtils.readUnigramsAndBigramsBinary(buffer, unigrams, frequencies,
bigrams);
- addWordsFromWordMap(unigrams, frequencies, bigrams, dict);
} catch (IOException e) {
Log.e(TAG, "IO exception while reading file: " + e);
} catch (UnsupportedFormatException e) {
Log.e(TAG, "Unsupported format: " + e);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ Log.e(TAG, "ArrayIndexOutOfBoundsException while reading file: " + e);
}
+ addWordsFromWordMap(unigrams, frequencies, bigrams, dict);
}
/**
* 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()) {
final String word1 = entry.getValue();
final int unigramFrequency = frequencies.get(entry.getKey());
to.setUnigram(word1, null, unigramFrequency);
-
final ArrayList<PendingAttribute> attrList = bigrams.get(entry.getKey());
-
if (attrList != null) {
for (final PendingAttribute attr : attrList) {
to.setBigram(word1, unigrams.get(attr.mAddress),
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
index 683ee4f5c..4fd9bfafb 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
@@ -16,24 +16,26 @@
package com.android.inputmethod.latin;
-import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.database.sqlite.SQLiteQueryBuilder;
import android.os.AsyncTask;
-import android.provider.BaseColumns;
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;
+import com.android.inputmethod.latin.UserHistoryDictIOUtils.OnAddWordListener;
import com.android.inputmethod.latin.UserHistoryForgettingCurveUtils.ForgettingCurveParams;
+import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
@@ -41,47 +43,29 @@ import java.util.concurrent.locks.ReentrantLock;
* Locally gathers stats about the words user types and various other signals like auto-correction
* cancellation or manual picks. This allows the keyboard to adapt to the typist over time.
*/
-public class UserHistoryDictionary extends ExpandableDictionary {
- private static final String TAG = "UserHistoryDictionary";
+public final class UserHistoryDictionary extends ExpandableDictionary {
+ private static final String TAG = UserHistoryDictionary.class.getSimpleName();
+ private static final String NAME = UserHistoryDictionary.class.getSimpleName();
public static final boolean DBG_SAVE_RESTORE = false;
public static final boolean DBG_STRESS_TEST = false;
public static final boolean DBG_ALWAYS_WRITE = false;
public static final boolean PROFILE_SAVE_RESTORE = LatinImeLogger.sDBG;
+ private static final FormatOptions VERSION3 = new FormatOptions(3,
+ true /* supportsDynamicUpdate */);
+
/** Any pair being typed or picked */
private static final int FREQUENCY_FOR_TYPED = 2;
/** Maximum number of pairs. Pruning will start when databases goes above this number. */
- public static final int sMaxHistoryBigrams = 10000;
+ public static final int MAX_HISTORY_BIGRAMS = 10000;
/**
* When it hits maximum bigram pair, it will delete until you are left with
* only (sMaxHistoryBigrams - sDeleteHistoryBigrams) pairs.
* Do not keep this number small to avoid deleting too often.
*/
- public static final int sDeleteHistoryBigrams = 1000;
-
- /**
- * Database version should increase if the database structure changes
- */
- private static final int DATABASE_VERSION = 1;
-
- private static final String DATABASE_NAME = "userbigram_dict.db";
-
- /** Name of the words table in the database */
- private static final String MAIN_TABLE_NAME = "main";
- // TODO: Consume less space by using a unique id for locale instead of the whole
- // 2-5 character string.
- private static final String MAIN_COLUMN_ID = BaseColumns._ID;
- private static final String MAIN_COLUMN_WORD1 = "word1";
- private static final String MAIN_COLUMN_WORD2 = "word2";
- private static final String MAIN_COLUMN_LOCALE = "locale";
-
- /** Name of the frequency table in the database */
- private static final String FREQ_TABLE_NAME = "frequency";
- private static final String FREQ_COLUMN_ID = BaseColumns._ID;
- private static final String FREQ_COLUMN_PAIR_ID = "pair_id";
- private static final String COLUMN_FORGETTING_CURVE_VALUE = "freq";
+ public static final int DELETE_HISTORY_BIGRAMS = 1000;
/** Locale for which this user history dictionary is storing words */
private final String mLocale;
@@ -91,29 +75,13 @@ public class UserHistoryDictionary extends ExpandableDictionary {
private final ReentrantLock mBigramListLock = new ReentrantLock();
private final SharedPreferences mPrefs;
- private final static HashMap<String, String> sDictProjectionMap;
- private final static ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>
- sLangDictCache = CollectionUtils.newConcurrentHashMap();
-
- static {
- sDictProjectionMap = CollectionUtils.newHashMap();
- sDictProjectionMap.put(MAIN_COLUMN_ID, MAIN_COLUMN_ID);
- sDictProjectionMap.put(MAIN_COLUMN_WORD1, MAIN_COLUMN_WORD1);
- sDictProjectionMap.put(MAIN_COLUMN_WORD2, MAIN_COLUMN_WORD2);
- sDictProjectionMap.put(MAIN_COLUMN_LOCALE, MAIN_COLUMN_LOCALE);
-
- sDictProjectionMap.put(FREQ_COLUMN_ID, FREQ_COLUMN_ID);
- sDictProjectionMap.put(FREQ_COLUMN_PAIR_ID, FREQ_COLUMN_PAIR_ID);
- sDictProjectionMap.put(COLUMN_FORGETTING_CURVE_VALUE, COLUMN_FORGETTING_CURVE_VALUE);
- }
-
- private static DatabaseHelper sOpenHelper = null;
+ // Should always be false except when we use this class for test
+ @UsedForTesting boolean isTest = false;
- public String getLocale() {
- return mLocale;
- }
+ private static final ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>
+ sLangDictCache = CollectionUtils.newConcurrentHashMap();
- public synchronized static UserHistoryDictionary getInstance(
+ public static synchronized UserHistoryDictionary getInstance(
final Context context, final String locale, final SharedPreferences sp) {
if (sLangDictCache.containsKey(locale)) {
final SoftReference<UserHistoryDictionary> ref = sLangDictCache.get(locale);
@@ -136,9 +104,6 @@ public class UserHistoryDictionary extends ExpandableDictionary {
super(context, Dictionary.TYPE_USER_HISTORY);
mLocale = locale;
mPrefs = sp;
- if (sOpenHelper == null) {
- sOpenHelper = new DatabaseHelper(getContext());
- }
if (mLocale != null && mLocale.length() > 1) {
loadDictionary();
}
@@ -158,7 +123,7 @@ public 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;
@@ -168,7 +133,7 @@ public 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;
}
@@ -181,7 +146,7 @@ public 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;
@@ -190,6 +155,7 @@ public class UserHistoryDictionary extends ExpandableDictionary {
try {
super.addWord(
word2, null /* the "shortcut" parameter is null */, FREQUENCY_FOR_TYPED);
+ mBigramList.addBigram(null, word2, (byte)FREQUENCY_FOR_TYPED);
// Do not insert a word as a bigram of itself
if (word2.equals(word1)) {
return 0;
@@ -210,7 +176,7 @@ public 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)) {
@@ -227,11 +193,8 @@ public class UserHistoryDictionary extends ExpandableDictionary {
* Schedules a background thread to write any pending words to the database.
*/
private void flushPendingWrites() {
- if (mBigramListLock.isLocked()) {
- return;
- }
// Create a background thread to write the pending entries
- new UpdateDbTask(sOpenHelper, mBigramList, mLocale, this, mPrefs).execute();
+ new UpdateBinaryTask(mBigramList, mLocale, this, mPrefs, getContext()).execute();
}
@Override
@@ -245,6 +208,8 @@ public class UserHistoryDictionary extends ExpandableDictionary {
}
}
+ private int profTotal;
+
private void loadDictionaryAsyncLocked() {
if (DBG_STRESS_TEST) {
try {
@@ -257,343 +222,184 @@ public class UserHistoryDictionary extends ExpandableDictionary {
final long last = SettingsValues.getLastUserHistoryWriteTime(mPrefs, mLocale);
final boolean initializing = last == 0;
final long now = System.currentTimeMillis();
- // Load the words that correspond to the current input locale
- final Cursor cursor = query(MAIN_COLUMN_LOCALE + "=?", new String[] { mLocale });
- if (null == cursor) return;
- try {
- // TODO: Call SQLiteDataBase.beginTransaction / SQLiteDataBase.endTransaction
- if (cursor.moveToFirst()) {
- final int word1Index = cursor.getColumnIndex(MAIN_COLUMN_WORD1);
- final int word2Index = cursor.getColumnIndex(MAIN_COLUMN_WORD2);
- final int fcIndex = cursor.getColumnIndex(COLUMN_FORGETTING_CURVE_VALUE);
- while (!cursor.isAfterLast()) {
- final String word1 = cursor.getString(word1Index);
- final String word2 = cursor.getString(word2Index);
- final int fc = cursor.getInt(fcIndex);
+ profTotal = 0;
+ final String fileName = NAME + "." + mLocale + ".dict";
+ final ExpandableDictionary dictionary = this;
+ final OnAddWordListener listener = new OnAddWordListener() {
+ @Override
+ public void setUnigram(final String word, final String shortcutTarget,
+ final int frequency) {
+ profTotal++;
+ if (DBG_SAVE_RESTORE) {
+ Log.d(TAG, "load unigram: " + word + "," + frequency);
+ }
+ dictionary.addWord(word, shortcutTarget, frequency);
+ mBigramList.addBigram(null, word, (byte)frequency);
+ }
+
+ @Override
+ 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++;
if (DBG_SAVE_RESTORE) {
- Log.d(TAG, "--- Load user history: " + word1 + ", " + word2 + ","
- + mLocale + "," + this);
- }
- // Safeguard against adding really long words. Stack may overflow due
- // to recursive lookup
- if (null == word1) {
- super.addWord(word2, null /* shortcut */, fc);
- } else if (word1.length() < BinaryDictionary.MAX_WORD_LENGTH
- && word2.length() < BinaryDictionary.MAX_WORD_LENGTH) {
- super.setBigramAndGetFrequency(
- word1, word2, initializing ? new ForgettingCurveParams(true)
- : new ForgettingCurveParams(fc, now, last));
+ Log.d(TAG, "load bigram: " + word1 + "," + word2 + "," + frequency);
}
- mBigramList.addBigram(word1, word2, (byte)fc);
- cursor.moveToNext();
+ dictionary.setBigramAndGetFrequency(
+ word1, word2, initializing ? new ForgettingCurveParams(true)
+ : new ForgettingCurveParams(frequency, now, last));
}
+ mBigramList.addBigram(word1, word2, (byte)frequency);
}
+ };
+
+ // Load the dictionary from binary file
+ FileInputStream inStream = null;
+ try {
+ final File file = new File(getContext().getFilesDir(), fileName);
+ final byte[] buffer = new byte[(int)file.length()];
+ inStream = new FileInputStream(file);
+ inStream.read(buffer);
+ UserHistoryDictIOUtils.readDictionaryBinary(
+ new UserHistoryDictIOUtils.ByteArrayWrapper(buffer), listener);
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "when loading: file not found" + e);
+ } catch (IOException e) {
+ Log.e(TAG, "IOException when open bytebuffer: " + e);
} finally {
- cursor.close();
+ if (inStream != null) {
+ try {
+ inStream.close();
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
if (PROFILE_SAVE_RESTORE) {
final long diff = System.currentTimeMillis() - now;
- Log.w(TAG, "PROF: Load User HistoryDictionary: "
- + mLocale + ", " + diff + "ms.");
+ Log.d(TAG, "PROF: Load UserHistoryDictionary: "
+ + mLocale + ", " + diff + "ms. load " + profTotal + "entries.");
}
}
}
/**
- * Query the database
- */
- private static Cursor query(String selection, String[] selectionArgs) {
- SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
-
- // main INNER JOIN frequency ON (main._id=freq.pair_id)
- qb.setTables(MAIN_TABLE_NAME + " INNER JOIN " + FREQ_TABLE_NAME + " ON ("
- + MAIN_TABLE_NAME + "." + MAIN_COLUMN_ID + "=" + FREQ_TABLE_NAME + "."
- + FREQ_COLUMN_PAIR_ID +")");
-
- qb.setProjectionMap(sDictProjectionMap);
-
- // Get the database and run the query
- try {
- SQLiteDatabase db = sOpenHelper.getReadableDatabase();
- Cursor c = qb.query(db,
- new String[] {
- MAIN_COLUMN_WORD1, MAIN_COLUMN_WORD2, COLUMN_FORGETTING_CURVE_VALUE },
- selection, selectionArgs, null, null, null);
- return c;
- } catch (android.database.sqlite.SQLiteCantOpenDatabaseException e) {
- // Can't open the database : presumably we can't access storage. That may happen
- // when the device is wedged; do a best effort to still start the keyboard.
- return null;
- }
- }
-
- /**
- * This class helps open, create, and upgrade the database file.
- */
- private static class DatabaseHelper extends SQLiteOpenHelper {
-
- DatabaseHelper(Context context) {
- super(context, DATABASE_NAME, null, DATABASE_VERSION);
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- db.execSQL("PRAGMA foreign_keys = ON;");
- db.execSQL("CREATE TABLE " + MAIN_TABLE_NAME + " ("
- + MAIN_COLUMN_ID + " INTEGER PRIMARY KEY,"
- + MAIN_COLUMN_WORD1 + " TEXT,"
- + MAIN_COLUMN_WORD2 + " TEXT,"
- + MAIN_COLUMN_LOCALE + " TEXT"
- + ");");
- db.execSQL("CREATE TABLE " + FREQ_TABLE_NAME + " ("
- + FREQ_COLUMN_ID + " INTEGER PRIMARY KEY,"
- + FREQ_COLUMN_PAIR_ID + " INTEGER,"
- + COLUMN_FORGETTING_CURVE_VALUE + " INTEGER,"
- + "FOREIGN KEY(" + FREQ_COLUMN_PAIR_ID + ") REFERENCES " + MAIN_TABLE_NAME
- + "(" + MAIN_COLUMN_ID + ")" + " ON DELETE CASCADE"
- + ");");
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
- + newVersion + ", which will destroy all old data");
- db.execSQL("DROP TABLE IF EXISTS " + MAIN_TABLE_NAME);
- db.execSQL("DROP TABLE IF EXISTS " + FREQ_TABLE_NAME);
- onCreate(db);
- }
- }
-
- /**
- * Async task to write pending words to the database so that it stays in sync with
- * the in-memory trie.
+ * Async task to write pending words to the binarydicts.
*/
- private static class UpdateDbTask extends AsyncTask<Void, Void, Void> {
+ private static final class UpdateBinaryTask extends AsyncTask<Void, Void, Void>
+ implements BigramDictionaryInterface {
private final UserHistoryDictionaryBigramList mBigramList;
- private final DatabaseHelper mDbHelper;
+ private final boolean mAddLevel0Bigrams;
private final String mLocale;
private final UserHistoryDictionary mUserHistoryDictionary;
private final SharedPreferences mPrefs;
+ private final Context mContext;
- public UpdateDbTask(
- DatabaseHelper openHelper, UserHistoryDictionaryBigramList pendingWrites,
- String locale, UserHistoryDictionary dict, SharedPreferences prefs) {
+ public UpdateBinaryTask(final UserHistoryDictionaryBigramList pendingWrites,
+ final String locale, final UserHistoryDictionary dict,
+ final SharedPreferences prefs, final Context context) {
mBigramList = pendingWrites;
mLocale = locale;
- mDbHelper = openHelper;
mUserHistoryDictionary = dict;
mPrefs = prefs;
- }
-
- /** Prune any old data if the database is getting too big. */
- private static void checkPruneData(SQLiteDatabase db) {
- db.execSQL("PRAGMA foreign_keys = ON;");
- Cursor c = db.query(FREQ_TABLE_NAME, new String[] { FREQ_COLUMN_PAIR_ID },
- null, null, null, null, null);
- try {
- int totalRowCount = c.getCount();
- // prune out old data if we have too much data
- if (totalRowCount > sMaxHistoryBigrams) {
- int numDeleteRows = (totalRowCount - sMaxHistoryBigrams)
- + sDeleteHistoryBigrams;
- int pairIdColumnId = c.getColumnIndex(FREQ_COLUMN_PAIR_ID);
- c.moveToFirst();
- int count = 0;
- while (count < numDeleteRows && !c.isAfterLast()) {
- String pairId = c.getString(pairIdColumnId);
- // Deleting from MAIN table will delete the frequencies
- // due to FOREIGN KEY .. ON DELETE CASCADE
- db.delete(MAIN_TABLE_NAME, MAIN_COLUMN_ID + "=?",
- new String[] { pairId });
- c.moveToNext();
- count++;
- }
- }
- } finally {
- c.close();
- }
+ mContext = context;
+ mAddLevel0Bigrams = mBigramList.size() <= MAX_HISTORY_BIGRAMS;
}
@Override
- protected Void doInBackground(Void... v) {
- SQLiteDatabase db = null;
- if (mUserHistoryDictionary.mBigramListLock.tryLock()) {
+ protected Void doInBackground(final Void... v) {
+ if (mUserHistoryDictionary.isTest) {
+ // If isTest == true, wait until the lock is released.
+ mUserHistoryDictionary.mBigramListLock.lock();
try {
- try {
- db = mDbHelper.getWritableDatabase();
- } catch (android.database.sqlite.SQLiteCantOpenDatabaseException e) {
- // If we can't open the db, don't do anything. Exit through the next test
- // for non-nullity of the db variable.
- }
- if (null == db) {
- // Not much we can do. Just exit.
- return null;
- }
- db.beginTransaction();
- return doLoadTaskLocked(db);
+ doWriteTaskLocked();
} finally {
- if (db != null) {
- db.endTransaction();
- }
mUserHistoryDictionary.mBigramListLock.unlock();
}
+ } else if (mUserHistoryDictionary.mBigramListLock.tryLock()) {
+ doWriteTaskLocked();
}
return null;
}
- private Void doLoadTaskLocked(SQLiteDatabase db) {
+ private void doWriteTaskLocked() {
if (DBG_STRESS_TEST) {
try {
Log.w(TAG, "Start stress in closing: " + mLocale);
Thread.sleep(15000);
Log.w(TAG, "End stress in closing");
} catch (InterruptedException e) {
+ Log.e(TAG, "In stress test: " + e);
}
}
+
final long now = PROFILE_SAVE_RESTORE ? System.currentTimeMillis() : 0;
- int profTotal = 0;
- int profInsert = 0;
- int profDelete = 0;
- db.execSQL("PRAGMA foreign_keys = ON;");
- final boolean addLevel0Bigram = mBigramList.size() <= sMaxHistoryBigrams;
-
- // Write all the entries to the db
- for (String word1 : mBigramList.keySet()) {
- final HashMap<String, Byte> word1Bigrams = mBigramList.getBigrams(word1);
- for (String word2 : word1Bigrams.keySet()) {
- if (PROFILE_SAVE_RESTORE) {
- ++profTotal;
- }
- // Get new frequency. Do not insert unigrams/bigrams which freq is "-1".
- final int freq; // -1, or 0~255
- if (word1 == null) { // unigram
- freq = FREQUENCY_FOR_TYPED;
- final byte prevFc = word1Bigrams.get(word2);
- if (prevFc == FREQUENCY_FOR_TYPED) {
- // No need to update since we found no changes for this entry.
- // Just skip to the next entry.
- if (DBG_SAVE_RESTORE) {
- Log.d(TAG, "Skip update user history: " + word1 + "," + word2
- + "," + prevFc);
- }
- if (!DBG_ALWAYS_WRITE) {
- continue;
- }
- }
- } else { // bigram
- final NextWord nw = mUserHistoryDictionary.getBigramWord(word1, word2);
- if (nw != null) {
- final ForgettingCurveParams fcp = nw.getFcParams();
- final byte prevFc = word1Bigrams.get(word2);
- final byte fc = (byte)fcp.getFc();
- final boolean isValid = fcp.isValid();
- if (prevFc > 0 && prevFc == fc) {
- // No need to update since we found no changes for this entry.
- // Just skip to the next entry.
- if (DBG_SAVE_RESTORE) {
- Log.d(TAG, "Skip update user history: " + word1 + ","
- + word2 + "," + prevFc);
- }
- if (!DBG_ALWAYS_WRITE) {
- continue;
- } else {
- freq = fc;
- }
- } else if (UserHistoryForgettingCurveUtils.
- needsToSave(fc, isValid, addLevel0Bigram)) {
- freq = fc;
- } else {
- // Delete this entry
- freq = -1;
- }
- } else {
- // Delete this entry
- freq = -1;
- }
- }
- // TODO: this process of making a text search for each pair each time
- // is terribly inefficient. Optimize this.
- // Find pair id
- Cursor c = null;
+ final String fileName = NAME + "." + mLocale + ".dict";
+ final File file = new File(mContext.getFilesDir(), fileName);
+ FileOutputStream out = null;
+
+ try {
+ out = new FileOutputStream(file);
+ UserHistoryDictIOUtils.writeDictionaryBinary(out, this, mBigramList, VERSION3);
+ out.flush();
+ out.close();
+ } catch (IOException e) {
+ Log.e(TAG, "IO Exception while writing file: " + e);
+ } finally {
+ if (out != null) {
try {
- if (null != word1) {
- c = db.query(MAIN_TABLE_NAME, new String[] { MAIN_COLUMN_ID },
- MAIN_COLUMN_WORD1 + "=? AND " + MAIN_COLUMN_WORD2 + "=? AND "
- + MAIN_COLUMN_LOCALE + "=?",
- new String[] { word1, word2, mLocale }, null, null,
- null);
- } else {
- c = db.query(MAIN_TABLE_NAME, new String[] { MAIN_COLUMN_ID },
- MAIN_COLUMN_WORD1 + " IS NULL AND " + MAIN_COLUMN_WORD2
- + "=? AND " + MAIN_COLUMN_LOCALE + "=?",
- new String[] { word2, mLocale }, null, null, null);
- }
-
- final int pairId;
- if (c.moveToFirst()) {
- if (PROFILE_SAVE_RESTORE) {
- ++profDelete;
- }
- // Delete existing pair
- pairId = c.getInt(c.getColumnIndex(MAIN_COLUMN_ID));
- db.delete(FREQ_TABLE_NAME, FREQ_COLUMN_PAIR_ID + "=?",
- new String[] { Integer.toString(pairId) });
- } else {
- // Create new pair
- Long pairIdLong = db.insert(MAIN_TABLE_NAME, null,
- getContentValues(word1, word2, mLocale));
- pairId = pairIdLong.intValue();
- }
- // Eliminate freq == 0 because that word is profanity.
- if (freq > 0) {
- if (PROFILE_SAVE_RESTORE) {
- ++profInsert;
- }
- if (DBG_SAVE_RESTORE) {
- Log.d(TAG, "--- Save user history: " + word1 + ", " + word2
- + mLocale + "," + this);
- }
- // Insert new frequency
- db.insert(FREQ_TABLE_NAME, null,
- getFrequencyContentValues(pairId, freq));
- // Update an existing bigram entry in mBigramList too in order to
- // synchronize the SQL DB and mBigramList.
- mBigramList.updateBigram(word1, word2, (byte)freq);
- }
- } finally {
- if (c != null) {
- c.close();
- }
+ out.close();
+ } catch (IOException e) {
+ // ignore
}
}
}
- checkPruneData(db);
- // Save the timestamp after we finish writing the SQL DB.
+ // Save the timestamp after we finish writing the binary dictionary.
SettingsValues.setLastUserHistoryWriteTime(mPrefs, mLocale);
if (PROFILE_SAVE_RESTORE) {
final long diff = System.currentTimeMillis() - now;
- Log.w(TAG, "PROF: Write User HistoryDictionary: " + mLocale + ", "+ diff
- + "ms. Total: " + profTotal + ". Insert: " + profInsert + ". Delete: "
- + profDelete);
+ Log.w(TAG, "PROF: Write User HistoryDictionary: " + mLocale + ", " + diff + "ms.");
}
- db.setTransactionSuccessful();
- return null;
}
- private static ContentValues getContentValues(String word1, String word2, String locale) {
- ContentValues values = new ContentValues(3);
- values.put(MAIN_COLUMN_WORD1, word1);
- values.put(MAIN_COLUMN_WORD2, word2);
- values.put(MAIN_COLUMN_LOCALE, locale);
- return values;
+ @Override
+ public int getFrequency(final String word1, final String word2) {
+ final int freq;
+ if (word1 == null) { // unigram
+ freq = FREQUENCY_FOR_TYPED;
+ final byte prevFc = mBigramList.getBigrams(word1).get(word2);
+ } else { // bigram
+ final NextWord nw = mUserHistoryDictionary.getBigramWord(word1, word2);
+ if (nw != null) {
+ final ForgettingCurveParams fcp = nw.getFcParams();
+ final byte prevFc = mBigramList.getBigrams(word1).get(word2);
+ final byte fc = fcp.getFc();
+ final boolean isValid = fcp.isValid();
+ if (prevFc > 0 && prevFc == fc) {
+ freq = ((int)fc) & 0xFF;
+ } else if (UserHistoryForgettingCurveUtils.
+ needsToSave(fc, isValid, mAddLevel0Bigrams)) {
+ freq = ((int)fc) & 0xFF;
+ } else {
+ // Delete this entry
+ freq = -1;
+ }
+ } else {
+ // Delete this entry
+ freq = -1;
+ }
+ }
+ return freq;
}
+ }
- private static ContentValues getFrequencyContentValues(int pairId, int frequency) {
- ContentValues values = new ContentValues(2);
- values.put(FREQ_COLUMN_PAIR_ID, pairId);
- values.put(COLUMN_FORGETTING_CURVE_VALUE, frequency);
- return values;
+ @UsedForTesting
+ void forceAddWordForTest(final String word1, final String word2, final boolean isValid) {
+ mBigramListLock.lock();
+ try {
+ addToUserHistory(word1, word2, isValid);
+ } finally {
+ mBigramListLock.unlock();
}
}
-
}
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java
index bb0f5429a..df44948f9 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java
@@ -26,7 +26,7 @@ import java.util.Set;
* All bigrams including stale ones in SQL DB should be stored in this class to avoid adding stale
* bigrams when we write to the SQL DB.
*/
-public class UserHistoryDictionaryBigramList {
+public final class UserHistoryDictionaryBigramList {
public static final byte FORGETTING_CURVE_INITIAL_VALUE = 0;
private static final String TAG = UserHistoryDictionaryBigramList.class.getSimpleName();
private static final HashMap<String, Byte> EMPTY_BIGRAM_MAP = CollectionUtils.newHashMap();
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java b/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java
index 3d3bd980c..9053d709b 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java
@@ -36,7 +36,7 @@ public final class UserHistoryForgettingCurveUtils {
// This utility class is not publicly instantiable.
}
- public static class ForgettingCurveParams {
+ public static final class ForgettingCurveParams {
private byte mFc;
long mLastTouchedTime = 0;
private final boolean mIsValid;
@@ -195,7 +195,7 @@ public final class UserHistoryForgettingCurveUtils {
return (elapsedTime < ELAPSED_TIME_MAX - 1 || level > 0);
}
- private static class MathUtils {
+ private static final class MathUtils {
public static final int[][] SCORE_TABLE = new int[FC_LEVEL_MAX][ELAPSED_TIME_MAX + 1];
static {
for (int i = 0; i < FC_LEVEL_MAX; ++i) {
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 275ebf305..daff442f3 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -24,7 +24,7 @@ import java.util.Arrays;
/**
* A place to store the currently composing word with information such as adjacent key codes as well
*/
-public class WordComposer {
+public final class WordComposer {
private static final int N = BinaryDictionary.MAX_WORD_LENGTH;
public static final int CAPS_MODE_OFF = 0;
@@ -38,7 +38,7 @@ public 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 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 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 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 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 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();
@@ -177,14 +178,16 @@ public class WordComposer {
* Internal method to retrieve reasonable proximity info for a character.
*/
private void addKeyInfo(final int codePoint, final Keyboard keyboard) {
- final Key key = keyboard.getKey(codePoint);
- if (key != null) {
- final int x = key.mX + key.mWidth / 2;
- final int y = key.mY + key.mHeight / 2;
- add(codePoint, x, y);
- return;
+ final int x, y;
+ final Key key;
+ if (keyboard != null && (key = keyboard.getKey(codePoint)) != null) {
+ x = key.mX + key.mWidth / 2;
+ y = key.mY + key.mHeight / 2;
+ } else {
+ x = Constants.NOT_A_COORDINATE;
+ y = Constants.NOT_A_COORDINATE;
}
- add(codePoint, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
+ add(codePoint, x, y);
}
/**
@@ -195,7 +198,7 @@ public class WordComposer {
reset();
final int length = word.length();
for (int i = 0; i < length; i = Character.offsetByCodePoints(word, i, 1)) {
- int codePoint = Character.codePointAt(word, i);
+ final int codePoint = Character.codePointAt(word, i);
addKeyInfo(codePoint, keyboard);
}
mIsResumed = true;
@@ -233,7 +236,7 @@ public 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;
}
}
@@ -319,14 +322,14 @@ public 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;
}
@@ -339,7 +342,7 @@ public 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/WordListInfo.java b/java/src/com/android/inputmethod/latin/WordListInfo.java
index 54f04d78f..095320e25 100644
--- a/java/src/com/android/inputmethod/latin/WordListInfo.java
+++ b/java/src/com/android/inputmethod/latin/WordListInfo.java
@@ -19,7 +19,7 @@ package com.android.inputmethod.latin;
/**
* Information container for a word list.
*/
-public class WordListInfo {
+public final class WordListInfo {
public final String mId;
public final String mLocale;
public WordListInfo(final String id, final String locale) {
diff --git a/java/src/com/android/inputmethod/latin/XmlParseUtils.java b/java/src/com/android/inputmethod/latin/XmlParseUtils.java
index b5cbaf19e..75dc68f1d 100644
--- a/java/src/com/android/inputmethod/latin/XmlParseUtils.java
+++ b/java/src/com/android/inputmethod/latin/XmlParseUtils.java
@@ -36,28 +36,28 @@ public final class XmlParseUtils {
}
@SuppressWarnings("serial")
- public static class IllegalStartTag extends ParseException {
+ public static final class IllegalStartTag extends ParseException {
public IllegalStartTag(XmlPullParser parser, String parent) {
super("Illegal start tag " + parser.getName() + " in " + parent, parser);
}
}
@SuppressWarnings("serial")
- public static class IllegalEndTag extends ParseException {
+ public static final class IllegalEndTag extends ParseException {
public IllegalEndTag(XmlPullParser parser, String parent) {
super("Illegal end tag " + parser.getName() + " in " + parent, parser);
}
}
@SuppressWarnings("serial")
- public static class IllegalAttribute extends ParseException {
+ public static final class IllegalAttribute extends ParseException {
public IllegalAttribute(XmlPullParser parser, String attribute) {
super("Tag " + parser.getName() + " has illegal attribute " + attribute, parser);
}
}
@SuppressWarnings("serial")
- public static class NonEmptyTag extends ParseException{
+ public static final class NonEmptyTag extends ParseException{
public NonEmptyTag(String tag, XmlPullParser parser) {
super(tag + " must be empty tag", parser);
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
index 7a1b9dcb7..ee0e9cd7e 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
@@ -16,21 +16,34 @@
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 class BinaryDictIOUtils {
+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 static class Position {
+ private BinaryDictIOUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ private static final class Position {
public static final int NOT_READ_GROUPCOUNT = -1;
public int mAddress;
@@ -77,7 +90,10 @@ public class BinaryDictIOUtils {
p.mAddress += BinaryDictInputOutput.getGroupCountSize(p.mNumOfCharGroup);
p.mPosition = 0;
}
-
+ if (p.mNumOfCharGroup == 0) {
+ stack.pop();
+ continue;
+ }
CharGroupInfo info = BinaryDictInputOutput.readCharGroup(buffer,
p.mAddress - headerSize, formatOptions);
for (int i = 0; i < info.mCharacters.length; ++i) {
@@ -85,20 +101,36 @@ public class BinaryDictIOUtils {
}
p.mPosition++;
- if (info.mFrequency != FusionDictionary.CharGroup.NOT_A_TERMINAL) { // found word
+ final boolean isMovedGroup = BinaryDictInputOutput.isMovedGroup(info.mFlags,
+ formatOptions);
+ 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);
if (info.mBigrams != null) bigrams.put(info.mOriginalAddress, info.mBigrams);
}
if (p.mPosition == p.mNumOfCharGroup) {
- stack.pop();
+ if (formatOptions.mSupportsDynamicUpdate) {
+ final int forwardLinkAddress = buffer.readUnsignedInt24();
+ if (forwardLinkAddress != FormatSpec.NO_FORWARD_LINK_ADDRESS) {
+ // the node has a forward link.
+ p.mNumOfCharGroup = Position.NOT_READ_GROUPCOUNT;
+ p.mAddress = forwardLinkAddress;
+ } else {
+ stack.pop();
+ }
+ } else {
+ stack.pop();
+ }
} else {
// the node has more groups.
p.mAddress = buffer.position();
}
- if (BinaryDictInputOutput.hasChildrenAddress(info.mChildrenAddress)) {
+ if (!isMovedGroup && BinaryDictInputOutput.hasChildrenAddress(info.mChildrenAddress)) {
Position childrenPos = new Position(info.mChildrenAddress + headerSize, index);
stack.push(childrenPos);
}
@@ -136,6 +168,7 @@ public 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;
@@ -146,48 +179,802 @@ public class BinaryDictIOUtils {
final int wordLen = word.codePointCount(0, word.length());
for (int depth = 0; depth < Constants.Dictionary.MAX_WORD_LENGTH; ++depth) {
if (wordPos >= wordLen) return FormatSpec.NOT_VALID_WORD;
- int groupOffset = buffer.position() - header.mHeaderSize;
+
+ do {
+ final int charGroupCount = BinaryDictInputOutput.readCharGroupCount(buffer);
+ 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);
+ 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;
+ ++p, j = word.offsetByCodePoints(j, 1)) {
+ if (wordPos + p >= wordLen
+ || word.codePointAt(j) != currentInfo.mCharacters[p]) {
+ same = false;
+ break;
+ }
+ }
+
+ if (same) {
+ // found the group matches the word.
+ if (wordPos + currentInfo.mCharacters.length == wordLen) {
+ if (currentInfo.mFrequency == CharGroup.NOT_A_TERMINAL
+ || isDeletedGroup) {
+ return FormatSpec.NOT_VALID_WORD;
+ } else {
+ return charGroupPos;
+ }
+ }
+ wordPos += currentInfo.mCharacters.length;
+ if (currentInfo.mChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS) {
+ return FormatSpec.NOT_VALID_WORD;
+ }
+ foundNextCharGroup = true;
+ buffer.position(currentInfo.mChildrenAddress);
+ break;
+ }
+ }
+
+ // If we found the next char group, it is under the file pointer.
+ // But if not, we are at the end of this node so we expect to have
+ // a forward link address that we need to consult and possibly resume
+ // search on the next node in the linked list.
+ if (foundNextCharGroup) break;
+ if (!header.mFormatOptions.mSupportsDynamicUpdate) {
+ return FormatSpec.NOT_VALID_WORD;
+ }
+
+ final int forwardLinkAddress = buffer.readUnsignedInt24();
+ if (forwardLinkAddress == FormatSpec.NO_FORWARD_LINK_ADDRESS) {
+ return FormatSpec.NOT_VALID_WORD;
+ }
+ buffer.position(forwardLinkAddress);
+ } while(true);
+ }
+ 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.
+ *
+ * @param buffer the buffer to write.
+ * @param word the word we delete
+ * @throws IOException
+ * @throws UnsupportedFormatException
+ */
+ @UsedForTesting
+ public static void deleteWord(final FusionDictionaryBufferInterface buffer,
+ final String word) throws IOException, UnsupportedFormatException {
+ buffer.position(0);
+ final FileHeader header = BinaryDictInputOutput.readHeader(buffer);
+ final int wordPosition = getTerminalPosition(buffer, word);
+ if (wordPosition == FormatSpec.NOT_VALID_WORD) return;
+
+ buffer.position(wordPosition);
+ final int flags = buffer.readUnsignedByte();
+ buffer.position(wordPosition);
+ buffer.put((byte)markAsDeleted(flags));
+ }
+
+ /**
+ * @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;
+ }
+
+ /**
+ * @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.
+ * @param newParentAddress the absolute address of the parent.
+ * @param formatOptions file format options.
+ */
+ public static void updateParentAddress(final FusionDictionaryBufferInterface buffer,
+ final int groupOriginAddress, final int newParentAddress,
+ final FormatOptions formatOptions) {
+ final int originalPosition = buffer.position();
+ buffer.position(groupOriginAddress);
+ if (!formatOptions.mSupportsDynamicUpdate) {
+ 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;
+ 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);
- groupOffset += BinaryDictInputOutput.getGroupCountSize(charGroupCount);
+ boolean foundNextGroup = false;
for (int i = 0; i < charGroupCount; ++i) {
- final int charGroupPos = buffer.position();
+ address = buffer.position();
final CharGroupInfo currentInfo = BinaryDictInputOutput.readCharGroup(buffer,
buffer.position(), header.mFormatOptions);
- boolean same = true;
- for (int p = 0, j = word.offsetByCodePoints(0, wordPos);
- p < currentInfo.mCharacters.length;
- ++p, j = word.offsetByCodePoints(j, 1)) {
- if (wordPos + p >= wordLen
- || word.codePointAt(j) != currentInfo.mCharacters[p]) {
- same = false;
+ 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 (same) {
+ if (matched) {
if (wordPos + currentInfo.mCharacters.length == wordLen) {
- if (currentInfo.mFrequency == CharGroup.NOT_A_TERMINAL) {
- return FormatSpec.NOT_VALID_WORD;
- } else {
- return charGroupPos;
- }
+ // 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) {
- return FormatSpec.NOT_VALID_WORD;
+ /*
+ * 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;
}
- groupOffset = currentInfo.mEndAddress;
+ }
- // not found
- if (i >= charGroupCount - 1) {
- return FormatSpec.NOT_VALID_WORD;
- }
+ 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);
}
}
- return FormatSpec.NOT_VALID_WORD;
+ }
+
+ /**
+ * 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 1d3e94bb7..d1a3c7b0a 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;
@@ -36,7 +37,6 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
-import java.util.Stack;
import java.util.TreeMap;
/**
@@ -44,7 +44,7 @@ import java.util.TreeMap;
*
* All the methods in this class are static.
*/
-public class BinaryDictInputOutput {
+public final class BinaryDictInputOutput {
private static final boolean DBG = MakedictLog.DBG;
@@ -54,6 +54,7 @@ public class BinaryDictInputOutput {
// If the number of passes exceeds this number, makedict bails with an exception on
// suspicion that a bug might be causing an infinite loop.
private static final int MAX_PASSES = 24;
+ private static final int MAX_JUMPS = 12;
public interface FusionDictionaryBufferInterface {
public int readUnsignedByte();
@@ -64,6 +65,7 @@ public class BinaryDictInputOutput {
public void position(int newPosition);
public void put(final byte b);
public int limit();
+ public int capacity();
}
public static final class ByteBufferWrapper implements FusionDictionaryBufferInterface {
@@ -75,12 +77,12 @@ public 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
@@ -113,13 +115,17 @@ public class BinaryDictInputOutput {
public int limit() {
return mBuffer.limit();
}
+
+ @Override
+ public int capacity() {
+ return mBuffer.capacity();
+ }
}
/**
* A class grouping utility function for our specific character encoding.
*/
- private static 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;
@@ -148,7 +154,7 @@ public 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;
@@ -257,7 +263,7 @@ public 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) {
@@ -271,6 +277,21 @@ public 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
@@ -280,9 +301,7 @@ public 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);
}
/**
@@ -332,7 +351,7 @@ public 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) {
@@ -376,7 +395,7 @@ public class BinaryDictInputOutput {
g.mCachedSize = groupSize;
size += groupSize;
}
- if (options.mHasLinkedListNode) {
+ if (options.mSupportsDynamicUpdate) {
size += FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
}
node.mCachedSize = size;
@@ -390,11 +409,27 @@ public class BinaryDictInputOutput {
}
/**
- * Helper method to check whether the CharGroup has a parent address.
+ * Helper method to check whether the group is moved.
*/
- private static boolean hasParentAddress(final FormatOptions options) {
- return options.mVersion >= FormatSpec.FIRST_VERSION_WITH_PARENT_ADDRESS
- && options.mHasParentAddress;
+ public static boolean isMovedGroup(final int flags, final FormatOptions options) {
+ 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);
+ }
+
+ /**
+ * Helper method to check whether the dictionary can be updated dynamically.
+ */
+ public static boolean supportsDynamicUpdate(final FormatOptions options) {
+ return options.mVersion >= FormatSpec.FIRST_VERSION_WITH_DYNAMIC_UPDATE
+ && options.mSupportsDynamicUpdate;
}
/**
@@ -404,7 +439,7 @@ public class BinaryDictInputOutput {
* @param options file format options.
*/
private static int getGroupHeaderSize(final CharGroup group, final FormatOptions options) {
- if (hasParentAddress(options)) {
+ if (supportsDynamicUpdate(options)) {
return FormatSpec.GROUP_FLAGS_SIZE + FormatSpec.PARENT_ADDRESS_SIZE
+ getGroupCharactersSize(group);
} else {
@@ -412,6 +447,10 @@ public class BinaryDictInputOutput {
}
}
+ private static final int UINT8_MAX = 0xFF;
+ private static final int UINT16_MAX = 0xFFFF;
+ private static final int UINT24_MAX = 0xFFFFFF;
+
/**
* Compute the size, in bytes, that an address will occupy.
*
@@ -422,18 +461,23 @@ public class BinaryDictInputOutput {
* @param address the address
* @return the byte size.
*/
- private static int getByteSize(final int address) {
- assert(address < 0x1000000);
+ static int getByteSize(final int address) {
+ assert(address <= UINT24_MAX);
if (!hasChildrenAddress(address)) {
return 0;
- } else if (Math.abs(address) < 0x100) {
+ } else if (Math.abs(address) <= UINT8_MAX) {
return 1;
- } else if (Math.abs(address) < 0x10000) {
+ } else if (Math.abs(address) <= UINT16_MAX) {
return 2;
} else {
return 3;
}
}
+
+ private static final int SINT24_MAX = 0x7FFFFF;
+ private static final int MSB8 = 0x80;
+ private static final int MSB24 = 0x800000;
+
// End utility methods.
// This method is responsible for finding a nice ordering of the nodes that favors run-time
@@ -509,13 +553,19 @@ public class BinaryDictInputOutput {
}
int groupSize = getGroupHeaderSize(group, formatOptions);
if (group.isTerminal()) groupSize += FormatSpec.GROUP_FREQUENCY_SIZE;
- if (null != group.mChildren) {
+ if (null == group.mChildren && formatOptions.mSupportsDynamicUpdate) {
+ groupSize += FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE;
+ } else if (null != group.mChildren) {
final int offsetBasePoint = groupSize + node.mCachedAddress + size;
final int offset = group.mChildren.mCachedAddress - offsetBasePoint;
// assign my address to children's parent address
group.mChildren.mCachedParentAddress = group.mCachedAddress
- group.mChildren.mCachedAddress;
- groupSize += getByteSize(offset);
+ if (formatOptions.mSupportsDynamicUpdate) {
+ groupSize += FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE;
+ } else {
+ groupSize += getByteSize(offset);
+ }
}
groupSize += getShortcutListSize(group.mShortcutTargets);
if (null != group.mBigrams) {
@@ -530,7 +580,7 @@ public class BinaryDictInputOutput {
group.mCachedSize = groupSize;
size += groupSize;
}
- if (formatOptions.mHasLinkedListNode) {
+ if (formatOptions.mSupportsDynamicUpdate) {
size += FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
}
if (node.mCachedSize != size) {
@@ -559,7 +609,8 @@ public class BinaryDictInputOutput {
groupOffset += g.mCachedSize;
}
final int nodeSize = groupCountSize + groupOffset
- + (formatOptions.mHasLinkedListNode ? FormatSpec.FORWARD_LINK_ADDRESS_SIZE : 0);
+ + (formatOptions.mSupportsDynamicUpdate
+ ? FormatSpec.FORWARD_LINK_ADDRESS_SIZE : 0);
if (nodeSize != n.mCachedSize) {
throw new RuntimeException("Bug : Stored and computed node size differ");
}
@@ -668,49 +719,81 @@ public class BinaryDictInputOutput {
}
}
- private static byte makeCharGroupFlags(final CharGroup group, final int groupAddress,
- final int childrenOffset) {
- 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) {
- switch (getByteSize(childrenOffset)) {
- 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");
- }
- }
- 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;
+ /**
+ * Helper method to write a variable-size signed address to a file.
+ *
+ * @param buffer the buffer to write to.
+ * @param index the index in the buffer to write the address to.
+ * @param address the address to write.
+ * @return the size in bytes the address actually took.
+ */
+ private static int writeVariableSignedAddress(final byte[] buffer, int index,
+ final int address) {
+ if (!hasChildrenAddress(address)) {
+ buffer[index] = buffer[index + 1] = buffer[index + 2] = 0;
+ } else {
+ final int absAddress = Math.abs(address);
+ buffer[index++] = (byte)((address < 0 ? MSB8 : 0) | (0xFF & (absAddress >> 16)));
+ buffer[index++] = (byte)(0xFF & (absAddress >> 8));
+ buffer[index++] = (byte)(0xFF & absAddress);
}
- if (null != group.mBigrams) {
- if (DBG && 0 == group.mBigrams.size()) {
- throw new RuntimeException("0-sized bigram list must be null");
+ return 3;
+ }
+
+ /**
+ * 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 (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.
*
@@ -792,8 +875,7 @@ public class BinaryDictInputOutput {
return (options.mFrenchLigatureProcessing ? FormatSpec.FRENCH_LIGATURE_PROCESSING_FLAG : 0)
+ (options.mGermanUmlautProcessing ? FormatSpec.GERMAN_UMLAUT_PROCESSING_FLAG : 0)
+ (hasBigrams ? FormatSpec.CONTAINS_BIGRAMS_FLAG : 0)
- + (formatOptions.mHasParentAddress ? FormatSpec.HAS_PARENT_ADDRESS : 0)
- + (formatOptions.mHasLinkedListNode ? FormatSpec.HAS_LINKEDLIST_NODE : 0);
+ + (formatOptions.mSupportsDynamicUpdate ? FormatSpec.SUPPORTS_DYNAMIC_UPDATE : 0);
}
/**
@@ -803,11 +885,30 @@ public 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);
}
+ private static final int writeParentAddress(final byte[] buffer, final int index,
+ final int address, final FormatOptions formatOptions) {
+ if (supportsDynamicUpdate(formatOptions)) {
+ if (address == FormatSpec.NO_PARENT_ADDRESS) {
+ buffer[index] = buffer[index + 1] = buffer[index + 2] = 0;
+ } else {
+ final int absAddress = Math.abs(address);
+ assert(absAddress <= SINT24_MAX);
+ buffer[index] = (byte)((address < 0 ? MSB8 : 0)
+ | ((absAddress >> 16) & 0xFF));
+ buffer[index + 1] = (byte)((absAddress >> 8) & 0xFF);
+ buffer[index + 2] = (byte)(absAddress & 0xFF);
+ }
+ return index + 3;
+ } else {
+ return index;
+ }
+ }
+
/**
* Write a node to memory. The node is expected to have its final position cached.
*
@@ -822,6 +923,7 @@ public class BinaryDictInputOutput {
*/
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();
@@ -854,22 +956,15 @@ public class BinaryDictInputOutput {
final int childrenOffset = null == group.mChildren
? FormatSpec.NO_CHILDREN_ADDRESS
: group.mChildren.mCachedAddress - groupAddress;
- byte flags = makeCharGroupFlags(group, groupAddress, childrenOffset);
+ byte flags = makeCharGroupFlags(group, groupAddress, childrenOffset, formatOptions);
buffer[index++] = flags;
- if (hasParentAddress(formatOptions)) {
- if (parentAddress == FormatSpec.NO_PARENT_ADDRESS) {
- // this node is the root node.
- buffer[index] = buffer[index + 1] = buffer[index + 2] = 0;
- } else {
- // write parent address. (version 3)
- final int actualParentAddress = Math.abs(parentAddress
- + (node.mCachedAddress - group.mCachedAddress));
- buffer[index] = (byte)((actualParentAddress >> 16) & 0xFF);
- buffer[index + 1] = (byte)((actualParentAddress >> 8) & 0xFF);
- buffer[index + 2] = (byte)(actualParentAddress & 0xFF);
- }
- index += 3;
+ if (parentAddress == FormatSpec.NO_PARENT_ADDRESS) {
+ index = writeParentAddress(buffer, index, parentAddress, formatOptions);
+ } else {
+ index = writeParentAddress(buffer, index,
+ parentAddress + (node.mCachedAddress - group.mCachedAddress),
+ formatOptions);
}
index = CharEncoding.writeCharArray(group.mChars, buffer, index);
@@ -879,7 +974,13 @@ public class BinaryDictInputOutput {
if (group.mFrequency >= 0) {
buffer[index++] = (byte) group.mFrequency;
}
- final int shift = writeVariableAddress(buffer, index, childrenOffset);
+
+ final int shift;
+ if (formatOptions.mSupportsDynamicUpdate) {
+ shift = writeVariableSignedAddress(buffer, index, childrenOffset);
+ } else {
+ shift = writeVariableAddress(buffer, index, childrenOffset);
+ }
index += shift;
groupAddress += shift;
@@ -927,7 +1028,7 @@ public class BinaryDictInputOutput {
}
}
- if (formatOptions.mHasLinkedListNode) {
+ if (formatOptions.mSupportsDynamicUpdate) {
buffer[index] = buffer[index + 1] = buffer[index + 2]
= FormatSpec.NO_FORWARD_LINK_ADDRESS;
index += FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
@@ -1104,6 +1205,58 @@ public class BinaryDictInputOutput {
// Input methods: Read a binary dictionary to memory.
// readDictionaryBinary is the public entry point for them.
+ 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) {
+ case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_ONEBYTE:
+ return 1;
+ case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_TWOBYTES:
+ return 2;
+ case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_THREEBYTES:
+ return 3;
+ case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_NOADDRESS:
+ default:
+ return 0;
+ }
+ }
+
+ static int readChildrenAddress(final FusionDictionaryBufferInterface buffer,
+ final int optionFlags, final FormatOptions options) {
+ if (options.mSupportsDynamicUpdate) {
+ final int address = buffer.readUnsignedInt24();
+ if (address == 0) return FormatSpec.NO_CHILDREN_ADDRESS;
+ if ((address & MSB24) != 0) {
+ return -(address & SINT24_MAX);
+ } else {
+ return address;
+ }
+ }
+ int address;
+ switch (optionFlags & FormatSpec.MASK_GROUP_ADDRESS_TYPE) {
+ case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_ONEBYTE:
+ return buffer.readUnsignedByte();
+ case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_TWOBYTES:
+ return buffer.readUnsignedShort();
+ case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_THREEBYTES:
+ return buffer.readUnsignedInt24();
+ case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_NOADDRESS:
+ default:
+ return FormatSpec.NO_CHILDREN_ADDRESS;
+ }
+ }
+
+ static int readParentAddress(final FusionDictionaryBufferInterface buffer,
+ final FormatOptions formatOptions) {
+ if (supportsDynamicUpdate(formatOptions)) {
+ final int parentAddress = buffer.readUnsignedInt24();
+ final int sign = ((parentAddress & MSB24) != 0) ? -1 : 1;
+ return sign * (parentAddress & SINT24_MAX);
+ } else {
+ return FormatSpec.NO_PARENT_ADDRESS;
+ }
+ }
+
private static final int[] CHARACTER_BUFFER = new int[FormatSpec.MAX_WORD_LENGTH];
public static CharGroupInfo readCharGroup(final FusionDictionaryBufferInterface buffer,
final int originalGroupAddress, final FormatOptions options) {
@@ -1111,13 +1264,9 @@ public class BinaryDictInputOutput {
final int flags = buffer.readUnsignedByte();
++addressPointer;
- final int parentAddress;
- if (hasParentAddress(options)) {
- // read the parent address. (version 3)
- parentAddress = -buffer.readUnsignedInt24();
+ final int parentAddress = readParentAddress(buffer, options);
+ if (supportsDynamicUpdate(options)) {
addressPointer += 3;
- } else {
- parentAddress = FormatSpec.NO_PARENT_ADDRESS;
}
final int characters[];
@@ -1146,25 +1295,11 @@ public class BinaryDictInputOutput {
} else {
frequency = CharGroup.NOT_A_TERMINAL;
}
- int childrenAddress = addressPointer;
- switch (flags & FormatSpec.MASK_GROUP_ADDRESS_TYPE) {
- case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_ONEBYTE:
- childrenAddress += buffer.readUnsignedByte();
- addressPointer += 1;
- break;
- case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_TWOBYTES:
- childrenAddress += buffer.readUnsignedShort();
- addressPointer += 2;
- break;
- case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_THREEBYTES:
- childrenAddress += buffer.readUnsignedInt24();
- addressPointer += 3;
- break;
- case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_NOADDRESS:
- default:
- childrenAddress = FormatSpec.NO_CHILDREN_ADDRESS;
- break;
+ int childrenAddress = readChildrenAddress(buffer, flags, options);
+ if (childrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) {
+ childrenAddress += addressPointer;
}
+ addressPointer += getChildrenAddressSize(flags, options);
ArrayList<WeightedString> shortcutTargets = null;
if (0 != (flags & FormatSpec.FLAG_HAS_SHORTCUT_TARGETS)) {
final int pointerBefore = buffer.position();
@@ -1182,7 +1317,8 @@ public 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)
@@ -1210,6 +1346,9 @@ public 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);
@@ -1232,7 +1371,8 @@ public 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.
*
@@ -1240,18 +1380,19 @@ public 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);
- if (hasParentAddress(formatOptions)) {
+ if (supportsDynamicUpdate(formatOptions)) {
result = getWordAtAddressWithParentAddress(buffer, headerSize, address, formatOptions);
} else {
result = getWordAtAddressWithoutParentAddress(buffer, headerSize, address,
@@ -1263,38 +1404,53 @@ public 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(
+ 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) {
- buffer.position(currentAddress + headerSize);
- final CharGroupInfo currentInfo = readCharGroup(buffer, currentAddress, options);
+ CharGroupInfo currentInfo;
+ int loopCounter = 0;
+ do {
+ buffer.position(currentAddress + headerSize);
+ currentInfo = readCharGroup(buffer, currentAddress, options);
+ if (isMovedGroup(currentInfo.mFlags, options)) {
+ currentAddress = currentInfo.mParentAddress + currentInfo.mOriginalAddress;
+ }
+ if (DBG && loopCounter++ > MAX_JUMPS) {
+ 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];
}
-
if (currentInfo.mParentAddress == FormatSpec.NO_PARENT_ADDRESS) break;
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) {
@@ -1302,7 +1458,7 @@ public 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)) {
@@ -1357,14 +1513,17 @@ public class BinaryDictInputOutput {
int groupOffset = nodeHeadPosition + getGroupCountSize(count);
for (int i = count; i > 0; --i) { // Scan the array of CharGroup.
CharGroupInfo info = readCharGroup(buffer, groupOffset, options);
+ if (isMovedGroup(info.mFlags, options)) continue;
ArrayList<WeightedString> shortcutTargets = info.mShortcutTargets;
ArrayList<WeightedString> bigrams = null;
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)) {
@@ -1392,7 +1551,7 @@ public class BinaryDictInputOutput {
}
// reach the end of the array.
- if (options.mHasLinkedListNode) {
+ if (options.mSupportsDynamicUpdate) {
final int nextAddress = buffer.readUnsignedInt24();
if (nextAddress >= 0 && nextAddress < buffer.limit()) {
buffer.position(nextAddress);
@@ -1400,7 +1559,7 @@ public class BinaryDictInputOutput {
break;
}
}
- } while (options.mHasLinkedListNode &&
+ } while (options.mSupportsDynamicUpdate &&
buffer.position() != FormatSpec.NO_FORWARD_LINK_ADDRESS);
final Node node = new Node(nodeContents);
@@ -1469,8 +1628,7 @@ public class BinaryDictInputOutput {
0 != (optionsFlags & FormatSpec.GERMAN_UMLAUT_PROCESSING_FLAG),
0 != (optionsFlags & FormatSpec.FRENCH_LIGATURE_PROCESSING_FLAG)),
new FormatOptions(version,
- 0 != (optionsFlags & FormatSpec.HAS_PARENT_ADDRESS),
- 0 != (optionsFlags & FormatSpec.HAS_LINKEDLIST_NODE)));
+ 0 != (optionsFlags & FormatSpec.SUPPORTS_DYNAMIC_UPDATE)));
return header;
}
@@ -1500,6 +1658,7 @@ public 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 {
@@ -1537,17 +1696,24 @@ public 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());
@@ -1582,8 +1748,7 @@ public 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/CharGroupInfo.java b/java/src/com/android/inputmethod/latin/makedict/CharGroupInfo.java
index ed9388409..8e64082e6 100644
--- a/java/src/com/android/inputmethod/latin/makedict/CharGroupInfo.java
+++ b/java/src/com/android/inputmethod/latin/makedict/CharGroupInfo.java
@@ -23,7 +23,7 @@ import java.util.ArrayList;
/**
* Raw char group info straight out of a file. This will contain numbers for addresses.
*/
-public class CharGroupInfo {
+public final class CharGroupInfo {
public final int mOriginalAddress;
public final int mEndAddress;
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index adc6037bb..705f66414 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -42,32 +42,43 @@ public final class FormatSpec {
* ps
*
* f |
- * o | IF HAS_LINKEDLIST_NODE (defined in the file header)
+ * o | IF SUPPORTS_DYNAMIC_UPDATE (defined in the file header)
* r | forward link address, 3byte
- * w | the address must be positive.
- * a |
- * rdlinkaddress
+ * w | 1 byte = bbbbbbbb match
+ * a | case 1xxxxxxx => -((xxxxxxx << 16) + (next byte << 8) + next byte)
+ * r | otherwise => (xxxxxxx << 16) + (next byte << 8) + next byte
+ * d |
+ * linkaddress
*/
/* Node(CharGroup) layout is as follows:
- * | addressType xx : mask with MASK_GROUP_ADDRESS_TYPE
- * 2 bits, 00 = no children : FLAG_GROUP_ADDRESS_TYPE_NOADDRESS
- * f | 01 = 1 byte : FLAG_GROUP_ADDRESS_TYPE_ONEBYTE
- * l | 10 = 2 bytes : FLAG_GROUP_ADDRESS_TYPE_TWOBYTES
- * a | 11 = 3 bytes : FLAG_GROUP_ADDRESS_TYPE_THREEBYTES
- * g | has several chars ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_MULTIPLE_CHARS
- * s | has a terminal ? 1 bit, 1 = yes, 0 = no : FLAG_IS_TERMINAL
+ * | IF !SUPPORTS_DYNAMIC_UPDATE
+ * | addressType xx : mask with MASK_GROUP_ADDRESS_TYPE
+ * | 2 bits, 00 = no children : FLAG_GROUP_ADDRESS_TYPE_NOADDRESS
+ * f | 01 = 1 byte : FLAG_GROUP_ADDRESS_TYPE_ONEBYTE
+ * 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 : 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
* | has bigrams ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_BIGRAMS
* | is not a word ? 1 bit, 1 = yes, 0 = no : FLAG_IS_NOT_A_WORD
* | is blacklisted ? 1 bit, 1 = yes, 0 = no : FLAG_IS_BLACKLISTED
*
* p |
- * a | IF HAS_PARENT_ADDRESS (defined in the file header)
+ * a | IF SUPPORTS_DYNAMIC_UPDATE (defined in the file header)
* r | parent address, 3byte
- * e | the address must be negative, so the absolute value of the address is stored.
- * n |
- * taddress
+ * e | 1 byte = bbbbbbbb match
+ * n | case 1xxxxxxx => -((0xxxxxxx << 16) + (next byte << 8) + next byte)
+ * t | otherwise => (bbbbbbbb << 16) + (next byte << 8) + next byte
+ * a |
+ * ddress
*
* c | IF FLAG_HAS_MULTIPLE_CHARS
* h | char, char, char, char n * (1 or 3 bytes) : use CharGroupInfo for i/o helpers
@@ -145,17 +156,14 @@ public final class FormatSpec {
static final int MAXIMUM_SUPPORTED_VERSION = 3;
static final int NOT_A_VERSION_NUMBER = -1;
static final int FIRST_VERSION_WITH_HEADER_SIZE = 2;
- static final int FIRST_VERSION_WITH_PARENT_ADDRESS = 3;
- static final int FIRST_VERSION_WITH_LINKEDLIST_NODE = 3;
+ static final int FIRST_VERSION_WITH_DYNAMIC_UPDATE = 3;
// These options need to be the same numeric values as the one in the native reading code.
static final int GERMAN_UMLAUT_PROCESSING_FLAG = 0x1;
// TODO: Make the native reading code read this variable.
- static final int HAS_PARENT_ADDRESS = 0x2;
+ static final int SUPPORTS_DYNAMIC_UPDATE = 0x2;
static final int FRENCH_LIGATURE_PROCESSING_FLAG = 0x4;
static final int CONTAINS_BIGRAMS_FLAG = 0x8;
- // TODO: Make the native reading code read this variable.
- static final int HAS_LINKEDLIST_NODE = 0x10;
// TODO: Make this value adaptative to content data, store it in the header, and
// use it in the reading code.
@@ -164,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;
@@ -178,6 +187,13 @@ public final class FormatSpec {
static final int FLAG_IS_NOT_A_WORD = 0x02;
static final int FLAG_IS_BLACKLISTED = 0x01;
+ // 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;
static final int MASK_ATTRIBUTE_ADDRESS_TYPE = 0x30;
@@ -203,43 +219,33 @@ 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;
/**
* Options about file format.
*/
- public static class FormatOptions {
+ public static final class FormatOptions {
public final int mVersion;
- public final boolean mHasParentAddress;
- public final boolean mHasLinkedListNode;
+ public final boolean mSupportsDynamicUpdate;
public FormatOptions(final int version) {
this(version, false);
}
- public FormatOptions(final int version, final boolean hasParentAddress) {
- this(version, hasParentAddress, false);
- }
- public FormatOptions(final int version, final boolean hasParentAddress,
- final boolean hasLinkedListNode) {
+ public FormatOptions(final int version, final boolean supportsDynamicUpdate) {
mVersion = version;
- if (version < FIRST_VERSION_WITH_PARENT_ADDRESS && hasParentAddress) {
- throw new RuntimeException("Parent addresses are only supported with versions "
- + FIRST_VERSION_WITH_PARENT_ADDRESS + " and ulterior.");
- }
- mHasParentAddress = hasParentAddress;
-
- if (version < FIRST_VERSION_WITH_LINKEDLIST_NODE && hasLinkedListNode) {
- throw new RuntimeException("Linked list nodes are only supported with versions "
- + FIRST_VERSION_WITH_LINKEDLIST_NODE + " and ulterior.");
- }
- if (!hasParentAddress && hasLinkedListNode) {
- throw new RuntimeException("Linked list nodes need parent addresses.");
+ if (version < FIRST_VERSION_WITH_DYNAMIC_UPDATE && supportsDynamicUpdate) {
+ throw new RuntimeException("Dynamic updates are only supported with versions "
+ + FIRST_VERSION_WITH_DYNAMIC_UPDATE + " and ulterior.");
}
- mHasLinkedListNode = hasLinkedListNode;
+ mSupportsDynamicUpdate = supportsDynamicUpdate;
}
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
index 98cf308c8..b0b3777df 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;
@@ -28,8 +29,7 @@ import java.util.LinkedList;
/**
* A dictionary that can fusion heads and tails of words for more compression.
*/
-public class FusionDictionary implements Iterable<Word> {
-
+public final class FusionDictionary implements Iterable<Word> {
private static final boolean DBG = MakedictLog.DBG;
/**
@@ -40,7 +40,7 @@ public class FusionDictionary implements Iterable<Word> {
* This class also contains fields to cache size and address, to help with binary
* generation.
*/
- public static class Node {
+ public static final class Node {
ArrayList<CharGroup> mData;
// To help with binary generation
int mCachedSize = Integer.MIN_VALUE;
@@ -60,7 +60,7 @@ public class FusionDictionary implements Iterable<Word> {
*
* This represents an "attribute", that is either a bigram or a shortcut.
*/
- public static class WeightedString {
+ public static final class WeightedString {
public final String mWord;
public int mFrequency;
public WeightedString(String word, int frequency) {
@@ -94,7 +94,7 @@ public class FusionDictionary implements Iterable<Word> {
* value is the frequency of this terminal. A terminal may have non-null shortcuts and/or
* bigrams, but a non-terminal may not. Moreover, children, if present, are null.
*/
- public static class CharGroup {
+ public static final class CharGroup {
public static final int NOT_A_TERMINAL = -1;
final int mChars[];
ArrayList<WeightedString> mShortcutTargets;
@@ -142,6 +142,33 @@ public 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;
@@ -250,10 +277,8 @@ public class FusionDictionary implements Iterable<Word> {
/**
* Options global to the dictionary.
- *
- * There are no options at the moment, so this class is empty.
*/
- public static class DictionaryOptions {
+ public static final class DictionaryOptions {
public final boolean mGermanUmlautProcessing;
public final boolean mFrenchLigatureProcessing;
public final HashMap<String, String> mAttributes;
@@ -263,6 +288,43 @@ public 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;
@@ -280,7 +342,7 @@ public 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();
@@ -359,6 +421,10 @@ public 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 {
@@ -511,7 +577,7 @@ public class FusionDictionary implements Iterable<Word> {
* is ignored.
* This comparator imposes orderings that are inconsistent with equals.
*/
- static private class CharGroupComparator implements java.util.Comparator<CharGroup> {
+ static private final class CharGroupComparator implements java.util.Comparator<CharGroup> {
@Override
public int compare(CharGroup c1, CharGroup c2) {
if (c1.mChars[0] == c2.mChars[0]) return 0;
@@ -746,9 +812,8 @@ public class FusionDictionary implements Iterable<Word> {
*
* This is purely for convenience.
*/
- public static class DictionaryIterator implements Iterator<Word> {
-
- private static class Position {
+ public static final class DictionaryIterator implements Iterator<Word> {
+ private static final class Position {
public Iterator<CharGroup> pos;
public int length;
public Position(ArrayList<CharGroup> groups) {
diff --git a/java/src/com/android/inputmethod/latin/makedict/MakedictLog.java b/java/src/com/android/inputmethod/latin/makedict/MakedictLog.java
index 3f0cd0796..6c6b00b6a 100644
--- a/java/src/com/android/inputmethod/latin/makedict/MakedictLog.java
+++ b/java/src/com/android/inputmethod/latin/makedict/MakedictLog.java
@@ -21,7 +21,7 @@ import android.util.Log;
/**
* Wrapper to redirect log events to the right output medium.
*/
-public class MakedictLog {
+public final class MakedictLog {
public static final boolean DBG = false;
private static final String TAG = MakedictLog.class.getSimpleName();
diff --git a/java/src/com/android/inputmethod/latin/makedict/PendingAttribute.java b/java/src/com/android/inputmethod/latin/makedict/PendingAttribute.java
index 5b41d27f2..5bb24da74 100644
--- a/java/src/com/android/inputmethod/latin/makedict/PendingAttribute.java
+++ b/java/src/com/android/inputmethod/latin/makedict/PendingAttribute.java
@@ -22,7 +22,7 @@ package com.android.inputmethod.latin.makedict;
* An attribute is either a bigram or a shortcut.
* All instances of this class are always immutable.
*/
-public class PendingAttribute {
+public final class PendingAttribute {
public final int mFrequency;
public final int mAddress;
public PendingAttribute(final int frequency, final int address) {
diff --git a/java/src/com/android/inputmethod/latin/makedict/UnsupportedFormatException.java b/java/src/com/android/inputmethod/latin/makedict/UnsupportedFormatException.java
index bd42fb8fa..dbb2ea870 100644
--- a/java/src/com/android/inputmethod/latin/makedict/UnsupportedFormatException.java
+++ b/java/src/com/android/inputmethod/latin/makedict/UnsupportedFormatException.java
@@ -19,7 +19,7 @@ package com.android.inputmethod.latin.makedict;
/**
* Simple exception thrown when a file format is not recognized.
*/
-public class UnsupportedFormatException extends Exception {
+public final class UnsupportedFormatException extends Exception {
public UnsupportedFormatException(String description) {
super(description);
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/Word.java b/java/src/com/android/inputmethod/latin/makedict/Word.java
index 4683ef154..4c4f18f1a 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Word.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Word.java
@@ -26,7 +26,7 @@ import java.util.Arrays;
*
* This is chiefly used to iterate a dictionary.
*/
-public class Word implements Comparable<Word> {
+public final class Word implements Comparable<Word> {
public final String mWord;
public final int mFrequency;
public final ArrayList<WeightedString> mShortcutTargets;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index eef7a51f2..49b98863f 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -50,7 +50,7 @@ import java.util.TreeMap;
/**
* Service for spell checking, using LatinIME's dictionaries and mechanisms.
*/
-public class AndroidSpellCheckerService extends SpellCheckerService
+public final class AndroidSpellCheckerService extends SpellCheckerService
implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = AndroidSpellCheckerService.class.getSimpleName();
private static final boolean DBG = false;
@@ -201,8 +201,8 @@ public class AndroidSpellCheckerService extends SpellCheckerService
}
// TODO: remove this class and replace it by storage local to the session.
- public static class SuggestionsGatherer {
- public static class Result {
+ public static final class SuggestionsGatherer {
+ public static final class Result {
public final String[] mSuggestions;
public final boolean mHasRecommendedSuggestions;
public Result(final String[] gatheredSuggestions,
@@ -212,7 +212,7 @@ public 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;
@@ -335,7 +335,7 @@ public 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);
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
index 5a1bd37f5..668e7a641 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
@@ -26,7 +26,7 @@ import com.android.inputmethod.latin.CollectionUtils;
import java.util.ArrayList;
-public class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheckerSession {
+public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheckerSession {
private static final String TAG = AndroidSpellCheckerSession.class.getSimpleName();
private static final boolean DBG = false;
private final static String[] EMPTY_STRING_ARRAY = new String[0];
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index d9b622a18..a8f323999 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -50,7 +50,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
protected final SuggestionsCache mSuggestionsCache = new SuggestionsCache();
private final ContentObserver mObserver;
- private static class SuggestionsParams {
+ private static final class SuggestionsParams {
public final String[] mSuggestions;
public final int mFlags;
public SuggestionsParams(String[] suggestions, int flags) {
@@ -59,7 +59,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
}
}
- protected static class SuggestionsCache {
+ protected static final class SuggestionsCache {
private static final char CHAR_DELIMITER = '\uFFFC';
private static final int MAX_CACHE_SIZE = 50;
private final LruCache<String, SuggestionsParams> mUnigramSuggestionsInfoCache =
@@ -268,7 +268,7 @@ 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);
}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java b/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java
index 3dbbd40cd..9d7c61a33 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java
@@ -22,7 +22,7 @@ import com.android.inputmethod.keyboard.ProximityInfo;
/**
* A simple container for both a Dictionary and a ProximityInfo.
*/
-public class DictAndProximity {
+public final class DictAndProximity {
public final Dictionary mDictionary;
public final ProximityInfo mProximityInfo;
public DictAndProximity(final Dictionary dictionary, final ProximityInfo proximityInfo) {
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
index 53aa6c719..eae5d2e60 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
@@ -36,7 +36,7 @@ import java.util.concurrent.TimeUnit;
* the client code, but may help with sloppy clients.
*/
@SuppressWarnings("serial")
-public class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> {
+public final class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> {
private final static String TAG = DictionaryPool.class.getSimpleName();
// How many seconds we wait for a dictionary to become available. Past this delay, we give up in
// fear some bug caused a deadlock, and reset the whole pool.
@@ -51,11 +51,11 @@ public 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 fe5225ebd..6c0d79c2b 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
@@ -16,14 +16,15 @@
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;
import java.util.TreeMap;
-public class SpellCheckerProximityInfo {
- /* public for test */
+public final class SpellCheckerProximityInfo {
+ @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
@@ -53,7 +54,7 @@ public class SpellCheckerProximityInfo {
return result;
}
- private static class Latin {
+ private static final class Latin {
// This is a map from the code point to the index in the PROXIMITY array.
// At the time the native code to read the binary dictionary needs the proximity info be
// passed as a flat array spaced by MAX_PROXIMITY_CHARS_SIZE columns, one for each input
@@ -122,7 +123,7 @@ public class SpellCheckerProximityInfo {
}
}
- private static class Cyrillic {
+ private static final class Cyrillic {
final private static TreeMap<Integer, Integer> INDICES = CollectionUtils.newTreeMap();
// TODO: The following table is solely based on the keyboard layout. Consult with Russian
// speakers on commonly misspelled words/letters.
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java
index e14db8797..e63dff312 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java
@@ -23,7 +23,7 @@ import android.preference.PreferenceActivity;
/**
* Spell checker preference screen.
*/
-public class SpellCheckerSettingsActivity extends PreferenceActivity {
+public final class SpellCheckerSettingsActivity extends PreferenceActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
index 7056874a1..ef5123d68 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
@@ -24,7 +24,7 @@ import com.android.inputmethod.latin.R;
/**
* Preference screen.
*/
-public class SpellCheckerSettingsFragment extends PreferenceFragment {
+public final class SpellCheckerSettingsFragment extends PreferenceFragment {
/**
* Empty constructor for fragment generation.
*/
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index 1f883aa60..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;
@@ -30,14 +29,14 @@ import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.Utils;
-public class MoreSuggestions extends Keyboard {
+public final class MoreSuggestions extends Keyboard {
public static final int SUGGESTION_CODE_BASE = 1024;
MoreSuggestions(final MoreSuggestionsParam params) {
super(params);
}
- private static class MoreSuggestionsParam extends KeyboardParams {
+ private static final class MoreSuggestionsParam extends KeyboardParams {
private final int[] mWidths = new int[SuggestionStripView.MAX_SUGGESTIONS];
private final int[] mRowNumbers = new int[SuggestionStripView.MAX_SUGGESTIONS];
private final int[] mColumnOrders = new int[SuggestionStripView.MAX_SUGGESTIONS];
@@ -51,10 +50,11 @@ public class MoreSuggestions extends Keyboard {
super();
}
+ // TODO: Remove {@link MoreSuggestionsView} argument.
public int layout(final SuggestedWords suggestions, final int fromPos, final int maxWidth,
final int minWidth, final int maxRow, final MoreSuggestionsView view) {
clearKeys();
- final Resources res = view.getContext().getResources();
+ final Resources res = view.getResources();
mDivider = res.getDrawable(R.drawable.more_suggestions_divider);
mDividerWidth = mDivider.getIntrinsicWidth();
final int padding = (int) res.getDimension(
@@ -65,7 +65,7 @@ public 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;
@@ -163,7 +163,7 @@ public class MoreSuggestions extends Keyboard {
}
}
- public static class Builder extends KeyboardBuilder<MoreSuggestionsParam> {
+ public static final class Builder extends KeyboardBuilder<MoreSuggestionsParam> {
private final MoreSuggestionsView mPaneView;
private SuggestedWords mSuggestions;
private int mFromPos;
@@ -175,12 +175,13 @@ public 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,
mPaneView);
mFromPos = fromPos;
@@ -216,7 +217,7 @@ public class MoreSuggestions extends Keyboard {
}
}
- private static class Divider extends Key.Spacer {
+ private static final class Divider extends Key.Spacer {
private final Drawable mIcon;
public Divider(final KeyboardParams params, final Drawable icon, final int x,
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
index 5b23d7f3c..6cdd9e2cd 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
@@ -40,7 +40,7 @@ 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 class MoreSuggestionsView extends KeyboardView implements MoreKeysPanel {
+public final class MoreSuggestionsView extends KeyboardView implements MoreKeysPanel {
private final int[] mCoordinates = new int[2];
final KeyDetector mModalPanelKeyDetector;
@@ -56,17 +56,17 @@ public class MoreSuggestionsView extends KeyboardView implements MoreKeysPanel {
final KeyboardActionListener mSuggestionsPaneListener =
new KeyboardActionListener.Adapter() {
@Override
- public void onPressKey(int primaryCode) {
+ public void onPressKey(final int primaryCode) {
mListener.onPressKey(primaryCode);
}
@Override
- public void onReleaseKey(int primaryCode, boolean withSliding) {
+ public void onReleaseKey(final int primaryCode, final boolean withSliding) {
mListener.onReleaseKey(primaryCode, withSliding);
}
@Override
- public void onCodeInput(int primaryCode, int x, int y) {
+ 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);
@@ -79,11 +79,12 @@ public class MoreSuggestionsView extends KeyboardView implements MoreKeysPanel {
}
};
- 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();
@@ -94,7 +95,7 @@ public class MoreSuggestionsView extends KeyboardView implements MoreKeysPanel {
}
@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();
@@ -105,8 +106,12 @@ public class MoreSuggestionsView extends KeyboardView implements MoreKeysPanel {
}
}
+ public void updateKeyboardGeometry(final int keyHeight) {
+ mKeyDrawParams.updateParams(keyHeight, mKeyVisualAttributes);
+ }
+
@Override
- public void setKeyboard(Keyboard keyboard) {
+ public void setKeyboard(final Keyboard keyboard) {
super.setKeyboard(keyboard);
mModalPanelKeyDetector.setKeyboard(keyboard, -getPaddingLeft(), -getPaddingTop());
mSlidingPanelKeyDetector.setKeyboard(keyboard, -getPaddingLeft(),
@@ -134,15 +139,16 @@ public class MoreSuggestionsView extends KeyboardView implements MoreKeysPanel {
}
@Override
- public void setKeyPreviewPopupEnabled(boolean previewEnabled, int delay) {
+ public void setKeyPreviewPopupEnabled(final boolean previewEnabled, final 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) {
+ public void showMoreKeysPanel(final View parentView, final Controller controller,
+ final int pointX, final int pointY, final PopupWindow window,
+ final KeyboardActionListener listener) {
mController = controller;
mListener = listener;
final View container = (View)getParent();
@@ -175,12 +181,12 @@ public class MoreSuggestionsView extends KeyboardView implements MoreKeysPanel {
}
@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;
}
@@ -207,7 +213,7 @@ public class MoreSuggestionsView extends KeyboardView implements MoreKeysPanel {
};
@Override
- public boolean onTouchEvent(MotionEvent me) {
+ public boolean onTouchEvent(final MotionEvent me) {
final int action = me.getAction();
final long eventTime = me.getEventTime();
final int index = me.getActionIndex();
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index 9e8ab81b0..e7cb97fc2 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -52,13 +52,16 @@ 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;
@@ -70,11 +73,11 @@ import com.android.inputmethod.research.ResearchLogger;
import java.util.ArrayList;
-public class SuggestionStripView extends RelativeLayout implements OnClickListener,
+public final class SuggestionStripView extends RelativeLayout implements OnClickListener,
OnLongClickListener {
public interface Listener {
public boolean 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,7 +86,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
static final boolean DBG = LatinImeLogger.sDBG;
private final ViewGroup mSuggestionsStrip;
- private KeyboardView mKeyboardView;
+ KeyboardView mKeyboardView;
private final View mMoreSuggestionsContainer;
private final MoreSuggestionsView mMoreSuggestionsView;
@@ -97,23 +100,23 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
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 class UiHandler extends StaticInnerHandlerWrapper<SuggestionStripView> {
+ private static final class UiHandler extends StaticInnerHandlerWrapper<SuggestionStripView> {
private static final int MSG_HIDE_PREVIEW = 0;
- public UiHandler(SuggestionStripView outerInstance) {
+ public UiHandler(final SuggestionStripView outerInstance) {
super(outerInstance);
}
@Override
- public void dispatchMessage(Message msg) {
+ public void dispatchMessage(final Message msg) {
final SuggestionStripView suggestionStripView = getOuterInstance();
switch (msg.what) {
case MSG_HIDE_PREVIEW:
@@ -131,7 +134,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
}
}
- private static class SuggestionStripViewParams {
+ 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;
private static final int DEFAULT_MAX_MORE_SUGGESTIONS_ROW = 2;
@@ -177,8 +180,9 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
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 +254,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
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 +266,8 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
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 +284,9 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
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 +305,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
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 +318,8 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
}
}
- 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 +338,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
// 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 +362,8 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
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 +409,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
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 +425,14 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
}
}
- 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 +441,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
}
}
- 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 +454,8 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
}
}
- 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 +466,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
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 +476,8 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
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 +518,11 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
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 +532,8 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
}
}
- 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 +542,8 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
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 +564,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
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 +579,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
return width;
}
- private static Typeface getTextTypeface(CharSequence text) {
+ private static Typeface getTextTypeface(final CharSequence text) {
if (!(text instanceof SpannableString))
return Typeface.DEFAULT;
@@ -593,11 +601,12 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
* @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);
@@ -658,15 +667,12 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
* 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 +681,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
}
}
- public int setMoreSuggestionsHeight(int remainingHeight) {
+ public int setMoreSuggestionsHeight(final int remainingHeight) {
return mParams.setMoreSuggestionsHeight(remainingHeight);
}
@@ -684,7 +690,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
&& 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 +714,16 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
dismissMoreSuggestions();
}
- private void hidePreview() {
+ 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;
@@ -737,7 +743,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
}
};
- private boolean dismissMoreSuggestions() {
+ boolean dismissMoreSuggestions() {
if (mMoreSuggestionsWindow.isShowing()) {
mMoreSuggestionsWindow.dismiss();
return true;
@@ -746,41 +752,43 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
}
@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,
+ 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;
}
// Working variables for onLongClick and dispatchTouchEvent.
@@ -807,7 +815,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
};
@Override
- public boolean dispatchTouchEvent(MotionEvent me) {
+ public boolean dispatchTouchEvent(final MotionEvent me) {
if (!mMoreSuggestionsWindow.isShowing()
|| mMoreSuggestionsMode == MORE_SUGGESTIONS_IN_MODAL_MODE) {
mLastX = (int)me.getX();
@@ -849,7 +857,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
}
@Override
- public void onClick(View view) {
+ public void onClick(final View view) {
if (mParams.isAddToDictionaryShowing(view)) {
mListener.addWordToUserDictionary(mParams.getAddToDictionaryWord().toString());
clear();
@@ -863,7 +871,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
if (index >= mSuggestedWords.size())
return;
- final CharSequence word = mSuggestedWords.getWord(index);
+ final String word = mSuggestedWords.getWord(index);
mListener.pickSuggestionManually(index, word);
}
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index 763fd6e00..6295abe8c 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -870,7 +870,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)) {
@@ -1006,7 +1006,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);
@@ -1092,7 +1092,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 +1109,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(
diff --git a/java/src/com/android/inputmethod/research/Statistics.java b/java/src/com/android/inputmethod/research/Statistics.java
index eab465aa2..98491bd23 100644
--- a/java/src/com/android/inputmethod/research/Statistics.java
+++ b/java/src/com/android/inputmethod/research/Statistics.java
@@ -16,7 +16,7 @@
package com.android.inputmethod.research;
-import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.Constants;
public class Statistics {
// Number of characters entered during a typing session
@@ -104,7 +104,7 @@ public class Statistics {
public void recordChar(int codePoint, long time) {
final long delta = time - mLastTapTime;
- if (codePoint == Keyboard.CODE_DELETE) {
+ if (codePoint == Constants.CODE_DELETE) {
mDeleteKeyCount++;
if (delta < MIN_DELETION_INTERMISSION) {
if (mIsLastKeyDeleteKey) {