aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java453
-rw-r--r--java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java80
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java8
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java (renamed from java/src/com/android/inputmethod/keyboard/internal/RecentsKeyboard.java)110
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java80
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java56
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java5
-rw-r--r--java/src/com/android/inputmethod/latin/Constants.java4
-rw-r--r--java/src/com/android/inputmethod/latin/DicTraverseSession.java10
-rw-r--r--java/src/com/android/inputmethod/latin/Dictionary.java5
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java10
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableDictionary.java9
-rw-r--r--java/src/com/android/inputmethod/latin/InputPointers.java11
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java76
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java9
-rw-r--r--java/src/com/android/inputmethod/latin/SuggestedWords.java43
-rw-r--r--java/src/com/android/inputmethod/latin/UserBinaryDictionary.java21
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java60
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java52
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java41
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/DictDecoder.java40
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/DictEncoder.java28
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/FormatSpec.java39
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java6
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java50
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java88
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java269
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java16
-rw-r--r--java/src/com/android/inputmethod/latin/settings/Settings.java31
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsValues.java6
-rw-r--r--java/src/com/android/inputmethod/latin/utils/PositionalInfoForUserDictPendingAddition.java108
-rw-r--r--java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java33
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java9
-rw-r--r--java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java4
34 files changed, 1389 insertions, 481 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java b/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java
index 25ff8d0ce..702ed2075 100644
--- a/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java
@@ -19,12 +19,18 @@ package com.android.inputmethod.keyboard;
import static com.android.inputmethod.latin.Constants.NOT_A_COORDINATE;
import android.content.Context;
+import android.content.SharedPreferences;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.os.Build;
+import android.preference.PreferenceManager;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Pair;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
@@ -35,16 +41,21 @@ import android.widget.TabHost;
import android.widget.TabHost.OnTabChangeListener;
import android.widget.TextView;
-import com.android.inputmethod.keyboard.internal.RecentsKeyboard;
+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.latin.Constants;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SubtypeSwitcher;
+import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.ResourceUtils;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
import java.util.HashMap;
+import java.util.concurrent.ConcurrentHashMap;
/**
* View class to implement Emoji keyboards.
@@ -60,51 +71,287 @@ import java.util.HashMap;
public final class EmojiKeyboardView extends LinearLayout implements OnTabChangeListener,
ViewPager.OnPageChangeListener, View.OnClickListener,
ScrollKeyboardView.OnKeyClickListener {
+ private static final String TAG = EmojiKeyboardView.class.getSimpleName();
private final int mKeyBackgroundId;
+ private final int mEmojiFunctionalKeyBackgroundId;
+ private final KeyboardLayoutSet mLayoutSet;
private final ColorStateList mTabLabelColor;
- private final EmojiKeyboardAdapter mEmojiKeyboardAdapter;
+ private EmojiKeyboardAdapter mEmojiKeyboardAdapter;
private TabHost mTabHost;
private ViewPager mEmojiPager;
private KeyboardActionListener mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER;
- private int mCurrentCategory = CATEGORY_UNSPECIFIED;
- private static final int CATEGORY_UNSPECIFIED = -1;
- private static final int CATEGORY_RECENTS = 0;
- private static final int CATEGORY_PEOPLE = 1;
- private static final int CATEGORY_OBJECTS = 2;
- private static final int CATEGORY_NATURE = 3;
- private static final int CATEGORY_PLACES = 4;
- private static final int CATEGORY_SYMBOLS = 5;
- private static final int CATEGORY_EMOTICONS = 6;
- private static final HashMap<String, Integer> sCategoryNameToIdMap =
- CollectionUtils.newHashMap();
- private static final String[] sCategoryName = {
- "recents", "people", "objects", "nature", "places", "symbols", "emoticons"
- };
- private static final int[] sCategoryIcon = new int[] {
- R.drawable.ic_emoji_recent_light,
- R.drawable.ic_emoji_people_light,
- R.drawable.ic_emoji_objects_light,
- R.drawable.ic_emoji_nature_light,
- R.drawable.ic_emoji_places_light,
- R.drawable.ic_emoji_symbols_light,
- 0
- };
- private static final String[] sCategoryLabel = {
- null, null, null, null, null, null,
- ":-)"
- };
- private static final int[] sCategoryElementId = {
- KeyboardId.ELEMENT_EMOJI_RECENTS,
- KeyboardId.ELEMENT_EMOJI_CATEGORY1,
- KeyboardId.ELEMENT_EMOJI_CATEGORY2,
- KeyboardId.ELEMENT_EMOJI_CATEGORY3,
- KeyboardId.ELEMENT_EMOJI_CATEGORY4,
- KeyboardId.ELEMENT_EMOJI_CATEGORY5,
- KeyboardId.ELEMENT_EMOJI_CATEGORY6,
- };
+ private static final int CATEGORY_ID_UNSPECIFIED = -1;
+ public static final int CATEGORY_ID_RECENTS = 0;
+ public static final int CATEGORY_ID_PEOPLE = 1;
+ public static final int CATEGORY_ID_OBJECTS = 2;
+ public static final int CATEGORY_ID_NATURE = 3;
+ public static final int CATEGORY_ID_PLACES = 4;
+ public static final int CATEGORY_ID_SYMBOLS = 5;
+ public static final int CATEGORY_ID_EMOTICONS = 6;
+
+ private static class CategoryProperties {
+ public int mCategoryId;
+ public int mPageCount;
+ public CategoryProperties(final int categoryId, final int pageCount) {
+ mCategoryId = categoryId;
+ mPageCount = pageCount;
+ }
+ }
+
+ private static class EmojiCategory {
+ private static final String[] sCategoryName = {
+ "recents",
+ "people",
+ "objects",
+ "nature",
+ "places",
+ "symbols",
+ "emoticons" };
+ private static final int[] sCategoryIcon = new int[] {
+ R.drawable.ic_emoji_recent_light,
+ R.drawable.ic_emoji_people_light,
+ R.drawable.ic_emoji_objects_light,
+ R.drawable.ic_emoji_nature_light,
+ R.drawable.ic_emoji_places_light,
+ R.drawable.ic_emoji_symbols_light,
+ 0 };
+ private static final String[] sCategoryLabel =
+ { null, null, null, null, null, null, ":-)" };
+ private static final int[] sCategoryElementId = {
+ KeyboardId.ELEMENT_EMOJI_RECENTS,
+ KeyboardId.ELEMENT_EMOJI_CATEGORY1,
+ KeyboardId.ELEMENT_EMOJI_CATEGORY2,
+ KeyboardId.ELEMENT_EMOJI_CATEGORY3,
+ KeyboardId.ELEMENT_EMOJI_CATEGORY4,
+ KeyboardId.ELEMENT_EMOJI_CATEGORY5,
+ KeyboardId.ELEMENT_EMOJI_CATEGORY6 };
+ private final SharedPreferences mPrefs;
+ private final int mMaxPageKeyCount;
+ private final KeyboardLayoutSet mLayoutSet;
+ private final HashMap<String, Integer> mCategoryNameToIdMap = CollectionUtils.newHashMap();
+ private final ArrayList<CategoryProperties> mShownCategories =
+ CollectionUtils.newArrayList();
+ private final ConcurrentHashMap<Long, DynamicGridKeyboard>
+ mCategoryKeyboardMap = new ConcurrentHashMap<Long, DynamicGridKeyboard>();
+
+ private int mCurrentCategoryId = CATEGORY_ID_UNSPECIFIED;
+ private int mCurrentCategoryPageId = 0;
+
+ public EmojiCategory(final SharedPreferences prefs, final Resources res,
+ final KeyboardLayoutSet layoutSet) {
+ mPrefs = prefs;
+ mMaxPageKeyCount = res.getInteger(R.integer.emoji_keyboard_max_key_count);
+ mLayoutSet = layoutSet;
+ for (int i = 0; i < sCategoryName.length; ++i) {
+ mCategoryNameToIdMap.put(sCategoryName[i], i);
+ }
+ addShownCategoryId(CATEGORY_ID_RECENTS);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ addShownCategoryId(CATEGORY_ID_PEOPLE);
+ addShownCategoryId(CATEGORY_ID_OBJECTS);
+ addShownCategoryId(CATEGORY_ID_NATURE);
+ addShownCategoryId(CATEGORY_ID_PLACES);
+ mCurrentCategoryId = CATEGORY_ID_PEOPLE;
+ } else {
+ mCurrentCategoryId = CATEGORY_ID_SYMBOLS;
+ }
+ addShownCategoryId(CATEGORY_ID_SYMBOLS);
+ addShownCategoryId(CATEGORY_ID_EMOTICONS);
+ getKeyboard(CATEGORY_ID_RECENTS, 0 /* cagetoryPageId */)
+ .loadRecentKeys(mCategoryKeyboardMap.values());
+ }
+
+ private void addShownCategoryId(int categoryId) {
+ // Load a keyboard of categoryId
+ getKeyboard(categoryId, 0 /* cagetoryPageId */);
+ final CategoryProperties properties =
+ new CategoryProperties(categoryId, getCategoryPageCount(categoryId));
+ mShownCategories.add(properties);
+ }
+
+ public String getCategoryName(int categoryId, int categoryPageId) {
+ return sCategoryName[categoryId] + "-" + categoryPageId;
+ }
+
+ public int getCategoryId(String name) {
+ final String[] strings = name.split("-");
+ return mCategoryNameToIdMap.get(strings[0]);
+ }
+
+ public int getCategoryIcon(int categoryId) {
+ return sCategoryIcon[categoryId];
+ }
+
+ public String getCategoryLabel(int categoryId) {
+ return sCategoryLabel[categoryId];
+ }
+
+ public ArrayList<CategoryProperties> getShownCategories() {
+ return mShownCategories;
+ }
+
+ public int getCurrentCategoryId() {
+ return mCurrentCategoryId;
+ }
+
+ public void setCurrentCategoryId(int categoryId) {
+ mCurrentCategoryId = categoryId;
+ }
+
+ public void setCurrentCategoryPageId(int id) {
+ mCurrentCategoryPageId = id;
+ }
+
+ public void saveLastTypedCategoryPage() {
+ Settings.writeEmojiCategoryLastTypedId(
+ mPrefs, mCurrentCategoryId, mCurrentCategoryPageId);
+ }
+
+ public boolean isInRecentTab() {
+ return mCurrentCategoryId == CATEGORY_ID_RECENTS;
+ }
+
+ public int getTabIdFromCategoryId(int categoryId) {
+ for (int i = 0; i < mShownCategories.size(); ++i) {
+ if (mShownCategories.get(i).mCategoryId == categoryId) {
+ return i;
+ }
+ }
+ Log.w(TAG, "categoryId not found: " + categoryId);
+ return 0;
+ }
+
+ // Returns the view pager's page position for the categoryId
+ public int getPageIdFromCategoryId(int categoryId) {
+ final int lastSavedCategoryPageId =
+ Settings.readEmojiCategoryLastTypedId(mPrefs, categoryId);
+ int sum = 0;
+ for (int i = 0; i < mShownCategories.size(); ++i) {
+ final CategoryProperties props = mShownCategories.get(i);
+ if (props.mCategoryId == categoryId) {
+ return sum + lastSavedCategoryPageId;
+ }
+ sum += props.mPageCount;
+ }
+ Log.w(TAG, "categoryId not found: " + categoryId);
+ return 0;
+ }
+
+ public int getRecentTabId() {
+ return getTabIdFromCategoryId(CATEGORY_ID_RECENTS);
+ }
+
+ private int getCategoryPageCount(int categoryId) {
+ final Keyboard keyboard = mLayoutSet.getKeyboard(sCategoryElementId[categoryId]);
+ return (keyboard.getKeys().length - 1) / mMaxPageKeyCount + 1;
+ }
+
+ // Returns a pair of the category id and the category page id from the view pager's page
+ // 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) {
+ int sum = 0;
+ for (CategoryProperties properties : mShownCategories) {
+ final int temp = sum;
+ sum += properties.mPageCount;
+ if (sum > position) {
+ return new Pair<Integer, Integer>(properties.mCategoryId, position - temp);
+ }
+ }
+ return null;
+ }
+
+ // Returns a keyboard from the view pager's page position.
+ public DynamicGridKeyboard getKeyboardFromPagePosition(int position) {
+ final Pair<Integer, Integer> categoryAndId =
+ getCategoryIdAndPageIdFromPagePosition(position);
+ if (categoryAndId != null) {
+ return getKeyboard(categoryAndId.first, categoryAndId.second);
+ }
+ 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);
+ }
+ kbd = mCategoryKeyboardMap.get(key);
+ } else {
+ kbd = new DynamicGridKeyboard(mPrefs,
+ mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
+ mMaxPageKeyCount, categoryId, 0 /* categoryPageId */);
+ mCategoryKeyboardMap.put(key, kbd);
+ }
+ } else {
+ kbd = mCategoryKeyboardMap.get(key);
+ }
+ return kbd;
+ }
+ }
+
+ public int getTotalPageCountOfAllCategories() {
+ int sum = 0;
+ for (CategoryProperties properties : mShownCategories) {
+ sum += properties.mPageCount;
+ }
+ 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;
+ }
+ });
+ final int pageCount = (keys.length - 1) / maxPageCount + 1;
+ final Key[][] retval = new Key[pageCount][maxPageCount];
+ for (int i = 0; i < keys.length; ++i) {
+ retval[i / maxPageCount][i % maxPageCount] = keys[i];
+ }
+ return retval;
+ }
+ }
+
+ private final EmojiCategory mEmojiCategory;
public EmojiKeyboardView(final Context context, final AttributeSet attrs) {
this(context, attrs, R.attr.emojiKeyboardViewStyle);
@@ -116,6 +363,8 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
mKeyBackgroundId = keyboardViewAttr.getResourceId(
R.styleable.KeyboardView_keyBackground, 0);
+ mEmojiFunctionalKeyBackgroundId = keyboardViewAttr.getResourceId(
+ R.styleable.KeyboardView_keyBackgroundEmojiFunctional, 0);
keyboardViewAttr.recycle();
final TypedArray emojiKeyboardViewAttr = context.obtainStyledAttributes(attrs,
R.styleable.EmojiKeyboardView, defStyle, R.style.EmojiKeyboardView);
@@ -125,15 +374,14 @@ public final class EmojiKeyboardView 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);
builder.setSubtype(SubtypeSwitcher.getInstance().getEmojiSubtype());
- // TODO: Make Keyboard height variable.
builder.setKeyboardGeometry(ResourceUtils.getDefaultKeyboardWidth(res),
- (int)(ResourceUtils.getDefaultKeyboardHeight(res)
- - res.getDimension(R.dimen.suggestions_strip_height)));
+ emojiLp.mEmojiKeyboardHeight);
builder.setOptions(false, false, false /* lanuageSwitchKeyEnabled */);
- final KeyboardLayoutSet layoutSet = builder.build();
- mEmojiKeyboardAdapter = new EmojiKeyboardAdapter(layoutSet, this);
- // TODO: Save/restore recent keys from/to preferences.
+ mLayoutSet = builder.build();
+ mEmojiCategory = new EmojiCategory(PreferenceManager.getDefaultSharedPreferences(context),
+ context.getResources(), builder.build());
}
@Override
@@ -149,23 +397,21 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
setMeasuredDimension(width, height);
}
- private void addTab(final TabHost host, final int category) {
- final String tabId = sCategoryName[category];
- sCategoryNameToIdMap.put(tabId, category);
+ private void addTab(final TabHost host, final int categoryId) {
+ final String tabId = mEmojiCategory.getCategoryName(categoryId, 0 /* categoryPageId */);
final TabHost.TabSpec tspec = host.newTabSpec(tabId);
tspec.setContent(R.id.emoji_keyboard_dummy);
- if (sCategoryIcon[category] != 0) {
+ if (mEmojiCategory.getCategoryIcon(categoryId) != 0) {
final ImageView iconView = (ImageView)LayoutInflater.from(getContext()).inflate(
R.layout.emoji_keyboard_tab_icon, null);
- iconView.setImageResource(sCategoryIcon[category]);
+ iconView.setImageResource(mEmojiCategory.getCategoryIcon(categoryId));
tspec.setIndicator(iconView);
}
- if (sCategoryLabel[category] != null) {
+ if (mEmojiCategory.getCategoryLabel(categoryId) != null) {
final TextView textView = (TextView)LayoutInflater.from(getContext()).inflate(
R.layout.emoji_keyboard_tab_label, null);
- textView.setText(sCategoryLabel[category]);
+ textView.setText(mEmojiCategory.getCategoryLabel(categoryId));
textView.setTextColor(mTabLabelColor);
- textView.setBackgroundResource(mKeyBackgroundId);
tspec.setIndicator(textView);
}
host.addTab(tspec);
@@ -175,55 +421,60 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
protected void onFinishInflate() {
mTabHost = (TabHost)findViewById(R.id.emoji_category_tabhost);
mTabHost.setup();
- addTab(mTabHost, CATEGORY_RECENTS);
- addTab(mTabHost, CATEGORY_PEOPLE);
- addTab(mTabHost, CATEGORY_OBJECTS);
- addTab(mTabHost, CATEGORY_NATURE);
- addTab(mTabHost, CATEGORY_PLACES);
- addTab(mTabHost, CATEGORY_SYMBOLS);
- addTab(mTabHost, CATEGORY_EMOTICONS);
+ for (final CategoryProperties properties : mEmojiCategory.getShownCategories()) {
+ addTab(mTabHost, properties.mCategoryId);
+ }
mTabHost.setOnTabChangedListener(this);
mTabHost.getTabWidget().setStripEnabled(true);
+ mEmojiKeyboardAdapter = new EmojiKeyboardAdapter(mEmojiCategory, mLayoutSet, this);
+
mEmojiPager = (ViewPager)findViewById(R.id.emoji_keyboard_pager);
mEmojiPager.setAdapter(mEmojiKeyboardAdapter);
mEmojiPager.setOnPageChangeListener(this);
mEmojiPager.setOffscreenPageLimit(0);
- final ViewGroup.LayoutParams lp = mEmojiPager.getLayoutParams();
final Resources res = getResources();
- lp.height = ResourceUtils.getDefaultKeyboardHeight(res)
- - res.getDimensionPixelSize(R.dimen.suggestions_strip_height);
- mEmojiPager.setLayoutParams(lp);
+ final EmojiLayoutParams emojiLp = new EmojiLayoutParams(res);
+ emojiLp.setPagerProps(mEmojiPager);
+
+ setCurrentCategoryId(mEmojiCategory.getCurrentCategoryId(), true /* force */);
- // TODO: Record current category.
- final int category = CATEGORY_PEOPLE;
- setCurrentCategory(category, true /* force */);
+ final LinearLayout actionBar = (LinearLayout)findViewById(R.id.emoji_action_bar);
+ emojiLp.setActionBarProps(actionBar);
// TODO: Implement auto repeat, using View.OnTouchListener?
- final View deleteKey = findViewById(R.id.emoji_keyboard_delete);
- deleteKey.setBackgroundResource(mKeyBackgroundId);
+ final ImageView deleteKey = (ImageView)findViewById(R.id.emoji_keyboard_delete);
+ deleteKey.setBackgroundResource(mEmojiFunctionalKeyBackgroundId);
deleteKey.setTag(Constants.CODE_DELETE);
deleteKey.setOnClickListener(this);
- final View alphabetKey = findViewById(R.id.emoji_keyboard_alphabet);
- alphabetKey.setBackgroundResource(mKeyBackgroundId);
+ final ImageView alphabetKey = (ImageView)findViewById(R.id.emoji_keyboard_alphabet);
+ alphabetKey.setBackgroundResource(mEmojiFunctionalKeyBackgroundId);
alphabetKey.setTag(Constants.CODE_SWITCH_ALPHA_SYMBOL);
alphabetKey.setOnClickListener(this);
- final View sendKey = findViewById(R.id.emoji_keyboard_send);
- sendKey.setBackgroundResource(mKeyBackgroundId);
+ final ImageView spaceKey = (ImageView)findViewById(R.id.emoji_keyboard_space);
+ spaceKey.setBackgroundResource(mKeyBackgroundId);
+ spaceKey.setTag(Constants.CODE_SPACE);
+ spaceKey.setOnClickListener(this);
+ emojiLp.setKeyProps(spaceKey);
+ final ImageView sendKey = (ImageView)findViewById(R.id.emoji_keyboard_send);
+ sendKey.setBackgroundResource(mEmojiFunctionalKeyBackgroundId);
sendKey.setTag(Constants.CODE_ENTER);
sendKey.setOnClickListener(this);
}
@Override
public void onTabChanged(final String tabId) {
- final int category = sCategoryNameToIdMap.get(tabId);
- setCurrentCategory(category, false /* force */);
+ final int categoryId = mEmojiCategory.getCategoryId(tabId);
+ setCurrentCategoryId(categoryId, false /* force */);
}
@Override
public void onPageSelected(final int position) {
- setCurrentCategory(position, false /* force */);
+ final Pair<Integer, Integer> newPos =
+ mEmojiCategory.getCategoryIdAndPageIdFromPagePosition(position);
+ setCurrentCategoryId(newPos.first /* categoryId */, false /* force */);
+ mEmojiCategory.setCurrentCategoryPageId(newPos.second /* categoryPageId */);
}
@Override
@@ -254,7 +505,11 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
@Override
public void onKeyClick(final Key key) {
- mEmojiKeyboardAdapter.addRecentKey(key);
+ // TODO: Save emoticons to recents
+ if (mEmojiCategory.getCurrentCategoryId() != CATEGORY_ID_EMOTICONS) {
+ mEmojiKeyboardAdapter.addRecentKey(key);
+ }
+ mEmojiCategory.saveLastTypedCategoryPage();
final int code = key.getCode();
if (code == Constants.CODE_OUTPUT_TEXT) {
mKeyboardActionListener.onTextInput(key.getOutputText());
@@ -271,43 +526,46 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
mKeyboardActionListener = listener;
}
- private void setCurrentCategory(final int category, final boolean force) {
- if (mCurrentCategory == category && !force) {
+ private void setCurrentCategoryId(final int categoryId, final boolean force) {
+ if (mEmojiCategory.getCurrentCategoryId() == categoryId && !force) {
return;
}
- mCurrentCategory = category;
- if (force || mEmojiPager.getCurrentItem() != category) {
- mEmojiPager.setCurrentItem(category, true /* smoothScroll */);
+ mEmojiCategory.setCurrentCategoryId(categoryId);
+ final int newTabId = mEmojiCategory.getTabIdFromCategoryId(categoryId);
+ final int newCategoryPageId = mEmojiCategory.getPageIdFromCategoryId(categoryId);
+ if (force || mEmojiCategory.getCategoryIdAndPageIdFromPagePosition(
+ mEmojiPager.getCurrentItem()).first != categoryId) {
+ mEmojiPager.setCurrentItem(newCategoryPageId, false /* smoothScroll */);
}
- if (force || mTabHost.getCurrentTab() != category) {
- mTabHost.setCurrentTab(category);
+ if (force || mTabHost.getCurrentTab() != newTabId) {
+ mTabHost.setCurrentTab(newTabId);
}
- // TODO: Record current category
}
private static class EmojiKeyboardAdapter extends PagerAdapter {
private final ScrollKeyboardView.OnKeyClickListener mListener;
- private final KeyboardLayoutSet mLayoutSet;
- private final RecentsKeyboard mRecentsKeyboard;
+ private final DynamicGridKeyboard mRecentsKeyboard;
private final SparseArray<ScrollKeyboardView> mActiveKeyboardView =
CollectionUtils.newSparseArray();
- private int mActivePosition = CATEGORY_UNSPECIFIED;
+ private final EmojiCategory mEmojiCategory;
+ private int mActivePosition = 0;
- public EmojiKeyboardAdapter(final KeyboardLayoutSet layoutSet,
+ public EmojiKeyboardAdapter(final EmojiCategory emojiCategory,
+ final KeyboardLayoutSet layoutSet,
final ScrollKeyboardView.OnKeyClickListener listener) {
+ mEmojiCategory = emojiCategory;
mListener = listener;
- mLayoutSet = layoutSet;
- mRecentsKeyboard = new RecentsKeyboard(
- layoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS));
+ mRecentsKeyboard = mEmojiCategory.getKeyboard(CATEGORY_ID_RECENTS, 0);
}
public void addRecentKey(final Key key) {
- if (mActivePosition == CATEGORY_RECENTS) {
+ if (mEmojiCategory.isInRecentTab()) {
return;
}
- mRecentsKeyboard.addRecentKey(key);
- final KeyboardView recentKeyboardView = mActiveKeyboardView.get(CATEGORY_RECENTS);
+ mRecentsKeyboard.addKeyFirst(key);
+ final KeyboardView recentKeyboardView =
+ mActiveKeyboardView.get(mEmojiCategory.getRecentTabId());
if (recentKeyboardView != null) {
recentKeyboardView.invalidateAllKeys();
}
@@ -315,7 +573,7 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
@Override
public int getCount() {
- return sCategoryName.length;
+ return mEmojiCategory.getTotalPageCountOfAllCategories();
}
@Override
@@ -333,9 +591,8 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
@Override
public Object instantiateItem(final ViewGroup container, final int position) {
- final int elementId = sCategoryElementId[position];
- final Keyboard keyboard = (elementId == KeyboardId.ELEMENT_EMOJI_RECENTS)
- ? mRecentsKeyboard : mLayoutSet.getKeyboard(elementId);
+ final Keyboard keyboard =
+ mEmojiCategory.getKeyboardFromPagePosition(position);
final LayoutInflater inflater = LayoutInflater.from(container.getContext());
final View view = inflater.inflate(
R.layout.emoji_keyboard_page, container, false /* attachToRoot */);
diff --git a/java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java b/java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java
new file mode 100644
index 000000000..5570d594d
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java
@@ -0,0 +1,80 @@
+/*
+ * 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;
+
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.ResourceUtils;
+
+import android.content.res.Resources;
+import android.support.v4.view.ViewPager;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+public class EmojiLayoutParams {
+ private static final int DEFAULT_KEYBOARD_ROWS = 4;
+
+ public final int mEmojiPagerHeight;
+ private final int mEmojiPagerBottomMargin;
+ public final int mEmojiKeyboardHeight;
+ public final int mEmojiActionBarHeight;
+ public final int mKeyVerticalGap;
+ private final int mKeyHorizontalGap;
+ private final int mBottomPadding;
+ private final int mTopPadding;
+
+ public EmojiLayoutParams(Resources res) {
+ final int defaultKeyboardHeight = ResourceUtils.getDefaultKeyboardHeight(res);
+ final int defaultKeyboardWidth = ResourceUtils.getDefaultKeyboardWidth(res);
+ mKeyVerticalGap = (int) res.getFraction(R.fraction.key_bottom_gap_ics,
+ (int) defaultKeyboardHeight, (int) defaultKeyboardHeight);
+ mBottomPadding = (int) res.getFraction(R.fraction.keyboard_bottom_padding_ics,
+ (int) defaultKeyboardHeight, (int) defaultKeyboardHeight);
+ mTopPadding = (int) res.getFraction(R.fraction.keyboard_top_padding_ics,
+ (int) defaultKeyboardHeight, (int) defaultKeyboardHeight);
+ mKeyHorizontalGap = (int) (res.getFraction(R.fraction.key_horizontal_gap_ics,
+ defaultKeyboardWidth, defaultKeyboardWidth));
+ final int baseheight = defaultKeyboardHeight - mBottomPadding - mTopPadding
+ + mKeyVerticalGap;
+ mEmojiActionBarHeight = ((int) baseheight) / DEFAULT_KEYBOARD_ROWS
+ - (mKeyVerticalGap - mBottomPadding) / 2;
+ mEmojiPagerHeight = defaultKeyboardHeight - mEmojiActionBarHeight;
+ mEmojiPagerBottomMargin = mKeyVerticalGap / 2;
+ mEmojiKeyboardHeight = mEmojiPagerHeight - mEmojiPagerBottomMargin - 1;
+ }
+
+ public void setPagerProps(ViewPager vp) {
+ final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) vp.getLayoutParams();
+ lp.height = mEmojiPagerHeight - mEmojiPagerBottomMargin;
+ lp.bottomMargin = mEmojiPagerBottomMargin;
+ vp.setLayoutParams(lp);
+ }
+
+ public void setActionBarProps(LinearLayout ll) {
+ final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) ll.getLayoutParams();
+ lp.height = mEmojiActionBarHeight;
+ lp.topMargin = 0;
+ lp.bottomMargin = mBottomPadding;
+ ll.setLayoutParams(lp);
+ }
+
+ public void setKeyProps(ImageView ib) {
+ final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) ib.getLayoutParams();
+ lp.leftMargin = mKeyHorizontalGap / 2;
+ lp.rightMargin = mKeyHorizontalGap / 2;
+ ib.setLayoutParams(lp);
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index d128d3595..cc1ffd183 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -314,15 +314,19 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
mState.onCodeInput(code, mLatinIME.getCurrentAutoCapsState());
}
+ public boolean isShowingEmojiKeyboard() {
+ return mEmojiKeyboardView.getVisibility() == View.VISIBLE;
+ }
+
public boolean isShowingMoreKeysPanel() {
- if (mEmojiKeyboardView.getVisibility() == View.VISIBLE) {
+ if (isShowingEmojiKeyboard()) {
return false;
}
return mKeyboardView.isShowingMoreKeysPanel();
}
public View getVisibleKeyboardView() {
- if (mEmojiKeyboardView.getVisibility() == View.VISIBLE) {
+ if (isShowingEmojiKeyboard()) {
return mEmojiKeyboardView;
}
return mKeyboardView;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/RecentsKeyboard.java b/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
index 629c60460..f203eb7d7 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/RecentsKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
@@ -16,33 +16,41 @@
package com.android.inputmethod.keyboard.internal;
+import android.content.SharedPreferences;
import android.text.TextUtils;
+import com.android.inputmethod.keyboard.EmojiKeyboardView;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.utils.CollectionUtils;
import java.util.ArrayDeque;
-import java.util.Random;
+import java.util.Collection;
/**
- * This is a Keyboard class to host recently used keys.
+ * This is a Keyboard class where you can add keys dynamically shown in a grid layout
*/
-// TODO: Save/restore recent keys from/to preferences.
-public class RecentsKeyboard extends Keyboard {
+public class DynamicGridKeyboard extends Keyboard {
private static final int TEMPLATE_KEY_CODE_0 = 0x30;
private static final int TEMPLATE_KEY_CODE_1 = 0x31;
+ // Recent codes are saved as an integer array, so we use comma as a separater.
+ private static final String RECENT_KEY_SEPARATOR = Constants.STRING_COMMA;
+ private final SharedPreferences mPrefs;
private final int mLeftPadding;
private final int mHorizontalStep;
private final int mVerticalStep;
private final int mColumnsNum;
- private final int mMaxRecentKeyCount;
- private final ArrayDeque<RecentKey> mRecentKeys = CollectionUtils.newArrayDeque();
+ private final int mMaxKeyCount;
+ private final boolean mIsRecents;
+ private final ArrayDeque<GridKey> mGridKeys = CollectionUtils.newArrayDeque();
- private Key[] mCachedRecentKeys;
+ private Key[] mCachedGridKeys;
- public RecentsKeyboard(final Keyboard templateKeyboard) {
+ public DynamicGridKeyboard(final SharedPreferences prefs, final Keyboard templateKeyboard,
+ final int maxKeyCount, final int categoryId, final int categoryPageId) {
super(templateKeyboard);
final Key key0 = getTemplateKey(TEMPLATE_KEY_CODE_0);
final Key key1 = getTemplateKey(TEMPLATE_KEY_CODE_1);
@@ -50,8 +58,9 @@ public class RecentsKeyboard extends Keyboard {
mHorizontalStep = Math.abs(key1.getX() - key0.getX());
mVerticalStep = key0.getHeight() + mVerticalGap;
mColumnsNum = mBaseWidth / mHorizontalStep;
- final int rowsNum = mBaseHeight / mVerticalStep;
- mMaxRecentKeyCount = mColumnsNum * rowsNum;
+ mMaxKeyCount = maxKeyCount;
+ mIsRecents = categoryId == EmojiKeyboardView.CATEGORY_ID_RECENTS;
+ mPrefs = prefs;
}
private Key getTemplateKey(final int code) {
@@ -63,32 +72,67 @@ public class RecentsKeyboard extends Keyboard {
throw new RuntimeException("Can't find template key: code=" + code);
}
- private final Random random = new Random();
+ public void addKeyFirst(final Key usedKey) {
+ addKey(usedKey, true);
+ if (mIsRecents) {
+ saveRecentKeys();
+ }
+ }
+
+ public void addKeyLast(final Key usedKey) {
+ addKey(usedKey, false);
+ }
- public void addRecentKey(final Key usedKey) {
- synchronized (mRecentKeys) {
- mCachedRecentKeys = null;
- final RecentKey key = (usedKey instanceof RecentKey)
- ? (RecentKey)usedKey : new RecentKey(usedKey);
- while (mRecentKeys.remove(key)) {
+ private void addKey(final Key usedKey, final boolean addFirst) {
+ synchronized (mGridKeys) {
+ mCachedGridKeys = null;
+ final GridKey key = new GridKey(usedKey);
+ while (mGridKeys.remove(key)) {
// Remove duplicate keys.
}
- mRecentKeys.addFirst(key);
- while (mRecentKeys.size() > mMaxRecentKeyCount) {
- mRecentKeys.removeLast();
+ if (addFirst) {
+ mGridKeys.addFirst(key);
+ } else {
+ mGridKeys.addLast(key);
+ }
+ while (mGridKeys.size() > mMaxKeyCount) {
+ mGridKeys.removeLast();
}
int index = 0;
- for (final RecentKey recentKey : mRecentKeys) {
+ for (final GridKey gridKey : mGridKeys) {
final int keyX = getKeyX(index);
final int keyY = getKeyY(index);
- final int x = keyX+random.nextInt(recentKey.getWidth());
- final int y = keyY+random.nextInt(recentKey.getHeight());
- recentKey.updateCorrdinates(keyX, keyY);
+ gridKey.updateCorrdinates(keyX, keyY);
index++;
}
}
}
+ private void saveRecentKeys() {
+ final StringBuilder sb = new StringBuilder();
+ for (final Key key : mGridKeys) {
+ sb.append(key.getCode()).append(RECENT_KEY_SEPARATOR);
+ }
+ Settings.writeEmojiRecentKeys(mPrefs, sb.toString());
+ }
+
+ public void loadRecentKeys(Collection<DynamicGridKeyboard> keyboards) {
+ final String str = Settings.readEmojiRecentKeys(mPrefs);
+ for (String s : str.split(RECENT_KEY_SEPARATOR)) {
+ if (TextUtils.isEmpty(s)) {
+ continue;
+ }
+ final int code = Integer.valueOf(s);
+ for (DynamicGridKeyboard kbd : keyboards) {
+ final Key key = kbd.getKey(code);
+ if (key != null) {
+ addKeyLast(key);
+ break;
+ }
+ }
+ }
+ }
+
private int getKeyX(final int index) {
final int column = index % mColumnsNum;
return column * mHorizontalStep + mLeftPadding;
@@ -101,26 +145,26 @@ public class RecentsKeyboard extends Keyboard {
@Override
public Key[] getKeys() {
- synchronized (mRecentKeys) {
- if (mCachedRecentKeys != null) {
- return mCachedRecentKeys;
+ synchronized (mGridKeys) {
+ if (mCachedGridKeys != null) {
+ return mCachedGridKeys;
}
- mCachedRecentKeys = mRecentKeys.toArray(new Key[mRecentKeys.size()]);
- return mCachedRecentKeys;
+ mCachedGridKeys = mGridKeys.toArray(new Key[mGridKeys.size()]);
+ return mCachedGridKeys;
}
}
@Override
public Key[] getNearestKeys(final int x, final int y) {
- // TODO: Calculate the nearest key index in mRecentKeys from x and y.
+ // TODO: Calculate the nearest key index in mGridKeys from x and y.
return getKeys();
}
- static final class RecentKey extends Key {
+ static final class GridKey extends Key {
private int mCurrentX;
private int mCurrentY;
- public RecentKey(final Key originalKey) {
+ public GridKey(final Key originalKey) {
super(originalKey);
}
@@ -151,7 +195,7 @@ public class RecentsKeyboard extends Keyboard {
@Override
public String toString() {
- return "RecentKey: " + super.toString();
+ return "GridKey: " + super.toString();
}
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
index 7008b0619..a72595f7c 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
@@ -383,7 +383,7 @@ public final class KeyboardTextsSet {
// Label for "switch to more symbol" modifier key. Must be short to fit on key!
/* 124 */ "= \\ <",
// Label for "switch to more symbol" modifier key on tablets. Must be short to fit on key!
- /* 125 */ "~ [ {",
+ /* 125 */ "~ [ <",
// Label for "Tab" key. Must be short to fit on key!
/* 126 */ "Tab",
// Label for "switch to phone numeric" key. Must be short to fit on key!
@@ -2056,6 +2056,25 @@ public final class KeyboardTextsSet {
/* 45 */ "\u0410\u0411\u0412",
};
+ /* Language lo: Lao */
+ private static final String[] LANGUAGE_lo = {
+ /* 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 */
+ // 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~ */
+ null, null, null, null, null,
+ /* ~50 */
+ // U+20AD: "₭" KIP SIGN
+ /* 51 */ "\u20AD",
+ };
+
/* Language lt: Lithuanian */
private static final String[] LANGUAGE_lt = {
// U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
@@ -2347,6 +2366,63 @@ public final class KeyboardTextsSet {
/* 47 */ "!text/double_9qm_rqm",
};
+ /* Language ne: Nepali */
+ private static final String[] LANGUAGE_ne = {
+ /* 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 */
+ // 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~ */
+ null, null, null, null, null,
+ /* ~50 */
+ // U+0930/U+0941/U+002E "रु." NEPALESE RUPEE SIGN
+ /* 51 */ "\u0930\u0941.",
+ /* 52~ */
+ null, null, null, null, null, null, null, null, null, null, null,
+ /* ~62 */
+ // U+0967: "१" DEVANAGARI DIGIT ONE
+ /* 63 */ "\u0967",
+ // U+0968: "२" DEVANAGARI DIGIT TWO
+ /* 64 */ "\u0968",
+ // U+0969: "३" DEVANAGARI DIGIT THREE
+ /* 65 */ "\u0969",
+ // U+096A: "४" DEVANAGARI DIGIT FOUR
+ /* 66 */ "\u096A",
+ // U+096B: "५" DEVANAGARI DIGIT FIVE
+ /* 67 */ "\u096B",
+ // U+096C: "६" DEVANAGARI DIGIT SIX
+ /* 68 */ "\u096C",
+ // U+096D: "७" DEVANAGARI DIGIT SEVEN
+ /* 69 */ "\u096D",
+ // U+096E: "८" DEVANAGARI DIGIT EIGHT
+ /* 70 */ "\u096E",
+ // U+096F: "९" DEVANAGARI DIGIT NINE
+ /* 71 */ "\u096F",
+ // U+0966: "०" DEVANAGARI DIGIT ZERO
+ /* 72 */ "\u0966",
+ // Label for "switch to symbols" key.
+ /* 73 */ "?\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",
+ };
+
/* Language nl: Dutch */
private static final String[] LANGUAGE_nl = {
// U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
@@ -3332,11 +3408,13 @@ public final class KeyboardTextsSet {
"ka", LANGUAGE_ka, /* Georgian */
"kk", LANGUAGE_kk, /* Kazakh */
"ky", LANGUAGE_ky, /* Kirghiz */
+ "lo", LANGUAGE_lo, /* Lao */
"lt", LANGUAGE_lt, /* Lithuanian */
"lv", LANGUAGE_lv, /* Latvian */
"mk", LANGUAGE_mk, /* Macedonian */
"mn", LANGUAGE_mn, /* Mongolian */
"nb", LANGUAGE_nb, /* Norwegian Bokmål */
+ "ne", LANGUAGE_ne, /* Nepali */
"nl", LANGUAGE_nl, /* Dutch */
"pl", LANGUAGE_pl, /* Polish */
"pt", LANGUAGE_pt, /* Portuguese */
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index e8b06570f..b49cd80ab 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -19,6 +19,7 @@ package com.android.inputmethod.latin;
import android.text.TextUtils;
import android.util.SparseArray;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.settings.NativeSuggestOptions;
@@ -40,14 +41,20 @@ public final class BinaryDictionary extends Dictionary {
private static final int MAX_WORD_LENGTH = Constants.DICTIONARY_MAX_WORD_LENGTH;
// Must be equal to MAX_RESULTS in native/jni/src/defines.h
private static final int MAX_RESULTS = 18;
+ // Required space count for auto commit.
+ // TODO: Remove this heuristic.
+ private static final int SPACE_COUNT_FOR_AUTO_COMMIT = 3;
private long mNativeDict;
private final Locale mLocale;
+ private final long mDictSize;
+ private final String mDictFilePath;
private final int[] mInputCodePoints = new int[MAX_WORD_LENGTH];
private final int[] mOutputCodePoints = new int[MAX_WORD_LENGTH * MAX_RESULTS];
private final int[] mSpaceIndices = new int[MAX_RESULTS];
private final int[] mOutputScores = new int[MAX_RESULTS];
private final int[] mOutputTypes = new int[MAX_RESULTS];
+ private final int[] mOutputAutoCommitFirstWordConfidence = new int[MAX_RESULTS];
private final NativeSuggestOptions mNativeSuggestOptions = new NativeSuggestOptions();
@@ -62,7 +69,7 @@ public final class BinaryDictionary extends Dictionary {
if (traverseSession == null) {
traverseSession = mDicTraverseSessions.get(traverseSessionId);
if (traverseSession == null) {
- traverseSession = new DicTraverseSession(mLocale, mNativeDict);
+ traverseSession = new DicTraverseSession(mLocale, mNativeDict, mDictSize);
mDicTraverseSessions.put(traverseSessionId, traverseSession);
}
}
@@ -85,6 +92,8 @@ public final class BinaryDictionary extends Dictionary {
final boolean isUpdatable) {
super(dictType);
mLocale = locale;
+ mDictSize = length;
+ mDictFilePath = filename;
mNativeSuggestOptions.setUseFullEditDistance(useFullEditDistance);
loadDictionary(filename, offset, length, isUpdatable);
}
@@ -95,6 +104,9 @@ public final class BinaryDictionary extends Dictionary {
private static native long openNative(String sourceDir, long dictOffset, long dictSize,
boolean isUpdatable);
+ private static native void flushNative(long dict, String filePath);
+ private static native boolean needsToRunGCNative(long dict);
+ private static native void flushWithGCNative(long dict, String filePath);
private static native void closeNative(long dict);
private static native int getProbabilityNative(long dict, int[] word);
private static native boolean isValidBigramNative(long dict, int[] word0, int[] word1);
@@ -102,7 +114,8 @@ public final class BinaryDictionary extends Dictionary {
long traverseSession, int[] xCoordinates, int[] yCoordinates, int[] times,
int[] pointerIds, int[] inputCodePoints, int inputSize, int commitPoint,
int[] suggestOptions, int[] prevWordCodePointArray,
- int[] outputCodePoints, int[] outputScores, int[] outputIndices, int[] outputTypes);
+ int[] outputCodePoints, int[] outputScores, int[] outputIndices, int[] outputTypes,
+ 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);
@@ -155,7 +168,7 @@ public final class BinaryDictionary extends Dictionary {
ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(), mInputCodePoints,
inputSize, 0 /* commitPoint */, mNativeSuggestOptions.getOptions(),
prevWordCodePointArray, mOutputCodePoints, mOutputScores, mSpaceIndices,
- mOutputTypes);
+ mOutputTypes, mOutputAutoCommitFirstWordConfidence);
final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
for (int j = 0; j < count; ++j) {
final int start = j * MAX_WORD_LENGTH;
@@ -179,7 +192,8 @@ public final class BinaryDictionary extends Dictionary {
// flags too and pass mOutputTypes[j] instead of kind
suggestions.add(new SuggestedWordInfo(new String(mOutputCodePoints, start, len),
score, kind, this /* sourceDict */,
- mSpaceIndices[0] /* indexOfTouchPointOfSecondWord */));
+ mSpaceIndices[j] /* indexOfTouchPointOfSecondWord */,
+ mOutputAutoCommitFirstWordConfidence[0]));
}
}
return suggestions;
@@ -253,6 +267,40 @@ public final class BinaryDictionary extends Dictionary {
removeBigramWordsNative(mNativeDict, codePoints0, codePoints1);
}
+ @UsedForTesting
+ public void flush() {
+ if (!isValidDictionary()) return;
+ flushNative(mNativeDict, mDictFilePath);
+ }
+
+ @UsedForTesting
+ public void flushWithGC() {
+ if (!isValidDictionary()) return;
+ flushWithGCNative(mNativeDict, mDictFilePath);
+ }
+
+ @UsedForTesting
+ public boolean needsToRunGC() {
+ if (!isValidDictionary()) return false;
+ return needsToRunGCNative(mNativeDict);
+ }
+
+ @Override
+ public boolean shouldAutoCommit(final SuggestedWordInfo candidate) {
+ // TODO: actually use the confidence rather than use this completely broken heuristic
+ final String word = candidate.mWord;
+ final int length = word.length();
+ int remainingSpaces = SPACE_COUNT_FOR_AUTO_COMMIT;
+ for (int i = 0; i < length; ++i) {
+ // This is okay because no low-surrogate and no high-surrogate can ever match the
+ // space character, so we don't need to take care of iterating on code points.
+ if (Constants.CODE_SPACE == word.charAt(i)) {
+ if (0 >= --remainingSpaces) return true;
+ }
+ }
+ return false;
+ }
+
@Override
public void close() {
synchronized (mDicTraverseSessions) {
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index 2b6d983c0..566184244 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -21,9 +21,10 @@ import android.content.SharedPreferences;
import android.content.res.AssetFileDescriptor;
import android.util.Log;
+import com.android.inputmethod.latin.makedict.DictDecoder;
+import com.android.inputmethod.latin.makedict.FormatSpec;
import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
-import com.android.inputmethod.latin.makedict.Ver3DictDecoder;
import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.DictionaryInfoUtils;
import com.android.inputmethod.latin.utils.LocaleUtils;
@@ -228,7 +229,7 @@ final public class BinaryDictionaryGetter {
private static boolean hackCanUseDictionaryFile(final Locale locale, final File f) {
try {
// Read the version of the file
- final Ver3DictDecoder dictDecoder = new Ver3DictDecoder(f);
+ final DictDecoder dictDecoder = FormatSpec.getDictDecoder(f);
final FileHeader header = dictDecoder.readHeader();
final String version = header.mDictionaryOptions.mAttributes.get(VERSION_KEY);
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index 8aec03f71..029ba02ed 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -220,7 +220,11 @@ public final class Constants {
}
}
+ public static final int MAX_INT_BIT_COUNT = 32;
+ public static final String STRING_COMMA = ",";
+
private Constants() {
// This utility class is not publicly instantiable.
}
+
}
diff --git a/java/src/com/android/inputmethod/latin/DicTraverseSession.java b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
index 45b281318..8d295adee 100644
--- a/java/src/com/android/inputmethod/latin/DicTraverseSession.java
+++ b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
@@ -25,16 +25,16 @@ public final class DicTraverseSession {
JniUtils.loadNativeLibrary();
}
- private static native long setDicTraverseSessionNative(String locale);
+ private static native long setDicTraverseSessionNative(String locale, long dictSize);
private static native void initDicTraverseSessionNative(long nativeDicTraverseSession,
long dictionary, int[] previousWord, int previousWordLength);
private static native void releaseDicTraverseSessionNative(long nativeDicTraverseSession);
private long mNativeDicTraverseSession;
- public DicTraverseSession(Locale locale, long dictionary) {
+ public DicTraverseSession(Locale locale, long dictionary, long dictSize) {
mNativeDicTraverseSession = createNativeDicTraverseSession(
- locale != null ? locale.toString() : "");
+ locale != null ? locale.toString() : "", dictSize);
initSession(dictionary);
}
@@ -51,8 +51,8 @@ public final class DicTraverseSession {
mNativeDicTraverseSession, dictionary, previousWord, previousWordLength);
}
- private final long createNativeDicTraverseSession(String locale) {
- return setDicTraverseSessionNative(locale);
+ private final long createNativeDicTraverseSession(String locale, long dictSize) {
+ return setDicTraverseSessionNative(locale, dictSize);
}
private void closeInternal() {
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index 8a3a88438..fa79f5af7 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -137,7 +137,10 @@ public abstract class Dictionary {
}
/**
- * Whether we think this suggestion should trigger an auto-commit.
+ * Whether we think this suggestion should trigger an auto-commit. prevWord is the word
+ * before the suggestion, so that we can use n-gram frequencies.
+ * @param candidate The candidate suggestion, in whole (not only the first part).
+ * @return whether we should auto-commit or not.
*/
public boolean shouldAutoCommit(final SuggestedWordInfo candidate) {
// If we don't have support for auto-commit, or if we don't know, we return false to
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index c884e7b1f..2a9076436 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -617,4 +617,14 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
});
return holder.get(false, TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS);
}
+
+ @UsedForTesting
+ public void shutdownExecutorForTests() {
+ getExecutor(mFilename).shutdown();
+ }
+
+ @UsedForTesting
+ public boolean isTerminatedForTests() {
+ return getExecutor(mFilename).isTerminated();
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index ba7d1a2b0..d491f988a 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -344,7 +344,8 @@ public class ExpandableDictionary extends Dictionary {
// 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_AN_INDEX /* indexOfTouchPointOfSecondWord */,
+ SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
if (suggestions.size() >= Suggest.MAX_SUGGESTIONS) return false;
}
if (null != node.mShortcutTargets) {
@@ -353,7 +354,8 @@ public class ExpandableDictionary extends Dictionary {
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_AN_INDEX /* indexOfTouchPointOfSecondWord */,
+ SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
if (suggestions.size() > Suggest.MAX_SUGGESTIONS) return false;
}
}
@@ -604,7 +606,8 @@ public class ExpandableDictionary extends Dictionary {
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_AN_INDEX /* indexOfTouchPointOfSecondWord */,
+ SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
}
}
}
diff --git a/java/src/com/android/inputmethod/latin/InputPointers.java b/java/src/com/android/inputmethod/latin/InputPointers.java
index e96a46e12..2e638aaf3 100644
--- a/java/src/com/android/inputmethod/latin/InputPointers.java
+++ b/java/src/com/android/inputmethod/latin/InputPointers.java
@@ -105,6 +105,17 @@ public final class InputPointers {
mTimes.append(times, startPos, length);
}
+ /**
+ * Shift to the left by elementCount, discarding elementCount pointers at the start.
+ * @param elementCount how many elements to shift.
+ */
+ public void shift(final int elementCount) {
+ mXCoordinates.shift(elementCount);
+ mYCoordinates.shift(elementCount);
+ mPointerIds.shift(elementCount);
+ mTimes.shift(elementCount);
+ }
+
public void reset() {
final int defaultCapacity = mDefaultCapacity;
mXCoordinates.reset(defaultCapacity);
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 6c83ac7ed..d3a18d410 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -95,7 +95,6 @@ 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.PositionalInfoForUserDictPendingAddition;
import com.android.inputmethod.latin.utils.RecapitalizeStatus;
import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper;
import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask;
@@ -185,8 +184,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private boolean mIsUserDictionaryAvailable;
private LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
- private PositionalInfoForUserDictPendingAddition
- mPositionalInfoForUserDictPendingAddition = null;
private final WordComposer mWordComposer = new WordComposer();
private final RichInputConnection mConnection = new RichInputConnection(this);
private final RecapitalizeStatus mRecapitalizeStatus = new RecapitalizeStatus();
@@ -898,19 +895,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
currentSettingsValues.mGestureTrailEnabled,
currentSettingsValues.mGestureFloatingPreviewTextEnabled);
- // If we have a user dictionary addition in progress, we should check now if we should
- // replace the previously committed string with the word that has actually been added
- // to the user dictionary.
- if (null != mPositionalInfoForUserDictPendingAddition
- && mPositionalInfoForUserDictPendingAddition.tryReplaceWithActualWord(
- mConnection, editorInfo, mLastSelectionEnd, currentLocale)) {
- mPositionalInfoForUserDictPendingAddition = null;
- }
- // If tryReplaceWithActualWord returns false, we don't know what word was
- // added to the user dictionary yet, so we keep the data and defer processing. The word will
- // be replaced when the user dictionary reports back with the actual word, which ends
- // up calling #onWordAddedToUserDictionary() in this class.
-
initPersonalizationDebugSettings(currentSettingsValues);
if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
@@ -1250,7 +1234,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
int visibleTopY = extraHeight;
// Need to set touchable region only if input view is being shown
if (visibleKeyboardView.isShown()) {
- if (mSuggestionStripView.getVisibility() == View.VISIBLE) {
+ // Note that the height of Emoji layout is the same as the height of the main keyboard
+ // and the suggestion strip
+ if (mKeyboardSwitcher.isShowingEmojiKeyboard()
+ || mSuggestionStripView.getVisibility() == View.VISIBLE) {
visibleTopY -= suggestionsHeight;
}
final int touchY = mKeyboardSwitcher.isShowingMoreKeysPanel() ? 0 : visibleTopY;
@@ -1416,7 +1403,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public void addWordToUserDictionary(final String word) {
if (TextUtils.isEmpty(word)) {
// Probably never supposed to happen, but just in case.
- mPositionalInfoForUserDictPendingAddition = null;
return;
}
final String wordToEdit;
@@ -1428,22 +1414,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mUserDictionary.addWordToUserDictionary(wordToEdit);
}
- public void onWordAddedToUserDictionary(final String newSpelling) {
- // If word was added but not by us, bail out
- if (null == mPositionalInfoForUserDictPendingAddition) return;
- if (mWordComposer.isComposingWord()) {
- // We are late... give up and return
- mPositionalInfoForUserDictPendingAddition = null;
- return;
- }
- mPositionalInfoForUserDictPendingAddition.setActualWordBeingAdded(newSpelling);
- if (mPositionalInfoForUserDictPendingAddition.tryReplaceWithActualWord(
- mConnection, getCurrentInputEditorInfo(), mLastSelectionEnd,
- mSubtypeSwitcher.getCurrentSubtypeLocale())) {
- mPositionalInfoForUserDictPendingAddition = null;
- }
- }
-
private void onSettingsKeyPressed() {
if (isShowingOptionDialog()) return;
showSubtypeSelectorAndSettings();
@@ -1877,10 +1847,18 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public void onUpdateBatchInput(final InputPointers batchPointers) {
- final SuggestedWordInfo candidate = mSuggestedWords.getAutoCommitCandidate();
- if (null != candidate) {
- if (candidate.mSourceDict.shouldAutoCommit(candidate)) {
- // TODO: implement auto-commit
+ if (mSettings.getCurrent().mPhraseGestureEnabled) {
+ final SuggestedWordInfo candidate = mSuggestedWords.getAutoCommitCandidate();
+ if (null != candidate) {
+ if (candidate.mSourceDict.shouldAutoCommit(candidate)) {
+ final String[] commitParts = candidate.mWord.split(" ", 2);
+ batchPointers.shift(candidate.mIndexOfTouchPointOfSecondWord);
+ promotePhantomSpace();
+ mConnection.commitText(commitParts[0], 0);
+ mSpaceState = SPACE_STATE_PHANTOM;
+ mKeyboardSwitcher.updateShiftState();
+ mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode());
+ }
}
}
mInputUpdater.onUpdateBatchInput(batchPointers);
@@ -1893,12 +1871,24 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (TextUtils.isEmpty(batchInputText)) {
return;
}
- mWordComposer.setBatchInputWord(batchInputText);
mConnection.beginBatchEdit();
if (SPACE_STATE_PHANTOM == mSpaceState) {
promotePhantomSpace();
}
- mConnection.setComposingText(batchInputText, 1);
+ if (mSettings.getCurrent().mPhraseGestureEnabled) {
+ // Find the last space
+ final int indexOfLastSpace = batchInputText.lastIndexOf(Constants.CODE_SPACE) + 1;
+ if (0 != indexOfLastSpace) {
+ mConnection.commitText(batchInputText.substring(0, indexOfLastSpace), 1);
+ showSuggestionStrip(suggestedWords.getSuggestedWordsForLastWordOfPhraseGesture());
+ }
+ final String lastWord = batchInputText.substring(indexOfLastSpace);
+ mWordComposer.setBatchInputWord(lastWord);
+ mConnection.setComposingText(lastWord, 1);
+ } else {
+ mWordComposer.setBatchInputWord(batchInputText);
+ mConnection.setComposingText(batchInputText, 1);
+ }
mExpectingUpdateSelection = true;
mConnection.endBatchEdit();
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
@@ -2704,6 +2694,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// recorrection. This is a temporary, stopgap measure that will be removed later.
// TODO: remove this.
if (mAppWorkAroundsUtils.isBrokenByRecorrection()) return;
+ // A simple way to test for support from the TextView.
+ if (!isSuggestionsStripVisible()) return;
// Recorrection is not supported in languages without spaces because we don't know
// how to segment them yet.
if (!mSettings.getCurrent().mCurrentLanguageHasSpaces) return;
@@ -2730,7 +2722,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
suggestions.add(new SuggestedWordInfo(s,
SuggestionStripView.MAX_SUGGESTIONS - i,
SuggestedWordInfo.KIND_RESUMED, Dictionary.DICTIONARY_RESUMED,
- SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */));
+ SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
+ SuggestedWordInfo.NOT_A_CONFIDENCE
+ /* autoCommitFirstWordConfidence */));
}
}
}
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 18ba15872..7815f4d41 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -48,8 +48,9 @@ public final class Suggest {
// Session id for
// {@link #getSuggestedWords(WordComposer,String,ProximityInfo,boolean,int)}.
+ // We are sharing the same ID between typing and gesture to save RAM footprint.
public static final int SESSION_TYPING = 0;
- public static final int SESSION_GESTURE = 1;
+ public static final int SESSION_GESTURE = 0;
// TODO: rename this to CORRECTION_OFF
public static final int CORRECTION_NONE = 0;
@@ -326,7 +327,8 @@ public final class Suggest {
suggestionsContainer.add(0, new SuggestedWordInfo(typedWord,
SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_TYPED,
Dictionary.DICTIONARY_USER_TYPED,
- SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */));
+ SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
+ SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
}
SuggestedWordInfo.removeDups(suggestionsContainer);
@@ -473,7 +475,8 @@ public final class Suggest {
sb.appendCodePoint(Constants.CODE_SINGLE_QUOTE);
}
return new SuggestedWordInfo(sb.toString(), wordInfo.mScore, wordInfo.mKind,
- wordInfo.mSourceDict, wordInfo.mIndexOfTouchPointOfSecondWord);
+ wordInfo.mSourceDict, wordInfo.mIndexOfTouchPointOfSecondWord,
+ SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */);
}
public void close() {
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index b27fd81e9..fed4cdbbb 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -114,7 +114,8 @@ public final class SuggestedWords {
final SuggestedWordInfo suggestedWordInfo = new SuggestedWordInfo(text.toString(),
SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_APP_DEFINED,
Dictionary.DICTIONARY_APPLICATION_DEFINED,
- SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */);
+ SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
+ SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */);
result.add(suggestedWordInfo);
}
return result;
@@ -128,7 +129,8 @@ public final class SuggestedWords {
final HashSet<String> alreadySeen = CollectionUtils.newHashSet();
suggestionsList.add(new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE,
SuggestedWordInfo.KIND_TYPED, Dictionary.DICTIONARY_USER_TYPED,
- SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */));
+ SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
+ SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
alreadySeen.add(typedWord.toString());
final int previousSize = previousSuggestions.size();
for (int index = 1; index < previousSize; index++) {
@@ -151,6 +153,7 @@ public final class SuggestedWords {
public static final class SuggestedWordInfo {
public static final int NOT_AN_INDEX = -1;
+ public static final int NOT_A_CONFIDENCE = -1;
public static final int MAX_SCORE = Integer.MAX_VALUE;
public static final int KIND_MASK_KIND = 0xFF; // Mask to get only the kind
public static final int KIND_TYPED = 0; // What user typed
@@ -180,16 +183,30 @@ public final class SuggestedWords {
// passed to native code to get suggestions for a gesture that corresponds to the first
// letter of the second word.
public final int mIndexOfTouchPointOfSecondWord;
+ // For auto-commit. This is a measure of how confident we are that we can commit the
+ // first word of this suggestion.
+ public final int mAutoCommitFirstWordConfidence;
private String mDebugString = "";
+ /**
+ * Create a new suggested word info.
+ * @param word The string to suggest.
+ * @param score A measure of how likely this suggestion is.
+ * @param kind The kind of suggestion, as one of the above KIND_* constants.
+ * @param sourceDict What instance of Dictionary produced this suggestion.
+ * @param indexOfTouchPointOfSecondWord See mIndexOfTouchPointOfSecondWord.
+ * @param autoCommitFirstWordConfidence See mAutoCommitFirstWordConfidence.
+ */
public SuggestedWordInfo(final String word, final int score, final int kind,
- final Dictionary sourceDict, final int indexOfTouchPointOfSecondWord) {
+ final Dictionary sourceDict, final int indexOfTouchPointOfSecondWord,
+ final int autoCommitFirstWordConfidence) {
mWord = word;
mScore = score;
mKind = kind;
mSourceDict = sourceDict;
mCodePointCount = StringUtils.codePointCount(mWord);
mIndexOfTouchPointOfSecondWord = indexOfTouchPointOfSecondWord;
+ mAutoCommitFirstWordConfidence = autoCommitFirstWordConfidence;
}
public boolean isEligibleForAutoCommit() {
@@ -259,4 +276,24 @@ public final class SuggestedWords {
false /* willAutoCorrect */, mIsPunctuationSuggestions, mIsObsoleteSuggestions,
mIsPrediction);
}
+
+ // Creates a new SuggestedWordInfo from the currently suggested words that removes all but the
+ // last word of all suggestions, separated by a space. This is necessary because when we commit
+ // a multiple-word suggestion, the IME only retains the last word as the composing word, and
+ // we should only suggest replacements for this last word.
+ // TODO: make this work with languages without spaces.
+ public SuggestedWords getSuggestedWordsForLastWordOfPhraseGesture() {
+ final ArrayList<SuggestedWordInfo> newSuggestions = CollectionUtils.newArrayList();
+ for (int i = 0; i < mSuggestedWordInfoList.size(); ++i) {
+ final SuggestedWordInfo info = mSuggestedWordInfoList.get(i);
+ final int indexOfLastSpace = info.mWord.lastIndexOf(Constants.CODE_SPACE) + 1;
+ final String lastWord = info.mWord.substring(indexOfLastSpace);
+ newSuggestions.add(new SuggestedWordInfo(lastWord, info.mScore, info.mKind,
+ info.mSourceDict, SuggestedWordInfo.NOT_AN_INDEX,
+ SuggestedWordInfo.NOT_A_CONFIDENCE));
+ }
+ return new SuggestedWords(newSuggestions, mTypedWordValid,
+ mWillAutoCorrect, mIsPunctuationSuggestions, mIsObsoleteSuggestions,
+ mIsPrediction);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index b2bb61596..a241b5505 100644
--- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
@@ -103,14 +103,6 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
@Override
public void onChange(final boolean self, final Uri uri) {
setRequiresReload(true);
- // We want to report back to Latin IME in case the user just entered the word.
- // If the user changed the word in the dialog box, then we want to replace
- // what was entered in the text field.
- if (null == uri || !(context instanceof LatinIME)) return;
- final long changedRowId = ContentUris.parseId(uri);
- if (-1 == changedRowId) return; // Unknown content... Not sure why we're here
- final String changedWord = getChangedWordForUri(uri);
- ((LatinIME)context).onWordAddedToUserDictionary(changedWord);
}
};
cres.registerContentObserver(Words.CONTENT_URI, true, mObserver);
@@ -118,19 +110,6 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
loadDictionary();
}
- private String getChangedWordForUri(final Uri uri) {
- final Cursor cursor = mContext.getContentResolver().query(uri,
- PROJECTION_QUERY, null, null, null);
- if (cursor == null) return null;
- try {
- if (!cursor.moveToFirst()) return null;
- final int indexWord = cursor.getColumnIndex(Words.WORD);
- return cursor.getString(indexWord);
- } finally {
- cursor.close();
- }
- }
-
@Override
public synchronized void close() {
if (mObserver != null) {
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
index ceb8fa81f..5b319ad90 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
@@ -342,13 +342,11 @@ public final class BinaryDictDecoderUtils {
* @param formatOptions file format options.
* @return the word with its frequency, as a weighted string.
*/
- /* package for tests */ static WeightedString getWordAtPosition(
- final Ver3DictDecoder dictDecoder, final int headerSize, final int pos,
- final FormatOptions formatOptions) {
- final DictBuffer dictBuffer = dictDecoder.getDictBuffer();
+ /* package for tests */ static WeightedString getWordAtPosition(final DictDecoder dictDecoder,
+ final int headerSize, final int pos, final FormatOptions formatOptions) {
final WeightedString result;
- final int originalPos = dictBuffer.position();
- dictBuffer.position(pos);
+ final int originalPos = dictDecoder.getPosition();
+ dictDecoder.setPosition(pos);
if (BinaryDictIOUtils.supportsDynamicUpdate(formatOptions)) {
result = getWordAtPositionWithParentAddress(dictDecoder, pos, formatOptions);
@@ -357,14 +355,13 @@ public final class BinaryDictDecoderUtils {
formatOptions);
}
- dictBuffer.position(originalPos);
+ dictDecoder.setPosition(originalPos);
return result;
}
@SuppressWarnings("unused")
- private static WeightedString getWordAtPositionWithParentAddress(
- final Ver3DictDecoder dictDecoder, final int pos, final FormatOptions options) {
- final DictBuffer dictBuffer = dictDecoder.getDictBuffer();
+ private static WeightedString getWordAtPositionWithParentAddress(final DictDecoder dictDecoder,
+ final int pos, final FormatOptions options) {
int currentPos = pos;
int frequency = Integer.MIN_VALUE;
final StringBuilder builder = new StringBuilder();
@@ -373,7 +370,7 @@ public final class BinaryDictDecoderUtils {
PtNodeInfo currentInfo;
int loopCounter = 0;
do {
- dictBuffer.position(currentPos);
+ dictDecoder.setPosition(currentPos);
currentInfo = dictDecoder.readPtNode(currentPos, options);
if (BinaryDictIOUtils.isMovedPtNode(currentInfo.mFlags, options)) {
currentPos = currentInfo.mParentAddress + currentInfo.mOriginalAddress;
@@ -392,11 +389,10 @@ public final class BinaryDictDecoderUtils {
}
private static WeightedString getWordAtPositionWithoutParentAddress(
- final Ver3DictDecoder dictDecoder, final int headerSize, final int pos,
+ final DictDecoder dictDecoder, final int headerSize, final int pos,
final FormatOptions options) {
- final DictBuffer dictBuffer = dictDecoder.getDictBuffer();
- dictBuffer.position(headerSize);
- final int count = readPtNodeCount(dictBuffer);
+ dictDecoder.setPosition(headerSize);
+ final int count = dictDecoder.readPtNodeCount();
int groupPos = headerSize + BinaryDictIOUtils.getPtNodeCountSize(count);
final StringBuilder builder = new StringBuilder();
WeightedString result = null;
@@ -414,8 +410,8 @@ public final class BinaryDictDecoderUtils {
if (info.mChildrenAddress > pos) {
if (null == last) continue;
builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
- dictBuffer.position(last.mChildrenAddress);
- i = readPtNodeCount(dictBuffer);
+ dictDecoder.setPosition(last.mChildrenAddress);
+ i = dictDecoder.readPtNodeCount();
groupPos = last.mChildrenAddress + BinaryDictIOUtils.getPtNodeCountSize(i);
last = null;
continue;
@@ -424,8 +420,8 @@ public final class BinaryDictDecoderUtils {
}
if (0 == i && BinaryDictIOUtils.hasChildrenAddress(last.mChildrenAddress)) {
builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
- dictBuffer.position(last.mChildrenAddress);
- i = readPtNodeCount(dictBuffer);
+ dictDecoder.setPosition(last.mChildrenAddress);
+ i = dictDecoder.readPtNodeCount();
groupPos = last.mChildrenAddress + BinaryDictIOUtils.getPtNodeCountSize(i);
last = null;
continue;
@@ -449,17 +445,16 @@ public final class BinaryDictDecoderUtils {
* @param options file format options.
* @return the read node array with all his children already read.
*/
- private static PtNodeArray readNodeArray(final Ver3DictDecoder dictDecoder,
+ private static PtNodeArray readNodeArray(final DictDecoder dictDecoder,
final int headerSize, final Map<Integer, PtNodeArray> reverseNodeArrayMap,
final Map<Integer, PtNode> reversePtNodeMap, final FormatOptions options)
throws IOException {
- final DictBuffer dictBuffer = dictDecoder.getDictBuffer();
final ArrayList<PtNode> nodeArrayContents = new ArrayList<PtNode>();
- final int nodeArrayOriginPos = dictBuffer.position();
+ final int nodeArrayOriginPos = dictDecoder.getPosition();
do { // Scan the linked-list node.
- final int nodeArrayHeadPos = dictBuffer.position();
- final int count = readPtNodeCount(dictBuffer);
+ final int nodeArrayHeadPos = dictDecoder.getPosition();
+ final int count = dictDecoder.readPtNodeCount();
int groupOffsetPos = nodeArrayHeadPos + BinaryDictIOUtils.getPtNodeCountSize(count);
for (int i = count; i > 0; --i) { // Scan the array of PtNode.
PtNodeInfo info = dictDecoder.readPtNode(groupOffsetPos, options);
@@ -480,11 +475,11 @@ public final class BinaryDictDecoderUtils {
if (BinaryDictIOUtils.hasChildrenAddress(info.mChildrenAddress)) {
PtNodeArray children = reverseNodeArrayMap.get(info.mChildrenAddress);
if (null == children) {
- final int currentPosition = dictBuffer.position();
- dictBuffer.position(info.mChildrenAddress);
+ final int currentPosition = dictDecoder.getPosition();
+ dictDecoder.setPosition(info.mChildrenAddress);
children = readNodeArray(dictDecoder, headerSize, reverseNodeArrayMap,
reversePtNodeMap, options);
- dictBuffer.position(currentPosition);
+ dictDecoder.setPosition(currentPosition);
}
nodeArrayContents.add(
new PtNode(info.mCharacters, shortcutTargets, bigrams,
@@ -503,15 +498,10 @@ public final class BinaryDictDecoderUtils {
// reach the end of the array.
if (options.mSupportsDynamicUpdate) {
- final int nextAddress = dictBuffer.readUnsignedInt24();
- if (nextAddress >= 0 && nextAddress < dictBuffer.limit()) {
- dictBuffer.position(nextAddress);
- } else {
- break;
- }
+ final boolean hasValidForwardLink = dictDecoder.readForwardLinkAndAdvancePosition();
+ if (!hasValidForwardLink) break;
}
- } while (options.mSupportsDynamicUpdate &&
- dictBuffer.position() != FormatSpec.NO_FORWARD_LINK_ADDRESS);
+ } while (options.mSupportsDynamicUpdate && dictDecoder.hasNextPtNodeArray());
final PtNodeArray nodeArray = new PtNodeArray(nodeArrayContents);
nodeArray.mCachedAddressBeforeUpdate = nodeArrayOriginPos;
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
index 5a213415a..f333b0d86 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
@@ -126,8 +126,14 @@ public class BinaryDictEncoderUtils {
*/
private static int getPtNodeMaximumSize(final PtNode ptNode, final FormatOptions options) {
int size = getNodeHeaderSize(ptNode, options);
- // If terminal, one byte for the frequency
- if (ptNode.isTerminal()) size += FormatSpec.PTNODE_FREQUENCY_SIZE;
+ if (ptNode.isTerminal()) {
+ // If terminal, one byte for the frequency or four bytes for the terminal id.
+ if (options.mHasTerminalId) {
+ size += FormatSpec.PTNODE_TERMINAL_ID_SIZE;
+ } else {
+ size += FormatSpec.PTNODE_FREQUENCY_SIZE;
+ }
+ }
size += FormatSpec.PTNODE_MAX_ADDRESS_SIZE; // For children address
size += getShortcutListSize(ptNode.mShortcutTargets);
if (null != ptNode.mBigrams) {
@@ -198,6 +204,27 @@ public class BinaryDictEncoderUtils {
}
}
+ static int writeUIntToBuffer(final byte[] buffer, int position, final int value,
+ final int size) {
+ switch(size) {
+ case 4:
+ buffer[position++] = (byte) ((value >> 24) & 0xFF);
+ /* fall through */
+ case 3:
+ buffer[position++] = (byte) ((value >> 16) & 0xFF);
+ /* fall through */
+ case 2:
+ buffer[position++] = (byte) ((value >> 8) & 0xFF);
+ /* fall through */
+ case 1:
+ buffer[position++] = (byte) (value & 0xFF);
+ break;
+ default:
+ /* nop */
+ }
+ return position;
+ }
+
// End utility methods
// This method is responsible for finding a nice ordering of the nodes that favors run-time
@@ -324,7 +351,13 @@ public class BinaryDictEncoderUtils {
changed = true;
}
int nodeSize = getNodeHeaderSize(ptNode, formatOptions);
- if (ptNode.isTerminal()) nodeSize += FormatSpec.PTNODE_FREQUENCY_SIZE;
+ if (ptNode.isTerminal()) {
+ if (formatOptions.mHasTerminalId) {
+ nodeSize += FormatSpec.PTNODE_TERMINAL_ID_SIZE;
+ } else {
+ nodeSize += FormatSpec.PTNODE_FREQUENCY_SIZE;
+ }
+ }
if (formatOptions.mSupportsDynamicUpdate) {
nodeSize += FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE;
} else if (null != ptNode.mChildren) {
@@ -733,7 +766,7 @@ public class BinaryDictEncoderUtils {
}
/**
- * Write a PtNodeArray to memory. The PtNodeArray is expected to have its final position cached.
+ * Write a PtNodeArray. The PtNodeArray is expected to have its final position cached.
*
* @param dict the dictionary the node array is a part of (for relative offsets).
* @param dictEncoder the dictionary encoder.
@@ -741,7 +774,7 @@ public class BinaryDictEncoderUtils {
* @param formatOptions file format options.
*/
@SuppressWarnings("unused")
- /* package */ static void writePlacedNode(final FusionDictionary dict,
+ /* package */ static void writePlacedPtNodeArray(final FusionDictionary dict,
final DictEncoder dictEncoder, final PtNodeArray ptNodeArray,
final FormatOptions formatOptions) {
// TODO: Make the code in common with BinaryDictIOUtils#writePtNode
@@ -766,14 +799,7 @@ public class BinaryDictEncoderUtils {
+ FormatSpec.MAX_TERMINAL_FREQUENCY
+ " : " + ptNode.mFrequency);
}
-
- dictEncoder.writePtNodeFlags(ptNode, parentPosition, formatOptions);
- dictEncoder.writeParentPosition(parentPosition, ptNode, formatOptions);
- dictEncoder.writeCharacters(ptNode.mChars, ptNode.hasSeveralChars());
- dictEncoder.writeFrequency(ptNode.mFrequency);
- dictEncoder.writeChildrenPosition(ptNode, formatOptions);
- dictEncoder.writeShortcuts(ptNode.mShortcutTargets);
- dictEncoder.writeBigrams(ptNode.mBigrams, dict);
+ dictEncoder.writePtNode(ptNode, parentPosition, formatOptions, dict);
}
if (formatOptions.mSupportsDynamicUpdate) {
dictEncoder.writeForwardLinkAddress(FormatSpec.NO_FORWARD_LINK_ADDRESS);
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
index 106f02519..2c5e93e5c 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
@@ -61,12 +61,11 @@ public final class BinaryDictIOUtils {
/**
* Retrieves all node arrays without recursive call.
*/
- private static void readUnigramsAndBigramsBinaryInner(
- final Ver3DictDecoder dictDecoder, final int headerSize,
- final Map<Integer, String> words, final Map<Integer, Integer> frequencies,
+ private static void readUnigramsAndBigramsBinaryInner(final DictDecoder dictDecoder,
+ final int headerSize, final Map<Integer, String> words,
+ final Map<Integer, Integer> frequencies,
final Map<Integer, ArrayList<PendingAttribute>> bigrams,
final FormatOptions formatOptions) {
- final DictBuffer dictBuffer = dictDecoder.getDictBuffer();
int[] pushedChars = new int[FormatSpec.MAX_WORD_LENGTH + 1];
Stack<Position> stack = new Stack<Position>();
@@ -83,11 +82,11 @@ public final class BinaryDictIOUtils {
p.mNumOfPtNode + ", position=" + p.mPosition + ", length=" + p.mLength);
}
- if (dictBuffer.position() != p.mAddress) dictBuffer.position(p.mAddress);
+ if (dictDecoder.getPosition() != p.mAddress) dictDecoder.setPosition(p.mAddress);
if (index != p.mLength) index = p.mLength;
if (p.mNumOfPtNode == Position.NOT_READ_PTNODE_COUNT) {
- p.mNumOfPtNode = BinaryDictDecoderUtils.readPtNodeCount(dictBuffer);
+ p.mNumOfPtNode = dictDecoder.readPtNodeCount();
p.mAddress += getPtNodeCountSize(p.mNumOfPtNode);
p.mPosition = 0;
}
@@ -114,11 +113,12 @@ public final class BinaryDictIOUtils {
if (p.mPosition == p.mNumOfPtNode) {
if (formatOptions.mSupportsDynamicUpdate) {
- final int forwardLinkAddress = dictBuffer.readUnsignedInt24();
- if (forwardLinkAddress != FormatSpec.NO_FORWARD_LINK_ADDRESS) {
+ final boolean hasValidForwardLinkAddress =
+ dictDecoder.readForwardLinkAndAdvancePosition();
+ if (hasValidForwardLinkAddress && dictDecoder.hasNextPtNodeArray()) {
// The node array has a forward link.
p.mNumOfPtNode = Position.NOT_READ_PTNODE_COUNT;
- p.mAddress = forwardLinkAddress;
+ p.mAddress = dictDecoder.getPosition();
} else {
stack.pop();
}
@@ -127,7 +127,7 @@ public final class BinaryDictIOUtils {
}
} else {
// The Ptnode array has more PtNodes.
- p.mAddress = dictBuffer.position();
+ p.mAddress = dictDecoder.getPosition();
}
if (!isMovedPtNode && hasChildrenAddress(info.mChildrenAddress)) {
@@ -148,7 +148,7 @@ public final class BinaryDictIOUtils {
* @throws IOException if the file can't be read.
* @throws UnsupportedFormatException if the format of the file is not recognized.
*/
- /* package */ static void readUnigramsAndBigramsBinary(final Ver3DictDecoder dictDecoder,
+ /* package */ static void readUnigramsAndBigramsBinary(final DictDecoder dictDecoder,
final Map<Integer, String> words, final Map<Integer, Integer> frequencies,
final Map<Integer, ArrayList<PendingAttribute>> bigrams) throws IOException,
UnsupportedFormatException {
@@ -169,11 +169,10 @@ public final class BinaryDictIOUtils {
* @throws UnsupportedFormatException if the format of the file is not recognized.
*/
@UsedForTesting
- /* package */ static int getTerminalPosition(final Ver3DictDecoder dictDecoder,
+ /* package */ static int getTerminalPosition(final DictDecoder dictDecoder,
final String word) throws IOException, UnsupportedFormatException {
- final DictBuffer dictBuffer = dictDecoder.getDictBuffer();
if (word == null) return FormatSpec.NOT_VALID_WORD;
- if (dictBuffer.position() != 0) dictBuffer.position(0);
+ dictDecoder.setPosition(0);
final FileHeader header = dictDecoder.readHeader();
int wordPos = 0;
@@ -182,10 +181,10 @@ public final class BinaryDictIOUtils {
if (wordPos >= wordLen) return FormatSpec.NOT_VALID_WORD;
do {
- final int ptNodeCount = BinaryDictDecoderUtils.readPtNodeCount(dictBuffer);
+ final int ptNodeCount = dictDecoder.readPtNodeCount();
boolean foundNextPtNode = false;
for (int i = 0; i < ptNodeCount; ++i) {
- final int ptNodePos = dictBuffer.position();
+ final int ptNodePos = dictDecoder.getPosition();
final PtNodeInfo currentInfo = dictDecoder.readPtNode(ptNodePos,
header.mFormatOptions);
final boolean isMovedNode = isMovedPtNode(currentInfo.mFlags,
@@ -219,7 +218,7 @@ public final class BinaryDictIOUtils {
return FormatSpec.NOT_VALID_WORD;
}
foundNextPtNode = true;
- dictBuffer.position(currentInfo.mChildrenAddress);
+ dictDecoder.setPosition(currentInfo.mChildrenAddress);
break;
}
}
@@ -233,11 +232,11 @@ public final class BinaryDictIOUtils {
return FormatSpec.NOT_VALID_WORD;
}
- final int forwardLinkAddress = dictBuffer.readUnsignedInt24();
- if (forwardLinkAddress == FormatSpec.NO_FORWARD_LINK_ADDRESS) {
+ final boolean hasValidForwardLinkAddress =
+ dictDecoder.readForwardLinkAndAdvancePosition();
+ if (!hasValidForwardLinkAddress || !dictDecoder.hasNextPtNodeArray()) {
return FormatSpec.NOT_VALID_WORD;
}
- dictBuffer.position(forwardLinkAddress);
} while(true);
}
return FormatSpec.NOT_VALID_WORD;
@@ -520,7 +519,7 @@ public final class BinaryDictIOUtils {
final File file, final long offset, final long length)
throws FileNotFoundException, IOException, UnsupportedFormatException {
final byte[] buffer = new byte[HEADER_READING_BUFFER_SIZE];
- final Ver3DictDecoder dictDecoder = new Ver3DictDecoder(file,
+ final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file,
new DictDecoder.DictionaryBufferFactory() {
@Override
public DictBuffer getDictionaryBuffer(File file)
diff --git a/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java
index 11a3f0b3a..40e852423 100644
--- a/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java
@@ -54,10 +54,13 @@ public interface DictDecoder {
* which words from the buffer should be added. If it is null, a new dictionary is created.
*
* @param dict an optional dictionary to add words to, or null.
+ * @param deleteDictIfBroken a flag indicating whether this method should remove the broken
+ * dictionary or not.
* @return the created (or merged) dictionary.
*/
@UsedForTesting
- public FusionDictionary readDictionaryBinary(final FusionDictionary dict)
+ public FusionDictionary readDictionaryBinary(final FusionDictionary dict,
+ final boolean deleteDictIfBroken)
throws FileNotFoundException, IOException, UnsupportedFormatException;
/**
@@ -88,6 +91,41 @@ public interface DictDecoder {
final TreeMap<Integer, ArrayList<PendingAttribute>> bigrams)
throws IOException, UnsupportedFormatException;
+ /**
+ * Sets the position of the buffer to the given value.
+ *
+ * @param newPos the new position
+ */
+ public void setPosition(final int newPos);
+
+ /**
+ * Gets the position of the buffer.
+ *
+ * @return the position
+ */
+ public int getPosition();
+
+ /**
+ * Reads and returns the PtNode count out of a buffer and forwards the pointer.
+ */
+ public int readPtNodeCount();
+
+ /**
+ * Reads the forward link and advances the position.
+ *
+ * @return if this method advances the position then true else false.
+ */
+ public boolean readForwardLinkAndAdvancePosition();
+ public boolean hasNextPtNodeArray();
+
+ /**
+ * Opens the dictionary file and makes DictBuffer.
+ */
+ @UsedForTesting
+ public void openDictBuffer() throws FileNotFoundException, IOException;
+ @UsedForTesting
+ public boolean isOpenedDictBuffer();
+
// Flags for DictionaryBufferFactory.
public static final int USE_READONLY_BYTEBUFFER = 0x01000000;
public static final int USE_BYTEARRAY = 0x02000000;
diff --git a/java/src/com/android/inputmethod/latin/makedict/DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/DictEncoder.java
index d1589a30e..ea5d492d8 100644
--- a/java/src/com/android/inputmethod/latin/makedict/DictEncoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/DictEncoder.java
@@ -18,10 +18,8 @@ package com.android.inputmethod.latin.makedict;
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 java.io.IOException;
-import java.util.ArrayList;
/**
* An interface of binary dictionary encoder.
@@ -33,28 +31,8 @@ public interface DictEncoder {
public void setPosition(final int position);
public int getPosition();
public void writePtNodeCount(final int ptNodeCount);
- public void writePtNodeFlags(final PtNode ptNode, final int parentAddress,
- final FormatOptions formatOptions);
- public void writeParentPosition(final int parentPosition, final PtNode ptNode,
- final FormatOptions formatOptions);
- public void writeCharacters(final int[] characters, final boolean hasSeveralChars);
- public void writeFrequency(final int frequency);
- public void writeChildrenPosition(final PtNode ptNode, final FormatOptions formatOptions);
-
- /**
- * Write a shortcut attributes list to memory.
- *
- * @param shortcuts the shortcut attributes list.
- */
- public void writeShortcuts(final ArrayList<WeightedString> shortcuts);
-
- /**
- * Write a bigram attributes list to memory.
- *
- * @param bigrams the bigram attributes list.
- * @param dict the dictionary the node array is a part of (for relative offsets).
- */
- public void writeBigrams(final ArrayList<WeightedString> bigrams, final FusionDictionary dict);
-
public void writeForwardLinkAddress(final int forwardLinkAddress);
+
+ public void writePtNode(final PtNode ptNode, final int parentPosition,
+ final FormatOptions formatOptions, final FusionDictionary dict);
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index bf35f6a8a..96ccd8e49 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -18,8 +18,11 @@ package com.android.inputmethod.latin.makedict;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.makedict.DictDecoder.DictionaryBufferFactory;
import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions;
+import java.io.File;
+
/**
* Dictionary File Format Specification.
*/
@@ -195,9 +198,12 @@ public final class FormatSpec {
public static final int MAGIC_NUMBER = 0x9BC13AFE;
static final int MINIMUM_SUPPORTED_VERSION = 2;
- static final int MAXIMUM_SUPPORTED_VERSION = 3;
+ 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 options need to be the same numeric values as the one in the native reading code.
static final int GERMAN_UMLAUT_PROCESSING_FLAG = 0x1;
@@ -248,11 +254,17 @@ public final class FormatSpec {
static final int PTNODE_TERMINATOR_SIZE = 1;
static final int PTNODE_FLAGS_SIZE = 1;
static final int PTNODE_FREQUENCY_SIZE = 1;
+ static final int PTNODE_TERMINAL_ID_SIZE = 4;
static final int PTNODE_MAX_ADDRESS_SIZE = 3;
static final int PTNODE_ATTRIBUTE_FLAGS_SIZE = 1;
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.
+ static final String TRIE_FILE_EXTENSION = ".trie";
+ static final String FREQ_FILE_EXTENSION = ".freq";
+ static final int FREQUENCY_AND_FLAGS_SIZE = 2;
+
static final int NO_CHILDREN_ADDRESS = Integer.MIN_VALUE;
static final int NO_PARENT_ADDRESS = 0;
static final int NO_FORWARD_LINK_ADDRESS = 0;
@@ -261,6 +273,7 @@ public final class FormatSpec {
static final int MAX_PTNODES_FOR_ONE_BYTE_PTNODE_COUNT = 0x7F; // 127
static final int MAX_PTNODES_IN_A_PT_NODE_ARRAY = 0x7FFF; // 32767
static final int MAX_BIGRAMS_IN_A_PTNODE = 10000;
+ static final int MAX_SHORTCUT_LIST_SIZE_IN_A_PTNODE = 0xFFFF;
static final int MAX_TERMINAL_FREQUENCY = 255;
static final int MAX_BIGRAM_FREQUENCY = 15;
@@ -284,6 +297,7 @@ public final class FormatSpec {
public static final class FormatOptions {
public final int mVersion;
public final boolean mSupportsDynamicUpdate;
+ public final boolean mHasTerminalId;
@UsedForTesting
public FormatOptions(final int version) {
this(version, false);
@@ -297,6 +311,7 @@ public final class FormatSpec {
+ FIRST_VERSION_WITH_DYNAMIC_UPDATE + " and ulterior.");
}
mSupportsDynamicUpdate = supportsDynamicUpdate;
+ mHasTerminalId = (version >= FIRST_VERSION_WITH_TERMINAL_ID);
}
}
@@ -341,6 +356,28 @@ public final class FormatSpec {
}
}
+ /**
+ * Returns new dictionary decoder.
+ *
+ * @param dictFile the dictionary file.
+ * @param bufferType the flag indicating buffer type which is used by the dictionary decoder.
+ * @return new dictionary decoder if the dictionary file exists, otherwise null.
+ */
+ public static DictDecoder getDictDecoder(final File dictFile, final int bufferType) {
+ if (!dictFile.isFile()) return null;
+ return new Ver3DictDecoder(dictFile, bufferType);
+ }
+
+ public static DictDecoder getDictDecoder(final File dictFile,
+ final DictionaryBufferFactory factory) {
+ if (!dictFile.isFile()) return null;
+ return new Ver3DictDecoder(dictFile, factory);
+ }
+
+ public static DictDecoder getDictDecoder(final File dictFile) {
+ return getDictDecoder(dictFile, DictDecoder.USE_READONLY_BYTEBUFFER);
+ }
+
private FormatSpec() {
// This utility class is not publicly instantiable.
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
index 3e685a3d7..be653feec 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
@@ -111,6 +111,7 @@ public final class FusionDictionary implements Iterable<Word> {
ArrayList<WeightedString> mShortcutTargets;
ArrayList<WeightedString> mBigrams;
int mFrequency; // NOT_A_TERMINAL == mFrequency indicates this is not a terminal.
+ int mTerminalId; // NOT_A_TERMINAL == mTerminalId indicates this is not a terminal.
PtNodeArray mChildren;
boolean mIsNotAWord; // Only a shortcut
boolean mIsBlacklistEntry;
@@ -129,6 +130,7 @@ public final class FusionDictionary implements Iterable<Word> {
final boolean isNotAWord, final boolean isBlacklistEntry) {
mChars = chars;
mFrequency = frequency;
+ mTerminalId = frequency;
mShortcutTargets = shortcutTargets;
mBigrams = bigrams;
mChildren = null;
@@ -156,6 +158,10 @@ public final class FusionDictionary implements Iterable<Word> {
mChildren.mData.add(n);
}
+ public int getTerminalId() {
+ return mTerminalId;
+ }
+
public boolean isTerminal() {
return NOT_A_TERMINAL != mFrequency;
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java
index 1a5023ef6..1a90a4b98 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java
@@ -173,11 +173,7 @@ public class Ver3DictDecoder implements DictDecoder {
private final DictionaryBufferFactory mBufferFactory;
private DictBuffer mDictBuffer;
- public Ver3DictDecoder(final File file) {
- this(file, USE_READONLY_BYTEBUFFER);
- }
-
- public Ver3DictDecoder(final File file, final int factoryFlag) {
+ /* package */ Ver3DictDecoder(final File file, final int factoryFlag) {
mDictionaryBinaryFile = file;
mDictBuffer = null;
@@ -192,15 +188,21 @@ public class Ver3DictDecoder implements DictDecoder {
}
}
- public Ver3DictDecoder(final File file, final DictionaryBufferFactory factory) {
+ /* package */ Ver3DictDecoder(final File file, final DictionaryBufferFactory factory) {
mDictionaryBinaryFile = file;
mBufferFactory = factory;
}
+ @Override
public void openDictBuffer() throws FileNotFoundException, IOException {
mDictBuffer = mBufferFactory.getDictionaryBuffer(mDictionaryBinaryFile);
}
+ @Override
+ public boolean isOpenedDictBuffer() {
+ return mDictBuffer != null;
+ }
+
/* package */ DictBuffer getDictBuffer() {
return mDictBuffer;
}
@@ -306,7 +308,8 @@ public class Ver3DictDecoder implements DictDecoder {
}
@Override
- public FusionDictionary readDictionaryBinary(final FusionDictionary dict)
+ public FusionDictionary readDictionaryBinary(final FusionDictionary dict,
+ final boolean deleteDictIfBroken)
throws FileNotFoundException, IOException, UnsupportedFormatException {
if (mDictBuffer == null) {
openDictBuffer();
@@ -315,13 +318,13 @@ public class Ver3DictDecoder implements DictDecoder {
return BinaryDictDecoderUtils.readDictionaryBinary(this, dict);
} catch (IOException e) {
Log.e(TAG, "The dictionary " + mDictionaryBinaryFile.getName() + " is broken.", e);
- if (!mDictionaryBinaryFile.delete()) {
+ if (deleteDictIfBroken && !mDictionaryBinaryFile.delete()) {
Log.e(TAG, "Failed to delete the broken dictionary.");
}
throw e;
} catch (UnsupportedFormatException e) {
Log.e(TAG, "The dictionary " + mDictionaryBinaryFile.getName() + " is broken.", e);
- if (!mDictionaryBinaryFile.delete()) {
+ if (deleteDictIfBroken && !mDictionaryBinaryFile.delete()) {
Log.e(TAG, "Failed to delete the broken dictionary.");
}
throw e;
@@ -347,4 +350,33 @@ public class Ver3DictDecoder implements DictDecoder {
BinaryDictIOUtils.readUnigramsAndBigramsBinary(this, words, frequencies, bigrams);
}
+ @Override
+ public void setPosition(int newPos) {
+ mDictBuffer.position(newPos);
+ }
+
+ @Override
+ public int getPosition() {
+ return mDictBuffer.position();
+ }
+
+ @Override
+ public int readPtNodeCount() {
+ return BinaryDictDecoderUtils.readPtNodeCount(mDictBuffer);
+ }
+
+ @Override
+ public boolean readForwardLinkAndAdvancePosition() {
+ final int nextAddress = mDictBuffer.readUnsignedInt24();
+ if (nextAddress >= 0 && nextAddress < mDictBuffer.limit()) {
+ mDictBuffer.position(nextAddress);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean hasNextPtNodeArray() {
+ return mDictBuffer.position() != FormatSpec.NO_FORWARD_LINK_ADDRESS;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java
index 3f26ff378..222a0f474 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java
@@ -68,7 +68,7 @@ public class Ver3DictEncoder implements DictEncoder {
@Override
public void writeDictionary(final FusionDictionary dict, final FormatOptions formatOptions)
throws IOException, UnsupportedFormatException {
- if (formatOptions.mVersion > 3) {
+ if (formatOptions.mVersion > FormatSpec.VERSION3) {
throw new UnsupportedFormatException(
"The given format options has wrong version number : "
+ formatOptions.mVersion);
@@ -103,7 +103,7 @@ public class Ver3DictEncoder implements DictEncoder {
MakedictLog.i("Writing file...");
for (PtNodeArray nodeArray : flatNodes) {
- BinaryDictEncoderUtils.writePlacedNode(dict, this, nodeArray, formatOptions);
+ BinaryDictEncoderUtils.writePlacedPtNodeArray(dict, this, nodeArray, formatOptions);
}
if (MakedictLog.DBG) BinaryDictEncoderUtils.showStatistics(flatNodes);
mOutStream.write(mBuffer, 0, mPosition);
@@ -126,26 +126,23 @@ public class Ver3DictEncoder implements DictEncoder {
@Override
public void writePtNodeCount(final int ptNodeCount) {
final int countSize = BinaryDictIOUtils.getPtNodeCountSize(ptNodeCount);
- if (1 == countSize) {
- mBuffer[mPosition++] = (byte) ptNodeCount;
- } else if (2 == countSize) {
- mBuffer[mPosition++] = (byte) ((ptNodeCount >> 8) & 0xFF);
- mBuffer[mPosition++] = (byte) (ptNodeCount & 0xFF);
- } else {
+ if (countSize != 1 && countSize != 2) {
throw new RuntimeException("Strange size from getGroupCountSize : " + countSize);
}
+ mPosition = BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, mPosition, ptNodeCount,
+ countSize);
}
- @Override
- public void writePtNodeFlags(final PtNode ptNode, final int parentAddress,
+ private void writePtNodeFlags(final PtNode ptNode, final int parentAddress,
final FormatOptions formatOptions) {
final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode, formatOptions);
- mBuffer[mPosition++] = BinaryDictEncoderUtils.makePtNodeFlags(ptNode, mPosition,
- childrenPos, formatOptions);
+ mPosition = BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, mPosition,
+ BinaryDictEncoderUtils.makePtNodeFlags(ptNode, mPosition, childrenPos,
+ formatOptions),
+ FormatSpec.PTNODE_FLAGS_SIZE);
}
- @Override
- public void writeParentPosition(final int parentPosition, final PtNode ptNode,
+ private void writeParentPosition(final int parentPosition, final PtNode ptNode,
final FormatOptions formatOptions) {
if (parentPosition == FormatSpec.NO_PARENT_ADDRESS) {
mPosition = BinaryDictEncoderUtils.writeParentAddress(mBuffer, mPosition,
@@ -156,22 +153,20 @@ public class Ver3DictEncoder implements DictEncoder {
}
}
- @Override
- public void writeCharacters(final int[] codePoints, final boolean hasSeveralChars) {
+ private void writeCharacters(final int[] codePoints, final boolean hasSeveralChars) {
mPosition = CharEncoding.writeCharArray(codePoints, mBuffer, mPosition);
if (hasSeveralChars) {
mBuffer[mPosition++] = FormatSpec.PTNODE_CHARACTERS_TERMINATOR;
}
}
- @Override
- public void writeFrequency(final int frequency) {
+ private void writeFrequency(final int frequency) {
if (frequency >= 0) {
- mBuffer[mPosition++] = (byte) frequency;
+ mPosition = BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, mPosition, frequency,
+ FormatSpec.PTNODE_FREQUENCY_SIZE);
}
}
- @Override
public void writeChildrenPosition(final PtNode ptNode, final FormatOptions formatOptions) {
final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode, formatOptions);
if (formatOptions.mSupportsDynamicUpdate) {
@@ -183,8 +178,12 @@ public class Ver3DictEncoder implements DictEncoder {
}
}
- @Override
- public void writeShortcuts(final ArrayList<WeightedString> shortcuts) {
+ /**
+ * Write a shortcut attributes list to mBuffer.
+ *
+ * @param shortcuts the shortcut attributes list.
+ */
+ private void writeShortcuts(final ArrayList<WeightedString> shortcuts) {
if (null == shortcuts || shortcuts.isEmpty()) return;
final int indexOfShortcutByteSize = mPosition;
@@ -195,20 +194,27 @@ public class Ver3DictEncoder implements DictEncoder {
final int shortcutFlags = BinaryDictEncoderUtils.makeShortcutFlags(
shortcutIterator.hasNext(),
target.mFrequency);
- mBuffer[mPosition++] = (byte)shortcutFlags;
+ mPosition = BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, mPosition, shortcutFlags,
+ FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE);
final int shortcutShift = CharEncoding.writeString(mBuffer, mPosition, target.mWord);
mPosition += shortcutShift;
}
final int shortcutByteSize = mPosition - indexOfShortcutByteSize;
- if (shortcutByteSize > 0xFFFF) {
+ if (shortcutByteSize > FormatSpec.MAX_SHORTCUT_LIST_SIZE_IN_A_PTNODE) {
throw new RuntimeException("Shortcut list too large");
}
- mBuffer[indexOfShortcutByteSize] = (byte)((shortcutByteSize >> 8) & 0xFF);
- mBuffer[indexOfShortcutByteSize + 1] = (byte)(shortcutByteSize & 0xFF);
- }
-
- @Override
- public void writeBigrams(final ArrayList<WeightedString> bigrams, final FusionDictionary dict) {
+ BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, indexOfShortcutByteSize, shortcutByteSize,
+ FormatSpec.PTNODE_SHORTCUT_LIST_SIZE_SIZE);
+ }
+
+ /**
+ * Write a bigram attributes list to mBuffer.
+ *
+ * @param bigrams the bigram attributes list.
+ * @param dict the dictionary the node array is a part of (for relative offsets).
+ */
+ private void writeBigrams(final ArrayList<WeightedString> bigrams,
+ final FusionDictionary dict) {
if (bigrams == null) return;
final Iterator<WeightedString> bigramIterator = bigrams.iterator();
@@ -220,9 +226,10 @@ public class Ver3DictEncoder implements DictEncoder {
final int unigramFrequencyForThisWord = target.mFrequency;
final int offset = addressOfBigram
- (mPosition + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE);
- int bigramFlags = BinaryDictEncoderUtils.makeBigramFlags(bigramIterator.hasNext(),
+ final int bigramFlags = BinaryDictEncoderUtils.makeBigramFlags(bigramIterator.hasNext(),
offset, bigram.mFrequency, unigramFrequencyForThisWord, bigram.mWord);
- mBuffer[mPosition++] = (byte) bigramFlags;
+ mPosition = BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, mPosition, bigramFlags,
+ FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE);
mPosition += BinaryDictEncoderUtils.writeChildrenPosition(mBuffer, mPosition,
Math.abs(offset));
}
@@ -230,8 +237,19 @@ public class Ver3DictEncoder implements DictEncoder {
@Override
public void writeForwardLinkAddress(final int forwardLinkAddress) {
- mBuffer[mPosition++] = (byte) ((forwardLinkAddress >> 16) & 0xFF);
- mBuffer[mPosition++] = (byte) ((forwardLinkAddress >> 8) & 0xFF);
- mBuffer[mPosition++] = (byte) (forwardLinkAddress & 0xFF);
+ mPosition = BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, mPosition, forwardLinkAddress,
+ FormatSpec.FORWARD_LINK_ADDRESS_SIZE);
+ }
+
+ @Override
+ public void writePtNode(final PtNode ptNode, final int parentPosition,
+ final FormatOptions formatOptions, final FusionDictionary dict) {
+ writePtNodeFlags(ptNode, parentPosition, formatOptions);
+ writeParentPosition(parentPosition, ptNode, formatOptions);
+ writeCharacters(ptNode.mChars, ptNode.hasSeveralChars());
+ writeFrequency(ptNode.mFrequency);
+ writeChildrenPosition(ptNode, formatOptions);
+ writeShortcuts(ptNode.mShortcutTargets);
+ writeBigrams(ptNode.mBigrams, dict);
}
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
new file mode 100644
index 000000000..75b75ae2e
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
@@ -0,0 +1,269 @@
+/*
+/*
+ * 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.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.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 java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * An implementation of DictEncoder for version 4 binary dictionary.
+ */
+@UsedForTesting
+public class Ver4DictEncoder implements DictEncoder {
+ private final File mDictPlacedDir;
+ private byte[] mTrieBuf;
+ private byte[] mFreqBuf;
+ private int mTriePos;
+ private OutputStream mTrieOutStream;
+ private OutputStream mFreqOutStream;
+
+ @UsedForTesting
+ public Ver4DictEncoder(final File dictPlacedDir) {
+ mDictPlacedDir = dictPlacedDir;
+ }
+
+ private void openStreams(final FormatOptions formatOptions, final DictionaryOptions dictOptions)
+ throws FileNotFoundException, IOException {
+ final FileHeader header = new FileHeader(0, dictOptions, formatOptions);
+ final String filename = header.getId() + "." + header.getVersion();
+ final File mDictDir = new File(mDictPlacedDir, filename);
+ final File trieFile = new File(mDictDir, filename + FormatSpec.TRIE_FILE_EXTENSION);
+ final File freqFile = new File(mDictDir, filename + FormatSpec.FREQ_FILE_EXTENSION);
+ if (!mDictDir.isDirectory()) {
+ if (mDictDir.exists()) mDictDir.delete();
+ mDictDir.mkdirs();
+ }
+ if (!trieFile.exists()) trieFile.createNewFile();
+ if (!freqFile.exists()) freqFile.createNewFile();
+ mTrieOutStream = new FileOutputStream(trieFile);
+ mFreqOutStream = new FileOutputStream(freqFile);
+ }
+
+ private void close() throws IOException {
+ try {
+ if (mTrieOutStream != null) {
+ mTrieOutStream.close();
+ }
+ if (mFreqOutStream != null) {
+ mFreqOutStream.close();
+ }
+ } finally {
+ mTrieOutStream = null;
+ mFreqOutStream = null;
+ }
+ }
+
+ @Override
+ public void writeDictionary(final FusionDictionary dict, final FormatOptions formatOptions)
+ throws IOException, UnsupportedFormatException {
+ if (formatOptions.mVersion != FormatSpec.VERSION4) {
+ throw new UnsupportedFormatException("File header has a wrong version number : "
+ + formatOptions.mVersion);
+ }
+ if (!mDictPlacedDir.isDirectory()) {
+ throw new UnsupportedFormatException("Given path is not a directory.");
+ }
+
+ if (mTrieOutStream == null) {
+ openStreams(formatOptions, dict.mOptions);
+ }
+
+ BinaryDictEncoderUtils.writeDictionaryHeader(mTrieOutStream, dict, formatOptions);
+
+ MakedictLog.i("Flattening the tree...");
+ ArrayList<PtNodeArray> flatNodes = BinaryDictEncoderUtils.flattenTree(dict.mRootNodeArray);
+ int terminalCount = 0;
+ for (final PtNodeArray array : flatNodes) {
+ for (final PtNode node : array.mData) {
+ if (node.isTerminal()) node.mTerminalId = terminalCount++;
+ }
+ }
+
+ MakedictLog.i("Computing addresses...");
+ BinaryDictEncoderUtils.computeAddresses(dict, flatNodes, formatOptions);
+ if (MakedictLog.DBG) BinaryDictEncoderUtils.checkFlatPtNodeArrayList(flatNodes);
+
+ final PtNodeArray lastNodeArray = flatNodes.get(flatNodes.size() - 1);
+ final int bufferSize = lastNodeArray.mCachedAddressAfterUpdate + lastNodeArray.mCachedSize;
+ mTrieBuf = new byte[bufferSize];
+ mFreqBuf = new byte[terminalCount * FormatSpec.FREQUENCY_AND_FLAGS_SIZE];
+
+ MakedictLog.i("Writing file...");
+ for (PtNodeArray nodeArray : flatNodes) {
+ BinaryDictEncoderUtils.writePlacedPtNodeArray(dict, this, nodeArray, formatOptions);
+ }
+ if (MakedictLog.DBG) {
+ BinaryDictEncoderUtils.showStatistics(flatNodes);
+ MakedictLog.i("has " + terminalCount + " terminals.");
+ }
+ mTrieOutStream.write(mTrieBuf);
+ mFreqOutStream.write(mFreqBuf);
+
+ MakedictLog.i("Done");
+ close();
+ }
+
+ @Override
+ public void setPosition(int position) {
+ if (mTrieBuf == null || position < 0 || position >- mTrieBuf.length) return;
+ mTriePos = position;
+ }
+
+ @Override
+ public int getPosition() {
+ return mTriePos;
+ }
+
+ @Override
+ public void writePtNodeCount(int ptNodeCount) {
+ final int countSize = BinaryDictIOUtils.getPtNodeCountSize(ptNodeCount);
+ // ptNodeCount 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);
+ }
+ mTriePos = BinaryDictEncoderUtils.writeUIntToBuffer(mTrieBuf, mTriePos, ptNodeCount,
+ countSize);
+ }
+
+ private void writePtNodeFlags(final PtNode ptNode, final int parentAddress,
+ final FormatOptions formatOptions) {
+ final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode, formatOptions);
+ mTriePos = BinaryDictEncoderUtils.writeUIntToBuffer(mTrieBuf, mTriePos,
+ BinaryDictEncoderUtils.makePtNodeFlags(ptNode, mTriePos, childrenPos,
+ formatOptions),
+ FormatSpec.PTNODE_FLAGS_SIZE);
+ }
+
+ private void writeParentPosition(int parentPos, final PtNode ptNode,
+ final FormatOptions formatOptions) {
+ if (parentPos != FormatSpec.NO_PARENT_ADDRESS) {
+ parentPos -= ptNode.mCachedAddressAfterUpdate;
+ }
+ mTriePos = BinaryDictEncoderUtils.writeParentAddress(mTrieBuf, mTriePos, parentPos,
+ formatOptions);
+ }
+
+ private void writeCharacters(final int[] characters, final boolean hasSeveralChars) {
+ mTriePos = CharEncoding.writeCharArray(characters, mTrieBuf, mTriePos);
+ if (hasSeveralChars) {
+ mTrieBuf[mTriePos++] = FormatSpec.PTNODE_CHARACTERS_TERMINATOR;
+ }
+ }
+
+ private void writeTerminalId(final int terminalId) {
+ mTriePos = BinaryDictEncoderUtils.writeUIntToBuffer(mTrieBuf, mTriePos, terminalId,
+ FormatSpec.PTNODE_TERMINAL_ID_SIZE);
+ }
+
+ private void writeFrequency(final int frequency, final int terminalId) {
+ final int freqPos = terminalId * FormatSpec.FREQUENCY_AND_FLAGS_SIZE;
+ BinaryDictEncoderUtils.writeUIntToBuffer(mFreqBuf, freqPos, frequency,
+ FormatSpec.FREQUENCY_AND_FLAGS_SIZE);
+ }
+
+ private void writeChildrenPosition(PtNode ptNode, FormatOptions formatOptions) {
+ final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode, formatOptions);
+ if (formatOptions.mSupportsDynamicUpdate) {
+ mTriePos += BinaryDictEncoderUtils.writeSignedChildrenPosition(mTrieBuf,
+ mTriePos, childrenPos);
+ } else {
+ mTriePos += BinaryDictEncoderUtils.writeChildrenPosition(mTrieBuf,
+ mTriePos, childrenPos);
+ }
+ }
+
+ private void writeShortcuts(ArrayList<WeightedString> shortcuts) {
+ if (null == shortcuts || shortcuts.isEmpty()) return;
+
+ final int indexOfShortcutByteSize = mTriePos;
+ mTriePos += FormatSpec.PTNODE_SHORTCUT_LIST_SIZE_SIZE;
+ final Iterator<WeightedString> shortcutIterator = shortcuts.iterator();
+ while (shortcutIterator.hasNext()) {
+ final WeightedString target = shortcutIterator.next();
+ final int shortcutFlags = BinaryDictEncoderUtils.makeShortcutFlags(
+ shortcutIterator.hasNext(),
+ target.mFrequency);
+ mTrieBuf[mTriePos++] = (byte)shortcutFlags;
+ final int shortcutShift = CharEncoding.writeString(mTrieBuf, mTriePos,
+ target.mWord);
+ mTriePos += shortcutShift;
+ }
+ final int shortcutByteSize = mTriePos - indexOfShortcutByteSize;
+ if (shortcutByteSize > FormatSpec.MAX_SHORTCUT_LIST_SIZE_IN_A_PTNODE) {
+ throw new RuntimeException("Shortcut list too large : " + shortcutByteSize);
+ }
+ BinaryDictEncoderUtils.writeUIntToBuffer(mTrieBuf, indexOfShortcutByteSize,
+ shortcutByteSize, FormatSpec.PTNODE_SHORTCUT_LIST_SIZE_SIZE);
+ }
+
+ private void writeBigrams(ArrayList<WeightedString> bigrams, FusionDictionary dict) {
+ if (bigrams == null) return;
+
+ final Iterator<WeightedString> bigramIterator = bigrams.iterator();
+ while (bigramIterator.hasNext()) {
+ final WeightedString bigram = bigramIterator.next();
+ final PtNode target =
+ FusionDictionary.findWordInTree(dict.mRootNodeArray, bigram.mWord);
+ final int addressOfBigram = target.mCachedAddressAfterUpdate;
+ final int unigramFrequencyForThisWord = target.mFrequency;
+ final int offset = addressOfBigram
+ - (mTriePos + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE);
+ int bigramFlags = BinaryDictEncoderUtils.makeBigramFlags(bigramIterator.hasNext(),
+ offset, bigram.mFrequency, unigramFrequencyForThisWord, bigram.mWord);
+ mTrieBuf[mTriePos++] = (byte) bigramFlags;
+ mTriePos += BinaryDictEncoderUtils.writeChildrenPosition(mTrieBuf,
+ mTriePos, Math.abs(offset));
+ }
+ }
+
+ @Override
+ public void writeForwardLinkAddress(int forwardLinkAddress) {
+ mTriePos = BinaryDictEncoderUtils.writeUIntToBuffer(mTrieBuf, mTriePos,
+ forwardLinkAddress, FormatSpec.FORWARD_LINK_ADDRESS_SIZE);
+ }
+
+ @Override
+ public void writePtNode(final PtNode ptNode, final int parentPosition,
+ final FormatOptions formatOptions, final FusionDictionary dict) {
+ writePtNodeFlags(ptNode, parentPosition, formatOptions);
+ writeParentPosition(parentPosition, ptNode, formatOptions);
+ writeCharacters(ptNode.mChars, ptNode.hasSeveralChars());
+ if (ptNode.isTerminal()) {
+ writeTerminalId(ptNode.mTerminalId);
+ writeFrequency(ptNode.mFrequency, ptNode.mTerminalId);
+ }
+ writeChildrenPosition(ptNode, formatOptions);
+ writeShortcuts(ptNode.mShortcutTargets);
+ writeBigrams(ptNode.mBigrams, dict);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
index 5b1d0647b..9364fb034 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
@@ -25,14 +25,13 @@ import com.android.inputmethod.latin.Constants;
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.Ver3DictDecoder;
+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.utils.UserHistoryDictIOUtils;
import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils.OnAddWordListener;
import java.io.File;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
@@ -173,16 +172,19 @@ public abstract class DynamicPredictionDictionaryBase extends ExpandableBinaryDi
// Load the dictionary from binary file
final File dictFile = new File(mContext.getFilesDir(), mFileName);
- final Ver3DictDecoder dictDecoder = new Ver3DictDecoder(dictFile,
+ final DictDecoder dictDecoder = FormatSpec.getDictDecoder(dictFile,
DictDecoder.USE_BYTEARRAY);
+ if (dictDecoder == null) {
+ // This is an expected condition: we don't have a user history dictionary for this
+ // language yet. It will be created sometime later.
+ return;
+ }
+
try {
dictDecoder.openDictBuffer();
UserHistoryDictIOUtils.readDictionaryBinary(dictDecoder, listener);
- } catch (FileNotFoundException e) {
- // This is an expected condition: we don't have a user history dictionary for this
- // language yet. It will be created sometime later.
} catch (IOException e) {
- Log.e(TAG, "IOException on opening a bytebuffer", e);
+ Log.d(TAG, "IOException on opening a bytebuffer", e);
} finally {
if (PROFILE_SAVE_RESTORE) {
final long diff = System.currentTimeMillis() - now;
diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java
index 8732a59f8..1a0fecc62 100644
--- a/java/src/com/android/inputmethod/latin/settings/Settings.java
+++ b/java/src/com/android/inputmethod/latin/settings/Settings.java
@@ -81,6 +81,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public static final String PREF_GESTURE_FLOATING_PREVIEW_TEXT =
"pref_gesture_floating_preview_text";
public static final String PREF_SHOW_SETUP_WIZARD_ICON = "pref_show_setup_wizard_icon";
+ public static final String PREF_PHRASE_GESTURE_ENABLED = "pref_gesture_space_aware";
public static final String PREF_INPUT_LANGUAGE = "input_language";
public static final String PREF_SELECTED_LANGUAGES = "selected_languages";
@@ -97,6 +98,10 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public static final String PREF_SEND_FEEDBACK = "send_feedback";
public static final String PREF_ABOUT_KEYBOARD = "about_keyboard";
+ // Emoji
+ public static final String PREF_EMOJI_RECENT_KEYS = "emoji_recent_keys";
+ public static final String PREF_EMOJI_CATEGORY_LAST_TYPED_ID = "emoji_category_last_typed_id";
+
private Resources mRes;
private SharedPreferences mPrefs;
private SettingsValues mSettingsValues;
@@ -216,6 +221,12 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
&& prefs.getBoolean(Settings.PREF_GESTURE_INPUT, true);
}
+ public static boolean readPhraseGestureEnabled(final SharedPreferences prefs,
+ final Resources res) {
+ return prefs.getBoolean(Settings.PREF_PHRASE_GESTURE_ENABLED,
+ 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);
@@ -363,4 +374,24 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
final String tokenStr = mPrefs.getString(PREF_LAST_USED_PERSONALIZATION_TOKEN, null);
return StringUtils.hexStringToByteArray(tokenStr);
}
+
+ public static void writeEmojiRecentKeys(final SharedPreferences prefs, String str) {
+ prefs.edit().putString(PREF_EMOJI_RECENT_KEYS, str).apply();
+ }
+
+ public static String readEmojiRecentKeys(final SharedPreferences prefs) {
+ return prefs.getString(PREF_EMOJI_RECENT_KEYS, "");
+ }
+
+ public static void writeEmojiCategoryLastTypedId(
+ final SharedPreferences prefs, final int category, final int id) {
+ final String key = PREF_EMOJI_CATEGORY_LAST_TYPED_ID + category;
+ prefs.edit().putInt(key, id).apply();
+ }
+
+ public static int readEmojiCategoryLastTypedId(
+ final SharedPreferences prefs, final int category) {
+ final String key = PREF_EMOJI_CATEGORY_LAST_TYPED_ID + category;
+ return prefs.getInt(key, 0);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index 072bb8731..ee322e91b 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -76,6 +76,7 @@ public final class SettingsValues {
public final boolean mGestureTrailEnabled;
public final boolean mGestureFloatingPreviewTextEnabled;
public final boolean mSlidingKeyInputPreviewEnabled;
+ public final boolean mPhraseGestureEnabled;
public final int mKeyLongpressTimeout;
public final Locale mLocale;
@@ -159,6 +160,7 @@ public final class SettingsValues {
mGestureTrailEnabled = prefs.getBoolean(Settings.PREF_GESTURE_PREVIEW_TRAIL, true);
mGestureFloatingPreviewTextEnabled = prefs.getBoolean(
Settings.PREF_GESTURE_FLOATING_PREVIEW_TEXT, true);
+ mPhraseGestureEnabled = Settings.readPhraseGestureEnabled(prefs, res);
mCorrectionEnabled = mAutoCorrectEnabled && !mInputAttributes.mInputTypeNoAutoCorrect;
final String showSuggestionsSetting = prefs.getString(
Settings.PREF_SHOW_SUGGESTIONS_SETTING,
@@ -211,6 +213,7 @@ public final class SettingsValues {
mGestureInputEnabled = true;
mGestureTrailEnabled = true;
mGestureFloatingPreviewTextEnabled = true;
+ mPhraseGestureEnabled = true;
mCorrectionEnabled = mAutoCorrectEnabled && !mInputAttributes.mInputTypeNoAutoCorrect;
mSuggestionVisibility = 0;
mIsInternal = false;
@@ -295,7 +298,8 @@ public final class SettingsValues {
puncList.add(new SuggestedWordInfo(KeySpecParser.getLabel(puncSpec),
SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_HARDCODED,
Dictionary.DICTIONARY_HARDCODED,
- SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */));
+ SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
+ SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
}
}
return new SuggestedWords(puncList,
diff --git a/java/src/com/android/inputmethod/latin/utils/PositionalInfoForUserDictPendingAddition.java b/java/src/com/android/inputmethod/latin/utils/PositionalInfoForUserDictPendingAddition.java
deleted file mode 100644
index 1fc7eccc6..000000000
--- a/java/src/com/android/inputmethod/latin/utils/PositionalInfoForUserDictPendingAddition.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.utils;
-
-import android.view.inputmethod.EditorInfo;
-
-import com.android.inputmethod.latin.RichInputConnection;
-
-import java.util.Locale;
-
-/**
- * Holder class for data about a word already committed but that may still be edited.
- *
- * When the user chooses to add a word to the user dictionary by pressing the appropriate
- * suggestion, a dialog is presented to give a chance to edit the word before it is actually
- * registered as a user dictionary word. If the word is actually modified, the IME needs to
- * go back and replace the word that was committed with the amended version.
- * The word we need to replace with will only be known after it's actually committed, so
- * the IME needs to take a note of what it has to replace and where it is.
- * This class encapsulates this data.
- */
-public final class PositionalInfoForUserDictPendingAddition {
- final private String mOriginalWord;
- final private int mCursorPos; // Position of the cursor after the word
- final private EditorInfo mEditorInfo; // On what binding this has been added
- final private int mCapitalizedMode;
- private String mActualWordBeingAdded;
-
- public PositionalInfoForUserDictPendingAddition(final String word, final int cursorPos,
- final EditorInfo editorInfo, final int capitalizedMode) {
- mOriginalWord = word;
- mCursorPos = cursorPos;
- mEditorInfo = editorInfo;
- mCapitalizedMode = capitalizedMode;
- }
-
- public void setActualWordBeingAdded(final String actualWordBeingAdded) {
- mActualWordBeingAdded = actualWordBeingAdded;
- }
-
- /**
- * Try to replace the string at the remembered position with the actual word being added.
- *
- * After the user validated the word being added, the IME has to replace the old version
- * (which has been committed in the text view) with the amended version if it's different.
- * This method tries to do that, but may fail because the IME is not yet ready to do so -
- * for example, it is still waiting for the new string, or it is waiting to return to the text
- * view in which the amendment should be made. In these cases, we should keep the data
- * and wait until all conditions are met.
- * This method returns true if the replacement has been successfully made and this data
- * can be forgotten; it returns false if the replacement can't be made yet and we need to
- * keep this until a later time.
- * The IME knows about the actual word being added through a callback called by the
- * user dictionary facility of the device. When this callback comes, the keyboard may still
- * be connected to the edition dialog, or it may have already returned to the original text
- * field. Replacement has to work in both cases.
- * Accordingly, this method is called at two different points in time : upon getting the
- * event that a new word was added to the user dictionary, and upon starting up in a
- * new text field.
- * @param connection The RichInputConnection through which to contact the editor.
- * @param editorInfo Information pertaining to the editor we are currently in.
- * @param currentCursorPosition The current cursor position, for checking purposes.
- * @param locale The locale for changing case, if necessary
- * @return true if the edit has been successfully made, false if we need to try again later
- */
- public boolean tryReplaceWithActualWord(final RichInputConnection connection,
- final EditorInfo editorInfo, final int currentCursorPosition, final Locale locale) {
- // If we still don't know the actual word being added, we need to try again later.
- if (null == mActualWordBeingAdded) return false;
- // The entered text and the registered text were the same anyway : we can
- // return success right away even if focus has not returned yet to the text field we
- // want to amend.
- if (mActualWordBeingAdded.equals(mOriginalWord)) return true;
- // Not the same text field : we need to try again later. This happens when the addition
- // is reported by the user dictionary provider before the focus has moved back to the
- // original text view, so the IME is still in the text view of the dialog and has no way to
- // edit the original text view at this time.
- if (!mEditorInfo.packageName.equals(editorInfo.packageName)
- || mEditorInfo.fieldId != editorInfo.fieldId) {
- return false;
- }
- // Same text field, but not the same cursor position : we give up, so we return success
- // so that it won't be tried again
- if (currentCursorPosition != mCursorPos) return true;
- // We have made all the checks : do the replacement and report success
- // If this was auto-capitalized, we need to restore the case before committing
- final String wordWithCaseFixed = CapsModeUtils.applyAutoCapsMode(mActualWordBeingAdded,
- mCapitalizedMode, locale);
- connection.setComposingRegion(currentCursorPosition - mOriginalWord.length(),
- currentCursorPosition);
- connection.commitText(wordWithCaseFixed, wordWithCaseFixed.length());
- return true;
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java b/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java
index 3c1db6529..5dc0b5893 100644
--- a/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java
+++ b/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java
@@ -31,6 +31,7 @@ public class PrioritizedSerialExecutor {
private static final int TASK_QUEUE_CAPACITY = 1000;
private final Queue<Runnable> mTasks;
private final Queue<Runnable> mPrioritizedTasks;
+ private boolean mIsShutdown;
// The task which is running now.
private Runnable mActive;
@@ -38,6 +39,7 @@ public class PrioritizedSerialExecutor {
public PrioritizedSerialExecutor() {
mTasks = new ArrayDeque<Runnable>(TASK_QUEUE_CAPACITY);
mPrioritizedTasks = new ArrayDeque<Runnable>(TASK_QUEUE_CAPACITY);
+ mIsShutdown = false;
}
/**
@@ -56,9 +58,11 @@ public class PrioritizedSerialExecutor {
*/
public void execute(final Runnable r) {
synchronized(mLock) {
- mTasks.offer(r);
- if (mActive == null) {
- scheduleNext();
+ if (!mIsShutdown) {
+ mTasks.offer(r);
+ if (mActive == null) {
+ scheduleNext();
+ }
}
}
}
@@ -69,9 +73,11 @@ public class PrioritizedSerialExecutor {
*/
public void executePrioritized(final Runnable r) {
synchronized(mLock) {
- mPrioritizedTasks.offer(r);
- if (mActive == null) {
- scheduleNext();
+ if (!mIsShutdown) {
+ mPrioritizedTasks.offer(r);
+ if (mActive == null) {
+ scheduleNext();
+ }
}
}
}
@@ -123,4 +129,19 @@ public class PrioritizedSerialExecutor {
execute(newTask);
}
}
+
+ public void shutdown() {
+ synchronized(mLock) {
+ mIsShutdown = true;
+ }
+ }
+
+ public boolean isTerminated() {
+ synchronized(mLock) {
+ if (!mIsShutdown) {
+ return false;
+ }
+ return mPrioritizedTasks.isEmpty() && mTasks.isEmpty() && mActive == null;
+ }
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java b/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java
index 4c7739a7a..7c6fe93ac 100644
--- a/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java
+++ b/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java
@@ -132,6 +132,15 @@ public final class ResizableIntArray {
}
}
+ /**
+ * Shift to the left by elementCount, discarding elementCount pointers at the start.
+ * @param elementCount how many elements to shift.
+ */
+ public void shift(final int elementCount) {
+ System.arraycopy(mArray, elementCount, mArray, 0, mLength - elementCount);
+ mLength -= elementCount;
+ }
+
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
diff --git a/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
index 05f3061a8..ea32a74ff 100644
--- a/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
@@ -20,13 +20,13 @@ import android.util.Log;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.makedict.BinaryDictIOUtils;
+import com.android.inputmethod.latin.makedict.DictDecoder;
import com.android.inputmethod.latin.makedict.DictEncoder;
import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
import com.android.inputmethod.latin.makedict.FusionDictionary;
import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
import com.android.inputmethod.latin.makedict.PendingAttribute;
import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
-import com.android.inputmethod.latin.makedict.Ver3DictDecoder;
import com.android.inputmethod.latin.personalization.UserHistoryDictionaryBigramList;
import java.io.IOException;
@@ -125,7 +125,7 @@ public final class UserHistoryDictIOUtils {
/**
* Reads dictionary from file.
*/
- public static void readDictionaryBinary(final Ver3DictDecoder dictDecoder,
+ public static void readDictionaryBinary(final DictDecoder dictDecoder,
final OnAddWordListener dict) {
final TreeMap<Integer, String> unigrams = CollectionUtils.newTreeMap();
final TreeMap<Integer, Integer> frequencies = CollectionUtils.newTreeMap();