aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/com/android/inputmethod')
-rw-r--r--java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java3
-rw-r--r--java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java2
-rw-r--r--java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java16
-rw-r--r--java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java4
-rw-r--r--java/src/com/android/inputmethod/compat/CompatUtils.java2
-rw-r--r--java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java2
-rw-r--r--java/src/com/android/inputmethod/compat/InputMethodServiceCompatUtils.java2
-rw-r--r--java/src/com/android/inputmethod/compat/SettingsSecureCompatUtils.java2
-rw-r--r--java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java13
-rw-r--r--java/src/com/android/inputmethod/compat/SuggestionsInfoCompatUtils.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/Key.java290
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyDetector.java6
-rw-r--r--java/src/com/android/inputmethod/keyboard/Keyboard.java1144
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java24
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java76
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java52
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardView.java604
-rw-r--r--java/src/com/android/inputmethod/keyboard/MainKeyboardView.java218
-rw-r--r--java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java432
-rw-r--r--java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java4
-rw-r--r--java/src/com/android/inputmethod/keyboard/PointerTracker.java449
-rw-r--r--java/src/com/android/inputmethod/keyboard/ProximityInfo.java16
-rw-r--r--java/src/com/android/inputmethod/keyboard/ViewLayoutUtils.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java166
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java35
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewTrail.java70
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java140
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java46
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java97
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java46
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java (renamed from java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java)83
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java130
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java814
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java6
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java3
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java137
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java154
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java227
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeysCache.java40
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java61
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java4
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java117
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/SuddenJumpingTouchEventHandler.java4
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java76
-rw-r--r--java/src/com/android/inputmethod/latin/AdditionalSubtype.java2
-rw-r--r--java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java4
-rw-r--r--java/src/com/android/inputmethod/latin/AutoCorrection.java2
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java54
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java4
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java69
-rw-r--r--java/src/com/android/inputmethod/latin/CollectionUtils.java95
-rw-r--r--java/src/com/android/inputmethod/latin/Constants.java7
-rw-r--r--java/src/com/android/inputmethod/latin/DicTraverseSession.java4
-rw-r--r--java/src/com/android/inputmethod/latin/Dictionary.java8
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryCollection.java10
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFactory.java2
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java12
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableDictionary.java17
-rw-r--r--java/src/com/android/inputmethod/latin/ImfUtils.java2
-rw-r--r--java/src/com/android/inputmethod/latin/InputAttributes.java6
-rw-r--r--java/src/com/android/inputmethod/latin/InputPointers.java8
-rw-r--r--java/src/com/android/inputmethod/latin/InputTypeUtils.java2
-rw-r--r--java/src/com/android/inputmethod/latin/JniUtils.java2
-rw-r--r--java/src/com/android/inputmethod/latin/LastComposedWord.java12
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java250
-rw-r--r--java/src/com/android/inputmethod/latin/LocaleUtils.java41
-rw-r--r--java/src/com/android/inputmethod/latin/ResizableIntArray.java12
-rw-r--r--java/src/com/android/inputmethod/latin/ResourceUtils.java128
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputConnection.java86
-rw-r--r--java/src/com/android/inputmethod/latin/SettingsValues.java20
-rw-r--r--java/src/com/android/inputmethod/latin/StringUtils.java4
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeLocale.java10
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeSwitcher.java94
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java64
-rw-r--r--java/src/com/android/inputmethod/latin/SuggestedWords.java10
-rw-r--r--java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java193
-rw-r--r--java/src/com/android/inputmethod/latin/UserHistoryDictionary.java19
-rw-r--r--java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java7
-rw-r--r--java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java2
-rw-r--r--java/src/com/android/inputmethod/latin/Utils.java141
-rw-r--r--java/src/com/android/inputmethod/latin/VibratorUtils.java2
-rw-r--r--java/src/com/android/inputmethod/latin/WhitelistDictionary.java119
-rw-r--r--java/src/com/android/inputmethod/latin/WordComposer.java55
-rw-r--r--java/src/com/android/inputmethod/latin/XmlParseUtils.java2
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java466
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java88
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Word.java15
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java31
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java8
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java9
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java29
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java9
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java272
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java56
-rw-r--r--java/src/com/android/inputmethod/research/BootBroadcastReceiver.java33
-rw-r--r--java/src/com/android/inputmethod/research/FeedbackActivity.java8
-rw-r--r--java/src/com/android/inputmethod/research/LogBuffer.java113
-rw-r--r--java/src/com/android/inputmethod/research/LogUnit.java83
-rw-r--r--java/src/com/android/inputmethod/research/MainLogBuffer.java127
-rw-r--r--java/src/com/android/inputmethod/research/ResearchLog.java259
-rw-r--r--java/src/com/android/inputmethod/research/ResearchLogUploader.java223
-rw-r--r--java/src/com/android/inputmethod/research/ResearchLogger.java876
-rw-r--r--java/src/com/android/inputmethod/research/Statistics.java146
-rw-r--r--java/src/com/android/inputmethod/research/UploaderService.java191
104 files changed, 6237 insertions, 4207 deletions
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
index 56f9c2ab2..5af5d044f 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
@@ -35,6 +35,7 @@ import android.view.inputmethod.EditorInfo;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardView;
+import com.android.inputmethod.latin.CollectionUtils;
/**
* Exposes a virtual view sub-tree for {@link KeyboardView} and generates
@@ -55,7 +56,7 @@ public class AccessibilityEntityProvider extends AccessibilityNodeProviderCompat
private final AccessibilityUtils mAccessibilityUtils;
/** A map of integer IDs to {@link Key}s. */
- private final SparseArray<Key> mVirtualViewIdToKey = new SparseArray<Key>();
+ private final SparseArray<Key> mVirtualViewIdToKey = CollectionUtils.newSparseArray();
/** Temporary rect used to calculate in-screen bounds. */
private final Rect mTempBoundsInScreen = new Rect();
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
index 58d3022c9..1eee1df87 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
@@ -37,7 +37,7 @@ import com.android.inputmethod.compat.SettingsSecureCompatUtils;
import com.android.inputmethod.latin.InputTypeUtils;
import com.android.inputmethod.latin.R;
-public class AccessibilityUtils {
+public final class AccessibilityUtils {
private static final String TAG = AccessibilityUtils.class.getSimpleName();
private static final String CLASS = AccessibilityUtils.class.getClass().getName();
private static final String PACKAGE = AccessibilityUtils.class.getClass().getPackage()
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
index ed3468afb..77940c086 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
@@ -24,7 +24,6 @@ import android.support.v4.view.accessibility.AccessibilityEventCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewConfiguration;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
@@ -44,7 +43,7 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
/**
* Inset in pixels to look for keys when the user's finger exits the
- * keyboard area. See {@link ViewConfiguration#getScaledEdgeSlop()}.
+ * keyboard area.
*/
private int mEdgeSlop;
@@ -62,7 +61,8 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
private void initInternal(InputMethodService inputMethod) {
mInputMethod = inputMethod;
- mEdgeSlop = ViewConfiguration.get(inputMethod).getScaledEdgeSlop();
+ mEdgeSlop = inputMethod.getResources().getDimensionPixelSize(
+ R.dimen.accessibility_edge_slop);
}
/**
@@ -127,8 +127,14 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
public boolean dispatchHoverEvent(MotionEvent event, PointerTracker tracker) {
final int x = (int) event.getX();
final int y = (int) event.getY();
- final Key key = tracker.getKeyOn(x, y);
final Key previousKey = mLastHoverKey;
+ final Key key;
+
+ if (pointInView(x, y)) {
+ key = tracker.getKeyOn(x, y);
+ } else {
+ key = null;
+ }
mLastHoverKey = key;
@@ -136,7 +142,7 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
case MotionEvent.ACTION_HOVER_EXIT:
// Make sure we're not getting an EXIT event because the user slid
// off the keyboard area, then force a key press.
- if (pointInView(x, y) && (key != null)) {
+ if (key != null) {
getAccessibilityNodeProvider().simulateKeyPress(key);
}
//$FALL-THROUGH$
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
index 9b74070af..5c45448a5 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -25,6 +25,7 @@ import android.view.inputmethod.EditorInfo;
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.R;
import java.util.HashMap;
@@ -38,7 +39,7 @@ public class KeyCodeDescriptionMapper {
private static KeyCodeDescriptionMapper sInstance = new KeyCodeDescriptionMapper();
// Map of key labels to spoken description resource IDs
- private final HashMap<CharSequence, Integer> mKeyLabelMap;
+ private final HashMap<CharSequence, Integer> mKeyLabelMap = CollectionUtils.newHashMap();
// Sparse array of spoken description resource IDs indexed by key codes
private final SparseIntArray mKeyCodeMap;
@@ -52,7 +53,6 @@ public class KeyCodeDescriptionMapper {
}
private KeyCodeDescriptionMapper() {
- mKeyLabelMap = new HashMap<CharSequence, Integer>();
mKeyCodeMap = new SparseIntArray();
}
diff --git a/java/src/com/android/inputmethod/compat/CompatUtils.java b/java/src/com/android/inputmethod/compat/CompatUtils.java
index ce427e9c9..ffed6ecb1 100644
--- a/java/src/com/android/inputmethod/compat/CompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/CompatUtils.java
@@ -24,7 +24,7 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
-public class CompatUtils {
+public final class CompatUtils {
private static final String TAG = CompatUtils.class.getSimpleName();
private static final String EXTRA_INPUT_METHOD_ID = "input_method_id";
// TODO: Can these be constants instead of literal String constants?
diff --git a/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java b/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java
index 08c246f8b..210058bec 100644
--- a/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java
@@ -20,7 +20,7 @@ import android.view.inputmethod.EditorInfo;
import java.lang.reflect.Field;
-public class EditorInfoCompatUtils {
+public final class EditorInfoCompatUtils {
// EditorInfo.IME_FLAG_FORCE_ASCII has been introduced since API#16 (JellyBean).
private static final Field FIELD_IME_FLAG_FORCE_ASCII = CompatUtils.getField(
EditorInfo.class, "IME_FLAG_FORCE_ASCII");
diff --git a/java/src/com/android/inputmethod/compat/InputMethodServiceCompatUtils.java b/java/src/com/android/inputmethod/compat/InputMethodServiceCompatUtils.java
index 0befa7a66..8eea31ed2 100644
--- a/java/src/com/android/inputmethod/compat/InputMethodServiceCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/InputMethodServiceCompatUtils.java
@@ -20,7 +20,7 @@ import android.inputmethodservice.InputMethodService;
import java.lang.reflect.Method;
-public class InputMethodServiceCompatUtils {
+public final class InputMethodServiceCompatUtils {
private static final Method METHOD_enableHardwareAcceleration =
CompatUtils.getMethod(InputMethodService.class, "enableHardwareAcceleration");
diff --git a/java/src/com/android/inputmethod/compat/SettingsSecureCompatUtils.java b/java/src/com/android/inputmethod/compat/SettingsSecureCompatUtils.java
index 1b79992f0..db5abd0fe 100644
--- a/java/src/com/android/inputmethod/compat/SettingsSecureCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/SettingsSecureCompatUtils.java
@@ -18,7 +18,7 @@ package com.android.inputmethod.compat;
import java.lang.reflect.Field;
-public class SettingsSecureCompatUtils {
+public final class SettingsSecureCompatUtils {
private static final Field FIELD_ACCESSIBILITY_SPEAK_PASSWORD = CompatUtils.getField(
android.provider.Settings.Secure.class, "ACCESSIBILITY_SPEAK_PASSWORD");
diff --git a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
index 1183b5fb9..159f43650 100644
--- a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
+++ b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
@@ -16,10 +16,6 @@
package com.android.inputmethod.compat;
-import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.SuggestedWords;
-import com.android.inputmethod.latin.SuggestionSpanPickedNotificationReceiver;
-
import android.content.Context;
import android.text.Spannable;
import android.text.SpannableString;
@@ -27,12 +23,17 @@ import android.text.Spanned;
import android.text.TextUtils;
import android.util.Log;
+import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.SuggestionSpanPickedNotificationReceiver;
+
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Locale;
-public class SuggestionSpanUtils {
+public final class SuggestionSpanUtils {
private static final String TAG = SuggestionSpanUtils.class.getSimpleName();
// TODO: Use reflection to get field values
public static final String ACTION_SUGGESTION_PICKED =
@@ -119,7 +120,7 @@ public class SuggestionSpanUtils {
} else {
spannable = new SpannableString(pickedWord);
}
- final ArrayList<String> suggestionsList = new ArrayList<String>();
+ final ArrayList<String> suggestionsList = CollectionUtils.newArrayList();
for (int i = 0; i < suggestedWords.size(); ++i) {
if (suggestionsList.size() >= OBJ_SUGGESTIONS_MAX_SIZE) {
break;
diff --git a/java/src/com/android/inputmethod/compat/SuggestionsInfoCompatUtils.java b/java/src/com/android/inputmethod/compat/SuggestionsInfoCompatUtils.java
index e5f9db27c..8314212c9 100644
--- a/java/src/com/android/inputmethod/compat/SuggestionsInfoCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/SuggestionsInfoCompatUtils.java
@@ -20,7 +20,7 @@ import android.view.textservice.SuggestionsInfo;
import java.lang.reflect.Field;
-public class SuggestionsInfoCompatUtils {
+public final class SuggestionsInfoCompatUtils {
private static final Field FIELD_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS = CompatUtils.getField(
SuggestionsInfo.class, "RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS");
private static final Integer OBJ_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS = (Integer) CompatUtils
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index 178c9ff05..03c216474 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -31,11 +31,16 @@ import android.text.TextUtils;
import android.util.Log;
import android.util.Xml;
+import com.android.inputmethod.keyboard.internal.KeyDrawParams;
import com.android.inputmethod.keyboard.internal.KeySpecParser;
-import com.android.inputmethod.keyboard.internal.KeySpecParser.MoreKeySpec;
-import com.android.inputmethod.keyboard.internal.KeyStyles.KeyStyle;
+import com.android.inputmethod.keyboard.internal.KeyStyle;
+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.keyboard.internal.KeyboardRow;
+import com.android.inputmethod.keyboard.internal.MoreKeySpec;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.ResourceUtils;
import com.android.inputmethod.latin.StringUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -54,7 +59,6 @@ public class Key {
* The key code (unicode or custom code) that this key generates.
*/
public final int mCode;
- public final int mAltCode;
/** Label to display */
public final String mLabel;
@@ -89,22 +93,11 @@ public class Key {
/** Icon to display instead of a label. Icon takes precedence over a label */
private final int mIconId;
- /** Icon for disabled state */
- private final int mDisabledIconId;
- /** Preview version of the icon, for the preview popup */
- private final int mPreviewIconId;
/** Width of the key, not including the gap */
public final int mWidth;
/** Height of the key, not including the gap */
public final int mHeight;
- /** The horizontal gap around this key */
- public final int mHorizontalGap;
- /** The vertical gap below this key */
- public final int mVerticalGap;
- /** The visual insets */
- public final int mVisualInsetsLeft;
- public final int mVisualInsetsRight;
/** X coordinate of the key in the keyboard layout */
public final int mX;
/** Y coordinate of the key in the keyboard layout */
@@ -112,8 +105,6 @@ public class Key {
/** Hit bounding box of the key */
public final Rect mHitBox = new Rect();
- /** Text to output when pressed. This can be multiple characters, like ".com" */
- public final CharSequence mOutputText;
/** More keys */
public final MoreKeySpec[] mMoreKeys;
/** More keys column number and flags */
@@ -143,6 +134,34 @@ public class Key {
private static final int ACTION_FLAGS_ALT_CODE_WHILE_TYPING = 0x04;
private static final int ACTION_FLAGS_ENABLE_LONG_PRESS = 0x08;
+ public final KeyVisualAttributes mKeyVisualAttributes;
+
+ private final OptionalAttributes mOptionalAttributes;
+
+ private static class OptionalAttributes {
+ /** Text to output when pressed. This can be multiple characters, like ".com" */
+ public final String mOutputText;
+ public final int mAltCode;
+ /** Icon for disabled state */
+ public final int mDisabledIconId;
+ /** Preview version of the icon, for the preview popup */
+ public final int mPreviewIconId;
+ /** The visual insets */
+ public final int mVisualInsetsLeft;
+ public final int mVisualInsetsRight;
+
+ public OptionalAttributes(final String outputText, final int altCode,
+ final int disabledIconId, final int previewIconId,
+ final int visualInsetsLeft, final int visualInsetsRight) {
+ mOutputText = outputText;
+ mAltCode = altCode;
+ mDisabledIconId = disabledIconId;
+ mPreviewIconId = previewIconId;
+ mVisualInsetsLeft = visualInsetsLeft;
+ mVisualInsetsRight = visualInsetsRight;
+ }
+ }
+
private final int mHashCode;
/** The current pressed state of this key */
@@ -153,8 +172,8 @@ public class Key {
/**
* This constructor is being used only for keys in more keys keyboard.
*/
- public Key(Keyboard.Params params, MoreKeySpec moreKeySpec, int x, int y, int width, int height,
- int labelFlags) {
+ public Key(final KeyboardParams params, final MoreKeySpec moreKeySpec, final int x, final int y,
+ final int width, final int height, final int labelFlags) {
this(params, moreKeySpec.mLabel, null, moreKeySpec.mIconId, moreKeySpec.mCode,
moreKeySpec.mOutputText, x, y, width, height, labelFlags);
}
@@ -162,13 +181,11 @@ public class Key {
/**
* This constructor is being used only for key in popup suggestions pane.
*/
- public Key(Keyboard.Params params, String label, String hintLabel, int iconId,
- int code, String outputText, int x, int y, int width, int height, int labelFlags) {
+ public Key(final KeyboardParams params, final String label, final String hintLabel,
+ final int iconId, final int code, final String outputText, final int x, final int y,
+ final int width, final int height, final int labelFlags) {
mHeight = height - params.mVerticalGap;
- mHorizontalGap = params.mHorizontalGap;
- mVerticalGap = params.mVerticalGap;
- mVisualInsetsLeft = mVisualInsetsRight = 0;
- mWidth = width - mHorizontalGap;
+ mWidth = width - params.mHorizontalGap;
mHintLabel = hintLabel;
mLabelFlags = labelFlags;
mBackgroundType = BACKGROUND_TYPE_NORMAL;
@@ -176,17 +193,20 @@ public class Key {
mMoreKeys = null;
mMoreKeysColumnAndFlags = 0;
mLabel = label;
- mOutputText = outputText;
+ if (outputText == null) {
+ mOptionalAttributes = null;
+ } else {
+ mOptionalAttributes = new OptionalAttributes(outputText, CODE_UNSPECIFIED,
+ ICON_UNDEFINED, ICON_UNDEFINED, 0, 0);
+ }
mCode = code;
mEnabled = (code != CODE_UNSPECIFIED);
- mAltCode = CODE_UNSPECIFIED;
mIconId = iconId;
- mDisabledIconId = ICON_UNDEFINED;
- mPreviewIconId = ICON_UNDEFINED;
// Horizontal gap is divided equally to both sides of the key.
- mX = x + mHorizontalGap / 2;
+ mX = x + params.mHorizontalGap / 2;
mY = y;
mHitBox.set(x, y, x + width + 1, y + height);
+ mKeyVisualAttributes = null;
mHashCode = computeHashCode(this);
}
@@ -201,12 +221,11 @@ public class Key {
* @param parser the XML parser containing the attributes for this key
* @throws XmlPullParserException
*/
- public Key(Resources res, Keyboard.Params params, Keyboard.Builder.Row row,
- XmlPullParser parser) throws XmlPullParserException {
+ public Key(final Resources res, final KeyboardParams params, final KeyboardRow row,
+ final XmlPullParser parser) throws XmlPullParserException {
final float horizontalGap = isSpacer() ? 0 : params.mHorizontalGap;
final int keyHeight = row.mRowHeight;
- mVerticalGap = params.mVerticalGap;
- mHeight = keyHeight - mVerticalGap;
+ mHeight = keyHeight - params.mVerticalGap;
final TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Keyboard_Key);
@@ -220,7 +239,6 @@ public class Key {
mX = Math.round(keyXPos + horizontalGap / 2);
mY = keyYPos;
mWidth = Math.round(keyWidth - horizontalGap);
- mHorizontalGap = Math.round(horizontalGap);
mHitBox.set(Math.round(keyXPos), keyYPos, Math.round(keyXPos + keyWidth) + 1,
keyYPos + keyHeight);
// Update row to have current x coordinate.
@@ -229,15 +247,15 @@ public class Key {
mBackgroundType = style.getInt(keyAttr,
R.styleable.Keyboard_Key_backgroundType, row.getDefaultBackgroundType());
- mVisualInsetsLeft = Math.round(Keyboard.Builder.getDimensionOrFraction(keyAttr,
+ final int visualInsetsLeft = Math.round(ResourceUtils.getDimensionOrFraction(keyAttr,
R.styleable.Keyboard_Key_visualInsetsLeft, params.mBaseWidth, 0));
- mVisualInsetsRight = Math.round(Keyboard.Builder.getDimensionOrFraction(keyAttr,
+ final int visualInsetsRight = Math.round(ResourceUtils.getDimensionOrFraction(keyAttr,
R.styleable.Keyboard_Key_visualInsetsRight, params.mBaseWidth, 0));
mIconId = KeySpecParser.getIconId(style.getString(keyAttr,
R.styleable.Keyboard_Key_keyIcon));
- mDisabledIconId = KeySpecParser.getIconId(style.getString(keyAttr,
+ final int disabledIconId = KeySpecParser.getIconId(style.getString(keyAttr,
R.styleable.Keyboard_Key_keyIconDisabled));
- mPreviewIconId = KeySpecParser.getIconId(style.getString(keyAttr,
+ final int previewIconId = KeySpecParser.getIconId(style.getString(keyAttr,
R.styleable.Keyboard_Key_keyIconPreview));
mLabelFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags)
@@ -331,21 +349,28 @@ public class Key {
} else {
mCode = KeySpecParser.toUpperCaseOfCodeForLocale(code, needsToUpperCase, locale);
}
- mOutputText = outputText;
- mAltCode = KeySpecParser.toUpperCaseOfCodeForLocale(
+ final int altCode = KeySpecParser.toUpperCaseOfCodeForLocale(
KeySpecParser.parseCode(style.getString(keyAttr,
R.styleable.Keyboard_Key_altCode), params.mCodesSet, CODE_UNSPECIFIED),
needsToUpperCase, locale);
- mHashCode = computeHashCode(this);
-
+ if (outputText == null && altCode == CODE_UNSPECIFIED
+ && disabledIconId == ICON_UNDEFINED && previewIconId == ICON_UNDEFINED
+ && visualInsetsLeft == 0 && visualInsetsRight == 0) {
+ mOptionalAttributes = null;
+ } else {
+ mOptionalAttributes = new OptionalAttributes(outputText, altCode,
+ disabledIconId, previewIconId,
+ visualInsetsLeft, visualInsetsRight);
+ }
+ mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr);
keyAttr.recycle();
-
+ mHashCode = computeHashCode(this);
if (hasShiftedLetterHint() && TextUtils.isEmpty(mHintLabel)) {
Log.w(TAG, "hasShiftedLetterHint specified without keyHintLabel: " + this);
}
}
- private static boolean needsToUpperCase(int labelFlags, int keyboardElementId) {
+ private static boolean needsToUpperCase(final int labelFlags, final int keyboardElementId) {
if ((labelFlags & LABEL_FLAGS_PRESERVE_CASE) != 0) return false;
switch (keyboardElementId) {
case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
@@ -358,7 +383,7 @@ public class Key {
}
}
- private static int computeHashCode(Key key) {
+ private static int computeHashCode(final Key key) {
return Arrays.hashCode(new Object[] {
key.mX,
key.mY,
@@ -370,22 +395,22 @@ public class Key {
key.mIconId,
key.mBackgroundType,
Arrays.hashCode(key.mMoreKeys),
- key.mOutputText,
+ key.getOutputText(),
key.mActionFlags,
key.mLabelFlags,
// Key can be distinguishable without the following members.
- // key.mAltCode,
- // key.mDisabledIconId,
- // key.mPreviewIconId,
+ // key.mOptionalAttributes.mAltCode,
+ // key.mOptionalAttributes.mDisabledIconId,
+ // key.mOptionalAttributes.mPreviewIconId,
// key.mHorizontalGap,
// key.mVerticalGap,
- // key.mVisualInsetLeft,
- // key.mVisualInsetRight,
+ // key.mOptionalAttributes.mVisualInsetLeft,
+ // key.mOptionalAttributes.mVisualInsetRight,
// key.mMaxMoreKeysColumn,
});
}
- private boolean equals(Key o) {
+ private boolean equals(final Key o) {
if (this == o) return true;
return o.mX == mX
&& o.mY == mY
@@ -397,7 +422,7 @@ public class Key {
&& o.mIconId == mIconId
&& o.mBackgroundType == mBackgroundType
&& Arrays.equals(o.mMoreKeys, mMoreKeys)
- && TextUtils.equals(o.mOutputText, mOutputText)
+ && TextUtils.equals(o.getOutputText(), getOutputText())
&& o.mActionFlags == mActionFlags
&& o.mLabelFlags == mLabelFlags;
}
@@ -408,7 +433,7 @@ public class Key {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(final Object o) {
return o instanceof Key && equals((Key)o);
}
@@ -425,7 +450,7 @@ public class Key {
KeyboardIconsSet.getIconName(mIconId), backgroundName(mBackgroundType));
}
- private static String backgroundName(int backgroundType) {
+ private static String backgroundName(final int backgroundType) {
switch (backgroundType) {
case BACKGROUND_TYPE_NORMAL: return "normal";
case BACKGROUND_TYPE_FUNCTIONAL: return "functional";
@@ -436,19 +461,19 @@ public class Key {
}
}
- public void markAsLeftEdge(Keyboard.Params params) {
+ public void markAsLeftEdge(final KeyboardParams params) {
mHitBox.left = params.mHorizontalEdgesPadding;
}
- public void markAsRightEdge(Keyboard.Params params) {
+ public void markAsRightEdge(final KeyboardParams params) {
mHitBox.right = params.mOccupiedWidth - params.mHorizontalEdgesPadding;
}
- public void markAsTopEdge(Keyboard.Params params) {
+ public void markAsTopEdge(final KeyboardParams params) {
mHitBox.top = params.mTopPadding;
}
- public void markAsBottomEdge(Keyboard.Params params) {
+ public void markAsBottomEdge(final KeyboardParams params) {
mHitBox.bottom = params.mOccupiedHeight + params.mBottomPadding;
}
@@ -456,129 +481,169 @@ public class Key {
return this instanceof Spacer;
}
- public boolean isShift() {
+ public final boolean isShift() {
return mCode == CODE_SHIFT;
}
- public boolean isModifier() {
+ public final boolean isModifier() {
return mCode == CODE_SHIFT || mCode == CODE_SWITCH_ALPHA_SYMBOL;
}
- public boolean isRepeatable() {
+ public final boolean isRepeatable() {
return (mActionFlags & ACTION_FLAGS_IS_REPEATABLE) != 0;
}
- public boolean noKeyPreview() {
+ public final boolean noKeyPreview() {
return (mActionFlags & ACTION_FLAGS_NO_KEY_PREVIEW) != 0;
}
- public boolean altCodeWhileTyping() {
+ public final boolean altCodeWhileTyping() {
return (mActionFlags & ACTION_FLAGS_ALT_CODE_WHILE_TYPING) != 0;
}
- public boolean isLongPressEnabled() {
+ public final boolean isLongPressEnabled() {
// We need not start long press timer on the key which has activated shifted letter.
return (mActionFlags & ACTION_FLAGS_ENABLE_LONG_PRESS) != 0
&& (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) == 0;
}
- public Typeface selectTypeface(Typeface defaultTypeface) {
+ public final Typeface selectTypeface(final KeyDrawParams params) {
// TODO: Handle "bold" here too?
if ((mLabelFlags & LABEL_FLAGS_FONT_NORMAL) != 0) {
return Typeface.DEFAULT;
} else if ((mLabelFlags & LABEL_FLAGS_FONT_MONO_SPACE) != 0) {
return Typeface.MONOSPACE;
} else {
- return defaultTypeface;
+ return params.mTypeface;
}
}
- public int selectTextSize(int letterSize, int largeLetterSize, int labelSize,
- int largeLabelSize, int hintLabelSize) {
+ public final int selectTextSize(final KeyDrawParams params) {
switch (mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_TEXT_RATIO_MASK) {
case LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO:
- return letterSize;
+ return params.mLetterSize;
case LABEL_FLAGS_FOLLOW_KEY_LARGE_LETTER_RATIO:
- return largeLetterSize;
+ return params.mLargeLetterSize;
case LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO:
- return labelSize;
+ return params.mLabelSize;
case LABEL_FLAGS_FOLLOW_KEY_LARGE_LABEL_RATIO:
- return largeLabelSize;
+ return params.mLargeLabelSize;
case LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO:
- return hintLabelSize;
+ return params.mHintLabelSize;
default: // No follow key ratio flag specified.
- return StringUtils.codePointCount(mLabel) == 1 ? letterSize : labelSize;
+ return StringUtils.codePointCount(mLabel) == 1 ? params.mLetterSize : params.mLabelSize;
}
}
- public boolean isAlignLeft() {
+ public final int selectTextColor(final KeyDrawParams params) {
+ return isShiftedLetterActivated() ? params.mTextInactivatedColor : params.mTextColor;
+ }
+
+ public final int selectHintTextSize(final KeyDrawParams params) {
+ if (hasHintLabel()) {
+ return params.mHintLabelSize;
+ } else if (hasShiftedLetterHint()) {
+ return params.mShiftedLetterHintSize;
+ } else {
+ return params.mHintLetterSize;
+ }
+ }
+
+ public final int selectHintTextColor(final KeyDrawParams params) {
+ if (hasHintLabel()) {
+ return params.mHintLabelColor;
+ } else if (hasShiftedLetterHint()) {
+ return isShiftedLetterActivated() ? params.mShiftedLetterHintActivatedColor
+ : params.mShiftedLetterHintInactivatedColor;
+ } else {
+ return params.mHintLetterColor;
+ }
+ }
+
+ public final int selectMoreKeyTextSize(final KeyDrawParams params) {
+ return hasLabelsInMoreKeys() ? params.mLabelSize : params.mLetterSize;
+ }
+
+ public final boolean isAlignLeft() {
return (mLabelFlags & LABEL_FLAGS_ALIGN_LEFT) != 0;
}
- public boolean isAlignRight() {
+ public final boolean isAlignRight() {
return (mLabelFlags & LABEL_FLAGS_ALIGN_RIGHT) != 0;
}
- public boolean isAlignLeftOfCenter() {
+ public final boolean isAlignLeftOfCenter() {
return (mLabelFlags & LABEL_FLAGS_ALIGN_LEFT_OF_CENTER) != 0;
}
- public boolean hasPopupHint() {
+ public final boolean hasPopupHint() {
return (mLabelFlags & LABEL_FLAGS_HAS_POPUP_HINT) != 0;
}
- public boolean hasShiftedLetterHint() {
+ public final boolean hasShiftedLetterHint() {
return (mLabelFlags & LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT) != 0;
}
- public boolean hasHintLabel() {
+ public final boolean hasHintLabel() {
return (mLabelFlags & LABEL_FLAGS_HAS_HINT_LABEL) != 0;
}
- public boolean hasLabelWithIconLeft() {
+ public final boolean hasLabelWithIconLeft() {
return (mLabelFlags & LABEL_FLAGS_WITH_ICON_LEFT) != 0;
}
- public boolean hasLabelWithIconRight() {
+ public final boolean hasLabelWithIconRight() {
return (mLabelFlags & LABEL_FLAGS_WITH_ICON_RIGHT) != 0;
}
- public boolean needsXScale() {
+ public final boolean needsXScale() {
return (mLabelFlags & LABEL_FLAGS_AUTO_X_SCALE) != 0;
}
- public boolean isShiftedLetterActivated() {
+ public final boolean isShiftedLetterActivated() {
return (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) != 0;
}
- public int getMoreKeysColumn() {
+ public final int getMoreKeysColumn() {
return mMoreKeysColumnAndFlags & MORE_KEYS_COLUMN_MASK;
}
- public boolean isFixedColumnOrderMoreKeys() {
+ public final boolean isFixedColumnOrderMoreKeys() {
return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER) != 0;
}
- public boolean hasLabelsInMoreKeys() {
+ public final boolean hasLabelsInMoreKeys() {
return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_HAS_LABELS) != 0;
}
- public int getMoreKeyLabelFlags() {
+ public final int getMoreKeyLabelFlags() {
return hasLabelsInMoreKeys()
? LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO
: LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO;
}
- public boolean needsDividersInMoreKeys() {
+ public final boolean needsDividersInMoreKeys() {
return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_NEEDS_DIVIDERS) != 0;
}
- public boolean hasEmbeddedMoreKey() {
+ public final boolean hasEmbeddedMoreKey() {
return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_EMBEDDED_MORE_KEY) != 0;
}
- public Drawable getIcon(KeyboardIconsSet iconSet, int alpha) {
- final int iconId = mEnabled ? mIconId : mDisabledIconId;
+ public final String getOutputText() {
+ final OptionalAttributes attrs = mOptionalAttributes;
+ return (attrs != null) ? attrs.mOutputText : null;
+ }
+
+ public final int getAltCode() {
+ final OptionalAttributes attrs = mOptionalAttributes;
+ return (attrs != null) ? attrs.mAltCode : CODE_UNSPECIFIED;
+ }
+
+ public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) {
+ final OptionalAttributes attrs = mOptionalAttributes;
+ final int disabledIconId = (attrs != null) ? attrs.mDisabledIconId : ICON_UNDEFINED;
+ final int iconId = mEnabled ? mIconId : disabledIconId;
final Drawable icon = iconSet.getIconDrawable(iconId);
if (icon != null) {
icon.setAlpha(alpha);
@@ -586,10 +651,22 @@ public class Key {
return icon;
}
- public Drawable getPreviewIcon(KeyboardIconsSet iconSet) {
- return mPreviewIconId != ICON_UNDEFINED
- ? iconSet.getIconDrawable(mPreviewIconId)
- : iconSet.getIconDrawable(mIconId);
+ public Drawable getPreviewIcon(final KeyboardIconsSet iconSet) {
+ final OptionalAttributes attrs = mOptionalAttributes;
+ final int previewIconId = (attrs != null) ? attrs.mPreviewIconId : ICON_UNDEFINED;
+ return previewIconId != ICON_UNDEFINED
+ ? iconSet.getIconDrawable(previewIconId) : iconSet.getIconDrawable(mIconId);
+ }
+
+ public final int getDrawX() {
+ final OptionalAttributes attrs = mOptionalAttributes;
+ return (attrs == null) ? mX : mX + attrs.mVisualInsetsLeft;
+ }
+
+ public final int getDrawWidth() {
+ final OptionalAttributes attrs = mOptionalAttributes;
+ return (attrs == null) ? mWidth
+ : mWidth - attrs.mVisualInsetsLeft - attrs.mVisualInsetsRight;
}
/**
@@ -610,11 +687,11 @@ public class Key {
mPressed = false;
}
- public boolean isEnabled() {
+ public final boolean isEnabled() {
return mEnabled;
}
- public void setEnabled(boolean enabled) {
+ public void setEnabled(final boolean enabled) {
mEnabled = enabled;
}
@@ -624,9 +701,9 @@ public class Key {
* @param y the y-coordinate of the point
* @return whether or not the point falls on the key. If the key is attached to an edge, it
* will assume that all points between the key and the edge are considered to be on the key.
- * @see #markAsLeftEdge(Keyboard.Params) etc.
+ * @see #markAsLeftEdge(KeyboardParams) etc.
*/
- public boolean isOnKey(int x, int y) {
+ public boolean isOnKey(final int x, final int y) {
return mHitBox.contains(x, y);
}
@@ -636,7 +713,7 @@ public class Key {
* @param y the y-coordinate of the point
* @return the square of the distance of the point from the nearest edge of the key
*/
- public int squaredDistanceToEdge(int x, int y) {
+ public int squaredDistanceToEdge(final int x, final int y) {
final int left = mX;
final int right = left + mWidth;
final int top = mY;
@@ -702,7 +779,7 @@ public class Key {
* @return the drawable state of the key.
* @see android.graphics.drawable.StateListDrawable#setState(int[])
*/
- public int[] getCurrentDrawableState() {
+ public final int[] getCurrentDrawableState() {
switch (mBackgroundType) {
case BACKGROUND_TYPE_FUNCTIONAL:
return mPressed ? KEY_STATE_FUNCTIONAL_PRESSED : KEY_STATE_FUNCTIONAL_NORMAL;
@@ -718,15 +795,16 @@ public class Key {
}
public static class Spacer extends Key {
- public Spacer(Resources res, Keyboard.Params params, Keyboard.Builder.Row row,
- XmlPullParser parser) throws XmlPullParserException {
+ public Spacer(final Resources res, final KeyboardParams params, final KeyboardRow row,
+ final XmlPullParser parser) throws XmlPullParserException {
super(res, params, row, parser);
}
/**
* This constructor is being used only for divider in more keys keyboard.
*/
- protected Spacer(Keyboard.Params params, int x, int y, int width, int height) {
+ protected Spacer(final KeyboardParams params, final int x, final int y, final int width,
+ final int height) {
super(params, null, null, ICON_UNDEFINED, CODE_UNSPECIFIED,
null, x, y, width, height, 0);
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
index c0e6aa8d7..868c8cab5 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
@@ -16,10 +16,10 @@
package com.android.inputmethod.keyboard;
+import com.android.inputmethod.latin.Constants;
-public class KeyDetector {
- public static final int NOT_A_CODE = -1;
+public class KeyDetector {
private final int mKeyHysteresisDistanceSquared;
private Keyboard mKeyboard;
@@ -103,7 +103,7 @@ public class KeyDetector {
final StringBuilder sb = new StringBuilder();
boolean addDelimiter = false;
for (final int code : codes) {
- if (code == NOT_A_CODE) break;
+ if (code == Constants.NOT_A_CODE) break;
if (addDelimiter) sb.append(", ");
sb.append(Keyboard.printableCode(code));
addDelimiter = true;
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index 919850095..261d1eba7 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -16,38 +16,15 @@
package com.android.inputmethod.keyboard;
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
import android.util.Log;
import android.util.SparseArray;
-import android.util.SparseIntArray;
-import android.util.TypedValue;
-import android.util.Xml;
-import android.view.InflateException;
-import com.android.inputmethod.keyboard.internal.KeyStyles;
-import com.android.inputmethod.keyboard.internal.KeyboardCodesSet;
+import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;
import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
-import com.android.inputmethod.keyboard.internal.KeyboardTextsSet;
-import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
-import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.SubtypeLocale;
-import com.android.inputmethod.latin.Utils;
-import com.android.inputmethod.latin.XmlParseUtils;
+import com.android.inputmethod.keyboard.internal.KeyboardParams;
+import com.android.inputmethod.latin.CollectionUtils;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Locale;
/**
* Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard
@@ -119,6 +96,9 @@ public class Keyboard {
/** Default gap between rows */
public final int mVerticalGap;
+ /** Per keyboard key visual parameters */
+ public final KeyVisualAttributes mKeyVisualAttributes;
+
public final int mMostCommonKeyHeight;
public final int mMostCommonKeyWidth;
@@ -134,12 +114,12 @@ public class Keyboard {
public final Key[] mAltCodeKeysWhileTyping;
public final KeyboardIconsSet mIconsSet;
- private final SparseArray<Key> mKeyCache = new SparseArray<Key>();
+ private final SparseArray<Key> mKeyCache = CollectionUtils.newSparseArray();
private final ProximityInfo mProximityInfo;
private final boolean mProximityCharsCorrectionEnabled;
- public Keyboard(Params params) {
+ public Keyboard(final KeyboardParams params) {
mId = params.mId;
mThemeId = params.mThemeId;
mOccupiedHeight = params.mOccupiedHeight;
@@ -148,7 +128,7 @@ public class Keyboard {
mMostCommonKeyWidth = params.mMostCommonKeyWidth;
mMoreKeysTemplate = params.mMoreKeysTemplate;
mMaxMoreKeysKeyboardColumn = params.mMaxMoreKeysKeyboardColumn;
-
+ mKeyVisualAttributes = params.mKeyVisualAttributes;
mTopPadding = params.mTopPadding;
mVerticalGap = params.mVerticalGap;
@@ -164,7 +144,7 @@ public class Keyboard {
mProximityCharsCorrectionEnabled = params.mProximityCharsCorrectionEnabled;
}
- public boolean hasProximityCharsCorrection(int code) {
+ public boolean hasProximityCharsCorrection(final int code) {
if (!mProximityCharsCorrectionEnabled) {
return false;
}
@@ -180,7 +160,7 @@ public class Keyboard {
return mProximityInfo;
}
- public Key getKey(int code) {
+ public Key getKey(final int code) {
if (code == CODE_UNSPECIFIED) {
return null;
}
@@ -201,7 +181,7 @@ public class Keyboard {
}
}
- public boolean hasKey(Key aKey) {
+ public boolean hasKey(final Key aKey) {
if (mKeyCache.indexOfValue(aKey) >= 0) {
return true;
}
@@ -215,7 +195,7 @@ public class Keyboard {
return false;
}
- public static boolean isLetterCode(int code) {
+ public static boolean isLetterCode(final int code) {
return code >= CODE_SPACE;
}
@@ -224,170 +204,6 @@ public class Keyboard {
return mId.toString();
}
- public static class Params {
- public KeyboardId mId;
- public int mThemeId;
-
- /** Total height and width of the keyboard, including the paddings and keys */
- public int mOccupiedHeight;
- public int mOccupiedWidth;
-
- /** Base height and width of the keyboard used to calculate rows' or keys' heights and
- * widths
- */
- public int mBaseHeight;
- public int mBaseWidth;
-
- public int mTopPadding;
- public int mBottomPadding;
- public int mHorizontalEdgesPadding;
- public int mHorizontalCenterPadding;
-
- public int mDefaultRowHeight;
- public int mDefaultKeyWidth;
- public int mHorizontalGap;
- public int mVerticalGap;
-
- public int mMoreKeysTemplate;
- public int mMaxMoreKeysKeyboardColumn;
-
- public int GRID_WIDTH;
- public int GRID_HEIGHT;
-
- public final HashSet<Key> mKeys = new HashSet<Key>();
- public final ArrayList<Key> mShiftKeys = new ArrayList<Key>();
- public final ArrayList<Key> mAltCodeKeysWhileTyping = new ArrayList<Key>();
- public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet();
- public final KeyboardCodesSet mCodesSet = new KeyboardCodesSet();
- public final KeyboardTextsSet mTextsSet = new KeyboardTextsSet();
- public final KeyStyles mKeyStyles = new KeyStyles(mTextsSet);
-
- public KeyboardLayoutSet.KeysCache mKeysCache;
-
- public int mMostCommonKeyHeight = 0;
- public int mMostCommonKeyWidth = 0;
-
- public boolean mProximityCharsCorrectionEnabled;
-
- public final TouchPositionCorrection mTouchPositionCorrection =
- new TouchPositionCorrection();
-
- public static class TouchPositionCorrection {
- private static final int TOUCH_POSITION_CORRECTION_RECORD_SIZE = 3;
-
- public boolean mEnabled;
- public float[] mXs;
- public float[] mYs;
- public float[] mRadii;
-
- public void load(String[] data) {
- final int dataLength = data.length;
- if (dataLength % TOUCH_POSITION_CORRECTION_RECORD_SIZE != 0) {
- if (LatinImeLogger.sDBG)
- throw new RuntimeException(
- "the size of touch position correction data is invalid");
- return;
- }
-
- final int length = dataLength / TOUCH_POSITION_CORRECTION_RECORD_SIZE;
- mXs = new float[length];
- mYs = new float[length];
- mRadii = new float[length];
- try {
- for (int i = 0; i < dataLength; ++i) {
- final int type = i % TOUCH_POSITION_CORRECTION_RECORD_SIZE;
- final int index = i / TOUCH_POSITION_CORRECTION_RECORD_SIZE;
- final float value = Float.parseFloat(data[i]);
- if (type == 0) {
- mXs[index] = value;
- } else if (type == 1) {
- mYs[index] = value;
- } else {
- mRadii[index] = value;
- }
- }
- } catch (NumberFormatException e) {
- if (LatinImeLogger.sDBG) {
- throw new RuntimeException(
- "the number format for touch position correction data is invalid");
- }
- mXs = null;
- mYs = null;
- mRadii = null;
- }
- }
-
- // TODO: Remove this method.
- public void setEnabled(boolean enabled) {
- mEnabled = enabled;
- }
-
- public boolean isValid() {
- return mEnabled && mXs != null && mYs != null && mRadii != null
- && mXs.length > 0 && mYs.length > 0 && mRadii.length > 0;
- }
- }
-
- protected void clearKeys() {
- mKeys.clear();
- mShiftKeys.clear();
- clearHistogram();
- }
-
- public void onAddKey(Key newKey) {
- final Key key = (mKeysCache != null) ? mKeysCache.get(newKey) : newKey;
- final boolean zeroWidthSpacer = key.isSpacer() && key.mWidth == 0;
- if (!zeroWidthSpacer) {
- mKeys.add(key);
- updateHistogram(key);
- }
- if (key.mCode == Keyboard.CODE_SHIFT) {
- mShiftKeys.add(key);
- }
- if (key.altCodeWhileTyping()) {
- mAltCodeKeysWhileTyping.add(key);
- }
- }
-
- private int mMaxHeightCount = 0;
- private int mMaxWidthCount = 0;
- private final SparseIntArray mHeightHistogram = new SparseIntArray();
- private final SparseIntArray mWidthHistogram = new SparseIntArray();
-
- private void clearHistogram() {
- mMostCommonKeyHeight = 0;
- mMaxHeightCount = 0;
- mHeightHistogram.clear();
-
- mMaxWidthCount = 0;
- mMostCommonKeyWidth = 0;
- mWidthHistogram.clear();
- }
-
- private static int updateHistogramCounter(SparseIntArray histogram, int key) {
- final int index = histogram.indexOfKey(key);
- final int count = (index >= 0 ? histogram.get(key) : 0) + 1;
- histogram.put(key, count);
- return count;
- }
-
- private void updateHistogram(Key key) {
- final int height = key.mHeight + key.mVerticalGap;
- final int heightCount = updateHistogramCounter(mHeightHistogram, height);
- if (heightCount > mMaxHeightCount) {
- mMaxHeightCount = heightCount;
- mMostCommonKeyHeight = height;
- }
-
- final int width = key.mWidth + key.mHorizontalGap;
- final int widthCount = updateHistogramCounter(mWidthHistogram, width);
- if (widthCount > mMaxWidthCount) {
- mMaxWidthCount = widthCount;
- mMostCommonKeyWidth = width;
- }
- }
- }
-
/**
* Returns the array of the keys that are closest to the given point.
* @param x the x-coordinate of the point
@@ -395,14 +211,14 @@ public class Keyboard {
* @return the array of the nearest keys to the given point. If the given
* point is out of range, then an array of size zero is returned.
*/
- public Key[] getNearestKeys(int x, int y) {
+ public Key[] getNearestKeys(final int x, final int y) {
// Avoid dead pixels at edges of the keyboard
final int adjustedX = Math.max(0, Math.min(x, mOccupiedWidth - 1));
final int adjustedY = Math.max(0, Math.min(y, mOccupiedHeight - 1));
return mProximityInfo.getNearestKeys(adjustedX, adjustedY);
}
- public static String printableCode(int code) {
+ public static String printableCode(final int code) {
switch (code) {
case CODE_SHIFT: return "shift";
case CODE_SWITCH_ALPHA_SYMBOL: return "symbol";
@@ -424,934 +240,4 @@ public class Keyboard {
return String.format("'\\u%04x'", code);
}
}
-
- /**
- * Keyboard Building helper.
- *
- * This class parses Keyboard XML file and eventually build a Keyboard.
- * The Keyboard XML file looks like:
- * <pre>
- * &lt;!-- xml/keyboard.xml --&gt;
- * &lt;Keyboard keyboard_attributes*&gt;
- * &lt;!-- Keyboard Content --&gt;
- * &lt;Row row_attributes*&gt;
- * &lt;!-- Row Content --&gt;
- * &lt;Key key_attributes* /&gt;
- * &lt;Spacer horizontalGap="32.0dp" /&gt;
- * &lt;include keyboardLayout="@xml/other_keys"&gt;
- * ...
- * &lt;/Row&gt;
- * &lt;include keyboardLayout="@xml/other_rows"&gt;
- * ...
- * &lt;/Keyboard&gt;
- * </pre>
- * The XML file which is included in other file must have &lt;merge&gt; as root element,
- * such as:
- * <pre>
- * &lt;!-- xml/other_keys.xml --&gt;
- * &lt;merge&gt;
- * &lt;Key key_attributes* /&gt;
- * ...
- * &lt;/merge&gt;
- * </pre>
- * and
- * <pre>
- * &lt;!-- xml/other_rows.xml --&gt;
- * &lt;merge&gt;
- * &lt;Row row_attributes*&gt;
- * &lt;Key key_attributes* /&gt;
- * &lt;/Row&gt;
- * ...
- * &lt;/merge&gt;
- * </pre>
- * You can also use switch-case-default tags to select Rows and Keys.
- * <pre>
- * &lt;switch&gt;
- * &lt;case case_attribute*&gt;
- * &lt;!-- Any valid tags at switch position --&gt;
- * &lt;/case&gt;
- * ...
- * &lt;default&gt;
- * &lt;!-- Any valid tags at switch position --&gt;
- * &lt;/default&gt;
- * &lt;/switch&gt;
- * </pre>
- * You can declare Key style and specify styles within Key tags.
- * <pre>
- * &lt;switch&gt;
- * &lt;case mode="email"&gt;
- * &lt;key-style styleName="f1-key" parentStyle="modifier-key"
- * keyLabel=".com"
- * /&gt;
- * &lt;/case&gt;
- * &lt;case mode="url"&gt;
- * &lt;key-style styleName="f1-key" parentStyle="modifier-key"
- * keyLabel="http://"
- * /&gt;
- * &lt;/case&gt;
- * &lt;/switch&gt;
- * ...
- * &lt;Key keyStyle="shift-key" ... /&gt;
- * </pre>
- */
-
- public static class Builder<KP extends Params> {
- private static final String BUILDER_TAG = "Keyboard.Builder";
- private static final boolean DEBUG = false;
-
- // Keyboard XML Tags
- private static final String TAG_KEYBOARD = "Keyboard";
- private static final String TAG_ROW = "Row";
- private static final String TAG_KEY = "Key";
- private static final String TAG_SPACER = "Spacer";
- private static final String TAG_INCLUDE = "include";
- private static final String TAG_MERGE = "merge";
- private static final String TAG_SWITCH = "switch";
- private static final String TAG_CASE = "case";
- private static final String TAG_DEFAULT = "default";
- public static final String TAG_KEY_STYLE = "key-style";
-
- private static final int DEFAULT_KEYBOARD_COLUMNS = 10;
- private static final int DEFAULT_KEYBOARD_ROWS = 4;
-
- protected final KP mParams;
- protected final Context mContext;
- protected final Resources mResources;
- private final DisplayMetrics mDisplayMetrics;
-
- private int mCurrentY = 0;
- private Row mCurrentRow = null;
- private boolean mLeftEdge;
- private boolean mTopEdge;
- private Key mRightEdgeKey = null;
-
- /**
- * Container for keys in the keyboard. All keys in a row are at the same Y-coordinate.
- * Some of the key size defaults can be overridden per row from what the {@link Keyboard}
- * defines.
- */
- public static class Row {
- // keyWidth enum constants
- private static final int KEYWIDTH_NOT_ENUM = 0;
- private static final int KEYWIDTH_FILL_RIGHT = -1;
-
- private final Params mParams;
- /** Default width of a key in this row. */
- private float mDefaultKeyWidth;
- /** Default height of a key in this row. */
- public final int mRowHeight;
- /** Default keyLabelFlags in this row. */
- private int mDefaultKeyLabelFlags;
- /** Default backgroundType for this row */
- private int mDefaultBackgroundType;
-
- private final int mCurrentY;
- // Will be updated by {@link Key}'s constructor.
- private float mCurrentX;
-
- public Row(Resources res, Params params, XmlPullParser parser, int y) {
- mParams = params;
- TypedArray keyboardAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
- R.styleable.Keyboard);
- mRowHeight = (int)Builder.getDimensionOrFraction(keyboardAttr,
- R.styleable.Keyboard_rowHeight,
- params.mBaseHeight, params.mDefaultRowHeight);
- keyboardAttr.recycle();
- TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
- R.styleable.Keyboard_Key);
- mDefaultKeyWidth = Builder.getDimensionOrFraction(keyAttr,
- R.styleable.Keyboard_Key_keyWidth,
- params.mBaseWidth, params.mDefaultKeyWidth);
- mDefaultBackgroundType = keyAttr.getInt(R.styleable.Keyboard_Key_backgroundType,
- Key.BACKGROUND_TYPE_NORMAL);
- keyAttr.recycle();
-
- // TODO: Initialize this with <Row> attribute as backgroundType is done.
- mDefaultKeyLabelFlags = 0;
- mCurrentY = y;
- mCurrentX = 0.0f;
- }
-
- public float getDefaultKeyWidth() {
- return mDefaultKeyWidth;
- }
-
- public void setDefaultKeyWidth(float defaultKeyWidth) {
- mDefaultKeyWidth = defaultKeyWidth;
- }
-
- public int getDefaultKeyLabelFlags() {
- return mDefaultKeyLabelFlags;
- }
-
- public void setDefaultKeyLabelFlags(int keyLabelFlags) {
- mDefaultKeyLabelFlags = keyLabelFlags;
- }
-
- public int getDefaultBackgroundType() {
- return mDefaultBackgroundType;
- }
-
- public void setDefaultBackgroundType(int backgroundType) {
- mDefaultBackgroundType = backgroundType;
- }
-
- public void setXPos(float keyXPos) {
- mCurrentX = keyXPos;
- }
-
- public void advanceXPos(float width) {
- mCurrentX += width;
- }
-
- public int getKeyY() {
- return mCurrentY;
- }
-
- public float getKeyX(TypedArray keyAttr) {
- final int keyboardRightEdge = mParams.mOccupiedWidth
- - mParams.mHorizontalEdgesPadding;
- if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) {
- final float keyXPos = Builder.getDimensionOrFraction(keyAttr,
- R.styleable.Keyboard_Key_keyXPos, mParams.mBaseWidth, 0);
- if (keyXPos < 0) {
- // If keyXPos is negative, the actual x-coordinate will be
- // keyboardWidth + keyXPos.
- // keyXPos shouldn't be less than mCurrentX because drawable area for this
- // key starts at mCurrentX. Or, this key will overlaps the adjacent key on
- // its left hand side.
- return Math.max(keyXPos + keyboardRightEdge, mCurrentX);
- } else {
- return keyXPos + mParams.mHorizontalEdgesPadding;
- }
- }
- return mCurrentX;
- }
-
- public float getKeyWidth(TypedArray keyAttr) {
- return getKeyWidth(keyAttr, mCurrentX);
- }
-
- public float getKeyWidth(TypedArray keyAttr, float keyXPos) {
- final int widthType = Builder.getEnumValue(keyAttr,
- R.styleable.Keyboard_Key_keyWidth, KEYWIDTH_NOT_ENUM);
- switch (widthType) {
- case KEYWIDTH_FILL_RIGHT:
- final int keyboardRightEdge =
- mParams.mOccupiedWidth - mParams.mHorizontalEdgesPadding;
- // If keyWidth is fillRight, the actual key width will be determined to fill
- // out the area up to the right edge of the keyboard.
- return keyboardRightEdge - keyXPos;
- default: // KEYWIDTH_NOT_ENUM
- return Builder.getDimensionOrFraction(keyAttr,
- R.styleable.Keyboard_Key_keyWidth,
- mParams.mBaseWidth, mDefaultKeyWidth);
- }
- }
- }
-
- public Builder(Context context, KP params) {
- mContext = context;
- final Resources res = context.getResources();
- mResources = res;
- mDisplayMetrics = res.getDisplayMetrics();
-
- mParams = params;
-
- params.GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width);
- params.GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height);
- }
-
- public void setAutoGenerate(KeyboardLayoutSet.KeysCache keysCache) {
- mParams.mKeysCache = keysCache;
- }
-
- public Builder<KP> load(int xmlId, KeyboardId id) {
- mParams.mId = id;
- final XmlResourceParser parser = mResources.getXml(xmlId);
- try {
- parseKeyboard(parser);
- } catch (XmlPullParserException e) {
- Log.w(BUILDER_TAG, "keyboard XML parse error: " + e);
- throw new IllegalArgumentException(e);
- } catch (IOException e) {
- Log.w(BUILDER_TAG, "keyboard XML parse error: " + e);
- throw new RuntimeException(e);
- } finally {
- parser.close();
- }
- return this;
- }
-
- // TODO: Remove this method.
- public void setTouchPositionCorrectionEnabled(boolean enabled) {
- mParams.mTouchPositionCorrection.setEnabled(enabled);
- }
-
- public void setProximityCharsCorrectionEnabled(boolean enabled) {
- mParams.mProximityCharsCorrectionEnabled = enabled;
- }
-
- public Keyboard build() {
- return new Keyboard(mParams);
- }
-
- private int mIndent;
- private static final String SPACES = " ";
-
- private static String spaces(int count) {
- return (count < SPACES.length()) ? SPACES.substring(0, count) : SPACES;
- }
-
- private void startTag(String format, Object ... args) {
- Log.d(BUILDER_TAG, String.format(spaces(++mIndent * 2) + format, args));
- }
-
- private void endTag(String format, Object ... args) {
- Log.d(BUILDER_TAG, String.format(spaces(mIndent-- * 2) + format, args));
- }
-
- private void startEndTag(String format, Object ... args) {
- Log.d(BUILDER_TAG, String.format(spaces(++mIndent * 2) + format, args));
- mIndent--;
- }
-
- private void parseKeyboard(XmlPullParser parser)
- throws XmlPullParserException, IOException {
- if (DEBUG) startTag("<%s> %s", TAG_KEYBOARD, mParams.mId);
- int event;
- while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
- if (event == XmlPullParser.START_TAG) {
- final String tag = parser.getName();
- if (TAG_KEYBOARD.equals(tag)) {
- parseKeyboardAttributes(parser);
- startKeyboard();
- parseKeyboardContent(parser, false);
- break;
- } else {
- throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEYBOARD);
- }
- }
- }
- }
-
- private void parseKeyboardAttributes(XmlPullParser parser) {
- final int displayWidth = mDisplayMetrics.widthPixels;
- final TypedArray keyboardAttr = mContext.obtainStyledAttributes(
- Xml.asAttributeSet(parser), R.styleable.Keyboard, R.attr.keyboardStyle,
- R.style.Keyboard);
- final TypedArray keyAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
- R.styleable.Keyboard_Key);
- try {
- final int displayHeight = mDisplayMetrics.heightPixels;
- final String keyboardHeightString = Utils.getDeviceOverrideValue(
- mResources, R.array.keyboard_heights, null);
- final float keyboardHeight;
- if (keyboardHeightString != null) {
- keyboardHeight = Float.parseFloat(keyboardHeightString)
- * mDisplayMetrics.density;
- } else {
- keyboardHeight = keyboardAttr.getDimension(
- R.styleable.Keyboard_keyboardHeight, displayHeight / 2);
- }
- final float maxKeyboardHeight = getDimensionOrFraction(keyboardAttr,
- R.styleable.Keyboard_maxKeyboardHeight, displayHeight, displayHeight / 2);
- float minKeyboardHeight = getDimensionOrFraction(keyboardAttr,
- R.styleable.Keyboard_minKeyboardHeight, displayHeight, displayHeight / 2);
- if (minKeyboardHeight < 0) {
- // Specified fraction was negative, so it should be calculated against display
- // width.
- minKeyboardHeight = -getDimensionOrFraction(keyboardAttr,
- R.styleable.Keyboard_minKeyboardHeight, displayWidth, displayWidth / 2);
- }
- final Params params = mParams;
- // Keyboard height will not exceed maxKeyboardHeight and will not be less than
- // minKeyboardHeight.
- params.mOccupiedHeight = (int)Math.max(
- Math.min(keyboardHeight, maxKeyboardHeight), minKeyboardHeight);
- params.mOccupiedWidth = params.mId.mWidth;
- params.mTopPadding = (int)getDimensionOrFraction(keyboardAttr,
- R.styleable.Keyboard_keyboardTopPadding, params.mOccupiedHeight, 0);
- params.mBottomPadding = (int)getDimensionOrFraction(keyboardAttr,
- R.styleable.Keyboard_keyboardBottomPadding, params.mOccupiedHeight, 0);
- params.mHorizontalEdgesPadding = (int)getDimensionOrFraction(keyboardAttr,
- R.styleable.Keyboard_keyboardHorizontalEdgesPadding,
- mParams.mOccupiedWidth, 0);
-
- params.mBaseWidth = params.mOccupiedWidth - params.mHorizontalEdgesPadding * 2
- - params.mHorizontalCenterPadding;
- params.mDefaultKeyWidth = (int)getDimensionOrFraction(keyAttr,
- R.styleable.Keyboard_Key_keyWidth, params.mBaseWidth,
- params.mBaseWidth / DEFAULT_KEYBOARD_COLUMNS);
- params.mHorizontalGap = (int)getDimensionOrFraction(keyboardAttr,
- R.styleable.Keyboard_horizontalGap, params.mBaseWidth, 0);
- params.mVerticalGap = (int)getDimensionOrFraction(keyboardAttr,
- R.styleable.Keyboard_verticalGap, params.mOccupiedHeight, 0);
- params.mBaseHeight = params.mOccupiedHeight - params.mTopPadding
- - params.mBottomPadding + params.mVerticalGap;
- params.mDefaultRowHeight = (int)getDimensionOrFraction(keyboardAttr,
- R.styleable.Keyboard_rowHeight, params.mBaseHeight,
- params.mBaseHeight / DEFAULT_KEYBOARD_ROWS);
-
- params.mMoreKeysTemplate = keyboardAttr.getResourceId(
- R.styleable.Keyboard_moreKeysTemplate, 0);
- params.mMaxMoreKeysKeyboardColumn = keyAttr.getInt(
- R.styleable.Keyboard_Key_maxMoreKeysColumn, 5);
-
- params.mThemeId = keyboardAttr.getInt(R.styleable.Keyboard_themeId, 0);
- params.mIconsSet.loadIcons(keyboardAttr);
- final String language = params.mId.mLocale.getLanguage();
- params.mCodesSet.setLanguage(language);
- params.mTextsSet.setLanguage(language);
- final RunInLocale<Void> job = new RunInLocale<Void>() {
- @Override
- protected Void job(Resources res) {
- params.mTextsSet.loadStringResources(mContext);
- return null;
- }
- };
- // Null means the current system locale.
- final Locale locale = SubtypeLocale.isNoLanguage(params.mId.mSubtype)
- ? null : params.mId.mLocale;
- job.runInLocale(mResources, locale);
-
- 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);
- }
- } finally {
- keyAttr.recycle();
- keyboardAttr.recycle();
- }
- }
-
- private void parseKeyboardContent(XmlPullParser parser, boolean skip)
- throws XmlPullParserException, IOException {
- int event;
- while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
- if (event == XmlPullParser.START_TAG) {
- final String tag = parser.getName();
- if (TAG_ROW.equals(tag)) {
- Row row = parseRowAttributes(parser);
- if (DEBUG) startTag("<%s>%s", TAG_ROW, skip ? " skipped" : "");
- if (!skip) {
- startRow(row);
- }
- parseRowContent(parser, row, skip);
- } else if (TAG_INCLUDE.equals(tag)) {
- parseIncludeKeyboardContent(parser, skip);
- } else if (TAG_SWITCH.equals(tag)) {
- parseSwitchKeyboardContent(parser, skip);
- } else if (TAG_KEY_STYLE.equals(tag)) {
- parseKeyStyle(parser, skip);
- } else {
- throw new XmlParseUtils.IllegalStartTag(parser, TAG_ROW);
- }
- } else if (event == XmlPullParser.END_TAG) {
- final String tag = parser.getName();
- if (DEBUG) endTag("</%s>", tag);
- if (TAG_KEYBOARD.equals(tag)) {
- endKeyboard();
- break;
- } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)
- || TAG_MERGE.equals(tag)) {
- break;
- } else {
- throw new XmlParseUtils.IllegalEndTag(parser, TAG_ROW);
- }
- }
- }
- }
-
- private Row parseRowAttributes(XmlPullParser parser) throws XmlPullParserException {
- final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
- R.styleable.Keyboard);
- try {
- if (a.hasValue(R.styleable.Keyboard_horizontalGap))
- throw new XmlParseUtils.IllegalAttribute(parser, "horizontalGap");
- if (a.hasValue(R.styleable.Keyboard_verticalGap))
- throw new XmlParseUtils.IllegalAttribute(parser, "verticalGap");
- return new Row(mResources, mParams, parser, mCurrentY);
- } finally {
- a.recycle();
- }
- }
-
- private void parseRowContent(XmlPullParser parser, Row row, boolean skip)
- throws XmlPullParserException, IOException {
- int event;
- while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
- if (event == XmlPullParser.START_TAG) {
- final String tag = parser.getName();
- if (TAG_KEY.equals(tag)) {
- parseKey(parser, row, skip);
- } else if (TAG_SPACER.equals(tag)) {
- parseSpacer(parser, row, skip);
- } else if (TAG_INCLUDE.equals(tag)) {
- parseIncludeRowContent(parser, row, skip);
- } else if (TAG_SWITCH.equals(tag)) {
- parseSwitchRowContent(parser, row, skip);
- } else if (TAG_KEY_STYLE.equals(tag)) {
- parseKeyStyle(parser, skip);
- } else {
- throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEY);
- }
- } else if (event == XmlPullParser.END_TAG) {
- final String tag = parser.getName();
- if (DEBUG) endTag("</%s>", tag);
- if (TAG_ROW.equals(tag)) {
- if (!skip) {
- endRow(row);
- }
- break;
- } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)
- || TAG_MERGE.equals(tag)) {
- break;
- } else {
- throw new XmlParseUtils.IllegalEndTag(parser, TAG_KEY);
- }
- }
- }
- }
-
- private void parseKey(XmlPullParser parser, Row row, boolean skip)
- throws XmlPullParserException, IOException {
- if (skip) {
- XmlParseUtils.checkEndTag(TAG_KEY, parser);
- if (DEBUG) startEndTag("<%s /> skipped", TAG_KEY);
- } else {
- final Key key = new Key(mResources, mParams, row, parser);
- if (DEBUG) {
- startEndTag("<%s%s %s moreKeys=%s />", TAG_KEY,
- (key.isEnabled() ? "" : " disabled"), key,
- Arrays.toString(key.mMoreKeys));
- }
- XmlParseUtils.checkEndTag(TAG_KEY, parser);
- endKey(key);
- }
- }
-
- private void parseSpacer(XmlPullParser parser, Row row, boolean skip)
- throws XmlPullParserException, IOException {
- if (skip) {
- XmlParseUtils.checkEndTag(TAG_SPACER, parser);
- if (DEBUG) startEndTag("<%s /> skipped", TAG_SPACER);
- } else {
- final Key.Spacer spacer = new Key.Spacer(mResources, mParams, row, parser);
- if (DEBUG) startEndTag("<%s />", TAG_SPACER);
- XmlParseUtils.checkEndTag(TAG_SPACER, parser);
- endKey(spacer);
- }
- }
-
- private void parseIncludeKeyboardContent(XmlPullParser parser, boolean skip)
- throws XmlPullParserException, IOException {
- parseIncludeInternal(parser, null, skip);
- }
-
- private void parseIncludeRowContent(XmlPullParser parser, Row row, boolean skip)
- throws XmlPullParserException, IOException {
- parseIncludeInternal(parser, row, skip);
- }
-
- private void parseIncludeInternal(XmlPullParser parser, Row row, boolean skip)
- throws XmlPullParserException, IOException {
- if (skip) {
- XmlParseUtils.checkEndTag(TAG_INCLUDE, parser);
- if (DEBUG) startEndTag("</%s> skipped", TAG_INCLUDE);
- } else {
- final AttributeSet attr = Xml.asAttributeSet(parser);
- final TypedArray keyboardAttr = mResources.obtainAttributes(attr,
- R.styleable.Keyboard_Include);
- final TypedArray keyAttr = mResources.obtainAttributes(attr,
- R.styleable.Keyboard_Key);
- int keyboardLayout = 0;
- float savedDefaultKeyWidth = 0;
- int savedDefaultKeyLabelFlags = 0;
- int savedDefaultBackgroundType = Key.BACKGROUND_TYPE_NORMAL;
- try {
- XmlParseUtils.checkAttributeExists(keyboardAttr,
- R.styleable.Keyboard_Include_keyboardLayout, "keyboardLayout",
- TAG_INCLUDE, parser);
- keyboardLayout = keyboardAttr.getResourceId(
- R.styleable.Keyboard_Include_keyboardLayout, 0);
- if (row != null) {
- if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) {
- // Override current x coordinate.
- row.setXPos(row.getKeyX(keyAttr));
- }
- // TODO: Remove this if-clause and do the same as backgroundType below.
- savedDefaultKeyWidth = row.getDefaultKeyWidth();
- if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyWidth)) {
- // Override default key width.
- row.setDefaultKeyWidth(row.getKeyWidth(keyAttr));
- }
- savedDefaultKeyLabelFlags = row.getDefaultKeyLabelFlags();
- // Bitwise-or default keyLabelFlag if exists.
- row.setDefaultKeyLabelFlags(keyAttr.getInt(
- R.styleable.Keyboard_Key_keyLabelFlags, 0)
- | savedDefaultKeyLabelFlags);
- savedDefaultBackgroundType = row.getDefaultBackgroundType();
- // Override default backgroundType if exists.
- row.setDefaultBackgroundType(keyAttr.getInt(
- R.styleable.Keyboard_Key_backgroundType,
- savedDefaultBackgroundType));
- }
- } finally {
- keyboardAttr.recycle();
- keyAttr.recycle();
- }
-
- XmlParseUtils.checkEndTag(TAG_INCLUDE, parser);
- if (DEBUG) {
- startEndTag("<%s keyboardLayout=%s />",TAG_INCLUDE,
- mResources.getResourceEntryName(keyboardLayout));
- }
- final XmlResourceParser parserForInclude = mResources.getXml(keyboardLayout);
- try {
- parseMerge(parserForInclude, row, skip);
- } finally {
- if (row != null) {
- // Restore default keyWidth, keyLabelFlags, and backgroundType.
- row.setDefaultKeyWidth(savedDefaultKeyWidth);
- row.setDefaultKeyLabelFlags(savedDefaultKeyLabelFlags);
- row.setDefaultBackgroundType(savedDefaultBackgroundType);
- }
- parserForInclude.close();
- }
- }
- }
-
- private void parseMerge(XmlPullParser parser, Row row, boolean skip)
- throws XmlPullParserException, IOException {
- if (DEBUG) startTag("<%s>", TAG_MERGE);
- int event;
- while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
- if (event == XmlPullParser.START_TAG) {
- final String tag = parser.getName();
- if (TAG_MERGE.equals(tag)) {
- if (row == null) {
- parseKeyboardContent(parser, skip);
- } else {
- parseRowContent(parser, row, skip);
- }
- break;
- } else {
- throw new XmlParseUtils.ParseException(
- "Included keyboard layout must have <merge> root element", parser);
- }
- }
- }
- }
-
- private void parseSwitchKeyboardContent(XmlPullParser parser, boolean skip)
- throws XmlPullParserException, IOException {
- parseSwitchInternal(parser, null, skip);
- }
-
- private void parseSwitchRowContent(XmlPullParser parser, Row row, boolean skip)
- throws XmlPullParserException, IOException {
- parseSwitchInternal(parser, row, skip);
- }
-
- private void parseSwitchInternal(XmlPullParser parser, Row row, boolean skip)
- throws XmlPullParserException, IOException {
- if (DEBUG) startTag("<%s> %s", TAG_SWITCH, mParams.mId);
- boolean selected = false;
- int event;
- while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
- if (event == XmlPullParser.START_TAG) {
- final String tag = parser.getName();
- if (TAG_CASE.equals(tag)) {
- selected |= parseCase(parser, row, selected ? true : skip);
- } else if (TAG_DEFAULT.equals(tag)) {
- selected |= parseDefault(parser, row, selected ? true : skip);
- } else {
- throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEY);
- }
- } else if (event == XmlPullParser.END_TAG) {
- final String tag = parser.getName();
- if (TAG_SWITCH.equals(tag)) {
- if (DEBUG) endTag("</%s>", TAG_SWITCH);
- break;
- } else {
- throw new XmlParseUtils.IllegalEndTag(parser, TAG_KEY);
- }
- }
- }
- }
-
- private boolean parseCase(XmlPullParser parser, Row row, boolean skip)
- throws XmlPullParserException, IOException {
- final boolean selected = parseCaseCondition(parser);
- if (row == null) {
- // Processing Rows.
- parseKeyboardContent(parser, selected ? skip : true);
- } else {
- // Processing Keys.
- parseRowContent(parser, row, selected ? skip : true);
- }
- return selected;
- }
-
- private boolean parseCaseCondition(XmlPullParser parser) {
- final KeyboardId id = mParams.mId;
- if (id == null)
- return true;
-
- final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
- R.styleable.Keyboard_Case);
- try {
- final boolean keyboardLayoutSetElementMatched = matchTypedValue(a,
- R.styleable.Keyboard_Case_keyboardLayoutSetElement, id.mElementId,
- KeyboardId.elementIdToName(id.mElementId));
- final boolean modeMatched = matchTypedValue(a,
- R.styleable.Keyboard_Case_mode, id.mMode, KeyboardId.modeName(id.mMode));
- final boolean navigateNextMatched = matchBoolean(a,
- R.styleable.Keyboard_Case_navigateNext, id.navigateNext());
- final boolean navigatePreviousMatched = matchBoolean(a,
- R.styleable.Keyboard_Case_navigatePrevious, id.navigatePrevious());
- final boolean passwordInputMatched = matchBoolean(a,
- R.styleable.Keyboard_Case_passwordInput, id.passwordInput());
- final boolean clobberSettingsKeyMatched = matchBoolean(a,
- R.styleable.Keyboard_Case_clobberSettingsKey, id.mClobberSettingsKey);
- final boolean shortcutKeyEnabledMatched = matchBoolean(a,
- R.styleable.Keyboard_Case_shortcutKeyEnabled, id.mShortcutKeyEnabled);
- final boolean hasShortcutKeyMatched = matchBoolean(a,
- R.styleable.Keyboard_Case_hasShortcutKey, id.mHasShortcutKey);
- final boolean languageSwitchKeyEnabledMatched = matchBoolean(a,
- R.styleable.Keyboard_Case_languageSwitchKeyEnabled,
- id.mLanguageSwitchKeyEnabled);
- final boolean isMultiLineMatched = matchBoolean(a,
- R.styleable.Keyboard_Case_isMultiLine, id.isMultiLine());
- final boolean imeActionMatched = matchInteger(a,
- R.styleable.Keyboard_Case_imeAction, id.imeAction());
- final boolean localeCodeMatched = matchString(a,
- R.styleable.Keyboard_Case_localeCode, id.mLocale.toString());
- final boolean languageCodeMatched = matchString(a,
- R.styleable.Keyboard_Case_languageCode, id.mLocale.getLanguage());
- final boolean countryCodeMatched = matchString(a,
- R.styleable.Keyboard_Case_countryCode, id.mLocale.getCountry());
- final boolean selected = keyboardLayoutSetElementMatched && modeMatched
- && navigateNextMatched && navigatePreviousMatched && passwordInputMatched
- && clobberSettingsKeyMatched && shortcutKeyEnabledMatched
- && hasShortcutKeyMatched && languageSwitchKeyEnabledMatched
- && isMultiLineMatched && imeActionMatched && localeCodeMatched
- && languageCodeMatched && countryCodeMatched;
-
- if (DEBUG) {
- startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE,
- textAttr(a.getString(
- R.styleable.Keyboard_Case_keyboardLayoutSetElement),
- "keyboardLayoutSetElement"),
- textAttr(a.getString(R.styleable.Keyboard_Case_mode), "mode"),
- textAttr(a.getString(R.styleable.Keyboard_Case_imeAction),
- "imeAction"),
- booleanAttr(a, R.styleable.Keyboard_Case_navigateNext,
- "navigateNext"),
- booleanAttr(a, R.styleable.Keyboard_Case_navigatePrevious,
- "navigatePrevious"),
- booleanAttr(a, R.styleable.Keyboard_Case_clobberSettingsKey,
- "clobberSettingsKey"),
- booleanAttr(a, R.styleable.Keyboard_Case_passwordInput,
- "passwordInput"),
- booleanAttr(a, R.styleable.Keyboard_Case_shortcutKeyEnabled,
- "shortcutKeyEnabled"),
- booleanAttr(a, R.styleable.Keyboard_Case_hasShortcutKey,
- "hasShortcutKey"),
- booleanAttr(a, R.styleable.Keyboard_Case_languageSwitchKeyEnabled,
- "languageSwitchKeyEnabled"),
- booleanAttr(a, R.styleable.Keyboard_Case_isMultiLine,
- "isMultiLine"),
- textAttr(a.getString(R.styleable.Keyboard_Case_localeCode),
- "localeCode"),
- textAttr(a.getString(R.styleable.Keyboard_Case_languageCode),
- "languageCode"),
- textAttr(a.getString(R.styleable.Keyboard_Case_countryCode),
- "countryCode"),
- selected ? "" : " skipped");
- }
-
- return selected;
- } finally {
- a.recycle();
- }
- }
-
- private static boolean matchInteger(TypedArray a, int index, int value) {
- // If <case> does not have "index" attribute, that means this <case> is wild-card for
- // the attribute.
- return !a.hasValue(index) || a.getInt(index, 0) == value;
- }
-
- private static boolean matchBoolean(TypedArray a, int index, boolean value) {
- // If <case> does not have "index" attribute, that means this <case> is wild-card for
- // the attribute.
- return !a.hasValue(index) || a.getBoolean(index, false) == value;
- }
-
- private static boolean matchString(TypedArray a, int index, String value) {
- // If <case> does not have "index" attribute, that means this <case> is wild-card for
- // the attribute.
- return !a.hasValue(index)
- || stringArrayContains(a.getString(index).split("\\|"), value);
- }
-
- private static boolean matchTypedValue(TypedArray a, int index, int intValue,
- String strValue) {
- // If <case> does not have "index" attribute, that means this <case> is wild-card for
- // the attribute.
- final TypedValue v = a.peekValue(index);
- if (v == null)
- return true;
-
- if (isIntegerValue(v)) {
- return intValue == a.getInt(index, 0);
- } else if (isStringValue(v)) {
- return stringArrayContains(a.getString(index).split("\\|"), strValue);
- }
- return false;
- }
-
- private static boolean stringArrayContains(String[] array, String value) {
- for (final String elem : array) {
- if (elem.equals(value))
- return true;
- }
- return false;
- }
-
- private boolean parseDefault(XmlPullParser parser, Row row, boolean skip)
- throws XmlPullParserException, IOException {
- if (DEBUG) startTag("<%s>", TAG_DEFAULT);
- if (row == null) {
- parseKeyboardContent(parser, skip);
- } else {
- parseRowContent(parser, row, skip);
- }
- return true;
- }
-
- private void parseKeyStyle(XmlPullParser parser, boolean skip)
- throws XmlPullParserException, IOException {
- TypedArray keyStyleAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
- R.styleable.Keyboard_KeyStyle);
- TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser),
- R.styleable.Keyboard_Key);
- try {
- if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName))
- throw new XmlParseUtils.ParseException("<" + TAG_KEY_STYLE
- + "/> needs styleName attribute", parser);
- if (DEBUG) {
- startEndTag("<%s styleName=%s />%s", TAG_KEY_STYLE,
- keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName),
- skip ? " skipped" : "");
- }
- if (!skip)
- mParams.mKeyStyles.parseKeyStyleAttributes(keyStyleAttr, keyAttrs, parser);
- } finally {
- keyStyleAttr.recycle();
- keyAttrs.recycle();
- }
- XmlParseUtils.checkEndTag(TAG_KEY_STYLE, parser);
- }
-
- private void startKeyboard() {
- mCurrentY += mParams.mTopPadding;
- mTopEdge = true;
- }
-
- private void startRow(Row row) {
- addEdgeSpace(mParams.mHorizontalEdgesPadding, row);
- mCurrentRow = row;
- mLeftEdge = true;
- mRightEdgeKey = null;
- }
-
- private void endRow(Row row) {
- if (mCurrentRow == null)
- throw new InflateException("orphan end row tag");
- if (mRightEdgeKey != null) {
- mRightEdgeKey.markAsRightEdge(mParams);
- mRightEdgeKey = null;
- }
- addEdgeSpace(mParams.mHorizontalEdgesPadding, row);
- mCurrentY += row.mRowHeight;
- mCurrentRow = null;
- mTopEdge = false;
- }
-
- private void endKey(Key key) {
- mParams.onAddKey(key);
- if (mLeftEdge) {
- key.markAsLeftEdge(mParams);
- mLeftEdge = false;
- }
- if (mTopEdge) {
- key.markAsTopEdge(mParams);
- }
- mRightEdgeKey = key;
- }
-
- private void endKeyboard() {
- // nothing to do here.
- }
-
- private void addEdgeSpace(float width, Row row) {
- row.advanceXPos(width);
- mLeftEdge = false;
- mRightEdgeKey = null;
- }
-
- public static float getDimensionOrFraction(TypedArray a, int index, int base,
- float defValue) {
- final TypedValue value = a.peekValue(index);
- if (value == null)
- return defValue;
- if (isFractionValue(value)) {
- return a.getFraction(index, base, base, defValue);
- } else if (isDimensionValue(value)) {
- return a.getDimension(index, defValue);
- }
- return defValue;
- }
-
- public static int getEnumValue(TypedArray a, int index, int defValue) {
- final TypedValue value = a.peekValue(index);
- if (value == null)
- return defValue;
- if (isIntegerValue(value)) {
- return a.getInt(index, defValue);
- }
- return defValue;
- }
-
- private static boolean isFractionValue(TypedValue v) {
- return v.type == TypedValue.TYPE_FRACTION;
- }
-
- private static boolean isDimensionValue(TypedValue v) {
- return v.type == TypedValue.TYPE_DIMENSION;
- }
-
- private static boolean isIntegerValue(TypedValue v) {
- return v.type >= TypedValue.TYPE_FIRST_INT && v.type <= TypedValue.TYPE_LAST_INT;
- }
-
- private static boolean isStringValue(TypedValue v) {
- return v.type == TypedValue.TYPE_STRING;
- }
-
- private static String textAttr(String value, String name) {
- return value != null ? String.format(" %s=%s", name, value) : "";
- }
-
- private static String booleanAttr(TypedArray a, int index, String name) {
- return a.hasValue(index)
- ? String.format(" %s=%s", name, a.getBoolean(index, false)) : "";
- }
- }
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
index b1621a55b..5c8f78f5e 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
@@ -16,6 +16,7 @@
package com.android.inputmethod.keyboard;
+import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.InputPointers;
public interface KeyboardActionListener {
@@ -44,21 +45,16 @@ public interface KeyboardActionListener {
*
* @param primaryCode this is the code of the key that was pressed
* @param x x-coordinate pixel of touched event. If {@link #onCodeInput} is not called by
- * {@link PointerTracker} or so, the value should be {@link #NOT_A_TOUCH_COORDINATE}.
- * If it's called on insertion from the suggestion strip, it should be
- * {@link #SUGGESTION_STRIP_COORDINATE}.
+ * {@link PointerTracker} or so, the value should be
+ * {@link Constants#NOT_A_COORDINATE}. If it's called on insertion from the
+ * suggestion strip, it should be {@link Constants#SUGGESTION_STRIP_COORDINATE}.
* @param y y-coordinate pixel of touched event. If {@link #onCodeInput} is not called by
- * {@link PointerTracker} or so, the value should be {@link #NOT_A_TOUCH_COORDINATE}.
- * If it's called on insertion from the suggestion strip, it should be
- * {@link #SUGGESTION_STRIP_COORDINATE}.
+ * {@link PointerTracker} or so, the value should be
+ * {@link Constants#NOT_A_COORDINATE}.If it's called on insertion from the
+ * suggestion strip, it should be {@link Constants#SUGGESTION_STRIP_COORDINATE}.
*/
public void onCodeInput(int primaryCode, int x, int y);
- // See {@link Adapter#isInvalidCoordinate(int)}.
- public static final int NOT_A_TOUCH_COORDINATE = -1;
- public static final int SUGGESTION_STRIP_COORDINATE = -2;
- public static final int SPELL_CHECKER_COORDINATE = -3;
-
/**
* Sends a sequence of characters to the listener.
*
@@ -119,9 +115,9 @@ public interface KeyboardActionListener {
// TODO: Remove this method when the vertical correction is removed.
public static boolean isInvalidCoordinate(int coordinate) {
- // Detect {@link KeyboardActionListener#NOT_A_TOUCH_COORDINATE},
- // {@link KeyboardActionListener#SUGGESTION_STRIP_COORDINATE}, and
- // {@link KeyboardActionListener#SPELL_CHECKER_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/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
index 64b3f0952..aaccf63ba 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
@@ -35,7 +35,10 @@ import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.compat.EditorInfoCompatUtils;
-import com.android.inputmethod.keyboard.KeyboardLayoutSet.Params.ElementParams;
+import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
+import com.android.inputmethod.keyboard.internal.KeyboardParams;
+import com.android.inputmethod.keyboard.internal.KeysCache;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.InputAttributes;
import com.android.inputmethod.latin.InputTypeUtils;
import com.android.inputmethod.latin.LatinImeLogger;
@@ -71,41 +74,25 @@ public class KeyboardLayoutSet {
private final Params mParams;
private static final HashMap<KeyboardId, SoftReference<Keyboard>> sKeyboardCache =
- new HashMap<KeyboardId, SoftReference<Keyboard>>();
+ CollectionUtils.newHashMap();
private static final KeysCache sKeysCache = new KeysCache();
public static class KeyboardLayoutSetException extends RuntimeException {
public final KeyboardId mKeyboardId;
- public KeyboardLayoutSetException(Throwable cause, KeyboardId keyboardId) {
+ public KeyboardLayoutSetException(final Throwable cause, final KeyboardId keyboardId) {
super(cause);
mKeyboardId = keyboardId;
}
}
- public static class KeysCache {
- private final HashMap<Key, Key> mMap;
-
- public KeysCache() {
- mMap = new HashMap<Key, Key>();
- }
-
- public void clear() {
- mMap.clear();
- }
-
- public Key get(Key key) {
- final Key existingKey = mMap.get(key);
- if (existingKey != null) {
- // Reuse the existing element that equals to "key" without adding "key" to the map.
- return existingKey;
- }
- mMap.put(key, key);
- return key;
- }
+ private static class ElementParams {
+ int mKeyboardXmlId;
+ boolean mProximityCharsCorrectionEnabled;
+ public ElementParams() {}
}
- static class Params {
+ private static class Params {
String mKeyboardLayoutSetName;
int mMode;
EditorInfo mEditorInfo;
@@ -120,12 +107,8 @@ public class KeyboardLayoutSet {
int mWidth;
// Sparse array of KeyboardLayoutSet element parameters indexed by element's id.
final SparseArray<ElementParams> mKeyboardLayoutSetElementIdToParamsMap =
- new SparseArray<ElementParams>();
-
- static class ElementParams {
- int mKeyboardXmlId;
- boolean mProximityCharsCorrectionEnabled;
- }
+ CollectionUtils.newSparseArray();
+ public Params() {}
}
public static void clearKeyboardCache() {
@@ -133,12 +116,12 @@ public class KeyboardLayoutSet {
sKeysCache.clear();
}
- private KeyboardLayoutSet(Context context, Params params) {
+ KeyboardLayoutSet(final Context context, final Params params) {
mContext = context;
mParams = params;
}
- public Keyboard getKeyboard(int baseKeyboardLayoutSetElementId) {
+ public Keyboard getKeyboard(final int baseKeyboardLayoutSetElementId) {
final int keyboardLayoutSetElementId;
switch (mParams.mMode) {
case KeyboardId.MODE_PHONE:
@@ -173,12 +156,12 @@ public class KeyboardLayoutSet {
}
}
- private Keyboard getKeyboard(ElementParams elementParams, final KeyboardId id) {
+ private Keyboard getKeyboard(final ElementParams elementParams, final KeyboardId id) {
final SoftReference<Keyboard> ref = sKeyboardCache.get(id);
Keyboard keyboard = (ref == null) ? null : ref.get();
if (keyboard == null) {
- final Keyboard.Builder<Keyboard.Params> builder =
- new Keyboard.Builder<Keyboard.Params>(mContext, new Keyboard.Params());
+ final KeyboardBuilder<KeyboardParams> builder =
+ new KeyboardBuilder<KeyboardParams>(mContext, new KeyboardParams());
if (id.isAlphabetKeyboard()) {
builder.setAutoGenerate(sKeysCache);
}
@@ -205,7 +188,7 @@ public class KeyboardLayoutSet {
// KeyboardLayoutSet element id that is a key in keyboard_set.xml. Also that file specifies
// which XML layout should be used for each keyboard. The KeyboardId is an internal key for
// Keyboard object.
- private KeyboardId getKeyboardId(int keyboardLayoutSetElementId) {
+ private KeyboardId getKeyboardId(final int keyboardLayoutSetElementId) {
final Params params = mParams;
final boolean isSymbols = (keyboardLayoutSetElementId == KeyboardId.ELEMENT_SYMBOLS
|| keyboardLayoutSetElementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED);
@@ -228,7 +211,7 @@ public class KeyboardLayoutSet {
private static final EditorInfo EMPTY_EDITOR_INFO = new EditorInfo();
- public Builder(Context context, EditorInfo editorInfo) {
+ public Builder(final Context context, final EditorInfo editorInfo) {
mContext = context;
mPackageName = context.getPackageName();
mResources = context.getResources();
@@ -241,7 +224,8 @@ public class KeyboardLayoutSet {
mPackageName, NO_SETTINGS_KEY, mEditorInfo);
}
- public Builder setScreenGeometry(int deviceFormFactor, int orientation, int widthPixels) {
+ public Builder setScreenGeometry(final int deviceFormFactor, final int orientation,
+ final int widthPixels) {
final Params params = mParams;
params.mDeviceFormFactor = deviceFormFactor;
params.mOrientation = orientation;
@@ -249,7 +233,7 @@ public class KeyboardLayoutSet {
return this;
}
- public Builder setSubtype(InputMethodSubtype subtype) {
+ public Builder setSubtype(final InputMethodSubtype subtype) {
final boolean asciiCapable = subtype.containsExtraValueKey(ASCII_CAPABLE);
@SuppressWarnings("deprecation")
final boolean deprecatedForceAscii = InputAttributes.inPrivateImeOptions(
@@ -266,8 +250,8 @@ public class KeyboardLayoutSet {
return this;
}
- public Builder setOptions(boolean voiceKeyEnabled, boolean voiceKeyOnMain,
- boolean languageSwitchKeyEnabled) {
+ public Builder setOptions(final boolean voiceKeyEnabled, final boolean voiceKeyOnMain,
+ final boolean languageSwitchKeyEnabled) {
@SuppressWarnings("deprecation")
final boolean deprecatedNoMicrophone = InputAttributes.inPrivateImeOptions(
null, NO_MICROPHONE_COMPAT, mEditorInfo);
@@ -280,7 +264,7 @@ public class KeyboardLayoutSet {
return this;
}
- public void setTouchPositionCorrectionEnabled(boolean enabled) {
+ public void setTouchPositionCorrectionEnabled(final boolean enabled) {
mParams.mTouchPositionCorrectionEnabled = enabled;
}
@@ -301,7 +285,7 @@ public class KeyboardLayoutSet {
return new KeyboardLayoutSet(mContext, mParams);
}
- private void parseKeyboardLayoutSet(Resources res, int resId)
+ private void parseKeyboardLayoutSet(final Resources res, final int resId)
throws XmlPullParserException, IOException {
final XmlResourceParser parser = res.getXml(resId);
try {
@@ -321,7 +305,7 @@ public class KeyboardLayoutSet {
}
}
- private void parseKeyboardLayoutSetContent(XmlPullParser parser)
+ private void parseKeyboardLayoutSetContent(final XmlPullParser parser)
throws XmlPullParserException, IOException {
int event;
while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
@@ -343,7 +327,7 @@ public class KeyboardLayoutSet {
}
}
- private void parseKeyboardLayoutSetElement(XmlPullParser parser)
+ private void parseKeyboardLayoutSetElement(final XmlPullParser parser)
throws XmlPullParserException, IOException {
final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.KeyboardLayoutSet_Element);
@@ -370,7 +354,7 @@ public class KeyboardLayoutSet {
}
}
- private static int getKeyboardMode(EditorInfo editorInfo) {
+ private static int getKeyboardMode(final EditorInfo editorInfo) {
if (editorInfo == null)
return KeyboardId.MODE_TEXT;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 10f651ad1..fd789f029 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -21,7 +21,6 @@ import android.content.SharedPreferences;
import android.content.res.Resources;
import android.util.Log;
import android.view.ContextThemeWrapper;
-import android.view.InflateException;
import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.EditorInfo;
@@ -38,7 +37,7 @@ import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SettingsValues;
import com.android.inputmethod.latin.SubtypeSwitcher;
-import com.android.inputmethod.latin.Utils;
+import com.android.inputmethod.latin.WordComposer;
public class KeyboardSwitcher implements KeyboardState.SwitchActions {
private static final String TAG = KeyboardSwitcher.class.getSimpleName();
@@ -46,24 +45,24 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions {
public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20110916";
static class KeyboardTheme {
- public final String mName;
public final int mThemeId;
public final int mStyleId;
- public KeyboardTheme(String name, int themeId, int styleId) {
- mName = name;
+ // Note: The themeId should be aligned with "themeId" attribute of Keyboard style
+ // in values/style.xml.
+ public KeyboardTheme(int themeId, int styleId) {
mThemeId = themeId;
mStyleId = styleId;
}
}
private static final KeyboardTheme[] KEYBOARD_THEMES = {
- new KeyboardTheme("Basic", 0, R.style.KeyboardTheme),
- new KeyboardTheme("HighContrast", 1, R.style.KeyboardTheme_HighContrast),
- new KeyboardTheme("Stone", 6, R.style.KeyboardTheme_Stone),
- new KeyboardTheme("Stone.Bold", 7, R.style.KeyboardTheme_Stone_Bold),
- new KeyboardTheme("GingerBread", 8, R.style.KeyboardTheme_Gingerbread),
- new KeyboardTheme("IceCreamSandwich", 5, R.style.KeyboardTheme_IceCreamSandwich),
+ new KeyboardTheme(0, R.style.KeyboardTheme),
+ new KeyboardTheme(1, R.style.KeyboardTheme_HighContrast),
+ new KeyboardTheme(6, R.style.KeyboardTheme_Stone),
+ new KeyboardTheme(7, R.style.KeyboardTheme_Stone_Bold),
+ new KeyboardTheme(8, R.style.KeyboardTheme_Gingerbread),
+ new KeyboardTheme(5, R.style.KeyboardTheme_IceCreamSandwich),
};
private SubtypeSwitcher mSubtypeSwitcher;
@@ -354,22 +353,9 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions {
mKeyboardView.closing();
}
- Utils.GCUtils.getInstance().reset();
- boolean tryGC = true;
- for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
- try {
- setContextThemeWrapper(mLatinIME, mKeyboardTheme);
- mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
- R.layout.input_view, null);
- tryGC = false;
- } catch (OutOfMemoryError e) {
- Log.w(TAG, "load keyboard failed: " + e);
- tryGC = Utils.GCUtils.getInstance().tryGCOrWait(mKeyboardTheme.mName, e);
- } catch (InflateException e) {
- Log.w(TAG, "load keyboard failed: " + e);
- tryGC = Utils.GCUtils.getInstance().tryGCOrWait(mKeyboardTheme.mName, e);
- }
- }
+ setContextThemeWrapper(mLatinIME, mKeyboardTheme);
+ mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
+ R.layout.input_view, null);
mKeyboardView = (MainKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view);
if (isHardwareAcceleratedDrawingEnabled) {
@@ -402,4 +388,16 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions {
}
}
}
+
+ public int getManualCapsMode() {
+ switch (getKeyboard().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;
+ 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 fcf97b99c..5b02f9f5a 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.Log;
import android.util.SparseArray;
import android.util.TypedValue;
import android.view.LayoutInflater;
@@ -37,7 +38,11 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
+import com.android.inputmethod.keyboard.internal.KeyDrawParams;
+import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams;
+import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;
import com.android.inputmethod.keyboard.internal.PreviewPlacerView;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
@@ -51,39 +56,72 @@ import java.util.HashSet;
/**
* A view that renders a virtual {@link Keyboard}.
*
- * @attr ref R.styleable#KeyboardView_backgroundDimAlpha
* @attr ref R.styleable#KeyboardView_keyBackground
- * @attr ref R.styleable#KeyboardView_keyLetterRatio
- * @attr ref R.styleable#KeyboardView_keyLargeLetterRatio
- * @attr ref R.styleable#KeyboardView_keyLabelRatio
- * @attr ref R.styleable#KeyboardView_keyHintLetterRatio
- * @attr ref R.styleable#KeyboardView_keyShiftedLetterHintRatio
- * @attr ref R.styleable#KeyboardView_keyHintLabelRatio
+ * @attr ref R.styleable#KeyboardView_moreKeysLayout
+ * @attr ref R.styleable#KeyboardView_keyPreviewLayout
+ * @attr ref R.styleable#KeyboardView_keyPreviewBackground
+ * @attr ref R.styleable#KeyboardView_keyPreviewLeftBackground
+ * @attr ref R.styleable#KeyboardView_keyPreviewRightBackground
+ * @attr ref R.styleable#KeyboardView_keyPreviewOffset
+ * @attr ref R.styleable#KeyboardView_keyPreviewHeight
+ * @attr ref R.styleable#KeyboardView_keyPreviewLingerTimeout
* @attr ref R.styleable#KeyboardView_keyLabelHorizontalPadding
* @attr ref R.styleable#KeyboardView_keyHintLetterPadding
* @attr ref R.styleable#KeyboardView_keyPopupHintLetterPadding
* @attr ref R.styleable#KeyboardView_keyShiftedLetterHintPadding
- * @attr ref R.styleable#KeyboardView_keyTextStyle
- * @attr ref R.styleable#KeyboardView_keyPreviewLayout
- * @attr ref R.styleable#KeyboardView_keyPreviewTextRatio
- * @attr ref R.styleable#KeyboardView_keyPreviewOffset
- * @attr ref R.styleable#KeyboardView_keyPreviewHeight
- * @attr ref R.styleable#KeyboardView_keyTextColor
- * @attr ref R.styleable#KeyboardView_keyTextColorDisabled
- * @attr ref R.styleable#KeyboardView_keyHintLetterColor
- * @attr ref R.styleable#KeyboardView_keyHintLabelColor
- * @attr ref R.styleable#KeyboardView_keyShiftedLetterHintInactivatedColor
- * @attr ref R.styleable#KeyboardView_keyShiftedLetterHintActivatedColor
- * @attr ref R.styleable#KeyboardView_shadowColor
- * @attr ref R.styleable#KeyboardView_shadowRadius
+ * @attr ref R.styleable#KeyboardView_keyTextShadowRadius
+ * @attr ref R.styleable#KeyboardView_backgroundDimAlpha
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextSize
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextColor
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextOffset
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextShadingColor
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextShadingBorder
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextShadowColor
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextShadowBorder
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextConnectorColor
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextConnectorWidth
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextLingerTimeout
+ * @attr ref R.styleable#KeyboardView_gesturePreviewTrailFadeoutStartDelay
+ * @attr ref R.styleable#KeyboardView_gesturePreviewTrailFadeoutDuration
+ * @attr ref R.styleable#KeyboardView_gesturePreviewTrailUpdateInterval
+ * @attr ref R.styleable#KeyboardView_gesturePreviewTrailColor
+ * @attr ref R.styleable#KeyboardView_gesturePreviewTrailWidth
+ * @attr ref R.styleable#KeyboardView_verticalCorrection
+ * @attr ref R.styleable#Keyboard_Key_keyTypeface
+ * @attr ref R.styleable#Keyboard_Key_keyLetterSize
+ * @attr ref R.styleable#Keyboard_Key_keyLabelSize
+ * @attr ref R.styleable#Keyboard_Key_keyLargeLetterRatio
+ * @attr ref R.styleable#Keyboard_Key_keyLargeLabelRatio
+ * @attr ref R.styleable#Keyboard_Key_keyHintLetterRatio
+ * @attr ref R.styleable#Keyboard_Key_keyShiftedLetterHintRatio
+ * @attr ref R.styleable#Keyboard_Key_keyHintLabelRatio
+ * @attr ref R.styleable#Keyboard_Key_keyPreviewTextRatio
+ * @attr ref R.styleable#Keyboard_Key_keyTextColor
+ * @attr ref R.styleable#Keyboard_Key_keyTextColorDisabled
+ * @attr ref R.styleable#Keyboard_Key_keyTextShadowColor
+ * @attr ref R.styleable#Keyboard_Key_keyHintLetterColor
+ * @attr ref R.styleable#Keyboard_Key_keyHintLabelColor
+ * @attr ref R.styleable#Keyboard_Key_keyShiftedLetterHintInactivatedColor
+ * @attr ref R.styleable#Keyboard_Key_keyShiftedLetterHintActivatedColor
+ * @attr ref R.styleable#Keyboard_Key_keyPreviewTextColor
*/
public class KeyboardView extends View implements PointerTracker.DrawingProxy {
+ private static final String TAG = KeyboardView.class.getSimpleName();
+
// Miscellaneous constants
private static final int[] LONG_PRESSABLE_STATE_SET = { android.R.attr.state_long_pressable };
// XML attributes
+ private final KeyVisualAttributes mKeyVisualAttributes;
+ private final int mKeyLabelHorizontalPadding;
+ private final float mKeyHintLetterPadding;
+ private final float mKeyPopupHintLetterPadding;
+ private final float mKeyShiftedLetterHintPadding;
+ private final float mKeyTextShadowRadius;
protected final float mVerticalCorrection;
protected final int mMoreKeysLayout;
+ protected final Drawable mKeyBackground;
+ protected final Rect mKeyBackgroundPadding = new Rect();
private final int mBackgroundDimAlpha;
// HORIZONTAL ELLIPSIS "...", character for popup hint.
@@ -99,11 +137,19 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
// Main keyboard
private Keyboard mKeyboard;
- protected final KeyDrawParams mKeyDrawParams;
+ protected final KeyDrawParams mKeyDrawParams = new KeyDrawParams();
// Key preview
+ private static final int PREVIEW_ALPHA = 240;
private final int mKeyPreviewLayoutId;
- protected final KeyPreviewDrawParams mKeyPreviewDrawParams;
+ private final Drawable mPreviewBackground;
+ private final Drawable mPreviewLeftBackground;
+ private final Drawable mPreviewRightBackground;
+ private final int mPreviewOffset;
+ private final int mPreviewHeight;
+ private final int mPreviewLingerTimeout;
+ private final SparseArray<TextView> mKeyPreviewTexts = CollectionUtils.newSparseArray();
+ protected final KeyPreviewDrawParams mKeyPreviewDrawParams = new KeyPreviewDrawParams();
private boolean mShowKeyPreviewPopup = true;
private int mDelayAfterPreview;
private final PreviewPlacerView mPreviewPlacerView;
@@ -114,7 +160,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
/** True if all keys should be drawn */
private boolean mInvalidateAllKeys;
/** The keys that should be drawn */
- private final HashSet<Key> mInvalidatedKeys = new HashSet<Key>();
+ private final HashSet<Key> mInvalidatedKeys = CollectionUtils.newHashSet();
/** The working rectangle variable */
private final Rect mWorkingRect = new Rect();
/** The keyboard bitmap buffer for faster updates */
@@ -126,9 +172,9 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
private final Paint mPaint = new Paint();
private final Paint.FontMetrics mFontMetrics = new Paint.FontMetrics();
// This sparse array caches key label text height in pixel indexed by key label text size.
- private static final SparseArray<Float> sTextHeightCache = new SparseArray<Float>();
+ private static final SparseArray<Float> sTextHeightCache = CollectionUtils.newSparseArray();
// This sparse array caches key label text width in pixel indexed by key label text size.
- private static final SparseArray<Float> sTextWidthCache = new SparseArray<Float>();
+ private static final SparseArray<Float> sTextWidthCache = CollectionUtils.newSparseArray();
private static final char[] KEY_LABEL_REFERENCE_CHAR = { 'M' };
private static final char[] KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR = { '8' };
@@ -137,31 +183,34 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
public static class DrawingHandler extends StaticInnerHandlerWrapper<KeyboardView> {
private static final int MSG_DISMISS_KEY_PREVIEW = 0;
- public DrawingHandler(KeyboardView outerInstance) {
+ public DrawingHandler(final KeyboardView outerInstance) {
super(outerInstance);
}
@Override
- public void handleMessage(Message msg) {
+ public void handleMessage(final Message msg) {
final KeyboardView keyboardView = getOuterInstance();
if (keyboardView == null) return;
final PointerTracker tracker = (PointerTracker) msg.obj;
switch (msg.what) {
case MSG_DISMISS_KEY_PREVIEW:
- tracker.getKeyPreviewText().setVisibility(View.INVISIBLE);
+ final TextView previewText = keyboardView.mKeyPreviewTexts.get(tracker.mPointerId);
+ if (previewText != null) {
+ previewText.setVisibility(INVISIBLE);
+ }
break;
}
}
- public void dismissKeyPreview(long delay, PointerTracker tracker) {
+ public void dismissKeyPreview(final long delay, final PointerTracker tracker) {
sendMessageDelayed(obtainMessage(MSG_DISMISS_KEY_PREVIEW, tracker), delay);
}
- public void cancelDismissKeyPreview(PointerTracker tracker) {
+ public void cancelDismissKeyPreview(final PointerTracker tracker) {
removeMessages(MSG_DISMISS_KEY_PREVIEW, tracker);
}
- public void cancelAllDismissKeyPreviews() {
+ private void cancelAllDismissKeyPreviews() {
removeMessages(MSG_DISMISS_KEY_PREVIEW);
}
@@ -170,220 +219,74 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
}
}
- protected static class KeyDrawParams {
- // XML attributes
- public final int mKeyTextColor;
- public final int mKeyTextInactivatedColor;
- public final Typeface mKeyTextStyle;
- public final float mKeyLabelHorizontalPadding;
- public final float mKeyHintLetterPadding;
- public final float mKeyPopupHintLetterPadding;
- public final float mKeyShiftedLetterHintPadding;
- public final int mShadowColor;
- public final float mShadowRadius;
- public final Drawable mKeyBackground;
- public final int mKeyHintLetterColor;
- public final int mKeyHintLabelColor;
- public final int mKeyShiftedLetterHintInactivatedColor;
- public final int mKeyShiftedLetterHintActivatedColor;
-
- /* package */ final float mKeyLetterRatio;
- private final float mKeyLargeLetterRatio;
- private final float mKeyLabelRatio;
- private final float mKeyLargeLabelRatio;
- private final float mKeyHintLetterRatio;
- private final float mKeyShiftedLetterHintRatio;
- private final float mKeyHintLabelRatio;
- private static final float UNDEFINED_RATIO = -1.0f;
-
- public final Rect mPadding = new Rect();
- public int mKeyLetterSize;
- public int mKeyLargeLetterSize;
- public int mKeyLabelSize;
- public int mKeyLargeLabelSize;
- public int mKeyHintLetterSize;
- public int mKeyShiftedLetterHintSize;
- public int mKeyHintLabelSize;
- public int mAnimAlpha;
-
- public KeyDrawParams(TypedArray a) {
- mKeyBackground = a.getDrawable(R.styleable.KeyboardView_keyBackground);
- if (a.hasValue(R.styleable.KeyboardView_keyLetterSize)) {
- mKeyLetterRatio = UNDEFINED_RATIO;
- mKeyLetterSize = a.getDimensionPixelSize(R.styleable.KeyboardView_keyLetterSize, 0);
- } else {
- mKeyLetterRatio = getRatio(a, R.styleable.KeyboardView_keyLetterRatio);
- }
- if (a.hasValue(R.styleable.KeyboardView_keyLabelSize)) {
- mKeyLabelRatio = UNDEFINED_RATIO;
- mKeyLabelSize = a.getDimensionPixelSize(R.styleable.KeyboardView_keyLabelSize, 0);
- } else {
- mKeyLabelRatio = getRatio(a, R.styleable.KeyboardView_keyLabelRatio);
- }
- mKeyLargeLabelRatio = getRatio(a, R.styleable.KeyboardView_keyLargeLabelRatio);
- mKeyLargeLetterRatio = getRatio(a, R.styleable.KeyboardView_keyLargeLetterRatio);
- mKeyHintLetterRatio = getRatio(a, R.styleable.KeyboardView_keyHintLetterRatio);
- mKeyShiftedLetterHintRatio = getRatio(a,
- R.styleable.KeyboardView_keyShiftedLetterHintRatio);
- mKeyHintLabelRatio = getRatio(a, R.styleable.KeyboardView_keyHintLabelRatio);
- mKeyLabelHorizontalPadding = a.getDimension(
- R.styleable.KeyboardView_keyLabelHorizontalPadding, 0);
- mKeyHintLetterPadding = a.getDimension(
- R.styleable.KeyboardView_keyHintLetterPadding, 0);
- mKeyPopupHintLetterPadding = a.getDimension(
- R.styleable.KeyboardView_keyPopupHintLetterPadding, 0);
- mKeyShiftedLetterHintPadding = a.getDimension(
- R.styleable.KeyboardView_keyShiftedLetterHintPadding, 0);
- mKeyTextColor = a.getColor(R.styleable.KeyboardView_keyTextColor, 0xFF000000);
- mKeyTextInactivatedColor = a.getColor(
- R.styleable.KeyboardView_keyTextInactivatedColor, 0xFF000000);
- mKeyHintLetterColor = a.getColor(R.styleable.KeyboardView_keyHintLetterColor, 0);
- mKeyHintLabelColor = a.getColor(R.styleable.KeyboardView_keyHintLabelColor, 0);
- mKeyShiftedLetterHintInactivatedColor = a.getColor(
- R.styleable.KeyboardView_keyShiftedLetterHintInactivatedColor, 0);
- mKeyShiftedLetterHintActivatedColor = a.getColor(
- R.styleable.KeyboardView_keyShiftedLetterHintActivatedColor, 0);
- mKeyTextStyle = Typeface.defaultFromStyle(
- a.getInt(R.styleable.KeyboardView_keyTextStyle, Typeface.NORMAL));
- mShadowColor = a.getColor(R.styleable.KeyboardView_shadowColor, 0);
- mShadowRadius = a.getFloat(R.styleable.KeyboardView_shadowRadius, 0f);
-
- mKeyBackground.getPadding(mPadding);
- }
-
- public void updateKeyHeight(int keyHeight) {
- if (mKeyLetterRatio >= 0.0f) {
- mKeyLetterSize = (int)(keyHeight * mKeyLetterRatio);
- }
- if (mKeyLabelRatio >= 0.0f) {
- mKeyLabelSize = (int)(keyHeight * mKeyLabelRatio);
- }
- mKeyLargeLabelSize = (int)(keyHeight * mKeyLargeLabelRatio);
- mKeyLargeLetterSize = (int)(keyHeight * mKeyLargeLetterRatio);
- mKeyHintLetterSize = (int)(keyHeight * mKeyHintLetterRatio);
- mKeyShiftedLetterHintSize = (int)(keyHeight * mKeyShiftedLetterHintRatio);
- mKeyHintLabelSize = (int)(keyHeight * mKeyHintLabelRatio);
- }
-
- public void blendAlpha(Paint paint) {
- final int color = paint.getColor();
- paint.setARGB((paint.getAlpha() * mAnimAlpha) / Constants.Color.ALPHA_OPAQUE,
- Color.red(color), Color.green(color), Color.blue(color));
- }
- }
-
- /* package */ static class KeyPreviewDrawParams {
- // XML attributes.
- public final Drawable mPreviewBackground;
- public final Drawable mPreviewLeftBackground;
- public final Drawable mPreviewRightBackground;
- public final int mPreviewTextColor;
- public final int mPreviewOffset;
- public final int mPreviewHeight;
- public final Typeface mKeyTextStyle;
- public final int mLingerTimeout;
-
- private final float mPreviewTextRatio;
- private final float mKeyLetterRatio;
-
- // The graphical geometry of the key preview.
- // <-width->
- // +-------+ ^
- // | | |
- // |preview| height (visible)
- // | | |
- // + + ^ v
- // \ / |offset
- // +-\ /-+ v
- // | +-+ |
- // |parent |
- // | key|
- // +-------+
- // The background of a {@link TextView} being used for a key preview may have invisible
- // paddings. To align the more keys keyboard panel's visible part with the visible part of
- // the background, we need to record the width and height of key preview that don't include
- // invisible paddings.
- public int mPreviewVisibleWidth;
- public int mPreviewVisibleHeight;
- // The key preview may have an arbitrary offset and its background that may have a bottom
- // padding. To align the more keys keyboard and the key preview we also need to record the
- // offset between the top edge of parent key and the bottom of the visible part of key
- // preview background.
- public int mPreviewVisibleOffset;
-
- public int mPreviewTextSize;
- public int mKeyLetterSize;
- public final int[] mCoordinates = new int[2];
-
- private static final int PREVIEW_ALPHA = 240;
-
- public KeyPreviewDrawParams(TypedArray a, KeyDrawParams keyDrawParams) {
- mPreviewBackground = a.getDrawable(R.styleable.KeyboardView_keyPreviewBackground);
- mPreviewLeftBackground = a.getDrawable(
- R.styleable.KeyboardView_keyPreviewLeftBackground);
- mPreviewRightBackground = a.getDrawable(
- R.styleable.KeyboardView_keyPreviewRightBackground);
- setAlpha(mPreviewBackground, PREVIEW_ALPHA);
- setAlpha(mPreviewLeftBackground, PREVIEW_ALPHA);
- setAlpha(mPreviewRightBackground, PREVIEW_ALPHA);
- mPreviewOffset = a.getDimensionPixelOffset(
- R.styleable.KeyboardView_keyPreviewOffset, 0);
- mPreviewHeight = a.getDimensionPixelSize(
- R.styleable.KeyboardView_keyPreviewHeight, 80);
- mPreviewTextRatio = getRatio(a, R.styleable.KeyboardView_keyPreviewTextRatio);
- mPreviewTextColor = a.getColor(R.styleable.KeyboardView_keyPreviewTextColor, 0);
- mLingerTimeout = a.getInt(R.styleable.KeyboardView_keyPreviewLingerTimeout, 0);
-
- mKeyLetterRatio = keyDrawParams.mKeyLetterRatio;
- mKeyTextStyle = keyDrawParams.mKeyTextStyle;
- }
-
- public void updateKeyHeight(int keyHeight) {
- if (mPreviewTextRatio >= 0.0f) {
- mPreviewTextSize = (int)(keyHeight * mPreviewTextRatio);
- }
- if (mKeyLetterRatio >= 0.0f) {
- mKeyLetterSize = (int)(keyHeight * mKeyLetterRatio);
- }
- }
-
- private static void setAlpha(Drawable drawable, int alpha) {
- if (drawable == null) return;
- drawable.setAlpha(alpha);
- }
- }
-
- public KeyboardView(Context context, AttributeSet attrs) {
+ public KeyboardView(final Context context, final AttributeSet attrs) {
this(context, attrs, R.attr.keyboardViewStyle);
}
- public KeyboardView(Context context, AttributeSet attrs, int defStyle) {
+ public KeyboardView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
- final TypedArray a = context.obtainStyledAttributes(
- attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
-
- mKeyDrawParams = new KeyDrawParams(a);
- mKeyPreviewDrawParams = new KeyPreviewDrawParams(a, mKeyDrawParams);
- mKeyPreviewLayoutId = a.getResourceId(R.styleable.KeyboardView_keyPreviewLayout, 0);
+ final TypedArray keyboardViewAttr = context.obtainStyledAttributes(attrs,
+ R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
+ mKeyBackground = keyboardViewAttr.getDrawable(R.styleable.KeyboardView_keyBackground);
+ mKeyBackground.getPadding(mKeyBackgroundPadding);
+ mPreviewBackground = keyboardViewAttr.getDrawable(
+ R.styleable.KeyboardView_keyPreviewBackground);
+ mPreviewLeftBackground = keyboardViewAttr.getDrawable(
+ R.styleable.KeyboardView_keyPreviewLeftBackground);
+ mPreviewRightBackground = keyboardViewAttr.getDrawable(
+ R.styleable.KeyboardView_keyPreviewRightBackground);
+ setAlpha(mPreviewBackground, PREVIEW_ALPHA);
+ setAlpha(mPreviewLeftBackground, PREVIEW_ALPHA);
+ setAlpha(mPreviewRightBackground, PREVIEW_ALPHA);
+ mPreviewOffset = keyboardViewAttr.getDimensionPixelOffset(
+ R.styleable.KeyboardView_keyPreviewOffset, 0);
+ mPreviewHeight = keyboardViewAttr.getDimensionPixelSize(
+ R.styleable.KeyboardView_keyPreviewHeight, 80);
+ mPreviewLingerTimeout = keyboardViewAttr.getInt(
+ R.styleable.KeyboardView_keyPreviewLingerTimeout, 0);
+ mDelayAfterPreview = mPreviewLingerTimeout;
+ mKeyLabelHorizontalPadding = keyboardViewAttr.getDimensionPixelOffset(
+ R.styleable.KeyboardView_keyLabelHorizontalPadding, 0);
+ mKeyHintLetterPadding = keyboardViewAttr.getDimension(
+ R.styleable.KeyboardView_keyHintLetterPadding, 0);
+ mKeyPopupHintLetterPadding = keyboardViewAttr.getDimension(
+ R.styleable.KeyboardView_keyPopupHintLetterPadding, 0);
+ mKeyShiftedLetterHintPadding = keyboardViewAttr.getDimension(
+ R.styleable.KeyboardView_keyShiftedLetterHintPadding, 0);
+ mKeyTextShadowRadius = keyboardViewAttr.getFloat(
+ R.styleable.KeyboardView_keyTextShadowRadius, 0.0f);
+ mKeyPreviewLayoutId = keyboardViewAttr.getResourceId(
+ R.styleable.KeyboardView_keyPreviewLayout, 0);
if (mKeyPreviewLayoutId == 0) {
mShowKeyPreviewPopup = false;
}
- mVerticalCorrection = a.getDimensionPixelOffset(
+ mVerticalCorrection = keyboardViewAttr.getDimension(
R.styleable.KeyboardView_verticalCorrection, 0);
- mMoreKeysLayout = a.getResourceId(R.styleable.KeyboardView_moreKeysLayout, 0);
- mBackgroundDimAlpha = a.getInt(R.styleable.KeyboardView_backgroundDimAlpha, 0);
- mPreviewPlacerView = new PreviewPlacerView(context, a);
- a.recycle();
-
- mDelayAfterPreview = mKeyPreviewDrawParams.mLingerTimeout;
-
+ mMoreKeysLayout = keyboardViewAttr.getResourceId(
+ R.styleable.KeyboardView_moreKeysLayout, 0);
+ mBackgroundDimAlpha = keyboardViewAttr.getInt(
+ R.styleable.KeyboardView_backgroundDimAlpha, 0);
+ keyboardViewAttr.recycle();
+
+ final TypedArray keyAttr = context.obtainStyledAttributes(attrs,
+ R.styleable.Keyboard_Key, defStyle, R.style.KeyboardView);
+ mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr);
+ keyAttr.recycle();
+
+ mPreviewPlacerView = new PreviewPlacerView(context, attrs);
mPaint.setAntiAlias(true);
}
- // Read fraction value in TypedArray as float.
- /* package */ static float getRatio(TypedArray a, int index) {
- return a.getFraction(index, 1000, 1000, 1) / 1000.0f;
+ private static void setAlpha(final Drawable drawable, final int alpha) {
+ if (drawable == null) return;
+ drawable.setAlpha(alpha);
+ }
+
+ private static void blendAlpha(final Paint paint, final int alpha) {
+ final int color = paint.getColor();
+ paint.setARGB((paint.getAlpha() * alpha) / Constants.Color.ALPHA_OPAQUE,
+ Color.red(color), Color.green(color), Color.blue(color));
}
/**
@@ -393,14 +296,14 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
* @see #getKeyboard()
* @param keyboard the keyboard to display in this view
*/
- public void setKeyboard(Keyboard keyboard) {
+ public void setKeyboard(final Keyboard keyboard) {
mKeyboard = keyboard;
LatinImeLogger.onSetKeyboard(keyboard);
requestLayout();
invalidateAllKeys();
final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
- mKeyDrawParams.updateKeyHeight(keyHeight);
- mKeyPreviewDrawParams.updateKeyHeight(keyHeight);
+ mKeyDrawParams.updateParams(keyHeight, mKeyVisualAttributes);
+ mKeyDrawParams.updateParams(keyHeight, keyboard.mKeyVisualAttributes);
}
/**
@@ -419,7 +322,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
* @param delay the delay after which the preview is dismissed
* @see #isKeyPreviewPopupEnabled()
*/
- public void setKeyPreviewPopupEnabled(boolean previewEnabled, int delay) {
+ public void setKeyPreviewPopupEnabled(final boolean previewEnabled, final int delay) {
mShowKeyPreviewPopup = previewEnabled;
mDelayAfterPreview = delay;
}
@@ -433,14 +336,14 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
return mShowKeyPreviewPopup;
}
- public void setGesturePreviewMode(boolean drawsGesturePreviewTrail,
- boolean drawsGestureFloatingPreviewText) {
+ public void setGesturePreviewMode(final boolean drawsGesturePreviewTrail,
+ final boolean drawsGestureFloatingPreviewText) {
mPreviewPlacerView.setGesturePreviewMode(
drawsGesturePreviewTrail, drawsGestureFloatingPreviewText);
}
@Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
if (mKeyboard != null) {
// The main keyboard expands to the display width.
final int height = mKeyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom();
@@ -451,7 +354,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
}
@Override
- public void onDraw(Canvas canvas) {
+ public void onDraw(final Canvas canvas) {
super.onDraw(canvas);
if (canvas.isHardwareAccelerated()) {
onDrawKeyboard(canvas);
@@ -462,12 +365,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
if (bufferNeedsUpdates || mOffscreenBuffer == null) {
if (maybeAllocateOffscreenBuffer()) {
mInvalidateAllKeys = true;
- // TODO: Stop using the offscreen canvas even when in software rendering
- if (mOffscreenCanvas != null) {
- mOffscreenCanvas.setBitmap(mOffscreenBuffer);
- } else {
- mOffscreenCanvas = new Canvas(mOffscreenBuffer);
- }
+ maybeCreateOffscreenCanvas();
}
onDrawKeyboard(mOffscreenCanvas);
}
@@ -496,13 +394,21 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
}
}
+ private void maybeCreateOffscreenCanvas() {
+ // TODO: Stop using the offscreen canvas even when in software rendering
+ if (mOffscreenCanvas != null) {
+ mOffscreenCanvas.setBitmap(mOffscreenBuffer);
+ } else {
+ mOffscreenCanvas = new Canvas(mOffscreenBuffer);
+ }
+ }
+
private void onDrawKeyboard(final Canvas canvas) {
if (mKeyboard == null) return;
final int width = getWidth();
final int height = getHeight();
final Paint paint = mPaint;
- final KeyDrawParams params = mKeyDrawParams;
// Calculate clip region and set.
final boolean drawAllKeys = mInvalidateAllKeys || mInvalidatedKeys.isEmpty();
@@ -535,13 +441,13 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
if (drawAllKeys || isHardwareAccelerated) {
// Draw all keys.
for (final Key key : mKeyboard.mKeys) {
- onDrawKey(key, canvas, paint, params);
+ onDrawKey(key, canvas, paint);
}
} else {
// Draw invalidated keys.
for (final Key key : mInvalidatedKeys) {
if (mKeyboard.hasKey(key)) {
- onDrawKey(key, canvas, paint, params);
+ onDrawKey(key, canvas, paint);
}
}
}
@@ -565,7 +471,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
mInvalidateAllKeys = false;
}
- public void dimEntireKeyboard(boolean dimmed) {
+ public void dimEntireKeyboard(final boolean dimmed) {
final boolean needsRedrawing = mNeedsToDimEntireKeyboard != dimmed;
mNeedsToDimEntireKeyboard = dimmed;
if (needsRedrawing) {
@@ -573,14 +479,18 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
}
}
- private void onDrawKey(Key key, Canvas canvas, Paint paint, KeyDrawParams params) {
- final int keyDrawX = key.mX + key.mVisualInsetsLeft + getPaddingLeft();
+ private void onDrawKey(final Key key, final Canvas canvas, final Paint paint) {
+ final int keyDrawX = key.getDrawX() + getPaddingLeft();
final int keyDrawY = key.mY + getPaddingTop();
canvas.translate(keyDrawX, keyDrawY);
+ final int keyHeight = mKeyboard.mMostCommonKeyHeight - mKeyboard.mVerticalGap;
+ final KeyVisualAttributes attr = key.mKeyVisualAttributes;
+ final KeyDrawParams params = mKeyDrawParams.mayCloneAndUpdateParams(keyHeight, attr);
params.mAnimAlpha = Constants.Color.ALPHA_OPAQUE;
+
if (!key.isSpacer()) {
- onDrawKeyBackground(key, canvas, params);
+ onDrawKeyBackground(key, canvas);
}
onDrawKeyTopVisuals(key, canvas, paint, params);
@@ -588,14 +498,14 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
}
// Draw key background.
- protected void onDrawKeyBackground(Key key, Canvas canvas, KeyDrawParams params) {
- final int bgWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight
- + params.mPadding.left + params.mPadding.right;
- final int bgHeight = key.mHeight + params.mPadding.top + params.mPadding.bottom;
- final int bgX = -params.mPadding.left;
- final int bgY = -params.mPadding.top;
+ protected void onDrawKeyBackground(Key key, Canvas canvas) {
+ final Rect padding = mKeyBackgroundPadding;
+ final int bgWidth = key.getDrawWidth() + padding.left + padding.right;
+ final int bgHeight = key.mHeight + padding.top + padding.bottom;
+ final int bgX = -padding.left;
+ final int bgY = -padding.top;
final int[] drawableState = key.getCurrentDrawableState();
- final Drawable background = params.mKeyBackground;
+ final Drawable background = mKeyBackground;
background.setState(drawableState);
final Rect bounds = background.getBounds();
if (bgWidth != bounds.right || bgHeight != bounds.bottom) {
@@ -611,7 +521,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
// Draw key top visuals.
protected void onDrawKeyTopVisuals(Key key, Canvas canvas, Paint paint, KeyDrawParams params) {
- final int keyWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight;
+ final int keyWidth = key.getDrawWidth();
final int keyHeight = key.mHeight;
final float centerX = keyWidth * 0.5f;
final float centerY = keyHeight * 0.5f;
@@ -625,12 +535,8 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
float positionX = centerX;
if (key.mLabel != null) {
final String label = key.mLabel;
- // For characters, use large font. For labels like "Done", use smaller font.
- paint.setTypeface(key.selectTypeface(params.mKeyTextStyle));
- final int labelSize = key.selectTextSize(params.mKeyLetterSize,
- params.mKeyLargeLetterSize, params.mKeyLabelSize, params.mKeyLargeLabelSize,
- params.mKeyHintLabelSize);
- paint.setTextSize(labelSize);
+ paint.setTypeface(key.selectTypeface(params));
+ paint.setTextSize(key.selectTextSize(params));
final float labelCharHeight = getCharHeight(KEY_LABEL_REFERENCE_CHAR, paint);
final float labelCharWidth = getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint);
@@ -640,10 +546,10 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
// Horizontal label text alignment
float labelWidth = 0;
if (key.isAlignLeft()) {
- positionX = (int)params.mKeyLabelHorizontalPadding;
+ positionX = mKeyLabelHorizontalPadding;
paint.setTextAlign(Align.LEFT);
} else if (key.isAlignRight()) {
- positionX = keyWidth - (int)params.mKeyLabelHorizontalPadding;
+ positionX = keyWidth - mKeyLabelHorizontalPadding;
paint.setTextAlign(Align.RIGHT);
} else if (key.isAlignLeftOfCenter()) {
// TODO: Parameterise this?
@@ -668,16 +574,15 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
Math.min(1.0f, (keyWidth * MAX_LABEL_RATIO) / getLabelWidth(label, paint)));
}
- paint.setColor(key.isShiftedLetterActivated()
- ? params.mKeyTextInactivatedColor : params.mKeyTextColor);
+ paint.setColor(key.selectTextColor(params));
if (key.isEnabled()) {
// Set a drop shadow for the text
- paint.setShadowLayer(params.mShadowRadius, 0, 0, params.mShadowColor);
+ paint.setShadowLayer(mKeyTextShadowRadius, 0, 0, params.mTextShadowColor);
} else {
// Make label invisible
paint.setColor(Color.TRANSPARENT);
}
- params.blendAlpha(paint);
+ blendAlpha(paint, params.mAnimAlpha);
canvas.drawText(label, 0, label.length(), positionX, baseline, paint);
// Turn off drop shadow and reset x-scale.
paint.setShadowLayer(0, 0, 0, 0);
@@ -705,25 +610,10 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
// Draw hint label.
if (key.mHintLabel != null) {
- final String hint = key.mHintLabel;
- final int hintColor;
- final int hintSize;
- if (key.hasHintLabel()) {
- hintColor = params.mKeyHintLabelColor;
- hintSize = params.mKeyHintLabelSize;
- paint.setTypeface(Typeface.DEFAULT);
- } else if (key.hasShiftedLetterHint()) {
- hintColor = key.isShiftedLetterActivated()
- ? params.mKeyShiftedLetterHintActivatedColor
- : params.mKeyShiftedLetterHintInactivatedColor;
- hintSize = params.mKeyShiftedLetterHintSize;
- } else { // key.hasHintLetter()
- hintColor = params.mKeyHintLetterColor;
- hintSize = params.mKeyHintLetterSize;
- }
- paint.setColor(hintColor);
- params.blendAlpha(paint);
- paint.setTextSize(hintSize);
+ final String hintLabel = key.mHintLabel;
+ paint.setTextSize(key.selectHintTextSize(params));
+ paint.setColor(key.selectHintTextColor(params));
+ blendAlpha(paint, params.mAnimAlpha);
final float hintX, hintY;
if (key.hasHintLabel()) {
// The hint label is placed just right of the key label. Used mainly on
@@ -734,19 +624,19 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
paint.setTextAlign(Align.LEFT);
} else if (key.hasShiftedLetterHint()) {
// The hint label is placed at top-right corner of the key. Used mainly on tablet.
- hintX = keyWidth - params.mKeyShiftedLetterHintPadding
+ hintX = keyWidth - mKeyShiftedLetterHintPadding
- getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) / 2;
paint.getFontMetrics(mFontMetrics);
hintY = -mFontMetrics.top;
paint.setTextAlign(Align.CENTER);
} else { // key.hasHintLetter()
// The hint letter is placed at top-right corner of the key. Used mainly on phone.
- hintX = keyWidth - params.mKeyHintLetterPadding
+ hintX = keyWidth - mKeyHintLetterPadding
- getCharWidth(KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR, paint) / 2;
hintY = -paint.ascent();
paint.setTextAlign(Align.CENTER);
}
- canvas.drawText(hint, 0, hint.length(), hintX, hintY, paint);
+ canvas.drawText(hintLabel, 0, hintLabel.length(), hintX, hintY, paint);
if (LatinImeLogger.sVISUALDEBUG) {
final Paint line = new Paint();
@@ -757,15 +647,15 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
// Draw key icon.
if (key.mLabel == null && icon != null) {
- final int iconWidth = icon.getIntrinsicWidth();
+ final int iconWidth = Math.min(icon.getIntrinsicWidth(), keyWidth);
final int iconHeight = icon.getIntrinsicHeight();
final int iconX, alignX;
final int iconY = (keyHeight - iconHeight) / 2;
if (key.isAlignLeft()) {
- iconX = (int)params.mKeyLabelHorizontalPadding;
+ iconX = mKeyLabelHorizontalPadding;
alignX = iconX;
} else if (key.isAlignRight()) {
- iconX = keyWidth - (int)params.mKeyLabelHorizontalPadding - iconWidth;
+ iconX = keyWidth - mKeyLabelHorizontalPadding - iconWidth;
alignX = iconX + iconWidth;
} else { // Align center
iconX = (keyWidth - iconWidth) / 2;
@@ -787,16 +677,16 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
// Draw popup hint "..." at the bottom right corner of the key.
protected void drawKeyPopupHint(Key key, Canvas canvas, Paint paint, KeyDrawParams params) {
- final int keyWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight;
+ final int keyWidth = key.getDrawWidth();
final int keyHeight = key.mHeight;
- paint.setTypeface(params.mKeyTextStyle);
- paint.setTextSize(params.mKeyHintLetterSize);
- paint.setColor(params.mKeyHintLabelColor);
+ paint.setTypeface(params.mTypeface);
+ paint.setTextSize(params.mHintLetterSize);
+ paint.setColor(params.mHintLabelColor);
paint.setTextAlign(Align.CENTER);
- final float hintX = keyWidth - params.mKeyHintLetterPadding
+ final float hintX = keyWidth - mKeyHintLetterPadding
- getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) / 2;
- final float hintY = keyHeight - params.mKeyPopupHintLetterPadding;
+ final float hintY = keyHeight - mKeyPopupHintLetterPadding;
canvas.drawText(POPUP_HINT_CHAR, hintX, hintY, paint);
if (LatinImeLogger.sVISUALDEBUG) {
@@ -889,8 +779,8 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
public Paint newDefaultLabelPaint() {
final Paint paint = new Paint();
paint.setAntiAlias(true);
- paint.setTypeface(mKeyDrawParams.mKeyTextStyle);
- paint.setTextSize(mKeyDrawParams.mKeyLabelSize);
+ paint.setTypeface(mKeyDrawParams.mTypeface);
+ paint.setTextSize(mKeyDrawParams.mLabelSize);
return paint;
}
@@ -901,23 +791,38 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
}
}
- // Called by {@link PointerTracker} constructor to create a TextView.
- @Override
- public TextView inflateKeyPreviewText() {
+ private TextView getKeyPreviewText(final int pointerId) {
+ TextView previewText = mKeyPreviewTexts.get(pointerId);
+ if (previewText != null) {
+ return previewText;
+ }
final Context context = getContext();
if (mKeyPreviewLayoutId != 0) {
- return (TextView)LayoutInflater.from(context).inflate(mKeyPreviewLayoutId, null);
+ previewText = (TextView)LayoutInflater.from(context).inflate(mKeyPreviewLayoutId, null);
} else {
- return new TextView(context);
+ previewText = new TextView(context);
+ }
+ mKeyPreviewTexts.put(pointerId, previewText);
+ return previewText;
+ }
+
+ private void dismissAllKeyPreviews() {
+ final int pointerCount = mKeyPreviewTexts.size();
+ for (int id = 0; id < pointerCount; id++) {
+ final TextView previewText = mKeyPreviewTexts.get(id);
+ if (previewText != null) {
+ previewText.setVisibility(INVISIBLE);
+ }
}
+ PointerTracker.setReleasedKeyGraphicsToAllKeys();
}
@Override
- public void dismissKeyPreview(PointerTracker tracker) {
+ public void dismissKeyPreview(final PointerTracker tracker) {
mDrawingHandler.dismissKeyPreview(mDelayAfterPreview, tracker);
}
- private void addKeyPreview(TextView keyPreview) {
+ private void addKeyPreview(final TextView keyPreview) {
locatePreviewPlacerView();
mPreviewPlacerView.addView(
keyPreview, ViewLayoutUtils.newLayoutParam(mPreviewPlacerView, 0, 0));
@@ -930,9 +835,18 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
final int[] viewOrigin = new int[2];
getLocationInWindow(viewOrigin);
mPreviewPlacerView.setOrigin(viewOrigin[0], viewOrigin[1]);
- final ViewGroup windowContentView =
- (ViewGroup)getRootView().findViewById(android.R.id.content);
- windowContentView.addView(mPreviewPlacerView);
+ final View rootView = getRootView();
+ if (rootView == null) {
+ Log.w(TAG, "Cannot find root view");
+ return;
+ }
+ final ViewGroup windowContentView = (ViewGroup)rootView.findViewById(android.R.id.content);
+ // Note: It'd be very weird if we get null by android.R.id.content.
+ if (windowContentView == null) {
+ Log.w(TAG, "Cannot find android.R.id.content view to add PreviewPlacerView");
+ } else {
+ windowContentView.addView(mPreviewPlacerView);
+ }
}
public void showGestureFloatingPreviewText(String gestureFloatingPreviewText) {
@@ -946,17 +860,21 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
}
@Override
- public void showGestureTrail(PointerTracker tracker) {
+ public void showGesturePreviewTrail(final PointerTracker tracker) {
locatePreviewPlacerView();
mPreviewPlacerView.invalidatePointer(tracker);
}
@SuppressWarnings("deprecation") // setBackgroundDrawable is replaced by setBackground in API16
@Override
- public void showKeyPreview(PointerTracker tracker) {
- if (!mShowKeyPreviewPopup) return;
+ public void showKeyPreview(final PointerTracker tracker) {
+ final KeyPreviewDrawParams previewParams = mKeyPreviewDrawParams;
+ if (!mShowKeyPreviewPopup) {
+ previewParams.mPreviewVisibleOffset = -mKeyboard.mVerticalGap;
+ return;
+ }
- final TextView previewText = tracker.getKeyPreviewText();
+ final TextView previewText = getKeyPreviewText(tracker.mPointerId);
// If the key preview has no parent view yet, add it to the ViewGroup which can place
// key preview absolutely in SoftInputWindow.
if (previewText.getParent() == null) {
@@ -971,18 +889,18 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
if (key == null)
return;
- final KeyPreviewDrawParams params = mKeyPreviewDrawParams;
+ final KeyDrawParams drawParams = mKeyDrawParams;
final String label = key.isShiftedLetterActivated() ? key.mHintLabel : key.mLabel;
- // What we show as preview should match what we show on a key top in onBufferDraw().
+ // What we show as preview should match what we show on a key top in onDraw().
if (label != null) {
// TODO Should take care of temporaryShiftLabel here.
previewText.setCompoundDrawables(null, null, null, null);
if (StringUtils.codePointCount(label) > 1) {
- previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, params.mKeyLetterSize);
+ previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, drawParams.mLetterSize);
previewText.setTypeface(Typeface.DEFAULT_BOLD);
} else {
- previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, params.mPreviewTextSize);
- previewText.setTypeface(params.mKeyTextStyle);
+ previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, drawParams.mPreviewTextSize);
+ previewText.setTypeface(key.selectTypeface(drawParams));
}
previewText.setText(label);
} else {
@@ -990,48 +908,48 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
key.getPreviewIcon(mKeyboard.mIconsSet));
previewText.setText(null);
}
- previewText.setBackgroundDrawable(params.mPreviewBackground);
+ previewText.setBackgroundDrawable(mPreviewBackground);
previewText.measure(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
- final int keyDrawWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight;
+ final int keyDrawWidth = key.getDrawWidth();
final int previewWidth = previewText.getMeasuredWidth();
- final int previewHeight = params.mPreviewHeight;
+ final int previewHeight = mPreviewHeight;
// The width and height of visible part of the key preview background. The content marker
// of the background 9-patch have to cover the visible part of the background.
- params.mPreviewVisibleWidth = previewWidth - previewText.getPaddingLeft()
+ previewParams.mPreviewVisibleWidth = previewWidth - previewText.getPaddingLeft()
- previewText.getPaddingRight();
- params.mPreviewVisibleHeight = previewHeight - previewText.getPaddingTop()
+ previewParams.mPreviewVisibleHeight = previewHeight - previewText.getPaddingTop()
- previewText.getPaddingBottom();
// The distance between the top edge of the parent key and the bottom of the visible part
// of the key preview background.
- params.mPreviewVisibleOffset = params.mPreviewOffset - previewText.getPaddingBottom();
- getLocationInWindow(params.mCoordinates);
+ previewParams.mPreviewVisibleOffset = mPreviewOffset - previewText.getPaddingBottom();
+ getLocationInWindow(previewParams.mCoordinates);
// The key preview is horizontally aligned with the center of the visible part of the
// parent key. If it doesn't fit in this {@link KeyboardView}, it is moved inward to fit and
// the left/right background is used if such background is specified.
- int previewX = key.mX + key.mVisualInsetsLeft - (previewWidth - keyDrawWidth) / 2
- + params.mCoordinates[0];
+ int previewX = key.getDrawX() - (previewWidth - keyDrawWidth) / 2
+ + previewParams.mCoordinates[0];
if (previewX < 0) {
previewX = 0;
- if (params.mPreviewLeftBackground != null) {
- previewText.setBackgroundDrawable(params.mPreviewLeftBackground);
+ if (mPreviewLeftBackground != null) {
+ previewText.setBackgroundDrawable(mPreviewLeftBackground);
}
} else if (previewX > getWidth() - previewWidth) {
previewX = getWidth() - previewWidth;
- if (params.mPreviewRightBackground != null) {
- previewText.setBackgroundDrawable(params.mPreviewRightBackground);
+ if (mPreviewRightBackground != null) {
+ previewText.setBackgroundDrawable(mPreviewRightBackground);
}
}
// The key preview is placed vertically above the top edge of the parent key with an
// arbitrary offset.
- final int previewY = key.mY - previewHeight + params.mPreviewOffset
- + params.mCoordinates[1];
+ final int previewY = key.mY - previewHeight + mPreviewOffset
+ + previewParams.mCoordinates[1];
// Set the preview background state
previewText.getBackground().setState(
key.mMoreKeys != null ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
- previewText.setTextColor(params.mPreviewTextColor);
+ previewText.setTextColor(drawParams.mPreviewTextColor);
ViewLayoutUtils.placeViewAt(
previewText, previewX, previewY, previewWidth, previewHeight);
previewText.setVisibility(VISIBLE);
@@ -1067,7 +985,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
}
public void closing() {
- PointerTracker.dismissAllKeyPreviews();
+ dismissAllKeyPreviews();
cancelAllMessages();
mInvalidateAllKeys = true;
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 0a929f33f..f6b66a79e 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -43,15 +43,16 @@ import com.android.inputmethod.accessibility.AccessibilityUtils;
import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
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.latin.Constants;
import com.android.inputmethod.latin.LatinIME;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.ResourceUtils;
import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
import com.android.inputmethod.latin.StringUtils;
import com.android.inputmethod.latin.SubtypeLocale;
-import com.android.inputmethod.latin.Utils;
import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils;
import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.research.ResearchLogger;
@@ -62,9 +63,25 @@ import java.util.WeakHashMap;
/**
* A view that is responsible for detecting key presses and touch movements.
*
- * @attr ref R.styleable#KeyboardView_keyHysteresisDistance
- * @attr ref R.styleable#KeyboardView_verticalCorrection
- * @attr ref R.styleable#KeyboardView_popupLayout
+ * @attr ref R.styleable#MainKeyboardView_autoCorrectionSpacebarLedEnabled
+ * @attr ref R.styleable#MainKeyboardView_autoCorrectionSpacebarLedIcon
+ * @attr ref R.styleable#MainKeyboardView_spacebarTextRatio
+ * @attr ref R.styleable#MainKeyboardView_spacebarTextColor
+ * @attr ref R.styleable#MainKeyboardView_spacebarTextShadowColor
+ * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFinalAlpha
+ * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFadeoutAnimator
+ * @attr ref R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator
+ * @attr ref R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator
+ * @attr ref R.styleable#MainKeyboardView_keyHysteresisDistance
+ * @attr ref R.styleable#MainKeyboardView_touchNoiseThresholdTime
+ * @attr ref R.styleable#MainKeyboardView_touchNoiseThresholdDistance
+ * @attr ref R.styleable#MainKeyboardView_slidingKeyInputEnable
+ * @attr ref R.styleable#MainKeyboardView_keyRepeatStartTimeout
+ * @attr ref R.styleable#MainKeyboardView_keyRepeatInterval
+ * @attr ref R.styleable#MainKeyboardView_longPressKeyTimeout
+ * @attr ref R.styleable#MainKeyboardView_longPressShiftKeyTimeout
+ * @attr ref R.styleable#MainKeyboardView_ignoreAltCodeKeyTimeout
+ * @attr ref R.styleable#MainKeyboardView_showMoreKeysKeyboardAtTouchPoint
*/
public class MainKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler,
SuddenJumpingTouchEventHandler.ProcessMotionEvent {
@@ -110,7 +127,6 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
new WeakHashMap<Key, MoreKeysPanel>();
private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint;
- private final PointerTrackerParams mPointerTrackerParams;
private final SuddenJumpingTouchEventHandler mTouchScreenRegulator;
protected KeyDetector mKeyDetector;
@@ -127,15 +143,30 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
private static final int MSG_LONGPRESS_KEY = 2;
private static final int MSG_DOUBLE_TAP = 3;
- private final KeyTimerParams mParams;
+ private final int mKeyRepeatStartTimeout;
+ private final int mKeyRepeatInterval;
+ private final int mLongPressKeyTimeout;
+ private final int mLongPressShiftKeyTimeout;
+ private final int mIgnoreAltCodeKeyTimeout;
- public KeyTimerHandler(MainKeyboardView outerInstance, KeyTimerParams params) {
+ public KeyTimerHandler(final MainKeyboardView outerInstance,
+ final TypedArray mainKeyboardViewAttr) {
super(outerInstance);
- mParams = params;
+
+ mKeyRepeatStartTimeout = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0);
+ mKeyRepeatInterval = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_keyRepeatInterval, 0);
+ mLongPressKeyTimeout = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_longPressKeyTimeout, 0);
+ mLongPressShiftKeyTimeout = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_longPressShiftKeyTimeout, 0);
+ mIgnoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0);
}
@Override
- public void handleMessage(Message msg) {
+ public void handleMessage(final Message msg) {
final MainKeyboardView keyboardView = getOuterInstance();
final PointerTracker tracker = (PointerTracker) msg.obj;
switch (msg.what) {
@@ -146,7 +177,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
final Key currentKey = tracker.getKey();
if (currentKey != null && currentKey.mCode == msg.arg1) {
tracker.onRegisterKey(currentKey);
- startKeyRepeatTimer(tracker, mParams.mKeyRepeatInterval);
+ startKeyRepeatTimer(tracker, mKeyRepeatInterval);
}
break;
case MSG_LONGPRESS_KEY:
@@ -159,15 +190,15 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
}
}
- private void startKeyRepeatTimer(PointerTracker tracker, long delay) {
+ private void startKeyRepeatTimer(final PointerTracker tracker, final long delay) {
final Key key = tracker.getKey();
if (key == null) return;
sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, key.mCode, 0, tracker), delay);
}
@Override
- public void startKeyRepeatTimer(PointerTracker tracker) {
- startKeyRepeatTimer(tracker, mParams.mKeyRepeatStartTimeout);
+ public void startKeyRepeatTimer(final PointerTracker tracker) {
+ startKeyRepeatTimer(tracker, mKeyRepeatStartTimeout);
}
public void cancelKeyRepeatTimer() {
@@ -180,12 +211,12 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
}
@Override
- public void startLongPressTimer(int code) {
+ public void startLongPressTimer(final int code) {
cancelLongPressTimer();
final int delay;
switch (code) {
case Keyboard.CODE_SHIFT:
- delay = mParams.mLongPressShiftKeyTimeout;
+ delay = mLongPressShiftKeyTimeout;
break;
default:
delay = 0;
@@ -197,7 +228,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
}
@Override
- public void startLongPressTimer(PointerTracker tracker) {
+ public void startLongPressTimer(final PointerTracker tracker) {
cancelLongPressTimer();
if (tracker == null) {
return;
@@ -206,15 +237,15 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
final int delay;
switch (key.mCode) {
case Keyboard.CODE_SHIFT:
- delay = mParams.mLongPressShiftKeyTimeout;
+ delay = mLongPressShiftKeyTimeout;
break;
default:
if (KeyboardSwitcher.getInstance().isInMomentarySwitchState()) {
// We use longer timeout for sliding finger input started from the symbols
// mode key.
- delay = mParams.mLongPressKeyTimeout * 3;
+ delay = mLongPressKeyTimeout * 3;
} else {
- delay = mParams.mLongPressKeyTimeout;
+ delay = mLongPressKeyTimeout;
}
break;
}
@@ -251,7 +282,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
}
@Override
- public void startTypingStateTimer(Key typedKey) {
+ public void startTypingStateTimer(final Key typedKey) {
if (typedKey.isModifier() || typedKey.altCodeWhileTyping()) {
return;
}
@@ -268,7 +299,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
}
sendMessageDelayed(
- obtainMessage(MSG_TYPING_STATE_EXPIRED), mParams.mIgnoreAltCodeKeyTimeout);
+ obtainMessage(MSG_TYPING_STATE_EXPIRED), mIgnoreAltCodeKeyTimeout);
if (isTyping) {
return;
}
@@ -307,55 +338,11 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
}
}
- public static class PointerTrackerParams {
- public final boolean mSlidingKeyInputEnabled;
- public final int mTouchNoiseThresholdTime;
- public final float mTouchNoiseThresholdDistance;
-
- public static final PointerTrackerParams DEFAULT = new PointerTrackerParams();
-
- private PointerTrackerParams() {
- mSlidingKeyInputEnabled = false;
- mTouchNoiseThresholdTime =0;
- mTouchNoiseThresholdDistance = 0;
- }
-
- public PointerTrackerParams(TypedArray mainKeyboardViewAttr) {
- mSlidingKeyInputEnabled = mainKeyboardViewAttr.getBoolean(
- R.styleable.MainKeyboardView_slidingKeyInputEnable, false);
- mTouchNoiseThresholdTime = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_touchNoiseThresholdTime, 0);
- mTouchNoiseThresholdDistance = mainKeyboardViewAttr.getDimension(
- R.styleable.MainKeyboardView_touchNoiseThresholdDistance, 0);
- }
- }
-
- static class KeyTimerParams {
- public final int mKeyRepeatStartTimeout;
- public final int mKeyRepeatInterval;
- public final int mLongPressKeyTimeout;
- public final int mLongPressShiftKeyTimeout;
- public final int mIgnoreAltCodeKeyTimeout;
-
- public KeyTimerParams(TypedArray mainKeyboardViewAttr) {
- mKeyRepeatStartTimeout = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0);
- mKeyRepeatInterval = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_keyRepeatInterval, 0);
- mLongPressKeyTimeout = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_longPressKeyTimeout, 0);
- mLongPressShiftKeyTimeout = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_longPressShiftKeyTimeout, 0);
- mIgnoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0);
- }
- }
-
- public MainKeyboardView(Context context, AttributeSet attrs) {
+ public MainKeyboardView(final Context context, final AttributeSet attrs) {
this(context, attrs, R.attr.mainKeyboardViewStyle);
}
- public MainKeyboardView(Context context, AttributeSet attrs, int defStyle) {
+ public MainKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
mTouchScreenRegulator = new SuddenJumpingTouchEventHandler(getContext(), this);
@@ -364,7 +351,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT);
final Resources res = getResources();
final boolean needsPhantomSuddenMoveEventHack = Boolean.parseBoolean(
- Utils.getDeviceOverrideValue(res,
+ ResourceUtils.getDeviceOverrideValue(res,
R.array.phantom_sudden_move_event_device_list, "false"));
PointerTracker.init(mHasDistinctMultitouch, needsPhantomSuddenMoveEventHack);
@@ -374,8 +361,8 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
R.styleable.MainKeyboardView_autoCorrectionSpacebarLedEnabled, false);
mAutoCorrectionSpacebarLedIcon = a.getDrawable(
R.styleable.MainKeyboardView_autoCorrectionSpacebarLedIcon);
- mSpacebarTextRatio = a.getFraction(R.styleable.MainKeyboardView_spacebarTextRatio,
- 1000, 1000, 1) / 1000.0f;
+ mSpacebarTextRatio = a.getFraction(
+ R.styleable.MainKeyboardView_spacebarTextRatio, 1, 1, 1.0f);
mSpacebarTextColor = a.getColor(R.styleable.MainKeyboardView_spacebarTextColor, 0);
mSpacebarTextShadowColor = a.getColor(
R.styleable.MainKeyboardView_spacebarTextShadowColor, 0);
@@ -389,19 +376,15 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
final int altCodeKeyWhileTypingFadeinAnimatorResId = a.getResourceId(
R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0);
- final KeyTimerParams keyTimerParams = new KeyTimerParams(a);
- mPointerTrackerParams = new PointerTrackerParams(a);
-
final float keyHysteresisDistance = a.getDimension(
R.styleable.MainKeyboardView_keyHysteresisDistance, 0);
mKeyDetector = new KeyDetector(keyHysteresisDistance);
- mKeyTimerHandler = new KeyTimerHandler(this, keyTimerParams);
+ mKeyTimerHandler = new KeyTimerHandler(this, a);
mConfigShowMoreKeysKeyboardAtTouchedPoint = a.getBoolean(
R.styleable.MainKeyboardView_showMoreKeysKeyboardAtTouchedPoint, false);
+ PointerTracker.setParameters(a);
a.recycle();
- PointerTracker.setParameters(mPointerTrackerParams);
-
mLanguageOnSpacebarFadeoutAnimator = loadObjectAnimator(
languageOnSpacebarFadeoutAnimatorResId, this);
mAltCodeKeyWhileTypingFadeoutAnimator = loadObjectAnimator(
@@ -410,7 +393,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
altCodeKeyWhileTypingFadeinAnimatorResId, this);
}
- private ObjectAnimator loadObjectAnimator(int resId, Object target) {
+ private ObjectAnimator loadObjectAnimator(final int resId, final Object target) {
if (resId == 0) return null;
final ObjectAnimator animator = (ObjectAnimator)AnimatorInflater.loadAnimator(
getContext(), resId);
@@ -425,7 +408,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
return mLanguageOnSpacebarAnimAlpha;
}
- public void setLanguageOnSpacebarAnimAlpha(int alpha) {
+ public void setLanguageOnSpacebarAnimAlpha(final int alpha) {
mLanguageOnSpacebarAnimAlpha = alpha;
invalidateKey(mSpaceKey);
}
@@ -434,12 +417,12 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
return mAltCodeKeyWhileTypingAnimAlpha;
}
- public void setAltCodeKeyWhileTypingAnimAlpha(int alpha) {
+ public void setAltCodeKeyWhileTypingAnimAlpha(final int alpha) {
mAltCodeKeyWhileTypingAnimAlpha = alpha;
updateAltCodeKeyWhileTyping();
}
- public void setKeyboardActionListener(KeyboardActionListener listener) {
+ public void setKeyboardActionListener(final KeyboardActionListener listener) {
mKeyboardActionListener = listener;
PointerTracker.setKeyboardActionListener(listener);
}
@@ -476,7 +459,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
* @param keyboard the keyboard to display in this view
*/
@Override
- public void setKeyboard(Keyboard keyboard) {
+ public void setKeyboard(final Keyboard keyboard) {
// Remove any pending messages, except dismissing preview and key repeat.
mKeyTimerHandler.cancelLongPressTimer();
super.setKeyboard(keyboard);
@@ -501,11 +484,11 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
}
// Note that this method is called from a non-UI thread.
- public void setMainDictionaryAvailability(boolean mainDictionaryAvailable) {
+ public void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) {
PointerTracker.setMainDictionaryAvailability(mainDictionaryAvailable);
}
- public void setGestureHandlingEnabledByUser(boolean gestureHandlingEnabledByUser) {
+ public void setGestureHandlingEnabledByUser(final boolean gestureHandlingEnabledByUser) {
PointerTracker.setGestureHandlingEnabledByUser(gestureHandlingEnabledByUser);
}
@@ -517,7 +500,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
return mHasDistinctMultitouch;
}
- public void setDistinctMultitouch(boolean hasDistinctMultitouch) {
+ public void setDistinctMultitouch(final boolean hasDistinctMultitouch) {
mHasDistinctMultitouch = hasDistinctMultitouch;
}
@@ -528,7 +511,17 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
// to properly show the splash screen, which requires that the window token of the
// KeyboardView be non-null.
if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.getInstance().mainKeyboardView_onAttachedToWindow();
+ ResearchLogger.getInstance().mainKeyboardView_onAttachedToWindow(this);
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ // Notify the research logger that the keyboard view has been detached. This is needed
+ // to invalidate the reference of {@link MainKeyboardView} to null.
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.getInstance().mainKeyboardView_onDetachedFromWindow();
}
}
@@ -538,7 +531,8 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
super.cancelAllMessages();
}
- private boolean openMoreKeysKeyboardIfRequired(Key parentKey, PointerTracker tracker) {
+ private boolean openMoreKeysKeyboardIfRequired(final Key parentKey,
+ final PointerTracker tracker) {
// Check if we have a popup layout specified first.
if (mMoreKeysLayout == 0) {
return false;
@@ -553,7 +547,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
}
// This default implementation returns a more keys panel.
- protected MoreKeysPanel onCreateMoreKeysPanel(Key parentKey) {
+ protected MoreKeysPanel onCreateMoreKeysPanel(final Key parentKey) {
if (parentKey.mMoreKeys == null)
return null;
@@ -579,7 +573,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
* @return true if the long press is handled, false otherwise. Subclasses should call the
* method on the base class if the subclass doesn't wish to handle the call.
*/
- protected boolean onLongPress(Key parentKey, PointerTracker tracker) {
+ protected boolean onLongPress(final Key parentKey, final PointerTracker tracker) {
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.mainKeyboardView_onLongPress();
}
@@ -603,21 +597,20 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
return openMoreKeysPanel(parentKey, tracker);
}
- private boolean invokeCustomRequest(int code) {
+ private boolean invokeCustomRequest(final int code) {
return mKeyboardActionListener.onCustomRequest(code);
}
- private void invokeCodeInput(int primaryCode) {
- mKeyboardActionListener.onCodeInput(primaryCode,
- KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
- KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
+ private void invokeCodeInput(final int primaryCode) {
+ mKeyboardActionListener.onCodeInput(
+ primaryCode, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
}
- private void invokeReleaseKey(int primaryCode) {
+ private void invokeReleaseKey(final int primaryCode) {
mKeyboardActionListener.onReleaseKey(primaryCode, false);
}
- private boolean openMoreKeysPanel(Key parentKey, PointerTracker tracker) {
+ private boolean openMoreKeysPanel(final Key parentKey, final PointerTracker tracker) {
MoreKeysPanel moreKeysPanel = mMoreKeysPanelCache.get(parentKey);
if (moreKeysPanel == null) {
moreKeysPanel = onCreateMoreKeysPanel(parentKey);
@@ -643,9 +636,9 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
// The more keys keyboard is usually vertically aligned with the top edge of the parent key
// (plus vertical gap). If the key preview is enabled, the more keys keyboard is vertically
// aligned with the bottom edge of the visible part of the key preview.
- final int pointY = parentKey.mY + (keyPreviewEnabled
- ? mKeyPreviewDrawParams.mPreviewVisibleOffset
- : -parentKey.mVerticalGap);
+ // {@code mPreviewVisibleOffset} has been set appropriately in
+ // {@link KeyboardView#showKeyPreview(PointerTracker)}.
+ final int pointY = parentKey.mY + mKeyPreviewDrawParams.mPreviewVisibleOffset;
moreKeysPanel.showMoreKeysPanel(
this, this, pointX, pointY, mMoreKeysWindow, mKeyboardActionListener);
final int translatedX = moreKeysPanel.translateX(tracker.getLastX());
@@ -668,7 +661,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
}
@Override
- public boolean onTouchEvent(MotionEvent me) {
+ public boolean onTouchEvent(final MotionEvent me) {
if (getKeyboard() == null) {
return false;
}
@@ -679,7 +672,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
}
@Override
- public boolean processMotionEvent(MotionEvent me) {
+ public boolean processMotionEvent(final MotionEvent me) {
final boolean nonDistinctMultitouch = !mHasDistinctMultitouch;
final int action = me.getActionMasked();
final int pointerCount = me.getPointerCount();
@@ -846,7 +839,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
* otherwise
*/
@Override
- public boolean dispatchHoverEvent(MotionEvent event) {
+ public boolean dispatchHoverEvent(final MotionEvent event) {
if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
final PointerTracker tracker = PointerTracker.getPointerTracker(0, this);
return AccessibleKeyboardViewProxy.getInstance().dispatchHoverEvent(event, tracker);
@@ -856,7 +849,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
return false;
}
- public void updateShortcutKey(boolean available) {
+ public void updateShortcutKey(final boolean available) {
final Keyboard keyboard = getKeyboard();
if (keyboard == null) return;
final Key shortcutKey = keyboard.getKey(Keyboard.CODE_SHORTCUT);
@@ -873,8 +866,8 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
}
}
- public void startDisplayLanguageOnSpacebar(boolean subtypeChanged,
- boolean needsToDisplayLanguage, boolean hasMultipleEnabledIMEsOrSubtypes) {
+ public void startDisplayLanguageOnSpacebar(final boolean subtypeChanged,
+ final boolean needsToDisplayLanguage, final boolean hasMultipleEnabledIMEsOrSubtypes) {
mNeedsToDisplayLanguage = needsToDisplayLanguage;
mHasMultipleEnabledIMEsOrSubtypes = hasMultipleEnabledIMEsOrSubtypes;
final ObjectAnimator animator = mLanguageOnSpacebarFadeoutAnimator;
@@ -896,14 +889,15 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
invalidateKey(mSpaceKey);
}
- public void updateAutoCorrectionState(boolean isAutoCorrection) {
+ public void updateAutoCorrectionState(final boolean isAutoCorrection) {
if (!mAutoCorrectionSpacebarLedEnabled) return;
mAutoCorrectionSpacebarLedOn = isAutoCorrection;
invalidateKey(mSpaceKey);
}
@Override
- protected void onDrawKeyTopVisuals(Key key, Canvas canvas, Paint paint, KeyDrawParams params) {
+ protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint,
+ final KeyDrawParams params) {
if (key.altCodeWhileTyping() && key.isEnabled()) {
params.mAnimAlpha = mAltCodeKeyWhileTypingAnimAlpha;
}
@@ -921,7 +915,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
}
}
- private boolean fitsTextIntoWidth(final int width, String text, Paint paint) {
+ 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;
@@ -934,7 +928,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
}
// Layout language name on spacebar.
- private String layoutLanguageOnSpacebar(Paint paint, InputMethodSubtype subtype,
+ 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());
@@ -955,7 +949,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
return "";
}
- private void drawSpacebar(Key key, Canvas canvas, Paint paint) {
+ private void drawSpacebar(final Key key, final Canvas canvas, final Paint paint) {
final int width = key.mWidth;
final int height = key.mHeight;
@@ -1010,7 +1004,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
// zz azerty T AZERTY AZERTY
// Get InputMethodSubtype's full display name in its locale.
- static String getFullDisplayName(InputMethodSubtype subtype, Resources res) {
+ static String getFullDisplayName(final InputMethodSubtype subtype, final Resources res) {
if (SubtypeLocale.isNoLanguage(subtype)) {
return SubtypeLocale.getKeyboardLayoutSetDisplayName(subtype);
}
@@ -1019,7 +1013,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
}
// Get InputMethodSubtype's short display name in its locale.
- static String getShortDisplayName(InputMethodSubtype subtype) {
+ static String getShortDisplayName(final InputMethodSubtype subtype) {
if (SubtypeLocale.isNoLanguage(subtype)) {
return "";
}
@@ -1028,7 +1022,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
}
// Get InputMethodSubtype's middle display name in its locale.
- static String getMiddleDisplayName(InputMethodSubtype subtype) {
+ static String getMiddleDisplayName(final InputMethodSubtype subtype) {
if (SubtypeLocale.isNoLanguage(subtype)) {
return SubtypeLocale.getKeyboardLayoutSetDisplayName(subtype);
}
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
index a3741a2d8..c9af888f9 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
@@ -20,15 +20,17 @@ import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.view.View;
-import com.android.inputmethod.keyboard.internal.KeySpecParser.MoreKeySpec;
+import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
+import com.android.inputmethod.keyboard.internal.KeyboardParams;
+import com.android.inputmethod.keyboard.internal.MoreKeySpec;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.StringUtils;
public class MoreKeysKeyboard extends Keyboard {
private final int mDefaultKeyCoordX;
- MoreKeysKeyboard(Builder.MoreKeysKeyboardParams params) {
+ MoreKeysKeyboard(final MoreKeysKeyboardParams params) {
super(params);
mDefaultKeyCoordX = params.getDefaultKeyCoordX() + params.mDefaultKeyWidth / 2;
}
@@ -37,228 +39,231 @@ public class MoreKeysKeyboard extends Keyboard {
return mDefaultKeyCoordX;
}
- public static class Builder extends Keyboard.Builder<Builder.MoreKeysKeyboardParams> {
- private final Key mParentKey;
- private final Drawable mDivider;
-
- private static final float LABEL_PADDING_RATIO = 0.2f;
- private static final float DIVIDER_RATIO = 0.2f;
+ /* package for test */
+ static class MoreKeysKeyboardParams extends KeyboardParams {
+ public boolean mIsFixedOrder;
+ /* package */int mTopRowAdjustment;
+ public int mNumRows;
+ public int mNumColumns;
+ public int mTopKeys;
+ public int mLeftKeys;
+ public int mRightKeys; // includes default key.
+ public int mDividerWidth;
+ public int mColumnWidth;
+
+ public MoreKeysKeyboardParams() {
+ super();
+ }
- public static class MoreKeysKeyboardParams extends Keyboard.Params {
- public boolean mIsFixedOrder;
- /* package */int mTopRowAdjustment;
- public int mNumRows;
- public int mNumColumns;
- public int mTopKeys;
- public int mLeftKeys;
- public int mRightKeys; // includes default key.
- public int mDividerWidth;
- public int mColumnWidth;
-
- public MoreKeysKeyboardParams() {
- super();
+ /**
+ * Set keyboard parameters of more keys keyboard.
+ *
+ * @param numKeys number of keys in this more keys keyboard.
+ * @param maxColumns number of maximum columns of this more keys keyboard.
+ * @param keyWidth more keys keyboard key width in pixel, including horizontal gap.
+ * @param rowHeight more keys keyboard row height in pixel, including vertical gap.
+ * @param coordXInParent coordinate x of the key preview in parent keyboard.
+ * @param parentKeyboardWidth parent keyboard width in pixel.
+ * @param isFixedColumnOrder if true, more keys should be laid out in fixed order.
+ * @param dividerWidth width of divider, zero for no dividers.
+ */
+ public void setParameters(final int numKeys, final int maxColumns, final int keyWidth,
+ final int rowHeight, final int coordXInParent, final int parentKeyboardWidth,
+ final boolean isFixedColumnOrder, final int dividerWidth) {
+ mIsFixedOrder = isFixedColumnOrder;
+ if (parentKeyboardWidth / keyWidth < maxColumns) {
+ throw new IllegalArgumentException(
+ "Keyboard is too small to hold more keys keyboard: "
+ + parentKeyboardWidth + " " + keyWidth + " " + maxColumns);
}
-
- /**
- * Set keyboard parameters of more keys keyboard.
- *
- * @param numKeys number of keys in this more keys keyboard.
- * @param maxColumns number of maximum columns of this more keys keyboard.
- * @param keyWidth more keys keyboard key width in pixel, including horizontal gap.
- * @param rowHeight more keys keyboard row height in pixel, including vertical gap.
- * @param coordXInParent coordinate x of the key preview in parent keyboard.
- * @param parentKeyboardWidth parent keyboard width in pixel.
- * @param isFixedColumnOrder if true, more keys should be laid out in fixed order.
- * @param dividerWidth width of divider, zero for no dividers.
- */
- public void setParameters(int numKeys, int maxColumns, int keyWidth, int rowHeight,
- int coordXInParent, int parentKeyboardWidth, boolean isFixedColumnOrder,
- int dividerWidth) {
- mIsFixedOrder = isFixedColumnOrder;
- if (parentKeyboardWidth / keyWidth < maxColumns) {
- throw new IllegalArgumentException(
- "Keyboard is too small to hold more keys keyboard: "
- + parentKeyboardWidth + " " + keyWidth + " " + maxColumns);
- }
- mDefaultKeyWidth = keyWidth;
- mDefaultRowHeight = rowHeight;
-
- final int numRows = (numKeys + maxColumns - 1) / maxColumns;
- mNumRows = numRows;
- final int numColumns = mIsFixedOrder ? Math.min(numKeys, maxColumns)
- : getOptimizedColumns(numKeys, maxColumns);
- mNumColumns = numColumns;
- final int topKeys = numKeys % numColumns;
- mTopKeys = topKeys == 0 ? numColumns : topKeys;
-
- final int numLeftKeys = (numColumns - 1) / 2;
- final int numRightKeys = numColumns - numLeftKeys; // including default key.
- // Maximum number of keys we can layout both side of the parent key
- final int maxLeftKeys = coordXInParent / keyWidth;
- final int maxRightKeys = (parentKeyboardWidth - coordXInParent) / keyWidth;
- int leftKeys, rightKeys;
- if (numLeftKeys > maxLeftKeys) {
- leftKeys = maxLeftKeys;
- rightKeys = numColumns - leftKeys;
- } else if (numRightKeys > maxRightKeys + 1) {
- rightKeys = maxRightKeys + 1; // include default key
- leftKeys = numColumns - rightKeys;
- } else {
- leftKeys = numLeftKeys;
- rightKeys = numRightKeys;
- }
- // If the left keys fill the left side of the parent key, entire more keys keyboard
- // should be shifted to the right unless the parent key is on the left edge.
- if (maxLeftKeys == leftKeys && leftKeys > 0) {
- leftKeys--;
- rightKeys++;
- }
- // If the right keys fill the right side of the parent key, entire more keys
- // should be shifted to the left unless the parent key is on the right edge.
- if (maxRightKeys == rightKeys - 1 && rightKeys > 1) {
- leftKeys++;
- rightKeys--;
- }
- mLeftKeys = leftKeys;
- mRightKeys = rightKeys;
-
- // Adjustment of the top row.
- mTopRowAdjustment = mIsFixedOrder ? getFixedOrderTopRowAdjustment()
- : getAutoOrderTopRowAdjustment();
- mDividerWidth = dividerWidth;
- mColumnWidth = mDefaultKeyWidth + mDividerWidth;
- mBaseWidth = mOccupiedWidth = mNumColumns * mColumnWidth - mDividerWidth;
- // Need to subtract the bottom row's gutter only.
- mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight - mVerticalGap
- + mTopPadding + mBottomPadding;
+ mDefaultKeyWidth = keyWidth;
+ mDefaultRowHeight = rowHeight;
+
+ final int numRows = (numKeys + maxColumns - 1) / maxColumns;
+ mNumRows = numRows;
+ final int numColumns = mIsFixedOrder ? Math.min(numKeys, maxColumns)
+ : getOptimizedColumns(numKeys, maxColumns);
+ mNumColumns = numColumns;
+ final int topKeys = numKeys % numColumns;
+ mTopKeys = topKeys == 0 ? numColumns : topKeys;
+
+ final int numLeftKeys = (numColumns - 1) / 2;
+ final int numRightKeys = numColumns - numLeftKeys; // including default key.
+ // Maximum number of keys we can layout both side of the parent key
+ final int maxLeftKeys = coordXInParent / keyWidth;
+ final int maxRightKeys = (parentKeyboardWidth - coordXInParent) / keyWidth;
+ int leftKeys, rightKeys;
+ if (numLeftKeys > maxLeftKeys) {
+ leftKeys = maxLeftKeys;
+ rightKeys = numColumns - leftKeys;
+ } else if (numRightKeys > maxRightKeys + 1) {
+ rightKeys = maxRightKeys + 1; // include default key
+ leftKeys = numColumns - rightKeys;
+ } else {
+ leftKeys = numLeftKeys;
+ rightKeys = numRightKeys;
}
-
- private int getFixedOrderTopRowAdjustment() {
- if (mNumRows == 1 || mTopKeys % 2 == 1 || mTopKeys == mNumColumns
- || mLeftKeys == 0 || mRightKeys == 1) {
- return 0;
- }
- return -1;
+ // If the left keys fill the left side of the parent key, entire more keys keyboard
+ // should be shifted to the right unless the parent key is on the left edge.
+ if (maxLeftKeys == leftKeys && leftKeys > 0) {
+ leftKeys--;
+ rightKeys++;
}
-
- private int getAutoOrderTopRowAdjustment() {
- if (mNumRows == 1 || mTopKeys == 1 || mNumColumns % 2 == mTopKeys % 2
- || mLeftKeys == 0 || mRightKeys == 1) {
- return 0;
- }
- return -1;
+ // If the right keys fill the right side of the parent key, entire more keys
+ // should be shifted to the left unless the parent key is on the right edge.
+ if (maxRightKeys == rightKeys - 1 && rightKeys > 1) {
+ leftKeys++;
+ rightKeys--;
}
+ mLeftKeys = leftKeys;
+ mRightKeys = rightKeys;
+
+ // Adjustment of the top row.
+ mTopRowAdjustment = mIsFixedOrder ? getFixedOrderTopRowAdjustment()
+ : getAutoOrderTopRowAdjustment();
+ mDividerWidth = dividerWidth;
+ mColumnWidth = mDefaultKeyWidth + mDividerWidth;
+ mBaseWidth = mOccupiedWidth = mNumColumns * mColumnWidth - mDividerWidth;
+ // Need to subtract the bottom row's gutter only.
+ mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight - mVerticalGap
+ + mTopPadding + mBottomPadding;
+ }
- // Return key position according to column count (0 is default).
- /* package */int getColumnPos(int n) {
- return mIsFixedOrder ? getFixedOrderColumnPos(n) : getAutomaticColumnPos(n);
+ private int getFixedOrderTopRowAdjustment() {
+ if (mNumRows == 1 || mTopKeys % 2 == 1 || mTopKeys == mNumColumns
+ || mLeftKeys == 0 || mRightKeys == 1) {
+ return 0;
}
+ return -1;
+ }
- private int getFixedOrderColumnPos(int n) {
- final int col = n % mNumColumns;
- final int row = n / mNumColumns;
- if (!isTopRow(row)) {
- return col - mLeftKeys;
- }
- final int rightSideKeys = mTopKeys / 2;
- final int leftSideKeys = mTopKeys - (rightSideKeys + 1);
- final int pos = col - leftSideKeys;
- final int numLeftKeys = mLeftKeys + mTopRowAdjustment;
- final int numRightKeys = mRightKeys - 1;
- if (numRightKeys >= rightSideKeys && numLeftKeys >= leftSideKeys) {
- return pos;
- } else if (numRightKeys < rightSideKeys) {
- return pos - (rightSideKeys - numRightKeys);
- } else { // numLeftKeys < leftSideKeys
- return pos + (leftSideKeys - numLeftKeys);
- }
+ private int getAutoOrderTopRowAdjustment() {
+ if (mNumRows == 1 || mTopKeys == 1 || mNumColumns % 2 == mTopKeys % 2
+ || mLeftKeys == 0 || mRightKeys == 1) {
+ return 0;
}
+ return -1;
+ }
- private int getAutomaticColumnPos(int n) {
- final int col = n % mNumColumns;
- final int row = n / mNumColumns;
- int leftKeys = mLeftKeys;
- if (isTopRow(row)) {
- leftKeys += mTopRowAdjustment;
- }
- if (col == 0) {
- // default position.
- return 0;
- }
+ // Return key position according to column count (0 is default).
+ /* package */int getColumnPos(final int n) {
+ return mIsFixedOrder ? getFixedOrderColumnPos(n) : getAutomaticColumnPos(n);
+ }
- int pos = 0;
- int right = 1; // include default position key.
- int left = 0;
- int i = 0;
- while (true) {
- // Assign right key if available.
- if (right < mRightKeys) {
- pos = right;
- right++;
- i++;
- }
- if (i >= col)
- break;
- // Assign left key if available.
- if (left < leftKeys) {
- left++;
- pos = -left;
- i++;
- }
- if (i >= col)
- break;
- }
+ private int getFixedOrderColumnPos(final int n) {
+ final int col = n % mNumColumns;
+ final int row = n / mNumColumns;
+ if (!isTopRow(row)) {
+ return col - mLeftKeys;
+ }
+ final int rightSideKeys = mTopKeys / 2;
+ final int leftSideKeys = mTopKeys - (rightSideKeys + 1);
+ final int pos = col - leftSideKeys;
+ final int numLeftKeys = mLeftKeys + mTopRowAdjustment;
+ final int numRightKeys = mRightKeys - 1;
+ if (numRightKeys >= rightSideKeys && numLeftKeys >= leftSideKeys) {
return pos;
+ } else if (numRightKeys < rightSideKeys) {
+ return pos - (rightSideKeys - numRightKeys);
+ } else { // numLeftKeys < leftSideKeys
+ return pos + (leftSideKeys - numLeftKeys);
}
+ }
- private static int getTopRowEmptySlots(int numKeys, int numColumns) {
- final int remainings = numKeys % numColumns;
- return remainings == 0 ? 0 : numColumns - remainings;
+ private int getAutomaticColumnPos(final int n) {
+ final int col = n % mNumColumns;
+ final int row = n / mNumColumns;
+ int leftKeys = mLeftKeys;
+ if (isTopRow(row)) {
+ leftKeys += mTopRowAdjustment;
+ }
+ if (col == 0) {
+ // default position.
+ return 0;
}
- private int getOptimizedColumns(int numKeys, int maxColumns) {
- int numColumns = Math.min(numKeys, maxColumns);
- while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) {
- numColumns--;
+ int pos = 0;
+ int right = 1; // include default position key.
+ int left = 0;
+ int i = 0;
+ while (true) {
+ // Assign right key if available.
+ if (right < mRightKeys) {
+ pos = right;
+ right++;
+ i++;
}
- return numColumns;
+ if (i >= col)
+ break;
+ // Assign left key if available.
+ if (left < leftKeys) {
+ left++;
+ pos = -left;
+ i++;
+ }
+ if (i >= col)
+ break;
}
+ return pos;
+ }
- public int getDefaultKeyCoordX() {
- return mLeftKeys * mColumnWidth;
- }
+ private static int getTopRowEmptySlots(final int numKeys, final int numColumns) {
+ final int remainings = numKeys % numColumns;
+ return remainings == 0 ? 0 : numColumns - remainings;
+ }
- public int getX(int n, int row) {
- final int x = getColumnPos(n) * mColumnWidth + getDefaultKeyCoordX();
- if (isTopRow(row)) {
- return x + mTopRowAdjustment * (mColumnWidth / 2);
- }
- return x;
+ private int getOptimizedColumns(final int numKeys, final int maxColumns) {
+ int numColumns = Math.min(numKeys, maxColumns);
+ while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) {
+ numColumns--;
}
+ return numColumns;
+ }
- public int getY(int row) {
- return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding;
- }
+ public int getDefaultKeyCoordX() {
+ return mLeftKeys * mColumnWidth;
+ }
- public void markAsEdgeKey(Key key, int row) {
- if (row == 0)
- key.markAsTopEdge(this);
- if (isTopRow(row))
- key.markAsBottomEdge(this);
+ public int getX(final int n, final int row) {
+ final int x = getColumnPos(n) * mColumnWidth + getDefaultKeyCoordX();
+ if (isTopRow(row)) {
+ return x + mTopRowAdjustment * (mColumnWidth / 2);
}
+ return x;
+ }
- private boolean isTopRow(int rowCount) {
- return mNumRows > 1 && rowCount == mNumRows - 1;
- }
+ public int getY(final int row) {
+ return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding;
}
+ public void markAsEdgeKey(final Key key, final int row) {
+ if (row == 0)
+ key.markAsTopEdge(this);
+ if (isTopRow(row))
+ key.markAsBottomEdge(this);
+ }
+
+ private boolean isTopRow(final int rowCount) {
+ return mNumRows > 1 && rowCount == mNumRows - 1;
+ }
+ }
+
+ public static class Builder extends KeyboardBuilder<MoreKeysKeyboardParams> {
+ private final Key mParentKey;
+ private final Drawable mDivider;
+
+ private static final float LABEL_PADDING_RATIO = 0.2f;
+ private static final float DIVIDER_RATIO = 0.2f;
+
+
/**
* The builder of MoreKeysKeyboard.
* @param containerView the container of {@link MoreKeysKeyboardView}.
* @param parentKey the {@link Key} that invokes more keys keyboard.
* @param parentKeyboardView the {@link KeyboardView} that contains the parentKey.
*/
- public Builder(View containerView, Key parentKey, KeyboardView parentKeyboardView) {
+ public Builder(final View containerView, final Key parentKey,
+ final KeyboardView parentKeyboardView) {
super(containerView.getContext(), new MoreKeysKeyboardParams());
final Keyboard parentKeyboard = parentKeyboardView.getKeyboard();
load(parentKeyboard.mMoreKeysTemplate, parentKeyboard.mId);
@@ -300,14 +305,14 @@ public class MoreKeysKeyboard extends Keyboard {
dividerWidth);
}
- private static int getMaxKeyWidth(KeyboardView view, Key parentKey, int minKeyWidth) {
+ private static int getMaxKeyWidth(final KeyboardView view, final Key parentKey,
+ final int minKeyWidth) {
final int padding = (int)(view.getResources()
.getDimension(R.dimen.more_keys_keyboard_key_horizontal_padding)
+ (parentKey.hasLabelsInMoreKeys() ? minKeyWidth * LABEL_PADDING_RATIO : 0));
final Paint paint = view.newDefaultLabelPaint();
- paint.setTextSize(parentKey.hasLabelsInMoreKeys()
- ? view.mKeyDrawParams.mKeyLabelSize
- : view.mKeyDrawParams.mKeyLetterSize);
+ paint.setTypeface(parentKey.selectTypeface(view.mKeyDrawParams));
+ paint.setTextSize(parentKey.selectMoreKeyTextSize(view.mKeyDrawParams));
int maxWidth = minKeyWidth;
for (final MoreKeySpec spec : parentKey.mMoreKeys) {
final String label = spec.mLabel;
@@ -322,24 +327,6 @@ public class MoreKeysKeyboard extends Keyboard {
return maxWidth;
}
- private static class MoreKeyDivider extends Key.Spacer {
- private final Drawable mIcon;
-
- public MoreKeyDivider(MoreKeysKeyboardParams params, Drawable icon, int x, int y) {
- super(params, x, y, params.mDividerWidth, params.mDefaultRowHeight);
- mIcon = icon;
- }
-
- @Override
- public Drawable getIcon(KeyboardIconsSet iconSet, int alpha) {
- // KeyboardIconsSet and alpha are unused. Use the icon that has been passed to the
- // constructor.
- // TODO: Drawable itself should have an alpha value.
- mIcon.setAlpha(128);
- return mIcon;
- }
- }
-
@Override
public MoreKeysKeyboard build() {
final MoreKeysKeyboardParams params = mParams;
@@ -368,4 +355,23 @@ public class MoreKeysKeyboard extends Keyboard {
return new MoreKeysKeyboard(params);
}
}
+
+ private static class MoreKeyDivider extends Key.Spacer {
+ private final Drawable mIcon;
+
+ public MoreKeyDivider(final MoreKeysKeyboardParams params, final Drawable icon,
+ final int x, final int y) {
+ super(params, x, y, params.mDividerWidth, params.mDefaultRowHeight);
+ mIcon = icon;
+ }
+
+ @Override
+ public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) {
+ // KeyboardIconsSet and alpha are unused. Use the icon that has been passed to the
+ // constructor.
+ // TODO: Drawable itself should have an alpha value.
+ mIcon.setAlpha(128);
+ return mIcon;
+ }
+ }
}
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
index 870eff29f..e513a1477 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
@@ -25,6 +25,7 @@ import android.widget.PopupWindow;
import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy;
import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
+import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.InputPointers;
import com.android.inputmethod.latin.R;
@@ -50,7 +51,8 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
public void onCodeInput(int primaryCode, int x, int y) {
// Because a more keys keyboard doesn't need proximity characters correction, we don't
// send touch event coordinates.
- mListener.onCodeInput(primaryCode, NOT_A_TOUCH_COORDINATE, NOT_A_TOUCH_COORDINATE);
+ mListener.onCodeInput(
+ primaryCode, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
}
@Override
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 7d565a64f..5a79d508f 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -16,19 +16,19 @@
package com.android.inputmethod.keyboard;
-import android.graphics.Canvas;
-import android.graphics.Paint;
+import android.content.res.TypedArray;
import android.os.SystemClock;
import android.util.Log;
import android.view.MotionEvent;
-import android.view.View;
-import android.widget.TextView;
import com.android.inputmethod.accessibility.AccessibilityUtils;
import com.android.inputmethod.keyboard.internal.GestureStroke;
+import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewTrail;
import com.android.inputmethod.keyboard.internal.PointerTrackerQueue;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.InputPointers;
import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.research.ResearchLogger;
@@ -78,10 +78,9 @@ public class PointerTracker implements PointerTrackerQueue.Element {
public interface DrawingProxy extends MoreKeysPanel.Controller {
public void invalidateKey(Key key);
- public TextView inflateKeyPreviewText();
public void showKeyPreview(PointerTracker tracker);
public void dismissKeyPreview(PointerTracker tracker);
- public void showGestureTrail(PointerTracker tracker);
+ public void showGesturePreviewTrail(PointerTracker tracker);
}
public interface TimerProxy {
@@ -120,14 +119,39 @@ public class PointerTracker implements PointerTrackerQueue.Element {
}
}
+ static class PointerTrackerParams {
+ public final boolean mSlidingKeyInputEnabled;
+ public final int mTouchNoiseThresholdTime;
+ public final float mTouchNoiseThresholdDistance;
+ public final int mTouchNoiseThresholdDistanceSquared;
+
+ public static final PointerTrackerParams DEFAULT = new PointerTrackerParams();
+
+ private PointerTrackerParams() {
+ mSlidingKeyInputEnabled = false;
+ mTouchNoiseThresholdTime = 0;
+ mTouchNoiseThresholdDistance = 0.0f;
+ mTouchNoiseThresholdDistanceSquared = 0;
+ }
+
+ public PointerTrackerParams(TypedArray mainKeyboardViewAttr) {
+ mSlidingKeyInputEnabled = mainKeyboardViewAttr.getBoolean(
+ R.styleable.MainKeyboardView_slidingKeyInputEnable, false);
+ mTouchNoiseThresholdTime = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_touchNoiseThresholdTime, 0);
+ final float touchNouseThresholdDistance = mainKeyboardViewAttr.getDimension(
+ R.styleable.MainKeyboardView_touchNoiseThresholdDistance, 0);
+ mTouchNoiseThresholdDistance = touchNouseThresholdDistance;
+ mTouchNoiseThresholdDistanceSquared =
+ (int)(touchNouseThresholdDistance * touchNouseThresholdDistance);
+ }
+ }
+
// Parameters for pointer handling.
- private static MainKeyboardView.PointerTrackerParams sParams;
- private static int sTouchNoiseThresholdDistanceSquared;
+ private static PointerTrackerParams sParams;
private static boolean sNeedsPhantomSuddenMoveEventHack;
- private static final ArrayList<PointerTracker> sTrackers = new ArrayList<PointerTracker>();
- private static final InputPointers sAggregratedPointers = new InputPointers(
- GestureStroke.DEFAULT_CAPACITY);
+ private static final ArrayList<PointerTracker> sTrackers = CollectionUtils.newArrayList();
private static PointerTrackerQueue sPointerTrackerQueue;
public final int mPointerId;
@@ -139,15 +163,14 @@ public class PointerTracker implements PointerTrackerQueue.Element {
private Keyboard mKeyboard;
private int mKeyQuarterWidthSquared;
- private final TextView mKeyPreviewText;
- private boolean mIsAlphabetKeyboard;
- private boolean mIsPossibleGesture = false;
- private boolean mInGesture = false;
-
- // TODO: Remove these variables
- private int mLastRecognitionPointSize = 0;
- private long mLastRecognitionTime = 0;
+ private boolean mIsDetectingGesture = false; // per PointerTracker.
+ private static boolean sInGesture = false;
+ private static long sGestureFirstDownTime;
+ private static final InputPointers sAggregratedPointers = new InputPointers(
+ GestureStroke.DEFAULT_CAPACITY);
+ private static int sLastRecognitionPointSize = 0;
+ private static long sLastRecognitionTime = 0;
// The position and time at which first down event occurred.
private long mDownTime;
@@ -185,7 +208,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
private static final KeyboardActionListener EMPTY_LISTENER =
new KeyboardActionListener.Adapter();
- private final GestureStroke mGestureStroke;
+ private final GestureStrokeWithPreviewTrail mGestureStrokeWithPreviewTrail;
public static void init(boolean hasDistinctMultitouch,
boolean needsPhantomSuddenMoveEventHack) {
@@ -195,14 +218,11 @@ public class PointerTracker implements PointerTrackerQueue.Element {
sPointerTrackerQueue = null;
}
sNeedsPhantomSuddenMoveEventHack = needsPhantomSuddenMoveEventHack;
-
- setParameters(MainKeyboardView.PointerTrackerParams.DEFAULT);
+ sParams = PointerTrackerParams.DEFAULT;
}
- public static void setParameters(MainKeyboardView.PointerTrackerParams params) {
- sParams = params;
- sTouchNoiseThresholdDistanceSquared = (int)(
- params.mTouchNoiseThresholdDistance * params.mTouchNoiseThresholdDistance);
+ public static void setParameters(final TypedArray mainKeyboardViewAttr) {
+ sParams = new PointerTrackerParams(mainKeyboardViewAttr);
}
private static void updateGestureHandlingMode() {
@@ -213,17 +233,17 @@ public class PointerTracker implements PointerTrackerQueue.Element {
}
// Note that this method is called from a non-UI thread.
- public static void setMainDictionaryAvailability(boolean mainDictionaryAvailable) {
+ public static void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) {
sMainDictionaryAvailable = mainDictionaryAvailable;
updateGestureHandlingMode();
}
- public static void setGestureHandlingEnabledByUser(boolean gestureHandlingEnabledByUser) {
+ public static void setGestureHandlingEnabledByUser(final boolean gestureHandlingEnabledByUser) {
sGestureHandlingEnabledByUser = gestureHandlingEnabledByUser;
updateGestureHandlingMode();
}
- public static PointerTracker getPointerTracker(final int id, KeyEventHandler handler) {
+ public static PointerTracker getPointerTracker(final int id, final KeyEventHandler handler) {
final ArrayList<PointerTracker> trackers = sTrackers;
// Create pointer trackers until we can get 'id+1'-th tracker, if needed.
@@ -239,7 +259,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
return sPointerTrackerQueue != null ? sPointerTrackerQueue.isAnyInSlidingKeyInput() : false;
}
- public static void setKeyboardActionListener(KeyboardActionListener listener) {
+ public static void setKeyboardActionListener(final KeyboardActionListener listener) {
final int trackersSize = sTrackers.size();
for (int i = 0; i < trackersSize; ++i) {
final PointerTracker tracker = sTrackers.get(i);
@@ -247,7 +267,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
}
}
- public static void setKeyDetector(KeyDetector keyDetector) {
+ public static void setKeyDetector(final KeyDetector keyDetector) {
final int trackersSize = sTrackers.size();
for (int i = 0; i < trackersSize; ++i) {
final PointerTracker tracker = sTrackers.get(i);
@@ -260,67 +280,29 @@ public class PointerTracker implements PointerTrackerQueue.Element {
updateGestureHandlingMode();
}
- public static void dismissAllKeyPreviews() {
+ public static void setReleasedKeyGraphicsToAllKeys() {
final int trackersSize = sTrackers.size();
for (int i = 0; i < trackersSize; ++i) {
final PointerTracker tracker = sTrackers.get(i);
- tracker.getKeyPreviewText().setVisibility(View.INVISIBLE);
tracker.setReleasedKeyGraphics(tracker.mCurrentKey);
}
}
- // TODO: To handle multi-touch gestures we may want to move this method to
- // {@link PointerTrackerQueue}.
- private static InputPointers getIncrementalBatchPoints() {
- final int trackersSize = sTrackers.size();
- for (int i = 0; i < trackersSize; ++i) {
- final PointerTracker tracker = sTrackers.get(i);
- tracker.mGestureStroke.appendIncrementalBatchPoints(sAggregratedPointers);
- }
- return sAggregratedPointers;
- }
-
- // TODO: To handle multi-touch gestures we may want to move this method to
- // {@link PointerTrackerQueue}.
- private static InputPointers getAllBatchPoints() {
- final int trackersSize = sTrackers.size();
- for (int i = 0; i < trackersSize; ++i) {
- final PointerTracker tracker = sTrackers.get(i);
- tracker.mGestureStroke.appendAllBatchPoints(sAggregratedPointers);
- }
- return sAggregratedPointers;
- }
-
- // TODO: To handle multi-touch gestures we may want to move this method to
- // {@link PointerTrackerQueue}.
- public static void clearBatchInputPointsOfAllPointerTrackers() {
- final int trackersSize = sTrackers.size();
- for (int i = 0; i < trackersSize; ++i) {
- final PointerTracker tracker = sTrackers.get(i);
- tracker.mGestureStroke.reset();
- }
- sAggregratedPointers.reset();
- }
-
- private PointerTracker(int id, KeyEventHandler handler) {
- if (handler == null)
+ private PointerTracker(final int id, final KeyEventHandler handler) {
+ if (handler == null) {
throw new NullPointerException();
+ }
mPointerId = id;
- mGestureStroke = new GestureStroke(id);
+ mGestureStrokeWithPreviewTrail = new GestureStrokeWithPreviewTrail(id);
setKeyDetectorInner(handler.getKeyDetector());
mListener = handler.getKeyboardActionListener();
mDrawingProxy = handler.getDrawingProxy();
mTimerProxy = handler.getTimerProxy();
- mKeyPreviewText = mDrawingProxy.inflateKeyPreviewText();
- }
-
- public TextView getKeyPreviewText() {
- return mKeyPreviewText;
}
// Returns true if keyboard has been changed by this callback.
- private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key) {
- if (mInGesture) {
+ private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key) {
+ if (sInGesture) {
return false;
}
final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
@@ -344,13 +326,14 @@ 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(Key key, int primaryCode, int x, int y) {
+ private void callListenerOnCodeInput(final Key key, final int primaryCode, final int x,
+ final int y) {
final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState();
- final int code = altersCode ? key.mAltCode : primaryCode;
+ final int code = altersCode ? key.getAltCode() : primaryCode;
if (DEBUG_LISTENER) {
- Log.d(TAG, "onCodeInput: " + Keyboard.printableCode(code) + " text=" + key.mOutputText
- + " x=" + x + " y=" + y
+ Log.d(TAG, "onCodeInput: " + Keyboard.printableCode(code)
+ + " text=" + key.getOutputText() + " x=" + x + " y=" + y
+ " ignoreModifier=" + ignoreModifierKey + " altersCode=" + altersCode
+ " enabled=" + key.isEnabled());
}
@@ -364,7 +347,7 @@ 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) {
- mListener.onTextInput(key.mOutputText);
+ mListener.onTextInput(key.getOutputText());
} else if (code != Keyboard.CODE_UNSPECIFIED) {
mListener.onCodeInput(code, x, y);
}
@@ -373,8 +356,9 @@ 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 callListenerOnRelease(Key key, int primaryCode, boolean withSliding) {
- if (mInGesture) {
+ private void callListenerOnRelease(final Key key, final int primaryCode,
+ final boolean withSliding) {
+ if (sInGesture) {
return;
}
final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
@@ -396,19 +380,19 @@ public class PointerTracker implements PointerTrackerQueue.Element {
}
private void callListenerOnCancelInput() {
- if (DEBUG_LISTENER)
+ if (DEBUG_LISTENER) {
Log.d(TAG, "onCancelInput");
+ }
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.pointerTracker_callListenerOnCancelInput();
}
mListener.onCancelInput();
}
- private void setKeyDetectorInner(KeyDetector keyDetector) {
+ private void setKeyDetectorInner(final KeyDetector keyDetector) {
mKeyDetector = keyDetector;
mKeyboard = keyDetector.getKeyboard();
- mIsAlphabetKeyboard = mKeyboard.mId.isAlphabetKeyboard();
- mGestureStroke.setGestureSampleLength(mKeyboard.mMostCommonKeyWidth);
+ mGestureStrokeWithPreviewTrail.setGestureSampleLength(mKeyboard.mMostCommonKeyWidth);
final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY);
if (newKey != mCurrentKey) {
if (mDrawingProxy != null) {
@@ -434,11 +418,11 @@ public class PointerTracker implements PointerTrackerQueue.Element {
return mCurrentKey != null && mCurrentKey.isModifier();
}
- public Key getKeyOn(int x, int y) {
+ public Key getKeyOn(final int x, final int y) {
return mKeyDetector.detectHitKey(x, y);
}
- private void setReleasedKeyGraphics(Key key) {
+ private void setReleasedKeyGraphics(final Key key) {
mDrawingProxy.dismissKeyPreview(this);
if (key == null) {
return;
@@ -456,20 +440,20 @@ public class PointerTracker implements PointerTrackerQueue.Element {
}
if (key.altCodeWhileTyping()) {
- final int altCode = key.mAltCode;
+ final int altCode = key.getAltCode();
final Key altKey = mKeyboard.getKey(altCode);
if (altKey != null) {
updateReleaseKeyGraphics(altKey);
}
for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) {
- if (k != key && k.mAltCode == altCode) {
+ if (k != key && k.getAltCode() == altCode) {
updateReleaseKeyGraphics(k);
}
}
}
}
- private void setPressedKeyGraphics(Key key) {
+ private void setPressedKeyGraphics(final Key key) {
if (key == null) {
return;
}
@@ -481,7 +465,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
return;
}
- if (!key.noKeyPreview() && !mInGesture) {
+ if (!key.noKeyPreview() && !sInGesture) {
mDrawingProxy.showKeyPreview(this);
}
updatePressKeyGraphics(key);
@@ -495,33 +479,31 @@ public class PointerTracker implements PointerTrackerQueue.Element {
}
if (key.altCodeWhileTyping() && mTimerProxy.isTypingState()) {
- final int altCode = key.mAltCode;
+ final int altCode = key.getAltCode();
final Key altKey = mKeyboard.getKey(altCode);
if (altKey != null) {
updatePressKeyGraphics(altKey);
}
for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) {
- if (k != key && k.mAltCode == altCode) {
+ if (k != key && k.getAltCode() == altCode) {
updatePressKeyGraphics(k);
}
}
}
}
- private void updateReleaseKeyGraphics(Key key) {
+ private void updateReleaseKeyGraphics(final Key key) {
key.onReleased();
mDrawingProxy.invalidateKey(key);
}
- private void updatePressKeyGraphics(Key key) {
+ private void updatePressKeyGraphics(final Key key) {
key.onPressed();
mDrawingProxy.invalidateKey(key);
}
- public void drawGestureTrail(Canvas canvas, Paint paint) {
- if (mInGesture) {
- mGestureStroke.drawGestureTrail(canvas, paint);
- }
+ public GestureStrokeWithPreviewTrail getGestureStrokeWithPreviewTrail() {
+ return mGestureStrokeWithPreviewTrail;
}
public int getLastX() {
@@ -536,76 +518,91 @@ public class PointerTracker implements PointerTrackerQueue.Element {
return mDownTime;
}
- private Key onDownKey(int x, int y, long eventTime) {
+ private Key onDownKey(final int x, final int y, final long eventTime) {
mDownTime = eventTime;
return onMoveToNewKey(onMoveKeyInternal(x, y), x, y);
}
- private Key onMoveKeyInternal(int x, int y) {
+ private Key onMoveKeyInternal(final int x, final int y) {
mLastX = x;
mLastY = y;
return mKeyDetector.detectHitKey(x, y);
}
- private Key onMoveKey(int x, int y) {
+ private Key onMoveKey(final int x, final int y) {
return onMoveKeyInternal(x, y);
}
- private Key onMoveToNewKey(Key newKey, int x, int y) {
+ private Key onMoveToNewKey(final Key newKey, final int x, final int y) {
mCurrentKey = newKey;
mKeyX = x;
mKeyY = y;
return newKey;
}
+ private static int getActivePointerTrackerCount() {
+ return (sPointerTrackerQueue == null) ? 1 : sPointerTrackerQueue.size();
+ }
+
private void startBatchInput() {
if (DEBUG_LISTENER) {
Log.d(TAG, "onStartBatchInput");
}
- mInGesture = true;
+ sInGesture = true;
mListener.onStartBatchInput();
- }
-
- private void updateBatchInput(InputPointers batchPoints) {
- if (DEBUG_LISTENER) {
- Log.d(TAG, "onUpdateBatchInput: batchPoints=" + batchPoints.getPointerSize());
+ mDrawingProxy.showGesturePreviewTrail(this);
+ }
+
+ private void updateBatchInput(final long eventTime) {
+ synchronized (sAggregratedPointers) {
+ mGestureStrokeWithPreviewTrail.appendIncrementalBatchPoints(sAggregratedPointers);
+ final int size = sAggregratedPointers.getPointerSize();
+ if (size > sLastRecognitionPointSize
+ && eventTime > sLastRecognitionTime + MIN_GESTURE_RECOGNITION_TIME) {
+ sLastRecognitionPointSize = size;
+ sLastRecognitionTime = eventTime;
+ if (DEBUG_LISTENER) {
+ Log.d(TAG, "onUpdateBatchInput: batchPoints=" + size);
+ }
+ mListener.onUpdateBatchInput(sAggregratedPointers);
+ }
}
- mListener.onUpdateBatchInput(batchPoints);
+ mDrawingProxy.showGesturePreviewTrail(this);
}
- private void endBatchInput(InputPointers batchPoints) {
- if (DEBUG_LISTENER) {
- Log.d(TAG, "onEndBatchInput: batchPoints=" + batchPoints.getPointerSize());
+ private void endBatchInput() {
+ synchronized (sAggregratedPointers) {
+ mGestureStrokeWithPreviewTrail.appendAllBatchPoints(sAggregratedPointers);
+ if (getActivePointerTrackerCount() == 1) {
+ if (DEBUG_LISTENER) {
+ Log.d(TAG, "onEndBatchInput: batchPoints="
+ + sAggregratedPointers.getPointerSize());
+ }
+ sInGesture = false;
+ mListener.onEndBatchInput(sAggregratedPointers);
+ clearBatchInputPointsOfAllPointerTrackers();
+ }
}
- mListener.onEndBatchInput(batchPoints);
- clearBatchInputRecognitionStateOfThisPointerTracker();
- clearBatchInputPointsOfAllPointerTrackers();
+ mDrawingProxy.showGesturePreviewTrail(this);
}
- private void abortBatchInput() {
- clearBatchInputRecognitionStateOfThisPointerTracker();
+ private static void abortBatchInput() {
clearBatchInputPointsOfAllPointerTrackers();
}
- private void clearBatchInputRecognitionStateOfThisPointerTracker() {
- mIsPossibleGesture = false;
- mInGesture = false;
- mLastRecognitionPointSize = 0;
- mLastRecognitionTime = 0;
- }
-
- private boolean updateBatchInputRecognitionState(long eventTime, int size) {
- if (size > mLastRecognitionPointSize
- && eventTime > mLastRecognitionTime + MIN_GESTURE_RECOGNITION_TIME) {
- mLastRecognitionPointSize = size;
- mLastRecognitionTime = eventTime;
- return true;
+ private static void clearBatchInputPointsOfAllPointerTrackers() {
+ final int trackersSize = sTrackers.size();
+ for (int i = 0; i < trackersSize; ++i) {
+ final PointerTracker tracker = sTrackers.get(i);
+ tracker.mGestureStrokeWithPreviewTrail.reset();
}
- return false;
+ sAggregratedPointers.reset();
+ sLastRecognitionPointSize = 0;
+ sLastRecognitionTime = 0;
}
- public void processMotionEvent(int action, int x, int y, long eventTime,
- KeyEventHandler handler) {
+ public void processMotionEvent(final int action, final int x, final int y, final long eventTime,
+ final KeyEventHandler handler) {
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
@@ -624,9 +621,11 @@ public class PointerTracker implements PointerTrackerQueue.Element {
}
}
- public void onDownEvent(int x, int y, long eventTime, KeyEventHandler handler) {
- if (DEBUG_EVENT)
+ public void onDownEvent(final int x, final int y, final long eventTime,
+ final KeyEventHandler handler) {
+ if (DEBUG_EVENT) {
printTouchEvent("onDownEvent:", x, y, eventTime);
+ }
mDrawingProxy = handler.getDrawingProxy();
mTimerProxy = handler.getTimerProxy();
@@ -638,7 +637,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
final int dx = x - mLastX;
final int dy = y - mLastY;
final int distanceSquared = (dx * dx + dy * dy);
- if (distanceSquared < sTouchNoiseThresholdDistanceSquared) {
+ if (distanceSquared < sParams.mTouchNoiseThresholdDistanceSquared) {
if (DEBUG_MODE)
Log.w(TAG, "onDownEvent: ignore potential noise: time=" + deltaT
+ " distance=" + distanceSquared);
@@ -650,8 +649,8 @@ public class PointerTracker implements PointerTrackerQueue.Element {
}
}
- final PointerTrackerQueue queue = sPointerTrackerQueue;
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
@@ -661,20 +660,30 @@ public class PointerTracker implements PointerTrackerQueue.Element {
queue.add(this);
}
onDownEventInternal(x, y, eventTime);
- if (queue != null && queue.size() == 1) {
- mIsPossibleGesture = false;
+ if (!sShouldHandleGesture) {
+ return;
+ }
+ final int activePointerTrackerCount = getActivePointerTrackerCount();
+ if (activePointerTrackerCount == 1) {
+ mIsDetectingGesture = false;
// A gesture should start only from the letter key.
- if (sShouldHandleGesture && mIsAlphabetKeyboard && !mIsShowingMoreKeysPanel
- && key != null && Keyboard.isLetterCode(key.mCode)) {
- mIsPossibleGesture = true;
- // TODO: pointer times should be relative to first down even in entire batch input
- // instead of resetting to 0 for each new down event.
- mGestureStroke.addPoint(x, y, 0, false);
+ final boolean isAlphabetKeyboard = (mKeyboard != null)
+ && mKeyboard.mId.isAlphabetKeyboard();
+ if (isAlphabetKeyboard && !mIsShowingMoreKeysPanel && key != null
+ && Keyboard.isLetterCode(key.mCode)) {
+ mIsDetectingGesture = true;
+ sGestureFirstDownTime = eventTime;
+ mGestureStrokeWithPreviewTrail.addPoint(x, y, 0, false /* isHistorical */);
}
+ } else if (sInGesture && activePointerTrackerCount > 1) {
+ mIsDetectingGesture = true;
+ final int elapsedTimeFromFirstDown = (int)(eventTime - sGestureFirstDownTime);
+ mGestureStrokeWithPreviewTrail.addPoint(x, y, elapsedTimeFromFirstDown,
+ false /* isHistorical */);
}
}
- private void onDownEventInternal(int x, int y, long eventTime) {
+ 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
// from modifier key, or 3) this pointer's KeyDetector always allows sliding input.
@@ -699,40 +708,38 @@ public class PointerTracker implements PointerTrackerQueue.Element {
}
}
- private void startSlidingKeyInput(Key key) {
+ private void startSlidingKeyInput(final Key key) {
if (!mIsInSlidingKeyInput) {
mIgnoreModifierKey = key.isModifier();
}
mIsInSlidingKeyInput = true;
}
- private void onGestureMoveEvent(PointerTracker tracker, int x, int y, long eventTime,
- boolean isHistorical, Key key) {
- final int gestureTime = (int)(eventTime - tracker.getDownTime());
- if (sShouldHandleGesture && mIsPossibleGesture) {
- final GestureStroke stroke = mGestureStroke;
+ private void onGestureMoveEvent(final int x, final int y, final long eventTime,
+ final boolean isHistorical, final Key key) {
+ final int gestureTime = (int)(eventTime - sGestureFirstDownTime);
+ if (mIsDetectingGesture) {
+ final GestureStroke stroke = mGestureStrokeWithPreviewTrail;
stroke.addPoint(x, y, gestureTime, isHistorical);
- if (!mInGesture && stroke.isStartOfAGesture()) {
+ if (!sInGesture && stroke.isStartOfAGesture()) {
startBatchInput();
}
- }
- if (key != null && mInGesture) {
- final InputPointers batchPoints = getIncrementalBatchPoints();
- mDrawingProxy.showGestureTrail(this);
- if (updateBatchInputRecognitionState(eventTime, batchPoints.getPointerSize())) {
- updateBatchInput(batchPoints);
+ if (sInGesture && key != null) {
+ updateBatchInput(eventTime);
}
}
}
- public void onMoveEvent(int x, int y, long eventTime, MotionEvent me) {
- if (DEBUG_MOVE_EVENT)
+ public void onMoveEvent(final int x, final int y, final long eventTime, final MotionEvent me) {
+ if (DEBUG_MOVE_EVENT) {
printTouchEvent("onMoveEvent:", x, y, eventTime);
- if (mKeyAlreadyProcessed)
+ }
+ if (mKeyAlreadyProcessed) {
return;
+ }
- if (me != null) {
+ if (sShouldHandleGesture && me != null) {
// Add historical points to gesture path.
final int pointerIndex = me.findPointerIndex(mPointerId);
final int historicalSize = me.getHistorySize();
@@ -740,24 +747,31 @@ public class PointerTracker implements PointerTrackerQueue.Element {
final int historicalX = (int)me.getHistoricalX(pointerIndex, h);
final int historicalY = (int)me.getHistoricalY(pointerIndex, h);
final long historicalTime = me.getHistoricalEventTime(h);
- onGestureMoveEvent(this, historicalX, historicalY, historicalTime,
+ onGestureMoveEvent(historicalX, historicalY, historicalTime,
true /* isHistorical */, null);
}
}
+ onMoveEventInternal(x, y, eventTime);
+ }
+
+ 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);
- // Register move event on gesture tracker.
- onGestureMoveEvent(this, x, y, eventTime, false /* isHistorical */, key);
- if (mInGesture) {
- mIgnoreModifierKey = true;
- mTimerProxy.cancelLongPressTimer();
- mIsInSlidingKeyInput = true;
- mCurrentKey = null;
- setReleasedKeyGraphics(oldKey);
+ if (sShouldHandleGesture) {
+ // Register move event on gesture tracker.
+ onGestureMoveEvent(x, y, eventTime, false /* isHistorical */, key);
+ if (sInGesture) {
+ mIgnoreModifierKey = true;
+ mTimerProxy.cancelLongPressTimer();
+ mIsInSlidingKeyInput = true;
+ mCurrentKey = null;
+ setReleasedKeyGraphics(oldKey);
+ return;
+ }
}
if (key != null) {
@@ -802,7 +816,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
// TODO: Should find a way to balance gesture detection and this hack.
if (sNeedsPhantomSuddenMoveEventHack
&& lastMoveSquared >= mKeyQuarterWidthSquared
- && !mIsPossibleGesture) {
+ && !mIsDetectingGesture) {
if (DEBUG_MODE) {
Log.w(TAG, String.format("onMoveEvent:"
+ " phantom sudden move event is translated to "
@@ -820,11 +834,11 @@ public class PointerTracker implements PointerTrackerQueue.Element {
// 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 (me != null && me.getPointerCount() > 1
+ if (getActivePointerTrackerCount() > 1 && sPointerTrackerQueue != null
&& !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) {
onUpEventInternal();
}
- if (!mIsPossibleGesture) {
+ if (!mIsDetectingGesture) {
mKeyAlreadyProcessed = true;
}
setReleasedKeyGraphics(oldKey);
@@ -842,7 +856,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
if (mIsAllowedSlidingKeyInput) {
onMoveToNewKey(key, x, y);
} else {
- if (!mIsPossibleGesture) {
+ if (!mIsDetectingGesture) {
mKeyAlreadyProcessed = true;
}
}
@@ -850,13 +864,14 @@ public class PointerTracker implements PointerTrackerQueue.Element {
}
}
- public void onUpEvent(int x, int y, long eventTime) {
- if (DEBUG_EVENT)
+ public void onUpEvent(final int x, final int y, final long eventTime) {
+ if (DEBUG_EVENT) {
printTouchEvent("onUpEvent :", x, y, eventTime);
+ }
final PointerTrackerQueue queue = sPointerTrackerQueue;
if (queue != null) {
- if (!mInGesture) {
+ if (!sInGesture) {
if (mCurrentKey != null && mCurrentKey.isModifier()) {
// Before processing an up event of modifier key, all pointers already being
// tracked should be released.
@@ -865,18 +880,21 @@ public class PointerTracker implements PointerTrackerQueue.Element {
queue.releaseAllPointersOlderThan(this, eventTime);
}
}
- queue.remove(this);
}
onUpEventInternal();
+ if (queue != null) {
+ queue.remove(this);
+ }
}
// Let this pointer tracker know that one of newer-than-this pointer trackers got an up event.
// This pointer tracker needs to keep the key top graphics "pressed", but needs to get a
// "virtual" up event.
@Override
- public void onPhantomUpEvent(long eventTime) {
- if (DEBUG_EVENT)
+ public void onPhantomUpEvent(final long eventTime) {
+ if (DEBUG_EVENT) {
printTouchEvent("onPhntEvent:", getLastX(), getLastY(), eventTime);
+ }
onUpEventInternal();
mKeyAlreadyProcessed = true;
}
@@ -884,37 +902,35 @@ public class PointerTracker implements PointerTrackerQueue.Element {
private void onUpEventInternal() {
mTimerProxy.cancelKeyTimers();
mIsInSlidingKeyInput = false;
- mIsPossibleGesture = false;
+ mIsDetectingGesture = false;
+ final Key currentKey = mCurrentKey;
+ mCurrentKey = null;
// Release the last pressed key.
- setReleasedKeyGraphics(mCurrentKey);
+ setReleasedKeyGraphics(currentKey);
if (mIsShowingMoreKeysPanel) {
mDrawingProxy.dismissMoreKeysPanel();
mIsShowingMoreKeysPanel = false;
}
- if (mInGesture) {
- // Register up event on gesture tracker.
- // TODO: Figure out how to deal with multiple fingers that are in gesture, sliding,
- // and/or tapping mode?
- endBatchInput(getAllBatchPoints());
- if (mCurrentKey != null) {
- callListenerOnRelease(mCurrentKey, mCurrentKey.mCode, true);
- mCurrentKey = null;
+ if (sInGesture) {
+ if (currentKey != null) {
+ callListenerOnRelease(currentKey, currentKey.mCode, true);
}
- mDrawingProxy.showGestureTrail(this);
+ endBatchInput();
return;
}
- // This event will be recognized as a regular code input. Clear unused batch points so they
- // are not mistakenly included in the next batch event.
+ // 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)
+ if (mKeyAlreadyProcessed) {
return;
- if (mCurrentKey != null && !mCurrentKey.isRepeatable()) {
- detectAndSendKey(mCurrentKey, mKeyX, mKeyY);
+ }
+ if (currentKey != null && !currentKey.isRepeatable()) {
+ detectAndSendKey(currentKey, mKeyX, mKeyY);
}
}
- public void onShowMoreKeysPanel(int x, int y, KeyEventHandler handler) {
+ public void onShowMoreKeysPanel(final int x, final int y, final KeyEventHandler handler) {
abortBatchInput();
onLongPressed();
mIsShowingMoreKeysPanel = true;
@@ -930,9 +946,10 @@ public class PointerTracker implements PointerTrackerQueue.Element {
}
}
- public void onCancelEvent(int x, int y, long eventTime) {
- if (DEBUG_EVENT)
+ public void onCancelEvent(final int x, final int y, final long eventTime) {
+ if (DEBUG_EVENT) {
printTouchEvent("onCancelEvt:", x, y, eventTime);
+ }
final PointerTrackerQueue queue = sPointerTrackerQueue;
if (queue != null) {
@@ -952,24 +969,25 @@ public class PointerTracker implements PointerTrackerQueue.Element {
}
}
- private void startRepeatKey(Key key) {
- if (key != null && key.isRepeatable() && !mInGesture) {
+ private void startRepeatKey(final Key key) {
+ if (key != null && key.isRepeatable() && !sInGesture) {
onRegisterKey(key);
mTimerProxy.startKeyRepeatTimer(this);
}
}
- public void onRegisterKey(Key key) {
+ public void onRegisterKey(final Key key) {
if (key != null) {
detectAndSendKey(key, key.mX, key.mY);
mTimerProxy.startTypingStateTimer(key);
}
}
- private boolean isMajorEnoughMoveToBeOnNewKey(int x, int y, Key newKey) {
- if (mKeyDetector == null)
+ private boolean isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final Key newKey) {
+ if (mKeyDetector == null) {
throw new NullPointerException("keyboard and/or key detector not set");
- Key curKey = mCurrentKey;
+ }
+ final Key curKey = mCurrentKey;
if (newKey == curKey) {
return false;
} else if (curKey != null) {
@@ -980,24 +998,25 @@ public class PointerTracker implements PointerTrackerQueue.Element {
}
}
- private void startLongPressTimer(Key key) {
- if (key != null && key.isLongPressEnabled() && !mInGesture) {
+ private void startLongPressTimer(final Key key) {
+ if (key != null && key.isLongPressEnabled() && !sInGesture) {
mTimerProxy.startLongPressTimer(this);
}
}
- private void detectAndSendKey(Key key, int x, int y) {
+ private void detectAndSendKey(final Key key, final int x, final int y) {
if (key == null) {
callListenerOnCancelInput();
return;
}
- int code = key.mCode;
+ final int code = key.mCode;
callListenerOnCodeInput(key, code, x, y);
callListenerOnRelease(key, code, false);
}
- private void printTouchEvent(String title, int x, int y, long eventTime) {
+ private void printTouchEvent(final String title, final int x, final int y,
+ 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,
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index ac0a56ba3..e1b082c16 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -19,7 +19,8 @@ package com.android.inputmethod.keyboard;
import android.graphics.Rect;
import android.text.TextUtils;
-import com.android.inputmethod.keyboard.Keyboard.Params.TouchPositionCorrection;
+import com.android.inputmethod.keyboard.internal.TouchPositionCorrection;
+import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.JniUtils;
import java.util.Arrays;
@@ -47,9 +48,10 @@ public class ProximityInfo {
private final Key[][] mGridNeighbors;
private final String mLocaleStr;
- ProximityInfo(String localeStr, int gridWidth, int gridHeight, int minWidth, int height,
- int mostCommonKeyWidth, int mostCommonKeyHeight, final Key[] keys,
- TouchPositionCorrection touchPositionCorrection) {
+ ProximityInfo(final String localeStr, final int gridWidth, final int gridHeight,
+ final int minWidth, final int height, final int mostCommonKeyWidth,
+ final int mostCommonKeyHeight, final Key[] keys,
+ final TouchPositionCorrection touchPositionCorrection) {
if (TextUtils.isEmpty(localeStr)) {
mLocaleStr = "";
} else {
@@ -80,7 +82,7 @@ public class ProximityInfo {
}
public static ProximityInfo createSpellCheckerProximityInfo(final int[] proximity,
- int rowSize, int gridWidth, int gridHeight) {
+ final int rowSize, final int gridWidth, final int gridHeight) {
final ProximityInfo spellCheckerProximityInfo = createDummyProximityInfo();
spellCheckerProximityInfo.mNativeProximityInfo =
spellCheckerProximityInfo.setProximityInfoNative("",
@@ -111,7 +113,7 @@ public class ProximityInfo {
final Key[] keys = mKeys;
final TouchPositionCorrection touchPositionCorrection = mTouchPositionCorrection;
final int[] proximityCharsArray = new int[mGridSize * MAX_PROXIMITY_CHARS_SIZE];
- Arrays.fill(proximityCharsArray, KeyDetector.NOT_A_CODE);
+ Arrays.fill(proximityCharsArray, Constants.NOT_A_CODE);
for (int i = 0; i < mGridSize; ++i) {
final int proximityCharsLength = gridNeighborKeys[i].length;
for (int j = 0; j < proximityCharsLength; ++j) {
@@ -234,7 +236,7 @@ public class ProximityInfo {
dest[index++] = code;
}
if (index < destLength) {
- dest[index] = KeyDetector.NOT_A_CODE;
+ dest[index] = Constants.NOT_A_CODE;
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/ViewLayoutUtils.java b/java/src/com/android/inputmethod/keyboard/ViewLayoutUtils.java
index ee5047083..dc12fa468 100644
--- a/java/src/com/android/inputmethod/keyboard/ViewLayoutUtils.java
+++ b/java/src/com/android/inputmethod/keyboard/ViewLayoutUtils.java
@@ -22,7 +22,7 @@ import android.view.ViewGroup.MarginLayoutParams;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
-public class ViewLayoutUtils {
+public final class ViewLayoutUtils {
private ViewLayoutUtils() {
// This utility class is not publicly instantiable.
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
new file mode 100644
index 000000000..e814d8009
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.SystemClock;
+
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.ResizableIntArray;
+
+class GesturePreviewTrail {
+ private static final int DEFAULT_CAPACITY = GestureStrokeWithPreviewTrail.PREVIEW_CAPACITY;
+
+ private final GesturePreviewTrailParams mPreviewParams;
+ private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
+ private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
+ private final ResizableIntArray mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY);
+ private int mCurrentStrokeId = -1;
+ private long mCurrentDownTime;
+ private int mTrailStartIndex;
+
+ // Use this value as imaginary zero because x-coordinates may be zero.
+ private static final int DOWN_EVENT_MARKER = -128;
+
+ static class GesturePreviewTrailParams {
+ public final int mFadeoutStartDelay;
+ public final int mFadeoutDuration;
+ public final int mUpdateInterval;
+
+ public GesturePreviewTrailParams(final TypedArray keyboardViewAttr) {
+ mFadeoutStartDelay = keyboardViewAttr.getInt(
+ R.styleable.KeyboardView_gesturePreviewTrailFadeoutStartDelay, 0);
+ mFadeoutDuration = keyboardViewAttr.getInt(
+ R.styleable.KeyboardView_gesturePreviewTrailFadeoutDuration, 0);
+ mUpdateInterval = keyboardViewAttr.getInt(
+ R.styleable.KeyboardView_gesturePreviewTrailUpdateInterval, 0);
+ }
+ }
+
+ public GesturePreviewTrail(final GesturePreviewTrailParams params) {
+ mPreviewParams = params;
+ }
+
+ private static int markAsDownEvent(final int xCoord) {
+ return DOWN_EVENT_MARKER - xCoord;
+ }
+
+ private static boolean isDownEventXCoord(final int xCoordOrMark) {
+ return xCoordOrMark <= DOWN_EVENT_MARKER;
+ }
+
+ private static int getXCoordValue(final int xCoordOrMark) {
+ return isDownEventXCoord(xCoordOrMark)
+ ? DOWN_EVENT_MARKER - xCoordOrMark : xCoordOrMark;
+ }
+
+ public void addStroke(final GestureStrokeWithPreviewTrail stroke, final long downTime) {
+ final int strokeId = stroke.getGestureStrokeId();
+ final boolean isNewStroke = strokeId != mCurrentStrokeId;
+ final int trailSize = mEventTimes.getLength();
+ stroke.appendPreviewStroke(mEventTimes, mXCoordinates, mYCoordinates);
+ final int newTrailSize = mEventTimes.getLength();
+ if (stroke.getGestureStrokePreviewSize() == 0) {
+ return;
+ }
+ if (isNewStroke) {
+ final int elapsedTime = (int)(downTime - mCurrentDownTime);
+ final int[] eventTimes = mEventTimes.getPrimitiveArray();
+ for (int i = mTrailStartIndex; i < trailSize; i++) {
+ eventTimes[i] -= elapsedTime;
+ }
+
+ if (newTrailSize > trailSize) {
+ final int[] xCoords = mXCoordinates.getPrimitiveArray();
+ xCoords[trailSize] = markAsDownEvent(xCoords[trailSize]);
+ }
+ mCurrentDownTime = downTime;
+ mCurrentStrokeId = strokeId;
+ }
+ }
+
+ private int getAlpha(final int elapsedTime) {
+ if (elapsedTime < mPreviewParams.mFadeoutStartDelay) {
+ return Constants.Color.ALPHA_OPAQUE;
+ }
+ final int decreasingAlpha = Constants.Color.ALPHA_OPAQUE
+ * (elapsedTime - mPreviewParams.mFadeoutStartDelay)
+ / mPreviewParams.mFadeoutDuration;
+ return Constants.Color.ALPHA_OPAQUE - decreasingAlpha;
+ }
+
+ /**
+ * Draw gesture preview trail
+ * @param canvas The canvas to draw the gesture preview trail
+ * @param paint The paint object to be used to draw the gesture preview trail
+ * @return true if some gesture preview trails remain to be drawn
+ */
+ public boolean drawGestureTrail(final Canvas canvas, final Paint paint) {
+ final int trailSize = mEventTimes.getLength();
+ if (trailSize == 0) {
+ return false;
+ }
+
+ final int[] eventTimes = mEventTimes.getPrimitiveArray();
+ final int[] xCoords = mXCoordinates.getPrimitiveArray();
+ final int[] yCoords = mYCoordinates.getPrimitiveArray();
+ final int sinceDown = (int)(SystemClock.uptimeMillis() - mCurrentDownTime);
+ final int lingeringDuration = mPreviewParams.mFadeoutStartDelay
+ + mPreviewParams.mFadeoutDuration;
+ int startIndex;
+ for (startIndex = mTrailStartIndex; startIndex < trailSize; startIndex++) {
+ final int elapsedTime = sinceDown - eventTimes[startIndex];
+ // Skip too old trail points.
+ if (elapsedTime < lingeringDuration) {
+ break;
+ }
+ }
+ mTrailStartIndex = startIndex;
+
+ if (startIndex < trailSize) {
+ int lastX = getXCoordValue(xCoords[startIndex]);
+ int lastY = yCoords[startIndex];
+ for (int i = startIndex + 1; i < trailSize - 1; i++) {
+ final int x = xCoords[i];
+ final int y = yCoords[i];
+ final int elapsedTime = sinceDown - eventTimes[i];
+ // Draw trail line only when the current point isn't a down point.
+ if (!isDownEventXCoord(x)) {
+ paint.setAlpha(getAlpha(elapsedTime));
+ canvas.drawLine(lastX, lastY, x, y, paint);
+ }
+ lastX = getXCoordValue(x);
+ lastY = y;
+ }
+ }
+
+ final int newSize = trailSize - startIndex;
+ if (newSize < startIndex) {
+ mTrailStartIndex = 0;
+ if (newSize > 0) {
+ System.arraycopy(eventTimes, startIndex, eventTimes, 0, newSize);
+ System.arraycopy(xCoords, startIndex, xCoords, 0, newSize);
+ System.arraycopy(yCoords, startIndex, yCoords, 0, newSize);
+ }
+ mEventTimes.setLength(newSize);
+ mXCoordinates.setLength(newSize);
+ mYCoordinates.setLength(newSize);
+ }
+ return newSize > 0;
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
index 79e977a40..825134468 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
@@ -14,10 +14,6 @@
package com.android.inputmethod.keyboard.internal;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-
-import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.InputPointers;
import com.android.inputmethod.latin.ResizableIntArray;
@@ -48,13 +44,8 @@ public class GestureStroke {
private static final float DOUBLE_PI = (float)(2.0f * Math.PI);
- // Fade based on number of gesture samples, see MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT
- private static final int DRAWING_GESTURE_FADE_START = 10;
- private static final int DRAWING_GESTURE_FADE_RATE = 6;
-
- public GestureStroke(int pointerId) {
+ public GestureStroke(final int pointerId) {
mPointerId = pointerId;
- reset();
}
public void setGestureSampleLength(final int keyWidth) {
@@ -139,8 +130,12 @@ public class GestureStroke {
}
private void appendBatchPoints(final InputPointers out, final int size) {
+ final int length = size - mLastIncrementalBatchSize;
+ if (length <= 0) {
+ return;
+ }
out.append(mPointerId, mEventTimes, mXCoordinates, mYCoordinates,
- mLastIncrementalBatchSize, size - mLastIncrementalBatchSize);
+ mLastIncrementalBatchSize, length);
mLastIncrementalBatchSize = size;
}
@@ -158,7 +153,7 @@ public class GestureStroke {
if (dx == 0 && dy == 0) return 0;
// Would it be faster to call atan2f() directly via JNI? Not sure about what the JIT
// does with Math.atan2().
- return (float)Math.atan2((double)dy, (double)dx);
+ return (float)Math.atan2(dy, dx);
}
private static float getAngleDiff(final float a1, final float a2) {
@@ -168,20 +163,4 @@ public class GestureStroke {
}
return diff;
}
-
- public void drawGestureTrail(final Canvas canvas, final Paint paint) {
- // TODO: These paint parameter interpolation should be tunable, possibly introduce an object
- // that implements an interface such as Paint getPaint(int step, int strokePoints)
- final int size = mXCoordinates.getLength();
- final int[] xCoords = mXCoordinates.getPrimitiveArray();
- final int[] yCoords = mYCoordinates.getPrimitiveArray();
- int alpha = Constants.Color.ALPHA_OPAQUE;
- for (int i = size - 1; i > 0 && alpha > 0; i--) {
- paint.setAlpha(alpha);
- if (size - i > DRAWING_GESTURE_FADE_START) {
- alpha -= DRAWING_GESTURE_FADE_RATE;
- }
- canvas.drawLine(xCoords[i - 1], yCoords[i - 1], xCoords[i], yCoords[i], paint);
- }
- }
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewTrail.java
new file mode 100644
index 000000000..6c1a9bc01
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewTrail.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import com.android.inputmethod.latin.ResizableIntArray;
+
+public class GestureStrokeWithPreviewTrail extends GestureStroke {
+ public static final int PREVIEW_CAPACITY = 256;
+
+ private final ResizableIntArray mPreviewEventTimes = new ResizableIntArray(PREVIEW_CAPACITY);
+ private final ResizableIntArray mPreviewXCoordinates = new ResizableIntArray(PREVIEW_CAPACITY);
+ private final ResizableIntArray mPreviewYCoordinates = new ResizableIntArray(PREVIEW_CAPACITY);
+
+ private int mStrokeId;
+ private int mLastPreviewSize;
+
+ public GestureStrokeWithPreviewTrail(final int pointerId) {
+ super(pointerId);
+ }
+
+ @Override
+ public void reset() {
+ super.reset();
+ mStrokeId++;
+ mLastPreviewSize = 0;
+ mPreviewEventTimes.setLength(0);
+ mPreviewXCoordinates.setLength(0);
+ mPreviewYCoordinates.setLength(0);
+ }
+
+ public int getGestureStrokeId() {
+ return mStrokeId;
+ }
+
+ public int getGestureStrokePreviewSize() {
+ return mPreviewEventTimes.getLength();
+ }
+
+ @Override
+ public void addPoint(final int x, final int y, final int time, final boolean isHistorical) {
+ super.addPoint(x, y, time, isHistorical);
+ mPreviewEventTimes.add(time);
+ mPreviewXCoordinates.add(x);
+ mPreviewYCoordinates.add(y);
+ }
+
+ public void appendPreviewStroke(final ResizableIntArray eventTimes,
+ final ResizableIntArray xCoords, final ResizableIntArray yCoords) {
+ final int length = mPreviewEventTimes.getLength() - mLastPreviewSize;
+ if (length <= 0) {
+ return;
+ }
+ eventTimes.append(mPreviewEventTimes, mLastPreviewSize, length);
+ xCoords.append(mPreviewXCoordinates, mLastPreviewSize, length);
+ yCoords.append(mPreviewYCoordinates, mLastPreviewSize, length);
+ mLastPreviewSize = mPreviewEventTimes.getLength();
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java
new file mode 100644
index 000000000..203bab6ff
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.graphics.Typeface;
+
+import com.android.inputmethod.latin.ResourceUtils;
+
+public class KeyDrawParams {
+ public Typeface mTypeface;
+
+ public int mLetterSize;
+ public int mLabelSize;
+ public int mLargeLetterSize;
+ public int mLargeLabelSize;
+ public int mHintLetterSize;
+ public int mShiftedLetterHintSize;
+ public int mHintLabelSize;
+ public int mPreviewTextSize;
+
+ public int mTextColor;
+ public int mTextInactivatedColor;
+ public int mTextShadowColor;
+ public int mHintLetterColor;
+ public int mHintLabelColor;
+ public int mShiftedLetterHintInactivatedColor;
+ public int mShiftedLetterHintActivatedColor;
+ public int mPreviewTextColor;
+
+ public int mAnimAlpha;
+
+ public KeyDrawParams() {}
+
+ private KeyDrawParams(final KeyDrawParams copyFrom) {
+ mTypeface = copyFrom.mTypeface;
+
+ mLetterSize = copyFrom.mLetterSize;
+ mLabelSize = copyFrom.mLabelSize;
+ mLargeLetterSize = copyFrom.mLargeLetterSize;
+ mLargeLabelSize = copyFrom.mLargeLabelSize;
+ mHintLetterSize = copyFrom.mHintLetterSize;
+ mShiftedLetterHintSize = copyFrom.mShiftedLetterHintSize;
+ mHintLabelSize = copyFrom.mHintLabelSize;
+ mPreviewTextSize = copyFrom.mPreviewTextSize;
+
+ mTextColor = copyFrom.mTextColor;
+ mTextInactivatedColor = copyFrom.mTextInactivatedColor;
+ mTextShadowColor = copyFrom.mTextShadowColor;
+ mHintLetterColor = copyFrom.mHintLetterColor;
+ mHintLabelColor = copyFrom.mHintLabelColor;
+ mShiftedLetterHintInactivatedColor = copyFrom.mShiftedLetterHintInactivatedColor;
+ mShiftedLetterHintActivatedColor = copyFrom.mShiftedLetterHintActivatedColor;
+ mPreviewTextColor = copyFrom.mPreviewTextColor;
+
+ mAnimAlpha = copyFrom.mAnimAlpha;
+ }
+
+ public void updateParams(final int keyHeight, final KeyVisualAttributes attr) {
+ if (attr == null) {
+ return;
+ }
+
+ if (attr.mTypeface != null) {
+ mTypeface = attr.mTypeface;
+ }
+
+ mLetterSize = selectTextSizeFromDimensionOrRatio(keyHeight,
+ attr.mLetterSize, attr.mLetterRatio, mLetterSize);
+ mLabelSize = selectTextSizeFromDimensionOrRatio(keyHeight,
+ attr.mLabelSize, attr.mLabelRatio, mLabelSize);
+ mLargeLabelSize = selectTextSize(keyHeight, attr.mLargeLabelRatio, mLargeLabelSize);
+ mLargeLetterSize = selectTextSize(keyHeight, attr.mLargeLetterRatio, mLargeLetterSize);
+ mHintLetterSize = selectTextSize(keyHeight, attr.mHintLetterRatio, mHintLetterSize);
+ mShiftedLetterHintSize = selectTextSize(keyHeight,
+ attr.mShiftedLetterHintRatio, mShiftedLetterHintSize);
+ mHintLabelSize = selectTextSize(keyHeight, attr.mHintLabelRatio, mHintLabelSize);
+ mPreviewTextSize = selectTextSize(keyHeight, attr.mPreviewTextRatio, mPreviewTextSize);
+
+ mTextColor = selectColor(attr.mTextColor, mTextColor);
+ mTextInactivatedColor = selectColor(attr.mTextInactivatedColor, mTextInactivatedColor);
+ mTextShadowColor = selectColor(attr.mTextShadowColor, mTextShadowColor);
+ mHintLetterColor = selectColor(attr.mHintLetterColor, mHintLetterColor);
+ mHintLabelColor = selectColor(attr.mHintLabelColor, mHintLabelColor);
+ mShiftedLetterHintInactivatedColor = selectColor(
+ attr.mShiftedLetterHintInactivatedColor, mShiftedLetterHintInactivatedColor);
+ mShiftedLetterHintActivatedColor = selectColor(
+ attr.mShiftedLetterHintActivatedColor, mShiftedLetterHintActivatedColor);
+ mPreviewTextColor = selectColor(attr.mPreviewTextColor, mPreviewTextColor);
+ }
+
+ public KeyDrawParams mayCloneAndUpdateParams(final int keyHeight,
+ final KeyVisualAttributes attr) {
+ if (attr == null) {
+ return this;
+ }
+ final KeyDrawParams newParams = new KeyDrawParams(this);
+ newParams.updateParams(keyHeight, attr);
+ return newParams;
+ }
+
+ private static final int selectTextSizeFromDimensionOrRatio(final int keyHeight,
+ final int dimens, final float ratio, final int defaultDimens) {
+ if (ResourceUtils.isValidDimensionPixelSize(dimens)) {
+ return dimens;
+ }
+ if (ResourceUtils.isValidFraction(ratio)) {
+ return (int)(keyHeight * ratio);
+ }
+ return defaultDimens;
+ }
+
+ private static final int selectTextSize(final int keyHeight, final float ratio,
+ final int defaultSize) {
+ if (ResourceUtils.isValidFraction(ratio)) {
+ return (int)(keyHeight * ratio);
+ }
+ return defaultSize;
+ }
+
+ private static final int selectColor(final int attrColor, final int defaultColor) {
+ if (attrColor != 0) {
+ return attrColor;
+ }
+ return defaultColor;
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java
new file mode 100644
index 000000000..996a722c0
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+public class KeyPreviewDrawParams {
+ // The graphical geometry of the key preview.
+ // <-width->
+ // +-------+ ^
+ // | | |
+ // |preview| height (visible)
+ // | | |
+ // + + ^ v
+ // \ / |offset
+ // +-\ /-+ v
+ // | +-+ |
+ // |parent |
+ // | key|
+ // +-------+
+ // The background of a {@link TextView} being used for a key preview may have invisible
+ // paddings. To align the more keys keyboard panel's visible part with the visible part of
+ // the background, we need to record the width and height of key preview that don't include
+ // invisible paddings.
+ public int mPreviewVisibleWidth;
+ public int mPreviewVisibleHeight;
+ // The key preview may have an arbitrary offset and its background that may have a bottom
+ // padding. To align the more keys keyboard and the key preview we also need to record the
+ // offset between the top edge of parent key and the bottom of the visible part of key
+ // preview background.
+ public int mPreviewVisibleOffset;
+
+ public final int[] mCoordinates = new int[2];
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
index 94a7b826f..2a57caa5f 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
@@ -21,6 +21,7 @@ import static com.android.inputmethod.keyboard.Keyboard.CODE_UNSPECIFIED;
import android.text.TextUtils;
import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.StringUtils;
@@ -55,59 +56,20 @@ public class KeySpecParser {
private static final char ESCAPE_CHAR = '\\';
private static final char LABEL_END = '|';
private static final String PREFIX_TEXT = "!text/";
- private static final String PREFIX_ICON = "!icon/";
+ static final String PREFIX_ICON = "!icon/";
private static final String PREFIX_CODE = "!code/";
private static final String PREFIX_HEX = "0x";
private static final String ADDITIONAL_MORE_KEY_MARKER = "%";
- public static class MoreKeySpec {
- public final int mCode;
- public final String mLabel;
- public final String mOutputText;
- public final int mIconId;
-
- public MoreKeySpec(final String moreKeySpec, boolean needsToUpperCase, Locale locale,
- final KeyboardCodesSet codesSet) {
- mLabel = toUpperCaseOfStringForLocale(getLabel(moreKeySpec),
- needsToUpperCase, locale);
- final int code = toUpperCaseOfCodeForLocale(getCode(moreKeySpec, codesSet),
- needsToUpperCase, locale);
- if (code == Keyboard.CODE_UNSPECIFIED) {
- // Some letter, for example German Eszett (U+00DF: "ß"), has multiple characters
- // upper case representation ("SS").
- mCode = Keyboard.CODE_OUTPUT_TEXT;
- mOutputText = mLabel;
- } else {
- mCode = code;
- mOutputText = toUpperCaseOfStringForLocale(getOutputText(moreKeySpec),
- needsToUpperCase, locale);
- }
- mIconId = getIconId(moreKeySpec);
- }
-
- @Override
- public String toString() {
- final String label = (mIconId == KeyboardIconsSet.ICON_UNDEFINED ? mLabel
- : PREFIX_ICON + KeyboardIconsSet.getIconName(mIconId));
- final String output = (mCode == Keyboard.CODE_OUTPUT_TEXT ? mOutputText
- : Keyboard.printableCode(mCode));
- if (StringUtils.codePointCount(label) == 1 && label.codePointAt(0) == mCode) {
- return output;
- } else {
- return label + "|" + output;
- }
- }
- }
-
private KeySpecParser() {
// Intentional empty constructor for utility class.
}
- private static boolean hasIcon(String moreKeySpec) {
+ private static boolean hasIcon(final String moreKeySpec) {
return moreKeySpec.startsWith(PREFIX_ICON);
}
- private static boolean hasCode(String moreKeySpec) {
+ private static boolean hasCode(final String moreKeySpec) {
final int end = indexOfLabelEnd(moreKeySpec, 0);
if (end > 0 && end + 1 < moreKeySpec.length() && moreKeySpec.startsWith(
PREFIX_CODE, end + 1)) {
@@ -116,7 +78,7 @@ public class KeySpecParser {
return false;
}
- private static String parseEscape(String text) {
+ private static String parseEscape(final String text) {
if (text.indexOf(ESCAPE_CHAR) < 0) {
return text;
}
@@ -135,7 +97,7 @@ public class KeySpecParser {
return sb.toString();
}
- private static int indexOfLabelEnd(String moreKeySpec, int start) {
+ private static int indexOfLabelEnd(final String moreKeySpec, final int start) {
if (moreKeySpec.indexOf(ESCAPE_CHAR, start) < 0) {
final int end = moreKeySpec.indexOf(LABEL_END, start);
if (end == 0) {
@@ -156,7 +118,7 @@ public class KeySpecParser {
return -1;
}
- public static String getLabel(String moreKeySpec) {
+ public static String getLabel(final String moreKeySpec) {
if (hasIcon(moreKeySpec)) {
return null;
}
@@ -169,7 +131,7 @@ public class KeySpecParser {
return label;
}
- private static String getOutputTextInternal(String moreKeySpec) {
+ private static String getOutputTextInternal(final String moreKeySpec) {
final int end = indexOfLabelEnd(moreKeySpec, 0);
if (end <= 0) {
return null;
@@ -180,7 +142,7 @@ public class KeySpecParser {
return parseEscape(moreKeySpec.substring(end + /* LABEL_END */1));
}
- static String getOutputText(String moreKeySpec) {
+ static String getOutputText(final String moreKeySpec) {
if (hasCode(moreKeySpec)) {
return null;
}
@@ -204,7 +166,7 @@ public class KeySpecParser {
return (StringUtils.codePointCount(label) == 1) ? null : label;
}
- static int getCode(String moreKeySpec, KeyboardCodesSet codesSet) {
+ static int getCode(final String moreKeySpec, final KeyboardCodesSet codesSet) {
if (hasCode(moreKeySpec)) {
final int end = indexOfLabelEnd(moreKeySpec, 0);
if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) {
@@ -229,7 +191,8 @@ public class KeySpecParser {
return Keyboard.CODE_OUTPUT_TEXT;
}
- public static int parseCode(String text, KeyboardCodesSet codesSet, int defCode) {
+ public static int parseCode(final String text, final KeyboardCodesSet codesSet,
+ final int defCode) {
if (text == null) return defCode;
if (text.startsWith(PREFIX_CODE)) {
return codesSet.getCode(text.substring(PREFIX_CODE.length()));
@@ -240,7 +203,7 @@ public class KeySpecParser {
}
}
- public static int getIconId(String moreKeySpec) {
+ public static int getIconId(final String moreKeySpec) {
if (moreKeySpec != null && hasIcon(moreKeySpec)) {
final int end = moreKeySpec.indexOf(LABEL_END, PREFIX_ICON.length());
final String name = (end < 0) ? moreKeySpec.substring(PREFIX_ICON.length())
@@ -250,7 +213,7 @@ public class KeySpecParser {
return KeyboardIconsSet.ICON_UNDEFINED;
}
- private static <T> ArrayList<T> arrayAsList(T[] array, int start, int end) {
+ private static <T> ArrayList<T> arrayAsList(final T[] array, final int start, final int end) {
if (array == null) {
throw new NullPointerException();
}
@@ -258,7 +221,7 @@ public class KeySpecParser {
throw new IllegalArgumentException();
}
- final ArrayList<T> list = new ArrayList<T>(end - start);
+ final ArrayList<T> list = CollectionUtils.newArrayList(end - start);
for (int i = start; i < end; i++) {
list.add(array[i]);
}
@@ -267,7 +230,7 @@ public class KeySpecParser {
private static final String[] EMPTY_STRING_ARRAY = new String[0];
- private static String[] filterOutEmptyString(String[] array) {
+ private static String[] filterOutEmptyString(final String[] array) {
if (array == null) {
return EMPTY_STRING_ARRAY;
}
@@ -288,8 +251,8 @@ public class KeySpecParser {
return out.toArray(new String[out.size()]);
}
- public static String[] insertAdditionalMoreKeys(String[] moreKeySpecs,
- String[] additionalMoreKeySpecs) {
+ public static String[] insertAdditionalMoreKeys(final String[] moreKeySpecs,
+ final String[] additionalMoreKeySpecs) {
final String[] moreKeys = filterOutEmptyString(moreKeySpecs);
final String[] additionalMoreKeys = filterOutEmptyString(additionalMoreKeySpecs);
final int moreKeysCount = moreKeys.length;
@@ -356,12 +319,13 @@ public class KeySpecParser {
@SuppressWarnings("serial")
public static class KeySpecParserError extends RuntimeException {
- public KeySpecParserError(String message) {
+ public KeySpecParserError(final String message) {
super(message);
}
}
- public static String resolveTextReference(String rawText, KeyboardTextsSet textsSet) {
+ public static String resolveTextReference(final String rawText,
+ final KeyboardTextsSet textsSet) {
int level = 0;
String text = rawText;
StringBuilder sb;
@@ -407,7 +371,7 @@ public class KeySpecParser {
return text;
}
- private static int searchTextNameEnd(String text, int start) {
+ private static int searchTextNameEnd(final String text, final int start) {
final int size = text.length();
for (int pos = start; pos < size; pos++) {
final char c = text.charAt(pos);
@@ -420,7 +384,7 @@ public class KeySpecParser {
return size;
}
- public static String[] parseCsvString(String rawText, KeyboardTextsSet textsSet) {
+ public static String[] parseCsvString(final String rawText, final KeyboardTextsSet textsSet) {
final String text = resolveTextReference(rawText, textsSet);
final int size = text.length();
if (size == 0) {
@@ -438,7 +402,7 @@ public class KeySpecParser {
// Skip empty entry.
if (pos - start > 0) {
if (list == null) {
- list = new ArrayList<String>();
+ list = CollectionUtils.newArrayList();
}
list.add(text.substring(start, pos));
}
@@ -459,7 +423,8 @@ public class KeySpecParser {
return list.toArray(new String[list.size()]);
}
- public static int getIntValue(String[] moreKeys, String key, int defaultValue) {
+ public static int getIntValue(final String[] moreKeys, final String key,
+ final int defaultValue) {
if (moreKeys == null) {
return defaultValue;
}
@@ -485,7 +450,7 @@ public class KeySpecParser {
return value;
}
- public static boolean getBooleanValue(String[] moreKeys, String key) {
+ public static boolean getBooleanValue(final String[] moreKeys, final String key) {
if (moreKeys == null) {
return false;
}
@@ -501,8 +466,8 @@ public class KeySpecParser {
return value;
}
- public static int toUpperCaseOfCodeForLocale(int code, boolean needsToUpperCase,
- Locale locale) {
+ public static int toUpperCaseOfCodeForLocale(final int code, final boolean needsToUpperCase,
+ final Locale locale) {
if (!Keyboard.isLetterCode(code) || !needsToUpperCase) return code;
final String text = new String(new int[] { code } , 0, 1);
final String casedText = KeySpecParser.toUpperCaseOfStringForLocale(
@@ -511,8 +476,8 @@ public class KeySpecParser {
? casedText.codePointAt(0) : CODE_UNSPECIFIED;
}
- public static String toUpperCaseOfStringForLocale(String text, boolean needsToUpperCase,
- Locale locale) {
+ public static String toUpperCaseOfStringForLocale(final String text,
+ final boolean needsToUpperCase, final Locale locale) {
if (text == null || !needsToUpperCase) return text;
return text.toUpperCase(locale);
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java
new file mode 100644
index 000000000..e8cacf9e7
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.content.res.TypedArray;
+
+public abstract class KeyStyle {
+ private final KeyboardTextsSet mTextsSet;
+
+ public abstract String[] getStringArray(TypedArray a, int index);
+ public abstract String getString(TypedArray a, int index);
+ public abstract int getInt(TypedArray a, int index, int defaultValue);
+ public abstract int getFlag(TypedArray a, int index);
+
+ protected KeyStyle(final KeyboardTextsSet textsSet) {
+ mTextsSet = textsSet;
+ }
+
+ protected String parseString(final TypedArray a, final int index) {
+ if (a.hasValue(index)) {
+ return KeySpecParser.resolveTextReference(a.getString(index), mTextsSet);
+ }
+ return null;
+ }
+
+ protected String[] parseStringArray(final TypedArray a, final int index) {
+ if (a.hasValue(index)) {
+ return KeySpecParser.parseCsvString(a.getString(index), mTextsSet);
+ }
+ return null;
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
index 291b3b943..71fd30563 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
@@ -20,7 +20,7 @@ import android.content.res.TypedArray;
import android.util.Log;
import android.util.SparseArray;
-import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.XmlParseUtils;
@@ -29,75 +29,62 @@ import org.xmlpull.v1.XmlPullParserException;
import java.util.HashMap;
-public class KeyStyles {
- private static final String TAG = KeyStyles.class.getSimpleName();
+public class KeyStylesSet {
+ private static final String TAG = KeyStylesSet.class.getSimpleName();
private static final boolean DEBUG = false;
- final HashMap<String, KeyStyle> mStyles = new HashMap<String, KeyStyle>();
+ private final HashMap<String, KeyStyle> mStyles = CollectionUtils.newHashMap();
- final KeyboardTextsSet mTextsSet;
+ private final KeyboardTextsSet mTextsSet;
private final KeyStyle mEmptyKeyStyle;
private static final String EMPTY_STYLE_NAME = "<empty>";
- public KeyStyles(KeyboardTextsSet textsSet) {
+ public KeyStylesSet(final KeyboardTextsSet textsSet) {
mTextsSet = textsSet;
- mEmptyKeyStyle = new EmptyKeyStyle();
+ mEmptyKeyStyle = new EmptyKeyStyle(textsSet);
mStyles.put(EMPTY_STYLE_NAME, mEmptyKeyStyle);
}
- public abstract class KeyStyle {
- public abstract String[] getStringArray(TypedArray a, int index);
- public abstract String getString(TypedArray a, int index);
- public abstract int getInt(TypedArray a, int index, int defaultValue);
- public abstract int getFlag(TypedArray a, int index);
-
- protected String parseString(TypedArray a, int index) {
- if (a.hasValue(index)) {
- return KeySpecParser.resolveTextReference(a.getString(index), mTextsSet);
- }
- return null;
- }
-
- protected String[] parseStringArray(TypedArray a, int index) {
- if (a.hasValue(index)) {
- return KeySpecParser.parseCsvString(a.getString(index), mTextsSet);
- }
- return null;
+ private static class EmptyKeyStyle extends KeyStyle {
+ EmptyKeyStyle(final KeyboardTextsSet textsSet) {
+ super(textsSet);
}
- }
- class EmptyKeyStyle extends KeyStyle {
@Override
- public String[] getStringArray(TypedArray a, int index) {
+ public String[] getStringArray(final TypedArray a, final int index) {
return parseStringArray(a, index);
}
@Override
- public String getString(TypedArray a, int index) {
+ public String getString(final TypedArray a, final int index) {
return parseString(a, index);
}
@Override
- public int getInt(TypedArray a, int index, int defaultValue) {
+ public int getInt(final TypedArray a, final int index, final int defaultValue) {
return a.getInt(index, defaultValue);
}
@Override
- public int getFlag(TypedArray a, int index) {
+ public int getFlag(final TypedArray a, final int index) {
return a.getInt(index, 0);
}
}
- private class DeclaredKeyStyle extends KeyStyle {
+ private static class DeclaredKeyStyle extends KeyStyle {
+ private final HashMap<String, KeyStyle> mStyles;
private final String mParentStyleName;
- private final SparseArray<Object> mStyleAttributes = new SparseArray<Object>();
+ private final SparseArray<Object> mStyleAttributes = CollectionUtils.newSparseArray();
- public DeclaredKeyStyle(String parentStyleName) {
+ public DeclaredKeyStyle(final String parentStyleName, final KeyboardTextsSet textsSet,
+ final HashMap<String, KeyStyle> styles) {
+ super(textsSet);
mParentStyleName = parentStyleName;
+ mStyles = styles;
}
@Override
- public String[] getStringArray(TypedArray a, int index) {
+ public String[] getStringArray(final TypedArray a, final int index) {
if (a.hasValue(index)) {
return parseStringArray(a, index);
}
@@ -110,7 +97,7 @@ public class KeyStyles {
}
@Override
- public String getString(TypedArray a, int index) {
+ public String getString(final TypedArray a, final int index) {
if (a.hasValue(index)) {
return parseString(a, index);
}
@@ -123,7 +110,7 @@ public class KeyStyles {
}
@Override
- public int getInt(TypedArray a, int index, int defaultValue) {
+ public int getInt(final TypedArray a, final int index, final int defaultValue) {
if (a.hasValue(index)) {
return a.getInt(index, defaultValue);
}
@@ -136,7 +123,7 @@ public class KeyStyles {
}
@Override
- public int getFlag(TypedArray a, int index) {
+ public int getFlag(final TypedArray a, final int index) {
int flags = a.getInt(index, 0);
final Object value = mStyleAttributes.get(index);
if (value != null) {
@@ -146,7 +133,7 @@ public class KeyStyles {
return flags | parentStyle.getFlag(a, index);
}
- void readKeyAttributes(TypedArray keyAttr) {
+ public void readKeyAttributes(final TypedArray keyAttr) {
// TODO: Currently not all Key attributes can be declared as style.
readString(keyAttr, R.styleable.Keyboard_Key_code);
readString(keyAttr, R.styleable.Keyboard_Key_altCode);
@@ -164,38 +151,38 @@ public class KeyStyles {
readFlag(keyAttr, R.styleable.Keyboard_Key_keyActionFlags);
}
- private void readString(TypedArray a, int index) {
+ private void readString(final TypedArray a, final int index) {
if (a.hasValue(index)) {
mStyleAttributes.put(index, parseString(a, index));
}
}
- private void readInt(TypedArray a, int index) {
+ private void readInt(final TypedArray a, final int index) {
if (a.hasValue(index)) {
mStyleAttributes.put(index, a.getInt(index, 0));
}
}
- private void readFlag(TypedArray a, int index) {
+ private void readFlag(final TypedArray a, final int index) {
if (a.hasValue(index)) {
final Integer value = (Integer)mStyleAttributes.get(index);
mStyleAttributes.put(index, a.getInt(index, 0) | (value != null ? value : 0));
}
}
- private void readStringArray(TypedArray a, int index) {
+ private void readStringArray(final TypedArray a, final int index) {
if (a.hasValue(index)) {
mStyleAttributes.put(index, parseStringArray(a, index));
}
}
}
- public void parseKeyStyleAttributes(TypedArray keyStyleAttr, TypedArray keyAttrs,
- XmlPullParser parser) throws XmlPullParserException {
+ public void parseKeyStyleAttributes(final TypedArray keyStyleAttr, final TypedArray keyAttrs,
+ final XmlPullParser parser) throws XmlPullParserException {
final String styleName = keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName);
if (DEBUG) {
Log.d(TAG, String.format("<%s styleName=%s />",
- Keyboard.Builder.TAG_KEY_STYLE, styleName));
+ KeyboardBuilder.TAG_KEY_STYLE, styleName));
if (mStyles.containsKey(styleName)) {
Log.d(TAG, "key-style " + styleName + " is overridden at "
+ parser.getPositionDescription());
@@ -210,12 +197,12 @@ public class KeyStyles {
"Unknown parentStyle " + parentStyleName, parser);
}
}
- final DeclaredKeyStyle style = new DeclaredKeyStyle(parentStyleName);
+ final DeclaredKeyStyle style = new DeclaredKeyStyle(parentStyleName, mTextsSet, mStyles);
style.readKeyAttributes(keyAttrs);
mStyles.put(styleName, style);
}
- public KeyStyle getKeyStyle(TypedArray keyAttr, XmlPullParser parser)
+ public KeyStyle getKeyStyle(final TypedArray keyAttr, final XmlPullParser parser)
throws XmlParseUtils.ParseException {
if (!keyAttr.hasValue(R.styleable.Keyboard_Key_keyStyle)) {
return mEmptyKeyStyle;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java b/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java
new file mode 100644
index 000000000..04cc152fe
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.content.res.TypedArray;
+import android.graphics.Typeface;
+import android.util.SparseIntArray;
+
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.ResourceUtils;
+
+public class KeyVisualAttributes {
+ public final Typeface mTypeface;
+
+ public final float mLetterRatio;
+ public final int mLetterSize;
+ public final float mLabelRatio;
+ public final int mLabelSize;
+ public final float mLargeLetterRatio;
+ public final float mLargeLabelRatio;
+ public final float mHintLetterRatio;
+ public final float mShiftedLetterHintRatio;
+ public final float mHintLabelRatio;
+ public final float mPreviewTextRatio;
+
+ public final int mTextColor;
+ public final int mTextInactivatedColor;
+ public final int mTextShadowColor;
+ public final int mHintLetterColor;
+ public final int mHintLabelColor;
+ public final int mShiftedLetterHintInactivatedColor;
+ public final int mShiftedLetterHintActivatedColor;
+ public final int mPreviewTextColor;
+
+ private static final int[] VISUAL_ATTRIBUTE_IDS = {
+ R.styleable.Keyboard_Key_keyTypeface,
+ R.styleable.Keyboard_Key_keyLetterSize,
+ R.styleable.Keyboard_Key_keyLabelSize,
+ R.styleable.Keyboard_Key_keyLargeLetterRatio,
+ R.styleable.Keyboard_Key_keyLargeLabelRatio,
+ R.styleable.Keyboard_Key_keyHintLetterRatio,
+ R.styleable.Keyboard_Key_keyShiftedLetterHintRatio,
+ R.styleable.Keyboard_Key_keyHintLabelRatio,
+ R.styleable.Keyboard_Key_keyPreviewTextRatio,
+ R.styleable.Keyboard_Key_keyTextColor,
+ R.styleable.Keyboard_Key_keyTextInactivatedColor,
+ R.styleable.Keyboard_Key_keyTextShadowColor,
+ R.styleable.Keyboard_Key_keyHintLetterColor,
+ R.styleable.Keyboard_Key_keyHintLabelColor,
+ R.styleable.Keyboard_Key_keyShiftedLetterHintInactivatedColor,
+ R.styleable.Keyboard_Key_keyShiftedLetterHintActivatedColor,
+ R.styleable.Keyboard_Key_keyPreviewTextColor,
+ };
+ private static final SparseIntArray sVisualAttributeIds = new SparseIntArray();
+ private static final int ATTR_DEFINED = 1;
+ private static final int ATTR_NOT_FOUND = 0;
+ static {
+ for (final int attrId : VISUAL_ATTRIBUTE_IDS) {
+ sVisualAttributeIds.put(attrId, ATTR_DEFINED);
+ }
+ }
+
+ public static KeyVisualAttributes newInstance(final TypedArray keyAttr) {
+ final int indexCount = keyAttr.getIndexCount();
+ for (int i = 0; i < indexCount; i++) {
+ final int attrId = keyAttr.getIndex(i);
+ if (sVisualAttributeIds.get(attrId, ATTR_NOT_FOUND) == ATTR_NOT_FOUND) {
+ continue;
+ }
+ return new KeyVisualAttributes(keyAttr);
+ }
+ return null;
+ }
+
+ private KeyVisualAttributes(final TypedArray keyAttr) {
+ if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyTypeface)) {
+ mTypeface = Typeface.defaultFromStyle(
+ keyAttr.getInt(R.styleable.Keyboard_Key_keyTypeface, Typeface.NORMAL));
+ } else {
+ mTypeface = null;
+ }
+
+ mLetterRatio = ResourceUtils.getFraction(keyAttr,
+ R.styleable.Keyboard_Key_keyLetterSize);
+ mLetterSize = ResourceUtils.getDimensionPixelSize(keyAttr,
+ R.styleable.Keyboard_Key_keyLetterSize);
+ mLabelRatio = ResourceUtils.getFraction(keyAttr,
+ R.styleable.Keyboard_Key_keyLabelSize);
+ mLabelSize = ResourceUtils.getDimensionPixelSize(keyAttr,
+ R.styleable.Keyboard_Key_keyLabelSize);
+ mLargeLetterRatio = ResourceUtils.getFraction(keyAttr,
+ R.styleable.Keyboard_Key_keyLargeLetterRatio);
+ mLargeLabelRatio = ResourceUtils.getFraction(keyAttr,
+ R.styleable.Keyboard_Key_keyLargeLabelRatio);
+ mHintLetterRatio = ResourceUtils.getFraction(keyAttr,
+ R.styleable.Keyboard_Key_keyHintLetterRatio);
+ mShiftedLetterHintRatio = ResourceUtils.getFraction(keyAttr,
+ R.styleable.Keyboard_Key_keyShiftedLetterHintRatio);
+ mHintLabelRatio = ResourceUtils.getFraction(keyAttr,
+ R.styleable.Keyboard_Key_keyHintLabelRatio);
+ mPreviewTextRatio = ResourceUtils.getFraction(keyAttr,
+ R.styleable.Keyboard_Key_keyPreviewTextRatio);
+
+ mTextColor = keyAttr.getColor(R.styleable.Keyboard_Key_keyTextColor, 0);
+ mTextInactivatedColor = keyAttr.getColor(
+ R.styleable.Keyboard_Key_keyTextInactivatedColor, 0);
+ mTextShadowColor = keyAttr.getColor(R.styleable.Keyboard_Key_keyTextShadowColor, 0);
+ mHintLetterColor = keyAttr.getColor(R.styleable.Keyboard_Key_keyHintLetterColor, 0);
+ mHintLabelColor = keyAttr.getColor(R.styleable.Keyboard_Key_keyHintLabelColor, 0);
+ mShiftedLetterHintInactivatedColor = keyAttr.getColor(
+ R.styleable.Keyboard_Key_keyShiftedLetterHintInactivatedColor, 0);
+ mShiftedLetterHintActivatedColor = keyAttr.getColor(
+ R.styleable.Keyboard_Key_keyShiftedLetterHintActivatedColor, 0);
+ mPreviewTextColor = keyAttr.getColor(R.styleable.Keyboard_Key_keyPreviewTextColor, 0);
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
new file mode 100644
index 000000000..31c7cb565
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
@@ -0,0 +1,814 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+import android.util.Xml;
+import android.view.InflateException;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardId;
+import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.ResourceUtils;
+import com.android.inputmethod.latin.StringUtils;
+import com.android.inputmethod.latin.SubtypeLocale;
+import com.android.inputmethod.latin.XmlParseUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Locale;
+
+/**
+ * Keyboard Building helper.
+ *
+ * This class parses Keyboard XML file and eventually build a Keyboard.
+ * The Keyboard XML file looks like:
+ * <pre>
+ * &lt;!-- xml/keyboard.xml --&gt;
+ * &lt;Keyboard keyboard_attributes*&gt;
+ * &lt;!-- Keyboard Content --&gt;
+ * &lt;Row row_attributes*&gt;
+ * &lt;!-- Row Content --&gt;
+ * &lt;Key key_attributes* /&gt;
+ * &lt;Spacer horizontalGap="32.0dp" /&gt;
+ * &lt;include keyboardLayout="@xml/other_keys"&gt;
+ * ...
+ * &lt;/Row&gt;
+ * &lt;include keyboardLayout="@xml/other_rows"&gt;
+ * ...
+ * &lt;/Keyboard&gt;
+ * </pre>
+ * The XML file which is included in other file must have &lt;merge&gt; as root element,
+ * such as:
+ * <pre>
+ * &lt;!-- xml/other_keys.xml --&gt;
+ * &lt;merge&gt;
+ * &lt;Key key_attributes* /&gt;
+ * ...
+ * &lt;/merge&gt;
+ * </pre>
+ * and
+ * <pre>
+ * &lt;!-- xml/other_rows.xml --&gt;
+ * &lt;merge&gt;
+ * &lt;Row row_attributes*&gt;
+ * &lt;Key key_attributes* /&gt;
+ * &lt;/Row&gt;
+ * ...
+ * &lt;/merge&gt;
+ * </pre>
+ * You can also use switch-case-default tags to select Rows and Keys.
+ * <pre>
+ * &lt;switch&gt;
+ * &lt;case case_attribute*&gt;
+ * &lt;!-- Any valid tags at switch position --&gt;
+ * &lt;/case&gt;
+ * ...
+ * &lt;default&gt;
+ * &lt;!-- Any valid tags at switch position --&gt;
+ * &lt;/default&gt;
+ * &lt;/switch&gt;
+ * </pre>
+ * You can declare Key style and specify styles within Key tags.
+ * <pre>
+ * &lt;switch&gt;
+ * &lt;case mode="email"&gt;
+ * &lt;key-style styleName="f1-key" parentStyle="modifier-key"
+ * keyLabel=".com"
+ * /&gt;
+ * &lt;/case&gt;
+ * &lt;case mode="url"&gt;
+ * &lt;key-style styleName="f1-key" parentStyle="modifier-key"
+ * keyLabel="http://"
+ * /&gt;
+ * &lt;/case&gt;
+ * &lt;/switch&gt;
+ * ...
+ * &lt;Key keyStyle="shift-key" ... /&gt;
+ * </pre>
+ */
+
+public class KeyboardBuilder<KP extends KeyboardParams> {
+ private static final String BUILDER_TAG = "Keyboard.Builder";
+ private static final boolean DEBUG = false;
+
+ // Keyboard XML Tags
+ private static final String TAG_KEYBOARD = "Keyboard";
+ private static final String TAG_ROW = "Row";
+ private static final String TAG_KEY = "Key";
+ private static final String TAG_SPACER = "Spacer";
+ private static final String TAG_INCLUDE = "include";
+ private static final String TAG_MERGE = "merge";
+ private static final String TAG_SWITCH = "switch";
+ private static final String TAG_CASE = "case";
+ private static final String TAG_DEFAULT = "default";
+ public static final String TAG_KEY_STYLE = "key-style";
+
+ private static final int DEFAULT_KEYBOARD_COLUMNS = 10;
+ private static final int DEFAULT_KEYBOARD_ROWS = 4;
+
+ protected final KP mParams;
+ protected final Context mContext;
+ protected final Resources mResources;
+ private final DisplayMetrics mDisplayMetrics;
+
+ private int mCurrentY = 0;
+ private KeyboardRow mCurrentRow = null;
+ private boolean mLeftEdge;
+ private boolean mTopEdge;
+ private Key mRightEdgeKey = null;
+
+ public KeyboardBuilder(final Context context, final KP params) {
+ mContext = context;
+ final Resources res = context.getResources();
+ mResources = res;
+ mDisplayMetrics = res.getDisplayMetrics();
+
+ mParams = params;
+
+ params.GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width);
+ params.GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height);
+ }
+
+ public void setAutoGenerate(final KeysCache keysCache) {
+ mParams.mKeysCache = keysCache;
+ }
+
+ public KeyboardBuilder<KP> load(final int xmlId, final KeyboardId id) {
+ mParams.mId = id;
+ final XmlResourceParser parser = mResources.getXml(xmlId);
+ try {
+ parseKeyboard(parser);
+ } catch (XmlPullParserException e) {
+ Log.w(BUILDER_TAG, "keyboard XML parse error: " + e);
+ throw new IllegalArgumentException(e);
+ } catch (IOException e) {
+ Log.w(BUILDER_TAG, "keyboard XML parse error: " + e);
+ throw new RuntimeException(e);
+ } finally {
+ parser.close();
+ }
+ return this;
+ }
+
+ // TODO: Remove this method.
+ public void setTouchPositionCorrectionEnabled(final boolean enabled) {
+ mParams.mTouchPositionCorrection.setEnabled(enabled);
+ }
+
+ public void setProximityCharsCorrectionEnabled(final boolean enabled) {
+ mParams.mProximityCharsCorrectionEnabled = enabled;
+ }
+
+ public Keyboard build() {
+ return new Keyboard(mParams);
+ }
+
+ private int mIndent;
+ private static final String SPACES = " ";
+
+ private static String spaces(final int count) {
+ return (count < SPACES.length()) ? SPACES.substring(0, count) : SPACES;
+ }
+
+ private void startTag(final String format, final Object ... args) {
+ Log.d(BUILDER_TAG, String.format(spaces(++mIndent * 2) + format, args));
+ }
+
+ private void endTag(final String format, final Object ... args) {
+ Log.d(BUILDER_TAG, String.format(spaces(mIndent-- * 2) + format, args));
+ }
+
+ private void startEndTag(final String format, final Object ... args) {
+ Log.d(BUILDER_TAG, String.format(spaces(++mIndent * 2) + format, args));
+ mIndent--;
+ }
+
+ private void parseKeyboard(final XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ if (DEBUG) startTag("<%s> %s", TAG_KEYBOARD, mParams.mId);
+ int event;
+ while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (event == XmlPullParser.START_TAG) {
+ final String tag = parser.getName();
+ if (TAG_KEYBOARD.equals(tag)) {
+ parseKeyboardAttributes(parser);
+ startKeyboard();
+ parseKeyboardContent(parser, false);
+ break;
+ } else {
+ throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEYBOARD);
+ }
+ }
+ }
+ }
+
+ private void parseKeyboardAttributes(final XmlPullParser parser) {
+ final int displayWidth = mDisplayMetrics.widthPixels;
+ final TypedArray keyboardAttr = mContext.obtainStyledAttributes(
+ Xml.asAttributeSet(parser), R.styleable.Keyboard, R.attr.keyboardStyle,
+ R.style.Keyboard);
+ final TypedArray keyAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
+ R.styleable.Keyboard_Key);
+ try {
+ final int displayHeight = mDisplayMetrics.heightPixels;
+ final String keyboardHeightString = ResourceUtils.getDeviceOverrideValue(
+ mResources, R.array.keyboard_heights, null);
+ final float keyboardHeight;
+ if (keyboardHeightString != null) {
+ keyboardHeight = Float.parseFloat(keyboardHeightString)
+ * mDisplayMetrics.density;
+ } else {
+ keyboardHeight = keyboardAttr.getDimension(
+ R.styleable.Keyboard_keyboardHeight, displayHeight / 2);
+ }
+ final float maxKeyboardHeight = ResourceUtils.getDimensionOrFraction(keyboardAttr,
+ R.styleable.Keyboard_maxKeyboardHeight, displayHeight, displayHeight / 2);
+ float minKeyboardHeight = ResourceUtils.getDimensionOrFraction(keyboardAttr,
+ R.styleable.Keyboard_minKeyboardHeight, displayHeight, displayHeight / 2);
+ if (minKeyboardHeight < 0) {
+ // Specified fraction was negative, so it should be calculated against display
+ // width.
+ minKeyboardHeight = -ResourceUtils.getDimensionOrFraction(keyboardAttr,
+ R.styleable.Keyboard_minKeyboardHeight, displayWidth, displayWidth / 2);
+ }
+ final KeyboardParams params = mParams;
+ // Keyboard height will not exceed maxKeyboardHeight and will not be less than
+ // minKeyboardHeight.
+ params.mOccupiedHeight = (int)Math.max(
+ Math.min(keyboardHeight, maxKeyboardHeight), minKeyboardHeight);
+ params.mOccupiedWidth = params.mId.mWidth;
+ params.mTopPadding = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
+ R.styleable.Keyboard_keyboardTopPadding, params.mOccupiedHeight, 0);
+ params.mBottomPadding = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
+ R.styleable.Keyboard_keyboardBottomPadding, params.mOccupiedHeight, 0);
+ params.mHorizontalEdgesPadding = (int)ResourceUtils.getDimensionOrFraction(
+ keyboardAttr,
+ R.styleable.Keyboard_keyboardHorizontalEdgesPadding,
+ mParams.mOccupiedWidth, 0);
+
+ params.mBaseWidth = params.mOccupiedWidth - params.mHorizontalEdgesPadding * 2
+ - params.mHorizontalCenterPadding;
+ params.mDefaultKeyWidth = (int)ResourceUtils.getDimensionOrFraction(keyAttr,
+ R.styleable.Keyboard_Key_keyWidth, params.mBaseWidth,
+ params.mBaseWidth / DEFAULT_KEYBOARD_COLUMNS);
+ params.mHorizontalGap = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
+ R.styleable.Keyboard_horizontalGap, params.mBaseWidth, 0);
+ params.mVerticalGap = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
+ R.styleable.Keyboard_verticalGap, params.mOccupiedHeight, 0);
+ params.mBaseHeight = params.mOccupiedHeight - params.mTopPadding
+ - params.mBottomPadding + params.mVerticalGap;
+ params.mDefaultRowHeight = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
+ R.styleable.Keyboard_rowHeight, params.mBaseHeight,
+ params.mBaseHeight / DEFAULT_KEYBOARD_ROWS);
+
+ params.mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr);
+
+ params.mMoreKeysTemplate = keyboardAttr.getResourceId(
+ R.styleable.Keyboard_moreKeysTemplate, 0);
+ params.mMaxMoreKeysKeyboardColumn = keyAttr.getInt(
+ R.styleable.Keyboard_Key_maxMoreKeysColumn, 5);
+
+ params.mThemeId = keyboardAttr.getInt(R.styleable.Keyboard_themeId, 0);
+ params.mIconsSet.loadIcons(keyboardAttr);
+ final String language = params.mId.mLocale.getLanguage();
+ params.mCodesSet.setLanguage(language);
+ params.mTextsSet.setLanguage(language);
+ final RunInLocale<Void> job = new RunInLocale<Void>() {
+ @Override
+ protected Void job(Resources res) {
+ params.mTextsSet.loadStringResources(mContext);
+ return null;
+ }
+ };
+ // Null means the current system locale.
+ final Locale locale = SubtypeLocale.isNoLanguage(params.mId.mSubtype)
+ ? null : params.mId.mLocale;
+ job.runInLocale(mResources, locale);
+
+ 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);
+ }
+ } finally {
+ keyAttr.recycle();
+ keyboardAttr.recycle();
+ }
+ }
+
+ private void parseKeyboardContent(final XmlPullParser parser, final boolean skip)
+ throws XmlPullParserException, IOException {
+ int event;
+ while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (event == XmlPullParser.START_TAG) {
+ final String tag = parser.getName();
+ if (TAG_ROW.equals(tag)) {
+ final KeyboardRow row = parseRowAttributes(parser);
+ if (DEBUG) startTag("<%s>%s", TAG_ROW, skip ? " skipped" : "");
+ if (!skip) {
+ startRow(row);
+ }
+ parseRowContent(parser, row, skip);
+ } else if (TAG_INCLUDE.equals(tag)) {
+ parseIncludeKeyboardContent(parser, skip);
+ } else if (TAG_SWITCH.equals(tag)) {
+ parseSwitchKeyboardContent(parser, skip);
+ } else if (TAG_KEY_STYLE.equals(tag)) {
+ parseKeyStyle(parser, skip);
+ } else {
+ throw new XmlParseUtils.IllegalStartTag(parser, TAG_ROW);
+ }
+ } else if (event == XmlPullParser.END_TAG) {
+ final String tag = parser.getName();
+ if (DEBUG) endTag("</%s>", tag);
+ if (TAG_KEYBOARD.equals(tag)) {
+ endKeyboard();
+ break;
+ } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)
+ || TAG_MERGE.equals(tag)) {
+ break;
+ } else {
+ throw new XmlParseUtils.IllegalEndTag(parser, TAG_ROW);
+ }
+ }
+ }
+ }
+
+ private KeyboardRow parseRowAttributes(final XmlPullParser parser)
+ throws XmlPullParserException {
+ final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
+ R.styleable.Keyboard);
+ try {
+ if (a.hasValue(R.styleable.Keyboard_horizontalGap)) {
+ throw new XmlParseUtils.IllegalAttribute(parser, "horizontalGap");
+ }
+ if (a.hasValue(R.styleable.Keyboard_verticalGap)) {
+ throw new XmlParseUtils.IllegalAttribute(parser, "verticalGap");
+ }
+ return new KeyboardRow(mResources, mParams, parser, mCurrentY);
+ } finally {
+ a.recycle();
+ }
+ }
+
+ private void parseRowContent(final XmlPullParser parser, final KeyboardRow row,
+ final boolean skip) throws XmlPullParserException, IOException {
+ int event;
+ while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (event == XmlPullParser.START_TAG) {
+ final String tag = parser.getName();
+ if (TAG_KEY.equals(tag)) {
+ parseKey(parser, row, skip);
+ } else if (TAG_SPACER.equals(tag)) {
+ parseSpacer(parser, row, skip);
+ } else if (TAG_INCLUDE.equals(tag)) {
+ parseIncludeRowContent(parser, row, skip);
+ } else if (TAG_SWITCH.equals(tag)) {
+ parseSwitchRowContent(parser, row, skip);
+ } else if (TAG_KEY_STYLE.equals(tag)) {
+ parseKeyStyle(parser, skip);
+ } else {
+ throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEY);
+ }
+ } else if (event == XmlPullParser.END_TAG) {
+ final String tag = parser.getName();
+ if (DEBUG) endTag("</%s>", tag);
+ if (TAG_ROW.equals(tag)) {
+ if (!skip) {
+ endRow(row);
+ }
+ break;
+ } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)
+ || TAG_MERGE.equals(tag)) {
+ break;
+ } else {
+ throw new XmlParseUtils.IllegalEndTag(parser, TAG_KEY);
+ }
+ }
+ }
+ }
+
+ private void parseKey(final XmlPullParser parser, final KeyboardRow row, final boolean skip)
+ throws XmlPullParserException, IOException {
+ if (skip) {
+ XmlParseUtils.checkEndTag(TAG_KEY, parser);
+ if (DEBUG) {
+ startEndTag("<%s /> skipped", TAG_KEY);
+ }
+ } else {
+ final Key key = new Key(mResources, mParams, row, parser);
+ if (DEBUG) {
+ startEndTag("<%s%s %s moreKeys=%s />", TAG_KEY,
+ (key.isEnabled() ? "" : " disabled"), key,
+ Arrays.toString(key.mMoreKeys));
+ }
+ XmlParseUtils.checkEndTag(TAG_KEY, parser);
+ endKey(key);
+ }
+ }
+
+ private void parseSpacer(final XmlPullParser parser, final KeyboardRow row, final boolean skip)
+ throws XmlPullParserException, IOException {
+ if (skip) {
+ XmlParseUtils.checkEndTag(TAG_SPACER, parser);
+ if (DEBUG) startEndTag("<%s /> skipped", TAG_SPACER);
+ } else {
+ final Key.Spacer spacer = new Key.Spacer(mResources, mParams, row, parser);
+ if (DEBUG) startEndTag("<%s />", TAG_SPACER);
+ XmlParseUtils.checkEndTag(TAG_SPACER, parser);
+ endKey(spacer);
+ }
+ }
+
+ private void parseIncludeKeyboardContent(final XmlPullParser parser, final boolean skip)
+ throws XmlPullParserException, IOException {
+ parseIncludeInternal(parser, null, skip);
+ }
+
+ private void parseIncludeRowContent(final XmlPullParser parser, final KeyboardRow row,
+ final boolean skip) throws XmlPullParserException, IOException {
+ parseIncludeInternal(parser, row, skip);
+ }
+
+ private void parseIncludeInternal(final XmlPullParser parser, final KeyboardRow row,
+ final boolean skip) throws XmlPullParserException, IOException {
+ if (skip) {
+ XmlParseUtils.checkEndTag(TAG_INCLUDE, parser);
+ if (DEBUG) startEndTag("</%s> skipped", TAG_INCLUDE);
+ } else {
+ final AttributeSet attr = Xml.asAttributeSet(parser);
+ final TypedArray keyboardAttr = mResources.obtainAttributes(attr,
+ R.styleable.Keyboard_Include);
+ final TypedArray keyAttr = mResources.obtainAttributes(attr,
+ R.styleable.Keyboard_Key);
+ int keyboardLayout = 0;
+ float savedDefaultKeyWidth = 0;
+ int savedDefaultKeyLabelFlags = 0;
+ int savedDefaultBackgroundType = Key.BACKGROUND_TYPE_NORMAL;
+ try {
+ XmlParseUtils.checkAttributeExists(keyboardAttr,
+ R.styleable.Keyboard_Include_keyboardLayout, "keyboardLayout",
+ TAG_INCLUDE, parser);
+ keyboardLayout = keyboardAttr.getResourceId(
+ R.styleable.Keyboard_Include_keyboardLayout, 0);
+ if (row != null) {
+ if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) {
+ // Override current x coordinate.
+ row.setXPos(row.getKeyX(keyAttr));
+ }
+ // TODO: Remove this if-clause and do the same as backgroundType below.
+ savedDefaultKeyWidth = row.getDefaultKeyWidth();
+ if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyWidth)) {
+ // Override default key width.
+ row.setDefaultKeyWidth(row.getKeyWidth(keyAttr));
+ }
+ savedDefaultKeyLabelFlags = row.getDefaultKeyLabelFlags();
+ // Bitwise-or default keyLabelFlag if exists.
+ row.setDefaultKeyLabelFlags(keyAttr.getInt(
+ R.styleable.Keyboard_Key_keyLabelFlags, 0)
+ | savedDefaultKeyLabelFlags);
+ savedDefaultBackgroundType = row.getDefaultBackgroundType();
+ // Override default backgroundType if exists.
+ row.setDefaultBackgroundType(keyAttr.getInt(
+ R.styleable.Keyboard_Key_backgroundType,
+ savedDefaultBackgroundType));
+ }
+ } finally {
+ keyboardAttr.recycle();
+ keyAttr.recycle();
+ }
+
+ XmlParseUtils.checkEndTag(TAG_INCLUDE, parser);
+ if (DEBUG) {
+ startEndTag("<%s keyboardLayout=%s />",TAG_INCLUDE,
+ mResources.getResourceEntryName(keyboardLayout));
+ }
+ final XmlResourceParser parserForInclude = mResources.getXml(keyboardLayout);
+ try {
+ parseMerge(parserForInclude, row, skip);
+ } finally {
+ if (row != null) {
+ // Restore default keyWidth, keyLabelFlags, and backgroundType.
+ row.setDefaultKeyWidth(savedDefaultKeyWidth);
+ row.setDefaultKeyLabelFlags(savedDefaultKeyLabelFlags);
+ row.setDefaultBackgroundType(savedDefaultBackgroundType);
+ }
+ parserForInclude.close();
+ }
+ }
+ }
+
+ private void parseMerge(final XmlPullParser parser, final KeyboardRow row, final boolean skip)
+ throws XmlPullParserException, IOException {
+ if (DEBUG) startTag("<%s>", TAG_MERGE);
+ int event;
+ while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (event == XmlPullParser.START_TAG) {
+ final String tag = parser.getName();
+ if (TAG_MERGE.equals(tag)) {
+ if (row == null) {
+ parseKeyboardContent(parser, skip);
+ } else {
+ parseRowContent(parser, row, skip);
+ }
+ break;
+ } else {
+ throw new XmlParseUtils.ParseException(
+ "Included keyboard layout must have <merge> root element", parser);
+ }
+ }
+ }
+ }
+
+ private void parseSwitchKeyboardContent(final XmlPullParser parser, final boolean skip)
+ throws XmlPullParserException, IOException {
+ parseSwitchInternal(parser, null, skip);
+ }
+
+ private void parseSwitchRowContent(final XmlPullParser parser, final KeyboardRow row,
+ final boolean skip) throws XmlPullParserException, IOException {
+ parseSwitchInternal(parser, row, skip);
+ }
+
+ private void parseSwitchInternal(final XmlPullParser parser, final KeyboardRow row,
+ final boolean skip) throws XmlPullParserException, IOException {
+ if (DEBUG) startTag("<%s> %s", TAG_SWITCH, mParams.mId);
+ boolean selected = false;
+ int event;
+ while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (event == XmlPullParser.START_TAG) {
+ final String tag = parser.getName();
+ if (TAG_CASE.equals(tag)) {
+ selected |= parseCase(parser, row, selected ? true : skip);
+ } else if (TAG_DEFAULT.equals(tag)) {
+ selected |= parseDefault(parser, row, selected ? true : skip);
+ } else {
+ throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEY);
+ }
+ } else if (event == XmlPullParser.END_TAG) {
+ final String tag = parser.getName();
+ if (TAG_SWITCH.equals(tag)) {
+ if (DEBUG) endTag("</%s>", TAG_SWITCH);
+ break;
+ } else {
+ throw new XmlParseUtils.IllegalEndTag(parser, TAG_KEY);
+ }
+ }
+ }
+ }
+
+ private boolean parseCase(final XmlPullParser parser, final KeyboardRow row, final boolean skip)
+ throws XmlPullParserException, IOException {
+ final boolean selected = parseCaseCondition(parser);
+ if (row == null) {
+ // Processing Rows.
+ parseKeyboardContent(parser, selected ? skip : true);
+ } else {
+ // Processing Keys.
+ parseRowContent(parser, row, selected ? skip : true);
+ }
+ return selected;
+ }
+
+ private boolean parseCaseCondition(final XmlPullParser parser) {
+ final KeyboardId id = mParams.mId;
+ if (id == null) {
+ return true;
+ }
+ final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
+ R.styleable.Keyboard_Case);
+ try {
+ final boolean keyboardLayoutSetElementMatched = matchTypedValue(a,
+ R.styleable.Keyboard_Case_keyboardLayoutSetElement, id.mElementId,
+ KeyboardId.elementIdToName(id.mElementId));
+ final boolean modeMatched = matchTypedValue(a,
+ R.styleable.Keyboard_Case_mode, id.mMode, KeyboardId.modeName(id.mMode));
+ final boolean navigateNextMatched = matchBoolean(a,
+ R.styleable.Keyboard_Case_navigateNext, id.navigateNext());
+ final boolean navigatePreviousMatched = matchBoolean(a,
+ R.styleable.Keyboard_Case_navigatePrevious, id.navigatePrevious());
+ final boolean passwordInputMatched = matchBoolean(a,
+ R.styleable.Keyboard_Case_passwordInput, id.passwordInput());
+ final boolean clobberSettingsKeyMatched = matchBoolean(a,
+ R.styleable.Keyboard_Case_clobberSettingsKey, id.mClobberSettingsKey);
+ final boolean shortcutKeyEnabledMatched = matchBoolean(a,
+ R.styleable.Keyboard_Case_shortcutKeyEnabled, id.mShortcutKeyEnabled);
+ final boolean hasShortcutKeyMatched = matchBoolean(a,
+ R.styleable.Keyboard_Case_hasShortcutKey, id.mHasShortcutKey);
+ final boolean languageSwitchKeyEnabledMatched = matchBoolean(a,
+ R.styleable.Keyboard_Case_languageSwitchKeyEnabled,
+ id.mLanguageSwitchKeyEnabled);
+ final boolean isMultiLineMatched = matchBoolean(a,
+ R.styleable.Keyboard_Case_isMultiLine, id.isMultiLine());
+ final boolean imeActionMatched = matchInteger(a,
+ R.styleable.Keyboard_Case_imeAction, id.imeAction());
+ final boolean localeCodeMatched = matchString(a,
+ R.styleable.Keyboard_Case_localeCode, id.mLocale.toString());
+ final boolean languageCodeMatched = matchString(a,
+ R.styleable.Keyboard_Case_languageCode, id.mLocale.getLanguage());
+ final boolean countryCodeMatched = matchString(a,
+ R.styleable.Keyboard_Case_countryCode, id.mLocale.getCountry());
+ final boolean selected = keyboardLayoutSetElementMatched && modeMatched
+ && navigateNextMatched && navigatePreviousMatched && passwordInputMatched
+ && clobberSettingsKeyMatched && shortcutKeyEnabledMatched
+ && hasShortcutKeyMatched && languageSwitchKeyEnabledMatched
+ && isMultiLineMatched && imeActionMatched && localeCodeMatched
+ && languageCodeMatched && countryCodeMatched;
+
+ if (DEBUG) {
+ startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE,
+ textAttr(a.getString(
+ R.styleable.Keyboard_Case_keyboardLayoutSetElement),
+ "keyboardLayoutSetElement"),
+ textAttr(a.getString(R.styleable.Keyboard_Case_mode), "mode"),
+ textAttr(a.getString(R.styleable.Keyboard_Case_imeAction),
+ "imeAction"),
+ booleanAttr(a, R.styleable.Keyboard_Case_navigateNext,
+ "navigateNext"),
+ booleanAttr(a, R.styleable.Keyboard_Case_navigatePrevious,
+ "navigatePrevious"),
+ booleanAttr(a, R.styleable.Keyboard_Case_clobberSettingsKey,
+ "clobberSettingsKey"),
+ booleanAttr(a, R.styleable.Keyboard_Case_passwordInput,
+ "passwordInput"),
+ booleanAttr(a, R.styleable.Keyboard_Case_shortcutKeyEnabled,
+ "shortcutKeyEnabled"),
+ booleanAttr(a, R.styleable.Keyboard_Case_hasShortcutKey,
+ "hasShortcutKey"),
+ booleanAttr(a, R.styleable.Keyboard_Case_languageSwitchKeyEnabled,
+ "languageSwitchKeyEnabled"),
+ booleanAttr(a, R.styleable.Keyboard_Case_isMultiLine,
+ "isMultiLine"),
+ textAttr(a.getString(R.styleable.Keyboard_Case_localeCode),
+ "localeCode"),
+ textAttr(a.getString(R.styleable.Keyboard_Case_languageCode),
+ "languageCode"),
+ textAttr(a.getString(R.styleable.Keyboard_Case_countryCode),
+ "countryCode"),
+ selected ? "" : " skipped");
+ }
+
+ return selected;
+ } finally {
+ a.recycle();
+ }
+ }
+
+ private static boolean matchInteger(final TypedArray a, final int index, final int value) {
+ // If <case> does not have "index" attribute, that means this <case> is wild-card for
+ // the attribute.
+ return !a.hasValue(index) || a.getInt(index, 0) == value;
+ }
+
+ private static boolean matchBoolean(final TypedArray a, final int index, final boolean value) {
+ // If <case> does not have "index" attribute, that means this <case> is wild-card for
+ // the attribute.
+ return !a.hasValue(index) || a.getBoolean(index, false) == value;
+ }
+
+ private static boolean matchString(final TypedArray a, final int index, final String value) {
+ // If <case> does not have "index" attribute, that means this <case> is wild-card for
+ // the attribute.
+ return !a.hasValue(index)
+ || StringUtils.containsInArray(value, a.getString(index).split("\\|"));
+ }
+
+ private static boolean matchTypedValue(final TypedArray a, final int index, final int intValue,
+ final String strValue) {
+ // If <case> does not have "index" attribute, that means this <case> is wild-card for
+ // the attribute.
+ final TypedValue v = a.peekValue(index);
+ if (v == null) {
+ return true;
+ }
+ if (ResourceUtils.isIntegerValue(v)) {
+ return intValue == a.getInt(index, 0);
+ } else if (ResourceUtils.isStringValue(v)) {
+ return StringUtils.containsInArray(strValue, a.getString(index).split("\\|"));
+ }
+ return false;
+ }
+
+ private boolean parseDefault(final XmlPullParser parser, final KeyboardRow row,
+ final boolean skip) throws XmlPullParserException, IOException {
+ if (DEBUG) startTag("<%s>", TAG_DEFAULT);
+ if (row == null) {
+ parseKeyboardContent(parser, skip);
+ } else {
+ parseRowContent(parser, row, skip);
+ }
+ return true;
+ }
+
+ private void parseKeyStyle(final XmlPullParser parser, final boolean skip)
+ throws XmlPullParserException, IOException {
+ TypedArray keyStyleAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
+ R.styleable.Keyboard_KeyStyle);
+ TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser),
+ R.styleable.Keyboard_Key);
+ try {
+ if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName)) {
+ throw new XmlParseUtils.ParseException("<" + TAG_KEY_STYLE
+ + "/> needs styleName attribute", parser);
+ }
+ if (DEBUG) {
+ startEndTag("<%s styleName=%s />%s", TAG_KEY_STYLE,
+ keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName),
+ skip ? " skipped" : "");
+ }
+ if (!skip) {
+ mParams.mKeyStyles.parseKeyStyleAttributes(keyStyleAttr, keyAttrs, parser);
+ }
+ } finally {
+ keyStyleAttr.recycle();
+ keyAttrs.recycle();
+ }
+ XmlParseUtils.checkEndTag(TAG_KEY_STYLE, parser);
+ }
+
+ private void startKeyboard() {
+ mCurrentY += mParams.mTopPadding;
+ mTopEdge = true;
+ }
+
+ private void startRow(final KeyboardRow row) {
+ addEdgeSpace(mParams.mHorizontalEdgesPadding, row);
+ mCurrentRow = row;
+ mLeftEdge = true;
+ mRightEdgeKey = null;
+ }
+
+ private void endRow(final KeyboardRow row) {
+ if (mCurrentRow == null) {
+ throw new InflateException("orphan end row tag");
+ }
+ if (mRightEdgeKey != null) {
+ mRightEdgeKey.markAsRightEdge(mParams);
+ mRightEdgeKey = null;
+ }
+ addEdgeSpace(mParams.mHorizontalEdgesPadding, row);
+ mCurrentY += row.mRowHeight;
+ mCurrentRow = null;
+ mTopEdge = false;
+ }
+
+ private void endKey(final Key key) {
+ mParams.onAddKey(key);
+ if (mLeftEdge) {
+ key.markAsLeftEdge(mParams);
+ mLeftEdge = false;
+ }
+ if (mTopEdge) {
+ key.markAsTopEdge(mParams);
+ }
+ mRightEdgeKey = key;
+ }
+
+ private void endKeyboard() {
+ // nothing to do here.
+ }
+
+ private void addEdgeSpace(final float width, final KeyboardRow row) {
+ row.advanceXPos(width);
+ mLeftEdge = false;
+ mRightEdgeKey = null;
+ }
+
+ private static String textAttr(final String value, final String name) {
+ return value != null ? String.format(" %s=%s", name, value) : "";
+ }
+
+ private static String booleanAttr(final TypedArray a, final int index, final String name) {
+ return a.hasValue(index)
+ ? String.format(" %s=%s", name, a.getBoolean(index, false)) : "";
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
index f7981a320..f7923d0b9 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
@@ -17,13 +17,13 @@
package com.android.inputmethod.keyboard.internal;
import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.CollectionUtils;
import java.util.HashMap;
public class KeyboardCodesSet {
- private static final HashMap<String, int[]> sLanguageToCodesMap =
- new HashMap<String, int[]>();
- private static final HashMap<String, Integer> sNameToIdMap = new HashMap<String, Integer>();
+ private static final HashMap<String, int[]> sLanguageToCodesMap = CollectionUtils.newHashMap();
+ private static final HashMap<String, Integer> sNameToIdMap = CollectionUtils.newHashMap();
private int[] mCodes = DEFAULT;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
index 5155851fe..4a98a3698 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
@@ -22,6 +22,7 @@ import android.graphics.drawable.Drawable;
import android.util.Log;
import android.util.SparseIntArray;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.R;
import java.util.HashMap;
@@ -35,7 +36,7 @@ public class KeyboardIconsSet {
private static final SparseIntArray ATTR_ID_TO_ICON_ID = new SparseIntArray();
// Icon name to icon id map.
- private static final HashMap<String, Integer> sNameToIdsMap = new HashMap<String, Integer>();
+ private static final HashMap<String, Integer> sNameToIdsMap = CollectionUtils.newHashMap();
private static final Object[] NAMES_AND_ATTR_IDS = {
"undefined", ATTR_UNDEFINED,
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
new file mode 100644
index 000000000..ab5d31d42
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.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 java.util.ArrayList;
+import java.util.HashSet;
+
+public class KeyboardParams {
+ public KeyboardId mId;
+ public int mThemeId;
+
+ /** Total height and width of the keyboard, including the paddings and keys */
+ public int mOccupiedHeight;
+ public int mOccupiedWidth;
+
+ /** Base height and width of the keyboard used to calculate rows' or keys' heights and
+ * widths
+ */
+ public int mBaseHeight;
+ public int mBaseWidth;
+
+ public int mTopPadding;
+ public int mBottomPadding;
+ public int mHorizontalEdgesPadding;
+ public int mHorizontalCenterPadding;
+
+ public KeyVisualAttributes mKeyVisualAttributes;
+
+ public int mDefaultRowHeight;
+ public int mDefaultKeyWidth;
+ public int mHorizontalGap;
+ public int mVerticalGap;
+
+ public int mMoreKeysTemplate;
+ public int mMaxMoreKeysKeyboardColumn;
+
+ public int GRID_WIDTH;
+ public int GRID_HEIGHT;
+
+ public final HashSet<Key> mKeys = CollectionUtils.newHashSet();
+ public final ArrayList<Key> mShiftKeys = CollectionUtils.newArrayList();
+ public final ArrayList<Key> mAltCodeKeysWhileTyping = CollectionUtils.newArrayList();
+ public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet();
+ public final KeyboardCodesSet mCodesSet = new KeyboardCodesSet();
+ public final KeyboardTextsSet mTextsSet = new KeyboardTextsSet();
+ public final KeyStylesSet mKeyStyles = new KeyStylesSet(mTextsSet);
+
+ public KeysCache mKeysCache;
+
+ public int mMostCommonKeyHeight = 0;
+ public int mMostCommonKeyWidth = 0;
+
+ public boolean mProximityCharsCorrectionEnabled;
+
+ public final TouchPositionCorrection mTouchPositionCorrection =
+ new TouchPositionCorrection();
+
+ protected void clearKeys() {
+ mKeys.clear();
+ mShiftKeys.clear();
+ clearHistogram();
+ }
+
+ public void onAddKey(final Key newKey) {
+ final Key key = (mKeysCache != null) ? mKeysCache.get(newKey) : newKey;
+ final boolean zeroWidthSpacer = key.isSpacer() && key.mWidth == 0;
+ if (!zeroWidthSpacer) {
+ mKeys.add(key);
+ updateHistogram(key);
+ }
+ if (key.mCode == Keyboard.CODE_SHIFT) {
+ mShiftKeys.add(key);
+ }
+ if (key.altCodeWhileTyping()) {
+ mAltCodeKeysWhileTyping.add(key);
+ }
+ }
+
+ private int mMaxHeightCount = 0;
+ private int mMaxWidthCount = 0;
+ private final SparseIntArray mHeightHistogram = new SparseIntArray();
+ private final SparseIntArray mWidthHistogram = new SparseIntArray();
+
+ private void clearHistogram() {
+ mMostCommonKeyHeight = 0;
+ mMaxHeightCount = 0;
+ mHeightHistogram.clear();
+
+ mMaxWidthCount = 0;
+ mMostCommonKeyWidth = 0;
+ mWidthHistogram.clear();
+ }
+
+ private static int updateHistogramCounter(final SparseIntArray histogram, final int key) {
+ final int index = histogram.indexOfKey(key);
+ final int count = (index >= 0 ? histogram.get(key) : 0) + 1;
+ histogram.put(key, count);
+ return count;
+ }
+
+ private void updateHistogram(final Key key) {
+ final int height = key.mHeight + mVerticalGap;
+ final int heightCount = updateHistogramCounter(mHeightHistogram, height);
+ if (heightCount > mMaxHeightCount) {
+ mMaxHeightCount = heightCount;
+ mMostCommonKeyHeight = height;
+ }
+
+ final int width = key.mWidth + mHorizontalGap;
+ final int widthCount = updateHistogramCounter(mWidthHistogram, width);
+ if (widthCount > mMaxWidthCount) {
+ mMaxWidthCount = widthCount;
+ mMostCommonKeyWidth = width;
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java
new file mode 100644
index 000000000..eb17b0ea4
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.util.Xml;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.ResourceUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * Container for keys in the keyboard. All keys in a row are at the same Y-coordinate.
+ * Some of the key size defaults can be overridden per row from what the {@link Keyboard}
+ * defines.
+ */
+public class KeyboardRow {
+ // keyWidth enum constants
+ private static final int KEYWIDTH_NOT_ENUM = 0;
+ private static final int KEYWIDTH_FILL_RIGHT = -1;
+
+ private final KeyboardParams mParams;
+ /** Default width of a key in this row. */
+ private float mDefaultKeyWidth;
+ /** Default height of a key in this row. */
+ public final int mRowHeight;
+ /** Default keyLabelFlags in this row. */
+ private int mDefaultKeyLabelFlags;
+ /** Default backgroundType for this row */
+ private int mDefaultBackgroundType;
+
+ private final int mCurrentY;
+ // Will be updated by {@link Key}'s constructor.
+ private float mCurrentX;
+
+ public KeyboardRow(final Resources res, final KeyboardParams params, final XmlPullParser parser,
+ final int y) {
+ mParams = params;
+ TypedArray keyboardAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
+ R.styleable.Keyboard);
+ mRowHeight = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
+ R.styleable.Keyboard_rowHeight,
+ params.mBaseHeight, params.mDefaultRowHeight);
+ keyboardAttr.recycle();
+ TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
+ R.styleable.Keyboard_Key);
+ mDefaultKeyWidth = ResourceUtils.getDimensionOrFraction(keyAttr,
+ R.styleable.Keyboard_Key_keyWidth,
+ params.mBaseWidth, params.mDefaultKeyWidth);
+ mDefaultBackgroundType = keyAttr.getInt(R.styleable.Keyboard_Key_backgroundType,
+ Key.BACKGROUND_TYPE_NORMAL);
+ keyAttr.recycle();
+
+ // TODO: Initialize this with <Row> attribute as backgroundType is done.
+ mDefaultKeyLabelFlags = 0;
+ mCurrentY = y;
+ mCurrentX = 0.0f;
+ }
+
+ public float getDefaultKeyWidth() {
+ return mDefaultKeyWidth;
+ }
+
+ public void setDefaultKeyWidth(final float defaultKeyWidth) {
+ mDefaultKeyWidth = defaultKeyWidth;
+ }
+
+ public int getDefaultKeyLabelFlags() {
+ return mDefaultKeyLabelFlags;
+ }
+
+ public void setDefaultKeyLabelFlags(final int keyLabelFlags) {
+ mDefaultKeyLabelFlags = keyLabelFlags;
+ }
+
+ public int getDefaultBackgroundType() {
+ return mDefaultBackgroundType;
+ }
+
+ public void setDefaultBackgroundType(final int backgroundType) {
+ mDefaultBackgroundType = backgroundType;
+ }
+
+ public void setXPos(final float keyXPos) {
+ mCurrentX = keyXPos;
+ }
+
+ public void advanceXPos(final float width) {
+ mCurrentX += width;
+ }
+
+ public int getKeyY() {
+ return mCurrentY;
+ }
+
+ public float getKeyX(final TypedArray keyAttr) {
+ final int keyboardRightEdge = mParams.mOccupiedWidth
+ - mParams.mHorizontalEdgesPadding;
+ if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) {
+ final float keyXPos = ResourceUtils.getDimensionOrFraction(keyAttr,
+ R.styleable.Keyboard_Key_keyXPos, mParams.mBaseWidth, 0);
+ if (keyXPos < 0) {
+ // If keyXPos is negative, the actual x-coordinate will be
+ // keyboardWidth + keyXPos.
+ // keyXPos shouldn't be less than mCurrentX because drawable area for this
+ // key starts at mCurrentX. Or, this key will overlaps the adjacent key on
+ // its left hand side.
+ return Math.max(keyXPos + keyboardRightEdge, mCurrentX);
+ } else {
+ return keyXPos + mParams.mHorizontalEdgesPadding;
+ }
+ }
+ return mCurrentX;
+ }
+
+ public float getKeyWidth(final TypedArray keyAttr) {
+ return getKeyWidth(keyAttr, mCurrentX);
+ }
+
+ public float getKeyWidth(final TypedArray keyAttr, final float keyXPos) {
+ final int widthType = ResourceUtils.getEnumValue(keyAttr,
+ R.styleable.Keyboard_Key_keyWidth, KEYWIDTH_NOT_ENUM);
+ switch (widthType) {
+ case KEYWIDTH_FILL_RIGHT:
+ final int keyboardRightEdge =
+ mParams.mOccupiedWidth - mParams.mHorizontalEdgesPadding;
+ // If keyWidth is fillRight, the actual key width will be determined to fill
+ // out the area up to the right edge of the keyboard.
+ return keyboardRightEdge - keyXPos;
+ default: // KEYWIDTH_NOT_ENUM
+ return ResourceUtils.getDimensionOrFraction(keyAttr,
+ R.styleable.Keyboard_Key_keyWidth,
+ mParams.mBaseWidth, mDefaultKeyWidth);
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
index bec0f1fef..3b7c6ad7a 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.latin.CollectionUtils;
import com.android.inputmethod.latin.R;
import java.util.HashMap;
@@ -45,14 +46,12 @@ import java.util.HashMap;
*/
public final class KeyboardTextsSet {
// Language to texts map.
- private static final HashMap<String, String[]> sLocaleToTextsMap =
- new HashMap<String, String[]>();
- private static final HashMap<String, Integer> sNameToIdsMap =
- new HashMap<String, Integer>();
+ private static final HashMap<String, String[]> sLocaleToTextsMap = CollectionUtils.newHashMap();
+ private static final HashMap<String, Integer> sNameToIdsMap = CollectionUtils.newHashMap();
private String[] mTexts;
// Resource name to text map.
- private HashMap<String, String> mResourceNameToTextsMap = new HashMap<String, String>();
+ private HashMap<String, String> mResourceNameToTextsMap = CollectionUtils.newHashMap();
public void setLanguage(final String language) {
mTexts = sLocaleToTextsMap.get(language);
@@ -211,22 +210,29 @@ public final class KeyboardTextsSet {
/* 103 */ "keylabel_for_apostrophe",
/* 104 */ "keyhintlabel_for_apostrophe",
/* 105 */ "more_keys_for_apostrophe",
- /* 106 */ "more_keys_for_am_pm",
- /* 107 */ "settings_as_more_key",
- /* 108 */ "shortcut_as_more_key",
- /* 109 */ "action_next_as_more_key",
- /* 110 */ "action_previous_as_more_key",
- /* 111 */ "label_to_more_symbol_key",
- /* 112 */ "label_to_more_symbol_for_tablet_key",
- /* 113 */ "label_tab_key",
- /* 114 */ "label_to_phone_numeric_key",
- /* 115 */ "label_to_phone_symbols_key",
- /* 116 */ "label_time_am",
- /* 117 */ "label_time_pm",
- /* 118 */ "label_to_symbol_key_pcqwerty",
- /* 119 */ "keylabel_for_popular_domain",
- /* 120 */ "more_keys_for_popular_domain",
- /* 121 */ "more_keys_for_smiley",
+ /* 106 */ "more_keys_for_q",
+ /* 107 */ "more_keys_for_x",
+ /* 108 */ "keylabel_for_q",
+ /* 109 */ "keylabel_for_w",
+ /* 110 */ "keylabel_for_y",
+ /* 111 */ "keylabel_for_x",
+ /* 112 */ "keylabel_for_spanish_row2_10",
+ /* 113 */ "more_keys_for_am_pm",
+ /* 114 */ "settings_as_more_key",
+ /* 115 */ "shortcut_as_more_key",
+ /* 116 */ "action_next_as_more_key",
+ /* 117 */ "action_previous_as_more_key",
+ /* 118 */ "label_to_more_symbol_key",
+ /* 119 */ "label_to_more_symbol_for_tablet_key",
+ /* 120 */ "label_tab_key",
+ /* 121 */ "label_to_phone_numeric_key",
+ /* 122 */ "label_to_phone_symbols_key",
+ /* 123 */ "label_time_am",
+ /* 124 */ "label_time_pm",
+ /* 125 */ "label_to_symbol_key_pcqwerty",
+ /* 126 */ "keylabel_for_popular_domain",
+ /* 127 */ "more_keys_for_popular_domain",
+ /* 128 */ "more_keys_for_smiley",
};
private static final String EMPTY = "";
@@ -349,33 +355,41 @@ public final class KeyboardTextsSet {
/* 103 */ "\'",
/* 104 */ "\"",
/* 105 */ "\"",
- /* 106 */ "!fixedColumnOrder!2,!hasLabels!,!text/label_time_am,!text/label_time_pm",
- /* 107 */ "!icon/settings_key|!code/key_settings",
- /* 108 */ "!icon/shortcut_key|!code/key_shortcut",
- /* 109 */ "!hasLabels!,!text/label_next_key|!code/key_action_next",
- /* 110 */ "!hasLabels!,!text/label_previous_key|!code/key_action_previous",
+ /* 106 */ EMPTY,
+ /* 107 */ EMPTY,
+ /* 108 */ "q",
+ /* 109 */ "w",
+ /* 110 */ "y",
+ /* 111 */ "x",
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ /* 112 */ "\u00F1",
+ /* 113 */ "!fixedColumnOrder!2,!hasLabels!,!text/label_time_am,!text/label_time_pm",
+ /* 114 */ "!icon/settings_key|!code/key_settings",
+ /* 115 */ "!icon/shortcut_key|!code/key_shortcut",
+ /* 116 */ "!hasLabels!,!text/label_next_key|!code/key_action_next",
+ /* 117 */ "!hasLabels!,!text/label_previous_key|!code/key_action_previous",
// Label for "switch to more symbol" modifier key. Must be short to fit on key!
- /* 111 */ "= \\ <",
+ /* 118 */ "= \\ <",
// Label for "switch to more symbol" modifier key on tablets. Must be short to fit on key!
- /* 112 */ "~ \\ {",
+ /* 119 */ "~ \\ {",
// Label for "Tab" key. Must be short to fit on key!
- /* 113 */ "Tab",
+ /* 120 */ "Tab",
// Label for "switch to phone numeric" key. Must be short to fit on key!
- /* 114 */ "123",
+ /* 121 */ "123",
// Label for "switch to phone symbols" key. Must be short to fit on key!
// U+FF0A: "*" FULLWIDTH ASTERISK
// U+FF03: "#" FULLWIDTH NUMBER SIGN
- /* 115 */ "\uFF0A\uFF03",
+ /* 122 */ "\uFF0A\uFF03",
// Key label for "ante meridiem"
- /* 116 */ "AM",
+ /* 123 */ "AM",
// Key label for "post meridiem"
- /* 117 */ "PM",
+ /* 124 */ "PM",
// Label for "switch to symbols" key on PC QWERTY layout
- /* 118 */ "Sym",
- /* 119 */ ".com",
+ /* 125 */ "Sym",
+ /* 126 */ ".com",
// popular web domains for the locale - most popular, displayed on the keyboard
- /* 120 */ "!hasLabels!,.net,.org,.gov,.edu",
- /* 121 */ "!fixedColumnOrder!5,!hasLabels!,=-O|=-O ,:-P|:-P ,;-)|;-) ,:-(|:-( ,:-)|:-) ,:-!|:-! ,:-$|:-$ ,B-)|B-) ,:O|:O ,:-*|:-* ,:-D|:-D ,:\'(|:\'( ,:-\\\\|:-\\\\ ,O:-)|O:-) ,:-[|:-[ ",
+ /* 127 */ "!hasLabels!,.net,.org,.gov,.edu",
+ /* 128 */ "!fixedColumnOrder!5,!hasLabels!,=-O|=-O ,:-P|:-P ,;-)|;-) ,:-(|:-( ,:-)|:-) ,:-!|:-! ,:-$|:-$ ,B-)|B-) ,:O|:O ,:-*|:-* ,:-D|:-D ,:\'(|:\'( ,:-\\\\|:-\\\\ ,O:-)|O:-) ,:-[|:-[ ",
};
/* Language af: Afrikaans */
@@ -858,6 +872,144 @@ public final class KeyboardTextsSet {
/* 7 */ "\u00E7",
};
+ /* Language eo: Esperanto */
+ private static final String[] LANGUAGE_eo = {
+ // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+ // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+ // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E6: "æ" LATIN SMALL LETTER AE
+ // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+ // U+0103: "ă" LATIN SMALL LETTER A WITH BREVE
+ // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
+ // U+00AA: "ª" FEMININE ORDINAL INDICATOR
+ /* 0 */ "\u00E1,\u00E0,\u00E2,\u00E4,\u00E6,\u00E3,\u00E5,\u0101,\u0103,\u0105,\u00AA",
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+011B: "ě" LATIN SMALL LETTER E WITH CARON
+ // 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+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+ // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+ // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
+ /* 1 */ "\u00E9,\u011B,\u00E8,\u00EA,\u00EB,\u0119,\u0117,\u0113",
+ // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+ // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+ // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+ // U+0129: "ĩ" LATIN SMALL LETTER I WITH TILDE
+ // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+ // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+ // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
+ // U+0131: "ı" LATIN SMALL LETTER DOTLESS I
+ // U+0133: "ij" LATIN SMALL LIGATURE IJ
+ /* 2 */ "\u00ED,\u00EE,\u00EF,\u0129,\u00EC,\u012F,\u012B,\u0131,\u0133",
+ // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+ // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+ // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+ // U+0153: "œ" LATIN SMALL LIGATURE OE
+ // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+ // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
+ // U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE
+ // U+00BA: "º" MASCULINE ORDINAL INDICATOR
+ /* 3 */ "\u00F3,\u00F6,\u00F4,\u00F2,\u00F5,\u0153,\u00F8,\u014D,\u0151,\u00BA",
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ // U+0169: "ũ" LATIN SMALL LETTER U WITH TILDE
+ // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE
+ // U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK
+ // U+00B5: "µ" MICRO SIGN
+ /* 4 */ "\u00FA,\u016F,\u00FB,\u00FC,\u00F9,\u016B,\u0169,\u0171,\u0173,\u00B5",
+ // U+00DF: "ß" LATIN SMALL LETTER SHARP S
+ // U+0161: "š" LATIN SMALL LETTER S WITH CARON
+ // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
+ // U+0219: "ș" LATIN SMALL LETTER S WITH COMMA BELOW
+ // U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA
+ /* 5 */ "\u00DF,\u0161,\u015B,\u0219,\u015F",
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+ // U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA
+ // U+0148: "ň" LATIN SMALL LETTER N WITH CARON
+ // U+0149: "ʼn" LATIN SMALL LETTER N PRECEDED BY APOSTROPHE
+ // U+014B: "ŋ" LATIN SMALL LETTER ENG
+ /* 6 */ "\u00F1,\u0144,\u0146,\u0148,\u0149,\u014B",
+ // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
+ // U+010D: "č" LATIN SMALL LETTER C WITH CARON
+ // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+ // U+010B: "ċ" LATIN SMALL LETTER C WITH DOT ABOVE
+ /* 7 */ "\u0107,\u010D,\u00E7,\u010B",
+ // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
+ // U+0177: "ŷ" LATIN SMALL LETTER Y WITH CIRCUMFLEX
+ // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
+ // U+00FE: "þ" LATIN SMALL LETTER THORN
+ /* 8 */ "y,\u00FD,\u0177,\u00FF,\u00FE",
+ // U+00F0: "ð" LATIN SMALL LETTER ETH
+ // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
+ // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE
+ /* 9 */ "\u00F0,\u010F,\u0111",
+ // U+0159: "ř" LATIN SMALL LETTER R WITH CARON
+ // U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE
+ // U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA
+ /* 10 */ "\u0159,\u0155,\u0157",
+ // U+0165: "ť" LATIN SMALL LETTER T WITH CARON
+ // U+021B: "ț" LATIN SMALL LETTER T WITH COMMA BELOW
+ // U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
+ // U+0167: "ŧ" LATIN SMALL LETTER T WITH STROKE
+ /* 11 */ "\u0165,\u021B,\u0163,\u0167",
+ // U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
+ // U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
+ // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
+ /* 12 */ "\u017A,\u017C,\u017E",
+ // U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA
+ // U+0138: "ĸ" LATIN SMALL LETTER KRA
+ /* 13 */ "\u0137,\u0138",
+ // U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE
+ // U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA
+ // U+013E: "ľ" LATIN SMALL LETTER L WITH CARON
+ // U+0140: "ŀ" LATIN SMALL LETTER L WITH MIDDLE DOT
+ // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
+ /* 14 */ "\u013A,\u013C,\u013E,\u0140,\u0142",
+ // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
+ // U+0121: "ġ" LATIN SMALL LETTER G WITH DOT ABOVE
+ // U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA
+ /* 15 */ "\u011F,\u0121,\u0123",
+ // U+0175: "ŵ" LATIN SMALL LETTER W WITH CIRCUMFLEX
+ /* 16 */ "w,\u0175",
+ // U+0125: "ĥ" LATIN SMALL LETTER H WITH CIRCUMFLEX
+ // U+0127: "ħ" LATIN SMALL LETTER H WITH STROKE
+ /* 17 */ "\u0125,\u0127",
+ /* 18 */ null,
+ // U+0175: "ŵ" LATIN SMALL LETTER W WITH CIRCUMFLEX
+ /* 19 */ "w,\u0175",
+ /* 20~ */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null,
+ /* ~105 */
+ /* 106 */ "q",
+ /* 107 */ "x",
+ // U+015D: "ŝ" LATIN SMALL LETTER S WITH CIRCUMFLEX
+ /* 108 */ "\u015D",
+ // U+011D: "ĝ" LATIN SMALL LETTER G WITH CIRCUMFLEX
+ /* 109 */ "\u011D",
+ // U+016D: "ŭ" LATIN SMALL LETTER U WITH BREVE
+ /* 110 */ "\u016D",
+ // U+0109: "ĉ" LATIN SMALL LETTER C WITH CIRCUMFLEX
+ /* 111 */ "\u0109",
+ // U+0135: "ĵ" LATIN SMALL LETTER J WITH CIRCUMFLEX
+ /* 112 */ "\u0135",
+ };
+
/* Language es: Spanish */
private static final String[] LANGUAGE_es = {
// U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
@@ -2696,6 +2848,7 @@ public final class KeyboardTextsSet {
"da", LANGUAGE_da, /* Danish */
"de", LANGUAGE_de, /* German */
"en", LANGUAGE_en, /* English */
+ "eo", LANGUAGE_eo, /* Esperanto */
"es", LANGUAGE_es, /* Spanish */
"et", LANGUAGE_et, /* Estonian */
"fa", LANGUAGE_fa, /* Persian */
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java b/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java
new file mode 100644
index 000000000..f54617c98
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.latin.CollectionUtils;
+
+import java.util.HashMap;
+
+public class KeysCache {
+ private final HashMap<Key, Key> mMap = CollectionUtils.newHashMap();
+
+ public void clear() {
+ mMap.clear();
+ }
+
+ public Key get(final Key key) {
+ final Key existingKey = mMap.get(key);
+ if (existingKey != null) {
+ // Reuse the existing element that equals to "key" without adding "key" to the map.
+ return existingKey;
+ }
+ mMap.put(key, key);
+ return key;
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
new file mode 100644
index 000000000..5da26543f
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.StringUtils;
+
+import java.util.Locale;
+
+public class MoreKeySpec {
+ public final int mCode;
+ public final String mLabel;
+ public final String mOutputText;
+ public final int mIconId;
+
+ public MoreKeySpec(final String moreKeySpec, boolean needsToUpperCase, final Locale locale,
+ final KeyboardCodesSet codesSet) {
+ mLabel = KeySpecParser.toUpperCaseOfStringForLocale(
+ KeySpecParser.getLabel(moreKeySpec), needsToUpperCase, locale);
+ final int code = KeySpecParser.toUpperCaseOfCodeForLocale(
+ KeySpecParser.getCode(moreKeySpec, codesSet), needsToUpperCase, locale);
+ if (code == Keyboard.CODE_UNSPECIFIED) {
+ // Some letter, for example German Eszett (U+00DF: "ß"), has multiple characters
+ // upper case representation ("SS").
+ mCode = Keyboard.CODE_OUTPUT_TEXT;
+ mOutputText = mLabel;
+ } else {
+ mCode = code;
+ mOutputText = KeySpecParser.toUpperCaseOfStringForLocale(
+ KeySpecParser.getOutputText(moreKeySpec), needsToUpperCase, locale);
+ }
+ mIconId = KeySpecParser.getIconId(moreKeySpec);
+ }
+
+ @Override
+ 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));
+ if (StringUtils.codePointCount(label) == 1 && label.codePointAt(0) == mCode) {
+ return output;
+ } else {
+ return label + "|" + output;
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
index 1c7ceaf92..e0858c019 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
@@ -18,6 +18,8 @@ package com.android.inputmethod.keyboard.internal;
import android.util.Log;
+import com.android.inputmethod.latin.CollectionUtils;
+
import java.util.ArrayList;
public class PointerTrackerQueue {
@@ -32,7 +34,7 @@ public class PointerTrackerQueue {
private static final int INITIAL_CAPACITY = 10;
private final ArrayList<Element> mExpandableArrayOfActivePointers =
- new ArrayList<Element>(INITIAL_CAPACITY);
+ CollectionUtils.newArrayList(INITIAL_CAPACITY);
private int mArraySize = 0;
public synchronized int size() {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
index d0fecf060..269b202b5 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
@@ -23,10 +23,13 @@ import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.os.Message;
import android.text.TextUtils;
+import android.util.AttributeSet;
import android.util.SparseArray;
import android.widget.RelativeLayout;
import com.android.inputmethod.keyboard.PointerTracker;
+import com.android.inputmethod.keyboard.internal.GesturePreviewTrail.GesturePreviewTrailParams;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
@@ -46,29 +49,42 @@ public class PreviewPlacerView extends RelativeLayout {
private int mXOrigin;
private int mYOrigin;
- private final SparseArray<PointerTracker> mPointers = new SparseArray<PointerTracker>();
+ private final SparseArray<GesturePreviewTrail> mGesturePreviewTrails =
+ CollectionUtils.newSparseArray();
+ private final GesturePreviewTrailParams mGesturePreviewTrailParams;
private String mGestureFloatingPreviewText;
+ private int mLastPointerX;
+ private int mLastPointerY;
+
private boolean mDrawsGesturePreviewTrail;
private boolean mDrawsGestureFloatingPreviewText;
- private final DrawingHandler mDrawingHandler = new DrawingHandler(this);
+ private final DrawingHandler mDrawingHandler;
private static 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;
+
+ private final GesturePreviewTrailParams mGesturePreviewTrailParams;
- public DrawingHandler(PreviewPlacerView outerInstance) {
+ public DrawingHandler(final PreviewPlacerView outerInstance,
+ final GesturePreviewTrailParams gesturePreviewTrailParams) {
super(outerInstance);
+ mGesturePreviewTrailParams = gesturePreviewTrailParams;
}
@Override
- public void handleMessage(Message msg) {
+ public void handleMessage(final Message msg) {
final PreviewPlacerView placerView = getOuterInstance();
if (placerView == null) return;
switch (msg.what) {
case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT:
placerView.setGestureFloatingPreviewText(null);
break;
+ case MSG_UPDATE_GESTURE_PREVIEW_TRAIL:
+ placerView.invalidate();
+ break;
}
}
@@ -84,15 +100,32 @@ public class PreviewPlacerView extends RelativeLayout {
placerView.mGestureFloatingPreviewTextLingerTimeout);
}
+ private void cancelUpdateGestureTrailPreview() {
+ removeMessages(MSG_UPDATE_GESTURE_PREVIEW_TRAIL);
+ }
+
+ public void postUpdateGestureTrailPreview() {
+ cancelUpdateGestureTrailPreview();
+ sendMessageDelayed(obtainMessage(MSG_UPDATE_GESTURE_PREVIEW_TRAIL),
+ mGesturePreviewTrailParams.mUpdateInterval);
+ }
+
public void cancelAllMessages() {
cancelDismissGestureFloatingPreviewText();
+ cancelUpdateGestureTrailPreview();
}
}
- public PreviewPlacerView(Context context, TypedArray keyboardViewAttr) {
+ public PreviewPlacerView(final Context context, final AttributeSet attrs) {
+ this(context, attrs, R.attr.keyboardViewStyle);
+ }
+
+ public PreviewPlacerView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context);
setWillNotDraw(false);
+ final TypedArray keyboardViewAttr = context.obtainStyledAttributes(
+ attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
final int gestureFloatingPreviewTextSize = keyboardViewAttr.getDimensionPixelSize(
R.styleable.KeyboardView_gestureFloatingPreviewTextSize, 0);
mGestureFloatingPreviewTextColor = keyboardViewAttr.getColor(
@@ -117,6 +150,10 @@ public class PreviewPlacerView extends RelativeLayout {
R.styleable.KeyboardView_gesturePreviewTrailColor, 0);
final int gesturePreviewTrailWidth = keyboardViewAttr.getDimensionPixelSize(
R.styleable.KeyboardView_gesturePreviewTrailWidth, 0);
+ mGesturePreviewTrailParams = new GesturePreviewTrailParams(keyboardViewAttr);
+ keyboardViewAttr.recycle();
+
+ mDrawingHandler = new DrawingHandler(this, mGesturePreviewTrailParams);
mGesturePaint = new Paint();
mGesturePaint.setAntiAlias(true);
@@ -132,48 +169,60 @@ public class PreviewPlacerView extends RelativeLayout {
mTextPaint.setTextSize(gestureFloatingPreviewTextSize);
}
- public void setOrigin(int x, int y) {
+ public void setOrigin(final int x, final int y) {
mXOrigin = x;
mYOrigin = y;
}
- public void setGesturePreviewMode(boolean drawsGesturePreviewTrail,
- boolean drawsGestureFloatingPreviewText) {
+ public void setGesturePreviewMode(final boolean drawsGesturePreviewTrail,
+ final boolean drawsGestureFloatingPreviewText) {
mDrawsGesturePreviewTrail = drawsGesturePreviewTrail;
mDrawsGestureFloatingPreviewText = drawsGestureFloatingPreviewText;
}
- public void invalidatePointer(PointerTracker tracker) {
- synchronized (mPointers) {
- mPointers.put(tracker.mPointerId, tracker);
- // TODO: Should narrow the invalidate region.
- invalidate();
+ public void invalidatePointer(final PointerTracker tracker) {
+ GesturePreviewTrail trail;
+ synchronized (mGesturePreviewTrails) {
+ trail = mGesturePreviewTrails.get(tracker.mPointerId);
+ if (trail == null) {
+ trail = new GesturePreviewTrail(mGesturePreviewTrailParams);
+ mGesturePreviewTrails.put(tracker.mPointerId, trail);
+ }
}
+ trail.addStroke(tracker.getGestureStrokeWithPreviewTrail(), tracker.getDownTime());
+
+ mLastPointerX = tracker.getLastX();
+ mLastPointerY = tracker.getLastY();
+ // TODO: Should narrow the invalidate region.
+ invalidate();
}
@Override
- public void onDraw(Canvas canvas) {
+ public void onDraw(final Canvas canvas) {
super.onDraw(canvas);
- synchronized (mPointers) {
- canvas.translate(mXOrigin, mYOrigin);
- final int trackerCount = mPointers.size();
- boolean hasDrawnFloatingPreviewText = false;
- for (int index = 0; index < trackerCount; index++) {
- final PointerTracker tracker = mPointers.valueAt(index);
- if (mDrawsGesturePreviewTrail) {
- tracker.drawGestureTrail(canvas, mGesturePaint);
- }
- // TODO: Figure out more cleaner way to draw gesture preview text.
- if (mDrawsGestureFloatingPreviewText && !hasDrawnFloatingPreviewText) {
- drawGestureFloatingPreviewText(canvas, tracker, mGestureFloatingPreviewText);
- hasDrawnFloatingPreviewText = true;
+ canvas.translate(mXOrigin, mYOrigin);
+ if (mDrawsGesturePreviewTrail) {
+ 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(canvas, mGesturePaint);
}
}
- canvas.translate(-mXOrigin, -mYOrigin);
+ if (needsUpdatingGesturePreviewTrail) {
+ mDrawingHandler.postUpdateGestureTrailPreview();
+ }
+ }
+ if (mDrawsGestureFloatingPreviewText) {
+ drawGestureFloatingPreviewText(canvas, mGestureFloatingPreviewText);
}
+ canvas.translate(-mXOrigin, -mYOrigin);
}
- public void setGestureFloatingPreviewText(String gestureFloatingPreviewText) {
+ public void setGestureFloatingPreviewText(final String gestureFloatingPreviewText) {
mGestureFloatingPreviewText = gestureFloatingPreviewText;
invalidate();
}
@@ -186,15 +235,17 @@ public class PreviewPlacerView extends RelativeLayout {
mDrawingHandler.cancelAllMessages();
}
- private void drawGestureFloatingPreviewText(Canvas canvas, PointerTracker tracker,
- String gestureFloatingPreviewText) {
+ private void drawGestureFloatingPreviewText(final Canvas canvas,
+ final String gestureFloatingPreviewText) {
if (TextUtils.isEmpty(gestureFloatingPreviewText)) {
return;
}
final Paint paint = mTextPaint;
- final int lastX = tracker.getLastX();
- final int lastY = tracker.getLastY();
+ // TODO: Figure out how we should deal with the floating preview text with multiple moving
+ // fingers.
+ final int lastX = mLastPointerX;
+ final int lastY = mLastPointerY;
final int textSize = (int)paint.getTextSize();
final int canvasWidth = canvas.getWidth();
diff --git a/java/src/com/android/inputmethod/keyboard/internal/SuddenJumpingTouchEventHandler.java b/java/src/com/android/inputmethod/keyboard/internal/SuddenJumpingTouchEventHandler.java
index 9e2cbec52..a591a7ac3 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/SuddenJumpingTouchEventHandler.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/SuddenJumpingTouchEventHandler.java
@@ -24,7 +24,7 @@ import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.MainKeyboardView;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.Utils;
+import com.android.inputmethod.latin.ResourceUtils;
import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.research.ResearchLogger;
@@ -53,7 +53,7 @@ public class SuddenJumpingTouchEventHandler {
public SuddenJumpingTouchEventHandler(Context context, ProcessMotionEvent view) {
mView = view;
- mNeedsSuddenJumpingHack = Boolean.parseBoolean(Utils.getDeviceOverrideValue(
+ mNeedsSuddenJumpingHack = Boolean.parseBoolean(ResourceUtils.getDeviceOverrideValue(
context.getResources(), R.array.sudden_jumping_touch_event_device_list, "false"));
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java b/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java
new file mode 100644
index 000000000..69dc01cd6
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import com.android.inputmethod.latin.LatinImeLogger;
+
+public class TouchPositionCorrection {
+ private static final int TOUCH_POSITION_CORRECTION_RECORD_SIZE = 3;
+
+ public boolean mEnabled;
+ public float[] mXs;
+ public float[] mYs;
+ public float[] mRadii;
+
+ public void load(final String[] data) {
+ final int dataLength = data.length;
+ if (dataLength % TOUCH_POSITION_CORRECTION_RECORD_SIZE != 0) {
+ if (LatinImeLogger.sDBG) {
+ throw new RuntimeException(
+ "the size of touch position correction data is invalid");
+ }
+ return;
+ }
+
+ final int length = dataLength / TOUCH_POSITION_CORRECTION_RECORD_SIZE;
+ mXs = new float[length];
+ mYs = new float[length];
+ mRadii = new float[length];
+ try {
+ for (int i = 0; i < dataLength; ++i) {
+ final int type = i % TOUCH_POSITION_CORRECTION_RECORD_SIZE;
+ final int index = i / TOUCH_POSITION_CORRECTION_RECORD_SIZE;
+ final float value = Float.parseFloat(data[i]);
+ if (type == 0) {
+ mXs[index] = value;
+ } else if (type == 1) {
+ mYs[index] = value;
+ } else {
+ mRadii[index] = value;
+ }
+ }
+ } catch (NumberFormatException e) {
+ if (LatinImeLogger.sDBG) {
+ throw new RuntimeException(
+ "the number format for touch position correction data is invalid");
+ }
+ mXs = null;
+ mYs = null;
+ mRadii = null;
+ }
+ }
+
+ // TODO: Remove this method.
+ 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;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
index f8f1395b3..4b47a261f 100644
--- a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
+++ b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
@@ -91,7 +91,7 @@ public class AdditionalSubtype {
}
final String[] prefSubtypeArray = prefSubtypes.split(PREF_SUBTYPE_SEPARATOR);
final ArrayList<InputMethodSubtype> subtypesList =
- new ArrayList<InputMethodSubtype>(prefSubtypeArray.length);
+ CollectionUtils.newArrayList(prefSubtypeArray.length);
for (final String prefSubtype : prefSubtypeArray) {
final InputMethodSubtype subtype = createAdditionalSubtype(prefSubtype);
if (subtype.getNameResId() == SubtypeLocale.UNKNOWN_KEYBOARD_LAYOUT) {
diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
index 779a38823..d01592a4d 100644
--- a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
+++ b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
@@ -89,7 +89,7 @@ public class AdditionalSubtypeSettings extends PreferenceFragment {
super(context, android.R.layout.simple_spinner_item);
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- final TreeSet<SubtypeLocaleItem> items = new TreeSet<SubtypeLocaleItem>();
+ final TreeSet<SubtypeLocaleItem> items = CollectionUtils.newTreeSet();
final InputMethodInfo imi = ImfUtils.getInputMethodInfoOfThisIme(context);
final int count = imi.getSubtypeCount();
for (int i = 0; i < count; i++) {
@@ -533,7 +533,7 @@ public class AdditionalSubtypeSettings extends PreferenceFragment {
private InputMethodSubtype[] getSubtypes() {
final PreferenceGroup group = getPreferenceScreen();
- final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
+ final ArrayList<InputMethodSubtype> subtypes = CollectionUtils.newArrayList();
final int count = group.getPreferenceCount();
for (int i = 0; i < count; i++) {
final Preference pref = group.getPreference(i);
diff --git a/java/src/com/android/inputmethod/latin/AutoCorrection.java b/java/src/com/android/inputmethod/latin/AutoCorrection.java
index 048166807..01ba30077 100644
--- a/java/src/com/android/inputmethod/latin/AutoCorrection.java
+++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java
@@ -39,7 +39,6 @@ public class AutoCorrection {
}
final CharSequence lowerCasedWord = word.toString().toLowerCase();
for (final String key : dictionaries.keySet()) {
- if (key.equals(Dictionary.TYPE_WHITELIST)) continue;
final Dictionary dictionary = dictionaries.get(key);
// It's unclear how realistically 'dictionary' can be null, but the monkey is somehow
// managing to get null in here. Presumably the language is changing to a language with
@@ -64,7 +63,6 @@ public class AutoCorrection {
}
int maxFreq = -1;
for (final String key : dictionaries.keySet()) {
- if (key.equals(Dictionary.TYPE_WHITELIST)) continue;
final Dictionary dictionary = dictionaries.get(key);
if (null == dictionary) continue;
final int tempFreq = dictionary.getFrequency(word);
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index f0f5cd320..8909526d8 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -18,6 +18,7 @@ package com.android.inputmethod.latin;
import android.content.Context;
import android.text.TextUtils;
+import android.util.SparseArray;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
@@ -51,6 +52,7 @@ public class BinaryDictionary extends Dictionary {
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];
@@ -59,7 +61,25 @@ public class BinaryDictionary extends Dictionary {
private final int[] mOutputTypes = new int[MAX_RESULTS];
private final boolean mUseFullEditDistance;
- private final DicTraverseSession mDicTraverseSession;
+
+ private final SparseArray<DicTraverseSession> mDicTraverseSessions =
+ CollectionUtils.newSparseArray();
+
+ // TODO: There should be a way to remove used DicTraverseSession objects from
+ // {@code mDicTraverseSessions}.
+ private DicTraverseSession getTraverseSession(int traverseSessionId) {
+ synchronized(mDicTraverseSessions) {
+ DicTraverseSession traverseSession = mDicTraverseSessions.get(traverseSessionId);
+ if (traverseSession == null) {
+ traverseSession = mDicTraverseSessions.get(traverseSessionId);
+ if (traverseSession == null) {
+ traverseSession = new DicTraverseSession(mLocale, mNativeDict);
+ mDicTraverseSessions.put(traverseSessionId, traverseSession);
+ }
+ }
+ return traverseSession;
+ }
+ }
/**
* Constructor for the binary dictionary. This is supposed to be called from the
@@ -76,10 +96,9 @@ public class BinaryDictionary extends Dictionary {
final String filename, final long offset, final long length,
final boolean useFullEditDistance, final Locale locale, final String dictType) {
super(dictType);
+ mLocale = locale;
mUseFullEditDistance = useFullEditDistance;
loadDictionary(filename, offset, length);
- mDicTraverseSession = new DicTraverseSession(locale);
- mDicTraverseSession.initSession(mNativeDict);
}
static {
@@ -109,8 +128,15 @@ public class BinaryDictionary extends Dictionary {
@Override
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
final CharSequence 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) {
if (!isValidDictionary()) return null;
- Arrays.fill(mInputCodePoints, WordComposer.NOT_A_CODE);
+
+ Arrays.fill(mInputCodePoints, Constants.NOT_A_CODE);
// TODO: toLowerCase in the native code
final int[] prevWordCodePointArray = (null == prevWord)
? null : StringUtils.toCodePointArray(prevWord.toString());
@@ -128,18 +154,18 @@ public class BinaryDictionary extends Dictionary {
final int codesSize = isGesture ? ips.getPointerSize() : composerSize;
// proximityInfo and/or prevWordForBigrams may not be null.
final int tmpCount = getSuggestionsNative(mNativeDict,
- proximityInfo.getNativeProximityInfo(), mDicTraverseSession.getSession(),
+ proximityInfo.getNativeProximityInfo(), getTraverseSession(sessionId).getSession(),
ips.getXCoordinates(), ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(),
mInputCodePoints, codesSize, 0 /* commitPoint */, isGesture, prevWordCodePointArray,
mUseFullEditDistance, mOutputChars, mOutputScores, mSpaceIndices, mOutputTypes);
final int count = Math.min(tmpCount, MAX_PREDICTIONS);
- final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>();
+ final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
for (int j = 0; j < count; ++j) {
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 && mOutputChars[start + len] != 0) {
++len;
}
if (len > 0) {
@@ -186,12 +212,20 @@ public class BinaryDictionary extends Dictionary {
}
@Override
- public synchronized void close() {
- mDicTraverseSession.close();
+ public void close() {
+ synchronized (mDicTraverseSessions) {
+ final int sessionsSize = mDicTraverseSessions.size();
+ for (int index = 0; index < sessionsSize; ++index) {
+ final DicTraverseSession traverseSession = mDicTraverseSessions.valueAt(index);
+ if (traverseSession != null) {
+ traverseSession.close();
+ }
+ }
+ }
closeInternal();
}
- private void closeInternal() {
+ private synchronized void closeInternal() {
if (mNativeDict != 0) {
closeNative(mNativeDict);
mNativeDict = 0;
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index 236c198ad..799aea8ef 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -99,7 +99,7 @@ public class BinaryDictionaryFileDumper {
}
try {
- final List<WordListInfo> list = new ArrayList<WordListInfo>();
+ final List<WordListInfo> list = CollectionUtils.newArrayList();
do {
final String wordListId = c.getString(0);
final String wordListLocale = c.getString(1);
@@ -267,7 +267,7 @@ public class BinaryDictionaryFileDumper {
final ContentResolver resolver = context.getContentResolver();
final List<WordListInfo> idList = getWordListWordListInfos(locale, context,
hasDefaultWordList);
- final List<AssetFileAddress> fileAddressList = new ArrayList<AssetFileAddress>();
+ final List<AssetFileAddress> fileAddressList = CollectionUtils.newArrayList();
for (WordListInfo id : idList) {
final AssetFileAddress afd = cacheWordList(id.mId, id.mLocale, resolver, context);
if (null != afd) {
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index 063243e1b..e1cb195bc 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -16,6 +16,8 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
+
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -23,6 +25,10 @@ import android.content.res.AssetFileDescriptor;
import android.util.Log;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
@@ -51,6 +57,9 @@ class BinaryDictionaryGetter {
private static final String MAIN_DICTIONARY_CATEGORY = "main";
public static final String ID_CATEGORY_SEPARATOR = ":";
+ // The key considered to read the version attribute in a dictionary file.
+ private static String VERSION_KEY = "version";
+
// Prevents this from being instantiated
private BinaryDictionaryGetter() {}
@@ -254,8 +263,7 @@ class BinaryDictionaryGetter {
final Context context) {
final File[] directoryList = getCachedDirectoryList(context);
if (null == directoryList) return EMPTY_FILE_ARRAY;
- final HashMap<String, FileAndMatchLevel> cacheFiles =
- new HashMap<String, FileAndMatchLevel>();
+ final HashMap<String, FileAndMatchLevel> cacheFiles = CollectionUtils.newHashMap();
for (File directory : directoryList) {
if (!directory.isDirectory()) continue;
final String dirLocale = getWordListIdFromFileName(directory.getName());
@@ -336,6 +344,54 @@ class BinaryDictionaryGetter {
return MAIN_DICTIONARY_CATEGORY.equals(idArray[0]);
}
+ // ## HACK ## we prevent usage of a dictionary before version 18 for English only. The reason
+ // for this is, since those do not include whitelist entries, the new code with an old version
+ // 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 ##
+ if (!Locale.ENGLISH.getLanguage().equals(locale.getLanguage())) return true;
+
+ FileInputStream inStream = null;
+ try {
+ // Read the version of the file
+ inStream = new FileInputStream(f);
+ final ByteBuffer buffer = inStream.getChannel().map(
+ FileChannel.MapMode.READ_ONLY, 0, f.length());
+ final int magic = buffer.getInt();
+ if (magic != BinaryDictInputOutput.VERSION_2_MAGIC_NUMBER) {
+ return false;
+ }
+ final int formatVersion = buffer.getInt();
+ final int headerSize = buffer.getInt();
+ final HashMap<String, String> options = CollectionUtils.newHashMap();
+ BinaryDictInputOutput.populateOptions(buffer, headerSize, options);
+
+ final String version = options.get(VERSION_KEY);
+ if (null == version) {
+ // No version in the options : the format is unexpected
+ return false;
+ }
+ // Version 18 is the first one to include the whitelist
+ // Obviously this is a big ## HACK ##
+ return Integer.parseInt(version) >= 18;
+ } catch (java.io.FileNotFoundException e) {
+ return false;
+ } catch (java.io.IOException e) {
+ return false;
+ } catch (NumberFormatException e) {
+ return false;
+ } finally {
+ if (inStream != null) {
+ try {
+ inStream.close();
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
+ }
+ }
+
/**
* Returns a list of file addresses for a given locale, trying relevant methods in order.
*
@@ -362,18 +418,19 @@ class BinaryDictionaryGetter {
final DictPackSettings dictPackSettings = new DictPackSettings(context);
boolean foundMainDict = false;
- final ArrayList<AssetFileAddress> fileList = new ArrayList<AssetFileAddress>();
+ final ArrayList<AssetFileAddress> fileList = CollectionUtils.newArrayList();
// cachedWordLists may not be null, see doc for getCachedDictionaryList
for (final File f : cachedWordLists) {
final String wordListId = getWordListIdFromFileName(f.getName());
- if (isMainWordListId(wordListId)) {
+ final boolean canUse = f.canRead() && hackCanUseDictionaryFile(locale, f);
+ if (canUse && isMainWordListId(wordListId)) {
foundMainDict = true;
}
if (!dictPackSettings.isWordListActive(wordListId)) continue;
- if (f.canRead()) {
+ if (canUse) {
fileList.add(AssetFileAddress.makeFromFileName(f.getPath()));
} else {
- Log.e(TAG, "Found a cached dictionary file but cannot read it");
+ Log.e(TAG, "Found a cached dictionary file but cannot read or use it");
}
}
diff --git a/java/src/com/android/inputmethod/latin/CollectionUtils.java b/java/src/com/android/inputmethod/latin/CollectionUtils.java
new file mode 100644
index 000000000..c75f2df5c
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/CollectionUtils.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public final class CollectionUtils {
+ private CollectionUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static <K,V> HashMap<K,V> newHashMap() {
+ return new HashMap<K,V>();
+ }
+
+ public static <K,V> TreeMap<K,V> newTreeMap() {
+ return new TreeMap<K,V>();
+ }
+
+ public static <K, V> Map<K,V> newSynchronizedTreeMap() {
+ final TreeMap<K,V> treeMap = newTreeMap();
+ return Collections.synchronizedMap(treeMap);
+ }
+
+ public static <K,V> ConcurrentHashMap<K,V> newConcurrentHashMap() {
+ return new ConcurrentHashMap<K,V>();
+ }
+
+ public static <E> HashSet<E> newHashSet() {
+ return new HashSet<E>();
+ }
+
+ public static <E> TreeSet<E> newTreeSet() {
+ return new TreeSet<E>();
+ }
+
+ public static <E> ArrayList<E> newArrayList() {
+ return new ArrayList<E>();
+ }
+
+ public static <E> ArrayList<E> newArrayList(final int initialCapacity) {
+ return new ArrayList<E>(initialCapacity);
+ }
+
+ public static <E> ArrayList<E> newArrayList(final Collection<E> collection) {
+ return new ArrayList<E>(collection);
+ }
+
+ public static <E> LinkedList<E> newLinkedList() {
+ return new LinkedList<E>();
+ }
+
+ public static <E> CopyOnWriteArrayList<E> newCopyOnWriteArrayList() {
+ return new CopyOnWriteArrayList<E>();
+ }
+
+ public static <E> CopyOnWriteArrayList<E> newCopyOnWriteArrayList(
+ final Collection<E> collection) {
+ return new CopyOnWriteArrayList<E>(collection);
+ }
+
+ public static <E> CopyOnWriteArrayList<E> newCopyOnWriteArrayList(final E[] array) {
+ return new CopyOnWriteArrayList<E>(array);
+ }
+
+ public static <E> SparseArray<E> newSparseArray() {
+ return new SparseArray<E>();
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index 1242967ad..d71c0f995 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -128,6 +128,13 @@ 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;
+
private Constants() {
// This utility class is not publicly instantiable.
}
diff --git a/java/src/com/android/inputmethod/latin/DicTraverseSession.java b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
index c76815363..359da72cc 100644
--- a/java/src/com/android/inputmethod/latin/DicTraverseSession.java
+++ b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
@@ -22,6 +22,7 @@ public class DicTraverseSession {
static {
JniUtils.loadNativeLibrary();
}
+
private native long setDicTraverseSessionNative(String locale);
private native void initDicTraverseSessionNative(long nativeDicTraverseSession,
long dictionary, int[] previousWord, int previousWordLength);
@@ -29,9 +30,10 @@ public class DicTraverseSession {
private long mNativeDicTraverseSession;
- public DicTraverseSession(Locale locale) {
+ public DicTraverseSession(Locale locale, long dictionary) {
mNativeDicTraverseSession = createNativeDicTraverseSession(
locale != null ? locale.toString() : "");
+ initSession(dictionary);
}
public long getSession() {
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index 60fe17b19..88d0c09dd 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -42,7 +42,6 @@ public abstract class Dictionary {
public static final String TYPE_USER = "user";
// User history dictionary internal to LatinIME.
public static final String TYPE_USER_HISTORY = "history";
- public static final String TYPE_WHITELIST ="whitelist";
protected final String mDictType;
public Dictionary(final String dictType) {
@@ -62,6 +61,13 @@ public abstract class Dictionary {
abstract public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
final CharSequence 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) {
+ return getSuggestions(composer, prevWord, proximityInfo);
+ }
+
/**
* Checks if the given word occurs in the dictionary
* @param word the word to search for. The search should be case-insensitive.
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
index ee80f2532..4acab6b05 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -35,22 +35,22 @@ public class DictionaryCollection extends Dictionary {
public DictionaryCollection(final String dictType) {
super(dictType);
- mDictionaries = new CopyOnWriteArrayList<Dictionary>();
+ mDictionaries = CollectionUtils.newCopyOnWriteArrayList();
}
public DictionaryCollection(final String dictType, Dictionary... dictionaries) {
super(dictType);
if (null == dictionaries) {
- mDictionaries = new CopyOnWriteArrayList<Dictionary>();
+ mDictionaries = CollectionUtils.newCopyOnWriteArrayList();
} else {
- mDictionaries = new CopyOnWriteArrayList<Dictionary>(dictionaries);
+ mDictionaries = CollectionUtils.newCopyOnWriteArrayList(dictionaries);
mDictionaries.removeAll(Collections.singleton(null));
}
}
public DictionaryCollection(final String dictType, Collection<Dictionary> dictionaries) {
super(dictType);
- mDictionaries = new CopyOnWriteArrayList<Dictionary>(dictionaries);
+ mDictionaries = CollectionUtils.newCopyOnWriteArrayList(dictionaries);
mDictionaries.removeAll(Collections.singleton(null));
}
@@ -63,7 +63,7 @@ public class DictionaryCollection extends Dictionary {
// dictionary and add the rest to it if not null, hence the get(0)
ArrayList<SuggestedWordInfo> suggestions = dictionaries.get(0).getSuggestions(composer,
prevWord, proximityInfo);
- if (null == suggestions) suggestions = new ArrayList<SuggestedWordInfo>();
+ if (null == suggestions) suggestions = CollectionUtils.newArrayList();
final int length = dictionaries.size();
for (int i = 1; i < length; ++ i) {
final ArrayList<SuggestedWordInfo> sugg = dictionaries.get(i).getSuggestions(composer,
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index 06a5f4b72..cdd01d0c7 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -53,7 +53,7 @@ public class DictionaryFactory {
createBinaryDictionary(context, locale));
}
- final LinkedList<Dictionary> dictList = new LinkedList<Dictionary>();
+ final LinkedList<Dictionary> dictList = CollectionUtils.newLinkedList();
final ArrayList<AssetFileAddress> assetFileList =
BinaryDictionaryGetter.getDictionaryFiles(locale, context);
if (null != assetFileList) {
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 016530abb..8a509be48 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -62,7 +62,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
* that filename.
*/
private static final HashMap<String, DictionaryController> sSharedDictionaryControllers =
- new HashMap<String, DictionaryController>();
+ CollectionUtils.newHashMap();
/** The application context. */
protected final Context mContext;
@@ -159,9 +159,9 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
* the native side.
*/
public void clearFusionDictionary() {
+ final HashMap<String, String> attributes = CollectionUtils.newHashMap();
mFusionDictionary = new FusionDictionary(new Node(),
- new FusionDictionary.DictionaryOptions(new HashMap<String, String>(), false,
- false));
+ new FusionDictionary.DictionaryOptions(attributes, false, false));
}
/**
@@ -172,12 +172,12 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
// considering performance regression.
protected void addWord(final String word, final String shortcutTarget, final int frequency) {
if (shortcutTarget == null) {
- mFusionDictionary.add(word, frequency, null);
+ mFusionDictionary.add(word, frequency, null, false /* isNotAWord */);
} else {
// TODO: Do this in the subclass, with this class taking an arraylist.
- final ArrayList<WeightedString> shortcutTargets = new ArrayList<WeightedString>();
+ final ArrayList<WeightedString> shortcutTargets = CollectionUtils.newArrayList();
shortcutTargets.add(new WeightedString(shortcutTarget, frequency));
- mFusionDictionary.add(word, frequency, shortcutTargets);
+ mFusionDictionary.add(word, frequency, shortcutTargets, false /* isNotAWord */);
}
}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index d101aaf15..8a38d1e1b 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.KeyDetector;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
@@ -231,7 +230,7 @@ public class ExpandableDictionary extends Dictionary {
childNode.mTerminal = true;
if (isShortcutOnly) {
if (null == childNode.mShortcutTargets) {
- childNode.mShortcutTargets = new ArrayList<char[]>();
+ childNode.mShortcutTargets = CollectionUtils.newArrayList();
}
childNode.mShortcutTargets.add(shortcutTarget.toCharArray());
} else {
@@ -251,7 +250,7 @@ public class ExpandableDictionary extends Dictionary {
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
final CharSequence prevWord, final ProximityInfo proximityInfo) {
if (reloadDictionaryIfRequired()) return null;
- if (composer.size() <= 1) {
+ if (composer.size() > 1) {
if (composer.size() >= BinaryDictionary.MAX_WORD_LENGTH) {
return null;
}
@@ -260,7 +259,7 @@ public class ExpandableDictionary extends Dictionary {
return suggestions;
} else {
if (TextUtils.isEmpty(prevWord)) return null;
- final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>();
+ final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
runBigramReverseLookUp(prevWord, suggestions);
return suggestions;
}
@@ -279,7 +278,7 @@ public class ExpandableDictionary extends Dictionary {
protected ArrayList<SuggestedWordInfo> getWordsInner(final WordComposer codes,
final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) {
- final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>();
+ final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
mInputLength = codes.size();
if (mCodes.length < mInputLength) mCodes = new int[mInputLength][];
final InputPointers ips = codes.getInputPointers();
@@ -292,9 +291,9 @@ public class ExpandableDictionary extends Dictionary {
mCodes[i] = new int[ProximityInfo.MAX_PROXIMITY_CHARS_SIZE];
}
final int x = xCoordinates != null && i < xCoordinates.length ?
- xCoordinates[i] : WordComposer.NOT_A_COORDINATE;
+ xCoordinates[i] : Constants.NOT_A_COORDINATE;
final int y = xCoordinates != null && i < yCoordinates.length ?
- yCoordinates[i] : WordComposer.NOT_A_COORDINATE;
+ yCoordinates[i] : Constants.NOT_A_COORDINATE;
proximityInfo.fillArrayWithNearestKeyCodes(x, y, codes.getCodeAt(i), mCodes[i]);
}
mMaxDepth = mInputLength * 3;
@@ -487,7 +486,7 @@ public class ExpandableDictionary extends Dictionary {
for (int j = 0; j < alternativesSize; j++) {
final int addedAttenuation = (j > 0 ? 1 : 2);
final int currentChar = currentChars[j];
- if (currentChar == KeyDetector.NOT_A_CODE) {
+ if (currentChar == Constants.NOT_A_CODE) {
break;
}
if (currentChar == lowerC || currentChar == c) {
@@ -551,7 +550,7 @@ public class ExpandableDictionary extends Dictionary {
Node secondWord = searchWord(mRoots, word2, 0, null);
LinkedList<NextWord> bigrams = firstWord.mNGrams;
if (bigrams == null || bigrams.size() == 0) {
- firstWord.mNGrams = new LinkedList<NextWord>();
+ firstWord.mNGrams = CollectionUtils.newLinkedList();
bigrams = firstWord.mNGrams;
} else {
for (NextWord nw : bigrams) {
diff --git a/java/src/com/android/inputmethod/latin/ImfUtils.java b/java/src/com/android/inputmethod/latin/ImfUtils.java
index 1461c0240..2674e4575 100644
--- a/java/src/com/android/inputmethod/latin/ImfUtils.java
+++ b/java/src/com/android/inputmethod/latin/ImfUtils.java
@@ -29,7 +29,7 @@ import java.util.List;
/**
* Utility class for Input Method Framework
*/
-public class ImfUtils {
+public final class ImfUtils {
private ImfUtils() {
// This utility class is not publicly instantiable.
}
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
index e561f5956..7bcda9bc4 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -29,10 +29,12 @@ public class InputAttributes {
final public boolean mInputTypeNoAutoCorrect;
final public boolean mIsSettingsSuggestionStripOn;
final public boolean mApplicationSpecifiedCompletionOn;
+ final private int mInputType;
public InputAttributes(final EditorInfo editorInfo, final boolean isFullscreenMode) {
final int inputType = null != editorInfo ? editorInfo.inputType : 0;
final int inputClass = inputType & InputType.TYPE_MASK_CLASS;
+ mInputType = inputType;
if (inputClass != InputType.TYPE_CLASS_TEXT) {
// If we are not looking at a TYPE_CLASS_TEXT field, the following strange
// cases may arise, so we do a couple sanity checks for them. If it's a
@@ -93,6 +95,10 @@ public class InputAttributes {
}
}
+ public boolean isSameInputType(final EditorInfo editorInfo) {
+ return editorInfo.inputType == mInputType;
+ }
+
@SuppressWarnings("unused")
private void dumpFlags(final int inputType) {
Log.i(TAG, "Input class:");
diff --git a/java/src/com/android/inputmethod/latin/InputPointers.java b/java/src/com/android/inputmethod/latin/InputPointers.java
index cbc916a7e..ff2feb51d 100644
--- a/java/src/com/android/inputmethod/latin/InputPointers.java
+++ b/java/src/com/android/inputmethod/latin/InputPointers.java
@@ -93,7 +93,7 @@ public class InputPointers {
}
mXCoordinates.append(xCoordinates, startPos, length);
mYCoordinates.append(yCoordinates, startPos, length);
- mPointerIds.fill(pointerId, startPos, length);
+ mPointerIds.fill(pointerId, mPointerIds.getLength(), length);
mTimes.append(times, startPos, length);
}
@@ -124,4 +124,10 @@ public class InputPointers {
public int[] getTimes() {
return mTimes.getPrimitiveArray();
}
+
+ @Override
+ public String toString() {
+ return "size=" + getPointerSize() + " id=" + mPointerIds + " time=" + mTimes
+ + " x=" + mXCoordinates + " y=" + mYCoordinates;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/InputTypeUtils.java b/java/src/com/android/inputmethod/latin/InputTypeUtils.java
index 40c3b765e..500866a13 100644
--- a/java/src/com/android/inputmethod/latin/InputTypeUtils.java
+++ b/java/src/com/android/inputmethod/latin/InputTypeUtils.java
@@ -18,7 +18,7 @@ package com.android.inputmethod.latin;
import android.text.InputType;
-public class InputTypeUtils implements InputType {
+public final class InputTypeUtils implements InputType {
private static final int WEB_TEXT_PASSWORD_INPUT_TYPE =
TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_WEB_PASSWORD;
private static final int WEB_TEXT_EMAIL_ADDRESS_INPUT_TYPE =
diff --git a/java/src/com/android/inputmethod/latin/JniUtils.java b/java/src/com/android/inputmethod/latin/JniUtils.java
index 86a3826d8..f9305991a 100644
--- a/java/src/com/android/inputmethod/latin/JniUtils.java
+++ b/java/src/com/android/inputmethod/latin/JniUtils.java
@@ -20,7 +20,7 @@ import android.util.Log;
import com.android.inputmethod.latin.define.JniLibName;
-public class JniUtils {
+public final class JniUtils {
private static final String TAG = JniUtils.class.getSimpleName();
private JniUtils() {
diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java
index bb39ce4f7..dd73a978c 100644
--- a/java/src/com/android/inputmethod/latin/LastComposedWord.java
+++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java
@@ -38,12 +38,12 @@ public class LastComposedWord {
// an auto-correction.
public static final int COMMIT_TYPE_CANCEL_AUTO_CORRECT = 3;
- public static final int NOT_A_SEPARATOR = -1;
+ public static final String NOT_A_SEPARATOR = "";
public final int[] mPrimaryKeyCodes;
public final String mTypedWord;
public final String mCommittedWord;
- public final int mSeparatorCode;
+ public final String mSeparatorString;
public final CharSequence mPrevWord;
public final InputPointers mInputPointers = new InputPointers(BinaryDictionary.MAX_WORD_LENGTH);
@@ -56,14 +56,14 @@ 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 int separatorCode, final CharSequence prevWord) {
+ final String separatorString, final CharSequence prevWord) {
mPrimaryKeyCodes = primaryKeyCodes;
if (inputPointers != null) {
mInputPointers.copy(inputPointers);
}
mTypedWord = typedWord;
mCommittedWord = committedWord;
- mSeparatorCode = separatorCode;
+ mSeparatorString = separatorString;
mActive = true;
mPrevWord = prevWord;
}
@@ -80,7 +80,7 @@ public class LastComposedWord {
return TextUtils.equals(mTypedWord, mCommittedWord);
}
- public static int getSeparatorLength(final int separatorCode) {
- return NOT_A_SEPARATOR == separatorCode ? 0 : 1;
+ public static int getSeparatorLength(final String separatorString) {
+ return StringUtils.codePointCount(separatorString);
}
}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 446d44e7a..39c3a808f 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -361,7 +361,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mPrefs = prefs;
LatinImeLogger.init(this, prefs);
if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.getInstance().init(this, prefs, mKeyboardSwitcher);
+ ResearchLogger.getInstance().init(this, prefs);
}
InputMethodManagerCompatWrapper.init(this);
SubtypeSwitcher.init(this);
@@ -381,18 +381,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
ImfUtils.setAdditionalInputMethodSubtypes(this, mCurrentSettings.getAdditionalSubtypes());
- Utils.GCUtils.getInstance().reset();
- boolean tryGC = true;
- // Shouldn't this be removed? I think that from Honeycomb on, the GC is now actually working
- // as expected and this code is useless.
- for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
- try {
- initSuggest();
- tryGC = false;
- } catch (OutOfMemoryError e) {
- tryGC = Utils.GCUtils.getInstance().tryGCOrWait("InitSuggest", e);
- }
- }
+ initSuggest();
mDisplayOrientation = res.getConfiguration().orientation;
@@ -416,7 +405,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
// Has to be package-visible for unit tests
- /* package */ void loadSettings() {
+ /* package for test */
+ 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.
if (null == mPrefs) mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
@@ -540,7 +530,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public void onConfigurationChanged(Configuration conf) {
- mSubtypeSwitcher.onConfigurationChanged(conf);
+ // System locale has been changed. Needs to reload keyboard.
+ if (mSubtypeSwitcher.onConfigurationChanged(conf, this)) {
+ loadKeyboard();
+ }
// If orientation changed while predicting, commit the change
if (mDisplayOrientation != conf.orientation) {
mDisplayOrientation = conf.orientation;
@@ -607,6 +600,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// 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.
mSubtypeSwitcher.updateSubtype(subtype);
+ loadKeyboard();
}
private void onStartInputInternal(EditorInfo editorInfo, boolean restarting) {
@@ -670,8 +664,20 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
accessUtils.onStartInputViewInternal(mainKeyboardView, editorInfo, restarting);
}
- if (!restarting) {
- mSubtypeSwitcher.updateParametersOnStartInputView();
+ final boolean selectionChanged = mLastSelectionStart != editorInfo.initialSelStart
+ || mLastSelectionEnd != editorInfo.initialSelEnd;
+ final boolean inputTypeChanged = !mCurrentSettings.isSameInputType(editorInfo);
+ final boolean isDifferentTextField = !restarting || inputTypeChanged;
+ if (isDifferentTextField) {
+ final boolean currentSubtypeEnabled = mSubtypeSwitcher
+ .updateParametersOnStartInputViewAndReturnIfCurrentSubtypeEnabled();
+ if (!currentSubtypeEnabled) {
+ // Current subtype is disabled. Needs to update subtype and keyboard.
+ final InputMethodSubtype newSubtype = ImfUtils.getCurrentInputMethodSubtype(
+ this, mSubtypeSwitcher.getNoLanguageSubtype());
+ mSubtypeSwitcher.updateSubtype(newSubtype);
+ loadKeyboard();
+ }
}
// The EditorInfo might have a flag that affects fullscreen mode.
@@ -679,9 +685,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
updateFullscreenMode();
mApplicationSpecifiedCompletions = null;
- final boolean selectionChanged = mLastSelectionStart != editorInfo.initialSelStart
- || mLastSelectionEnd != editorInfo.initialSelEnd;
- if (!restarting || selectionChanged) {
+ if (isDifferentTextField || selectionChanged) {
// If the selection changed, we reset the input state. Essentially, we come here with
// restarting == true when the app called setText() or similar. We should reset the
// state if the app set the text to something else, but keep it if it set a suggestion
@@ -696,7 +700,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
- if (!restarting) {
+ if (isDifferentTextField) {
mainKeyboardView.closing();
loadSettings();
@@ -905,13 +909,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
}
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_onDisplayCompletions(applicationSpecifiedCompletions);
- }
if (!mCurrentSettings.isApplicationSpecifiedCompletionsOn()) return;
mApplicationSpecifiedCompletions = applicationSpecifiedCompletions;
if (applicationSpecifiedCompletions == null) {
clearSuggestionStrip();
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_onDisplayCompletions(null);
+ }
return;
}
@@ -933,6 +937,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// this case? This says to keep whatever the user typed.
mWordComposer.setAutoCorrection(mWordComposer.getTypedWord());
setSuggestionStripShown(true);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_onDisplayCompletions(applicationSpecifiedCompletions);
+ }
}
private void setSuggestionStripShownInternal(boolean shown, boolean needsInputViewShown) {
@@ -1048,18 +1055,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
}
- private void commitTyped(final int separatorCode) {
+ private void commitTyped(final String separatorString) {
if (!mWordComposer.isComposingWord()) return;
final CharSequence typedWord = mWordComposer.getTypedWord();
if (typedWord.length() > 0) {
mConnection.commitText(typedWord, 1);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_commitText(typedWord);
- }
final CharSequence prevWord = addToUserHistoryDictionary(typedWord);
mLastComposedWord = mWordComposer.commitWord(
LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, typedWord.toString(),
- separatorCode, prevWord);
+ separatorString, prevWord);
}
}
@@ -1091,18 +1095,27 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return mConnection.getCursorCapsMode(inputType);
}
+ // 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 auto = getCurrentAutoCapsState();
+ if (0 != (auto & TextUtils.CAP_MODE_CHARACTERS)) {
+ return WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED;
+ }
+ if (0 != auto) return WordComposer.CAPS_MODE_AUTO_SHIFTED;
+ return WordComposer.CAPS_MODE_OFF;
+ }
+
private void swapSwapperAndSpace() {
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) {
mConnection.deleteSurroundingText(2, 0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(2);
- }
mConnection.commitText(lastTwo.charAt(1) + " ", 1);
if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_swapSwapperAndSpaceWhileInBatchEdit();
+ ResearchLogger.latinIME_swapSwapperAndSpace();
}
mKeyboardSwitcher.updateShiftState();
}
@@ -1119,9 +1132,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mHandler.cancelDoubleSpacesTimer();
mConnection.deleteSurroundingText(2, 0);
mConnection.commitText(". ", 1);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_doubleSpaceAutoPeriod();
- }
mKeyboardSwitcher.updateShiftState();
return true;
}
@@ -1185,9 +1195,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private void performEditorAction(int actionId) {
mConnection.performEditorAction(actionId);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_performEditorAction(actionId);
- }
}
private void handleLanguageSwitchKey() {
@@ -1224,6 +1231,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}.
if (code >= '0' && code <= '9') {
super.sendKeyChar((char)code);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_sendKeyCodePoint(code);
+ }
return;
}
@@ -1240,9 +1250,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final String text = new String(new int[] { code }, 0, 1);
mConnection.commitText(text, text.length());
}
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_sendKeyCodePoint(code);
- }
}
// Implementation of {@link KeyboardActionListener}.
@@ -1254,11 +1261,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
mLastKeyTime = when;
mConnection.beginBatchEdit();
-
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_onCodeInput(primaryCode, x, y);
- }
-
final KeyboardSwitcher switcher = mKeyboardSwitcher;
// The space state depends only on the last character pressed and its own previous
// state. Here, we revert the space state to neutral if the key is actually modifying
@@ -1291,7 +1293,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
onSettingsKeyPressed();
break;
case Keyboard.CODE_SHORTCUT:
- mSubtypeSwitcher.switchToShortcutIME();
+ mSubtypeSwitcher.switchToShortcutIME(this);
break;
case Keyboard.CODE_ACTION_ENTER:
performEditorAction(getActionId(switcher.getKeyboard()));
@@ -1307,7 +1309,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
break;
case Keyboard.CODE_RESEARCH:
if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.getInstance().presentResearchDialog(this);
+ ResearchLogger.getInstance().onResearchKeySelected(this);
}
break;
default:
@@ -1324,8 +1326,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
keyX = x;
keyY = y;
} else {
- keyX = NOT_A_TOUCH_COORDINATE;
- keyY = NOT_A_TOUCH_COORDINATE;
+ keyX = Constants.NOT_A_COORDINATE;
+ keyY = Constants.NOT_A_COORDINATE;
}
handleCharacter(primaryCode, keyX, keyY, spaceState);
}
@@ -1338,45 +1340,49 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (!didAutoCorrect && primaryCode != Keyboard.CODE_SHIFT
&& primaryCode != Keyboard.CODE_SWITCH_ALPHA_SYMBOL)
mLastComposedWord.deactivate();
- mEnteredText = null;
+ if (Keyboard.CODE_DELETE != primaryCode) {
+ mEnteredText = null;
+ }
mConnection.endBatchEdit();
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_onCodeInput(primaryCode, x, y);
+ }
}
// Called from PointerTracker through the KeyboardActionListener interface
@Override
public void onTextInput(CharSequence rawText) {
mConnection.beginBatchEdit();
- commitTyped(LastComposedWord.NOT_A_SEPARATOR);
+ if (mWordComposer.isComposingWord()) {
+ commitCurrentAutoCorrection(rawText.toString());
+ } else {
+ resetComposingState(true /* alsoResetLastComposedWord */);
+ }
mHandler.postUpdateSuggestionStrip();
final CharSequence text = specificTldProcessingOnTextInput(rawText);
if (SPACE_STATE_PHANTOM == mSpaceState) {
sendKeyCodePoint(Keyboard.CODE_SPACE);
}
mConnection.commitText(text, 1);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_commitText(text);
- }
mConnection.endBatchEdit();
mKeyboardSwitcher.updateShiftState();
mKeyboardSwitcher.onCodeInput(Keyboard.CODE_OUTPUT_TEXT);
mSpaceState = SPACE_STATE_NONE;
mEnteredText = text;
- resetComposingState(true /* alsoResetLastComposedWord */);
}
@Override
public void onStartBatchInput() {
mConnection.beginBatchEdit();
if (mWordComposer.isComposingWord()) {
- commitTyped(LastComposedWord.NOT_A_SEPARATOR);
+ commitCurrentAutoCorrection(LastComposedWord.NOT_A_SEPARATOR);
mExpectingUpdateSelection = true;
// TODO: Can we remove this?
mSpaceState = SPACE_STATE_PHANTOM;
}
mConnection.endBatchEdit();
// TODO: Should handle TextUtils.CAP_MODE_CHARACTER.
- mWordComposer.setAutoCapitalized(
- getCurrentAutoCapsState() != Constants.TextUtils.CAP_MODE_OFF);
+ mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode());
}
@Override
@@ -1448,21 +1454,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// In many cases, we may have to put the keyboard in auto-shift state again.
mHandler.postUpdateShiftState();
- if (mEnteredText != null && mConnection.sameAsTextBeforeCursor(mEnteredText)) {
- // Cancel multi-character input: remove the text we just entered.
- // This is triggered on backspace after a key that inputs multiple characters,
- // like the smiley key or the .com key.
- final int length = mEnteredText.length();
- mConnection.deleteSurroundingText(length, 0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(length);
- }
- // If we have mEnteredText, then we know that mHasUncommittedTypedChars == false.
- // In addition we know that spaceState is false, and that we should not be
- // reverting any autocorrect at this point. So we can safely return.
- return;
- }
-
if (mWordComposer.isComposingWord()) {
final int length = mWordComposer.size();
if (length > 0) {
@@ -1476,9 +1467,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mHandler.postUpdateSuggestionStrip();
} else {
mConnection.deleteSurroundingText(1, 0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(1);
- }
}
} else {
if (mLastComposedWord.canRevertCommit()) {
@@ -1486,6 +1474,18 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
revertCommit();
return;
}
+ if (mEnteredText != null && mConnection.sameAsTextBeforeCursor(mEnteredText)) {
+ // Cancel multi-character input: remove the text we just entered.
+ // This is triggered on backspace after a key that inputs multiple characters,
+ // like the smiley key or the .com key.
+ final int length = mEnteredText.length();
+ mConnection.deleteSurroundingText(length, 0);
+ mEnteredText = null;
+ // If we have mEnteredText, then we know that mHasUncommittedTypedChars == false.
+ // In addition we know that spaceState is false, and that we should not be
+ // reverting any autocorrect at this point. So we can safely return.
+ return;
+ }
if (SPACE_STATE_DOUBLE == spaceState) {
mHandler.cancelDoubleSpacesTimer();
if (mConnection.revertDoubleSpace()) {
@@ -1507,9 +1507,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final int lengthToDelete = mLastSelectionEnd - mLastSelectionStart;
mConnection.setSelection(mLastSelectionEnd, mLastSelectionEnd);
mConnection.deleteSurroundingText(lengthToDelete, 0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(lengthToDelete);
- }
} else {
// There is no selection, just delete one character.
if (NOT_A_CURSOR_POSITION == mLastSelectionEnd) {
@@ -1528,14 +1525,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} else {
mConnection.deleteSurroundingText(1, 0);
}
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(1);
- }
if (mDeleteCount > DELETE_ACCELERATE_AT) {
mConnection.deleteSurroundingText(1, 0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(1);
- }
}
}
if (mCurrentSettings.isSuggestionsRequested(mDisplayOrientation)) {
@@ -1611,13 +1602,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mWordComposer.add(primaryCode, keyX, keyY);
// If it's the first letter, make note of auto-caps state
if (mWordComposer.size() == 1) {
- mWordComposer.setAutoCapitalized(
- getCurrentAutoCapsState() != Constants.TextUtils.CAP_MODE_OFF);
+ mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode());
}
mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
} else {
final boolean swapWeakSpace = maybeStripSpace(primaryCode,
- spaceState, KeyboardActionListener.SUGGESTION_STRIP_COORDINATE == x);
+ spaceState, Constants.SUGGESTION_STRIP_COORDINATE == x);
sendKeyCodePoint(primaryCode);
@@ -1639,15 +1629,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Handle separator
if (mWordComposer.isComposingWord()) {
if (mCurrentSettings.mCorrectionEnabled) {
- commitCurrentAutoCorrection(primaryCode);
+ // TODO: maybe cache Strings in an <String> sparse array or something
+ commitCurrentAutoCorrection(new String(new int[]{primaryCode}, 0, 1));
didAutoCorrect = true;
} else {
- commitTyped(primaryCode);
+ commitTyped(new String(new int[]{primaryCode}, 0, 1));
}
}
final boolean swapWeakSpace = maybeStripSpace(primaryCode, spaceState,
- KeyboardActionListener.SUGGESTION_STRIP_COORDINATE == x);
+ Constants.SUGGESTION_STRIP_COORDINATE == x);
if (SPACE_STATE_PHANTOM == spaceState &&
mCurrentSettings.isPhantomSpacePromotingSymbol(primaryCode)) {
@@ -1694,6 +1685,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
Utils.Stats.onSeparator((char)primaryCode, x, y);
+ mHandler.postUpdateShiftState();
return didAutoCorrect;
}
@@ -1714,7 +1706,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// TODO: make this private
// Outside LatinIME, only used by the test suite.
- /* package for tests */ boolean isShowingPunctuationList() {
+ /* package for tests */
+ boolean isShowingPunctuationList() {
if (mSuggestionStripView == null) return false;
return mCurrentSettings.mSuggestPuncList == mSuggestionStripView.getSuggestions();
}
@@ -1845,7 +1838,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
setSuggestionStripShown(isSuggestionsStripVisible());
}
- private void commitCurrentAutoCorrection(final int separatorCodePoint) {
+ private void commitCurrentAutoCorrection(final String separatorString) {
// Complete any pending suggestions query first
if (mHandler.hasPendingUpdateSuggestions()) {
updateSuggestionStrip();
@@ -1859,14 +1852,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
throw new RuntimeException("We have an auto-correction but the typed word "
+ "is empty? Impossible! I must commit suicide.");
}
- Utils.Stats.onAutoCorrection(typedWord, autoCorrection.toString(), separatorCodePoint);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_commitCurrentAutoCorrection(typedWord,
- autoCorrection.toString());
- }
+ Utils.Stats.onAutoCorrection(typedWord, autoCorrection.toString(), separatorString);
mExpectingUpdateSelection = true;
commitChosenWord(autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD,
- separatorCodePoint);
+ separatorString);
if (!typedWord.equals(autoCorrection)) {
// This will make the correction flash for a short while as a visual clue
// to the user that auto-correction happened.
@@ -1880,8 +1869,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,
- final int x, final int y) {
+ public void pickSuggestionManually(final int index, final CharSequence 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()) {
@@ -1889,13 +1877,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// So, LatinImeLogger logs "" as a user's input.
LatinImeLogger.logOnManualSuggestion("", suggestion.toString(), index, suggestedWords);
// Rely on onCodeInput to do the complicated swapping/stripping logic consistently.
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_punctuationSuggestion(index, suggestion, x, y);
- }
final int primaryCode = suggestion.charAt(0);
onCodeInput(primaryCode,
- KeyboardActionListener.SUGGESTION_STRIP_COORDINATE,
- KeyboardActionListener.SUGGESTION_STRIP_COORDINATE);
+ Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_punctuationSuggestion(index, suggestion);
+ }
return;
}
@@ -1922,10 +1909,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final CompletionInfo completionInfo = mApplicationSpecifiedCompletions[index];
mConnection.commitCompletion(completionInfo);
mConnection.endBatchEdit();
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_pickApplicationSpecifiedCompletion(index,
- completionInfo.getText(), x, y);
- }
return;
}
@@ -1934,12 +1917,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final String replacedWord = mWordComposer.getTypedWord().toString();
LatinImeLogger.logOnManualSuggestion(replacedWord,
suggestion.toString(), index, suggestedWords);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion, x, y);
- }
mExpectingUpdateSelection = true;
commitChosenWord(suggestion, LastComposedWord.COMMIT_TYPE_MANUAL_PICK,
LastComposedWord.NOT_A_SEPARATOR);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion);
+ }
mConnection.endBatchEdit();
// Don't allow cancellation of manual pick
mLastComposedWord.deactivate();
@@ -1955,8 +1938,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// If the suggestion is not in the dictionary, the hint should be shown.
&& !AutoCorrection.isValidWord(mSuggest.getUnigramDictionaries(), suggestion, true);
- Utils.Stats.onSeparator((char)Keyboard.CODE_SPACE, WordComposer.NOT_A_COORDINATE,
- WordComposer.NOT_A_COORDINATE);
+ Utils.Stats.onSeparator((char)Keyboard.CODE_SPACE,
+ Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
if (showingAddToDictionaryHint && mIsUserDictionaryAvailable) {
mSuggestionStripView.showAddToDictionaryHint(
suggestion, mCurrentSettings.mHintToSaveText);
@@ -1970,13 +1953,10 @@ 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,
- final int separatorCode) {
+ final String separatorString) {
final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions();
mConnection.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan(
this, chosenWord, suggestedWords, mIsMainDictionaryAvailable), 1);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_commitText(chosenWord);
- }
// Add the word to the user history dictionary
final CharSequence prevWord = addToUserHistoryDictionary(chosenWord);
// TODO: figure out here if this is an auto-correct or if the best word is actually
@@ -1984,7 +1964,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// LastComposedWord#didCommitTypedWord by string equality of the remembered
// strings.
mLastComposedWord = mWordComposer.commitWord(commitType, chosenWord.toString(),
- separatorCode, prevWord);
+ separatorString, prevWord);
}
private void setPunctuationSuggestions() {
@@ -1999,6 +1979,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private CharSequence addToUserHistoryDictionary(final CharSequence suggestion) {
if (TextUtils.isEmpty(suggestion)) return null;
+ if (mSuggest == null) return null;
// If correction is not enabled, we don't add words to the user history dictionary.
// That's to avoid unintended additions in some sensitive fields, or fields that
@@ -2010,7 +1991,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final CharSequence prevWord
= mConnection.getNthPreviousWord(mCurrentSettings.mWordSeparators, 2);
final String secondWord;
- if (mWordComposer.isAutoCapitalized() && !mWordComposer.isMostlyCaps()) {
+ if (mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps()) {
secondWord = suggestion.toString().toLowerCase(
mSubtypeSwitcher.getCurrentSubtypeLocale());
} else {
@@ -2043,9 +2024,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mWordComposer.setComposingWord(word, mKeyboardSwitcher.getKeyboard());
final int length = word.length();
mConnection.deleteSurroundingText(length, 0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(length);
- }
mConnection.setComposingText(word, 1);
mHandler.postUpdateSuggestionStrip();
}
@@ -2056,7 +2034,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final CharSequence committedWord = mLastComposedWord.mCommittedWord;
final int cancelLength = committedWord.length();
final int separatorLength = LastComposedWord.getSeparatorLength(
- mLastComposedWord.mSeparatorCode);
+ mLastComposedWord.mSeparatorString);
// TODO: should we check our saved separator against the actual contents of the text view?
final int deleteLength = cancelLength + separatorLength;
if (DEBUG) {
@@ -2073,18 +2051,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
mConnection.deleteSurroundingText(deleteLength, 0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(deleteLength);
- }
if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) {
mUserHistoryDictionary.cancelAddingUserHistory(
previousWord.toString(), committedWord.toString());
}
- mConnection.commitText(originallyTypedWord, 1);
- // Re-insert the separator
- sendKeyCodePoint(mLastComposedWord.mSeparatorCode);
- Utils.Stats.onSeparator(mLastComposedWord.mSeparatorCode, WordComposer.NOT_A_COORDINATE,
- WordComposer.NOT_A_COORDINATE);
+ mConnection.commitText(originallyTypedWord + mLastComposedWord.mSeparatorString, 1);
+ Utils.Stats.onSeparator(mLastComposedWord.mSeparatorString,
+ Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_revertCommit(originallyTypedWord);
}
@@ -2100,9 +2073,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return mCurrentSettings.isWordSeparator(code);
}
- // Notify that language or mode have been changed and toggleLanguage will update KeyboardID
- // according to new language or mode. Called from SubtypeSwitcher.
- public void onRefreshKeyboard() {
+ // TODO: Make this private
+ // Outside LatinIME, only used by the {@link InputTestsBase} test suite.
+ /* package for test */
+ void loadKeyboard() {
// When the device locale is changed in SetupWizard etc., this method may get called via
// onConfigurationChanged before SoftInputWindow is shown.
initSuggest();
diff --git a/java/src/com/android/inputmethod/latin/LocaleUtils.java b/java/src/com/android/inputmethod/latin/LocaleUtils.java
index b938dd336..feb1b2d0e 100644
--- a/java/src/com/android/inputmethod/latin/LocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/LocaleUtils.java
@@ -31,7 +31,10 @@ import java.util.Locale;
* update/bugfix to this file, consider also updating/fixing the version in the
* dictionary pack.
*/
-public class LocaleUtils {
+public final class LocaleUtils {
+ private static final HashMap<String, Long> EMPTY_LT_HASH_MAP = CollectionUtils.newHashMap();
+ private static final String LOCALE_AND_TIME_STR_SEPARATER = ",";
+
private LocaleUtils() {
// Intentional empty constructor for utility class.
}
@@ -193,7 +196,7 @@ public class LocaleUtils {
}
}
- private static final HashMap<String, Locale> sLocaleCache = new HashMap<String, Locale>();
+ private static final HashMap<String, Locale> sLocaleCache = CollectionUtils.newHashMap();
/**
* Creates a locale from a string specification.
@@ -219,4 +222,38 @@ public class LocaleUtils {
return retval;
}
}
+
+ public static HashMap<String, Long> localeAndTimeStrToHashMap(String str) {
+ if (TextUtils.isEmpty(str)) {
+ return EMPTY_LT_HASH_MAP;
+ }
+ final String[] ss = str.split(LOCALE_AND_TIME_STR_SEPARATER);
+ final int N = ss.length;
+ if (N < 2 || N % 2 != 0) {
+ return EMPTY_LT_HASH_MAP;
+ }
+ final HashMap<String, Long> retval = CollectionUtils.newHashMap();
+ for (int i = 0; i < N / 2; ++i) {
+ final String localeStr = ss[i * 2];
+ final long time = Long.valueOf(ss[i * 2 + 1]);
+ retval.put(localeStr, time);
+ }
+ return retval;
+ }
+
+ public static String localeAndTimeHashMapToStr(HashMap<String, Long> map) {
+ if (map == null || map.isEmpty()) {
+ return "";
+ }
+ final StringBuilder builder = new StringBuilder();
+ for (String localeStr : map.keySet()) {
+ if (builder.length() > 0) {
+ builder.append(LOCALE_AND_TIME_STR_SEPARATER);
+ }
+ final Long time = map.get(localeStr);
+ builder.append(localeStr).append(LOCALE_AND_TIME_STR_SEPARATER);
+ builder.append(String.valueOf(time));
+ }
+ return builder.toString();
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/ResizableIntArray.java b/java/src/com/android/inputmethod/latin/ResizableIntArray.java
index 387d45a53..c660f92c4 100644
--- a/java/src/com/android/inputmethod/latin/ResizableIntArray.java
+++ b/java/src/com/android/inputmethod/latin/ResizableIntArray.java
@@ -131,4 +131,16 @@ public class ResizableIntArray {
mLength = endPos;
}
}
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < mLength; i++) {
+ if (i != 0) {
+ sb.append(",");
+ }
+ sb.append(mArray[i]);
+ }
+ return "[" + sb + "]";
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/ResourceUtils.java b/java/src/com/android/inputmethod/latin/ResourceUtils.java
new file mode 100644
index 000000000..5021ad384
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/ResourceUtils.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.os.Build;
+import android.util.TypedValue;
+
+import java.util.HashMap;
+
+public final class ResourceUtils {
+ public static final float UNDEFINED_RATIO = -1.0f;
+ public static final int UNDEFINED_DIMENSION = -1;
+
+ private ResourceUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ private static final String HARDWARE_PREFIX = Build.HARDWARE + ",";
+ private static final HashMap<String, String> sDeviceOverrideValueMap =
+ CollectionUtils.newHashMap();
+
+ public static String getDeviceOverrideValue(Resources res, int overrideResId, String defValue) {
+ final int orientation = res.getConfiguration().orientation;
+ final String key = overrideResId + "-" + orientation;
+ if (!sDeviceOverrideValueMap.containsKey(key)) {
+ String overrideValue = defValue;
+ for (final String element : res.getStringArray(overrideResId)) {
+ if (element.startsWith(HARDWARE_PREFIX)) {
+ overrideValue = element.substring(HARDWARE_PREFIX.length());
+ break;
+ }
+ }
+ sDeviceOverrideValueMap.put(key, overrideValue);
+ }
+ return sDeviceOverrideValueMap.get(key);
+ }
+
+ public static boolean isValidFraction(final float fraction) {
+ return fraction >= 0.0f;
+ }
+
+ // {@link Resources#getDimensionPixelSize(int)} returns at least one pixel size.
+ public static boolean isValidDimensionPixelSize(final int dimension) {
+ return dimension > 0;
+ }
+
+ // {@link Resources#getDimensionPixelOffset(int)} may return zero pixel offset.
+ public static boolean isValidDimensionPixelOffset(final int dimension) {
+ return dimension >= 0;
+ }
+
+ public static float getFraction(final TypedArray a, final int index, final float defValue) {
+ final TypedValue value = a.peekValue(index);
+ if (value == null || !isFractionValue(value)) {
+ return defValue;
+ }
+ return a.getFraction(index, 1, 1, defValue);
+ }
+
+ public static float getFraction(final TypedArray a, final int index) {
+ return getFraction(a, index, UNDEFINED_RATIO);
+ }
+
+ public static int getDimensionPixelSize(final TypedArray a, final int index) {
+ final TypedValue value = a.peekValue(index);
+ if (value == null || !isDimensionValue(value)) {
+ return ResourceUtils.UNDEFINED_DIMENSION;
+ }
+ return a.getDimensionPixelSize(index, ResourceUtils.UNDEFINED_DIMENSION);
+ }
+
+ public static float getDimensionOrFraction(TypedArray a, int index, int base,
+ float defValue) {
+ final TypedValue value = a.peekValue(index);
+ if (value == null) {
+ return defValue;
+ }
+ if (isFractionValue(value)) {
+ return a.getFraction(index, base, base, defValue);
+ } else if (isDimensionValue(value)) {
+ return a.getDimension(index, defValue);
+ }
+ return defValue;
+ }
+
+ public static int getEnumValue(TypedArray a, int index, int defValue) {
+ final TypedValue value = a.peekValue(index);
+ if (value == null) {
+ return defValue;
+ }
+ if (isIntegerValue(value)) {
+ return a.getInt(index, defValue);
+ }
+ return defValue;
+ }
+
+ public static boolean isFractionValue(TypedValue v) {
+ return v.type == TypedValue.TYPE_FRACTION;
+ }
+
+ public static boolean isDimensionValue(TypedValue v) {
+ return v.type == TypedValue.TYPE_DIMENSION;
+ }
+
+ public static boolean isIntegerValue(TypedValue v) {
+ return v.type >= TypedValue.TYPE_FIRST_INT && v.type <= TypedValue.TYPE_LAST_INT;
+ }
+
+ public static boolean isStringValue(TypedValue v) {
+ return v.type == TypedValue.TYPE_STRING;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 8b4c17322..41e59e92d 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -55,7 +55,9 @@ public class RichInputConnection {
public void beginBatchEdit() {
if (++mNestLevel == 1) {
mIC = mParent.getCurrentInputConnection();
- if (null != mIC) mIC.beginBatchEdit();
+ if (null != mIC) {
+ mIC.beginBatchEdit();
+ }
} else {
if (DBG) {
throw new RuntimeException("Nest level too deep");
@@ -66,7 +68,9 @@ public class RichInputConnection {
}
public void endBatchEdit() {
if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead
- if (--mNestLevel == 0 && null != mIC) mIC.endBatchEdit();
+ if (--mNestLevel == 0 && null != mIC) {
+ mIC.endBatchEdit();
+ }
}
private void checkBatchEdit() {
@@ -79,12 +83,22 @@ public class RichInputConnection {
public void finishComposingText() {
checkBatchEdit();
- if (null != mIC) mIC.finishComposingText();
+ if (null != mIC) {
+ mIC.finishComposingText();
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.richInputConnection_finishComposingText();
+ }
+ }
}
public void commitText(final CharSequence text, final int i) {
checkBatchEdit();
- if (null != mIC) mIC.commitText(text, i);
+ if (null != mIC) {
+ mIC.commitText(text, i);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.richInputConnection_commitText(text, i);
+ }
+ }
}
public int getCursorCapsMode(final int inputType) {
@@ -107,37 +121,72 @@ public class RichInputConnection {
public void deleteSurroundingText(final int i, final int j) {
checkBatchEdit();
- if (null != mIC) mIC.deleteSurroundingText(i, j);
+ if (null != mIC) {
+ mIC.deleteSurroundingText(i, j);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.richInputConnection_deleteSurroundingText(i, j);
+ }
+ }
}
public void performEditorAction(final int actionId) {
mIC = mParent.getCurrentInputConnection();
- if (null != mIC) mIC.performEditorAction(actionId);
+ if (null != mIC) {
+ mIC.performEditorAction(actionId);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.richInputConnection_performEditorAction(actionId);
+ }
+ }
}
public void sendKeyEvent(final KeyEvent keyEvent) {
checkBatchEdit();
- if (null != mIC) mIC.sendKeyEvent(keyEvent);
+ if (null != mIC) {
+ mIC.sendKeyEvent(keyEvent);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.richInputConnection_sendKeyEvent(keyEvent);
+ }
+ }
}
public void setComposingText(final CharSequence text, final int i) {
checkBatchEdit();
- if (null != mIC) mIC.setComposingText(text, i);
+ if (null != mIC) {
+ mIC.setComposingText(text, i);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.richInputConnection_setComposingText(text, i);
+ }
+ }
}
public void setSelection(final int from, final int to) {
checkBatchEdit();
- if (null != mIC) mIC.setSelection(from, to);
+ if (null != mIC) {
+ mIC.setSelection(from, to);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.richInputConnection_setSelection(from, to);
+ }
+ }
}
public void commitCorrection(final CorrectionInfo correctionInfo) {
checkBatchEdit();
- if (null != mIC) mIC.commitCorrection(correctionInfo);
+ if (null != mIC) {
+ mIC.commitCorrection(correctionInfo);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.richInputConnection_commitCorrection(correctionInfo);
+ }
+ }
}
public void commitCompletion(final CompletionInfo completionInfo) {
checkBatchEdit();
- if (null != mIC) mIC.commitCompletion(completionInfo);
+ if (null != mIC) {
+ mIC.commitCompletion(completionInfo);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.richInputConnection_commitCompletion(completionInfo);
+ }
+ }
}
public CharSequence getNthPreviousWord(final String sentenceSeperators, final int n) {
@@ -315,9 +364,6 @@ public class RichInputConnection {
if (lastOne != null && lastOne.length() == 1
&& lastOne.charAt(0) == Keyboard.CODE_SPACE) {
deleteSurroundingText(1, 0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(1);
- }
}
}
@@ -382,13 +428,7 @@ public class RichInputConnection {
return false;
}
deleteSurroundingText(2, 0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(2);
- }
commitText(" ", 1);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_revertDoubleSpaceWhileInBatchEdit();
- }
return true;
}
@@ -409,13 +449,7 @@ public class RichInputConnection {
return false;
}
deleteSurroundingText(2, 0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(2);
- }
commitText(" " + textBeforeCursor.subSequence(0, 1), 1);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_revertSwapPunctuation();
- }
return true;
}
}
diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java
index 0843bdbbc..5e355a3b8 100644
--- a/java/src/com/android/inputmethod/latin/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -185,7 +185,7 @@ public class SettingsValues {
// Helper functions to create member values.
private static SuggestedWords createSuggestPuncList(final String[] puncs) {
- final ArrayList<SuggestedWordInfo> puncList = new ArrayList<SuggestedWordInfo>();
+ final ArrayList<SuggestedWordInfo> puncList = CollectionUtils.newArrayList();
if (puncs != null) {
for (final String puncSpec : puncs) {
puncList.add(new SuggestedWordInfo(KeySpecParser.getLabel(puncSpec),
@@ -375,8 +375,8 @@ public class SettingsValues {
return volume;
}
- return Float.parseFloat(
- Utils.getDeviceOverrideValue(res, R.array.keypress_volumes, "-1.0f"));
+ return Float.parseFloat(ResourceUtils.getDeviceOverrideValue(
+ res, R.array.keypress_volumes, "-1.0f"));
}
// Likewise
@@ -388,8 +388,8 @@ public class SettingsValues {
return ms;
}
- return Integer.parseInt(
- Utils.getDeviceOverrideValue(res, R.array.keypress_vibration_durations, "-1"));
+ return Integer.parseInt(ResourceUtils.getDeviceOverrideValue(
+ res, R.array.keypress_vibration_durations, "-1"));
}
// Likewise
@@ -401,7 +401,7 @@ public class SettingsValues {
public static long getLastUserHistoryWriteTime(
final SharedPreferences prefs, final String locale) {
final String str = prefs.getString(Settings.PREF_LAST_USER_DICTIONARY_WRITE_TIME, "");
- final HashMap<String, Long> map = Utils.localeAndTimeStrToHashMap(str);
+ final HashMap<String, Long> map = LocaleUtils.localeAndTimeStrToHashMap(str);
if (map.containsKey(locale)) {
return map.get(locale);
}
@@ -411,12 +411,16 @@ public class SettingsValues {
public static void setLastUserHistoryWriteTime(
final SharedPreferences prefs, final String locale) {
final String oldStr = prefs.getString(Settings.PREF_LAST_USER_DICTIONARY_WRITE_TIME, "");
- final HashMap<String, Long> map = Utils.localeAndTimeStrToHashMap(oldStr);
+ final HashMap<String, Long> map = LocaleUtils.localeAndTimeStrToHashMap(oldStr);
map.put(locale, System.currentTimeMillis());
- final String newStr = Utils.localeAndTimeHashMapToStr(map);
+ final String newStr = LocaleUtils.localeAndTimeHashMapToStr(map);
prefs.edit().putString(Settings.PREF_LAST_USER_DICTIONARY_WRITE_TIME, newStr).apply();
}
+ public boolean isSameInputType(final EditorInfo editorInfo) {
+ return mInputAttributes.isSameInputType(editorInfo);
+ }
+
// For debug.
public String getInputAttributesDebugString() {
return mInputAttributes.toString();
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
index 6e7d985d6..9c47a38c2 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -21,7 +21,7 @@ import android.text.TextUtils;
import java.util.ArrayList;
import java.util.Locale;
-public class StringUtils {
+public final class StringUtils {
private StringUtils() {
// This utility class is not publicly instantiable.
}
@@ -53,7 +53,7 @@ public class StringUtils {
if (TextUtils.isEmpty(csv)) return "";
final String[] elements = csv.split(",");
if (!containsInArray(key, elements)) return csv;
- final ArrayList<String> result = new ArrayList<String>(elements.length - 1);
+ final ArrayList<String> result = CollectionUtils.newArrayList(elements.length - 1);
for (final String element : elements) {
if (!key.equals(element)) result.add(element);
}
diff --git a/java/src/com/android/inputmethod/latin/SubtypeLocale.java b/java/src/com/android/inputmethod/latin/SubtypeLocale.java
index 21c9c0d1e..de5f515b0 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeLocale.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeLocale.java
@@ -45,13 +45,13 @@ public class SubtypeLocale {
private static String[] sPredefinedKeyboardLayoutSet;
// Keyboard layout to its display name map.
private static final HashMap<String, String> sKeyboardLayoutToDisplayNameMap =
- new HashMap<String, String>();
+ CollectionUtils.newHashMap();
// Keyboard layout to subtype name resource id map.
private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap =
- new HashMap<String, Integer>();
+ CollectionUtils.newHashMap();
// Exceptional locale to subtype name resource id map.
private static final HashMap<String, Integer> sExceptionalLocaleToWithLayoutNameIdsMap =
- new HashMap<String, Integer>();
+ CollectionUtils.newHashMap();
private static final String SUBTYPE_NAME_RESOURCE_GENERIC_PREFIX =
"string/subtype_generic_";
private static final String SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX =
@@ -60,11 +60,11 @@ public class SubtypeLocale {
"string/subtype_no_language_";
// Exceptional locales to display name map.
private static final HashMap<String, String> sExceptionalDisplayNamesMap =
- new HashMap<String, String>();
+ CollectionUtils.newHashMap();
// Keyboard layout set name for the subtypes that don't have a keyboardLayoutSet extra value.
// This is for compatibility to keep the same subtype ids as pre-JellyBean.
private static final HashMap<String,String> sLocaleAndExtraValueToKeyboardLayoutSetMap =
- new HashMap<String,String>();
+ CollectionUtils.newHashMap();
private SubtypeLocale() {
// Intentional empty constructor for utility class.
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index a7a5fcb5f..c693edcca 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -22,6 +22,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.inputmethodservice.InputMethodService;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
@@ -42,7 +43,6 @@ public class SubtypeSwitcher {
private static final String TAG = SubtypeSwitcher.class.getSimpleName();
private static final SubtypeSwitcher sInstance = new SubtypeSwitcher();
- private /* final */ LatinIME mService;
private /* final */ InputMethodManager mImm;
private /* final */ Resources mResources;
private /* final */ ConnectivityManager mConnectivityManager;
@@ -68,11 +68,11 @@ public class SubtypeSwitcher {
return mEnabledSubtypeCount >= 2 || !mIsSystemLanguageSameAsInputLanguage;
}
- public void updateEnabledSubtypeCount(int count) {
+ public void updateEnabledSubtypeCount(final int count) {
mEnabledSubtypeCount = count;
}
- public void updateIsSystemLanguageSameAsInputLanguage(boolean isSame) {
+ public void updateIsSystemLanguageSameAsInputLanguage(final boolean isSame) {
mIsSystemLanguageSameAsInputLanguage = isSame;
}
}
@@ -81,18 +81,17 @@ public class SubtypeSwitcher {
return sInstance;
}
- public static void init(LatinIME service) {
- SubtypeLocale.init(service);
- sInstance.initialize(service);
- sInstance.updateAllParameters();
+ public static void init(final Context context) {
+ SubtypeLocale.init(context);
+ sInstance.initialize(context);
+ sInstance.updateAllParameters(context);
}
private SubtypeSwitcher() {
// Intentional empty constructor for singleton.
}
- private void initialize(LatinIME service) {
- mService = service;
+ private void initialize(final Context service) {
mResources = service.getResources();
mImm = ImfUtils.getInputMethodManager(service);
mConnectivityManager = (ConnectivityManager) service.getSystemService(
@@ -111,39 +110,46 @@ public class SubtypeSwitcher {
// Update all parameters stored in SubtypeSwitcher.
// Only configuration changed event is allowed to call this because this is heavy.
- private void updateAllParameters() {
+ private void updateAllParameters(final Context context) {
mCurrentSystemLocale = mResources.getConfiguration().locale;
- updateSubtype(ImfUtils.getCurrentInputMethodSubtype(mService, mNoLanguageSubtype));
- updateParametersOnStartInputView();
+ updateSubtype(ImfUtils.getCurrentInputMethodSubtype(context, mNoLanguageSubtype));
+ updateParametersOnStartInputViewAndReturnIfCurrentSubtypeEnabled();
}
- // Update parameters which are changed outside LatinIME. This parameters affect UI so they
- // should be updated every time onStartInputview.
- public void updateParametersOnStartInputView() {
- updateEnabledSubtypes();
+ /**
+ * Update parameters which are changed outside LatinIME. This parameters affect UI so they
+ * should be updated every time onStartInputView.
+ *
+ * @return true if the current subtype is enabled.
+ */
+ public boolean updateParametersOnStartInputViewAndReturnIfCurrentSubtypeEnabled() {
+ final boolean currentSubtypeEnabled =
+ updateEnabledSubtypesAndReturnIfEnabled(mCurrentSubtype);
updateShortcutIME();
+ return currentSubtypeEnabled;
}
- // Reload enabledSubtypes from the framework.
- private void updateEnabledSubtypes() {
- final InputMethodSubtype currentSubtype = mCurrentSubtype;
- boolean foundCurrentSubtypeBecameDisabled = true;
+ /**
+ * Update enabled subtypes from the framework.
+ *
+ * @param subtype the subtype to be checked
+ * @return true if the {@code subtype} is enabled.
+ */
+ private boolean updateEnabledSubtypesAndReturnIfEnabled(final InputMethodSubtype subtype) {
final List<InputMethodSubtype> enabledSubtypesOfThisIme =
mImm.getEnabledInputMethodSubtypeList(null, true);
- for (InputMethodSubtype ims : enabledSubtypesOfThisIme) {
- if (ims.equals(currentSubtype)) {
- foundCurrentSubtypeBecameDisabled = false;
- }
- }
mNeedsToDisplayLanguage.updateEnabledSubtypeCount(enabledSubtypesOfThisIme.size());
- if (foundCurrentSubtypeBecameDisabled) {
- if (DBG) {
- Log.w(TAG, "Last subtype: "
- + currentSubtype.getLocale() + "/" + currentSubtype.getExtraValue());
- Log.w(TAG, "Last subtype was disabled. Update to the current one.");
+
+ for (final InputMethodSubtype ims : enabledSubtypesOfThisIme) {
+ if (ims.equals(subtype)) {
+ return true;
}
- updateSubtype(ImfUtils.getCurrentInputMethodSubtype(mService, mNoLanguageSubtype));
}
+ if (DBG) {
+ Log.w(TAG, "Subtype: " + subtype.getLocale() + "/" + subtype.getExtraValue()
+ + " was disabled");
+ }
+ return false;
}
private void updateShortcutIME() {
@@ -159,8 +165,8 @@ public class SubtypeSwitcher {
mImm.getShortcutInputMethodsAndSubtypes();
mShortcutInputMethodInfo = null;
mShortcutSubtype = null;
- for (InputMethodInfo imi : shortcuts.keySet()) {
- List<InputMethodSubtype> subtypes = shortcuts.get(imi);
+ for (final InputMethodInfo imi : shortcuts.keySet()) {
+ final List<InputMethodSubtype> subtypes = shortcuts.get(imi);
// TODO: Returns the first found IMI for now. Should handle all shortcuts as
// appropriate.
mShortcutInputMethodInfo = imi;
@@ -194,24 +200,24 @@ public class SubtypeSwitcher {
mCurrentSubtype = newSubtype;
updateShortcutIME();
- mService.onRefreshKeyboard();
}
////////////////////////////
// Shortcut IME functions //
////////////////////////////
- public void switchToShortcutIME() {
+ public void switchToShortcutIME(final InputMethodService context) {
if (mShortcutInputMethodInfo == null) {
return;
}
final String imiId = mShortcutInputMethodInfo.getId();
- switchToTargetIME(imiId, mShortcutSubtype);
+ switchToTargetIME(imiId, mShortcutSubtype, context);
}
- private void switchToTargetIME(final String imiId, final InputMethodSubtype subtype) {
- final IBinder token = mService.getWindow().getWindow().getAttributes().token;
+ private void switchToTargetIME(final String imiId, final InputMethodSubtype subtype,
+ final InputMethodService context) {
+ final IBinder token = context.getWindow().getWindow().getAttributes().token;
if (token == null) {
return;
}
@@ -253,7 +259,7 @@ public class SubtypeSwitcher {
return true;
}
- public void onNetworkStateChanged(Intent intent) {
+ public void onNetworkStateChanged(final Intent intent) {
final boolean noConnection = intent.getBooleanExtra(
ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
mIsNetworkConnected = !noConnection;
@@ -265,7 +271,7 @@ public class SubtypeSwitcher {
// Subtype Switching functions //
//////////////////////////////////
- public boolean needsToDisplayLanguage(Locale keyboardLocale) {
+ public boolean needsToDisplayLanguage(final Locale keyboardLocale) {
if (keyboardLocale.toString().equals(SubtypeLocale.NO_LANGUAGE)) {
return true;
}
@@ -279,12 +285,14 @@ public class SubtypeSwitcher {
return SubtypeLocale.getSubtypeLocale(mCurrentSubtype);
}
- public void onConfigurationChanged(Configuration conf) {
+ public boolean onConfigurationChanged(final Configuration conf, final Context context) {
final Locale systemLocale = conf.locale;
+ final boolean systemLocaleChanged = !systemLocale.equals(mCurrentSystemLocale);
// If system configuration was changed, update all parameters.
- if (!systemLocale.equals(mCurrentSystemLocale)) {
- updateAllParameters();
+ if (systemLocaleChanged) {
+ updateAllParameters(context);
}
+ return systemLocaleChanged;
}
public InputMethodSubtype getCurrentSubtype() {
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 8a2341d5e..51ed09604 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -50,9 +50,8 @@ public class Suggest {
private Dictionary mMainDictionary;
private ContactsBinaryDictionary mContactsDict;
- private WhitelistDictionary mWhiteListDictionary;
private final ConcurrentHashMap<String, Dictionary> mDictionaries =
- new ConcurrentHashMap<String, Dictionary>();
+ CollectionUtils.newConcurrentHashMap();
public static final int MAX_SUGGESTIONS = 18;
@@ -74,21 +73,11 @@ public class Suggest {
mLocale = locale;
mMainDictionary = mainDict;
addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, mainDict);
- initWhitelistAndAutocorrectAndPool(context, locale);
- }
-
- private void initWhitelistAndAutocorrectAndPool(final Context context, final Locale locale) {
- mWhiteListDictionary = new WhitelistDictionary(context, locale);
- addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_WHITELIST, mWhiteListDictionary);
}
private void initAsynchronously(final Context context, final Locale locale,
final SuggestInitializationListener listener) {
resetMainDict(context, locale, listener);
-
- // TODO: read the whitelist and init the pool asynchronously too.
- // initPool should be done asynchronously now that the pool is thread-safe.
- initWhitelistAndAutocorrectAndPool(context, locale);
}
private static void addOrReplaceDictionary(
@@ -169,9 +158,17 @@ public class Suggest {
public SuggestedWords getSuggestedWords(
final WordComposer wordComposer, CharSequence prevWordForBigram,
final ProximityInfo proximityInfo, final boolean isCorrectionEnabled) {
+ return getSuggestedWordsWithSessionId(
+ wordComposer, prevWordForBigram, proximityInfo, isCorrectionEnabled, 0);
+ }
+
+ public SuggestedWords getSuggestedWordsWithSessionId(
+ final WordComposer wordComposer, CharSequence prevWordForBigram,
+ final ProximityInfo proximityInfo, final boolean isCorrectionEnabled, int sessionId) {
LatinImeLogger.onStartSuggestion(prevWordForBigram);
if (wordComposer.isBatchMode()) {
- return getSuggestedWordsForBatchInput(wordComposer, prevWordForBigram, proximityInfo);
+ return getSuggestedWordsForBatchInput(
+ wordComposer, prevWordForBigram, proximityInfo, sessionId);
} else {
return getSuggestedWordsForTypingInput(wordComposer, prevWordForBigram, proximityInfo,
isCorrectionEnabled);
@@ -208,15 +205,6 @@ public class Suggest {
wordComposerForLookup, prevWordForBigram, proximityInfo));
}
- final CharSequence whitelistedWordFromWhitelistDictionary =
- mWhiteListDictionary.getWhitelistedWord(consideredWord);
- if (whitelistedWordFromWhitelistDictionary != null) {
- // MAX_SCORE ensures this will be considered strong enough to be auto-corrected
- suggestionsSet.add(new SuggestedWordInfo(whitelistedWordFromWhitelistDictionary,
- SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_WHITELIST,
- Dictionary.TYPE_WHITELIST));
- }
-
final CharSequence whitelistedWord;
if (suggestionsSet.isEmpty()) {
whitelistedWord = null;
@@ -226,11 +214,6 @@ public class Suggest {
whitelistedWord = suggestionsSet.first().mWord;
}
- // TODO: Change this scheme - a boolean is not enough. A whitelisted word may be "valid"
- // but still autocorrected from - in the case the whitelist only capitalizes the word.
- // The whitelist should be case-insensitive, so it's not possible to be consistent with
- // a boolean flag. Right now this is handled with a slight hack in
- // WhitelistDictionary#shouldForciblyAutoCorrectFrom.
final boolean allowsToBeAutoCorrected = (null != whitelistedWord
&& !whitelistedWord.equals(consideredWord))
|| AutoCorrection.isNotAWord(mDictionaries, consideredWord,
@@ -259,7 +242,7 @@ public class Suggest {
}
final ArrayList<SuggestedWordInfo> suggestionsContainer =
- new ArrayList<SuggestedWordInfo>(suggestionsSet);
+ CollectionUtils.newArrayList(suggestionsSet);
final int suggestionsCount = suggestionsContainer.size();
final boolean isFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
final boolean isAllUpperCase = wordComposer.isAllUpperCase();
@@ -306,29 +289,28 @@ public class Suggest {
// Retrieves suggestions for the batch input.
private SuggestedWords getSuggestedWordsForBatchInput(
final WordComposer wordComposer, CharSequence prevWordForBigram,
- final ProximityInfo proximityInfo) {
+ final ProximityInfo proximityInfo, int sessionId) {
final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator,
MAX_SUGGESTIONS);
// At second character typed, search the unigrams (scores being affected by bigrams)
for (final String key : mDictionaries.keySet()) {
- // Skip UserUnigramDictionary and WhitelistDictionary to lookup
- if (key.equals(Dictionary.TYPE_USER_HISTORY)
- || key.equals(Dictionary.TYPE_WHITELIST)) {
+ // Skip User history dictionary for lookup
+ // TODO: The user history dictionary should just override getSuggestionsWithSessionId
+ // to make sure it doesn't return anything and we should remove this test
+ if (key.equals(Dictionary.TYPE_USER_HISTORY)) {
continue;
}
final Dictionary dictionary = mDictionaries.get(key);
- suggestionsSet.addAll(dictionary.getSuggestions(
- wordComposer, prevWordForBigram, proximityInfo));
+ suggestionsSet.addAll(dictionary.getSuggestionsWithSessionId(
+ wordComposer, prevWordForBigram, proximityInfo, sessionId));
}
final ArrayList<SuggestedWordInfo> suggestionsContainer =
- new ArrayList<SuggestedWordInfo>(suggestionsSet);
+ CollectionUtils.newArrayList(suggestionsSet);
final int suggestionsCount = suggestionsContainer.size();
- final boolean isFirstCharCapitalized = wordComposer.isAutoCapitalized();
- // TODO: Handle the manual temporary shifted mode.
- // TODO: Should handle TextUtils.CAP_MODE_CHARACTER.
- final boolean isAllUpperCase = false;
+ final boolean isFirstCharCapitalized = wordComposer.wasShiftedNoLock();
+ final boolean isAllUpperCase = wordComposer.isAllUpperCase();
if (isFirstCharCapitalized || isAllUpperCase) {
for (int i = 0; i < suggestionsCount; ++i) {
final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
@@ -356,7 +338,7 @@ public class Suggest {
typedWordInfo.setDebugString("+");
final int suggestionsSize = suggestions.size();
final ArrayList<SuggestedWordInfo> suggestionsList =
- new ArrayList<SuggestedWordInfo>(suggestionsSize);
+ CollectionUtils.newArrayList(suggestionsSize);
suggestionsList.add(typedWordInfo);
// Note: i here is the index in mScores[], but the index in mSuggestions is one more
// than i because we added the typed word to mSuggestions without touching mScores.
@@ -409,7 +391,7 @@ public class Suggest {
}
public void close() {
- final HashSet<Dictionary> dictionaries = new HashSet<Dictionary>();
+ final HashSet<Dictionary> dictionaries = CollectionUtils.newHashSet();
dictionaries.addAll(mDictionaries.values());
for (final Dictionary dictionary : dictionaries) {
dictionary.close();
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index 88fc006df..68ecfa0d7 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -24,8 +24,10 @@ import java.util.Arrays;
import java.util.HashSet;
public class SuggestedWords {
+ private static final ArrayList<SuggestedWordInfo> EMPTY_WORD_INFO_LIST =
+ CollectionUtils.newArrayList(0);
public static final SuggestedWords EMPTY = new SuggestedWords(
- new ArrayList<SuggestedWordInfo>(0), false, false, false, false, false);
+ EMPTY_WORD_INFO_LIST, false, false, false, false, false);
public final boolean mTypedWordValid;
// Note: this INCLUDES cases where the word will auto-correct to itself. A good definition
@@ -83,7 +85,7 @@ public class SuggestedWords {
public static ArrayList<SuggestedWordInfo> getFromApplicationSpecifiedCompletions(
final CompletionInfo[] infos) {
- final ArrayList<SuggestedWordInfo> result = new ArrayList<SuggestedWordInfo>();
+ 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,
@@ -97,8 +99,8 @@ public class SuggestedWords {
// and replace it with what the user currently typed.
public static ArrayList<SuggestedWordInfo> getTypedWordAndPreviousSuggestions(
final CharSequence typedWord, final SuggestedWords previousSuggestions) {
- final ArrayList<SuggestedWordInfo> suggestionsList = new ArrayList<SuggestedWordInfo>();
- final HashSet<String> alreadySeen = new HashSet<String>();
+ final ArrayList<SuggestedWordInfo> suggestionsList = CollectionUtils.newArrayList();
+ final HashSet<String> alreadySeen = CollectionUtils.newHashSet();
suggestionsList.add(new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE,
SuggestedWordInfo.KIND_TYPED, Dictionary.TYPE_USER_TYPED));
alreadySeen.add(typedWord.toString());
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
new file mode 100644
index 000000000..942c82837
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.util.Log;
+
+import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
+import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.FusionDictionaryBufferInterface;
+import com.android.inputmethod.latin.makedict.FusionDictionary;
+import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
+import com.android.inputmethod.latin.makedict.PendingAttribute;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Reads and writes Binary files for a UserHistoryDictionary.
+ *
+ * All the methods in this class are static.
+ */
+public class UserHistoryDictIOUtils {
+ private static final String TAG = UserHistoryDictIOUtils.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ public interface OnAddWordListener {
+ public void setUnigram(final String word, final String shortcutTarget, final int frequency);
+ public void setBigram(final String word1, final String word2, final int frequency);
+ }
+
+ public interface BigramDictionaryInterface {
+ public int getFrequency(final String word1, final String word2);
+ }
+
+ public static final class ByteArrayWrapper implements FusionDictionaryBufferInterface {
+ private byte[] mBuffer;
+ private int mPosition;
+
+ ByteArrayWrapper(final byte[] buffer) {
+ mBuffer = buffer;
+ mPosition = 0;
+ }
+
+ @Override
+ public int readUnsignedByte() {
+ return ((int)mBuffer[mPosition++]) & 0xFF;
+ }
+
+ @Override
+ public int readUnsignedShort() {
+ final int retval = readUnsignedByte();
+ return (retval << 8) + readUnsignedByte();
+ }
+
+ @Override
+ public int readUnsignedInt24() {
+ final int retval = readUnsignedShort();
+ return (retval << 8) + readUnsignedByte();
+ }
+
+ @Override
+ public int readInt() {
+ final int retval = readUnsignedShort();
+ return (retval << 16) + readUnsignedShort();
+ }
+
+ @Override
+ public int position() {
+ return mPosition;
+ }
+
+ @Override
+ public void position(int position) {
+ mPosition = position;
+ }
+ }
+
+ /**
+ * Writes dictionary to file.
+ */
+ public static void writeDictionaryBinary(final OutputStream destination,
+ final BigramDictionaryInterface dict, final UserHistoryDictionaryBigramList bigrams,
+ final int version) {
+
+ final FusionDictionary fusionDict = constructFusionDictionary(dict, bigrams);
+
+ try {
+ BinaryDictInputOutput.writeDictionaryBinary(destination, fusionDict, version);
+ } catch (IOException e) {
+ Log.e(TAG, "IO exception while writing file: " + e);
+ } catch (UnsupportedFormatException e) {
+ Log.e(TAG, "Unsupported fomat: " + e);
+ }
+ }
+
+ /**
+ * Constructs a new FusionDictionary from BigramDictionaryInterface.
+ */
+ /* packages for test */ 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));
+
+ 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 (DEBUG) {
+ if (word1 == null) {
+ Log.d(TAG, "add unigram: " + word2 + "," + Integer.toString(freq));
+ } else {
+ Log.d(TAG, "add bigram: " + word1
+ + "," + word2 + "," + Integer.toString(freq));
+ }
+ }
+
+ if (word1 == null) { // unigram
+ fusionDict.add(word2, freq, null, false /* isNotAWord */);
+ } else { // bigram
+ fusionDict.setBigram(word1, word2, freq);
+ }
+ bigrams.updateBigram(word1, word2, (byte)freq);
+ }
+ }
+
+ return fusionDict;
+ }
+
+ /**
+ * Reads dictionary from file.
+ */
+ public static void readDictionaryBinary(final FusionDictionaryBufferInterface buffer,
+ final OnAddWordListener dict) {
+ final Map<Integer, String> unigrams = CollectionUtils.newTreeMap();
+ final Map<Integer, Integer> frequencies = CollectionUtils.newTreeMap();
+ final Map<Integer, ArrayList<PendingAttribute>> bigrams = CollectionUtils.newTreeMap();
+
+ try {
+ BinaryDictInputOutput.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);
+ }
+ }
+
+ /**
+ * Adds all unigrams and bigrams in maps to OnAddWordListener.
+ */
+ /* package for test */ 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),
+ BinaryDictInputOutput.reconstructBigramFrequency(unigramFrequency,
+ attr.mFrequency));
+ }
+ }
+ }
+
+ }
+} \ No newline at end of file
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
index 3bb670c9a..6c9d1c250 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
@@ -52,14 +52,14 @@ public class UserHistoryDictionary extends ExpandableDictionary {
private static final int FREQUENCY_FOR_TYPED = 2;
/** Maximum number of pairs. Pruning will start when databases goes above this number. */
- private static int sMaxHistoryBigrams = 10000;
+ public static final int sMaxHistoryBigrams = 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.
*/
- private static int sDeleteHistoryBigrams = 1000;
+ public static final int sDeleteHistoryBigrams = 1000;
/**
* Database version should increase if the database structure changes
@@ -93,10 +93,10 @@ public class UserHistoryDictionary extends ExpandableDictionary {
private final static HashMap<String, String> sDictProjectionMap;
private final static ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>
- sLangDictCache = new ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>();
+ sLangDictCache = CollectionUtils.newConcurrentHashMap();
static {
- sDictProjectionMap = new HashMap<String, String>();
+ 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);
@@ -109,12 +109,8 @@ public class UserHistoryDictionary extends ExpandableDictionary {
private static DatabaseHelper sOpenHelper = null;
- public void setDatabaseMax(int maxHistoryBigram) {
- sMaxHistoryBigrams = maxHistoryBigram;
- }
-
- public void setDatabaseDelete(int deleteHistoryBigram) {
- sDeleteHistoryBigrams = deleteHistoryBigram;
+ public String getLocale() {
+ return mLocale;
}
public synchronized static UserHistoryDictionary getInstance(
@@ -502,9 +498,11 @@ public class UserHistoryDictionary extends ExpandableDictionary {
needsToSave(fc, isValid, addLevel0Bigram)) {
freq = fc;
} else {
+ // Delete this entry
freq = -1;
}
} else {
+ // Delete this entry
freq = -1;
}
}
@@ -541,6 +539,7 @@ public class UserHistoryDictionary extends ExpandableDictionary {
getContentValues(word1, word2, mLocale));
pairId = pairIdLong.intValue();
}
+ // Eliminate freq == 0 because that word is profanity.
if (freq > 0) {
if (PROFILE_SAVE_RESTORE) {
++profInsert;
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java
index 610652ac1..bb0f5429a 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java
@@ -29,9 +29,8 @@ import java.util.Set;
public 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 = new HashMap<String, Byte>();
- private final HashMap<String, HashMap<String, Byte>> mBigramMap =
- new HashMap<String, HashMap<String, Byte>>();
+ private static final HashMap<String, Byte> EMPTY_BIGRAM_MAP = CollectionUtils.newHashMap();
+ private final HashMap<String, HashMap<String, Byte>> mBigramMap = CollectionUtils.newHashMap();
private int mSize = 0;
public void evictAll() {
@@ -57,7 +56,7 @@ public class UserHistoryDictionaryBigramList {
if (mBigramMap.containsKey(word1)) {
map = mBigramMap.get(word1);
} else {
- map = new HashMap<String, Byte>();
+ map = CollectionUtils.newHashMap();
mBigramMap.put(word1, map);
}
if (!map.containsKey(word2)) {
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java b/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java
index 5a2fdf48e..3d3bd980c 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java
@@ -19,7 +19,7 @@ package com.android.inputmethod.latin;
import android.text.format.DateUtils;
import android.util.Log;
-public class UserHistoryForgettingCurveUtils {
+public final class UserHistoryForgettingCurveUtils {
private static final String TAG = UserHistoryForgettingCurveUtils.class.getSimpleName();
private static final boolean DEBUG = false;
private static final int FC_FREQ_MAX = 127;
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index c6b5c338b..1c98b92cd 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -16,20 +16,16 @@
package com.android.inputmethod.latin;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.content.res.Resources;
import android.inputmethodservice.InputMethodService;
import android.net.Uri;
import android.os.AsyncTask;
-import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
import android.text.TextUtils;
-import android.text.format.DateUtils;
import android.util.Log;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
@@ -45,9 +41,8 @@ import java.io.PrintWriter;
import java.nio.channels.FileChannel;
import java.text.SimpleDateFormat;
import java.util.Date;
-import java.util.HashMap;
-public class Utils {
+public final class Utils {
private Utils() {
// This utility class is not publicly instantiable.
}
@@ -65,44 +60,6 @@ public class Utils {
}
}
- public static class GCUtils {
- private static final String GC_TAG = GCUtils.class.getSimpleName();
- public static final int GC_TRY_COUNT = 2;
- // GC_TRY_LOOP_MAX is used for the hard limit of GC wait,
- // GC_TRY_LOOP_MAX should be greater than GC_TRY_COUNT.
- public static final int GC_TRY_LOOP_MAX = 5;
- private static final long GC_INTERVAL = DateUtils.SECOND_IN_MILLIS;
- private static GCUtils sInstance = new GCUtils();
- private int mGCTryCount = 0;
-
- public static GCUtils getInstance() {
- return sInstance;
- }
-
- public void reset() {
- mGCTryCount = 0;
- }
-
- public boolean tryGCOrWait(String metaData, Throwable t) {
- if (mGCTryCount == 0) {
- System.gc();
- }
- if (++mGCTryCount > GC_TRY_COUNT) {
- LatinImeLogger.logOnException(metaData, t);
- return false;
- } else {
- try {
- Thread.sleep(GC_INTERVAL);
- return true;
- } catch (InterruptedException e) {
- Log.e(GC_TAG, "Sleep was interrupted.");
- LatinImeLogger.logOnException(metaData, t);
- return false;
- }
- }
- }
- }
-
/* package */ static class RingCharBuffer {
private static RingCharBuffer sRingCharBuffer = new RingCharBuffer();
private static final char PLACEHOLDER_DELIMITER_CHAR = '\uFFFC';
@@ -222,7 +179,7 @@ public class Utils {
return getStackTrace(Integer.MAX_VALUE - 1);
}
- public static class UsabilityStudyLogUtils {
+ public static final class UsabilityStudyLogUtils {
// TODO: remove code duplication with ResearchLog class
private static final String USABILITY_TAG = UsabilityStudyLogUtils.class.getSimpleName();
private static final String FILENAME = "log.txt";
@@ -431,34 +388,38 @@ public class Utils {
}
}
- public static float getDipScale(Context context) {
- final float scale = context.getResources().getDisplayMetrics().density;
- return scale;
- }
-
- /** Convert pixel to DIP */
- public static int dipToPixel(float scale, int dip) {
- return (int) (dip * scale + 0.5);
- }
-
- public static class Stats {
+ public static final class Stats {
public static void onNonSeparator(final char code, final int x,
final int y) {
RingCharBuffer.getInstance().push(code, x, y);
LatinImeLogger.logOnInputChar();
}
- public static void onSeparator(final int code, final int x,
- final int y) {
- // TODO: accept code points
- RingCharBuffer.getInstance().push((char)code, x, y);
+ public static void onSeparator(final int code, final int x, final int y) {
+ // Helper method to log a single code point separator
+ // TODO: cache this mapping of a code point to a string in a sparse array in StringUtils
+ onSeparator(new String(new int[]{code}, 0, 1), x, y);
+ }
+
+ public static void onSeparator(final String separator, final int x, final int y) {
+ final int length = separator.length();
+ for (int i = 0; i < length; i = Character.offsetByCodePoints(separator, i, 1)) {
+ int codePoint = Character.codePointAt(separator, i);
+ // TODO: accept code points
+ RingCharBuffer.getInstance().push((char)codePoint, x, y);
+ }
LatinImeLogger.logOnInputSeparator();
}
public static void onAutoCorrection(final String typedWord, final String correctedWord,
- final int separatorCode) {
+ final String separatorString) {
if (TextUtils.isEmpty(typedWord)) return;
- LatinImeLogger.logOnAutoCorrection(typedWord, correctedWord, separatorCode);
+ // TODO: this fails when the separator is more than 1 code point long, but
+ // the backend can't handle it yet. The only case when this happens is with
+ // smileys and other multi-character keys.
+ final int codePoint = TextUtils.isEmpty(separatorString) ? Constants.NOT_A_CODE
+ : separatorString.codePointAt(0);
+ LatinImeLogger.logOnAutoCorrection(typedWord, correctedWord, codePoint);
}
public static void onAutoCorrectionCancellation() {
@@ -474,60 +435,4 @@ public class Utils {
if (TextUtils.isEmpty(info)) return null;
return info;
}
-
- private static final String HARDWARE_PREFIX = Build.HARDWARE + ",";
- private static final HashMap<String, String> sDeviceOverrideValueMap =
- new HashMap<String, String>();
-
- public static String getDeviceOverrideValue(Resources res, int overrideResId, String defValue) {
- final int orientation = res.getConfiguration().orientation;
- final String key = overrideResId + "-" + orientation;
- if (!sDeviceOverrideValueMap.containsKey(key)) {
- String overrideValue = defValue;
- for (final String element : res.getStringArray(overrideResId)) {
- if (element.startsWith(HARDWARE_PREFIX)) {
- overrideValue = element.substring(HARDWARE_PREFIX.length());
- break;
- }
- }
- sDeviceOverrideValueMap.put(key, overrideValue);
- }
- return sDeviceOverrideValueMap.get(key);
- }
-
- private static final HashMap<String, Long> EMPTY_LT_HASH_MAP = new HashMap<String, Long>();
- private static final String LOCALE_AND_TIME_STR_SEPARATER = ",";
- public static HashMap<String, Long> localeAndTimeStrToHashMap(String str) {
- if (TextUtils.isEmpty(str)) {
- return EMPTY_LT_HASH_MAP;
- }
- final String[] ss = str.split(LOCALE_AND_TIME_STR_SEPARATER);
- final int N = ss.length;
- if (N < 2 || N % 2 != 0) {
- return EMPTY_LT_HASH_MAP;
- }
- final HashMap<String, Long> retval = new HashMap<String, Long>();
- for (int i = 0; i < N / 2; ++i) {
- final String localeStr = ss[i * 2];
- final long time = Long.valueOf(ss[i * 2 + 1]);
- retval.put(localeStr, time);
- }
- return retval;
- }
-
- public static String localeAndTimeHashMapToStr(HashMap<String, Long> map) {
- if (map == null || map.isEmpty()) {
- return "";
- }
- final StringBuilder builder = new StringBuilder();
- for (String localeStr : map.keySet()) {
- if (builder.length() > 0) {
- builder.append(LOCALE_AND_TIME_STR_SEPARATER);
- }
- final Long time = map.get(localeStr);
- builder.append(localeStr).append(LOCALE_AND_TIME_STR_SEPARATER);
- builder.append(String.valueOf(time));
- }
- return builder.toString();
- }
}
diff --git a/java/src/com/android/inputmethod/latin/VibratorUtils.java b/java/src/com/android/inputmethod/latin/VibratorUtils.java
index 33ffdd9c9..b6696cec0 100644
--- a/java/src/com/android/inputmethod/latin/VibratorUtils.java
+++ b/java/src/com/android/inputmethod/latin/VibratorUtils.java
@@ -19,7 +19,7 @@ package com.android.inputmethod.latin;
import android.content.Context;
import android.os.Vibrator;
-public class VibratorUtils {
+public final class VibratorUtils {
private static final VibratorUtils sInstance = new VibratorUtils();
private Vibrator mVibrator;
diff --git a/java/src/com/android/inputmethod/latin/WhitelistDictionary.java b/java/src/com/android/inputmethod/latin/WhitelistDictionary.java
deleted file mode 100644
index 14476dcf0..000000000
--- a/java/src/com/android/inputmethod/latin/WhitelistDictionary.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.latin;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.inputmethod.keyboard.ProximityInfo;
-import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Locale;
-
-public class WhitelistDictionary extends ExpandableDictionary {
-
- private static final boolean DBG = LatinImeLogger.sDBG;
- private static final String TAG = WhitelistDictionary.class.getSimpleName();
-
- private final HashMap<String, Pair<Integer, String>> mWhitelistWords =
- new HashMap<String, Pair<Integer, String>>();
-
- // TODO: Conform to the async load contact of ExpandableDictionary
- public WhitelistDictionary(final Context context, final Locale locale) {
- super(context, Dictionary.TYPE_WHITELIST);
- // TODO: Move whitelist dictionary into main dictionary.
- final RunInLocale<Void> job = new RunInLocale<Void>() {
- @Override
- protected Void job(Resources res) {
- initWordlist(res.getStringArray(R.array.wordlist_whitelist));
- return null;
- }
- };
- job.runInLocale(context.getResources(), locale);
- }
-
- private void initWordlist(String[] wordlist) {
- mWhitelistWords.clear();
- final int N = wordlist.length;
- if (N % 3 != 0) {
- if (DBG) {
- Log.d(TAG, "The number of the whitelist is invalid.");
- }
- return;
- }
- try {
- for (int i = 0; i < N; i += 3) {
- final int score = Integer.valueOf(wordlist[i]);
- final String before = wordlist[i + 1];
- final String after = wordlist[i + 2];
- if (before != null && after != null) {
- mWhitelistWords.put(
- before.toLowerCase(), new Pair<Integer, String>(score, after));
- addWord(after, null /* shortcut */, score);
- }
- }
- } catch (NumberFormatException e) {
- if (DBG) {
- Log.d(TAG, "The score of the word is invalid.");
- }
- }
- }
-
- public String getWhitelistedWord(String before) {
- if (before == null) return null;
- final String lowerCaseBefore = before.toLowerCase();
- if(mWhitelistWords.containsKey(lowerCaseBefore)) {
- if (DBG) {
- Log.d(TAG, "--- found whitelistedWord: " + lowerCaseBefore);
- }
- return mWhitelistWords.get(lowerCaseBefore).second;
- }
- return null;
- }
-
- @Override
- public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final CharSequence prevWord, final ProximityInfo proximityInfo) {
- // Whitelist does not supply any suggestions or predictions.
- return null;
- }
-
- // See LatinIME#updateSuggestions. This breaks in the (queer) case that the whitelist
- // lists that word a should autocorrect to word b, and word c would autocorrect to
- // an upper-cased version of a. In this case, the way this return value is used would
- // remove the first candidate when the user typed the upper-cased version of A.
- // Example : abc -> def and xyz -> Abc
- // A user typing Abc would experience it being autocorrected to something else (not
- // necessarily def).
- // There is no such combination in the whitelist at the time and there probably won't
- // ever be - it doesn't make sense. But still.
- public boolean shouldForciblyAutoCorrectFrom(CharSequence word) {
- if (TextUtils.isEmpty(word)) return false;
- final String correction = getWhitelistedWord(word.toString());
- if (TextUtils.isEmpty(correction)) return false;
- return !correction.equals(word);
- }
-
- // Leave implementation of getWords and isValidWord to the superclass.
- // The words have been added to the ExpandableDictionary with addWord() inside initWordlist.
-}
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 5606a58e4..4b7adf26b 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -17,7 +17,6 @@
package com.android.inputmethod.latin;
import com.android.inputmethod.keyboard.Key;
-import com.android.inputmethod.keyboard.KeyDetector;
import com.android.inputmethod.keyboard.Keyboard;
import java.util.Arrays;
@@ -26,12 +25,16 @@ 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 static final int NOT_A_CODE = KeyDetector.NOT_A_CODE;
- public static final int NOT_A_COORDINATE = -1;
-
private static final int N = BinaryDictionary.MAX_WORD_LENGTH;
+ public static final int CAPS_MODE_OFF = 0;
+ // 1 is shift bit, 2 is caps bit, 4 is auto bit but this is just a convention as these bits
+ // aren't used anywhere in the code
+ public static final int CAPS_MODE_MANUAL_SHIFTED = 0x1;
+ public static final int CAPS_MODE_MANUAL_SHIFT_LOCKED = 0x3;
+ public static final int CAPS_MODE_AUTO_SHIFTED = 0x5;
+ public static final int CAPS_MODE_AUTO_SHIFT_LOCKED = 0x7;
+
private int[] mPrimaryKeyCodes;
private final InputPointers mInputPointers = new InputPointers(N);
private final StringBuilder mTypedWord;
@@ -42,7 +45,7 @@ public class WordComposer {
// Cache these values for performance
private int mCapsCount;
private int mDigitsCount;
- private boolean mAutoCapitalized;
+ private int mCapitalizedMode;
private int mTrailingSingleQuotesCount;
private int mCodePointSize;
@@ -68,7 +71,7 @@ public class WordComposer {
mCapsCount = source.mCapsCount;
mDigitsCount = source.mDigitsCount;
mIsFirstCharCapitalized = source.mIsFirstCharCapitalized;
- mAutoCapitalized = source.mAutoCapitalized;
+ mCapitalizedMode = source.mCapitalizedMode;
mTrailingSingleQuotesCount = source.mTrailingSingleQuotesCount;
mIsResumed = source.mIsResumed;
mIsBatchMode = source.mIsBatchMode;
@@ -166,7 +169,7 @@ public class WordComposer {
final int codePoint = Character.codePointAt(word, i);
// We don't want to override the batch input points that are held in mInputPointers
// (See {@link #add(int,int,int)}).
- add(codePoint, NOT_A_COORDINATE, NOT_A_COORDINATE);
+ add(codePoint, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
}
}
@@ -181,7 +184,7 @@ public class WordComposer {
add(codePoint, x, y);
return;
}
- add(codePoint, NOT_A_COORDINATE, NOT_A_COORDINATE);
+ add(codePoint, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
}
/**
@@ -262,7 +265,14 @@ public class WordComposer {
* @return true if all user typed chars are upper case, false otherwise
*/
public boolean isAllUpperCase() {
- return (mCapsCount > 0) && (mCapsCount == size());
+ return mCapitalizedMode == CAPS_MODE_AUTO_SHIFT_LOCKED
+ || mCapitalizedMode == CAPS_MODE_MANUAL_SHIFT_LOCKED
+ || (mCapsCount > 0) && (mCapsCount == size());
+ }
+
+ public boolean wasShiftedNoLock() {
+ return mCapitalizedMode == CAPS_MODE_AUTO_SHIFTED
+ || mCapitalizedMode == CAPS_MODE_MANUAL_SHIFTED;
}
/**
@@ -280,20 +290,27 @@ public class WordComposer {
}
/**
- * Saves the reason why the word is capitalized - whether it was automatic or
- * due to the user hitting shift in the middle of a sentence.
- * @param auto whether it was an automatic capitalization due to start of sentence
+ * Saves the caps mode at the start of composing.
+ *
+ * WordComposer needs to know about this for several reasons. The first is, we need to know
+ * after the fact what the reason was, to register the correct form into the user history
+ * dictionary: if the word was automatically capitalized, we should insert it in all-lower
+ * case but if it's a manual pressing of shift, then it should be inserted as is.
+ * Also, batch input needs to know about the current caps mode to display correctly
+ * capitalized suggestions.
+ * @param mode the mode at the time of start
*/
- public void setAutoCapitalized(boolean auto) {
- mAutoCapitalized = auto;
+ public void setCapitalizedModeAtStartComposingTime(final int mode) {
+ mCapitalizedMode = mode;
}
/**
* Returns whether the word was automatically capitalized.
* @return whether the word was automatically capitalized
*/
- public boolean isAutoCapitalized() {
- return mAutoCapitalized;
+ public boolean wasAutoCapitalized() {
+ return mCapitalizedMode == CAPS_MODE_AUTO_SHIFT_LOCKED
+ || mCapitalizedMode == CAPS_MODE_AUTO_SHIFTED;
}
/**
@@ -319,14 +336,14 @@ public class WordComposer {
// `type' should be one of the LastComposedWord.COMMIT_TYPE_* constants above.
public LastComposedWord commitWord(final int type, final String committedWord,
- final int separatorCode, final CharSequence prevWord) {
+ final String separatorString, final CharSequence 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.
final int[] primaryKeyCodes = mPrimaryKeyCodes;
mPrimaryKeyCodes = new int[N];
final LastComposedWord lastComposedWord = new LastComposedWord(primaryKeyCodes,
- mInputPointers, mTypedWord.toString(), committedWord, separatorCode,
+ mInputPointers, mTypedWord.toString(), committedWord, separatorString,
prevWord);
mInputPointers.reset();
if (type != LastComposedWord.COMMIT_TYPE_DECIDED_WORD
diff --git a/java/src/com/android/inputmethod/latin/XmlParseUtils.java b/java/src/com/android/inputmethod/latin/XmlParseUtils.java
index 481cdfa47..b5cbaf19e 100644
--- a/java/src/com/android/inputmethod/latin/XmlParseUtils.java
+++ b/java/src/com/android/inputmethod/latin/XmlParseUtils.java
@@ -23,7 +23,7 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
-public class XmlParseUtils {
+public final class XmlParseUtils {
private XmlParseUtils() {
// This utility class is not publicly instantiable.
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
index 2c3eee74c..abc39d923 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
@@ -22,15 +22,19 @@ import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
-import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
+import java.util.Stack;
import java.util.TreeMap;
/**
@@ -52,6 +56,8 @@ public class BinaryDictInputOutput {
* s | 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
*
* c | IF FLAG_HAS_MULTIPLE_CHARS
* h | char, char, char, char n * (1 or 3 bytes) : use CharGroupInfo for i/o helpers
@@ -124,7 +130,7 @@ public class BinaryDictInputOutput {
*/
private static final int VERSION_1_MAGIC_NUMBER = 0x78B1;
- private static final int VERSION_2_MAGIC_NUMBER = 0x9BC13AFE;
+ public static final int VERSION_2_MAGIC_NUMBER = 0x9BC13AFE;
private static final int MINIMUM_SUPPORTED_VERSION = 1;
private static final int MAXIMUM_SUPPORTED_VERSION = 2;
private static final int NOT_A_VERSION_NUMBER = -1;
@@ -150,6 +156,8 @@ public class BinaryDictInputOutput {
private static final int FLAG_IS_TERMINAL = 0x10;
private static final int FLAG_HAS_SHORTCUT_TARGETS = 0x08;
private static final int FLAG_HAS_BIGRAMS = 0x04;
+ private static final int FLAG_IS_NOT_A_WORD = 0x02;
+ private static final int FLAG_IS_BLACKLISTED = 0x01;
private static final int FLAG_ATTRIBUTE_HAS_NEXT = 0x80;
private static final int FLAG_ATTRIBUTE_OFFSET_NEGATIVE = 0x40;
@@ -185,6 +193,54 @@ public class BinaryDictInputOutput {
// suspicion that a bug might be causing an infinite loop.
private static final int MAX_PASSES = 24;
+ public interface FusionDictionaryBufferInterface {
+ public int readUnsignedByte();
+ public int readUnsignedShort();
+ public int readUnsignedInt24();
+ public int readInt();
+ public int position();
+ public void position(int newPosition);
+ }
+
+ public static final class ByteBufferWrapper implements FusionDictionaryBufferInterface {
+ private ByteBuffer mBuffer;
+
+ public ByteBufferWrapper(final ByteBuffer buffer) {
+ mBuffer = buffer;
+ }
+
+ @Override
+ public int readUnsignedByte() {
+ return ((int)mBuffer.get()) & 0xFF;
+ }
+
+ @Override
+ public int readUnsignedShort() {
+ return ((int)mBuffer.getShort()) & 0xFFFF;
+ }
+
+ @Override
+ public int readUnsignedInt24() {
+ final int retval = readUnsignedByte();
+ return (retval << 16) + readUnsignedShort();
+ }
+
+ @Override
+ public int readInt() {
+ return mBuffer.getInt();
+ }
+
+ @Override
+ public int position() {
+ return mBuffer.position();
+ }
+
+ @Override
+ public void position(int newPos) {
+ mBuffer.position(newPos);
+ }
+ }
+
/**
* A class grouping utility function for our specific character encoding.
*/
@@ -307,33 +363,32 @@ public class BinaryDictInputOutput {
}
/**
- * Reads a string from a RandomAccessFile. This is the converse of the above method.
+ * Reads a string from a buffer. This is the converse of the above method.
*/
- private static String readString(final RandomAccessFile source) throws IOException {
+ private static String readString(final FusionDictionaryBufferInterface buffer) {
final StringBuilder s = new StringBuilder();
- int character = readChar(source);
+ int character = readChar(buffer);
while (character != INVALID_CHARACTER) {
s.appendCodePoint(character);
- character = readChar(source);
+ character = readChar(buffer);
}
return s.toString();
}
/**
- * Reads a character from the file.
+ * Reads a character from the buffer.
*
* This follows the character format documented earlier in this source file.
*
- * @param source the file, positioned over an encoded character.
+ * @param buffer the buffer, positioned over an encoded character.
* @return the character code.
*/
- private static int readChar(RandomAccessFile source) throws IOException {
- int character = source.readUnsignedByte();
+ private static int readChar(final FusionDictionaryBufferInterface buffer) {
+ int character = buffer.readUnsignedByte();
if (!fitsOnOneByte(character)) {
- if (GROUP_CHARACTERS_TERMINATOR == character)
- return INVALID_CHARACTER;
+ if (GROUP_CHARACTERS_TERMINATOR == character) return INVALID_CHARACTER;
character <<= 16;
- character += source.readUnsignedShort();
+ character += buffer.readUnsignedShort();
}
return character;
}
@@ -728,6 +783,12 @@ public class BinaryDictInputOutput {
}
flags |= FLAG_HAS_BIGRAMS;
}
+ if (group.mIsNotAWord) {
+ flags |= FLAG_IS_NOT_A_WORD;
+ }
+ if (group.mIsBlacklistEntry) {
+ flags |= FLAG_IS_BLACKLISTED;
+ }
return flags;
}
@@ -783,10 +844,10 @@ public class BinaryDictInputOutput {
// their lower bound and exclude their higher bound so we need to have the first step
// start at exactly 1 unit higher than floor(unigramFreq + half a step).
// Note : to reconstruct the score, the dictionary reader will need to divide
- // MAX_TERMINAL_FREQUENCY - unigramFreq by 16.5 likewise, and add
- // (discretizedFrequency + 0.5) times this value to get the median value of the step,
- // which is the best approximation. This is how we get the most precise result with
- // only four bits.
+ // MAX_TERMINAL_FREQUENCY - unigramFreq by 16.5 likewise to get the value of the step,
+ // and add (discretizedFrequency + 0.5 + 0.5) times this value to get the best
+ // approximation. (0.5 to get the first step start, and 0.5 to get the middle of the
+ // step pointed by the discretized frequency.
final float stepSize =
(MAX_TERMINAL_FREQUENCY - unigramFrequency) / (1.5f + MAX_BIGRAM_FREQUENCY);
final float firstStepStart = 1 + unigramFrequency + (stepSize / 2.0f);
@@ -1091,46 +1152,46 @@ public class BinaryDictInputOutput {
// readDictionaryBinary is the public entry point for them.
static final int[] characterBuffer = new int[MAX_WORD_LENGTH];
- private static CharGroupInfo readCharGroup(RandomAccessFile source,
- final int originalGroupAddress) throws IOException {
+ private static CharGroupInfo readCharGroup(final FusionDictionaryBufferInterface buffer,
+ final int originalGroupAddress) {
int addressPointer = originalGroupAddress;
- final int flags = source.readUnsignedByte();
+ final int flags = buffer.readUnsignedByte();
++addressPointer;
final int characters[];
if (0 != (flags & FLAG_HAS_MULTIPLE_CHARS)) {
int index = 0;
- int character = CharEncoding.readChar(source);
+ int character = CharEncoding.readChar(buffer);
addressPointer += CharEncoding.getCharSize(character);
while (-1 != character) {
characterBuffer[index++] = character;
- character = CharEncoding.readChar(source);
+ character = CharEncoding.readChar(buffer);
addressPointer += CharEncoding.getCharSize(character);
}
characters = Arrays.copyOfRange(characterBuffer, 0, index);
} else {
- final int character = CharEncoding.readChar(source);
+ final int character = CharEncoding.readChar(buffer);
addressPointer += CharEncoding.getCharSize(character);
characters = new int[] { character };
}
final int frequency;
if (0 != (FLAG_IS_TERMINAL & flags)) {
++addressPointer;
- frequency = source.readUnsignedByte();
+ frequency = buffer.readUnsignedByte();
} else {
frequency = CharGroup.NOT_A_TERMINAL;
}
int childrenAddress = addressPointer;
switch (flags & MASK_GROUP_ADDRESS_TYPE) {
case FLAG_GROUP_ADDRESS_TYPE_ONEBYTE:
- childrenAddress += source.readUnsignedByte();
+ childrenAddress += buffer.readUnsignedByte();
addressPointer += 1;
break;
case FLAG_GROUP_ADDRESS_TYPE_TWOBYTES:
- childrenAddress += source.readUnsignedShort();
+ childrenAddress += buffer.readUnsignedShort();
addressPointer += 2;
break;
case FLAG_GROUP_ADDRESS_TYPE_THREEBYTES:
- childrenAddress += (source.readUnsignedByte() << 16) + source.readUnsignedShort();
+ childrenAddress += buffer.readUnsignedInt24();
addressPointer += 3;
break;
case FLAG_GROUP_ADDRESS_TYPE_NOADDRESS:
@@ -1140,38 +1201,38 @@ public class BinaryDictInputOutput {
}
ArrayList<WeightedString> shortcutTargets = null;
if (0 != (flags & FLAG_HAS_SHORTCUT_TARGETS)) {
- final long pointerBefore = source.getFilePointer();
+ final int pointerBefore = buffer.position();
shortcutTargets = new ArrayList<WeightedString>();
- source.readUnsignedShort(); // Skip the size
+ buffer.readUnsignedShort(); // Skip the size
while (true) {
- final int targetFlags = source.readUnsignedByte();
- final String word = CharEncoding.readString(source);
+ final int targetFlags = buffer.readUnsignedByte();
+ final String word = CharEncoding.readString(buffer);
shortcutTargets.add(new WeightedString(word,
targetFlags & FLAG_ATTRIBUTE_FREQUENCY));
if (0 == (targetFlags & FLAG_ATTRIBUTE_HAS_NEXT)) break;
}
- addressPointer += (source.getFilePointer() - pointerBefore);
+ addressPointer += buffer.position() - pointerBefore;
}
ArrayList<PendingAttribute> bigrams = null;
if (0 != (flags & FLAG_HAS_BIGRAMS)) {
bigrams = new ArrayList<PendingAttribute>();
while (true) {
- final int bigramFlags = source.readUnsignedByte();
+ final int bigramFlags = buffer.readUnsignedByte();
++addressPointer;
final int sign = 0 == (bigramFlags & FLAG_ATTRIBUTE_OFFSET_NEGATIVE) ? 1 : -1;
int bigramAddress = addressPointer;
switch (bigramFlags & MASK_ATTRIBUTE_ADDRESS_TYPE) {
case FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE:
- bigramAddress += sign * source.readUnsignedByte();
+ bigramAddress += sign * buffer.readUnsignedByte();
addressPointer += 1;
break;
case FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES:
- bigramAddress += sign * source.readUnsignedShort();
+ bigramAddress += sign * buffer.readUnsignedShort();
addressPointer += 2;
break;
case FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES:
- final int offset = ((source.readUnsignedByte() << 16)
- + source.readUnsignedShort());
+ final int offset = (buffer.readUnsignedByte() << 16)
+ + buffer.readUnsignedShort();
bigramAddress += sign * offset;
addressPointer += 3;
break;
@@ -1188,15 +1249,15 @@ public class BinaryDictInputOutput {
}
/**
- * Reads and returns the char group count out of a file and forwards the pointer.
+ * Reads and returns the char group count out of a buffer and forwards the pointer.
*/
- private static int readCharGroupCount(RandomAccessFile source) throws IOException {
- final int msb = source.readUnsignedByte();
+ private static int readCharGroupCount(final FusionDictionaryBufferInterface buffer) {
+ final int msb = buffer.readUnsignedByte();
if (MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT >= msb) {
return msb;
} else {
return ((MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT & msb) << 8)
- + source.readUnsignedByte();
+ + buffer.readUnsignedByte();
}
}
@@ -1204,31 +1265,29 @@ 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:
- // TODO: perform buffered I/O here and in other places in the code.
private static TreeMap<Integer, String> wordCache = new TreeMap<Integer, String>();
/**
* Finds, as a string, the word at the address passed as an argument.
*
- * @param source the file to read from.
+ * @param buffer the buffer to read from.
* @param headerSize the size of the header.
* @param address the address to seek.
* @return the word, as a string.
- * @throws IOException if the file can't be read.
*/
- private static String getWordAtAddress(final RandomAccessFile source, final long headerSize,
- int address) throws IOException {
+ private static String getWordAtAddress(final FusionDictionaryBufferInterface buffer,
+ final int headerSize, final int address) {
final String cachedString = wordCache.get(address);
if (null != cachedString) return cachedString;
- final long originalPointer = source.getFilePointer();
- source.seek(headerSize);
- final int count = readCharGroupCount(source);
+ final int originalPointer = buffer.position();
+ buffer.position(headerSize);
+ final int count = readCharGroupCount(buffer);
int groupOffset = getGroupCountSize(count);
final StringBuilder builder = new StringBuilder();
String result = null;
CharGroupInfo last = null;
for (int i = count - 1; i >= 0; --i) {
- CharGroupInfo info = readCharGroup(source, groupOffset);
+ CharGroupInfo info = readCharGroup(buffer, groupOffset);
groupOffset = info.mEndAddress;
if (info.mOriginalAddress == address) {
builder.append(new String(info.mCharacters, 0, info.mCharacters.length));
@@ -1239,9 +1298,9 @@ public class BinaryDictInputOutput {
if (info.mChildrenAddress > address) {
if (null == last) continue;
builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
- source.seek(last.mChildrenAddress + headerSize);
+ buffer.position(last.mChildrenAddress + headerSize);
groupOffset = last.mChildrenAddress + 1;
- i = source.readUnsignedByte();
+ i = buffer.readUnsignedByte();
last = null;
continue;
}
@@ -1249,64 +1308,69 @@ public class BinaryDictInputOutput {
}
if (0 == i && hasChildrenAddress(last.mChildrenAddress)) {
builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
- source.seek(last.mChildrenAddress + headerSize);
+ buffer.position(last.mChildrenAddress + headerSize);
groupOffset = last.mChildrenAddress + 1;
- i = source.readUnsignedByte();
+ i = buffer.readUnsignedByte();
last = null;
continue;
}
}
- source.seek(originalPointer);
+ buffer.position(originalPointer);
wordCache.put(address, result);
return result;
}
/**
- * Reads a single node from a binary file.
+ * Reads a single node from a buffer.
*
- * This methods reads the file at the current position of its file pointer. A node is
- * fully expected to start at the current position.
+ * This methods reads the file at the current position. A node is fully expected to start at
+ * the current position.
* This will recursively read other nodes into the structure, populating the reverse
* maps on the fly and using them to keep track of already read nodes.
*
- * @param source the data file, correctly positioned at the start of a node.
+ * @param buffer the buffer, correctly positioned at the start of a node.
* @param headerSize the size, in bytes, of the file header.
* @param reverseNodeMap a mapping from addresses to already read nodes.
* @param reverseGroupMap a mapping from addresses to already read character groups.
* @return the read node with all his children already read.
*/
- private static Node readNode(RandomAccessFile source, long headerSize,
- Map<Integer, Node> reverseNodeMap, Map<Integer, CharGroup> reverseGroupMap)
+ private static Node readNode(final FusionDictionaryBufferInterface buffer, final int headerSize,
+ final Map<Integer, Node> reverseNodeMap, final Map<Integer, CharGroup> reverseGroupMap)
throws IOException {
- final int nodeOrigin = (int)(source.getFilePointer() - headerSize);
- final int count = readCharGroupCount(source);
+ final int nodeOrigin = buffer.position() - headerSize;
+ final int count = readCharGroupCount(buffer);
final ArrayList<CharGroup> nodeContents = new ArrayList<CharGroup>();
int groupOffset = nodeOrigin + getGroupCountSize(count);
for (int i = count; i > 0; --i) {
- CharGroupInfo info = readCharGroup(source, groupOffset);
+ CharGroupInfo info = readCharGroup(buffer, groupOffset);
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(source, headerSize, bigram.mAddress);
+ final String word = getWordAtAddress(
+ buffer, headerSize, bigram.mAddress);
bigrams.add(new WeightedString(word, bigram.mFrequency));
}
}
if (hasChildrenAddress(info.mChildrenAddress)) {
Node children = reverseNodeMap.get(info.mChildrenAddress);
if (null == children) {
- final long currentPosition = source.getFilePointer();
- source.seek(info.mChildrenAddress + headerSize);
- children = readNode(source, headerSize, reverseNodeMap, reverseGroupMap);
- source.seek(currentPosition);
+ final int currentPosition = buffer.position();
+ buffer.position(info.mChildrenAddress + headerSize);
+ children = readNode(
+ buffer, headerSize, reverseNodeMap, reverseGroupMap);
+ buffer.position(currentPosition);
}
nodeContents.add(
new CharGroup(info.mCharacters, shortcutTargets, bigrams, info.mFrequency,
- children));
+ 0 != (info.mFlags & FLAG_IS_NOT_A_WORD),
+ 0 != (info.mFlags & FLAG_IS_BLACKLISTED), children));
} else {
nodeContents.add(
- new CharGroup(info.mCharacters, shortcutTargets, bigrams, info.mFrequency));
+ new CharGroup(info.mCharacters, shortcutTargets, bigrams, info.mFrequency,
+ 0 != (info.mFlags & FLAG_IS_NOT_A_WORD),
+ 0 != (info.mFlags & FLAG_IS_BLACKLISTED)));
}
groupOffset = info.mEndAddress;
}
@@ -1316,59 +1380,205 @@ public class BinaryDictInputOutput {
return node;
}
+ // TODO: move these methods (readUnigramsAndBigramsBinary(|Inner)) and an inner class (Position)
+ // out of this class.
+ private static class Position {
+ public static final int NOT_READ_GROUPCOUNT = -1;
+
+ public int mAddress;
+ public int mNumOfCharGroup;
+ public int mPosition;
+ public int mLength;
+
+ public Position(int address, int length) {
+ mAddress = address;
+ mLength = length;
+ mNumOfCharGroup = NOT_READ_GROUPCOUNT;
+ }
+ }
+
+ /**
+ * Tours all node without recursive call.
+ */
+ private static void readUnigramsAndBigramsBinaryInner(
+ final FusionDictionaryBufferInterface buffer, final int headerSize,
+ final Map<Integer, String> words, final Map<Integer, Integer> frequencies,
+ final Map<Integer, ArrayList<PendingAttribute>> bigrams) {
+ int[] pushedChars = new int[MAX_WORD_LENGTH + 1];
+
+ Stack<Position> stack = new Stack<Position>();
+ int index = 0;
+
+ Position initPos = new Position(headerSize, 0);
+ stack.push(initPos);
+
+ while (!stack.empty()) {
+ Position p = stack.peek();
+
+ if (DBG) {
+ MakedictLog.d("read: address=" + p.mAddress + ", numOfCharGroup=" +
+ p.mNumOfCharGroup + ", position=" + p.mPosition + ", length=" + p.mLength);
+ }
+
+ if (buffer.position() != p.mAddress) buffer.position(p.mAddress);
+ if (index != p.mLength) index = p.mLength;
+
+ if (p.mNumOfCharGroup == Position.NOT_READ_GROUPCOUNT) {
+ p.mNumOfCharGroup = readCharGroupCount(buffer);
+ p.mAddress += getGroupCountSize(p.mNumOfCharGroup);
+ p.mPosition = 0;
+ }
+
+ CharGroupInfo info = readCharGroup(buffer, p.mAddress - headerSize);
+ for (int i = 0; i < info.mCharacters.length; ++i) {
+ pushedChars[index++] = info.mCharacters[i];
+ }
+ p.mPosition++;
+
+ if (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();
+ } else {
+ // the node has more groups.
+ p.mAddress = buffer.position();
+ }
+
+ if (hasChildrenAddress(info.mChildrenAddress)) {
+ Position childrenPos = new Position(info.mChildrenAddress + headerSize, index);
+ stack.push(childrenPos);
+ }
+ }
+ }
+
+ /**
+ * Reads unigrams and bigrams from the binary file.
+ * Doesn't make the memory representation of the dictionary.
+ *
+ * @param buffer the buffer to read.
+ * @param words the map to store the address as a key and the word as a value.
+ * @param frequencies the map to store the address as a key and the frequency as a value.
+ * @param bigrams the map to store the address as a key and the list of address as a value.
+ * @throws IOException
+ * @throws UnsupportedFormatException
+ */
+ public static void readUnigramsAndBigramsBinary(final FusionDictionaryBufferInterface buffer,
+ final Map<Integer, String> words, final Map<Integer, Integer> frequencies,
+ final Map<Integer, ArrayList<PendingAttribute>> bigrams) throws IOException,
+ UnsupportedFormatException {
+ // Read header
+ final int version = checkFormatVersion(buffer);
+ final int optionsFlags = buffer.readUnsignedShort();
+ final HashMap<String, String> options = new HashMap<String, String>();
+ final int headerSize = readHeader(buffer, options, version);
+
+ readUnigramsAndBigramsBinaryInner(buffer, headerSize, words, frequencies, bigrams);
+ }
+
/**
* Helper function to get the binary format version from the header.
+ * @throws IOException
*/
- private static int getFormatVersion(final RandomAccessFile source) throws IOException {
- final int magic_v1 = source.readUnsignedShort();
- if (VERSION_1_MAGIC_NUMBER == magic_v1) return source.readUnsignedByte();
- final int magic_v2 = (magic_v1 << 16) + source.readUnsignedShort();
- if (VERSION_2_MAGIC_NUMBER == magic_v2) return source.readUnsignedShort();
+ private static int getFormatVersion(final FusionDictionaryBufferInterface buffer)
+ throws IOException {
+ final int magic_v1 = buffer.readUnsignedShort();
+ if (VERSION_1_MAGIC_NUMBER == magic_v1) return buffer.readUnsignedByte();
+ final int magic_v2 = (magic_v1 << 16) + buffer.readUnsignedShort();
+ if (VERSION_2_MAGIC_NUMBER == magic_v2) return buffer.readUnsignedShort();
return NOT_A_VERSION_NUMBER;
}
/**
- * Reads a random access file and returns the memory representation of the dictionary.
- *
- * This high-level method takes a binary file and reads its contents, populating a
- * FusionDictionary structure. The optional dict argument is an existing dictionary to
- * which words from the file should be added. If it is null, a new dictionary is created.
- *
- * @param source the file to read.
- * @param dict an optional dictionary to add words to, or null.
- * @return the created (or merged) dictionary.
+ * Helper function to get and validate the binary format version.
+ * @throws UnsupportedFormatException
+ * @throws IOException
*/
- public static FusionDictionary readDictionaryBinary(final RandomAccessFile source,
- final FusionDictionary dict) throws IOException, UnsupportedFormatException {
- // Check file version
- final int version = getFormatVersion(source);
- if (version < MINIMUM_SUPPORTED_VERSION || version > MAXIMUM_SUPPORTED_VERSION ) {
+ private static int checkFormatVersion(final FusionDictionaryBufferInterface buffer)
+ throws IOException, UnsupportedFormatException {
+ final int version = getFormatVersion(buffer);
+ if (version < MINIMUM_SUPPORTED_VERSION || version > MAXIMUM_SUPPORTED_VERSION) {
throw new UnsupportedFormatException("This file has version " + version
+ ", but this implementation does not support versions above "
+ MAXIMUM_SUPPORTED_VERSION);
}
+ return version;
+ }
- // Read options
- final int optionsFlags = source.readUnsignedShort();
-
- final long headerSize;
- final HashMap<String, String> options = new HashMap<String, String>();
+ /**
+ * Reads a header from a buffer.
+ * @throws IOException
+ * @throws UnsupportedFormatException
+ */
+ private static int readHeader(final FusionDictionaryBufferInterface buffer,
+ final HashMap<String, String> options, final int version)
+ throws IOException, UnsupportedFormatException {
+ final int headerSize;
if (version < FIRST_VERSION_WITH_HEADER_SIZE) {
- headerSize = source.getFilePointer();
+ headerSize = buffer.position();
} else {
- headerSize = (source.readUnsignedByte() << 24) + (source.readUnsignedByte() << 16)
- + (source.readUnsignedByte() << 8) + source.readUnsignedByte();
- while (source.getFilePointer() < headerSize) {
- final String key = CharEncoding.readString(source);
- final String value = CharEncoding.readString(source);
- options.put(key, value);
- }
- source.seek(headerSize);
+ headerSize = buffer.readInt();
+ populateOptions(buffer, headerSize, options);
+ buffer.position(headerSize);
}
+ if (headerSize < 0) {
+ throw new UnsupportedFormatException("header size can't be negative.");
+ }
+ return headerSize;
+ }
+
+ /**
+ * Reads options from a buffer and populate a map with their contents.
+ *
+ * The buffer is read at the current position, so the caller must take care the pointer
+ * is in the right place before calling this.
+ */
+ public static void populateOptions(final FusionDictionaryBufferInterface buffer,
+ final int headerSize, final HashMap<String, String> options) {
+ while (buffer.position() < headerSize) {
+ final String key = CharEncoding.readString(buffer);
+ final String value = CharEncoding.readString(buffer);
+ options.put(key, value);
+ }
+ }
+ // TODO: remove this method.
+ public static void populateOptions(final ByteBuffer buffer, final int headerSize,
+ final HashMap<String, String> options) {
+ populateOptions(new ByteBufferWrapper(buffer), headerSize, options);
+ }
+
+ /**
+ * Reads a buffer and returns the memory representation of the dictionary.
+ *
+ * This high-level method takes a buffer and reads its contents, populating a
+ * FusionDictionary structure. The optional dict argument is an existing dictionary to
+ * which words from the buffer should be added. If it is null, a new dictionary is created.
+ *
+ * @param buffer the buffer to read.
+ * @param dict an optional dictionary to add words to, or null.
+ * @return the created (or merged) dictionary.
+ */
+ public static FusionDictionary readDictionaryBinary(
+ final FusionDictionaryBufferInterface buffer, final FusionDictionary dict)
+ throws IOException, UnsupportedFormatException {
+ // clear cache
+ wordCache.clear();
+
+ // Read header
+ final int version = checkFormatVersion(buffer);
+ final int optionsFlags = buffer.readUnsignedShort();
+
+ final HashMap<String, String> options = new HashMap<String, String>();
+ final int headerSize = readHeader(buffer, options, version);
+
Map<Integer, Node> reverseNodeMapping = new TreeMap<Integer, Node>();
Map<Integer, CharGroup> reverseGroupMapping = new TreeMap<Integer, CharGroup>();
- final Node root = readNode(source, headerSize, reverseNodeMapping, reverseGroupMapping);
+ final Node root = readNode(
+ buffer, headerSize, reverseNodeMapping, reverseGroupMapping);
FusionDictionary newDict = new FusionDictionary(root,
new FusionDictionary.DictionaryOptions(options,
@@ -1376,7 +1586,11 @@ public class BinaryDictInputOutput {
0 != (optionsFlags & FRENCH_LIGATURE_PROCESSING_FLAG)));
if (null != dict) {
for (final Word w : dict) {
- newDict.add(w.mWord, w.mFrequency, w.mShortcutTargets);
+ if (w.mIsBlacklistEntry) {
+ newDict.addBlacklistEntry(w.mWord, w.mShortcutTargets, w.mIsNotAWord);
+ } else {
+ newDict.add(w.mWord, w.mFrequency, w.mShortcutTargets, w.mIsNotAWord);
+ }
}
for (final Word w : dict) {
// By construction a binary dictionary may not have bigrams pointing to
@@ -1391,6 +1605,12 @@ public class BinaryDictInputOutput {
return newDict;
}
+ // TODO: remove this method.
+ public static FusionDictionary readDictionaryBinary(final ByteBuffer buffer,
+ final FusionDictionary dict) throws IOException, UnsupportedFormatException {
+ return readDictionaryBinary(new ByteBufferWrapper(buffer), dict);
+ }
+
/**
* Basic test to find out whether the file is a binary dictionary or not.
*
@@ -1400,14 +1620,44 @@ public class BinaryDictInputOutput {
* @return true if it's a binary dictionary, false otherwise
*/
public static boolean isBinaryDictionary(final String filename) {
+ FileInputStream inStream = null;
try {
- RandomAccessFile f = new RandomAccessFile(filename, "r");
- final int version = getFormatVersion(f);
+ final File file = new File(filename);
+ inStream = new FileInputStream(file);
+ final ByteBuffer buffer = inStream.getChannel().map(
+ FileChannel.MapMode.READ_ONLY, 0, file.length());
+ final int version = getFormatVersion(new ByteBufferWrapper(buffer));
return (version >= MINIMUM_SUPPORTED_VERSION && version <= MAXIMUM_SUPPORTED_VERSION);
} catch (FileNotFoundException e) {
return false;
} catch (IOException e) {
return false;
+ } finally {
+ if (inStream != null) {
+ try {
+ inStream.close();
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
}
}
+
+ /**
+ * Calculate bigram frequency from compressed value
+ *
+ * @see #makeBigramFlags
+ *
+ * @param unigramFrequency
+ * @param bigramFrequency compressed frequency
+ * @return approximate bigram frequency
+ */
+ public static int reconstructBigramFrequency(final int unigramFrequency,
+ final int bigramFrequency) {
+ final float stepSize = (MAX_TERMINAL_FREQUENCY - unigramFrequency)
+ / (1.5f + MAX_BIGRAM_FREQUENCY);
+ final float resultFreqFloat = (float)unigramFrequency
+ + stepSize * (bigramFrequency + 1.0f);
+ return (int)resultFreqFloat;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
index 5864db28e..f1abea9ec 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
@@ -101,26 +101,34 @@ public class FusionDictionary implements Iterable<Word> {
ArrayList<WeightedString> mBigrams;
int mFrequency; // NOT_A_TERMINAL == mFrequency indicates this is not a terminal.
Node mChildren;
+ boolean mIsNotAWord; // Only a shortcut
+ boolean mIsBlacklistEntry;
// The two following members to help with binary generation
int mCachedSize;
int mCachedAddress;
public CharGroup(final int[] chars, final ArrayList<WeightedString> shortcutTargets,
- final ArrayList<WeightedString> bigrams, final int frequency) {
+ final ArrayList<WeightedString> bigrams, final int frequency,
+ final boolean isNotAWord, final boolean isBlacklistEntry) {
mChars = chars;
mFrequency = frequency;
mShortcutTargets = shortcutTargets;
mBigrams = bigrams;
mChildren = null;
+ mIsNotAWord = isNotAWord;
+ mIsBlacklistEntry = isBlacklistEntry;
}
public CharGroup(final int[] chars, final ArrayList<WeightedString> shortcutTargets,
- final ArrayList<WeightedString> bigrams, final int frequency, final Node children) {
+ final ArrayList<WeightedString> bigrams, final int frequency,
+ final boolean isNotAWord, final boolean isBlacklistEntry, final Node children) {
mChars = chars;
mFrequency = frequency;
mShortcutTargets = shortcutTargets;
mBigrams = bigrams;
mChildren = children;
+ mIsNotAWord = isNotAWord;
+ mIsBlacklistEntry = isBlacklistEntry;
}
public void addChild(CharGroup n) {
@@ -197,8 +205,9 @@ public class FusionDictionary implements Iterable<Word> {
* the existing ones if any. Note: unigram, bigram, and shortcut frequencies are only
* updated if they are higher than the existing ones.
*/
- public void update(int frequency, ArrayList<WeightedString> shortcutTargets,
- ArrayList<WeightedString> bigrams) {
+ public void update(final int frequency, final ArrayList<WeightedString> shortcutTargets,
+ final ArrayList<WeightedString> bigrams,
+ final boolean isNotAWord, final boolean isBlacklistEntry) {
if (frequency > mFrequency) {
mFrequency = frequency;
}
@@ -234,6 +243,8 @@ public class FusionDictionary implements Iterable<Word> {
}
}
}
+ mIsNotAWord = isNotAWord;
+ mIsBlacklistEntry = isBlacklistEntry;
}
}
@@ -296,10 +307,24 @@ public class FusionDictionary implements Iterable<Word> {
* @param word the word to add.
* @param frequency the frequency of the word, in the range [0..255].
* @param shortcutTargets a list of shortcut targets for this word, or null.
+ * @param isNotAWord true if this should not be considered a word (e.g. shortcut only)
*/
public void add(final String word, final int frequency,
- final ArrayList<WeightedString> shortcutTargets) {
- add(getCodePoints(word), frequency, shortcutTargets);
+ final ArrayList<WeightedString> shortcutTargets, final boolean isNotAWord) {
+ add(getCodePoints(word), frequency, shortcutTargets, isNotAWord,
+ false /* isBlacklistEntry */);
+ }
+
+ /**
+ * Helper method to add a blacklist entry as a string.
+ *
+ * @param word the word to add as a blacklist entry.
+ * @param shortcutTargets a list of shortcut targets for this word, or null.
+ * @param isNotAWord true if this is not a word for spellcheking purposes (shortcut only or so)
+ */
+ public void addBlacklistEntry(final String word,
+ final ArrayList<WeightedString> shortcutTargets, final boolean isNotAWord) {
+ add(getCodePoints(word), 0, shortcutTargets, isNotAWord, true /* isBlacklistEntry */);
}
/**
@@ -332,7 +357,8 @@ public class FusionDictionary implements Iterable<Word> {
if (charGroup != null) {
final CharGroup charGroup2 = findWordInTree(mRoot, word2);
if (charGroup2 == null) {
- add(getCodePoints(word2), 0, null);
+ add(getCodePoints(word2), 0, null, false /* isNotAWord */,
+ false /* isBlacklistEntry */);
}
charGroup.addBigram(word2, frequency);
} else {
@@ -349,9 +375,12 @@ public class FusionDictionary implements Iterable<Word> {
* @param word the word, as an int array.
* @param frequency the frequency of the word, in the range [0..255].
* @param shortcutTargets an optional list of shortcut targets for this word (null if none).
+ * @param isNotAWord true if this is not a word for spellcheking purposes (shortcut only or so)
+ * @param isBlacklistEntry true if this is a blacklisted word, false otherwise
*/
private void add(final int[] word, final int frequency,
- final ArrayList<WeightedString> shortcutTargets) {
+ final ArrayList<WeightedString> shortcutTargets,
+ final boolean isNotAWord, final boolean isBlacklistEntry) {
assert(frequency >= 0 && frequency <= 255);
Node currentNode = mRoot;
int charIndex = 0;
@@ -376,7 +405,7 @@ public class FusionDictionary implements Iterable<Word> {
final int insertionIndex = findInsertionIndex(currentNode, word[charIndex]);
final CharGroup newGroup = new CharGroup(
Arrays.copyOfRange(word, charIndex, word.length),
- shortcutTargets, null /* bigrams */, frequency);
+ shortcutTargets, null /* bigrams */, frequency, isNotAWord, isBlacklistEntry);
currentNode.mData.add(insertionIndex, newGroup);
if (DBG) checkStack(currentNode);
} else {
@@ -386,13 +415,15 @@ public class FusionDictionary implements Iterable<Word> {
// The new word is a prefix of an existing word, but the node on which it
// should end already exists as is. Since the old CharNode was not a terminal,
// make it one by filling in its frequency and other attributes
- currentGroup.update(frequency, shortcutTargets, null);
+ currentGroup.update(frequency, shortcutTargets, null, isNotAWord,
+ isBlacklistEntry);
} else {
// The new word matches the full old word and extends past it.
// We only have to create a new node and add it to the end of this.
final CharGroup newNode = new CharGroup(
Arrays.copyOfRange(word, charIndex + differentCharIndex, word.length),
- shortcutTargets, null /* bigrams */, frequency);
+ shortcutTargets, null /* bigrams */, frequency, isNotAWord,
+ isBlacklistEntry);
currentGroup.mChildren = new Node();
currentGroup.mChildren.mData.add(newNode);
}
@@ -400,7 +431,9 @@ public class FusionDictionary implements Iterable<Word> {
if (0 == differentCharIndex) {
// Exact same word. Update the frequency if higher. This will also add the
// new shortcuts to the existing shortcut list if it already exists.
- currentGroup.update(frequency, shortcutTargets, null);
+ currentGroup.update(frequency, shortcutTargets, null,
+ currentGroup.mIsNotAWord && isNotAWord,
+ currentGroup.mIsBlacklistEntry || isBlacklistEntry);
} else {
// Partial prefix match only. We have to replace the current node with a node
// containing the current prefix and create two new ones for the tails.
@@ -408,21 +441,26 @@ public class FusionDictionary implements Iterable<Word> {
final CharGroup newOldWord = new CharGroup(
Arrays.copyOfRange(currentGroup.mChars, differentCharIndex,
currentGroup.mChars.length), currentGroup.mShortcutTargets,
- currentGroup.mBigrams, currentGroup.mFrequency, currentGroup.mChildren);
+ currentGroup.mBigrams, currentGroup.mFrequency,
+ currentGroup.mIsNotAWord, currentGroup.mIsBlacklistEntry,
+ currentGroup.mChildren);
newChildren.mData.add(newOldWord);
final CharGroup newParent;
if (charIndex + differentCharIndex >= word.length) {
newParent = new CharGroup(
Arrays.copyOfRange(currentGroup.mChars, 0, differentCharIndex),
- shortcutTargets, null /* bigrams */, frequency, newChildren);
+ shortcutTargets, null /* bigrams */, frequency,
+ isNotAWord, isBlacklistEntry, newChildren);
} else {
newParent = new CharGroup(
Arrays.copyOfRange(currentGroup.mChars, 0, differentCharIndex),
- null /* shortcutTargets */, null /* bigrams */, -1, newChildren);
+ null /* shortcutTargets */, null /* bigrams */, -1,
+ false /* isNotAWord */, false /* isBlacklistEntry */, newChildren);
final CharGroup newWord = new CharGroup(Arrays.copyOfRange(word,
charIndex + differentCharIndex, word.length),
- shortcutTargets, null /* bigrams */, frequency);
+ shortcutTargets, null /* bigrams */, frequency,
+ isNotAWord, isBlacklistEntry);
final int addIndex = word[charIndex + differentCharIndex]
> currentGroup.mChars[differentCharIndex] ? 1 : 0;
newChildren.mData.add(addIndex, newWord);
@@ -483,7 +521,8 @@ public class FusionDictionary implements Iterable<Word> {
private static int findInsertionIndex(final Node node, int character) {
final ArrayList<CharGroup> data = node.mData;
final CharGroup reference = new CharGroup(new int[] { character },
- null /* shortcutTargets */, null /* bigrams */, 0);
+ null /* shortcutTargets */, null /* bigrams */, 0, false /* isNotAWord */,
+ false /* isBlacklistEntry */);
int result = Collections.binarySearch(data, reference, CHARGROUP_COMPARATOR);
return result >= 0 ? result : -result - 1;
}
@@ -516,13 +555,23 @@ public class FusionDictionary implements Iterable<Word> {
int indexOfGroup = findIndexOfChar(node, s.codePointAt(index));
if (CHARACTER_NOT_FOUND == indexOfGroup) return null;
currentGroup = node.mData.get(indexOfGroup);
+
+ if (s.length() - index < currentGroup.mChars.length) return null;
+ int newIndex = index;
+ while (newIndex < s.length() && newIndex - index < currentGroup.mChars.length) {
+ if (currentGroup.mChars[newIndex - index] != s.codePointAt(newIndex)) return null;
+ newIndex++;
+ }
+ index = newIndex;
+
if (DBG) checker.append(new String(currentGroup.mChars, 0, currentGroup.mChars.length));
- index += currentGroup.mChars.length;
if (index < s.length()) {
node = currentGroup.mChildren;
}
} while (null != node && index < s.length());
+ if (index < s.length()) return null;
+ if (!currentGroup.isTerminal()) return null;
if (DBG && !s.equals(checker.toString())) return null;
return currentGroup;
}
@@ -738,7 +787,8 @@ public class FusionDictionary implements Iterable<Word> {
}
if (currentGroup.mFrequency >= 0)
return new Word(mCurrentString.toString(), currentGroup.mFrequency,
- currentGroup.mShortcutTargets, currentGroup.mBigrams);
+ currentGroup.mShortcutTargets, currentGroup.mBigrams,
+ currentGroup.mIsNotAWord, currentGroup.mIsBlacklistEntry);
} else {
mPositions.removeLast();
currentPos = mPositions.getLast();
diff --git a/java/src/com/android/inputmethod/latin/makedict/Word.java b/java/src/com/android/inputmethod/latin/makedict/Word.java
index 65fc72c40..4683ef154 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Word.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Word.java
@@ -31,16 +31,21 @@ public class Word implements Comparable<Word> {
public final int mFrequency;
public final ArrayList<WeightedString> mShortcutTargets;
public final ArrayList<WeightedString> mBigrams;
+ public final boolean mIsNotAWord;
+ public final boolean mIsBlacklistEntry;
private int mHashCode = 0;
public Word(final String word, final int frequency,
final ArrayList<WeightedString> shortcutTargets,
- final ArrayList<WeightedString> bigrams) {
+ final ArrayList<WeightedString> bigrams,
+ final boolean isNotAWord, final boolean isBlacklistEntry) {
mWord = word;
mFrequency = frequency;
mShortcutTargets = shortcutTargets;
mBigrams = bigrams;
+ mIsNotAWord = isNotAWord;
+ mIsBlacklistEntry = isBlacklistEntry;
}
private static int computeHashCode(Word word) {
@@ -48,7 +53,9 @@ public class Word implements Comparable<Word> {
word.mWord,
word.mFrequency,
word.mShortcutTargets.hashCode(),
- word.mBigrams.hashCode()
+ word.mBigrams.hashCode(),
+ word.mIsNotAWord,
+ word.mIsBlacklistEntry
});
}
@@ -78,7 +85,9 @@ public class Word implements Comparable<Word> {
Word w = (Word)o;
return mFrequency == w.mFrequency && mWord.equals(w.mWord)
&& mShortcutTargets.equals(w.mShortcutTargets)
- && mBigrams.equals(w.mBigrams);
+ && mBigrams.equals(w.mBigrams)
+ && mIsNotAWord == w.mIsNotAWord
+ && mIsBlacklistEntry == w.mIsBlacklistEntry;
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 3bdfe1f27..eef7a51f2 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -25,6 +25,7 @@ import android.view.textservice.SuggestionsInfo;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.BinaryDictionary;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.ContactsBinaryDictionary;
import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.DictionaryCollection;
@@ -35,7 +36,6 @@ import com.android.inputmethod.latin.StringUtils;
import com.android.inputmethod.latin.SynchronouslyLoadedContactsBinaryDictionary;
import com.android.inputmethod.latin.SynchronouslyLoadedUserBinaryDictionary;
import com.android.inputmethod.latin.UserBinaryDictionary;
-import com.android.inputmethod.latin.WhitelistDictionary;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -63,12 +63,9 @@ public class AndroidSpellCheckerService extends SpellCheckerService
public static final int CAPITALIZE_ALL = 2; // All caps
private final static String[] EMPTY_STRING_ARRAY = new String[0];
- private Map<String, DictionaryPool> mDictionaryPools =
- Collections.synchronizedMap(new TreeMap<String, DictionaryPool>());
+ private Map<String, DictionaryPool> mDictionaryPools = CollectionUtils.newSynchronizedTreeMap();
private Map<String, UserBinaryDictionary> mUserDictionaries =
- Collections.synchronizedMap(new TreeMap<String, UserBinaryDictionary>());
- private Map<String, Dictionary> mWhitelistDictionaries =
- Collections.synchronizedMap(new TreeMap<String, Dictionary>());
+ CollectionUtils.newSynchronizedTreeMap();
private ContactsBinaryDictionary mContactsDictionary;
// The threshold for a candidate to be offered as a suggestion.
@@ -80,7 +77,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService
private final Object mUseContactsLock = new Object();
private final HashSet<WeakReference<DictionaryCollection>> mDictionaryCollectionsList =
- new HashSet<WeakReference<DictionaryCollection>>();
+ CollectionUtils.newHashSet();
public static final int SCRIPT_LATIN = 0;
public static final int SCRIPT_CYRILLIC = 1;
@@ -96,7 +93,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService
// proximity to pass to the dictionary descent algorithm.
// IMPORTANT: this only contains languages - do not write countries in there.
// Only the language is searched from the map.
- mLanguageToScript = new TreeMap<String, Integer>();
+ mLanguageToScript = CollectionUtils.newTreeMap();
mLanguageToScript.put("en", SCRIPT_LATIN);
mLanguageToScript.put("fr", SCRIPT_LATIN);
mLanguageToScript.put("de", SCRIPT_LATIN);
@@ -234,7 +231,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService
mSuggestionThreshold = suggestionThreshold;
mRecommendedThreshold = recommendedThreshold;
mMaxLength = maxLength;
- mSuggestions = new ArrayList<CharSequence>(maxLength + 1);
+ mSuggestions = CollectionUtils.newArrayList(maxLength + 1);
mScores = new int[mMaxLength];
}
@@ -362,12 +359,9 @@ public class AndroidSpellCheckerService extends SpellCheckerService
private void closeAllDictionaries() {
final Map<String, DictionaryPool> oldPools = mDictionaryPools;
- mDictionaryPools = Collections.synchronizedMap(new TreeMap<String, DictionaryPool>());
+ mDictionaryPools = CollectionUtils.newSynchronizedTreeMap();
final Map<String, UserBinaryDictionary> oldUserDictionaries = mUserDictionaries;
- mUserDictionaries =
- Collections.synchronizedMap(new TreeMap<String, UserBinaryDictionary>());
- final Map<String, Dictionary> oldWhitelistDictionaries = mWhitelistDictionaries;
- mWhitelistDictionaries = Collections.synchronizedMap(new TreeMap<String, Dictionary>());
+ mUserDictionaries = CollectionUtils.newSynchronizedTreeMap();
new Thread("spellchecker_close_dicts") {
@Override
public void run() {
@@ -377,9 +371,6 @@ public class AndroidSpellCheckerService extends SpellCheckerService
for (Dictionary dict : oldUserDictionaries.values()) {
dict.close();
}
- for (Dictionary dict : oldWhitelistDictionaries.values()) {
- dict.close();
- }
synchronized (mUseContactsLock) {
if (null != mContactsDictionary) {
// The synchronously loaded contacts dictionary should have been in one
@@ -423,12 +414,6 @@ public class AndroidSpellCheckerService extends SpellCheckerService
mUserDictionaries.put(localeStr, userDictionary);
}
dictionaryCollection.addDictionary(userDictionary);
- Dictionary whitelistDictionary = mWhitelistDictionaries.get(localeStr);
- if (null == whitelistDictionary) {
- whitelistDictionary = new WhitelistDictionary(this, locale);
- mWhitelistDictionaries.put(localeStr, whitelistDictionary);
- }
- dictionaryCollection.addDictionary(whitelistDictionary);
synchronized (mUseContactsLock) {
if (mUseContactsDictionary) {
if (null == mContactsDictionary) {
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
index 501a0e221..5a1bd37f5 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
@@ -22,6 +22,8 @@ import android.view.textservice.SentenceSuggestionsInfo;
import android.view.textservice.SuggestionsInfo;
import android.view.textservice.TextInfo;
+import com.android.inputmethod.latin.CollectionUtils;
+
import java.util.ArrayList;
public class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheckerSession {
@@ -40,10 +42,10 @@ public class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheckerSess
return null;
}
final int N = ssi.getSuggestionsCount();
- final ArrayList<Integer> additionalOffsets = new ArrayList<Integer>();
- final ArrayList<Integer> additionalLengths = new ArrayList<Integer>();
+ final ArrayList<Integer> additionalOffsets = CollectionUtils.newArrayList();
+ final ArrayList<Integer> additionalLengths = CollectionUtils.newArrayList();
final ArrayList<SuggestionsInfo> additionalSuggestionsInfos =
- new ArrayList<SuggestionsInfo>();
+ CollectionUtils.newArrayList();
String currentWord = null;
for (int i = 0; i < N; ++i) {
final SuggestionsInfo si = ssi.getSuggestionsInfoAt(i);
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index 06f5db749..f4784ff1a 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -24,6 +24,7 @@ import android.view.textservice.SuggestionsInfo;
import android.view.textservice.TextInfo;
import com.android.inputmethod.compat.SuggestionsInfoCompatUtils;
+import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.LocaleUtils;
import com.android.inputmethod.latin.WordComposer;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
@@ -194,7 +195,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
DictAndProximity dictInfo = null;
try {
dictInfo = mDictionaryPool.pollWithDefaultTimeout();
- if (null == dictInfo) {
+ if (!DictionaryPool.isAValidDictionary(dictInfo)) {
return AndroidSpellCheckerService.getNotInDictEmptySuggestions();
}
return dictInfo.mDictionary.isValidWord(inText)
@@ -225,8 +226,8 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
final int xy = SpellCheckerProximityInfo.getXYForCodePointAndScript(
codePoint, mScript);
if (SpellCheckerProximityInfo.NOT_A_COORDINATE_PAIR == xy) {
- composer.add(codePoint, WordComposer.NOT_A_COORDINATE,
- WordComposer.NOT_A_COORDINATE);
+ composer.add(codePoint,
+ Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
} else {
composer.add(codePoint, xy & 0xFFFF, xy >> 16);
}
@@ -237,7 +238,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
DictAndProximity dictInfo = null;
try {
dictInfo = mDictionaryPool.pollWithDefaultTimeout();
- if (null == dictInfo) {
+ if (!DictionaryPool.isAValidDictionary(dictInfo)) {
return AndroidSpellCheckerService.getNotInDictEmptySuggestions();
}
final ArrayList<SuggestedWordInfo> suggestions =
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
index 83f82faeb..53aa6c719 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
@@ -18,6 +18,13 @@ package com.android.inputmethod.latin.spellcheck;
import android.util.Log;
+import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.WordComposer;
+
+import java.util.ArrayList;
import java.util.Locale;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -39,6 +46,26 @@ public class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> {
private final Locale mLocale;
private int mSize;
private volatile boolean mClosed;
+ final static ArrayList<SuggestedWordInfo> noSuggestions = CollectionUtils.newArrayList();
+ private final static DictAndProximity dummyDict = new DictAndProximity(
+ new Dictionary(Dictionary.TYPE_MAIN) {
+ @Override
+ public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
+ final CharSequence prevWord, final ProximityInfo proximityInfo) {
+ return noSuggestions;
+ }
+ @Override
+ public boolean isValidWord(CharSequence 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).
+ return true;
+ }
+ }, null);
+
+ static public boolean isAValidDictionary(final DictAndProximity dictInfo) {
+ return null != dictInfo && dummyDict != dictInfo;
+ }
public DictionaryPool(final int maxSize, final AndroidSpellCheckerService service,
final Locale locale) {
@@ -98,7 +125,7 @@ public class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> {
public boolean offer(final DictAndProximity dict) {
if (mClosed) {
dict.mDictionary.close();
- return false;
+ return super.offer(dummyDict);
} else {
return super.offer(dict);
}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
index bd92d883b..fe5225ebd 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.keyboard.KeyDetector;
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 */
- final public static int NUL = KeyDetector.NOT_A_CODE;
+ 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
// native code - this value is passed at creation of the binary object and reused
@@ -59,7 +60,7 @@ public class SpellCheckerProximityInfo {
// character.
// Since we need to build such an array, we want to be able to search in our big proximity
// data quickly by character, and a map is probably the best way to do this.
- final private static TreeMap<Integer, Integer> INDICES = new TreeMap<Integer, Integer>();
+ final private static TreeMap<Integer, Integer> INDICES = CollectionUtils.newTreeMap();
// The proximity here is the union of
// - the proximity for a QWERTY keyboard.
@@ -122,7 +123,7 @@ public class SpellCheckerProximityInfo {
}
private static class Cyrillic {
- final private static TreeMap<Integer, Integer> INDICES = new TreeMap<Integer, Integer>();
+ 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.
final static int[] PROXIMITY = {
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index 58b01aa55..1f883aa60 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -23,7 +23,9 @@ 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;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.Utils;
@@ -31,145 +33,149 @@ import com.android.inputmethod.latin.Utils;
public class MoreSuggestions extends Keyboard {
public static final int SUGGESTION_CODE_BASE = 1024;
- MoreSuggestions(Builder.MoreSuggestionsParam params) {
+ MoreSuggestions(final MoreSuggestionsParam params) {
super(params);
}
- public static class Builder extends Keyboard.Builder<Builder.MoreSuggestionsParam> {
- private final MoreSuggestionsView mPaneView;
- private SuggestedWords mSuggestions;
- private int mFromPos;
- private int mToPos;
+ private static 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];
+ private final int[] mNumColumnsInRow = new int[SuggestionStripView.MAX_SUGGESTIONS];
+ private static final int MAX_COLUMNS_IN_ROW = 3;
+ private int mNumRows;
+ public Drawable mDivider;
+ public int mDividerWidth;
+
+ public MoreSuggestionsParam() {
+ super();
+ }
- public static class MoreSuggestionsParam extends Keyboard.Params {
- 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];
- private final int[] mNumColumnsInRow = new int[SuggestionStripView.MAX_SUGGESTIONS];
- private static final int MAX_COLUMNS_IN_ROW = 3;
- private int mNumRows;
- public Drawable mDivider;
- public int mDividerWidth;
-
- public int layout(SuggestedWords suggestions, int fromPos, int maxWidth, int minWidth,
- int maxRow, MoreSuggestionsView view) {
- clearKeys();
- final Resources res = view.getContext().getResources();
- mDivider = res.getDrawable(R.drawable.more_suggestions_divider);
- mDividerWidth = mDivider.getIntrinsicWidth();
- final int padding = (int) res.getDimension(
- R.dimen.more_suggestions_key_horizontal_padding);
- final Paint paint = view.newDefaultLabelPaint();
-
- int row = 0;
- 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();
- // TODO: Should take care of text x-scaling.
- mWidths[pos] = (int)view.getLabelWidth(word, paint) + padding;
- final int numColumn = pos - rowStartPos + 1;
- final int columnWidth =
- (maxWidth - mDividerWidth * (numColumn - 1)) / numColumn;
- if (numColumn > MAX_COLUMNS_IN_ROW
- || !fitInWidth(rowStartPos, pos + 1, columnWidth)) {
- if ((row + 1) >= maxRow) {
- break;
- }
- mNumColumnsInRow[row] = pos - rowStartPos;
- rowStartPos = pos;
- row++;
+ 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();
+ mDivider = res.getDrawable(R.drawable.more_suggestions_divider);
+ mDividerWidth = mDivider.getIntrinsicWidth();
+ final int padding = (int) res.getDimension(
+ R.dimen.more_suggestions_key_horizontal_padding);
+ final Paint paint = view.newDefaultLabelPaint();
+
+ int row = 0;
+ 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();
+ // TODO: Should take care of text x-scaling.
+ mWidths[pos] = (int)view.getLabelWidth(word, paint) + padding;
+ final int numColumn = pos - rowStartPos + 1;
+ final int columnWidth =
+ (maxWidth - mDividerWidth * (numColumn - 1)) / numColumn;
+ if (numColumn > MAX_COLUMNS_IN_ROW
+ || !fitInWidth(rowStartPos, pos + 1, columnWidth)) {
+ if ((row + 1) >= maxRow) {
+ break;
}
- mColumnOrders[pos] = pos - rowStartPos;
- mRowNumbers[pos] = row;
- pos++;
+ mNumColumnsInRow[row] = pos - rowStartPos;
+ rowStartPos = pos;
+ row++;
}
- mNumColumnsInRow[row] = pos - rowStartPos;
- mNumRows = row + 1;
- mBaseWidth = mOccupiedWidth = Math.max(
- minWidth, calcurateMaxRowWidth(fromPos, pos));
- mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap;
- return pos - fromPos;
+ mColumnOrders[pos] = pos - rowStartPos;
+ mRowNumbers[pos] = row;
+ pos++;
}
+ mNumColumnsInRow[row] = pos - rowStartPos;
+ mNumRows = row + 1;
+ mBaseWidth = mOccupiedWidth = Math.max(
+ minWidth, calcurateMaxRowWidth(fromPos, pos));
+ mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap;
+ return pos - fromPos;
+ }
- private boolean fitInWidth(int startPos, int endPos, int width) {
- for (int pos = startPos; pos < endPos; pos++) {
- if (mWidths[pos] > width)
- return false;
- }
- return true;
+ private boolean fitInWidth(final int startPos, final int endPos, final int width) {
+ for (int pos = startPos; pos < endPos; pos++) {
+ if (mWidths[pos] > width)
+ return false;
}
+ return true;
+ }
- private int calcurateMaxRowWidth(int startPos, int endPos) {
- int maxRowWidth = 0;
- int pos = startPos;
- for (int row = 0; row < mNumRows; row++) {
- final int numColumnInRow = mNumColumnsInRow[row];
- int maxKeyWidth = 0;
- while (pos < endPos && mRowNumbers[pos] == row) {
- maxKeyWidth = Math.max(maxKeyWidth, mWidths[pos]);
- pos++;
- }
- maxRowWidth = Math.max(maxRowWidth,
- maxKeyWidth * numColumnInRow + mDividerWidth * (numColumnInRow - 1));
+ private int calcurateMaxRowWidth(final int startPos, final int endPos) {
+ int maxRowWidth = 0;
+ int pos = startPos;
+ for (int row = 0; row < mNumRows; row++) {
+ final int numColumnInRow = mNumColumnsInRow[row];
+ int maxKeyWidth = 0;
+ while (pos < endPos && mRowNumbers[pos] == row) {
+ maxKeyWidth = Math.max(maxKeyWidth, mWidths[pos]);
+ pos++;
}
- return maxRowWidth;
+ maxRowWidth = Math.max(maxRowWidth,
+ maxKeyWidth * numColumnInRow + mDividerWidth * (numColumnInRow - 1));
}
+ return maxRowWidth;
+ }
- private static final int[][] COLUMN_ORDER_TO_NUMBER = {
- { 0, },
- { 1, 0, },
- { 2, 0, 1},
- };
-
- public int getNumColumnInRow(int pos) {
- return mNumColumnsInRow[mRowNumbers[pos]];
- }
+ private static final int[][] COLUMN_ORDER_TO_NUMBER = {
+ { 0, },
+ { 1, 0, },
+ { 2, 0, 1},
+ };
- public int getColumnNumber(int pos) {
- final int columnOrder = mColumnOrders[pos];
- final int numColumn = getNumColumnInRow(pos);
- return COLUMN_ORDER_TO_NUMBER[numColumn - 1][columnOrder];
- }
+ public int getNumColumnInRow(final int pos) {
+ return mNumColumnsInRow[mRowNumbers[pos]];
+ }
- public int getX(int pos) {
- final int columnNumber = getColumnNumber(pos);
- return columnNumber * (getWidth(pos) + mDividerWidth);
- }
+ public int getColumnNumber(final int pos) {
+ final int columnOrder = mColumnOrders[pos];
+ final int numColumn = getNumColumnInRow(pos);
+ return COLUMN_ORDER_TO_NUMBER[numColumn - 1][columnOrder];
+ }
- public int getY(int pos) {
- final int row = mRowNumbers[pos];
- return (mNumRows -1 - row) * mDefaultRowHeight + mTopPadding;
- }
+ public int getX(final int pos) {
+ final int columnNumber = getColumnNumber(pos);
+ return columnNumber * (getWidth(pos) + mDividerWidth);
+ }
- public int getWidth(int pos) {
- final int numColumnInRow = getNumColumnInRow(pos);
- return (mOccupiedWidth - mDividerWidth * (numColumnInRow - 1)) / numColumnInRow;
- }
+ public int getY(final int pos) {
+ final int row = mRowNumbers[pos];
+ return (mNumRows -1 - row) * mDefaultRowHeight + mTopPadding;
+ }
- public void markAsEdgeKey(Key key, int pos) {
- final int row = mRowNumbers[pos];
- if (row == 0)
- key.markAsBottomEdge(this);
- if (row == mNumRows - 1)
- key.markAsTopEdge(this);
+ public int getWidth(final int pos) {
+ final int numColumnInRow = getNumColumnInRow(pos);
+ return (mOccupiedWidth - mDividerWidth * (numColumnInRow - 1)) / numColumnInRow;
+ }
- final int numColumnInRow = mNumColumnsInRow[row];
- final int column = getColumnNumber(pos);
- if (column == 0)
- key.markAsLeftEdge(this);
- if (column == numColumnInRow - 1)
- key.markAsRightEdge(this);
- }
+ public void markAsEdgeKey(final Key key, final int pos) {
+ final int row = mRowNumbers[pos];
+ if (row == 0)
+ key.markAsBottomEdge(this);
+ if (row == mNumRows - 1)
+ key.markAsTopEdge(this);
+
+ final int numColumnInRow = mNumColumnsInRow[row];
+ final int column = getColumnNumber(pos);
+ if (column == 0)
+ key.markAsLeftEdge(this);
+ if (column == numColumnInRow - 1)
+ key.markAsRightEdge(this);
}
+ }
- public Builder(MoreSuggestionsView paneView) {
+ public static class Builder extends KeyboardBuilder<MoreSuggestionsParam> {
+ private final MoreSuggestionsView mPaneView;
+ private SuggestedWords mSuggestions;
+ private int mFromPos;
+ private int mToPos;
+
+ public Builder(final MoreSuggestionsView paneView) {
super(paneView.getContext(), new MoreSuggestionsParam());
mPaneView = paneView;
}
- public Builder layout(SuggestedWords suggestions, int fromPos, int maxWidth,
- int minWidth, int maxRow) {
+ 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 xmlId = R.xml.kbd_suggestions_pane_template;
load(xmlId, keyboard.mId);
@@ -183,25 +189,6 @@ public class MoreSuggestions extends Keyboard {
return this;
}
- private static class Divider extends Key.Spacer {
- private final Drawable mIcon;
-
- public Divider(Keyboard.Params params, Drawable icon, int x, int y, int width,
- int height) {
- super(params, x, y, width, height);
- mIcon = icon;
- }
-
- @Override
- public Drawable getIcon(KeyboardIconsSet iconSet, int alpha) {
- // KeyboardIconsSet and alpha are unused. Use the icon that has been passed to the
- // constructor.
- // TODO: Drawable itself should have an alpha value.
- mIcon.setAlpha(128);
- return mIcon;
- }
- }
-
@Override
public MoreSuggestions build() {
final MoreSuggestionsParam params = mParams;
@@ -228,4 +215,23 @@ public class MoreSuggestions extends Keyboard {
return new MoreSuggestions(params);
}
}
+
+ private static class Divider extends Key.Spacer {
+ private final Drawable mIcon;
+
+ public Divider(final KeyboardParams params, final Drawable icon, final int x,
+ final int y, final int width, final int height) {
+ super(params, x, y, width, height);
+ mIcon = icon;
+ }
+
+ @Override
+ public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) {
+ // KeyboardIconsSet and alpha are unused. Use the icon that has been passed to the
+ // constructor.
+ // TODO: Drawable itself should have an alpha value.
+ mIcon.setAlpha(128);
+ return mIcon;
+ }
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index b57ffd2de..9e8ab81b0 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -58,8 +58,10 @@ 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.LatinImeLogger;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.ResourceUtils;
import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.Utils;
@@ -72,7 +74,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
OnLongClickListener {
public interface Listener {
public boolean addWordToUserDictionary(String word);
- public void pickSuggestionManually(int index, CharSequence word, int x, int y);
+ public void pickSuggestionManually(int index, CharSequence word);
}
// The maximum number of suggestions available. See {@link Suggest#mPrefMaxSuggestions}.
@@ -88,9 +90,9 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
private final MoreSuggestions.Builder mMoreSuggestionsBuilder;
private final PopupWindow mMoreSuggestionsWindow;
- private final ArrayList<TextView> mWords = new ArrayList<TextView>();
- private final ArrayList<TextView> mInfos = new ArrayList<TextView>();
- private final ArrayList<View> mDividers = new ArrayList<View>();
+ private final ArrayList<TextView> mWords = CollectionUtils.newArrayList();
+ private final ArrayList<TextView> mInfos = CollectionUtils.newArrayList();
+ private final ArrayList<View> mDividers = CollectionUtils.newArrayList();
private final PopupWindow mPreviewPopup;
private final TextView mPreviewText;
@@ -131,7 +133,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
private static class SuggestionStripViewParams {
private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3;
- private static final int DEFAULT_CENTER_SUGGESTION_PERCENTILE = 40;
+ private static final float DEFAULT_CENTER_SUGGESTION_PERCENTILE = 0.40f;
private static final int DEFAULT_MAX_MORE_SUGGESTIONS_ROW = 2;
private static final int PUNCTUATIONS_IN_STRIP = 5;
@@ -167,7 +169,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
private final int mSuggestionStripOption;
- private final ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>();
+ private final ArrayList<CharSequence> mTexts = CollectionUtils.newArrayList();
public boolean mMoreSuggestionsAvailable;
@@ -195,16 +197,16 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
R.styleable.SuggestionStripView, defStyle, R.style.SuggestionStripViewStyle);
mSuggestionStripOption = a.getInt(
R.styleable.SuggestionStripView_suggestionStripOption, 0);
- final float alphaValidTypedWord = getPercent(a,
- R.styleable.SuggestionStripView_alphaValidTypedWord, 100);
- final float alphaTypedWord = getPercent(a,
- R.styleable.SuggestionStripView_alphaTypedWord, 100);
- final float alphaAutoCorrect = getPercent(a,
- R.styleable.SuggestionStripView_alphaAutoCorrect, 100);
- final float alphaSuggested = getPercent(a,
- R.styleable.SuggestionStripView_alphaSuggested, 100);
- mAlphaObsoleted = getPercent(a,
- R.styleable.SuggestionStripView_alphaSuggested, 100);
+ final float alphaValidTypedWord = ResourceUtils.getFraction(a,
+ R.styleable.SuggestionStripView_alphaValidTypedWord, 1.0f);
+ final float alphaTypedWord = ResourceUtils.getFraction(a,
+ R.styleable.SuggestionStripView_alphaTypedWord, 1.0f);
+ final float alphaAutoCorrect = ResourceUtils.getFraction(a,
+ R.styleable.SuggestionStripView_alphaAutoCorrect, 1.0f);
+ final float alphaSuggested = ResourceUtils.getFraction(a,
+ R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
+ mAlphaObsoleted = ResourceUtils.getFraction(a,
+ R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
mColorValidTypedWord = applyAlpha(a.getColor(
R.styleable.SuggestionStripView_colorValidTypedWord, 0), alphaValidTypedWord);
mColorTypedWord = applyAlpha(a.getColor(
@@ -216,14 +218,14 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
mSuggestionsCountInStrip = a.getInt(
R.styleable.SuggestionStripView_suggestionsCountInStrip,
DEFAULT_SUGGESTIONS_COUNT_IN_STRIP);
- mCenterSuggestionWeight = getPercent(a,
+ mCenterSuggestionWeight = ResourceUtils.getFraction(a,
R.styleable.SuggestionStripView_centerSuggestionPercentile,
DEFAULT_CENTER_SUGGESTION_PERCENTILE);
mMaxMoreSuggestionsRow = a.getInt(
R.styleable.SuggestionStripView_maxMoreSuggestionsRow,
DEFAULT_MAX_MORE_SUGGESTIONS_ROW);
- mMinMoreSuggestionsWidth = getRatio(a,
- R.styleable.SuggestionStripView_minMoreSuggestionsWidth);
+ mMinMoreSuggestionsWidth = ResourceUtils.getFraction(a,
+ R.styleable.SuggestionStripView_minMoreSuggestionsWidth, 1.0f);
a.recycle();
mMoreSuggestionsHint = getMoreSuggestionsHint(res,
@@ -277,16 +279,6 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
return new BitmapDrawable(res, buffer);
}
- // Read integer value in TypedArray as percent.
- private static float getPercent(TypedArray a, int index, int defValue) {
- return a.getInt(index, defValue) / 100.0f;
- }
-
- // Read fraction value in TypedArray as float.
- private static float getRatio(TypedArray a, int index) {
- return a.getFraction(index, 1000, 1000, 1) / 1000.0f;
- }
-
private CharSequence getStyledSuggestionWord(SuggestedWords suggestedWords, int pos) {
final CharSequence word = suggestedWords.getWord(pos);
final boolean isAutoCorrect = pos == 1 && suggestedWords.willAutoCorrect();
@@ -726,9 +718,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
public boolean onCustomRequest(int requestCode) {
final int index = requestCode;
final CharSequence word = mSuggestedWords.getWord(index);
- // TODO: change caller path so coordinates are passed through here
- mListener.pickSuggestionManually(index, word, NOT_A_TOUCH_COORDINATE,
- NOT_A_TOUCH_COORDINATE);
+ mListener.pickSuggestionManually(index, word);
dismissMoreSuggestions();
return true;
}
@@ -874,7 +864,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
return;
final CharSequence word = mSuggestedWords.getWord(index);
- mListener.pickSuggestionManually(index, word, mLastX, mLastY);
+ mListener.pickSuggestionManually(index, word);
}
@Override
diff --git a/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java b/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java
new file mode 100644
index 000000000..5124a35a6
--- /dev/null
+++ b/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java
@@ -0,0 +1,33 @@
+/*
+ * 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.research;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * Arrange for the uploading service to be run on regular intervals.
+ */
+public final class BootBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
+ ResearchLogger.scheduleUploadingService(context);
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/research/FeedbackActivity.java b/java/src/com/android/inputmethod/research/FeedbackActivity.java
index c9f3b476a..11eae8813 100644
--- a/java/src/com/android/inputmethod/research/FeedbackActivity.java
+++ b/java/src/com/android/inputmethod/research/FeedbackActivity.java
@@ -18,10 +18,7 @@ package com.android.inputmethod.research;
import android.app.Activity;
import android.os.Bundle;
-import android.text.Editable;
-import android.view.View;
import android.widget.CheckBox;
-import android.widget.EditText;
import com.android.inputmethod.latin.R;
@@ -31,6 +28,11 @@ public class FeedbackActivity extends Activity {
super.onCreate(savedInstanceState);
setContentView(R.layout.research_feedback_activity);
final FeedbackLayout layout = (FeedbackLayout) findViewById(R.id.research_feedback_layout);
+ final CheckBox checkbox = (CheckBox) findViewById(R.id.research_feedback_include_history);
+ final CharSequence cs = checkbox.getText();
+ final String actualString = String.format(cs.toString(),
+ ResearchLogger.FEEDBACK_WORD_BUFFER_SIZE);
+ checkbox.setText(actualString);
layout.setActivity(this);
}
diff --git a/java/src/com/android/inputmethod/research/LogBuffer.java b/java/src/com/android/inputmethod/research/LogBuffer.java
new file mode 100644
index 000000000..ae7b1579a
--- /dev/null
+++ b/java/src/com/android/inputmethod/research/LogBuffer.java
@@ -0,0 +1,113 @@
+/*
+ * 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.research;
+
+import com.android.inputmethod.latin.CollectionUtils;
+
+import java.util.LinkedList;
+
+/**
+ * A buffer that holds a fixed number of LogUnits.
+ *
+ * LogUnits are added in and shifted out in temporal order. Only a subset of the LogUnits are
+ * actual words; the other LogUnits do not count toward the word limit. Once the buffer reaches
+ * capacity, adding another LogUnit that is a word evicts the oldest LogUnits out one at a time to
+ * stay under the capacity limit.
+ */
+public class LogBuffer {
+ protected final LinkedList<LogUnit> mLogUnits;
+ /* package for test */ int mWordCapacity;
+ // The number of members of mLogUnits that are actual words.
+ protected int mNumActualWords;
+
+ /**
+ * Create a new LogBuffer that can hold a fixed number of LogUnits that are words (and
+ * unlimited number of non-word LogUnits), and that outputs its result to a researchLog.
+ *
+ * @param wordCapacity maximum number of words
+ */
+ LogBuffer(final int wordCapacity) {
+ if (wordCapacity <= 0) {
+ throw new IllegalArgumentException("wordCapacity must be 1 or greater.");
+ }
+ mLogUnits = CollectionUtils.newLinkedList();
+ mWordCapacity = wordCapacity;
+ mNumActualWords = 0;
+ }
+
+ /**
+ * Adds a new LogUnit to the front of the LIFO queue, evicting existing LogUnit's
+ * (oldest first) if word capacity is reached.
+ */
+ public void shiftIn(LogUnit newLogUnit) {
+ if (newLogUnit.getWord() == null) {
+ // This LogUnit isn't a word, so it doesn't count toward the word-limit.
+ mLogUnits.add(newLogUnit);
+ return;
+ }
+ if (mNumActualWords == mWordCapacity) {
+ shiftOutThroughFirstWord();
+ }
+ mLogUnits.add(newLogUnit);
+ mNumActualWords++; // Must be a word, or we wouldn't be here.
+ }
+
+ private void shiftOutThroughFirstWord() {
+ while (!mLogUnits.isEmpty()) {
+ final LogUnit logUnit = mLogUnits.removeFirst();
+ onShiftOut(logUnit);
+ if (logUnit.hasWord()) {
+ // Successfully shifted out a word-containing LogUnit and made space for the new
+ // LogUnit.
+ mNumActualWords--;
+ break;
+ }
+ }
+ }
+
+ /**
+ * Removes all LogUnits from the buffer without calling onShiftOut().
+ */
+ public void clear() {
+ mLogUnits.clear();
+ mNumActualWords = 0;
+ }
+
+ /**
+ * Called when a LogUnit is removed from the LogBuffer as a result of a shiftIn. LogUnits are
+ * removed in the order entered. This method is not called when shiftOut is called directly.
+ *
+ * Base class does nothing; subclasses may override.
+ */
+ protected void onShiftOut(LogUnit logUnit) {
+ }
+
+ /**
+ * Called to deliberately remove the oldest LogUnit. Usually called when draining the
+ * LogBuffer.
+ */
+ public LogUnit shiftOut() {
+ if (mLogUnits.isEmpty()) {
+ return null;
+ }
+ final LogUnit logUnit = mLogUnits.removeFirst();
+ if (logUnit.hasWord()) {
+ mNumActualWords--;
+ }
+ return logUnit;
+ }
+}
diff --git a/java/src/com/android/inputmethod/research/LogUnit.java b/java/src/com/android/inputmethod/research/LogUnit.java
new file mode 100644
index 000000000..d8b3a29ff
--- /dev/null
+++ b/java/src/com/android/inputmethod/research/LogUnit.java
@@ -0,0 +1,83 @@
+/*
+ * 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.research;
+
+import com.android.inputmethod.latin.CollectionUtils;
+
+import java.util.ArrayList;
+
+/**
+ * A group of log statements related to each other.
+ *
+ * A LogUnit is collection of LogStatements, each of which is generated by at a particular point
+ * in the code. (There is no LogStatement class; the data is stored across the instance variables
+ * here.) A single LogUnit's statements can correspond to all the calls made while in the same
+ * composing region, or all the calls between committing the last composing region, and the first
+ * character of the next composing region.
+ *
+ * Individual statements in a log may be marked as potentially private. If so, then they are only
+ * published to a ResearchLog if the ResearchLogger determines that publishing the entire LogUnit
+ * will not violate the user's privacy. Checks for this may include whether other LogUnits have
+ * been published recently, or whether the LogUnit contains numbers, etc.
+ */
+/* package */ class LogUnit {
+ private final ArrayList<String[]> mKeysList = CollectionUtils.newArrayList();
+ private final ArrayList<Object[]> mValuesList = CollectionUtils.newArrayList();
+ private final ArrayList<Boolean> mIsPotentiallyPrivate = CollectionUtils.newArrayList();
+ private String mWord;
+ private boolean mContainsDigit;
+
+ public void addLogStatement(final String[] keys, final Object[] values,
+ final Boolean isPotentiallyPrivate) {
+ mKeysList.add(keys);
+ mValuesList.add(values);
+ mIsPotentiallyPrivate.add(isPotentiallyPrivate);
+ }
+
+ public void publishTo(final ResearchLog researchLog, final boolean isIncludingPrivateData) {
+ final int size = mKeysList.size();
+ for (int i = 0; i < size; i++) {
+ if (!mIsPotentiallyPrivate.get(i) || isIncludingPrivateData) {
+ researchLog.outputEvent(mKeysList.get(i), mValuesList.get(i));
+ }
+ }
+ }
+
+ public void setWord(String word) {
+ mWord = word;
+ }
+
+ public String getWord() {
+ return mWord;
+ }
+
+ public boolean hasWord() {
+ return mWord != null;
+ }
+
+ public void setContainsDigit() {
+ mContainsDigit = true;
+ }
+
+ public boolean hasDigit() {
+ return mContainsDigit;
+ }
+
+ public boolean isEmpty() {
+ return mKeysList.isEmpty();
+ }
+}
diff --git a/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java
new file mode 100644
index 000000000..745768d35
--- /dev/null
+++ b/java/src/com/android/inputmethod/research/MainLogBuffer.java
@@ -0,0 +1,127 @@
+/*
+ * 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.research;
+
+import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.Suggest;
+
+import java.util.Random;
+
+public class MainLogBuffer extends LogBuffer {
+ // The size of the n-grams logged. E.g. N_GRAM_SIZE = 2 means to sample bigrams.
+ private static final int N_GRAM_SIZE = 2;
+ // The number of words between n-grams to omit from the log.
+ private static final int DEFAULT_NUMBER_OF_WORDS_BETWEEN_SAMPLES = 18;
+
+ private final ResearchLog mResearchLog;
+ private Suggest mSuggest;
+
+ // The minimum periodicity with which n-grams can be sampled. E.g. mWinWordPeriod is 10 if
+ // every 10th bigram is sampled, i.e., words 1-8 are not, but the bigram at words 9 and 10, etc.
+ // for 11-18, and the bigram at words 19 and 20. If an n-gram is not safe (e.g. it contains a
+ // number in the middle or an out-of-vocabulary word), then sampling is delayed until a safe
+ // n-gram does appear.
+ /* package for test */ int mMinWordPeriod;
+
+ // Counter for words left to suppress before an n-gram can be sampled. Reset to mMinWordPeriod
+ // after a sample is taken.
+ /* package for test */ int mWordsUntilSafeToSample;
+
+ public MainLogBuffer(final ResearchLog researchLog) {
+ super(N_GRAM_SIZE);
+ mResearchLog = researchLog;
+ mMinWordPeriod = DEFAULT_NUMBER_OF_WORDS_BETWEEN_SAMPLES + N_GRAM_SIZE;
+ final Random random = new Random();
+ mWordsUntilSafeToSample = random.nextInt(mMinWordPeriod);
+ }
+
+ public void setSuggest(Suggest suggest) {
+ mSuggest = suggest;
+ }
+
+ @Override
+ public void shiftIn(final LogUnit newLogUnit) {
+ super.shiftIn(newLogUnit);
+ if (newLogUnit.hasWord()) {
+ if (mWordsUntilSafeToSample > 0) {
+ mWordsUntilSafeToSample--;
+ }
+ }
+ }
+
+ public void resetWordCounter() {
+ mWordsUntilSafeToSample = mMinWordPeriod;
+ }
+
+ /**
+ * Determines whether the content of the MainLogBuffer can be safely uploaded in its complete
+ * form and still protect the user's privacy.
+ *
+ * The size of the MainLogBuffer is just enough to hold one n-gram, its corrections, and any
+ * non-character data that is typed between words. The decision about privacy is made based on
+ * the buffer's entire content. If it is decided that the privacy risks are too great to upload
+ * the contents of this buffer, a censored version of the LogItems may still be uploaded. E.g.,
+ * the screen orientation and other characteristics about the device can be uploaded without
+ * revealing much about the user.
+ */
+ public boolean isSafeToLog() {
+ // Check that we are not sampling too frequently. Having sampled recently might disclose
+ // too much of the user's intended meaning.
+ if (mWordsUntilSafeToSample > 0) {
+ return false;
+ }
+ if (mSuggest == null || !mSuggest.hasMainDictionary()) {
+ // Main dictionary is unavailable. Since we cannot check it, we cannot tell if a word
+ // is out-of-vocabulary or not. Therefore, we must judge the entire buffer contents to
+ // potentially pose a privacy risk.
+ return false;
+ }
+ // Reload the dictionary in case it has changed (e.g., because the user has changed
+ // languages).
+ final Dictionary dictionary = mSuggest.getMainDictionary();
+ if (dictionary == null) {
+ return false;
+ }
+ // Check each word in the buffer. If any word poses a privacy threat, we cannot upload the
+ // complete buffer contents in detail.
+ final int length = mLogUnits.size();
+ for (int i = 0; i < length; i++) {
+ final LogUnit logUnit = mLogUnits.get(i);
+ final String word = logUnit.getWord();
+ if (word == null) {
+ // Digits outside words are a privacy threat.
+ if (logUnit.hasDigit()) {
+ return false;
+ }
+ } else {
+ // Words not in the dictionary are a privacy threat.
+ if (!(dictionary.isValidWord(word))) {
+ return false;
+ }
+ }
+ }
+ // All checks have passed; this buffer's content can be safely uploaded.
+ return true;
+ }
+
+ @Override
+ protected void onShiftOut(LogUnit logUnit) {
+ if (mResearchLog != null) {
+ mResearchLog.publish(logUnit, false /* isIncludingPrivateData */);
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java
index 18bf3c07f..cd9ff85f8 100644
--- a/java/src/com/android/inputmethod/research/ResearchLog.java
+++ b/java/src/com/android/inputmethod/research/ResearchLog.java
@@ -26,7 +26,6 @@ import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.define.ProductionFlag;
-import com.android.inputmethod.research.ResearchLogger.LogUnit;
import java.io.BufferedWriter;
import java.io.File;
@@ -37,6 +36,7 @@ import java.io.OutputStreamWriter;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
+import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@@ -51,21 +51,22 @@ import java.util.concurrent.TimeUnit;
*/
public class ResearchLog {
private static final String TAG = ResearchLog.class.getSimpleName();
- private static final JsonWriter NULL_JSON_WRITER = new JsonWriter(
- new OutputStreamWriter(new NullOutputStream()));
+ private static final boolean DEBUG = false;
+ private static final long FLUSH_DELAY_IN_MS = 1000 * 5;
+ private static final int ABORT_TIMEOUT_IN_MS = 1000 * 4;
- final ScheduledExecutorService mExecutor;
+ /* package */ final ScheduledExecutorService mExecutor;
/* package */ final File mFile;
private JsonWriter mJsonWriter = NULL_JSON_WRITER;
+ // true if at least one byte of data has been written out to the log file. This must be
+ // remembered because JsonWriter requires that calls matching calls to beginObject and
+ // endObject, as well as beginArray and endArray, and the file is opened lazily, only when
+ // it is certain that data will be written. Alternatively, the matching call exceptions
+ // could be caught, but this might suppress other errors.
+ private boolean mHasWrittenData = false;
- private int mLoggingState;
- private static final int LOGGING_STATE_UNSTARTED = 0;
- private static final int LOGGING_STATE_READY = 1; // don't create file until necessary
- private static final int LOGGING_STATE_RUNNING = 2;
- private static final int LOGGING_STATE_STOPPING = 3;
- private static final int LOGGING_STATE_STOPPED = 4;
- private static final long FLUSH_DELAY_IN_MS = 1000 * 5;
-
+ private static final JsonWriter NULL_JSON_WRITER = new JsonWriter(
+ new OutputStreamWriter(new NullOutputStream()));
private static class NullOutputStream extends OutputStream {
/** {@inheritDoc} */
@Override
@@ -84,128 +85,81 @@ public class ResearchLog {
}
}
- public ResearchLog(File outputFile) {
- mExecutor = Executors.newSingleThreadScheduledExecutor();
+ public ResearchLog(final File outputFile) {
if (outputFile == null) {
throw new IllegalArgumentException();
}
+ mExecutor = Executors.newSingleThreadScheduledExecutor();
mFile = outputFile;
- mLoggingState = LOGGING_STATE_UNSTARTED;
- }
-
- public synchronized void start() throws IOException {
- switch (mLoggingState) {
- case LOGGING_STATE_UNSTARTED:
- mLoggingState = LOGGING_STATE_READY;
- break;
- case LOGGING_STATE_READY:
- case LOGGING_STATE_RUNNING:
- case LOGGING_STATE_STOPPING:
- case LOGGING_STATE_STOPPED:
- break;
- }
}
- public synchronized void stop() {
- switch (mLoggingState) {
- case LOGGING_STATE_UNSTARTED:
- mLoggingState = LOGGING_STATE_STOPPED;
- break;
- case LOGGING_STATE_READY:
- case LOGGING_STATE_RUNNING:
- mExecutor.submit(new Callable<Object>() {
- @Override
- public Object call() throws Exception {
- try {
- mJsonWriter.endArray();
- mJsonWriter.flush();
- mJsonWriter.close();
- } finally {
- boolean success = mFile.setWritable(false, false);
- mLoggingState = LOGGING_STATE_STOPPED;
- }
- return null;
+ public synchronized void close() {
+ mExecutor.submit(new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ try {
+ if (mHasWrittenData) {
+ mJsonWriter.endArray();
+ mJsonWriter.flush();
+ mJsonWriter.close();
+ mHasWrittenData = false;
}
- });
- removeAnyScheduledFlush();
- mExecutor.shutdown();
- mLoggingState = LOGGING_STATE_STOPPING;
- break;
- case LOGGING_STATE_STOPPING:
- case LOGGING_STATE_STOPPED:
- }
+ } catch (Exception e) {
+ Log.d(TAG, "error when closing ResearchLog:");
+ e.printStackTrace();
+ } finally {
+ if (mFile.exists()) {
+ mFile.setWritable(false, false);
+ }
+ }
+ return null;
+ }
+ });
+ removeAnyScheduledFlush();
+ mExecutor.shutdown();
}
- public boolean isAlive() {
- switch (mLoggingState) {
- case LOGGING_STATE_UNSTARTED:
- case LOGGING_STATE_READY:
- case LOGGING_STATE_RUNNING:
- return true;
- }
- return false;
- }
+ private boolean mIsAbortSuccessful;
- public void waitUntilStopped(final int timeoutInMs) throws InterruptedException {
+ public synchronized void abort() {
+ mExecutor.submit(new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ try {
+ if (mHasWrittenData) {
+ mJsonWriter.endArray();
+ mJsonWriter.close();
+ mHasWrittenData = false;
+ }
+ } finally {
+ mIsAbortSuccessful = mFile.delete();
+ }
+ return null;
+ }
+ });
removeAnyScheduledFlush();
mExecutor.shutdown();
- mExecutor.awaitTermination(timeoutInMs, TimeUnit.MILLISECONDS);
}
- public synchronized void abort() {
- switch (mLoggingState) {
- case LOGGING_STATE_UNSTARTED:
- mLoggingState = LOGGING_STATE_STOPPED;
- isAbortSuccessful = true;
- break;
- case LOGGING_STATE_READY:
- case LOGGING_STATE_RUNNING:
- mExecutor.submit(new Callable<Object>() {
- @Override
- public Object call() throws Exception {
- try {
- mJsonWriter.endArray();
- mJsonWriter.close();
- } finally {
- isAbortSuccessful = mFile.delete();
- }
- return null;
- }
- });
- removeAnyScheduledFlush();
- mExecutor.shutdown();
- mLoggingState = LOGGING_STATE_STOPPING;
- break;
- case LOGGING_STATE_STOPPING:
- case LOGGING_STATE_STOPPED:
- }
+ public boolean blockingAbort() throws InterruptedException {
+ abort();
+ mExecutor.awaitTermination(ABORT_TIMEOUT_IN_MS, TimeUnit.MILLISECONDS);
+ return mIsAbortSuccessful;
}
- private boolean isAbortSuccessful;
- public boolean isAbortSuccessful() {
- return isAbortSuccessful;
+ public void awaitTermination(int delay, TimeUnit timeUnit) throws InterruptedException {
+ mExecutor.awaitTermination(delay, timeUnit);
}
/* package */ synchronized void flush() {
- switch (mLoggingState) {
- case LOGGING_STATE_UNSTARTED:
- break;
- case LOGGING_STATE_READY:
- case LOGGING_STATE_RUNNING:
- removeAnyScheduledFlush();
- mExecutor.submit(mFlushCallable);
- break;
- case LOGGING_STATE_STOPPING:
- case LOGGING_STATE_STOPPED:
- }
+ removeAnyScheduledFlush();
+ mExecutor.submit(mFlushCallable);
}
- private Callable<Object> mFlushCallable = new Callable<Object>() {
+ private final Callable<Object> mFlushCallable = new Callable<Object>() {
@Override
public Object call() throws Exception {
- if (mLoggingState == LOGGING_STATE_RUNNING) {
- mJsonWriter.flush();
- }
+ mJsonWriter.flush();
return null;
}
};
@@ -224,56 +178,40 @@ public class ResearchLog {
mFlushFuture = mExecutor.schedule(mFlushCallable, FLUSH_DELAY_IN_MS, TimeUnit.MILLISECONDS);
}
- public synchronized void publishPublicEvents(final LogUnit logUnit) {
- switch (mLoggingState) {
- case LOGGING_STATE_UNSTARTED:
- break;
- case LOGGING_STATE_READY:
- case LOGGING_STATE_RUNNING:
- mExecutor.submit(new Callable<Object>() {
- @Override
- public Object call() throws Exception {
- logUnit.publishPublicEventsTo(ResearchLog.this);
- scheduleFlush();
- return null;
- }
- });
- break;
- case LOGGING_STATE_STOPPING:
- case LOGGING_STATE_STOPPED:
- }
- }
-
- public synchronized void publishAllEvents(final LogUnit logUnit) {
- switch (mLoggingState) {
- case LOGGING_STATE_UNSTARTED:
- break;
- case LOGGING_STATE_READY:
- case LOGGING_STATE_RUNNING:
- mExecutor.submit(new Callable<Object>() {
- @Override
- public Object call() throws Exception {
- logUnit.publishAllEventsTo(ResearchLog.this);
- scheduleFlush();
- return null;
- }
- });
- break;
- case LOGGING_STATE_STOPPING:
- case LOGGING_STATE_STOPPED:
+ public synchronized void publish(final LogUnit logUnit, final boolean isIncludingPrivateData) {
+ try {
+ mExecutor.submit(new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ logUnit.publishTo(ResearchLog.this, isIncludingPrivateData);
+ scheduleFlush();
+ return null;
+ }
+ });
+ } catch (RejectedExecutionException e) {
+ // TODO: Add code to record loss of data, and report.
}
}
private static final String CURRENT_TIME_KEY = "_ct";
private static final String UPTIME_KEY = "_ut";
private static final String EVENT_TYPE_KEY = "_ty";
+
void outputEvent(final String[] keys, final Object[] values) {
- // not thread safe.
+ // Not thread safe.
+ if (keys.length == 0) {
+ return;
+ }
+ if (DEBUG) {
+ if (keys.length != values.length + 1) {
+ Log.d(TAG, "Key and Value list sizes do not match. " + keys[0]);
+ }
+ }
try {
if (mJsonWriter == NULL_JSON_WRITER) {
mJsonWriter = new JsonWriter(new BufferedWriter(new FileWriter(mFile)));
- mJsonWriter.setLenient(true);
mJsonWriter.beginArray();
+ mHasWrittenData = true;
}
mJsonWriter.beginObject();
mJsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis());
@@ -283,8 +221,8 @@ public class ResearchLog {
for (int i = 0; i < length; i++) {
mJsonWriter.name(keys[i + 1]);
Object value = values[i];
- if (value instanceof String) {
- mJsonWriter.value((String) value);
+ if (value instanceof CharSequence) {
+ mJsonWriter.value(value.toString());
} else if (value instanceof Number) {
mJsonWriter.value((Number) value);
} else if (value instanceof Boolean) {
@@ -319,7 +257,7 @@ public class ResearchLog {
for (Key keyboardKey : keyboardKeys) {
mJsonWriter.beginObject();
mJsonWriter.name("code").value(keyboardKey.mCode);
- mJsonWriter.name("altCode").value(keyboardKey.mAltCode);
+ mJsonWriter.name("altCode").value(keyboardKey.getAltCode());
mJsonWriter.name("x").value(keyboardKey.mX);
mJsonWriter.name("y").value(keyboardKey.mY);
mJsonWriter.name("w").value(keyboardKey.mWidth);
@@ -331,14 +269,11 @@ public class ResearchLog {
SuggestedWords words = (SuggestedWords) value;
mJsonWriter.beginObject();
mJsonWriter.name("typedWordValid").value(words.mTypedWordValid);
- mJsonWriter.name("willAutoCorrect")
- .value(words.mWillAutoCorrect);
+ mJsonWriter.name("willAutoCorrect").value(words.mWillAutoCorrect);
mJsonWriter.name("isPunctuationSuggestions")
- .value(words.mIsPunctuationSuggestions);
- mJsonWriter.name("isObsoleteSuggestions")
- .value(words.mIsObsoleteSuggestions);
- mJsonWriter.name("isPrediction")
- .value(words.mIsPrediction);
+ .value(words.mIsPunctuationSuggestions);
+ mJsonWriter.name("isObsoleteSuggestions").value(words.mIsObsoleteSuggestions);
+ mJsonWriter.name("isPrediction").value(words.mIsPrediction);
mJsonWriter.name("words");
mJsonWriter.beginArray();
final int size = words.size();
@@ -363,8 +298,8 @@ public class ResearchLog {
try {
mJsonWriter.close();
} catch (IllegalStateException e1) {
- // assume that this is just the json not being terminated properly.
- // ignore
+ // Assume that this is just the json not being terminated properly.
+ // Ignore
} catch (IOException e1) {
e1.printStackTrace();
} finally {
diff --git a/java/src/com/android/inputmethod/research/ResearchLogUploader.java b/java/src/com/android/inputmethod/research/ResearchLogUploader.java
deleted file mode 100644
index 3b1213009..000000000
--- a/java/src/com/android/inputmethod/research/ResearchLogUploader.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.research;
-
-import android.Manifest;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.BatteryManager;
-import android.util.Log;
-
-import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.R.string;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileFilter;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-
-public final class ResearchLogUploader {
- private static final String TAG = ResearchLogUploader.class.getSimpleName();
- private static final int UPLOAD_INTERVAL_IN_MS = 1000 * 60 * 15; // every 15 min
- private static final int BUF_SIZE = 1024 * 8;
-
- private final boolean mCanUpload;
- private final Context mContext;
- private final File mFilesDir;
- private final URL mUrl;
- private final ScheduledExecutorService mExecutor;
-
- private Runnable doUploadRunnable = new UploadRunnable(null, false);
-
- public ResearchLogUploader(final Context context, final File filesDir) {
- mContext = context;
- mFilesDir = filesDir;
- final PackageManager packageManager = context.getPackageManager();
- final boolean hasPermission = packageManager.checkPermission(Manifest.permission.INTERNET,
- context.getPackageName()) == PackageManager.PERMISSION_GRANTED;
- if (!hasPermission) {
- mCanUpload = false;
- mUrl = null;
- mExecutor = null;
- return;
- }
- URL tempUrl = null;
- boolean canUpload = false;
- ScheduledExecutorService executor = null;
- try {
- final String urlString = context.getString(R.string.research_logger_upload_url);
- if (urlString == null || urlString.equals("")) {
- return;
- }
- tempUrl = new URL(urlString);
- canUpload = true;
- executor = Executors.newSingleThreadScheduledExecutor();
- } catch (MalformedURLException e) {
- tempUrl = null;
- e.printStackTrace();
- return;
- } finally {
- mCanUpload = canUpload;
- mUrl = tempUrl;
- mExecutor = executor;
- }
- }
-
- public void start() {
- if (mCanUpload) {
- Log.d(TAG, "scheduling regular uploading");
- mExecutor.scheduleWithFixedDelay(doUploadRunnable, UPLOAD_INTERVAL_IN_MS,
- UPLOAD_INTERVAL_IN_MS, TimeUnit.MILLISECONDS);
- } else {
- Log.d(TAG, "no permission to upload");
- }
- }
-
- public void uploadNow(final Callback callback) {
- // Perform an immediate upload. Note that this should happen even if there is
- // another upload happening right now, as it may have missed the latest changes.
- // TODO: Reschedule regular upload tests starting from now.
- if (mCanUpload) {
- mExecutor.submit(new UploadRunnable(callback, true));
- }
- }
-
- public interface Callback {
- public void onUploadCompleted(final boolean success);
- }
-
- private boolean isExternallyPowered() {
- final Intent intent = mContext.registerReceiver(null, new IntentFilter(
- Intent.ACTION_BATTERY_CHANGED));
- final int pluggedState = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
- return pluggedState == BatteryManager.BATTERY_PLUGGED_AC
- || pluggedState == BatteryManager.BATTERY_PLUGGED_USB;
- }
-
- private boolean hasWifiConnection() {
- final ConnectivityManager manager =
- (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
- final NetworkInfo wifiInfo = manager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
- return wifiInfo.isConnected();
- }
-
- class UploadRunnable implements Runnable {
- private final Callback mCallback;
- private final boolean mForceUpload;
-
- public UploadRunnable(final Callback callback, final boolean forceUpload) {
- mCallback = callback;
- mForceUpload = forceUpload;
- }
-
- @Override
- public void run() {
- doUpload();
- }
-
- private void doUpload() {
- if (!mForceUpload && (!isExternallyPowered() || !hasWifiConnection())) {
- return;
- }
- if (mFilesDir == null) {
- return;
- }
- final File[] files = mFilesDir.listFiles(new FileFilter() {
- @Override
- public boolean accept(File pathname) {
- return pathname.getName().startsWith(ResearchLogger.FILENAME_PREFIX)
- && !pathname.canWrite();
- }
- });
- boolean success = true;
- if (files.length == 0) {
- success = false;
- }
- for (final File file : files) {
- if (!uploadFile(file)) {
- success = false;
- }
- }
- if (mCallback != null) {
- mCallback.onUploadCompleted(success);
- }
- }
-
- private boolean uploadFile(File file) {
- Log.d(TAG, "attempting upload of " + file.getAbsolutePath());
- boolean success = false;
- final int contentLength = (int) file.length();
- HttpURLConnection connection = null;
- InputStream fileIs = null;
- try {
- fileIs = new FileInputStream(file);
- connection = (HttpURLConnection) mUrl.openConnection();
- connection.setRequestMethod("PUT");
- connection.setDoOutput(true);
- connection.setFixedLengthStreamingMode(contentLength);
- final OutputStream os = connection.getOutputStream();
- final byte[] buf = new byte[BUF_SIZE];
- int numBytesRead;
- while ((numBytesRead = fileIs.read(buf)) != -1) {
- os.write(buf, 0, numBytesRead);
- }
- if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
- Log.d(TAG, "upload failed: " + connection.getResponseCode());
- InputStream netIs = connection.getInputStream();
- BufferedReader reader = new BufferedReader(new InputStreamReader(netIs));
- String line;
- while ((line = reader.readLine()) != null) {
- Log.d(TAG, "| " + reader.readLine());
- }
- reader.close();
- return success;
- }
- file.delete();
- success = true;
- Log.d(TAG, "upload successful");
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- if (fileIs != null) {
- try {
- fileIs.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- if (connection != null) {
- connection.disconnect();
- }
- }
- return success;
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index cf6f31a0a..5c2487195 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -18,11 +18,14 @@ package com.android.inputmethod.research;
import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET;
+import android.app.AlarmManager;
import android.app.AlertDialog;
import android.app.Dialog;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
+import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.PackageInfo;
@@ -32,28 +35,30 @@ import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.inputmethodservice.InputMethodService;
+import android.net.Uri;
import android.os.Build;
import android.os.IBinder;
+import android.os.SystemClock;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Log;
+import android.view.KeyEvent;
import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnClickListener;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.CorrectionInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
-import android.widget.Button;
import android.widget.Toast;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardId;
-import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.keyboard.KeyboardView;
import com.android.inputmethod.keyboard.MainKeyboardView;
+import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.LatinIME;
import com.android.inputmethod.latin.R;
@@ -64,11 +69,8 @@ import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.define.ProductionFlag;
import java.io.File;
-import java.io.IOException;
import java.text.SimpleDateFormat;
-import java.util.ArrayList;
import java.util.Date;
-import java.util.List;
import java.util.Locale;
import java.util.UUID;
@@ -94,24 +96,26 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
new SimpleDateFormat("yyyyMMddHHmmssS", Locale.US);
private static final boolean IS_SHOWING_INDICATOR = true;
private static final boolean IS_SHOWING_INDICATOR_CLEARLY = false;
+ public static final int FEEDBACK_WORD_BUFFER_SIZE = 5;
// constants related to specific log points
private static final String WHITESPACE_SEPARATORS = " \t\n\r";
private static final int MAX_INPUTVIEW_LENGTH_TO_CAPTURE = 8192; // must be >=1
private static final String PREF_RESEARCH_LOGGER_UUID_STRING = "pref_research_logger_uuid";
- private static final int ABORT_TIMEOUT_IN_MS = 10 * 1000; // timeout to notify user
private static final ResearchLogger sInstance = new ResearchLogger();
// to write to a different filename, e.g., for testing, set mFile before calling start()
/* package */ File mFilesDir;
/* package */ String mUUIDString;
/* package */ ResearchLog mMainResearchLog;
- // The mIntentionalResearchLog records all events for the session, private or not (excepting
+ // mFeedbackLog records all events for the session, private or not (excepting
// passwords). It is written to permanent storage only if the user explicitly commands
// the system to do so.
- /* package */ ResearchLog mIntentionalResearchLog;
- // LogUnits are queued here and released only when the user requests the intentional log.
- private List<LogUnit> mIntentionalResearchLogQueue = new ArrayList<LogUnit>();
+ // LogUnits are queued in the LogBuffers and published to the ResearchLogs when words are
+ // complete.
+ /* package */ ResearchLog mFeedbackLog;
+ /* package */ MainLogBuffer mMainLogBuffer;
+ /* package */ LogBuffer mFeedbackLogBuffer;
private boolean mIsPasswordView = false;
private boolean mIsLoggingSuspended = false;
@@ -133,20 +137,24 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
// used to check whether words are not unique
private Suggest mSuggest;
private Dictionary mDictionary;
- private KeyboardSwitcher mKeyboardSwitcher;
+ private MainKeyboardView mMainKeyboardView;
private InputMethodService mInputMethodService;
+ private final Statistics mStatistics;
- private ResearchLogUploader mResearchLogUploader;
+ private Intent mUploadIntent;
+ private PendingIntent mUploadPendingIntent;
+
+ private LogUnit mCurrentLogUnit = new LogUnit();
private ResearchLogger() {
+ mStatistics = Statistics.getInstance();
}
public static ResearchLogger getInstance() {
return sInstance;
}
- public void init(final InputMethodService ims, final SharedPreferences prefs,
- KeyboardSwitcher keyboardSwitcher) {
+ public void init(final InputMethodService ims, final SharedPreferences prefs) {
assert ims != null;
if (ims == null) {
Log.w(TAG, "IMS is null; logging is off");
@@ -176,11 +184,33 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
e.apply();
}
}
- mResearchLogUploader = new ResearchLogUploader(ims, mFilesDir);
- mResearchLogUploader.start();
- mKeyboardSwitcher = keyboardSwitcher;
mInputMethodService = ims;
mPrefs = prefs;
+ mUploadIntent = new Intent(mInputMethodService, UploaderService.class);
+ mUploadPendingIntent = PendingIntent.getService(mInputMethodService, 0, mUploadIntent, 0);
+
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ scheduleUploadingService(mInputMethodService);
+ }
+ }
+
+ /**
+ * Arrange for the UploaderService to be run on a regular basis.
+ *
+ * Any existing scheduled invocation of UploaderService is removed and rescheduled. This may
+ * cause problems if this method is called often and frequent updates are required, but since
+ * the user will likely be sleeping at some point, if the interval is less that the expected
+ * sleep duration and this method is not called during that time, the service should be invoked
+ * at some point.
+ */
+ public static void scheduleUploadingService(Context context) {
+ final Intent intent = new Intent(context, UploaderService.class);
+ final PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0);
+ final AlarmManager manager =
+ (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ manager.cancel(pendingIntent);
+ manager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ UploaderService.RUN_INTERVAL, UploaderService.RUN_INTERVAL, pendingIntent);
}
private void cleanupLoggingDir(final File dir, final long time) {
@@ -192,10 +222,15 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
}
}
- public void mainKeyboardView_onAttachedToWindow() {
+ public void mainKeyboardView_onAttachedToWindow(final MainKeyboardView mainKeyboardView) {
+ mMainKeyboardView = mainKeyboardView;
maybeShowSplashScreen();
}
+ public void mainKeyboardView_onDetachedFromWindow() {
+ mMainKeyboardView = null;
+ }
+
private boolean hasSeenSplash() {
return mPrefs.getBoolean(PREF_RESEARCH_HAS_SEEN_SPLASH, false);
}
@@ -209,54 +244,71 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
if (mSplashDialog != null && mSplashDialog.isShowing()) {
return;
}
- final IBinder windowToken = mKeyboardSwitcher.getMainKeyboardView().getWindowToken();
+ final IBinder windowToken = mMainKeyboardView != null
+ ? mMainKeyboardView.getWindowToken() : null;
if (windowToken == null) {
return;
}
- mSplashDialog = new Dialog(mInputMethodService, android.R.style.Theme_Holo_Dialog);
- mSplashDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
- mSplashDialog.setContentView(R.layout.research_splash);
- mSplashDialog.setCancelable(true);
+ final AlertDialog.Builder builder = new AlertDialog.Builder(mInputMethodService)
+ .setTitle(R.string.research_splash_title)
+ .setMessage(R.string.research_splash_content)
+ .setPositiveButton(android.R.string.yes,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ onUserLoggingConsent();
+ mSplashDialog.dismiss();
+ }
+ })
+ .setNegativeButton(android.R.string.no,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final String packageName = mInputMethodService.getPackageName();
+ final Uri packageUri = Uri.parse("package:" + packageName);
+ final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE,
+ packageUri);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mInputMethodService.startActivity(intent);
+ }
+ })
+ .setCancelable(true)
+ .setOnCancelListener(
+ new OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ mInputMethodService.requestHideSelf(0);
+ }
+ });
+ mSplashDialog = builder.create();
final Window w = mSplashDialog.getWindow();
final WindowManager.LayoutParams lp = w.getAttributes();
lp.token = windowToken;
lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
w.setAttributes(lp);
w.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
- mSplashDialog.setOnCancelListener(new OnCancelListener() {
- @Override
- public void onCancel(DialogInterface dialog) {
- mInputMethodService.requestHideSelf(0);
- }
- });
- final Button doNotLogButton = (Button) mSplashDialog.findViewById(
- R.id.research_do_not_log_button);
- doNotLogButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- onUserLoggingElection(false);
- mSplashDialog.dismiss();
- }
- });
- final Button doLogButton = (Button) mSplashDialog.findViewById(R.id.research_do_log_button);
- doLogButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- onUserLoggingElection(true);
- mSplashDialog.dismiss();
- }
- });
mSplashDialog.show();
}
- public void onUserLoggingElection(final boolean enableLogging) {
- setLoggingAllowed(enableLogging);
+ public void onUserLoggingConsent() {
+ setLoggingAllowed(true);
if (mPrefs == null) {
return;
}
final Editor e = mPrefs.edit();
e.putBoolean(PREF_RESEARCH_HAS_SEEN_SPLASH, true);
e.apply();
+ restart();
+ }
+
+ private void setLoggingAllowed(boolean enableLogging) {
+ if (mPrefs == null) {
+ return;
+ }
+ Editor e = mPrefs.edit();
+ e.putBoolean(PREF_USABILITY_STUDY_MODE, enableLogging);
+ e.apply();
+ sIsLogging = enableLogging;
}
private File createLogFile(File filesDir) {
@@ -268,10 +320,35 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
return new File(filesDir, sb.toString());
}
+ private void checkForEmptyEditor() {
+ if (mInputMethodService == null) {
+ return;
+ }
+ final InputConnection ic = mInputMethodService.getCurrentInputConnection();
+ if (ic == null) {
+ return;
+ }
+ final CharSequence textBefore = ic.getTextBeforeCursor(1, 0);
+ if (!TextUtils.isEmpty(textBefore)) {
+ mStatistics.setIsEmptyUponStarting(false);
+ return;
+ }
+ final CharSequence textAfter = ic.getTextAfterCursor(1, 0);
+ if (!TextUtils.isEmpty(textAfter)) {
+ mStatistics.setIsEmptyUponStarting(false);
+ return;
+ }
+ if (textBefore != null && textAfter != null) {
+ mStatistics.setIsEmptyUponStarting(true);
+ }
+ }
+
private void start() {
maybeShowSplashScreen();
updateSuspendedState();
requestIndicatorRedraw();
+ mStatistics.reset();
+ checkForEmptyEditor();
if (!isAllowedToLog()) {
// Log.w(TAG, "not in usability mode; not logging");
return;
@@ -280,73 +357,58 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
Log.w(TAG, "IME storage directory does not exist. Cannot start logging.");
return;
}
- try {
- if (mMainResearchLog == null || !mMainResearchLog.isAlive()) {
- mMainResearchLog = new ResearchLog(createLogFile(mFilesDir));
- }
- mMainResearchLog.start();
- if (mIntentionalResearchLog == null || !mIntentionalResearchLog.isAlive()) {
- mIntentionalResearchLog = new ResearchLog(createLogFile(mFilesDir));
- }
- mIntentionalResearchLog.start();
- } catch (IOException e) {
- Log.w(TAG, "Could not start ResearchLogger.");
+ if (mMainLogBuffer == null) {
+ mMainResearchLog = new ResearchLog(createLogFile(mFilesDir));
+ mMainLogBuffer = new MainLogBuffer(mMainResearchLog);
+ mMainLogBuffer.setSuggest(mSuggest);
+ }
+ if (mFeedbackLogBuffer == null) {
+ mFeedbackLog = new ResearchLog(createLogFile(mFilesDir));
+ // LogBuffer is one more than FEEDBACK_WORD_BUFFER_SIZE, because it must also hold
+ // the feedback LogUnit itself.
+ mFeedbackLogBuffer = new LogBuffer(FEEDBACK_WORD_BUFFER_SIZE + 1);
}
}
/* package */ void stop() {
- if (mMainResearchLog != null) {
- mMainResearchLog.stop();
- }
- if (mIntentionalResearchLog != null) {
- mIntentionalResearchLog.stop();
- }
- }
+ logStatistics();
+ commitCurrentLogUnit();
- private void setLoggingAllowed(boolean enableLogging) {
- if (mPrefs == null) {
- return;
+ if (mMainLogBuffer != null) {
+ publishLogBuffer(mMainLogBuffer, mMainResearchLog, false /* isIncludingPrivateData */);
+ mMainResearchLog.close();
+ mMainLogBuffer = null;
+ }
+ if (mFeedbackLogBuffer != null) {
+ mFeedbackLog.close();
+ mFeedbackLogBuffer = null;
}
- Editor e = mPrefs.edit();
- e.putBoolean(PREF_USABILITY_STUDY_MODE, enableLogging);
- e.apply();
- sIsLogging = enableLogging;
}
public boolean abort() {
boolean didAbortMainLog = false;
- if (mMainResearchLog != null) {
- mMainResearchLog.abort();
+ if (mMainLogBuffer != null) {
+ mMainLogBuffer.clear();
try {
- mMainResearchLog.waitUntilStopped(ABORT_TIMEOUT_IN_MS);
+ didAbortMainLog = mMainResearchLog.blockingAbort();
} catch (InterruptedException e) {
- // interrupted early. carry on.
+ // Don't know whether this succeeded or not. We assume not; this is reported
+ // to the caller.
}
- if (mMainResearchLog.isAbortSuccessful()) {
- didAbortMainLog = true;
- }
- mMainResearchLog = null;
+ mMainLogBuffer = null;
}
- boolean didAbortIntentionalLog = false;
- if (mIntentionalResearchLog != null) {
- mIntentionalResearchLog.abort();
+ boolean didAbortFeedbackLog = false;
+ if (mFeedbackLogBuffer != null) {
+ mFeedbackLogBuffer.clear();
try {
- mIntentionalResearchLog.waitUntilStopped(ABORT_TIMEOUT_IN_MS);
+ didAbortFeedbackLog = mFeedbackLog.blockingAbort();
} catch (InterruptedException e) {
- // interrupted early. carry on.
- }
- if (mIntentionalResearchLog.isAbortSuccessful()) {
- didAbortIntentionalLog = true;
+ // Don't know whether this succeeded or not. We assume not; this is reported
+ // to the caller.
}
- mIntentionalResearchLog = null;
- }
- return didAbortMainLog && didAbortIntentionalLog;
- }
-
- /* package */ void flush() {
- if (mMainResearchLog != null) {
- mMainResearchLog.flush();
+ mFeedbackLogBuffer = null;
}
+ return didAbortMainLog && didAbortFeedbackLog;
}
private void restart() {
@@ -387,14 +449,22 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
abort();
}
requestIndicatorRedraw();
+ mPrefs = prefs;
+ prefsChanged(prefs);
}
- public void presentResearchDialog(final LatinIME latinIME) {
+ public void onResearchKeySelected(final LatinIME latinIME) {
if (mInFeedbackDialog) {
Toast.makeText(latinIME, R.string.research_please_exit_feedback_form,
Toast.LENGTH_LONG).show();
return;
}
+ presentFeedbackDialog(latinIME);
+ }
+
+ // TODO: currently unreachable. Remove after being sure no menu is needed.
+ /*
+ public void presentResearchDialog(final LatinIME latinIME) {
final CharSequence title = latinIME.getString(R.string.english_ime_research_log);
final boolean showEnable = mIsLoggingSuspended || !sIsLogging;
final CharSequence[] items = new CharSequence[] {
@@ -411,28 +481,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
presentFeedbackDialog(latinIME);
break;
case 1:
- if (showEnable) {
- if (!sIsLogging) {
- setLoggingAllowed(true);
- }
- resumeLogging();
- Toast.makeText(latinIME,
- R.string.research_notify_session_logging_enabled,
- Toast.LENGTH_LONG).show();
- } else {
- Toast toast = Toast.makeText(latinIME,
- R.string.research_notify_session_log_deleting,
- Toast.LENGTH_LONG);
- toast.show();
- boolean isLogDeleted = abort();
- final long currentTime = System.currentTimeMillis();
- final long resumeTime = currentTime + 1000 * 60 *
- SUSPEND_DURATION_IN_MINUTES;
- suspendLoggingUntil(resumeTime);
- toast.cancel();
- Toast.makeText(latinIME, R.string.research_notify_logging_suspended,
- Toast.LENGTH_LONG).show();
- }
+ enableOrDisable(showEnable, latinIME);
break;
}
}
@@ -443,6 +492,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
.setTitle(title);
latinIME.showOptionDialog(builder.create());
}
+ */
private boolean mInFeedbackDialog = false;
public void presentFeedbackDialog(LatinIME latinIME) {
@@ -450,79 +500,73 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
latinIME.launchKeyboardedDialogActivity(FeedbackActivity.class);
}
- private ResearchLog mFeedbackLog;
- private List<LogUnit> mFeedbackQueue;
- private ResearchLog mSavedMainResearchLog;
- private ResearchLog mSavedIntentionalResearchLog;
- private List<LogUnit> mSavedIntentionalResearchLogQueue;
-
- private void saveLogsForFeedback() {
- mFeedbackLog = mIntentionalResearchLog;
- if (mIntentionalResearchLogQueue != null) {
- mFeedbackQueue = new ArrayList<LogUnit>(mIntentionalResearchLogQueue);
+ // TODO: currently unreachable. Remove after being sure enable/disable is
+ // not needed.
+ /*
+ public void enableOrDisable(final boolean showEnable, final LatinIME latinIME) {
+ if (showEnable) {
+ if (!sIsLogging) {
+ setLoggingAllowed(true);
+ }
+ resumeLogging();
+ Toast.makeText(latinIME,
+ R.string.research_notify_session_logging_enabled,
+ Toast.LENGTH_LONG).show();
} else {
- mFeedbackQueue = null;
+ Toast toast = Toast.makeText(latinIME,
+ R.string.research_notify_session_log_deleting,
+ Toast.LENGTH_LONG);
+ toast.show();
+ boolean isLogDeleted = abort();
+ final long currentTime = System.currentTimeMillis();
+ final long resumeTime = currentTime + 1000 * 60 *
+ SUSPEND_DURATION_IN_MINUTES;
+ suspendLoggingUntil(resumeTime);
+ toast.cancel();
+ Toast.makeText(latinIME, R.string.research_notify_logging_suspended,
+ Toast.LENGTH_LONG).show();
}
- mSavedMainResearchLog = mMainResearchLog;
- mSavedIntentionalResearchLog = mIntentionalResearchLog;
- mSavedIntentionalResearchLogQueue = mIntentionalResearchLogQueue;
-
- mMainResearchLog = null;
- mIntentionalResearchLog = null;
- mIntentionalResearchLogQueue = new ArrayList<LogUnit>();
}
+ */
- private static final int LOG_DRAIN_TIMEOUT_IN_MS = 1000 * 5;
+ private static final String[] EVENTKEYS_FEEDBACK = {
+ "UserTimestamp", "contents"
+ };
public void sendFeedback(final String feedbackContents, final boolean includeHistory) {
- if (includeHistory && mFeedbackLog != null) {
- try {
- LogUnit headerLogUnit = new LogUnit();
- headerLogUnit.addLogAtom(EVENTKEYS_INTENTIONAL_LOG, EVENTKEYS_NULLVALUES, false);
- mFeedbackLog.publishAllEvents(headerLogUnit);
- for (LogUnit logUnit : mFeedbackQueue) {
- mFeedbackLog.publishAllEvents(logUnit);
- }
- userFeedback(mFeedbackLog, feedbackContents);
- mFeedbackLog.stop();
- try {
- mFeedbackLog.waitUntilStopped(LOG_DRAIN_TIMEOUT_IN_MS);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- mIntentionalResearchLog = new ResearchLog(createLogFile(mFilesDir));
- mIntentionalResearchLog.start();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- mIntentionalResearchLogQueue.clear();
- }
- mResearchLogUploader.uploadNow(null);
+ if (mFeedbackLogBuffer == null) {
+ return;
+ }
+ if (includeHistory) {
+ commitCurrentLogUnit();
} else {
- // create a separate ResearchLog just for feedback
- final ResearchLog feedbackLog = new ResearchLog(createLogFile(mFilesDir));
- try {
- feedbackLog.start();
- userFeedback(feedbackLog, feedbackContents);
- feedbackLog.stop();
- feedbackLog.waitUntilStopped(LOG_DRAIN_TIMEOUT_IN_MS);
- mResearchLogUploader.uploadNow(null);
- } catch (IOException e) {
- e.printStackTrace();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
+ mFeedbackLogBuffer.clear();
}
+ final LogUnit feedbackLogUnit = new LogUnit();
+ final Object[] values = {
+ feedbackContents
+ };
+ feedbackLogUnit.addLogStatement(EVENTKEYS_FEEDBACK, values,
+ false /* isPotentiallyPrivate */);
+ mFeedbackLogBuffer.shiftIn(feedbackLogUnit);
+ publishLogBuffer(mFeedbackLogBuffer, mFeedbackLog, true /* isIncludingPrivateData */);
+ mFeedbackLog.close();
+ uploadNow();
+ mFeedbackLog = new ResearchLog(createLogFile(mFilesDir));
+ }
+
+ public void uploadNow() {
+ mInputMethodService.startService(mUploadIntent);
}
public void onLeavingSendFeedbackDialog() {
mInFeedbackDialog = false;
- mMainResearchLog = mSavedMainResearchLog;
- mIntentionalResearchLog = mSavedIntentionalResearchLog;
- mIntentionalResearchLogQueue = mSavedIntentionalResearchLogQueue;
}
public void initSuggest(Suggest suggest) {
mSuggest = suggest;
+ if (mMainLogBuffer != null) {
+ mMainLogBuffer.setSuggest(mSuggest);
+ }
}
private void setIsPasswordView(boolean isPasswordView) {
@@ -530,21 +574,17 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
}
private boolean isAllowedToLog() {
- return !mIsPasswordView && !mIsLoggingSuspended && sIsLogging;
+ return !mIsPasswordView && !mIsLoggingSuspended && sIsLogging && !mInFeedbackDialog;
}
public void requestIndicatorRedraw() {
if (!IS_SHOWING_INDICATOR) {
return;
}
- if (mKeyboardSwitcher == null) {
- return;
- }
- final KeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
- if (mainKeyboardView == null) {
+ if (mMainKeyboardView == null) {
return;
}
- mainKeyboardView.invalidateAllKeys();
+ mMainKeyboardView.invalidateAllKeys();
}
@@ -577,13 +617,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
}
}
- private static final String CURRENT_TIME_KEY = "_ct";
- private static final String UPTIME_KEY = "_ut";
- private static final String EVENT_TYPE_KEY = "_ty";
private static final Object[] EVENTKEYS_NULLVALUES = {};
- private LogUnit mCurrentLogUnit = new LogUnit();
-
/**
* Buffer a research log event, flagging it as privacy-sensitive.
*
@@ -599,10 +634,14 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
final Object[] values) {
assert values.length + 1 == keys.length;
if (isAllowedToLog()) {
- mCurrentLogUnit.addLogAtom(keys, values, true);
+ mCurrentLogUnit.addLogStatement(keys, values, true /* isPotentiallyPrivate */);
}
}
+ private void setCurrentLogUnitContainsDigitFlag() {
+ mCurrentLogUnit.setContainsDigit();
+ }
+
/**
* Buffer a research log event, flaggint it as not privacy-sensitive.
*
@@ -618,139 +657,54 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
private synchronized void enqueueEvent(final String[] keys, final Object[] values) {
assert values.length + 1 == keys.length;
if (isAllowedToLog()) {
- mCurrentLogUnit.addLogAtom(keys, values, false);
+ mCurrentLogUnit.addLogStatement(keys, values, false /* isPotentiallyPrivate */);
}
}
- // Used to track how often words are logged. Too-frequent logging can leak
- // semantics, disclosing private data.
- /* package for test */ static class LoggingFrequencyState {
- private static final int DEFAULT_WORD_LOG_FREQUENCY = 10;
- private int mWordsRemainingToSkip;
- private final int mFrequency;
-
- /**
- * Tracks how often words may be uploaded.
- *
- * @param frequency 1=Every word, 2=Every other word, etc.
- */
- public LoggingFrequencyState(int frequency) {
- mFrequency = frequency;
- mWordsRemainingToSkip = mFrequency;
- }
-
- public void onWordLogged() {
- mWordsRemainingToSkip = mFrequency;
- }
-
- public void onWordNotLogged() {
- if (mWordsRemainingToSkip > 1) {
- mWordsRemainingToSkip--;
+ /* package for test */ void commitCurrentLogUnit() {
+ if (!mCurrentLogUnit.isEmpty()) {
+ if (mMainLogBuffer != null) {
+ mMainLogBuffer.shiftIn(mCurrentLogUnit);
+ if (mMainLogBuffer.isSafeToLog() && mMainResearchLog != null) {
+ publishLogBuffer(mMainLogBuffer, mMainResearchLog,
+ true /* isIncludingPrivateData */);
+ mMainLogBuffer.resetWordCounter();
+ }
}
+ if (mFeedbackLogBuffer != null) {
+ mFeedbackLogBuffer.shiftIn(mCurrentLogUnit);
+ }
+ mCurrentLogUnit = new LogUnit();
+ Log.d(TAG, "commitCurrentLogUnit");
}
+ }
- public boolean isSafeToLog() {
- return mWordsRemainingToSkip <= 1;
+ /* package for test */ void publishLogBuffer(final LogBuffer logBuffer,
+ final ResearchLog researchLog, final boolean isIncludingPrivateData) {
+ LogUnit logUnit;
+ while ((logUnit = logBuffer.shiftOut()) != null) {
+ researchLog.publish(logUnit, isIncludingPrivateData);
}
}
- /* package for test */ LoggingFrequencyState mLoggingFrequencyState =
- new LoggingFrequencyState(LoggingFrequencyState.DEFAULT_WORD_LOG_FREQUENCY);
-
- /* package for test */ boolean isPrivacyThreat(String word) {
- // Current checks:
- // - Word not in dictionary
- // - Word contains numbers
- // - Privacy-safe word not logged recently
- if (TextUtils.isEmpty(word)) {
- return false;
- }
- if (!mLoggingFrequencyState.isSafeToLog()) {
- return true;
- }
+ private boolean hasOnlyLetters(final String word) {
final int length = word.length();
- boolean hasLetter = false;
for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
- final int codePoint = Character.codePointAt(word, i);
- if (Character.isDigit(codePoint)) {
- return true;
- }
- if (Character.isLetter(codePoint)) {
- hasLetter = true;
- break; // Word may contain digits, but will only be allowed if in the dictionary.
- }
- }
- if (hasLetter) {
- if (mDictionary == null && mSuggest != null && mSuggest.hasMainDictionary()) {
- mDictionary = mSuggest.getMainDictionary();
- }
- if (mDictionary == null) {
- // Can't access dictionary. Assume privacy threat.
- return true;
+ final int codePoint = word.codePointAt(i);
+ if (!Character.isLetter(codePoint)) {
+ return false;
}
- return !(mDictionary.isValidWord(word));
- }
- // No letters, no numbers. Punctuation, space, or something else.
- return false;
- }
-
- private void onWordComplete(String word) {
- if (isPrivacyThreat(word)) {
- publishLogUnit(mCurrentLogUnit, true);
- mLoggingFrequencyState.onWordNotLogged();
- } else {
- publishLogUnit(mCurrentLogUnit, false);
- mLoggingFrequencyState.onWordLogged();
}
- mCurrentLogUnit = new LogUnit();
+ return true;
}
- private void publishLogUnit(LogUnit logUnit, boolean isPrivacySensitive) {
- if (!isAllowedToLog()) {
- return;
- }
- if (mMainResearchLog == null) {
- return;
- }
- if (isPrivacySensitive) {
- mMainResearchLog.publishPublicEvents(logUnit);
- } else {
- mMainResearchLog.publishAllEvents(logUnit);
- }
- mIntentionalResearchLogQueue.add(logUnit);
- }
-
- /* package */ void publishCurrentLogUnit(ResearchLog researchLog, boolean isPrivacySensitive) {
- publishLogUnit(mCurrentLogUnit, isPrivacySensitive);
- }
-
- static class LogUnit {
- private final List<String[]> mKeysList = new ArrayList<String[]>();
- private final List<Object[]> mValuesList = new ArrayList<Object[]>();
- private final List<Boolean> mIsPotentiallyPrivate = new ArrayList<Boolean>();
-
- private void addLogAtom(final String[] keys, final Object[] values,
- final Boolean isPotentiallyPrivate) {
- mKeysList.add(keys);
- mValuesList.add(values);
- mIsPotentiallyPrivate.add(isPotentiallyPrivate);
- }
-
- public void publishPublicEventsTo(ResearchLog researchLog) {
- final int size = mKeysList.size();
- for (int i = 0; i < size; i++) {
- if (!mIsPotentiallyPrivate.get(i)) {
- researchLog.outputEvent(mKeysList.get(i), mValuesList.get(i));
- }
- }
- }
-
- public void publishAllEventsTo(ResearchLog researchLog) {
- final int size = mKeysList.size();
- for (int i = 0; i < size; i++) {
- researchLog.outputEvent(mKeysList.get(i), mValuesList.get(i));
- }
+ private void onWordComplete(final String word) {
+ Log.d(TAG, "onWordComplete: " + word);
+ if (word != null && word.length() > 0 && hasOnlyLetters(word)) {
+ mCurrentLogUnit.setWord(word);
+ mStatistics.recordWordEntered();
}
+ commitCurrentLogUnit();
}
private static int scrubDigitFromCodePoint(int codePoint) {
@@ -803,12 +757,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
return WORD_REPLACEMENT_STRING;
}
- // Special methods related to startup, shutdown, logging itself
-
- private static final String[] EVENTKEYS_INTENTIONAL_LOG = {
- "IntentionalLog"
- };
-
private static final String[] EVENTKEYS_LATINIME_ONSTARTINPUTVIEWINTERNAL = {
"LatinIMEOnStartInputViewInternal", "uuid", "packageName", "inputType", "imeOptions",
"fieldId", "display", "model", "prefs", "versionCode", "versionName", "outputFormatVersion"
@@ -816,9 +764,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo,
final SharedPreferences prefs) {
final ResearchLogger researchLogger = getInstance();
- if (researchLogger.mInFeedbackDialog) {
- researchLogger.saveLogsForFeedback();
- }
researchLogger.start();
if (editorInfo != null) {
final Context context = researchLogger.mInputMethodService;
@@ -846,32 +791,19 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
stop();
}
- private static final String[] EVENTKEYS_LATINIME_COMMITTEXT = {
- "LatinIMECommitText", "typedWord"
- };
-
- public static void latinIME_commitText(final CharSequence typedWord) {
- final String scrubbedWord = scrubDigitsFromString(typedWord.toString());
- final Object[] values = {
- scrubbedWord
- };
- final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_COMMITTEXT, values);
- researchLogger.onWordComplete(scrubbedWord);
- }
-
private static final String[] EVENTKEYS_USER_FEEDBACK = {
"UserFeedback", "FeedbackContents"
};
- private void userFeedback(ResearchLog researchLog, String feedbackContents) {
- // this method is special; it directs the feedbackContents to a particular researchLog
- final LogUnit logUnit = new LogUnit();
+ private static final String[] EVENTKEYS_PREFS_CHANGED = {
+ "PrefsChanged", "prefs"
+ };
+ public static void prefsChanged(final SharedPreferences prefs) {
+ final ResearchLogger researchLogger = getInstance();
final Object[] values = {
- feedbackContents
+ prefs
};
- logUnit.addLogAtom(EVENTKEYS_USER_FEEDBACK, values, false);
- researchLog.publishAllEvents(logUnit);
+ researchLogger.enqueueEvent(EVENTKEYS_PREFS_CHANGED, values);
}
// Regular logging methods
@@ -908,51 +840,16 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
"LatinIMEOnCodeInput", "code", "x", "y"
};
public static void latinIME_onCodeInput(final int code, final int x, final int y) {
- final Object[] values = {
- Keyboard.printableCode(scrubDigitFromCodePoint(code)), x, y
- };
- getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONCODEINPUT, values);
- }
-
- private static final String[] EVENTKEYS_CORRECTION = {
- "LogCorrection", "subgroup", "before", "after", "position"
- };
- public static void logCorrection(final String subgroup, final String before, final String after,
- final int position) {
- final Object[] values = {
- subgroup, scrubDigitsFromString(before), scrubDigitsFromString(after), position
- };
- getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_CORRECTION, values);
- }
-
- private static final String[] EVENTKEYS_LATINIME_COMMITCURRENTAUTOCORRECTION = {
- "LatinIMECommitCurrentAutoCorrection", "typedWord", "autoCorrection"
- };
- public static void latinIME_commitCurrentAutoCorrection(final String typedWord,
- final String autoCorrection) {
- final Object[] values = {
- scrubDigitsFromString(typedWord), scrubDigitsFromString(autoCorrection)
- };
+ final long time = SystemClock.uptimeMillis();
final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueuePotentiallyPrivateEvent(
- EVENTKEYS_LATINIME_COMMITCURRENTAUTOCORRECTION, values);
- }
-
- private static final String[] EVENTKEYS_LATINIME_DELETESURROUNDINGTEXT = {
- "LatinIMEDeleteSurroundingText", "length"
- };
- public static void latinIME_deleteSurroundingText(final int length) {
final Object[] values = {
- length
+ Keyboard.printableCode(scrubDigitFromCodePoint(code)), x, y
};
- getInstance().enqueueEvent(EVENTKEYS_LATINIME_DELETESURROUNDINGTEXT, values);
- }
-
- private static final String[] EVENTKEYS_LATINIME_DOUBLESPACEAUTOPERIOD = {
- "LatinIMEDoubleSpaceAutoPeriod"
- };
- public static void latinIME_doubleSpaceAutoPeriod() {
- getInstance().enqueueEvent(EVENTKEYS_LATINIME_DOUBLESPACEAUTOPERIOD, EVENTKEYS_NULLVALUES);
+ researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONCODEINPUT, values);
+ if (Character.isDigit(code)) {
+ researchLogger.setCurrentLogUnitContainsDigitFlag();
+ }
+ researchLogger.mStatistics.recordChar(code, time);
}
private static final String[] EVENTKEYS_LATINIME_ONDISPLAYCOMPLETIONS = {
@@ -979,6 +876,10 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
public static void latinIME_onWindowHidden(final int savedSelectionStart,
final int savedSelectionEnd, final InputConnection ic) {
if (ic != null) {
+ // Capture the TextView contents. This will trigger onUpdateSelection(), so we
+ // set sLatinIMEExpectingUpdateSelection so that when onUpdateSelection() is called,
+ // it can tell that it was generated by the logging code, and not by the user, and
+ // therefore keep user-visible state as is.
ic.beginBatchEdit();
ic.performContextMenuAction(android.R.id.selectAll);
CharSequence charSequence = ic.getSelectedText(0);
@@ -1013,9 +914,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
}
final ResearchLogger researchLogger = getInstance();
researchLogger.enqueueEvent(EVENTKEYS_LATINIME_ONWINDOWHIDDEN, values);
- // Play it safe. Remove privacy-sensitive events.
- researchLogger.publishLogUnit(researchLogger.mCurrentLogUnit, true);
- researchLogger.mCurrentLogUnit = new LogUnit();
+ researchLogger.commitCurrentLogUnit();
getInstance().stop();
}
}
@@ -1048,37 +947,15 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONUPDATESELECTION, values);
}
- private static final String[] EVENTKEYS_LATINIME_PERFORMEDITORACTION = {
- "LatinIMEPerformEditorAction", "imeActionNext"
- };
- public static void latinIME_performEditorAction(final int imeActionNext) {
- final Object[] values = {
- imeActionNext
- };
- getInstance().enqueueEvent(EVENTKEYS_LATINIME_PERFORMEDITORACTION, values);
- }
-
- private static final String[] EVENTKEYS_LATINIME_PICKAPPLICATIONSPECIFIEDCOMPLETION = {
- "LatinIMEPickApplicationSpecifiedCompletion", "index", "text", "x", "y"
- };
- public static void latinIME_pickApplicationSpecifiedCompletion(final int index,
- final CharSequence cs, int x, int y) {
- final Object[] values = {
- index, cs, x, y
- };
- final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueuePotentiallyPrivateEvent(
- EVENTKEYS_LATINIME_PICKAPPLICATIONSPECIFIEDCOMPLETION, values);
- }
-
private static final String[] EVENTKEYS_LATINIME_PICKSUGGESTIONMANUALLY = {
"LatinIMEPickSuggestionManually", "replacedWord", "index", "suggestion", "x", "y"
};
public static void latinIME_pickSuggestionManually(final String replacedWord,
- final int index, CharSequence suggestion, int x, int y) {
+ final int index, CharSequence suggestion) {
final Object[] values = {
- scrubDigitsFromString(replacedWord), index, suggestion == null ? null :
- scrubDigitsFromString(suggestion.toString()), x, y
+ scrubDigitsFromString(replacedWord), index,
+ (suggestion == null ? null : scrubDigitsFromString(suggestion.toString())),
+ Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE
};
final ResearchLogger researchLogger = getInstance();
researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_PICKSUGGESTIONMANUALLY,
@@ -1089,28 +966,14 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
"LatinIMEPunctuationSuggestion", "index", "suggestion", "x", "y"
};
public static void latinIME_punctuationSuggestion(final int index,
- final CharSequence suggestion, int x, int y) {
+ final CharSequence suggestion) {
final Object[] values = {
- index, suggestion, x, y
+ index, suggestion,
+ Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE
};
getInstance().enqueueEvent(EVENTKEYS_LATINIME_PUNCTUATIONSUGGESTION, values);
}
- private static final String[] EVENTKEYS_LATINIME_REVERTDOUBLESPACEWHILEINBATCHEDIT = {
- "LatinIMERevertDoubleSpaceWhileInBatchEdit"
- };
- public static void latinIME_revertDoubleSpaceWhileInBatchEdit() {
- getInstance().enqueueEvent(EVENTKEYS_LATINIME_REVERTDOUBLESPACEWHILEINBATCHEDIT,
- EVENTKEYS_NULLVALUES);
- }
-
- private static final String[] EVENTKEYS_LATINIME_REVERTSWAPPUNCTUATION = {
- "LatinIMERevertSwapPunctuation"
- };
- public static void latinIME_revertSwapPunctuation() {
- getInstance().enqueueEvent(EVENTKEYS_LATINIME_REVERTSWAPPUNCTUATION, EVENTKEYS_NULLVALUES);
- }
-
private static final String[] EVENTKEYS_LATINIME_SENDKEYCODEPOINT = {
"LatinIMESendKeyCodePoint", "code"
};
@@ -1118,15 +981,18 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
final Object[] values = {
Keyboard.printableCode(scrubDigitFromCodePoint(code))
};
- getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_SENDKEYCODEPOINT, values);
+ final ResearchLogger researchLogger = getInstance();
+ researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_SENDKEYCODEPOINT, values);
+ if (Character.isDigit(code)) {
+ researchLogger.setCurrentLogUnitContainsDigitFlag();
+ }
}
- private static final String[] EVENTKEYS_LATINIME_SWAPSWAPPERANDSPACEWHILEINBATCHEDIT = {
- "LatinIMESwapSwapperAndSpaceWhileInBatchEdit"
+ private static final String[] EVENTKEYS_LATINIME_SWAPSWAPPERANDSPACE = {
+ "LatinIMESwapSwapperAndSpace"
};
- public static void latinIME_swapSwapperAndSpaceWhileInBatchEdit() {
- getInstance().enqueueEvent(EVENTKEYS_LATINIME_SWAPSWAPPERANDSPACEWHILEINBATCHEDIT,
- EVENTKEYS_NULLVALUES);
+ public static void latinIME_swapSwapperAndSpace() {
+ getInstance().enqueueEvent(EVENTKEYS_LATINIME_SWAPSWAPPERANDSPACE, EVENTKEYS_NULLVALUES);
}
private static final String[] EVENTKEYS_MAINKEYBOARDVIEW_ONLONGPRESS = {
@@ -1197,7 +1063,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
final int y, final boolean ignoreModifierKey, final boolean altersCode,
final int code) {
if (key != null) {
- CharSequence outputText = key.mOutputText;
+ String outputText = key.getOutputText();
final Object[] values = {
Keyboard.printableCode(scrubDigitFromCodePoint(code)), outputText == null ? null
: scrubDigitsFromString(outputText.toString()),
@@ -1245,6 +1111,128 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_POINTERTRACKER_ONMOVEEVENT, values);
}
+ private static final String[] EVENTKEYS_RICHINPUTCONNECTION_COMMITCOMPLETION = {
+ "RichInputConnectionCommitCompletion", "completionInfo"
+ };
+ public static void richInputConnection_commitCompletion(final CompletionInfo completionInfo) {
+ final Object[] values = {
+ completionInfo
+ };
+ final ResearchLogger researchLogger = getInstance();
+ researchLogger.enqueuePotentiallyPrivateEvent(
+ EVENTKEYS_RICHINPUTCONNECTION_COMMITCOMPLETION, values);
+ }
+
+ // Disabled for privacy-protection reasons. Because this event comes after
+ // richInputConnection_commitText, which is the event used to separate LogUnits, the
+ // data in this event can be associated with the next LogUnit, revealing information
+ // about the current word even if it was supposed to be suppressed. The occurrance of
+ // autocorrection can be determined by examining the difference between the text strings in
+ // the last call to richInputConnection_setComposingText before
+ // richInputConnection_commitText, so it's not a data loss.
+ // TODO: Figure out how to log this event without loss of privacy.
+ /*
+ private static final String[] EVENTKEYS_RICHINPUTCONNECTION_COMMITCORRECTION = {
+ "RichInputConnectionCommitCorrection", "typedWord", "autoCorrection"
+ };
+ */
+ public static void richInputConnection_commitCorrection(CorrectionInfo correctionInfo) {
+ /*
+ final String typedWord = correctionInfo.getOldText().toString();
+ final String autoCorrection = correctionInfo.getNewText().toString();
+ final Object[] values = {
+ scrubDigitsFromString(typedWord), scrubDigitsFromString(autoCorrection)
+ };
+ final ResearchLogger researchLogger = getInstance();
+ researchLogger.enqueuePotentiallyPrivateEvent(
+ EVENTKEYS_RICHINPUTCONNECTION_COMMITCORRECTION, values);
+ */
+ }
+
+ private static final String[] EVENTKEYS_RICHINPUTCONNECTION_COMMITTEXT = {
+ "RichInputConnectionCommitText", "typedWord", "newCursorPosition"
+ };
+ public static void richInputConnection_commitText(final CharSequence typedWord,
+ final int newCursorPosition) {
+ final String scrubbedWord = scrubDigitsFromString(typedWord.toString());
+ final Object[] values = {
+ scrubbedWord, newCursorPosition
+ };
+ final ResearchLogger researchLogger = getInstance();
+ researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_RICHINPUTCONNECTION_COMMITTEXT,
+ values);
+ researchLogger.onWordComplete(scrubbedWord);
+ }
+
+ private static final String[] EVENTKEYS_RICHINPUTCONNECTION_DELETESURROUNDINGTEXT = {
+ "RichInputConnectionDeleteSurroundingText", "beforeLength", "afterLength"
+ };
+ public static void richInputConnection_deleteSurroundingText(final int beforeLength,
+ final int afterLength) {
+ final Object[] values = {
+ beforeLength, afterLength
+ };
+ getInstance().enqueuePotentiallyPrivateEvent(
+ EVENTKEYS_RICHINPUTCONNECTION_DELETESURROUNDINGTEXT, values);
+ }
+
+ private static final String[] EVENTKEYS_RICHINPUTCONNECTION_FINISHCOMPOSINGTEXT = {
+ "RichInputConnectionFinishComposingText"
+ };
+ public static void richInputConnection_finishComposingText() {
+ getInstance().enqueueEvent(EVENTKEYS_RICHINPUTCONNECTION_FINISHCOMPOSINGTEXT,
+ EVENTKEYS_NULLVALUES);
+ }
+
+ private static final String[] EVENTKEYS_RICHINPUTCONNECTION_PERFORMEDITORACTION = {
+ "RichInputConnectionPerformEditorAction", "imeActionNext"
+ };
+ public static void richInputConnection_performEditorAction(final int imeActionNext) {
+ final Object[] values = {
+ imeActionNext
+ };
+ getInstance().enqueueEvent(EVENTKEYS_RICHINPUTCONNECTION_PERFORMEDITORACTION, values);
+ }
+
+ private static final String[] EVENTKEYS_RICHINPUTCONNECTION_SENDKEYEVENT = {
+ "RichInputConnectionSendKeyEvent", "eventTime", "action", "code"
+ };
+ public static void richInputConnection_sendKeyEvent(final KeyEvent keyEvent) {
+ final Object[] values = {
+ keyEvent.getEventTime(),
+ keyEvent.getAction(),
+ keyEvent.getKeyCode()
+ };
+ getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_RICHINPUTCONNECTION_SENDKEYEVENT,
+ values);
+ }
+
+ private static final String[] EVENTKEYS_RICHINPUTCONNECTION_SETCOMPOSINGTEXT = {
+ "RichInputConnectionSetComposingText", "text", "newCursorPosition"
+ };
+ public static void richInputConnection_setComposingText(final CharSequence text,
+ final int newCursorPosition) {
+ if (text == null) {
+ throw new RuntimeException("setComposingText is null");
+ }
+ final Object[] values = {
+ text, newCursorPosition
+ };
+ getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_RICHINPUTCONNECTION_SETCOMPOSINGTEXT,
+ values);
+ }
+
+ private static final String[] EVENTKEYS_RICHINPUTCONNECTION_SETSELECTION = {
+ "RichInputConnectionSetSelection", "from", "to"
+ };
+ public static void richInputConnection_setSelection(final int from, final int to) {
+ final Object[] values = {
+ from, to
+ };
+ getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_RICHINPUTCONNECTION_SETSELECTION,
+ values);
+ }
+
private static final String[] EVENTKEYS_SUDDENJUMPINGTOUCHEVENTHANDLER_ONTOUCHEVENT = {
"SuddenJumpingTouchEventHandlerOnTouchEvent", "motionEvent"
};
@@ -1277,4 +1265,24 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
public void userTimestamp() {
getInstance().enqueueEvent(EVENTKEYS_USER_TIMESTAMP, EVENTKEYS_NULLVALUES);
}
+
+ private static final String[] EVENTKEYS_STATISTICS = {
+ "Statistics", "charCount", "letterCount", "numberCount", "spaceCount", "deleteOpsCount",
+ "wordCount", "isEmptyUponStarting", "isEmptinessStateKnown", "averageTimeBetweenKeys",
+ "averageTimeBeforeDelete", "averageTimeDuringRepeatedDelete", "averageTimeAfterDelete"
+ };
+ private static void logStatistics() {
+ final ResearchLogger researchLogger = getInstance();
+ final Statistics statistics = researchLogger.mStatistics;
+ final Object[] values = {
+ statistics.mCharCount, statistics.mLetterCount, statistics.mNumberCount,
+ statistics.mSpaceCount, statistics.mDeleteKeyCount,
+ statistics.mWordCount, statistics.mIsEmptyUponStarting,
+ statistics.mIsEmptinessStateKnown, statistics.mKeyCounter.getAverageTime(),
+ statistics.mBeforeDeleteKeyCounter.getAverageTime(),
+ statistics.mDuringRepeatedDeleteKeysCounter.getAverageTime(),
+ statistics.mAfterDeleteKeyCounter.getAverageTime()
+ };
+ researchLogger.enqueueEvent(EVENTKEYS_STATISTICS, values);
+ }
}
diff --git a/java/src/com/android/inputmethod/research/Statistics.java b/java/src/com/android/inputmethod/research/Statistics.java
new file mode 100644
index 000000000..eab465aa2
--- /dev/null
+++ b/java/src/com/android/inputmethod/research/Statistics.java
@@ -0,0 +1,146 @@
+/*
+ * 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.research;
+
+import com.android.inputmethod.keyboard.Keyboard;
+
+public class Statistics {
+ // Number of characters entered during a typing session
+ int mCharCount;
+ // Number of letter characters entered during a typing session
+ int mLetterCount;
+ // Number of number characters entered
+ int mNumberCount;
+ // Number of space characters entered
+ int mSpaceCount;
+ // Number of delete operations entered (taps on the backspace key)
+ int mDeleteKeyCount;
+ // Number of words entered during a session.
+ int mWordCount;
+ // Whether the text field was empty upon editing
+ boolean mIsEmptyUponStarting;
+ boolean mIsEmptinessStateKnown;
+
+ // Timers to count average time to enter a key, first press a delete key,
+ // between delete keys, and then to return typing after a delete key.
+ final AverageTimeCounter mKeyCounter = new AverageTimeCounter();
+ final AverageTimeCounter mBeforeDeleteKeyCounter = new AverageTimeCounter();
+ final AverageTimeCounter mDuringRepeatedDeleteKeysCounter = new AverageTimeCounter();
+ final AverageTimeCounter mAfterDeleteKeyCounter = new AverageTimeCounter();
+
+ static class AverageTimeCounter {
+ int mCount;
+ int mTotalTime;
+
+ public void reset() {
+ mCount = 0;
+ mTotalTime = 0;
+ }
+
+ public void add(long deltaTime) {
+ mCount++;
+ mTotalTime += deltaTime;
+ }
+
+ public int getAverageTime() {
+ if (mCount == 0) {
+ return 0;
+ }
+ return mTotalTime / mCount;
+ }
+ }
+
+ // To account for the interruptions when the user's attention is directed elsewhere, times
+ // longer than MIN_TYPING_INTERMISSION are not counted when estimating this statistic.
+ public static final int MIN_TYPING_INTERMISSION = 2 * 1000; // in milliseconds
+ public static final int MIN_DELETION_INTERMISSION = 10 * 1000; // in milliseconds
+
+ // The last time that a tap was performed
+ private long mLastTapTime;
+ // The type of the last keypress (delete key or not)
+ boolean mIsLastKeyDeleteKey;
+
+ private static final Statistics sInstance = new Statistics();
+
+ public static Statistics getInstance() {
+ return sInstance;
+ }
+
+ private Statistics() {
+ reset();
+ }
+
+ public void reset() {
+ mCharCount = 0;
+ mLetterCount = 0;
+ mNumberCount = 0;
+ mSpaceCount = 0;
+ mDeleteKeyCount = 0;
+ mWordCount = 0;
+ mIsEmptyUponStarting = true;
+ mIsEmptinessStateKnown = false;
+ mKeyCounter.reset();
+ mBeforeDeleteKeyCounter.reset();
+ mDuringRepeatedDeleteKeysCounter.reset();
+ mAfterDeleteKeyCounter.reset();
+
+ mLastTapTime = 0;
+ mIsLastKeyDeleteKey = false;
+ }
+
+ public void recordChar(int codePoint, long time) {
+ final long delta = time - mLastTapTime;
+ if (codePoint == Keyboard.CODE_DELETE) {
+ mDeleteKeyCount++;
+ if (delta < MIN_DELETION_INTERMISSION) {
+ if (mIsLastKeyDeleteKey) {
+ mDuringRepeatedDeleteKeysCounter.add(delta);
+ } else {
+ mBeforeDeleteKeyCounter.add(delta);
+ }
+ }
+ mIsLastKeyDeleteKey = true;
+ } else {
+ mCharCount++;
+ if (Character.isDigit(codePoint)) {
+ mNumberCount++;
+ }
+ if (Character.isLetter(codePoint)) {
+ mLetterCount++;
+ }
+ if (Character.isSpaceChar(codePoint)) {
+ mSpaceCount++;
+ }
+ if (mIsLastKeyDeleteKey && delta < MIN_DELETION_INTERMISSION) {
+ mAfterDeleteKeyCounter.add(delta);
+ } else if (!mIsLastKeyDeleteKey && delta < MIN_TYPING_INTERMISSION) {
+ mKeyCounter.add(delta);
+ }
+ mIsLastKeyDeleteKey = false;
+ }
+ mLastTapTime = time;
+ }
+
+ public void recordWordEntered() {
+ mWordCount++;
+ }
+
+ public void setIsEmptyUponStarting(final boolean isEmpty) {
+ mIsEmptyUponStarting = isEmpty;
+ mIsEmptinessStateKnown = true;
+ }
+}
diff --git a/java/src/com/android/inputmethod/research/UploaderService.java b/java/src/com/android/inputmethod/research/UploaderService.java
new file mode 100644
index 000000000..7a5749096
--- /dev/null
+++ b/java/src/com/android/inputmethod/research/UploaderService.java
@@ -0,0 +1,191 @@
+/*
+ * 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.research;
+
+import android.Manifest;
+import android.app.AlarmManager;
+import android.app.IntentService;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.BatteryManager;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.inputmethod.latin.R;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+public final class UploaderService extends IntentService {
+ private static final String TAG = UploaderService.class.getSimpleName();
+ public static final long RUN_INTERVAL = AlarmManager.INTERVAL_HOUR;
+ private static final String EXTRA_UPLOAD_UNCONDITIONALLY = UploaderService.class.getName()
+ + ".extra.UPLOAD_UNCONDITIONALLY";
+ private static final int BUF_SIZE = 1024 * 8;
+ protected static final int TIMEOUT_IN_MS = 1000 * 4;
+
+ private boolean mCanUpload;
+ private File mFilesDir;
+ private URL mUrl;
+
+ public UploaderService() {
+ super("Research Uploader Service");
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ mCanUpload = false;
+ mFilesDir = null;
+ mUrl = null;
+
+ final PackageManager packageManager = getPackageManager();
+ final boolean hasPermission = packageManager.checkPermission(Manifest.permission.INTERNET,
+ getPackageName()) == PackageManager.PERMISSION_GRANTED;
+ if (!hasPermission) {
+ return;
+ }
+
+ try {
+ final String urlString = getString(R.string.research_logger_upload_url);
+ if (urlString == null || urlString.equals("")) {
+ return;
+ }
+ mFilesDir = getFilesDir();
+ mUrl = new URL(urlString);
+ mCanUpload = true;
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ if (!mCanUpload) {
+ return;
+ }
+ boolean isUploadingUnconditionally = false;
+ Bundle bundle = intent.getExtras();
+ if (bundle != null && bundle.containsKey(EXTRA_UPLOAD_UNCONDITIONALLY)) {
+ isUploadingUnconditionally = bundle.getBoolean(EXTRA_UPLOAD_UNCONDITIONALLY);
+ }
+ doUpload(isUploadingUnconditionally);
+ }
+
+ private boolean isExternallyPowered() {
+ final Intent intent = registerReceiver(null, new IntentFilter(
+ Intent.ACTION_BATTERY_CHANGED));
+ final int pluggedState = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
+ return pluggedState == BatteryManager.BATTERY_PLUGGED_AC
+ || pluggedState == BatteryManager.BATTERY_PLUGGED_USB;
+ }
+
+ private boolean hasWifiConnection() {
+ final ConnectivityManager manager =
+ (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
+ final NetworkInfo wifiInfo = manager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+ return wifiInfo.isConnected();
+ }
+
+ private void doUpload(final boolean isUploadingUnconditionally) {
+ if (!isUploadingUnconditionally && (!isExternallyPowered() || !hasWifiConnection())) {
+ return;
+ }
+ if (mFilesDir == null) {
+ return;
+ }
+ final File[] files = mFilesDir.listFiles(new FileFilter() {
+ @Override
+ public boolean accept(File pathname) {
+ return pathname.getName().startsWith(ResearchLogger.FILENAME_PREFIX)
+ && !pathname.canWrite();
+ }
+ });
+ boolean success = true;
+ if (files.length == 0) {
+ success = false;
+ }
+ for (final File file : files) {
+ if (!uploadFile(file)) {
+ success = false;
+ }
+ }
+ }
+
+ private boolean uploadFile(File file) {
+ Log.d(TAG, "attempting upload of " + file.getAbsolutePath());
+ boolean success = false;
+ final int contentLength = (int) file.length();
+ HttpURLConnection connection = null;
+ InputStream fileInputStream = null;
+ try {
+ fileInputStream = new FileInputStream(file);
+ connection = (HttpURLConnection) mUrl.openConnection();
+ connection.setRequestMethod("PUT");
+ connection.setDoOutput(true);
+ connection.setFixedLengthStreamingMode(contentLength);
+ final OutputStream os = connection.getOutputStream();
+ final byte[] buf = new byte[BUF_SIZE];
+ int numBytesRead;
+ while ((numBytesRead = fileInputStream.read(buf)) != -1) {
+ os.write(buf, 0, numBytesRead);
+ }
+ if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
+ Log.d(TAG, "upload failed: " + connection.getResponseCode());
+ InputStream netInputStream = connection.getInputStream();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(netInputStream));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ Log.d(TAG, "| " + reader.readLine());
+ }
+ reader.close();
+ return success;
+ }
+ file.delete();
+ success = true;
+ Log.d(TAG, "upload successful");
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ if (fileInputStream != null) {
+ try {
+ fileInputStream.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ if (connection != null) {
+ connection.disconnect();
+ }
+ }
+ return success;
+ }
+}