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/AccessibleKeyboardViewProxy.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/EmojiCategoryPageIndicatorView.java8
-rw-r--r--java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java223
-rw-r--r--java/src/com/android/inputmethod/keyboard/Key.java24
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyDetector.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardId.java34
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java42
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java4
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardView.java58
-rw-r--r--java/src/com/android/inputmethod/keyboard/MainKeyboardView.java714
-rw-r--r--java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java6
-rw-r--r--java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java10
-rw-r--r--java/src/com/android/inputmethod/keyboard/PointerTracker.java382
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/CustomViewPager.java47
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/DrawingHandler.java77
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java58
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/EmojiLayoutParams.java (renamed from java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java)30
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/EmojiPageKeyboardView.java (renamed from java/src/com/android/inputmethod/keyboard/internal/ScrollKeyboardView.java)66
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java14
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java13
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java4
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java6
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java25
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java1
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java1306
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java27
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java6
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/ScrollViewWithNotifier.java66
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java206
-rw-r--r--java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java16
-rw-r--r--java/src/com/android/inputmethod/latin/AssetFileAddress.java10
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java139
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java26
-rw-r--r--java/src/com/android/inputmethod/latin/Constants.java29
-rw-r--r--java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java14
-rw-r--r--java/src/com/android/inputmethod/latin/Dictionary.java7
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFactory.java50
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryWriter.java22
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java461
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableDictionary.java894
-rw-r--r--java/src/com/android/inputmethod/latin/InputAttributes.java6
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java289
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputConnection.java213
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeSwitcher.java63
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java11
-rw-r--r--java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java5
-rw-r--r--java/src/com/android/inputmethod/latin/UserBinaryDictionary.java12
-rw-r--r--java/src/com/android/inputmethod/latin/WordComposer.java39
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/AbstractDictDecoder.java39
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java96
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java32
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java76
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/DictDecoder.java11
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java2
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/FormatSpec.java40
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/SparseTableContentReader.java120
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/SparseTableContentUpdater.java123
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/SparseTableContentWriter.java93
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java343
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java101
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Ver4DictUpdater.java726
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java167
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java190
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java50
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegister.java8
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java84
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java70
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java37
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java23
-rw-r--r--java/src/com/android/inputmethod/latin/settings/DebugSettings.java3
-rw-r--r--java/src/com/android/inputmethod/latin/settings/Settings.java30
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsFragment.java119
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsValues.java33
-rw-r--r--java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java10
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java2
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java1
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java5
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java2
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java11
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java79
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java18
-rw-r--r--java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java12
-rw-r--r--java/src/com/android/inputmethod/latin/utils/FileUtils.java33
-rw-r--r--java/src/com/android/inputmethod/latin/utils/JsonUtils.java103
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java (renamed from java/src/com/android/inputmethod/latin/utils/StaticInnerHandlerWrapper.java)20
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LocaleUtils.java47
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ResourceUtils.java8
-rw-r--r--java/src/com/android/inputmethod/latin/utils/StringUtils.java100
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java4
-rw-r--r--java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java36
-rw-r--r--java/src/com/android/inputmethod/latin/utils/UnigramProperty.java82
-rw-r--r--java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java11
-rw-r--r--java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java9
-rw-r--r--java/src/com/android/inputmethod/research/ResearchLogger.java69
97 files changed, 4863 insertions, 4188 deletions
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
index 73896dfd3..c601cf17e 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
@@ -82,7 +82,7 @@ public final class AccessibleKeyboardViewProxy extends AccessibilityDelegateComp
private void initInternal(final InputMethodService inputMethod) {
mInputMethod = inputMethod;
mEdgeSlop = inputMethod.getResources().getDimensionPixelSize(
- R.dimen.accessibility_edge_slop);
+ R.dimen.config_accessibility_edge_slop);
}
/**
diff --git a/java/src/com/android/inputmethod/keyboard/EmojiCategoryPageIndicatorView.java b/java/src/com/android/inputmethod/keyboard/EmojiCategoryPageIndicatorView.java
index e23131a30..d56a3cf25 100644
--- a/java/src/com/android/inputmethod/keyboard/EmojiCategoryPageIndicatorView.java
+++ b/java/src/com/android/inputmethod/keyboard/EmojiCategoryPageIndicatorView.java
@@ -31,17 +31,17 @@ public class EmojiCategoryPageIndicatorView extends LinearLayout {
private int mCurrentCategoryPageId = 0;
private float mOffset = 0.0f;
- public EmojiCategoryPageIndicatorView(Context context) {
+ public EmojiCategoryPageIndicatorView(final Context context) {
this(context, null /* attrs */);
}
- public EmojiCategoryPageIndicatorView(Context context, AttributeSet attrs) {
+ public EmojiCategoryPageIndicatorView(final Context context, final AttributeSet attrs) {
super(context, attrs);
mPaint.setColor(context.getResources().getColor(
R.color.emoji_category_page_id_view_foreground));
}
- public void setCategoryPageId(int size, int id, float offset) {
+ public void setCategoryPageId(final int size, final int id, final float offset) {
mCategoryPageSize = size;
mCurrentCategoryPageId = id;
mOffset = offset;
@@ -49,7 +49,7 @@ public class EmojiCategoryPageIndicatorView extends LinearLayout {
}
@Override
- protected void onDraw(Canvas canvas) {
+ protected void onDraw(final Canvas canvas) {
if (mCategoryPageSize <= 1) {
// If the category is not set yet or contains only one category,
// just clear and return.
diff --git a/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java b/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java
index f12373503..ff0d53865 100644
--- a/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java
+++ b/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java
@@ -44,8 +44,8 @@ import android.widget.TabHost.OnTabChangeListener;
import android.widget.TextView;
import com.android.inputmethod.keyboard.internal.DynamicGridKeyboard;
-import com.android.inputmethod.keyboard.internal.ScrollKeyboardView;
-import com.android.inputmethod.keyboard.internal.ScrollViewWithNotifier;
+import com.android.inputmethod.keyboard.internal.EmojiLayoutParams;
+import com.android.inputmethod.keyboard.internal.EmojiPageKeyboardView;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SubtypeSwitcher;
@@ -72,15 +72,15 @@ import java.util.concurrent.ConcurrentHashMap;
*/
public final class EmojiPalettesView extends LinearLayout implements OnTabChangeListener,
ViewPager.OnPageChangeListener, View.OnClickListener,
- ScrollKeyboardView.OnKeyClickListener {
- private static final String TAG = EmojiPalettesView.class.getSimpleName();
+ EmojiPageKeyboardView.OnKeyClickListener {
+ static final String TAG = EmojiPalettesView.class.getSimpleName();
private static final boolean DEBUG_PAGER = false;
private final int mKeyBackgroundId;
private final int mEmojiFunctionalKeyBackgroundId;
- private final KeyboardLayoutSet mLayoutSet;
private final ColorStateList mTabLabelColor;
private final DeleteKeyOnTouchListener mDeleteKeyOnTouchListener;
private EmojiPalettesAdapter mEmojiPalettesAdapter;
+ private final EmojiLayoutParams mEmojiLayoutParams;
private TabHost mTabHost;
private ViewPager mEmojiPager;
@@ -149,7 +149,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
public EmojiCategory(final SharedPreferences prefs, final Resources res,
final KeyboardLayoutSet layoutSet) {
mPrefs = prefs;
- mMaxPageKeyCount = res.getInteger(R.integer.emoji_keyboard_max_key_count);
+ mMaxPageKeyCount = res.getInteger(R.integer.config_emoji_keyboard_max_page_key_count);
mLayoutSet = layoutSet;
for (int i = 0; i < sCategoryName.length; ++i) {
mCategoryNameToIdMap.put(sCategoryName[i], i);
@@ -174,7 +174,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
.loadRecentKeys(mCategoryKeyboardMap.values());
}
- private void addShownCategoryId(int categoryId) {
+ private void addShownCategoryId(final int categoryId) {
// Load a keyboard of categoryId
getKeyboard(categoryId, 0 /* cagetoryPageId */);
final CategoryProperties properties =
@@ -182,20 +182,20 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
mShownCategories.add(properties);
}
- public String getCategoryName(int categoryId, int categoryPageId) {
+ public String getCategoryName(final int categoryId, final int categoryPageId) {
return sCategoryName[categoryId] + "-" + categoryPageId;
}
- public int getCategoryId(String name) {
+ public int getCategoryId(final String name) {
final String[] strings = name.split("-");
return mCategoryNameToIdMap.get(strings[0]);
}
- public int getCategoryIcon(int categoryId) {
+ public int getCategoryIcon(final int categoryId) {
return sCategoryIcon[categoryId];
}
- public String getCategoryLabel(int categoryId) {
+ public String getCategoryLabel(final int categoryId) {
return sCategoryLabel[categoryId];
}
@@ -211,7 +211,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
return getCategoryPageSize(mCurrentCategoryId);
}
- public int getCategoryPageSize(int categoryId) {
+ public int getCategoryPageSize(final int categoryId) {
for (final CategoryProperties prop : mShownCategories) {
if (prop.mCategoryId == categoryId) {
return prop.mPageCount;
@@ -222,12 +222,12 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
return 0;
}
- public void setCurrentCategoryId(int categoryId) {
+ public void setCurrentCategoryId(final int categoryId) {
mCurrentCategoryId = categoryId;
Settings.writeLastShownEmojiCategoryId(mPrefs, categoryId);
}
- public void setCurrentCategoryPageId(int id) {
+ public void setCurrentCategoryPageId(final int id) {
mCurrentCategoryPageId = id;
}
@@ -244,7 +244,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
return mCurrentCategoryId == CATEGORY_ID_RECENTS;
}
- public int getTabIdFromCategoryId(int categoryId) {
+ public int getTabIdFromCategoryId(final int categoryId) {
for (int i = 0; i < mShownCategories.size(); ++i) {
if (mShownCategories.get(i).mCategoryId == categoryId) {
return i;
@@ -255,7 +255,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
}
// Returns the view pager's page position for the categoryId
- public int getPageIdFromCategoryId(int categoryId) {
+ public int getPageIdFromCategoryId(final int categoryId) {
final int lastSavedCategoryPageId =
Settings.readLastTypedEmojiCategoryPageId(mPrefs, categoryId);
int sum = 0;
@@ -274,7 +274,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
return getTabIdFromCategoryId(CATEGORY_ID_RECENTS);
}
- private int getCategoryPageCount(int categoryId) {
+ private int getCategoryPageCount(final int categoryId) {
final Keyboard keyboard = mLayoutSet.getKeyboard(sCategoryElementId[categoryId]);
return (keyboard.getKeys().length - 1) / mMaxPageKeyCount + 1;
}
@@ -283,9 +283,9 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
// position. The category page id is numbered in each category. And the view page position
// is the position of the current shown page in the view pager which contains all pages of
// all categories.
- public Pair<Integer, Integer> getCategoryIdAndPageIdFromPagePosition(int position) {
+ public Pair<Integer, Integer> getCategoryIdAndPageIdFromPagePosition(final int position) {
int sum = 0;
- for (CategoryProperties properties : mShownCategories) {
+ for (final CategoryProperties properties : mShownCategories) {
final int temp = sum;
sum += properties.mPageCount;
if (sum > position) {
@@ -296,7 +296,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
}
// Returns a keyboard from the view pager's page position.
- public DynamicGridKeyboard getKeyboardFromPagePosition(int position) {
+ public DynamicGridKeyboard getKeyboardFromPagePosition(final int position) {
final Pair<Integer, Integer> categoryAndId =
getCategoryIdAndPageIdFromPagePosition(position);
if (categoryAndId != null) {
@@ -305,39 +305,41 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
return null;
}
- public DynamicGridKeyboard getKeyboard(int categoryId, int id) {
- synchronized(mCategoryKeyboardMap) {
- final long key = (((long) categoryId) << Constants.MAX_INT_BIT_COUNT) | id;
- final DynamicGridKeyboard kbd;
- if (!mCategoryKeyboardMap.containsKey(key)) {
- if (categoryId != CATEGORY_ID_RECENTS) {
- final Keyboard keyboard =
- mLayoutSet.getKeyboard(sCategoryElementId[categoryId]);
- final Key[][] sortedKeys = sortKeys(keyboard.getKeys(), mMaxPageKeyCount);
- for (int i = 0; i < sortedKeys.length; ++i) {
- final DynamicGridKeyboard tempKbd = new DynamicGridKeyboard(mPrefs,
- mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
- mMaxPageKeyCount, categoryId, i /* categoryPageId */);
- for (Key emojiKey : sortedKeys[i]) {
- if (emojiKey == null) {
- break;
- }
- tempKbd.addKeyLast(emojiKey);
- }
- mCategoryKeyboardMap.put((((long) categoryId)
- << Constants.MAX_INT_BIT_COUNT) | i, tempKbd);
+ private static final Long getCategoryKeyboardMapKey(final int categoryId, final int id) {
+ return (((long) categoryId) << Constants.MAX_INT_BIT_COUNT) | id;
+ }
+
+ public DynamicGridKeyboard getKeyboard(final int categoryId, final int id) {
+ synchronized (mCategoryKeyboardMap) {
+ final Long categotyKeyboardMapKey = getCategoryKeyboardMapKey(categoryId, id);
+ if (mCategoryKeyboardMap.containsKey(categotyKeyboardMapKey)) {
+ return mCategoryKeyboardMap.get(categotyKeyboardMapKey);
+ }
+
+ if (categoryId == CATEGORY_ID_RECENTS) {
+ final DynamicGridKeyboard kbd = new DynamicGridKeyboard(mPrefs,
+ mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
+ mMaxPageKeyCount, categoryId);
+ mCategoryKeyboardMap.put(categotyKeyboardMapKey, kbd);
+ return kbd;
+ }
+
+ final Keyboard keyboard = mLayoutSet.getKeyboard(sCategoryElementId[categoryId]);
+ final Key[][] sortedKeys = sortKeysIntoPages(keyboard.getKeys(), mMaxPageKeyCount);
+ for (int pageId = 0; pageId < sortedKeys.length; ++pageId) {
+ final DynamicGridKeyboard tempKeyboard = new DynamicGridKeyboard(mPrefs,
+ mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
+ mMaxPageKeyCount, categoryId);
+ for (final Key emojiKey : sortedKeys[pageId]) {
+ if (emojiKey == null) {
+ break;
}
- kbd = mCategoryKeyboardMap.get(key);
- } else {
- kbd = new DynamicGridKeyboard(mPrefs,
- mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
- mMaxPageKeyCount, categoryId, 0 /* categoryPageId */);
- mCategoryKeyboardMap.put(key, kbd);
+ tempKeyboard.addKeyLast(emojiKey);
}
- } else {
- kbd = mCategoryKeyboardMap.get(key);
+ mCategoryKeyboardMap.put(
+ getCategoryKeyboardMapKey(categoryId, pageId), tempKeyboard);
}
- return kbd;
+ return mCategoryKeyboardMap.get(categotyKeyboardMapKey);
}
}
@@ -349,29 +351,31 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
return sum;
}
- private Key[][] sortKeys(Key[] inKeys, int maxPageCount) {
- Key[] keys = Arrays.copyOf(inKeys, inKeys.length);
- Arrays.sort(keys, 0, keys.length, new Comparator<Key>() {
- @Override
- public int compare(Key lhs, Key rhs) {
- final Rect lHitBox = lhs.getHitBox();
- final Rect rHitBox = rhs.getHitBox();
- if (lHitBox.top < rHitBox.top) {
- return -1;
- } else if (lHitBox.top > rHitBox.top) {
- return 1;
- }
- if (lHitBox.left < rHitBox.left) {
- return -1;
- } else if (lHitBox.left > rHitBox.left) {
- return 1;
- }
- if (lhs.getCode() == rhs.getCode()) {
- return 0;
- }
- return lhs.getCode() < rhs.getCode() ? -1 : 1;
+ private static Comparator<Key> EMOJI_KEY_COMPARATOR = new Comparator<Key>() {
+ @Override
+ public int compare(final Key lhs, final Key rhs) {
+ final Rect lHitBox = lhs.getHitBox();
+ final Rect rHitBox = rhs.getHitBox();
+ if (lHitBox.top < rHitBox.top) {
+ return -1;
+ } else if (lHitBox.top > rHitBox.top) {
+ return 1;
+ }
+ if (lHitBox.left < rHitBox.left) {
+ return -1;
+ } else if (lHitBox.left > rHitBox.left) {
+ return 1;
+ }
+ if (lhs.getCode() == rhs.getCode()) {
+ return 0;
}
- });
+ return lhs.getCode() < rhs.getCode() ? -1 : 1;
+ }
+ };
+
+ private static Key[][] sortKeysIntoPages(final Key[] inKeys, final int maxPageCount) {
+ final Key[] keys = Arrays.copyOf(inKeys, inKeys.length);
+ Arrays.sort(keys, 0, keys.length, EMOJI_KEY_COMPARATOR);
final int pageCount = (keys.length - 1) / maxPageCount + 1;
final Key[][] retval = new Key[pageCount][maxPageCount];
for (int i = 0; i < keys.length; ++i) {
@@ -404,12 +408,12 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(
context, null /* editorInfo */);
final Resources res = context.getResources();
- final EmojiLayoutParams emojiLp = new EmojiLayoutParams(res);
+ mEmojiLayoutParams = new EmojiLayoutParams(res);
builder.setSubtype(SubtypeSwitcher.getInstance().getEmojiSubtype());
builder.setKeyboardGeometry(ResourceUtils.getDefaultKeyboardWidth(res),
- emojiLp.mEmojiKeyboardHeight);
- builder.setOptions(false, false, false /* lanuageSwitchKeyEnabled */);
- mLayoutSet = builder.build();
+ mEmojiLayoutParams.mEmojiKeyboardHeight);
+ builder.setOptions(false /* shortcutImeEnabled */, false /* showsVoiceInputKey */,
+ false /* languageSwitchKeyEnabled */);
mEmojiCategory = new EmojiCategory(PreferenceManager.getDefaultSharedPreferences(context),
context.getResources(), builder.build());
mDeleteKeyOnTouchListener = new DeleteKeyOnTouchListener(context);
@@ -423,7 +427,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
final int width = ResourceUtils.getDefaultKeyboardWidth(res)
+ getPaddingLeft() + getPaddingRight();
final int height = ResourceUtils.getDefaultKeyboardHeight(res)
- + res.getDimensionPixelSize(R.dimen.suggestions_strip_height)
+ + res.getDimensionPixelSize(R.dimen.config_suggestions_strip_height)
+ getPaddingTop() + getPaddingBottom();
setMeasuredDimension(width, height);
}
@@ -458,25 +462,23 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
mTabHost.setOnTabChangedListener(this);
mTabHost.getTabWidget().setStripEnabled(true);
- mEmojiPalettesAdapter = new EmojiPalettesAdapter(mEmojiCategory, mLayoutSet, this);
+ mEmojiPalettesAdapter = new EmojiPalettesAdapter(mEmojiCategory, this);
mEmojiPager = (ViewPager)findViewById(R.id.emoji_keyboard_pager);
mEmojiPager.setAdapter(mEmojiPalettesAdapter);
mEmojiPager.setOnPageChangeListener(this);
mEmojiPager.setOffscreenPageLimit(0);
- mEmojiPager.setPersistentDrawingCache(ViewPager.PERSISTENT_NO_CACHE);
- final Resources res = getResources();
- final EmojiLayoutParams emojiLp = new EmojiLayoutParams(res);
- emojiLp.setPagerProperties(mEmojiPager);
+ mEmojiPager.setPersistentDrawingCache(PERSISTENT_NO_CACHE);
+ mEmojiLayoutParams.setPagerProperties(mEmojiPager);
mEmojiCategoryPageIndicatorView =
(EmojiCategoryPageIndicatorView)findViewById(R.id.emoji_category_page_id_view);
- emojiLp.setCategoryPageIdViewProperties(mEmojiCategoryPageIndicatorView);
+ mEmojiLayoutParams.setCategoryPageIdViewProperties(mEmojiCategoryPageIndicatorView);
setCurrentCategoryId(mEmojiCategory.getCurrentCategoryId(), true /* force */);
final LinearLayout actionBar = (LinearLayout)findViewById(R.id.emoji_action_bar);
- emojiLp.setActionBarProperties(actionBar);
+ mEmojiLayoutParams.setActionBarProperties(actionBar);
final ImageView deleteKey = (ImageView)findViewById(R.id.emoji_keyboard_delete);
deleteKey.setTag(Constants.CODE_DELETE);
@@ -489,7 +491,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
spaceKey.setBackgroundResource(mKeyBackgroundId);
spaceKey.setTag(Constants.CODE_SPACE);
spaceKey.setOnClickListener(this);
- emojiLp.setKeyProperties(spaceKey);
+ mEmojiLayoutParams.setKeyProperties(spaceKey);
final ImageView alphabetKey2 = (ImageView)findViewById(R.id.emoji_keyboard_alphabet2);
alphabetKey2.setBackgroundResource(mEmojiFunctionalKeyBackgroundId);
alphabetKey2.setTag(Constants.CODE_SWITCH_ALPHA_SYMBOL);
@@ -628,16 +630,15 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
}
private static class EmojiPalettesAdapter extends PagerAdapter {
- private final ScrollKeyboardView.OnKeyClickListener mListener;
+ private final EmojiPageKeyboardView.OnKeyClickListener mListener;
private final DynamicGridKeyboard mRecentsKeyboard;
- private final SparseArray<ScrollKeyboardView> mActiveKeyboardViews =
+ private final SparseArray<EmojiPageKeyboardView> mActiveKeyboardViews =
CollectionUtils.newSparseArray();
private final EmojiCategory mEmojiCategory;
private int mActivePosition = 0;
public EmojiPalettesAdapter(final EmojiCategory emojiCategory,
- final KeyboardLayoutSet layoutSet,
- final ScrollKeyboardView.OnKeyClickListener listener) {
+ final EmojiPageKeyboardView.OnKeyClickListener listener) {
mEmojiCategory = emojiCategory;
mListener = listener;
mRecentsKeyboard = mEmojiCategory.getKeyboard(CATEGORY_ID_RECENTS, 0);
@@ -671,11 +672,12 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
}
@Override
- public void setPrimaryItem(final View container, final int position, final Object object) {
+ public void setPrimaryItem(final ViewGroup container, final int position,
+ final Object object) {
if (mActivePosition == position) {
return;
}
- final ScrollKeyboardView oldKeyboardView = mActiveKeyboardViews.get(mActivePosition);
+ final EmojiPageKeyboardView oldKeyboardView = mActiveKeyboardViews.get(mActivePosition);
if (oldKeyboardView != null) {
oldKeyboardView.releaseCurrentKey();
oldKeyboardView.deallocateMemory();
@@ -688,7 +690,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
if (DEBUG_PAGER) {
Log.d(TAG, "instantiate item: " + position);
}
- final ScrollKeyboardView oldKeyboardView = mActiveKeyboardViews.get(position);
+ final EmojiPageKeyboardView oldKeyboardView = mActiveKeyboardViews.get(position);
if (oldKeyboardView != null) {
oldKeyboardView.deallocateMemory();
// This may be redundant but wanted to be safer..
@@ -697,18 +699,13 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
final Keyboard keyboard =
mEmojiCategory.getKeyboardFromPagePosition(position);
final LayoutInflater inflater = LayoutInflater.from(container.getContext());
- final View view = inflater.inflate(
+ final EmojiPageKeyboardView keyboardView = (EmojiPageKeyboardView)inflater.inflate(
R.layout.emoji_keyboard_page, container, false /* attachToRoot */);
- final ScrollKeyboardView keyboardView = (ScrollKeyboardView)view.findViewById(
- R.id.emoji_keyboard_page);
keyboardView.setKeyboard(keyboard);
keyboardView.setOnKeyClickListener(mListener);
- final ScrollViewWithNotifier scrollView = (ScrollViewWithNotifier)view.findViewById(
- R.id.emoji_keyboard_scroller);
- keyboardView.setScrollView(scrollView);
- container.addView(view);
+ container.addView(keyboardView);
mActiveKeyboardViews.put(position, keyboardView);
- return view;
+ return keyboardView;
}
@Override
@@ -722,7 +719,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
if (DEBUG_PAGER) {
Log.d(TAG, "destroy item: " + position + ", " + object.getClass().getSimpleName());
}
- final ScrollKeyboardView keyboardView = mActiveKeyboardViews.get(position);
+ final EmojiPageKeyboardView keyboardView = mActiveKeyboardViews.get(position);
if (keyboardView != null) {
keyboardView.deallocateMemory();
mActiveKeyboardViews.remove(position);
@@ -795,7 +792,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
}
}
- public void pressDelete(int repeatCount) {
+ public void pressDelete(final int repeatCount) {
mKeyboardActionListener.onPressKey(
Constants.CODE_DELETE, repeatCount, true /* isSinglePointer */);
mKeyboardActionListener.onCodeInput(
@@ -804,22 +801,22 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
Constants.CODE_DELETE, false /* withSliding */);
}
- public void setKeyboardActionListener(KeyboardActionListener listener) {
+ public void setKeyboardActionListener(final KeyboardActionListener listener) {
mKeyboardActionListener = listener;
}
@Override
- public boolean onTouch(View v, MotionEvent event) {
+ public boolean onTouch(final View v, final MotionEvent event) {
switch(event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- v.setBackgroundColor(mDeleteKeyPressedBackgroundColor);
- pressDelete(0 /* repeatCount */);
- startRepeat();
- return true;
- case MotionEvent.ACTION_UP:
- v.setBackgroundColor(0);
- abortRepeat();
- return true;
+ case MotionEvent.ACTION_DOWN:
+ v.setBackgroundColor(mDeleteKeyPressedBackgroundColor);
+ pressDelete(0 /* repeatCount */);
+ startRepeat();
+ return true;
+ case MotionEvent.ACTION_UP:
+ v.setBackgroundColor(0);
+ abortRepeat();
+ return true;
}
return false;
}
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index f7ec9509d..b975b9c70 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -28,7 +28,6 @@ import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
-import android.util.Log;
import android.util.Xml;
import com.android.inputmethod.keyboard.internal.KeyDrawParams;
@@ -53,8 +52,6 @@ import java.util.Locale;
* Class for describing the position and characteristics of a single key in the keyboard.
*/
public class Key implements Comparable<Key> {
- private static final String TAG = Key.class.getSimpleName();
-
/**
* The key code (unicode or custom code) that this key generates.
*/
@@ -84,10 +81,16 @@ public class Key implements Comparable<Key> {
private static final int LABEL_FLAGS_HAS_HINT_LABEL = 0x800;
private static final int LABEL_FLAGS_WITH_ICON_LEFT = 0x1000;
private static final int LABEL_FLAGS_WITH_ICON_RIGHT = 0x2000;
+ // The bit to calculate the ratio of key label width against key width. If autoXScale bit is on
+ // and autoYScale bit is off, the key label may be shrunk only for X-direction.
+ // If both autoXScale and autoYScale bits are on, the key label text size may be auto scaled.
private static final int LABEL_FLAGS_AUTO_X_SCALE = 0x4000;
- private static final int LABEL_FLAGS_PRESERVE_CASE = 0x8000;
- private static final int LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED = 0x10000;
- private static final int LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL = 0x20000;
+ private static final int LABEL_FLAGS_AUTO_Y_SCALE = 0x8000;
+ private static final int LABEL_FLAGS_AUTO_SCALE = LABEL_FLAGS_AUTO_X_SCALE
+ | LABEL_FLAGS_AUTO_Y_SCALE;
+ private static final int LABEL_FLAGS_PRESERVE_CASE = 0x10000;
+ private static final int LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED = 0x20000;
+ private static final int LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL = 0x40000;
private static final int LABEL_FLAGS_DISABLE_HINT_LABEL = 0x40000000;
private static final int LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS = 0x80000000;
@@ -376,9 +379,6 @@ public class Key implements Comparable<Key> {
mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr);
keyAttr.recycle();
mHashCode = computeHashCode(this);
- if (hasShiftedLetterHint() && TextUtils.isEmpty(mHintLabel)) {
- Log.w(TAG, "hasShiftedLetterHint specified without keyHintLabel: " + this);
- }
}
/**
@@ -702,10 +702,14 @@ public class Key implements Comparable<Key> {
return (mLabelFlags & LABEL_FLAGS_WITH_ICON_RIGHT) != 0;
}
- public final boolean needsXScale() {
+ public final boolean needsAutoXScale() {
return (mLabelFlags & LABEL_FLAGS_AUTO_X_SCALE) != 0;
}
+ public final boolean needsAutoScale() {
+ return (mLabelFlags & LABEL_FLAGS_AUTO_SCALE) == LABEL_FLAGS_AUTO_SCALE;
+ }
+
public final boolean isShiftedLetterActivated() {
return (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) != 0;
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
index befb6fa92..149f10fd7 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
@@ -81,7 +81,7 @@ public class KeyDetector {
return mKeyboard;
}
- public boolean alwaysAllowsSlidingInput() {
+ public boolean alwaysAllowsKeySelectionByDraggingFinger() {
return false;
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index 736f13ed6..02beb3f11 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -70,8 +70,7 @@ public final class KeyboardId {
public final int mElementId;
private final EditorInfo mEditorInfo;
public final boolean mClobberSettingsKey;
- public final boolean mShortcutKeyEnabled;
- public final boolean mShortcutKeyOnSymbols;
+ public final boolean mSupportsSwitchingToShortcutIme;
public final boolean mLanguageSwitchKeyEnabled;
public final String mCustomActionLabel;
public final boolean mHasShortcutKey;
@@ -87,17 +86,11 @@ public final class KeyboardId {
mElementId = elementId;
mEditorInfo = params.mEditorInfo;
mClobberSettingsKey = params.mNoSettingsKey;
- mShortcutKeyEnabled = params.mVoiceKeyEnabled;
- mShortcutKeyOnSymbols = mShortcutKeyEnabled && !params.mVoiceKeyOnMain;
+ mSupportsSwitchingToShortcutIme = params.mSupportsSwitchingToShortcutIme;
mLanguageSwitchKeyEnabled = params.mLanguageSwitchKeyEnabled;
mCustomActionLabel = (mEditorInfo.actionLabel != null)
? mEditorInfo.actionLabel.toString() : null;
- final boolean alphabetMayHaveShortcutKey = isAlphabetKeyboard(elementId)
- && !mShortcutKeyOnSymbols;
- final boolean symbolsMayHaveShortcutKey = (elementId == KeyboardId.ELEMENT_SYMBOLS)
- && mShortcutKeyOnSymbols;
- mHasShortcutKey = mShortcutKeyEnabled
- && (alphabetMayHaveShortcutKey || symbolsMayHaveShortcutKey);
+ mHasShortcutKey = mSupportsSwitchingToShortcutIme && params.mShowsVoiceInputKey;
mHashCode = computeHashCode(this);
}
@@ -110,8 +103,8 @@ public final class KeyboardId {
id.mHeight,
id.passwordInput(),
id.mClobberSettingsKey,
- id.mShortcutKeyEnabled,
- id.mShortcutKeyOnSymbols,
+ id.mSupportsSwitchingToShortcutIme,
+ id.mHasShortcutKey,
id.mLanguageSwitchKeyEnabled,
id.isMultiLine(),
id.imeAction(),
@@ -131,8 +124,8 @@ public final class KeyboardId {
&& other.mHeight == mHeight
&& other.passwordInput() == passwordInput()
&& other.mClobberSettingsKey == mClobberSettingsKey
- && other.mShortcutKeyEnabled == mShortcutKeyEnabled
- && other.mShortcutKeyOnSymbols == mShortcutKeyOnSymbols
+ && other.mSupportsSwitchingToShortcutIme == mSupportsSwitchingToShortcutIme
+ && other.mHasShortcutKey == mHasShortcutKey
&& other.mLanguageSwitchKeyEnabled == mLanguageSwitchKeyEnabled
&& other.isMultiLine() == isMultiLine()
&& other.imeAction() == imeAction()
@@ -186,21 +179,20 @@ public final class KeyboardId {
@Override
public String toString() {
- return String.format(Locale.ROOT, "[%s %s:%s %dx%d %s %s %s%s%s%s%s%s%s%s%s]",
+ return String.format(Locale.ROOT, "[%s %s:%s %dx%d %s %s%s%s%s%s%s%s%s%s]",
elementIdToName(mElementId),
mLocale, mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET),
mWidth, mHeight,
modeName(mMode),
- imeAction(),
- (navigateNext() ? "navigateNext" : ""),
- (navigatePrevious() ? "navigatePrevious" : ""),
+ actionName(imeAction()),
+ (navigateNext() ? " navigateNext" : ""),
+ (navigatePrevious() ? " navigatePrevious" : ""),
(mClobberSettingsKey ? " clobberSettingsKey" : ""),
(passwordInput() ? " passwordInput" : ""),
- (mShortcutKeyEnabled ? " shortcutKeyEnabled" : ""),
- (mShortcutKeyOnSymbols ? " shortcutKeyOnSymbols" : ""),
+ (mSupportsSwitchingToShortcutIme ? " supportsSwitchingToShortcutIme" : ""),
(mHasShortcutKey ? " hasShortcutKey" : ""),
(mLanguageSwitchKeyEnabled ? " languageSwitchKeyEnabled" : ""),
- (isMultiLine() ? "isMultiLine" : "")
+ (isMultiLine() ? " isMultiLine" : "")
);
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
index 1eccdf341..e5b814faf 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
@@ -105,10 +105,10 @@ public final class KeyboardLayoutSet {
int mMode;
EditorInfo mEditorInfo;
boolean mDisableTouchPositionCorrectionDataForTest;
- boolean mVoiceKeyEnabled;
- // TODO: Remove mVoiceKeyOnMain when it's certainly confirmed that we no longer show
- // the voice input key on the symbol layout
- boolean mVoiceKeyOnMain;
+ boolean mIsPasswordField;
+ boolean mSupportsSwitchingToShortcutIme;
+ boolean mShowsVoiceInputKey;
+ boolean mNoMicrophoneKey;
boolean mNoSettingsKey;
boolean mLanguageSwitchKeyEnabled;
InputMethodSubtype mSubtype;
@@ -221,16 +221,24 @@ public final class KeyboardLayoutSet {
private static final EditorInfo EMPTY_EDITOR_INFO = new EditorInfo();
- public Builder(final Context context, final EditorInfo editorInfo) {
+ public Builder(final Context context, final EditorInfo ei) {
mContext = context;
mPackageName = context.getPackageName();
mResources = context.getResources();
final Params params = mParams;
+ final EditorInfo editorInfo = (ei != null) ? ei : EMPTY_EDITOR_INFO;
params.mMode = getKeyboardMode(editorInfo);
- params.mEditorInfo = (editorInfo != null) ? editorInfo : EMPTY_EDITOR_INFO;
+ params.mEditorInfo = editorInfo;
+ params.mIsPasswordField = InputTypeUtils.isPasswordInputType(editorInfo.inputType);
+ @SuppressWarnings("deprecation")
+ final boolean deprecatedNoMicrophone = InputAttributes.inPrivateImeOptions(
+ null, NO_MICROPHONE_COMPAT, editorInfo);
+ params.mNoMicrophoneKey = InputAttributes.inPrivateImeOptions(
+ mPackageName, NO_MICROPHONE, editorInfo)
+ || deprecatedNoMicrophone;
params.mNoSettingsKey = InputAttributes.inPrivateImeOptions(
- mPackageName, NO_SETTINGS_KEY, params.mEditorInfo);
+ mPackageName, NO_SETTINGS_KEY, editorInfo);
}
public Builder setKeyboardGeometry(final int keyboardWidth, final int keyboardHeight) {
@@ -261,18 +269,11 @@ public final class KeyboardLayoutSet {
return this;
}
- // TODO: Remove mVoiceKeyOnMain when it's certainly confirmed that we no longer show
- // the voice input key on the symbol layout
- public Builder setOptions(final boolean voiceKeyEnabled, final boolean voiceKeyOnMain,
- final boolean languageSwitchKeyEnabled) {
- @SuppressWarnings("deprecation")
- final boolean deprecatedNoMicrophone = InputAttributes.inPrivateImeOptions(
- null, NO_MICROPHONE_COMPAT, mParams.mEditorInfo);
- final boolean noMicrophone = InputAttributes.inPrivateImeOptions(
- mPackageName, NO_MICROPHONE, mParams.mEditorInfo)
- || deprecatedNoMicrophone;
- mParams.mVoiceKeyEnabled = voiceKeyEnabled && !noMicrophone;
- mParams.mVoiceKeyOnMain = voiceKeyOnMain;
+ public Builder setOptions(final boolean isShortcutImeEnabled,
+ final boolean showsVoiceInputKey, final boolean languageSwitchKeyEnabled) {
+ mParams.mSupportsSwitchingToShortcutIme =
+ isShortcutImeEnabled && !mParams.mNoMicrophoneKey && !mParams.mIsPasswordField;
+ mParams.mShowsVoiceInputKey = showsVoiceInputKey;
mParams.mLanguageSwitchKeyEnabled = languageSwitchKeyEnabled;
return this;
}
@@ -368,9 +369,6 @@ public final class KeyboardLayoutSet {
}
private static int getKeyboardMode(final EditorInfo editorInfo) {
- if (editorInfo == null)
- return KeyboardId.MODE_TEXT;
-
final int inputType = editorInfo.inputType;
final int variation = inputType & InputType.TYPE_MASK_VARIATION;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 5abc9ab38..fb84f1d73 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -154,8 +154,8 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
builder.setKeyboardGeometry(keyboardWidth, keyboardHeight);
builder.setSubtype(mSubtypeSwitcher.getCurrentSubtype());
builder.setOptions(
- settingsValues.isVoiceKeyEnabled(editorInfo),
- true /* always show a voice key on the main keyboard */,
+ mSubtypeSwitcher.isShortcutImeEnabled(),
+ settingsValues.mShowsVoiceInputKey,
settingsValues.isLanguageSwitchKeyEnabled());
mKeyboardLayoutSet = builder.build();
try {
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 5578713a0..422bd12a3 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -113,9 +113,6 @@ public class KeyboardView extends View {
private final Canvas mOffscreenCanvas = new Canvas();
private final Paint mPaint = new Paint();
private final Paint.FontMetrics mFontMetrics = new Paint.FontMetrics();
- private static final char[] KEY_LABEL_REFERENCE_CHAR = { 'M' };
- private static final char[] KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR = { '8' };
-
public KeyboardView(final Context context, final AttributeSet attrs) {
this(context, attrs, R.attr.keyboardViewStyle);
}
@@ -322,7 +319,7 @@ public class KeyboardView extends View {
params.mAnimAlpha = Constants.Color.ALPHA_OPAQUE;
if (!key.isSpacer()) {
- onDrawKeyBackground(key, canvas);
+ onDrawKeyBackground(key, canvas, mKeyBackground);
}
onDrawKeyTopVisuals(key, canvas, paint, params);
@@ -330,14 +327,14 @@ public class KeyboardView extends View {
}
// Draw key background.
- protected void onDrawKeyBackground(final Key key, final Canvas canvas) {
+ protected void onDrawKeyBackground(final Key key, final Canvas canvas,
+ final Drawable background) {
final Rect padding = mKeyBackgroundPadding;
final int bgWidth = key.getDrawWidth() + padding.left + padding.right;
final int bgHeight = key.getHeight() + padding.top + padding.bottom;
final int bgX = -padding.left;
final int bgY = -padding.top;
final int[] drawableState = key.getCurrentDrawableState();
- final Drawable background = mKeyBackground;
background.setState(drawableState);
final Rect bounds = background.getBounds();
if (bgWidth != bounds.right || bgHeight != bounds.bottom) {
@@ -370,10 +367,8 @@ public class KeyboardView extends View {
if (label != null) {
paint.setTypeface(key.selectTypeface(params));
paint.setTextSize(key.selectTextSize(params));
- final float labelCharHeight = TypefaceUtils.getCharHeight(
- KEY_LABEL_REFERENCE_CHAR, paint);
- final float labelCharWidth = TypefaceUtils.getCharWidth(
- KEY_LABEL_REFERENCE_CHAR, paint);
+ final float labelCharHeight = TypefaceUtils.getReferenceCharHeight(paint);
+ final float labelCharWidth = TypefaceUtils.getReferenceCharWidth(paint);
// Vertical label text alignment.
final float baseline = centerY + labelCharHeight / 2.0f;
@@ -391,12 +386,12 @@ public class KeyboardView extends View {
positionX = centerX - labelCharWidth * 7.0f / 4.0f;
paint.setTextAlign(Align.LEFT);
} else if (key.hasLabelWithIconLeft() && icon != null) {
- labelWidth = TypefaceUtils.getLabelWidth(label, paint) + icon.getIntrinsicWidth()
+ labelWidth = TypefaceUtils.getStringWidth(label, paint) + icon.getIntrinsicWidth()
+ LABEL_ICON_MARGIN * keyWidth;
positionX = centerX + labelWidth / 2.0f;
paint.setTextAlign(Align.RIGHT);
} else if (key.hasLabelWithIconRight() && icon != null) {
- labelWidth = TypefaceUtils.getLabelWidth(label, paint) + icon.getIntrinsicWidth()
+ labelWidth = TypefaceUtils.getStringWidth(label, paint) + icon.getIntrinsicWidth()
+ LABEL_ICON_MARGIN * keyWidth;
positionX = centerX - labelWidth / 2.0f;
paint.setTextAlign(Align.LEFT);
@@ -404,9 +399,15 @@ public class KeyboardView extends View {
positionX = centerX;
paint.setTextAlign(Align.CENTER);
}
- if (key.needsXScale()) {
- paint.setTextScaleX(Math.min(1.0f,
- (keyWidth * MAX_LABEL_RATIO) / TypefaceUtils.getLabelWidth(label, paint)));
+ if (key.needsAutoXScale()) {
+ final float ratio = Math.min(1.0f, (keyWidth * MAX_LABEL_RATIO) /
+ TypefaceUtils.getStringWidth(label, paint));
+ if (key.needsAutoScale()) {
+ final float autoSize = paint.getTextSize() * ratio;
+ paint.setTextSize(autoSize);
+ } else {
+ paint.setTextScaleX(ratio);
+ }
}
paint.setColor(key.selectTextColor(params));
@@ -451,36 +452,35 @@ public class KeyboardView extends View {
// TODO: Should add a way to specify type face for hint letters
paint.setTypeface(Typeface.DEFAULT_BOLD);
blendAlpha(paint, params.mAnimAlpha);
+ final float labelCharHeight = TypefaceUtils.getReferenceCharHeight(paint);
+ final float labelCharWidth = TypefaceUtils.getReferenceCharWidth(paint);
+ final KeyVisualAttributes visualAttr = key.getVisualAttributes();
+ final float adjustmentY = (visualAttr == null) ? 0.0f
+ : visualAttr.mHintLabelVerticalAdjustment * labelCharHeight;
final float hintX, hintY;
if (key.hasHintLabel()) {
// The hint label is placed just right of the key label. Used mainly on
// "phone number" layout.
// TODO: Generalize the following calculations.
- hintX = positionX
- + TypefaceUtils.getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) * 2.0f;
- hintY = centerY
- + TypefaceUtils.getCharHeight(KEY_LABEL_REFERENCE_CHAR, paint) / 2.0f;
+ hintX = positionX + labelCharWidth * 2.0f;
+ hintY = centerY + labelCharHeight / 2.0f;
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 - mKeyShiftedLetterHintPadding
- - TypefaceUtils.getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) / 2.0f;
+ hintX = keyWidth - mKeyShiftedLetterHintPadding - labelCharWidth / 2.0f;
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.
- final float keyNumericHintLabelReferenceCharWidth =
- TypefaceUtils.getCharWidth(KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR, paint);
- final float keyHintLabelStringWidth =
- TypefaceUtils.getStringWidth(hintLabel, paint);
+ final float hintDigitWidth = TypefaceUtils.getReferenceDigitWidth(paint);
+ final float hintLabelWidth = TypefaceUtils.getStringWidth(hintLabel, paint);
hintX = keyWidth - mKeyHintLetterPadding
- - Math.max(keyNumericHintLabelReferenceCharWidth, keyHintLabelStringWidth)
- / 2.0f;
+ - Math.max(hintDigitWidth, hintLabelWidth) / 2.0f;
hintY = -paint.ascent();
paint.setTextAlign(Align.CENTER);
}
- canvas.drawText(hintLabel, 0, hintLabel.length(), hintX, hintY, paint);
+ canvas.drawText(hintLabel, 0, hintLabel.length(), hintX, hintY + adjustmentY, paint);
if (LatinImeLogger.sVISUALDEBUG) {
final Paint line = new Paint();
@@ -530,7 +530,7 @@ public class KeyboardView extends View {
paint.setColor(params.mHintLabelColor);
paint.setTextAlign(Align.CENTER);
final float hintX = keyWidth - mKeyHintLetterPadding
- - TypefaceUtils.getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) / 2.0f;
+ - TypefaceUtils.getReferenceCharWidth(paint) / 2.0f;
final float hintY = keyHeight - mKeyPopupHintLetterPadding;
canvas.drawText(POPUP_HINT_CHAR, hintX, hintY, paint);
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 13db47004..1400e05c8 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -16,7 +16,10 @@
package com.android.inputmethod.keyboard;
+import android.animation.Animator;
import android.animation.AnimatorInflater;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.SharedPreferences;
@@ -28,27 +31,24 @@ import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
-import android.os.Message;
-import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
-import android.util.SparseArray;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewConfiguration;
import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
import android.view.inputmethod.InputMethodSubtype;
import android.widget.TextView;
import com.android.inputmethod.accessibility.AccessibilityUtils;
import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
import com.android.inputmethod.annotations.ExternallyReferenced;
-import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy;
-import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
+import com.android.inputmethod.keyboard.internal.DrawingHandler;
import com.android.inputmethod.keyboard.internal.GestureFloatingPreviewText;
import com.android.inputmethod.keyboard.internal.GestureTrailsPreview;
import com.android.inputmethod.keyboard.internal.KeyDrawParams;
@@ -56,6 +56,7 @@ import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams;
import com.android.inputmethod.keyboard.internal.NonDistinctMultitouchHelper;
import com.android.inputmethod.keyboard.internal.PreviewPlacerView;
import com.android.inputmethod.keyboard.internal.SlidingKeyInputPreview;
+import com.android.inputmethod.keyboard.internal.TimerHandler;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
@@ -64,13 +65,15 @@ import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.settings.DebugSettings;
import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.CoordinateUtils;
-import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
import com.android.inputmethod.latin.utils.TypefaceUtils;
import com.android.inputmethod.latin.utils.UsabilityStudyLogUtils;
import com.android.inputmethod.latin.utils.ViewLayoutUtils;
import com.android.inputmethod.research.ResearchLogger;
+import java.util.ArrayDeque;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.WeakHashMap;
/**
@@ -78,9 +81,10 @@ import java.util.WeakHashMap;
*
* @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_languageOnSpacebarTextRatio
+ * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarTextColor
+ * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarTextShadowColor
+ * @attr ref R.styleable#MainKeyboardView_spacebarBackground
* @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFinalAlpha
* @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFadeoutAnimator
* @attr ref R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator
@@ -88,7 +92,7 @@ import java.util.WeakHashMap;
* @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_keySelectionByDraggingFinger
* @attr ref R.styleable#MainKeyboardView_keyRepeatStartTimeout
* @attr ref R.styleable#MainKeyboardView_keyRepeatInterval
* @attr ref R.styleable#MainKeyboardView_longPressKeyTimeout
@@ -114,26 +118,27 @@ import java.util.WeakHashMap;
* @attr ref R.styleable#MainKeyboardView_gestureRecognitionSpeedThreshold
* @attr ref R.styleable#MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration
*/
-public final class MainKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler,
- PointerTracker.DrawingProxy, MoreKeysPanel.Controller {
+public final class MainKeyboardView extends KeyboardView implements PointerTracker.DrawingProxy,
+ MoreKeysPanel.Controller, DrawingHandler.Callbacks, TimerHandler.Callbacks {
private static final String TAG = MainKeyboardView.class.getSimpleName();
/** Listener for {@link KeyboardActionListener}. */
private KeyboardActionListener mKeyboardActionListener;
- /* Space key and its icons */
+ /* Space key and its icon and background. */
private Key mSpaceKey;
- private Drawable mSpaceIcon;
+ private Drawable mSpacebarIcon;
+ private final Drawable mSpacebarBackground;
// Stuff to draw language name on spacebar.
private final int mLanguageOnSpacebarFinalAlpha;
private ObjectAnimator mLanguageOnSpacebarFadeoutAnimator;
private boolean mNeedsToDisplayLanguage;
private boolean mHasMultipleEnabledIMEsOrSubtypes;
private int mLanguageOnSpacebarAnimAlpha = Constants.Color.ALPHA_OPAQUE;
- private final float mSpacebarTextRatio;
- private float mSpacebarTextSize;
- private final int mSpacebarTextColor;
- private final int mSpacebarTextShadowColor;
+ private final float mLanguageOnSpacebarTextRatio;
+ private float mLanguageOnSpacebarTextSize;
+ private final int mLanguageOnSpacebarTextColor;
+ private final int mLanguageOnSpacebarTextShadowColor;
// The minimum x-scale to fit the language name on spacebar.
private static final float MINIMUM_XSCALE_OF_LANGUAGE_NAME = 0.8f;
// Stuff to draw auto correction LED on spacebar.
@@ -143,8 +148,8 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
private static final int SPACE_LED_LENGTH_PERCENT = 80;
// Stuff to draw altCodeWhileTyping keys.
- private ObjectAnimator mAltCodeKeyWhileTypingFadeoutAnimator;
- private ObjectAnimator mAltCodeKeyWhileTypingFadeinAnimator;
+ private final ObjectAnimator mAltCodeKeyWhileTypingFadeoutAnimator;
+ private final ObjectAnimator mAltCodeKeyWhileTypingFadeinAnimator;
private int mAltCodeKeyWhileTypingAnimAlpha = Constants.Color.ALPHA_OPAQUE;
// Preview placer view
@@ -155,13 +160,26 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
private final SlidingKeyInputPreview mSlidingKeyInputPreview;
// Key preview
+ private static final boolean FADE_OUT_KEY_TOP_LETTER_WHEN_KEY_IS_PRESSED = false;
private final int mKeyPreviewLayoutId;
private final int mKeyPreviewOffset;
private final int mKeyPreviewHeight;
- private final SparseArray<TextView> mKeyPreviewTexts = CollectionUtils.newSparseArray();
+ // Free {@link TextView} pool that can be used for key preview.
+ private final ArrayDeque<TextView> mFreeKeyPreviewTextViews = CollectionUtils.newArrayDeque();
+ // Map from {@link Key} to {@link TextView} that is currently being displayed as key preview.
+ private final HashMap<Key,TextView> mShowingKeyPreviewTextViews = CollectionUtils.newHashMap();
private final KeyPreviewDrawParams mKeyPreviewDrawParams = new KeyPreviewDrawParams();
private boolean mShowKeyPreviewPopup = true;
private int mKeyPreviewLingerTimeout;
+ private int mKeyPreviewZoomInDuration;
+ private int mKeyPreviewZoomOutDuration;
+ private static final float KEY_PREVIEW_START_ZOOM_IN_SCALE = 0.7f;
+ private static final float KEY_PREVIEW_END_ZOOM_IN_SCALE = 1.0f;
+ private static final float KEY_PREVIEW_END_ZOOM_OUT_SCALE = 0.7f;
+ private static final AccelerateInterpolator ACCELERATE_INTERPOLATOR =
+ new AccelerateInterpolator();
+ private static final DecelerateInterpolator DECELERATE_INTERPOLATOR =
+ new DecelerateInterpolator();
// More keys keyboard
private final Paint mBackgroundDimAlphaPaint = new Paint();
@@ -178,244 +196,14 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
// TODO: Make this parameter customizable by user via settings.
private int mGestureFloatingPreviewTextLingerTimeout;
- private KeyDetector mKeyDetector;
+ private final KeyDetector mKeyDetector;
private final NonDistinctMultitouchHelper mNonDistinctMultitouchHelper;
- private final KeyTimerHandler mKeyTimerHandler;
+ private final TimerHandler mKeyTimerHandler;
private final int mLanguageOnSpacebarHorizontalMargin;
- private static final class KeyTimerHandler extends StaticInnerHandlerWrapper<MainKeyboardView>
- implements TimerProxy {
- private static final int MSG_TYPING_STATE_EXPIRED = 0;
- private static final int MSG_REPEAT_KEY = 1;
- private static final int MSG_LONGPRESS_KEY = 2;
- private static final int MSG_DOUBLE_TAP_SHIFT_KEY = 3;
- private static final int MSG_UPDATE_BATCH_INPUT = 4;
-
- private final int mIgnoreAltCodeKeyTimeout;
- private final int mGestureRecognitionUpdateTime;
-
- public KeyTimerHandler(final MainKeyboardView outerInstance,
- final TypedArray mainKeyboardViewAttr) {
- super(outerInstance);
-
- mIgnoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0);
- mGestureRecognitionUpdateTime = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_gestureRecognitionUpdateTime, 0);
- }
-
- @Override
- public void handleMessage(final Message msg) {
- final MainKeyboardView keyboardView = getOuterInstance();
- if (keyboardView == null) {
- return;
- }
- final PointerTracker tracker = (PointerTracker) msg.obj;
- switch (msg.what) {
- case MSG_TYPING_STATE_EXPIRED:
- startWhileTypingFadeinAnimation(keyboardView);
- break;
- case MSG_REPEAT_KEY:
- tracker.onKeyRepeat(msg.arg1 /* code */, msg.arg2 /* repeatCount */);
- break;
- case MSG_LONGPRESS_KEY:
- keyboardView.onLongPress(tracker);
- break;
- case MSG_UPDATE_BATCH_INPUT:
- tracker.updateBatchInputByTimer(SystemClock.uptimeMillis());
- startUpdateBatchInputTimer(tracker);
- break;
- }
- }
-
- @Override
- public void startKeyRepeatTimer(final PointerTracker tracker, final int repeatCount,
- final int delay) {
- final Key key = tracker.getKey();
- if (key == null || delay == 0) {
- return;
- }
- sendMessageDelayed(
- obtainMessage(MSG_REPEAT_KEY, key.getCode(), repeatCount, tracker), delay);
- }
-
- public void cancelKeyRepeatTimer() {
- removeMessages(MSG_REPEAT_KEY);
- }
-
- // TODO: Suppress layout changes in key repeat mode
- public boolean isInKeyRepeat() {
- return hasMessages(MSG_REPEAT_KEY);
- }
-
- @Override
- public void startLongPressTimer(final PointerTracker tracker, final int delay) {
- cancelLongPressTimer();
- if (delay <= 0) return;
- sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, tracker), delay);
- }
-
- @Override
- public void cancelLongPressTimer() {
- removeMessages(MSG_LONGPRESS_KEY);
- }
-
- private static void cancelAndStartAnimators(final ObjectAnimator animatorToCancel,
- final ObjectAnimator animatorToStart) {
- if (animatorToCancel == null || animatorToStart == null) {
- // TODO: Stop using null as a no-operation animator.
- return;
- }
- float startFraction = 0.0f;
- if (animatorToCancel.isStarted()) {
- animatorToCancel.cancel();
- startFraction = 1.0f - animatorToCancel.getAnimatedFraction();
- }
- final long startTime = (long)(animatorToStart.getDuration() * startFraction);
- animatorToStart.start();
- animatorToStart.setCurrentPlayTime(startTime);
- }
-
- private static void startWhileTypingFadeinAnimation(final MainKeyboardView keyboardView) {
- cancelAndStartAnimators(keyboardView.mAltCodeKeyWhileTypingFadeoutAnimator,
- keyboardView.mAltCodeKeyWhileTypingFadeinAnimator);
- }
-
- private static void startWhileTypingFadeoutAnimation(final MainKeyboardView keyboardView) {
- cancelAndStartAnimators(keyboardView.mAltCodeKeyWhileTypingFadeinAnimator,
- keyboardView.mAltCodeKeyWhileTypingFadeoutAnimator);
- }
-
- @Override
- public void startTypingStateTimer(final Key typedKey) {
- if (typedKey.isModifier() || typedKey.altCodeWhileTyping()) {
- return;
- }
-
- final boolean isTyping = isTypingState();
- removeMessages(MSG_TYPING_STATE_EXPIRED);
- final MainKeyboardView keyboardView = getOuterInstance();
-
- // When user hits the space or the enter key, just cancel the while-typing timer.
- final int typedCode = typedKey.getCode();
- if (typedCode == Constants.CODE_SPACE || typedCode == Constants.CODE_ENTER) {
- if (isTyping) {
- startWhileTypingFadeinAnimation(keyboardView);
- }
- return;
- }
-
- sendMessageDelayed(
- obtainMessage(MSG_TYPING_STATE_EXPIRED), mIgnoreAltCodeKeyTimeout);
- if (isTyping) {
- return;
- }
- startWhileTypingFadeoutAnimation(keyboardView);
- }
-
- @Override
- public boolean isTypingState() {
- return hasMessages(MSG_TYPING_STATE_EXPIRED);
- }
-
- @Override
- public void startDoubleTapShiftKeyTimer() {
- sendMessageDelayed(obtainMessage(MSG_DOUBLE_TAP_SHIFT_KEY),
- ViewConfiguration.getDoubleTapTimeout());
- }
-
- @Override
- public void cancelDoubleTapShiftKeyTimer() {
- removeMessages(MSG_DOUBLE_TAP_SHIFT_KEY);
- }
-
- @Override
- public boolean isInDoubleTapShiftKeyTimeout() {
- return hasMessages(MSG_DOUBLE_TAP_SHIFT_KEY);
- }
-
- @Override
- public void cancelKeyTimers() {
- cancelKeyRepeatTimer();
- cancelLongPressTimer();
- }
-
- @Override
- public void startUpdateBatchInputTimer(final PointerTracker tracker) {
- if (mGestureRecognitionUpdateTime <= 0) {
- return;
- }
- removeMessages(MSG_UPDATE_BATCH_INPUT, tracker);
- sendMessageDelayed(obtainMessage(MSG_UPDATE_BATCH_INPUT, tracker),
- mGestureRecognitionUpdateTime);
- }
-
- @Override
- public void cancelUpdateBatchInputTimer(final PointerTracker tracker) {
- removeMessages(MSG_UPDATE_BATCH_INPUT, tracker);
- }
-
- @Override
- public void cancelAllUpdateBatchInputTimers() {
- removeMessages(MSG_UPDATE_BATCH_INPUT);
- }
-
- public void cancelAllMessages() {
- cancelKeyTimers();
- cancelAllUpdateBatchInputTimers();
- }
- }
-
- private final DrawingHandler mDrawingHandler = new DrawingHandler(this);
-
- public static class DrawingHandler extends StaticInnerHandlerWrapper<MainKeyboardView> {
- private static final int MSG_DISMISS_KEY_PREVIEW = 0;
- private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
-
- public DrawingHandler(final MainKeyboardView outerInstance) {
- super(outerInstance);
- }
-
- @Override
- public void handleMessage(final Message msg) {
- final MainKeyboardView mainKeyboardView = getOuterInstance();
- if (mainKeyboardView == null) return;
- final PointerTracker tracker = (PointerTracker) msg.obj;
- switch (msg.what) {
- case MSG_DISMISS_KEY_PREVIEW:
- final TextView previewText = mainKeyboardView.mKeyPreviewTexts.get(
- tracker.mPointerId);
- if (previewText != null) {
- previewText.setVisibility(INVISIBLE);
- }
- break;
- case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT:
- mainKeyboardView.showGestureFloatingPreviewText(SuggestedWords.EMPTY);
- break;
- }
- }
-
- public void dismissKeyPreview(final long delay, final PointerTracker tracker) {
- sendMessageDelayed(obtainMessage(MSG_DISMISS_KEY_PREVIEW, tracker), delay);
- }
-
- public void cancelDismissKeyPreview(final PointerTracker tracker) {
- removeMessages(MSG_DISMISS_KEY_PREVIEW, tracker);
- }
-
- private void cancelAllDismissKeyPreviews() {
- removeMessages(MSG_DISMISS_KEY_PREVIEW);
- }
-
- public void dismissGestureFloatingPreviewText(final long delay) {
- sendMessageDelayed(obtainMessage(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT), delay);
- }
-
- public void cancelAllMessages() {
- cancelAllDismissKeyPreviews();
- }
- }
+ private final DrawingHandler mDrawingHandler =
+ new DrawingHandler(this);
public MainKeyboardView(final Context context, final AttributeSet attrs) {
this(context, attrs, R.attr.mainKeyboardViewStyle);
@@ -424,7 +212,27 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
public MainKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
- PointerTracker.init(getResources());
+ mPreviewPlacerView = new PreviewPlacerView(context, attrs);
+
+ final TypedArray mainKeyboardViewAttr = context.obtainStyledAttributes(
+ attrs, R.styleable.MainKeyboardView, defStyle, R.style.MainKeyboardView);
+ final int ignoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0);
+ final int gestureRecognitionUpdateTime = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_gestureRecognitionUpdateTime, 0);
+ mKeyTimerHandler = new TimerHandler(
+ this, ignoreAltCodeKeyTimeout, gestureRecognitionUpdateTime);
+
+ final float keyHysteresisDistance = mainKeyboardViewAttr.getDimension(
+ R.styleable.MainKeyboardView_keyHysteresisDistance, 0.0f);
+ final float keyHysteresisDistanceForSlidingModifier = mainKeyboardViewAttr.getDimension(
+ R.styleable.MainKeyboardView_keyHysteresisDistanceForSlidingModifier, 0.0f);
+ mKeyDetector = new KeyDetector(
+ keyHysteresisDistance, keyHysteresisDistanceForSlidingModifier);
+
+ PointerTracker.init(mainKeyboardViewAttr, mKeyTimerHandler, this /* DrawingProxy */,
+ mKeyDetector);
+
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
final boolean forceNonDistinctMultitouch = prefs.getBoolean(
DebugSettings.PREF_FORCE_NON_DISTINCT_MULTITOUCH, false);
@@ -432,26 +240,24 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)
&& !forceNonDistinctMultitouch;
mNonDistinctMultitouchHelper = hasDistinctMultitouch ? null
- : new NonDistinctMultitouchHelper();
-
- mPreviewPlacerView = new PreviewPlacerView(context, attrs);
+ : new NonDistinctMultitouchHelper(PointerTracker.getPointerTracker(0));
- final TypedArray mainKeyboardViewAttr = context.obtainStyledAttributes(
- attrs, R.styleable.MainKeyboardView, defStyle, R.style.MainKeyboardView);
final int backgroundDimAlpha = mainKeyboardViewAttr.getInt(
R.styleable.MainKeyboardView_backgroundDimAlpha, 0);
mBackgroundDimAlphaPaint.setColor(Color.BLACK);
mBackgroundDimAlphaPaint.setAlpha(backgroundDimAlpha);
+ mSpacebarBackground = mainKeyboardViewAttr.getDrawable(
+ R.styleable.MainKeyboardView_spacebarBackground);
mAutoCorrectionSpacebarLedEnabled = mainKeyboardViewAttr.getBoolean(
R.styleable.MainKeyboardView_autoCorrectionSpacebarLedEnabled, false);
mAutoCorrectionSpacebarLedIcon = mainKeyboardViewAttr.getDrawable(
R.styleable.MainKeyboardView_autoCorrectionSpacebarLedIcon);
- mSpacebarTextRatio = mainKeyboardViewAttr.getFraction(
- R.styleable.MainKeyboardView_spacebarTextRatio, 1, 1, 1.0f);
- mSpacebarTextColor = mainKeyboardViewAttr.getColor(
- R.styleable.MainKeyboardView_spacebarTextColor, 0);
- mSpacebarTextShadowColor = mainKeyboardViewAttr.getColor(
- R.styleable.MainKeyboardView_spacebarTextShadowColor, 0);
+ mLanguageOnSpacebarTextRatio = mainKeyboardViewAttr.getFraction(
+ R.styleable.MainKeyboardView_languageOnSpacebarTextRatio, 1, 1, 1.0f);
+ mLanguageOnSpacebarTextColor = mainKeyboardViewAttr.getColor(
+ R.styleable.MainKeyboardView_languageOnSpacebarTextColor, 0);
+ mLanguageOnSpacebarTextShadowColor = mainKeyboardViewAttr.getColor(
+ R.styleable.MainKeyboardView_languageOnSpacebarTextShadowColor, 0);
mLanguageOnSpacebarFinalAlpha = mainKeyboardViewAttr.getInt(
R.styleable.MainKeyboardView_languageOnSpacebarFinalAlpha,
Constants.Color.ALPHA_OPAQUE);
@@ -462,13 +268,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
final int altCodeKeyWhileTypingFadeinAnimatorResId = mainKeyboardViewAttr.getResourceId(
R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0);
- final float keyHysteresisDistance = mainKeyboardViewAttr.getDimension(
- R.styleable.MainKeyboardView_keyHysteresisDistance, 0.0f);
- final float keyHysteresisDistanceForSlidingModifier = mainKeyboardViewAttr.getDimension(
- R.styleable.MainKeyboardView_keyHysteresisDistanceForSlidingModifier, 0.0f);
- mKeyDetector = new KeyDetector(
- keyHysteresisDistance, keyHysteresisDistanceForSlidingModifier);
- mKeyTimerHandler = new KeyTimerHandler(this, mainKeyboardViewAttr);
mKeyPreviewOffset = mainKeyboardViewAttr.getDimensionPixelOffset(
R.styleable.MainKeyboardView_keyPreviewOffset, 0);
mKeyPreviewHeight = mainKeyboardViewAttr.getDimensionPixelSize(
@@ -480,6 +279,10 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
if (mKeyPreviewLayoutId == 0) {
mShowKeyPreviewPopup = false;
}
+ mKeyPreviewZoomInDuration = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_keyPreviewZoomInDuration, 0);
+ mKeyPreviewZoomOutDuration = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_keyPreviewZoomOutDuration, 0);
final int moreKeysKeyboardLayoutId = mainKeyboardViewAttr.getResourceId(
R.styleable.MainKeyboardView_moreKeysKeyboardLayout, 0);
mConfigShowMoreKeysKeyboardAtTouchedPoint = mainKeyboardViewAttr.getBoolean(
@@ -487,7 +290,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
mGestureFloatingPreviewTextLingerTimeout = mainKeyboardViewAttr.getInt(
R.styleable.MainKeyboardView_gestureFloatingPreviewTextLingerTimeout, 0);
- PointerTracker.setParameters(mainKeyboardViewAttr);
mGestureFloatingPreviewText = new GestureFloatingPreviewText(
mPreviewPlacerView, mainKeyboardViewAttr);
@@ -513,8 +315,8 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER;
- mLanguageOnSpacebarHorizontalMargin =
- (int) getResources().getDimension(R.dimen.language_on_spacebar_horizontal_margin);
+ mLanguageOnSpacebarHorizontalMargin = (int)getResources().getDimension(
+ R.dimen.config_language_on_spacebar_horizontal_margin);
}
@Override
@@ -536,6 +338,35 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
return animator;
}
+ private static void cancelAndStartAnimators(final ObjectAnimator animatorToCancel,
+ final ObjectAnimator animatorToStart) {
+ if (animatorToCancel == null || animatorToStart == null) {
+ // TODO: Stop using null as a no-operation animator.
+ return;
+ }
+ float startFraction = 0.0f;
+ if (animatorToCancel.isStarted()) {
+ animatorToCancel.cancel();
+ startFraction = 1.0f - animatorToCancel.getAnimatedFraction();
+ }
+ final long startTime = (long)(animatorToStart.getDuration() * startFraction);
+ animatorToStart.start();
+ animatorToStart.setCurrentPlayTime(startTime);
+ }
+
+ // Implements {@link TimerHander.Callbacks} method.
+ @Override
+ public void startWhileTypingFadeinAnimation() {
+ cancelAndStartAnimators(
+ mAltCodeKeyWhileTypingFadeoutAnimator, mAltCodeKeyWhileTypingFadeinAnimator);
+ }
+
+ @Override
+ public void startWhileTypingFadeoutAnimation() {
+ cancelAndStartAnimators(
+ mAltCodeKeyWhileTypingFadeinAnimator, mAltCodeKeyWhileTypingFadeoutAnimator);
+ }
+
@ExternallyReferenced
public int getLanguageOnSpacebarAnimAlpha() {
return mLanguageOnSpacebarAnimAlpha;
@@ -573,28 +404,16 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
PointerTracker.setKeyboardActionListener(listener);
}
- /**
- * Returns the {@link KeyboardActionListener} object.
- * @return the listener attached to this keyboard
- */
- @Override
- public KeyboardActionListener getKeyboardActionListener() {
- return mKeyboardActionListener;
+ // TODO: We should reconsider which coordinate system should be used to represent keyboard
+ // event.
+ public int getKeyX(final int x) {
+ return Constants.isValidCoordinate(x) ? mKeyDetector.getTouchX(x) : x;
}
- @Override
- public KeyDetector getKeyDetector() {
- return mKeyDetector;
- }
-
- @Override
- public DrawingProxy getDrawingProxy() {
- return this;
- }
-
- @Override
- public TimerProxy getTimerProxy() {
- return mKeyTimerHandler;
+ // TODO: We should reconsider which coordinate system should be used to represent keyboard
+ // event.
+ public int getKeyY(final int y) {
+ return Constants.isValidCoordinate(y) ? mKeyDetector.getTouchY(y) : y;
}
/**
@@ -606,19 +425,20 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
*/
@Override
public void setKeyboard(final Keyboard keyboard) {
- // Remove any pending messages, except dismissing preview and key repeat.
- mKeyTimerHandler.cancelLongPressTimer();
+ // Remove any pending messages.
+ mKeyTimerHandler.cancelAllKeyTimers();
super.setKeyboard(keyboard);
mKeyDetector.setKeyboard(
keyboard, -getPaddingLeft(), -getPaddingTop() + getVerticalCorrection());
PointerTracker.setKeyDetector(mKeyDetector);
+ PointerTracker.setKeyboardActionListener(mKeyboardActionListener);
mMoreKeysKeyboardCache.clear();
mSpaceKey = keyboard.getKey(Constants.CODE_SPACE);
- mSpaceIcon = (mSpaceKey != null)
+ mSpacebarIcon = (mSpaceKey != null)
? mSpaceKey.getIcon(keyboard.mIconsSet, Constants.Color.ALPHA_OPAQUE) : null;
final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
- mSpacebarTextSize = keyHeight * mSpacebarTextRatio;
+ mLanguageOnSpacebarTextSize = keyHeight * mLanguageOnSpacebarTextRatio;
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
final int orientation = getContext().getResources().getConfiguration().orientation;
ResearchLogger.mainKeyboardView_setKeyboard(keyboard, orientation);
@@ -681,34 +501,33 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
return mShowKeyPreviewPopup;
}
- private void addKeyPreview(final TextView keyPreview) {
- locatePreviewPlacerView();
- mPreviewPlacerView.addView(
- keyPreview, ViewLayoutUtils.newLayoutParam(mPreviewPlacerView, 0, 0));
- }
-
- private TextView getKeyPreviewText(final int pointerId) {
- TextView previewText = mKeyPreviewTexts.get(pointerId);
- if (previewText != null) {
- return previewText;
+ private TextView getKeyPreviewTextView(final Key key) {
+ TextView previewTextView = mShowingKeyPreviewTextViews.remove(key);
+ if (previewTextView != null) {
+ return previewTextView;
+ }
+ previewTextView = mFreeKeyPreviewTextViews.poll();
+ if (previewTextView != null) {
+ return previewTextView;
}
final Context context = getContext();
if (mKeyPreviewLayoutId != 0) {
- previewText = (TextView)LayoutInflater.from(context).inflate(mKeyPreviewLayoutId, null);
+ previewTextView = (TextView)LayoutInflater.from(context)
+ .inflate(mKeyPreviewLayoutId, null);
} else {
- previewText = new TextView(context);
+ previewTextView = new TextView(context);
}
- mKeyPreviewTexts.put(pointerId, previewText);
- return previewText;
+ locatePreviewPlacerView();
+ mPreviewPlacerView.addView(
+ previewTextView, ViewLayoutUtils.newLayoutParam(mPreviewPlacerView, 0, 0));
+ return previewTextView;
}
- 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);
- }
+ // Implements {@link DrawingHandler.Callbacks} method.
+ @Override
+ public void dismissAllKeyPreviews() {
+ for (final Key key : new HashSet<Key>(mShowingKeyPreviewTextViews.keySet())) {
+ dismissKeyPreviewWithoutDelay(key);
}
PointerTracker.setReleasedKeyGraphicsToAllKeys();
}
@@ -734,24 +553,9 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
private static final int STATE_NORMAL = 0;
private static final int STATE_HAS_MOREKEYS = 1;
+ // TODO: Take this method out of this class.
@Override
- public void showKeyPreview(final PointerTracker tracker) {
- final KeyPreviewDrawParams previewParams = mKeyPreviewDrawParams;
- final Keyboard keyboard = getKeyboard();
- if (!mShowKeyPreviewPopup) {
- previewParams.mPreviewVisibleOffset = -keyboard.mVerticalGap;
- return;
- }
-
- 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) {
- addKeyPreview(previewText);
- }
-
- mDrawingHandler.cancelDismissKeyPreview(tracker);
- final Key key = tracker.getKey();
+ public void showKeyPreview(final Key key) {
// If key is invalid or IME is already closed, we must not show key preview.
// Trying to show key preview while root window is closed causes
// WindowManager.BadTokenException.
@@ -759,38 +563,47 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
return;
}
+ final KeyPreviewDrawParams previewParams = mKeyPreviewDrawParams;
+ final Keyboard keyboard = getKeyboard();
+ if (!mShowKeyPreviewPopup) {
+ previewParams.mPreviewVisibleOffset = -keyboard.mVerticalGap;
+ return;
+ }
+
+ final TextView previewTextView = getKeyPreviewTextView(key);
final KeyDrawParams drawParams = mKeyDrawParams;
- previewText.setTextColor(drawParams.mPreviewTextColor);
- final Drawable background = previewText.getBackground();
+ previewTextView.setTextColor(drawParams.mPreviewTextColor);
+ final Drawable background = previewTextView.getBackground();
final String label = key.getPreviewLabel();
// 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);
- previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX,
+ previewTextView.setCompoundDrawables(null, null, null, null);
+ previewTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
key.selectPreviewTextSize(drawParams));
- previewText.setTypeface(key.selectPreviewTypeface(drawParams));
- previewText.setText(label);
+ previewTextView.setTypeface(key.selectPreviewTypeface(drawParams));
+ previewTextView.setText(label);
} else {
- previewText.setCompoundDrawables(null, null, null,
+ previewTextView.setCompoundDrawables(null, null, null,
key.getPreviewIcon(keyboard.mIconsSet));
- previewText.setText(null);
+ previewTextView.setText(null);
}
- previewText.measure(
+ previewTextView.measure(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
final int keyDrawWidth = key.getDrawWidth();
- final int previewWidth = previewText.getMeasuredWidth();
+ final int previewWidth = previewTextView.getMeasuredWidth();
final int previewHeight = mKeyPreviewHeight;
// 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.
- previewParams.mPreviewVisibleWidth = previewWidth - previewText.getPaddingLeft()
- - previewText.getPaddingRight();
- previewParams.mPreviewVisibleHeight = previewHeight - previewText.getPaddingTop()
- - previewText.getPaddingBottom();
+ previewParams.mPreviewVisibleWidth = previewWidth - previewTextView.getPaddingLeft()
+ - previewTextView.getPaddingRight();
+ previewParams.mPreviewVisibleHeight = previewHeight - previewTextView.getPaddingTop()
+ - previewTextView.getPaddingBottom();
// The distance between the top edge of the parent key and the bottom of the visible part
// of the key preview background.
- previewParams.mPreviewVisibleOffset = mKeyPreviewOffset - previewText.getPaddingBottom();
+ previewParams.mPreviewVisibleOffset =
+ mKeyPreviewOffset - previewTextView.getPaddingBottom();
getLocationInWindow(mOriginCoords);
// The key preview is horizontally aligned with the center of the visible part of the
// parent key. If it doesn't fit in this {@link KeyboardView}, it is moved inward to fit and
@@ -817,13 +630,132 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
background.setState(KEY_PREVIEW_BACKGROUND_STATE_TABLE[statePosition][hasMoreKeys]);
}
ViewLayoutUtils.placeViewAt(
- previewText, previewX, previewY, previewWidth, previewHeight);
- previewText.setVisibility(VISIBLE);
+ previewTextView, previewX, previewY, previewWidth, previewHeight);
+
+ if (!isHardwareAccelerated()) {
+ previewTextView.setVisibility(VISIBLE);
+ mShowingKeyPreviewTextViews.put(key, previewTextView);
+ return;
+ }
+ previewTextView.setPivotX(previewWidth / 2.0f);
+ previewTextView.setPivotY(previewHeight);
+
+ final Animator zoomIn = createZoomInAniation(key, previewTextView);
+ final Animator zoomOut = createZoomOutAnimation(key, previewTextView);
+ final KeyPreviewAnimations animation = new KeyPreviewAnimations(zoomIn, zoomOut);
+ previewTextView.setTag(animation);
+ animation.startZoomIn();
}
+ // TODO: Move this internal class out to a separate external class.
+ private static class KeyPreviewAnimations extends AnimatorListenerAdapter {
+ private final Animator mZoomIn;
+ private final Animator mZoomOut;
+
+ public KeyPreviewAnimations(final Animator zoomIn, final Animator zoomOut) {
+ mZoomIn = zoomIn;
+ mZoomOut = zoomOut;
+ }
+
+ public void startZoomIn() {
+ mZoomIn.start();
+ }
+
+ public void startZoomOut() {
+ if (mZoomIn.isRunning()) {
+ mZoomIn.addListener(this);
+ return;
+ }
+ mZoomOut.start();
+ }
+
+ @Override
+ public void onAnimationEnd(final Animator animation) {
+ mZoomOut.start();
+ }
+ }
+
+ // TODO: Take this method out of this class.
+ private Animator createZoomInAniation(final Key key, final TextView previewTextView) {
+ final ObjectAnimator scaleXAnimation = ObjectAnimator.ofFloat(
+ previewTextView, SCALE_X, KEY_PREVIEW_START_ZOOM_IN_SCALE,
+ KEY_PREVIEW_END_ZOOM_IN_SCALE);
+ final ObjectAnimator scaleYAnimation = ObjectAnimator.ofFloat(
+ previewTextView, SCALE_Y, KEY_PREVIEW_START_ZOOM_IN_SCALE,
+ KEY_PREVIEW_END_ZOOM_IN_SCALE);
+ final AnimatorSet zoomInAnimation = new AnimatorSet();
+ zoomInAnimation.play(scaleXAnimation).with(scaleYAnimation);
+ // TODO: Implement preference option to control key preview animation duration.
+ zoomInAnimation.setDuration(mKeyPreviewZoomInDuration);
+ zoomInAnimation.setInterpolator(DECELERATE_INTERPOLATOR);
+ zoomInAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(final Animator animation) {
+ previewTextView.setVisibility(VISIBLE);
+ mShowingKeyPreviewTextViews.put(key, previewTextView);
+ }
+ });
+ return zoomInAnimation;
+ }
+
+ // TODO: Take this method out of this class.
+ private Animator createZoomOutAnimation(final Key key, final TextView previewTextView) {
+ final ObjectAnimator scaleXAnimation = ObjectAnimator.ofFloat(
+ previewTextView, SCALE_X, KEY_PREVIEW_END_ZOOM_OUT_SCALE);
+ final ObjectAnimator scaleYAnimation = ObjectAnimator.ofFloat(
+ previewTextView, SCALE_Y, KEY_PREVIEW_END_ZOOM_OUT_SCALE);
+ final AnimatorSet zoomOutAnimation = new AnimatorSet();
+ zoomOutAnimation.play(scaleXAnimation).with(scaleYAnimation);
+ // TODO: Implement preference option to control key preview animation duration.
+ zoomOutAnimation.setDuration(mKeyPreviewZoomOutDuration);
+ zoomOutAnimation.setInterpolator(ACCELERATE_INTERPOLATOR);
+ zoomOutAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(final Animator animation) {
+ dismissKeyPreviewWithoutDelay(key);
+ }
+ });
+ return zoomOutAnimation;
+ }
+
+ // Implements {@link TimerHandler.Callbacks} method.
+ // TODO: Take this method out of this class.
@Override
- public void dismissKeyPreview(final PointerTracker tracker) {
- mDrawingHandler.dismissKeyPreview(mKeyPreviewLingerTimeout, tracker);
+ public void dismissKeyPreviewWithoutDelay(final Key key) {
+ if (key == null) {
+ return;
+ }
+ final TextView previewTextView = mShowingKeyPreviewTextViews.remove(key);
+ if (previewTextView != null) {
+ final Object tag = previewTextView.getTag();
+ if (tag instanceof Animator) {
+ ((Animator)tag).cancel();
+ }
+ previewTextView.setTag(null);
+ previewTextView.setVisibility(INVISIBLE);
+ mFreeKeyPreviewTextViews.add(previewTextView);
+ }
+ // To redraw key top letter.
+ invalidateKey(key);
+ }
+
+ // TODO: Take this method out of this class.
+ @Override
+ public void dismissKeyPreview(final Key key) {
+ final TextView previewTextView = mShowingKeyPreviewTextViews.get(key);
+ if (previewTextView == null) {
+ return;
+ }
+ if (!isHardwareAccelerated()) {
+ // TODO: Implement preference option to control key preview method and duration.
+ mDrawingHandler.dismissKeyPreview(mKeyPreviewLingerTimeout, key);
+ return;
+ }
+ final Object tag = previewTextView.getTag();
+ if (tag instanceof KeyPreviewAnimations) {
+ final KeyPreviewAnimations animation = (KeyPreviewAnimations)tag;
+ animation.startZoomOut();
+ }
}
public void setSlidingKeyInputPreviewEnabled(final boolean enabled) {
@@ -847,6 +779,8 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
mGestureTrailsPreview.setPreviewEnabled(isGestureTrailEnabled);
}
+ // Implements {@link DrawingHandler.Callbacks} method.
+ @Override
public void showGestureFloatingPreviewText(final SuggestedWords suggestedWords) {
locatePreviewPlacerView();
mGestureFloatingPreviewText.setSuggetedWords(suggestedWords);
@@ -922,11 +856,13 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
return moreKeysKeyboardView;
}
+ // Implements {@link TimerHandler.Callbacks} method.
/**
* Called when a key is long pressed.
* @param tracker the pointer tracker which pressed the parent key
*/
- private void onLongPress(final PointerTracker tracker) {
+ @Override
+ public void onLongPress(final PointerTracker tracker) {
if (isShowingMoreKeysPanel()) {
return;
}
@@ -982,13 +918,15 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
final int pointY = key.getY() + mKeyPreviewDrawParams.mPreviewVisibleOffset;
moreKeysPanel.showMoreKeysPanel(this, this, pointX, pointY, mKeyboardActionListener);
tracker.onShowMoreKeysPanel(moreKeysPanel);
+ // TODO: Implement zoom in animation of more keys panel.
+ dismissKeyPreviewWithoutDelay(key);
}
- public boolean isInSlidingKeyInput() {
+ public boolean isInDraggingFinger() {
if (isShowingMoreKeysPanel()) {
return true;
}
- return PointerTracker.isAnyInSlidingKeyInput();
+ return PointerTracker.isAnyInDraggingFinger();
}
@Override
@@ -1049,10 +987,10 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
if (mNonDistinctMultitouchHelper != null) {
if (me.getPointerCount() > 1 && mKeyTimerHandler.isInKeyRepeat()) {
// Key repeating timer will be canceled if 2 or more keys are in action.
- mKeyTimerHandler.cancelKeyRepeatTimer();
+ mKeyTimerHandler.cancelKeyRepeatTimers();
}
// Non distinct multitouch screen support
- mNonDistinctMultitouchHelper.processMotionEvent(me, this);
+ mNonDistinctMultitouchHelper.processMotionEvent(me, mKeyDetector);
return true;
}
return processMotionEvent(me);
@@ -1069,8 +1007,14 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
final int index = me.getActionIndex();
final int id = me.getPointerId(index);
- final PointerTracker tracker = PointerTracker.getPointerTracker(id, this);
- tracker.processMotionEvent(me, this);
+ final PointerTracker tracker = PointerTracker.getPointerTracker(id);
+ // When a more keys panel is showing, we should ignore other fingers' single touch events
+ // other than the finger that is showing the more keys panel.
+ if (isShowingMoreKeysPanel() && !tracker.isShowingMoreKeysPanel()
+ && PointerTracker.getActivePointerTrackerCount() == 1) {
+ return true;
+ }
+ tracker.processMotionEvent(me, mKeyDetector);
return true;
}
@@ -1099,7 +1043,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
@Override
public boolean dispatchHoverEvent(final MotionEvent event) {
if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
- final PointerTracker tracker = PointerTracker.getPointerTracker(0, this);
+ final PointerTracker tracker = PointerTracker.getPointerTracker(0);
return AccessibleKeyboardViewProxy.getInstance().dispatchHoverEvent(event, tracker);
}
@@ -1169,12 +1113,30 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
}
}
+ // Draw key background.
+ @Override
+ protected void onDrawKeyBackground(final Key key, final Canvas canvas,
+ final Drawable background) {
+ if (key.getCode() == Constants.CODE_SPACE) {
+ super.onDrawKeyBackground(key, canvas, mSpacebarBackground);
+ return;
+ }
+ super.onDrawKeyBackground(key, canvas, background);
+ }
+
@Override
protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint,
final KeyDrawParams params) {
if (key.altCodeWhileTyping() && key.isEnabled()) {
params.mAnimAlpha = mAltCodeKeyWhileTypingAnimAlpha;
}
+ // Don't draw key top letter when key preview is showing.
+ if (FADE_OUT_KEY_TOP_LETTER_WHEN_KEY_IS_PRESSED
+ && mShowingKeyPreviewTextViews.containsKey(key)) {
+ // TODO: Fade out animation for the key top letter, and fade in animation for the key
+ // background color when the user presses the key.
+ return;
+ }
final int code = key.getCode();
if (code == Constants.CODE_SPACE) {
drawSpacebar(key, canvas, paint);
@@ -1193,7 +1155,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
private boolean fitsTextIntoWidth(final int width, final String text, final Paint paint) {
final int maxTextWidth = width - mLanguageOnSpacebarHorizontalMargin * 2;
paint.setTextScaleX(1.0f);
- final float textWidth = TypefaceUtils.getLabelWidth(text, paint);
+ final float textWidth = TypefaceUtils.getStringWidth(text, paint);
if (textWidth < width) {
return true;
}
@@ -1204,7 +1166,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
}
paint.setTextScaleX(scaleX);
- return TypefaceUtils.getLabelWidth(text, paint) < maxTextWidth;
+ return TypefaceUtils.getStringWidth(text, paint) < maxTextWidth;
}
// Layout language name on spacebar.
@@ -1238,17 +1200,17 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
if (mNeedsToDisplayLanguage) {
paint.setTextAlign(Align.CENTER);
paint.setTypeface(Typeface.DEFAULT);
- paint.setTextSize(mSpacebarTextSize);
+ paint.setTextSize(mLanguageOnSpacebarTextSize);
final InputMethodSubtype subtype = getKeyboard().mId.mSubtype;
final String language = layoutLanguageOnSpacebar(paint, subtype, width);
// Draw language text with shadow
final float descent = paint.descent();
final float textHeight = -paint.ascent() + descent;
final float baseline = height / 2 + textHeight / 2;
- paint.setColor(mSpacebarTextShadowColor);
+ paint.setColor(mLanguageOnSpacebarTextShadowColor);
paint.setAlpha(mLanguageOnSpacebarAnimAlpha);
canvas.drawText(language, width / 2, baseline - descent - 1, paint);
- paint.setColor(mSpacebarTextColor);
+ paint.setColor(mLanguageOnSpacebarTextColor);
paint.setAlpha(mLanguageOnSpacebarAnimAlpha);
canvas.drawText(language, width / 2, baseline - descent, paint);
}
@@ -1260,12 +1222,12 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
int x = (width - iconWidth) / 2;
int y = height - iconHeight;
drawIcon(canvas, mAutoCorrectionSpacebarLedIcon, x, y, iconWidth, iconHeight);
- } else if (mSpaceIcon != null) {
- final int iconWidth = mSpaceIcon.getIntrinsicWidth();
- final int iconHeight = mSpaceIcon.getIntrinsicHeight();
+ } else if (mSpacebarIcon != null) {
+ final int iconWidth = mSpacebarIcon.getIntrinsicWidth();
+ final int iconHeight = mSpacebarIcon.getIntrinsicHeight();
int x = (width - iconWidth) / 2;
int y = height - iconHeight;
- drawIcon(canvas, mSpaceIcon, x, y, iconWidth, iconHeight);
+ drawIcon(canvas, mSpacebarIcon, x, y, iconWidth, iconHeight);
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java b/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
index 6b76e2461..81b8f0428 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
@@ -28,7 +28,7 @@ public final class MoreKeysDetector extends KeyDetector {
}
@Override
- public boolean alwaysAllowsSlidingInput() {
+ public boolean alwaysAllowsKeySelectionByDraggingFinger() {
return true;
}
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
index 8256d4623..670524380 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
@@ -223,7 +223,7 @@ public final class MoreKeysKeyboard extends Keyboard {
}
public int getDefaultKeyCoordX() {
- return mLeftKeys * mColumnWidth;
+ return mLeftKeys * mColumnWidth + mLeftPadding;
}
public int getX(final int n, final int row) {
@@ -298,7 +298,7 @@ public final class MoreKeysKeyboard extends Keyboard {
height = keyPreviewDrawParams.mPreviewVisibleHeight + mParams.mVerticalGap;
} else {
final float padding = context.getResources().getDimension(
- R.dimen.more_keys_keyboard_key_horizontal_padding)
+ R.dimen.config_more_keys_keyboard_key_horizontal_padding)
+ (parentKey.hasLabelsInMoreKeys()
? mParams.mDefaultKeyWidth * LABEL_PADDING_RATIO : 0.0f);
width = getMaxKeyWidth(parentKey, mParams.mDefaultKeyWidth, padding,
@@ -327,7 +327,7 @@ public final class MoreKeysKeyboard extends Keyboard {
// If the label is single letter, minKeyWidth is enough to hold the label.
if (label != null && StringUtils.codePointCount(label) > 1) {
maxWidth = Math.max(maxWidth,
- (int)(TypefaceUtils.getLabelWidth(label, paint) + padding));
+ (int)(TypefaceUtils.getStringWidth(label, paint) + padding));
}
}
return maxWidth;
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
index 973128d36..a7c468538 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
@@ -52,7 +52,7 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
final Resources res = context.getResources();
mKeyDetector = new MoreKeysDetector(
- res.getDimension(R.dimen.more_keys_keyboard_slide_allowance));
+ res.getDimension(R.dimen.config_more_keys_keyboard_slide_allowance));
}
@Override
@@ -81,11 +81,13 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
mListener = listener;
final View container = getContainerView();
// The coordinates of panel's left-top corner in parentView's coordinate system.
- final int x = pointX - getDefaultCoordX() - container.getPaddingLeft();
- final int y = pointY - container.getMeasuredHeight() + container.getPaddingBottom();
+ // We need to consider background drawable paddings.
+ final int x = pointX - getDefaultCoordX() - container.getPaddingLeft() - getPaddingLeft();
+ final int y = pointY - container.getMeasuredHeight() + container.getPaddingBottom()
+ + getPaddingBottom();
parentView.getLocationInWindow(mCoordinates);
- // Ensure the horizontal position of the panel does not extend past the screen edges.
+ // Ensure the horizontal position of the panel does not extend past the parentView edges.
final int maxX = parentView.getMeasuredWidth() - container.getMeasuredWidth();
final int panelX = Math.max(0, Math.min(maxX, x)) + CoordinateUtils.x(mCoordinates);
final int panelY = y + CoordinateUtils.y(mCoordinates);
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 52f190e77..f9e78bfd9 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -55,37 +55,10 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
private static boolean sGestureHandlingEnabledByInputField = false;
private static boolean sGestureHandlingEnabledByUser = false;
- public interface KeyEventHandler {
- /**
- * Get KeyDetector object that is used for this PointerTracker.
- * @return the KeyDetector object that is used for this PointerTracker
- */
- public KeyDetector getKeyDetector();
-
- /**
- * Get KeyboardActionListener object that is used to register key code and so on.
- * @return the KeyboardActionListner for this PointerTracke
- */
- public KeyboardActionListener getKeyboardActionListener();
-
- /**
- * Get DrawingProxy object that is used for this PointerTracker.
- * @return the DrawingProxy object that is used for this PointerTracker
- */
- public DrawingProxy getDrawingProxy();
-
- /**
- * Get TimerProxy object that handles key repeat and long press timer event for this
- * PointerTracker.
- * @return the TimerProxy object that handles key repeat and long press timer event.
- */
- public TimerProxy getTimerProxy();
- }
-
public interface DrawingProxy {
public void invalidateKey(Key key);
- public void showKeyPreview(PointerTracker tracker);
- public void dismissKeyPreview(PointerTracker tracker);
+ public void showKeyPreview(Key key);
+ public void dismissKeyPreview(Key key);
public void showSlidingKeyInputPreview(PointerTracker tracker);
public void dismissSlidingKeyInputPreview();
public void showGestureTrail(PointerTracker tracker, boolean showsFloatingPreviewText);
@@ -94,13 +67,13 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
public interface TimerProxy {
public void startTypingStateTimer(Key typedKey);
public boolean isTypingState();
- public void startKeyRepeatTimer(PointerTracker tracker, int repeatCount, int delay);
- public void startLongPressTimer(PointerTracker tracker, int delay);
- public void cancelLongPressTimer();
+ public void startKeyRepeatTimerOf(PointerTracker tracker, int repeatCount, int delay);
+ public void startLongPressTimerOf(PointerTracker tracker, int delay);
+ public void cancelLongPressTimerOf(PointerTracker tracker);
+ public void cancelKeyTimersOf(PointerTracker tracker);
public void startDoubleTapShiftKeyTimer();
public void cancelDoubleTapShiftKeyTimer();
public boolean isInDoubleTapShiftKeyTimeout();
- public void cancelKeyTimers();
public void startUpdateBatchInputTimer(PointerTracker tracker);
public void cancelUpdateBatchInputTimer(PointerTracker tracker);
public void cancelAllUpdateBatchInputTimers();
@@ -111,11 +84,13 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
@Override
public boolean isTypingState() { return false; }
@Override
- public void startKeyRepeatTimer(PointerTracker tracker, int repeatCount, int delay) {}
+ public void startKeyRepeatTimerOf(PointerTracker tracker, int repeatCount, int delay) {}
+ @Override
+ public void startLongPressTimerOf(PointerTracker tracker, int delay) {}
@Override
- public void startLongPressTimer(PointerTracker tracker, int delay) {}
+ public void cancelLongPressTimerOf(PointerTracker tracker) {}
@Override
- public void cancelLongPressTimer() {}
+ public void cancelKeyTimersOf(PointerTracker tracker) {}
@Override
public void startDoubleTapShiftKeyTimer() {}
@Override
@@ -123,8 +98,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
@Override
public boolean isInDoubleTapShiftKeyTimeout() { return false; }
@Override
- public void cancelKeyTimers() {}
- @Override
public void startUpdateBatchInputTimer(PointerTracker tracker) {}
@Override
public void cancelUpdateBatchInputTimer(PointerTracker tracker) {}
@@ -134,7 +107,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
static final class PointerTrackerParams {
- public final boolean mSlidingKeyInputEnabled;
+ public final boolean mKeySelectionByDraggingFinger;
public final int mTouchNoiseThresholdTime;
public final int mTouchNoiseThresholdDistance;
public final int mSuppressKeyPreviewAfterBatchInputDuration;
@@ -142,21 +115,9 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
public final int mKeyRepeatInterval;
public final int mLongPressShiftLockTimeout;
- public static final PointerTrackerParams DEFAULT = new PointerTrackerParams();
-
- private PointerTrackerParams() {
- mSlidingKeyInputEnabled = false;
- mTouchNoiseThresholdTime = 0;
- mTouchNoiseThresholdDistance = 0;
- mSuppressKeyPreviewAfterBatchInputDuration = 0;
- mKeyRepeatStartTimeout = 0;
- mKeyRepeatInterval = 0;
- mLongPressShiftLockTimeout = 0;
- }
-
public PointerTrackerParams(final TypedArray mainKeyboardViewAttr) {
- mSlidingKeyInputEnabled = mainKeyboardViewAttr.getBoolean(
- R.styleable.MainKeyboardView_slidingKeyInputEnable, false);
+ mKeySelectionByDraggingFinger = mainKeyboardViewAttr.getBoolean(
+ R.styleable.MainKeyboardView_keySelectionByDraggingFinger, false);
mTouchNoiseThresholdTime = mainKeyboardViewAttr.getInt(
R.styleable.MainKeyboardView_touchNoiseThresholdTime, 0);
mTouchNoiseThresholdDistance = mainKeyboardViewAttr.getDimensionPixelSize(
@@ -189,13 +150,14 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
public final int mPointerId;
- private DrawingProxy mDrawingProxy;
- private TimerProxy mTimerProxy;
+ private static DrawingProxy sDrawingProxy;
+ private static TimerProxy sTimerProxy;
+ private static KeyDetector sDefaultKeyDetector;
private KeyDetector mKeyDetector;
- private KeyboardActionListener mListener = KeyboardActionListener.EMPTY_LISTENER;
+ private static KeyboardActionListener sListener = KeyboardActionListener.EMPTY_LISTENER;
private Keyboard mKeyboard;
- private int mPhantonSuddenMoveThreshold;
+ private int mPhantomSuddenMoveThreshold;
private final BogusMoveEventDetector mBogusMoveEventDetector = new BogusMoveEventDetector();
private boolean mIsDetectingGesture = false; // per PointerTracker.
@@ -341,16 +303,16 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
private MoreKeysPanel mMoreKeysPanel;
private static final int MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT = 3;
- // true if this pointer is in a sliding key input.
- boolean mIsInSlidingKeyInput;
- // true if this pointer is in a sliding key input from a modifier key,
+ // true if this pointer is in the dragging finger mode.
+ boolean mIsInDraggingFinger;
+ // true if this pointer is sliding from a modifier key and in the sliding key input mode,
// so that further modifier keys should be ignored.
- boolean mIsInSlidingKeyInputFromModifier;
+ boolean mIsInSlidingKeyInput;
// if not a NOT_A_CODE, the key of this code is repeating
private int mCurrentRepeatingKeyCode = Constants.NOT_A_CODE;
- // true if a sliding key input is allowed.
- private boolean mIsAllowedSlidingKeyInput;
+ // true if dragging finger is allowed.
+ private boolean mIsAllowedDraggingFinger;
private final GestureStrokeWithPreviewPoints mGestureStrokeWithPreviewPoints;
@@ -377,22 +339,26 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
return needsTheHack;
}
- public static void init(final Resources res) {
+ // TODO: Add PointerTrackerFactory singleton and move some class static methods into it.
+ public static void init(final TypedArray mainKeyboardViewAttr, final TimerProxy timerProxy,
+ final DrawingProxy drawingProxy, final KeyDetector defaultKeyDetector) {
+ sParams = new PointerTrackerParams(mainKeyboardViewAttr);
+ sGestureStrokeParams = new GestureStrokeParams(mainKeyboardViewAttr);
+ sGesturePreviewParams = new GestureStrokePreviewParams(mainKeyboardViewAttr);
+ sTimeRecorder = new TimeRecorder(sParams, sGestureStrokeParams);
+
+ final Resources res = mainKeyboardViewAttr.getResources();
sNeedsPhantomSuddenMoveEventHack = Boolean.parseBoolean(
ResourceUtils.getDeviceOverrideValue(
res, R.array.phantom_sudden_move_event_device_list));
sNeedsProximateBogusDownMoveUpEventHack = needsProximateBogusDownMoveUpEventHack(res);
- sParams = PointerTrackerParams.DEFAULT;
sGestureStrokeParams = GestureStrokeParams.DEFAULT;
sGesturePreviewParams = GestureStrokePreviewParams.DEFAULT;
sTimeRecorder = new TimeRecorder(sParams, sGestureStrokeParams);
- }
- public static void setParameters(final TypedArray mainKeyboardViewAttr) {
- sParams = new PointerTrackerParams(mainKeyboardViewAttr);
- sGestureStrokeParams = new GestureStrokeParams(mainKeyboardViewAttr);
- sGesturePreviewParams = new GestureStrokePreviewParams(mainKeyboardViewAttr);
- sTimeRecorder = new TimeRecorder(sParams, sGestureStrokeParams);
+ sTimerProxy = timerProxy;
+ sDrawingProxy = drawingProxy;
+ sDefaultKeyDetector = defaultKeyDetector;
}
private static void updateGestureHandlingMode() {
@@ -413,20 +379,20 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
updateGestureHandlingMode();
}
- public static PointerTracker getPointerTracker(final int id, final KeyEventHandler handler) {
+ public static PointerTracker getPointerTracker(final int id) {
final ArrayList<PointerTracker> trackers = sTrackers;
// Create pointer trackers until we can get 'id+1'-th tracker, if needed.
for (int i = trackers.size(); i <= id; i++) {
- final PointerTracker tracker = new PointerTracker(i, handler);
+ final PointerTracker tracker = new PointerTracker(i);
trackers.add(tracker);
}
return trackers.get(id);
}
- public static boolean isAnyInSlidingKeyInput() {
- return sPointerTrackerQueue.isAnyInSlidingKeyInput();
+ public static boolean isAnyInDraggingFinger() {
+ return sPointerTrackerQueue.isAnyInDraggingFinger();
}
public static void cancelAllPointerTrackers() {
@@ -434,11 +400,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
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);
- tracker.mListener = listener;
- }
+ sListener = listener;
}
public static void setKeyDetector(final KeyDetector keyDetector) {
@@ -446,8 +408,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
for (int i = 0; i < trackersSize; ++i) {
final PointerTracker tracker = sTrackers.get(i);
tracker.setKeyDetectorInner(keyDetector);
- // Mark that keyboard layout has been changed.
- tracker.mKeyboardLayoutHasBeenChanged = true;
}
final Keyboard keyboard = keyDetector.getKeyboard();
sGestureHandlingEnabledByInputField = !keyboard.mId.passwordInput();
@@ -458,7 +418,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
final int trackersSize = sTrackers.size();
for (int i = 0; i < trackersSize; ++i) {
final PointerTracker tracker = sTrackers.get(i);
- tracker.setReleasedKeyGraphics(tracker.mCurrentKey);
+ tracker.setReleasedKeyGraphics(tracker.getKey());
}
}
@@ -466,28 +426,15 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
final int trackersSize = sTrackers.size();
for (int i = 0; i < trackersSize; ++i) {
final PointerTracker tracker = sTrackers.get(i);
- if (tracker.isShowingMoreKeysPanel()) {
- tracker.mMoreKeysPanel.dismissMoreKeysPanel();
- tracker.mMoreKeysPanel = null;
- }
+ tracker.dismissMoreKeysPanel();
}
}
- private PointerTracker(final int id, final KeyEventHandler handler) {
- if (handler == null) {
- throw new NullPointerException();
- }
+ private PointerTracker(final int id) {
mPointerId = id;
mGestureStrokeWithPreviewPoints = new GestureStrokeWithPreviewPoints(
id, sGestureStrokeParams, sGesturePreviewParams);
- setKeyEventHandler(handler);
- }
-
- private void setKeyEventHandler(final KeyEventHandler handler) {
- setKeyDetectorInner(handler.getKeyDetector());
- mListener = handler.getKeyboardActionListener();
- mDrawingProxy = handler.getDrawingProxy();
- mTimerProxy = handler.getTimerProxy();
+ setKeyDetectorInner(sDefaultKeyDetector);
}
// Returns true if keyboard has been changed by this callback.
@@ -500,7 +447,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
if (sInGesture || mIsDetectingGesture || mIsTrackingForActionDisabled) {
return false;
}
- final boolean ignoreModifierKey = mIsInSlidingKeyInput && key.isModifier();
+ final boolean ignoreModifierKey = mIsInDraggingFinger && key.isModifier();
if (DEBUG_LISTENER) {
Log.d(TAG, String.format("[%d] onPress : %s%s%s%s", mPointerId,
KeyDetector.printableCode(key),
@@ -512,10 +459,10 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
return false;
}
if (key.isEnabled()) {
- mListener.onPressKey(key.getCode(), repeatCount, getActivePointerTrackerCount() == 1);
+ sListener.onPressKey(key.getCode(), repeatCount, getActivePointerTrackerCount() == 1);
final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged;
mKeyboardLayoutHasBeenChanged = false;
- mTimerProxy.startTypingStateTimer(key);
+ sTimerProxy.startTypingStateTimer(key);
return keyboardLayoutHasBeenChanged;
}
return false;
@@ -525,8 +472,8 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
// primaryCode is different from {@link Key#mCode}.
private void callListenerOnCodeInput(final Key key, final int primaryCode, final int x,
final int y, final long eventTime) {
- final boolean ignoreModifierKey = mIsInSlidingKeyInput && key.isModifier();
- final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState();
+ final boolean ignoreModifierKey = mIsInDraggingFinger && key.isModifier();
+ final boolean altersCode = key.altCodeWhileTyping() && sTimerProxy.isTypingState();
final int code = altersCode ? key.getAltCode() : primaryCode;
if (DEBUG_LISTENER) {
final String output = code == Constants.CODE_OUTPUT_TEXT
@@ -546,9 +493,9 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
if (key.isEnabled() || altersCode) {
sTimeRecorder.onCodeInput(code, eventTime);
if (code == Constants.CODE_OUTPUT_TEXT) {
- mListener.onTextInput(key.getOutputText());
+ sListener.onTextInput(key.getOutputText());
} else if (code != Constants.CODE_UNSPECIFIED) {
- mListener.onCodeInput(code, x, y);
+ sListener.onCodeInput(code, x, y);
}
}
}
@@ -561,7 +508,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
if (sInGesture || mIsDetectingGesture || mIsTrackingForActionDisabled) {
return;
}
- final boolean ignoreModifierKey = mIsInSlidingKeyInput && key.isModifier();
+ final boolean ignoreModifierKey = mIsInDraggingFinger && key.isModifier();
if (DEBUG_LISTENER) {
Log.d(TAG, String.format("[%d] onRelease : %s%s%s%s", mPointerId,
Constants.printableCode(primaryCode),
@@ -576,7 +523,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
return;
}
if (key.isEnabled()) {
- mListener.onReleaseKey(primaryCode, withSliding);
+ sListener.onReleaseKey(primaryCode, withSliding);
}
}
@@ -584,7 +531,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
if (DEBUG_LISTENER) {
Log.d(TAG, String.format("[%d] onFinishSlidingInput", mPointerId));
}
- mListener.onFinishSlidingInput();
+ sListener.onFinishSlidingInput();
}
private void callListenerOnCancelInput() {
@@ -594,7 +541,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.pointerTracker_callListenerOnCancelInput();
}
- mListener.onCancelInput();
+ sListener.onCancelInput();
}
private void setKeyDetectorInner(final KeyDetector keyDetector) {
@@ -604,23 +551,25 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
mKeyDetector = keyDetector;
mKeyboard = keyDetector.getKeyboard();
+ // Mark that keyboard layout has been changed.
+ mKeyboardLayoutHasBeenChanged = true;
final int keyWidth = mKeyboard.mMostCommonKeyWidth;
final int keyHeight = mKeyboard.mMostCommonKeyHeight;
mGestureStrokeWithPreviewPoints.setKeyboardGeometry(keyWidth, mKeyboard.mOccupiedHeight);
final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY);
if (newKey != mCurrentKey) {
- if (mDrawingProxy != null) {
+ if (sDrawingProxy != null) {
setReleasedKeyGraphics(mCurrentKey);
}
// Keep {@link #mCurrentKey} that comes from previous keyboard.
}
- mPhantonSuddenMoveThreshold = (int)(keyWidth * PHANTOM_SUDDEN_MOVE_THRESHOLD);
+ mPhantomSuddenMoveThreshold = (int)(keyWidth * PHANTOM_SUDDEN_MOVE_THRESHOLD);
mBogusMoveEventDetector.setKeyboardGeometry(keyWidth, keyHeight);
}
@Override
- public boolean isInSlidingKeyInput() {
- return mIsInSlidingKeyInput;
+ public boolean isInDraggingFinger() {
+ return mIsInDraggingFinger;
}
public Key getKey() {
@@ -637,7 +586,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
private void setReleasedKeyGraphics(final Key key) {
- mDrawingProxy.dismissKeyPreview(this);
+ sDrawingProxy.dismissKeyPreview(key);
if (key == null) {
return;
}
@@ -678,14 +627,14 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
// Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state.
- final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState();
+ final boolean altersCode = key.altCodeWhileTyping() && sTimerProxy.isTypingState();
final boolean needsToUpdateGraphics = key.isEnabled() || altersCode;
if (!needsToUpdateGraphics) {
return;
}
if (!key.noKeyPreview() && !sInGesture && !needsToSuppressKeyPreviewPopup(eventTime)) {
- mDrawingProxy.showKeyPreview(this);
+ sDrawingProxy.showKeyPreview(key);
}
updatePressKeyGraphics(key);
@@ -697,7 +646,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
}
- if (key.altCodeWhileTyping() && mTimerProxy.isTypingState()) {
+ if (key.altCodeWhileTyping() && sTimerProxy.isTypingState()) {
final int altCode = key.getAltCode();
final Key altKey = mKeyboard.getKey(altCode);
if (altKey != null) {
@@ -711,14 +660,14 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
}
- private void updateReleaseKeyGraphics(final Key key) {
+ private static void updateReleaseKeyGraphics(final Key key) {
key.onReleased();
- mDrawingProxy.invalidateKey(key);
+ sDrawingProxy.invalidateKey(key);
}
- private void updatePressKeyGraphics(final Key key) {
+ private static void updatePressKeyGraphics(final Key key) {
key.onPressed();
- mDrawingProxy.invalidateKey(key);
+ sDrawingProxy.invalidateKey(key);
}
public GestureStrokeWithPreviewPoints getGestureStrokeWithPreviewPoints() {
@@ -766,7 +715,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
return newKey;
}
- private static int getActivePointerTrackerCount() {
+ /* package */ static int getActivePointerTrackerCount() {
return sPointerTrackerQueue.size();
}
@@ -789,12 +738,12 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
sAggregratedPointers.reset();
sLastRecognitionPointSize = 0;
sLastRecognitionTime = 0;
- mListener.onStartBatchInput();
+ sListener.onStartBatchInput();
dismissAllMoreKeysPanels();
}
- mTimerProxy.cancelLongPressTimer();
+ sTimerProxy.cancelLongPressTimerOf(this);
// A gesture floating preview text will be shown at the oldest pointer/finger on the screen.
- mDrawingProxy.showGestureTrail(
+ sDrawingProxy.showGestureTrail(
this, isOldestTrackerInQueue() /* showsFloatingPreviewText */);
}
@@ -812,7 +761,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
return;
}
// A gesture floating preview text will be shown at the oldest pointer/finger on the screen.
- mDrawingProxy.showGestureTrail(
+ sDrawingProxy.showGestureTrail(
this, isOldestTrackerInQueue() /* showsFloatingPreviewText */);
}
@@ -827,8 +776,8 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
Log.d(TAG, String.format("[%d] onUpdateBatchInput: batchPoints=%d", mPointerId,
size));
}
- mTimerProxy.startUpdateBatchInputTimer(this);
- mListener.onUpdateBatchInput(sAggregratedPointers);
+ sTimerProxy.startUpdateBatchInputTimer(this);
+ sListener.onUpdateBatchInput(sAggregratedPointers);
// The listener may change the size of the pointers (when auto-committing
// for example), so we need to get the size from the pointers again.
sLastRecognitionPointSize = sAggregratedPointers.getPointerSize();
@@ -843,13 +792,13 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
if (getActivePointerTrackerCount() == 1) {
sInGesture = false;
sTimeRecorder.onEndBatchInput(eventTime);
- mTimerProxy.cancelAllUpdateBatchInputTimers();
+ sTimerProxy.cancelAllUpdateBatchInputTimers();
if (!mIsTrackingForActionDisabled) {
if (DEBUG_LISTENER) {
Log.d(TAG, String.format("[%d] onEndBatchInput : batchPoints=%d",
mPointerId, sAggregratedPointers.getPointerSize()));
}
- mListener.onEndBatchInput(sAggregratedPointers);
+ sListener.onEndBatchInput(sAggregratedPointers);
}
}
}
@@ -857,7 +806,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
return;
}
// A gesture floating preview text will be shown at the oldest pointer/finger on the screen.
- mDrawingProxy.showGestureTrail(
+ sDrawingProxy.showGestureTrail(
this, isOldestTrackerInQueue() /* showsFloatingPreviewText */);
}
@@ -871,19 +820,26 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
if (DEBUG_LISTENER) {
Log.d(TAG, String.format("[%d] onCancelBatchInput", mPointerId));
}
- mListener.onCancelBatchInput();
+ sListener.onCancelBatchInput();
}
- public void processMotionEvent(final MotionEvent me, final KeyEventHandler handler) {
+ public void processMotionEvent(final MotionEvent me, final KeyDetector keyDetector) {
final int action = me.getActionMasked();
final long eventTime = me.getEventTime();
if (action == MotionEvent.ACTION_MOVE) {
+ // When this pointer is the only active pointer and is showing a more keys panel,
+ // we should ignore other pointers' motion event.
+ final boolean shouldIgnoreOtherPointers =
+ isShowingMoreKeysPanel() && getActivePointerTrackerCount() == 1;
final int pointerCount = me.getPointerCount();
for (int index = 0; index < pointerCount; index++) {
final int id = me.getPointerId(index);
- final PointerTracker tracker = getPointerTracker(id, handler);
+ if (shouldIgnoreOtherPointers && id != mPointerId) {
+ continue;
+ }
final int x = (int)me.getX(index);
final int y = (int)me.getY(index);
+ final PointerTracker tracker = getPointerTracker(id);
tracker.onMoveEvent(x, y, eventTime, me);
}
return;
@@ -894,7 +850,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
- onDownEvent(x, y, eventTime, handler);
+ onDownEvent(x, y, eventTime, keyDetector);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
@@ -907,11 +863,11 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
private void onDownEvent(final int x, final int y, final long eventTime,
- final KeyEventHandler handler) {
+ final KeyDetector keyDetector) {
if (DEBUG_EVENT) {
printTouchEvent("onDownEvent:", x, y, eventTime);
}
- setKeyEventHandler(handler);
+ setKeyDetectorInner(keyDetector);
// Naive up-to-down noise filter.
final long deltaT = eventTime - mUpTime;
if (deltaT < sParams.mTouchNoiseThresholdTime) {
@@ -954,20 +910,29 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
}
- private boolean isShowingMoreKeysPanel() {
+ /* package */ boolean isShowingMoreKeysPanel() {
return (mMoreKeysPanel != null);
}
+ private void dismissMoreKeysPanel() {
+ if (isShowingMoreKeysPanel()) {
+ mMoreKeysPanel.dismissMoreKeysPanel();
+ mMoreKeysPanel = null;
+ }
+ }
+
private void onDownEventInternal(final int x, final int y, final long eventTime) {
Key key = onDownKey(x, y, eventTime);
- // Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding
- // from modifier key, or 3) this pointer's KeyDetector always allows sliding input.
- mIsAllowedSlidingKeyInput = sParams.mSlidingKeyInputEnabled
+ // Key selection by dragging finger is allowed when 1) key selection by dragging finger is
+ // enabled by configuration, 2) this pointer starts dragging from modifier key, or 3) this
+ // pointer's KeyDetector always allows key selection by dragging finger, such as
+ // {@link MoreKeysKeyboard}.
+ mIsAllowedDraggingFinger = sParams.mKeySelectionByDraggingFinger
|| (key != null && key.isModifier())
- || mKeyDetector.alwaysAllowsSlidingInput();
+ || mKeyDetector.alwaysAllowsKeySelectionByDraggingFinger();
mKeyboardLayoutHasBeenChanged = false;
mIsTrackingForActionDisabled = false;
- resetSlidingKeyInput();
+ resetKeySelectionByDraggingFinger();
if (key != null) {
// This onPress call may have changed keyboard layout. Those cases are detected at
// {@link #setKeyboard}. In those cases, we should update key according to the new
@@ -982,17 +947,17 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
}
- private void startSlidingKeyInput(final Key key) {
- if (!mIsInSlidingKeyInput) {
- mIsInSlidingKeyInputFromModifier = key.isModifier();
+ private void startKeySelectionByDraggingFinger(final Key key) {
+ if (!mIsInDraggingFinger) {
+ mIsInSlidingKeyInput = key.isModifier();
}
- mIsInSlidingKeyInput = true;
+ mIsInDraggingFinger = true;
}
- private void resetSlidingKeyInput() {
+ private void resetKeySelectionByDraggingFinger() {
+ mIsInDraggingFinger = false;
mIsInSlidingKeyInput = false;
- mIsInSlidingKeyInputFromModifier = false;
- mDrawingProxy.dismissSlidingKeyInputPreview();
+ sDrawingProxy.dismissSlidingKeyInputPreview();
}
private void onGestureMoveEvent(final int x, final int y, final long eventTime,
@@ -1003,7 +968,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
final boolean onValidArea = mGestureStrokeWithPreviewPoints.addPointOnKeyboard(
x, y, gestureTime, isMajorEvent);
if (mGestureStrokeWithPreviewPoints.getLength() > beforeLength) {
- mTimerProxy.startUpdateBatchInputTimer(this);
+ sTimerProxy.startUpdateBatchInputTimer(this);
}
// If the move event goes out from valid batch input area, cancel batch input.
if (!onValidArea) {
@@ -1048,15 +1013,15 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
final int translatedY = mMoreKeysPanel.translateY(y);
mMoreKeysPanel.onMoveEvent(translatedX, translatedY, mPointerId, eventTime);
onMoveKey(x, y);
- if (mIsInSlidingKeyInputFromModifier) {
- mDrawingProxy.showSlidingKeyInputPreview(this);
+ if (mIsInSlidingKeyInput) {
+ sDrawingProxy.showSlidingKeyInputPreview(this);
}
return;
}
onMoveEventInternal(x, y, eventTime);
}
- private void processSlidingKeyInput(final Key newKey, final int x, final int y,
+ private void processDraggingFingerInToNewKey(final Key newKey, final int x, final int y,
final long eventTime) {
// This onPress call may have changed keyboard layout. Those cases are detected
// at {@link #setKeyboard}. In those cases, we should update key according
@@ -1110,29 +1075,29 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
onDownEventInternal(x, y, eventTime);
}
- private void processSildeOutFromOldKey(final Key oldKey) {
+ private void processDraggingFingerOutFromOldKey(final Key oldKey) {
setReleasedKeyGraphics(oldKey);
callListenerOnRelease(oldKey, oldKey.getCode(), true /* withSliding */);
- startSlidingKeyInput(oldKey);
- mTimerProxy.cancelKeyTimers();
+ startKeySelectionByDraggingFinger(oldKey);
+ sTimerProxy.cancelKeyTimersOf(this);
}
- private void slideFromOldKeyToNewKey(final Key key, final int x, final int y,
+ private void dragFingerFromOldKeyToNewKey(final Key key, final int x, final int y,
final long eventTime, final Key oldKey, final int lastX, final int lastY) {
// The pointer has been slid in to the new key from the previous key, we must call
// onRelease() first to notify that the previous key has been released, then call
// onPress() to notify that the new key is being pressed.
- processSildeOutFromOldKey(oldKey);
+ processDraggingFingerOutFromOldKey(oldKey);
startRepeatKey(key);
- if (mIsAllowedSlidingKeyInput) {
- processSlidingKeyInput(key, x, y, eventTime);
+ if (mIsAllowedDraggingFinger) {
+ processDraggingFingerInToNewKey(key, x, y, eventTime);
}
// HACK: On some devices, quick successive touches may be reported as a sudden move by
// touch panel firmware. This hack detects such cases and translates the move event to
// successive up and down events.
// TODO: Should find a way to balance gesture detection and this hack.
else if (sNeedsPhantomSuddenMoveEventHack
- && getDistance(x, y, lastX, lastY) >= mPhantonSuddenMoveThreshold) {
+ && getDistance(x, y, lastX, lastY) >= mPhantomSuddenMoveThreshold) {
processPhantomSuddenMoveHack(key, x, y, eventTime, oldKey, lastX, lastY);
}
// HACK: On some devices, quick successive proximate touches may be reported as a bogus
@@ -1163,11 +1128,11 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
}
- private void slideOutFromOldKey(final Key oldKey, final int x, final int y) {
+ private void dragFingerOutFromOldKey(final Key oldKey, final int x, final int y) {
// The pointer has been slid out from the previous key, we must call onRelease() to
// notify that the previous key has been released.
- processSildeOutFromOldKey(oldKey);
- if (mIsAllowedSlidingKeyInput) {
+ processDraggingFingerOutFromOldKey(oldKey);
+ if (mIsAllowedDraggingFinger) {
onMoveToNewKey(null, x, y);
} else {
if (!mIsDetectingGesture) {
@@ -1194,19 +1159,19 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
if (newKey != null) {
if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, eventTime, newKey)) {
- slideFromOldKeyToNewKey(newKey, x, y, eventTime, oldKey, lastX, lastY);
+ dragFingerFromOldKeyToNewKey(newKey, x, y, eventTime, oldKey, lastX, lastY);
} else if (oldKey == null) {
// The pointer has been slid in to the new key, but the finger was not on any keys.
// In this case, we must call onPress() to notify that the new key is being pressed.
- processSlidingKeyInput(newKey, x, y, eventTime);
+ processDraggingFingerInToNewKey(newKey, x, y, eventTime);
}
} else { // newKey == null
if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, eventTime, newKey)) {
- slideOutFromOldKey(oldKey, x, y);
+ dragFingerOutFromOldKey(oldKey, x, y);
}
}
- if (mIsInSlidingKeyInputFromModifier) {
- mDrawingProxy.showSlidingKeyInputPreview(this);
+ if (mIsInSlidingKeyInput) {
+ sDrawingProxy.showSlidingKeyInputPreview(this);
}
}
@@ -1215,7 +1180,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
printTouchEvent("onUpEvent :", x, y, eventTime);
}
- mTimerProxy.cancelUpdateBatchInputTimer(this);
+ sTimerProxy.cancelUpdateBatchInputTimer(this);
if (!sInGesture) {
if (mCurrentKey != null && mCurrentKey.isModifier()) {
// Before processing an up event of modifier key, all pointers already being
@@ -1237,18 +1202,15 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
if (DEBUG_EVENT) {
printTouchEvent("onPhntEvent:", mLastX, mLastY, eventTime);
}
- if (isShowingMoreKeysPanel()) {
- return;
- }
onUpEventInternal(mLastX, mLastY, eventTime);
cancelTrackingForAction();
}
private void onUpEventInternal(final int x, final int y, final long eventTime) {
- mTimerProxy.cancelKeyTimers();
+ sTimerProxy.cancelKeyTimersOf(this);
+ final boolean isInDraggingFinger = mIsInDraggingFinger;
final boolean isInSlidingKeyInput = mIsInSlidingKeyInput;
- final boolean isInSlidingKeyInputFromModifier = mIsInSlidingKeyInputFromModifier;
- resetSlidingKeyInput();
+ resetKeySelectionByDraggingFinger();
mIsDetectingGesture = false;
final Key currentKey = mCurrentKey;
mCurrentKey = null;
@@ -1280,11 +1242,11 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
return;
}
if (currentKey != null && currentKey.isRepeatable()
- && (currentKey.getCode() == currentRepeatingKeyCode) && !isInSlidingKeyInput) {
+ && (currentKey.getCode() == currentRepeatingKeyCode) && !isInDraggingFinger) {
return;
}
detectAndSendKey(currentKey, mKeyX, mKeyY, eventTime);
- if (isInSlidingKeyInputFromModifier) {
+ if (isInSlidingKeyInput) {
callListenerOnFinishSlidingInput();
}
}
@@ -1306,7 +1268,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
public void onLongPressed() {
- resetSlidingKeyInput();
+ resetKeySelectionByDraggingFinger();
cancelTrackingForAction();
setReleasedKeyGraphics(mCurrentKey);
sPointerTrackerQueue.remove(this);
@@ -1324,9 +1286,9 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
private void onCancelEventInternal() {
- mTimerProxy.cancelKeyTimers();
+ sTimerProxy.cancelKeyTimersOf(this);
setReleasedKeyGraphics(mCurrentKey);
- resetSlidingKeyInput();
+ resetKeySelectionByDraggingFinger();
if (isShowingMoreKeysPanel()) {
mMoreKeysPanel.dismissMoreKeysPanel();
mMoreKeysPanel = null;
@@ -1347,7 +1309,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
// Here curKey points to the different key from newKey.
final int keyHysteresisDistanceSquared = mKeyDetector.getKeyHysteresisDistanceSquared(
- mIsInSlidingKeyInputFromModifier);
+ mIsInSlidingKeyInput);
final int distanceFromKeyEdgeSquared = curKey.squaredDistanceToEdge(x, y);
if (distanceFromKeyEdgeSquared >= keyHysteresisDistanceSquared) {
if (DEBUG_MODE) {
@@ -1358,7 +1320,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
}
return true;
}
- if (sNeedsProximateBogusDownMoveUpEventHack && !mIsAllowedSlidingKeyInput
+ if (sNeedsProximateBogusDownMoveUpEventHack && !mIsAllowedDraggingFinger
&& sTimeRecorder.isInFastTyping(eventTime)
&& mBogusMoveEventDetector.hasTraveledLongDistance(x, y)) {
if (DEBUG_MODE) {
@@ -1380,26 +1342,26 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
if (key == null) return;
if (!key.isLongPressEnabled()) return;
// Caveat: Please note that isLongPressEnabled() can be true even if the current key
- // doesn't have its more keys. (e.g. spacebar, globe key)
+ // doesn't have its more keys. (e.g. spacebar, globe key) If we are in the dragging finger
+ // mode, we will disable long press timer of such key.
// We always need to start the long press timer if the key has its more keys regardless of
- // whether or not we are in the sliding input mode.
- if (mIsInSlidingKeyInput && key.getMoreKeys() == null) return;
- final int delay;
- switch (key.getCode()) {
- case Constants.CODE_SHIFT:
- delay = sParams.mLongPressShiftLockTimeout;
- break;
- default:
- final int longpressTimeout = Settings.getInstance().getCurrent().mKeyLongpressTimeout;
- if (mIsInSlidingKeyInputFromModifier) {
- // We use longer timeout for sliding finger input started from the modifier key.
- delay = longpressTimeout * MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT;
- } else {
- delay = longpressTimeout;
- }
- break;
+ // whether or not we are in the dragging finger mode.
+ if (mIsInDraggingFinger && key.getMoreKeys() == null) return;
+
+ final int delay = getLongPressTimeout(key.getCode());
+ sTimerProxy.startLongPressTimerOf(this, delay);
+ }
+
+ private int getLongPressTimeout(final int code) {
+ if (code == Constants.CODE_SHIFT) {
+ return sParams.mLongPressShiftLockTimeout;
+ }
+ final int longpressTimeout = Settings.getInstance().getCurrent().mKeyLongpressTimeout;
+ if (mIsInSlidingKeyInput) {
+ // We use longer timeout for sliding finger input started from the modifier key.
+ return longpressTimeout * MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT;
}
- mTimerProxy.startLongPressTimer(this, delay);
+ return longpressTimeout;
}
private void detectAndSendKey(final Key key, final int x, final int y, final long eventTime) {
@@ -1417,10 +1379,10 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
if (sInGesture) return;
if (key == null) return;
if (!key.isRepeatable()) return;
- // Don't start key repeat when we are in sliding input mode.
- if (mIsInSlidingKeyInput) return;
+ // Don't start key repeat when we are in the dragging finger mode.
+ if (mIsInDraggingFinger) return;
final int startRepeatCount = 1;
- mTimerProxy.startKeyRepeatTimer(this, startRepeatCount, sParams.mKeyRepeatStartTimeout);
+ startKeyRepeatTimer(startRepeatCount);
}
public void onKeyRepeat(final int code, final int repeatCount) {
@@ -1432,11 +1394,17 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
mCurrentRepeatingKeyCode = code;
mIsDetectingGesture = false;
final int nextRepeatCount = repeatCount + 1;
- mTimerProxy.startKeyRepeatTimer(this, nextRepeatCount, sParams.mKeyRepeatInterval);
+ startKeyRepeatTimer(nextRepeatCount);
callListenerOnPressAndCheckKeyboardLayoutChange(key, repeatCount);
callListenerOnCodeInput(key, code, mKeyX, mKeyY, SystemClock.uptimeMillis());
}
+ private void startKeyRepeatTimer(final int repeatCount) {
+ final int delay =
+ (repeatCount == 1) ? sParams.mKeyRepeatStartTimeout : sParams.mKeyRepeatInterval;
+ sTimerProxy.startKeyRepeatTimerOf(this, repeatCount, delay);
+ }
+
private void printTouchEvent(final String title, final int x, final int y,
final long eventTime) {
final Key key = mKeyDetector.detectHitKey(x, y);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/CustomViewPager.java b/java/src/com/android/inputmethod/keyboard/internal/CustomViewPager.java
new file mode 100644
index 000000000..f5cc45ca7
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/CustomViewPager.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2013 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.support.v4.view.ViewPager;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+
+/**
+ * Custom view pager to prevent {@link ViewPager} from crashing while handling multi-touch
+ * event.
+ */
+public class CustomViewPager extends ViewPager {
+ private static final String TAG = CustomViewPager.class.getSimpleName();
+
+ public CustomViewPager(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(final MotionEvent event) {
+ // This only happens when you multi-touch, take the first finger off and move.
+ // Unfortunately this causes {@link ViewPager} to crash, so we will ignore such events.
+ if (event.getAction() == MotionEvent.ACTION_MOVE && event.getPointerId(0) != 0) {
+ Log.w(TAG, "Ignored multi-touch move event to prevent ViewPager from crashing");
+ return false;
+ }
+
+ return super.onInterceptTouchEvent(event);
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/DrawingHandler.java b/java/src/com/android/inputmethod/keyboard/internal/DrawingHandler.java
new file mode 100644
index 000000000..df82becae
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/DrawingHandler.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2013 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.os.Message;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.internal.DrawingHandler.Callbacks;
+import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
+
+// TODO: Separate this class into KeyPreviewHandler and BatchInputPreviewHandler or so.
+public class DrawingHandler extends LeakGuardHandlerWrapper<Callbacks> {
+ public interface Callbacks {
+ public void dismissKeyPreviewWithoutDelay(Key key);
+ public void dismissAllKeyPreviews();
+ public void showGestureFloatingPreviewText(SuggestedWords suggestedWords);
+ }
+
+ private static final int MSG_DISMISS_KEY_PREVIEW = 0;
+ private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
+
+ public DrawingHandler(final Callbacks ownerInstance) {
+ super(ownerInstance);
+ }
+
+ @Override
+ public void handleMessage(final Message msg) {
+ final Callbacks callbacks = getOwnerInstance();
+ if (callbacks == null) {
+ return;
+ }
+ switch (msg.what) {
+ case MSG_DISMISS_KEY_PREVIEW:
+ callbacks.dismissKeyPreviewWithoutDelay((Key)msg.obj);
+ break;
+ case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT:
+ callbacks.showGestureFloatingPreviewText(SuggestedWords.EMPTY);
+ break;
+ }
+ }
+
+ public void dismissKeyPreview(final long delay, final Key key) {
+ sendMessageDelayed(obtainMessage(MSG_DISMISS_KEY_PREVIEW, key), delay);
+ }
+
+ private void cancelAllDismissKeyPreviews() {
+ removeMessages(MSG_DISMISS_KEY_PREVIEW);
+ final Callbacks callbacks = getOwnerInstance();
+ if (callbacks == null) {
+ return;
+ }
+ callbacks.dismissAllKeyPreviews();
+ }
+
+ public void dismissGestureFloatingPreviewText(final long delay) {
+ sendMessageDelayed(obtainMessage(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT), delay);
+ }
+
+ public void cancelAllMessages() {
+ cancelAllDismissKeyPreviews();
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java b/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
index 3133e54be..e2fd39017 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
@@ -25,7 +25,7 @@ import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.utils.CollectionUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.utils.JsonUtils;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -53,7 +53,7 @@ public class DynamicGridKeyboard extends Keyboard {
private Key[] mCachedGridKeys;
public DynamicGridKeyboard(final SharedPreferences prefs, final Keyboard templateKeyboard,
- final int maxKeyCount, final int categoryId, final int categoryPageId) {
+ final int maxKeyCount, final int categoryId) {
super(templateKeyboard);
final Key key0 = getTemplateKey(TEMPLATE_KEY_CODE_0);
final Key key1 = getTemplateKey(TEMPLATE_KEY_CODE_1);
@@ -124,7 +124,7 @@ public class DynamicGridKeyboard extends Keyboard {
final int keyY0 = getKeyY0(index);
final int keyX1 = getKeyX1(index);
final int keyY1 = getKeyY1(index);
- gridKey.updateCorrdinates(keyX0, keyY0, keyX1, keyY1);
+ gridKey.updateCoordinates(keyX0, keyY0, keyX1, keyY1);
index++;
}
}
@@ -139,36 +139,48 @@ public class DynamicGridKeyboard extends Keyboard {
keys.add(key.getCode());
}
}
- final String jsonStr = StringUtils.listToJsonStr(keys);
+ final String jsonStr = JsonUtils.listToJsonStr(keys);
Settings.writeEmojiRecentKeys(mPrefs, jsonStr);
}
- private static Key getKey(final Collection<DynamicGridKeyboard> keyboards, final Object o) {
+ private static Key getKeyByCode(final Collection<DynamicGridKeyboard> keyboards,
+ final int code) {
+ for (final DynamicGridKeyboard keyboard : keyboards) {
+ final Key key = keyboard.getKey(code);
+ if (key != null) {
+ return key;
+ }
+ }
+ return null;
+ }
+
+ private static Key getKeyByOutputText(final Collection<DynamicGridKeyboard> keyboards,
+ final String outputText) {
for (final DynamicGridKeyboard kbd : keyboards) {
- if (o instanceof Integer) {
- final int code = (Integer) o;
- final Key key = kbd.getKey(code);
- if (key != null) {
- return key;
- }
- } else if (o instanceof String) {
- final String outputText = (String) o;
- final Key key = kbd.getKeyFromOutputText(outputText);
- if (key != null) {
- return key;
- }
- } else {
- Log.w(TAG, "Invalid object: " + o);
+ final Key key = kbd.getKeyFromOutputText(outputText);
+ if (key != null) {
+ return key;
}
}
return null;
}
- public void loadRecentKeys(Collection<DynamicGridKeyboard> keyboards) {
+ public void loadRecentKeys(final Collection<DynamicGridKeyboard> keyboards) {
final String str = Settings.readEmojiRecentKeys(mPrefs);
- final List<Object> keys = StringUtils.jsonStrToList(str);
+ final List<Object> keys = JsonUtils.jsonStrToList(str);
for (final Object o : keys) {
- addKeyLast(getKey(keyboards, o));
+ final Key key;
+ if (o instanceof Integer) {
+ final int code = (Integer)o;
+ key = getKeyByCode(keyboards, code);
+ } else if (o instanceof String) {
+ final String outputText = (String)o;
+ key = getKeyByOutputText(keyboards, outputText);
+ } else {
+ Log.w(TAG, "Invalid object: " + o);
+ continue;
+ }
+ addKeyLast(key);
}
}
@@ -217,7 +229,7 @@ public class DynamicGridKeyboard extends Keyboard {
super(originalKey);
}
- public void updateCorrdinates(final int x0, final int y0, final int x1, final int y1) {
+ public void updateCoordinates(final int x0, final int y0, final int x1, final int y1) {
mCurrentX = x0;
mCurrentY = y0;
getHitBox().set(x0, y0, x1, y1);
diff --git a/java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java b/java/src/com/android/inputmethod/keyboard/internal/EmojiLayoutParams.java
index 967448c28..12e063261 100644
--- a/java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/EmojiLayoutParams.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.inputmethod.keyboard;
+package com.android.inputmethod.keyboard.internal;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.utils.ResourceUtils;
@@ -37,22 +37,22 @@ public class EmojiLayoutParams {
private final int mBottomPadding;
private final int mTopPadding;
- public EmojiLayoutParams(Resources res) {
+ public EmojiLayoutParams(final Resources res) {
final int defaultKeyboardHeight = ResourceUtils.getDefaultKeyboardHeight(res);
final int defaultKeyboardWidth = ResourceUtils.getDefaultKeyboardWidth(res);
- mKeyVerticalGap = (int) res.getFraction(R.fraction.key_bottom_gap_holo,
- (int) defaultKeyboardHeight, (int) defaultKeyboardHeight);
- mBottomPadding = (int) res.getFraction(R.fraction.keyboard_bottom_padding_holo,
- (int) defaultKeyboardHeight, (int) defaultKeyboardHeight);
- mTopPadding = (int) res.getFraction(R.fraction.keyboard_top_padding_holo,
- (int) defaultKeyboardHeight, (int) defaultKeyboardHeight);
- mKeyHorizontalGap = (int) (res.getFraction(R.fraction.key_horizontal_gap_holo,
+ mKeyVerticalGap = (int) res.getFraction(R.fraction.config_key_vertical_gap_holo,
+ defaultKeyboardHeight, defaultKeyboardHeight);
+ mBottomPadding = (int) res.getFraction(R.fraction.config_keyboard_bottom_padding_holo,
+ defaultKeyboardHeight, defaultKeyboardHeight);
+ mTopPadding = (int) res.getFraction(R.fraction.config_keyboard_top_padding_holo,
+ defaultKeyboardHeight, defaultKeyboardHeight);
+ mKeyHorizontalGap = (int) (res.getFraction(R.fraction.config_key_horizontal_gap_holo,
defaultKeyboardWidth, defaultKeyboardWidth));
mEmojiCategoryPageIdViewHeight =
- (int) (res.getDimension(R.dimen.emoji_category_page_id_height));
+ (int) (res.getDimension(R.dimen.config_emoji_category_page_id_height));
final int baseheight = defaultKeyboardHeight - mBottomPadding - mTopPadding
+ mKeyVerticalGap;
- mEmojiActionBarHeight = ((int) baseheight) / DEFAULT_KEYBOARD_ROWS
+ mEmojiActionBarHeight = baseheight / DEFAULT_KEYBOARD_ROWS
- (mKeyVerticalGap - mBottomPadding) / 2;
mEmojiPagerHeight = defaultKeyboardHeight - mEmojiActionBarHeight
- mEmojiCategoryPageIdViewHeight;
@@ -60,26 +60,26 @@ public class EmojiLayoutParams {
mEmojiKeyboardHeight = mEmojiPagerHeight - mEmojiPagerBottomMargin - 1;
}
- public void setPagerProperties(ViewPager vp) {
+ public void setPagerProperties(final ViewPager vp) {
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) vp.getLayoutParams();
lp.height = mEmojiKeyboardHeight;
lp.bottomMargin = mEmojiPagerBottomMargin;
vp.setLayoutParams(lp);
}
- public void setCategoryPageIdViewProperties(LinearLayout ll) {
+ public void setCategoryPageIdViewProperties(final LinearLayout ll) {
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) ll.getLayoutParams();
lp.height = mEmojiCategoryPageIdViewHeight;
ll.setLayoutParams(lp);
}
- public void setActionBarProperties(LinearLayout ll) {
+ public void setActionBarProperties(final LinearLayout ll) {
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) ll.getLayoutParams();
lp.height = mEmojiActionBarHeight - mBottomPadding;
ll.setLayoutParams(lp);
}
- public void setKeyProperties(ImageView ib) {
+ public void setKeyProperties(final ImageView ib) {
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) ib.getLayoutParams();
lp.leftMargin = mKeyHorizontalGap / 2;
lp.rightMargin = mKeyHorizontalGap / 2;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/ScrollKeyboardView.java b/java/src/com/android/inputmethod/keyboard/internal/EmojiPageKeyboardView.java
index 9cf68d43d..5a996ff53 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/ScrollKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/EmojiPageKeyboardView.java
@@ -20,25 +20,21 @@ import android.content.Context;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
-import android.widget.ScrollView;
-import android.widget.Scroller;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.KeyDetector;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardView;
+import com.android.inputmethod.keyboard.PointerTracker;
import com.android.inputmethod.latin.R;
/**
- * This is an extended {@link KeyboardView} class that hosts a vertical scroll keyboard.
+ * This is an extended {@link KeyboardView} class that hosts an emoji page keyboard.
* Multi-touch unsupported. No {@link PointerTracker}s. No gesture support.
- * TODO: Vertical scroll capability should be removed from this class because it's no longer used.
*/
// TODO: Implement key popup preview.
-public final class ScrollKeyboardView extends KeyboardView implements
- ScrollViewWithNotifier.ScrollListener, GestureDetector.OnGestureListener {
- private static final boolean PAGINATION = false;
-
+public final class EmojiPageKeyboardView extends KeyboardView implements
+ GestureDetector.OnGestureListener {
public interface OnKeyClickListener {
public void onKeyClick(Key key);
}
@@ -52,63 +48,15 @@ public final class ScrollKeyboardView extends KeyboardView implements
private final KeyDetector mKeyDetector = new KeyDetector(0.0f /*keyHysteresisDistance */);
private final GestureDetector mGestureDetector;
- private final Scroller mScroller;
- private ScrollViewWithNotifier mScrollView;
-
- public ScrollKeyboardView(final Context context, final AttributeSet attrs) {
+ public EmojiPageKeyboardView(final Context context, final AttributeSet attrs) {
this(context, attrs, R.attr.keyboardViewStyle);
}
- public ScrollKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) {
+ public EmojiPageKeyboardView(final Context context, final AttributeSet attrs,
+ final int defStyle) {
super(context, attrs, defStyle);
mGestureDetector = new GestureDetector(context, this);
mGestureDetector.setIsLongpressEnabled(false /* isLongpressEnabled */);
- mScroller = new Scroller(context);
- }
-
- public void setScrollView(final ScrollViewWithNotifier scrollView) {
- mScrollView = scrollView;
- scrollView.setScrollListener(this);
- }
-
- private final Runnable mScrollTask = new Runnable() {
- @Override
- public void run() {
- final Scroller scroller = mScroller;
- final ScrollView scrollView = mScrollView;
- scroller.computeScrollOffset();
- scrollView.scrollTo(0, scroller.getCurrY());
- if (!scroller.isFinished()) {
- scrollView.post(this);
- }
- }
- };
-
- // {@link ScrollViewWithNotified#ScrollListener} methods.
- @Override
- public void notifyScrollChanged(final int scrollX, final int scrollY, final int oldX,
- final int oldY) {
- if (PAGINATION) {
- mScroller.forceFinished(true /* finished */);
- mScrollView.removeCallbacks(mScrollTask);
- final int currentTop = mScrollView.getScrollY();
- final int pageHeight = getKeyboard().mBaseHeight;
- final int lastPageNo = currentTop / pageHeight;
- final int lastPageTop = lastPageNo * pageHeight;
- final int nextPageNo = lastPageNo + 1;
- final int nextPageTop = Math.min(nextPageNo * pageHeight, getHeight() - pageHeight);
- final int scrollTo = (currentTop - lastPageTop) < (nextPageTop - currentTop)
- ? lastPageTop : nextPageTop;
- final int deltaY = scrollTo - currentTop;
- mScroller.startScroll(0, currentTop, 0, deltaY, 300);
- mScrollView.post(mScrollTask);
- }
- }
-
- @Override
- public void notifyOverScrolled(final int scrollX, final int scrollY, final boolean clampedX,
- final boolean clampedY) {
- releaseCurrentKey();
}
public void setOnKeyClickListener(final OnKeyClickListener listener) {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java
index 19e995548..8b413e5e2 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java
@@ -31,7 +31,7 @@ import android.view.View;
import com.android.inputmethod.keyboard.PointerTracker;
import com.android.inputmethod.keyboard.internal.GestureTrail.Params;
import com.android.inputmethod.latin.utils.CollectionUtils;
-import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper;
+import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
/**
* Draw gesture trail preview graphics during gesture.
@@ -52,21 +52,23 @@ public final class GestureTrailsPreview extends AbstractDrawingPreview {
private final DrawingHandler mDrawingHandler;
private static final class DrawingHandler
- extends StaticInnerHandlerWrapper<GestureTrailsPreview> {
+ extends LeakGuardHandlerWrapper<GestureTrailsPreview> {
private static final int MSG_UPDATE_GESTURE_TRAIL = 0;
private final Params mGestureTrailParams;
- public DrawingHandler(final GestureTrailsPreview outerInstance,
+ public DrawingHandler(final GestureTrailsPreview ownerInstance,
final Params gestureTrailParams) {
- super(outerInstance);
+ super(ownerInstance);
mGestureTrailParams = gestureTrailParams;
}
@Override
public void handleMessage(final Message msg) {
- final GestureTrailsPreview preview = getOuterInstance();
- if (preview == null) return;
+ final GestureTrailsPreview preview = getOwnerInstance();
+ if (preview == null) {
+ return;
+ }
switch (msg.what) {
case MSG_UPDATE_GESTURE_TRAIL:
preview.getDrawingView().invalidate();
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
index 22f5b3dd1..accfaedcb 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
@@ -78,10 +78,10 @@ public final class KeySpecParser {
* or has no key specifications.
*/
public static String[] splitKeySpecs(final String text) {
- final int size = text.length();
- if (size == 0) {
+ if (TextUtils.isEmpty(text)) {
return null;
}
+ final int size = text.length();
// Optimization for one-letter key specification.
if (size == 1) {
return text.charAt(0) == COMMA ? null : new String[] { text };
@@ -380,6 +380,9 @@ public final class KeySpecParser {
public static String resolveTextReference(final String rawText,
final KeyboardTextsSet textsSet) {
+ if (TextUtils.isEmpty(rawText)) {
+ return null;
+ }
int level = 0;
String text = rawText;
StringBuilder sb;
@@ -392,7 +395,7 @@ public final class KeySpecParser {
final int prefixLen = PREFIX_TEXT.length();
final int size = text.length();
if (size < prefixLen) {
- return text;
+ return TextUtils.isEmpty(text) ? null : text;
}
sb = null;
@@ -421,7 +424,7 @@ public final class KeySpecParser {
text = sb.toString();
}
} while (sb != null);
- return text;
+ return TextUtils.isEmpty(text) ? null : text;
}
private static int searchTextNameEnd(final String text, final int start) {
@@ -483,7 +486,7 @@ public final class KeySpecParser {
public static int toUpperCaseOfCodeForLocale(final int code, final boolean needsToUpperCase,
final Locale locale) {
if (!Constants.isLetterCode(code) || !needsToUpperCase) return code;
- final String text = new String(new int[] { code } , 0, 1);
+ final String text = StringUtils.newSingleCodePointString(code);
final String casedText = KeySpecParser.toUpperCaseOfStringForLocale(
text, needsToUpperCase, locale);
return StringUtils.codePointCount(casedText) == 1
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
index 05d855e31..f7e43a6c2 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
@@ -27,6 +27,7 @@ import com.android.inputmethod.latin.utils.XmlParseUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import java.util.Arrays;
import java.util.HashMap;
public final class KeyStylesSet {
@@ -90,7 +91,8 @@ public final class KeyStylesSet {
}
final Object value = mStyleAttributes.get(index);
if (value != null) {
- return (String[])value;
+ final String[] array = (String[])value;
+ return Arrays.copyOf(array, array.length);
}
final KeyStyle parentStyle = mStyles.get(mParentStyleName);
return parentStyle.getStringArray(a, index);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java b/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java
index 8bdad364c..c3e0aa685 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java
@@ -47,6 +47,8 @@ public final class KeyVisualAttributes {
public final int mShiftedLetterHintActivatedColor;
public final int mPreviewTextColor;
+ public final float mHintLabelVerticalAdjustment;
+
private static final int[] VISUAL_ATTRIBUTE_IDS = {
R.styleable.Keyboard_Key_keyTypeface,
R.styleable.Keyboard_Key_keyLetterSize,
@@ -65,6 +67,7 @@ public final class KeyVisualAttributes {
R.styleable.Keyboard_Key_keyShiftedLetterHintInactivatedColor,
R.styleable.Keyboard_Key_keyShiftedLetterHintActivatedColor,
R.styleable.Keyboard_Key_keyPreviewTextColor,
+ R.styleable.Keyboard_Key_keyHintLabelVerticalAdjustment,
};
private static final SparseIntArray sVisualAttributeIds = new SparseIntArray();
private static final int ATTR_DEFINED = 1;
@@ -127,5 +130,8 @@ public final class KeyVisualAttributes {
mShiftedLetterHintActivatedColor = keyAttr.getColor(
R.styleable.Keyboard_Key_keyShiftedLetterHintActivatedColor, 0);
mPreviewTextColor = keyAttr.getColor(R.styleable.Keyboard_Key_keyPreviewTextColor, 0);
+
+ mHintLabelVerticalAdjustment = ResourceUtils.getFraction(keyAttr,
+ R.styleable.Keyboard_Key_keyHintLabelVerticalAdjustment, 0.0f);
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
index c1ae65695..b31358f3c 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
@@ -459,7 +459,7 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
final int x = (int)row.getKeyX(null);
final int y = row.getKeyY();
final Key key = new Key(mParams, label, null /* hintLabel */, 0 /* iconId */,
- code, outputText, x, y, (int)keyWidth, (int)row.getRowHeight(),
+ code, outputText, x, y, (int)keyWidth, row.getRowHeight(),
row.getDefaultKeyLabelFlags(), row.getDefaultBackgroundType());
endKey(key);
row.advanceXPos(keyWidth);
@@ -649,10 +649,9 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
R.styleable.Keyboard_Case_passwordInput, id.passwordInput());
final boolean clobberSettingsKeyMatched = matchBoolean(caseAttr,
R.styleable.Keyboard_Case_clobberSettingsKey, id.mClobberSettingsKey);
- final boolean shortcutKeyEnabledMatched = matchBoolean(caseAttr,
- R.styleable.Keyboard_Case_shortcutKeyEnabled, id.mShortcutKeyEnabled);
- final boolean shortcutKeyOnSymbolsMatched = matchBoolean(caseAttr,
- R.styleable.Keyboard_Case_shortcutKeyOnSymbols, id.mShortcutKeyOnSymbols);
+ final boolean supportsSwitchingToShortcutImeMatched = matchBoolean(caseAttr,
+ R.styleable.Keyboard_Case_supportsSwitchingToShortcutIme,
+ id.mSupportsSwitchingToShortcutIme);
final boolean hasShortcutKeyMatched = matchBoolean(caseAttr,
R.styleable.Keyboard_Case_hasShortcutKey, id.mHasShortcutKey);
final boolean languageSwitchKeyEnabledMatched = matchBoolean(caseAttr,
@@ -671,13 +670,12 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
final boolean selected = keyboardLayoutSetMatched && keyboardLayoutSetElementMatched
&& modeMatched && navigateNextMatched && navigatePreviousMatched
&& passwordInputMatched && clobberSettingsKeyMatched
- && shortcutKeyEnabledMatched && shortcutKeyOnSymbolsMatched
- && hasShortcutKeyMatched && languageSwitchKeyEnabledMatched
- && isMultiLineMatched && imeActionMatched && localeCodeMatched
- && languageCodeMatched && countryCodeMatched;
+ && supportsSwitchingToShortcutImeMatched && 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%s>%s", TAG_CASE,
+ startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE,
textAttr(caseAttr.getString(
R.styleable.Keyboard_Case_keyboardLayoutSet), "keyboardLayoutSet"),
textAttr(caseAttr.getString(
@@ -694,10 +692,9 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
"clobberSettingsKey"),
booleanAttr(caseAttr, R.styleable.Keyboard_Case_passwordInput,
"passwordInput"),
- booleanAttr(caseAttr, R.styleable.Keyboard_Case_shortcutKeyEnabled,
- "shortcutKeyEnabled"),
- booleanAttr(caseAttr, R.styleable.Keyboard_Case_shortcutKeyOnSymbols,
- "shortcutKeyOnSymbols"),
+ booleanAttr(
+ caseAttr, R.styleable.Keyboard_Case_supportsSwitchingToShortcutIme,
+ "supportsSwitchingToShortcutIme"),
booleanAttr(caseAttr, R.styleable.Keyboard_Case_hasShortcutKey,
"hasShortcutKey"),
booleanAttr(caseAttr, R.styleable.Keyboard_Case_languageSwitchKeyEnabled,
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
index 336db186e..0ee935f60 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
@@ -48,7 +48,6 @@ public final class KeyboardIconsSet {
"search_key", R.styleable.Keyboard_iconSearchKey,
"tab_key", R.styleable.Keyboard_iconTabKey,
"shortcut_key", R.styleable.Keyboard_iconShortcutKey,
- "shortcut_for_label", R.styleable.Keyboard_iconShortcutForLabel,
"space_key_for_number_layout", R.styleable.Keyboard_iconSpaceKeyForNumberLayout,
"shift_key_shifted", R.styleable.Keyboard_iconShiftKeyShifted,
"shortcut_key_disabled", R.styleable.Keyboard_iconShortcutKeyDisabled,
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
index c2a01b5e8..1b722c5ce 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
@@ -87,11 +87,11 @@ public final class KeyboardTextsSet {
return (text == null) ? LANGUAGE_DEFAULT[id] : text;
}
+ // These texts' name should be aligned with the @string/<name> in
+ // values*/strings-action-keys.xml.
private static final String[] RESOURCE_NAMES = {
- // These texts' name should be aligned with the @string/<name> in values/strings.xml.
// Labels for action.
"label_go_key",
- // "label_search_key",
"label_send_key",
"label_next_key",
"label_done_key",
@@ -147,111 +147,118 @@ public final class KeyboardTextsSet {
/* 42 */ "keylabel_for_south_slavic_row3_8",
/* 43 */ "more_keys_for_cyrillic_ie",
/* 44 */ "more_keys_for_cyrillic_i",
- /* 45 */ "label_to_alpha_key",
- /* 46 */ "single_quotes",
- /* 47 */ "double_quotes",
- /* 48 */ "single_angle_quotes",
- /* 49 */ "double_angle_quotes",
- /* 50 */ "more_keys_for_currency_dollar",
- /* 51 */ "keylabel_for_currency",
- /* 52 */ "more_keys_for_currency",
- /* 53 */ "more_keys_for_punctuation",
- /* 54 */ "more_keys_for_star",
- /* 55 */ "more_keys_for_bullet",
- /* 56 */ "more_keys_for_plus",
- /* 57 */ "more_keys_for_left_parenthesis",
- /* 58 */ "more_keys_for_right_parenthesis",
- /* 59 */ "more_keys_for_less_than",
- /* 60 */ "more_keys_for_greater_than",
- /* 61 */ "more_keys_for_arabic_diacritics",
- /* 62 */ "keyhintlabel_for_arabic_diacritics",
- /* 63 */ "keylabel_for_symbols_1",
- /* 64 */ "keylabel_for_symbols_2",
- /* 65 */ "keylabel_for_symbols_3",
- /* 66 */ "keylabel_for_symbols_4",
- /* 67 */ "keylabel_for_symbols_5",
- /* 68 */ "keylabel_for_symbols_6",
- /* 69 */ "keylabel_for_symbols_7",
- /* 70 */ "keylabel_for_symbols_8",
- /* 71 */ "keylabel_for_symbols_9",
- /* 72 */ "keylabel_for_symbols_0",
- /* 73 */ "label_to_symbol_key",
- /* 74 */ "label_to_symbol_with_microphone_key",
- /* 75 */ "additional_more_keys_for_symbols_1",
- /* 76 */ "additional_more_keys_for_symbols_2",
- /* 77 */ "additional_more_keys_for_symbols_3",
- /* 78 */ "additional_more_keys_for_symbols_4",
- /* 79 */ "additional_more_keys_for_symbols_5",
- /* 80 */ "additional_more_keys_for_symbols_6",
- /* 81 */ "additional_more_keys_for_symbols_7",
- /* 82 */ "additional_more_keys_for_symbols_8",
- /* 83 */ "additional_more_keys_for_symbols_9",
- /* 84 */ "additional_more_keys_for_symbols_0",
- /* 85 */ "more_keys_for_symbols_1",
- /* 86 */ "more_keys_for_symbols_2",
- /* 87 */ "more_keys_for_symbols_3",
- /* 88 */ "more_keys_for_symbols_4",
- /* 89 */ "more_keys_for_symbols_5",
- /* 90 */ "more_keys_for_symbols_6",
- /* 91 */ "more_keys_for_symbols_7",
- /* 92 */ "more_keys_for_symbols_8",
- /* 93 */ "more_keys_for_symbols_9",
- /* 94 */ "more_keys_for_symbols_0",
- /* 95 */ "keylabel_for_comma",
- /* 96 */ "more_keys_for_comma",
- /* 97 */ "keylabel_for_symbols_question",
- /* 98 */ "keylabel_for_symbols_semicolon",
- /* 99 */ "keylabel_for_symbols_percent",
- /* 100 */ "more_keys_for_symbols_exclamation",
- /* 101 */ "more_keys_for_symbols_question",
- /* 102 */ "more_keys_for_symbols_semicolon",
- /* 103 */ "more_keys_for_symbols_percent",
- /* 104 */ "keylabel_for_tablet_comma",
- /* 105 */ "keyhintlabel_for_tablet_comma",
- /* 106 */ "more_keys_for_tablet_comma",
+ /* 45 */ "keylabel_for_swiss_row1_11",
+ /* 46 */ "keylabel_for_swiss_row2_10",
+ /* 47 */ "keylabel_for_swiss_row2_11",
+ /* 48 */ "more_keys_for_swiss_row1_11",
+ /* 49 */ "more_keys_for_swiss_row2_10",
+ /* 50 */ "more_keys_for_swiss_row2_11",
+ /* 51 */ "label_to_alpha_key",
+ /* 52 */ "single_quotes",
+ /* 53 */ "double_quotes",
+ /* 54 */ "single_angle_quotes",
+ /* 55 */ "double_angle_quotes",
+ /* 56 */ "more_keys_for_currency_dollar",
+ /* 57 */ "keylabel_for_currency",
+ /* 58 */ "more_keys_for_currency",
+ /* 59 */ "more_keys_for_punctuation",
+ /* 60 */ "more_keys_for_tablet_punctuation",
+ /* 61 */ "more_keys_for_star",
+ /* 62 */ "more_keys_for_bullet",
+ /* 63 */ "more_keys_for_plus",
+ /* 64 */ "more_keys_for_left_parenthesis",
+ /* 65 */ "more_keys_for_right_parenthesis",
+ /* 66 */ "more_keys_for_less_than",
+ /* 67 */ "more_keys_for_greater_than",
+ /* 68 */ "more_keys_for_arabic_diacritics",
+ /* 69 */ "keylabel_for_symbols_1",
+ /* 70 */ "keylabel_for_symbols_2",
+ /* 71 */ "keylabel_for_symbols_3",
+ /* 72 */ "keylabel_for_symbols_4",
+ /* 73 */ "keylabel_for_symbols_5",
+ /* 74 */ "keylabel_for_symbols_6",
+ /* 75 */ "keylabel_for_symbols_7",
+ /* 76 */ "keylabel_for_symbols_8",
+ /* 77 */ "keylabel_for_symbols_9",
+ /* 78 */ "keylabel_for_symbols_0",
+ /* 79 */ "label_to_symbol_key",
+ /* 80 */ "label_to_symbol_with_microphone_key",
+ /* 81 */ "additional_more_keys_for_symbols_1",
+ /* 82 */ "additional_more_keys_for_symbols_2",
+ /* 83 */ "additional_more_keys_for_symbols_3",
+ /* 84 */ "additional_more_keys_for_symbols_4",
+ /* 85 */ "additional_more_keys_for_symbols_5",
+ /* 86 */ "additional_more_keys_for_symbols_6",
+ /* 87 */ "additional_more_keys_for_symbols_7",
+ /* 88 */ "additional_more_keys_for_symbols_8",
+ /* 89 */ "additional_more_keys_for_symbols_9",
+ /* 90 */ "additional_more_keys_for_symbols_0",
+ /* 91 */ "more_keys_for_symbols_1",
+ /* 92 */ "more_keys_for_symbols_2",
+ /* 93 */ "more_keys_for_symbols_3",
+ /* 94 */ "more_keys_for_symbols_4",
+ /* 95 */ "more_keys_for_symbols_5",
+ /* 96 */ "more_keys_for_symbols_6",
+ /* 97 */ "more_keys_for_symbols_7",
+ /* 98 */ "more_keys_for_symbols_8",
+ /* 99 */ "more_keys_for_symbols_9",
+ /* 100 */ "more_keys_for_symbols_0",
+ /* 101 */ "keylabel_for_comma",
+ /* 102 */ "more_keys_for_comma",
+ /* 103 */ "keylabel_for_tablet_comma",
+ /* 104 */ "keyhintlabel_for_tablet_comma",
+ /* 105 */ "more_keys_for_tablet_comma",
+ /* 106 */ "keylabel_for_period",
/* 107 */ "keyhintlabel_for_period",
/* 108 */ "more_keys_for_period",
- /* 109 */ "keylabel_for_apostrophe",
- /* 110 */ "keyhintlabel_for_apostrophe",
- /* 111 */ "more_keys_for_apostrophe",
- /* 112 */ "more_keys_for_q",
- /* 113 */ "more_keys_for_x",
- /* 114 */ "keylabel_for_q",
- /* 115 */ "keylabel_for_w",
- /* 116 */ "keylabel_for_y",
- /* 117 */ "keylabel_for_x",
- /* 118 */ "keylabel_for_spanish_row2_10",
- /* 119 */ "more_keys_for_am_pm",
- /* 120 */ "settings_as_more_key",
- /* 121 */ "shortcut_as_more_key",
- /* 122 */ "action_next_as_more_key",
- /* 123 */ "action_previous_as_more_key",
- /* 124 */ "label_to_more_symbol_key",
- /* 125 */ "label_to_more_symbol_for_tablet_key",
- /* 126 */ "label_tab_key",
- /* 127 */ "label_to_phone_numeric_key",
- /* 128 */ "label_to_phone_symbols_key",
- /* 129 */ "label_time_am",
- /* 130 */ "label_time_pm",
- /* 131 */ "keylabel_for_popular_domain",
- /* 132 */ "more_keys_for_popular_domain",
- /* 133 */ "more_keys_for_smiley",
- /* 134 */ "single_laqm_raqm",
- /* 135 */ "single_laqm_raqm_rtl",
- /* 136 */ "single_raqm_laqm",
- /* 137 */ "double_laqm_raqm",
- /* 138 */ "double_laqm_raqm_rtl",
- /* 139 */ "double_raqm_laqm",
- /* 140 */ "single_lqm_rqm",
- /* 141 */ "single_9qm_lqm",
- /* 142 */ "single_9qm_rqm",
- /* 143 */ "double_lqm_rqm",
- /* 144 */ "double_9qm_lqm",
- /* 145 */ "double_9qm_rqm",
- /* 146 */ "more_keys_for_single_quote",
- /* 147 */ "more_keys_for_double_quote",
- /* 148 */ "more_keys_for_tablet_double_quote",
- /* 149 */ "emoji_key_as_more_key",
+ /* 109 */ "keylabel_for_tablet_period",
+ /* 110 */ "keyhintlabel_for_tablet_period",
+ /* 111 */ "more_keys_for_tablet_period",
+ /* 112 */ "keylabel_for_symbols_question",
+ /* 113 */ "keylabel_for_symbols_semicolon",
+ /* 114 */ "keylabel_for_symbols_percent",
+ /* 115 */ "more_keys_for_exclamation",
+ /* 116 */ "more_keys_for_question",
+ /* 117 */ "more_keys_for_symbols_semicolon",
+ /* 118 */ "more_keys_for_symbols_percent",
+ /* 119 */ "more_keys_for_q",
+ /* 120 */ "more_keys_for_x",
+ /* 121 */ "keylabel_for_q",
+ /* 122 */ "keylabel_for_w",
+ /* 123 */ "keylabel_for_y",
+ /* 124 */ "keylabel_for_x",
+ /* 125 */ "keylabel_for_spanish_row2_10",
+ /* 126 */ "more_keys_for_am_pm",
+ /* 127 */ "settings_as_more_key",
+ /* 128 */ "shortcut_as_more_key",
+ /* 129 */ "action_next_as_more_key",
+ /* 130 */ "action_previous_as_more_key",
+ /* 131 */ "label_to_more_symbol_key",
+ /* 132 */ "label_to_more_symbol_for_tablet_key",
+ /* 133 */ "label_tab_key",
+ /* 134 */ "label_to_phone_numeric_key",
+ /* 135 */ "label_to_phone_symbols_key",
+ /* 136 */ "label_time_am",
+ /* 137 */ "label_time_pm",
+ /* 138 */ "keylabel_for_popular_domain",
+ /* 139 */ "more_keys_for_popular_domain",
+ /* 140 */ "more_keys_for_smiley",
+ /* 141 */ "single_laqm_raqm",
+ /* 142 */ "single_laqm_raqm_rtl",
+ /* 143 */ "single_raqm_laqm",
+ /* 144 */ "double_laqm_raqm",
+ /* 145 */ "double_laqm_raqm_rtl",
+ /* 146 */ "double_raqm_laqm",
+ /* 147 */ "single_lqm_rqm",
+ /* 148 */ "single_9qm_lqm",
+ /* 149 */ "single_9qm_rqm",
+ /* 150 */ "double_lqm_rqm",
+ /* 151 */ "double_9qm_lqm",
+ /* 152 */ "double_9qm_rqm",
+ /* 153 */ "more_keys_for_single_quote",
+ /* 154 */ "more_keys_for_double_quote",
+ /* 155 */ "more_keys_for_tablet_double_quote",
+ /* 156 */ "emoji_key_as_more_key",
};
private static final String EMPTY = "";
@@ -262,145 +269,148 @@ public final class KeyboardTextsSet {
EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
- EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
- /* ~44 */
+ EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
+ /* ~50 */
// Label for "switch to alphabetic" key.
- /* 45 */ "ABC",
- /* 46 */ "!text/single_lqm_rqm",
- /* 47 */ "!text/double_lqm_rqm",
- /* 48 */ "!text/single_laqm_raqm",
- /* 49 */ "!text/double_laqm_raqm",
+ /* 51 */ "ABC",
+ /* 52 */ "!text/single_lqm_rqm",
+ /* 53 */ "!text/double_lqm_rqm",
+ /* 54 */ "!text/single_laqm_raqm",
+ /* 55 */ "!text/double_laqm_raqm",
// U+00A2: "¢" CENT SIGN
// U+00A3: "£" POUND SIGN
// U+20AC: "€" EURO SIGN
// U+00A5: "¥" YEN SIGN
// U+20B1: "₱" PESO SIGN
- /* 50 */ "\u00A2,\u00A3,\u20AC,\u00A5,\u20B1",
- /* 51 */ "$",
- /* 52 */ "$,\u00A2,\u20AC,\u00A3,\u00A5,\u20B1",
- /* 53 */ "!fixedColumnOrder!8,;,/,(,),#,!,\\,,?,&,\\%,+,\",-,:,',@",
+ /* 56 */ "\u00A2,\u00A3,\u20AC,\u00A5,\u20B1",
+ /* 57 */ "$",
+ /* 58 */ "$,\u00A2,\u20AC,\u00A3,\u00A5,\u20B1",
+ /* 59 */ "!fixedColumnOrder!8,;,/,(,),#,!,\\,,?,&,\\%,+,\",-,:,',@",
+ /* 60 */ "!fixedColumnOrder!7,;,/,(,),#,',\\,,&,\\%,+,\",-,:,@",
// U+2020: "†" DAGGER
// U+2021: "‡" DOUBLE DAGGER
// U+2605: "★" BLACK STAR
- /* 54 */ "\u2020,\u2021,\u2605",
+ /* 61 */ "\u2020,\u2021,\u2605",
// U+266A: "♪" EIGHTH NOTE
// U+2665: "♥" BLACK HEART SUIT
// U+2660: "♠" BLACK SPADE SUIT
// U+2666: "♦" BLACK DIAMOND SUIT
// U+2663: "♣" BLACK CLUB SUIT
- /* 55 */ "\u266A,\u2665,\u2660,\u2666,\u2663",
+ /* 62 */ "\u266A,\u2665,\u2660,\u2666,\u2663",
// U+00B1: "±" PLUS-MINUS SIGN
- /* 56 */ "\u00B1",
+ /* 63 */ "\u00B1",
// The all letters need to be mirrored are found at
// http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
- /* 57 */ "!fixedColumnOrder!3,<,{,[",
- /* 58 */ "!fixedColumnOrder!3,>,},]",
+ /* 64 */ "!fixedColumnOrder!3,<,{,[",
+ /* 65 */ "!fixedColumnOrder!3,>,},]",
// U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
// U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
// U+2264: "≤" LESS-THAN OR EQUAL TO
// U+2265: "≥" GREATER-THAN EQUAL TO
// U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
// U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
- /* 59 */ "!fixedColumnOrder!3,\u2039,\u2264,\u00AB",
- /* 60 */ "!fixedColumnOrder!3,\u203A,\u2265,\u00BB",
- /* 61 */ EMPTY,
- /* 62 */ EMPTY,
- /* 63 */ "1",
- /* 64 */ "2",
- /* 65 */ "3",
- /* 66 */ "4",
- /* 67 */ "5",
- /* 68 */ "6",
- /* 69 */ "7",
- /* 70 */ "8",
- /* 71 */ "9",
- /* 72 */ "0",
+ /* 66 */ "!fixedColumnOrder!3,\u2039,\u2264,\u00AB",
+ /* 67 */ "!fixedColumnOrder!3,\u203A,\u2265,\u00BB",
+ /* 68 */ EMPTY,
+ /* 69 */ "1",
+ /* 70 */ "2",
+ /* 71 */ "3",
+ /* 72 */ "4",
+ /* 73 */ "5",
+ /* 74 */ "6",
+ /* 75 */ "7",
+ /* 76 */ "8",
+ /* 77 */ "9",
+ /* 78 */ "0",
// Label for "switch to symbols" key.
- /* 73 */ "?123",
+ /* 79 */ "?123",
// Label for "switch to symbols with microphone" key. This string shouldn't include the "mic"
// part because it'll be appended by the code.
- /* 74 */ "123",
- /* 75~ */
+ /* 80 */ "123",
+ /* 81~ */
EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
- /* ~84 */
+ /* ~90 */
// U+00B9: "¹" SUPERSCRIPT ONE
// U+00BD: "½" VULGAR FRACTION ONE HALF
// U+2153: "⅓" VULGAR FRACTION ONE THIRD
// U+00BC: "¼" VULGAR FRACTION ONE QUARTER
// U+215B: "⅛" VULGAR FRACTION ONE EIGHTH
- /* 85 */ "\u00B9,\u00BD,\u2153,\u00BC,\u215B",
+ /* 91 */ "\u00B9,\u00BD,\u2153,\u00BC,\u215B",
// U+00B2: "²" SUPERSCRIPT TWO
// U+2154: "⅔" VULGAR FRACTION TWO THIRDS
- /* 86 */ "\u00B2,\u2154",
+ /* 92 */ "\u00B2,\u2154",
// U+00B3: "³" SUPERSCRIPT THREE
// U+00BE: "¾" VULGAR FRACTION THREE QUARTERS
// U+215C: "⅜" VULGAR FRACTION THREE EIGHTHS
- /* 87 */ "\u00B3,\u00BE,\u215C",
+ /* 93 */ "\u00B3,\u00BE,\u215C",
// U+2074: "⁴" SUPERSCRIPT FOUR
- /* 88 */ "\u2074",
+ /* 94 */ "\u2074",
// U+215D: "⅝" VULGAR FRACTION FIVE EIGHTHS
- /* 89 */ "\u215D",
- /* 90 */ EMPTY,
+ /* 95 */ "\u215D",
+ /* 96 */ EMPTY,
// U+215E: "⅞" VULGAR FRACTION SEVEN EIGHTHS
- /* 91 */ "\u215E",
- /* 92 */ EMPTY,
- /* 93 */ EMPTY,
+ /* 97 */ "\u215E",
+ /* 98 */ EMPTY,
+ /* 99 */ EMPTY,
// U+207F: "ⁿ" SUPERSCRIPT LATIN SMALL LETTER N
// U+2205: "∅" EMPTY SET
- /* 94 */ "\u207F,\u2205",
- /* 95 */ ",",
- /* 96 */ EMPTY,
- /* 97 */ "?",
- /* 98 */ ";",
- /* 99 */ "%",
+ /* 100 */ "\u207F,\u2205",
+ // Comma key
+ /* 101 */ ",",
+ /* 102 */ EMPTY,
+ /* 103 */ ",",
+ /* 104 */ EMPTY,
+ /* 105 */ EMPTY,
+ // Period key
+ /* 106 */ ".",
+ /* 107 */ EMPTY,
+ /* 108 */ "!text/more_keys_for_punctuation",
+ /* 109 */ ".",
+ /* 110 */ EMPTY,
+ /* 111 */ "!text/more_keys_for_tablet_punctuation",
+ /* 112 */ "?",
+ /* 113 */ ";",
+ /* 114 */ "%",
// U+00A1: "¡" INVERTED EXCLAMATION MARK
- /* 100 */ "\u00A1",
+ /* 115 */ "\u00A1",
// U+00BF: "¿" INVERTED QUESTION MARK
- /* 101 */ "\u00BF",
- /* 102 */ EMPTY,
+ /* 116 */ "\u00BF",
+ /* 117 */ EMPTY,
// U+2030: "‰" PER MILLE SIGN
- /* 103 */ "\u2030",
- /* 104 */ ",",
- /* 105~ */
- EMPTY, EMPTY, EMPTY,
- /* ~107 */
- // U+2026: "…" HORIZONTAL ELLIPSIS
- /* 108 */ "\u2026",
- /* 109 */ "\'",
- /* 110 */ "\"",
- /* 111 */ "\"",
- /* 112 */ EMPTY,
- /* 113 */ EMPTY,
- /* 114 */ "q",
- /* 115 */ "w",
- /* 116 */ "y",
- /* 117 */ "x",
- /* 118 */ EMPTY,
- /* 119 */ "!fixedColumnOrder!2,!hasLabels!,!text/label_time_am,!text/label_time_pm",
- /* 120 */ "!icon/settings_key|!code/key_settings",
- /* 121 */ "!icon/shortcut_key|!code/key_shortcut",
- /* 122 */ "!hasLabels!,!text/label_next_key|!code/key_action_next",
- /* 123 */ "!hasLabels!,!text/label_previous_key|!code/key_action_previous",
+ /* 118 */ "\u2030",
+ /* 119 */ EMPTY,
+ /* 120 */ EMPTY,
+ /* 121 */ "q",
+ /* 122 */ "w",
+ /* 123 */ "y",
+ /* 124 */ "x",
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ /* 125 */ "\u00F1",
+ /* 126 */ "!fixedColumnOrder!2,!hasLabels!,!text/label_time_am,!text/label_time_pm",
+ /* 127 */ "!icon/settings_key|!code/key_settings",
+ /* 128 */ "!icon/shortcut_key|!code/key_shortcut",
+ /* 129 */ "!hasLabels!,!text/label_next_key|!code/key_action_next",
+ /* 130 */ "!hasLabels!,!text/label_previous_key|!code/key_action_previous",
// Label for "switch to more symbol" modifier key. Must be short to fit on key!
- /* 124 */ "= \\ <",
+ /* 131 */ "= \\ <",
// Label for "switch to more symbol" modifier key on tablets. Must be short to fit on key!
- /* 125 */ "~ [ <",
+ /* 132 */ "~ [ <",
// Label for "Tab" key. Must be short to fit on key!
- /* 126 */ "Tab",
+ /* 133 */ "Tab",
// Label for "switch to phone numeric" key. Must be short to fit on key!
- /* 127 */ "123",
+ /* 134 */ "123",
// Label for "switch to phone symbols" key. Must be short to fit on key!
// U+FF0A: "*" FULLWIDTH ASTERISK
// U+FF03: "#" FULLWIDTH NUMBER SIGN
- /* 128 */ "\uFF0A\uFF03",
+ /* 135 */ "\uFF0A\uFF03",
// Key label for "ante meridiem"
- /* 129 */ "AM",
+ /* 136 */ "AM",
// Key label for "post meridiem"
- /* 130 */ "PM",
- /* 131 */ ".com",
+ /* 137 */ "PM",
+ /* 138 */ ".com",
// popular web domains for the locale - most popular, displayed on the keyboard
- /* 132 */ "!hasLabels!,.net,.org,.gov,.edu",
- /* 133 */ "!fixedColumnOrder!5,!hasLabels!,=-O|=-O ,:-P|:-P ,;-)|;-) ,:-(|:-( ,:-)|:-) ,:-!|:-! ,:-$|:-$ ,B-)|B-) ,:O|:O ,:-*|:-* ,:-D|:-D ,:\'(|:\'( ,:-\\\\|:-\\\\ ,O:-)|O:-) ,:-[|:-[ ",
+ /* 139 */ "!hasLabels!,.net,.org,.gov,.edu",
+ /* 140 */ "!fixedColumnOrder!5,!hasLabels!,=-O|=-O ,:-P|:-P ,;-)|;-) ,:-(|:-( ,:-)|:-) ,:-!|:-! ,:-$|:-$ ,B-)|B-) ,:O|:O ,:-*|:-* ,:-D|:-D ,:\'(|:\'( ,:-\\\\|:-\\\\ ,O:-)|O:-) ,:-[|:-[ ",
// U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
// U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
// U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
@@ -422,25 +432,25 @@ public final class KeyboardTextsSet {
// The following each quotation mark pair consist of
// <opening quotation mark>, <closing quotation mark>
// and is named after (single|double)_<opening quotation mark>_<closing quotation mark>.
- /* 134 */ "\u2039,\u203A",
- /* 135 */ "\u2039|\u203A,\u203A|\u2039",
- /* 136 */ "\u203A,\u2039",
- /* 137 */ "\u00AB,\u00BB",
- /* 138 */ "\u00AB|\u00BB,\u00BB|\u00AB",
- /* 139 */ "\u00BB,\u00AB",
+ /* 141 */ "\u2039,\u203A",
+ /* 142 */ "\u2039|\u203A,\u203A|\u2039",
+ /* 143 */ "\u203A,\u2039",
+ /* 144 */ "\u00AB,\u00BB",
+ /* 145 */ "\u00AB|\u00BB,\u00BB|\u00AB",
+ /* 146 */ "\u00BB,\u00AB",
// The following each quotation mark triplet consists of
// <another quotation mark>, <opening quotation mark>, <closing quotation mark>
// and is named after (single|double)_<opening quotation mark>_<closing quotation mark>.
- /* 140 */ "\u201A,\u2018,\u2019",
- /* 141 */ "\u2019,\u201A,\u2018",
- /* 142 */ "\u2018,\u201A,\u2019",
- /* 143 */ "\u201E,\u201C,\u201D",
- /* 144 */ "\u201D,\u201E,\u201C",
- /* 145 */ "\u201C,\u201E,\u201D",
- /* 146 */ "!fixedColumnOrder!5,!text/single_quotes,!text/single_angle_quotes",
- /* 147 */ "!fixedColumnOrder!5,!text/double_quotes,!text/double_angle_quotes",
- /* 148 */ "!fixedColumnOrder!6,!text/double_quotes,!text/single_quotes,!text/double_angle_quotes,!text/single_angle_quotes",
- /* 149 */ "!icon/emoji_key|!code/key_emoji",
+ /* 147 */ "\u201A,\u2018,\u2019",
+ /* 148 */ "\u2019,\u201A,\u2018",
+ /* 149 */ "\u2018,\u201A,\u2019",
+ /* 150 */ "\u201E,\u201C,\u201D",
+ /* 151 */ "\u201D,\u201E,\u201C",
+ /* 152 */ "\u201C,\u201E,\u201D",
+ /* 153 */ "!fixedColumnOrder!5,!text/single_quotes,!text/single_angle_quotes",
+ /* 154 */ "!fixedColumnOrder!5,!text/double_quotes,!text/double_angle_quotes",
+ /* 155 */ "!fixedColumnOrder!6,!text/double_quotes,!text/single_quotes,!text/double_angle_quotes,!text/single_angle_quotes",
+ /* 156 */ "!icon/emoji_key|!code/key_emoji",
};
/* Language af: Afrikaans */
@@ -502,44 +512,43 @@ public final class KeyboardTextsSet {
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~44 */
+ null, null, null, null, null, null,
+ /* ~50 */
// Label for "switch to alphabetic" key.
// U+0623: "ا" ARABIC LETTER ALEF
// U+200C: ZERO WIDTH NON-JOINER
// U+0628: "ب" ARABIC LETTER BEH
// U+062C: "پ" ARABIC LETTER PEH
- /* 45 */ "\u0623\u200C\u0628\u200C\u062C",
- /* 46 */ null,
- /* 47 */ null,
- /* 48 */ "!text/single_laqm_raqm_rtl",
- /* 49 */ "!text/double_laqm_raqm_rtl",
- /* 50~ */
+ /* 51 */ "\u0623\u200C\u0628\u200C\u062C",
+ /* 52 */ null,
+ /* 53 */ null,
+ /* 54 */ "!text/single_laqm_raqm_rtl",
+ /* 55 */ "!text/double_laqm_raqm_rtl",
+ /* 56~ */
null, null, null,
- /* ~52 */
- // U+061F: "؟" ARABIC QUESTION MARK
- // U+060C: "،" ARABIC COMMA
- // U+061B: "؛" ARABIC SEMICOLON
- /* 53 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(|),)|(",
+ /* ~58 */
+ /* 59 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(|),)|(",
+ /* 60 */ null,
// U+2605: "★" BLACK STAR
// U+066D: "٭" ARABIC FIVE POINTED STAR
- /* 54 */ "\u2605,\u066D",
+ /* 61 */ "\u2605,\u066D",
// U+266A: "♪" EIGHTH NOTE
- /* 55 */ "\u266A",
- /* 56 */ null,
+ /* 62 */ "\u266A",
+ /* 63 */ null,
// The all letters need to be mirrored are found at
// http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
// U+FD3E: "﴾" ORNATE LEFT PARENTHESIS
// U+FD3F: "﴿" ORNATE RIGHT PARENTHESIS
- /* 57 */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,<|>,{|},[|]",
- /* 58 */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,>|<,}|{,]|[",
+ /* 64 */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,<|>,{|},[|]",
+ /* 65 */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,>|<,}|{,]|[",
// U+2264: "≤" LESS-THAN OR EQUAL TO
// U+2265: "≥" GREATER-THAN EQUAL TO
// U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
// U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
// U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
// U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
- /* 59 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB",
- /* 60 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB",
+ /* 66 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB",
+ /* 67 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB",
// U+0655: "ٕ" ARABIC HAMZA BELOW
// U+0654: "ٔ" ARABIC HAMZA ABOVE
// U+0652: "ْ" ARABIC SUKUN
@@ -556,70 +565,74 @@ public final class KeyboardTextsSet {
// U+0640: "ـ" ARABIC TATWEEL
// In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label.
// Note: The space character is needed as a preceding letter to draw Arabic diacritics characters correctly.
- /* 61 */ "!fixedColumnOrder!7, \u0655|\u0655, \u0654|\u0654, \u0652|\u0652, \u064D|\u064D, \u064C|\u064C, \u064B|\u064B, \u0651|\u0651, \u0656|\u0656, \u0670|\u0670, \u0653|\u0653, \u0650|\u0650, \u064F|\u064F, \u064E|\u064E,\u0640\u0640\u0640|\u0640",
- /* 62 */ "\u0651",
+ /* 68 */ "!fixedColumnOrder!7, \u0655|\u0655, \u0654|\u0654, \u0652|\u0652, \u064D|\u064D, \u064C|\u064C, \u064B|\u064B, \u0651|\u0651, \u0656|\u0656, \u0670|\u0670, \u0653|\u0653, \u0650|\u0650, \u064F|\u064F, \u064E|\u064E,\u0640\u0640\u0640|\u0640",
// U+0661: "١" ARABIC-INDIC DIGIT ONE
- /* 63 */ "\u0661",
+ /* 69 */ "\u0661",
// U+0662: "٢" ARABIC-INDIC DIGIT TWO
- /* 64 */ "\u0662",
+ /* 70 */ "\u0662",
// U+0663: "٣" ARABIC-INDIC DIGIT THREE
- /* 65 */ "\u0663",
+ /* 71 */ "\u0663",
// U+0664: "٤" ARABIC-INDIC DIGIT FOUR
- /* 66 */ "\u0664",
+ /* 72 */ "\u0664",
// U+0665: "٥" ARABIC-INDIC DIGIT FIVE
- /* 67 */ "\u0665",
+ /* 73 */ "\u0665",
// U+0666: "٦" ARABIC-INDIC DIGIT SIX
- /* 68 */ "\u0666",
+ /* 74 */ "\u0666",
// U+0667: "٧" ARABIC-INDIC DIGIT SEVEN
- /* 69 */ "\u0667",
+ /* 75 */ "\u0667",
// U+0668: "٨" ARABIC-INDIC DIGIT EIGHT
- /* 70 */ "\u0668",
+ /* 76 */ "\u0668",
// U+0669: "٩" ARABIC-INDIC DIGIT NINE
- /* 71 */ "\u0669",
+ /* 77 */ "\u0669",
// U+0660: "٠" ARABIC-INDIC DIGIT ZERO
- /* 72 */ "\u0660",
+ /* 78 */ "\u0660",
// Label for "switch to symbols" key.
// U+061F: "؟" ARABIC QUESTION MARK
- /* 73 */ "\u0663\u0662\u0661\u061F",
+ /* 79 */ "\u0663\u0662\u0661\u061F",
// Label for "switch to symbols with microphone" key. This string shouldn't include the "mic"
// part because it'll be appended by the code.
- /* 74 */ "\u0663\u0662\u0661",
- /* 75 */ "1",
- /* 76 */ "2",
- /* 77 */ "3",
- /* 78 */ "4",
- /* 79 */ "5",
- /* 80 */ "6",
- /* 81 */ "7",
- /* 82 */ "8",
- /* 83 */ "9",
+ /* 80 */ "\u0663\u0662\u0661",
+ /* 81 */ "1",
+ /* 82 */ "2",
+ /* 83 */ "3",
+ /* 84 */ "4",
+ /* 85 */ "5",
+ /* 86 */ "6",
+ /* 87 */ "7",
+ /* 88 */ "8",
+ /* 89 */ "9",
// U+066B: "٫" ARABIC DECIMAL SEPARATOR
// U+066C: "٬" ARABIC THOUSANDS SEPARATOR
- /* 84 */ "0,\u066B,\u066C",
- /* 85~ */
+ /* 90 */ "0,\u066B,\u066C",
+ /* 91~ */
null, null, null, null, null, null, null, null, null, null,
- /* ~94 */
+ /* ~100 */
// U+060C: "،" ARABIC COMMA
- /* 95 */ "\u060C",
- /* 96 */ "\\,",
- /* 97 */ "\u061F",
- /* 98 */ "\u061B",
- // U+066A: "٪" ARABIC PERCENT SIGN
- /* 99 */ "\u066A",
- /* 100 */ null,
- /* 101 */ "?",
- /* 102 */ ";",
- // U+2030: "‰" PER MILLE SIGN
- /* 103 */ "\\%,\u2030",
- /* 104~ */
- null, null, null, null, null,
- /* ~108 */
+ /* 101 */ "\u060C",
+ /* 102 */ "\\,",
+ // U+061F: "؟" ARABIC QUESTION MARK
// U+060C: "،" ARABIC COMMA
// U+061B: "؛" ARABIC SEMICOLON
- // U+061F: "؟" ARABIC QUESTION MARK
- /* 109 */ "\u060C",
- /* 110 */ "\u061F",
- /* 111 */ "\u061F,\u061B,!,:,-,/,\',\"",
+ /* 103 */ "\u060C",
+ /* 104 */ "\u061F",
+ /* 105 */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,/,\",\'",
+ /* 106 */ null,
+ // U+0651: "ّ" ARABIC SHADDA
+ /* 107 */ "\u0651",
+ /* 108 */ "!text/more_keys_for_arabic_diacritics",
+ /* 109 */ null,
+ /* 110 */ "\u0651",
+ /* 111 */ "!text/more_keys_for_arabic_diacritics",
+ /* 112 */ "\u061F",
+ /* 113 */ "\u061B",
+ // U+066A: "٪" ARABIC PERCENT SIGN
+ /* 114 */ "\u066A",
+ /* 115 */ null,
+ // U+00BF: "¿" INVERTED QUESTION MARK
+ /* 116 */ "?,\u00BF",
+ /* 117 */ ";",
+ // U+2030: "‰" PER MILLE SIGN
+ /* 118 */ "\\%,\u2030",
};
/* Language az: Azerbaijani */
@@ -668,8 +681,8 @@ public final class KeyboardTextsSet {
/* 15 */ "\u011F",
};
- /* Language be: Belarusian */
- private static final String[] LANGUAGE_be = {
+ /* Language be_BY: Belarusian (Belarus) */
+ private static final String[] LANGUAGE_be_BY = {
/* 0~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null,
@@ -694,14 +707,16 @@ public final class KeyboardTextsSet {
/* ~42 */
// U+0451: "ё" CYRILLIC SMALL LETTER IO
/* 43 */ "\u0451",
- /* 44 */ null,
+ /* 44~ */
+ null, null, null, null, null, null, null,
+ /* ~50 */
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
- /* 45 */ "\u0410\u0411\u0412",
- /* 46 */ "!text/single_9qm_lqm",
- /* 47 */ "!text/double_9qm_lqm",
+ /* 51 */ "\u0410\u0411\u0412",
+ /* 52 */ "!text/single_9qm_lqm",
+ /* 53 */ "!text/double_9qm_lqm",
};
/* Language bg: Bulgarian */
@@ -710,15 +725,16 @@ public final class KeyboardTextsSet {
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~44 */
+ null, null, null, null, null, null,
+ /* ~50 */
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
- /* 45 */ "\u0410\u0411\u0412",
- /* 46 */ null,
+ /* 51 */ "\u0410\u0411\u0412",
+ /* 52 */ null,
// single_quotes of Bulgarian is default single_quotes_right_left.
- /* 47 */ "!text/double_9qm_lqm",
+ /* 53 */ "!text/double_9qm_lqm",
};
/* Language ca: Catalan */
@@ -782,22 +798,20 @@ public final class KeyboardTextsSet {
/* 15~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null,
- /* ~52 */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~58 */
// U+00B7: "·" MIDDLE DOT
- /* 53 */ "!fixedColumnOrder!9,;,/,(,),#,\u00B7,!,\\,,?,&,\\%,+,\",-,:,',@",
- /* 54~ */
+ /* 59 */ "!fixedColumnOrder!9,;,/,(,),#,\u00B7,!,\\,,?,&,\\%,+,\",-,:,',@",
+ /* 60 */ "!fixedColumnOrder!8,;,/,(,),#,\u00B7,',\\,,&,\\%,+,\",-,:,@",
+ /* 61~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null,
- /* ~107 */
- /* 108 */ "?,\u00B7",
- /* 109~ */
- null, null, null, null, null, null, null, null, null,
- /* ~117 */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null,
+ /* ~124 */
// U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
- /* 118 */ "\u00E7",
+ /* 125 */ "\u00E7",
};
/* Language cs: Czech */
@@ -871,12 +885,12 @@ public final class KeyboardTextsSet {
/* 13~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null,
- /* ~45 */
- /* 46 */ "!text/single_9qm_lqm",
- /* 47 */ "!text/double_9qm_lqm",
- /* 48 */ "!text/single_raqm_laqm",
- /* 49 */ "!text/double_raqm_laqm",
+ null, null, null, null, null, null, null, null, null,
+ /* ~51 */
+ /* 52 */ "!text/single_9qm_lqm",
+ /* 53 */ "!text/double_9qm_lqm",
+ /* 54 */ "!text/single_raqm_laqm",
+ /* 55 */ "!text/double_raqm_laqm",
};
/* Language da: Danish */
@@ -940,12 +954,12 @@ public final class KeyboardTextsSet {
/* 24 */ "\u00F6",
/* 25~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null,
- /* ~45 */
- /* 46 */ "!text/single_9qm_lqm",
- /* 47 */ "!text/double_9qm_lqm",
- /* 48 */ "!text/single_raqm_laqm",
- /* 49 */ "!text/double_raqm_laqm",
+ null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~51 */
+ /* 52 */ "!text/single_9qm_lqm",
+ /* 53 */ "!text/double_9qm_lqm",
+ /* 54 */ "!text/single_raqm_laqm",
+ /* 55 */ "!text/double_raqm_laqm",
};
/* Language de: German */
@@ -991,12 +1005,25 @@ public final class KeyboardTextsSet {
/* 7~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null,
- /* ~45 */
- /* 46 */ "!text/single_9qm_lqm",
- /* 47 */ "!text/double_9qm_lqm",
- /* 48 */ "!text/single_raqm_laqm",
- /* 49 */ "!text/double_raqm_laqm",
+ null, null, null, null, null, null, null, null,
+ /* ~44 */
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ /* 45 */ "\u00FC",
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ /* 46 */ "\u00F6",
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ /* 47 */ "\u00E4",
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ /* 48 */ "\u00E8",
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ /* 49 */ "\u00E9",
+ // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+ /* 50 */ "\u00E0",
+ /* 51 */ null,
+ /* 52 */ "!text/single_9qm_lqm",
+ /* 53 */ "!text/double_9qm_lqm",
+ /* 54 */ "!text/single_raqm_laqm",
+ /* 55 */ "!text/double_raqm_laqm",
};
/* Language el: Greek */
@@ -1005,12 +1032,13 @@ public final class KeyboardTextsSet {
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~44 */
+ null, null, null, null, null, null,
+ /* ~50 */
// Label for "switch to alphabetic" key.
// U+0391: "Α" GREEK CAPITAL LETTER ALPHA
// U+0392: "Β" GREEK CAPITAL LETTER BETA
// U+0393: "Γ" GREEK CAPITAL LETTER GAMMA
- /* 45 */ "\u0391\u0392\u0393",
+ /* 51 */ "\u0391\u0392\u0393",
};
/* Language en: English */
@@ -1182,20 +1210,20 @@ public final class KeyboardTextsSet {
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null,
- /* ~111 */
- /* 112 */ "q",
- /* 113 */ "x",
+ null, null, null, null, null, null, null, null, null,
+ /* ~118 */
+ /* 119 */ "q",
+ /* 120 */ "x",
// U+015D: "ŝ" LATIN SMALL LETTER S WITH CIRCUMFLEX
- /* 114 */ "\u015D",
+ /* 121 */ "\u015D",
// U+011D: "ĝ" LATIN SMALL LETTER G WITH CIRCUMFLEX
- /* 115 */ "\u011D",
+ /* 122 */ "\u011D",
// U+016D: "ŭ" LATIN SMALL LETTER U WITH BREVE
- /* 116 */ "\u016D",
+ /* 123 */ "\u016D",
// U+0109: "ĉ" LATIN SMALL LETTER C WITH CIRCUMFLEX
- /* 117 */ "\u0109",
+ /* 124 */ "\u0109",
// U+0135: "ĵ" LATIN SMALL LETTER J WITH CIRCUMFLEX
- /* 118 */ "\u0135",
+ /* 125 */ "\u0135",
};
/* Language es: Spanish */
@@ -1254,33 +1282,15 @@ public final class KeyboardTextsSet {
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~52 */
- // U+00A1: "¡" INVERTED EXCLAMATION MARK
- // U+00BF: "¿" INVERTED QUESTION MARK
- /* 53 */ "!fixedColumnOrder!4,;,!,\\,,?,:,\u00A1,@,\u00BF",
- /* 54~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, 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 */
+ null, null, null, null, null, null,
+ /* ~58 */
// U+00A1: "¡" INVERTED EXCLAMATION MARK
- /* 106 */ "!,\u00A1",
- /* 107 */ null,
// U+00BF: "¿" INVERTED QUESTION MARK
- /* 108 */ "?,\u00BF",
- /* 109 */ "\"",
- /* 110 */ "\'",
- /* 111 */ "\'",
- /* 112~ */
- null, null, null, null, null, null,
- /* ~117 */
- // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
- /* 118 */ "\u00F1",
+ /* 59 */ "!fixedColumnOrder!9,\u00A1,;,/,(,),#,!,\\,,?,\u00BF,&,\\%,+,\",-,:,',@",
};
- /* Language et: Estonian */
- private static final String[] LANGUAGE_et = {
+ /* Language et_EE: Estonian (Estonia) */
+ private static final String[] LANGUAGE_et_EE = {
// U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
// U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
// U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
@@ -1379,10 +1389,10 @@ public final class KeyboardTextsSet {
/* 23 */ "\u00F5",
/* 24~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null,
- /* ~45 */
- /* 46 */ "!text/single_9qm_lqm",
- /* 47 */ "!text/double_9qm_lqm",
+ null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~51 */
+ /* 52 */ "!text/single_9qm_lqm",
+ /* 53 */ "!text/double_9qm_lqm",
};
/* Language fa: Persian */
@@ -1391,45 +1401,47 @@ public final class KeyboardTextsSet {
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~44 */
+ null, null, null, null, null, null,
+ /* ~50 */
// Label for "switch to alphabetic" key.
// U+0627: "ا" ARABIC LETTER ALEF
// U+200C: ZERO WIDTH NON-JOINER
// U+0628: "ب" ARABIC LETTER BEH
// U+067E: "پ" ARABIC LETTER PEH
- /* 45 */ "\u0627\u200C\u0628\u200C\u067E",
- /* 46 */ null,
- /* 47 */ null,
- /* 48 */ "!text/single_laqm_raqm_rtl",
- /* 49 */ "!text/double_laqm_raqm_rtl",
- /* 50 */ null,
- // U+FDFC: "﷼" RIAL SIGN
- /* 51 */ "\uFDFC",
+ /* 51 */ "\u0627\u200C\u0628\u200C\u067E",
/* 52 */ null,
+ /* 53 */ null,
+ /* 54 */ "!text/single_laqm_raqm_rtl",
+ /* 55 */ "!text/double_laqm_raqm_rtl",
+ /* 56 */ null,
+ // U+FDFC: "﷼" RIAL SIGN
+ /* 57 */ "\uFDFC",
+ /* 58 */ null,
// U+061F: "؟" ARABIC QUESTION MARK
// U+060C: "،" ARABIC COMMA
// U+061B: "؛" ARABIC SEMICOLON
- /* 53 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(|),)|(",
+ /* 59 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(|),)|(",
+ /* 60 */ null,
// U+2605: "★" BLACK STAR
// U+066D: "٭" ARABIC FIVE POINTED STAR
- /* 54 */ "\u2605,\u066D",
+ /* 61 */ "\u2605,\u066D",
// U+266A: "♪" EIGHTH NOTE
- /* 55 */ "\u266A",
- /* 56 */ null,
+ /* 62 */ "\u266A",
+ /* 63 */ null,
// The all letters need to be mirrored are found at
// http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
// U+FD3E: "﴾" ORNATE LEFT PARENTHESIS
// U+FD3F: "﴿" ORNATE RIGHT PARENTHESIS
- /* 57 */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,<|>,{|},[|]",
- /* 58 */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,>|<,}|{,]|[",
+ /* 64 */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,<|>,{|},[|]",
+ /* 65 */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,>|<,}|{,]|[",
// U+2264: "≤" LESS-THAN OR EQUAL TO
// U+2265: "≥" GREATER-THAN EQUAL TO
// U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
// U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
// U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
// U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
- /* 59 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,<|>",
- /* 60 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,>|<",
+ /* 66 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,<|>",
+ /* 67 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,>|<",
// U+0655: "ٕ" ARABIC HAMZA BELOW
// U+0652: "ْ" ARABIC SUKUN
// U+0651: "ّ" ARABIC SHADDA
@@ -1446,74 +1458,75 @@ public final class KeyboardTextsSet {
// U+0640: "ـ" ARABIC TATWEEL
// In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label.
// Note: The space character is needed as a preceding letter to draw Arabic diacritics characters correctly.
- /* 61 */ "!fixedColumnOrder!7, \u0655|\u0655, \u0652|\u0652, \u0651|\u0651, \u064C|\u064C, \u064D|\u064D, \u064B|\u064B, \u0654|\u0654, \u0656|\u0656, \u0670|\u0670, \u0653|\u0653, \u064F|\u064F, \u0650|\u0650, \u064E|\u064E,\u0640\u0640\u0640|\u0640",
- /* 62 */ "\u064B",
+ /* 68 */ "!fixedColumnOrder!7, \u0655|\u0655, \u0652|\u0652, \u0651|\u0651, \u064C|\u064C, \u064D|\u064D, \u064B|\u064B, \u0654|\u0654, \u0656|\u0656, \u0670|\u0670, \u0653|\u0653, \u064F|\u064F, \u0650|\u0650, \u064E|\u064E,\u0640\u0640\u0640|\u0640",
// U+06F1: "۱" EXTENDED ARABIC-INDIC DIGIT ONE
- /* 63 */ "\u06F1",
+ /* 69 */ "\u06F1",
// U+06F2: "۲" EXTENDED ARABIC-INDIC DIGIT TWO
- /* 64 */ "\u06F2",
+ /* 70 */ "\u06F2",
// U+06F3: "۳" EXTENDED ARABIC-INDIC DIGIT THREE
- /* 65 */ "\u06F3",
+ /* 71 */ "\u06F3",
// U+06F4: "۴" EXTENDED ARABIC-INDIC DIGIT FOUR
- /* 66 */ "\u06F4",
+ /* 72 */ "\u06F4",
// U+06F5: "۵" EXTENDED ARABIC-INDIC DIGIT FIVE
- /* 67 */ "\u06F5",
+ /* 73 */ "\u06F5",
// U+06F6: "۶" EXTENDED ARABIC-INDIC DIGIT SIX
- /* 68 */ "\u06F6",
+ /* 74 */ "\u06F6",
// U+06F7: "۷" EXTENDED ARABIC-INDIC DIGIT SEVEN
- /* 69 */ "\u06F7",
+ /* 75 */ "\u06F7",
// U+06F8: "۸" EXTENDED ARABIC-INDIC DIGIT EIGHT
- /* 70 */ "\u06F8",
+ /* 76 */ "\u06F8",
// U+06F9: "۹" EXTENDED ARABIC-INDIC DIGIT NINE
- /* 71 */ "\u06F9",
+ /* 77 */ "\u06F9",
// U+06F0: "۰" EXTENDED ARABIC-INDIC DIGIT ZERO
- /* 72 */ "\u06F0",
+ /* 78 */ "\u06F0",
// Label for "switch to symbols" key.
// U+061F: "؟" ARABIC QUESTION MARK
- /* 73 */ "\u06F3\u06F2\u06F1\u061F",
+ /* 79 */ "\u06F3\u06F2\u06F1\u061F",
// Label for "switch to symbols with microphone" key. This string shouldn't include the "mic"
// part because it'll be appended by the code.
- /* 74 */ "\u06F3\u06F2\u06F1",
- /* 75 */ "1",
- /* 76 */ "2",
- /* 77 */ "3",
- /* 78 */ "4",
- /* 79 */ "5",
- /* 80 */ "6",
- /* 81 */ "7",
- /* 82 */ "8",
- /* 83 */ "9",
+ /* 80 */ "\u06F3\u06F2\u06F1",
+ /* 81 */ "1",
+ /* 82 */ "2",
+ /* 83 */ "3",
+ /* 84 */ "4",
+ /* 85 */ "5",
+ /* 86 */ "6",
+ /* 87 */ "7",
+ /* 88 */ "8",
+ /* 89 */ "9",
// U+066B: "٫" ARABIC DECIMAL SEPARATOR
// U+066C: "٬" ARABIC THOUSANDS SEPARATOR
- /* 84 */ "0,\u066B,\u066C",
- /* 85~ */
+ /* 90 */ "0,\u066B,\u066C",
+ /* 91~ */
null, null, null, null, null, null, null, null, null, null,
- /* ~94 */
+ /* ~100 */
// U+060C: "،" ARABIC COMMA
- /* 95 */ "\u060C",
- /* 96 */ "\\,",
- /* 97 */ "\u061F",
- /* 98 */ "\u061B",
- // U+066A: "٪" ARABIC PERCENT SIGN
- /* 99 */ "\u066A",
- /* 100 */ null,
- /* 101 */ "?",
- /* 102 */ ";",
- // U+2030: "‰" PER MILLE SIGN
- /* 103 */ "\\%,\u2030",
+ /* 101 */ "\u060C",
+ /* 102 */ "\\,",
// U+060C: "،" ARABIC COMMA
// U+061B: "؛" ARABIC SEMICOLON
// U+061F: "؟" ARABIC QUESTION MARK
// U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
// U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
- /* 104 */ "\u060C",
- /* 105 */ "!",
- /* 106 */ "!,\\,",
+ /* 103 */ "\u060C",
+ /* 104 */ "\u061F",
+ /* 105 */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,/,\u00AB|\u00BB,\u00BB|\u00AB",
+ /* 106 */ null,
/* 107 */ "\u061F",
- /* 108 */ "\u061F,?",
- /* 109 */ "\u060C",
- /* 110 */ "\u061F",
- /* 111 */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,/,\u00AB|\u00BB,\u00BB|\u00AB",
+ /* 108 */ "!text/more_keys_for_arabic_diacritics",
+ /* 109 */ null,
+ /* 110 */ "\u064B",
+ /* 111 */ "!text/more_keys_for_arabic_diacritics",
+ /* 112 */ "\u061F",
+ /* 113 */ "\u061B",
+ // U+066A: "٪" ARABIC PERCENT SIGN
+ /* 114 */ "\u066A",
+ /* 115 */ null,
+ // U+00BF: "¿" INVERTED QUESTION MARK
+ /* 116 */ "?,\u00BF",
+ /* 117 */ ";",
+ // U+2030: "‰" PER MILLE SIGN
+ /* 118 */ "\\%,\u2030",
};
/* Language fi: Finnish */
@@ -1614,6 +1627,23 @@ public final class KeyboardTextsSet {
/* 7 */ "\u00E7,\u0107,\u010D",
// U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
/* 8 */ "%,\u00FF",
+ /* 9~ */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null,
+ /* ~44 */
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ /* 45 */ "\u00E8",
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ /* 46 */ "\u00E9",
+ // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+ /* 47 */ "\u00E0",
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ /* 48 */ "\u00FC",
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ /* 49 */ "\u00F6",
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ /* 50 */ "\u00E4",
};
/* Language hi: Hindi */
@@ -1622,55 +1652,56 @@ public final class KeyboardTextsSet {
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~44 */
+ null, null, null, null, null, null,
+ /* ~50 */
// Label for "switch to alphabetic" key.
// U+0915: "क" DEVANAGARI LETTER KA
// U+0916: "ख" DEVANAGARI LETTER KHA
// U+0917: "ग" DEVANAGARI LETTER GA
- /* 45 */ "\u0915\u0916\u0917",
- /* 46~ */
+ /* 51 */ "\u0915\u0916\u0917",
+ /* 52~ */
null, null, null, null, null,
- /* ~50 */
+ /* ~56 */
// U+20B9: "₹" INDIAN RUPEE SIGN
- /* 51 */ "\u20B9",
- /* 52~ */
+ /* 57 */ "\u20B9",
+ /* 58~ */
null, null, null, null, null, null, null, null, null, null, null,
- /* ~62 */
+ /* ~68 */
// U+0967: "१" DEVANAGARI DIGIT ONE
- /* 63 */ "\u0967",
+ /* 69 */ "\u0967",
// U+0968: "२" DEVANAGARI DIGIT TWO
- /* 64 */ "\u0968",
+ /* 70 */ "\u0968",
// U+0969: "३" DEVANAGARI DIGIT THREE
- /* 65 */ "\u0969",
+ /* 71 */ "\u0969",
// U+096A: "४" DEVANAGARI DIGIT FOUR
- /* 66 */ "\u096A",
+ /* 72 */ "\u096A",
// U+096B: "५" DEVANAGARI DIGIT FIVE
- /* 67 */ "\u096B",
+ /* 73 */ "\u096B",
// U+096C: "६" DEVANAGARI DIGIT SIX
- /* 68 */ "\u096C",
+ /* 74 */ "\u096C",
// U+096D: "७" DEVANAGARI DIGIT SEVEN
- /* 69 */ "\u096D",
+ /* 75 */ "\u096D",
// U+096E: "८" DEVANAGARI DIGIT EIGHT
- /* 70 */ "\u096E",
+ /* 76 */ "\u096E",
// U+096F: "९" DEVANAGARI DIGIT NINE
- /* 71 */ "\u096F",
+ /* 77 */ "\u096F",
// U+0966: "०" DEVANAGARI DIGIT ZERO
- /* 72 */ "\u0966",
+ /* 78 */ "\u0966",
// Label for "switch to symbols" key.
- /* 73 */ "?\u0967\u0968\u0969",
+ /* 79 */ "?\u0967\u0968\u0969",
// Label for "switch to symbols with microphone" key. This string shouldn't include the "mic"
// part because it'll be appended by the code.
- /* 74 */ "\u0967\u0968\u0969",
- /* 75 */ "1",
- /* 76 */ "2",
- /* 77 */ "3",
- /* 78 */ "4",
- /* 79 */ "5",
- /* 80 */ "6",
- /* 81 */ "7",
- /* 82 */ "8",
- /* 83 */ "9",
- /* 84 */ "0",
+ /* 80 */ "\u0967\u0968\u0969",
+ /* 81 */ "1",
+ /* 82 */ "2",
+ /* 83 */ "3",
+ /* 84 */ "4",
+ /* 85 */ "5",
+ /* 86 */ "6",
+ /* 87 */ "7",
+ /* 88 */ "8",
+ /* 89 */ "9",
+ /* 90 */ "0",
};
/* Language hr: Croatian */
@@ -1701,12 +1732,12 @@ public final class KeyboardTextsSet {
/* 13~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null,
- /* ~45 */
- /* 46 */ "!text/single_9qm_rqm",
- /* 47 */ "!text/double_9qm_rqm",
- /* 48 */ "!text/single_raqm_laqm",
- /* 49 */ "!text/double_raqm_laqm",
+ null, null, null, null, null, null, null, null, null,
+ /* ~51 */
+ /* 52 */ "!text/single_9qm_rqm",
+ /* 53 */ "!text/double_9qm_rqm",
+ /* 54 */ "!text/single_raqm_laqm",
+ /* 55 */ "!text/double_raqm_laqm",
};
/* Language hu: Hungarian */
@@ -1755,22 +1786,23 @@ public final class KeyboardTextsSet {
/* 5~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null,
- /* ~45 */
- /* 46 */ "!text/single_9qm_rqm",
- /* 47 */ "!text/double_9qm_rqm",
- /* 48 */ "!text/single_raqm_laqm",
- /* 49 */ "!text/double_raqm_laqm",
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null,
+ /* ~51 */
+ /* 52 */ "!text/single_9qm_rqm",
+ /* 53 */ "!text/double_9qm_rqm",
+ /* 54 */ "!text/single_raqm_laqm",
+ /* 55 */ "!text/double_raqm_laqm",
};
- /* Language hy: Armenian */
- private static final String[] LANGUAGE_hy = {
+ /* Language hy_AM: Armenian (Armenia) */
+ private static final String[] LANGUAGE_hy_AM = {
/* 0~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null,
- /* ~52 */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~58 */
// U+058A: "֊" ARMENIAN HYPHEN
// U+055C: "՜" ARMENIAN EXCLAMATION MARK
// U+055D: "՝" ARMENIAN COMMA
@@ -1779,19 +1811,36 @@ public final class KeyboardTextsSet {
// U+055A: "՚" ARMENIAN APOSTROPHE
// U+055B: "՛" ARMENIAN EMPHASIS MARK
// U+055F: "՟" ARMENIAN ABBREVIATION MARK
- /* 53 */ "!fixedColumnOrder!8,!,?,\\,,.,\u058A,\u055C,\u055D,\u055E,:,;,@,\u0559,\u055A,\u055B,\u055F",
- /* 54~ */
+ /* 59 */ "!fixedColumnOrder!8,!,?,\\,,.,\u058A,\u055C,\u055D,\u055E,:,;,@,\u0559,\u055A,\u055B,\u055F",
+ /* 60~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null,
- /* ~99 */
+ null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~102 */
+ // U+058F: "֏" ARMENIAN DRAM SIGN
+ // TODO: Enable this when we have glyph for the following letter
+ // <string name="keylabel_for_currency">&#x058F;</string>
+ //
+ // U+055D: "՝" ARMENIAN COMMA
+ /* 103 */ "\u055D",
+ /* 104 */ null,
+ /* 105 */ null,
+ // U+0589: "։" ARMENIAN FULL STOP
+ /* 106 */ "\u0589",
+ /* 107 */ null,
+ /* 108 */ null,
+ /* 109 */ "\u0589",
+ /* 110 */ null,
+ /* 111 */ "!text/more_keys_for_punctuation",
+ /* 112~ */
+ null, null, null,
+ /* ~114 */
// U+055C: "՜" ARMENIAN EXCLAMATION MARK
// U+00A1: "¡" INVERTED EXCLAMATION MARK
- /* 100 */ "\u055C,\u00A1",
+ /* 115 */ "\u055C,\u00A1",
// U+055E: "՞" ARMENIAN QUESTION MARK
// U+00BF: "¿" INVERTED QUESTION MARK
- /* 101 */ "\u055E,\u00BF",
+ /* 116 */ "\u055E,\u00BF",
};
/* Language is: Icelandic */
@@ -1857,10 +1906,10 @@ public final class KeyboardTextsSet {
/* 22 */ "\u00FE",
/* 23~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null,
- /* ~45 */
- /* 46 */ "!text/single_9qm_lqm",
- /* 47 */ "!text/double_9qm_lqm",
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~51 */
+ /* 52 */ "!text/single_9qm_lqm",
+ /* 53 */ "!text/double_9qm_lqm",
};
/* Language it: Italian */
@@ -1914,12 +1963,13 @@ public final class KeyboardTextsSet {
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~44 */
+ null, null, null, null, null, null,
+ /* ~50 */
// Label for "switch to alphabetic" key.
// U+05D0: "א" HEBREW LETTER ALEF
// U+05D1: "ב" HEBREW LETTER BET
// U+05D2: "ג" HEBREW LETTER GIMEL
- /* 45 */ "\u05D0\u05D1\u05D2",
+ /* 51 */ "\u05D0\u05D1\u05D2",
// The following characters don't need BIDI mirroring.
// U+2018: "‘" LEFT SINGLE QUOTATION MARK
// U+2019: "’" RIGHT SINGLE QUOTATION MARK
@@ -1927,58 +1977,51 @@ public final class KeyboardTextsSet {
// U+201C: "“" LEFT DOUBLE QUOTATION MARK
// U+201D: "”" RIGHT DOUBLE QUOTATION MARK
// U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
- /* 46 */ "\u2018,\u2019,\u201A",
- /* 47 */ "\u201C,\u201D,\u201E",
- /* 48 */ "!text/single_laqm_raqm_rtl",
- /* 49 */ "!text/double_laqm_raqm_rtl",
- /* 50 */ null,
+ /* 52 */ "\u2018,\u2019,\u201A",
+ /* 53 */ "\u201C,\u201D,\u201E",
+ /* 54 */ "!text/single_laqm_raqm_rtl",
+ /* 55 */ "!text/double_laqm_raqm_rtl",
+ /* 56 */ null,
// U+20AA: "₪" NEW SHEQEL SIGN
- /* 51 */ "\u20AA",
- /* 52 */ null,
- /* 53 */ "!fixedColumnOrder!8,;,/,(|),)|(,#,!,\\,,?,&,\\%,+,\",-,:,',@",
+ /* 57 */ "\u20AA",
+ /* 58 */ null,
+ /* 59 */ "!fixedColumnOrder!8,;,/,(|),)|(,#,!,\\,,?,&,\\%,+,\",-,:,',@",
+ /* 60 */ "!fixedColumnOrder!7,;,/,(|),)|(,#,',\\,,&,\\%,+,\",-,:,@",
// U+2605: "★" BLACK STAR
- /* 54 */ "\u2605",
- /* 55 */ null,
+ /* 61 */ "\u2605",
+ /* 62 */ null,
// U+00B1: "±" PLUS-MINUS SIGN
// U+FB29: "﬩" HEBREW LETTER ALTERNATIVE PLUS SIGN
- /* 56 */ "\u00B1,\uFB29",
+ /* 63 */ "\u00B1,\uFB29",
// The all letters need to be mirrored are found at
// http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
- /* 57 */ "!fixedColumnOrder!3,<|>,{|},[|]",
- /* 58 */ "!fixedColumnOrder!3,>|<,}|{,]|[",
+ /* 64 */ "!fixedColumnOrder!3,<|>,{|},[|]",
+ /* 65 */ "!fixedColumnOrder!3,>|<,}|{,]|[",
// U+2264: "≤" LESS-THAN OR EQUAL TO
// U+2265: "≥" GREATER-THAN EQUAL TO
// U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
// U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
// U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
// U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
- /* 59 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB",
- /* 60 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB",
- /* 61~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~104 */
- /* 105 */ "!",
- /* 106 */ "!",
- /* 107 */ "?",
- /* 108 */ "?",
+ /* 66 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB",
+ /* 67 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB",
};
- /* Language ka: Georgian */
- private static final String[] LANGUAGE_ka = {
+ /* Language ka_GE: Georgian (Georgia) */
+ private static final String[] LANGUAGE_ka_GE = {
/* 0~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~44 */
+ null, null, null, null, null, null,
+ /* ~50 */
// Label for "switch to alphabetic" key.
// U+10D0: "ა" GEORGIAN LETTER AN
// U+10D1: "ბ" GEORGIAN LETTER BAN
// U+10D2: "გ" GEORGIAN LETTER GAN
- /* 45 */ "\u10D0\u10D1\u10D2",
- /* 46 */ "!text/single_9qm_lqm",
- /* 47 */ "!text/double_9qm_lqm",
+ /* 51 */ "\u10D0\u10D1\u10D2",
+ /* 52 */ "!text/single_9qm_lqm",
+ /* 53 */ "!text/double_9qm_lqm",
};
/* Language kk: Kazakh */
@@ -2021,31 +2064,34 @@ public final class KeyboardTextsSet {
/* ~42 */
// U+0451: "ё" CYRILLIC SMALL LETTER IO
/* 43 */ "\u0451",
- /* 44 */ null,
+ /* 44~ */
+ null, null, null, null, null, null, null,
+ /* ~50 */
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
- /* 45 */ "\u0410\u0411\u0412",
+ /* 51 */ "\u0410\u0411\u0412",
};
- /* Language km: Khmer */
- private static final String[] LANGUAGE_km = {
+ /* Language km_KH: Khmer (Cambodia) */
+ private static final String[] LANGUAGE_km_KH = {
/* 0~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~44 */
+ null, null, null, null, null, null,
+ /* ~50 */
// Label for "switch to alphabetic" key.
// U+1780: "ក" KHMER LETTER KA
// U+1781: "ខ" KHMER LETTER KHA
// U+1782: "គ" KHMER LETTER KO
- /* 45 */ "\u1780\u1781\u1782",
- /* 46~ */
+ /* 51 */ "\u1780\u1781\u1782",
+ /* 52~ */
null, null, null, null,
- /* ~49 */
+ /* ~55 */
// U+17DB: "៛" KHMER CURRENCY SYMBOL RIEL
- /* 50 */ "\u17DB,\u00A2,\u00A3,\u20AC,\u00A5,\u20B1",
+ /* 56 */ "\u17DB,\u00A2,\u00A3,\u20AC,\u00A5,\u20B1",
};
/* Language ky: Kirghiz */
@@ -2081,31 +2127,34 @@ public final class KeyboardTextsSet {
/* ~42 */
// U+0451: "ё" CYRILLIC SMALL LETTER IO
/* 43 */ "\u0451",
- /* 44 */ null,
+ /* 44~ */
+ null, null, null, null, null, null, null,
+ /* ~50 */
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
- /* 45 */ "\u0410\u0411\u0412",
+ /* 51 */ "\u0410\u0411\u0412",
};
- /* Language lo: Lao */
- private static final String[] LANGUAGE_lo = {
+ /* Language lo_LA: Lao (Laos) */
+ private static final String[] LANGUAGE_lo_LA = {
/* 0~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~44 */
+ null, null, null, null, null, null,
+ /* ~50 */
// Label for "switch to alphabetic" key.
// U+0E81: "ກ" LAO LETTER KO
// U+0E82: "ຂ" LAO LETTER KHO SUNG
// U+0E84: "ຄ" LAO LETTER KHO TAM
- /* 45 */ "\u0E81\u0E82\u0E84",
- /* 46~ */
+ /* 51 */ "\u0E81\u0E82\u0E84",
+ /* 52~ */
null, null, null, null, null,
- /* ~50 */
+ /* ~56 */
// U+20AD: "₭" KIP SIGN
- /* 51 */ "\u20AD",
+ /* 57 */ "\u20AD",
};
/* Language lt: Lithuanian */
@@ -2199,9 +2248,10 @@ public final class KeyboardTextsSet {
/* 16~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~45 */
- /* 46 */ "!text/single_9qm_lqm",
- /* 47 */ "!text/double_9qm_lqm",
+ null, null, null, null, null, null,
+ /* ~51 */
+ /* 52 */ "!text/single_9qm_lqm",
+ /* 53 */ "!text/double_9qm_lqm",
};
/* Language lv: Latvian */
@@ -2294,9 +2344,10 @@ public final class KeyboardTextsSet {
/* 16~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~45 */
- /* 46 */ "!text/single_9qm_lqm",
- /* 47 */ "!text/double_9qm_lqm",
+ null, null, null, null, null, null,
+ /* ~51 */
+ /* 52 */ "!text/single_9qm_lqm",
+ /* 53 */ "!text/double_9qm_lqm",
};
/* Language mk: Macedonian */
@@ -2318,32 +2369,36 @@ public final class KeyboardTextsSet {
/* 43 */ "\u0450",
// U+045D: "ѝ" CYRILLIC SMALL LETTER I WITH GRAVE
/* 44 */ "\u045D",
+ /* 45~ */
+ null, null, null, null, null, null,
+ /* ~50 */
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
- /* 45 */ "\u0410\u0411\u0412",
- /* 46 */ "!text/single_9qm_lqm",
- /* 47 */ "!text/double_9qm_lqm",
+ /* 51 */ "\u0410\u0411\u0412",
+ /* 52 */ "!text/single_9qm_lqm",
+ /* 53 */ "!text/double_9qm_lqm",
};
- /* Language mn: Mongolian */
- private static final String[] LANGUAGE_mn = {
+ /* Language mn_MN: Mongolian (Mongolia) */
+ private static final String[] LANGUAGE_mn_MN = {
/* 0~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~44 */
+ null, null, null, null, null, null,
+ /* ~50 */
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
- /* 45 */ "\u0410\u0411\u0412",
- /* 46~ */
+ /* 51 */ "\u0410\u0411\u0412",
+ /* 52~ */
null, null, null, null, null,
- /* ~50 */
+ /* ~56 */
// U+20AE: "₮" TUGRIK SIGN
- /* 51 */ "\u20AE",
+ /* 57 */ "\u20AE",
};
/* Language nb: Norwegian Bokmål */
@@ -2393,10 +2448,10 @@ public final class KeyboardTextsSet {
/* 24 */ "\u00E4",
/* 25~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null,
- /* ~45 */
- /* 46 */ "!text/single_9qm_rqm",
- /* 47 */ "!text/double_9qm_rqm",
+ null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~51 */
+ /* 52 */ "!text/single_9qm_rqm",
+ /* 53 */ "!text/double_9qm_rqm",
};
/* Language ne: Nepali */
@@ -2405,55 +2460,56 @@ public final class KeyboardTextsSet {
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~44 */
+ null, null, null, null, null, null,
+ /* ~50 */
// Label for "switch to alphabetic" key.
// U+0915: "क" DEVANAGARI LETTER KA
// U+0916: "ख" DEVANAGARI LETTER KHA
// U+0917: "ग" DEVANAGARI LETTER GA
- /* 45 */ "\u0915\u0916\u0917",
- /* 46~ */
+ /* 51 */ "\u0915\u0916\u0917",
+ /* 52~ */
null, null, null, null, null,
- /* ~50 */
+ /* ~56 */
// U+0930/U+0941/U+002E "रु." NEPALESE RUPEE SIGN
- /* 51 */ "\u0930\u0941.",
- /* 52~ */
+ /* 57 */ "\u0930\u0941.",
+ /* 58~ */
null, null, null, null, null, null, null, null, null, null, null,
- /* ~62 */
+ /* ~68 */
// U+0967: "१" DEVANAGARI DIGIT ONE
- /* 63 */ "\u0967",
+ /* 69 */ "\u0967",
// U+0968: "२" DEVANAGARI DIGIT TWO
- /* 64 */ "\u0968",
+ /* 70 */ "\u0968",
// U+0969: "३" DEVANAGARI DIGIT THREE
- /* 65 */ "\u0969",
+ /* 71 */ "\u0969",
// U+096A: "४" DEVANAGARI DIGIT FOUR
- /* 66 */ "\u096A",
+ /* 72 */ "\u096A",
// U+096B: "५" DEVANAGARI DIGIT FIVE
- /* 67 */ "\u096B",
+ /* 73 */ "\u096B",
// U+096C: "६" DEVANAGARI DIGIT SIX
- /* 68 */ "\u096C",
+ /* 74 */ "\u096C",
// U+096D: "७" DEVANAGARI DIGIT SEVEN
- /* 69 */ "\u096D",
+ /* 75 */ "\u096D",
// U+096E: "८" DEVANAGARI DIGIT EIGHT
- /* 70 */ "\u096E",
+ /* 76 */ "\u096E",
// U+096F: "९" DEVANAGARI DIGIT NINE
- /* 71 */ "\u096F",
+ /* 77 */ "\u096F",
// U+0966: "०" DEVANAGARI DIGIT ZERO
- /* 72 */ "\u0966",
+ /* 78 */ "\u0966",
// Label for "switch to symbols" key.
- /* 73 */ "?\u0967\u0968\u0969",
+ /* 79 */ "?\u0967\u0968\u0969",
// Label for "switch to symbols with microphone" key. This string shouldn't include the "mic"
// part because it'll be appended by the code.
- /* 74 */ "\u0967\u0968\u0969",
- /* 75 */ "1",
- /* 76 */ "2",
- /* 77 */ "3",
- /* 78 */ "4",
- /* 79 */ "5",
- /* 80 */ "6",
- /* 81 */ "7",
- /* 82 */ "8",
- /* 83 */ "9",
- /* 84 */ "0",
+ /* 80 */ "\u0967\u0968\u0969",
+ /* 81 */ "1",
+ /* 82 */ "2",
+ /* 83 */ "3",
+ /* 84 */ "4",
+ /* 85 */ "5",
+ /* 86 */ "6",
+ /* 87 */ "7",
+ /* 88 */ "8",
+ /* 89 */ "9",
+ /* 90 */ "0",
};
/* Language nl: Dutch */
@@ -2508,10 +2564,10 @@ public final class KeyboardTextsSet {
/* 9~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null,
- /* ~45 */
- /* 46 */ "!text/single_9qm_rqm",
- /* 47 */ "!text/double_9qm_rqm",
+ null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~51 */
+ /* 52 */ "!text/single_9qm_rqm",
+ /* 53 */ "!text/double_9qm_rqm",
};
/* Language pl: Polish */
@@ -2569,10 +2625,10 @@ public final class KeyboardTextsSet {
/* 15~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null,
- /* ~45 */
- /* 46 */ "!text/single_9qm_rqm",
- /* 47 */ "!text/double_9qm_rqm",
+ null, null, null, null, null, null, null,
+ /* ~51 */
+ /* 52 */ "!text/single_9qm_rqm",
+ /* 53 */ "!text/double_9qm_rqm",
};
/* Language pt: Portuguese */
@@ -2675,10 +2731,10 @@ public final class KeyboardTextsSet {
/* 12~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null,
- /* ~45 */
- /* 46 */ "!text/single_9qm_rqm",
- /* 47 */ "!text/double_9qm_rqm",
+ null, null, null, null, null, null, null, null, null, null,
+ /* ~51 */
+ /* 52 */ "!text/single_9qm_rqm",
+ /* 53 */ "!text/double_9qm_rqm",
};
/* Language ru: Russian */
@@ -2707,14 +2763,16 @@ public final class KeyboardTextsSet {
/* ~42 */
// U+0451: "ё" CYRILLIC SMALL LETTER IO
/* 43 */ "\u0451",
- /* 44 */ null,
+ /* 44~ */
+ null, null, null, null, null, null, null,
+ /* ~50 */
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
- /* 45 */ "\u0410\u0411\u0412",
- /* 46 */ "!text/single_9qm_lqm",
- /* 47 */ "!text/double_9qm_lqm",
+ /* 51 */ "\u0410\u0411\u0412",
+ /* 52 */ "!text/single_9qm_lqm",
+ /* 53 */ "!text/double_9qm_lqm",
};
/* Language sk: Slovak */
@@ -2808,11 +2866,12 @@ public final class KeyboardTextsSet {
/* 16~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~45 */
- /* 46 */ "!text/single_9qm_lqm",
- /* 47 */ "!text/double_9qm_lqm",
- /* 48 */ "!text/single_raqm_laqm",
- /* 49 */ "!text/double_raqm_laqm",
+ null, null, null, null, null, null,
+ /* ~51 */
+ /* 52 */ "!text/single_9qm_lqm",
+ /* 53 */ "!text/double_9qm_lqm",
+ /* 54 */ "!text/single_raqm_laqm",
+ /* 55 */ "!text/double_raqm_laqm",
};
/* Language sl: Slovenian */
@@ -2836,12 +2895,12 @@ public final class KeyboardTextsSet {
/* 13~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null,
- /* ~45 */
- /* 46 */ "!text/single_9qm_lqm",
- /* 47 */ "!text/double_9qm_lqm",
- /* 48 */ "!text/single_raqm_laqm",
- /* 49 */ "!text/double_raqm_laqm",
+ null, null, null, null, null, null, null, null, null,
+ /* ~51 */
+ /* 52 */ "!text/single_9qm_lqm",
+ /* 53 */ "!text/double_9qm_lqm",
+ /* 54 */ "!text/single_raqm_laqm",
+ /* 55 */ "!text/double_raqm_laqm",
};
/* Language sr: Serbian */
@@ -2881,16 +2940,19 @@ public final class KeyboardTextsSet {
/* 43 */ "\u0450",
// U+045D: "ѝ" CYRILLIC SMALL LETTER I WITH GRAVE
/* 44 */ "\u045D",
+ /* 45~ */
+ null, null, null, null, null, null,
+ /* ~50 */
// END: More keys definitions for Serbian (Cyrillic)
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
- /* 45 */ "\u0410\u0411\u0412",
- /* 46 */ "!text/single_9qm_lqm",
- /* 47 */ "!text/double_9qm_lqm",
- /* 48 */ "!text/single_raqm_laqm",
- /* 49 */ "!text/double_raqm_laqm",
+ /* 51 */ "\u0410\u0411\u0412",
+ /* 52 */ "!text/single_9qm_lqm",
+ /* 53 */ "!text/double_9qm_lqm",
+ /* 54 */ "!text/single_raqm_laqm",
+ /* 55 */ "!text/double_raqm_laqm",
};
/* Language sv: Swedish */
@@ -2972,10 +3034,10 @@ public final class KeyboardTextsSet {
/* 24 */ "\u00E6",
/* 25~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null,
- /* ~47 */
- /* 48 */ "!text/single_raqm_laqm",
- /* 49 */ "!text/double_raqm_laqm",
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~53 */
+ /* 54 */ "!text/single_raqm_laqm",
+ /* 55 */ "!text/double_raqm_laqm",
};
/* Language sw: Swahili */
@@ -3035,17 +3097,18 @@ public final class KeyboardTextsSet {
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~44 */
+ null, null, null, null, null, null,
+ /* ~50 */
// Label for "switch to alphabetic" key.
// U+0E01: "ก" THAI CHARACTER KO KAI
// U+0E02: "ข" THAI CHARACTER KHO KHAI
// U+0E04: "ค" THAI CHARACTER KHO KHWAI
- /* 45 */ "\u0E01\u0E02\u0E04",
- /* 46~ */
+ /* 51 */ "\u0E01\u0E02\u0E04",
+ /* 52~ */
null, null, null, null, null,
- /* ~50 */
+ /* ~56 */
// U+0E3F: "฿" THAI CURRENCY SYMBOL BAHT
- /* 51 */ "\u0E3F",
+ /* 57 */ "\u0E3F",
};
/* Language tl: Tagalog */
@@ -3175,20 +3238,20 @@ public final class KeyboardTextsSet {
// U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
/* 37 */ "\u044A",
/* 38~ */
- null, null, null, null, null, null, null,
- /* ~44 */
+ null, null, null, null, null, null, null, null, null, null, null, null, null,
+ /* ~50 */
// Label for "switch to alphabetic" key.
// U+0410: "А" CYRILLIC CAPITAL LETTER A
// U+0411: "Б" CYRILLIC CAPITAL LETTER BE
// U+0412: "В" CYRILLIC CAPITAL LETTER VE
- /* 45 */ "\u0410\u0411\u0412",
- /* 46 */ "!text/single_9qm_lqm",
- /* 47 */ "!text/double_9qm_lqm",
- /* 48~ */
+ /* 51 */ "\u0410\u0411\u0412",
+ /* 52 */ "!text/single_9qm_lqm",
+ /* 53 */ "!text/double_9qm_lqm",
+ /* 54~ */
null, null, null,
- /* ~50 */
+ /* ~56 */
// U+20B4: "₴" HRYVNIA SIGN
- /* 51 */ "\u20B4",
+ /* 57 */ "\u20B4",
};
/* Language vi: Vietnamese */
@@ -3273,10 +3336,11 @@ public final class KeyboardTextsSet {
/* 10~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null,
- /* ~50 */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null,
+ /* ~56 */
// U+20AB: "₫" DONG SIGN
- /* 51 */ "\u20AB",
+ /* 57 */ "\u20AB",
};
/* Language zu: Zulu */
@@ -3449,12 +3513,14 @@ public final class KeyboardTextsSet {
/* 19 */ "\u0175",
};
+ // TODO: Use the language + "_" + region representation for the locale string key.
+ // Currently we are dropping the region from the key.
private static final Object[] LANGUAGES_AND_TEXTS = {
"DEFAULT", LANGUAGE_DEFAULT, /* default */
"af", LANGUAGE_af, /* Afrikaans */
"ar", LANGUAGE_ar, /* Arabic */
"az", LANGUAGE_az, /* Azerbaijani */
- "be", LANGUAGE_be, /* Belarusian */
+ "be" /* "be_BY" */, LANGUAGE_be_BY, /* Belarusian (Belarus) */
"bg", LANGUAGE_bg, /* Bulgarian */
"ca", LANGUAGE_ca, /* Catalan */
"cs", LANGUAGE_cs, /* Czech */
@@ -3464,26 +3530,26 @@ public final class KeyboardTextsSet {
"en", LANGUAGE_en, /* English */
"eo", LANGUAGE_eo, /* Esperanto */
"es", LANGUAGE_es, /* Spanish */
- "et", LANGUAGE_et, /* Estonian */
+ "et" /* "et_EE" */, LANGUAGE_et_EE, /* Estonian (Estonia) */
"fa", LANGUAGE_fa, /* Persian */
"fi", LANGUAGE_fi, /* Finnish */
"fr", LANGUAGE_fr, /* French */
"hi", LANGUAGE_hi, /* Hindi */
"hr", LANGUAGE_hr, /* Croatian */
"hu", LANGUAGE_hu, /* Hungarian */
- "hy", LANGUAGE_hy, /* Armenian */
+ "hy" /* "hy_AM" */, LANGUAGE_hy_AM, /* Armenian (Armenia) */
"is", LANGUAGE_is, /* Icelandic */
"it", LANGUAGE_it, /* Italian */
"iw", LANGUAGE_iw, /* Hebrew */
- "ka", LANGUAGE_ka, /* Georgian */
+ "ka" /* "ka_GE" */, LANGUAGE_ka_GE, /* Georgian (Georgia) */
"kk", LANGUAGE_kk, /* Kazakh */
- "km", LANGUAGE_km, /* Khmer */
+ "km" /* "km_KH" */, LANGUAGE_km_KH, /* Khmer (Cambodia) */
"ky", LANGUAGE_ky, /* Kirghiz */
- "lo", LANGUAGE_lo, /* Lao */
+ "lo" /* "lo_LA" */, LANGUAGE_lo_LA, /* Lao (Laos) */
"lt", LANGUAGE_lt, /* Lithuanian */
"lv", LANGUAGE_lv, /* Latvian */
"mk", LANGUAGE_mk, /* Macedonian */
- "mn", LANGUAGE_mn, /* Mongolian */
+ "mn" /* "mn_MN" */, LANGUAGE_mn_MN, /* Mongolian (Mongolia) */
"nb", LANGUAGE_nb, /* Norwegian Bokmål */
"ne", LANGUAGE_ne, /* Nepali */
"nl", LANGUAGE_nl, /* Dutch */
diff --git a/java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java b/java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java
index a0935b985..111eb6db6 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java
@@ -20,18 +20,24 @@ import android.util.Log;
import android.view.MotionEvent;
import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.KeyDetector;
import com.android.inputmethod.keyboard.PointerTracker;
-import com.android.inputmethod.keyboard.PointerTracker.KeyEventHandler;
import com.android.inputmethod.latin.utils.CoordinateUtils;
public final class NonDistinctMultitouchHelper {
private static final String TAG = NonDistinctMultitouchHelper.class.getSimpleName();
+ // Use only main (id=0) pointer tracker.
+ private final PointerTracker mMainTracker;
private int mOldPointerCount = 1;
private Key mOldKey;
private int[] mLastCoords = CoordinateUtils.newInstance();
- public void processMotionEvent(final MotionEvent me, final KeyEventHandler keyEventHandler) {
+ public NonDistinctMultitouchHelper(final PointerTracker mainTracker) {
+ mMainTracker = mainTracker;
+ }
+
+ public void processMotionEvent(final MotionEvent me, final KeyDetector keyDetector) {
final int pointerCount = me.getPointerCount();
final int oldPointerCount = mOldPointerCount;
mOldPointerCount = pointerCount;
@@ -41,8 +47,7 @@ public final class NonDistinctMultitouchHelper {
return;
}
- // Use only main (id=0) pointer tracker.
- final PointerTracker mainTracker = PointerTracker.getPointerTracker(0, keyEventHandler);
+ final PointerTracker mainTracker = mMainTracker;
final int action = me.getActionMasked();
final int index = me.getActionIndex();
final long eventTime = me.getEventTime();
@@ -51,12 +56,12 @@ public final class NonDistinctMultitouchHelper {
// In single-touch.
if (oldPointerCount == 1 && pointerCount == 1) {
if (me.getPointerId(index) == mainTracker.mPointerId) {
- mainTracker.processMotionEvent(me, keyEventHandler);
+ mainTracker.processMotionEvent(me, keyDetector);
return;
}
// Inject a copied event.
injectMotionEvent(action, me.getX(index), me.getY(index), downTime, eventTime,
- mainTracker, keyEventHandler);
+ mainTracker, keyDetector);
return;
}
@@ -70,7 +75,7 @@ public final class NonDistinctMultitouchHelper {
mOldKey = mainTracker.getKeyOn(x, y);
// Inject an artifact up event for the old key.
injectMotionEvent(MotionEvent.ACTION_UP, x, y, downTime, eventTime,
- mainTracker, keyEventHandler);
+ mainTracker, keyDetector);
return;
}
@@ -85,11 +90,11 @@ public final class NonDistinctMultitouchHelper {
// Inject an artifact down event for the new key.
// An artifact up event for the new key will usually be injected as a single-touch.
injectMotionEvent(MotionEvent.ACTION_DOWN, x, y, downTime, eventTime,
- mainTracker, keyEventHandler);
+ mainTracker, keyDetector);
if (action == MotionEvent.ACTION_UP) {
// Inject an artifact up event for the new key also.
injectMotionEvent(MotionEvent.ACTION_UP, x, y, downTime, eventTime,
- mainTracker, keyEventHandler);
+ mainTracker, keyDetector);
}
}
return;
@@ -101,11 +106,11 @@ public final class NonDistinctMultitouchHelper {
private static void injectMotionEvent(final int action, final float x, final float y,
final long downTime, final long eventTime, final PointerTracker tracker,
- final KeyEventHandler handler) {
+ final KeyDetector keyDetector) {
final MotionEvent me = MotionEvent.obtain(
downTime, eventTime, action, x, y, 0 /* metaState */);
try {
- tracker.processMotionEvent(me, handler);
+ tracker.processMotionEvent(me, keyDetector);
} finally {
me.recycle();
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
index 7ee45e8f6..5ac34188c 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
@@ -28,7 +28,7 @@ public final class PointerTrackerQueue {
public interface Element {
public boolean isModifier();
- public boolean isInSlidingKeyInput();
+ public boolean isInDraggingFinger();
public void onPhantomUpEvent(long eventTime);
public void cancelTrackingForAction();
}
@@ -193,13 +193,13 @@ public final class PointerTrackerQueue {
}
}
- public boolean isAnyInSlidingKeyInput() {
+ public boolean isAnyInDraggingFinger() {
synchronized (mExpandableArrayOfActivePointers) {
final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
final int arraySize = mArraySize;
for (int index = 0; index < arraySize; index++) {
final Element element = expandableArray.get(index);
- if (element.isInSlidingKeyInput()) {
+ if (element.isInDraggingFinger()) {
return true;
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/ScrollViewWithNotifier.java b/java/src/com/android/inputmethod/keyboard/internal/ScrollViewWithNotifier.java
deleted file mode 100644
index d1ccdc7b5..000000000
--- a/java/src/com/android/inputmethod/keyboard/internal/ScrollViewWithNotifier.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2013 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.util.AttributeSet;
-import android.widget.ScrollView;
-
-/**
- * This is an extended {@link ScrollView} that can notify
- * {@link ScrollView#onScrollChanged(int,int,int,int} and
- * {@link ScrollView#onOverScrolled(int,int,int,int)} to a content view.
- */
-public class ScrollViewWithNotifier extends ScrollView {
- private ScrollListener mScrollListener = EMPTY_LISTER;
-
- public interface ScrollListener {
- public void notifyScrollChanged(int scrollX, int scrollY, int oldX, int oldY);
- public void notifyOverScrolled(int scrollX, int scrollY, boolean clampedX,
- boolean clampedY);
- }
-
- private static final ScrollListener EMPTY_LISTER = new ScrollListener() {
- @Override
- public void notifyScrollChanged(int scrollX, int scrollY, int oldX, int oldY) {}
- @Override
- public void notifyOverScrolled(int scrollX, int scrollY, boolean clampedX,
- boolean clampedY) {}
- };
-
- public ScrollViewWithNotifier(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onScrollChanged(final int scrollX, final int scrollY, final int oldX,
- final int oldY) {
- super.onScrollChanged(scrollX, scrollY, oldX, oldY);
- mScrollListener.notifyScrollChanged(scrollX, scrollY, oldX, oldY);
- }
-
- @Override
- protected void onOverScrolled(final int scrollX, final int scrollY, final boolean clampedX,
- final boolean clampedY) {
- super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
- mScrollListener.notifyOverScrolled(scrollX, scrollY, clampedX, clampedY);
- }
-
- public void setScrollListener(final ScrollListener listener) {
- mScrollListener = listener;
- }
-}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java b/java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java
new file mode 100644
index 000000000..88d0878d1
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2013 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.os.Message;
+import android.os.SystemClock;
+import android.view.ViewConfiguration;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.PointerTracker;
+import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
+import com.android.inputmethod.keyboard.internal.TimerHandler.Callbacks;
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
+
+// TODO: Separate this class into KeyTimerHandler and BatchInputTimerHandler or so.
+public final class TimerHandler extends LeakGuardHandlerWrapper<Callbacks> implements TimerProxy {
+ public interface Callbacks {
+ public void startWhileTypingFadeinAnimation();
+ public void startWhileTypingFadeoutAnimation();
+ public void onLongPress(PointerTracker tracker);
+ }
+
+ private static final int MSG_TYPING_STATE_EXPIRED = 0;
+ private static final int MSG_REPEAT_KEY = 1;
+ private static final int MSG_LONGPRESS_KEY = 2;
+ private static final int MSG_DOUBLE_TAP_SHIFT_KEY = 3;
+ private static final int MSG_UPDATE_BATCH_INPUT = 4;
+
+ private final int mIgnoreAltCodeKeyTimeout;
+ private final int mGestureRecognitionUpdateTime;
+
+ public TimerHandler(final Callbacks ownerInstance, final int ignoreAltCodeKeyTimeout,
+ final int gestureRecognitionUpdateTime) {
+ super(ownerInstance);
+ mIgnoreAltCodeKeyTimeout = ignoreAltCodeKeyTimeout;
+ mGestureRecognitionUpdateTime = gestureRecognitionUpdateTime;
+ }
+
+ @Override
+ public void handleMessage(final Message msg) {
+ final Callbacks callbacks = getOwnerInstance();
+ if (callbacks == null) {
+ return;
+ }
+ final PointerTracker tracker = (PointerTracker) msg.obj;
+ switch (msg.what) {
+ case MSG_TYPING_STATE_EXPIRED:
+ callbacks.startWhileTypingFadeinAnimation();
+ break;
+ case MSG_REPEAT_KEY:
+ tracker.onKeyRepeat(msg.arg1 /* code */, msg.arg2 /* repeatCount */);
+ break;
+ case MSG_LONGPRESS_KEY:
+ cancelLongPressTimers();
+ callbacks.onLongPress(tracker);
+ break;
+ case MSG_UPDATE_BATCH_INPUT:
+ tracker.updateBatchInputByTimer(SystemClock.uptimeMillis());
+ startUpdateBatchInputTimer(tracker);
+ break;
+ }
+ }
+
+ @Override
+ public void startKeyRepeatTimerOf(final PointerTracker tracker, final int repeatCount,
+ final int delay) {
+ final Key key = tracker.getKey();
+ if (key == null || delay == 0) {
+ return;
+ }
+ sendMessageDelayed(
+ obtainMessage(MSG_REPEAT_KEY, key.getCode(), repeatCount, tracker), delay);
+ }
+
+ private void cancelKeyRepeatTimerOf(final PointerTracker tracker) {
+ removeMessages(MSG_REPEAT_KEY, tracker);
+ }
+
+ public void cancelKeyRepeatTimers() {
+ removeMessages(MSG_REPEAT_KEY);
+ }
+
+ // TODO: Suppress layout changes in key repeat mode
+ public boolean isInKeyRepeat() {
+ return hasMessages(MSG_REPEAT_KEY);
+ }
+
+ @Override
+ public void startLongPressTimerOf(final PointerTracker tracker, final int delay) {
+ if (delay <= 0) {
+ return;
+ }
+ sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, tracker), delay);
+ }
+
+ @Override
+ public void cancelLongPressTimerOf(final PointerTracker tracker) {
+ removeMessages(MSG_LONGPRESS_KEY, tracker);
+ }
+
+ private void cancelLongPressTimers() {
+ removeMessages(MSG_LONGPRESS_KEY);
+ }
+
+ @Override
+ public void startTypingStateTimer(final Key typedKey) {
+ if (typedKey.isModifier() || typedKey.altCodeWhileTyping()) {
+ return;
+ }
+
+ final boolean isTyping = isTypingState();
+ removeMessages(MSG_TYPING_STATE_EXPIRED);
+ final Callbacks callbacks = getOwnerInstance();
+ if (callbacks == null) {
+ return;
+ }
+
+ // When user hits the space or the enter key, just cancel the while-typing timer.
+ final int typedCode = typedKey.getCode();
+ if (typedCode == Constants.CODE_SPACE || typedCode == Constants.CODE_ENTER) {
+ if (isTyping) {
+ callbacks.startWhileTypingFadeinAnimation();
+ }
+ return;
+ }
+
+ sendMessageDelayed(
+ obtainMessage(MSG_TYPING_STATE_EXPIRED), mIgnoreAltCodeKeyTimeout);
+ if (isTyping) {
+ return;
+ }
+ callbacks.startWhileTypingFadeoutAnimation();
+ }
+
+ @Override
+ public boolean isTypingState() {
+ return hasMessages(MSG_TYPING_STATE_EXPIRED);
+ }
+
+ @Override
+ public void startDoubleTapShiftKeyTimer() {
+ sendMessageDelayed(obtainMessage(MSG_DOUBLE_TAP_SHIFT_KEY),
+ ViewConfiguration.getDoubleTapTimeout());
+ }
+
+ @Override
+ public void cancelDoubleTapShiftKeyTimer() {
+ removeMessages(MSG_DOUBLE_TAP_SHIFT_KEY);
+ }
+
+ @Override
+ public boolean isInDoubleTapShiftKeyTimeout() {
+ return hasMessages(MSG_DOUBLE_TAP_SHIFT_KEY);
+ }
+
+ @Override
+ public void cancelKeyTimersOf(final PointerTracker tracker) {
+ cancelKeyRepeatTimerOf(tracker);
+ cancelLongPressTimerOf(tracker);
+ }
+
+ public void cancelAllKeyTimers() {
+ cancelKeyRepeatTimers();
+ cancelLongPressTimers();
+ }
+
+ @Override
+ public void startUpdateBatchInputTimer(final PointerTracker tracker) {
+ if (mGestureRecognitionUpdateTime <= 0) {
+ return;
+ }
+ removeMessages(MSG_UPDATE_BATCH_INPUT, tracker);
+ sendMessageDelayed(obtainMessage(MSG_UPDATE_BATCH_INPUT, tracker),
+ mGestureRecognitionUpdateTime);
+ }
+
+ @Override
+ public void cancelUpdateBatchInputTimer(final PointerTracker tracker) {
+ removeMessages(MSG_UPDATE_BATCH_INPUT, tracker);
+ }
+
+ @Override
+ public void cancelAllUpdateBatchInputTimers() {
+ removeMessages(MSG_UPDATE_BATCH_INPUT);
+ }
+
+ public void cancelAllMessages() {
+ cancelAllKeyTimers();
+ cancelAllUpdateBatchInputTimers();
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java b/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
index 463d09344..e6fb9807e 100644
--- a/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
+++ b/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
@@ -27,15 +27,13 @@ import java.io.File;
import java.io.IOException;
import java.util.Map;
-// TODO: Quit extending Dictionary after implementing dynamic binary dictionary.
-abstract public class AbstractDictionaryWriter extends Dictionary {
+abstract public class AbstractDictionaryWriter {
/** Used for Log actions from this class */
private static final String TAG = AbstractDictionaryWriter.class.getSimpleName();
private final Context mContext;
- public AbstractDictionaryWriter(final Context context, final String dictType) {
- super(dictType);
+ public AbstractDictionaryWriter(final Context context) {
mContext = context;
}
@@ -55,18 +53,16 @@ abstract public class AbstractDictionaryWriter extends Dictionary {
// TODO: Remove lastModifiedTime after making binary dictionary support forgetting curve.
abstract public void addBigramWords(final String word0, final String word1,
- final int frequency, final boolean isValid,
- final long lastModifiedTime);
+ final int frequency, final boolean isValid, final long lastModifiedTime);
abstract public void removeBigramWords(final String word0, final String word1);
abstract protected void writeDictionary(final DictEncoder dictEncoder,
final Map<String, String> attributeMap) throws IOException, UnsupportedFormatException;
- public void write(final String fileName, final Map<String, String> attributeMap) {
- final String tempFileName = fileName + ".temp";
- final File file = new File(mContext.getFilesDir(), fileName);
- final File tempFile = new File(mContext.getFilesDir(), tempFileName);
+ public void write(final File file, final Map<String, String> attributeMap) {
+ final String tempFilePath = file.getAbsolutePath() + ".temp";
+ final File tempFile = new File(tempFilePath);
try {
final DictEncoder dictEncoder = new Ver3DictEncoder(tempFile);
writeDictionary(dictEncoder, attributeMap);
diff --git a/java/src/com/android/inputmethod/latin/AssetFileAddress.java b/java/src/com/android/inputmethod/latin/AssetFileAddress.java
index 875192554..fd6c24dfe 100644
--- a/java/src/com/android/inputmethod/latin/AssetFileAddress.java
+++ b/java/src/com/android/inputmethod/latin/AssetFileAddress.java
@@ -16,6 +16,8 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.latin.utils.FileUtils;
+
import java.io.File;
/**
@@ -52,4 +54,12 @@ public final class AssetFileAddress {
if (!f.isFile()) return null;
return new AssetFileAddress(filename, offset, length);
}
+
+ public boolean pointsToPhysicalFile() {
+ return 0 == mOffset;
+ }
+
+ public void deleteUnderlyingFile() {
+ FileUtils.deleteRecursively(new File(mFilename));
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index fd296988e..db4234c63 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -26,6 +26,7 @@ import com.android.inputmethod.latin.settings.NativeSuggestOptions;
import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.JniUtils;
import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.utils.UnigramProperty;
import java.io.File;
import java.util.ArrayList;
@@ -57,6 +58,21 @@ public final class BinaryDictionary extends Dictionary {
@UsedForTesting
public static final String MAX_BIGRAM_COUNT_QUERY = "MAX_BIGRAM_COUNT";
+ public static final int NOT_A_VALID_TIMESTAMP = -1;
+
+ // Format to get unigram flags from native side via getUnigramPropertyNative().
+ private static final int FORMAT_UNIGRAM_PROPERTY_OUTPUT_FLAG_COUNT = 4;
+ private static final int FORMAT_UNIGRAM_PROPERTY_IS_NOT_A_WORD_INDEX = 0;
+ private static final int FORMAT_UNIGRAM_PROPERTY_IS_BLACKLISTED_INDEX = 1;
+ private static final int FORMAT_UNIGRAM_PROPERTY_HAS_BIGRAMS_INDEX = 2;
+ private static final int FORMAT_UNIGRAM_PROPERTY_HAS_SHORTCUTS_INDEX = 3;
+
+ // Format to get unigram historical info from native side via getUnigramPropertyNative().
+ private static final int FORMAT_UNIGRAM_PROPERTY_OUTPUT_HISTORICAL_INFO_COUNT = 3;
+ private static final int FORMAT_UNIGRAM_PROPERTY_TIMESTAMP_INDEX = 0;
+ private static final int FORMAT_UNIGRAM_PROPERTY_LEVEL_INDEX = 1;
+ private static final int FORMAT_UNIGRAM_PROPERTY_COUNT_INDEX = 2;
+
private long mNativeDict;
private final Locale mLocale;
private final long mDictSize;
@@ -123,8 +139,13 @@ public final class BinaryDictionary extends Dictionary {
private static native boolean needsToRunGCNative(long dict, boolean mindsBlockByGC);
private static native void flushWithGCNative(long dict, String filePath);
private static native void closeNative(long dict);
+ private static native int getFormatVersionNative(long dict);
private static native int getProbabilityNative(long dict, int[] word);
private static native int getBigramProbabilityNative(long dict, int[] word0, int[] word1);
+ private static native void getUnigramPropertyNative(long dict, int[] word,
+ int[] outCodePoints, boolean[] outFlags, int[] outProbability,
+ int[] outHistoricalInfo, ArrayList<int[]> outShortcutTargets,
+ ArrayList<Integer> outShortcutProbabilities);
private static native int getSuggestionsNative(long dict, long proximityInfo,
long traverseSession, int[] xCoordinates, int[] yCoordinates, int[] times,
int[] pointerIds, int[] inputCodePoints, int inputSize, int commitPoint,
@@ -133,10 +154,14 @@ public final class BinaryDictionary extends Dictionary {
int[] outputAutoCommitFirstWordConfidence);
private static native float calcNormalizedScoreNative(int[] before, int[] after, int score);
private static native int editDistanceNative(int[] before, int[] after);
- private static native void addUnigramWordNative(long dict, int[] word, int probability);
+ private static native void addUnigramWordNative(long dict, int[] word, int probability,
+ int[] shortcutTarget, int shortcutProbability, boolean isNotAWord,
+ boolean isBlacklisted, int timestamp);
private static native void addBigramWordsNative(long dict, int[] word0, int[] word1,
- int probability);
+ int probability, int timestamp);
private static native void removeBigramWordsNative(long dict, int[] word0, int[] word1);
+ private static native int addMultipleDictionaryEntriesNative(long dict,
+ LanguageModelParam[] languageModelParams, int startIndex);
private static native int calculateProbabilityNative(long dict, int unigramProbability,
int bigramProbability);
private static native String getPropertyNative(long dict, String query);
@@ -235,6 +260,10 @@ public final class BinaryDictionary extends Dictionary {
return mNativeDict != 0;
}
+ public int getFormatVersion() {
+ return getFormatVersionNative(mNativeDict);
+ }
+
public static float calcNormalizedScore(final String before, final String after,
final int score) {
return calcNormalizedScoreNative(StringUtils.toCodePointArray(before),
@@ -274,23 +303,55 @@ public final class BinaryDictionary extends Dictionary {
return getBigramProbabilityNative(mNativeDict, codePoints0, codePoints1);
}
- // Add a unigram entry to binary dictionary in native code.
- public void addUnigramWord(final String word, final int probability) {
+ @UsedForTesting
+ public UnigramProperty getUnigramProperty(final String word) {
+ if (TextUtils.isEmpty(word)) {
+ return null;
+ }
+ final int[] codePoints = StringUtils.toCodePointArray(word);
+ final int[] outCodePoints = new int[MAX_WORD_LENGTH];
+ final boolean[] outFlags = new boolean[FORMAT_UNIGRAM_PROPERTY_OUTPUT_FLAG_COUNT];
+ final int[] outProbability = new int[1];
+ final int[] outHistoricalInfo =
+ new int[FORMAT_UNIGRAM_PROPERTY_OUTPUT_HISTORICAL_INFO_COUNT];
+ final ArrayList<int[]> outShortcutTargets = CollectionUtils.newArrayList();
+ final ArrayList<Integer> outShortcutProbabilities = CollectionUtils.newArrayList();
+ getUnigramPropertyNative(mNativeDict, codePoints, outCodePoints, outFlags, outProbability,
+ outHistoricalInfo, outShortcutTargets, outShortcutProbabilities);
+ return new UnigramProperty(codePoints,
+ outFlags[FORMAT_UNIGRAM_PROPERTY_IS_NOT_A_WORD_INDEX],
+ outFlags[FORMAT_UNIGRAM_PROPERTY_IS_BLACKLISTED_INDEX],
+ outFlags[FORMAT_UNIGRAM_PROPERTY_HAS_BIGRAMS_INDEX],
+ outFlags[FORMAT_UNIGRAM_PROPERTY_HAS_SHORTCUTS_INDEX], outProbability[0],
+ outHistoricalInfo[FORMAT_UNIGRAM_PROPERTY_TIMESTAMP_INDEX],
+ outHistoricalInfo[FORMAT_UNIGRAM_PROPERTY_LEVEL_INDEX],
+ outHistoricalInfo[FORMAT_UNIGRAM_PROPERTY_COUNT_INDEX],
+ outShortcutTargets, outShortcutProbabilities);
+ }
+
+ // Add a unigram entry to binary dictionary with unigram attributes in native code.
+ public void addUnigramWord(final String word, final int probability,
+ final String shortcutTarget, final int shortcutProbability, final boolean isNotAWord,
+ final boolean isBlacklisted, final int timestamp) {
if (TextUtils.isEmpty(word)) {
return;
}
final int[] codePoints = StringUtils.toCodePointArray(word);
- addUnigramWordNative(mNativeDict, codePoints, probability);
+ final int[] shortcutTargetCodePoints = (shortcutTarget != null) ?
+ StringUtils.toCodePointArray(shortcutTarget) : null;
+ addUnigramWordNative(mNativeDict, codePoints, probability, shortcutTargetCodePoints,
+ shortcutProbability, isNotAWord, isBlacklisted, timestamp);
}
- // Add a bigram entry to binary dictionary in native code.
- public void addBigramWords(final String word0, final String word1, final int probability) {
+ // Add a bigram entry to binary dictionary with timestamp in native code.
+ public void addBigramWords(final String word0, final String word1, final int probability,
+ final int timestamp) {
if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) {
return;
}
final int[] codePoints0 = StringUtils.toCodePointArray(word0);
final int[] codePoints1 = StringUtils.toCodePointArray(word1);
- addBigramWordsNative(mNativeDict, codePoints0, codePoints1, probability);
+ addBigramWordsNative(mNativeDict, codePoints0, codePoints1, probability, timestamp);
}
// Remove a bigram entry form binary dictionary in native code.
@@ -303,10 +364,70 @@ public final class BinaryDictionary extends Dictionary {
removeBigramWordsNative(mNativeDict, codePoints0, codePoints1);
}
+ public static class LanguageModelParam {
+ public final int[] mWord0;
+ public final int[] mWord1;
+ public final int[] mShortcutTarget;
+ public final int mUnigramProbability;
+ public final int mBigramProbability;
+ public final int mShortcutProbability;
+ public final boolean mIsNotAWord;
+ public final boolean mIsBlacklisted;
+ public final int mTimestamp;
+
+ // Constructor for unigram.
+ public LanguageModelParam(final String word, final int unigramProbability,
+ final int timestamp) {
+ mWord0 = null;
+ mWord1 = StringUtils.toCodePointArray(word);
+ mShortcutTarget = null;
+ mUnigramProbability = unigramProbability;
+ mBigramProbability = NOT_A_PROBABILITY;
+ mShortcutProbability = NOT_A_PROBABILITY;
+ mIsNotAWord = false;
+ mIsBlacklisted = false;
+ mTimestamp = timestamp;
+ }
+
+ // Constructor for unigram and bigram.
+ public LanguageModelParam(final String word0, final String word1,
+ final int unigramProbability, final int bigramProbability,
+ final int timestamp) {
+ mWord0 = StringUtils.toCodePointArray(word0);
+ mWord1 = StringUtils.toCodePointArray(word1);
+ mShortcutTarget = null;
+ mUnigramProbability = unigramProbability;
+ mBigramProbability = bigramProbability;
+ mShortcutProbability = NOT_A_PROBABILITY;
+ mIsNotAWord = false;
+ mIsBlacklisted = false;
+ mTimestamp = timestamp;
+ }
+ }
+
+ public void addMultipleDictionaryEntries(final LanguageModelParam[] languageModelParams) {
+ if (!isValidDictionary()) return;
+ int processedParamCount = 0;
+ while (processedParamCount < languageModelParams.length) {
+ if (needsToRunGC(true /* mindsBlockByGC */)) {
+ flushWithGC();
+ }
+ processedParamCount = addMultipleDictionaryEntriesNative(mNativeDict,
+ languageModelParams, processedParamCount);
+ if (processedParamCount <= 0) {
+ return;
+ }
+ }
+
+ }
+
private void reopen() {
close();
final File dictFile = new File(mDictFilePath);
- mNativeDict = openNative(dictFile.getAbsolutePath(), 0 /* startOffset */,
+ // WARNING: Because we pass 0 as the offset and file.length() as the length, this can
+ // only be called for actual files. Right now it's only called by the flush() family of
+ // functions, which require an updatable dictionary, so it's okay. But beware.
+ loadDictionary(dictFile.getAbsolutePath(), 0 /* startOffset */,
dictFile.length(), true /* isUpdatable */);
}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index 722a82961..b4382bc2c 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -98,7 +98,7 @@ public final class BinaryDictionaryFileDumper {
* This creates a URI builder able to build a URI pointing to the dictionary
* pack content provider for a specific dictionary id.
*/
- private static Uri.Builder getProviderUriBuilder(final String path) {
+ public static Uri.Builder getProviderUriBuilder(final String path) {
return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
.authority(DictionaryPackConstants.AUTHORITY).appendPath(path);
}
@@ -339,15 +339,25 @@ public final class BinaryDictionaryFileDumper {
Log.e(TAG, "Could not copy a word list. Will not be able to use it.");
// If we can't copy it we should warn the dictionary provider so that it can mark it
// as invalid.
- wordListUriBuilder.appendQueryParameter(QUERY_PARAMETER_DELETE_RESULT,
- QUERY_PARAMETER_FAILURE);
+ reportBrokenFileToDictionaryProvider(providerClient, clientId, wordlistId);
+ }
+
+ public static boolean reportBrokenFileToDictionaryProvider(
+ final ContentProviderClient providerClient, final String clientId,
+ final String wordlistId) {
try {
+ final Uri.Builder wordListUriBuilder = getContentUriBuilderForType(clientId,
+ providerClient, QUERY_PATH_DATAFILE, wordlistId /* extraPath */);
+ wordListUriBuilder.appendQueryParameter(QUERY_PARAMETER_DELETE_RESULT,
+ QUERY_PARAMETER_FAILURE);
if (0 >= providerClient.delete(wordListUriBuilder.build(), null, null)) {
- Log.e(TAG, "In addition, we were unable to delete it.");
+ Log.e(TAG, "Unable to delete a word list.");
}
} catch (RemoteException e) {
- Log.e(TAG, "In addition, communication with the dictionary provider was cut", e);
+ Log.e(TAG, "Communication with the dictionary provider was cut", e);
+ return false;
}
+ return true;
}
// Ideally the two following methods should be merged, but AssetFileDescriptor does not
@@ -432,8 +442,9 @@ public final class BinaryDictionaryFileDumper {
// Actually copy the file
final byte[] buffer = new byte[FILE_READ_BUFFER_SIZE];
- for (int readBytes = input.read(buffer); readBytes >= 0; readBytes = input.read(buffer))
+ for (int readBytes = input.read(buffer); readBytes >= 0; readBytes = input.read(buffer)) {
output.write(buffer, 0, readBytes);
+ }
input.close();
}
@@ -478,8 +489,7 @@ public final class BinaryDictionaryFileDumper {
* @param context the context for resources and providers.
* @param clientId the client ID to use.
*/
- public static void initializeClientRecordHelper(final Context context,
- final String clientId) {
+ public static void initializeClientRecordHelper(final Context context, final String clientId) {
try {
final ContentProviderClient client = context.getContentResolver().
acquireContentProviderClient(getProviderUriBuilder("").build());
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index 9a9653094..c260434d5 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -70,38 +70,47 @@ public final class Constants {
public static final class ExtraValue {
/**
- * The subtype extra value used to indicate that the subtype keyboard layout is capable
- * for typing ASCII characters.
+ * The subtype extra value used to indicate that this subtype is capable of
+ * entering ASCII characters.
*/
public static final String ASCII_CAPABLE = "AsciiCapable";
/**
- * The subtype extra value used to indicate that the subtype keyboard layout is capable
- * for typing EMOJI characters.
+ * The subtype extra value used to indicate that this subtype is enabled
+ * when the default subtype is not marked as ascii capable.
+ */
+ public static final String ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE =
+ "EnabledWhenDefaultIsNotAsciiCapable";
+
+ /**
+ * The subtype extra value used to indicate that this subtype is capable of
+ * entering emoji characters.
*/
public static final String EMOJI_CAPABLE = "EmojiCapable";
+
/**
- * The subtype extra value used to indicate that the subtype require network connection
- * to work.
+ * The subtype extra value used to indicate that this subtype requires a network
+ * connection to work.
*/
public static final String REQ_NETWORK_CONNECTIVITY = "requireNetworkConnectivity";
/**
- * The subtype extra value used to indicate that the subtype display name contains "%s"
- * for replacement mark and it should be replaced by this extra value.
+ * The subtype extra value used to indicate that the display name of this subtype
+ * contains a "%s" for printf-like replacement and it should be replaced by
+ * this extra value.
* This extra value is supported on JellyBean and later.
*/
public static final String UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME =
"UntranslatableReplacementStringInSubtypeName";
/**
- * The subtype extra value used to indicate that the subtype keyboard layout set name.
+ * The subtype extra value used to indicate this subtype keyboard layout set name.
* This extra value is private to LatinIME.
*/
public static final String KEYBOARD_LAYOUT_SET = "KeyboardLayoutSet";
/**
- * The subtype extra value used to indicate that the subtype is additional subtype
+ * The subtype extra value used to indicate that this subtype is an additional subtype
* that the user defined. This extra value is private to LatinIME.
*/
public static final String IS_ADDITIONAL_SUBTYPE = "isAdditionalSubtype";
diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index 47891c6b7..b6cfcd064 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -44,7 +44,8 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
private static final String TAG = ContactsBinaryDictionary.class.getSimpleName();
private static final String NAME = "contacts";
- private static boolean DEBUG = false;
+ private static final boolean DEBUG = false;
+ private static final boolean DEBUG_DUMP = false;
/**
* Frequency for contacts information into the dictionary
@@ -71,8 +72,8 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
private final boolean mUseFirstLastBigrams;
public ContactsBinaryDictionary(final Context context, final Locale locale) {
- super(context, getFilenameWithLocale(NAME, locale.toString()), Dictionary.TYPE_CONTACTS,
- false /* isUpdatable */);
+ super(context, getDictNameWithLocale(NAME, locale), locale,
+ Dictionary.TYPE_CONTACTS, false /* isUpdatable */);
mLocale = locale;
mUseFirstLastBigrams = useFirstLastBigramsForLocale(locale);
registerObserver(context);
@@ -168,6 +169,10 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
if (isValidName(name)) {
addName(name);
++count;
+ } else {
+ if (DEBUG_DUMP) {
+ Log.d(TAG, "Invalid name: " + name);
+ }
}
cursor.moveToNext();
}
@@ -204,6 +209,9 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
if (Character.isLetter(name.codePointAt(i))) {
int end = getWordEndPosition(name, len, i);
String word = name.substring(i, end);
+ if (DEBUG_DUMP) {
+ Log.d(TAG, "addName word = " + word);
+ }
i = end - 1;
// Don't add single letter words, possibly confuses
// capitalization of i.
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index fa79f5af7..e04524843 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -52,13 +52,10 @@ public abstract class Dictionary {
public static final String TYPE_CONTACTS = "contacts";
// User dictionary, the system-managed one.
public static final String TYPE_USER = "user";
- // User history dictionary internal to LatinIME. This assumes bigram prediction for now.
+ // User history dictionary internal to LatinIME.
public static final String TYPE_USER_HISTORY = "history";
- // Personalization binary dictionary internal to LatinIME.
+ // Personalization dictionary.
public static final String TYPE_PERSONALIZATION = "personalization";
- // Personalization prediction dictionary internal to LatinIME's Java code.
- public static final String TYPE_PERSONALIZATION_PREDICTION_IN_JAVA =
- "personalization_prediction_in_java";
public final String mDictType;
public Dictionary(final String dictType) {
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index 828e54f14..e09c309ea 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -16,6 +16,7 @@
package com.android.inputmethod.latin;
+import android.content.ContentProviderClient;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
@@ -64,6 +65,10 @@ public final class DictionaryFactory {
useFullEditDistance, locale, Dictionary.TYPE_MAIN);
if (readOnlyBinaryDictionary.isValidDictionary()) {
dictList.add(readOnlyBinaryDictionary);
+ } else {
+ readOnlyBinaryDictionary.close();
+ // Prevent this dictionary to do any further harm.
+ killDictionary(context, f);
}
}
}
@@ -75,6 +80,51 @@ public final class DictionaryFactory {
}
/**
+ * Kills a dictionary so that it is never used again, if possible.
+ * @param context The context to contact the dictionary provider, if possible.
+ * @param f A file address to the dictionary to kill.
+ */
+ private static void killDictionary(final Context context, final AssetFileAddress f) {
+ if (f.pointsToPhysicalFile()) {
+ f.deleteUnderlyingFile();
+ // Warn the dictionary provider if the dictionary came from there.
+ final ContentProviderClient providerClient;
+ try {
+ providerClient = context.getContentResolver().acquireContentProviderClient(
+ BinaryDictionaryFileDumper.getProviderUriBuilder("").build());
+ } catch (final SecurityException e) {
+ Log.e(TAG, "No permission to communicate with the dictionary provider", e);
+ return;
+ }
+ if (null == providerClient) {
+ Log.e(TAG, "Can't establish communication with the dictionary provider");
+ return;
+ }
+ final String wordlistId =
+ DictionaryInfoUtils.getWordListIdFromFileName(new File(f.mFilename).getName());
+ if (null != wordlistId) {
+ // TODO: this is a reasonable last resort, but it is suboptimal.
+ // The following will remove the entry for this dictionary with the dictionary
+ // provider. When the metadata is downloaded again, we will try downloading it
+ // again.
+ // However, in the practice that will mean the user will find themselves without
+ // the new dictionary. That's fine for languages where it's included in the APK,
+ // but for other languages it will leave the user without a dictionary at all until
+ // the next update, which may be a few days away.
+ // Ideally, we would trigger a new download right away, and use increasing retry
+ // delays for this particular id/version combination.
+ // Then again, this is expected to only ever happen in case of human mistake. If
+ // the wrong file is on the server, the following is still doing the right thing.
+ // If it's a file left over from the last version however, it's not great.
+ BinaryDictionaryFileDumper.reportBrokenFileToDictionaryProvider(
+ providerClient,
+ context.getString(R.string.dictionary_pack_client_id),
+ wordlistId);
+ }
+ }
+ }
+
+ /**
* Initializes a main dictionary collection from a dictionary pack, with default flags.
*
* This searches for a content provider providing a dictionary pack for the specified
diff --git a/java/src/com/android/inputmethod/latin/DictionaryWriter.java b/java/src/com/android/inputmethod/latin/DictionaryWriter.java
index 3df2a2b63..f960c5343 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryWriter.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryWriter.java
@@ -37,14 +37,14 @@ import java.util.Map;
* An in memory dictionary for memorizing entries and writing a binary dictionary.
*/
public class DictionaryWriter extends AbstractDictionaryWriter {
- private static final int BINARY_DICT_VERSION = 3;
+ private static final int BINARY_DICT_VERSION = 2;
private static final FormatSpec.FormatOptions FORMAT_OPTIONS =
- new FormatSpec.FormatOptions(BINARY_DICT_VERSION, true /* supportsDynamicUpdate */);
+ new FormatSpec.FormatOptions(BINARY_DICT_VERSION, false /* supportsDynamicUpdate */);
private FusionDictionary mFusionDictionary;
- public DictionaryWriter(final Context context, final String dictType) {
- super(context, dictType);
+ public DictionaryWriter(final Context context) {
+ super(context);
clear();
}
@@ -92,18 +92,4 @@ public class DictionaryWriter extends AbstractDictionaryWriter {
}
dictEncoder.writeDictionary(mFusionDictionary, FORMAT_OPTIONS);
}
-
- @Override
- public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final String prevWord, final ProximityInfo proximityInfo,
- boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
- // This class doesn't support suggestion.
- return null;
- }
-
- @Override
- public boolean isValidWord(String word) {
- // This class doesn't support dictionary retrieval.
- return false;
- }
}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index eb8650e6f..9f5cd162f 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -22,18 +22,22 @@ import android.util.Log;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.BinaryDictionary.LanguageModelParam;
import com.android.inputmethod.latin.makedict.FormatSpec;
-import com.android.inputmethod.latin.personalization.DynamicPersonalizationDictionaryWriter;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.utils.AsyncResultHolder;
import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.FileUtils;
import com.android.inputmethod.latin.utils.PrioritizedSerialExecutor;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
@@ -52,10 +56,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
/** Whether to print debug output to log */
private static boolean DEBUG = false;
-
- // TODO: Remove.
- /** Whether to call binary dictionary dynamically updating methods. */
- public static boolean ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE = true;
+ private static final boolean DBG_STRESS_TEST = false;
private static final int TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS = 100;
@@ -64,22 +65,19 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
protected static final int MAX_WORD_LENGTH = Constants.DICTIONARY_MAX_WORD_LENGTH;
- private static final int DICTIONARY_FORMAT_VERSION = 3;
-
- private static final String SUPPORTS_DYNAMIC_UPDATE =
- FormatSpec.FileHeader.ATTRIBUTE_VALUE_TRUE;
+ private static final int DICTIONARY_FORMAT_VERSION = FormatSpec.VERSION4;
/**
* A static map of update controllers, each of which records the time of accesses to a single
* binary dictionary file and tracks whether the file is regenerating. The key for this map is
- * the filename and the value is the shared dictionary time recorder associated with that
- * filename.
+ * the dictionary name and the value is the shared dictionary time recorder associated with
+ * that dictionary name.
*/
private static final ConcurrentHashMap<String, DictionaryUpdateController>
- sFilenameDictionaryUpdateControllerMap = CollectionUtils.newConcurrentHashMap();
+ sDictNameDictionaryUpdateControllerMap = CollectionUtils.newConcurrentHashMap();
private static final ConcurrentHashMap<String, PrioritizedSerialExecutor>
- sFilenameExecutorMap = CollectionUtils.newConcurrentHashMap();
+ sDictNameExecutorMap = CollectionUtils.newConcurrentHashMap();
/** The application context. */
protected final Context mContext;
@@ -95,18 +93,24 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
protected AbstractDictionaryWriter mDictionaryWriter;
/**
- * The name of this dictionary, used as the filename for storing the binary dictionary. Multiple
- * dictionary instances with the same filename is supported, with access controlled by
- * DictionaryTimeRecorder.
+ * The name of this dictionary, used as a part of the filename for storing the binary
+ * dictionary. Multiple dictionary instances with the same name is supported, with access
+ * controlled by DictionaryUpdateController.
*/
- private final String mFilename;
+ private final String mDictName;
+
+ /** Dictionary locale */
+ private final Locale mLocale;
/** Whether to support dynamically updating the dictionary */
private final boolean mIsUpdatable;
+ /** Dictionary file */
+ private final File mDictFile;
+
// TODO: remove, once dynamic operations is serialized
/** Controls updating the shared binary dictionary file across multiple instances. */
- private final DictionaryUpdateController mFilenameDictionaryUpdateController;
+ private final DictionaryUpdateController mDictNameDictionaryUpdateController;
// TODO: remove, once dynamic operations is serialized
/** Controls updating the local binary dictionary for this instance. */
@@ -132,45 +136,57 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
protected abstract boolean hasContentChanged();
+ protected boolean matchesExpectedBinaryDictFormatVersionForThisType(final int formatVersion) {
+ // This class is using format 2 because it's used by the User and Contacts dictionary
+ // only, which right now use format 2 (dicts using format 4 use Decaying*, which overrides
+ // this method).
+ // TODO: Migrate these dicts to ver4 format, and remove this function.
+ return formatVersion == 2;
+ }
+
+ public boolean isValidDictionary() {
+ return mBinaryDictionary.isValidDictionary();
+ }
+
+ private File getDictFile() {
+ return mDictFile;
+ }
+
/**
- * Gets the dictionary update controller for the given filename.
+ * Gets the dictionary update controller for the given dictionary name.
*/
private static DictionaryUpdateController getDictionaryUpdateController(
- String filename) {
- DictionaryUpdateController recorder = sFilenameDictionaryUpdateControllerMap.get(filename);
+ final String dictName) {
+ DictionaryUpdateController recorder = sDictNameDictionaryUpdateControllerMap.get(dictName);
if (recorder == null) {
- synchronized(sFilenameDictionaryUpdateControllerMap) {
+ synchronized(sDictNameDictionaryUpdateControllerMap) {
recorder = new DictionaryUpdateController();
- sFilenameDictionaryUpdateControllerMap.put(filename, recorder);
+ sDictNameDictionaryUpdateControllerMap.put(dictName, recorder);
}
}
return recorder;
}
/**
- * Gets the executor for the given filename.
+ * Gets the executor for the given dictionary name.
*/
- private static PrioritizedSerialExecutor getExecutor(final String filename) {
- PrioritizedSerialExecutor executor = sFilenameExecutorMap.get(filename);
+ private static PrioritizedSerialExecutor getExecutor(final String dictName) {
+ PrioritizedSerialExecutor executor = sDictNameExecutorMap.get(dictName);
if (executor == null) {
- synchronized(sFilenameExecutorMap) {
+ synchronized(sDictNameExecutorMap) {
executor = new PrioritizedSerialExecutor();
- sFilenameExecutorMap.put(filename, executor);
+ sDictNameExecutorMap.put(dictName, executor);
}
}
return executor;
}
private static AbstractDictionaryWriter getDictionaryWriter(final Context context,
- final String dictType, final boolean isDynamicPersonalizationDictionary) {
+ final boolean isDynamicPersonalizationDictionary) {
if (isDynamicPersonalizationDictionary) {
- if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
- return null;
- } else {
- return new DynamicPersonalizationDictionaryWriter(context, dictType);
- }
+ return null;
} else {
- return new DictionaryWriter(context, dictType);
+ return new DictionaryWriter(context);
}
}
@@ -178,26 +194,37 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
* Creates a new expandable binary dictionary.
*
* @param context The application context of the parent.
- * @param filename The filename for this binary dictionary. Multiple dictionaries with the same
- * filename is supported.
+ * @param dictName The name of the dictionary. Multiple instances with the same
+ * name is supported.
+ * @param locale the dictionary locale.
* @param dictType the dictionary type, as a human-readable string
* @param isUpdatable whether to support dynamically updating the dictionary. Please note that
* dynamic dictionary has negative effects on memory space and computation time.
*/
- public ExpandableBinaryDictionary(final Context context, final String filename,
- final String dictType, final boolean isUpdatable) {
+ public ExpandableBinaryDictionary(final Context context, final String dictName,
+ final Locale locale, final String dictType, final boolean isUpdatable) {
+ this(context, dictName, locale, dictType, isUpdatable,
+ new File(context.getFilesDir(), dictName + DICT_FILE_EXTENSION));
+ }
+
+ // Creates an instance that uses a given dictionary file.
+ public ExpandableBinaryDictionary(final Context context, final String dictName,
+ final Locale locale, final String dictType, final boolean isUpdatable,
+ final File dictFile) {
super(dictType);
- mFilename = filename;
+ mDictName = dictName;
mContext = context;
+ mLocale = locale;
mIsUpdatable = isUpdatable;
+ mDictFile = dictFile;
mBinaryDictionary = null;
- mFilenameDictionaryUpdateController = getDictionaryUpdateController(filename);
+ mDictNameDictionaryUpdateController = getDictionaryUpdateController(dictName);
// Currently, only dynamic personalization dictionary is updatable.
- mDictionaryWriter = getDictionaryWriter(context, dictType, isUpdatable);
+ mDictionaryWriter = getDictionaryWriter(context, isUpdatable);
}
- protected static String getFilenameWithLocale(final String name, final String localeStr) {
- return name + "." + localeStr + DICT_FILE_EXTENSION;
+ protected static String getDictNameWithLocale(final String name, final Locale locale) {
+ return name + "." + locale.toString();
}
/**
@@ -205,23 +232,20 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
@Override
public void close() {
- getExecutor(mFilename).execute(new Runnable() {
+ getExecutor(mDictName).execute(new Runnable() {
@Override
public void run() {
if (mBinaryDictionary!= null) {
mBinaryDictionary.close();
mBinaryDictionary = null;
}
- if (mDictionaryWriter != null) {
- mDictionaryWriter.close();
- }
}
});
}
protected void closeBinaryDictionary() {
// Ensure that no other threads are accessing the local binary dictionary.
- getExecutor(mFilename).execute(new Runnable() {
+ getExecutor(mDictName).execute(new Runnable() {
@Override
public void run() {
if (mBinaryDictionary != null) {
@@ -234,19 +258,23 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
protected Map<String, String> getHeaderAttributeMap() {
HashMap<String, String> attributeMap = new HashMap<String, String>();
- attributeMap.put(FormatSpec.FileHeader.SUPPORTS_DYNAMIC_UPDATE_ATTRIBUTE,
- SUPPORTS_DYNAMIC_UPDATE);
- attributeMap.put(FormatSpec.FileHeader.DICTIONARY_ID_ATTRIBUTE, mFilename);
+ attributeMap.put(FormatSpec.FileHeader.DICTIONARY_ID_ATTRIBUTE, mDictName);
+ attributeMap.put(FormatSpec.FileHeader.DICTIONARY_LOCALE_ATTRIBUTE, mLocale.toString());
+ attributeMap.put(FormatSpec.FileHeader.DICTIONARY_VERSION_ATTRIBUTE,
+ String.valueOf(TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())));
return attributeMap;
}
protected void clear() {
- getExecutor(mFilename).execute(new Runnable() {
+ getExecutor(mDictName).execute(new Runnable() {
@Override
public void run() {
- if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE && mDictionaryWriter == null) {
+ if (mDictionaryWriter == null) {
mBinaryDictionary.close();
- final File file = new File(mContext.getFilesDir(), mFilename);
+ final File file = getDictFile();
+ if (file.exists() && !FileUtils.deleteRecursively(file)) {
+ Log.e(TAG, "Can't remove a file: " + file.getName());
+ }
BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(),
DICTIONARY_FORMAT_VERSION, getHeaderAttributeMap());
mBinaryDictionary = new BinaryDictionary(
@@ -286,8 +314,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
* Check whether GC is needed and run GC if required.
*/
protected void runGCIfRequired(final boolean mindsBlockByGC) {
- if (!ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) return;
- getExecutor(mFilename).execute(new Runnable() {
+ getExecutor(mDictName).execute(new Runnable() {
@Override
public void run() {
runGCIfRequiredInternalLocked(mindsBlockByGC);
@@ -296,18 +323,17 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
private void runGCIfRequiredInternalLocked(final boolean mindsBlockByGC) {
- if (!ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) return;
// Calls to needsToRunGC() need to be serialized.
if (mBinaryDictionary.needsToRunGC(mindsBlockByGC)) {
- if (setIsRegeneratingIfNotRegenerating()) {
+ if (setProcessingLargeTaskIfNot()) {
// Run GC after currently existing time sensitive operations.
- getExecutor(mFilename).executePrioritized(new Runnable() {
+ getExecutor(mDictName).executePrioritized(new Runnable() {
@Override
public void run() {
try {
mBinaryDictionary.flushWithGC();
} finally {
- mFilenameDictionaryUpdateController.mIsRegenerating.set(false);
+ mDictNameDictionaryUpdateController.mProcessingLargeTask.set(false);
}
}
});
@@ -318,23 +344,19 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
/**
* Dynamically adds a word unigram to the dictionary. May overwrite an existing entry.
*/
- protected void addWordDynamically(final String word, final String shortcutTarget,
- final int frequency, final int shortcutFreq, final boolean isNotAWord) {
+ protected void addWordDynamically(final String word, final int frequency,
+ final String shortcutTarget, final int shortcutFreq, final boolean isNotAWord,
+ final boolean isBlacklisted, final int timestamp) {
if (!mIsUpdatable) {
- Log.w(TAG, "addWordDynamically is called for non-updatable dictionary: " + mFilename);
+ Log.w(TAG, "addWordDynamically is called for non-updatable dictionary: " + mDictName);
return;
}
- getExecutor(mFilename).execute(new Runnable() {
+ getExecutor(mDictName).execute(new Runnable() {
@Override
public void run() {
- if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
- runGCIfRequiredInternalLocked(true /* mindsBlockByGC */);
- mBinaryDictionary.addUnigramWord(word, frequency);
- } else {
- // TODO: Remove.
- mDictionaryWriter.addUnigramWord(word, shortcutTarget, frequency, shortcutFreq,
- isNotAWord);
- }
+ runGCIfRequiredInternalLocked(true /* mindsBlockByGC */);
+ mBinaryDictionary.addUnigramWord(word, frequency, shortcutTarget, shortcutFreq,
+ isNotAWord, isBlacklisted, timestamp);
}
});
}
@@ -343,23 +365,17 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
* Dynamically adds a word bigram in the dictionary. May overwrite an existing entry.
*/
protected void addBigramDynamically(final String word0, final String word1,
- final int frequency, final boolean isValid) {
+ final int frequency, final int timestamp) {
if (!mIsUpdatable) {
Log.w(TAG, "addBigramDynamically is called for non-updatable dictionary: "
- + mFilename);
+ + mDictName);
return;
}
- getExecutor(mFilename).execute(new Runnable() {
+ getExecutor(mDictName).execute(new Runnable() {
@Override
public void run() {
- if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
- runGCIfRequiredInternalLocked(true /* mindsBlockByGC */);
- mBinaryDictionary.addBigramWords(word0, word1, frequency);
- } else {
- // TODO: Remove.
- mDictionaryWriter.addBigramWords(word0, word1, frequency, isValid,
- 0 /* lastTouchedTime */);
- }
+ runGCIfRequiredInternalLocked(true /* mindsBlockByGC */);
+ mBinaryDictionary.addBigramWords(word0, word1, frequency, timestamp);
}
});
}
@@ -370,18 +386,48 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
protected void removeBigramDynamically(final String word0, final String word1) {
if (!mIsUpdatable) {
Log.w(TAG, "removeBigramDynamically is called for non-updatable dictionary: "
- + mFilename);
+ + mDictName);
return;
}
- getExecutor(mFilename).execute(new Runnable() {
+ getExecutor(mDictName).execute(new Runnable() {
@Override
public void run() {
- if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
- runGCIfRequiredInternalLocked(true /* mindsBlockByGC */);
- mBinaryDictionary.removeBigramWords(word0, word1);
- } else {
- // TODO: Remove.
- mDictionaryWriter.removeBigramWords(word0, word1);
+ runGCIfRequiredInternalLocked(true /* mindsBlockByGC */);
+ mBinaryDictionary.removeBigramWords(word0, word1);
+ }
+ });
+ }
+
+ public interface AddMultipleDictionaryEntriesCallback {
+ public void onFinished();
+ }
+
+ /**
+ * Dynamically add multiple entries to the dictionary.
+ */
+ protected void addMultipleDictionaryEntriesDynamically(
+ final ArrayList<LanguageModelParam> languageModelParams,
+ final AddMultipleDictionaryEntriesCallback callback) {
+ if (!mIsUpdatable) {
+ Log.w(TAG, "addMultipleDictionaryEntriesDynamically is called for non-updatable " +
+ "dictionary: " + mDictName);
+ return;
+ }
+ getExecutor(mDictName).execute(new Runnable() {
+ @Override
+ public void run() {
+ final boolean locked = setProcessingLargeTaskIfNot();
+ try {
+ mBinaryDictionary.addMultipleDictionaryEntries(
+ languageModelParams.toArray(
+ new LanguageModelParam[languageModelParams.size()]));
+ } finally {
+ if (callback != null) {
+ callback.onFinished();
+ }
+ if (locked) {
+ mDictNameDictionaryUpdateController.mProcessingLargeTask.set(false);
+ }
}
}
});
@@ -393,49 +439,23 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
final int sessionId) {
reloadDictionaryIfRequired();
- if (isRegenerating()) {
+ if (processingLargeTask()) {
return null;
}
- final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
final AsyncResultHolder<ArrayList<SuggestedWordInfo>> holder =
new AsyncResultHolder<ArrayList<SuggestedWordInfo>>();
- getExecutor(mFilename).executePrioritized(new Runnable() {
+ getExecutor(mDictName).executePrioritized(new Runnable() {
@Override
public void run() {
- if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
- if (mBinaryDictionary == null) {
- holder.set(null);
- return;
- }
- final ArrayList<SuggestedWordInfo> binarySuggestion =
- mBinaryDictionary.getSuggestionsWithSessionId(composer, prevWord,
- proximityInfo, blockOffensiveWords, additionalFeaturesOptions,
- sessionId);
- holder.set(binarySuggestion);
- } else {
- final ArrayList<SuggestedWordInfo> inMemDictSuggestion =
- composer.isBatchMode() ? null :
- mDictionaryWriter.getSuggestionsWithSessionId(composer,
- prevWord, proximityInfo, blockOffensiveWords,
- additionalFeaturesOptions, sessionId);
- // TODO: Remove checking mIsUpdatable and use native suggestion.
- if (mBinaryDictionary != null && !mIsUpdatable) {
- final ArrayList<SuggestedWordInfo> binarySuggestion =
- mBinaryDictionary.getSuggestionsWithSessionId(composer, prevWord,
- proximityInfo, blockOffensiveWords,
- additionalFeaturesOptions, sessionId);
- if (inMemDictSuggestion == null) {
- holder.set(binarySuggestion);
- } else if (binarySuggestion == null) {
- holder.set(inMemDictSuggestion);
- } else {
- binarySuggestion.addAll(inMemDictSuggestion);
- holder.set(binarySuggestion);
- }
- } else {
- holder.set(inMemDictSuggestion);
- }
+ if (mBinaryDictionary == null) {
+ holder.set(null);
+ return;
}
+ final ArrayList<SuggestedWordInfo> binarySuggestion =
+ mBinaryDictionary.getSuggestionsWithSessionId(composer, prevWord,
+ proximityInfo, blockOffensiveWords, additionalFeaturesOptions,
+ sessionId);
+ holder.set(binarySuggestion);
}
});
return holder.get(null, TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS);
@@ -456,11 +476,11 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
protected boolean isValidWordInner(final String word) {
- if (isRegenerating()) {
+ if (processingLargeTask()) {
return false;
}
final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>();
- getExecutor(mFilename).executePrioritized(new Runnable() {
+ getExecutor(mDictName).executePrioritized(new Runnable() {
@Override
public void run() {
holder.set(isValidWordLocked(word));
@@ -494,12 +514,22 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
private void loadBinaryDictionary() {
if (DEBUG) {
- Log.d(TAG, "Loading binary dictionary: " + mFilename + " request="
- + mFilenameDictionaryUpdateController.mLastUpdateRequestTime + " update="
- + mFilenameDictionaryUpdateController.mLastUpdateTime);
+ Log.d(TAG, "Loading binary dictionary: " + mDictName + " request="
+ + mDictNameDictionaryUpdateController.mLastUpdateRequestTime + " update="
+ + mDictNameDictionaryUpdateController.mLastUpdateTime);
+ }
+ if (DBG_STRESS_TEST) {
+ // Test if this class does not cause problems when it takes long time to load binary
+ // dictionary.
+ try {
+ Log.w(TAG, "Start stress in loading: " + mDictName);
+ Thread.sleep(15000);
+ Log.w(TAG, "End stress in loading");
+ } catch (InterruptedException e) {
+ }
}
- final File file = new File(mContext.getFilesDir(), mFilename);
+ final File file = getDictFile();
final String filename = file.getAbsolutePath();
final long length = file.length();
@@ -511,7 +541,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
// swapping in the new one.
// TODO: Ensure multi-thread assignment of mBinaryDictionary.
final BinaryDictionary oldBinaryDictionary = mBinaryDictionary;
- getExecutor(mFilename).executePrioritized(new Runnable() {
+ getExecutor(mDictName).executePrioritized(new Runnable() {
@Override
public void run() {
mBinaryDictionary = newBinaryDictionary;
@@ -533,29 +563,31 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
private void writeBinaryDictionary() {
if (DEBUG) {
- Log.d(TAG, "Generating binary dictionary: " + mFilename + " request="
- + mFilenameDictionaryUpdateController.mLastUpdateRequestTime + " update="
- + mFilenameDictionaryUpdateController.mLastUpdateTime);
+ Log.d(TAG, "Generating binary dictionary: " + mDictName + " request="
+ + mDictNameDictionaryUpdateController.mLastUpdateRequestTime + " update="
+ + mDictNameDictionaryUpdateController.mLastUpdateTime);
}
if (needsToReloadBeforeWriting()) {
mDictionaryWriter.clear();
loadDictionaryAsync();
- mDictionaryWriter.write(mFilename, getHeaderAttributeMap());
+ mDictionaryWriter.write(getDictFile(), getHeaderAttributeMap());
} else {
- if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
- if (mBinaryDictionary == null || !mBinaryDictionary.isValidDictionary()) {
- final File file = new File(mContext.getFilesDir(), mFilename);
- BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(),
- DICTIONARY_FORMAT_VERSION, getHeaderAttributeMap());
- } else {
- if (mBinaryDictionary.needsToRunGC(false /* mindsBlockByGC */)) {
- mBinaryDictionary.flushWithGC();
- } else {
- mBinaryDictionary.flush();
- }
+ if (mBinaryDictionary == null || !isValidDictionary()
+ // TODO: remove the check below
+ || !matchesExpectedBinaryDictFormatVersionForThisType(
+ mBinaryDictionary.getFormatVersion())) {
+ final File file = getDictFile();
+ if (file.exists() && !FileUtils.deleteRecursively(file)) {
+ Log.e(TAG, "Can't remove a file: " + file.getName());
}
+ BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(),
+ DICTIONARY_FORMAT_VERSION, getHeaderAttributeMap());
} else {
- mDictionaryWriter.write(mFilename, getHeaderAttributeMap());
+ if (mBinaryDictionary.needsToRunGC(false /* mindsBlockByGC */)) {
+ mBinaryDictionary.flushWithGC();
+ } else {
+ mBinaryDictionary.flush();
+ }
}
}
}
@@ -570,10 +602,10 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
protected void setRequiresReload(final boolean requiresRebuild) {
final long time = SystemClock.uptimeMillis();
mPerInstanceDictionaryUpdateController.mLastUpdateRequestTime = time;
- mFilenameDictionaryUpdateController.mLastUpdateRequestTime = time;
+ mDictNameDictionaryUpdateController.mLastUpdateRequestTime = time;
if (DEBUG) {
- Log.d(TAG, "Reload request: " + mFilename + ": request=" + time + " update="
- + mFilenameDictionaryUpdateController.mLastUpdateTime);
+ Log.d(TAG, "Reload request: " + mDictName + ": request=" + time + " update="
+ + mDictNameDictionaryUpdateController.mLastUpdateTime);
}
}
@@ -582,7 +614,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
public final void reloadDictionaryIfRequired() {
if (!isReloadRequired()) return;
- if (setIsRegeneratingIfNotRegenerating()) {
+ if (setProcessingLargeTaskIfNot()) {
reloadDictionary();
}
}
@@ -594,13 +626,14 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
return mBinaryDictionary == null || mPerInstanceDictionaryUpdateController.isOutOfDate();
}
- private boolean isRegenerating() {
- return mFilenameDictionaryUpdateController.mIsRegenerating.get();
+ private boolean processingLargeTask() {
+ return mDictNameDictionaryUpdateController.mProcessingLargeTask.get();
}
- // Returns whether the dictionary can be regenerated.
- private boolean setIsRegeneratingIfNotRegenerating() {
- return mFilenameDictionaryUpdateController.mIsRegenerating.compareAndSet(
+ // Returns whether the dictionary is being used for a large task. If true, we should not use
+ // this dictionary for latency sensitive operations.
+ private boolean setProcessingLargeTaskIfNot() {
+ return mDictNameDictionaryUpdateController.mProcessingLargeTask.compareAndSet(
false /* expect */ , true /* update */);
}
@@ -611,13 +644,13 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
private final void reloadDictionary() {
// Ensure that only one thread attempts to read or write to the shared binary dictionary
// file at the same time.
- getExecutor(mFilename).execute(new Runnable() {
+ getExecutor(mDictName).execute(new Runnable() {
@Override
public void run() {
try {
final long time = SystemClock.uptimeMillis();
final boolean dictionaryFileExists = dictionaryFileExists();
- if (mFilenameDictionaryUpdateController.isOutOfDate()
+ if (mDictNameDictionaryUpdateController.isOutOfDate()
|| !dictionaryFileExists) {
// If the shared dictionary file does not exist or is out of date, the
// first instance that acquires the lock will generate a new one.
@@ -626,31 +659,44 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
// rebuild the binary dictionary. Empty dictionaries are supported (in
// the case where loadDictionaryAsync() adds nothing) in order to
// provide a uniform framework.
- mFilenameDictionaryUpdateController.mLastUpdateTime = time;
+ mDictNameDictionaryUpdateController.mLastUpdateTime = time;
writeBinaryDictionary();
loadBinaryDictionary();
} else {
// If not, the reload request was unnecessary so revert
// LastUpdateRequestTime to LastUpdateTime.
- mFilenameDictionaryUpdateController.mLastUpdateRequestTime =
- mFilenameDictionaryUpdateController.mLastUpdateTime;
+ mDictNameDictionaryUpdateController.mLastUpdateRequestTime =
+ mDictNameDictionaryUpdateController.mLastUpdateTime;
}
} else if (mBinaryDictionary == null ||
mPerInstanceDictionaryUpdateController.mLastUpdateTime
- < mFilenameDictionaryUpdateController.mLastUpdateTime) {
+ < mDictNameDictionaryUpdateController.mLastUpdateTime) {
// Otherwise, if the local dictionary is older than the shared dictionary,
// load the shared dictionary.
loadBinaryDictionary();
}
- if (mBinaryDictionary != null && !mBinaryDictionary.isValidDictionary()) {
- // Binary dictionary is not valid. Regenerate the dictionary file.
- mFilenameDictionaryUpdateController.mLastUpdateTime = time;
- writeBinaryDictionary();
- loadBinaryDictionary();
- }
- mPerInstanceDictionaryUpdateController.mLastUpdateTime = time;
+ // If we just loaded the binary dictionary, then mBinaryDictionary is not
+ // up-to-date yet so it's useless to test it right away. Schedule the check
+ // for right after it's loaded instead.
+ getExecutor(mDictName).executePrioritized(new Runnable() {
+ @Override
+ public void run() {
+ if (mBinaryDictionary != null && !(isValidDictionary()
+ // TODO: remove the check below
+ && matchesExpectedBinaryDictFormatVersionForThisType(
+ mBinaryDictionary.getFormatVersion()))) {
+ // Binary dictionary or its format version is not valid. Regenerate
+ // the dictionary file. writeBinaryDictionary will remove the
+ // existing files if appropriate.
+ mDictNameDictionaryUpdateController.mLastUpdateTime = time;
+ writeBinaryDictionary();
+ loadBinaryDictionary();
+ }
+ mPerInstanceDictionaryUpdateController.mLastUpdateTime = time;
+ }
+ });
} finally {
- mFilenameDictionaryUpdateController.mIsRegenerating.set(false);
+ mDictNameDictionaryUpdateController.mProcessingLargeTask.set(false);
}
}
});
@@ -658,28 +704,13 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
// TODO: cache the file's existence so that we avoid doing a disk access each time.
private boolean dictionaryFileExists() {
- final File file = new File(mContext.getFilesDir(), mFilename);
- return file.exists();
- }
-
- /**
- * Load the dictionary to memory.
- */
- protected void asyncLoadDictionaryToMemory() {
- getExecutor(mFilename).executePrioritized(new Runnable() {
- @Override
- public void run() {
- if (!ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
- loadDictionaryAsync();
- }
- }
- });
+ return getDictFile().exists();
}
/**
* Generate binary dictionary using DictionaryWriter.
*/
- protected void asyncFlashAllBinaryDictionary() {
+ protected void asyncFlushBinaryDictionary() {
final Runnable newTask = new Runnable() {
@Override
public void run() {
@@ -687,37 +718,32 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
};
final Runnable oldTask = mUnfinishedFlushingTask.getAndSet(newTask);
- getExecutor(mFilename).replaceAndExecute(oldTask, newTask);
+ getExecutor(mDictName).replaceAndExecute(oldTask, newTask);
}
/**
- * For tracking whether the dictionary is out of date and the dictionary is regenerating.
- * Can be shared across multiple dictionary instances that access the same filename.
+ * For tracking whether the dictionary is out of date and the dictionary is used in a large
+ * task. Can be shared across multiple dictionary instances that access the same filename.
*/
private static class DictionaryUpdateController {
public volatile long mLastUpdateTime = 0;
public volatile long mLastUpdateRequestTime = 0;
- public volatile AtomicBoolean mIsRegenerating = new AtomicBoolean();
+ public volatile AtomicBoolean mProcessingLargeTask = new AtomicBoolean();
public boolean isOutOfDate() {
return (mLastUpdateRequestTime > mLastUpdateTime);
}
}
- // TODO: Implement native binary methods once the dynamic dictionary implementation is done.
+ // TODO: Implement BinaryDictionary.isInDictionary().
@UsedForTesting
public boolean isInDictionaryForTests(final String word) {
final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>();
- getExecutor(mFilename).executePrioritized(new Runnable() {
+ getExecutor(mDictName).executePrioritized(new Runnable() {
@Override
public void run() {
if (mDictType == Dictionary.TYPE_USER_HISTORY) {
- if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
- holder.set(mBinaryDictionary.isValidWord(word));
- } else {
- holder.set(((DynamicPersonalizationDictionaryWriter) mDictionaryWriter)
- .isInBigramListForTests(word));
- }
+ holder.set(mBinaryDictionary.isValidWord(word));
}
}
});
@@ -725,12 +751,33 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
@UsedForTesting
- public void shutdownExecutorForTests() {
- getExecutor(mFilename).shutdown();
+ public void waitAllTasksForTests() {
+ final CountDownLatch countDownLatch = new CountDownLatch(1);
+ getExecutor(mDictName).execute(new Runnable() {
+ @Override
+ public void run() {
+ countDownLatch.countDown();
+ }
+ });
+ try {
+ countDownLatch.await();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Interrupted while waiting for finishing dictionary operations.", e);
+ }
}
@UsedForTesting
- public boolean isTerminatedForTests() {
- return getExecutor(mFilename).isTerminated();
+ protected void runAfterGcForDebug(final Runnable r) {
+ getExecutor(mDictName).executePrioritized(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mBinaryDictionary.flushWithGC();
+ r.run();
+ } finally {
+ mDictNameDictionaryUpdateController.mProcessingLargeTask.set(false);
+ }
+ }
+ });
}
}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
deleted file mode 100644
index 95c9bcab9..000000000
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ /dev/null
@@ -1,894 +0,0 @@
-/*
- * Copyright (C) 2009 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.text.TextUtils;
-import android.util.Log;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.keyboard.ProximityInfo;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.utils.CollectionUtils;
-import com.android.inputmethod.latin.utils.UserHistoryForgettingCurveUtils.ForgettingCurveParams;
-
-import java.util.ArrayList;
-import java.util.LinkedList;
-
-/**
- * Class for an in-memory dictionary that can grow dynamically and can
- * be searched for suggestions and valid words.
- */
-// TODO: Remove after binary dictionary supports dynamic update.
-public class ExpandableDictionary extends Dictionary {
- private static final String TAG = ExpandableDictionary.class.getSimpleName();
- /**
- * The weight to give to a word if it's length is the same as the number of typed characters.
- */
- private static final int FULL_WORD_SCORE_MULTIPLIER = 2;
-
- private char[] mWordBuilder = new char[Constants.DICTIONARY_MAX_WORD_LENGTH];
- private int mMaxDepth;
- private int mInputLength;
-
- private static final class Node {
- char mCode;
- int mFrequency;
- boolean mTerminal;
- Node mParent;
- NodeArray mChildren;
- ArrayList<char[]> mShortcutTargets;
- boolean mShortcutOnly;
- LinkedList<NextWord> mNGrams; // Supports ngram
- }
-
- private static final class NodeArray {
- Node[] mData;
- int mLength = 0;
- private static final int INCREMENT = 2;
-
- NodeArray() {
- mData = new Node[INCREMENT];
- }
-
- void add(final Node n) {
- if (mLength + 1 > mData.length) {
- Node[] tempData = new Node[mLength + INCREMENT];
- if (mLength > 0) {
- System.arraycopy(mData, 0, tempData, 0, mLength);
- }
- mData = tempData;
- }
- mData[mLength++] = n;
- }
- }
-
- public interface NextWord {
- public Node getWordNode();
- public int getFrequency();
- public ForgettingCurveParams getFcParams();
- public int notifyTypedAgainAndGetFrequency();
- }
-
- private static final class NextStaticWord implements NextWord {
- public final Node mWord;
- private final int mFrequency;
- public NextStaticWord(Node word, int frequency) {
- mWord = word;
- mFrequency = frequency;
- }
-
- @Override
- public Node getWordNode() {
- return mWord;
- }
-
- @Override
- public int getFrequency() {
- return mFrequency;
- }
-
- @Override
- public ForgettingCurveParams getFcParams() {
- return null;
- }
-
- @Override
- public int notifyTypedAgainAndGetFrequency() {
- return mFrequency;
- }
- }
-
- private static final class NextHistoryWord implements NextWord {
- public final Node mWord;
- public final ForgettingCurveParams mFcp;
-
- public NextHistoryWord(Node word, ForgettingCurveParams fcp) {
- mWord = word;
- mFcp = fcp;
- }
-
- @Override
- public Node getWordNode() {
- return mWord;
- }
-
- @Override
- public int getFrequency() {
- return mFcp.getFrequency();
- }
-
- @Override
- public ForgettingCurveParams getFcParams() {
- return mFcp;
- }
-
- @Override
- public int notifyTypedAgainAndGetFrequency() {
- return mFcp.notifyTypedAgainAndGetFrequency();
- }
- }
-
- private NodeArray mRoots;
-
- private int[][] mCodes;
-
- public ExpandableDictionary(final String dictType) {
- super(dictType);
- clearDictionary();
- mCodes = new int[Constants.DICTIONARY_MAX_WORD_LENGTH][];
- }
-
- public int getMaxWordLength() {
- return Constants.DICTIONARY_MAX_WORD_LENGTH;
- }
-
- /**
- * Add a word with an optional shortcut to the dictionary.
- * @param word The word to add.
- * @param shortcutTarget A shortcut target for this word, or null if none.
- * @param frequency The frequency for this unigram.
- * @param shortcutFreq The frequency of the shortcut (0~15, with 15 = whitelist). Ignored
- * if shortcutTarget is null.
- */
- public void addWord(final String word, final String shortcutTarget, final int frequency,
- final int shortcutFreq) {
- if (word.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH) {
- return;
- }
- addWordRec(mRoots, word, 0, shortcutTarget, frequency, shortcutFreq, null);
- }
-
- /**
- * Add a word, recursively searching for its correct place in the trie tree.
- * @param children The node to recursively search for addition. Initially, the root of the tree.
- * @param word The word to add.
- * @param depth The current depth in the tree.
- * @param shortcutTarget A shortcut target for this word, or null if none.
- * @param frequency The frequency for this unigram.
- * @param shortcutFreq The frequency of the shortcut (0~15, with 15 = whitelist). Ignored
- * if shortcutTarget is null.
- * @param parentNode The parent node, for up linking. Initially null, as the root has no parent.
- */
- private void addWordRec(final NodeArray children, final String word, final int depth,
- final String shortcutTarget, final int frequency, final int shortcutFreq,
- final Node parentNode) {
- final int wordLength = word.length();
- if (wordLength <= depth) return;
- final char c = word.charAt(depth);
- // Does children have the current character?
- final int childrenLength = children.mLength;
- Node childNode = null;
- for (int i = 0; i < childrenLength; i++) {
- final Node node = children.mData[i];
- if (node.mCode == c) {
- childNode = node;
- break;
- }
- }
- final boolean isShortcutOnly = (null != shortcutTarget);
- if (childNode == null) {
- childNode = new Node();
- childNode.mCode = c;
- childNode.mParent = parentNode;
- childNode.mShortcutOnly = isShortcutOnly;
- children.add(childNode);
- }
- if (wordLength == depth + 1) {
- // Terminate this word
- childNode.mTerminal = true;
- if (isShortcutOnly) {
- if (null == childNode.mShortcutTargets) {
- childNode.mShortcutTargets = CollectionUtils.newArrayList();
- }
- childNode.mShortcutTargets.add(shortcutTarget.toCharArray());
- } else {
- childNode.mShortcutOnly = false;
- }
- childNode.mFrequency = Math.max(frequency, childNode.mFrequency);
- if (childNode.mFrequency > 255) childNode.mFrequency = 255;
- return;
- }
- if (childNode.mChildren == null) {
- childNode.mChildren = new NodeArray();
- }
- addWordRec(childNode.mChildren, word, depth + 1, shortcutTarget, frequency, shortcutFreq,
- childNode);
- }
-
- @Override
- public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final String prevWord, final ProximityInfo proximityInfo,
- final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
- if (composer.size() > 1) {
- if (composer.size() >= Constants.DICTIONARY_MAX_WORD_LENGTH) {
- return null;
- }
- final ArrayList<SuggestedWordInfo> suggestions =
- getWordsInner(composer, prevWord, proximityInfo);
- return suggestions;
- } else {
- if (TextUtils.isEmpty(prevWord)) return null;
- final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
- runBigramReverseLookUp(prevWord, suggestions);
- return suggestions;
- }
- }
-
- private ArrayList<SuggestedWordInfo> getWordsInner(final WordComposer codes,
- final String prevWordForBigrams, final ProximityInfo proximityInfo) {
- final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
- mInputLength = codes.size();
- if (mCodes.length < mInputLength) mCodes = new int[mInputLength][];
- final InputPointers ips = codes.getInputPointers();
- final int[] xCoordinates = ips.getXCoordinates();
- final int[] yCoordinates = ips.getYCoordinates();
- // Cache the codes so that we don't have to lookup an array list
- for (int i = 0; i < mInputLength; i++) {
- // TODO: Calculate proximity info here.
- if (mCodes[i] == null || mCodes[i].length < 1) {
- mCodes[i] = new int[ProximityInfo.MAX_PROXIMITY_CHARS_SIZE];
- }
- final int x = xCoordinates != null && i < xCoordinates.length ?
- xCoordinates[i] : Constants.NOT_A_COORDINATE;
- final int y = xCoordinates != null && i < yCoordinates.length ?
- yCoordinates[i] : Constants.NOT_A_COORDINATE;
- proximityInfo.fillArrayWithNearestKeyCodes(x, y, codes.getCodeAt(i), mCodes[i]);
- }
- mMaxDepth = mInputLength * 3;
- getWordsRec(mRoots, codes, mWordBuilder, 0, false, 1, 0, -1, suggestions);
- for (int i = 0; i < mInputLength; i++) {
- getWordsRec(mRoots, codes, mWordBuilder, 0, false, 1, 0, i, suggestions);
- }
- return suggestions;
- }
-
- @Override
- public synchronized boolean isValidWord(final String word) {
- final Node node = searchNode(mRoots, word, 0, word.length());
- // If node is null, we didn't find the word, so it's not valid.
- // If node.mShortcutOnly is true, then it exists as a shortcut but not as a word,
- // so that means it's not a valid word.
- // If node.mShortcutOnly is false, then it exists as a word (it may also exist as
- // a shortcut, but this does not matter), so it's a valid word.
- return (node == null) ? false : !node.mShortcutOnly;
- }
-
- public boolean removeBigram(final String word0, final String word1) {
- // Refer to addOrSetBigram() about word1.toLowerCase()
- final Node firstWord = searchWord(mRoots, word0.toLowerCase(), 0, null);
- final Node secondWord = searchWord(mRoots, word1, 0, null);
- LinkedList<NextWord> bigrams = firstWord.mNGrams;
- NextWord bigramNode = null;
- if (bigrams == null || bigrams.size() == 0) {
- return false;
- } else {
- for (NextWord nw : bigrams) {
- if (nw.getWordNode() == secondWord) {
- bigramNode = nw;
- break;
- }
- }
- }
- if (bigramNode == null) {
- return false;
- }
- return bigrams.remove(bigramNode);
- }
-
- /**
- * Returns the word's frequency or -1 if not found
- */
- @UsedForTesting
- public int getWordFrequency(final String word) {
- // Case-sensitive search
- final Node node = searchNode(mRoots, word, 0, word.length());
- return (node == null) ? -1 : node.mFrequency;
- }
-
- public NextWord getBigramWord(final String word0, final String word1) {
- // Refer to addOrSetBigram() about word0.toLowerCase()
- final Node firstWord = searchWord(mRoots, word0.toLowerCase(), 0, null);
- final Node secondWord = searchWord(mRoots, word1, 0, null);
- LinkedList<NextWord> bigrams = firstWord.mNGrams;
- if (bigrams == null || bigrams.size() == 0) {
- return null;
- } else {
- for (NextWord nw : bigrams) {
- if (nw.getWordNode() == secondWord) {
- return nw;
- }
- }
- }
- return null;
- }
-
- private static int computeSkippedWordFinalFreq(final int freq, final int snr,
- final int inputLength) {
- // The computation itself makes sense for >= 2, but the == 2 case returns 0
- // anyway so we may as well test against 3 instead and return the constant
- if (inputLength >= 3) {
- return (freq * snr * (inputLength - 2)) / (inputLength - 1);
- } else {
- return 0;
- }
- }
-
- /**
- * Helper method to add a word and its shortcuts.
- *
- * @param node the terminal node
- * @param word the word to insert, as an array of code points
- * @param depth the depth of the node in the tree
- * @param finalFreq the frequency for this word
- * @param suggestions the suggestion collection to add the suggestions to
- * @return whether there is still space for more words.
- */
- private boolean addWordAndShortcutsFromNode(final Node node, final char[] word, final int depth,
- final int finalFreq, final ArrayList<SuggestedWordInfo> suggestions) {
- if (finalFreq > 0 && !node.mShortcutOnly) {
- // Use KIND_CORRECTION always. This dictionary does not really have a notion of
- // COMPLETION against CORRECTION; we could artificially add one by looking at
- // the respective size of the typed word and the suggestion if it matters sometime
- // in the future.
- suggestions.add(new SuggestedWordInfo(new String(word, 0, depth + 1), finalFreq,
- SuggestedWordInfo.KIND_CORRECTION, this /* sourceDict */,
- SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
- SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
- if (suggestions.size() >= Suggest.MAX_SUGGESTIONS) return false;
- }
- if (null != node.mShortcutTargets) {
- final int length = node.mShortcutTargets.size();
- for (int shortcutIndex = 0; shortcutIndex < length; ++shortcutIndex) {
- final char[] shortcut = node.mShortcutTargets.get(shortcutIndex);
- suggestions.add(new SuggestedWordInfo(new String(shortcut, 0, shortcut.length),
- finalFreq, SuggestedWordInfo.KIND_SHORTCUT, this /* sourceDict */,
- SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
- SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
- if (suggestions.size() > Suggest.MAX_SUGGESTIONS) return false;
- }
- }
- return true;
- }
-
- /**
- * Recursively traverse the tree for words that match the input. Input consists of
- * a list of arrays. Each item in the list is one input character position. An input
- * character is actually an array of multiple possible candidates. This function is not
- * optimized for speed, assuming that the user dictionary will only be a few hundred words in
- * size.
- * @param roots node whose children have to be search for matches
- * @param codes the input character codes
- * @param word the word being composed as a possible match
- * @param depth the depth of traversal - the length of the word being composed thus far
- * @param completion whether the traversal is now in completion mode - meaning that we've
- * exhausted the input and we're looking for all possible suffixes.
- * @param snr current weight of the word being formed
- * @param inputIndex position in the input characters. This can be off from the depth in
- * case we skip over some punctuations such as apostrophe in the traversal. That is, if you type
- * "wouldve", it could be matching "would've", so the depth will be one more than the
- * inputIndex
- * @param suggestions the list in which to add suggestions
- */
- // TODO: Share this routine with the native code for BinaryDictionary
- private void getWordsRec(final NodeArray roots, final WordComposer codes, final char[] word,
- final int depth, final boolean completion, final int snr, final int inputIndex,
- final int skipPos, final ArrayList<SuggestedWordInfo> suggestions) {
- final int count = roots.mLength;
- final int codeSize = mInputLength;
- // Optimization: Prune out words that are too long compared to how much was typed.
- if (depth > mMaxDepth) {
- return;
- }
- final int[] currentChars;
- if (codeSize <= inputIndex) {
- currentChars = null;
- } else {
- currentChars = mCodes[inputIndex];
- }
-
- for (int i = 0; i < count; i++) {
- final Node node = roots.mData[i];
- final char c = node.mCode;
- final char lowerC = toLowerCase(c);
- final boolean terminal = node.mTerminal;
- final NodeArray children = node.mChildren;
- final int freq = node.mFrequency;
- if (completion || currentChars == null) {
- word[depth] = c;
- if (terminal) {
- final int finalFreq;
- if (skipPos < 0) {
- finalFreq = freq * snr;
- } else {
- finalFreq = computeSkippedWordFinalFreq(freq, snr, mInputLength);
- }
- if (!addWordAndShortcutsFromNode(node, word, depth, finalFreq, suggestions)) {
- // No space left in the queue, bail out
- return;
- }
- }
- if (children != null) {
- getWordsRec(children, codes, word, depth + 1, true, snr, inputIndex,
- skipPos, suggestions);
- }
- } else if ((c == Constants.CODE_SINGLE_QUOTE
- && currentChars[0] != Constants.CODE_SINGLE_QUOTE) || depth == skipPos) {
- // Skip the ' and continue deeper
- word[depth] = c;
- if (children != null) {
- getWordsRec(children, codes, word, depth + 1, completion, snr, inputIndex,
- skipPos, suggestions);
- }
- } else {
- // Don't use alternatives if we're looking for missing characters
- final int alternativesSize = skipPos >= 0 ? 1 : currentChars.length;
- for (int j = 0; j < alternativesSize; j++) {
- final int addedAttenuation = (j > 0 ? 1 : 2);
- final int currentChar = currentChars[j];
- if (currentChar == Constants.NOT_A_CODE) {
- break;
- }
- if (currentChar == lowerC || currentChar == c) {
- word[depth] = c;
-
- if (codeSize == inputIndex + 1) {
- if (terminal) {
- final int finalFreq;
- if (skipPos < 0) {
- finalFreq = freq * snr * addedAttenuation
- * FULL_WORD_SCORE_MULTIPLIER;
- } else {
- finalFreq = computeSkippedWordFinalFreq(freq,
- snr * addedAttenuation, mInputLength);
- }
- if (!addWordAndShortcutsFromNode(node, word, depth, finalFreq,
- suggestions)) {
- // No space left in the queue, bail out
- return;
- }
- }
- if (children != null) {
- getWordsRec(children, codes, word, depth + 1,
- true, snr * addedAttenuation, inputIndex + 1,
- skipPos, suggestions);
- }
- } else if (children != null) {
- getWordsRec(children, codes, word, depth + 1,
- false, snr * addedAttenuation, inputIndex + 1,
- skipPos, suggestions);
- }
- }
- }
- }
- }
- }
-
- public int setBigramAndGetFrequency(final String word0, final String word1,
- final int frequency) {
- return setBigramAndGetFrequency(word0, word1, frequency, null /* unused */);
- }
-
- public int setBigramAndGetFrequency(final String word0, final String word1,
- final ForgettingCurveParams fcp) {
- return setBigramAndGetFrequency(word0, word1, 0 /* unused */, fcp);
- }
-
- /**
- * Adds bigrams to the in-memory trie structure that is being used to retrieve any word
- * @param word0 the first word of this bigram
- * @param word1 the second word of this bigram
- * @param frequency frequency for this bigram
- * @param fcp an instance of ForgettingCurveParams to use for decay policy
- * @return returns the final bigram frequency
- */
- private int setBigramAndGetFrequency(final String word0, final String word1,
- final int frequency, final ForgettingCurveParams fcp) {
- if (TextUtils.isEmpty(word0)) {
- Log.e(TAG, "Invalid bigram previous word: " + word0);
- return frequency;
- }
- // We don't want results to be different according to case of the looked up left hand side
- // word. We do want however to return the correct case for the right hand side.
- // So we want to squash the case of the left hand side, and preserve that of the right
- // hand side word.
- final String word0Lower = word0.toLowerCase();
- if (TextUtils.isEmpty(word0Lower) || TextUtils.isEmpty(word1)) {
- Log.e(TAG, "Invalid bigram pair: " + word0 + ", " + word0Lower + ", " + word1);
- return frequency;
- }
- final Node firstWord = searchWord(mRoots, word0Lower, 0, null);
- final Node secondWord = searchWord(mRoots, word1, 0, null);
- LinkedList<NextWord> bigrams = firstWord.mNGrams;
- if (bigrams == null || bigrams.size() == 0) {
- firstWord.mNGrams = CollectionUtils.newLinkedList();
- bigrams = firstWord.mNGrams;
- } else {
- for (NextWord nw : bigrams) {
- if (nw.getWordNode() == secondWord) {
- return nw.notifyTypedAgainAndGetFrequency();
- }
- }
- }
- if (fcp != null) {
- // history
- firstWord.mNGrams.add(new NextHistoryWord(secondWord, fcp));
- } else {
- firstWord.mNGrams.add(new NextStaticWord(secondWord, frequency));
- }
- return frequency;
- }
-
- /**
- * Searches for the word and add the word if it does not exist.
- * @return Returns the terminal node of the word we are searching for.
- */
- private Node searchWord(final NodeArray children, final String word, final int depth,
- final Node parentNode) {
- final int wordLength = word.length();
- final char c = word.charAt(depth);
- // Does children have the current character?
- final int childrenLength = children.mLength;
- Node childNode = null;
- for (int i = 0; i < childrenLength; i++) {
- final Node node = children.mData[i];
- if (node.mCode == c) {
- childNode = node;
- break;
- }
- }
- if (childNode == null) {
- childNode = new Node();
- childNode.mCode = c;
- childNode.mParent = parentNode;
- children.add(childNode);
- }
- if (wordLength == depth + 1) {
- // Terminate this word
- childNode.mTerminal = true;
- return childNode;
- }
- if (childNode.mChildren == null) {
- childNode.mChildren = new NodeArray();
- }
- return searchWord(childNode.mChildren, word, depth + 1, childNode);
- }
-
- private void runBigramReverseLookUp(final String previousWord,
- final ArrayList<SuggestedWordInfo> suggestions) {
- // Search for the lowercase version of the word only, because that's where bigrams
- // store their sons.
- final Node prevWord = searchNode(mRoots, previousWord.toLowerCase(), 0,
- previousWord.length());
- if (prevWord != null && prevWord.mNGrams != null) {
- reverseLookUp(prevWord.mNGrams, suggestions);
- }
- }
-
- // Local to reverseLookUp, but do not allocate each time.
- private final char[] mLookedUpString = new char[Constants.DICTIONARY_MAX_WORD_LENGTH];
-
- /**
- * reverseLookUp retrieves the full word given a list of terminal nodes and adds those words
- * to the suggestions list passed as an argument.
- * @param terminalNodes list of terminal nodes we want to add
- * @param suggestions the suggestion collection to add the word to
- */
- private void reverseLookUp(final LinkedList<NextWord> terminalNodes,
- final ArrayList<SuggestedWordInfo> suggestions) {
- Node node;
- int freq;
- for (NextWord nextWord : terminalNodes) {
- node = nextWord.getWordNode();
- freq = nextWord.getFrequency();
- int index = Constants.DICTIONARY_MAX_WORD_LENGTH;
- do {
- --index;
- mLookedUpString[index] = node.mCode;
- node = node.mParent;
- } while (node != null && index > 0);
-
- // If node is null, we have a word longer than MAX_WORD_LENGTH in the dictionary.
- // It's a little unclear how this can happen, but just in case it does it's safer
- // to ignore the word in this case.
- if (freq >= 0 && node == null) {
- suggestions.add(new SuggestedWordInfo(new String(mLookedUpString, index,
- Constants.DICTIONARY_MAX_WORD_LENGTH - index),
- freq, SuggestedWordInfo.KIND_CORRECTION, this /* sourceDict */,
- SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
- SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
- }
- }
- }
-
- /**
- * Recursively search for the terminal node of the word.
- *
- * One iteration takes the full word to search for and the current index of the recursion.
- *
- * @param children the node of the trie to search under.
- * @param word the word to search for. Only read [offset..length] so there may be trailing chars
- * @param offset the index in {@code word} this recursion should operate on.
- * @param length the length of the input word.
- * @return Returns the terminal node of the word if the word exists
- */
- private Node searchNode(final NodeArray children, final CharSequence word, final int offset,
- final int length) {
- final int count = children.mLength;
- final char currentChar = word.charAt(offset);
- for (int j = 0; j < count; j++) {
- final Node node = children.mData[j];
- if (node.mCode == currentChar) {
- if (offset == length - 1) {
- if (node.mTerminal) {
- return node;
- }
- } else {
- if (node.mChildren != null) {
- Node returnNode = searchNode(node.mChildren, word, offset + 1, length);
- if (returnNode != null) return returnNode;
- }
- }
- }
- }
- return null;
- }
-
- public void clearDictionary() {
- mRoots = new NodeArray();
- }
-
- private static char toLowerCase(final char c) {
- char baseChar = c;
- if (c < BASE_CHARS.length) {
- baseChar = BASE_CHARS[c];
- }
- if (baseChar >= 'A' && baseChar <= 'Z') {
- return (char)(baseChar | 32);
- } else if (baseChar > 127) {
- return Character.toLowerCase(baseChar);
- }
- return baseChar;
- }
-
- /**
- * Table mapping most combined Latin, Greek, and Cyrillic characters
- * to their base characters. If c is in range, BASE_CHARS[c] == c
- * if c is not a combined character, or the base character if it
- * is combined.
- *
- * cf. native/jni/src/utils/char_utils.cpp
- */
- private static final char BASE_CHARS[] = {
- /* U+0000 */ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
- /* U+0008 */ 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
- /* U+0010 */ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
- /* U+0018 */ 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
- /* U+0020 */ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
- /* U+0028 */ 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
- /* U+0030 */ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
- /* U+0038 */ 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
- /* U+0040 */ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
- /* U+0048 */ 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
- /* U+0050 */ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
- /* U+0058 */ 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
- /* U+0060 */ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
- /* U+0068 */ 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
- /* U+0070 */ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
- /* U+0078 */ 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
- /* U+0080 */ 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
- /* U+0088 */ 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
- /* U+0090 */ 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
- /* U+0098 */ 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
- /* U+00A0 */ 0x0020, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
- /* U+00A8 */ 0x0020, 0x00A9, 0x0061, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x0020,
- /* U+00B0 */ 0x00B0, 0x00B1, 0x0032, 0x0033, 0x0020, 0x03BC, 0x00B6, 0x00B7,
- /* U+00B8 */ 0x0020, 0x0031, 0x006F, 0x00BB, 0x0031, 0x0031, 0x0033, 0x00BF,
- /* U+00C0 */ 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x00C6, 0x0043,
- /* U+00C8 */ 0x0045, 0x0045, 0x0045, 0x0045, 0x0049, 0x0049, 0x0049, 0x0049,
- /* U+00D0 */ 0x00D0, 0x004E, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x00D7,
- /* U+00D8 */ 0x004F, 0x0055, 0x0055, 0x0055, 0x0055, 0x0059, 0x00DE, 0x0073,
- // U+00D8: Manually changed from 00D8 to 004F
- // TODO: Check if it's really acceptable to consider Ø a diacritical variant of O
- // U+00DF: Manually changed from 00DF to 0073
- /* U+00E0 */ 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x00E6, 0x0063,
- /* U+00E8 */ 0x0065, 0x0065, 0x0065, 0x0065, 0x0069, 0x0069, 0x0069, 0x0069,
- /* U+00F0 */ 0x00F0, 0x006E, 0x006F, 0x006F, 0x006F, 0x006F, 0x006F, 0x00F7,
- /* U+00F8 */ 0x006F, 0x0075, 0x0075, 0x0075, 0x0075, 0x0079, 0x00FE, 0x0079,
- // U+00F8: Manually changed from 00F8 to 006F
- // TODO: Check if it's really acceptable to consider ø a diacritical variant of o
- /* U+0100 */ 0x0041, 0x0061, 0x0041, 0x0061, 0x0041, 0x0061, 0x0043, 0x0063,
- /* U+0108 */ 0x0043, 0x0063, 0x0043, 0x0063, 0x0043, 0x0063, 0x0044, 0x0064,
- /* U+0110 */ 0x0110, 0x0111, 0x0045, 0x0065, 0x0045, 0x0065, 0x0045, 0x0065,
- /* U+0118 */ 0x0045, 0x0065, 0x0045, 0x0065, 0x0047, 0x0067, 0x0047, 0x0067,
- /* U+0120 */ 0x0047, 0x0067, 0x0047, 0x0067, 0x0048, 0x0068, 0x0126, 0x0127,
- /* U+0128 */ 0x0049, 0x0069, 0x0049, 0x0069, 0x0049, 0x0069, 0x0049, 0x0069,
- /* U+0130 */ 0x0049, 0x0131, 0x0049, 0x0069, 0x004A, 0x006A, 0x004B, 0x006B,
- /* U+0138 */ 0x0138, 0x004C, 0x006C, 0x004C, 0x006C, 0x004C, 0x006C, 0x004C,
- /* U+0140 */ 0x006C, 0x004C, 0x006C, 0x004E, 0x006E, 0x004E, 0x006E, 0x004E,
- // U+0141: Manually changed from 0141 to 004C
- // U+0142: Manually changed from 0142 to 006C
- /* U+0148 */ 0x006E, 0x02BC, 0x014A, 0x014B, 0x004F, 0x006F, 0x004F, 0x006F,
- /* U+0150 */ 0x004F, 0x006F, 0x0152, 0x0153, 0x0052, 0x0072, 0x0052, 0x0072,
- /* U+0158 */ 0x0052, 0x0072, 0x0053, 0x0073, 0x0053, 0x0073, 0x0053, 0x0073,
- /* U+0160 */ 0x0053, 0x0073, 0x0054, 0x0074, 0x0054, 0x0074, 0x0166, 0x0167,
- /* U+0168 */ 0x0055, 0x0075, 0x0055, 0x0075, 0x0055, 0x0075, 0x0055, 0x0075,
- /* U+0170 */ 0x0055, 0x0075, 0x0055, 0x0075, 0x0057, 0x0077, 0x0059, 0x0079,
- /* U+0178 */ 0x0059, 0x005A, 0x007A, 0x005A, 0x007A, 0x005A, 0x007A, 0x0073,
- /* U+0180 */ 0x0180, 0x0181, 0x0182, 0x0183, 0x0184, 0x0185, 0x0186, 0x0187,
- /* U+0188 */ 0x0188, 0x0189, 0x018A, 0x018B, 0x018C, 0x018D, 0x018E, 0x018F,
- /* U+0190 */ 0x0190, 0x0191, 0x0192, 0x0193, 0x0194, 0x0195, 0x0196, 0x0197,
- /* U+0198 */ 0x0198, 0x0199, 0x019A, 0x019B, 0x019C, 0x019D, 0x019E, 0x019F,
- /* U+01A0 */ 0x004F, 0x006F, 0x01A2, 0x01A3, 0x01A4, 0x01A5, 0x01A6, 0x01A7,
- /* U+01A8 */ 0x01A8, 0x01A9, 0x01AA, 0x01AB, 0x01AC, 0x01AD, 0x01AE, 0x0055,
- /* U+01B0 */ 0x0075, 0x01B1, 0x01B2, 0x01B3, 0x01B4, 0x01B5, 0x01B6, 0x01B7,
- /* U+01B8 */ 0x01B8, 0x01B9, 0x01BA, 0x01BB, 0x01BC, 0x01BD, 0x01BE, 0x01BF,
- /* U+01C0 */ 0x01C0, 0x01C1, 0x01C2, 0x01C3, 0x0044, 0x0044, 0x0064, 0x004C,
- /* U+01C8 */ 0x004C, 0x006C, 0x004E, 0x004E, 0x006E, 0x0041, 0x0061, 0x0049,
- /* U+01D0 */ 0x0069, 0x004F, 0x006F, 0x0055, 0x0075, 0x0055, 0x0075, 0x0055,
- // U+01D5: Manually changed from 00DC to 0055
- // U+01D6: Manually changed from 00FC to 0075
- // U+01D7: Manually changed from 00DC to 0055
- /* U+01D8 */ 0x0075, 0x0055, 0x0075, 0x0055, 0x0075, 0x01DD, 0x0041, 0x0061,
- // U+01D8: Manually changed from 00FC to 0075
- // U+01D9: Manually changed from 00DC to 0055
- // U+01DA: Manually changed from 00FC to 0075
- // U+01DB: Manually changed from 00DC to 0055
- // U+01DC: Manually changed from 00FC to 0075
- // U+01DE: Manually changed from 00C4 to 0041
- // U+01DF: Manually changed from 00E4 to 0061
- /* U+01E0 */ 0x0041, 0x0061, 0x00C6, 0x00E6, 0x01E4, 0x01E5, 0x0047, 0x0067,
- // U+01E0: Manually changed from 0226 to 0041
- // U+01E1: Manually changed from 0227 to 0061
- /* U+01E8 */ 0x004B, 0x006B, 0x004F, 0x006F, 0x004F, 0x006F, 0x01B7, 0x0292,
- // U+01EC: Manually changed from 01EA to 004F
- // U+01ED: Manually changed from 01EB to 006F
- /* U+01F0 */ 0x006A, 0x0044, 0x0044, 0x0064, 0x0047, 0x0067, 0x01F6, 0x01F7,
- /* U+01F8 */ 0x004E, 0x006E, 0x0041, 0x0061, 0x00C6, 0x00E6, 0x004F, 0x006F,
- // U+01FA: Manually changed from 00C5 to 0041
- // U+01FB: Manually changed from 00E5 to 0061
- // U+01FE: Manually changed from 00D8 to 004F
- // TODO: Check if it's really acceptable to consider Ø a diacritical variant of O
- // U+01FF: Manually changed from 00F8 to 006F
- // TODO: Check if it's really acceptable to consider ø a diacritical variant of o
- /* U+0200 */ 0x0041, 0x0061, 0x0041, 0x0061, 0x0045, 0x0065, 0x0045, 0x0065,
- /* U+0208 */ 0x0049, 0x0069, 0x0049, 0x0069, 0x004F, 0x006F, 0x004F, 0x006F,
- /* U+0210 */ 0x0052, 0x0072, 0x0052, 0x0072, 0x0055, 0x0075, 0x0055, 0x0075,
- /* U+0218 */ 0x0053, 0x0073, 0x0054, 0x0074, 0x021C, 0x021D, 0x0048, 0x0068,
- /* U+0220 */ 0x0220, 0x0221, 0x0222, 0x0223, 0x0224, 0x0225, 0x0041, 0x0061,
- /* U+0228 */ 0x0045, 0x0065, 0x004F, 0x006F, 0x004F, 0x006F, 0x004F, 0x006F,
- // U+022A: Manually changed from 00D6 to 004F
- // U+022B: Manually changed from 00F6 to 006F
- // U+022C: Manually changed from 00D5 to 004F
- // U+022D: Manually changed from 00F5 to 006F
- /* U+0230 */ 0x004F, 0x006F, 0x0059, 0x0079, 0x0234, 0x0235, 0x0236, 0x0237,
- // U+0230: Manually changed from 022E to 004F
- // U+0231: Manually changed from 022F to 006F
- /* U+0238 */ 0x0238, 0x0239, 0x023A, 0x023B, 0x023C, 0x023D, 0x023E, 0x023F,
- /* U+0240 */ 0x0240, 0x0241, 0x0242, 0x0243, 0x0244, 0x0245, 0x0246, 0x0247,
- /* U+0248 */ 0x0248, 0x0249, 0x024A, 0x024B, 0x024C, 0x024D, 0x024E, 0x024F,
- /* U+0250 */ 0x0250, 0x0251, 0x0252, 0x0253, 0x0254, 0x0255, 0x0256, 0x0257,
- /* U+0258 */ 0x0258, 0x0259, 0x025A, 0x025B, 0x025C, 0x025D, 0x025E, 0x025F,
- /* U+0260 */ 0x0260, 0x0261, 0x0262, 0x0263, 0x0264, 0x0265, 0x0266, 0x0267,
- /* U+0268 */ 0x0268, 0x0269, 0x026A, 0x026B, 0x026C, 0x026D, 0x026E, 0x026F,
- /* U+0270 */ 0x0270, 0x0271, 0x0272, 0x0273, 0x0274, 0x0275, 0x0276, 0x0277,
- /* U+0278 */ 0x0278, 0x0279, 0x027A, 0x027B, 0x027C, 0x027D, 0x027E, 0x027F,
- /* U+0280 */ 0x0280, 0x0281, 0x0282, 0x0283, 0x0284, 0x0285, 0x0286, 0x0287,
- /* U+0288 */ 0x0288, 0x0289, 0x028A, 0x028B, 0x028C, 0x028D, 0x028E, 0x028F,
- /* U+0290 */ 0x0290, 0x0291, 0x0292, 0x0293, 0x0294, 0x0295, 0x0296, 0x0297,
- /* U+0298 */ 0x0298, 0x0299, 0x029A, 0x029B, 0x029C, 0x029D, 0x029E, 0x029F,
- /* U+02A0 */ 0x02A0, 0x02A1, 0x02A2, 0x02A3, 0x02A4, 0x02A5, 0x02A6, 0x02A7,
- /* U+02A8 */ 0x02A8, 0x02A9, 0x02AA, 0x02AB, 0x02AC, 0x02AD, 0x02AE, 0x02AF,
- /* U+02B0 */ 0x0068, 0x0266, 0x006A, 0x0072, 0x0279, 0x027B, 0x0281, 0x0077,
- /* U+02B8 */ 0x0079, 0x02B9, 0x02BA, 0x02BB, 0x02BC, 0x02BD, 0x02BE, 0x02BF,
- /* U+02C0 */ 0x02C0, 0x02C1, 0x02C2, 0x02C3, 0x02C4, 0x02C5, 0x02C6, 0x02C7,
- /* U+02C8 */ 0x02C8, 0x02C9, 0x02CA, 0x02CB, 0x02CC, 0x02CD, 0x02CE, 0x02CF,
- /* U+02D0 */ 0x02D0, 0x02D1, 0x02D2, 0x02D3, 0x02D4, 0x02D5, 0x02D6, 0x02D7,
- /* U+02D8 */ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x02DE, 0x02DF,
- /* U+02E0 */ 0x0263, 0x006C, 0x0073, 0x0078, 0x0295, 0x02E5, 0x02E6, 0x02E7,
- /* U+02E8 */ 0x02E8, 0x02E9, 0x02EA, 0x02EB, 0x02EC, 0x02ED, 0x02EE, 0x02EF,
- /* U+02F0 */ 0x02F0, 0x02F1, 0x02F2, 0x02F3, 0x02F4, 0x02F5, 0x02F6, 0x02F7,
- /* U+02F8 */ 0x02F8, 0x02F9, 0x02FA, 0x02FB, 0x02FC, 0x02FD, 0x02FE, 0x02FF,
- /* U+0300 */ 0x0300, 0x0301, 0x0302, 0x0303, 0x0304, 0x0305, 0x0306, 0x0307,
- /* U+0308 */ 0x0308, 0x0309, 0x030A, 0x030B, 0x030C, 0x030D, 0x030E, 0x030F,
- /* U+0310 */ 0x0310, 0x0311, 0x0312, 0x0313, 0x0314, 0x0315, 0x0316, 0x0317,
- /* U+0318 */ 0x0318, 0x0319, 0x031A, 0x031B, 0x031C, 0x031D, 0x031E, 0x031F,
- /* U+0320 */ 0x0320, 0x0321, 0x0322, 0x0323, 0x0324, 0x0325, 0x0326, 0x0327,
- /* U+0328 */ 0x0328, 0x0329, 0x032A, 0x032B, 0x032C, 0x032D, 0x032E, 0x032F,
- /* U+0330 */ 0x0330, 0x0331, 0x0332, 0x0333, 0x0334, 0x0335, 0x0336, 0x0337,
- /* U+0338 */ 0x0338, 0x0339, 0x033A, 0x033B, 0x033C, 0x033D, 0x033E, 0x033F,
- /* U+0340 */ 0x0300, 0x0301, 0x0342, 0x0313, 0x0308, 0x0345, 0x0346, 0x0347,
- /* U+0348 */ 0x0348, 0x0349, 0x034A, 0x034B, 0x034C, 0x034D, 0x034E, 0x034F,
- /* U+0350 */ 0x0350, 0x0351, 0x0352, 0x0353, 0x0354, 0x0355, 0x0356, 0x0357,
- /* U+0358 */ 0x0358, 0x0359, 0x035A, 0x035B, 0x035C, 0x035D, 0x035E, 0x035F,
- /* U+0360 */ 0x0360, 0x0361, 0x0362, 0x0363, 0x0364, 0x0365, 0x0366, 0x0367,
- /* U+0368 */ 0x0368, 0x0369, 0x036A, 0x036B, 0x036C, 0x036D, 0x036E, 0x036F,
- /* U+0370 */ 0x0370, 0x0371, 0x0372, 0x0373, 0x02B9, 0x0375, 0x0376, 0x0377,
- /* U+0378 */ 0x0378, 0x0379, 0x0020, 0x037B, 0x037C, 0x037D, 0x003B, 0x037F,
- /* U+0380 */ 0x0380, 0x0381, 0x0382, 0x0383, 0x0020, 0x00A8, 0x0391, 0x00B7,
- /* U+0388 */ 0x0395, 0x0397, 0x0399, 0x038B, 0x039F, 0x038D, 0x03A5, 0x03A9,
- /* U+0390 */ 0x03CA, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397,
- /* U+0398 */ 0x0398, 0x0399, 0x039A, 0x039B, 0x039C, 0x039D, 0x039E, 0x039F,
- /* U+03A0 */ 0x03A0, 0x03A1, 0x03A2, 0x03A3, 0x03A4, 0x03A5, 0x03A6, 0x03A7,
- /* U+03A8 */ 0x03A8, 0x03A9, 0x0399, 0x03A5, 0x03B1, 0x03B5, 0x03B7, 0x03B9,
- /* U+03B0 */ 0x03CB, 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7,
- /* U+03B8 */ 0x03B8, 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF,
- /* U+03C0 */ 0x03C0, 0x03C1, 0x03C2, 0x03C3, 0x03C4, 0x03C5, 0x03C6, 0x03C7,
- /* U+03C8 */ 0x03C8, 0x03C9, 0x03B9, 0x03C5, 0x03BF, 0x03C5, 0x03C9, 0x03CF,
- /* U+03D0 */ 0x03B2, 0x03B8, 0x03A5, 0x03D2, 0x03D2, 0x03C6, 0x03C0, 0x03D7,
- /* U+03D8 */ 0x03D8, 0x03D9, 0x03DA, 0x03DB, 0x03DC, 0x03DD, 0x03DE, 0x03DF,
- /* U+03E0 */ 0x03E0, 0x03E1, 0x03E2, 0x03E3, 0x03E4, 0x03E5, 0x03E6, 0x03E7,
- /* U+03E8 */ 0x03E8, 0x03E9, 0x03EA, 0x03EB, 0x03EC, 0x03ED, 0x03EE, 0x03EF,
- /* U+03F0 */ 0x03BA, 0x03C1, 0x03C2, 0x03F3, 0x0398, 0x03B5, 0x03F6, 0x03F7,
- /* U+03F8 */ 0x03F8, 0x03A3, 0x03FA, 0x03FB, 0x03FC, 0x03FD, 0x03FE, 0x03FF,
- /* U+0400 */ 0x0415, 0x0415, 0x0402, 0x0413, 0x0404, 0x0405, 0x0406, 0x0406,
- /* U+0408 */ 0x0408, 0x0409, 0x040A, 0x040B, 0x041A, 0x0418, 0x0423, 0x040F,
- /* U+0410 */ 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417,
- /* U+0418 */ 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F,
- // U+0419: Manually changed from 0418 to 0419
- /* U+0420 */ 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427,
- /* U+0428 */ 0x0428, 0x0429, 0x042C, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F,
- // U+042A: Manually changed from 042A to 042C
- /* U+0430 */ 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437,
- /* U+0438 */ 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F,
- // U+0439: Manually changed from 0438 to 0439
- /* U+0440 */ 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447,
- /* U+0448 */ 0x0448, 0x0449, 0x044C, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F,
- // U+044A: Manually changed from 044A to 044C
- /* U+0450 */ 0x0435, 0x0435, 0x0452, 0x0433, 0x0454, 0x0455, 0x0456, 0x0456,
- /* U+0458 */ 0x0458, 0x0459, 0x045A, 0x045B, 0x043A, 0x0438, 0x0443, 0x045F,
- /* U+0460 */ 0x0460, 0x0461, 0x0462, 0x0463, 0x0464, 0x0465, 0x0466, 0x0467,
- /* U+0468 */ 0x0468, 0x0469, 0x046A, 0x046B, 0x046C, 0x046D, 0x046E, 0x046F,
- /* U+0470 */ 0x0470, 0x0471, 0x0472, 0x0473, 0x0474, 0x0475, 0x0474, 0x0475,
- /* U+0478 */ 0x0478, 0x0479, 0x047A, 0x047B, 0x047C, 0x047D, 0x047E, 0x047F,
- /* U+0480 */ 0x0480, 0x0481, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487,
- /* U+0488 */ 0x0488, 0x0489, 0x048A, 0x048B, 0x048C, 0x048D, 0x048E, 0x048F,
- /* U+0490 */ 0x0490, 0x0491, 0x0492, 0x0493, 0x0494, 0x0495, 0x0496, 0x0497,
- /* U+0498 */ 0x0498, 0x0499, 0x049A, 0x049B, 0x049C, 0x049D, 0x049E, 0x049F,
- /* U+04A0 */ 0x04A0, 0x04A1, 0x04A2, 0x04A3, 0x04A4, 0x04A5, 0x04A6, 0x04A7,
- /* U+04A8 */ 0x04A8, 0x04A9, 0x04AA, 0x04AB, 0x04AC, 0x04AD, 0x04AE, 0x04AF,
- /* U+04B0 */ 0x04B0, 0x04B1, 0x04B2, 0x04B3, 0x04B4, 0x04B5, 0x04B6, 0x04B7,
- /* U+04B8 */ 0x04B8, 0x04B9, 0x04BA, 0x04BB, 0x04BC, 0x04BD, 0x04BE, 0x04BF,
- /* U+04C0 */ 0x04C0, 0x0416, 0x0436, 0x04C3, 0x04C4, 0x04C5, 0x04C6, 0x04C7,
- /* U+04C8 */ 0x04C8, 0x04C9, 0x04CA, 0x04CB, 0x04CC, 0x04CD, 0x04CE, 0x04CF,
- /* U+04D0 */ 0x0410, 0x0430, 0x0410, 0x0430, 0x04D4, 0x04D5, 0x0415, 0x0435,
- /* U+04D8 */ 0x04D8, 0x04D9, 0x04D8, 0x04D9, 0x0416, 0x0436, 0x0417, 0x0437,
- /* U+04E0 */ 0x04E0, 0x04E1, 0x0418, 0x0438, 0x0418, 0x0438, 0x041E, 0x043E,
- /* U+04E8 */ 0x04E8, 0x04E9, 0x04E8, 0x04E9, 0x042D, 0x044D, 0x0423, 0x0443,
- /* U+04F0 */ 0x0423, 0x0443, 0x0423, 0x0443, 0x0427, 0x0447, 0x04F6, 0x04F7,
- /* U+04F8 */ 0x042B, 0x044B, 0x04FA, 0x04FB, 0x04FC, 0x04FD, 0x04FE, 0x04FF,
- };
-}
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
index 8caf6f17f..fcf043031 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -52,8 +52,7 @@ public final class InputAttributes {
} else if (inputClass == 0) {
// TODO: is this check still necessary?
Log.w(TAG, String.format("Unexpected input class: inputType=0x%08x"
- + " imeOptions=0x%08x",
- inputType, editorInfo.imeOptions));
+ + " imeOptions=0x%08x", inputType, editorInfo.imeOptions));
}
mIsSettingsSuggestionStripOn = false;
mInputTypeNoAutoCorrect = false;
@@ -204,8 +203,7 @@ public final class InputAttributes {
public static boolean inPrivateImeOptions(String packageName, String key,
EditorInfo editorInfo) {
if (editorInfo == null) return false;
- final String findingKey = (packageName != null) ? packageName + "." + key
- : key;
+ final String findingKey = (packageName != null) ? packageName + "." + key : key;
return StringUtils.containsInCommaSplittableText(findingKey, editorInfo.privateImeOptions);
}
}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 77d07019f..6a10131b0 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -68,7 +68,6 @@ import com.android.inputmethod.compat.InputMethodServiceCompatUtils;
import com.android.inputmethod.compat.SuggestionSpanUtils;
import com.android.inputmethod.dictionarypack.DictionaryPackConstants;
import com.android.inputmethod.event.EventInterpreter;
-import com.android.inputmethod.keyboard.KeyDetector;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardActionListener;
import com.android.inputmethod.keyboard.KeyboardId;
@@ -81,7 +80,6 @@ import com.android.inputmethod.latin.personalization.DictionaryDecayBroadcastRec
import com.android.inputmethod.latin.personalization.PersonalizationDictionary;
import com.android.inputmethod.latin.personalization.PersonalizationDictionarySessionRegister;
import com.android.inputmethod.latin.personalization.PersonalizationHelper;
-import com.android.inputmethod.latin.personalization.PersonalizationPredictionDictionary;
import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.settings.SettingsActivity;
@@ -97,8 +95,8 @@ import com.android.inputmethod.latin.utils.InputTypeUtils;
import com.android.inputmethod.latin.utils.IntentUtils;
import com.android.inputmethod.latin.utils.JniUtils;
import com.android.inputmethod.latin.utils.LatinImeLoggerUtils;
+import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
import com.android.inputmethod.latin.utils.RecapitalizeStatus;
-import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper;
import com.android.inputmethod.latin.utils.StringUtils;
import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask;
import com.android.inputmethod.latin.utils.TextRange;
@@ -110,6 +108,7 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Locale;
import java.util.TreeSet;
+import java.util.concurrent.TimeUnit;
/**
* Input method implementation for Qwerty'ish keyboard.
@@ -182,7 +181,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private boolean mIsMainDictionaryAvailable;
private UserBinaryDictionary mUserDictionary;
private UserHistoryDictionary mUserHistoryDictionary;
- private PersonalizationPredictionDictionary mPersonalizationPredictionDictionary;
private PersonalizationDictionary mPersonalizationDictionary;
private boolean mIsUserDictionaryAvailable;
@@ -196,9 +194,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private int mLastSelectionStart = NOT_A_CURSOR_POSITION;
private int mLastSelectionEnd = NOT_A_CURSOR_POSITION;
- // Whether we are expecting an onUpdateSelection event to fire. If it does when we don't
- // "expect" it, it means the user actually moved the cursor.
- private boolean mExpectingUpdateSelection;
private int mDeleteCount;
private long mLastKeyTime;
private final TreeSet<Long> mCurrentlyPressedHardwareKeys = CollectionUtils.newTreeSet();
@@ -227,7 +222,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public final UIHandler mHandler = new UIHandler(this);
private InputUpdater mInputUpdater;
- public static final class UIHandler extends StaticInnerHandlerWrapper<LatinIME> {
+ public static final class UIHandler extends LeakGuardHandlerWrapper<LatinIME> {
private static final int MSG_UPDATE_SHIFT_STATE = 0;
private static final int MSG_PENDING_IMS_CALLBACK = 1;
private static final int MSG_UPDATE_SUGGESTION_STRIP = 2;
@@ -236,6 +231,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private static final int MSG_REOPEN_DICTIONARIES = 5;
private static final int MSG_ON_END_BATCH_INPUT = 6;
private static final int MSG_RESET_CACHES = 7;
+ // Update this when adding new messages
+ private static final int MSG_LAST = MSG_RESET_CACHES;
private static final int ARG1_NOT_GESTURE_INPUT = 0;
private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
@@ -248,12 +245,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private long mDoubleSpacePeriodTimeout;
private long mDoubleSpacePeriodTimerStart;
- public UIHandler(final LatinIME outerInstance) {
- super(outerInstance);
+ public UIHandler(final LatinIME ownerInstance) {
+ super(ownerInstance);
}
public void onCreate() {
- final Resources res = getOuterInstance().getResources();
+ final Resources res = getOwnerInstance().getResources();
mDelayUpdateSuggestions =
res.getInteger(R.integer.config_delay_update_suggestions);
mDelayUpdateShiftState =
@@ -264,7 +261,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public void handleMessage(final Message msg) {
- final LatinIME latinIme = getOuterInstance();
+ final LatinIME latinIme = getOwnerInstance();
final KeyboardSwitcher switcher = latinIme.mKeyboardSwitcher;
switch (msg.what) {
case MSG_UPDATE_SUGGESTION_STRIP:
@@ -347,6 +344,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
removeMessages(MSG_UPDATE_SHIFT_STATE);
}
+ @UsedForTesting
+ public void removeAllMessages() {
+ for (int i = 0; i <= MSG_LAST; ++i) {
+ removeMessages(i);
+ }
+ }
+
public void showGesturePreviewAndSuggestionStrip(final SuggestedWords suggestedWords,
final boolean dismissGestureFloatingPreviewText) {
removeMessages(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP);
@@ -401,7 +405,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
removeMessages(MSG_PENDING_IMS_CALLBACK);
resetPendingImsCallback();
mIsOrientationChanging = true;
- final LatinIME latinIme = getOuterInstance();
+ final LatinIME latinIme = getOwnerInstance();
if (latinIme.isInputViewShown()) {
latinIme.mKeyboardSwitcher.saveKeyboardState();
}
@@ -434,7 +438,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mIsOrientationChanging = false;
mPendingSuccessiveImsCallback = true;
}
- final LatinIME latinIme = getOuterInstance();
+ final LatinIME latinIme = getOwnerInstance();
executePendingImsCallback(latinIme, editorInfo, restarting);
latinIme.onStartInputInternal(editorInfo, restarting);
}
@@ -453,7 +457,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
sendMessageDelayed(obtainMessage(MSG_PENDING_IMS_CALLBACK),
PENDING_IMS_CALLBACK_DURATION);
}
- final LatinIME latinIme = getOuterInstance();
+ final LatinIME latinIme = getOwnerInstance();
executePendingImsCallback(latinIme, editorInfo, restarting);
latinIme.onStartInputViewInternal(editorInfo, restarting);
mAppliedEditorInfo = editorInfo;
@@ -465,7 +469,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Typically this is the first onFinishInputView after orientation changed.
mHasPendingFinishInputView = true;
} else {
- final LatinIME latinIme = getOuterInstance();
+ final LatinIME latinIme = getOwnerInstance();
latinIme.onFinishInputViewInternal(finishingInput);
mAppliedEditorInfo = null;
}
@@ -476,7 +480,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Typically this is the first onFinishInput after orientation changed.
mHasPendingFinishInput = true;
} else {
- final LatinIME latinIme = getOuterInstance();
+ final LatinIME latinIme = getOwnerInstance();
executePendingImsCallback(latinIme, null, false);
latinIme.onFinishInputInternal();
}
@@ -609,7 +613,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final Locale switcherSubtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
final String switcherLocaleStr = switcherSubtypeLocale.toString();
final Locale subtypeLocale;
- final String localeStr;
if (TextUtils.isEmpty(switcherLocaleStr)) {
// This happens in very rare corner cases - for example, immediately after a switch
// to LatinIME has been requested, about a frame later another switch happens. In this
@@ -619,10 +622,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// of knowing anyway.
Log.e(TAG, "System is reporting no current subtype.");
subtypeLocale = getResources().getConfiguration().locale;
- localeStr = subtypeLocale.toString();
} else {
subtypeLocale = switcherSubtypeLocale;
- localeStr = switcherLocaleStr;
}
final Suggest newSuggest = new Suggest(this /* Context */, subtypeLocale,
@@ -637,21 +638,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
ResearchLogger.getInstance().initSuggest(newSuggest);
}
- mUserDictionary = new UserBinaryDictionary(this, localeStr);
+ mUserDictionary = new UserBinaryDictionary(this, subtypeLocale);
mIsUserDictionaryAvailable = mUserDictionary.isEnabled();
newSuggest.setUserDictionary(mUserDictionary);
- final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
-
mUserHistoryDictionary = PersonalizationHelper.getUserHistoryDictionary(
- this, localeStr, prefs);
+ this, subtypeLocale);
newSuggest.setUserHistoryDictionary(mUserHistoryDictionary);
- mPersonalizationDictionary = PersonalizationHelper
- .getPersonalizationDictionary(this, localeStr, prefs);
+ mPersonalizationDictionary =
+ PersonalizationHelper.getPersonalizationDictionary(this, subtypeLocale);
newSuggest.setPersonalizationDictionary(mPersonalizationDictionary);
- mPersonalizationPredictionDictionary = PersonalizationHelper
- .getPersonalizationPredictionDictionary(this, localeStr, prefs);
- newSuggest.setPersonalizationPredictionDictionary(mPersonalizationPredictionDictionary);
final Suggest oldSuggest = mSuggest;
resetContactsDictionary(null != oldSuggest ? oldSuggest.getContactsDictionary() : null);
@@ -760,8 +756,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
.findViewById(android.R.id.extractArea);
mKeyPreviewBackingView = view.findViewById(R.id.key_preview_backing);
mSuggestionStripView = (SuggestionStripView)view.findViewById(R.id.suggestion_strip_view);
- if (mSuggestionStripView != null)
+ if (mSuggestionStripView != null) {
mSuggestionStripView.setListener(this, view);
+ }
if (LatinImeLogger.sVISUALDEBUG) {
mKeyPreviewBackingView.setBackgroundColor(0x10FF0000);
}
@@ -907,7 +904,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// refresh later.
final boolean canReachInputConnection;
if (!mConnection.resetCachesUponCursorMoveAndReturnSuccess(editorInfo.initialSelStart,
- false /* shouldFinishComposition */)) {
+ editorInfo.initialSelEnd, false /* shouldFinishComposition */)) {
// We try resetting the caches up to 5 times before giving up.
mHandler.postResetCaches(isDifferentTextField, 5 /* remainingTries */);
// mLastSelection{Start,End} are reset later in this method, don't need to do it here
@@ -1088,16 +1085,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
+ ", ce=" + composingSpanEnd);
}
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- final boolean expectingUpdateSelectionFromLogger =
- ResearchLogger.getAndClearLatinIMEExpectingUpdateSelection();
ResearchLogger.latinIME_onUpdateSelection(mLastSelectionStart, mLastSelectionEnd,
oldSelStart, oldSelEnd, newSelStart, newSelEnd, composingSpanStart,
- composingSpanEnd, mExpectingUpdateSelection,
- expectingUpdateSelectionFromLogger, mConnection);
- if (expectingUpdateSelectionFromLogger) {
- // TODO: Investigate. Quitting now sounds wrong - we won't do the resetting work
- return;
- }
+ composingSpanEnd, mConnection);
}
final boolean selectionChanged = mLastSelectionStart != newSelStart
@@ -1116,14 +1106,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// TODO: revisit this when LatinIME supports hardware keyboards.
// NOTE: the test harness subclasses LatinIME and overrides isInputViewShown().
// TODO: find a better way to simulate actual execution.
- if (isInputViewShown() && !mExpectingUpdateSelection
- && !mConnection.isBelatedExpectedUpdate(oldSelStart, newSelStart)) {
- // TAKE CARE: there is a race condition when we enter this test even when the user
- // did not explicitly move the cursor. This happens when typing fast, where two keys
- // turn this flag on in succession and both onUpdateSelection() calls arrive after
- // the second one - the first call successfully avoids this test, but the second one
- // enters. For the moment we rely on noComposingSpan to further reduce the impact.
-
+ if (isInputViewShown() && !mConnection.isBelatedExpectedUpdate(oldSelStart, newSelStart,
+ oldSelEnd, newSelEnd)) {
// TODO: the following is probably better done in resetEntireInputState().
// it should only happen when the cursor moved, and the very purpose of the
// test below is to narrow down whether this happened or not. Likewise with
@@ -1149,13 +1133,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Another option would be to send suggestions each time we set the composing
// text, but that is probably too expensive to do, so we decided to leave things
// as is.
- resetEntireInputState(newSelStart);
+ resetEntireInputState(newSelStart, newSelEnd);
} else {
- // resetEntireInputState calls resetCachesUponCursorMove, but with the second
- // argument as true. But in all cases where we don't reset the entire input state,
- // we still want to tell the rich input connection about the new cursor position so
- // that it can update its caches.
- mConnection.resetCachesUponCursorMoveAndReturnSuccess(newSelStart,
+ // resetEntireInputState calls resetCachesUponCursorMove, but forcing the
+ // composition to end. But in all cases where we don't reset the entire input
+ // state, we still want to tell the rich input connection about the new cursor
+ // position so that it can update its caches.
+ mConnection.resetCachesUponCursorMoveAndReturnSuccess(newSelStart, newSelEnd,
false /* shouldFinishComposition */);
}
@@ -1168,7 +1152,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mRecapitalizeStatus.deactivate();
mKeyboardSwitcher.updateShiftState();
}
- mExpectingUpdateSelection = false;
// Make a note of the cursor position
mLastSelectionStart = newSelStart;
@@ -1354,8 +1337,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public boolean onEvaluateFullscreenMode() {
// Reread resource value here, because this method is called by framework anytime as needed.
- final boolean isFullscreenModeAllowed =
- Settings.readUseFullscreenMode(getResources());
+ final boolean isFullscreenModeAllowed = Settings.readUseFullscreenMode(getResources());
if (super.onEvaluateFullscreenMode() && isFullscreenModeAllowed) {
// TODO: Remove this hack. Actually we should not really assume NO_EXTRACT_UI
// implies NO_FULLSCREEN. However, the framework mistakenly does. i.e. NO_EXTRACT_UI
@@ -1380,7 +1362,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// This will reset the whole input state to the starting state. It will clear
// the composing word, reset the last composed word, tell the inputconnection about it.
- private void resetEntireInputState(final int newCursorPosition) {
+ private void resetEntireInputState(final int newSelStart, final int newSelEnd) {
final boolean shouldFinishComposition = mWordComposer.isComposingWord();
resetComposingState(true /* alsoResetLastComposedWord */);
final SettingsValues settingsValues = mSettings.getCurrent();
@@ -1389,14 +1371,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} else {
setSuggestedWords(settingsValues.mSuggestPuncList, false);
}
- mConnection.resetCachesUponCursorMoveAndReturnSuccess(newCursorPosition,
+ mConnection.resetCachesUponCursorMoveAndReturnSuccess(newSelStart, newSelEnd,
shouldFinishComposition);
}
private void resetComposingState(final boolean alsoResetLastComposedWord) {
mWordComposer.reset();
- if (alsoResetLastComposedWord)
+ if (alsoResetLastComposedWord) {
mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
+ }
}
private void commitTyped(final String separatorString) {
@@ -1443,15 +1426,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (0 != (auto & TextUtils.CAP_MODE_CHARACTERS)) {
return WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED;
}
- if (0 != auto) return WordComposer.CAPS_MODE_AUTO_SHIFTED;
+ if (0 != auto) {
+ return WordComposer.CAPS_MODE_AUTO_SHIFTED;
+ }
return WordComposer.CAPS_MODE_OFF;
}
private void swapSwapperAndSpace() {
final 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) == Constants.CODE_SPACE) {
+ if (lastTwo != null && lastTwo.length() == 2 && lastTwo.charAt(0) == Constants.CODE_SPACE) {
mConnection.deleteSurroundingText(2, 0);
final String text = lastTwo.charAt(1) + " ";
mConnection.commitText(text, 1);
@@ -1730,7 +1714,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
// If we are in the middle of a recorrection, we need to commit the recorrection
// first so that we can insert the character at the current cursor position.
- resetEntireInputState(mLastSelectionStart);
+ resetEntireInputState(mLastSelectionStart, mLastSelectionEnd);
} else {
commitTyped(LastComposedWord.NOT_A_SEPARATOR);
}
@@ -1746,7 +1730,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
handleCharacter(primaryCode, keyX, keyY, spaceState);
}
- mExpectingUpdateSelection = true;
return didAutoCorrect;
}
@@ -1786,9 +1769,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mInputUpdater.onStartBatchInput();
mHandler.cancelUpdateSuggestionStrip();
mConnection.beginBatchEdit();
- final SettingsValues settingsValues = mSettings.getCurrent();
+ final SettingsValues currentSettingsValues = mSettings.getCurrent();
if (mWordComposer.isComposingWord()) {
- if (settingsValues.mIsInternal) {
+ if (currentSettingsValues.mIsInternal) {
if (mWordComposer.isBatchMode()) {
LatinImeLoggerUtils.onAutoCorrection(
"", mWordComposer.getTypedWord(), " ", mWordComposer);
@@ -1799,7 +1782,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
// If we are in the middle of a recorrection, we need to commit the recorrection
// first so that we can insert the batch input at the current cursor position.
- resetEntireInputState(mLastSelectionStart);
+ resetEntireInputState(mLastSelectionStart, mLastSelectionEnd);
} else if (wordComposerSize <= 1) {
// We auto-correct the previous (typed, not gestured) string iff it's one character
// long. The reason for this is, even in the middle of gesture typing, you'll still
@@ -1812,15 +1795,17 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} else {
commitTyped(LastComposedWord.NOT_A_SEPARATOR);
}
- mExpectingUpdateSelection = true;
}
final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
if (Character.isLetterOrDigit(codePointBeforeCursor)
- || settingsValues.isUsuallyFollowedBySpace(codePointBeforeCursor)) {
+ || currentSettingsValues.isUsuallyFollowedBySpace(codePointBeforeCursor)) {
mSpaceState = SPACE_STATE_PHANTOM;
}
mConnection.endBatchEdit();
- mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode());
+ mKeyboardSwitcher.updateShiftState();
+ mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(getActualCapsMode(),
+ // Prev word is 1st word before cursor
+ getNthPreviousWordForSuggestion(currentSettingsValues, 1 /* nthPreviousWord */));
}
static final class InputUpdater implements Handler.Callback {
@@ -1993,7 +1978,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mConnection.commitText(commitParts[0], 0);
mSpaceState = SPACE_STATE_PHANTOM;
mKeyboardSwitcher.updateShiftState();
- mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode());
+ mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
+ getActualCapsMode(), commitParts[0]);
++mAutoCommitSequenceNumber;
}
}
@@ -2003,8 +1989,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// This method must run in UI Thread.
public void onEndBatchInputAsyncInternal(final SuggestedWords suggestedWords) {
- final String batchInputText = suggestedWords.isEmpty()
- ? null : suggestedWords.getWord(0);
+ final String batchInputText = suggestedWords.isEmpty() ? null : suggestedWords.getWord(0);
if (TextUtils.isEmpty(batchInputText)) {
return;
}
@@ -2026,7 +2011,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mWordComposer.setBatchInputWord(batchInputText);
mConnection.setComposingText(batchInputText, 1);
}
- mExpectingUpdateSelection = true;
mConnection.endBatchEdit();
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_onEndBatchInput(batchInputText, 0, suggestedWords);
@@ -2080,9 +2064,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
private void handleBackspace(final int spaceState) {
- // We revert these in this method if the deletion doesn't happen.
mDeleteCount++;
- mExpectingUpdateSelection = true;
// In many cases, we may have to put the keyboard in auto-shift state again. However
// we want to wait a few milliseconds before doing it to avoid the keyboard flashing
@@ -2092,7 +2074,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
// If we are in the middle of a recorrection, we need to commit the recorrection
// first so that we can remove the character at the current cursor position.
- resetEntireInputState(mLastSelectionStart);
+ resetEntireInputState(mLastSelectionStart, mLastSelectionEnd);
// When we exit this if-clause, mWordComposer.isComposingWord() will return false.
}
if (mWordComposer.isComposingWord()) {
@@ -2163,6 +2145,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// later (typically, in a subsequent press on backspace).
mLastSelectionEnd = mLastSelectionStart;
mConnection.deleteSurroundingText(numCharsDeleted, 0);
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_handleBackspace(numCharsDeleted,
+ false /* shouldUncommitLogUnit */);
+ }
} else {
// There is no selection, just delete one character.
if (NOT_A_CURSOR_POSITION == mLastSelectionEnd) {
@@ -2185,22 +2171,27 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} else {
final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
if (codePointBeforeCursor == Constants.NOT_A_CODE) {
- // Nothing to delete before the cursor. We have to revert the deletion
- // states that were updated at the beginning of this method.
- mDeleteCount--;
- mExpectingUpdateSelection = false;
+ // Nothing to delete before the cursor.
return;
}
final int lengthToDelete =
Character.isSupplementaryCodePoint(codePointBeforeCursor) ? 2 : 1;
mConnection.deleteSurroundingText(lengthToDelete, 0);
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_handleBackspace(lengthToDelete,
+ true /* shouldUncommitLogUnit */);
+ }
if (mDeleteCount > DELETE_ACCELERATE_AT) {
final int codePointBeforeCursorToDeleteAgain =
mConnection.getCodePointBeforeCursor();
if (codePointBeforeCursorToDeleteAgain != Constants.NOT_A_CODE) {
final int lengthToDeleteAgain = Character.isSupplementaryCodePoint(
- codePointBeforeCursorToDeleteAgain) ? 2 : 1;
+ codePointBeforeCursorToDeleteAgain) ? 2 : 1;
mConnection.deleteSurroundingText(lengthToDeleteAgain, 0);
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_handleBackspace(lengthToDeleteAgain,
+ true /* shouldUncommitLogUnit */);
+ }
}
}
}
@@ -2217,8 +2208,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
/*
* Strip a trailing space if necessary and returns whether it's a swap weak space situation.
*/
- private boolean maybeStripSpace(final int code,
- final int spaceState, final boolean isFromSuggestionStrip) {
+ private boolean maybeStripSpace(final int code, final int spaceState,
+ final boolean isFromSuggestionStrip) {
if (Constants.CODE_ENTER == code && SPACE_STATE_SWAP_PUNCTUATION == spaceState) {
mConnection.removeTrailingSpace();
return false;
@@ -2233,8 +2224,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return false;
}
- private void handleCharacter(final int primaryCode, final int x,
- final int y, final int spaceState) {
+ private void handleCharacter(final int primaryCode, final int x, final int y,
+ final int spaceState) {
// TODO: refactor this method to stop flipping isComposingWord around all the time, and
// make it shorter (possibly cut into several pieces). Also factor handleNonSpecialCharacter
// which has the same name as other handle* methods but is not the same.
@@ -2254,7 +2245,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
// If we are in the middle of a recorrection, we need to commit the recorrection
// first so that we can insert the character at the current cursor position.
- resetEntireInputState(mLastSelectionStart);
+ resetEntireInputState(mLastSelectionStart, mLastSelectionEnd);
isComposingWord = false;
}
// We want to find out whether to start composing a new word with this character. If so,
@@ -2284,25 +2275,24 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
resetComposingState(false /* alsoResetLastComposedWord */);
}
if (isComposingWord) {
- final int keyX, keyY;
- if (Constants.isValidCoordinate(x) && Constants.isValidCoordinate(y)) {
- final KeyDetector keyDetector =
- mKeyboardSwitcher.getMainKeyboardView().getKeyDetector();
- keyX = keyDetector.getTouchX(x);
- keyY = keyDetector.getTouchY(y);
- } else {
- keyX = x;
- keyY = y;
- }
+ final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
+ // TODO: We should reconsider which coordinate system should be used to represent
+ // keyboard event.
+ final int keyX = mainKeyboardView.getKeyX(x);
+ final int keyY = mainKeyboardView.getKeyY(y);
mWordComposer.add(primaryCode, keyX, keyY);
// If it's the first letter, make note of auto-caps state
if (mWordComposer.size() == 1) {
- mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode());
+ // We pass 1 to getPreviousWordForSuggestion because we were not composing a word
+ // yet, so the word we want is the 1st word before the cursor.
+ mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
+ getActualCapsMode(),
+ getNthPreviousWordForSuggestion(currentSettings, 1 /* nthPreviousWord */));
}
mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
} else {
- final boolean swapWeakSpace = maybeStripSpace(primaryCode,
- spaceState, Constants.SUGGESTION_STRIP_COORDINATE == x);
+ final boolean swapWeakSpace = maybeStripSpace(primaryCode, spaceState,
+ Constants.SUGGESTION_STRIP_COORDINATE == x);
sendKeyCodePoint(primaryCode);
@@ -2366,7 +2356,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
// If we are in the middle of a recorrection, we need to commit the recorrection
// first so that we can insert the separator at the current cursor position.
- resetEntireInputState(mLastSelectionStart);
+ resetEntireInputState(mLastSelectionStart, mLastSelectionEnd);
}
if (mWordComposer.isComposingWord()) { // May have changed since we stored wasComposing
if (currentSettings.mCorrectionEnabled) {
@@ -2539,6 +2529,24 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
+ /**
+ * Get the nth previous word before the cursor as context for the suggestion process.
+ * @param currentSettings the current settings values.
+ * @param nthPreviousWord reverse index of the word to get (1-indexed)
+ * @return the nth previous word before the cursor.
+ */
+ private String getNthPreviousWordForSuggestion(final SettingsValues currentSettings,
+ final int nthPreviousWord) {
+ if (currentSettings.mCurrentLanguageHasSpaces) {
+ // If we are typing in a language with spaces we can just look up the previous
+ // word from textview.
+ return mConnection.getNthPreviousWord(currentSettings, nthPreviousWord);
+ } else {
+ return LastComposedWord.NOT_A_COMPOSED_WORD == mLastComposedWord ? null
+ : mLastComposedWord.mCommittedWord;
+ }
+ }
+
private void getSuggestedWords(final int sessionId, final int sequenceNumber,
final OnGetSuggestedWordsCallback callback) {
final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
@@ -2552,17 +2560,31 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// should just skip whitespace if any, so 1.
final SettingsValues currentSettings = mSettings.getCurrent();
final int[] additionalFeaturesOptions = currentSettings.mAdditionalFeaturesSettingValues;
- final String prevWord;
- if (currentSettings.mCurrentLanguageHasSpaces) {
- // If we are typing in a language with spaces we can just look up the previous
- // word from textview.
- prevWord = mConnection.getNthPreviousWord(currentSettings.mWordSeparators,
- mWordComposer.isComposingWord() ? 2 : 1);
+
+ final String previousWord;
+ if (mWordComposer.isComposingWord() || mWordComposer.isBatchMode()) {
+ previousWord = mWordComposer.getPreviousWord();
} else {
- prevWord = LastComposedWord.NOT_A_COMPOSED_WORD == mLastComposedWord ? null
- : mLastComposedWord.mCommittedWord;
+ // Not composing: this is for prediction.
+ // TODO: read the previous word earlier for prediction, like we are doing for
+ // normal suggestions.
+ previousWord = getNthPreviousWordForSuggestion(currentSettings, 1 /* nthPreviousWord*/);
+ }
+ if (DEBUG) {
+ // TODO: this is for checking consistency with older versions. Remove this when
+ // we are confident this is stable.
+ // We're checking the previous word in the text field against the memorized previous
+ // word. If we are composing a word we should have the second word before the cursor
+ // memorized, otherwise we should have the first.
+ final String rereadPrevWord = getNthPreviousWordForSuggestion(currentSettings,
+ mWordComposer.isComposingWord() ? 2 : 1);
+ if (!TextUtils.equals(previousWord, rereadPrevWord)) {
+ throw new RuntimeException("Unexpected previous word: "
+ + previousWord + " <> " + rereadPrevWord);
+ }
}
- suggest.getSuggestedWords(mWordComposer, prevWord, keyboard.getProximityInfo(),
+ suggest.getSuggestedWords(mWordComposer, mWordComposer.getPreviousWord(),
+ keyboard.getProximityInfo(),
currentSettings.mBlockPotentiallyOffensive, currentSettings.mCorrectionEnabled,
additionalFeaturesOptions, sessionId, sequenceNumber, callback);
}
@@ -2679,7 +2701,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
ResearchLogger.latinIme_commitCurrentAutoCorrection(typedWord, autoCorrection,
separator, mWordComposer.isBatchMode(), suggestedWords);
}
- mExpectingUpdateSelection = true;
commitChosenWord(autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD,
separator);
if (!typedWord.equals(autoCorrection)) {
@@ -2750,7 +2771,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// typed word.
final String replacedWord = mWordComposer.getTypedWord();
LatinImeLogger.logOnManualSuggestion(replacedWord, suggestion, index, suggestedWords);
- mExpectingUpdateSelection = true;
commitChosenWord(suggestion, LastComposedWord.COMMIT_TYPE_MANUAL_PICK,
LastComposedWord.NOT_A_SEPARATOR);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
@@ -2833,7 +2853,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final UserHistoryDictionary userHistoryDictionary = mUserHistoryDictionary;
if (userHistoryDictionary == null) return null;
- final String prevWord = mConnection.getNthPreviousWord(currentSettings.mWordSeparators, 2);
+ final String prevWord = mConnection.getNthPreviousWord(currentSettings, 2);
final String secondWord;
if (mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps()) {
secondWord = suggestion.toLowerCase(mSubtypeSwitcher.getCurrentSubtypeLocale());
@@ -2845,7 +2865,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final int maxFreq = AutoCorrectionUtils.getMaxFrequency(
suggest.getUnigramDictionaries(), suggestion);
if (maxFreq == 0) return null;
- userHistoryDictionary.addToDictionary(prevWord, secondWord, maxFreq > 0);
+ userHistoryDictionary.addToDictionary(prevWord, secondWord, maxFreq > 0,
+ (int)TimeUnit.MILLISECONDS.toSeconds((System.currentTimeMillis())));
return prevWord;
}
@@ -2901,7 +2922,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
}
- mWordComposer.setComposingWord(typedWord, mKeyboardSwitcher.getKeyboard());
+ mWordComposer.setComposingWord(typedWord,
+ getNthPreviousWordForSuggestion(currentSettings,
+ // We want the previous word for suggestion. If we have chars in the word
+ // before the cursor, then we want the word before that, hence 2; otherwise,
+ // we want the word immediately before the cursor, hence 1.
+ 0 == numberOfCharsInWordBeforeCursor ? 1 : 2),
+ mKeyboardSwitcher.getKeyboard());
mWordComposer.setCursorPositionWithinWord(
typedWord.codePointCount(0, numberOfCharsInWordBeforeCursor));
mConnection.setComposingRegion(
@@ -2979,7 +3006,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
private void restartSuggestionsOnWordBeforeCursor(final String word) {
- mWordComposer.setComposingWord(word, mKeyboardSwitcher.getKeyboard());
+ mWordComposer.setComposingWord(word,
+ // Previous word is the 2nd word before cursor because we are restarting on the
+ // 1st word before cursor.
+ getNthPreviousWordForSuggestion(mSettings.getCurrent(), 2 /* nthPreviousWord */),
+ mKeyboardSwitcher.getKeyboard());
final int length = word.length();
mConnection.deleteSurroundingText(length, 0);
mConnection.setComposingText(word, 1);
@@ -2997,7 +3028,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
* @param remainingTries How many times we may try again before giving up.
*/
private void retryResetCaches(final boolean tryResumeSuggestions, final int remainingTries) {
- if (!mConnection.resetCachesUponCursorMoveAndReturnSuccess(mLastSelectionStart, false)) {
+ if (!mConnection.resetCachesUponCursorMoveAndReturnSuccess(mLastSelectionStart,
+ mLastSelectionEnd, false)) {
if (0 < remainingTries) {
mHandler.postResetCaches(tryResumeSuggestions, remainingTries - 1);
return;
@@ -3024,8 +3056,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
throw new RuntimeException("revertCommit, but we are composing a word");
}
final CharSequence wordBeforeCursor =
- mConnection.getTextBeforeCursor(deleteLength, 0)
- .subSequence(0, cancelLength);
+ mConnection.getTextBeforeCursor(deleteLength, 0).subSequence(0, cancelLength);
if (!TextUtils.equals(committedWord, wordBeforeCursor)) {
throw new RuntimeException("revertCommit check failed: we thought we were "
+ "reverting \"" + committedWord
@@ -3045,7 +3076,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} else {
// For languages without spaces, we revert the typed string but the cursor is flush
// with the typed word, so we need to resume suggestions right away.
- mWordComposer.setComposingWord(stringToCommit, mKeyboardSwitcher.getKeyboard());
+ mWordComposer.setComposingWord(stringToCommit, previousWord,
+ mKeyboardSwitcher.getKeyboard());
mConnection.setComposingText(stringToCommit, 1);
}
if (mSettings.isInternal()) {
@@ -3095,8 +3127,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private void hapticAndAudioFeedback(final int code, final int repeatCount) {
final MainKeyboardView keyboardView = mKeyboardSwitcher.getMainKeyboardView();
- if (keyboardView != null && keyboardView.isInSlidingKeyInput()) {
- // No need to feedback while sliding input.
+ if (keyboardView != null && keyboardView.isInDraggingFinger()) {
+ // No need to feedback while finger is dragging.
return;
}
if (repeatCount > 0) {
@@ -3226,8 +3258,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final Intent intent = IntentUtils.getInputLanguageSelectionIntent(
mRichImm.getInputMethodIdOfThisIme(),
Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
- | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
break;
case 1:
@@ -3236,9 +3268,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
};
- final AlertDialog.Builder builder = new AlertDialog.Builder(this)
- .setItems(items, listener)
- .setTitle(title);
+ final AlertDialog.Builder builder =
+ new AlertDialog.Builder(this).setItems(items, listener).setTitle(title);
showOptionDialog(builder.create());
}
@@ -3299,11 +3330,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final Printer p = new PrintWriterPrinter(fout);
p.println("LatinIME state :");
+ p.println(" VersionCode = " + ApplicationUtils.getVersionCode(this));
+ p.println(" VersionName = " + ApplicationUtils.getVersionName(this));
final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
final int keyboardMode = keyboard != null ? keyboard.mId.mMode : -1;
p.println(" Keyboard mode = " + keyboardMode);
final SettingsValues settingsValues = mSettings.getCurrent();
- p.println(" mIsSuggestionsSuggestionsRequested = "
+ p.println(" mIsSuggestionsRequested = "
+ settingsValues.isSuggestionsRequested(mDisplayOrientation));
p.println(" mCorrectionEnabled=" + settingsValues.mCorrectionEnabled);
p.println(" isComposingWord=" + mWordComposer.isComposingWord());
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 673d1b4c2..37311acf2 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -57,14 +57,19 @@ public final class RichInputConnection {
private static final int INVALID_CURSOR_POSITION = -1;
/**
- * This variable contains an expected value for the cursor position. This is where the
- * cursor may end up after all the keyboard-triggered updates have passed. We keep this to
- * compare it to the actual cursor position to guess whether the move was caused by a
- * keyboard command or not.
- * It's not really the cursor position: the cursor may not be there yet, and it's also expected
- * there be cases where it never actually comes to be there.
+ * This variable contains an expected value for the selection start position. This is where the
+ * cursor or selection start may end up after all the keyboard-triggered updates have passed. We
+ * keep this to compare it to the actual selection start to guess whether the move was caused by
+ * a keyboard command or not.
+ * It's not really the selection start position: the selection start may not be there yet, and
+ * in some cases, it may never arrive there.
*/
- private int mExpectedCursorPosition = INVALID_CURSOR_POSITION; // in chars, not code points
+ private int mExpectedSelStart = INVALID_CURSOR_POSITION; // in chars, not code points
+ /**
+ * The expected selection end. Only differs from mExpectedSelStart if a non-empty selection is
+ * expected. The same caveats as mExpectedSelStart apply.
+ */
+ private int mExpectedSelEnd = INVALID_CURSOR_POSITION; // in chars, not code points
/**
* This contains the committed text immediately preceding the cursor and the composing
* text if any. It is refreshed when the cursor moves by calling upon the TextView.
@@ -103,16 +108,16 @@ public final class RichInputConnection {
final String reference = (beforeCursor.length() <= actualLength) ? beforeCursor.toString()
: beforeCursor.subSequence(beforeCursor.length() - actualLength,
beforeCursor.length()).toString();
- if (et.selectionStart != mExpectedCursorPosition
+ if (et.selectionStart != mExpectedSelStart
|| !(reference.equals(internal.toString()))) {
- final String context = "Expected cursor position = " + mExpectedCursorPosition
- + "\nActual cursor position = " + et.selectionStart
+ final String context = "Expected selection start = " + mExpectedSelStart
+ + "\nActual selection start = " + et.selectionStart
+ "\nExpected text = " + internal.length() + " " + internal
+ "\nActual text = " + reference.length() + " " + reference;
((LatinIME)mParent).debugDumpStateAndCrashWithException(context);
} else {
Log.e(TAG, DebugLogUtils.getStackTrace(2));
- Log.e(TAG, "Exp <> Actual : " + mExpectedCursorPosition + " <> " + et.selectionStart);
+ Log.e(TAG, "Exp <> Actual : " + mExpectedSelStart + " <> " + et.selectionStart);
}
}
@@ -150,16 +155,50 @@ public final class RichInputConnection {
* data, so we empty the cache and note that we don't know the new cursor position, and we
* return false so that the caller knows about this and can retry later.
*
- * @param newCursorPosition The new position of the cursor, as received from the system.
- * @param shouldFinishComposition Whether we should finish the composition in progress.
+ * @param newSelStart the new position of the selection start, as received from the system.
+ * @param newSelEnd the new position of the selection end, as received from the system.
+ * @param shouldFinishComposition whether we should finish the composition in progress.
* @return true if we were able to connect to the editor successfully, false otherwise. When
* this method returns false, the caches could not be correctly refreshed so they were only
* reset: the caller should try again later to return to normal operation.
*/
- public boolean resetCachesUponCursorMoveAndReturnSuccess(final int newCursorPosition,
- final boolean shouldFinishComposition) {
- mExpectedCursorPosition = newCursorPosition;
+ public boolean resetCachesUponCursorMoveAndReturnSuccess(final int newSelStart,
+ final int newSelEnd, final boolean shouldFinishComposition) {
+ mExpectedSelStart = newSelStart;
+ mExpectedSelEnd = newSelEnd;
mComposingText.setLength(0);
+ final boolean didReloadTextSuccessfully = reloadTextCache();
+ if (!didReloadTextSuccessfully) {
+ Log.d(TAG, "Will try to retrieve text later.");
+ return false;
+ }
+ final int lengthOfTextBeforeCursor = mCommittedTextBeforeComposingText.length();
+ if (lengthOfTextBeforeCursor > newSelStart
+ || (lengthOfTextBeforeCursor < Constants.EDITOR_CONTENTS_CACHE_SIZE
+ && newSelStart < Constants.EDITOR_CONTENTS_CACHE_SIZE)) {
+ // newSelStart and newSelEnd may be lying -- when rotating the device (probably a
+ // framework bug). If we have less chars than we asked for, then we know how many chars
+ // we have, and if we got more than newSelStart says, then we know it was lying. In both
+ // cases the length is more reliable. Note that we only have to check newSelStart (not
+ // newSelEnd) since if newSelEnd is wrong, the newSelStart will be wrong as well.
+ mExpectedSelStart = lengthOfTextBeforeCursor;
+ mExpectedSelEnd = lengthOfTextBeforeCursor;
+ }
+ if (null != mIC && shouldFinishComposition) {
+ mIC.finishComposingText();
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.richInputConnection_finishComposingText();
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Reload the cached text from the InputConnection.
+ *
+ * @return true if successful
+ */
+ private boolean reloadTextCache() {
mCommittedTextBeforeComposingText.setLength(0);
mIC = mParent.getCurrentInputConnection();
// Call upon the inputconnection directly since our own method is using the cache, and
@@ -169,27 +208,12 @@ public final class RichInputConnection {
if (null == textBeforeCursor) {
// For some reason the app thinks we are not connected to it. This looks like a
// framework bug... Fall back to ground state and return false.
- mExpectedCursorPosition = INVALID_CURSOR_POSITION;
- Log.e(TAG, "Unable to connect to the editor to retrieve text... will retry later");
+ mExpectedSelStart = INVALID_CURSOR_POSITION;
+ mExpectedSelEnd = INVALID_CURSOR_POSITION;
+ Log.e(TAG, "Unable to connect to the editor to retrieve text.");
return false;
}
mCommittedTextBeforeComposingText.append(textBeforeCursor);
- final int lengthOfTextBeforeCursor = textBeforeCursor.length();
- if (lengthOfTextBeforeCursor > newCursorPosition
- || (lengthOfTextBeforeCursor < Constants.EDITOR_CONTENTS_CACHE_SIZE
- && newCursorPosition < Constants.EDITOR_CONTENTS_CACHE_SIZE)) {
- // newCursorPosition may be lying -- when rotating the device (probably a framework
- // bug). If we have less chars than we asked for, then we know how many chars we have,
- // and if we got more than newCursorPosition says, then we know it was lying. In both
- // cases the length is more reliable
- mExpectedCursorPosition = lengthOfTextBeforeCursor;
- }
- if (null != mIC && shouldFinishComposition) {
- mIC.finishComposingText();
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_finishComposingText();
- }
- }
return true;
}
@@ -218,7 +242,8 @@ public final class RichInputConnection {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
mCommittedTextBeforeComposingText.append(text);
- mExpectedCursorPosition += text.length() - mComposingText.length();
+ mExpectedSelStart += text.length() - mComposingText.length();
+ mExpectedSelEnd = mExpectedSelStart;
mComposingText.setLength(0);
if (null != mIC) {
mIC.commitText(text, i);
@@ -231,7 +256,7 @@ public final class RichInputConnection {
}
public boolean canDeleteCharacters() {
- return mExpectedCursorPosition > 0;
+ return mExpectedSelStart > 0;
}
/**
@@ -268,11 +293,10 @@ public final class RichInputConnection {
// heavy pressing of delete, for example DEFAULT_TEXT_CACHE_SIZE - 5 times or so.
// getCapsMode should be updated to be able to return a "not enough info" result so that
// we can get more context only when needed.
- if (TextUtils.isEmpty(mCommittedTextBeforeComposingText) && 0 != mExpectedCursorPosition) {
- final CharSequence textBeforeCursor = getTextBeforeCursor(
- Constants.EDITOR_CONTENTS_CACHE_SIZE, 0);
- if (!TextUtils.isEmpty(textBeforeCursor)) {
- mCommittedTextBeforeComposingText.append(textBeforeCursor);
+ if (TextUtils.isEmpty(mCommittedTextBeforeComposingText) && 0 != mExpectedSelStart) {
+ if (!reloadTextCache()) {
+ Log.w(TAG, "Unable to connect to the editor. "
+ + "Setting caps mode without knowing text.");
}
}
// This never calls InputConnection#getCapsMode - in fact, it's a static method that
@@ -295,8 +319,8 @@ public final class RichInputConnection {
// However, if we don't have an expected cursor position, then we should always
// go fetch the cache again (as it happens, INVALID_CURSOR_POSITION < 0, so we need to
// test for this explicitly)
- if (INVALID_CURSOR_POSITION != mExpectedCursorPosition
- && (cachedLength >= n || cachedLength >= mExpectedCursorPosition)) {
+ if (INVALID_CURSOR_POSITION != mExpectedSelStart
+ && (cachedLength >= n || cachedLength >= mExpectedSelStart)) {
final StringBuilder s = new StringBuilder(mCommittedTextBeforeComposingText);
// We call #toString() here to create a temporary object.
// In some situations, this method is called on a worker thread, and it's possible
@@ -336,10 +360,14 @@ public final class RichInputConnection {
+ remainingChars, 0);
mCommittedTextBeforeComposingText.setLength(len);
}
- if (mExpectedCursorPosition > beforeLength) {
- mExpectedCursorPosition -= beforeLength;
+ if (mExpectedSelStart > beforeLength) {
+ mExpectedSelStart -= beforeLength;
+ mExpectedSelEnd -= beforeLength;
} else {
- mExpectedCursorPosition = 0;
+ // There are fewer characters before the cursor in the buffer than we are being asked to
+ // delete. Only delete what is there.
+ mExpectedSelStart = 0;
+ mExpectedSelEnd -= mExpectedSelStart;
}
if (null != mIC) {
mIC.deleteSurroundingText(beforeLength, afterLength);
@@ -373,7 +401,8 @@ public final class RichInputConnection {
switch (keyEvent.getKeyCode()) {
case KeyEvent.KEYCODE_ENTER:
mCommittedTextBeforeComposingText.append("\n");
- mExpectedCursorPosition += 1;
+ mExpectedSelStart += 1;
+ mExpectedSelEnd = mExpectedSelStart;
break;
case KeyEvent.KEYCODE_DEL:
if (0 == mComposingText.length()) {
@@ -385,18 +414,24 @@ public final class RichInputConnection {
} else {
mComposingText.delete(mComposingText.length() - 1, mComposingText.length());
}
- if (mExpectedCursorPosition > 0) mExpectedCursorPosition -= 1;
+ if (mExpectedSelStart > 0 && mExpectedSelStart == mExpectedSelEnd) {
+ // TODO: Handle surrogate pairs.
+ mExpectedSelStart -= 1;
+ }
+ mExpectedSelEnd = mExpectedSelStart;
break;
case KeyEvent.KEYCODE_UNKNOWN:
if (null != keyEvent.getCharacters()) {
mCommittedTextBeforeComposingText.append(keyEvent.getCharacters());
- mExpectedCursorPosition += keyEvent.getCharacters().length();
+ mExpectedSelStart += keyEvent.getCharacters().length();
+ mExpectedSelEnd = mExpectedSelStart;
}
break;
default:
- final String text = new String(new int[] { keyEvent.getUnicodeChar() }, 0, 1);
+ final String text = StringUtils.newSingleCodePointString(keyEvent.getUnicodeChar());
mCommittedTextBeforeComposingText.append(text);
- mExpectedCursorPosition += text.length();
+ mExpectedSelStart += text.length();
+ mExpectedSelEnd = mExpectedSelStart;
break;
}
}
@@ -430,10 +465,12 @@ public final class RichInputConnection {
public void setComposingText(final CharSequence text, final int newCursorPosition) {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
- mExpectedCursorPosition += text.length() - mComposingText.length();
+ mExpectedSelStart += text.length() - mComposingText.length();
+ mExpectedSelEnd = mExpectedSelStart;
mComposingText.setLength(0);
mComposingText.append(text);
- // TODO: support values of i != 1. At this time, this is never called with i != 1.
+ // TODO: support values of newCursorPosition != 1. At this time, this is never called with
+ // newCursorPosition != 1.
if (null != mIC) {
mIC.setComposingText(text, newCursorPosition);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
@@ -443,19 +480,31 @@ public final class RichInputConnection {
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
}
- public void setSelection(final int start, final int end) {
+ /**
+ * Set the selection of the text editor.
+ *
+ * Calls through to {@link InputConnection#setSelection(int, int)}.
+ *
+ * @param start the character index where the selection should start.
+ * @param end the character index where the selection should end.
+ * @return Returns true on success, false if the input connection is no longer valid either when
+ * setting the selection or when retrieving the text cache at that point.
+ */
+ public boolean setSelection(final int start, final int end) {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
+ mExpectedSelStart = start;
+ mExpectedSelEnd = end;
if (null != mIC) {
- mIC.setSelection(start, end);
+ final boolean isIcValid = mIC.setSelection(start, end);
+ if (!isIcValid) {
+ return false;
+ }
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.richInputConnection_setSelection(start, end);
}
}
- mExpectedCursorPosition = start;
- mCommittedTextBeforeComposingText.setLength(0);
- mCommittedTextBeforeComposingText.append(
- getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE, 0));
+ return reloadTextCache();
}
public void commitCorrection(final CorrectionInfo correctionInfo) {
@@ -476,7 +525,8 @@ public final class RichInputConnection {
// text should never be null, but just in case, it's better to insert nothing than to crash
if (null == text) text = "";
mCommittedTextBeforeComposingText.append(text);
- mExpectedCursorPosition += text.length() - mComposingText.length();
+ mExpectedSelStart += text.length() - mComposingText.length();
+ mExpectedSelEnd = mExpectedSelStart;
mComposingText.setLength(0);
if (null != mIC) {
mIC.commitCompletion(completionInfo);
@@ -488,7 +538,7 @@ public final class RichInputConnection {
}
@SuppressWarnings("unused")
- public String getNthPreviousWord(final String sentenceSeperators, final int n) {
+ public String getNthPreviousWord(final SettingsValues currentSettingsValues, final int n) {
mIC = mParent.getCurrentInputConnection();
if (null == mIC) return null;
final CharSequence prev = getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0);
@@ -507,7 +557,7 @@ public final class RichInputConnection {
}
}
}
- return getNthPreviousWord(prev, sentenceSeperators, n);
+ return getNthPreviousWord(prev, currentSettingsValues, n);
}
private static boolean isSeparator(int code, String sep) {
@@ -531,7 +581,7 @@ public final class RichInputConnection {
// (n = 2) "abc |" -> null
// (n = 2) "abc. def|" -> null
public static String getNthPreviousWord(final CharSequence prev,
- final String sentenceSeperators, final int n) {
+ final SettingsValues currentSettingsValues, final int n) {
if (prev == null) return null;
final String[] w = spaceRegex.split(prev);
@@ -543,7 +593,8 @@ public final class RichInputConnection {
// If ends in a separator, return null
final char lastChar = nthPrevWord.charAt(length - 1);
- if (sentenceSeperators.contains(String.valueOf(lastChar))) return null;
+ if (currentSettingsValues.isWordSeparator(lastChar)
+ || currentSettingsValues.isWordConnector(lastChar)) return null;
return nthPrevWord;
}
@@ -758,20 +809,30 @@ public final class RichInputConnection {
* this update and not the ones in-between. This is almost impossible to achieve even trying
* very very hard.
*
- * @param oldSelStart The value of the old cursor position in the update.
- * @param newSelStart The value of the new cursor position in the update.
+ * @param oldSelStart The value of the old selection in the update.
+ * @param newSelStart The value of the new selection in the update.
+ * @param oldSelEnd The value of the old selection end in the update.
+ * @param newSelEnd The value of the new selection end in the update.
* @return whether this is a belated expected update or not.
*/
- public boolean isBelatedExpectedUpdate(final int oldSelStart, final int newSelStart) {
- // If this is an update that arrives at our expected position, it's a belated update.
- if (newSelStart == mExpectedCursorPosition) return true;
- // If this is an update that moves the cursor from our expected position, it must be
- // an explicit move.
- if (oldSelStart == mExpectedCursorPosition) return false;
- // The following returns true if newSelStart is between oldSelStart and
- // mCurrentCursorPosition. We assume that if the updated position is between the old
- // position and the expected position, then it must be a belated update.
- return (newSelStart - oldSelStart) * (mExpectedCursorPosition - newSelStart) >= 0;
+ public boolean isBelatedExpectedUpdate(final int oldSelStart, final int newSelStart,
+ final int oldSelEnd, final int newSelEnd) {
+ // This update is "belated" if we are expecting it. That is, mExpectedSelStart and
+ // mExpectedSelEnd match the new values that the TextView is updating TO.
+ if (mExpectedSelStart == newSelStart && mExpectedSelEnd == newSelEnd) return true;
+ // This update is not belated if mExpectedSelStart and mExpeectedSelend match the old
+ // values, and one of newSelStart or newSelEnd is updated to a different value. In this
+ // case, there is likely something other than the IME that has moved the selection endpoint
+ // to the new value.
+ if (mExpectedSelStart == oldSelStart && mExpectedSelEnd == oldSelEnd
+ && (oldSelStart != newSelStart || oldSelEnd != newSelEnd)) return false;
+ // If nether of the above two cases holds, then the system may be having trouble keeping up
+ // with updates. If 1) the selection is a cursor, 2) newSelStart is between oldSelStart
+ // and mExpectedSelStart, and 3) newSelEnd is between oldSelEnd and mExpectedSelEnd, then
+ // assume a belated update.
+ return (newSelStart == newSelEnd)
+ && (newSelStart - oldSelStart) * (mExpectedSelStart - newSelStart) >= 0
+ && (newSelEnd - oldSelEnd) * (mExpectedSelEnd - newSelEnd) >= 0;
}
/**
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index cd9c89f04..379eaaa3d 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -32,7 +32,9 @@ import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
+import com.android.inputmethod.latin.utils.LocaleUtils;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
import java.util.List;
@@ -56,23 +58,34 @@ public final class SubtypeSwitcher {
private InputMethodSubtype mEmojiSubtype;
private boolean mIsNetworkConnected;
+ private static final String KEYBOARD_MODE = "keyboard";
// Dummy no language QWERTY subtype. See {@link R.xml.method}.
- private static final InputMethodSubtype DUMMY_NO_LANGUAGE_SUBTYPE = new InputMethodSubtype(
- R.string.subtype_no_language_qwerty, R.drawable.ic_ime_switcher_dark,
- SubtypeLocaleUtils.NO_LANGUAGE, "keyboard", "KeyboardLayoutSet="
- + SubtypeLocaleUtils.QWERTY
- + "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE
- + ",EnabledWhenDefaultIsNotAsciiCapable,"
- + Constants.Subtype.ExtraValue.EMOJI_CAPABLE,
- false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */);
+ private static final int SUBTYPE_ID_OF_DUMMY_NO_LANGUAGE_SUBTYPE = 0xdde0bfd3;
+ private static final String EXTRA_VALUE_OF_DUMMY_NO_LANGUAGE_SUBTYPE =
+ "KeyboardLayoutSet=" + SubtypeLocaleUtils.QWERTY
+ + "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE
+ + "," + Constants.Subtype.ExtraValue.ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE
+ + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
+ private static final InputMethodSubtype DUMMY_NO_LANGUAGE_SUBTYPE =
+ InputMethodSubtypeCompatUtils.newInputMethodSubtype(
+ R.string.subtype_no_language_qwerty, R.drawable.ic_ime_switcher_dark,
+ SubtypeLocaleUtils.NO_LANGUAGE, KEYBOARD_MODE,
+ EXTRA_VALUE_OF_DUMMY_NO_LANGUAGE_SUBTYPE,
+ false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */,
+ SUBTYPE_ID_OF_DUMMY_NO_LANGUAGE_SUBTYPE);
// Caveat: We probably should remove this when we add an Emoji subtype in {@link R.xml.method}.
// Dummy Emoji subtype. See {@link R.xml.method}.
- private static final InputMethodSubtype DUMMY_EMOJI_SUBTYPE = new InputMethodSubtype(
- R.string.subtype_emoji, R.drawable.ic_ime_switcher_dark,
- SubtypeLocaleUtils.NO_LANGUAGE, "keyboard", "KeyboardLayoutSet="
- + SubtypeLocaleUtils.EMOJI + ","
- + Constants.Subtype.ExtraValue.EMOJI_CAPABLE,
- false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */);
+ private static final int SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE = 0xd78b2ed0;
+ private static final String EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE =
+ "KeyboardLayoutSet=" + SubtypeLocaleUtils.EMOJI
+ + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
+ private static final InputMethodSubtype DUMMY_EMOJI_SUBTYPE =
+ InputMethodSubtypeCompatUtils.newInputMethodSubtype(
+ R.string.subtype_emoji, R.drawable.ic_ime_switcher_dark,
+ SubtypeLocaleUtils.NO_LANGUAGE, KEYBOARD_MODE,
+ EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE,
+ false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */,
+ SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE);
static final class NeedsToDisplayLanguage {
private int mEnabledSubtypeCount;
@@ -256,18 +269,23 @@ public final class SubtypeSwitcher {
return mNeedsToDisplayLanguage.getValue();
}
- private static Locale sForcedLocaleForTesting = null;
+ private static InputMethodSubtype sForcedSubtypeForTesting = null;
@UsedForTesting
- void forceLocale(final Locale locale) {
- sForcedLocaleForTesting = locale;
+ void forceSubtype(final InputMethodSubtype subtype) {
+ sForcedSubtypeForTesting = subtype;
}
public Locale getCurrentSubtypeLocale() {
- if (null != sForcedLocaleForTesting) return sForcedLocaleForTesting;
+ if (null != sForcedSubtypeForTesting) {
+ return LocaleUtils.constructLocaleFromString(sForcedSubtypeForTesting.getLocale());
+ }
return SubtypeLocaleUtils.getSubtypeLocale(getCurrentSubtype());
}
public InputMethodSubtype getCurrentSubtype() {
+ if (null != sForcedSubtypeForTesting) {
+ return sForcedSubtypeForTesting;
+ }
return mRichImm.getCurrentInputMethodSubtype(getNoLanguageSubtype());
}
@@ -279,8 +297,8 @@ public final class SubtypeSwitcher {
if (mNoLanguageSubtype != null) {
return mNoLanguageSubtype;
}
- Log.w(TAG, "Can't find no lanugage with QWERTY subtype");
- Log.w(TAG, "No input method subtype found; return dummy subtype: "
+ Log.w(TAG, "Can't find any language with QWERTY subtype");
+ Log.w(TAG, "No input method subtype found; returning dummy subtype: "
+ DUMMY_NO_LANGUAGE_SUBTYPE);
return DUMMY_NO_LANGUAGE_SUBTYPE;
}
@@ -293,8 +311,9 @@ public final class SubtypeSwitcher {
if (mEmojiSubtype != null) {
return mEmojiSubtype;
}
- Log.w(TAG, "Can't find Emoji subtype");
- Log.w(TAG, "No input method subtype found; return dummy subtype: " + DUMMY_EMOJI_SUBTYPE);
+ Log.w(TAG, "Can't find emoji subtype");
+ Log.w(TAG, "No input method subtype found; returning dummy subtype: "
+ + DUMMY_EMOJI_SUBTYPE);
return DUMMY_EMOJI_SUBTYPE;
}
}
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 0a4c7a55d..0ecb41100 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -25,7 +25,6 @@ import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.personalization.PersonalizationDictionary;
-import com.android.inputmethod.latin.personalization.PersonalizationPredictionDictionary;
import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
@@ -90,7 +89,6 @@ public final class Suggest {
PreferenceManager.getDefaultSharedPreferences(context))) {
mOnlyDictionarySetForDebug = new HashSet<String>();
mOnlyDictionarySetForDebug.add(Dictionary.TYPE_PERSONALIZATION);
- mOnlyDictionarySetForDebug.add(Dictionary.TYPE_PERSONALIZATION_PREDICTION_IN_JAVA);
}
}
@@ -194,12 +192,6 @@ public final class Suggest {
addOrReplaceDictionaryInternal(Dictionary.TYPE_USER_HISTORY, userHistoryDictionary);
}
- public void setPersonalizationPredictionDictionary(
- final PersonalizationPredictionDictionary personalizationPredictionDictionary) {
- addOrReplaceDictionaryInternal(Dictionary.TYPE_PERSONALIZATION_PREDICTION_IN_JAVA,
- personalizationPredictionDictionary);
- }
-
public void setPersonalizationDictionary(
final PersonalizationDictionary personalizationDictionary) {
addOrReplaceDictionaryInternal(Dictionary.TYPE_PERSONALIZATION,
@@ -432,7 +424,8 @@ public final class Suggest {
final String scoreInfoString;
if (normalizedScore > 0) {
scoreInfoString = String.format(
- Locale.ROOT, "%d (%4.2f)", cur.mScore, normalizedScore);
+ Locale.ROOT, "%d (%4.2f), %s", cur.mScore, normalizedScore,
+ cur.mSourceDict.mDictType);
} else {
scoreInfoString = Integer.toString(cur.mScore);
}
diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
index 6405b5e46..9cb2f5bc4 100644
--- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
@@ -22,14 +22,15 @@ import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import java.util.ArrayList;
+import java.util.Locale;
public final class SynchronouslyLoadedUserBinaryDictionary extends UserBinaryDictionary {
- public SynchronouslyLoadedUserBinaryDictionary(final Context context, final String locale) {
+ public SynchronouslyLoadedUserBinaryDictionary(final Context context, final Locale locale) {
this(context, locale, false);
}
- public SynchronouslyLoadedUserBinaryDictionary(final Context context, final String locale,
+ public SynchronouslyLoadedUserBinaryDictionary(final Context context, final Locale locale,
final boolean alsoUseMoreRestrictiveLocales) {
super(context, locale, alsoUseMoreRestrictiveLocales);
}
diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index 15b3d8d02..1dc3c54bb 100644
--- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
@@ -18,7 +18,6 @@ package com.android.inputmethod.latin;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
-import android.content.ContentUris;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
@@ -75,20 +74,21 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
final private String mLocale;
final private boolean mAlsoUseMoreRestrictiveLocales;
- public UserBinaryDictionary(final Context context, final String locale) {
+ public UserBinaryDictionary(final Context context, final Locale locale) {
this(context, locale, false);
}
- public UserBinaryDictionary(final Context context, final String locale,
+ public UserBinaryDictionary(final Context context, final Locale locale,
final boolean alsoUseMoreRestrictiveLocales) {
- super(context, getFilenameWithLocale(NAME, locale), Dictionary.TYPE_USER,
+ super(context, getDictNameWithLocale(NAME, locale), locale, Dictionary.TYPE_USER,
false /* isUpdatable */);
if (null == locale) throw new NullPointerException(); // Catch the error earlier
- if (SubtypeLocaleUtils.NO_LANGUAGE.equals(locale)) {
+ final String localeStr = locale.toString();
+ if (SubtypeLocaleUtils.NO_LANGUAGE.equals(localeStr)) {
// If we don't have a locale, insert into the "all locales" user dictionary.
mLocale = USER_DICTIONARY_ALL_LANGUAGES;
} else {
- mLocale = locale;
+ mLocale = localeStr;
}
mAlsoUseMoreRestrictiveLocales = alsoUseMoreRestrictiveLocales;
// Perform a managed query. The Activity will handle closing and re-querying the cursor
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 039dadc66..2f81d15d5 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -48,6 +48,10 @@ public final class WordComposer {
// at any given time. However this is not limited in size, while mPrimaryKeyCodes is limited
// to MAX_WORD_LENGTH code points.
private final StringBuilder mTypedWord;
+ // The previous word (before the composing word). Used as context for suggestions. May be null
+ // after resetting and before starting a new composing word, or when there is no context like
+ // at the start of text for example.
+ private String mPreviousWord;
private String mAutoCorrection;
private boolean mIsResumed;
private boolean mIsBatchMode;
@@ -85,6 +89,7 @@ public final class WordComposer {
mIsBatchMode = false;
mCursorPositionWithinWord = 0;
mRejectedBatchModeSuggestion = null;
+ mPreviousWord = null;
refreshSize();
}
@@ -101,6 +106,7 @@ public final class WordComposer {
mIsBatchMode = source.mIsBatchMode;
mCursorPositionWithinWord = source.mCursorPositionWithinWord;
mRejectedBatchModeSuggestion = source.mRejectedBatchModeSuggestion;
+ mPreviousWord = source.mPreviousWord;
refreshSize();
}
@@ -118,6 +124,7 @@ public final class WordComposer {
mIsBatchMode = false;
mCursorPositionWithinWord = 0;
mRejectedBatchModeSuggestion = null;
+ mPreviousWord = null;
refreshSize();
}
@@ -284,8 +291,13 @@ public final class WordComposer {
/**
* Set the currently composing word to the one passed as an argument.
* This will register NOT_A_COORDINATE for X and Ys, and use the passed keyboard for proximity.
+ * @param word the char sequence to set as the composing word.
+ * @param previousWord the previous word, to use as context for suggestions. Can be null if
+ * the context is nil (typically, at start of text).
+ * @param keyboard the keyboard this is typed on, for coordinate info/proximity.
*/
- public void setComposingWord(final CharSequence word, final Keyboard keyboard) {
+ public void setComposingWord(final CharSequence word, final String previousWord,
+ final Keyboard keyboard) {
reset();
final int length = word.length();
for (int i = 0; i < length; i = Character.offsetByCodePoints(word, i, 1)) {
@@ -293,6 +305,7 @@ public final class WordComposer {
addKeyInfo(codePoint, keyboard);
}
mIsResumed = true;
+ mPreviousWord = previousWord;
}
/**
@@ -343,6 +356,10 @@ public final class WordComposer {
return mTypedWord.toString();
}
+ public String getPreviousWord() {
+ return mPreviousWord;
+ }
+
/**
* Whether or not the user typed a capital letter as the first letter in the word
* @return capitalization preference
@@ -388,18 +405,21 @@ public final class WordComposer {
}
/**
- * Saves the caps mode at the start of composing.
+ * Saves the caps mode and the previous word 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.
+ * WordComposer needs to know about the caps mode 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
+ * @param previousWord the previous word as context for suggestions. May be null if none.
*/
- public void setCapitalizedModeAtStartComposingTime(final int mode) {
+ public void setCapitalizedModeAndPreviousWordAtStartComposingTime(final int mode,
+ final String previousWord) {
mCapitalizedMode = mode;
+ mPreviousWord = previousWord;
}
/**
@@ -451,6 +471,7 @@ public final class WordComposer {
mCapsCount = 0;
mDigitsCount = 0;
mIsBatchMode = false;
+ mPreviousWord = mTypedWord.toString();
mTypedWord.setLength(0);
mCodePointSize = 0;
mTrailingSingleQuotesCount = 0;
@@ -464,7 +485,8 @@ public final class WordComposer {
return lastComposedWord;
}
- public void resumeSuggestionOnLastComposedWord(final LastComposedWord lastComposedWord) {
+ public void resumeSuggestionOnLastComposedWord(final LastComposedWord lastComposedWord,
+ final String previousWord) {
mPrimaryKeyCodes = lastComposedWord.mPrimaryKeyCodes;
mInputPointers.set(lastComposedWord.mInputPointers);
mTypedWord.setLength(0);
@@ -475,6 +497,7 @@ public final class WordComposer {
mCursorPositionWithinWord = mCodePointSize;
mRejectedBatchModeSuggestion = null;
mIsResumed = true;
+ mPreviousWord = previousWord;
}
public boolean isBatchMode() {
diff --git a/java/src/com/android/inputmethod/latin/makedict/AbstractDictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/AbstractDictDecoder.java
index fda97dafc..bbbb8e461 100644
--- a/java/src/com/android/inputmethod/latin/makedict/AbstractDictDecoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/AbstractDictDecoder.java
@@ -32,27 +32,29 @@ import java.util.TreeMap;
* A base class of the binary dictionary decoder.
*/
public abstract class AbstractDictDecoder implements DictDecoder {
- protected FileHeader readHeader(final DictBuffer dictBuffer)
+ private static final int SUCCESS = 0;
+ private static final int ERROR_CANNOT_READ = 1;
+ private static final int ERROR_WRONG_FORMAT = 2;
+
+ protected FileHeader readHeader(final DictBuffer headerBuffer)
throws IOException, UnsupportedFormatException {
- if (dictBuffer == null) {
+ if (headerBuffer == null) {
openDictBuffer();
}
- final int version = HeaderReader.readVersion(dictBuffer);
+ final int version = HeaderReader.readVersion(headerBuffer);
if (version < FormatSpec.MINIMUM_SUPPORTED_VERSION
|| version > FormatSpec.MAXIMUM_SUPPORTED_VERSION) {
throw new UnsupportedFormatException("Unsupported version : " + version);
}
// TODO: Remove this field.
- final int optionsFlags = HeaderReader.readOptionFlags(dictBuffer);
-
- final int headerSize = HeaderReader.readHeaderSize(dictBuffer);
-
+ final int optionsFlags = HeaderReader.readOptionFlags(headerBuffer);
+ final int headerSize = HeaderReader.readHeaderSize(headerBuffer);
if (headerSize < 0) {
throw new UnsupportedFormatException("header size can't be negative.");
}
- final HashMap<String, String> attributes = HeaderReader.readAttributes(dictBuffer,
+ final HashMap<String, String> attributes = HeaderReader.readAttributes(headerBuffer,
headerSize);
final FileHeader header = new FileHeader(headerSize,
@@ -204,4 +206,25 @@ public abstract class AbstractDictDecoder implements DictDecoder {
return readLength;
}
}
+
+ /**
+ * Check whether the header contains the expected information. This is a no-error method,
+ * that will return an error code and never throw a checked exception.
+ * @return an error code, either ERROR_* or SUCCESS.
+ */
+ private int checkHeader() {
+ try {
+ readHeader();
+ } catch (IOException e) {
+ return ERROR_CANNOT_READ;
+ } catch (UnsupportedFormatException e) {
+ return ERROR_WRONG_FORMAT;
+ }
+ return SUCCESS;
+ }
+
+ @Override
+ public boolean hasValidRawBinaryDictionary() {
+ return checkHeader() == SUCCESS;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
index 216492b4d..83ee7d685 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
@@ -24,12 +24,9 @@ import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Map;
import java.util.TreeMap;
@@ -169,6 +166,14 @@ public final class BinaryDictDecoderUtils {
return size;
}
+ static int getCharArraySize(final int[] chars, final int start, final int end) {
+ int size = 0;
+ for (int i = start; i < end; ++i) {
+ size += getCharSize(chars[i]);
+ }
+ return size;
+ }
+
/**
* Writes a char array to a byte buffer.
*
@@ -200,8 +205,7 @@ public final class BinaryDictDecoderUtils {
* @param word the string to write.
* @return the size written, in bytes.
*/
- static int writeString(final byte[] buffer, final int origin,
- final String word) {
+ static int writeString(final byte[] buffer, final int origin, final String word) {
final int length = word.length();
int index = origin;
for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
@@ -223,22 +227,62 @@ public final class BinaryDictDecoderUtils {
*
* This will also write the terminator byte.
*
- * @param buffer the OutputStream to write to.
+ * @param stream the OutputStream to write to.
* @param word the string to write.
+ * @return the size written, in bytes.
*/
- static void writeString(final OutputStream buffer, final String word) throws IOException {
+ static int writeString(final OutputStream stream, final String word) throws IOException {
final int length = word.length();
+ int written = 0;
for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
final int codePoint = word.codePointAt(i);
- if (1 == getCharSize(codePoint)) {
- buffer.write((byte) codePoint);
+ final int charSize = getCharSize(codePoint);
+ if (1 == charSize) {
+ stream.write((byte) codePoint);
} else {
- buffer.write((byte) (0xFF & (codePoint >> 16)));
- buffer.write((byte) (0xFF & (codePoint >> 8)));
- buffer.write((byte) (0xFF & codePoint));
+ stream.write((byte) (0xFF & (codePoint >> 16)));
+ stream.write((byte) (0xFF & (codePoint >> 8)));
+ stream.write((byte) (0xFF & codePoint));
}
+ written += charSize;
}
- buffer.write(FormatSpec.PTNODE_CHARACTERS_TERMINATOR);
+ stream.write(FormatSpec.PTNODE_CHARACTERS_TERMINATOR);
+ written += FormatSpec.PTNODE_TERMINATOR_SIZE;
+ return written;
+ }
+
+ /**
+ * Writes an array of code points with our character format to an OutputStream.
+ *
+ * This will also write the terminator byte.
+ *
+ * @param stream the OutputStream to write to.
+ * @param codePoints the array of code points
+ * @return the size written, in bytes.
+ */
+ // TODO: Merge this method with writeCharArray and rename the various write* methods to
+ // make the difference clear.
+ static int writeCodePoints(final OutputStream stream, final int[] codePoints,
+ final int startIndex, final int endIndex)
+ throws IOException {
+ int written = 0;
+ for (int i = startIndex; i < endIndex; ++i) {
+ final int codePoint = codePoints[i];
+ final int charSize = getCharSize(codePoint);
+ if (1 == charSize) {
+ stream.write((byte) codePoint);
+ } else {
+ stream.write((byte) (0xFF & (codePoint >> 16)));
+ stream.write((byte) (0xFF & (codePoint >> 8)));
+ stream.write((byte) (0xFF & codePoint));
+ }
+ written += charSize;
+ }
+ if (endIndex - startIndex > 1) {
+ stream.write(FormatSpec.PTNODE_CHARACTERS_TERMINATOR);
+ written += FormatSpec.PTNODE_TERMINATOR_SIZE;
+ }
+ return written;
}
/**
@@ -556,7 +600,7 @@ public final class BinaryDictDecoderUtils {
Map<Integer, PtNodeArray> reverseNodeArrayMapping = new TreeMap<Integer, PtNodeArray>();
Map<Integer, PtNode> reversePtNodeMapping = new TreeMap<Integer, PtNode>();
- final PtNodeArray root = readNodeArray(dictDecoder, fileHeader.mHeaderSize,
+ final PtNodeArray root = readNodeArray(dictDecoder, fileHeader.mBodyOffset,
reverseNodeArrayMapping, reversePtNodeMapping, fileHeader.mFormatOptions);
FusionDictionary newDict = new FusionDictionary(root, fileHeader.mDictionaryOptions);
@@ -592,32 +636,10 @@ public final class BinaryDictDecoderUtils {
/**
* Basic test to find out whether the file is a binary dictionary or not.
*
- * Concretely this only tests the magic number.
- *
* @param file The file to test.
* @return true if it's a binary dictionary, false otherwise
*/
public static boolean isBinaryDictionary(final File file) {
- FileInputStream inStream = null;
- try {
- inStream = new FileInputStream(file);
- final ByteBuffer buffer = inStream.getChannel().map(
- FileChannel.MapMode.READ_ONLY, 0, file.length());
- final int version = getFormatVersion(new ByteBufferDictBuffer(buffer));
- return (version >= FormatSpec.MINIMUM_SUPPORTED_VERSION
- && version <= FormatSpec.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
- }
- }
- }
+ return FormatSpec.getDictDecoder(file).hasValidRawBinaryDictionary();
}
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
index f761829de..c0dad3db2 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
@@ -17,6 +17,7 @@
package com.android.inputmethod.latin.makedict;
import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding;
+import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer;
import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions;
@@ -245,6 +246,26 @@ public class BinaryDictEncoderUtils {
}
}
+ static void writeUIntToDictBuffer(final DictBuffer dictBuffer, final int value,
+ final int size) {
+ switch(size) {
+ case 4:
+ dictBuffer.put((byte) ((value >> 24) & 0xFF));
+ /* fall through */
+ case 3:
+ dictBuffer.put((byte) ((value >> 16) & 0xFF));
+ /* fall through */
+ case 2:
+ dictBuffer.put((byte) ((value >> 8) & 0xFF));
+ /* fall through */
+ case 1:
+ dictBuffer.put((byte) (value & 0xFF));
+ break;
+ default:
+ /* nop */
+ }
+ }
+
// End utility methods
// This method is responsible for finding a nice ordering of the nodes that favors run-time
@@ -690,6 +711,13 @@ public class BinaryDictEncoderUtils {
+ word + " is " + unigramFrequency);
bigramFrequency = unigramFrequency;
}
+ bigramFlags += getBigramFrequencyDiff(unigramFrequency, bigramFrequency)
+ & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY;
+ return bigramFlags;
+ }
+
+ public static int getBigramFrequencyDiff(final int unigramFrequency,
+ final int bigramFrequency) {
// We compute the difference between 255 (which means probability = 1) and the
// unigram score. We split this into a number of discrete steps.
// Now, the steps are numbered 0~15; 0 represents an increase of 1 step while 15
@@ -723,9 +751,7 @@ public class BinaryDictEncoderUtils {
// include this bigram in the dictionary. For now, register as 0, and live with the
// small over-estimation that we get in this case. TODO: actually remove this bigram
// if discretizedFrequency < 0.
- final int finalBigramFrequency = discretizedFrequency > 0 ? discretizedFrequency : 0;
- bigramFlags += finalBigramFrequency & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY;
- return bigramFlags;
+ return discretizedFrequency > 0 ? discretizedFrequency : 0;
}
/**
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
index d5516ef46..86ebf5844 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
@@ -62,7 +62,7 @@ public final class BinaryDictIOUtils {
* Retrieves all node arrays without recursive call.
*/
private static void readUnigramsAndBigramsBinaryInner(final DictDecoder dictDecoder,
- final int headerSize, final Map<Integer, String> words,
+ final int bodyOffset, final Map<Integer, String> words,
final Map<Integer, Integer> frequencies,
final Map<Integer, ArrayList<PendingAttribute>> bigrams,
final FormatOptions formatOptions) {
@@ -71,7 +71,7 @@ public final class BinaryDictIOUtils {
Stack<Position> stack = new Stack<Position>();
int index = 0;
- Position initPos = new Position(headerSize, 0);
+ Position initPos = new Position(bodyOffset, 0);
stack.push(initPos);
while (!stack.empty()) {
@@ -154,7 +154,7 @@ public final class BinaryDictIOUtils {
UnsupportedFormatException {
// Read header
final FileHeader header = dictDecoder.readHeader();
- readUnigramsAndBigramsBinaryInner(dictDecoder, header.mHeaderSize, words,
+ readUnigramsAndBigramsBinaryInner(dictDecoder, header.mBodyOffset, words,
frequencies, bigrams, header.mFormatOptions);
}
@@ -245,8 +245,7 @@ public final class BinaryDictIOUtils {
/**
* @return the size written, in bytes. Always 3 bytes.
*/
- static int writeSInt24ToBuffer(final DictBuffer dictBuffer,
- final int value) {
+ static int writeSInt24ToBuffer(final DictBuffer dictBuffer, final int value) {
final int absValue = Math.abs(value);
dictBuffer.put((byte)(((value < 0 ? 0x80 : 0) | (absValue >> 16)) & 0xFF));
dictBuffer.put((byte)((absValue >> 8) & 0xFF));
@@ -301,35 +300,6 @@ public final class BinaryDictIOUtils {
}
/**
- * Write a string to a stream.
- *
- * @param destination the stream to write.
- * @param word the string to be written.
- * @return the size written, in bytes.
- * @throws IOException
- */
- private static int writeString(final OutputStream destination, final String word)
- throws IOException {
- int size = 0;
- final int length = word.length();
- for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
- final int codePoint = word.codePointAt(i);
- if (CharEncoding.getCharSize(codePoint) == 1) {
- destination.write((byte)codePoint);
- size++;
- } else {
- destination.write((byte)(0xFF & (codePoint >> 16)));
- destination.write((byte)(0xFF & (codePoint >> 8)));
- destination.write((byte)(0xFF & codePoint));
- size += 3;
- }
- }
- destination.write((byte)FormatSpec.PTNODE_CHARACTERS_TERMINATOR);
- size += FormatSpec.PTNODE_TERMINATOR_SIZE;
- return size;
- }
-
- /**
* Write a PtNode to an output stream from a PtNodeInfo.
* A PtNode is an in-memory representation of a node in the patricia trie.
* A PtNode info is a container for low-level information about how the
@@ -387,7 +357,7 @@ public final class BinaryDictIOUtils {
destination.write((byte)BinaryDictEncoderUtils.makeShortcutFlags(
shortcutIterator.hasNext(), target.mFrequency));
size++;
- size += writeString(destination, target.mWord);
+ size += CharEncoding.writeString(destination, target.mWord);
}
}
@@ -445,6 +415,27 @@ public final class BinaryDictIOUtils {
}
/**
+ * Writes a PtNodeCount to the stream.
+ *
+ * @param destination the stream to write.
+ * @param ptNodeCount the count.
+ * @return the size written in bytes.
+ */
+ static int writePtNodeCount(final OutputStream destination, final int ptNodeCount)
+ throws IOException {
+ final int countSize = BinaryDictIOUtils.getPtNodeCountSize(ptNodeCount);
+ // the count must fit on one byte or two bytes.
+ // Please see comments in FormatSpec.
+ if (countSize != 1 && countSize != 2) {
+ throw new RuntimeException("Strange size from getPtNodeCountSize : " + countSize);
+ }
+ final int encodedPtNodeCount = (countSize == 2) ?
+ (ptNodeCount | FormatSpec.LARGE_PTNODE_ARRAY_SIZE_FIELD_SIZE_FLAG) : ptNodeCount;
+ BinaryDictEncoderUtils.writeUIntToStream(destination, encodedPtNodeCount, countSize);
+ return countSize;
+ }
+
+ /**
* Write a node array to the stream.
*
* @param destination the stream to write.
@@ -454,20 +445,7 @@ public final class BinaryDictIOUtils {
*/
static int writeNodes(final OutputStream destination, final PtNodeInfo[] infos)
throws IOException {
- int size = getPtNodeCountSize(infos.length);
- switch (getPtNodeCountSize(infos.length)) {
- case 1:
- destination.write((byte)infos.length);
- break;
- case 2:
- final int encodedPtNodeCount =
- infos.length | FormatSpec.LARGE_PTNODE_ARRAY_SIZE_FIELD_SIZE_FLAG;
- destination.write((byte)(encodedPtNodeCount >> 8));
- destination.write((byte)(encodedPtNodeCount & 0xFF));
- break;
- default:
- throw new RuntimeException("Invalid node count size.");
- }
+ int size = writePtNodeCount(destination, infos.length);
for (final PtNodeInfo info : infos) size += writePtNode(destination, info);
writeSInt24ToStream(destination, FormatSpec.NO_FORWARD_LINK_ADDRESS);
return size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
diff --git a/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java
index 3dbeee099..b4838f00f 100644
--- a/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java
@@ -35,6 +35,7 @@ import java.util.TreeMap;
/**
* An interface of binary dictionary decoders.
*/
+// TODO: Straighten out responsibility for the buffer's file pointer.
public interface DictDecoder {
/**
@@ -43,7 +44,7 @@ public interface DictDecoder {
public FileHeader readHeader() throws IOException, UnsupportedFormatException;
/**
- * Reads PtNode from nodeAddress.
+ * Reads PtNode from ptNodePos.
* @param ptNodePos the position of PtNode.
* @param formatOptions the format options.
* @return PtNodeInfo.
@@ -127,7 +128,8 @@ public interface DictDecoder {
* Opens the dictionary file and makes DictBuffer.
*/
@UsedForTesting
- public void openDictBuffer() throws FileNotFoundException, IOException;
+ public void openDictBuffer() throws FileNotFoundException, IOException,
+ UnsupportedFormatException;
@UsedForTesting
public boolean isDictBufferOpen();
@@ -228,4 +230,9 @@ public interface DictDecoder {
}
public void skipPtNode(final FormatOptions formatOptions);
+
+ /**
+ * @return whether this decoder has a valid binary dictionary that it can decode.
+ */
+ public boolean hasValidRawBinaryDictionary();
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java
index 28da9ffdd..971b4ff9f 100644
--- a/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java
@@ -37,7 +37,7 @@ import java.util.Arrays;
@UsedForTesting
public final class DynamicBinaryDictIOUtils {
private static final boolean DBG = false;
- private static final int MAX_JUMPS = 10000;
+ static final int MAX_JUMPS = 10000;
private DynamicBinaryDictIOUtils() {
// This utility class is not publicly instantiable.
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index b56234f6d..846aacf11 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -199,13 +199,19 @@ public final class FormatSpec {
*/
public static final int MAGIC_NUMBER = 0x9BC13AFE;
- static final int MINIMUM_SUPPORTED_VERSION = 2;
- static final int MAXIMUM_SUPPORTED_VERSION = 4;
static final int NOT_A_VERSION_NUMBER = -1;
static final int FIRST_VERSION_WITH_DYNAMIC_UPDATE = 3;
static final int FIRST_VERSION_WITH_TERMINAL_ID = 4;
- static final int VERSION3 = 3;
- static final int VERSION4 = 4;
+
+ // These MUST have the same values as the relevant constants in format_utils.h.
+ // From version 4 on, we use version * 100 + revision as a version number. That allows
+ // us to change the format during development while having testing devices remove
+ // older files with each upgrade, while still having a readable versioning scheme.
+ public static final int VERSION2 = 2;
+ public static final int VERSION3 = 3;
+ public static final int VERSION4 = 400;
+ static final int MINIMUM_SUPPORTED_VERSION = VERSION2;
+ static final int MAXIMUM_SUPPORTED_VERSION = VERSION4;
// These options need to be the same numeric values as the one in the native reading code.
static final int GERMAN_UMLAUT_PROCESSING_FLAG = 0x1;
@@ -263,8 +269,10 @@ public final class FormatSpec {
static final int PTNODE_ATTRIBUTE_MAX_ADDRESS_SIZE = 3;
static final int PTNODE_SHORTCUT_LIST_SIZE_SIZE = 2;
- // These values are used only by version 4 or later.
+ // These values are used only by version 4 or later. They MUST match the definitions in
+ // ver4_dict_constants.cpp.
static final String TRIE_FILE_EXTENSION = ".trie";
+ public static final String HEADER_FILE_EXTENSION = ".header";
static final String FREQ_FILE_EXTENSION = ".freq";
static final String UNIGRAM_TIMESTAMP_FILE_EXTENSION = ".timestamp";
// tat = Terminal Address Table
@@ -278,9 +286,9 @@ public final class FormatSpec {
static final int UNIGRAM_TIMESTAMP_SIZE = 4;
// With the English main dictionary as of October 2013, the size of bigram address table is
- // is 584KB with the block size being 4.
- // This is 91% of that of full address table.
- static final int BIGRAM_ADDRESS_TABLE_BLOCK_SIZE = 4;
+ // is 345KB with the block size being 16.
+ // This is 54% of that of full address table.
+ static final int BIGRAM_ADDRESS_TABLE_BLOCK_SIZE = 16;
static final int BIGRAM_CONTENT_COUNT = 2;
static final int BIGRAM_FREQ_CONTENT_INDEX = 0;
static final int BIGRAM_TIMESTAMP_CONTENT_INDEX = 1;
@@ -293,7 +301,7 @@ public final class FormatSpec {
static final int SHORTCUT_CONTENT_COUNT = 1;
static final int SHORTCUT_CONTENT_INDEX = 0;
// With the English main dictionary as of October 2013, the size of shortcut address table is
- // 29KB with the block size being 64.
+ // 26KB with the block size being 64.
// This is only 4.4% of that of full address table.
static final int SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE = 64;
static final String SHORTCUT_CONTENT_ID = "_shortcut";
@@ -361,13 +369,14 @@ public final class FormatSpec {
* Class representing file header.
*/
public static final class FileHeader {
- public final int mHeaderSize;
+ public final int mBodyOffset;
public final DictionaryOptions mDictionaryOptions;
public final FormatOptions mFormatOptions;
// Note that these are corresponding definitions in native code in latinime::HeaderPolicy
// and latinime::HeaderReadWriteUtils.
public static final String SUPPORTS_DYNAMIC_UPDATE_ATTRIBUTE = "SUPPORTS_DYNAMIC_UPDATE";
public static final String USES_FORGETTING_CURVE_ATTRIBUTE = "USES_FORGETTING_CURVE";
+ public static final String HAS_HISTORICAL_INFO_ATTRIBUTE = "HAS_HISTORICAL_INFO";
public static final String ATTRIBUTE_VALUE_TRUE = "1";
public static final String DICTIONARY_VERSION_ATTRIBUTE = "version";
@@ -376,9 +385,18 @@ public final class FormatSpec {
private static final String DICTIONARY_DESCRIPTION_ATTRIBUTE = "description";
public FileHeader(final int headerSize, final DictionaryOptions dictionaryOptions,
final FormatOptions formatOptions) {
- mHeaderSize = headerSize;
mDictionaryOptions = dictionaryOptions;
mFormatOptions = formatOptions;
+ mBodyOffset = formatOptions.mVersion < VERSION4 ? headerSize : 0;
+ if (null == getLocaleString()) {
+ throw new RuntimeException("Cannot create a FileHeader without a locale");
+ }
+ if (null == getVersion()) {
+ throw new RuntimeException("Cannot create a FileHeader without a version");
+ }
+ if (null == getId()) {
+ throw new RuntimeException("Cannot create a FileHeader without an ID");
+ }
}
// Helper method to get the locale as a String
diff --git a/java/src/com/android/inputmethod/latin/makedict/SparseTableContentReader.java b/java/src/com/android/inputmethod/latin/makedict/SparseTableContentReader.java
new file mode 100644
index 000000000..06088b651
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/SparseTableContentReader.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2013 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.makedict;
+
+import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer;
+import com.android.inputmethod.latin.makedict.DictDecoder.DictionaryBufferFactory;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * An auxiliary class for reading SparseTable and data written by SparseTableContentWriter.
+ */
+public class SparseTableContentReader {
+
+ /**
+ * An interface of a function which is passed to SparseTableContentReader.read.
+ */
+ public interface SparseTableContentReaderInterface {
+ /**
+ * Reads data.
+ *
+ * @param buffer the DictBuffer. The position of the buffer is set to the head of data.
+ */
+ public void read(final DictBuffer buffer);
+ }
+
+ protected final int mContentCount;
+ protected final int mBlockSize;
+ protected final File mBaseDir;
+ protected final File mLookupTableFile;
+ protected final File[] mAddressTableFiles;
+ protected final File[] mContentFiles;
+ protected DictBuffer mLookupTableBuffer;
+ protected final DictBuffer[] mAddressTableBuffers;
+ private final DictBuffer[] mContentBuffers;
+ protected final DictionaryBufferFactory mFactory;
+
+ /**
+ * Sole constructor of SparseTableContentReader.
+ *
+ * @param name the name of SparseTable.
+ * @param blockSize the block size of the content table.
+ * @param baseDir the directory which contains the files of the content table.
+ * @param contentFilenames the file names of content files.
+ * @param contentIds the ids of contents. These ids are used for a suffix of a name of
+ * address files and content files.
+ * @param factory the DictionaryBufferFactory which is used for opening the files.
+ */
+ public SparseTableContentReader(final String name, final int blockSize, final File baseDir,
+ final String[] contentFilenames, final String[] contentIds,
+ final DictionaryBufferFactory factory) {
+ if (contentFilenames.length != contentIds.length) {
+ throw new RuntimeException("The length of contentFilenames and the length of"
+ + " contentIds are different " + contentFilenames.length + ", "
+ + contentIds.length);
+ }
+ mBlockSize = blockSize;
+ mBaseDir = baseDir;
+ mFactory = factory;
+ mContentCount = contentFilenames.length;
+ mLookupTableFile = new File(baseDir, name + FormatSpec.LOOKUP_TABLE_FILE_SUFFIX);
+ mAddressTableFiles = new File[mContentCount];
+ mContentFiles = new File[mContentCount];
+ for (int i = 0; i < mContentCount; ++i) {
+ mAddressTableFiles[i] = new File(mBaseDir,
+ name + FormatSpec.CONTENT_TABLE_FILE_SUFFIX + contentIds[i]);
+ mContentFiles[i] = new File(mBaseDir, contentFilenames[i] + contentIds[i]);
+ }
+ mAddressTableBuffers = new DictBuffer[mContentCount];
+ mContentBuffers = new DictBuffer[mContentCount];
+ }
+
+ public void openBuffers() throws FileNotFoundException, IOException {
+ mLookupTableBuffer = mFactory.getDictionaryBuffer(mLookupTableFile);
+ for (int i = 0; i < mContentCount; ++i) {
+ mAddressTableBuffers[i] = mFactory.getDictionaryBuffer(mAddressTableFiles[i]);
+ mContentBuffers[i] = mFactory.getDictionaryBuffer(mContentFiles[i]);
+ }
+ }
+
+ protected void read(final int contentIndex, final int index,
+ final SparseTableContentReaderInterface reader) {
+ if (index < 0 || (index / mBlockSize) * SparseTable.SIZE_OF_INT_IN_BYTES
+ >= mLookupTableBuffer.limit()) {
+ return;
+ }
+
+ mLookupTableBuffer.position((index / mBlockSize) * SparseTable.SIZE_OF_INT_IN_BYTES);
+ final int posInAddressTable = mLookupTableBuffer.readInt();
+ if (posInAddressTable == SparseTable.NOT_EXIST) {
+ return;
+ }
+
+ mAddressTableBuffers[contentIndex].position(
+ (posInAddressTable + index % mBlockSize) * SparseTable.SIZE_OF_INT_IN_BYTES);
+ final int address = mAddressTableBuffers[contentIndex].readInt();
+ if (address == SparseTable.NOT_EXIST) {
+ return;
+ }
+
+ mContentBuffers[contentIndex].position(address);
+ reader.read(mContentBuffers[contentIndex]);
+ }
+} \ No newline at end of file
diff --git a/java/src/com/android/inputmethod/latin/makedict/SparseTableContentUpdater.java b/java/src/com/android/inputmethod/latin/makedict/SparseTableContentUpdater.java
new file mode 100644
index 000000000..4518f21b9
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/SparseTableContentUpdater.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2013 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.makedict;
+
+import com.android.inputmethod.latin.makedict.DictDecoder.DictionaryBufferFactory;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * An auxiliary class for updating data associated with SparseTable.
+ */
+public class SparseTableContentUpdater extends SparseTableContentReader {
+ protected OutputStream mLookupTableOutStream;
+ protected OutputStream[] mAddressTableOutStreams;
+ protected OutputStream[] mContentOutStreams;
+
+ public SparseTableContentUpdater(final String name, final int blockSize,
+ final File baseDir, final String[] contentFilenames, final String[] contentIds,
+ final DictionaryBufferFactory factory) {
+ super(name, blockSize, baseDir, contentFilenames, contentIds, factory);
+ mAddressTableOutStreams = new OutputStream[mContentCount];
+ mContentOutStreams = new OutputStream[mContentCount];
+ }
+
+ protected void openStreamsAndBuffers() throws IOException {
+ openBuffers();
+ mLookupTableOutStream = new FileOutputStream(mLookupTableFile, true /* append */);
+ for (int i = 0; i < mContentCount; ++i) {
+ mAddressTableOutStreams[i] = new FileOutputStream(mAddressTableFiles[i],
+ true /* append */);
+ mContentOutStreams[i] = new FileOutputStream(mContentFiles[i], true /* append */);
+ }
+ }
+
+ /**
+ * Set the contentIndex-th elements of contentId-th table.
+ *
+ * @param contentId the id of the content table.
+ * @param contentIndex the index where to set the valie.
+ * @param value the value to set.
+ */
+ protected void setContentValue(final int contentId, final int contentIndex, final int value)
+ throws IOException {
+ if ((contentIndex / mBlockSize) * SparseTable.SIZE_OF_INT_IN_BYTES
+ >= mLookupTableBuffer.limit()) {
+ // Need to extend the lookup table
+ final int currentSize = mLookupTableBuffer.limit()
+ / SparseTable.SIZE_OF_INT_IN_BYTES;
+ final int target = contentIndex / mBlockSize + 1;
+ for (int i = currentSize; i < target; ++i) {
+ BinaryDictEncoderUtils.writeUIntToStream(mLookupTableOutStream,
+ SparseTable.NOT_EXIST, SparseTable.SIZE_OF_INT_IN_BYTES);
+ }
+ // We need to reopen the byte buffer of the lookup table because a MappedByteBuffer in
+ // Java isn't expanded automatically when the underlying file is expanded.
+ reopenLookupTable();
+ }
+
+ mLookupTableBuffer.position((contentIndex / mBlockSize) * SparseTable.SIZE_OF_INT_IN_BYTES);
+ int posInAddressTable = mLookupTableBuffer.readInt();
+ if (posInAddressTable == SparseTable.NOT_EXIST) {
+ // Need to extend the address table
+ mLookupTableBuffer.position(mLookupTableBuffer.position()
+ - SparseTable.SIZE_OF_INT_IN_BYTES);
+ posInAddressTable = mAddressTableBuffers[0].limit() / mBlockSize;
+ BinaryDictEncoderUtils.writeUIntToDictBuffer(mLookupTableBuffer,
+ posInAddressTable, SparseTable.SIZE_OF_INT_IN_BYTES);
+ for (int i = 0; i < mContentCount; ++i) {
+ for (int j = 0; j < mBlockSize; ++j) {
+ BinaryDictEncoderUtils.writeUIntToStream(mAddressTableOutStreams[i],
+ SparseTable.NOT_EXIST, SparseTable.SIZE_OF_INT_IN_BYTES);
+ }
+ }
+ // We need to reopen the byte buffers of the address tables because a MappedByteBuffer
+ // in Java isn't expanded automatically when the underlying file is expanded.
+ reopenAddressTables();
+ }
+ posInAddressTable += (contentIndex % mBlockSize) * SparseTable.SIZE_OF_INT_IN_BYTES;
+
+ mAddressTableBuffers[contentId].position(posInAddressTable);
+ BinaryDictEncoderUtils.writeUIntToDictBuffer(mAddressTableBuffers[contentId],
+ value, SparseTable.SIZE_OF_INT_IN_BYTES);
+ }
+
+ private void reopenLookupTable() throws IOException {
+ mLookupTableOutStream.flush();
+ mLookupTableBuffer = mFactory.getDictionaryBuffer(mLookupTableFile);
+ }
+
+ private void reopenAddressTables() throws IOException {
+ for (int i = 0; i < mContentCount; ++i) {
+ mAddressTableOutStreams[i].flush();
+ mAddressTableBuffers[i] = mFactory.getDictionaryBuffer(mAddressTableFiles[i]);
+ }
+ }
+
+ protected void close() throws IOException {
+ mLookupTableOutStream.close();
+ for (final OutputStream stream : mAddressTableOutStreams) {
+ stream.close();
+ }
+ for (final OutputStream stream : mContentOutStreams) {
+ stream.close();
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/makedict/SparseTableContentWriter.java b/java/src/com/android/inputmethod/latin/makedict/SparseTableContentWriter.java
new file mode 100644
index 000000000..49f0fd624
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/SparseTableContentWriter.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2013 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.makedict;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * An auxiliary class for writing data associated with SparseTable to files.
+ */
+public class SparseTableContentWriter {
+ public interface SparseTableContentWriterInterface {
+ public void write(final OutputStream outStream) throws IOException;
+ }
+
+ private final int mContentCount;
+ private final SparseTable mSparseTable;
+ private final File mLookupTableFile;
+ protected final File mBaseDir;
+ private final File[] mAddressTableFiles;
+ private final File[] mContentFiles;
+ protected final OutputStream[] mContentOutStreams;
+
+ /**
+ * Sole constructor of SparseTableContentWriter.
+ *
+ * @param name the name of SparseTable.
+ * @param initialCapacity the initial capacity of SparseTable.
+ * @param blockSize the block size of the content table.
+ * @param baseDir the directory which contains the files of the content table.
+ * @param contentFilenames the file names of content files.
+ * @param contentIds the ids of contents. These ids are used for a suffix of a name of address
+ * files and content files.
+ */
+ public SparseTableContentWriter(final String name, final int initialCapacity,
+ final int blockSize, final File baseDir, final String[] contentFilenames,
+ final String[] contentIds) {
+ if (contentFilenames.length != contentIds.length) {
+ throw new RuntimeException("The length of contentFilenames and the length of"
+ + " contentIds are different " + contentFilenames.length + ", "
+ + contentIds.length);
+ }
+ mContentCount = contentFilenames.length;
+ mSparseTable = new SparseTable(initialCapacity, blockSize, mContentCount);
+ mLookupTableFile = new File(baseDir, name + FormatSpec.LOOKUP_TABLE_FILE_SUFFIX);
+ mAddressTableFiles = new File[mContentCount];
+ mContentFiles = new File[mContentCount];
+ mBaseDir = baseDir;
+ for (int i = 0; i < mContentCount; ++i) {
+ mAddressTableFiles[i] = new File(mBaseDir,
+ name + FormatSpec.CONTENT_TABLE_FILE_SUFFIX + contentIds[i]);
+ mContentFiles[i] = new File(mBaseDir, contentFilenames[i] + contentIds[i]);
+ }
+ mContentOutStreams = new OutputStream[mContentCount];
+ }
+
+ public void openStreams() throws FileNotFoundException {
+ for (int i = 0; i < mContentCount; ++i) {
+ mContentOutStreams[i] = new FileOutputStream(mContentFiles[i]);
+ }
+ }
+
+ protected void write(final int contentIndex, final int index,
+ final SparseTableContentWriterInterface writer) throws IOException {
+ mSparseTable.set(contentIndex, index, (int) mContentFiles[contentIndex].length());
+ writer.write(mContentOutStreams[contentIndex]);
+ mContentOutStreams[contentIndex].flush();
+ }
+
+ public void closeStreams() throws IOException {
+ mSparseTable.writeToFiles(mLookupTableFile, mAddressTableFiles);
+ for (int i = 0; i < mContentCount; ++i) {
+ mContentOutStreams[i].close();
+ }
+ }
+} \ No newline at end of file
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java
index 734223ec2..07522b54b 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java
@@ -40,26 +40,52 @@ import java.util.Arrays;
public class Ver4DictDecoder extends AbstractDictDecoder {
private static final String TAG = Ver4DictDecoder.class.getSimpleName();
- private static final int FILETYPE_TRIE = 1;
- private static final int FILETYPE_FREQUENCY = 2;
- private static final int FILETYPE_TERMINAL_ADDRESS_TABLE = 3;
- private static final int FILETYPE_BIGRAM_FREQ = 4;
- private static final int FILETYPE_SHORTCUT = 5;
-
- private final File mDictDirectory;
- private final DictionaryBufferFactory mBufferFactory;
+ protected static final int FILETYPE_TRIE = 1;
+ protected static final int FILETYPE_FREQUENCY = 2;
+ protected static final int FILETYPE_TERMINAL_ADDRESS_TABLE = 3;
+ protected static final int FILETYPE_BIGRAM_FREQ = 4;
+ protected static final int FILETYPE_SHORTCUT = 5;
+ protected static final int FILETYPE_HEADER = 6;
+
+ protected final File mDictDirectory;
+ protected final DictionaryBufferFactory mBufferFactory;
protected DictBuffer mDictBuffer;
- private DictBuffer mFrequencyBuffer;
- private DictBuffer mTerminalAddressTableBuffer;
- private DictBuffer mBigramBuffer;
- private DictBuffer mShortcutBuffer;
- private SparseTable mBigramAddressTable;
- private SparseTable mShortcutAddressTable;
+ protected DictBuffer mHeaderBuffer;
+ protected DictBuffer mFrequencyBuffer;
+ protected DictBuffer mTerminalAddressTableBuffer;
+ private BigramContentReader mBigramReader;
+ private ShortcutContentReader mShortcutReader;
+
+ /**
+ * Raw PtNode info straight out of a trie file in version 4 dictionary.
+ */
+ protected static final class Ver4PtNodeInfo {
+ public final int mFlags;
+ public final int[] mCharacters;
+ public final int mTerminalId;
+ public final int mChildrenPos;
+ public final int mParentPos;
+ public final int mNodeSize;
+ public int mStartIndexOfCharacters;
+ public int mEndIndexOfCharacters; // exclusive
+
+ public Ver4PtNodeInfo(final int flags, final int[] characters, final int terminalId,
+ final int childrenPos, final int parentPos, final int nodeSize) {
+ mFlags = flags;
+ mCharacters = characters;
+ mTerminalId = terminalId;
+ mChildrenPos = childrenPos;
+ mParentPos = parentPos;
+ mNodeSize = nodeSize;
+ mStartIndexOfCharacters = 0;
+ mEndIndexOfCharacters = characters.length;
+ }
+ }
@UsedForTesting
/* package */ Ver4DictDecoder(final File dictDirectory, final int factoryFlag) {
mDictDirectory = dictDirectory;
- mDictBuffer = mFrequencyBuffer = null;
+ mDictBuffer = mHeaderBuffer = mFrequencyBuffer = null;
if ((factoryFlag & MASK_DICTBUFFER) == USE_READONLY_BYTEBUFFER) {
mBufferFactory = new DictionaryBufferFromReadOnlyByteBufferFactory();
@@ -76,13 +102,16 @@ public class Ver4DictDecoder extends AbstractDictDecoder {
/* package */ Ver4DictDecoder(final File dictDirectory, final DictionaryBufferFactory factory) {
mDictDirectory = dictDirectory;
mBufferFactory = factory;
- mDictBuffer = mFrequencyBuffer = null;
+ mDictBuffer = mHeaderBuffer = mFrequencyBuffer = null;
}
- private File getFile(final int fileType) {
+ protected File getFile(final int fileType) throws UnsupportedFormatException {
if (fileType == FILETYPE_TRIE) {
return new File(mDictDirectory,
mDictDirectory.getName() + FormatSpec.TRIE_FILE_EXTENSION);
+ } else if (fileType == FILETYPE_HEADER) {
+ return new File(mDictDirectory,
+ mDictDirectory.getName() + FormatSpec.HEADER_FILE_EXTENSION);
} else if (fileType == FILETYPE_FREQUENCY) {
return new File(mDictDirectory,
mDictDirectory.getName() + FormatSpec.FREQ_FILE_EXTENSION);
@@ -98,20 +127,27 @@ public class Ver4DictDecoder extends AbstractDictDecoder {
mDictDirectory.getName() + FormatSpec.SHORTCUT_FILE_EXTENSION
+ FormatSpec.SHORTCUT_CONTENT_ID);
} else {
- throw new RuntimeException("Unsupported kind of file : " + fileType);
+ throw new UnsupportedFormatException("Unsupported kind of file : " + fileType);
}
}
@Override
- public void openDictBuffer() throws FileNotFoundException, IOException {
+ public void openDictBuffer() throws FileNotFoundException, IOException,
+ UnsupportedFormatException {
+ if (!mDictDirectory.isDirectory()) {
+ throw new UnsupportedFormatException("Format 4 dictionary needs a directory");
+ }
+ mHeaderBuffer = mBufferFactory.getDictionaryBuffer(getFile(FILETYPE_HEADER));
mDictBuffer = mBufferFactory.getDictionaryBuffer(getFile(FILETYPE_TRIE));
mFrequencyBuffer = mBufferFactory.getDictionaryBuffer(getFile(FILETYPE_FREQUENCY));
mTerminalAddressTableBuffer = mBufferFactory.getDictionaryBuffer(
getFile(FILETYPE_TERMINAL_ADDRESS_TABLE));
- mBigramBuffer = mBufferFactory.getDictionaryBuffer(getFile(FILETYPE_BIGRAM_FREQ));
- loadBigramAddressSparseTable();
- mShortcutBuffer = mBufferFactory.getDictionaryBuffer(getFile(FILETYPE_SHORTCUT));
- loadShortcutAddressSparseTable();
+ mBigramReader = new BigramContentReader(mDictDirectory.getName(),
+ mDictDirectory, mBufferFactory, false);
+ mBigramReader.openBuffers();
+ mShortcutReader = new ShortcutContentReader(mDictDirectory.getName(), mDictDirectory,
+ mBufferFactory);
+ mShortcutReader.openBuffers();
}
@Override
@@ -119,46 +155,137 @@ public class Ver4DictDecoder extends AbstractDictDecoder {
return mDictBuffer != null;
}
+ @UsedForTesting
+ /* package */ DictBuffer getHeaderBuffer() {
+ return mHeaderBuffer;
+ }
+
+ @UsedForTesting
/* package */ DictBuffer getDictBuffer() {
return mDictBuffer;
}
@Override
public FileHeader readHeader() throws IOException, UnsupportedFormatException {
- if (mDictBuffer == null) {
+ if (mHeaderBuffer == null) {
openDictBuffer();
}
- final FileHeader header = super.readHeader(mDictBuffer);
+ mHeaderBuffer.position(0);
+ final FileHeader header = super.readHeader(mHeaderBuffer);
final int version = header.mFormatOptions.mVersion;
- if (version != 4) {
+ if (version != FormatSpec.VERSION4) {
throw new UnsupportedFormatException("File header has a wrong version : " + version);
}
return header;
}
- private void loadBigramAddressSparseTable() throws IOException {
- final File lookupIndexFile = new File(mDictDirectory, mDictDirectory.getName()
- + FormatSpec.BIGRAM_FILE_EXTENSION + FormatSpec.LOOKUP_TABLE_FILE_SUFFIX);
- final File freqsFile = new File(mDictDirectory, mDictDirectory.getName()
- + FormatSpec.BIGRAM_FILE_EXTENSION + FormatSpec.CONTENT_TABLE_FILE_SUFFIX
- + FormatSpec.BIGRAM_FREQ_CONTENT_ID);
- mBigramAddressTable = SparseTable.readFromFiles(lookupIndexFile, new File[] { freqsFile },
- FormatSpec.BIGRAM_ADDRESS_TABLE_BLOCK_SIZE);
+ /**
+ * An auxiliary class for reading bigrams.
+ */
+ protected static class BigramContentReader extends SparseTableContentReader {
+ private final boolean mHasTimestamp;
+
+ public BigramContentReader(final String name, final File baseDir,
+ final DictionaryBufferFactory factory, final boolean hasTimestamp) {
+ super(name + FormatSpec.BIGRAM_FILE_EXTENSION,
+ FormatSpec.BIGRAM_ADDRESS_TABLE_BLOCK_SIZE, baseDir,
+ getContentFilenames(name, hasTimestamp), getContentIds(hasTimestamp), factory);
+ mHasTimestamp = hasTimestamp;
+ }
+
+ // TODO: Consolidate this method and BigramContentWriter.getContentFilenames.
+ protected static String[] getContentFilenames(final String name,
+ final boolean hasTimestamp) {
+ final String[] contentFilenames;
+ if (hasTimestamp) {
+ contentFilenames = new String[] { name + FormatSpec.BIGRAM_FILE_EXTENSION,
+ name + FormatSpec.BIGRAM_FILE_EXTENSION };
+ } else {
+ contentFilenames = new String[] { name + FormatSpec.BIGRAM_FILE_EXTENSION };
+ }
+ return contentFilenames;
+ }
+
+ // TODO: Consolidate this method and BigramContentWriter.getContentIds.
+ protected static String[] getContentIds(final boolean hasTimestamp) {
+ final String[] contentIds;
+ if (hasTimestamp) {
+ contentIds = new String[] { FormatSpec.BIGRAM_FREQ_CONTENT_ID,
+ FormatSpec.BIGRAM_TIMESTAMP_CONTENT_ID };
+ } else {
+ contentIds = new String[] { FormatSpec.BIGRAM_FREQ_CONTENT_ID };
+ }
+ return contentIds;
+ }
+
+ public ArrayList<PendingAttribute> readTargetsAndFrequencies(final int terminalId,
+ final DictBuffer terminalAddressTableBuffer) {
+ final ArrayList<PendingAttribute> bigrams = CollectionUtils.newArrayList();
+ read(FormatSpec.BIGRAM_FREQ_CONTENT_INDEX, terminalId,
+ new SparseTableContentReaderInterface() {
+ @Override
+ public void read(final DictBuffer buffer) {
+ while (bigrams.size() < FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
+ // If bigrams.size() reaches FormatSpec.MAX_BIGRAMS_IN_A_PTNODE,
+ // remaining bigram entries are ignored.
+ final int bigramFlags = buffer.readUnsignedByte();
+ final int targetTerminalId = buffer.readUnsignedInt24();
+ terminalAddressTableBuffer.position(targetTerminalId
+ * FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE);
+ final int targetAddress =
+ terminalAddressTableBuffer.readUnsignedInt24();
+ bigrams.add(new PendingAttribute(bigramFlags
+ & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY,
+ targetAddress));
+ if (0 == (bigramFlags
+ & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) {
+ break;
+ }
+ }
+ if (bigrams.size() >= FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
+ throw new RuntimeException("Too many bigrams in a PtNode ("
+ + bigrams.size() + " but max is "
+ + FormatSpec.MAX_BIGRAMS_IN_A_PTNODE + ")");
+ }
+ }
+ });
+ if (bigrams.isEmpty()) return null;
+ return bigrams;
+ }
}
- // TODO: Let's have something like SparseTableContentsReader in this class.
- private void loadShortcutAddressSparseTable() throws IOException {
- final File lookupIndexFile = new File(mDictDirectory, mDictDirectory.getName()
- + FormatSpec.SHORTCUT_FILE_EXTENSION + FormatSpec.LOOKUP_TABLE_FILE_SUFFIX);
- final File contentFile = new File(mDictDirectory, mDictDirectory.getName()
- + FormatSpec.SHORTCUT_FILE_EXTENSION + FormatSpec.CONTENT_TABLE_FILE_SUFFIX
- + FormatSpec.SHORTCUT_CONTENT_ID);
- final File timestampsFile = new File(mDictDirectory, mDictDirectory.getName()
- + FormatSpec.SHORTCUT_FILE_EXTENSION + FormatSpec.CONTENT_TABLE_FILE_SUFFIX
- + FormatSpec.SHORTCUT_CONTENT_ID);
- mShortcutAddressTable = SparseTable.readFromFiles(lookupIndexFile,
- new File[] { contentFile, timestampsFile },
- FormatSpec.SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE);
+ /**
+ * An auxiliary class for reading shortcuts.
+ */
+ protected static class ShortcutContentReader extends SparseTableContentReader {
+ public ShortcutContentReader(final String name, final File baseDir,
+ final DictionaryBufferFactory factory) {
+ super(name + FormatSpec.SHORTCUT_FILE_EXTENSION,
+ FormatSpec.SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE, baseDir,
+ new String[] { name + FormatSpec.SHORTCUT_FILE_EXTENSION },
+ new String[] { FormatSpec.SHORTCUT_CONTENT_ID }, factory);
+ }
+
+ public ArrayList<WeightedString> readShortcuts(final int terminalId) {
+ final ArrayList<WeightedString> shortcuts = CollectionUtils.newArrayList();
+ read(FormatSpec.SHORTCUT_CONTENT_INDEX, terminalId,
+ new SparseTableContentReaderInterface() {
+ @Override
+ public void read(final DictBuffer buffer) {
+ while (true) {
+ final int flags = buffer.readUnsignedByte();
+ final String word = CharEncoding.readString(buffer);
+ shortcuts.add(new WeightedString(word,
+ flags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY));
+ if (0 == (flags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) {
+ break;
+ }
+ }
+ }
+ });
+ if (shortcuts.isEmpty()) return null;
+ return shortcuts;
+ }
}
protected static class PtNodeReader extends AbstractDictDecoder.PtNodeReader {
@@ -172,102 +299,82 @@ public class Ver4DictDecoder extends AbstractDictDecoder {
}
}
- private ArrayList<WeightedString> readShortcuts(final int terminalId) {
- if (mShortcutAddressTable.get(0, terminalId) == SparseTable.NOT_EXIST) return null;
-
- final ArrayList<WeightedString> ret = CollectionUtils.newArrayList();
- final int posOfShortcuts = mShortcutAddressTable.get(FormatSpec.SHORTCUT_CONTENT_INDEX,
- terminalId);
- mShortcutBuffer.position(posOfShortcuts);
- while (true) {
- final int flags = mShortcutBuffer.readUnsignedByte();
- final String word = CharEncoding.readString(mShortcutBuffer);
- ret.add(new WeightedString(word,
- flags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY));
- if (0 == (flags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) break;
- }
- return ret;
- }
+ private final int[] mCharacterBufferForReadingVer4PtNodeInfo
+ = new int[FormatSpec.MAX_WORD_LENGTH];
+ /**
+ * Reads PtNode from ptNodePos in the trie file and returns Ver4PtNodeInfo.
+ *
+ * @param ptNodePos the position of PtNode.
+ * @param options the format options.
+ * @return Ver4PtNodeInfo.
+ */
// TODO: Make this buffer thread safe.
// TODO: Support words longer than FormatSpec.MAX_WORD_LENGTH.
- private final int[] mCharacterBuffer = new int[FormatSpec.MAX_WORD_LENGTH];
- @Override
- public PtNodeInfo readPtNode(int ptNodePos, FormatOptions options) {
- int addressPointer = ptNodePos;
+ protected Ver4PtNodeInfo readVer4PtNodeInfo(final int ptNodePos, final FormatOptions options) {
+ int readingPos = ptNodePos;
final int flags = PtNodeReader.readPtNodeOptionFlags(mDictBuffer);
- addressPointer += FormatSpec.PTNODE_FLAGS_SIZE;
+ readingPos += FormatSpec.PTNODE_FLAGS_SIZE;
- final int parentAddress = PtNodeReader.readParentAddress(mDictBuffer, options);
+ final int parentPos = PtNodeReader.readParentAddress(mDictBuffer, options);
if (BinaryDictIOUtils.supportsDynamicUpdate(options)) {
- addressPointer += FormatSpec.PARENT_ADDRESS_SIZE;
+ readingPos += FormatSpec.PARENT_ADDRESS_SIZE;
}
final int characters[];
if (0 != (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS)) {
int index = 0;
int character = CharEncoding.readChar(mDictBuffer);
- addressPointer += CharEncoding.getCharSize(character);
+ readingPos += CharEncoding.getCharSize(character);
while (FormatSpec.INVALID_CHARACTER != character
&& index < FormatSpec.MAX_WORD_LENGTH) {
- mCharacterBuffer[index++] = character;
+ mCharacterBufferForReadingVer4PtNodeInfo[index++] = character;
character = CharEncoding.readChar(mDictBuffer);
- addressPointer += CharEncoding.getCharSize(character);
+ readingPos += CharEncoding.getCharSize(character);
}
- characters = Arrays.copyOfRange(mCharacterBuffer, 0, index);
+ characters = Arrays.copyOfRange(mCharacterBufferForReadingVer4PtNodeInfo, 0, index);
} else {
final int character = CharEncoding.readChar(mDictBuffer);
- addressPointer += CharEncoding.getCharSize(character);
+ readingPos += CharEncoding.getCharSize(character);
characters = new int[] { character };
}
final int terminalId;
if (0 != (FormatSpec.FLAG_IS_TERMINAL & flags)) {
terminalId = PtNodeReader.readTerminalId(mDictBuffer);
- addressPointer += FormatSpec.PTNODE_TERMINAL_ID_SIZE;
+ readingPos += FormatSpec.PTNODE_TERMINAL_ID_SIZE;
} else {
terminalId = PtNode.NOT_A_TERMINAL;
}
+ int childrenPos = PtNodeReader.readChildrenAddress(mDictBuffer, flags, options);
+ if (childrenPos != FormatSpec.NO_CHILDREN_ADDRESS) {
+ childrenPos += readingPos;
+ }
+ readingPos += BinaryDictIOUtils.getChildrenAddressSize(flags, options);
+
+ return new Ver4PtNodeInfo(flags, characters, terminalId, childrenPos, parentPos,
+ readingPos - ptNodePos);
+ }
+
+ @Override
+ public PtNodeInfo readPtNode(int ptNodePos, FormatOptions options) {
+ final Ver4PtNodeInfo nodeInfo = readVer4PtNodeInfo(ptNodePos, options);
+
final int frequency;
- if (0 != (FormatSpec.FLAG_IS_TERMINAL & flags)) {
- frequency = PtNodeReader.readFrequency(mFrequencyBuffer, terminalId);
+ if (0 != (FormatSpec.FLAG_IS_TERMINAL & nodeInfo.mFlags)) {
+ frequency = PtNodeReader.readFrequency(mFrequencyBuffer, nodeInfo.mTerminalId);
} else {
frequency = PtNode.NOT_A_TERMINAL;
}
- int childrenAddress = PtNodeReader.readChildrenAddress(mDictBuffer, flags, options);
- if (childrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) {
- childrenAddress += addressPointer;
- }
- addressPointer += BinaryDictIOUtils.getChildrenAddressSize(flags, options);
- final ArrayList<WeightedString> shortcutTargets = readShortcuts(terminalId);
-
- final ArrayList<PendingAttribute> bigrams;
- if (0 != (flags & FormatSpec.FLAG_HAS_BIGRAMS)) {
- bigrams = new ArrayList<PendingAttribute>();
- final int posOfBigrams = mBigramAddressTable.get(0 /* contentTableIndex */, terminalId);
- mBigramBuffer.position(posOfBigrams);
- while (bigrams.size() < FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
- // If bigrams.size() reaches FormatSpec.MAX_BIGRAMS_IN_A_PTNODE,
- // remaining bigram entries are ignored.
- final int bigramFlags = mBigramBuffer.readUnsignedByte();
- final int targetTerminalId = mBigramBuffer.readUnsignedInt24();
- mTerminalAddressTableBuffer.position(
- targetTerminalId * FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE);
- final int targetAddress = mTerminalAddressTableBuffer.readUnsignedInt24();
- bigrams.add(new PendingAttribute(
- bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY,
- targetAddress));
- if (0 == (bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) break;
- }
- if (bigrams.size() >= FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
- throw new RuntimeException("Too many bigrams in a PtNode (" + bigrams.size()
- + " but max is " + FormatSpec.MAX_BIGRAMS_IN_A_PTNODE + ")");
- }
- } else {
- bigrams = null;
- }
- return new PtNodeInfo(ptNodePos, addressPointer, flags, characters, frequency,
- parentAddress, childrenAddress, shortcutTargets, bigrams);
+
+ final ArrayList<WeightedString> shortcutTargets = mShortcutReader.readShortcuts(
+ nodeInfo.mTerminalId);
+ final ArrayList<PendingAttribute> bigrams = mBigramReader.readTargetsAndFrequencies(
+ nodeInfo.mTerminalId, mTerminalAddressTableBuffer);
+
+ return new PtNodeInfo(ptNodePos, ptNodePos + nodeInfo.mNodeSize, nodeInfo.mFlags,
+ nodeInfo.mCharacters, frequency, nodeInfo.mParentPos, nodeInfo.mChildrenPos,
+ shortcutTargets, bigrams);
}
private void deleteDictFiles() {
@@ -318,10 +425,14 @@ public class Ver4DictDecoder extends AbstractDictDecoder {
@Override
public boolean readAndFollowForwardLink() {
- final int nextAddress = mDictBuffer.readUnsignedInt24();
- if (nextAddress >= 0 && nextAddress < mDictBuffer.limit()) {
- mDictBuffer.position(nextAddress);
- return true;
+ final int forwardLinkPos = mDictBuffer.position();
+ int nextRelativePos = BinaryDictDecoderUtils.readSInt24(mDictBuffer);
+ if (nextRelativePos != FormatSpec.NO_FORWARD_LINK_ADDRESS) {
+ final int nextPos = forwardLinkPos + nextRelativePos;
+ if (nextPos >= 0 && nextPos < mDictBuffer.limit()) {
+ mDictBuffer.position(nextPos);
+ return true;
+ }
}
return false;
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
index 8d5b48a9b..1a245b6db 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
@@ -25,6 +25,8 @@ import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions
import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.FileUtils;
import java.io.File;
import java.io.FileNotFoundException;
@@ -32,6 +34,8 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.Iterator;
/**
@@ -42,8 +46,8 @@ public class Ver4DictEncoder implements DictEncoder {
private final File mDictPlacedDir;
private byte[] mTrieBuf;
private int mTriePos;
- private int mHeaderSize;
private OutputStream mTrieOutStream;
+ private OutputStream mHeaderOutStream;
private OutputStream mFreqOutStream;
private OutputStream mUnigramTimestampOutStream;
private OutputStream mTerminalAddressTableOutStream;
@@ -57,62 +61,6 @@ public class Ver4DictEncoder implements DictEncoder {
mDictPlacedDir = dictPlacedDir;
}
- private interface SparseTableContentWriterInterface {
- public void write(final OutputStream outStream) throws IOException;
- }
-
- private static class SparseTableContentWriter {
- private final int mContentCount;
- private final SparseTable mSparseTable;
- private final File mLookupTableFile;
- protected final File mBaseDir;
- private final File[] mAddressTableFiles;
- private final File[] mContentFiles;
- protected final OutputStream[] mContentOutStreams;
-
- public SparseTableContentWriter(final String name, final int initialCapacity,
- final int blockSize, final File baseDir, final String[] contentFilenames,
- final String[] contentIds) {
- if (contentFilenames.length != contentIds.length) {
- throw new RuntimeException("The length of contentFilenames and the length of"
- + " contentIds are different " + contentFilenames.length + ", "
- + contentIds.length);
- }
- mContentCount = contentFilenames.length;
- mSparseTable = new SparseTable(initialCapacity, blockSize, mContentCount);
- mLookupTableFile = new File(baseDir, name + FormatSpec.LOOKUP_TABLE_FILE_SUFFIX);
- mAddressTableFiles = new File[mContentCount];
- mContentFiles = new File[mContentCount];
- mBaseDir = baseDir;
- for (int i = 0; i < mContentCount; ++i) {
- mAddressTableFiles[i] = new File(mBaseDir,
- name + FormatSpec.CONTENT_TABLE_FILE_SUFFIX + contentIds[i]);
- mContentFiles[i] = new File(mBaseDir, contentFilenames[i] + contentIds[i]);
- }
- mContentOutStreams = new OutputStream[mContentCount];
- }
-
- public void openStreams() throws FileNotFoundException {
- for (int i = 0; i < mContentCount; ++i) {
- mContentOutStreams[i] = new FileOutputStream(mContentFiles[i]);
- }
- }
-
- protected void write(final int contentIndex, final int index,
- final SparseTableContentWriterInterface writer) throws IOException {
- mSparseTable.set(contentIndex, index, (int) mContentFiles[contentIndex].length());
- writer.write(mContentOutStreams[contentIndex]);
- mContentOutStreams[contentIndex].flush();
- }
-
- public void closeStreams() throws IOException {
- mSparseTable.writeToFiles(mLookupTableFile, mAddressTableFiles);
- for (int i = 0; i < mContentCount; ++i) {
- mContentOutStreams[i].close();
- }
- }
- }
-
private static class BigramContentWriter extends SparseTableContentWriter {
private final boolean mWriteTimestamp;
@@ -238,16 +186,21 @@ public class Ver4DictEncoder implements DictEncoder {
mBaseFilename = header.getId() + "." + header.getVersion();
mDictDir = new File(mDictPlacedDir, mBaseFilename);
final File trieFile = new File(mDictDir, mBaseFilename + FormatSpec.TRIE_FILE_EXTENSION);
+ final File headerFile = new File(mDictDir,
+ mBaseFilename + FormatSpec.HEADER_FILE_EXTENSION);
final File freqFile = new File(mDictDir, mBaseFilename + FormatSpec.FREQ_FILE_EXTENSION);
final File timestampFile = new File(mDictDir,
mBaseFilename + FormatSpec.UNIGRAM_TIMESTAMP_FILE_EXTENSION);
final File terminalAddressTableFile = new File(mDictDir,
mBaseFilename + FormatSpec.TERMINAL_ADDRESS_TABLE_FILE_EXTENSION);
if (!mDictDir.isDirectory()) {
- if (mDictDir.exists()) mDictDir.delete();
+ if (mDictDir.exists()) {
+ FileUtils.deleteRecursively(mDictDir);
+ }
mDictDir.mkdirs();
}
mTrieOutStream = new FileOutputStream(trieFile);
+ mHeaderOutStream = new FileOutputStream(headerFile);
mFreqOutStream = new FileOutputStream(freqFile);
mTerminalAddressTableOutStream = new FileOutputStream(terminalAddressTableFile);
if (formatOptions.mHasTimestamp) {
@@ -260,6 +213,9 @@ public class Ver4DictEncoder implements DictEncoder {
if (mTrieOutStream != null) {
mTrieOutStream.close();
}
+ if (mHeaderOutStream != null) {
+ mHeaderOutStream.close();
+ }
if (mFreqOutStream != null) {
mFreqOutStream.close();
}
@@ -271,6 +227,7 @@ public class Ver4DictEncoder implements DictEncoder {
}
} finally {
mTrieOutStream = null;
+ mHeaderOutStream = null;
mFreqOutStream = null;
mTerminalAddressTableOutStream = null;
}
@@ -291,16 +248,34 @@ public class Ver4DictEncoder implements DictEncoder {
openStreams(formatOptions, dict.mOptions);
}
- mHeaderSize = BinaryDictEncoderUtils.writeDictionaryHeader(mTrieOutStream, dict,
- formatOptions);
+ BinaryDictEncoderUtils.writeDictionaryHeader(mHeaderOutStream, dict, formatOptions);
MakedictLog.i("Flattening the tree...");
ArrayList<PtNodeArray> flatNodes = BinaryDictEncoderUtils.flattenTree(dict.mRootNodeArray);
int terminalCount = 0;
+ final ArrayList<PtNode> nodes = CollectionUtils.newArrayList();
for (final PtNodeArray array : flatNodes) {
for (final PtNode node : array.mData) {
- if (node.isTerminal()) node.mTerminalId = terminalCount++;
+ if (node.isTerminal()) {
+ nodes.add(node);
+ node.mTerminalId = terminalCount++;
+ }
+ }
+ }
+ Collections.sort(nodes, new Comparator<PtNode>() {
+ @Override
+ public int compare(final PtNode lhs, final PtNode rhs) {
+ if (lhs.mFrequency != rhs.mFrequency) {
+ return lhs.mFrequency < rhs.mFrequency ? -1 : 1;
+ }
+ if (lhs.mTerminalId < rhs.mTerminalId) return -1;
+ if (lhs.mTerminalId > rhs.mTerminalId) return 1;
+ return 0;
}
+ });
+ int count = 0;
+ for (final PtNode node : nodes) {
+ node.mTerminalId = count++;
}
MakedictLog.i("Computing addresses...");
@@ -337,7 +312,7 @@ public class Ver4DictEncoder implements DictEncoder {
@Override
public void setPosition(int position) {
- if (mTrieBuf == null || position < 0 || position >- mTrieBuf.length) return;
+ if (mTrieBuf == null || position < 0 || position > mTrieBuf.length) return;
mTriePos = position;
}
@@ -457,7 +432,7 @@ public class Ver4DictEncoder implements DictEncoder {
ptNode.mFrequency, FormatSpec.FREQUENCY_AND_FLAGS_SIZE);
BinaryDictEncoderUtils.writeUIntToBuffer(terminalAddressTableBuf,
ptNode.mTerminalId * FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE,
- ptNode.mCachedAddressAfterUpdate + mHeaderSize,
+ ptNode.mCachedAddressAfterUpdate,
FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE);
}
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictUpdater.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictUpdater.java
index 3d8f186ba..91d9cf345 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver4DictUpdater.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver4DictUpdater.java
@@ -17,29 +17,133 @@
package com.android.inputmethod.latin.makedict;
import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding;
+import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
+import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
+import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+
+import android.util.Log;
import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.OutputStream;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
/**
* An implementation of DictUpdater for version 4 binary dictionary.
*/
@UsedForTesting
public class Ver4DictUpdater extends Ver4DictDecoder implements DictUpdater {
+ private static final String TAG = Ver4DictUpdater.class.getSimpleName();
+
+ private OutputStream mDictStream;
+ private final File mFrequencyFile;
@UsedForTesting
- public Ver4DictUpdater(final File dictDirectory, final int factoryType) {
+ public Ver4DictUpdater(final File dictDirectory, final int factoryType)
+ throws UnsupportedFormatException {
// DictUpdater must have an updatable DictBuffer.
super(dictDirectory, ((factoryType & MASK_DICTBUFFER) == USE_BYTEARRAY)
? USE_BYTEARRAY : USE_WRITABLE_BYTEBUFFER);
+ mFrequencyFile = getFile(FILETYPE_FREQUENCY);
+ }
+
+ private static class BigramContentUpdater extends SparseTableContentUpdater {
+ private final boolean mHasTimestamp;
+
+ public BigramContentUpdater(final String name, final File baseDir,
+ final boolean hasTimestamp) {
+ super(name + FormatSpec.BIGRAM_FILE_EXTENSION,
+ FormatSpec.BIGRAM_ADDRESS_TABLE_BLOCK_SIZE, baseDir,
+ BigramContentReader.getContentFilenames(name, hasTimestamp),
+ BigramContentReader.getContentIds(hasTimestamp),
+ new DictionaryBufferFromWritableByteBufferFactory());
+ mHasTimestamp = hasTimestamp;
+ }
+
+ public void insertBigramEntries(final int terminalId, final int frequency,
+ final ArrayList<PendingAttribute> entries) throws IOException {
+ if (terminalId < 0) {
+ throw new RuntimeException("Invalid terminal id : " + terminalId);
+ }
+ openStreamsAndBuffers();
+
+ if (entries == null || entries.isEmpty()) {
+ setContentValue(FormatSpec.BIGRAM_FREQ_CONTENT_INDEX, terminalId,
+ SparseTable.NOT_EXIST);
+ return;
+ }
+ final int positionOfEntries =
+ (int) mContentFiles[FormatSpec.BIGRAM_FREQ_CONTENT_INDEX].length();
+ setContentValue(FormatSpec.BIGRAM_FREQ_CONTENT_INDEX, terminalId, positionOfEntries);
+
+ final Iterator<PendingAttribute> bigramIterator = entries.iterator();
+ while (bigramIterator.hasNext()) {
+ final PendingAttribute entry = bigramIterator.next();
+ final int flags = BinaryDictEncoderUtils.makeBigramFlags(bigramIterator.hasNext(),
+ 0 /* offset */, entry.mFrequency, frequency, "" /* word */);
+ BinaryDictEncoderUtils.writeUIntToStream(
+ mContentOutStreams[FormatSpec.BIGRAM_FREQ_CONTENT_INDEX], flags,
+ FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE);
+ BinaryDictEncoderUtils.writeUIntToStream(
+ mContentOutStreams[FormatSpec.BIGRAM_FREQ_CONTENT_INDEX], entry.mAddress,
+ FormatSpec.PTNODE_ATTRIBUTE_MAX_ADDRESS_SIZE);
+ }
+ close();
+ }
+ }
+
+ private static class ShortcutContentUpdater extends SparseTableContentUpdater {
+ public ShortcutContentUpdater(final String name, final File baseDir) {
+ super(name + FormatSpec.SHORTCUT_FILE_EXTENSION,
+ FormatSpec.SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE, baseDir,
+ new String[] { name + FormatSpec.SHORTCUT_FILE_EXTENSION },
+ new String[] { FormatSpec.SHORTCUT_CONTENT_ID },
+ new DictionaryBufferFromWritableByteBufferFactory());
+ }
+
+ public void insertShortcuts(final int terminalId,
+ final ArrayList<WeightedString> shortcuts) throws IOException {
+ if (terminalId < 0) {
+ throw new RuntimeException("Invalid terminal id : " + terminalId);
+ }
+ openStreamsAndBuffers();
+ if (shortcuts == null || shortcuts.isEmpty()) {
+ setContentValue(FormatSpec.SHORTCUT_CONTENT_INDEX, terminalId,
+ SparseTable.NOT_EXIST);
+ return;
+ }
+
+ final int positionOfShortcuts =
+ (int) mContentFiles[FormatSpec.SHORTCUT_CONTENT_INDEX].length();
+ setContentValue(FormatSpec.SHORTCUT_CONTENT_INDEX, terminalId, positionOfShortcuts);
+
+ final Iterator<WeightedString> shortcutIterator = shortcuts.iterator();
+ while (shortcutIterator.hasNext()) {
+ final WeightedString target = shortcutIterator.next();
+ final int shortcutFlags = BinaryDictEncoderUtils.makeShortcutFlags(
+ shortcutIterator.hasNext(), target.mFrequency);
+ BinaryDictEncoderUtils.writeUIntToStream(
+ mContentOutStreams[FormatSpec.SHORTCUT_CONTENT_INDEX], shortcutFlags,
+ FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE);
+ CharEncoding.writeString(mContentOutStreams[FormatSpec.SHORTCUT_CONTENT_INDEX],
+ target.mWord);
+ }
+ close();
+ }
}
@Override
public void deleteWord(final String word) throws IOException, UnsupportedFormatException {
- if (mDictBuffer == null) openDictBuffer();
- readHeader();
+ if (mDictBuffer == null) {
+ openDictBuffer();
+ readHeader();
+ }
final int wordPos = getTerminalPosition(word);
if (wordPos != FormatSpec.NOT_VALID_WORD) {
mDictBuffer.position(wordPos);
@@ -49,11 +153,623 @@ public class Ver4DictUpdater extends Ver4DictDecoder implements DictUpdater {
}
}
- @Override
+ private int getNewTerminalId() {
+ // The size of frequency file is FormatSpec.FREQUENCY_AND_FLAGS_SIZE * number of terminals
+ // because each terminal always has a frequency.
+ // So we can get a fresh terminal id by this logic.
+ // CAVEAT: we are reading the file size from the disk each time: beware of race conditions,
+ // even on one thread.
+ return (int) (mFrequencyFile.length() / FormatSpec.FREQUENCY_AND_FLAGS_SIZE);
+ }
+
+ private void updateParentPosIfNotMoved(final int nodePos, final int newParentPos,
+ final FormatOptions formatOptions) {
+ final int originalPos = getPosition();
+ setPosition(nodePos);
+ final int flags = PtNodeReader.readPtNodeOptionFlags(mDictBuffer);
+ if (!BinaryDictIOUtils.isMovedPtNode(flags, formatOptions)) {
+ final int parentOffset = newParentPos - nodePos;
+ BinaryDictIOUtils.writeSInt24ToBuffer(mDictBuffer, parentOffset);
+ }
+ setPosition(originalPos);
+ }
+
+ private void updateParentPositions(final int nodeArrayPos, final int newParentPos,
+ final FormatOptions formatOptions) {
+ final int originalPos = mDictBuffer.position();
+ mDictBuffer.position(nodeArrayPos);
+ int jumpCount = 0;
+ do {
+ final int count = readPtNodeCount();
+ for (int i = 0; i < count; ++i) {
+ updateParentPosIfNotMoved(getPosition(), newParentPos, formatOptions);
+ skipPtNode(formatOptions);
+ }
+ if (!readAndFollowForwardLink()) break;
+ } while (jumpCount++ < DynamicBinaryDictIOUtils.MAX_JUMPS);
+ setPosition(originalPos);
+ }
+
+ private void updateChildrenPos(final int nodePos, final int newChildrenPos,
+ final FormatOptions options) {
+ final int originalPos = getPosition();
+ setPosition(nodePos);
+ final int flags = PtNodeReader.readPtNodeOptionFlags(mDictBuffer);
+ PtNodeReader.readParentAddress(mDictBuffer, options);
+ BinaryDictIOUtils.skipString(mDictBuffer,
+ (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS) != 0);
+ if ((flags & FormatSpec.FLAG_IS_TERMINAL) != 0) PtNodeReader.readTerminalId(mDictBuffer);
+ final int basePos = getPosition();
+ BinaryDictIOUtils.writeSInt24ToBuffer(mDictBuffer, newChildrenPos - basePos);
+ setPosition(originalPos);
+ }
+
+ private void updateTerminalPosition(final int terminalId, final int position) {
+ if (terminalId == PtNode.NOT_A_TERMINAL
+ || terminalId * FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE
+ >= mTerminalAddressTableBuffer.limit()) return;
+ mTerminalAddressTableBuffer.position(terminalId
+ * FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE);
+ BinaryDictEncoderUtils.writeUIntToDictBuffer(mTerminalAddressTableBuffer, position,
+ FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE);
+ }
+
+ private void updateForwardLink(final int nodeArrayPos, final int newForwardLink,
+ final FormatOptions formatOptions) {
+ final int originalPos = getPosition();
+ setPosition(nodeArrayPos);
+ int jumpCount = 0;
+ while (jumpCount++ < DynamicBinaryDictIOUtils.MAX_JUMPS) {
+ final int ptNodeCount = readPtNodeCount();
+ for (int i = 0; i < ptNodeCount; ++i) {
+ skipPtNode(formatOptions);
+ }
+ final int forwardLinkPos = getPosition();
+ if (!readAndFollowForwardLink()) {
+ setPosition(forwardLinkPos);
+ BinaryDictIOUtils.writeSInt24ToBuffer(mDictBuffer, newForwardLink - forwardLinkPos);
+ break;
+ }
+ }
+ setPosition(originalPos);
+ }
+
+ private void markPtNodeAsMoved(final int nodePos, final int newNodePos,
+ final FormatOptions options) {
+ final int originalPos = getPosition();
+ updateParentPosIfNotMoved(nodePos, newNodePos, options);
+ setPosition(nodePos);
+ final int currentFlags = PtNodeReader.readPtNodeOptionFlags(mDictBuffer);
+ setPosition(nodePos);
+ mDictBuffer.put((byte) (FormatSpec.FLAG_IS_MOVED
+ | (currentFlags & (~FormatSpec.MASK_MOVE_AND_DELETE_FLAG))));
+ final int offset = newNodePos - nodePos;
+ BinaryDictIOUtils.writeSInt24ToBuffer(mDictBuffer, offset);
+ setPosition(originalPos);
+ }
+
+ /**
+ * Writes a PtNode to an output stream from a Ver4PtNodeInfo.
+ *
+ * @param nodePos the position of the head of the PtNode.
+ * @param info the PtNode info to be written.
+ * @return the size written, in bytes.
+ */
+ private int writePtNode(final int nodePos, final Ver4PtNodeInfo info) throws IOException {
+ int written = 0;
+
+ // Write flags.
+ mDictStream.write((byte) (info.mFlags & 0xFF));
+ written += FormatSpec.PTNODE_FLAGS_SIZE;
+
+ // Write the parent position.
+ final int parentOffset = info.mParentPos == FormatSpec.NO_PARENT_ADDRESS ?
+ FormatSpec.NO_PARENT_ADDRESS : info.mParentPos - nodePos;
+ BinaryDictIOUtils.writeSInt24ToStream(mDictStream, parentOffset);
+ written += FormatSpec.PARENT_ADDRESS_SIZE;
+
+ // Write a string.
+ if (((info.mFlags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS) != 0)
+ != (info.mEndIndexOfCharacters - info.mStartIndexOfCharacters > 1)) {
+ throw new RuntimeException("Inconsistent flags : hasMultipleChars = "
+ + ((info.mFlags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS) != 0) + ", length = "
+ + (info.mEndIndexOfCharacters - info.mStartIndexOfCharacters));
+ }
+ written += CharEncoding.writeCodePoints(mDictStream, info.mCharacters,
+ info.mStartIndexOfCharacters, info.mEndIndexOfCharacters);
+
+ // Write the terminal id.
+ if ((info.mFlags & FormatSpec.FLAG_IS_TERMINAL) != 0) {
+ BinaryDictEncoderUtils.writeUIntToStream(mDictStream, info.mTerminalId,
+ FormatSpec.PTNODE_TERMINAL_ID_SIZE);
+ written += FormatSpec.PTNODE_TERMINAL_ID_SIZE;
+ }
+
+ // Write the children position.
+ final int childrenOffset = info.mChildrenPos == FormatSpec.NO_CHILDREN_ADDRESS
+ ? 0 : info.mChildrenPos - (nodePos + written);
+ BinaryDictIOUtils.writeSInt24ToStream(mDictStream, childrenOffset);
+ written += FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE;
+
+ return written;
+ }
+
+ /**
+ * Helper method to split and move PtNode.
+ *
+ * @param ptNodeArrayPos the position of PtNodeArray which contains the split and moved PtNode.
+ * @param splittedPtNodeToMovePos the position of the split and moved PtNode.
+ * @param newParent the parent PtNode after splitting.
+ * @param newChildren the children PtNodes after splitting.
+ * @param newParentStartPos where to write the new parent.
+ * @param formatOptions the format options.
+ */
+ private void writeSplittedPtNodes(final int ptNodeArrayPos, final int splittedPtNodeToMovePos,
+ final Ver4PtNodeInfo newParent, final Ver4PtNodeInfo[] newChildren,
+ final int newParentStartPos,
+ final FormatOptions formatOptions) throws IOException {
+ updateTerminalPosition(newParent.mTerminalId,
+ newParentStartPos + 1 /* size of PtNodeCount */);
+ int written = writePtNodeArray(newParentStartPos, new Ver4PtNodeInfo[] { newParent },
+ FormatSpec.NO_FORWARD_LINK_ADDRESS);
+ final int childrenStartPos = newParentStartPos + written;
+ writePtNodeArray(childrenStartPos, newChildren, FormatSpec.NO_FORWARD_LINK_ADDRESS);
+ int childrenNodePos = childrenStartPos + 1 /* size of PtNodeCount */;
+ for (final Ver4PtNodeInfo info : newChildren) {
+ updateTerminalPosition(info.mTerminalId, childrenNodePos);
+ childrenNodePos += computePtNodeSize(info.mCharacters, info.mStartIndexOfCharacters,
+ info.mEndIndexOfCharacters,
+ (info.mFlags & FormatSpec.FLAG_IS_TERMINAL) != 0);
+ }
+
+ // Mark as moved.
+ markPtNodeAsMoved(splittedPtNodeToMovePos, newParentStartPos + 1 /* size of PtNodeCount */,
+ formatOptions);
+ updateForwardLink(ptNodeArrayPos, newParentStartPos, formatOptions);
+ }
+
+ /**
+ * Writes a node array to the stream.
+ *
+ * @param nodeArrayPos the position of the head of the node array.
+ * @param infos an array of Ver4PtNodeInfo to be written.
+ * @return the written length in bytes.
+ */
+ private int writePtNodeArray(final int nodeArrayPos, final Ver4PtNodeInfo[] infos,
+ final int forwardLink) throws IOException {
+ int written = BinaryDictIOUtils.writePtNodeCount(mDictStream, infos.length);
+ for (int i = 0; i < infos.length; ++i) {
+ written += writePtNode(nodeArrayPos + written, infos[i]);
+ }
+ BinaryDictIOUtils.writeSInt24ToStream(mDictStream, forwardLink);
+ written += FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
+ return written;
+ }
+
+ private int computePtNodeSize(final int[] codePoints, final int startIndex, final int endIndex,
+ final boolean isTerminal) {
+ return FormatSpec.PTNODE_FLAGS_SIZE + FormatSpec.PARENT_ADDRESS_SIZE
+ + CharEncoding.getCharArraySize(codePoints, startIndex, endIndex)
+ + (endIndex - startIndex > 1 ? FormatSpec.PTNODE_TERMINATOR_SIZE : 0)
+ + (isTerminal ? FormatSpec.PTNODE_TERMINAL_ID_SIZE : 0)
+ + FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE;
+ }
+
+ private void writeNewSinglePtNodeWithAttributes(final int[] codePoints,
+ final boolean hasShortcuts, final int terminalId, final boolean hasBigrams,
+ final boolean isNotAWord, final boolean isBlackListEntry, final int parentPos,
+ final FormatOptions formatOptions) throws IOException {
+ final int newNodeArrayPos = mDictBuffer.limit();
+ final int newNodeFlags = BinaryDictEncoderUtils.makePtNodeFlags(codePoints.length > 1,
+ terminalId != PtNode.NOT_A_TERMINAL, FormatSpec.FLAG_IS_NOT_MOVED, hasShortcuts,
+ hasBigrams, isNotAWord, isBlackListEntry, formatOptions);
+ final Ver4PtNodeInfo info = new Ver4PtNodeInfo(newNodeFlags, codePoints, terminalId,
+ FormatSpec.NO_CHILDREN_ADDRESS, parentPos, 0 /* nodeSize */);
+ writePtNodeArray(newNodeArrayPos, new Ver4PtNodeInfo[] { info },
+ FormatSpec.NO_FORWARD_LINK_ADDRESS);
+ }
+
+ private int setMultipleCharsInFlags(final int currentFlags, final boolean hasMultipleChars) {
+ final int flags;
+ if (hasMultipleChars) {
+ flags = currentFlags | FormatSpec.FLAG_HAS_MULTIPLE_CHARS;
+ } else {
+ flags = currentFlags & (~FormatSpec.FLAG_HAS_MULTIPLE_CHARS);
+ }
+ return flags;
+ }
+
+ private int setIsNotAWordInFlags(final int currentFlags, final boolean isNotAWord) {
+ final int flags;
+ if (isNotAWord) {
+ flags = currentFlags | FormatSpec.FLAG_IS_NOT_A_WORD;
+ } else {
+ flags = currentFlags & (~FormatSpec.FLAG_IS_NOT_A_WORD);
+ }
+ return flags;
+ }
+
+ private int setIsBlackListEntryInFlags(final int currentFlags, final boolean isBlackListEntry) {
+ final int flags;
+ if (isBlackListEntry) {
+ flags = currentFlags | FormatSpec.FLAG_IS_BLACKLISTED;
+ } else {
+ flags = currentFlags & (~FormatSpec.FLAG_IS_BLACKLISTED);
+ }
+ return flags;
+ }
+
+ /**
+ * Splits a PtNode.
+ *
+ * abcd - ef
+ *
+ * -> inserting "abc"
+ *
+ * abc - d - ef
+ *
+ * @param nodeArrayToSplitPos the position of PtNodeArray which contains the PtNode to split.
+ * @param nodeToSplitPos the position of the PtNode to split.
+ * @param nodeToSplitInfo the information of the PtNode to split.
+ * @param indexToSplit the index where to split in the code points array.
+ * @param parentOfNodeToSplitPos the absolute position of a parent of the node to split.
+ * @param newTerminalId the terminal id of the inserted node (corresponds to "d").
+ * @param hasShortcuts whether the inserted word should have shortcuts.
+ * @param hasBigrams whether the inserted word should have bigrams.
+ * @param isNotAWord whether the inserted word should be not a word.
+ * @param isBlackListEntry whether the inserted word should be a black list entry.
+ * @param formatOptions the format options.
+ */
+ private void splitOnly(final int nodeArrayToSplitPos, final int nodeToSplitPos,
+ final Ver4PtNodeInfo nodeToSplitInfo, final int indexToSplit,
+ final int parentOfNodeToSplitPos, final int newTerminalId, final boolean hasShortcuts,
+ final boolean hasBigrams, final boolean isNotAWord, final boolean isBlackListEntry,
+ final FormatOptions formatOptions) throws IOException {
+ final int parentNodeArrayStartPos = mDictBuffer.limit();
+ final int parentNodeStartPos = parentNodeArrayStartPos + 1 /* size of PtNodeCount */;
+ final int parentFlags = BinaryDictEncoderUtils.makePtNodeFlags(indexToSplit > 1,
+ true /* isTerminal */, FormatSpec.FLAG_IS_NOT_MOVED, hasShortcuts, hasBigrams,
+ isNotAWord, isBlackListEntry, formatOptions);
+ final Ver4PtNodeInfo parentInfo = new Ver4PtNodeInfo(parentFlags,
+ nodeToSplitInfo.mCharacters, newTerminalId, parentNodeStartPos
+ + computePtNodeSize(nodeToSplitInfo.mCharacters, 0, indexToSplit, true)
+ + FormatSpec.FORWARD_LINK_ADDRESS_SIZE,
+ parentOfNodeToSplitPos, 0 /* nodeSize */);
+ parentInfo.mStartIndexOfCharacters = 0;
+ parentInfo.mEndIndexOfCharacters = indexToSplit;
+
+ // Write the child.
+ final int childrenFlags = setMultipleCharsInFlags(nodeToSplitInfo.mFlags,
+ nodeToSplitInfo.mCharacters.length - indexToSplit > 1);
+ final Ver4PtNodeInfo childrenInfo = new Ver4PtNodeInfo(childrenFlags,
+ nodeToSplitInfo.mCharacters, nodeToSplitInfo.mTerminalId,
+ nodeToSplitInfo.mChildrenPos, parentNodeStartPos, 0 /* nodeSize */);
+ childrenInfo.mStartIndexOfCharacters = indexToSplit;
+ childrenInfo.mEndIndexOfCharacters = nodeToSplitInfo.mCharacters.length;
+ if (nodeToSplitInfo.mChildrenPos != FormatSpec.NO_CHILDREN_ADDRESS) {
+ updateParentPositions(nodeToSplitInfo.mChildrenPos,
+ parentInfo.mChildrenPos + 1 /* size of PtNodeCount */, formatOptions);
+ }
+
+ writeSplittedPtNodes(nodeArrayToSplitPos, nodeToSplitPos, parentInfo,
+ new Ver4PtNodeInfo[] { childrenInfo }, parentNodeArrayStartPos, formatOptions);
+ }
+
+ /**
+ * Split and branch a PtNode.
+ *
+ * ab - cd
+ *
+ * -> inserting "ac"
+ *
+ * a - b - cd
+ * |
+ * - c
+ *
+ * @param nodeArrayToSplitPos the position of PtNodeArray which contains the PtNode to split.
+ * @param nodeToSplitPos the position of the PtNode to split.
+ * @param nodeToSplitInfo the information of the PtNode to split.
+ * @param indexToSplit the index where to split in the code points array.
+ * @param parentOfNodeToSplitPos the absolute position of parent of the node to split.
+ * @param newWordSuffixCodePoints the suffix of the newly inserted word (corresponds to "c").
+ * @param startIndexOfNewWordSuffixCodePoints the start index in newWordSuffixCodePoints where
+ * the suffix starts.
+ * @param newTerminalId the terminal id of the inserted node (correspond to "c").
+ * @param hasShortcuts whether the inserted word should have shortcuts.
+ * @param hasBigrams whether the inserted word should have bigrams.
+ * @param isNotAWord whether the inserted word should be not a word.
+ * @param isBlackListEntry whether the inserted word should be a black list entry.
+ * @param formatOptions the format options.
+ */
+ private void splitAndBranch(final int nodeArrayToSplitPos, final int nodeToSplitPos,
+ final Ver4PtNodeInfo nodeToSplitInfo, final int indexToSplit,
+ final int parentOfNodeToSplitPos, final int[] newWordSuffixCodePoints,
+ final int startIndexOfNewWordSuffixCodePoints,
+ final int newTerminalId,
+ final boolean hasShortcuts, final boolean hasBigrams, final boolean isNotAWord,
+ final boolean isBlackListEntry, final FormatOptions formatOptions) throws IOException {
+ final int parentNodeArrayStartPos = mDictBuffer.limit();
+ final int parentNodeStartPos = parentNodeArrayStartPos + 1 /* size of PtNodeCount */;
+ final int parentFlags = BinaryDictEncoderUtils.makePtNodeFlags(
+ indexToSplit > 1,
+ false /* isTerminal */, FormatSpec.FLAG_IS_NOT_MOVED,
+ false /* hasShortcut */, false /* hasBigrams */,
+ false /* isNotAWord */, false /* isBlackListEntry */, formatOptions);
+ final Ver4PtNodeInfo parentInfo = new Ver4PtNodeInfo(parentFlags,
+ nodeToSplitInfo.mCharacters, PtNode.NOT_A_TERMINAL,
+ parentNodeStartPos
+ + computePtNodeSize(nodeToSplitInfo.mCharacters, 0, indexToSplit, false)
+ + FormatSpec.FORWARD_LINK_ADDRESS_SIZE,
+ parentOfNodeToSplitPos, 0 /* nodeSize */);
+ parentInfo.mStartIndexOfCharacters = 0;
+ parentInfo.mEndIndexOfCharacters = indexToSplit;
+
+ final int childrenNodeArrayStartPos = parentNodeStartPos
+ + computePtNodeSize(nodeToSplitInfo.mCharacters, 0, indexToSplit, false)
+ + FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
+ final int firstChildrenFlags = BinaryDictEncoderUtils.makePtNodeFlags(
+ newWordSuffixCodePoints.length - startIndexOfNewWordSuffixCodePoints > 1,
+ true /* isTerminal */, FormatSpec.FLAG_IS_NOT_MOVED, hasShortcuts, hasBigrams,
+ isNotAWord, isBlackListEntry, formatOptions);
+ final Ver4PtNodeInfo firstChildrenInfo = new Ver4PtNodeInfo(firstChildrenFlags,
+ newWordSuffixCodePoints, newTerminalId,
+ FormatSpec.NO_CHILDREN_ADDRESS, parentNodeStartPos,
+ 0 /* nodeSize */);
+ firstChildrenInfo.mStartIndexOfCharacters = startIndexOfNewWordSuffixCodePoints;
+ firstChildrenInfo.mEndIndexOfCharacters = newWordSuffixCodePoints.length;
+
+ final int secondChildrenStartPos = childrenNodeArrayStartPos + 1 /* size of ptNodeCount */
+ + computePtNodeSize(newWordSuffixCodePoints, startIndexOfNewWordSuffixCodePoints,
+ newWordSuffixCodePoints.length, true /* isTerminal */);
+ final int secondChildrenFlags = setMultipleCharsInFlags(nodeToSplitInfo.mFlags,
+ nodeToSplitInfo.mCharacters.length - indexToSplit > 1);
+ final Ver4PtNodeInfo secondChildrenInfo = new Ver4PtNodeInfo(secondChildrenFlags,
+ nodeToSplitInfo.mCharacters, nodeToSplitInfo.mTerminalId,
+ nodeToSplitInfo.mChildrenPos, parentNodeStartPos, 0 /* nodeSize */);
+ secondChildrenInfo.mStartIndexOfCharacters = indexToSplit;
+ secondChildrenInfo.mEndIndexOfCharacters = nodeToSplitInfo.mCharacters.length;
+ if (nodeToSplitInfo.mChildrenPos != FormatSpec.NO_CHILDREN_ADDRESS) {
+ updateParentPositions(nodeToSplitInfo.mChildrenPos, secondChildrenStartPos,
+ formatOptions);
+ }
+
+ writeSplittedPtNodes(nodeArrayToSplitPos, nodeToSplitPos, parentInfo,
+ new Ver4PtNodeInfo[] { firstChildrenInfo, secondChildrenInfo },
+ parentNodeArrayStartPos, formatOptions);
+ }
+
+ /**
+ * Inserts a word into the trie file and returns the position of inserted terminal node.
+ * If the insertion is failed, returns FormatSpec.NOT_VALID_WORD.
+ */
+ @UsedForTesting
+ private int insertWordToTrie(final String word, final int newTerminalId,
+ final boolean isNotAWord, final boolean isBlackListEntry, final boolean hasBigrams,
+ final boolean hasShortcuts) throws IOException, UnsupportedFormatException {
+ setPosition(0);
+ final FileHeader header = readHeader();
+
+ final int[] codePoints = FusionDictionary.getCodePoints(word);
+ final int wordLen = codePoints.length;
+
+ int wordPos = 0;
+ for (int depth = 0; depth < FormatSpec.MAX_WORD_LENGTH; /* nop */) {
+ final int nodeArrayPos = getPosition();
+ final int ptNodeCount = readPtNodeCount();
+ boolean goToChildren = false;
+ int parentPos = FormatSpec.NO_PARENT_ADDRESS;
+ for (int i = 0; i < ptNodeCount; ++i) {
+ final int nodePos = getPosition();
+ final Ver4PtNodeInfo nodeInfo = readVer4PtNodeInfo(nodePos, header.mFormatOptions);
+ if (BinaryDictIOUtils.isMovedPtNode(nodeInfo.mFlags, header.mFormatOptions)) {
+ continue;
+ }
+ if (nodeInfo.mParentPos != FormatSpec.NO_PARENT_ADDRESS) {
+ parentPos = nodePos + nodeInfo.mParentPos;
+ }
+
+ final boolean firstCharacterMatched =
+ codePoints[wordPos] == nodeInfo.mCharacters[0];
+ boolean allCharactersMatched = true;
+ int firstDifferentCharacterIndex = -1;
+ for (int p = 0; p < nodeInfo.mCharacters.length; ++p) {
+ if (wordPos + p >= codePoints.length) break;
+ if (codePoints[wordPos + p] != nodeInfo.mCharacters[p]) {
+ if (firstDifferentCharacterIndex == -1) {
+ firstDifferentCharacterIndex = p;
+ }
+ allCharactersMatched = false;
+ }
+ }
+
+ if (!firstCharacterMatched) {
+ // Go to the next sibling node.
+ continue;
+ }
+
+ if (!allCharactersMatched) {
+ final int parentNodeArrayStartPos = mDictBuffer.limit();
+ splitAndBranch(nodeArrayPos, nodePos, nodeInfo, firstDifferentCharacterIndex,
+ parentPos, codePoints, wordPos + firstDifferentCharacterIndex,
+ newTerminalId, hasShortcuts, hasBigrams, isNotAWord,
+ isBlackListEntry, header.mFormatOptions);
+
+ return parentNodeArrayStartPos + computePtNodeSize(codePoints, wordPos,
+ wordPos + firstDifferentCharacterIndex, false)
+ + FormatSpec.FORWARD_LINK_ADDRESS_SIZE + 1 /* size of PtNodeCount */;
+ }
+
+ if (wordLen - wordPos < nodeInfo.mCharacters.length) {
+ final int parentNodeArrayStartPos = mDictBuffer.limit();
+ splitOnly(nodeArrayPos, nodePos, nodeInfo, wordLen - wordPos, parentPos,
+ newTerminalId, hasShortcuts, hasBigrams, isNotAWord, isBlackListEntry,
+ header.mFormatOptions);
+
+ // Return the position of the inserted word.
+ return parentNodeArrayStartPos + 1 /* size of PtNodeCount */;
+ }
+
+ wordPos += nodeInfo.mCharacters.length;
+ if (wordPos == wordLen) {
+ // This dictionary already contains the word.
+ Log.e(TAG, "Something went wrong. If the word is already contained, "
+ + " there is no need to insert new PtNode.");
+ return FormatSpec.NOT_VALID_WORD;
+ }
+ if (nodeInfo.mChildrenPos == FormatSpec.NO_CHILDREN_ADDRESS) {
+ // There are no children.
+ // We need to add a new node as a child of this node.
+ final int newNodeArrayPos = mDictBuffer.limit();
+ final int[] newNodeCodePoints = Arrays.copyOfRange(codePoints, wordPos,
+ codePoints.length);
+ writeNewSinglePtNodeWithAttributes(newNodeCodePoints, hasShortcuts,
+ newTerminalId, hasBigrams, isNotAWord, isBlackListEntry, nodePos,
+ header.mFormatOptions);
+ updateChildrenPos(nodePos, newNodeArrayPos, header.mFormatOptions);
+ return newNodeArrayPos + 1 /* size of PtNodeCount */;
+ } else {
+ // Found the matched node.
+ // Go to the children of this node.
+ setPosition(nodeInfo.mChildrenPos);
+ goToChildren = true;
+ depth++;
+ break;
+ }
+ }
+
+ if (goToChildren) continue;
+ if (!readAndFollowForwardLink()) {
+ // Add a new node that contains [wordPos, word.length()-1].
+ // and update the forward link.
+ final int newNodeArrayPos = mDictBuffer.limit();
+ final int[] newCodePoints = Arrays.copyOfRange(codePoints, wordPos,
+ codePoints.length);
+ writeNewSinglePtNodeWithAttributes(newCodePoints, hasShortcuts, newTerminalId,
+ hasBigrams, isNotAWord, isBlackListEntry, parentPos, header.mFormatOptions);
+ updateForwardLink(nodeArrayPos, newNodeArrayPos, header.mFormatOptions);
+ return newNodeArrayPos + 1 /* size of PtNodeCount */;
+ }
+ }
+ return FormatSpec.NOT_VALID_WORD;
+ }
+
+ private void updateFrequency(final int terminalId, final int frequency) {
+ mFrequencyBuffer.position(terminalId * FormatSpec.FREQUENCY_AND_FLAGS_SIZE);
+ BinaryDictEncoderUtils.writeUIntToDictBuffer(mFrequencyBuffer, frequency,
+ FormatSpec.FREQUENCY_AND_FLAGS_SIZE);
+ }
+
+ private void insertFrequency(final int frequency) throws IOException {
+ final OutputStream frequencyStream = new FileOutputStream(mFrequencyFile,
+ true /* append */);
+ BinaryDictEncoderUtils.writeUIntToStream(frequencyStream, frequency,
+ FormatSpec.FREQUENCY_AND_FLAGS_SIZE);
+ frequencyStream.close();
+ }
+
+ private void insertTerminalPosition(final int posOfTerminal) throws IOException,
+ UnsupportedFormatException {
+ final OutputStream terminalPosStream = new FileOutputStream(
+ getFile(FILETYPE_TERMINAL_ADDRESS_TABLE), true /* append */);
+ BinaryDictEncoderUtils.writeUIntToStream(terminalPosStream, posOfTerminal,
+ FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE);
+ terminalPosStream.close();
+ }
+
+ private void insertBigrams(final int terminalId, final int frequency,
+ final ArrayList<PendingAttribute> bigramAddresses)
+ throws IOException, UnsupportedFormatException {
+ openDictBuffer();
+ final BigramContentUpdater updater = new BigramContentUpdater(mDictDirectory.getName(),
+ mDictDirectory, false);
+
+ // Convert addresses to terminal ids.
+ final ArrayList<PendingAttribute> bigrams = CollectionUtils.newArrayList();
+ mDictBuffer.position(0);
+ final FileHeader header = readHeader();
+ for (PendingAttribute attr : bigramAddresses) {
+ mDictBuffer.position(attr.mAddress);
+ final Ver4PtNodeInfo info = readVer4PtNodeInfo(attr.mAddress, header.mFormatOptions);
+ if (info.mTerminalId == PtNode.NOT_A_TERMINAL) {
+ throw new RuntimeException("We can't have a bigram target that's not a terminal.");
+ }
+ bigrams.add(new PendingAttribute(frequency, info.mTerminalId));
+ }
+ updater.insertBigramEntries(terminalId, frequency, bigrams);
+ close();
+ }
+
+ private void insertShortcuts(final int terminalId, final ArrayList<WeightedString> shortcuts)
+ throws IOException {
+ final ShortcutContentUpdater updater = new ShortcutContentUpdater(mDictDirectory.getName(),
+ mDictDirectory);
+ updater.insertShortcuts(terminalId, shortcuts);
+ }
+
+ private void openBuffersAndStream() throws IOException, UnsupportedFormatException {
+ openDictBuffer();
+ mDictStream = new FileOutputStream(getFile(FILETYPE_TRIE), true /* append */);
+ }
+
+ private void close() throws IOException {
+ if (mDictStream != null) {
+ mDictStream.close();
+ mDictStream = null;
+ }
+ mDictBuffer = null;
+ mFrequencyBuffer = null;
+ mTerminalAddressTableBuffer = null;
+ }
+
+ private void updateAttributes(final int posOfWord, final int frequency,
+ final ArrayList<WeightedString> bigramStrings,
+ final ArrayList<WeightedString> shortcuts, final boolean isNotAWord,
+ final boolean isBlackListEntry) throws IOException, UnsupportedFormatException {
+ mDictBuffer.position(0);
+ final FileHeader header = readHeader();
+ mDictBuffer.position(posOfWord);
+ final Ver4PtNodeInfo info = readVer4PtNodeInfo(posOfWord, header.mFormatOptions);
+ final int terminalId = info.mTerminalId;
+
+ // Update the flags.
+ final int newFlags = setIsNotAWordInFlags(
+ setIsBlackListEntryInFlags(info.mFlags, isBlackListEntry), isNotAWord);
+ mDictBuffer.position(posOfWord);
+ mDictBuffer.put((byte) newFlags);
+
+ updateFrequency(terminalId, frequency);
+ insertBigrams(terminalId, frequency,
+ DynamicBinaryDictIOUtils.resolveBigramPositions(this, bigramStrings));
+ insertShortcuts(terminalId, shortcuts);
+ }
+
+ @Override @UsedForTesting
public void insertWord(final String word, final int frequency,
final ArrayList<WeightedString> bigramStrings, final ArrayList<WeightedString> shortcuts,
final boolean isNotAWord, final boolean isBlackListEntry)
throws IOException, UnsupportedFormatException {
- // TODO: Implement this method.
+ final int newTerminalId = getNewTerminalId();
+
+ openBuffersAndStream();
+ final int posOfWord = getTerminalPosition(word);
+ if (posOfWord != FormatSpec.NOT_VALID_WORD) {
+ // The word is already contained in the dictionary.
+ updateAttributes(posOfWord, frequency, bigramStrings, shortcuts, isNotAWord,
+ isBlackListEntry);
+ close();
+ return;
+ }
+
+ // Insert new PtNode into trie.
+ final int posOfTerminal = insertWordToTrie(word, newTerminalId, isNotAWord,
+ isBlackListEntry, bigramStrings != null && !bigramStrings.isEmpty(),
+ shortcuts != null && !shortcuts.isEmpty());
+ insertFrequency(frequency);
+ insertTerminalPosition(posOfTerminal);
+ close();
+
+ insertBigrams(newTerminalId, frequency,
+ DynamicBinaryDictIOUtils.resolveBigramPositions(this, bigramStrings));
+ insertShortcuts(newTerminalId, shortcuts);
}
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
index 1de15a333..296733fad 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
@@ -17,18 +17,16 @@
package com.android.inputmethod.latin.personalization;
import android.content.Context;
-import android.content.SharedPreferences;
import android.util.Log;
import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.BinaryDictionary.LanguageModelParam;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.ExpandableBinaryDictionary;
-import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.makedict.DictDecoder;
import com.android.inputmethod.latin.makedict.FormatSpec;
-import com.android.inputmethod.latin.settings.Settings;
-import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils;
import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils.OnAddWordListener;
@@ -36,7 +34,9 @@ import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Locale;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
/**
* This class is a base class of a dictionary that supports decaying for the personalized language
@@ -45,8 +45,7 @@ import java.util.Map;
public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableBinaryDictionary {
private static final String TAG = DecayingExpandableBinaryDictionaryBase.class.getSimpleName();
public static final boolean DBG_SAVE_RESTORE = false;
- private static final boolean DBG_STRESS_TEST = false;
- private static final boolean PROFILE_SAVE_RESTORE = LatinImeLogger.sDBG;
+ private static final boolean DBG_DUMP_ON_CLOSE = false;
/** Any pair being typed or picked */
public static final int FREQUENCY_FOR_TYPED = 2;
@@ -54,41 +53,43 @@ public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableB
public static final int FREQUENCY_FOR_WORDS_IN_DICTS = FREQUENCY_FOR_TYPED;
public static final int FREQUENCY_FOR_WORDS_NOT_IN_DICTS = Dictionary.NOT_A_PROBABILITY;
- /** Locale for which this user history dictionary is storing words */
- private final String mLocale;
-
- private final String mFileName;
+ public static final int REQUIRED_BINARY_DICTIONARY_VERSION = 4;
- private final SharedPreferences mPrefs;
+ /** Locale for which this user history dictionary is storing words */
+ private final Locale mLocale;
- private final ArrayList<PersonalizationDictionaryUpdateSession> mSessions =
- CollectionUtils.newArrayList();
+ private final String mDictName;
- // Should always be false except when we use this class for test
- @UsedForTesting boolean mIsTest = false;
+ /* package */ DecayingExpandableBinaryDictionaryBase(final Context context,
+ final Locale locale, final String dictionaryType, final String dictName) {
+ super(context, dictName, locale, dictionaryType, true);
+ mLocale = locale;
+ mDictName = dictName;
+ if (mLocale != null && mLocale.toString().length() > 1) {
+ reloadDictionaryIfRequired();
+ }
+ }
+ // Creates an instance that uses a given dictionary file for testing.
+ @UsedForTesting
/* package */ DecayingExpandableBinaryDictionaryBase(final Context context,
- final String locale, final SharedPreferences sp, final String dictionaryType,
- final String fileName) {
- super(context, fileName, dictionaryType, true);
+ final Locale locale, final String dictionaryType, final String dictName,
+ final File dictFile) {
+ super(context, dictName, locale, dictionaryType, true, dictFile);
mLocale = locale;
- mFileName = fileName;
- mPrefs = sp;
- if (mLocale != null && mLocale.length() > 1) {
- asyncLoadDictionaryToMemory();
+ mDictName = dictName;
+ if (mLocale != null && mLocale.toString().length() > 1) {
reloadDictionaryIfRequired();
}
}
@Override
public void close() {
- if (!ExpandableBinaryDictionary.ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
- closeBinaryDictionary();
+ if (DBG_DUMP_ON_CLOSE) {
+ dumpAllWordsForDebug();
}
// Flush pending writes.
- // TODO: Remove after this class become to use a dynamic binary dictionary.
- asyncFlashAllBinaryDictionary();
- Settings.writeLastUserHistoryWriteTime(mPrefs, mLocale);
+ asyncFlushBinaryDictionary();
}
@Override
@@ -98,8 +99,12 @@ public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableB
FormatSpec.FileHeader.ATTRIBUTE_VALUE_TRUE);
attributeMap.put(FormatSpec.FileHeader.USES_FORGETTING_CURVE_ATTRIBUTE,
FormatSpec.FileHeader.ATTRIBUTE_VALUE_TRUE);
- attributeMap.put(FormatSpec.FileHeader.DICTIONARY_ID_ATTRIBUTE, mFileName);
- attributeMap.put(FormatSpec.FileHeader.DICTIONARY_LOCALE_ATTRIBUTE, mLocale);
+ attributeMap.put(FormatSpec.FileHeader.HAS_HISTORICAL_INFO_ATTRIBUTE,
+ FormatSpec.FileHeader.ATTRIBUTE_VALUE_TRUE);
+ attributeMap.put(FormatSpec.FileHeader.DICTIONARY_ID_ATTRIBUTE, mDictName);
+ attributeMap.put(FormatSpec.FileHeader.DICTIONARY_LOCALE_ATTRIBUTE, mLocale.toString());
+ attributeMap.put(FormatSpec.FileHeader.DICTIONARY_VERSION_ATTRIBUTE,
+ String.valueOf(TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())));
return attributeMap;
}
@@ -113,6 +118,25 @@ public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableB
return false;
}
+ @Override
+ protected boolean matchesExpectedBinaryDictFormatVersionForThisType(final int formatVersion) {
+ // This class is using format 4 because it's used by all version 4 dictionaries.
+ // TODO: remove this when all dynamically generated dicts use version 4.
+ return formatVersion == REQUIRED_BINARY_DICTIONARY_VERSION;
+ }
+
+ public void addMultipleDictionaryEntriesToDictionary(
+ final ArrayList<LanguageModelParam> languageModelParams,
+ final ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback callback) {
+ if (languageModelParams == null || languageModelParams.isEmpty()) {
+ if (callback != null) {
+ callback.onFinished();
+ }
+ return;
+ }
+ addMultipleDictionaryEntriesDynamically(languageModelParams, callback);
+ }
+
/**
* Pair will be added to the decaying dictionary.
*
@@ -121,70 +145,63 @@ public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableB
* context, as in beginning of a sentence for example.
* The second word may not be null (a NullPointerException would be thrown).
*/
- public void addToDictionary(final String word0, final String word1, final boolean isValid) {
+ public void addToDictionary(final String word0, final String word1, final boolean isValid,
+ final int timestamp) {
if (word1.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH ||
(word0 != null && word0.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH)) {
return;
}
- final int frequency = ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE ?
- (isValid ? FREQUENCY_FOR_WORDS_IN_DICTS : FREQUENCY_FOR_WORDS_NOT_IN_DICTS) :
- FREQUENCY_FOR_TYPED;
- addWordDynamically(word1, null /* shortcutTarget */, frequency, 0 /* shortcutFreq */,
- false /* isNotAWord */);
+ final int frequency = isValid ?
+ FREQUENCY_FOR_WORDS_IN_DICTS : FREQUENCY_FOR_WORDS_NOT_IN_DICTS;
+ addWordDynamically(word1, frequency, null /* shortcutTarget */, 0 /* shortcutFreq */,
+ false /* isNotAWord */, false /* isBlacklisted */, timestamp);
// Do not insert a word as a bigram of itself
if (word1.equals(word0)) {
return;
}
if (null != word0) {
- addBigramDynamically(word0, word1, frequency, isValid);
+ addBigramDynamically(word0, word1, frequency, timestamp);
}
}
- public void cancelAddingUserHistory(final String word0, final String word1) {
- removeBigramDynamically(word0, word1);
- }
-
@Override
protected void loadDictionaryAsync() {
- final int[] profTotalCount = { 0 };
- final String locale = getLocale();
- if (DBG_STRESS_TEST) {
- try {
- Log.w(TAG, "Start stress in loading: " + locale);
- Thread.sleep(15000);
- Log.w(TAG, "End stress in loading");
- } catch (InterruptedException e) {
+ // Never loaded to memory in Java side.
+ }
+
+ @UsedForTesting
+ public void dumpAllWordsForDebug() {
+ runAfterGcForDebug(new Runnable() {
+ @Override
+ public void run() {
+ dumpAllWordsForDebugLocked();
}
- }
- final long last = Settings.readLastUserHistoryWriteTime(mPrefs, locale);
- final long now = System.currentTimeMillis();
- final ExpandableBinaryDictionary dictionary = this;
+ });
+ }
+
+ private void dumpAllWordsForDebugLocked() {
+ Log.d(TAG, "dumpAllWordsForDebug started.");
final OnAddWordListener listener = new OnAddWordListener() {
@Override
public void setUnigram(final String word, final String shortcutTarget,
final int frequency, final int shortcutFreq) {
- if (DBG_SAVE_RESTORE) {
- Log.d(TAG, "load unigram: " + word + "," + frequency);
- }
- addWord(word, shortcutTarget, frequency, shortcutFreq, false /* isNotAWord */);
- ++profTotalCount[0];
+ Log.d(TAG, "load unigram: " + word + "," + frequency);
}
@Override
public void setBigram(final String word0, final String word1, final int frequency) {
if (word0.length() < Constants.DICTIONARY_MAX_WORD_LENGTH
&& word1.length() < Constants.DICTIONARY_MAX_WORD_LENGTH) {
- if (DBG_SAVE_RESTORE) {
- Log.d(TAG, "load bigram: " + word0 + "," + word1 + "," + frequency);
- }
- ++profTotalCount[0];
- addBigram(word0, word1, frequency, last);
+ Log.d(TAG, "load bigram: " + word0 + "," + word1 + "," + frequency);
+ } else {
+ Log.d(TAG, "Skip inserting a too long bigram: " + word0 + "," + word1 + ","
+ + frequency);
}
}
};
// Load the dictionary from binary file
- final File dictFile = new File(mContext.getFilesDir(), mFileName);
+ final File dictFile = new File(mContext.getFilesDir(), mDictName);
final DictDecoder dictDecoder = FormatSpec.getDictDecoder(dictFile,
DictDecoder.USE_BYTEARRAY);
if (dictDecoder == null) {
@@ -198,35 +215,17 @@ public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableB
UserHistoryDictIOUtils.readDictionaryBinary(dictDecoder, listener);
} catch (IOException e) {
Log.d(TAG, "IOException on opening a bytebuffer", e);
- } finally {
- if (PROFILE_SAVE_RESTORE) {
- final long diff = System.currentTimeMillis() - now;
- Log.d(TAG, "PROF: Load UserHistoryDictionary: "
- + locale + ", " + diff + "ms. load " + profTotalCount[0] + "entries.");
- }
+ } catch (UnsupportedFormatException e) {
+ Log.d(TAG, "Unsupported format, can't read the dictionary", e);
}
}
- protected String getLocale() {
- return mLocale;
- }
-
- public void registerUpdateSession(PersonalizationDictionaryUpdateSession session) {
- session.setPredictionDictionary(this);
- mSessions.add(session);
- session.onDictionaryReady();
- }
-
- public void unRegisterUpdateSession(PersonalizationDictionaryUpdateSession session) {
- mSessions.remove(session);
- }
-
@UsedForTesting
public void clearAndFlushDictionary() {
// Clear the node structure on memory
clear();
// Then flush the cleared state of the dictionary on disk.
- asyncFlashAllBinaryDictionary();
+ asyncFlushBinaryDictionary();
}
/* package */ void decayIfNeeded() {
diff --git a/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java b/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java
deleted file mode 100644
index 6f152bb91..000000000
--- a/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright (C) 2013 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.personalization;
-
-import android.content.Context;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.compat.ActivityManagerCompatUtils;
-import com.android.inputmethod.keyboard.ProximityInfo;
-import com.android.inputmethod.latin.AbstractDictionaryWriter;
-import com.android.inputmethod.latin.ExpandableDictionary;
-import com.android.inputmethod.latin.WordComposer;
-import com.android.inputmethod.latin.ExpandableDictionary.NextWord;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.makedict.DictEncoder;
-import com.android.inputmethod.latin.makedict.FormatSpec;
-import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
-import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils;
-import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils.BigramDictionaryInterface;
-import com.android.inputmethod.latin.utils.UserHistoryForgettingCurveUtils;
-import com.android.inputmethod.latin.utils.UserHistoryForgettingCurveUtils.ForgettingCurveParams;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Map;
-
-// Currently this class is used to implement dynamic prodiction dictionary.
-// TODO: Move to native code.
-public class DynamicPersonalizationDictionaryWriter extends AbstractDictionaryWriter {
- private static final String TAG = DynamicPersonalizationDictionaryWriter.class.getSimpleName();
- /** Maximum number of pairs. Pruning will start when databases goes above this number. */
- public static final int DEFAULT_MAX_HISTORY_BIGRAMS = 10000;
- public static final int LOW_MEMORY_MAX_HISTORY_BIGRAMS = 2000;
-
- /** Any pair being typed or picked */
- private static final int FREQUENCY_FOR_TYPED = 2;
-
- private static final int BINARY_DICT_VERSION = 3;
- private static final FormatSpec.FormatOptions FORMAT_OPTIONS =
- new FormatSpec.FormatOptions(BINARY_DICT_VERSION, true /* supportsDynamicUpdate */);
-
- private final UserHistoryDictionaryBigramList mBigramList =
- new UserHistoryDictionaryBigramList();
- private final ExpandableDictionary mExpandableDictionary;
- private final int mMaxHistoryBigrams;
-
- public DynamicPersonalizationDictionaryWriter(final Context context, final String dictType) {
- super(context, dictType);
- mExpandableDictionary = new ExpandableDictionary(dictType);
- final boolean isLowRamDevice = ActivityManagerCompatUtils.isLowRamDevice(context);
- mMaxHistoryBigrams = isLowRamDevice ?
- LOW_MEMORY_MAX_HISTORY_BIGRAMS : DEFAULT_MAX_HISTORY_BIGRAMS;
- }
-
- @Override
- public void clear() {
- mBigramList.evictAll();
- mExpandableDictionary.clearDictionary();
- }
-
- /**
- * Adds a word unigram to the fusion dictionary. Call updateBinaryDictionary when all changes
- * are done to update the binary dictionary.
- * @param word The word to add.
- * @param shortcutTarget A shortcut target for this word, or null if none.
- * @param frequency The frequency for this unigram.
- * @param shortcutFreq The frequency of the shortcut (0~15, with 15 = whitelist). Ignored
- * if shortcutTarget is null.
- * @param isNotAWord true if this is not a word, i.e. shortcut only.
- */
- @Override
- public void addUnigramWord(final String word, final String shortcutTarget, final int frequency,
- final int shortcutFreq, final boolean isNotAWord) {
- if (mBigramList.size() > mMaxHistoryBigrams * 2) {
- // Too many entries: just stop adding new vocabulary and wait next refresh.
- return;
- }
- mExpandableDictionary.addWord(word, shortcutTarget, frequency, shortcutFreq);
- mBigramList.addBigram(null, word, (byte)frequency);
- }
-
- @Override
- public void addBigramWords(final String word0, final String word1, final int frequency,
- final boolean isValid, final long lastModifiedTime) {
- if (mBigramList.size() > mMaxHistoryBigrams * 2) {
- // Too many entries: just stop adding new vocabulary and wait next refresh.
- return;
- }
- if (lastModifiedTime > 0) {
- mExpandableDictionary.setBigramAndGetFrequency(word0, word1,
- new ForgettingCurveParams(frequency, System.currentTimeMillis(),
- lastModifiedTime));
- mBigramList.addBigram(word0, word1, (byte)frequency);
- } else {
- mExpandableDictionary.setBigramAndGetFrequency(word0, word1,
- new ForgettingCurveParams(isValid));
- mBigramList.addBigram(word0, word1, (byte)frequency);
- }
- }
-
- @Override
- public void removeBigramWords(final String word0, final String word1) {
- if (mBigramList.removeBigram(word0, word1)) {
- mExpandableDictionary.removeBigram(word0, word1);
- }
- }
-
- @Override
- protected void writeDictionary(final DictEncoder dictEncoder,
- final Map<String, String> attributeMap) throws IOException, UnsupportedFormatException {
- UserHistoryDictIOUtils.writeDictionary(dictEncoder,
- new FrequencyProvider(mBigramList, mExpandableDictionary, mMaxHistoryBigrams),
- mBigramList, FORMAT_OPTIONS);
- }
-
- private static class FrequencyProvider implements BigramDictionaryInterface {
- private final UserHistoryDictionaryBigramList mBigramList;
- private final ExpandableDictionary mExpandableDictionary;
- private final int mMaxHistoryBigrams;
-
- public FrequencyProvider(final UserHistoryDictionaryBigramList bigramList,
- final ExpandableDictionary expandableDictionary, final int maxHistoryBigrams) {
- mBigramList = bigramList;
- mExpandableDictionary = expandableDictionary;
- mMaxHistoryBigrams = maxHistoryBigrams;
- }
-
- @Override
- public int getFrequency(final String word0, final String word1) {
- final int freq;
- if (word0 == null) { // unigram
- freq = FREQUENCY_FOR_TYPED;
- } else { // bigram
- final NextWord nw = mExpandableDictionary.getBigramWord(word0, word1);
- if (nw != null) {
- final ForgettingCurveParams forgettingCurveParams = nw.getFcParams();
- final byte prevFc = mBigramList.getBigrams(word0).get(word1);
- final byte fc = forgettingCurveParams.getFc();
- final boolean isValid = forgettingCurveParams.isValid();
- if (prevFc > 0 && prevFc == fc) {
- freq = fc & 0xFF;
- } else if (UserHistoryForgettingCurveUtils.
- needsToSave(fc, isValid, mBigramList.size() <= mMaxHistoryBigrams)) {
- freq = fc & 0xFF;
- } else {
- // Delete this entry
- freq = -1;
- }
- } else {
- // Delete this entry
- freq = -1;
- }
- }
- return freq;
- }
- }
-
- @Override
- public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final String prevWord, final ProximityInfo proximityInfo,
- boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
- return mExpandableDictionary.getSuggestions(composer, prevWord, proximityInfo,
- blockOffensiveWords, additionalFeaturesOptions);
- }
-
- @Override
- public boolean isValidWord(final String word) {
- return mExpandableDictionary.isValidWord(word);
- }
-
- @UsedForTesting
- public boolean isInBigramListForTests(final String word) {
- // TODO: Use native method to determine whether the word is in dictionary or not
- return mBigramList.containsKey(word) || mBigramList.getBigrams(null).containsKey(word);
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
index f257165cb..596562f1d 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
@@ -16,53 +16,37 @@
package com.android.inputmethod.latin.personalization;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.ExpandableBinaryDictionary;
import com.android.inputmethod.latin.utils.CollectionUtils;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Locale;
+
import android.content.Context;
-import android.content.SharedPreferences;
-import java.util.ArrayList;
+public class PersonalizationDictionary extends DecayingExpandableBinaryDictionaryBase {
+ private static final String NAME = PersonalizationDictionary.class.getSimpleName();
-/**
- * This class is a dictionary for the personalized language model that uses binary dictionary.
- */
-public class PersonalizationDictionary extends ExpandableBinaryDictionary {
- private static final String NAME = "personalization";
private final ArrayList<PersonalizationDictionaryUpdateSession> mSessions =
CollectionUtils.newArrayList();
- /** Locale for which this user history dictionary is storing words */
- private final String mLocale;
-
- public PersonalizationDictionary(final Context context, final String locale,
- final SharedPreferences prefs) {
- // TODO: Make isUpdatable true.
- super(context, getFilenameWithLocale(NAME, locale), Dictionary.TYPE_PERSONALIZATION,
- false /* isUpdatable */);
- mLocale = locale;
- // TODO: Restore last updated time
- loadDictionary();
- }
-
- @Override
- protected void loadDictionaryAsync() {
- // TODO: Implement
- }
-
- @Override
- protected boolean hasContentChanged() {
- return false;
+ /* package */ PersonalizationDictionary(final Context context, final Locale locale) {
+ super(context, locale, Dictionary.TYPE_PERSONALIZATION,
+ getDictNameWithLocale(NAME, locale));
}
- @Override
- protected boolean needsToReloadBeforeWriting() {
- return false;
+ // Creates an instance that uses a given dictionary file for testing.
+ @UsedForTesting
+ public PersonalizationDictionary(final Context context, final Locale locale,
+ final File dictFile) {
+ super(context, locale, Dictionary.TYPE_PERSONALIZATION, getDictNameWithLocale(NAME, locale),
+ dictFile);
}
public void registerUpdateSession(PersonalizationDictionaryUpdateSession session) {
- session.setDictionary(this);
+ session.setPredictionDictionary(this);
mSessions.add(session);
session.onDictionaryReady();
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegister.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegister.java
index c1833ff14..542bda621 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegister.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegister.java
@@ -20,18 +20,18 @@ import android.content.Context;
import android.content.res.Configuration;
public class PersonalizationDictionarySessionRegister {
- public static void init(Context context) {
+ public static void init(final Context context) {
}
public static void onConfigurationChanged(final Context context, final Configuration conf) {
}
- public static void onUpdateData(Context context, String type) {
+ public static void onUpdateData(final Context context, final String type) {
}
- public static void onRemoveData(Context context, String type) {
+ public static void onRemoveData(final Context context, final String type) {
}
- public static void onDestroy(Context context) {
+ public static void onDestroy(final Context context) {
}
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java
index a86f6e584..61354762b 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java
@@ -18,61 +18,37 @@ package com.android.inputmethod.latin.personalization;
import android.content.Context;
+import com.android.inputmethod.latin.BinaryDictionary.LanguageModelParam;
+import com.android.inputmethod.latin.ExpandableBinaryDictionary;
+
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Locale;
/**
* This class is a session where a data provider can communicate with a personalization
* dictionary.
*/
public abstract class PersonalizationDictionaryUpdateSession {
- /**
- * This class is a parameter for a new unigram or bigram word which will be added
- * to the personalization dictionary.
- */
- public static class PersonalizationLanguageModelParam {
- public final String mWord0;
- public final String mWord1;
- public final boolean mIsValid;
- public final int mFrequency;
- public PersonalizationLanguageModelParam(String word0, String word1, boolean isValid,
- int frequency) {
- mWord0 = word0;
- mWord1 = word1;
- mIsValid = isValid;
- mFrequency = frequency;
- }
- }
-
- // TODO: Use a dynamic binary dictionary instead
public WeakReference<PersonalizationDictionary> mDictionary;
- public WeakReference<DecayingExpandableBinaryDictionaryBase> mPredictionDictionary;
- public final String mSystemLocale;
- public PersonalizationDictionaryUpdateSession(String locale) {
+ public final Locale mSystemLocale;
+
+ public PersonalizationDictionaryUpdateSession(final Locale locale) {
mSystemLocale = locale;
}
public abstract void onDictionaryReady();
- public abstract void onDictionaryClosed(Context context);
+ public abstract void onDictionaryClosed(final Context context);
- public void setDictionary(PersonalizationDictionary dictionary) {
+ public void setPredictionDictionary(final PersonalizationDictionary dictionary) {
mDictionary = new WeakReference<PersonalizationDictionary>(dictionary);
}
- public void setPredictionDictionary(DecayingExpandableBinaryDictionaryBase dictionary) {
- mPredictionDictionary =
- new WeakReference<DecayingExpandableBinaryDictionaryBase>(dictionary);
- }
-
protected PersonalizationDictionary getDictionary() {
return mDictionary == null ? null : mDictionary.get();
}
- protected DecayingExpandableBinaryDictionaryBase getPredictionDictionary() {
- return mPredictionDictionary == null ? null : mPredictionDictionary.get();
- }
-
private void unsetDictionary() {
final PersonalizationDictionary dictionary = getDictionary();
if (dictionary == null) {
@@ -81,48 +57,30 @@ public abstract class PersonalizationDictionaryUpdateSession {
dictionary.unRegisterUpdateSession(this);
}
- private void unsetPredictionDictionary() {
- final DecayingExpandableBinaryDictionaryBase dictionary = getPredictionDictionary();
- if (dictionary == null) {
- return;
- }
- dictionary.unRegisterUpdateSession(this);
- }
-
- public void clearAndFlushPredictionDictionary(Context context) {
- final DecayingExpandableBinaryDictionaryBase dictionary = getPredictionDictionary();
+ public void clearAndFlushDictionary(final Context context) {
+ final PersonalizationDictionary dictionary = getDictionary();
if (dictionary == null) {
return;
}
dictionary.clearAndFlushDictionary();
}
- public void closeSession(Context context) {
+ public void closeSession(final Context context) {
unsetDictionary();
- unsetPredictionDictionary();
onDictionaryClosed(context);
}
- // TODO: Support multi locale to add bigram
- public void addBigramToPersonalizationDictionary(String word0, String word1, boolean isValid,
- int frequency) {
- final DecayingExpandableBinaryDictionaryBase dictionary = getPredictionDictionary();
- if (dictionary == null) {
- return;
- }
- dictionary.addToDictionary(word0, word1, isValid);
- }
-
- // Bulk import
- // TODO: Support multi locale to add bigram
- public void addBigramsToPersonalizationDictionary(
- final ArrayList<PersonalizationLanguageModelParam> lmParams) {
- final DecayingExpandableBinaryDictionaryBase dictionary = getPredictionDictionary();
+ // TODO: Support multi locale.
+ public void addMultipleDictionaryEntriesToDictionary(
+ final ArrayList<LanguageModelParam> languageModelParams,
+ final ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback callback) {
+ final PersonalizationDictionary dictionary = getDictionary();
if (dictionary == null) {
+ if (callback != null) {
+ callback.onFinished();
+ }
return;
}
- for (final PersonalizationLanguageModelParam lmParam : lmParams) {
- dictionary.addToDictionary(lmParam.mWord0, lmParam.mWord1, lmParam.mIsValid);
- }
+ dictionary.addMultipleDictionaryEntriesToDictionary(languageModelParams, callback);
}
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
index 221ddeeba..d55cae132 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
@@ -19,11 +19,10 @@ package com.android.inputmethod.latin.personalization;
import com.android.inputmethod.latin.utils.CollectionUtils;
import android.content.Context;
-import android.content.SharedPreferences;
-import android.preference.PreferenceManager;
import android.util.Log;
import java.lang.ref.SoftReference;
+import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
public class PersonalizationHelper {
@@ -31,21 +30,16 @@ public class PersonalizationHelper {
private static final boolean DEBUG = false;
private static final ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>
sLangUserHistoryDictCache = CollectionUtils.newConcurrentHashMap();
-
private static final ConcurrentHashMap<String, SoftReference<PersonalizationDictionary>>
sLangPersonalizationDictCache = CollectionUtils.newConcurrentHashMap();
- private static final ConcurrentHashMap<String,
- SoftReference<PersonalizationPredictionDictionary>>
- sLangPersonalizationPredictionDictCache =
- CollectionUtils.newConcurrentHashMap();
-
public static UserHistoryDictionary getUserHistoryDictionary(
- final Context context, final String locale, final SharedPreferences sp) {
+ final Context context, final Locale locale) {
+ final String localeStr = locale.toString();
synchronized (sLangUserHistoryDictCache) {
- if (sLangUserHistoryDictCache.containsKey(locale)) {
+ if (sLangUserHistoryDictCache.containsKey(localeStr)) {
final SoftReference<UserHistoryDictionary> ref =
- sLangUserHistoryDictCache.get(locale);
+ sLangUserHistoryDictCache.get(localeStr);
final UserHistoryDictionary dict = ref == null ? null : ref.get();
if (dict != null) {
if (DEBUG) {
@@ -55,8 +49,9 @@ public class PersonalizationHelper {
return dict;
}
}
- final UserHistoryDictionary dict = new UserHistoryDictionary(context, locale, sp);
- sLangUserHistoryDictCache.put(locale, new SoftReference<UserHistoryDictionary>(dict));
+ final UserHistoryDictionary dict = new UserHistoryDictionary(context, locale);
+ sLangUserHistoryDictCache.put(localeStr,
+ new SoftReference<UserHistoryDictionary>(dict));
return dict;
}
}
@@ -74,57 +69,30 @@ public class PersonalizationHelper {
}
public static void registerPersonalizationDictionaryUpdateSession(final Context context,
- final PersonalizationDictionaryUpdateSession session, String locale) {
- final PersonalizationPredictionDictionary predictionDictionary =
- getPersonalizationPredictionDictionary(context, locale,
- PreferenceManager.getDefaultSharedPreferences(context));
- predictionDictionary.registerUpdateSession(session);
- final PersonalizationDictionary dictionary =
- getPersonalizationDictionary(context, locale,
- PreferenceManager.getDefaultSharedPreferences(context));
- dictionary.registerUpdateSession(session);
+ final PersonalizationDictionaryUpdateSession session, final Locale locale) {
+ final PersonalizationDictionary personalizationDictionary =
+ getPersonalizationDictionary(context, locale);
+ personalizationDictionary.registerUpdateSession(session);
}
public static PersonalizationDictionary getPersonalizationDictionary(
- final Context context, final String locale, final SharedPreferences sp) {
+ final Context context, final Locale locale) {
+ final String localeStr = locale.toString();
synchronized (sLangPersonalizationDictCache) {
- if (sLangPersonalizationDictCache.containsKey(locale)) {
+ if (sLangPersonalizationDictCache.containsKey(localeStr)) {
final SoftReference<PersonalizationDictionary> ref =
- sLangPersonalizationDictCache.get(locale);
+ sLangPersonalizationDictCache.get(localeStr);
final PersonalizationDictionary dict = ref == null ? null : ref.get();
if (dict != null) {
if (DEBUG) {
- Log.w(TAG, "Use cached PersonalizationDictCache for " + locale);
+ Log.w(TAG, "Use cached PersonalizationDictionary for " + locale);
}
return dict;
}
}
- final PersonalizationDictionary dict =
- new PersonalizationDictionary(context, locale, sp);
+ final PersonalizationDictionary dict = new PersonalizationDictionary(context, locale);
sLangPersonalizationDictCache.put(
- locale, new SoftReference<PersonalizationDictionary>(dict));
- return dict;
- }
- }
-
- public static PersonalizationPredictionDictionary getPersonalizationPredictionDictionary(
- final Context context, final String locale, final SharedPreferences sp) {
- synchronized (sLangPersonalizationPredictionDictCache) {
- if (sLangPersonalizationPredictionDictCache.containsKey(locale)) {
- final SoftReference<PersonalizationPredictionDictionary> ref =
- sLangPersonalizationPredictionDictCache.get(locale);
- final PersonalizationPredictionDictionary dict = ref == null ? null : ref.get();
- if (dict != null) {
- if (DEBUG) {
- Log.w(TAG, "Use cached PersonalizationPredictionDictionary for " + locale);
- }
- return dict;
- }
- }
- final PersonalizationPredictionDictionary dict =
- new PersonalizationPredictionDictionary(context, locale, sp);
- sLangPersonalizationPredictionDictCache.put(
- locale, new SoftReference<PersonalizationPredictionDictionary>(dict));
+ localeStr, new SoftReference<PersonalizationDictionary>(dict));
return dict;
}
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java
deleted file mode 100644
index 432954453..000000000
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2013 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.personalization;
-
-import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.ExpandableBinaryDictionary;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-
-public class PersonalizationPredictionDictionary extends DecayingExpandableBinaryDictionaryBase {
- private static final String NAME = PersonalizationPredictionDictionary.class.getSimpleName();
-
- /* package */ PersonalizationPredictionDictionary(final Context context, final String locale,
- final SharedPreferences sp) {
- super(context, locale, sp, Dictionary.TYPE_PERSONALIZATION_PREDICTION_IN_JAVA,
- getDictionaryFileName(locale));
- }
-
- private static String getDictionaryFileName(final String locale) {
- return NAME + "." + locale + ExpandableBinaryDictionary.DICT_FILE_EXTENSION;
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
index a60226d7e..868f21cbc 100644
--- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
@@ -16,11 +16,13 @@
package com.android.inputmethod.latin.personalization;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.ExpandableBinaryDictionary;
+
+import java.io.File;
+import java.util.Locale;
import android.content.Context;
-import android.content.SharedPreferences;
/**
* Locally gathers stats about the words user types and various other signals like auto-correction
@@ -29,12 +31,19 @@ import android.content.SharedPreferences;
public class UserHistoryDictionary extends DecayingExpandableBinaryDictionaryBase {
/* package for tests */ static final String NAME =
UserHistoryDictionary.class.getSimpleName();
- /* package */ UserHistoryDictionary(final Context context, final String locale,
- final SharedPreferences sp) {
- super(context, locale, sp, Dictionary.TYPE_USER_HISTORY, getDictionaryFileName(locale));
+ /* package */ UserHistoryDictionary(final Context context, final Locale locale) {
+ super(context, locale, Dictionary.TYPE_USER_HISTORY, getDictNameWithLocale(NAME, locale));
+ }
+
+ // Creates an instance that uses a given dictionary file for testing.
+ @UsedForTesting
+ public UserHistoryDictionary(final Context context, final Locale locale,
+ final File dictFile) {
+ super(context, locale, Dictionary.TYPE_USER_HISTORY, getDictNameWithLocale(NAME, locale),
+ dictFile);
}
- private static String getDictionaryFileName(final String locale) {
- return NAME + "." + locale + ExpandableBinaryDictionary.DICT_FILE_EXTENSION;
+ public void cancelAddingUserHistory(final String word0, final String word1) {
+ removeBigramDynamically(word0, word1);
}
}
diff --git a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
index da1fb73fe..d060485bd 100644
--- a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
@@ -112,8 +112,7 @@ public final class DebugSettings extends PreferenceFragment
updateDebugMode();
mServiceNeedsRestart = true;
}
- } else if (key.equals(PREF_FORCE_NON_DISTINCT_MULTITOUCH)
- || key.equals(PREF_USE_ONLY_PERSONALIZATION_DICTIONARY_FOR_DEBUG)) {
+ } else if (key.equals(PREF_FORCE_NON_DISTINCT_MULTITOUCH)) {
mServiceNeedsRestart = true;
}
}
diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java
index df2c6907f..714c3a97a 100644
--- a/java/src/com/android/inputmethod/latin/settings/Settings.java
+++ b/java/src/com/android/inputmethod/latin/settings/Settings.java
@@ -27,12 +27,10 @@ import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
import com.android.inputmethod.latin.InputAttributes;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
-import com.android.inputmethod.latin.utils.LocaleUtils;
import com.android.inputmethod.latin.utils.ResourceUtils;
import com.android.inputmethod.latin.utils.RunInLocale;
import com.android.inputmethod.latin.utils.StringUtils;
-import java.util.HashMap;
import java.util.Locale;
import java.util.concurrent.locks.ReentrantLock;
@@ -53,8 +51,6 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public static final String PREF_AUTO_CORRECTION_THRESHOLD = "auto_correction_threshold";
public static final String PREF_SHOW_SUGGESTIONS_SETTING = "show_suggestions_setting";
public static final String PREF_MISC_SETTINGS = "misc_settings";
- public static final String PREF_LAST_USER_DICTIONARY_WRITE_TIME =
- "last_user_dictionary_write_time";
public static final String PREF_ADVANCED_SETTINGS = "pref_advanced_settings";
public static final String PREF_KEY_USE_CONTACTS_DICT = "pref_key_use_contacts_dict";
public static final String PREF_KEY_USE_DOUBLE_SPACE_PERIOD =
@@ -229,16 +225,15 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
res.getBoolean(R.bool.config_default_phrase_gesture_enabled));
}
- public static boolean readFromBuildConfigIfToShowKeyPreviewPopupSettingsOption(
- final Resources res) {
- return res.getBoolean(R.bool.config_enable_show_option_of_key_preview_popup);
+ public static boolean readFromBuildConfigIfToShowKeyPreviewPopupOption(final Resources res) {
+ return res.getBoolean(R.bool.config_enable_show_key_preview_popup_option);
}
public static boolean readKeyPreviewPopupEnabled(final SharedPreferences prefs,
final Resources res) {
final boolean defaultKeyPreviewPopup = res.getBoolean(
R.bool.config_default_key_preview_popup);
- if (!readFromBuildConfigIfToShowKeyPreviewPopupSettingsOption(res)) {
+ if (!readFromBuildConfigIfToShowKeyPreviewPopupOption(res)) {
return defaultKeyPreviewPopup;
}
return prefs.getBoolean(PREF_POPUP_ON, defaultKeyPreviewPopup);
@@ -333,25 +328,6 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
return prefs.getBoolean(DebugSettings.PREF_USABILITY_STUDY_MODE, true);
}
- public static long readLastUserHistoryWriteTime(final SharedPreferences prefs,
- final String locale) {
- final String str = prefs.getString(PREF_LAST_USER_DICTIONARY_WRITE_TIME, "");
- final HashMap<String, Long> map = LocaleUtils.localeAndTimeStrToHashMap(str);
- if (map.containsKey(locale)) {
- return map.get(locale);
- }
- return 0;
- }
-
- public static void writeLastUserHistoryWriteTime(final SharedPreferences prefs,
- final String locale) {
- final String oldStr = prefs.getString(PREF_LAST_USER_DICTIONARY_WRITE_TIME, "");
- final HashMap<String, Long> map = LocaleUtils.localeAndTimeStrToHashMap(oldStr);
- map.put(locale, System.currentTimeMillis());
- final String newStr = LocaleUtils.localeAndTimeHashMapToStr(map);
- prefs.edit().putString(PREF_LAST_USER_DICTIONARY_WRITE_TIME, newStr).apply();
- }
-
public static boolean readUseFullscreenMode(final Resources res) {
return res.getBoolean(R.bool.config_use_fullscreen_mode);
}
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
index 5c60a7350..d7a3e95b3 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
@@ -48,7 +48,6 @@ import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
import com.android.inputmethod.latin.utils.ApplicationUtils;
import com.android.inputmethod.latin.utils.FeedbackUtils;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
-import com.android.inputmethod.research.ResearchLogger;
import com.android.inputmethodcommon.InputMethodSettingsFragment;
import java.util.TreeSet;
@@ -61,13 +60,6 @@ public final class SettingsFragment extends InputMethodSettingsFragment
DBG_USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS
|| Build.VERSION.SDK_INT <= 18 /* Build.VERSION.JELLY_BEAN_MR2 */;
- private CheckBoxPreference mVoiceInputKeyPreference;
- private ListPreference mShowCorrectionSuggestionsPreference;
- private ListPreference mAutoCorrectionThresholdPreference;
- private ListPreference mKeyPreviewPopupDismissDelay;
- // Use bigrams to predict the next word when there is no input for it yet
- private CheckBoxPreference mBigramPrediction;
-
private void setPreferenceEnabled(final String preferenceKey, final boolean enabled) {
final Preference preference = findPreference(preferenceKey);
if (preference != null) {
@@ -75,6 +67,18 @@ public final class SettingsFragment extends InputMethodSettingsFragment
}
}
+ private void updateListPreferenceSummaryToCurrentValue(final String prefKey) {
+ // Because the "%s" summary trick of {@link ListPreference} doesn't work properly before
+ // KitKat, we need to update the summary programmatically.
+ final ListPreference listPreference = (ListPreference)findPreference(prefKey);
+ if (listPreference == null) {
+ return;
+ }
+ final CharSequence entries[] = listPreference.getEntries();
+ final int entryIndex = listPreference.findIndexOfValue(listPreference.getValue());
+ listPreference.setSummary(entryIndex < 0 ? null : entries[entryIndex]);
+ }
+
private static void removePreference(final String preferenceKey, final PreferenceGroup parent) {
if (parent == null) {
return;
@@ -107,16 +111,9 @@ public final class SettingsFragment extends InputMethodSettingsFragment
SubtypeLocaleUtils.init(context);
AudioAndHapticFeedbackManager.init(context);
- mVoiceInputKeyPreference =
- (CheckBoxPreference) findPreference(Settings.PREF_VOICE_INPUT_KEY);
- mShowCorrectionSuggestionsPreference =
- (ListPreference) findPreference(Settings.PREF_SHOW_SUGGESTIONS_SETTING);
final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
prefs.registerOnSharedPreferenceChangeListener(this);
- mAutoCorrectionThresholdPreference =
- (ListPreference) findPreference(Settings.PREF_AUTO_CORRECTION_THRESHOLD);
- mBigramPrediction = (CheckBoxPreference) findPreference(Settings.PREF_BIGRAM_PREDICTIONS);
ensureConsistencyOfAutoCorrectionSettings();
final PreferenceGroup generalSettings =
@@ -143,12 +140,7 @@ public final class SettingsFragment extends InputMethodSettingsFragment
feedbackSettings.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(final Preference pref) {
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- // Use development-only feedback mechanism
- ResearchLogger.getInstance().presentFeedbackDialogFromSettings();
- } else {
- FeedbackUtils.showFeedbackForm(getActivity());
- }
+ FeedbackUtils.showFeedbackForm(getActivity());
return true;
}
});
@@ -167,7 +159,7 @@ public final class SettingsFragment extends InputMethodSettingsFragment
final boolean showVoiceKeyOption = res.getBoolean(
R.bool.config_enable_show_voice_key_option);
if (!showVoiceKeyOption) {
- generalSettings.removePreference(mVoiceInputKeyPreference);
+ removePreference(Settings.PREF_VOICE_INPUT_KEY, generalSettings);
}
final PreferenceGroup advancedSettings =
@@ -177,26 +169,27 @@ public final class SettingsFragment extends InputMethodSettingsFragment
removePreference(Settings.PREF_VIBRATION_DURATION_SETTINGS, advancedSettings);
}
- mKeyPreviewPopupDismissDelay =
- (ListPreference) findPreference(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
- if (!Settings.readFromBuildConfigIfToShowKeyPreviewPopupSettingsOption(res)) {
+ if (!Settings.readFromBuildConfigIfToShowKeyPreviewPopupOption(res)) {
removePreference(Settings.PREF_POPUP_ON, generalSettings);
removePreference(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY, advancedSettings);
} else {
+ // TODO: Cleanup this setup.
+ final ListPreference keyPreviewPopupDismissDelay =
+ (ListPreference) findPreference(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
final String popupDismissDelayDefaultValue = Integer.toString(res.getInteger(
R.integer.config_key_preview_linger_timeout));
- mKeyPreviewPopupDismissDelay.setEntries(new String[] {
+ keyPreviewPopupDismissDelay.setEntries(new String[] {
res.getString(R.string.key_preview_popup_dismiss_no_delay),
res.getString(R.string.key_preview_popup_dismiss_default_delay),
});
- mKeyPreviewPopupDismissDelay.setEntryValues(new String[] {
+ keyPreviewPopupDismissDelay.setEntryValues(new String[] {
"0",
popupDismissDelayDefaultValue
});
- if (null == mKeyPreviewPopupDismissDelay.getValue()) {
- mKeyPreviewPopupDismissDelay.setValue(popupDismissDelayDefaultValue);
+ if (null == keyPreviewPopupDismissDelay.getValue()) {
+ keyPreviewPopupDismissDelay.setValue(popupDismissDelayDefaultValue);
}
- mKeyPreviewPopupDismissDelay.setEnabled(
+ keyPreviewPopupDismissDelay.setEnabled(
Settings.readKeyPreviewPopupEnabled(prefs, res));
}
@@ -243,20 +236,19 @@ public final class SettingsFragment extends InputMethodSettingsFragment
@Override
public void onResume() {
super.onResume();
- final boolean isShortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled();
- if (!isShortcutImeEnabled) {
- getPreferenceScreen().removePreference(mVoiceInputKeyPreference);
- }
final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
+ final Resources res = getResources();
+ final boolean isShortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled();
+ setPreferenceEnabled(Settings.PREF_VOICE_INPUT_KEY, isShortcutImeEnabled);
final CheckBoxPreference showSetupWizardIcon =
(CheckBoxPreference)findPreference(Settings.PREF_SHOW_SETUP_WIZARD_ICON);
if (showSetupWizardIcon != null) {
showSetupWizardIcon.setChecked(Settings.readShowSetupWizardIcon(prefs, getActivity()));
}
- updateShowCorrectionSuggestionsSummary();
- updateKeyPreviewPopupDelaySummary();
- updateColorSchemeSummary(prefs, getResources());
- updateCustomInputStylesSummary();
+ updateListPreferenceSummaryToCurrentValue(Settings.PREF_SHOW_SUGGESTIONS_SETTING);
+ updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
+ updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEYBOARD_LAYOUT);
+ updateCustomInputStylesSummary(prefs, res);
}
@Override
@@ -287,50 +279,26 @@ public final class SettingsFragment extends InputMethodSettingsFragment
LauncherIconVisibilityManager.updateSetupWizardIconVisibility(getActivity());
}
ensureConsistencyOfAutoCorrectionSettings();
- updateShowCorrectionSuggestionsSummary();
- updateKeyPreviewPopupDelaySummary();
- updateColorSchemeSummary(prefs, res);
+ updateListPreferenceSummaryToCurrentValue(Settings.PREF_SHOW_SUGGESTIONS_SETTING);
+ updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
+ updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEYBOARD_LAYOUT);
refreshEnablingsOfKeypressSoundAndVibrationSettings(prefs, getResources());
}
private void ensureConsistencyOfAutoCorrectionSettings() {
final String autoCorrectionOff = getResources().getString(
R.string.auto_correction_threshold_mode_index_off);
- final String currentSetting = mAutoCorrectionThresholdPreference.getValue();
- mBigramPrediction.setEnabled(!currentSetting.equals(autoCorrectionOff));
- }
-
- private void updateShowCorrectionSuggestionsSummary() {
- mShowCorrectionSuggestionsPreference.setSummary(
- getResources().getStringArray(R.array.prefs_suggestion_visibilities)
- [mShowCorrectionSuggestionsPreference.findIndexOfValue(
- mShowCorrectionSuggestionsPreference.getValue())]);
+ final ListPreference autoCorrectionThresholdPref = (ListPreference)findPreference(
+ Settings.PREF_AUTO_CORRECTION_THRESHOLD);
+ final String currentSetting = autoCorrectionThresholdPref.getValue();
+ setPreferenceEnabled(
+ Settings.PREF_BIGRAM_PREDICTIONS, !currentSetting.equals(autoCorrectionOff));
}
- private void updateColorSchemeSummary(final SharedPreferences prefs, final Resources res) {
- // Because the "%s" summary trick of {@link ListPreference} doesn't work properly before
- // KitKat, we need to update the summary by code.
- final Preference preference = findPreference(Settings.PREF_KEYBOARD_LAYOUT);
- if (!(preference instanceof ListPreference)) {
- Log.w(TAG, "Can't find Keyboard Color Scheme preference");
- return;
- }
- final ListPreference colorSchemePreference = (ListPreference)preference;
- final int themeIndex = Settings.readKeyboardThemeIndex(prefs, res);
- int entryIndex = colorSchemePreference.findIndexOfValue(Integer.toString(themeIndex));
- if (entryIndex < 0) {
- final int defaultThemeIndex = Settings.resetAndGetDefaultKeyboardThemeIndex(prefs, res);
- entryIndex = colorSchemePreference.findIndexOfValue(
- Integer.toString(defaultThemeIndex));
- }
- colorSchemePreference.setSummary(colorSchemePreference.getEntries()[entryIndex]);
- }
-
- private void updateCustomInputStylesSummary() {
+ private void updateCustomInputStylesSummary(final SharedPreferences prefs,
+ final Resources res) {
final PreferenceScreen customInputStyles =
(PreferenceScreen)findPreference(Settings.PREF_CUSTOM_INPUT_STYLES);
- final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
- final Resources res = getResources();
final String prefSubtype = Settings.readPrefAdditionalSubtypes(prefs, res);
final InputMethodSubtype[] subtypes =
AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefSubtype);
@@ -342,13 +310,6 @@ public final class SettingsFragment extends InputMethodSettingsFragment
customInputStyles.setSummary(styles);
}
- private void updateKeyPreviewPopupDelaySummary() {
- final ListPreference lp = mKeyPreviewPopupDismissDelay;
- final CharSequence[] entries = lp.getEntries();
- if (entries == null || entries.length <= 0) return;
- lp.setSummary(entries[lp.findIndexOfValue(lp.getValue())]);
- }
-
private void refreshEnablingsOfKeypressSoundAndVibrationSettings(
final SharedPreferences sp, final Resources res) {
setPreferenceEnabled(Settings.PREF_VIBRATION_DURATION_SETTINGS,
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index f331c78e5..06406c19b 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -67,7 +67,7 @@ public final class SettingsValues {
public final boolean mVibrateOn;
public final boolean mSoundOn;
public final boolean mKeyPreviewPopupOn;
- private final boolean mShowsVoiceInputKey;
+ public final boolean mShowsVoiceInputKey;
public final boolean mIncludesOtherImesInLanguageSwitchList;
public final boolean mShowsLanguageSwitchKey;
public final boolean mUseContactsDict;
@@ -271,13 +271,6 @@ public final class SettingsValues {
return mInputAttributes.mShouldInsertSpacesAutomatically;
}
- public boolean isVoiceKeyEnabled(final EditorInfo editorInfo) {
- final boolean shortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled();
- final int inputType = (editorInfo != null) ? editorInfo.inputType : 0;
- return shortcutImeEnabled && mShowsVoiceInputKey
- && !InputTypeUtils.isPasswordInputType(inputType);
- }
-
public boolean isLanguageSwitchKeyEnabled() {
if (!mShowsLanguageSwitchKey) {
return false;
@@ -374,16 +367,20 @@ public final class SettingsValues {
return autoCorrectionThreshold;
}
- private static boolean needsToShowVoiceInputKey(SharedPreferences prefs, Resources res) {
- final String voiceModeMain = res.getString(R.string.voice_mode_main);
- final String voiceMode = prefs.getString(Settings.PREF_VOICE_MODE_OBSOLETE, voiceModeMain);
- final boolean showsVoiceInputKey = voiceMode == null || voiceMode.equals(voiceModeMain);
- if (!showsVoiceInputKey) {
- // Migrate settings from PREF_VOICE_MODE_OBSOLETE to PREF_VOICE_INPUT_KEY
- // Set voiceModeMain as a value of obsolete voice mode settings.
- prefs.edit().putString(Settings.PREF_VOICE_MODE_OBSOLETE, voiceModeMain).apply();
- // Disable voice input key.
- prefs.edit().putBoolean(Settings.PREF_VOICE_INPUT_KEY, false).apply();
+ private static boolean needsToShowVoiceInputKey(final SharedPreferences prefs,
+ final Resources res) {
+ if (!prefs.contains(Settings.PREF_VOICE_INPUT_KEY)) {
+ // Migrate preference from {@link Settings#PREF_VOICE_MODE_OBSOLETE} to
+ // {@link Settings#PREF_VOICE_INPUT_KEY}.
+ final String voiceModeMain = res.getString(R.string.voice_mode_main);
+ final String voiceMode = prefs.getString(
+ Settings.PREF_VOICE_MODE_OBSOLETE, voiceModeMain);
+ final boolean shouldShowVoiceInputKey = voiceModeMain.equals(voiceMode);
+ prefs.edit().putBoolean(Settings.PREF_VOICE_INPUT_KEY, shouldShowVoiceInputKey).apply();
+ }
+ // Remove the obsolete preference if exists.
+ if (prefs.contains(Settings.PREF_VOICE_MODE_OBSOLETE)) {
+ prefs.edit().remove(Settings.PREF_VOICE_MODE_OBSOLETE).apply();
}
return prefs.getBoolean(Settings.PREF_VOICE_INPUT_KEY, true);
}
diff --git a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java
index c4a813c24..5072fabd6 100644
--- a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java
+++ b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java
@@ -38,7 +38,7 @@ import com.android.inputmethod.compat.ViewCompatUtils;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.settings.SettingsActivity;
import com.android.inputmethod.latin.utils.CollectionUtils;
-import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper;
+import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
import java.util.ArrayList;
@@ -74,21 +74,21 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL
private SettingsPoolingHandler mHandler;
private static final class SettingsPoolingHandler
- extends StaticInnerHandlerWrapper<SetupWizardActivity> {
+ extends LeakGuardHandlerWrapper<SetupWizardActivity> {
private static final int MSG_POLLING_IME_SETTINGS = 0;
private static final long IME_SETTINGS_POLLING_INTERVAL = 200;
private final InputMethodManager mImmInHandler;
- public SettingsPoolingHandler(final SetupWizardActivity outerInstance,
+ public SettingsPoolingHandler(final SetupWizardActivity ownerInstance,
final InputMethodManager imm) {
- super(outerInstance);
+ super(ownerInstance);
mImmInHandler = imm;
}
@Override
public void handleMessage(final Message msg) {
- final SetupWizardActivity setupWizardActivity = getOuterInstance();
+ final SetupWizardActivity setupWizardActivity = getOwnerInstance();
if (setupWizardActivity == null) {
return;
}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 503b18b1b..c108b20ed 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -428,7 +428,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
final String localeStr = locale.toString();
UserBinaryDictionary userDictionary = mUserDictionaries.get(localeStr);
if (null == userDictionary) {
- userDictionary = new SynchronouslyLoadedUserBinaryDictionary(this, localeStr, true);
+ userDictionary = new SynchronouslyLoadedUserBinaryDictionary(this, locale, true);
mUserDictionaries.put(localeStr, userDictionary);
}
dictionaryCollection.addDictionary(userDictionary);
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
index a0aed2829..b7a5a4026 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
@@ -49,6 +49,7 @@ public final class DictionaryPool extends LinkedBlockingQueue<DictAndKeyboard> {
final static ArrayList<SuggestedWordInfo> noSuggestions = CollectionUtils.newArrayList();
private final static DictAndKeyboard dummyDict = new DictAndKeyboard(
new Dictionary(Dictionary.TYPE_MAIN) {
+ // TODO: this dummy dictionary should be a singleton in the Dictionary class.
@Override
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
final String prevWord, final ProximityInfo proximityInfo,
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index acd47450b..52012c846 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -66,7 +66,8 @@ public final class MoreSuggestions extends Keyboard {
clearKeys();
mDivider = res.getDrawable(R.drawable.more_suggestions_divider);
mDividerWidth = mDivider.getIntrinsicWidth();
- final float padding = res.getDimension(R.dimen.more_suggestions_key_horizontal_padding);
+ final float padding = res.getDimension(
+ R.dimen.config_more_suggestions_key_horizontal_padding);
int row = 0;
int index = fromIndex;
@@ -75,7 +76,7 @@ public final class MoreSuggestions extends Keyboard {
while (index < size) {
final String word = suggestedWords.getWord(index);
// TODO: Should take care of text x-scaling.
- mWidths[index] = (int)(TypefaceUtils.getLabelWidth(word, paint) + padding);
+ mWidths[index] = (int)(TypefaceUtils.getStringWidth(word, paint) + padding);
final int numColumn = index - rowStartIndex + 1;
final int columnWidth =
(maxWidth - mDividerWidth * (numColumn - 1)) / numColumn;
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
index 0ebe37782..549ff0d9d 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
@@ -54,7 +54,7 @@ public final class MoreSuggestionsView extends MoreKeysKeyboardView {
public void adjustVerticalCorrectionForModalMode() {
// Set vertical correction to zero (Reset more keys keyboard sliding allowance
- // {@link R#dimen.more_keys_keyboard_slide_allowance}).
+ // {@link R#dimen.config_more_keys_keyboard_slide_allowance}).
mKeyDetector.setKeyboard(getKeyboard(), -getPaddingLeft(), -getPaddingTop());
}
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
index faa5560e4..72281e62c 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
@@ -119,7 +119,8 @@ final class SuggestionStripLayoutHelper {
mDividerWidth = dividerView.getMeasuredWidth();
final Resources res = wordView.getResources();
- mSuggestionsStripHeight = res.getDimensionPixelSize(R.dimen.suggestions_strip_height);
+ mSuggestionsStripHeight = res.getDimensionPixelSize(
+ R.dimen.config_suggestions_strip_height);
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.SuggestionStripView, defStyle, R.style.SuggestionStripView);
@@ -145,15 +146,17 @@ final class SuggestionStripLayoutHelper {
a.recycle();
mMoreSuggestionsHint = getMoreSuggestionsHint(res,
- res.getDimension(R.dimen.more_suggestions_hint_text_size), mColorAutoCorrect);
+ res.getDimension(R.dimen.config_more_suggestions_hint_text_size),
+ mColorAutoCorrect);
mCenterPositionInStrip = mSuggestionsCountInStrip / 2;
// Assuming there are at least three suggestions. Also, note that the suggestions are
// laid out according to script direction, so this is left of the center for LTR scripts
// and right of the center for RTL scripts.
mTypedWordPositionWhenAutocorrect = mCenterPositionInStrip - 1;
mMoreSuggestionsBottomGap = res.getDimensionPixelOffset(
- R.dimen.more_suggestions_bottom_gap);
- mMoreSuggestionsRowHeight = res.getDimensionPixelSize(R.dimen.more_suggestions_row_height);
+ R.dimen.config_more_suggestions_bottom_gap);
+ mMoreSuggestionsRowHeight = res.getDimensionPixelSize(
+ R.dimen.config_more_suggestions_row_height);
final LayoutInflater inflater = LayoutInflater.from(context);
mWordToSaveView = (TextView)inflater.inflate(R.layout.suggestion_word, null);
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index 75f17c559..aa87affa2 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -112,7 +112,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
final Resources res = context.getResources();
mMoreSuggestionsModalTolerance = res.getDimensionPixelOffset(
- R.dimen.more_suggestions_modal_tolerance);
+ R.dimen.config_more_suggestions_modal_tolerance);
mMoreSuggestionsSlidingDetector = new GestureDetector(
context, mMoreSuggestionsSlidingListener);
}
diff --git a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
index d87f6f3c4..ef1d0f42c 100644
--- a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
@@ -17,21 +17,25 @@
package com.android.inputmethod.latin.utils;
import static com.android.inputmethod.latin.Constants.Subtype.KEYBOARD_MODE;
+import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.IS_ADDITIONAL_SUBTYPE;
import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET;
import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME;
import android.os.Build;
import android.text.TextUtils;
+import android.util.Log;
import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
-import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
import java.util.ArrayList;
+import java.util.Arrays;
public final class AdditionalSubtypeUtils {
+ private static final String TAG = AdditionalSubtypeUtils.class.getSimpleName();
+
private static final InputMethodSubtype[] EMPTY_SUBTYPE_ARRAY = new InputMethodSubtype[0];
private AdditionalSubtypeUtils() {
@@ -43,6 +47,11 @@ public final class AdditionalSubtypeUtils {
}
private static final String LOCALE_AND_LAYOUT_SEPARATOR = ":";
+ private static final int INDEX_OF_LOCALE = 0;
+ private static final int INDEX_OF_KEYBOARD_LAYOUT = 1;
+ private static final int INDEX_OF_EXTRA_VALUE = 2;
+ private static final int LENGTH_WITHOUT_EXTRA_VALUE = (INDEX_OF_KEYBOARD_LAYOUT + 1);
+ private static final int LENGTH_WITH_EXTRA_VALUE = (INDEX_OF_EXTRA_VALUE + 1);
private static final String PREF_SUBTYPE_SEPARATOR = ";";
public static InputMethodSubtype createAdditionalSubtype(final String localeString,
@@ -79,17 +88,6 @@ public final class AdditionalSubtypeUtils {
: basePrefSubtype + LOCALE_AND_LAYOUT_SEPARATOR + extraValue;
}
- public static InputMethodSubtype createAdditionalSubtype(final String prefSubtype) {
- final String elems[] = prefSubtype.split(LOCALE_AND_LAYOUT_SEPARATOR);
- if (elems.length < 2 || elems.length > 3) {
- throw new RuntimeException("Unknown additional subtype specified: " + prefSubtype);
- }
- final String localeString = elems[0];
- final String keyboardLayoutSetName = elems[1];
- final String extraValue = (elems.length == 3) ? elems[2] : null;
- return createAdditionalSubtype(localeString, keyboardLayoutSetName, extraValue);
- }
-
public static InputMethodSubtype[] createAdditionalSubtypesArray(final String prefSubtypes) {
if (TextUtils.isEmpty(prefSubtypes)) {
return EMPTY_SUBTYPE_ARRAY;
@@ -98,7 +96,19 @@ public final class AdditionalSubtypeUtils {
final ArrayList<InputMethodSubtype> subtypesList =
CollectionUtils.newArrayList(prefSubtypeArray.length);
for (final String prefSubtype : prefSubtypeArray) {
- final InputMethodSubtype subtype = createAdditionalSubtype(prefSubtype);
+ final String elems[] = prefSubtype.split(LOCALE_AND_LAYOUT_SEPARATOR);
+ if (elems.length != LENGTH_WITHOUT_EXTRA_VALUE
+ && elems.length != LENGTH_WITH_EXTRA_VALUE) {
+ Log.w(TAG, "Unknown additional subtype specified: " + prefSubtype + " in "
+ + prefSubtypes);
+ continue;
+ }
+ final String localeString = elems[INDEX_OF_LOCALE];
+ final String keyboardLayoutSetName = elems[INDEX_OF_KEYBOARD_LAYOUT];
+ final String extraValue = (elems.length == LENGTH_WITH_EXTRA_VALUE)
+ ? elems[INDEX_OF_EXTRA_VALUE] : null;
+ final InputMethodSubtype subtype = createAdditionalSubtype(
+ localeString, keyboardLayoutSetName, extraValue);
if (subtype.getNameResId() == SubtypeLocaleUtils.UNKNOWN_KEYBOARD_LAYOUT) {
// Skip unknown keyboard layout subtype. This may happen when predefined keyboard
// layout has been removed.
@@ -137,31 +147,36 @@ public final class AdditionalSubtypeUtils {
return sb.toString();
}
- private static InputMethodSubtype buildInputMethodSubtype(int nameId, String localeString,
- String layoutExtraValue, String additionalSubtypeExtraValue) {
- // CAVEAT! If you want to change subtypeId after changing the extra values,
- // you must change "getInputMethodSubtypeId". But it will remove the additional keyboard
- // from the current users. So, you should be really careful to change it.
- final int subtypeId = getInputMethodSubtypeId(nameId, localeString, layoutExtraValue,
- additionalSubtypeExtraValue);
+ private static InputMethodSubtype buildInputMethodSubtype(final int nameId,
+ final String localeString, final String layoutExtraValue,
+ final String additionalSubtypeExtraValue) {
+ // To preserve additional subtype settings and user's selection across OS updates, subtype
+ // id shouldn't be changed. New attributes, such as emojiCapable, are carefully excluded
+ // from the calculation of subtype id.
+ final String compatibleExtraValue = StringUtils.joinCommaSplittableText(
+ layoutExtraValue, additionalSubtypeExtraValue);
+ final int compatibleSubtypeId = getInputMethodSubtypeId(localeString, compatibleExtraValue);
final String extraValue;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
- extraValue = layoutExtraValue + "," + additionalSubtypeExtraValue
- + "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE
- + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
+ // Color Emoji is supported from KitKat.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ extraValue = StringUtils.appendToCommaSplittableTextIfNotExists(
+ EMOJI_CAPABLE, compatibleExtraValue);
} else {
- extraValue = layoutExtraValue + "," + additionalSubtypeExtraValue;
+ extraValue = compatibleExtraValue;
}
return InputMethodSubtypeCompatUtils.newInputMethodSubtype(nameId,
R.drawable.ic_ime_switcher_dark, localeString, KEYBOARD_MODE, extraValue,
- false, false, subtypeId);
+ false, false, compatibleSubtypeId);
}
- private static int getInputMethodSubtypeId(int nameId, String localeString,
- String layoutExtraValue, String additionalSubtypeExtraValue) {
- // TODO: Use InputMethodSubtypeBuilder once we use SDK version 19.
- return (new InputMethodSubtype(nameId, R.drawable.ic_ime_switcher_dark,
- localeString, KEYBOARD_MODE, layoutExtraValue + "," + additionalSubtypeExtraValue,
- false, false)).hashCode();
+ private static int getInputMethodSubtypeId(final String localeString, final String extraValue) {
+ // From the compatibility point of view, the calculation of subtype id has been copied from
+ // {@link InputMethodSubtype} of JellyBean MR2.
+ return Arrays.hashCode(new Object[] {
+ localeString,
+ KEYBOARD_MODE,
+ extraValue,
+ false /* isAuxiliary */,
+ false /* overrideImplicitlyEnabledSubtype */ });
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java b/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java
index 08a2a8c5a..e521ec807 100644
--- a/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java
@@ -62,4 +62,22 @@ public final class ApplicationUtils {
}
return "";
}
+
+ /**
+ * A utility method to get the application's PackageInfo.versionCode
+ * @return the application's PackageInfo.versionCode
+ */
+ public static int getVersionCode(final Context context) {
+ try {
+ if (context == null) {
+ return 0;
+ }
+ final String packageName = context.getPackageName();
+ final PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
+ return info.versionCode;
+ } catch (final NameNotFoundException e) {
+ Log.e(TAG, "Could not find version info.", e);
+ }
+ return 0;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java b/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java
index c2e97a36f..d12aad639 100644
--- a/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java
+++ b/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java
@@ -20,7 +20,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
- * This class is a holder of a result of asynchronous computation.
+ * This class is a holder of the result of an asynchronous computation.
*
* @param <E> the type of the result.
*/
@@ -36,9 +36,9 @@ public class AsyncResultHolder<E> {
}
/**
- * Sets the result value to this holder.
+ * Sets the result value of this holder.
*
- * @param result the value which is set.
+ * @param result the value to set.
*/
public void set(final E result) {
synchronized(mLock) {
@@ -54,12 +54,12 @@ public class AsyncResultHolder<E> {
* Causes the current thread to wait unless the value is set or the specified time is elapsed.
*
* @param defaultValue the default value.
- * @param timeOut the time to wait.
- * @return if the result is set until the time limit then the result, otherwise defaultValue.
+ * @param timeOut the maximum time to wait.
+ * @return if the result is set before the time limit then the result, otherwise defaultValue.
*/
public E get(final E defaultValue, final long timeOut) {
try {
- if(mLatch.await(timeOut, TimeUnit.MILLISECONDS)) {
+ if (mLatch.await(timeOut, TimeUnit.MILLISECONDS)) {
return mResult;
} else {
return defaultValue;
diff --git a/java/src/com/android/inputmethod/latin/utils/FileUtils.java b/java/src/com/android/inputmethod/latin/utils/FileUtils.java
new file mode 100644
index 000000000..83c1e7c4d
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/FileUtils.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2013 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.utils;
+
+import java.io.File;
+
+/**
+ * A simple class to help with removing directories recursively.
+ */
+public class FileUtils {
+ public static boolean deleteRecursively(final File path) {
+ if (path.isDirectory()) {
+ for (final File child : path.listFiles()) {
+ deleteRecursively(child);
+ }
+ }
+ return path.delete();
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/JsonUtils.java b/java/src/com/android/inputmethod/latin/utils/JsonUtils.java
new file mode 100644
index 000000000..764ef72ce
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/JsonUtils.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2013 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.utils;
+
+import android.util.JsonReader;
+import android.util.JsonWriter;
+import android.util.Log;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public final class JsonUtils {
+ private static final String TAG = JsonUtils.class.getSimpleName();
+
+ private static final String INTEGER_CLASS_NAME = Integer.class.getSimpleName();
+ private static final String STRING_CLASS_NAME = String.class.getSimpleName();
+
+ private static final String EMPTY_STRING = "";
+
+ public static List<Object> jsonStrToList(final String s) {
+ final ArrayList<Object> list = CollectionUtils.newArrayList();
+ final JsonReader reader = new JsonReader(new StringReader(s));
+ try {
+ reader.beginArray();
+ while (reader.hasNext()) {
+ reader.beginObject();
+ while (reader.hasNext()) {
+ final String name = reader.nextName();
+ if (name.equals(INTEGER_CLASS_NAME)) {
+ list.add(reader.nextInt());
+ } else if (name.equals(STRING_CLASS_NAME)) {
+ list.add(reader.nextString());
+ } else {
+ Log.w(TAG, "Invalid name: " + name);
+ reader.skipValue();
+ }
+ }
+ reader.endObject();
+ }
+ reader.endArray();
+ return list;
+ } catch (final IOException e) {
+ } finally {
+ close(reader);
+ }
+ return Collections.<Object>emptyList();
+ }
+
+ public static String listToJsonStr(final List<Object> list) {
+ if (list == null || list.isEmpty()) {
+ return EMPTY_STRING;
+ }
+ final StringWriter sw = new StringWriter();
+ final JsonWriter writer = new JsonWriter(sw);
+ try {
+ writer.beginArray();
+ for (final Object o : list) {
+ writer.beginObject();
+ if (o instanceof Integer) {
+ writer.name(INTEGER_CLASS_NAME).value((Integer)o);
+ } else if (o instanceof String) {
+ writer.name(STRING_CLASS_NAME).value((String)o);
+ }
+ writer.endObject();
+ }
+ writer.endArray();
+ return sw.toString();
+ } catch (final IOException e) {
+ } finally {
+ close(writer);
+ }
+ return EMPTY_STRING;
+ }
+
+ private static void close(final Closeable closeable) {
+ try {
+ if (closeable != null) {
+ closeable.close();
+ }
+ } catch (final IOException e) {
+ // Ignore
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java b/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java
index e958a7e71..d14ba508b 100644
--- a/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java
@@ -35,7 +35,7 @@ public final class LatinImeLoggerUtils {
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);
+ onSeparator(StringUtils.newSingleCodePointString(code), x, y);
}
public static void onSeparator(final String separator, final int x, final int y) {
diff --git a/java/src/com/android/inputmethod/latin/utils/StaticInnerHandlerWrapper.java b/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java
index 44e5d17b4..8469c87b0 100644
--- a/java/src/com/android/inputmethod/latin/utils/StaticInnerHandlerWrapper.java
+++ b/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java
@@ -21,22 +21,22 @@ import android.os.Looper;
import java.lang.ref.WeakReference;
-public class StaticInnerHandlerWrapper<T> extends Handler {
- private final WeakReference<T> mOuterInstanceRef;
+public class LeakGuardHandlerWrapper<T> extends Handler {
+ private final WeakReference<T> mOwnerInstanceRef;
- public StaticInnerHandlerWrapper(final T outerInstance) {
- this(outerInstance, Looper.myLooper());
+ public LeakGuardHandlerWrapper(final T ownerInstance) {
+ this(ownerInstance, Looper.myLooper());
}
- public StaticInnerHandlerWrapper(final T outerInstance, final Looper looper) {
+ public LeakGuardHandlerWrapper(final T ownerInstance, final Looper looper) {
super(looper);
- if (outerInstance == null) {
- throw new NullPointerException("outerInstance is null");
+ if (ownerInstance == null) {
+ throw new NullPointerException("ownerInstance is null");
}
- mOuterInstanceRef = new WeakReference<T>(outerInstance);
+ mOwnerInstanceRef = new WeakReference<T>(ownerInstance);
}
- public T getOuterInstance() {
- return mOuterInstanceRef.get();
+ public T getOwnerInstance() {
+ return mOwnerInstanceRef.get();
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java
index 22045aa38..0c55484b4 100644
--- a/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java
@@ -30,9 +30,6 @@ import java.util.Locale;
* dictionary pack.
*/
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.
}
@@ -168,12 +165,14 @@ public final class LocaleUtils {
* Creates a locale from a string specification.
*/
public static Locale constructLocaleFromString(final String localeStr) {
- if (localeStr == null)
+ if (localeStr == null) {
return null;
+ }
synchronized (sLocaleCache) {
- if (sLocaleCache.containsKey(localeStr))
- return sLocaleCache.get(localeStr);
- Locale retval = null;
+ Locale retval = sLocaleCache.get(localeStr);
+ if (retval != null) {
+ return retval;
+ }
String[] localeParams = localeStr.split("_", 3);
if (localeParams.length == 1) {
retval = new Locale(localeParams[0]);
@@ -188,38 +187,4 @@ public final 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/utils/ResourceUtils.java b/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
index 22c92446a..deb28a08d 100644
--- a/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
@@ -227,19 +227,19 @@ public final class ResourceUtils {
final String keyboardHeightString = getDeviceOverrideValue(res, R.array.keyboard_heights);
final float keyboardHeight;
if (TextUtils.isEmpty(keyboardHeightString)) {
- keyboardHeight = res.getDimension(R.dimen.keyboardHeight);
+ keyboardHeight = res.getDimension(R.dimen.config_default_keyboard_height);
} else {
keyboardHeight = Float.parseFloat(keyboardHeightString) * dm.density;
}
final float maxKeyboardHeight = res.getFraction(
- R.fraction.maxKeyboardHeight, dm.heightPixels, dm.heightPixels);
+ R.fraction.config_max_keyboard_height, dm.heightPixels, dm.heightPixels);
float minKeyboardHeight = res.getFraction(
- R.fraction.minKeyboardHeight, dm.heightPixels, dm.heightPixels);
+ R.fraction.config_min_keyboard_height, dm.heightPixels, dm.heightPixels);
if (minKeyboardHeight < 0.0f) {
// Specified fraction was negative, so it should be calculated against display
// width.
minKeyboardHeight = -res.getFraction(
- R.fraction.minKeyboardHeight, dm.widthPixels, dm.widthPixels);
+ R.fraction.config_min_keyboard_height, dm.widthPixels, dm.widthPixels);
}
// Keyboard height will not exceed maxKeyboardHeight and will not be less than
// minKeyboardHeight.
diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
index a36548392..df420417d 100644
--- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
@@ -16,20 +16,15 @@
package com.android.inputmethod.latin.utils;
+import android.text.TextUtils;
+import android.util.Log;
+
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.settings.SettingsValues;
-import android.text.TextUtils;
-import android.util.JsonReader;
-import android.util.JsonWriter;
-import android.util.Log;
-
import java.io.IOException;
-import java.io.StringReader;
-import java.io.StringWriter;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Locale;
@@ -39,6 +34,8 @@ public final class StringUtils {
public static final int CAPITALIZE_FIRST = 1; // First only
public static final int CAPITALIZE_ALL = 2; // All caps
+ private static final String EMPTY_STRING = "";
+
private StringUtils() {
// This utility class is not publicly instantiable.
}
@@ -80,6 +77,20 @@ public final class StringUtils {
return containsInArray(text, extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT));
}
+ public static String joinCommaSplittableText(final String head, final String tail) {
+ if (TextUtils.isEmpty(head) && TextUtils.isEmpty(tail)) {
+ return EMPTY_STRING;
+ }
+ // Here either head or tail is not null.
+ if (TextUtils.isEmpty(head)) {
+ return tail;
+ }
+ if (TextUtils.isEmpty(tail)) {
+ return head;
+ }
+ return head + SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT + tail;
+ }
+
public static String appendToCommaSplittableTextIfNotExists(final String text,
final String extraValues) {
if (TextUtils.isEmpty(extraValues)) {
@@ -94,7 +105,7 @@ public final class StringUtils {
public static String removeFromCommaSplittableTextIfExists(final String text,
final String extraValues) {
if (TextUtils.isEmpty(extraValues)) {
- return "";
+ return EMPTY_STRING;
}
final String[] elements = extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT);
if (!containsInArray(text, elements)) {
@@ -367,7 +378,7 @@ public final class StringUtils {
return false;
}
- public static boolean isEmptyStringOrWhiteSpaces(String s) {
+ public static boolean isEmptyStringOrWhiteSpaces(final String s) {
final int N = codePointCount(s);
for (int i = 0; i < N; ++i) {
if (!Character.isWhitespace(s.codePointAt(i))) {
@@ -378,9 +389,9 @@ public final class StringUtils {
}
@UsedForTesting
- public static String byteArrayToHexString(byte[] bytes) {
+ public static String byteArrayToHexString(final byte[] bytes) {
if (bytes == null || bytes.length == 0) {
- return "";
+ return EMPTY_STRING;
}
final StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
@@ -393,7 +404,7 @@ public final class StringUtils {
* Convert hex string to byte array. The string length must be an even number.
*/
@UsedForTesting
- public static byte[] hexStringToByteArray(String hexString) {
+ public static byte[] hexStringToByteArray(final String hexString) {
if (TextUtils.isEmpty(hexString)) {
return null;
}
@@ -409,67 +420,4 @@ public final class StringUtils {
}
return bytes;
}
-
- public static List<Object> jsonStrToList(String s) {
- final ArrayList<Object> retval = CollectionUtils.newArrayList();
- final JsonReader reader = new JsonReader(new StringReader(s));
- try {
- reader.beginArray();
- while(reader.hasNext()) {
- reader.beginObject();
- while (reader.hasNext()) {
- final String name = reader.nextName();
- if (name.equals(Integer.class.getSimpleName())) {
- retval.add(reader.nextInt());
- } else if (name.equals(String.class.getSimpleName())) {
- retval.add(reader.nextString());
- } else {
- Log.w(TAG, "Invalid name: " + name);
- reader.skipValue();
- }
- }
- reader.endObject();
- }
- reader.endArray();
- return retval;
- } catch (IOException e) {
- } finally {
- try {
- reader.close();
- } catch (IOException e) {
- }
- }
- return Collections.<Object>emptyList();
- }
-
- public static String listToJsonStr(List<Object> list) {
- if (list == null || list.isEmpty()) {
- return "";
- }
- final StringWriter sw = new StringWriter();
- final JsonWriter writer = new JsonWriter(sw);
- try {
- writer.beginArray();
- for (final Object o : list) {
- writer.beginObject();
- if (o instanceof Integer) {
- writer.name(Integer.class.getSimpleName()).value((Integer)o);
- } else if (o instanceof String) {
- writer.name(String.class.getSimpleName()).value((String)o);
- }
- writer.endObject();
- }
- writer.endArray();
- return sw.toString();
- } catch (IOException e) {
- } finally {
- try {
- if (writer != null) {
- writer.close();
- }
- } catch (IOException e) {
- }
- }
- return "";
- }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
index 102a41b4e..fdbe81ab6 100644
--- a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
@@ -197,7 +197,9 @@ public final class SubtypeLocaleUtils {
// es_US spanish F Español (EE.UU.) exception
// fr azerty F Français
// fr_CA qwerty F Français (Canada)
+ // fr_CH swiss F Français (Suisse)
// de qwertz F Deutsch
+ // de_CH swiss T Deutsch (Schweiz)
// zz qwerty F No language (QWERTY) in system locale
// fr qwertz T Français (QWERTZ)
// de qwerty T Deutsch (QWERTY)
@@ -298,7 +300,9 @@ public final class SubtypeLocaleUtils {
// es_US spanish F Es Español Español (EE.UU.) exception
// fr azerty F Fr Français Français
// fr_CA qwerty F Fr Français Français (Canada)
+ // fr_CH swiss F Fr Français Français (Suisse)
// de qwertz F De Deutsch Deutsch
+ // de_CH swiss T De Deutsch Deutsch (Schweiz)
// zz qwerty F QWERTY QWERTY
// fr qwertz T Fr Français Français
// de qwerty T De Deutsch Deutsch
diff --git a/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java b/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
index 47ea1ea75..087a7f255 100644
--- a/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
@@ -22,6 +22,9 @@ import android.graphics.Typeface;
import android.util.SparseArray;
public final class TypefaceUtils {
+ private static final char[] KEY_LABEL_REFERENCE_CHAR = { 'M' };
+ private static final char[] KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR = { '8' };
+
private TypefaceUtils() {
// This utility class is not publicly instantiable.
}
@@ -31,7 +34,7 @@ public final class TypefaceUtils {
// Working variable for the following method.
private static final Rect sTextHeightBounds = new Rect();
- public static float getCharHeight(final char[] referenceChar, final Paint paint) {
+ private static float getCharHeight(final char[] referenceChar, final Paint paint) {
final int key = getCharGeometryCacheKey(referenceChar[0], paint);
synchronized (sTextHeightCache) {
final Float cachedValue = sTextHeightCache.get(key);
@@ -51,7 +54,7 @@ public final class TypefaceUtils {
// Working variable for the following method.
private static final Rect sTextWidthBounds = new Rect();
- public static float getCharWidth(final char[] referenceChar, final Paint paint) {
+ private static float getCharWidth(final char[] referenceChar, final Paint paint) {
final int key = getCharGeometryCacheKey(referenceChar[0], paint);
synchronized (sTextWidthCache) {
final Float cachedValue = sTextWidthCache.get(key);
@@ -66,11 +69,6 @@ public final class TypefaceUtils {
}
}
- public static float getStringWidth(final String string, final Paint paint) {
- paint.getTextBounds(string, 0, string.length(), sTextWidthBounds);
- return sTextWidthBounds.width();
- }
-
private static int getCharGeometryCacheKey(final char referenceChar, final Paint paint) {
final int labelSize = (int)paint.getTextSize();
final Typeface face = paint.getTypeface();
@@ -86,9 +84,25 @@ public final class TypefaceUtils {
}
}
- public static float getLabelWidth(final String label, final Paint paint) {
- final Rect textBounds = new Rect();
- paint.getTextBounds(label, 0, label.length(), textBounds);
- return textBounds.width();
+ public static float getReferenceCharHeight(final Paint paint) {
+ return getCharHeight(KEY_LABEL_REFERENCE_CHAR, paint);
+ }
+
+ public static float getReferenceCharWidth(final Paint paint) {
+ return getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint);
+ }
+
+ public static float getReferenceDigitWidth(final Paint paint) {
+ return getCharWidth(KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR, paint);
+ }
+
+ // Working variable for the following method.
+ private static final Rect sStringWidthBounds = new Rect();
+
+ public static float getStringWidth(final String string, final Paint paint) {
+ synchronized (sStringWidthBounds) {
+ paint.getTextBounds(string, 0, string.length(), sStringWidthBounds);
+ return sStringWidthBounds.width();
+ }
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/UnigramProperty.java b/java/src/com/android/inputmethod/latin/utils/UnigramProperty.java
new file mode 100644
index 000000000..4feee4393
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/UnigramProperty.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2013 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.utils;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.BinaryDictionary;
+import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+
+import java.util.ArrayList;
+
+// This has information that belong to a unigram. This class has some detailed attributes such as
+// historical information but they have to be checked only for testing purpose.
+@UsedForTesting
+public class UnigramProperty {
+ public final String mCodePoints;
+ public final boolean mIsNotAWord;
+ public final boolean mIsBlacklisted;
+ public final boolean mHasBigrams;
+ public final boolean mHasShortcuts;
+ public final int mProbability;
+ // mTimestamp, mLevel and mCount are historical info. These values are depend on the
+ // implementation in native code; thus, we must not use them and have any assumptions about
+ // them except for tests.
+ public final int mTimestamp;
+ public final int mLevel;
+ public final int mCount;
+ public final ArrayList<WeightedString> mShortcutTargets = CollectionUtils.newArrayList();
+
+ private static int getCodePointCount(final int[] codePoints) {
+ for (int i = 0; i < codePoints.length; i++) {
+ if (codePoints[i] == 0) {
+ return i;
+ }
+ }
+ return codePoints.length;
+ }
+
+ // This represents invalid unigram when the probability is BinaryDictionary.NOT_A_PROBABILITY.
+ public UnigramProperty(final int[] codePoints, final boolean isNotAWord,
+ final boolean isBlacklisted, final boolean hasBigram,
+ final boolean hasShortcuts, final int probability, final int timestamp,
+ final int level, final int count, final ArrayList<int[]> shortcutTargets,
+ final ArrayList<Integer> shortcutProbabilities) {
+ mCodePoints = new String(codePoints, 0 /* offset */, getCodePointCount(codePoints));
+ mIsNotAWord = isNotAWord;
+ mIsBlacklisted = isBlacklisted;
+ mHasBigrams = hasBigram;
+ mHasShortcuts = hasShortcuts;
+ mProbability = probability;
+ mTimestamp = timestamp;
+ mLevel = level;
+ mCount = count;
+ final int shortcutTargetCount = shortcutTargets.size();
+ for (int i = 0; i < shortcutTargetCount; i++) {
+ final int[] shortcutTargetCodePointArray = shortcutTargets.get(i);
+ final String shortcutTargetString = new String(shortcutTargetCodePointArray,
+ 0 /* offset */, getCodePointCount(shortcutTargetCodePointArray));
+ mShortcutTargets.add(
+ new WeightedString(shortcutTargetString, shortcutProbabilities.get(i)));
+ }
+ }
+
+ @UsedForTesting
+ public boolean isValid() {
+ return mProbability != BinaryDictionary.NOT_A_PROBABILITY;
+ }
+} \ No newline at end of file
diff --git a/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
index 635afe7cc..a2c3ed44d 100644
--- a/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
@@ -70,10 +70,11 @@ public final class UserHistoryDictIOUtils {
/**
* Writes dictionary to file.
*/
+ @UsedForTesting
public static void writeDictionary(final DictEncoder dictEncoder,
final BigramDictionaryInterface dict, final UserHistoryDictionaryBigramList bigrams,
- final FormatOptions formatOptions) {
- final FusionDictionary fusionDict = constructFusionDictionary(dict, bigrams);
+ final FormatOptions formatOptions, final HashMap<String, String> options) {
+ final FusionDictionary fusionDict = constructFusionDictionary(dict, bigrams, options);
fusionDict.addOptionAttribute(USES_FORGETTING_CURVE_KEY, USES_FORGETTING_CURVE_VALUE);
fusionDict.addOptionAttribute(LAST_UPDATED_TIME_KEY,
String.valueOf(TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())));
@@ -91,10 +92,10 @@ public final class UserHistoryDictIOUtils {
* Constructs a new FusionDictionary from BigramDictionaryInterface.
*/
@UsedForTesting
- static FusionDictionary constructFusionDictionary(
- final BigramDictionaryInterface dict, final UserHistoryDictionaryBigramList bigrams) {
+ static FusionDictionary constructFusionDictionary(final BigramDictionaryInterface dict,
+ final UserHistoryDictionaryBigramList bigrams, final HashMap<String, String> options) {
final FusionDictionary fusionDict = new FusionDictionary(new PtNodeArray(),
- new FusionDictionary.DictionaryOptions(new HashMap<String, String>(), false,
+ new FusionDictionary.DictionaryOptions(options, false,
false));
int profTotal = 0;
for (final String word1 : bigrams.keySet()) {
diff --git a/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java b/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java
index 1992b2f5d..677035ed6 100644
--- a/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java
@@ -20,6 +20,9 @@ import android.util.Log;
import java.util.concurrent.TimeUnit;
+import com.android.inputmethod.annotations.UsedForTesting;
+
+@UsedForTesting
public final class UserHistoryForgettingCurveUtils {
private static final String TAG = UserHistoryForgettingCurveUtils.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -118,18 +121,22 @@ public final class UserHistoryForgettingCurveUtils {
}
}
+ @UsedForTesting
/* package */ static int fcToElapsedTime(byte fc) {
return fc & 0x0F;
}
+ @UsedForTesting
/* package */ static int fcToCount(byte fc) {
return (fc >> 4) & 0x03;
}
+ @UsedForTesting
/* package */ static int fcToLevel(byte fc) {
return (fc >> 6) & 0x03;
}
+ @UsedForTesting
private static int calcFreq(int elapsedTime, int count, int level) {
if (level <= 0) {
// Reserved words, just return -1
@@ -158,6 +165,7 @@ public final class UserHistoryForgettingCurveUtils {
return calcFreq(elapsedTime, count, level);
}
+ @UsedForTesting
public static byte pushElapsedTime(byte fc) {
int elapsedTime = fcToElapsedTime(fc);
int count = fcToCount(fc);
@@ -173,6 +181,7 @@ public final class UserHistoryForgettingCurveUtils {
return calcFc(elapsedTime, count, level);
}
+ @UsedForTesting
public static byte pushCount(byte fc, boolean isValid) {
final int elapsedTime = fcToElapsedTime(fc);
int count = fcToCount(fc);
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index da9c61103..28a647b02 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -102,10 +102,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
&& ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
private static final boolean DEBUG_REPLAY_AFTER_FEEDBACK = false
&& ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
- // Whether the TextView contents are logged at the end of the session. true will disclose
- // private info.
- private static final boolean LOG_FULL_TEXTVIEW_CONTENTS = false
- && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
// Whether the feedback dialog preserves the editable text across invocations. Should be false
// for normal research builds so users do not have to delete the same feedback string they
// entered earlier. Should be true for builds internal to a development team so when the text
@@ -168,9 +164,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
// U+E001 is in the "private-use area"
/* package for test */ static final String WORD_REPLACEMENT_STRING = "\uE001";
protected static final int SUSPEND_DURATION_IN_MINUTES = 1;
- // set when LatinIME should ignore an onUpdateSelection() callback that
- // arises from operations in this class
- private static boolean sLatinIMEExpectingUpdateSelection = false;
// used to check whether words are not unique
private Suggest mSuggest;
@@ -1126,12 +1119,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
new Object[] { applicationSpecifiedCompletions });
}
- public static boolean getAndClearLatinIMEExpectingUpdateSelection() {
- boolean returnValue = sLatinIMEExpectingUpdateSelection;
- sLatinIMEExpectingUpdateSelection = false;
- return returnValue;
- }
-
/**
* The IME is finishing; it is either being destroyed, or is about to be hidden.
*
@@ -1149,51 +1136,12 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
// if called from finishViews(), which is called from hideWindow() and onDestroy(). These
// are the situations in which we want to finish up the researchLog.
if (ic != null && !finishingInput) {
- final boolean isTextTruncated;
- final String text;
- if (LOG_FULL_TEXTVIEW_CONTENTS) {
- // Capture the TextView contents. This will trigger onUpdateSelection(), so we
- // set sLatinIMEExpectingUpdateSelection so that when onUpdateSelection() is called,
- // it can tell that it was generated by the logging code, and not by the user, and
- // therefore keep user-visible state as is.
- ic.beginBatchEdit();
- ic.performContextMenuAction(android.R.id.selectAll);
- CharSequence charSequence = ic.getSelectedText(0);
- if (savedSelectionStart != -1 && savedSelectionEnd != -1) {
- ic.setSelection(savedSelectionStart, savedSelectionEnd);
- }
- ic.endBatchEdit();
- sLatinIMEExpectingUpdateSelection = true;
- if (TextUtils.isEmpty(charSequence)) {
- isTextTruncated = false;
- text = "";
- } else {
- if (charSequence.length() > MAX_INPUTVIEW_LENGTH_TO_CAPTURE) {
- int length = MAX_INPUTVIEW_LENGTH_TO_CAPTURE;
- // do not cut in the middle of a supplementary character
- final char c = charSequence.charAt(length - 1);
- if (Character.isHighSurrogate(c)) {
- length--;
- }
- final CharSequence truncatedCharSequence = charSequence.subSequence(0,
- length);
- isTextTruncated = true;
- text = truncatedCharSequence.toString();
- } else {
- isTextTruncated = false;
- text = charSequence.toString();
- }
- }
- } else {
- isTextTruncated = true;
- text = "";
- }
final ResearchLogger researchLogger = getInstance();
// Assume that OUTPUT_ENTIRE_BUFFER is only true when we don't care about privacy (e.g.
// during a live user test), so the normal isPotentiallyPrivate and
// isPotentiallyRevealing flags do not apply
researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_ONFINISHINPUTVIEWINTERNAL,
- isTextTruncated, text);
+ true /* isTextTruncated */, "" /* text */);
researchLogger.commitCurrentLogUnit();
getInstance().stop();
}
@@ -1213,9 +1161,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
public static void latinIME_onUpdateSelection(final int lastSelectionStart,
final int lastSelectionEnd, final int oldSelStart, final int oldSelEnd,
final int newSelStart, final int newSelEnd, final int composingSpanStart,
- final int composingSpanEnd, final boolean expectingUpdateSelection,
- final boolean expectingUpdateSelectionFromLogger,
- final RichInputConnection connection) {
+ final int composingSpanEnd, final RichInputConnection connection) {
String word = "";
if (connection != null) {
TextRange range = connection.getWordRangeAtCursor(WHITESPACE_SEPARATORS, 1);
@@ -1227,8 +1173,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
final String scrubbedWord = researchLogger.scrubWord(word);
researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_ONUPDATESELECTION, lastSelectionStart,
lastSelectionEnd, oldSelStart, oldSelEnd, newSelStart, newSelEnd,
- composingSpanStart, composingSpanEnd, expectingUpdateSelection,
- expectingUpdateSelectionFromLogger, scrubbedWord);
+ composingSpanStart, composingSpanEnd, false /* expectingUpdateSelection */,
+ false /* expectingUpdateSelectionFromLogger */, scrubbedWord);
}
/**
@@ -1411,8 +1357,9 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
private static final LogStatement LOGSTATEMENT_MAINKEYBOARDVIEW_SETKEYBOARD =
new LogStatement("MainKeyboardViewSetKeyboard", false, false, "elementId", "locale",
"orientation", "width", "modeName", "action", "navigateNext",
- "navigatePrevious", "clobberSettingsKey", "passwordInput", "shortcutKeyEnabled",
- "hasShortcutKey", "languageSwitchKeyEnabled", "isMultiLine", "tw", "th",
+ "navigatePrevious", "clobberSettingsKey", "passwordInput",
+ "supportsSwitchingToShortcutIme", "hasShortcutKey", "languageSwitchKeyEnabled",
+ "isMultiLine", "tw", "th",
"keys");
public static void mainKeyboardView_setKeyboard(final Keyboard keyboard,
final int orientation) {
@@ -1425,7 +1372,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
kid.mLocale + ":" + kid.mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET),
orientation, kid.mWidth, KeyboardId.modeName(kid.mMode), kid.imeAction(),
kid.navigateNext(), kid.navigatePrevious(), kid.mClobberSettingsKey,
- isPasswordView, kid.mShortcutKeyEnabled, kid.mHasShortcutKey,
+ isPasswordView, kid.mSupportsSwitchingToShortcutIme, kid.mHasShortcutKey,
kid.mLanguageSwitchKeyEnabled, kid.isMultiLine(), keyboard.mOccupiedWidth,
keyboard.mOccupiedHeight, keyboard.getKeys());
}