aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java12
-rw-r--r--java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java53
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionarySettingsActivity.java10
-rw-r--r--java/src/com/android/inputmethod/keyboard/EmojiCategoryPageIndicatorView.java5
-rw-r--r--java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java10
-rw-r--r--java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java4
-rw-r--r--java/src/com/android/inputmethod/keyboard/Key.java19
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java4
-rw-r--r--java/src/com/android/inputmethod/keyboard/PointerTracker.java6
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java32
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java9
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java5
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java8
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java4
-rw-r--r--java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java9
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java55
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFactory.java31
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryWriter.java8
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java94
-rw-r--r--java/src/com/android/inputmethod/latin/InputAttributes.java4
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java51
-rw-r--r--java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java120
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java8
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java1
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java22
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java34
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/DictDecoder.java2
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/DictUpdater.java50
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java107
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/FormatSpec.java13
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/SparseTable.java153
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java43
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java8
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Ver3DictUpdater.java82
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java50
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java152
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java23
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/DictionaryDecayBroadcastReciever.java66
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java5
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java5
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java29
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java (renamed from java/src/com/android/inputmethod/latin/personalization/UserHistoryPredictionDictionary.java)6
-rw-r--r--java/src/com/android/inputmethod/latin/settings/DebugSettingsActivity.java6
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsActivity.java6
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java10
-rw-r--r--java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java29
47 files changed, 1087 insertions, 378 deletions
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
index 7639432aa..c628c5b09 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
@@ -35,6 +35,8 @@ import android.view.inputmethod.EditorInfo;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardView;
+import com.android.inputmethod.latin.settings.Settings;
+import com.android.inputmethod.latin.settings.SettingsValues;
import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.CoordinateUtils;
@@ -285,9 +287,15 @@ public final class AccessibilityEntityProvider extends AccessibilityNodeProvider
private String getKeyDescription(final Key key) {
final EditorInfo editorInfo = mInputMethodService.getCurrentInputEditorInfo();
final boolean shouldObscure = mAccessibilityUtils.shouldObscureInput(editorInfo);
- final String keyDescription = mKeyCodeDescriptionMapper.getDescriptionForKey(
+ final SettingsValues currentSettings = Settings.getInstance().getCurrent();
+ final String keyCodeDescription = mKeyCodeDescriptionMapper.getDescriptionForKey(
mKeyboardView.getContext(), mKeyboardView.getKeyboard(), key, shouldObscure);
- return keyDescription;
+ if (currentSettings.isWordSeparator(key.getCode())) {
+ return mAccessibilityUtils.getAutoCorrectionDescription(
+ keyCodeDescription, shouldObscure);
+ } else {
+ return keyCodeDescription;
+ }
}
/**
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
index 8929dc7e9..10fb9fef4 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
@@ -23,6 +23,7 @@ import android.os.Build;
import android.os.SystemClock;
import android.provider.Settings;
import android.support.v4.view.accessibility.AccessibilityEventCompat;
+import android.text.TextUtils;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
@@ -34,6 +35,7 @@ import android.view.inputmethod.EditorInfo;
import com.android.inputmethod.compat.SettingsSecureCompatUtils;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.utils.InputTypeUtils;
public final class AccessibilityUtils {
@@ -48,6 +50,12 @@ public final class AccessibilityUtils {
private AccessibilityManager mAccessibilityManager;
private AudioManager mAudioManager;
+ /** The most recent auto-correction. */
+ private String mAutoCorrectionWord;
+
+ /** The most recent typed word for auto-correction. */
+ private String mTypedWord;
+
/*
* Setting this constant to {@code false} will disable all keyboard
* accessibility code, regardless of whether Accessibility is turned on in
@@ -142,6 +150,51 @@ public final class AccessibilityUtils {
}
/**
+ * Sets the current auto-correction word and typed word. These may be used
+ * to provide the user with a spoken description of what auto-correction
+ * will occur when a key is typed.
+ *
+ * @param suggestedWords the list of suggested auto-correction words
+ * @param typedWord the currently typed word
+ */
+ public void setAutoCorrection(final SuggestedWords suggestedWords, final String typedWord) {
+ if (suggestedWords != null && suggestedWords.mWillAutoCorrect) {
+ mAutoCorrectionWord = suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION);
+ mTypedWord = typedWord;
+ } else {
+ mAutoCorrectionWord = null;
+ mTypedWord = null;
+ }
+ }
+
+ /**
+ * Obtains a description for an auto-correction key, taking into account the
+ * currently typed word and auto-correction.
+ *
+ * @param keyCodeDescription spoken description of the key that will insert
+ * an auto-correction
+ * @param shouldObscure whether the key should be obscured
+ * @return a description including a description of the auto-correction, if
+ * needed
+ */
+ public String getAutoCorrectionDescription(
+ final String keyCodeDescription, final boolean shouldObscure) {
+ if (!TextUtils.isEmpty(mAutoCorrectionWord)) {
+ if (!TextUtils.equals(mAutoCorrectionWord, mTypedWord)) {
+ if (shouldObscure) {
+ // This should never happen, but just in case...
+ return mContext.getString(R.string.spoken_auto_correct_obscured,
+ keyCodeDescription);
+ }
+ return mContext.getString(R.string.spoken_auto_correct, keyCodeDescription,
+ mTypedWord, mAutoCorrectionWord);
+ }
+ }
+
+ return keyCodeDescription;
+ }
+
+ /**
* Sends the specified text to the {@link AccessibilityManager} to be
* spoken.
*
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsActivity.java b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsActivity.java
index 684165240..c28d72949 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsActivity.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsActivity.java
@@ -24,6 +24,8 @@ import android.preference.PreferenceActivity;
* Preference screen.
*/
public final class DictionarySettingsActivity extends PreferenceActivity {
+ private static final String DEFAULT_FRAGMENT = DictionarySettingsFragment.class.getName();
+
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -32,11 +34,17 @@ public final class DictionarySettingsActivity extends PreferenceActivity {
@Override
public Intent getIntent() {
final Intent modIntent = new Intent(super.getIntent());
- modIntent.putExtra(EXTRA_SHOW_FRAGMENT, DictionarySettingsFragment.class.getName());
+ modIntent.putExtra(EXTRA_SHOW_FRAGMENT, DEFAULT_FRAGMENT);
modIntent.putExtra(EXTRA_NO_HEADERS, true);
// Important note : the original intent should contain a String extra with the key
// DictionarySettingsFragment.DICT_SETTINGS_FRAGMENT_CLIENT_ID_ARGUMENT so that the
// fragment can know who the client is.
return modIntent;
}
+
+ // TODO: Uncomment the override annotation once we start using SDK version 19.
+ // @Override
+ public boolean isValidFragment(String fragmentName) {
+ return fragmentName.equals(DEFAULT_FRAGMENT);
+ }
}
diff --git a/java/src/com/android/inputmethod/keyboard/EmojiCategoryPageIndicatorView.java b/java/src/com/android/inputmethod/keyboard/EmojiCategoryPageIndicatorView.java
index fed134eb9..e23131a30 100644
--- a/java/src/com/android/inputmethod/keyboard/EmojiCategoryPageIndicatorView.java
+++ b/java/src/com/android/inputmethod/keyboard/EmojiCategoryPageIndicatorView.java
@@ -50,8 +50,9 @@ public class EmojiCategoryPageIndicatorView extends LinearLayout {
@Override
protected void onDraw(Canvas canvas) {
- if (mCategoryPageSize == 0) {
- // If the category is not set yet, just clear and return.
+ if (mCategoryPageSize <= 1) {
+ // If the category is not set yet or contains only one category,
+ // just clear and return.
canvas.drawColor(0);
return;
}
diff --git a/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java b/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java
index 61dc56ed1..eb48d01f6 100644
--- a/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java
@@ -718,12 +718,14 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
@Override
public void run() {
+ int repeatCount = 1;
int timeCount = 0;
while (timeCount < MAX_REPEAT_COUNT_TIME && !mAborted) {
if (timeCount > mKeyRepeatStartTimeout) {
- pressDelete();
+ pressDelete(repeatCount);
}
timeCount += mKeyRepeatInterval;
+ ++repeatCount;
try {
Thread.sleep(mKeyRepeatInterval);
} catch (InterruptedException e) {
@@ -736,9 +738,9 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
}
}
- public void pressDelete() {
+ public void pressDelete(int repeatCount) {
mKeyboardActionListener.onPressKey(
- Constants.CODE_DELETE, 0 /* repeatCount */, true /* isSinglePointer */);
+ Constants.CODE_DELETE, repeatCount, true /* isSinglePointer */);
mKeyboardActionListener.onCodeInput(
Constants.CODE_DELETE, NOT_A_COORDINATE, NOT_A_COORDINATE);
mKeyboardActionListener.onReleaseKey(
@@ -754,7 +756,7 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
v.setBackgroundColor(mDeleteKeyPressedBackgroundColor);
- pressDelete();
+ pressDelete(0 /* repeatCount */);
startRepeat();
return true;
case MotionEvent.ACTION_UP:
diff --git a/java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java b/java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java
index 267fad5cd..71790b7d6 100644
--- a/java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java
+++ b/java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java
@@ -75,9 +75,7 @@ public class EmojiLayoutParams {
public void setActionBarProperties(LinearLayout ll) {
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) ll.getLayoutParams();
- lp.height = mEmojiActionBarHeight;
- lp.topMargin = 0;
- lp.bottomMargin = mBottomPadding;
+ lp.height = mEmojiActionBarHeight - mBottomPadding;
ll.setLayoutParams(lp);
}
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index 3ea68806b..f7ec9509d 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -139,6 +139,8 @@ public class Key implements Comparable<Key> {
private final OptionalAttributes mOptionalAttributes;
+ private static final int DEFAULT_TEXT_COLOR = 0xFFFFFFFF;
+
private static final class OptionalAttributes {
/** Text to output when pressed. This can be multiple characters, like ".com" */
public final String mOutputText;
@@ -602,7 +604,22 @@ public class Key implements Comparable<Key> {
}
public final int selectTextColor(final KeyDrawParams params) {
- return isShiftedLetterActivated() ? params.mTextInactivatedColor : params.mTextColor;
+ if (isShiftedLetterActivated()) {
+ return params.mTextInactivatedColor;
+ }
+ if (params.mTextColorStateList == null) {
+ return DEFAULT_TEXT_COLOR;
+ }
+ final int[] state;
+ // TODO: Hack!!!!!!!! Consider having a new attribute for the functional text labels.
+ // Currently, we distinguish "input key" from "functional key" by checking the
+ // length of the label( > 1) and "functional" attributes (= true).
+ if (mLabel != null && mLabel.length() > 1) {
+ state = getCurrentDrawableState();
+ } else {
+ state = KEY_STATE_NORMAL;
+ }
+ return params.mTextColorStateList.getColorForState(state, DEFAULT_TEXT_COLOR);
}
public final int selectHintTextSize(final KeyDrawParams params) {
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index cc1ffd183..ad6e2c0f2 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -155,7 +155,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
}
public void saveKeyboardState() {
- if (getKeyboard() != null) {
+ if (getKeyboard() != null || isShowingEmojiKeyboard()) {
mState.onSaveKeyboardState();
}
}
@@ -315,7 +315,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
}
public boolean isShowingEmojiKeyboard() {
- return mEmojiKeyboardView.getVisibility() == View.VISIBLE;
+ return mEmojiKeyboardView != null && mEmojiKeyboardView.getVisibility() == View.VISIBLE;
}
public boolean isShowingMoreKeysPanel() {
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index ee4ac950c..52f190e77 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -823,14 +823,16 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
final int size = sAggregratedPointers.getPointerSize();
if (size > sLastRecognitionPointSize
&& stroke.hasRecognitionTimePast(eventTime, sLastRecognitionTime)) {
- sLastRecognitionPointSize = size;
- sLastRecognitionTime = eventTime;
if (DEBUG_LISTENER) {
Log.d(TAG, String.format("[%d] onUpdateBatchInput: batchPoints=%d", mPointerId,
size));
}
mTimerProxy.startUpdateBatchInputTimer(this);
mListener.onUpdateBatchInput(sAggregratedPointers);
+ // The listener may change the size of the pointers (when auto-committing
+ // for example), so we need to get the size from the pointers again.
+ sLastRecognitionPointSize = sAggregratedPointers.getPointerSize();
+ sLastRecognitionTime = eventTime;
}
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java b/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java
index c10fdbace..4ccecb2f0 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java
@@ -18,6 +18,8 @@ package com.android.inputmethod.keyboard.internal;
import com.android.inputmethod.latin.Constants;
+import android.text.TextUtils;
+
/**
* The string parser of codesArray specification for <GridRows />. The attribute codesArray is an
* array of string.
@@ -34,7 +36,7 @@ import com.android.inputmethod.latin.Constants;
public final class CodesArrayParser {
// Constants for parsing.
private static final char COMMA = ',';
- private static final char VERTICAL_BAR = '|';
+ private static final String VERTICAL_BAR_STRING = "\\|";
private static final String COMMA_STRING = ",";
private static final int BASE_HEX = 16;
@@ -43,8 +45,11 @@ public final class CodesArrayParser {
}
private static String getLabelSpec(final String codesArraySpec) {
- final int pos = codesArraySpec.indexOf(VERTICAL_BAR);
- return (pos < 0) ? codesArraySpec : codesArraySpec.substring(0, pos);
+ final String[] strs = codesArraySpec.split(VERTICAL_BAR_STRING, -1);
+ if (strs.length <= 1) {
+ return codesArraySpec;
+ }
+ return strs[0];
}
public static String parseLabel(final String codesArraySpec) {
@@ -58,8 +63,25 @@ public final class CodesArrayParser {
}
private static String getCodeSpec(final String codesArraySpec) {
- final int pos = codesArraySpec.indexOf(VERTICAL_BAR);
- return (pos < 0) ? codesArraySpec : codesArraySpec.substring(pos + 1);
+ final String[] strs = codesArraySpec.split(VERTICAL_BAR_STRING, -1);
+ if (strs.length <= 1) {
+ return codesArraySpec;
+ }
+ return TextUtils.isEmpty(strs[1]) ? strs[0] : strs[1];
+ }
+
+ // codesArraySpec consists of:
+ // <label>|<code0>,<code1>,...|<minSupportSdkVersion>
+ public static int getMinSupportSdkVersion(final String codesArraySpec) {
+ final String[] strs = codesArraySpec.split(VERTICAL_BAR_STRING, -1);
+ if (strs.length <= 2) {
+ return 0;
+ }
+ try {
+ return Integer.parseInt(strs[2]);
+ } catch (NumberFormatException e) {
+ return 0;
+ }
}
public static int parseCode(final String codesArraySpec) {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java
index 1716fa049..b528b692e 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java
@@ -16,6 +16,7 @@
package com.android.inputmethod.keyboard.internal;
+import android.content.res.ColorStateList;
import android.graphics.Typeface;
import com.android.inputmethod.latin.utils.ResourceUtils;
@@ -32,7 +33,7 @@ public final class KeyDrawParams {
public int mHintLabelSize;
public int mPreviewTextSize;
- public int mTextColor;
+ public ColorStateList mTextColorStateList;
public int mTextInactivatedColor;
public int mTextShadowColor;
public int mHintLetterColor;
@@ -57,7 +58,7 @@ public final class KeyDrawParams {
mHintLabelSize = copyFrom.mHintLabelSize;
mPreviewTextSize = copyFrom.mPreviewTextSize;
- mTextColor = copyFrom.mTextColor;
+ mTextColorStateList = copyFrom.mTextColorStateList;
mTextInactivatedColor = copyFrom.mTextInactivatedColor;
mTextShadowColor = copyFrom.mTextShadowColor;
mHintLetterColor = copyFrom.mHintLetterColor;
@@ -89,8 +90,8 @@ public final class KeyDrawParams {
attr.mShiftedLetterHintRatio, mShiftedLetterHintSize);
mHintLabelSize = selectTextSize(keyHeight, attr.mHintLabelRatio, mHintLabelSize);
mPreviewTextSize = selectTextSize(keyHeight, attr.mPreviewTextRatio, mPreviewTextSize);
-
- mTextColor = selectColor(attr.mTextColor, mTextColor);
+ mTextColorStateList =
+ attr.mTextColorStateList != null ? attr.mTextColorStateList : mTextColorStateList;
mTextInactivatedColor = selectColor(attr.mTextInactivatedColor, mTextInactivatedColor);
mTextShadowColor = selectColor(attr.mTextShadowColor, mTextShadowColor);
mHintLetterColor = selectColor(attr.mHintLetterColor, mHintLetterColor);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java b/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java
index 7a2622cbb..8bdad364c 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java
@@ -16,6 +16,7 @@
package com.android.inputmethod.keyboard.internal;
+import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.util.SparseIntArray;
@@ -37,7 +38,7 @@ public final class KeyVisualAttributes {
public final float mHintLabelRatio;
public final float mPreviewTextRatio;
- public final int mTextColor;
+ public final ColorStateList mTextColorStateList;
public final int mTextInactivatedColor;
public final int mTextShadowColor;
public final int mHintLetterColor;
@@ -115,7 +116,7 @@ public final class KeyVisualAttributes {
mPreviewTextRatio = ResourceUtils.getFraction(keyAttr,
R.styleable.Keyboard_Key_keyPreviewTextRatio);
- mTextColor = keyAttr.getColor(R.styleable.Keyboard_Key_keyTextColor, 0);
+ mTextColorStateList = keyAttr.getColorStateList(R.styleable.Keyboard_Key_keyTextColor);
mTextInactivatedColor = keyAttr.getColor(
R.styleable.Keyboard_Key_keyTextInactivatedColor, 0);
mTextShadowColor = keyAttr.getColor(R.styleable.Keyboard_Key_keyTextShadowColor, 0);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
index 22f7a83fc..c1ae65695 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
+import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
@@ -436,17 +437,24 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
final String label;
final int code;
final String outputText;
+ final int supportedMinSdkVersion;
if (codesArrayId != 0) {
final String codeArraySpec = array[i];
label = CodesArrayParser.parseLabel(codeArraySpec);
code = CodesArrayParser.parseCode(codeArraySpec);
outputText = CodesArrayParser.parseOutputText(codeArraySpec);
+ supportedMinSdkVersion =
+ CodesArrayParser.getMinSupportSdkVersion(codeArraySpec);
} else {
final String textArraySpec = array[i];
// TODO: Utilize KeySpecParser or write more generic TextsArrayParser.
label = textArraySpec;
code = Constants.CODE_OUTPUT_TEXT;
outputText = textArraySpec + (char)Constants.CODE_SPACE;
+ supportedMinSdkVersion = 0;
+ }
+ if (Build.VERSION.SDK_INT < supportedMinSdkVersion) {
+ continue;
}
final int x = (int)row.getKeyX(null);
final int y = row.getKeyY();
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
index 9f9fdaa6f..506dfa751 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
@@ -178,6 +178,8 @@ public final class KeyboardState {
if (!state.mIsAlphabetShiftLocked) {
setShifted(state.mShiftMode);
}
+ // TODO: is this the right place to do this? Should we do this in setShift* instead?
+ mSwitchActions.requestUpdatingShiftState();
} else {
mPrevMainKeyboardWasShiftLocked = state.mIsAlphabetShiftLocked;
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
index 67553fb75..684cf632b 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
@@ -209,8 +209,8 @@ public final class KeyboardTextsSet {
/* 104 */ "keylabel_for_tablet_comma",
/* 105 */ "keyhintlabel_for_tablet_comma",
/* 106 */ "more_keys_for_tablet_comma",
- /* 107 */ "keyhintlabel_for_tablet_period",
- /* 108 */ "more_keys_for_tablet_period",
+ /* 107 */ "keyhintlabel_for_period",
+ /* 108 */ "more_keys_for_period",
/* 109 */ "keylabel_for_apostrophe",
/* 110 */ "keyhintlabel_for_apostrophe",
/* 111 */ "more_keys_for_apostrophe",
diff --git a/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java b/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
index 845a9b987..4a0ce3735 100644
--- a/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
+++ b/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
@@ -25,6 +25,7 @@ import com.android.inputmethod.latin.makedict.Ver3DictEncoder;
import java.io.File;
import java.io.IOException;
+import java.util.Map;
// TODO: Quit extending Dictionary after implementing dynamic binary dictionary.
abstract public class AbstractDictionaryWriter extends Dictionary {
@@ -50,16 +51,16 @@ abstract public class AbstractDictionaryWriter extends Dictionary {
abstract public void removeBigramWords(final String word0, final String word1);
- abstract protected void writeDictionary(final DictEncoder dictEncoder)
- throws IOException, UnsupportedFormatException;
+ abstract protected void writeDictionary(final DictEncoder dictEncoder,
+ final Map<String, String> attributeMap) throws IOException, UnsupportedFormatException;
- public void write(final String fileName) {
+ public void write(final String fileName, final Map<String, String> attributeMap) {
final String tempFileName = fileName + ".temp";
final File file = new File(mContext.getFilesDir(), fileName);
final File tempFile = new File(mContext.getFilesDir(), tempFileName);
try {
final DictEncoder dictEncoder = new Ver3DictEncoder(tempFile);
- writeDictionary(dictEncoder);
+ writeDictionary(dictEncoder, attributeMap);
tempFile.renameTo(file);
} catch (IOException e) {
Log.e(TAG, "IO exception while writing file", e);
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index a463651d5..541e69788 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -44,9 +44,14 @@ 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;
+ // The cutoff returned by native for auto-commit confidence.
+ // Must be equal to CONFIDENCE_TO_AUTO_COMMIT in native/jni/src/defines.h
+ private static final int CONFIDENCE_TO_AUTO_COMMIT = 1000000;
+
+ @UsedForTesting
+ public static final String UNIGRAM_COUNT_QUERY = "UNIGRAM_COUNT";
+ @UsedForTesting
+ public static final String BIGRAM_COUNT_QUERY = "BIGRAM_COUNT";
private long mNativeDict;
private final Locale mLocale;
@@ -57,7 +62,8 @@ public final class BinaryDictionary extends Dictionary {
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];
+ // Only one result is ever used
+ private final int[] mOutputAutoCommitFirstWordConfidence = new int[1];
private final NativeSuggestOptions mNativeSuggestOptions = new NativeSuggestOptions();
@@ -110,7 +116,7 @@ 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 boolean needsToRunGCNative(long dict, boolean mindsBlockByGC);
private static native void flushWithGCNative(long dict, String filePath);
private static native void closeNative(long dict);
private static native int getProbabilityNative(long dict, int[] word);
@@ -129,6 +135,7 @@ public final class BinaryDictionary extends Dictionary {
private static native void removeBigramWordsNative(long dict, int[] word0, int[] word1);
private static native int calculateProbabilityNative(long dict, int unigramProbability,
int bigramProbability);
+ private static native String getPropertyNative(long dict, String query);
@UsedForTesting
public static boolean createEmptyDictFile(final String filePath, final long dictVersion,
@@ -263,18 +270,11 @@ public final class BinaryDictionary extends Dictionary {
return getBigramProbabilityNative(mNativeDict, codePoints0, codePoints1);
}
- private void runGCIfRequired() {
- if (needsToRunGCNative(mNativeDict)) {
- flushWithGC();
- }
- }
-
// Add a unigram entry to binary dictionary in native code.
public void addUnigramWord(final String word, final int probability) {
if (TextUtils.isEmpty(word)) {
return;
}
- runGCIfRequired();
final int[] codePoints = StringUtils.toCodePointArray(word);
addUnigramWordNative(mNativeDict, codePoints, probability);
}
@@ -284,7 +284,6 @@ public final class BinaryDictionary extends Dictionary {
if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) {
return;
}
- runGCIfRequired();
final int[] codePoints0 = StringUtils.toCodePointArray(word0);
final int[] codePoints1 = StringUtils.toCodePointArray(word1);
addBigramWordsNative(mNativeDict, codePoints0, codePoints1, probability);
@@ -295,7 +294,6 @@ public final class BinaryDictionary extends Dictionary {
if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) {
return;
}
- runGCIfRequired();
final int[] codePoints0 = StringUtils.toCodePointArray(word0);
final int[] codePoints1 = StringUtils.toCodePointArray(word1);
removeBigramWordsNative(mNativeDict, codePoints0, codePoints1);
@@ -320,9 +318,15 @@ public final class BinaryDictionary extends Dictionary {
reopen();
}
- public boolean needsToRunGC() {
+ /**
+ * Checks whether GC is needed to run or not.
+ * @param mindsBlockByGC Whether to mind operations blocked by GC. We don't need to care about
+ * the blocking in some situations such as in idle time or just before closing.
+ * @return whether GC is needed to run or not.
+ */
+ public boolean needsToRunGC(final boolean mindsBlockByGC) {
if (!isValidDictionary()) return false;
- return needsToRunGCNative(mNativeDict);
+ return needsToRunGCNative(mNativeDict, mindsBlockByGC);
}
@UsedForTesting
@@ -331,20 +335,15 @@ public final class BinaryDictionary extends Dictionary {
return calculateProbabilityNative(mNativeDict, unigramProbability, bigramProbability);
}
+ @UsedForTesting
+ public String getPropertyForTests(String query) {
+ if (!isValidDictionary()) return "";
+ return getPropertyNative(mNativeDict, query);
+ }
+
@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;
+ return candidate.mAutoCommitFirstWordConfidence > CONFIDENCE_TO_AUTO_COMMIT;
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index 3721132c5..828e54f14 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -51,7 +51,7 @@ public final class DictionaryFactory {
if (null == locale) {
Log.e(TAG, "No locale defined for dictionary");
return new DictionaryCollection(Dictionary.TYPE_MAIN,
- createBinaryDictionary(context, locale));
+ createReadOnlyBinaryDictionary(context, locale));
}
final LinkedList<Dictionary> dictList = CollectionUtils.newLinkedList();
@@ -59,11 +59,11 @@ public final class DictionaryFactory {
BinaryDictionaryGetter.getDictionaryFiles(locale, context);
if (null != assetFileList) {
for (final AssetFileAddress f : assetFileList) {
- final BinaryDictionary binaryDictionary = new BinaryDictionary(f.mFilename,
- f.mOffset, f.mLength, useFullEditDistance, locale, Dictionary.TYPE_MAIN,
- false /* isUpdatable */);
- if (binaryDictionary.isValidDictionary()) {
- dictList.add(binaryDictionary);
+ final ReadOnlyBinaryDictionary readOnlyBinaryDictionary =
+ new ReadOnlyBinaryDictionary(f.mFilename, f.mOffset, f.mLength,
+ useFullEditDistance, locale, Dictionary.TYPE_MAIN);
+ if (readOnlyBinaryDictionary.isValidDictionary()) {
+ dictList.add(readOnlyBinaryDictionary);
}
}
}
@@ -89,12 +89,12 @@ public final class DictionaryFactory {
}
/**
- * Initializes a dictionary from a raw resource file
+ * Initializes a read-only binary dictionary from a raw resource file
* @param context application context for reading resources
* @param locale the locale to use for the resource
- * @return an initialized instance of BinaryDictionary
+ * @return an initialized instance of ReadOnlyBinaryDictionary
*/
- protected static BinaryDictionary createBinaryDictionary(final Context context,
+ protected static ReadOnlyBinaryDictionary createReadOnlyBinaryDictionary(final Context context,
final Locale locale) {
AssetFileDescriptor afd = null;
try {
@@ -113,9 +113,8 @@ public final class DictionaryFactory {
Log.e(TAG, "sourceDir is not a file: " + sourceDir);
return null;
}
- return new BinaryDictionary(sourceDir, afd.getStartOffset(), afd.getLength(),
- false /* useFullEditDistance */, locale, Dictionary.TYPE_MAIN,
- false /* isUpdatable */);
+ return new ReadOnlyBinaryDictionary(sourceDir, afd.getStartOffset(), afd.getLength(),
+ false /* useFullEditDistance */, locale, Dictionary.TYPE_MAIN);
} catch (android.content.res.Resources.NotFoundException e) {
Log.e(TAG, "Could not find the resource");
return null;
@@ -142,10 +141,10 @@ public final class DictionaryFactory {
final DictionaryCollection dictionaryCollection =
new DictionaryCollection(Dictionary.TYPE_MAIN);
for (final AssetFileAddress address : dictionaryList) {
- final BinaryDictionary binaryDictionary = new BinaryDictionary(address.mFilename,
- address.mOffset, address.mLength, useFullEditDistance, locale,
- Dictionary.TYPE_MAIN, false /* isUpdatable */);
- dictionaryCollection.addDictionary(binaryDictionary);
+ final ReadOnlyBinaryDictionary readOnlyBinaryDictionary = new ReadOnlyBinaryDictionary(
+ address.mFilename, address.mOffset, address.mLength, useFullEditDistance,
+ locale, Dictionary.TYPE_MAIN);
+ dictionaryCollection.addDictionary(readOnlyBinaryDictionary);
}
return dictionaryCollection;
}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryWriter.java b/java/src/com/android/inputmethod/latin/DictionaryWriter.java
index 5a453dde5..84abfa66d 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryWriter.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryWriter.java
@@ -31,6 +31,7 @@ import com.android.inputmethod.latin.utils.CollectionUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Map;
/**
* An in memory dictionary for memorizing entries and writing a binary dictionary.
@@ -84,8 +85,11 @@ public class DictionaryWriter extends AbstractDictionaryWriter {
}
@Override
- protected void writeDictionary(final DictEncoder dictEncoder)
- throws IOException, UnsupportedFormatException {
+ protected void writeDictionary(final DictEncoder dictEncoder,
+ final Map<String, String> attributeMap) throws IOException, UnsupportedFormatException {
+ for (final Map.Entry<String, String> entry : attributeMap.entrySet()) {
+ mFusionDictionary.addOptionAttribute(entry.getKey(), entry.getValue());
+ }
dictEncoder.writeDictionary(mFusionDictionary, FORMAT_OPTIONS);
}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index dc4b0a0cc..c79a4ff90 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -236,6 +236,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
HashMap<String, String> attributeMap = new HashMap<String, String>();
attributeMap.put(FormatSpec.FileHeader.SUPPORTS_DYNAMIC_UPDATE_ATTRIBUTE,
SUPPORTS_DYNAMIC_UPDATE);
+ attributeMap.put(FormatSpec.FileHeader.DICTIONARY_ID_ATTRIBUTE, mFilename);
return attributeMap;
}
@@ -248,6 +249,9 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
final File file = new File(mContext.getFilesDir(), mFilename);
BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(),
DICTIONARY_FORMAT_VERSION, getHeaderAttributeMap());
+ mBinaryDictionary = new BinaryDictionary(
+ file.getAbsolutePath(), 0 /* offset */, file.length(),
+ true /* useFullEditDistance */, null, mDictType, mIsUpdatable);
} else {
mDictionaryWriter.clear();
}
@@ -273,6 +277,39 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
/**
+ * Check whether GC is needed and run GC if required.
+ */
+ protected void runGCIfRequired(final boolean mindsBlockByGC) {
+ if (!ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) return;
+ getExecutor(mFilename).execute(new Runnable() {
+ @Override
+ public void run() {
+ runGCIfRequiredInternalLocked(mindsBlockByGC);
+ }
+ });
+ }
+
+ private void runGCIfRequiredInternalLocked(final boolean mindsBlockByGC) {
+ if (!ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) return;
+ // Calls to needsToRunGC() need to be serialized.
+ if (mBinaryDictionary.needsToRunGC(mindsBlockByGC)) {
+ if (setIsRegeneratingIfNotRegenerating()) {
+ // Run GC after currently existing time sensitive operations.
+ getExecutor(mFilename).executePrioritized(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mBinaryDictionary.flushWithGC();
+ } finally {
+ mFilenameDictionaryUpdateController.mIsRegenerating.set(false);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ /**
* Dynamically adds a word unigram to the dictionary. May overwrite an existing entry.
*/
protected void addWordDynamically(final String word, final String shortcutTarget,
@@ -281,11 +318,11 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
Log.w(TAG, "addWordDynamically is called for non-updatable dictionary: " + mFilename);
return;
}
-
getExecutor(mFilename).execute(new Runnable() {
@Override
public void run() {
if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
+ runGCIfRequiredInternalLocked(true /* mindsBlockByGC */);
mBinaryDictionary.addUnigramWord(word, frequency);
} else {
// TODO: Remove.
@@ -305,11 +342,11 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
+ mFilename);
return;
}
-
getExecutor(mFilename).execute(new Runnable() {
@Override
public void run() {
if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
+ runGCIfRequiredInternalLocked(true /* mindsBlockByGC */);
mBinaryDictionary.addBigramWords(word0, word1, frequency);
} else {
// TODO: Remove.
@@ -329,11 +366,11 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
+ mFilename);
return;
}
-
getExecutor(mFilename).execute(new Runnable() {
@Override
public void run() {
if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
+ runGCIfRequiredInternalLocked(true /* mindsBlockByGC */);
mBinaryDictionary.removeBigramWords(word0, word1);
} else {
// TODO: Remove.
@@ -460,8 +497,8 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
final long length = file.length();
// Build the new binary dictionary
- final BinaryDictionary newBinaryDictionary = new BinaryDictionary(filename, 0, length,
- true /* useFullEditDistance */, null, mDictType, mIsUpdatable);
+ final BinaryDictionary newBinaryDictionary = new BinaryDictionary(filename, 0 /* offset */,
+ length, true /* useFullEditDistance */, null, mDictType, mIsUpdatable);
// Ensure all threads accessing the current dictionary have finished before
// swapping in the new one.
@@ -496,7 +533,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
if (needsToReloadBeforeWriting()) {
mDictionaryWriter.clear();
loadDictionaryAsync();
- mDictionaryWriter.write(mFilename);
+ mDictionaryWriter.write(mFilename, getHeaderAttributeMap());
} else {
if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
if (mBinaryDictionary == null || !mBinaryDictionary.isValidDictionary()) {
@@ -504,14 +541,14 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(),
DICTIONARY_FORMAT_VERSION, getHeaderAttributeMap());
} else {
- if (mBinaryDictionary.needsToRunGC()) {
+ if (mBinaryDictionary.needsToRunGC(false /* mindsBlockByGC */)) {
mBinaryDictionary.flushWithGC();
} else {
mBinaryDictionary.flush();
}
}
} else {
- mDictionaryWriter.write(mFilename);
+ mDictionaryWriter.write(mFilename, getHeaderAttributeMap());
}
}
}
@@ -660,47 +697,6 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
}
- /**
- * Dynamically adds a word unigram to the dictionary for testing with blocking-lock.
- */
- @UsedForTesting
- protected void addWordDynamicallyForTests(final String word, final String shortcutTarget,
- final int frequency, final boolean isNotAWord) {
- getExecutor(mFilename).executePrioritized(new Runnable() {
- @Override
- public void run() {
- addWordDynamically(word, shortcutTarget, frequency, isNotAWord);
- }
- });
- }
-
- /**
- * Dynamically adds a word bigram in the dictionary for testing with blocking-lock.
- */
- @UsedForTesting
- protected void addBigramDynamicallyForTests(final String word0, final String word1,
- final int frequency, final boolean isValid) {
- getExecutor(mFilename).executePrioritized(new Runnable() {
- @Override
- public void run() {
- addBigramDynamically(word0, word1, frequency, isValid);
- }
- });
- }
-
- /**
- * Dynamically remove a word bigram in the dictionary for testing with blocking-lock.
- */
- @UsedForTesting
- protected void removeBigramDynamicallyForTests(final String word0, final String word1) {
- getExecutor(mFilename).executePrioritized(new Runnable() {
- @Override
- public void run() {
- removeBigramDynamically(word0, word1);
- }
- });
- }
-
// TODO: Implement native binary methods once the dynamic dictionary implementation is done.
@UsedForTesting
public boolean isInDictionaryForTests(final String word) {
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
index 21b103e5a..8caf6f17f 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -103,6 +103,10 @@ public final class InputAttributes {
}
}
+ public boolean isTypeNull() {
+ return InputType.TYPE_NULL == mInputType;
+ }
+
public boolean isSameInputType(final EditorInfo editorInfo) {
return editorInfo.inputType == mInputType;
}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 270dc4c06..0f3d28976 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -77,11 +77,12 @@ import com.android.inputmethod.keyboard.MainKeyboardView;
import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.define.ProductionFlag;
+import com.android.inputmethod.latin.personalization.DictionaryDecayBroadcastReciever;
import com.android.inputmethod.latin.personalization.PersonalizationDictionary;
import com.android.inputmethod.latin.personalization.PersonalizationDictionarySessionRegister;
import com.android.inputmethod.latin.personalization.PersonalizationHelper;
import com.android.inputmethod.latin.personalization.PersonalizationPredictionDictionary;
-import com.android.inputmethod.latin.personalization.UserHistoryPredictionDictionary;
+import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.settings.SettingsActivity;
import com.android.inputmethod.latin.settings.SettingsValues;
@@ -179,7 +180,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private boolean mIsMainDictionaryAvailable;
private UserBinaryDictionary mUserDictionary;
- private UserHistoryPredictionDictionary mUserHistoryPredictionDictionary;
+ private UserHistoryDictionary mUserHistoryDictionary;
private PersonalizationPredictionDictionary mPersonalizationPredictionDictionary;
private PersonalizationDictionary mPersonalizationDictionary;
private boolean mIsUserDictionaryAvailable;
@@ -567,6 +568,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
newDictFilter.addAction(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION);
registerReceiver(mDictionaryPackInstallReceiver, newDictFilter);
+ DictionaryDecayBroadcastReciever.setUpIntervalAlarmForDictionaryDecaying(this);
+
mInputUpdater = new InputUpdater(this);
}
@@ -623,9 +626,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
- mUserHistoryPredictionDictionary = PersonalizationHelper
- .getUserHistoryPredictionDictionary(this, localeStr, prefs);
- newSuggest.setUserHistoryPredictionDictionary(mUserHistoryPredictionDictionary);
+ mUserHistoryDictionary = PersonalizationHelper.getUserHistoryDictionary(
+ this, localeStr, prefs);
+ newSuggest.setUserHistoryDictionary(mUserHistoryDictionary);
mPersonalizationDictionary = PersonalizationHelper
.getPersonalizationDictionary(this, localeStr, prefs);
newSuggest.setPersonalizationDictionary(mPersonalizationDictionary);
@@ -1521,7 +1524,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mSubtypeState.switchSubtype(token, mRichImm);
}
- private void sendDownUpKeyEventForBackwardCompatibility(final int code) {
+ private void sendDownUpKeyEvent(final int code) {
final long eventTime = SystemClock.uptimeMillis();
mConnection.sendKeyEvent(new KeyEvent(eventTime, eventTime,
KeyEvent.ACTION_DOWN, code, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
@@ -1538,7 +1541,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// TODO: Remove this special handling of digit letters.
// For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}.
if (code >= '0' && code <= '9') {
- sendDownUpKeyEventForBackwardCompatibility(code - '0' + KeyEvent.KEYCODE_0);
+ sendDownUpKeyEvent(code - '0' + KeyEvent.KEYCODE_0);
return;
}
@@ -1547,7 +1550,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// a hardware keyboard event on pressing enter or delete. This is bad for many
// reasons (there are race conditions with commits) but some applications are
// relying on this behavior so we continue to support it for older apps.
- sendDownUpKeyEventForBackwardCompatibility(KeyEvent.KEYCODE_ENTER);
+ sendDownUpKeyEvent(KeyEvent.KEYCODE_ENTER);
} else {
final String text = new String(new int[] { code }, 0, 1);
mConnection.commitText(text, text.length());
@@ -2104,12 +2107,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
final int lengthToDelete = Character.isSupplementaryCodePoint(
mConnection.getCodePointBeforeCursor()) ? 2 : 1;
- if (mAppWorkAroundsUtils.isBeforeJellyBean()) {
- // Backward compatibility mode. Before Jelly bean, the keyboard would simulate
- // a hardware keyboard event on pressing enter or delete. This is bad for many
- // reasons (there are race conditions with commits) but some applications are
- // relying on this behavior so we continue to support it for older apps.
- sendDownUpKeyEventForBackwardCompatibility(KeyEvent.KEYCODE_DEL);
+ if (mAppWorkAroundsUtils.isBeforeJellyBean() ||
+ currentSettings.mInputAttributes.isTypeNull()) {
+ // There are two possible reasons to send a key event: either the field has
+ // type TYPE_NULL, in which case the keyboard should send events, or we are
+ // running in backward compatibility mode. Before Jelly bean, the keyboard
+ // would simulate a hardware keyboard event on pressing enter or delete. This
+ // is bad for many reasons (there are race conditions with commits) but some
+ // applications are relying on this behavior so we continue to support it for
+ // older apps, so we retain this behavior if the app has target SDK < JellyBean.
+ sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);
} else {
mConnection.deleteSurroundingText(lengthToDelete, 0);
}
@@ -2553,6 +2560,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private void showSuggestionStripWithTypedWord(final SuggestedWords suggestedWords,
final String typedWord) {
if (suggestedWords.isEmpty()) {
+ // No auto-correction is available, clear the cached values.
+ AccessibilityUtils.getInstance().setAutoCorrection(null, null);
clearSuggestionStrip();
return;
}
@@ -2561,6 +2570,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
setSuggestedWords(suggestedWords, isAutoCorrection);
setAutoCorrectionIndicator(isAutoCorrection);
setSuggestionStripShown(isSuggestionsStripVisible());
+ // An auto-correction is available, cache it in accessibility code so
+ // we can be speak it if the user touches a key that will insert it.
+ AccessibilityUtils.getInstance().setAutoCorrection(suggestedWords, typedWord);
}
private void showSuggestionStrip(final SuggestedWords suggestedWords) {
@@ -2746,9 +2758,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final SettingsValues currentSettings = mSettings.getCurrent();
if (!currentSettings.mCorrectionEnabled) return null;
- final UserHistoryPredictionDictionary userHistoryPredictionDictionary =
- mUserHistoryPredictionDictionary;
- if (userHistoryPredictionDictionary == null) return null;
+ final UserHistoryDictionary userHistoryDictionary = mUserHistoryDictionary;
+ if (userHistoryDictionary == null) return null;
final String prevWord = mConnection.getNthPreviousWord(currentSettings.mWordSeparators, 2);
final String secondWord;
@@ -2762,8 +2773,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final int maxFreq = AutoCorrectionUtils.getMaxFrequency(
suggest.getUnigramDictionaries(), suggestion);
if (maxFreq == 0) return null;
- userHistoryPredictionDictionary
- .addToPersonalizationPredictionDictionary(prevWord, secondWord, maxFreq > 0);
+ userHistoryDictionary.addToDictionary(prevWord, secondWord, maxFreq > 0);
return prevWord;
}
@@ -2922,6 +2932,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return;
}
tryFixLyingCursorPosition();
+ mKeyboardSwitcher.updateShiftState();
if (tryResumeSuggestions) mHandler.postResumeSuggestions();
}
@@ -2949,7 +2960,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
mConnection.deleteSurroundingText(deleteLength, 0);
if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) {
- mUserHistoryPredictionDictionary.cancelAddingUserHistory(previousWord, committedWord);
+ mUserHistoryDictionary.cancelAddingUserHistory(previousWord, committedWord);
}
final String stringToCommit = originallyTypedWord + mLastComposedWord.mSeparatorString;
if (mSettings.getCurrent().mCurrentLanguageHasSpaces) {
diff --git a/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java
new file mode 100644
index 000000000..68505ce38
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * This class provides binary dictionary reading operations with locking. An instance of this class
+ * can be used by multiple threads. Note that different session IDs must be used when multiple
+ * threads get suggestions using this class.
+ */
+public final class ReadOnlyBinaryDictionary extends Dictionary {
+ /**
+ * A lock for accessing binary dictionary. Only closing binary dictionary is the operation
+ * that change the state of dictionary.
+ */
+ private final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
+
+ private final BinaryDictionary mBinaryDictionary;
+
+ public ReadOnlyBinaryDictionary(final String filename, final long offset, final long length,
+ final boolean useFullEditDistance, final Locale locale, final String dictType) {
+ super(dictType);
+ mBinaryDictionary = new BinaryDictionary(filename, offset, length, useFullEditDistance,
+ locale, dictType, false /* isUpdatable */);
+ }
+
+ public boolean isValidDictionary() {
+ return mBinaryDictionary.isValidDictionary();
+ }
+
+ @Override
+ public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
+ final String prevWord, final ProximityInfo proximityInfo,
+ final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
+ return getSuggestionsWithSessionId(composer, prevWord, proximityInfo, blockOffensiveWords,
+ additionalFeaturesOptions, 0 /* sessionId */);
+ }
+
+ @Override
+ public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
+ final String prevWord, final ProximityInfo proximityInfo,
+ final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
+ final int sessionId) {
+ if (mLock.readLock().tryLock()) {
+ try {
+ return mBinaryDictionary.getSuggestions(composer, prevWord, proximityInfo,
+ blockOffensiveWords, additionalFeaturesOptions);
+ } finally {
+ mLock.readLock().unlock();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean isValidWord(final String word) {
+ if (mLock.readLock().tryLock()) {
+ try {
+ return mBinaryDictionary.isValidWord(word);
+ } finally {
+ mLock.readLock().unlock();
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean shouldAutoCommit(final SuggestedWordInfo candidate) {
+ if (mLock.readLock().tryLock()) {
+ try {
+ return mBinaryDictionary.shouldAutoCommit(candidate);
+ } finally {
+ mLock.readLock().unlock();
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public int getFrequency(final String word) {
+ if (mLock.readLock().tryLock()) {
+ try {
+ return mBinaryDictionary.getFrequency(word);
+ } finally {
+ mLock.readLock().unlock();
+ }
+ }
+ return NOT_A_PROBABILITY;
+ }
+
+ @Override
+ public void close() {
+ mLock.writeLock().lock();
+ try {
+ mBinaryDictionary.close();
+ } finally {
+ mLock.writeLock().unlock();
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 6c18c948f..9fd1f53a2 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -26,7 +26,7 @@ import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.personalization.PersonalizationDictionary;
import com.android.inputmethod.latin.personalization.PersonalizationPredictionDictionary;
-import com.android.inputmethod.latin.personalization.UserHistoryPredictionDictionary;
+import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
import com.android.inputmethod.latin.utils.BoundedTreeSet;
@@ -190,10 +190,8 @@ public final class Suggest {
addOrReplaceDictionaryInternal(Dictionary.TYPE_CONTACTS, contactsDictionary);
}
- public void setUserHistoryPredictionDictionary(
- final UserHistoryPredictionDictionary userHistoryPredictionDictionary) {
- addOrReplaceDictionaryInternal(Dictionary.TYPE_USER_HISTORY,
- userHistoryPredictionDictionary);
+ public void setUserHistoryDictionary(final UserHistoryDictionary userHistoryDictionary) {
+ addOrReplaceDictionaryInternal(Dictionary.TYPE_USER_HISTORY, userHistoryDictionary);
}
public void setPersonalizationPredictionDictionary(
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
index 665c7a27c..2c3d1346f 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
@@ -295,7 +295,6 @@ public final class BinaryDictDecoderUtils {
return address;
}
}
- int address;
switch (optionFlags & FormatSpec.MASK_CHILDREN_ADDRESS_TYPE) {
case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE:
return dictBuffer.readUnsignedByte();
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
index 6cc0bfb76..b6024243f 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
@@ -278,7 +278,6 @@ public class BinaryDictEncoderUtils {
// For future reference, the code to remove duplicate is a simple : list.remove(node);
list.add(ptNodeArray);
final ArrayList<PtNode> branches = ptNodeArray.mData;
- final int nodeSize = branches.size();
for (PtNode ptNode : branches) {
if (null != ptNode.mChildren) flattenTreeInner(list, ptNode.mChildren);
}
@@ -385,12 +384,14 @@ public class BinaryDictEncoderUtils {
nodeSize + size, ptNode.mChildren));
}
nodeSize += getShortcutListSize(ptNode.mShortcutTargets);
- if (null != ptNode.mBigrams) {
- for (WeightedString bigram : ptNode.mBigrams) {
- final int offset = getOffsetToTargetPtNodeDuringUpdate(ptNodeArray,
- nodeSize + size + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE,
- FusionDictionary.findWordInTree(dict.mRootNodeArray, bigram.mWord));
- nodeSize += getByteSize(offset) + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE;
+ if (formatOptions.mVersion < FormatSpec.FIRST_VERSION_WITH_TERMINAL_ID) {
+ if (null != ptNode.mBigrams) {
+ for (WeightedString bigram : ptNode.mBigrams) {
+ final int offset = getOffsetToTargetPtNodeDuringUpdate(ptNodeArray,
+ nodeSize + size + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE,
+ FusionDictionary.findWordInTree(dict.mRootNodeArray, bigram.mWord));
+ nodeSize += getByteSize(offset) + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE;
+ }
}
}
ptNode.mCachedSize = nodeSize;
@@ -425,9 +426,6 @@ public class BinaryDictEncoderUtils {
nodeCountSize + nodeArrayOffset + nodeffset;
nodeffset += ptNode.mCachedSize;
}
- final int nodeSize = nodeCountSize + nodeffset
- + (formatOptions.mSupportsDynamicUpdate
- ? FormatSpec.FORWARD_LINK_ADDRESS_SIZE : 0);
nodeArrayOffset += nodeArray.mCachedSize;
}
return nodeArrayOffset;
@@ -651,8 +649,8 @@ public class BinaryDictEncoderUtils {
return flags;
}
- /* package */ static byte makePtNodeFlags(final PtNode node, final int ptNodeAddress,
- final int childrenOffset, final FormatOptions formatOptions) {
+ /* package */ static byte makePtNodeFlags(final PtNode node, final int childrenOffset,
+ final FormatOptions formatOptions) {
return (byte) makePtNodeFlags(node.mChars.length > 1, node.mFrequency >= 0,
getByteSize(childrenOffset),
node.mShortcutTargets != null && !node.mShortcutTargets.isEmpty(),
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
index a282f595c..0f7d2f6c9 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
@@ -288,40 +288,6 @@ public final class BinaryDictIOUtils {
return BinaryDictEncoderUtils.getByteSize(value);
}
- static void skipPtNode(final DictBuffer dictBuffer, final FormatOptions formatOptions) {
- final int flags = dictBuffer.readUnsignedByte();
- BinaryDictDecoderUtils.readParentAddress(dictBuffer, formatOptions);
- skipString(dictBuffer, (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS) != 0);
- BinaryDictDecoderUtils.readChildrenAddress(dictBuffer, flags, formatOptions);
- if ((flags & FormatSpec.FLAG_IS_TERMINAL) != 0) dictBuffer.readUnsignedByte();
- if ((flags & FormatSpec.FLAG_HAS_SHORTCUT_TARGETS) != 0) {
- final int shortcutsSize = dictBuffer.readUnsignedShort();
- dictBuffer.position(dictBuffer.position() + shortcutsSize
- - FormatSpec.PTNODE_SHORTCUT_LIST_SIZE_SIZE);
- }
- if ((flags & FormatSpec.FLAG_HAS_BIGRAMS) != 0) {
- int bigramCount = 0;
- while (bigramCount++ < FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
- final int bigramFlags = dictBuffer.readUnsignedByte();
- switch (bigramFlags & FormatSpec.MASK_BIGRAM_ATTR_ADDRESS_TYPE) {
- case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE:
- dictBuffer.readUnsignedByte();
- break;
- case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_TWOBYTES:
- dictBuffer.readUnsignedShort();
- break;
- case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_THREEBYTES:
- dictBuffer.readUnsignedInt24();
- break;
- }
- if ((bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT) == 0) break;
- }
- if (bigramCount >= FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
- throw new RuntimeException("Too many bigrams in a PtNode.");
- }
- }
- }
-
static void skipString(final DictBuffer dictBuffer,
final boolean hasMultipleChars) {
if (hasMultipleChars) {
diff --git a/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java
index 3796a466c..e251f7df7 100644
--- a/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java
@@ -391,4 +391,6 @@ public abstract class DictDecoder {
return readLength;
}
}
+
+ public abstract void skipPtNode(final FormatOptions formatOptions);
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/DictUpdater.java b/java/src/com/android/inputmethod/latin/makedict/DictUpdater.java
new file mode 100644
index 000000000..413d0301c
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/DictUpdater.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.makedict;
+
+import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * An interface of a binary dictionary updater.
+ */
+public interface DictUpdater {
+
+ /**
+ * Deletes the word from the binary dictionary.
+ *
+ * @param word the word to be deleted.
+ */
+ public void deleteWord(final String word) throws IOException, UnsupportedFormatException;
+
+ /**
+ * Inserts a word into a binary dictionary.
+ *
+ * @param word the word to be inserted.
+ * @param frequency the frequency of the new word.
+ * @param bigramStrings bigram list, or null if none.
+ * @param shortcuts shortcut list, or null if none.
+ * @param isBlackListEntry whether this should be a blacklist entry.
+ */
+ // TODO: Support batch insertion.
+ public void insertWord(final String word, final int frequency,
+ final ArrayList<WeightedString> bigramStrings,
+ final ArrayList<WeightedString> shortcuts, final boolean isNotAWord,
+ final boolean isBlackListEntry) throws IOException, UnsupportedFormatException;
+}
diff --git a/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java
index bf3d19101..336277196 100644
--- a/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java
@@ -42,44 +42,22 @@ public final class DynamicBinaryDictIOUtils {
// This utility class is not publicly instantiable.
}
- private static int markAsDeleted(final int flags) {
+ /* package */ static int markAsDeleted(final int flags) {
return (flags & (~FormatSpec.MASK_CHILDREN_ADDRESS_TYPE)) | FormatSpec.FLAG_IS_DELETED;
}
/**
- * Delete the word from the binary file.
- *
- * @param dictDecoder the dict decoder.
- * @param word the word we delete
- * @throws IOException
- * @throws UnsupportedFormatException
- */
- @UsedForTesting
- public static void deleteWord(final Ver3DictDecoder dictDecoder, final String word)
- throws IOException, UnsupportedFormatException {
- final DictBuffer dictBuffer = dictDecoder.getDictBuffer();
- dictBuffer.position(0);
- final FileHeader header = dictDecoder.readHeader();
- final int wordPosition = dictDecoder.getTerminalPosition(word);
- if (wordPosition == FormatSpec.NOT_VALID_WORD) return;
-
- dictBuffer.position(wordPosition);
- final int flags = dictBuffer.readUnsignedByte();
- dictBuffer.position(wordPosition);
- dictBuffer.put((byte)markAsDeleted(flags));
- }
-
- /**
* Update a parent address in a PtNode that is referred to by ptNodeOriginAddress.
*
- * @param dictBuffer the DictBuffer to write.
+ * @param dictUpdater the DictUpdater to write.
* @param ptNodeOriginAddress the address of the PtNode.
* @param newParentAddress the absolute address of the parent.
* @param formatOptions file format options.
*/
- public static void updateParentAddress(final DictBuffer dictBuffer,
+ private static void updateParentAddress(final Ver3DictUpdater dictUpdater,
final int ptNodeOriginAddress, final int newParentAddress,
final FormatOptions formatOptions) {
+ final DictBuffer dictBuffer = dictUpdater.getDictBuffer();
final int originalPosition = dictBuffer.position();
dictBuffer.position(ptNodeOriginAddress);
if (!formatOptions.mSupportsDynamicUpdate) {
@@ -104,46 +82,45 @@ public final class DynamicBinaryDictIOUtils {
/**
* Update parent addresses in a node array stored at ptNodeOriginAddress.
*
- * @param dictBuffer the DictBuffer to be modified.
+ * @param dictUpdater the DictUpdater to be modified.
* @param ptNodeOriginAddress the address of the node array to update.
* @param newParentAddress the address to be written.
* @param formatOptions file format options.
*/
- public static void updateParentAddresses(final DictBuffer dictBuffer,
+ private static void updateParentAddresses(final Ver3DictUpdater dictUpdater,
final int ptNodeOriginAddress, final int newParentAddress,
final FormatOptions formatOptions) {
- final int originalPosition = dictBuffer.position();
- dictBuffer.position(ptNodeOriginAddress);
+ final int originalPosition = dictUpdater.getPosition();
+ dictUpdater.setPosition(ptNodeOriginAddress);
do {
- final int count = BinaryDictDecoderUtils.readPtNodeCount(dictBuffer);
+ final int count = dictUpdater.readPtNodeCount();
for (int i = 0; i < count; ++i) {
- updateParentAddress(dictBuffer, dictBuffer.position(), newParentAddress,
+ updateParentAddress(dictUpdater, dictUpdater.getPosition(), newParentAddress,
formatOptions);
- BinaryDictIOUtils.skipPtNode(dictBuffer, formatOptions);
+ dictUpdater.skipPtNode(formatOptions);
}
- final int forwardLinkAddress = dictBuffer.readUnsignedInt24();
- dictBuffer.position(forwardLinkAddress);
- } while (formatOptions.mSupportsDynamicUpdate
- && dictBuffer.position() != FormatSpec.NO_FORWARD_LINK_ADDRESS);
- dictBuffer.position(originalPosition);
+ if (!dictUpdater.readAndFollowForwardLink()) break;
+ if (dictUpdater.getPosition() == FormatSpec.NO_FORWARD_LINK_ADDRESS) break;
+ } while (formatOptions.mSupportsDynamicUpdate);
+ dictUpdater.setPosition(originalPosition);
}
/**
* Update a children address in a PtNode that is addressed by ptNodeOriginAddress.
*
- * @param dictBuffer the DictBuffer to write.
+ * @param dictUpdater the DictUpdater to write.
* @param ptNodeOriginAddress the address of the PtNode.
* @param newChildrenAddress the absolute address of the child.
* @param formatOptions file format options.
*/
- public static void updateChildrenAddress(final DictBuffer dictBuffer,
+ private static void updateChildrenAddress(final Ver3DictUpdater dictUpdater,
final int ptNodeOriginAddress, final int newChildrenAddress,
final FormatOptions formatOptions) {
+ final DictBuffer dictBuffer = dictUpdater.getDictBuffer();
final int originalPosition = dictBuffer.position();
dictBuffer.position(ptNodeOriginAddress);
final int flags = dictBuffer.readUnsignedByte();
- final int parentAddress = BinaryDictDecoderUtils.readParentAddress(dictBuffer,
- formatOptions);
+ BinaryDictDecoderUtils.readParentAddress(dictBuffer, formatOptions);
BinaryDictIOUtils.skipString(dictBuffer, (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS) != 0);
if ((flags & FormatSpec.FLAG_IS_TERMINAL) != 0) dictBuffer.readUnsignedByte();
final int childrenOffset = newChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS
@@ -156,31 +133,33 @@ public final class DynamicBinaryDictIOUtils {
* Helper method to move a PtNode to the tail of the file.
*/
private static int movePtNode(final OutputStream destination,
- final DictBuffer dictBuffer, final PtNodeInfo info,
+ final Ver3DictUpdater dictUpdater, final PtNodeInfo info,
final int nodeArrayOriginAddress, final int oldNodeAddress,
final FormatOptions formatOptions) throws IOException {
- updateParentAddress(dictBuffer, oldNodeAddress, dictBuffer.limit() + 1, formatOptions);
+ final DictBuffer dictBuffer = dictUpdater.getDictBuffer();
+ updateParentAddress(dictUpdater, oldNodeAddress, dictBuffer.limit() + 1, formatOptions);
dictBuffer.position(oldNodeAddress);
final int currentFlags = dictBuffer.readUnsignedByte();
dictBuffer.position(oldNodeAddress);
dictBuffer.put((byte)(FormatSpec.FLAG_IS_MOVED | (currentFlags
& (~FormatSpec.MASK_MOVE_AND_DELETE_FLAG))));
int size = FormatSpec.PTNODE_FLAGS_SIZE;
- updateForwardLink(dictBuffer, nodeArrayOriginAddress, dictBuffer.limit(), formatOptions);
+ updateForwardLink(dictUpdater, nodeArrayOriginAddress, dictBuffer.limit(), formatOptions);
size += BinaryDictIOUtils.writeNodes(destination, new PtNodeInfo[] { info });
return size;
}
@SuppressWarnings("unused")
- private static void updateForwardLink(final DictBuffer dictBuffer,
+ private static void updateForwardLink(final Ver3DictUpdater dictUpdater,
final int nodeArrayOriginAddress, final int newNodeArrayAddress,
final FormatOptions formatOptions) {
- dictBuffer.position(nodeArrayOriginAddress);
+ final DictBuffer dictBuffer = dictUpdater.getDictBuffer();
+ dictUpdater.setPosition(nodeArrayOriginAddress);
int jumpCount = 0;
while (jumpCount++ < MAX_JUMPS) {
- final int count = BinaryDictDecoderUtils.readPtNodeCount(dictBuffer);
+ final int count = dictUpdater.readPtNodeCount();
for (int i = 0; i < count; ++i) {
- BinaryDictIOUtils.skipPtNode(dictBuffer, formatOptions);
+ dictUpdater.readPtNode(dictUpdater.getPosition(), formatOptions);
}
final int forwardLinkAddress = dictBuffer.readUnsignedInt24();
if (forwardLinkAddress == FormatSpec.NO_FORWARD_LINK_ADDRESS) {
@@ -208,7 +187,7 @@ public final class DynamicBinaryDictIOUtils {
* @param shortcutTargets the shortcut targets for this PtNode.
* @param bigrams the bigrams for this PtNode.
* @param destination the stream representing the tail of the file.
- * @param dictBuffer the DictBuffer representing the (constant-size) body of the file.
+ * @param dictUpdater the DictUpdater.
* @param oldPtNodeArrayOrigin the origin of the old PtNode array this PtNode was a part of.
* @param oldPtNodeOrigin the old origin where this PtNode used to be stored.
* @param formatOptions format options for this dictionary.
@@ -219,7 +198,7 @@ public final class DynamicBinaryDictIOUtils {
final int length, final int flags, final int frequency, final int parentAddress,
final ArrayList<WeightedString> shortcutTargets,
final ArrayList<PendingAttribute> bigrams, final OutputStream destination,
- final DictBuffer dictBuffer, final int oldPtNodeArrayOrigin,
+ final Ver3DictUpdater dictUpdater, final int oldPtNodeArrayOrigin,
final int oldPtNodeOrigin, final FormatOptions formatOptions) throws IOException {
int size = 0;
final int newPtNodeOrigin = fileEndAddress + 1;
@@ -232,7 +211,7 @@ public final class DynamicBinaryDictIOUtils {
flags, writtenCharacters, frequency, parentAddress,
fileEndAddress + 1 + size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE, shortcutTargets,
bigrams);
- movePtNode(destination, dictBuffer, newInfo, oldPtNodeArrayOrigin, oldPtNodeOrigin,
+ movePtNode(destination, dictUpdater, newInfo, oldPtNodeArrayOrigin, oldPtNodeOrigin,
formatOptions);
return 1 + size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
}
@@ -240,7 +219,7 @@ public final class DynamicBinaryDictIOUtils {
/**
* Insert a word into a binary dictionary.
*
- * @param dictDecoder the dict decoder.
+ * @param dictUpdater the dict updater.
* @param destination a stream to the underlying file, with the pointer at the end of the file.
* @param word the word to insert.
* @param frequency the frequency of the new word.
@@ -253,17 +232,17 @@ public final class DynamicBinaryDictIOUtils {
// TODO: Support batch insertion.
// TODO: Remove @UsedForTesting once UserHistoryDictionary is implemented by BinaryDictionary.
@UsedForTesting
- public static void insertWord(final Ver3DictDecoder dictDecoder,
+ public static void insertWord(final Ver3DictUpdater dictUpdater,
final OutputStream destination, final String word, final int frequency,
final ArrayList<WeightedString> bigramStrings,
final ArrayList<WeightedString> shortcuts, final boolean isNotAWord,
final boolean isBlackListEntry)
throws IOException, UnsupportedFormatException {
final ArrayList<PendingAttribute> bigrams = new ArrayList<PendingAttribute>();
- final DictBuffer dictBuffer = dictDecoder.getDictBuffer();
+ final DictBuffer dictBuffer = dictUpdater.getDictBuffer();
if (bigramStrings != null) {
for (final WeightedString bigram : bigramStrings) {
- int position = dictDecoder.getTerminalPosition(bigram.mWord);
+ int position = dictUpdater.getTerminalPosition(bigram.mWord);
if (position == FormatSpec.NOT_VALID_WORD) {
// TODO: figure out what is the correct thing to do here.
} else {
@@ -278,7 +257,7 @@ public final class DynamicBinaryDictIOUtils {
// find the insert position of the word.
if (dictBuffer.position() != 0) dictBuffer.position(0);
- final FileHeader fileHeader = dictDecoder.readHeader();
+ final FileHeader fileHeader = dictUpdater.readHeader();
int wordPos = 0, address = dictBuffer.position(), nodeOriginAddress = dictBuffer.position();
final int[] codePoints = FusionDictionary.getCodePoints(word);
@@ -293,7 +272,7 @@ public final class DynamicBinaryDictIOUtils {
for (int i = 0; i < ptNodeCount; ++i) {
address = dictBuffer.position();
- final PtNodeInfo currentInfo = dictDecoder.readPtNode(address,
+ final PtNodeInfo currentInfo = dictUpdater.readPtNode(address,
fileHeader.mFormatOptions);
final boolean isMovedNode = BinaryDictIOUtils.isMovedPtNode(currentInfo.mFlags,
fileHeader.mFormatOptions);
@@ -319,12 +298,12 @@ public final class DynamicBinaryDictIOUtils {
false /* isBlackListEntry */, fileHeader.mFormatOptions);
int written = movePtNode(newNodeAddress, currentInfo.mCharacters, p, flags,
frequency, nodeParentAddress, shortcuts, bigrams, destination,
- dictBuffer, nodeOriginAddress, address, fileHeader.mFormatOptions);
+ dictUpdater, nodeOriginAddress, address, fileHeader.mFormatOptions);
final int[] characters2 = Arrays.copyOfRange(currentInfo.mCharacters, p,
currentInfo.mCharacters.length);
if (currentInfo.mChildrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) {
- updateParentAddresses(dictBuffer, currentInfo.mChildrenAddress,
+ updateParentAddresses(dictUpdater, currentInfo.mChildrenAddress,
newNodeAddress + written + 1, fileHeader.mFormatOptions);
}
final PtNodeInfo newInfo2 = new PtNodeInfo(
@@ -360,13 +339,13 @@ public final class DynamicBinaryDictIOUtils {
fileHeader.mFormatOptions);
int written = movePtNode(newNodeAddress, currentInfo.mCharacters, p,
prefixFlags, -1 /* frequency */, nodeParentAddress, null, null,
- destination, dictBuffer, nodeOriginAddress, address,
+ destination, dictUpdater, nodeOriginAddress, address,
fileHeader.mFormatOptions);
final int[] suffixCharacters = Arrays.copyOfRange(
currentInfo.mCharacters, p, currentInfo.mCharacters.length);
if (currentInfo.mChildrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) {
- updateParentAddresses(dictBuffer, currentInfo.mChildrenAddress,
+ updateParentAddresses(dictUpdater, currentInfo.mChildrenAddress,
newNodeAddress + written + 1, fileHeader.mFormatOptions);
}
final int suffixFlags = BinaryDictEncoderUtils.makePtNodeFlags(
@@ -417,7 +396,7 @@ public final class DynamicBinaryDictIOUtils {
-1 /* endAddress */, flags, currentInfo.mCharacters, frequency,
nodeParentAddress, currentInfo.mChildrenAddress, shortcuts,
bigrams);
- movePtNode(destination, dictBuffer, newInfo, nodeOriginAddress, address,
+ movePtNode(destination, dictUpdater, newInfo, nodeOriginAddress, address,
fileHeader.mFormatOptions);
return;
}
@@ -436,7 +415,7 @@ public final class DynamicBinaryDictIOUtils {
* ab - cd - e
*/
final int newNodeArrayAddress = dictBuffer.limit();
- updateChildrenAddress(dictBuffer, address, newNodeArrayAddress,
+ updateChildrenAddress(dictUpdater, address, newNodeArrayAddress,
fileHeader.mFormatOptions);
final int newNodeAddress = newNodeArrayAddress + 1;
final boolean hasMultipleChars = (wordLen - wordPos) > 1;
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index 849bff050..a5516bd41 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -265,8 +265,15 @@ public final class FormatSpec {
static final String FREQ_FILE_EXTENSION = ".freq";
// tat = Terminal Address Table
static final String TERMINAL_ADDRESS_TABLE_FILE_EXTENSION = ".tat";
+ static final String BIGRAM_FILE_EXTENSION = ".bigram";
+ static final String LOOKUP_TABLE_FILE_SUFFIX = "_lookup";
+ static final String CONTENT_TABLE_FILE_SUFFIX = "_index";
static final int FREQUENCY_AND_FLAGS_SIZE = 2;
static final int TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE = 3;
+ static final int BIGRAM_ADDRESS_TABLE_BLOCK_SIZE = 4;
+ static final int BIGRAM_CONTENT_COUNT = 1;
+ static final int BIGRAM_FREQ_CONTENT_INDEX = 0;
+ static final String BIGRAM_FREQ_CONTENT_ID = "_freq";
static final int NO_CHILDREN_ADDRESS = Integer.MIN_VALUE;
static final int NO_PARENT_ADDRESS = 0;
@@ -331,9 +338,9 @@ public final class FormatSpec {
public static final String USES_FORGETTING_CURVE_ATTRIBUTE = "USES_FORGETTING_CURVE";
public static final String ATTRIBUTE_VALUE_TRUE = "1";
- private static final String DICTIONARY_VERSION_ATTRIBUTE = "version";
- private static final String DICTIONARY_LOCALE_ATTRIBUTE = "locale";
- private static final String DICTIONARY_ID_ATTRIBUTE = "dictionary";
+ public static final String DICTIONARY_VERSION_ATTRIBUTE = "version";
+ public static final String DICTIONARY_LOCALE_ATTRIBUTE = "locale";
+ public static final String DICTIONARY_ID_ATTRIBUTE = "dictionary";
private static final String DICTIONARY_DESCRIPTION_ATTRIBUTE = "description";
public FileHeader(final int headerSize, final DictionaryOptions dictionaryOptions,
final FormatOptions formatOptions) {
diff --git a/java/src/com/android/inputmethod/latin/makedict/SparseTable.java b/java/src/com/android/inputmethod/latin/makedict/SparseTable.java
index 0b9cf91d2..7592a0c13 100644
--- a/java/src/com/android/inputmethod/latin/makedict/SparseTable.java
+++ b/java/src/com/android/inputmethod/latin/makedict/SparseTable.java
@@ -17,7 +17,11 @@
package com.android.inputmethod.latin.makedict;
import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
@@ -34,35 +38,39 @@ public class SparseTable {
/**
* mLookupTable is indexed by terminal ID, containing exactly one entry for every mBlockSize
* terminals.
- * It contains at index i = j / mBlockSize the index in mContentsTable where the values for
- * terminals with IDs j to j + mBlockSize - 1 are stored as an mBlockSize-sized integer array.
+ * It contains at index i = j / mBlockSize the index in each ArrayList in mContentsTables where
+ * the values for terminals with IDs j to j + mBlockSize - 1 are stored as an mBlockSize-sized
+ * integer array.
*/
private final ArrayList<Integer> mLookupTable;
- private final ArrayList<Integer> mContentTable;
+ private final ArrayList<ArrayList<Integer>> mContentTables;
private final int mBlockSize;
+ private final int mContentTableCount;
public static final int NOT_EXIST = -1;
+ public static final int SIZE_OF_INT_IN_BYTES = 4;
@UsedForTesting
- public SparseTable(final int initialCapacity, final int blockSize) {
+ public SparseTable(final int initialCapacity, final int blockSize,
+ final int contentTableCount) {
mBlockSize = blockSize;
final int lookupTableSize = initialCapacity / mBlockSize
+ (initialCapacity % mBlockSize > 0 ? 1 : 0);
mLookupTable = new ArrayList<Integer>(Collections.nCopies(lookupTableSize, NOT_EXIST));
- mContentTable = new ArrayList<Integer>();
+ mContentTableCount = contentTableCount;
+ mContentTables = CollectionUtils.newArrayList();
+ for (int i = 0; i < mContentTableCount; ++i) {
+ mContentTables.add(new ArrayList<Integer>());
+ }
}
@UsedForTesting
- public SparseTable(final int[] lookupTable, final int[] contentTable, final int blockSize) {
+ public SparseTable(final ArrayList<Integer> lookupTable,
+ final ArrayList<ArrayList<Integer>> contentTables, final int blockSize) {
mBlockSize = blockSize;
- mLookupTable = new ArrayList<Integer>(lookupTable.length);
- for (int i = 0; i < lookupTable.length; ++i) {
- mLookupTable.add(lookupTable[i]);
- }
- mContentTable = new ArrayList<Integer>(contentTable.length);
- for (int i = 0; i < contentTable.length; ++i) {
- mContentTable.add(contentTable[i]);
- }
+ mContentTableCount = contentTables.size();
+ mLookupTable = lookupTable;
+ mContentTables = contentTables;
}
/**
@@ -72,8 +80,8 @@ public class SparseTable {
* Otherwise, IndexOutOfBoundsException will be raised.
*/
@UsedForTesting
- private static void convertByteArrayToIntegerArray(final byte[] byteArray,
- final ArrayList<Integer> integerArray) {
+ private static ArrayList<Integer> convertByteArrayToIntegerArray(final byte[] byteArray) {
+ final ArrayList<Integer> integerArray = new ArrayList<Integer>(byteArray.length / 4);
for (int i = 0; i < byteArray.length; i += 4) {
int value = 0;
for (int j = i; j < i + 4; ++j) {
@@ -82,39 +90,43 @@ public class SparseTable {
}
integerArray.add(value);
}
+ return integerArray;
}
@UsedForTesting
- public SparseTable(final byte[] lookupTable, final byte[] contentTable, final int blockSize) {
- mBlockSize = blockSize;
- mLookupTable = new ArrayList<Integer>(lookupTable.length / 4);
- mContentTable = new ArrayList<Integer>(contentTable.length / 4);
- convertByteArrayToIntegerArray(lookupTable, mLookupTable);
- convertByteArrayToIntegerArray(contentTable, mContentTable);
+ public int get(final int contentTableIndex, final int index) {
+ if (!contains(index)) {
+ return NOT_EXIST;
+ }
+ return mContentTables.get(contentTableIndex).get(
+ mLookupTable.get(index / mBlockSize) + (index % mBlockSize));
}
@UsedForTesting
- public int get(final int index) {
- if (index < 0 || index / mBlockSize >= mLookupTable.size()
- || mLookupTable.get(index / mBlockSize) == NOT_EXIST) {
- return NOT_EXIST;
+ public ArrayList<Integer> getAll(final int index) {
+ final ArrayList<Integer> ret = CollectionUtils.newArrayList();
+ for (int i = 0; i < mContentTableCount; ++i) {
+ ret.add(get(i, index));
}
- return mContentTable.get(mLookupTable.get(index / mBlockSize) + (index % mBlockSize));
+ return ret;
}
@UsedForTesting
- public void set(final int index, final int value) {
+ public void set(final int contentTableIndex, final int index, final int value) {
if (mLookupTable.get(index / mBlockSize) == NOT_EXIST) {
- mLookupTable.set(index / mBlockSize, mContentTable.size());
- for (int i = 0; i < mBlockSize; ++i) {
- mContentTable.add(NOT_EXIST);
+ mLookupTable.set(index / mBlockSize, mContentTables.get(contentTableIndex).size());
+ for (int i = 0; i < mContentTableCount; ++i) {
+ for (int j = 0; j < mBlockSize; ++j) {
+ mContentTables.get(i).add(NOT_EXIST);
+ }
}
}
- mContentTable.set(mLookupTable.get(index / mBlockSize) + (index % mBlockSize), value);
+ mContentTables.get(contentTableIndex).set(
+ mLookupTable.get(index / mBlockSize) + (index % mBlockSize), value);
}
- public void remove(final int index) {
- set(index, NOT_EXIST);
+ public void remove(final int indexOfContent, final int index) {
+ set(indexOfContent, index, NOT_EXIST);
}
@UsedForTesting
@@ -124,7 +136,8 @@ public class SparseTable {
@UsedForTesting
/* package */ int getContentTableSize() {
- return mContentTable.size();
+ // This class always has at least one content table.
+ return mContentTables.get(0).size();
}
@UsedForTesting
@@ -133,18 +146,78 @@ public class SparseTable {
}
public boolean contains(final int index) {
- return get(index) != NOT_EXIST;
+ if (index < 0 || index / mBlockSize >= mLookupTable.size()
+ || mLookupTable.get(index / mBlockSize) == NOT_EXIST) {
+ return false;
+ }
+ return true;
}
@UsedForTesting
- public void write(final OutputStream lookupOutStream, final OutputStream contentOutStream)
+ public void write(final OutputStream lookupOutStream, final OutputStream[] contentOutStreams)
throws IOException {
+ if (contentOutStreams.length != mContentTableCount) {
+ throw new RuntimeException(contentOutStreams.length + " streams are given, but the"
+ + " table has " + mContentTableCount + " content tables.");
+ }
for (final int index : mLookupTable) {
- BinaryDictEncoderUtils.writeUIntToStream(lookupOutStream, index, 4);
+ BinaryDictEncoderUtils.writeUIntToStream(lookupOutStream, index, SIZE_OF_INT_IN_BYTES);
}
- for (final int index : mContentTable) {
- BinaryDictEncoderUtils.writeUIntToStream(contentOutStream, index, 4);
+ for (int i = 0; i < contentOutStreams.length; ++i) {
+ for (final int data : mContentTables.get(i)) {
+ BinaryDictEncoderUtils.writeUIntToStream(contentOutStreams[i], data,
+ SIZE_OF_INT_IN_BYTES);
+ }
+ }
+ }
+
+ @UsedForTesting
+ public void writeToFiles(final File lookupTableFile, final File[] contentFiles)
+ throws IOException {
+ FileOutputStream lookupTableOutStream = null;
+ final FileOutputStream[] contentTableOutStreams = new FileOutputStream[mContentTableCount];
+ try {
+ lookupTableOutStream = new FileOutputStream(lookupTableFile);
+ for (int i = 0; i < contentFiles.length; ++i) {
+ contentTableOutStreams[i] = new FileOutputStream(contentFiles[i]);
+ }
+ write(lookupTableOutStream, contentTableOutStreams);
+ } finally {
+ if (lookupTableOutStream != null) {
+ lookupTableOutStream.close();
+ }
+ for (int i = 0; i < contentTableOutStreams.length; ++i) {
+ if (contentTableOutStreams[i] != null) {
+ contentTableOutStreams[i].close();
+ }
+ }
+ }
+ }
+
+ private static byte[] readFileToByteArray(final File file) throws IOException {
+ final byte[] contents = new byte[(int) file.length()];
+ FileInputStream inStream = null;
+ try {
+ inStream = new FileInputStream(file);
+ inStream.read(contents);
+ } finally {
+ if (inStream != null) {
+ inStream.close();
+ }
+ }
+ return contents;
+ }
+
+ @UsedForTesting
+ public static SparseTable readFromFiles(final File lookupTableFile, final File[] contentFiles,
+ final int blockSize) throws IOException {
+ final ArrayList<ArrayList<Integer>> contentTables =
+ new ArrayList<ArrayList<Integer>>(contentFiles.length);
+ for (int i = 0; i < contentFiles.length; ++i) {
+ contentTables.add(convertByteArrayToIntegerArray(readFileToByteArray(contentFiles[i])));
}
+ return new SparseTable(convertByteArrayToIntegerArray(readFileToByteArray(lookupTableFile)),
+ contentTables, blockSize);
}
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java
index 848277cd4..b87259c38 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java
@@ -53,9 +53,9 @@ public class Ver3DictDecoder extends DictDecoder {
}
}
- private final File mDictionaryBinaryFile;
+ protected final File mDictionaryBinaryFile;
private final DictionaryBufferFactory mBufferFactory;
- private DictBuffer mDictBuffer;
+ protected DictBuffer mDictBuffer;
/* package */ Ver3DictDecoder(final File file, final int factoryFlag) {
mDictionaryBinaryFile = file;
@@ -169,7 +169,8 @@ public class Ver3DictDecoder extends DictDecoder {
addressPointer += PtNodeReader.readBigramAddresses(mDictBuffer, bigrams,
addressPointer);
if (bigrams.size() >= FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
- MakedictLog.d("too many bigrams in a PtNode.");
+ throw new RuntimeException("Too many bigrams in a PtNode (" + bigrams.size()
+ + " but max is " + FormatSpec.MAX_BIGRAMS_IN_A_PTNODE + ")");
}
} else {
bigrams = null;
@@ -231,4 +232,40 @@ public class Ver3DictDecoder extends DictDecoder {
public boolean hasNextPtNodeArray() {
return mDictBuffer.position() != FormatSpec.NO_FORWARD_LINK_ADDRESS;
}
+
+ @Override
+ public void skipPtNode(final FormatOptions formatOptions) {
+ final int flags = PtNodeReader.readPtNodeOptionFlags(mDictBuffer);
+ PtNodeReader.readParentAddress(mDictBuffer, formatOptions);
+ BinaryDictIOUtils.skipString(mDictBuffer,
+ (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS) != 0);
+ PtNodeReader.readChildrenAddress(mDictBuffer, flags, formatOptions);
+ if ((flags & FormatSpec.FLAG_IS_TERMINAL) != 0) PtNodeReader.readFrequency(mDictBuffer);
+ if ((flags & FormatSpec.FLAG_HAS_SHORTCUT_TARGETS) != 0) {
+ final int shortcutsSize = mDictBuffer.readUnsignedShort();
+ mDictBuffer.position(mDictBuffer.position() + shortcutsSize
+ - FormatSpec.PTNODE_SHORTCUT_LIST_SIZE_SIZE);
+ }
+ if ((flags & FormatSpec.FLAG_HAS_BIGRAMS) != 0) {
+ int bigramCount = 0;
+ while (bigramCount++ < FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
+ final int bigramFlags = mDictBuffer.readUnsignedByte();
+ switch (bigramFlags & FormatSpec.MASK_BIGRAM_ATTR_ADDRESS_TYPE) {
+ case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE:
+ mDictBuffer.readUnsignedByte();
+ break;
+ case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_TWOBYTES:
+ mDictBuffer.readUnsignedShort();
+ break;
+ case FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_THREEBYTES:
+ mDictBuffer.readUnsignedInt24();
+ break;
+ }
+ if ((bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT) == 0) break;
+ }
+ if (bigramCount >= FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
+ throw new RuntimeException("Too many bigrams in a PtNode.");
+ }
+ }
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java
index 76f0f4052..d9e19899c 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java
@@ -133,12 +133,10 @@ public class Ver3DictEncoder implements DictEncoder {
countSize);
}
- private void writePtNodeFlags(final PtNode ptNode, final int parentAddress,
- final FormatOptions formatOptions) {
+ private void writePtNodeFlags(final PtNode ptNode, final FormatOptions formatOptions) {
final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode, formatOptions);
mPosition = BinaryDictEncoderUtils.writeUIntToBuffer(mBuffer, mPosition,
- BinaryDictEncoderUtils.makePtNodeFlags(ptNode, mPosition, childrenPos,
- formatOptions),
+ BinaryDictEncoderUtils.makePtNodeFlags(ptNode, childrenPos, formatOptions),
FormatSpec.PTNODE_FLAGS_SIZE);
}
@@ -244,7 +242,7 @@ public class Ver3DictEncoder implements DictEncoder {
@Override
public void writePtNode(final PtNode ptNode, final int parentPosition,
final FormatOptions formatOptions, final FusionDictionary dict) {
- writePtNodeFlags(ptNode, parentPosition, formatOptions);
+ writePtNodeFlags(ptNode, formatOptions);
writeParentPosition(parentPosition, ptNode, formatOptions);
writeCharacters(ptNode.mChars, ptNode.hasSeveralChars());
writeFrequency(ptNode.mFrequency);
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver3DictUpdater.java b/java/src/com/android/inputmethod/latin/makedict/Ver3DictUpdater.java
new file mode 100644
index 000000000..fa7ae310a
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver3DictUpdater.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.makedict;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+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;
+
+/**
+ * An implementation of DictUpdater for version 3 binary dictionary.
+ */
+@UsedForTesting
+public class Ver3DictUpdater extends Ver3DictDecoder implements DictUpdater {
+ private OutputStream mOutStream;
+
+ @UsedForTesting
+ public Ver3DictUpdater(final File dictFile, final int factoryType) {
+ // DictUpdater must have an updatable DictBuffer.
+ super(dictFile, ((factoryType & MASK_DICTBUFFER) == USE_BYTEARRAY)
+ ? USE_BYTEARRAY : USE_WRITABLE_BYTEBUFFER);
+ mOutStream = null;
+ }
+
+ private void openStreamAndBuffer() throws FileNotFoundException, IOException {
+ super.openDictBuffer();
+ mOutStream = new FileOutputStream(mDictionaryBinaryFile, true /* append */);
+ }
+
+ private void close() throws IOException {
+ if (mOutStream != null) {
+ mOutStream.close();
+ mOutStream = null;
+ }
+ }
+
+ @Override @UsedForTesting
+ public void deleteWord(final String word) throws IOException, UnsupportedFormatException {
+ if (mOutStream == null) openStreamAndBuffer();
+ mDictBuffer.position(0);
+ super.readHeader();
+ final int wordPos = getTerminalPosition(word);
+ if (wordPos != FormatSpec.NOT_VALID_WORD) {
+ mDictBuffer.position(wordPos);
+ final int flags = mDictBuffer.readUnsignedByte();
+ mDictBuffer.position(wordPos);
+ mDictBuffer.put((byte) DynamicBinaryDictIOUtils.markAsDeleted(flags));
+ }
+ close();
+ }
+
+ @Override @UsedForTesting
+ public void insertWord(final String word, final int frequency,
+ final ArrayList<WeightedString> bigramStrings,
+ final ArrayList<WeightedString> shortcuts,
+ final boolean isNotAWord, final boolean isBlackListEntry)
+ throws IOException, UnsupportedFormatException {
+ if (mOutStream == null) openStreamAndBuffer();
+ DynamicBinaryDictIOUtils.insertWord(this, mOutStream, word, frequency, bigramStrings,
+ shortcuts, isNotAWord, isBlackListEntry);
+ close();
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java
index 4c8ff8ea4..5089687da 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java
@@ -42,12 +42,15 @@ public class Ver4DictDecoder extends DictDecoder {
private static final int FILETYPE_TRIE = 1;
private static final int FILETYPE_FREQUENCY = 2;
private static final int FILETYPE_TERMINAL_ADDRESS_TABLE = 3;
+ private static final int FILETYPE_BIGRAM_FREQ = 4;
private final File mDictDirectory;
private final DictionaryBufferFactory mBufferFactory;
private DictBuffer mDictBuffer;
private DictBuffer mFrequencyBuffer;
private DictBuffer mTerminalAddressTableBuffer;
+ private DictBuffer mBigramBuffer;
+ private SparseTable mBigramAddressTable;
@UsedForTesting
/* package */ Ver4DictDecoder(final File dictDirectory, final int factoryFlag) {
@@ -82,6 +85,10 @@ public class Ver4DictDecoder extends DictDecoder {
} else if (fileType == FILETYPE_TERMINAL_ADDRESS_TABLE) {
return new File(mDictDirectory,
mDictDirectory.getName() + FormatSpec.TERMINAL_ADDRESS_TABLE_FILE_EXTENSION);
+ } else if (fileType == FILETYPE_BIGRAM_FREQ) {
+ return new File(mDictDirectory,
+ mDictDirectory.getName() + FormatSpec.BIGRAM_FILE_EXTENSION
+ + FormatSpec.BIGRAM_FREQ_CONTENT_ID);
} else {
throw new RuntimeException("Unsupported kind of file : " + fileType);
}
@@ -89,11 +96,12 @@ public class Ver4DictDecoder extends DictDecoder {
@Override
public void openDictBuffer() throws FileNotFoundException, IOException {
- final String filename = mDictDirectory.getName();
mDictBuffer = mBufferFactory.getDictionaryBuffer(getFile(FILETYPE_TRIE));
mFrequencyBuffer = mBufferFactory.getDictionaryBuffer(getFile(FILETYPE_FREQUENCY));
mTerminalAddressTableBuffer = mBufferFactory.getDictionaryBuffer(
getFile(FILETYPE_TERMINAL_ADDRESS_TABLE));
+ mBigramBuffer = mBufferFactory.getDictionaryBuffer(getFile(FILETYPE_BIGRAM_FREQ));
+ loadBigramAddressSparseTable();
}
@Override
@@ -118,6 +126,16 @@ public class Ver4DictDecoder extends DictDecoder {
return header;
}
+ private void loadBigramAddressSparseTable() throws IOException {
+ final File lookupIndexFile = new File(mDictDirectory, mDictDirectory.getName()
+ + FormatSpec.BIGRAM_FILE_EXTENSION + FormatSpec.LOOKUP_TABLE_FILE_SUFFIX);
+ final File freqsFile = new File(mDictDirectory, mDictDirectory.getName()
+ + FormatSpec.BIGRAM_FILE_EXTENSION + FormatSpec.CONTENT_TABLE_FILE_SUFFIX
+ + FormatSpec.BIGRAM_FREQ_CONTENT_ID);
+ mBigramAddressTable = SparseTable.readFromFiles(lookupIndexFile, new File[] { freqsFile },
+ FormatSpec.BIGRAM_ADDRESS_TABLE_BLOCK_SIZE);
+ }
+
protected static class PtNodeReader extends DictDecoder.PtNodeReader {
protected static int readFrequency(final DictBuffer frequencyBuffer, final int terminalId) {
frequencyBuffer.position(terminalId * FormatSpec.FREQUENCY_AND_FLAGS_SIZE + 1);
@@ -191,10 +209,24 @@ public class Ver4DictDecoder extends DictDecoder {
final ArrayList<PendingAttribute> bigrams;
if (0 != (flags & FormatSpec.FLAG_HAS_BIGRAMS)) {
bigrams = new ArrayList<PendingAttribute>();
- addressPointer += PtNodeReader.readBigramAddresses(mDictBuffer, bigrams,
- addressPointer);
+ final int posOfBigrams = mBigramAddressTable.get(0 /* contentTableIndex */, terminalId);
+ mBigramBuffer.position(posOfBigrams);
+ while (bigrams.size() < FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
+ // If bigrams.size() reaches FormatSpec.MAX_BIGRAMS_IN_A_PTNODE,
+ // remaining bigram entries are ignored.
+ final int bigramFlags = mBigramBuffer.readUnsignedByte();
+ final int targetTerminalId = mBigramBuffer.readUnsignedInt24();
+ mTerminalAddressTableBuffer.position(
+ targetTerminalId * FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE);
+ final int targetAddress = mTerminalAddressTableBuffer.readUnsignedInt24();
+ bigrams.add(new PendingAttribute(
+ bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY,
+ targetAddress));
+ if (0 == (bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) break;
+ }
if (bigrams.size() >= FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
- MakedictLog.d("too many bigrams in a node.");
+ throw new RuntimeException("Too many bigrams in a PtNode (" + bigrams.size()
+ + " but max is " + FormatSpec.MAX_BIGRAMS_IN_A_PTNODE + ")");
}
} else {
bigrams = null;
@@ -263,4 +295,14 @@ public class Ver4DictDecoder extends DictDecoder {
public boolean hasNextPtNodeArray() {
return mDictBuffer.position() != FormatSpec.NO_FORWARD_LINK_ADDRESS;
}
+
+ @Override
+ public void skipPtNode(final FormatOptions formatOptions) {
+ final int flags = PtNodeReader.readPtNodeOptionFlags(mDictBuffer);
+ PtNodeReader.readParentAddress(mDictBuffer, formatOptions);
+ BinaryDictIOUtils.skipString(mDictBuffer,
+ (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS) != 0);
+ if ((flags & FormatSpec.FLAG_IS_TERMINAL) != 0) PtNodeReader.readTerminalId(mDictBuffer);
+ PtNodeReader.readChildrenAddress(mDictBuffer, flags, formatOptions);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
index 4fb89671f..b38c33019 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
@@ -46,21 +46,121 @@ public class Ver4DictEncoder implements DictEncoder {
private OutputStream mTrieOutStream;
private OutputStream mFreqOutStream;
private OutputStream mTerminalAddressTableOutStream;
+ private File mDictDir;
+ private String mBaseFilename;
+ private BigramContentWriter mBigramWriter;
@UsedForTesting
public Ver4DictEncoder(final File dictPlacedDir) {
mDictPlacedDir = dictPlacedDir;
}
+ private interface SparseTableContentWriterInterface {
+ public void write(final OutputStream outStream) throws IOException;
+ }
+
+ private static class SparseTableContentWriter {
+ private final int mContentCount;
+ private final SparseTable mSparseTable;
+ private final File mLookupTableFile;
+ protected final File mBaseDir;
+ private final File[] mAddressTableFiles;
+ private final File[] mContentFiles;
+ protected final OutputStream[] mContentOutStreams;
+
+ public SparseTableContentWriter(final String name, final int contentCount,
+ final int initialCapacity, final int blockSize, final File baseDir,
+ final String[] contentFilenames, final String[] contentIds) {
+ if (contentFilenames.length != contentIds.length) {
+ throw new RuntimeException("The length of contentFilenames and the length of"
+ + " contentIds are different " + contentFilenames.length + ", "
+ + contentIds.length);
+ }
+ mContentCount = contentCount;
+ mSparseTable = new SparseTable(initialCapacity, blockSize, contentCount);
+ mLookupTableFile = new File(baseDir, name + FormatSpec.LOOKUP_TABLE_FILE_SUFFIX);
+ mAddressTableFiles = new File[mContentCount];
+ mContentFiles = new File[mContentCount];
+ mBaseDir = baseDir;
+ for (int i = 0; i < mContentCount; ++i) {
+ mAddressTableFiles[i] = new File(mBaseDir,
+ name + FormatSpec.CONTENT_TABLE_FILE_SUFFIX + contentIds[i]);
+ mContentFiles[i] = new File(mBaseDir, contentFilenames[i] + contentIds[i]);
+ }
+ mContentOutStreams = new OutputStream[mContentCount];
+ }
+
+ public void openStreams() throws FileNotFoundException {
+ for (int i = 0; i < mContentCount; ++i) {
+ mContentOutStreams[i] = new FileOutputStream(mContentFiles[i]);
+ }
+ }
+
+ protected void write(final int contentIndex, final int index,
+ final SparseTableContentWriterInterface writer) throws IOException {
+ mSparseTable.set(contentIndex, index, (int) mContentFiles[contentIndex].length());
+ writer.write(mContentOutStreams[contentIndex]);
+ mContentOutStreams[contentIndex].flush();
+ }
+
+ public void closeStreams() throws IOException {
+ mSparseTable.writeToFiles(mLookupTableFile, mAddressTableFiles);
+ for (int i = 0; i < mContentCount; ++i) {
+ mContentOutStreams[i].close();
+ }
+ }
+ }
+
+ private static class BigramContentWriter extends SparseTableContentWriter {
+
+ public BigramContentWriter(final String name, final int initialCapacity,
+ final File baseDir) {
+ super(name + FormatSpec.BIGRAM_FILE_EXTENSION, FormatSpec.BIGRAM_CONTENT_COUNT,
+ initialCapacity, FormatSpec.BIGRAM_ADDRESS_TABLE_BLOCK_SIZE, baseDir,
+ new String[] { name + FormatSpec.BIGRAM_FILE_EXTENSION },
+ new String[] { FormatSpec.BIGRAM_FREQ_CONTENT_ID });
+ }
+
+ public void writeBigramsForOneWord(final int terminalId,
+ final Iterator<WeightedString> bigramIterator, final FusionDictionary dict)
+ throws IOException {
+ write(FormatSpec.BIGRAM_FREQ_CONTENT_INDEX, terminalId,
+ new SparseTableContentWriterInterface() {
+ @Override
+ public void write(final OutputStream outStream) throws IOException {
+ writeBigramsForOneWordInternal(outStream, bigramIterator, dict);
+ }
+ });
+ }
+
+ private void writeBigramsForOneWordInternal(final OutputStream outStream,
+ final Iterator<WeightedString> bigramIterator, final FusionDictionary dict)
+ throws IOException {
+ while (bigramIterator.hasNext()) {
+ final WeightedString bigram = bigramIterator.next();
+ final PtNode target =
+ FusionDictionary.findWordInTree(dict.mRootNodeArray, bigram.mWord);
+ final int unigramFrequencyForThisWord = target.mFrequency;
+ final int bigramFlags = BinaryDictEncoderUtils.makeBigramFlags(
+ bigramIterator.hasNext(), 0, bigram.mFrequency,
+ unigramFrequencyForThisWord, bigram.mWord);
+ BinaryDictEncoderUtils.writeUIntToStream(outStream, bigramFlags,
+ FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE);
+ BinaryDictEncoderUtils.writeUIntToStream(outStream, target.mTerminalId,
+ FormatSpec.PTNODE_ATTRIBUTE_MAX_ADDRESS_SIZE);
+ }
+ }
+ }
+
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);
+ mBaseFilename = header.getId() + "." + header.getVersion();
+ mDictDir = new File(mDictPlacedDir, mBaseFilename);
+ final File trieFile = new File(mDictDir, mBaseFilename + FormatSpec.TRIE_FILE_EXTENSION);
+ final File freqFile = new File(mDictDir, mBaseFilename + FormatSpec.FREQ_FILE_EXTENSION);
final File terminalAddressTableFile = new File(mDictDir,
- filename + FormatSpec.TERMINAL_ADDRESS_TABLE_FILE_EXTENSION);
+ mBaseFilename + FormatSpec.TERMINAL_ADDRESS_TABLE_FILE_EXTENSION);
if (!mDictDir.isDirectory()) {
if (mDictDir.exists()) mDictDir.delete();
mDictDir.mkdirs();
@@ -123,6 +223,8 @@ public class Ver4DictEncoder implements DictEncoder {
if (MakedictLog.DBG) BinaryDictEncoderUtils.checkFlatPtNodeArrayList(flatNodes);
writeTerminalData(flatNodes, terminalCount);
+ mBigramWriter = new BigramContentWriter(mBaseFilename, terminalCount, mDictDir);
+ writeBigrams(flatNodes, dict);
final PtNodeArray lastNodeArray = flatNodes.get(flatNodes.size() - 1);
final int bufferSize = lastNodeArray.mCachedAddressAfterUpdate + lastNodeArray.mCachedSize;
@@ -165,12 +267,10 @@ public class Ver4DictEncoder implements DictEncoder {
countSize);
}
- private void writePtNodeFlags(final PtNode ptNode, final int parentAddress,
- final FormatOptions formatOptions) {
+ private void writePtNodeFlags(final PtNode ptNode, final FormatOptions formatOptions) {
final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode, formatOptions);
mTriePos = BinaryDictEncoderUtils.writeUIntToBuffer(mTrieBuf, mTriePos,
- BinaryDictEncoderUtils.makePtNodeFlags(ptNode, mTriePos, childrenPos,
- formatOptions),
+ BinaryDictEncoderUtils.makePtNodeFlags(ptNode, childrenPos, formatOptions),
FormatSpec.PTNODE_FLAGS_SIZE);
}
@@ -215,8 +315,7 @@ public class Ver4DictEncoder implements DictEncoder {
while (shortcutIterator.hasNext()) {
final WeightedString target = shortcutIterator.next();
final int shortcutFlags = BinaryDictEncoderUtils.makeShortcutFlags(
- shortcutIterator.hasNext(),
- target.mFrequency);
+ shortcutIterator.hasNext(), target.mFrequency);
mTrieBuf[mTriePos++] = (byte)shortcutFlags;
final int shortcutShift = CharEncoding.writeString(mTrieBuf, mTriePos,
target.mWord);
@@ -230,24 +329,18 @@ public class Ver4DictEncoder implements DictEncoder {
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));
+ private void writeBigrams(final ArrayList<PtNodeArray> flatNodes, final FusionDictionary dict)
+ throws IOException {
+ mBigramWriter.openStreams();
+ for (final PtNodeArray nodeArray : flatNodes) {
+ for (final PtNode ptNode : nodeArray.mData) {
+ if (ptNode.mBigrams != null) {
+ mBigramWriter.writeBigramsForOneWord(ptNode.mTerminalId,
+ ptNode.mBigrams.iterator(), dict);
+ }
+ }
}
+ mBigramWriter.closeStreams();
}
@Override
@@ -259,7 +352,7 @@ public class Ver4DictEncoder implements DictEncoder {
@Override
public void writePtNode(final PtNode ptNode, final int parentPosition,
final FormatOptions formatOptions, final FusionDictionary dict) {
- writePtNodeFlags(ptNode, parentPosition, formatOptions);
+ writePtNodeFlags(ptNode, formatOptions);
writeParentPosition(parentPosition, ptNode, formatOptions);
writeCharacters(ptNode.mChars, ptNode.hasSeveralChars());
if (ptNode.isTerminal()) {
@@ -267,7 +360,6 @@ public class Ver4DictEncoder implements DictEncoder {
}
writeChildrenPosition(ptNode, formatOptions);
writeShortcuts(ptNode.mShortcutTargets);
- writeBigrams(ptNode.mBigrams, dict);
}
private void writeTerminalData(final ArrayList<PtNodeArray> flatNodes,
diff --git a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
index 66517a800..c8b62b6c8 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
@@ -22,6 +22,7 @@ import android.util.Log;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.ExpandableBinaryDictionary;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.makedict.DictDecoder;
@@ -50,6 +51,9 @@ public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableB
/** Any pair being typed or picked */
public static final int FREQUENCY_FOR_TYPED = 2;
+ public static final int FREQUENCY_FOR_WORDS_IN_DICTS = FREQUENCY_FOR_TYPED;
+ public static final int FREQUENCY_FOR_WORDS_NOT_IN_DICTS = Dictionary.NOT_A_PROBABILITY;
+
/** Locale for which this user history dictionary is storing words */
private final String mLocale;
@@ -94,6 +98,8 @@ public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableB
FormatSpec.FileHeader.ATTRIBUTE_VALUE_TRUE);
attributeMap.put(FormatSpec.FileHeader.USES_FORGETTING_CURVE_ATTRIBUTE,
FormatSpec.FileHeader.ATTRIBUTE_VALUE_TRUE);
+ attributeMap.put(FormatSpec.FileHeader.DICTIONARY_ID_ATTRIBUTE, mFileName);
+ attributeMap.put(FormatSpec.FileHeader.DICTIONARY_LOCALE_ATTRIBUTE, mLocale);
return attributeMap;
}
@@ -117,27 +123,29 @@ public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableB
}
/**
- * Pair will be added to the personalization prediction dictionary.
+ * Pair will be added to the decaying dictionary.
*
* The first word may be null. That means we don't know the context, in other words,
* it's only a unigram. The first word may also be an empty string : this means start
* context, as in beginning of a sentence for example.
* The second word may not be null (a NullPointerException would be thrown).
*/
- public void addToPersonalizationPredictionDictionary(
- final String word0, final String word1, final boolean isValid) {
+ public void addToDictionary(final String word0, final String word1, final boolean isValid) {
if (word1.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH ||
(word0 != null && word0.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH)) {
return;
}
- addWordDynamically(word1, null /* the "shortcut" parameter is null */, FREQUENCY_FOR_TYPED,
+ final int frequency = ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE ?
+ (isValid ? FREQUENCY_FOR_WORDS_IN_DICTS : FREQUENCY_FOR_WORDS_NOT_IN_DICTS) :
+ FREQUENCY_FOR_TYPED;
+ addWordDynamically(word1, null /* the "shortcut" parameter is null */, frequency,
false /* isNotAWord */);
// Do not insert a word as a bigram of itself
if (word1.equals(word0)) {
return;
}
if (null != word0) {
- addBigramDynamically(word0, word1, FREQUENCY_FOR_TYPED, isValid);
+ addBigramDynamically(word0, word1, frequency, isValid);
}
}
@@ -222,10 +230,15 @@ public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableB
mSessions.remove(session);
}
+ @UsedForTesting
public void clearAndFlushDictionary() {
// Clear the node structure on memory
clear();
// Then flush the cleared state of the dictionary on disk.
asyncFlashAllBinaryDictionary();
}
+
+ /* package */ void decayIfNeeded() {
+ runGCIfRequired(false /* mindsBlockByGC */);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/DictionaryDecayBroadcastReciever.java b/java/src/com/android/inputmethod/latin/personalization/DictionaryDecayBroadcastReciever.java
new file mode 100644
index 000000000..e9ca662e7
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/personalization/DictionaryDecayBroadcastReciever.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.personalization;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Broadcast receiver for periodically updating decaying dictionaries.
+ */
+public class DictionaryDecayBroadcastReciever extends BroadcastReceiver {
+ /**
+ * The root domain for the personalization.
+ */
+ private static final String PERSONALIZATION_DOMAIN =
+ "com.android.inputmethod.latin.personalization";
+
+ /**
+ * The action of the intent to tell the time to decay dictionaries.
+ */
+ private static final String DICTIONARY_DECAY_INTENT_ACTION =
+ PERSONALIZATION_DOMAIN + ".DICT_DECAY";
+
+ /**
+ * Interval to update for decaying dictionaries.
+ */
+ private static final long DICTIONARY_DECAY_INTERVAL = TimeUnit.MINUTES.toMillis(60);
+
+ public static void setUpIntervalAlarmForDictionaryDecaying(Context context) {
+ AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
+ final Intent updateIntent = new Intent(DICTIONARY_DECAY_INTENT_ACTION);
+ updateIntent.setClass(context, DictionaryDecayBroadcastReciever.class);
+ final long alarmTime = System.currentTimeMillis() + DICTIONARY_DECAY_INTERVAL;
+ final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0 /* requestCode */,
+ updateIntent, PendingIntent.FLAG_CANCEL_CURRENT);
+ if (null != alarmManager) alarmManager.setInexactRepeating(AlarmManager.RTC,
+ alarmTime, DICTIONARY_DECAY_INTERVAL, pendingIntent);
+ }
+
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ final String action = intent.getAction();
+ if (action.equals(DICTIONARY_DECAY_INTENT_ACTION)) {
+ PersonalizationHelper.tryDecayingAllOpeningUserHistoryDictionary();
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java b/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java
index 305088536..039b25337 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java
@@ -36,6 +36,7 @@ import com.android.inputmethod.latin.utils.UserHistoryForgettingCurveUtils.Forge
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Map;
// Currently this class is used to implement dynamic prodiction dictionary.
// TODO: Move to native code.
@@ -113,8 +114,8 @@ public class DynamicPersonalizationDictionaryWriter extends AbstractDictionaryWr
}
@Override
- protected void writeDictionary(final DictEncoder dictEncoder)
- throws IOException, UnsupportedFormatException {
+ protected void writeDictionary(final DictEncoder dictEncoder,
+ final Map<String, String> attributeMap) throws IOException, UnsupportedFormatException {
UserHistoryDictIOUtils.writeDictionary(dictEncoder,
new FrequencyProvider(mBigramList, mExpandableDictionary, mMaxHistoryBigrams),
mBigramList, FORMAT_OPTIONS);
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java
index c616a296c..a86f6e584 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java
@@ -110,7 +110,7 @@ public abstract class PersonalizationDictionaryUpdateSession {
if (dictionary == null) {
return;
}
- dictionary.addToPersonalizationPredictionDictionary(word0, word1, isValid);
+ dictionary.addToDictionary(word0, word1, isValid);
}
// Bulk import
@@ -122,8 +122,7 @@ public abstract class PersonalizationDictionaryUpdateSession {
return;
}
for (final PersonalizationLanguageModelParam lmParam : lmParams) {
- dictionary.addToPersonalizationPredictionDictionary(
- lmParam.mWord0, lmParam.mWord1, lmParam.mIsValid);
+ dictionary.addToDictionary(lmParam.mWord0, lmParam.mWord1, lmParam.mIsValid);
}
}
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
index 5f702ee3f..221ddeeba 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
@@ -29,8 +29,7 @@ import java.util.concurrent.ConcurrentHashMap;
public class PersonalizationHelper {
private static final String TAG = PersonalizationHelper.class.getSimpleName();
private static final boolean DEBUG = false;
-
- private static final ConcurrentHashMap<String, SoftReference<UserHistoryPredictionDictionary>>
+ private static final ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>
sLangUserHistoryDictCache = CollectionUtils.newConcurrentHashMap();
private static final ConcurrentHashMap<String, SoftReference<PersonalizationDictionary>>
@@ -41,29 +40,39 @@ public class PersonalizationHelper {
sLangPersonalizationPredictionDictCache =
CollectionUtils.newConcurrentHashMap();
- public static UserHistoryPredictionDictionary getUserHistoryPredictionDictionary(
+ public static UserHistoryDictionary getUserHistoryDictionary(
final Context context, final String locale, final SharedPreferences sp) {
synchronized (sLangUserHistoryDictCache) {
if (sLangUserHistoryDictCache.containsKey(locale)) {
- final SoftReference<UserHistoryPredictionDictionary> ref =
+ final SoftReference<UserHistoryDictionary> ref =
sLangUserHistoryDictCache.get(locale);
- final UserHistoryPredictionDictionary dict = ref == null ? null : ref.get();
+ final UserHistoryDictionary dict = ref == null ? null : ref.get();
if (dict != null) {
if (DEBUG) {
- Log.w(TAG, "Use cached UserHistoryPredictionDictionary for " + locale);
+ Log.w(TAG, "Use cached UserHistoryDictionary for " + locale);
}
dict.reloadDictionaryIfRequired();
return dict;
}
}
- final UserHistoryPredictionDictionary dict =
- new UserHistoryPredictionDictionary(context, locale, sp);
- sLangUserHistoryDictCache.put(
- locale, new SoftReference<UserHistoryPredictionDictionary>(dict));
+ final UserHistoryDictionary dict = new UserHistoryDictionary(context, locale, sp);
+ sLangUserHistoryDictCache.put(locale, new SoftReference<UserHistoryDictionary>(dict));
return dict;
}
}
+ public static void tryDecayingAllOpeningUserHistoryDictionary() {
+ for (final ConcurrentHashMap.Entry<String, SoftReference<UserHistoryDictionary>> entry
+ : sLangUserHistoryDictCache.entrySet()) {
+ if (entry.getValue() != null) {
+ final UserHistoryDictionary dict = entry.getValue().get();
+ if (dict != null) {
+ dict.decayIfNeeded();
+ }
+ }
+ }
+ }
+
public static void registerPersonalizationDictionaryUpdateSession(final Context context,
final PersonalizationDictionaryUpdateSession session, String locale) {
final PersonalizationPredictionDictionary predictionDictionary =
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryPredictionDictionary.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
index 38e308a4e..a60226d7e 100644
--- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryPredictionDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
@@ -26,10 +26,10 @@ import android.content.SharedPreferences;
* Locally gathers stats about the words user types and various other signals like auto-correction
* cancellation or manual picks. This allows the keyboard to adapt to the typist over time.
*/
-public class UserHistoryPredictionDictionary extends DecayingExpandableBinaryDictionaryBase {
+public class UserHistoryDictionary extends DecayingExpandableBinaryDictionaryBase {
/* package for tests */ static final String NAME =
- UserHistoryPredictionDictionary.class.getSimpleName();
- /* package */ UserHistoryPredictionDictionary(final Context context, final String locale,
+ UserHistoryDictionary.class.getSimpleName();
+ /* package */ UserHistoryDictionary(final Context context, final String locale,
final SharedPreferences sp) {
super(context, locale, sp, Dictionary.TYPE_USER_HISTORY, getDictionaryFileName(locale));
}
diff --git a/java/src/com/android/inputmethod/latin/settings/DebugSettingsActivity.java b/java/src/com/android/inputmethod/latin/settings/DebugSettingsActivity.java
index b499c26b6..ef6ab2a38 100644
--- a/java/src/com/android/inputmethod/latin/settings/DebugSettingsActivity.java
+++ b/java/src/com/android/inputmethod/latin/settings/DebugSettingsActivity.java
@@ -38,4 +38,10 @@ public final class DebugSettingsActivity extends PreferenceActivity {
super.onCreate(savedInstanceState);
setTitle(R.string.english_ime_debug_settings);
}
+
+ // TODO: Uncomment the override annotation once we start using SDK version 19.
+ // @Override
+ public boolean isValidFragment(String fragmentName) {
+ return fragmentName.equals(DEFAULT_FRAGMENT);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsActivity.java b/java/src/com/android/inputmethod/latin/settings/SettingsActivity.java
index 6c3818651..ad68f8c37 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsActivity.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsActivity.java
@@ -32,4 +32,10 @@ public final class SettingsActivity extends PreferenceActivity {
intent.putExtra(EXTRA_NO_HEADERS, true);
return intent;
}
+
+ // TODO: Uncomment the override annotation once we start using SDK version 19.
+ // @Override
+ public boolean isValidFragment(String fragmentName) {
+ return fragmentName.equals(DEFAULT_FRAGMENT);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java
index 119ca4755..aba563746 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java
@@ -24,6 +24,8 @@ import android.preference.PreferenceActivity;
* Spell checker preference screen.
*/
public final class SpellCheckerSettingsActivity extends PreferenceActivity {
+ private static final String DEFAULT_FRAGMENT = SpellCheckerSettingsFragment.class.getName();
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -32,8 +34,14 @@ public final class SpellCheckerSettingsActivity extends PreferenceActivity {
@Override
public Intent getIntent() {
final Intent modIntent = new Intent(super.getIntent());
- modIntent.putExtra(EXTRA_SHOW_FRAGMENT, SpellCheckerSettingsFragment.class.getName());
+ modIntent.putExtra(EXTRA_SHOW_FRAGMENT, DEFAULT_FRAGMENT);
modIntent.putExtra(EXTRA_NO_HEADERS, true);
return modIntent;
}
+
+ // TODO: Uncomment the override annotation once we start using SDK version 19.
+ // @Override
+ public boolean isValidFragment(String fragmentName) {
+ return fragmentName.equals(DEFAULT_FRAGMENT);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
index 44b201642..ff332cdee 100644
--- a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
@@ -61,10 +61,8 @@ public final class AdditionalSubtypeUtils {
StringUtils.appendToCommaSplittableTextIfNotExists(
IS_ADDITIONAL_SUBTYPE, layoutDisplayNameExtraValue);
final int nameId = SubtypeLocaleUtils.getSubtypeNameId(localeString, keyboardLayoutSetName);
- return new InputMethodSubtype(nameId, R.drawable.ic_ime_switcher_dark,
- localeString, KEYBOARD_MODE, layoutExtraValue + "," + additionalSubtypeExtraValue
- + "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE
- + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE, false, false);
+ return buildInputMethodSubtype(
+ nameId, localeString, layoutExtraValue, additionalSubtypeExtraValue);
}
public static String getPrefSubtype(final InputMethodSubtype subtype) {
@@ -137,4 +135,27 @@ public final class AdditionalSubtypeUtils {
}
return sb.toString();
}
+
+ private static InputMethodSubtype buildInputMethodSubtype(int nameId, String localeString,
+ String layoutExtraValue, String additionalSubtypeExtraValue) {
+ // CAVEAT! If you want to change subtypeId after changing the extra values,
+ // you must change "getInputMethodSubtypeId". But it will remove the additional keyboard
+ // from the current users. So, you should be really careful to change it.
+ final int subtypeId = getInputMethodSubtypeId(nameId, localeString, layoutExtraValue,
+ additionalSubtypeExtraValue);
+ // TODO: Use InputMethodSubtypeBuilder once we use SDK version 19.
+ return new InputMethodSubtype(nameId, R.drawable.ic_ime_switcher_dark,
+ localeString, KEYBOARD_MODE, layoutExtraValue + "," + additionalSubtypeExtraValue
+ + "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE
+ + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE, false, false,
+ subtypeId);
+ }
+
+ private static int getInputMethodSubtypeId(int nameId, String localeString,
+ String layoutExtraValue, String additionalSubtypeExtraValue) {
+ // TODO: Use InputMethodSubtypeBuilder once we use SDK version 19.
+ return (new InputMethodSubtype(nameId, R.drawable.ic_ime_switcher_dark,
+ localeString, KEYBOARD_MODE, layoutExtraValue + "," + additionalSubtypeExtraValue,
+ false, false)).hashCode();
+ }
}