aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java2
-rw-r--r--java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardId.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java4
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java8
-rw-r--r--java/src/com/android/inputmethod/keyboard/MainKeyboardView.java15
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java153
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java110
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java427
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableDictionary.java42
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java51
-rw-r--r--java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java4
-rw-r--r--java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java4
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java238
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/DictEncoder.java31
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/FormatSpec.java29
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java173
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java7
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java8
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java2
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java4
-rw-r--r--java/src/com/android/inputmethod/latin/settings/Settings.java4
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsFragment.java20
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsValues.java34
-rw-r--r--java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java71
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DebugLogUtils.java6
-rw-r--r--java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java126
-rw-r--r--java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java7
28 files changed, 989 insertions, 595 deletions
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
index b3bb767af..73896dfd3 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
@@ -357,6 +357,7 @@ public final class AccessibleKeyboardViewProxy extends AccessibilityDelegateComp
break;
case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
+ case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
text = context.getText(R.string.spoken_description_shiftmode_on);
break;
default:
@@ -388,6 +389,7 @@ public final class AccessibleKeyboardViewProxy extends AccessibilityDelegateComp
resId = R.string.spoken_description_mode_alpha;
break;
case KeyboardId.ELEMENT_SYMBOLS:
+ case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
resId = R.string.spoken_description_mode_symbol;
break;
case KeyboardId.ELEMENT_PHONE:
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
index 085ca93d5..58624a2e6 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -156,6 +156,7 @@ public final class KeyCodeDescriptionMapper {
resId = R.string.spoken_description_to_symbol;
break;
case KeyboardId.ELEMENT_SYMBOLS:
+ case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
resId = R.string.spoken_description_to_alpha;
break;
case KeyboardId.ELEMENT_PHONE:
@@ -190,6 +191,7 @@ public final class KeyCodeDescriptionMapper {
break;
case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
+ case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
resId = R.string.spoken_description_shift_shifted;
break;
default:
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index 53748bb58..736f13ed6 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -50,6 +50,7 @@ public final class KeyboardId {
public static final int ELEMENT_ALPHABET_SHIFT_LOCKED = 3;
public static final int ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED = 4;
public static final int ELEMENT_SYMBOLS = 5;
+ public static final int ELEMENT_SYMBOLS_SHIFTED = 6;
public static final int ELEMENT_PHONE = 7;
public static final int ELEMENT_PHONE_SYMBOLS = 8;
public static final int ELEMENT_NUMBER = 9;
@@ -219,6 +220,7 @@ public final class KeyboardId {
case ELEMENT_ALPHABET_SHIFT_LOCKED: return "alphabetShiftLocked";
case ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED: return "alphabetShiftLockShifted";
case ELEMENT_SYMBOLS: return "symbols";
+ case ELEMENT_SYMBOLS_SHIFTED: return "symbolsShifted";
case ELEMENT_PHONE: return "phone";
case ELEMENT_PHONE_SYMBOLS: return "phoneSymbols";
case ELEMENT_NUMBER: return "number";
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
index 711de63b3..1eccdf341 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
@@ -106,6 +106,8 @@ public final class KeyboardLayoutSet {
EditorInfo mEditorInfo;
boolean mDisableTouchPositionCorrectionDataForTest;
boolean mVoiceKeyEnabled;
+ // TODO: Remove mVoiceKeyOnMain when it's certainly confirmed that we no longer show
+ // the voice input key on the symbol layout
boolean mVoiceKeyOnMain;
boolean mNoSettingsKey;
boolean mLanguageSwitchKeyEnabled;
@@ -259,6 +261,8 @@ public final class KeyboardLayoutSet {
return this;
}
+ // TODO: Remove mVoiceKeyOnMain when it's certainly confirmed that we no longer show
+ // the voice input key on the symbol layout
public Builder setOptions(final boolean voiceKeyEnabled, final boolean voiceKeyOnMain,
final boolean languageSwitchKeyEnabled) {
@SuppressWarnings("deprecation")
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index c319c57a1..d128d3595 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -142,7 +142,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
builder.setSubtype(mSubtypeSwitcher.getCurrentSubtype());
builder.setOptions(
settingsValues.isVoiceKeyEnabled(editorInfo),
- settingsValues.isVoiceKeyOnMain(),
+ true /* always show a voice key on the main keyboard */,
settingsValues.isLanguageSwitchKeyEnabled());
mKeyboardLayoutSet = builder.build();
try {
@@ -271,6 +271,12 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
// Implements {@link KeyboardState.SwitchActions}.
@Override
+ public void setSymbolsShiftedKeyboard() {
+ setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS_SHIFTED));
+ }
+
+ // Implements {@link KeyboardState.SwitchActions}.
+ @Override
public void requestUpdatingShiftState() {
mState.onUpdateShiftState(mLatinIME.getCurrentAutoCapsState(),
mLatinIME.getCurrentRecapitalizeState());
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index e4a8f4cb2..f8ad43e74 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -183,6 +183,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
private final NonDistinctMultitouchHelper mNonDistinctMultitouchHelper;
private final KeyTimerHandler mKeyTimerHandler;
+ private final int mLanguageOnSpacebarHorizontalMargin;
private static final class KeyTimerHandler extends StaticInnerHandlerWrapper<MainKeyboardView>
implements TimerProxy {
@@ -512,6 +513,9 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
altCodeKeyWhileTypingFadeinAnimatorResId, this);
mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER;
+
+ mLanguageOnSpacebarHorizontalMargin =
+ (int) getResources().getDimension(R.dimen.language_on_spacebar_horizontal_margin);
}
@Override
@@ -1188,26 +1192,27 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
}
}
- private static boolean fitsTextIntoWidth(final int width, final String text,
- final Paint paint) {
+ private boolean fitsTextIntoWidth(final int width, final String text, final Paint paint) {
+ final int maxTextWidth = width - mLanguageOnSpacebarHorizontalMargin * 2;
paint.setTextScaleX(1.0f);
final float textWidth = TypefaceUtils.getLabelWidth(text, paint);
if (textWidth < width) {
return true;
}
- final float scaleX = width / textWidth;
+ final float scaleX = maxTextWidth / textWidth;
if (scaleX < MINIMUM_XSCALE_OF_LANGUAGE_NAME) {
return false;
}
paint.setTextScaleX(scaleX);
- return TypefaceUtils.getLabelWidth(text, paint) < width;
+ return TypefaceUtils.getLabelWidth(text, paint) < maxTextWidth;
}
// Layout language name on spacebar.
- private static String layoutLanguageOnSpacebar(final Paint paint,
+ private String layoutLanguageOnSpacebar(final Paint paint,
final InputMethodSubtype subtype, final int width) {
+
// Choose appropriate language name to fit into the width.
final String fullText = SubtypeLocaleUtils.getFullDisplayName(subtype);
if (fitsTextIntoWidth(width, fullText, paint)) {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
index 089db12a2..b3491d807 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
@@ -45,8 +45,9 @@ public final class KeyboardState {
public void setAlphabetAutomaticShiftedKeyboard();
public void setAlphabetShiftLockedKeyboard();
public void setAlphabetShiftLockShiftedKeyboard();
- public void setSymbolsKeyboard();
public void setEmojiKeyboard();
+ public void setSymbolsKeyboard();
+ public void setSymbolsShiftedKeyboard();
/**
* Request to call back {@link KeyboardState#onUpdateShiftState(int, int)}.
@@ -64,11 +65,13 @@ public final class KeyboardState {
private ModifierKeyState mSymbolKeyState = new ModifierKeyState("Symbol");
// TODO: Merge {@link #mSwitchState}, {@link #mIsAlphabetMode}, {@link #mAlphabetShiftState},
- // {@link #mPrevMainKeyboardWasShiftLocked} into single state variable.
+ // {@link #mIsSymbolShifted}, {@link #mPrevMainKeyboardWasShiftLocked}, and
+ // {@link #mPrevSymbolsKeyboardWasShifted} into single state variable.
private static final int SWITCH_STATE_ALPHA = 0;
private static final int SWITCH_STATE_SYMBOL_BEGIN = 1;
private static final int SWITCH_STATE_SYMBOL = 2;
private static final int SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL = 3;
+ private static final int SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE = 4;
private static final int SWITCH_STATE_MOMENTARY_ALPHA_SHIFT = 5;
private int mSwitchState = SWITCH_STATE_ALPHA;
@@ -77,7 +80,9 @@ public final class KeyboardState {
private boolean mIsAlphabetMode;
private boolean mIsEmojiMode;
private AlphabetShiftState mAlphabetShiftState = new AlphabetShiftState();
+ private boolean mIsSymbolShifted;
private boolean mPrevMainKeyboardWasShiftLocked;
+ private boolean mPrevSymbolsKeyboardWasShifted;
private int mRecapitalizeMode;
// For handling double tap.
@@ -102,7 +107,7 @@ public final class KeyboardState {
} else if (mIsEmojiMode) {
return "EMOJI";
} else {
- return "SYMBOLS";
+ return "SYMBOLS_" + shiftModeToString(mShiftMode);
}
}
}
@@ -119,6 +124,7 @@ public final class KeyboardState {
// Reset alphabet shift state.
mAlphabetShiftState.setShiftLocked(false);
mPrevMainKeyboardWasShiftLocked = false;
+ mPrevSymbolsKeyboardWasShifted = false;
mShiftKeyState.onRelease();
mSymbolKeyState.onRelease();
onRestoreKeyboardState();
@@ -139,6 +145,7 @@ public final class KeyboardState {
: (mAlphabetShiftState.isShiftedOrShiftLocked() ? MANUAL_SHIFT : UNSHIFT);
} else {
state.mIsAlphabetShiftLocked = mPrevMainKeyboardWasShiftLocked;
+ state.mShiftMode = mIsSymbolShifted ? MANUAL_SHIFT : UNSHIFT;
}
state.mIsValid = true;
if (DEBUG_EVENT) {
@@ -156,7 +163,11 @@ public final class KeyboardState {
} else if (state.mIsEmojiMode) {
setEmojiKeyboard();
} else {
- setSymbolsKeyboard();
+ if (state.mShiftMode == MANUAL_SHIFT) {
+ setSymbolsShiftedKeyboard();
+ } else {
+ setSymbolsKeyboard();
+ }
}
if (!state.mIsValid) return;
@@ -232,8 +243,14 @@ public final class KeyboardState {
}
if (mIsAlphabetMode) {
mPrevMainKeyboardWasShiftLocked = mAlphabetShiftState.isShiftLocked();
- setSymbolsKeyboard();
+ if (mPrevSymbolsKeyboardWasShifted) {
+ setSymbolsShiftedKeyboard();
+ } else {
+ setSymbolsKeyboard();
+ }
+ mPrevSymbolsKeyboardWasShifted = false;
} else {
+ mPrevSymbolsKeyboardWasShifted = mIsSymbolShifted;
setAlphabetKeyboard();
if (mPrevMainKeyboardWasShiftLocked) {
setShiftLocked(true);
@@ -250,6 +267,7 @@ public final class KeyboardState {
}
if (mIsAlphabetMode) return;
+ mPrevSymbolsKeyboardWasShifted = mIsSymbolShifted;
setAlphabetKeyboard();
if (mPrevMainKeyboardWasShiftLocked) {
setShiftLocked(true);
@@ -257,6 +275,14 @@ public final class KeyboardState {
mPrevMainKeyboardWasShiftLocked = false;
}
+ private void toggleShiftInSymbols() {
+ if (mIsSymbolShifted) {
+ setSymbolsKeyboard();
+ } else {
+ setSymbolsShiftedKeyboard();
+ }
+ }
+
private void setAlphabetKeyboard() {
if (DEBUG_ACTION) {
Log.d(TAG, "setAlphabetKeyboard");
@@ -265,6 +291,7 @@ public final class KeyboardState {
mSwitchActions.setAlphabetKeyboard();
mIsAlphabetMode = true;
mIsEmojiMode = false;
+ mIsSymbolShifted = false;
mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE;
mSwitchState = SWITCH_STATE_ALPHA;
mSwitchActions.requestUpdatingShiftState();
@@ -276,6 +303,19 @@ public final class KeyboardState {
}
mSwitchActions.setSymbolsKeyboard();
mIsAlphabetMode = false;
+ mIsSymbolShifted = false;
+ // Reset alphabet shift state.
+ mAlphabetShiftState.setShiftLocked(false);
+ mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
+ }
+
+ private void setSymbolsShiftedKeyboard() {
+ if (DEBUG_ACTION) {
+ Log.d(TAG, "setSymbolsShiftedKeyboard");
+ }
+ mSwitchActions.setSymbolsShiftedKeyboard();
+ mIsAlphabetMode = false;
+ mIsSymbolShifted = true;
// Reset alphabet shift state.
mAlphabetShiftState.setShiftLocked(false);
mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
@@ -337,7 +377,7 @@ public final class KeyboardState {
} else if (code == Constants.CODE_CAPSLOCK) {
setShiftLocked(!mAlphabetShiftState.isShiftLocked());
} else if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) {
- onReleaseSymbol();
+ onReleaseSymbol(withSliding);
}
}
@@ -347,11 +387,16 @@ public final class KeyboardState {
mSwitchState = SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL;
}
- private void onReleaseSymbol() {
+ private void onReleaseSymbol(final boolean withSliding) {
if (mSymbolKeyState.isChording()) {
// Switch back to the previous keyboard mode if the user chords the mode change key and
// another key, then releases the mode change key.
toggleAlphabetAndSymbols();
+ } else if (!withSliding) {
+ // If the mode change key is being released without sliding, we should forget the
+ // previous symbols keyboard shift state and simply switch back to symbols layout
+ // (never symbols shifted) next time the mode gets changed to symbols layout.
+ mPrevSymbolsKeyboardWasShifted = false;
}
mSymbolKeyState.onRelease();
}
@@ -417,43 +462,48 @@ public final class KeyboardState {
if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != mRecapitalizeMode) {
return;
}
- if (!mIsAlphabetMode) {
- // There is no shift key on symbols keyboard.
- return;
- }
- mIsInDoubleTapShiftKey = mSwitchActions.isInDoubleTapShiftKeyTimeout();
- if (!mIsInDoubleTapShiftKey) {
- // This is first tap.
- mSwitchActions.startDoubleTapShiftKeyTimer();
- }
- if (mIsInDoubleTapShiftKey) {
- if (mAlphabetShiftState.isManualShifted() || mIsInAlphabetUnshiftedFromShifted) {
- // Shift key has been double tapped while in manual shifted or automatic
- // shifted state.
- setShiftLocked(true);
- } else {
- // Shift key has been double tapped while in normal state. This is the second
- // tap to disable shift locked state, so just ignore this.
+ if (mIsAlphabetMode) {
+ mIsInDoubleTapShiftKey = mSwitchActions.isInDoubleTapShiftKeyTimeout();
+ if (!mIsInDoubleTapShiftKey) {
+ // This is first tap.
+ mSwitchActions.startDoubleTapShiftKeyTimer();
}
- } else {
- if (mAlphabetShiftState.isShiftLocked()) {
- // Shift key is pressed while shift locked state, we will treat this state as
- // shift lock shifted state and mark as if shift key pressed while normal state.
- setShifted(SHIFT_LOCK_SHIFTED);
- mShiftKeyState.onPress();
- } else if (mAlphabetShiftState.isAutomaticShifted()) {
- // Shift key is pressed while automatic shifted, we have to move to manual shifted.
- setShifted(MANUAL_SHIFT);
- mShiftKeyState.onPress();
- } else if (mAlphabetShiftState.isShiftedOrShiftLocked()) {
- // In manual shifted state, we just record shift key has been pressing while
- // shifted state.
- mShiftKeyState.onPressOnShifted();
+ if (mIsInDoubleTapShiftKey) {
+ if (mAlphabetShiftState.isManualShifted() || mIsInAlphabetUnshiftedFromShifted) {
+ // Shift key has been double tapped while in manual shifted or automatic
+ // shifted state.
+ setShiftLocked(true);
+ } else {
+ // Shift key has been double tapped while in normal state. This is the second
+ // tap to disable shift locked state, so just ignore this.
+ }
} else {
- // In base layout, chording or manual shifted mode is started.
- setShifted(MANUAL_SHIFT);
- mShiftKeyState.onPress();
+ if (mAlphabetShiftState.isShiftLocked()) {
+ // Shift key is pressed while shift locked state, we will treat this state as
+ // shift lock shifted state and mark as if shift key pressed while normal
+ // state.
+ setShifted(SHIFT_LOCK_SHIFTED);
+ mShiftKeyState.onPress();
+ } else if (mAlphabetShiftState.isAutomaticShifted()) {
+ // Shift key is pressed while automatic shifted, we have to move to manual
+ // shifted.
+ setShifted(MANUAL_SHIFT);
+ mShiftKeyState.onPress();
+ } else if (mAlphabetShiftState.isShiftedOrShiftLocked()) {
+ // In manual shifted state, we just record shift key has been pressing while
+ // shifted state.
+ mShiftKeyState.onPressOnShifted();
+ } else {
+ // In base layout, chording or manual shifted mode is started.
+ setShifted(MANUAL_SHIFT);
+ mShiftKeyState.onPress();
+ }
}
+ } else {
+ // In symbol mode, just toggle symbol and symbol more keyboard.
+ toggleShiftInSymbols();
+ mSwitchState = SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE;
+ mShiftKeyState.onPress();
}
}
@@ -508,7 +558,11 @@ public final class KeyboardState {
mIsInAlphabetUnshiftedFromShifted = true;
}
} else {
- // There is no shift key on symbols keyboard.
+ // In symbol mode, switch back to the previous keyboard mode if the user chords the
+ // shift key and another key, then releases the shift key.
+ if (mShiftKeyState.isChording()) {
+ toggleShiftInSymbols();
+ }
}
mShiftKeyState.onRelease();
}
@@ -522,6 +576,9 @@ public final class KeyboardState {
case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL:
toggleAlphabetAndSymbols();
break;
+ case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE:
+ toggleShiftInSymbols();
+ break;
case SWITCH_STATE_MOMENTARY_ALPHA_SHIFT:
setAlphabetKeyboard();
break;
@@ -549,6 +606,13 @@ public final class KeyboardState {
}
}
break;
+ case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE:
+ if (code == Constants.CODE_SHIFT) {
+ // Detected only the shift key has been pressed on symbol layout, and then
+ // released.
+ mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
+ }
+ break;
case SWITCH_STATE_SYMBOL_BEGIN:
if (!isSpaceCharacter(code) && (Constants.isLetterCode(code)
|| code == Constants.CODE_OUTPUT_TEXT)) {
@@ -560,6 +624,7 @@ public final class KeyboardState {
// characters followed by a space/enter.
if (isSpaceCharacter(code)) {
toggleAlphabetAndSymbols();
+ mPrevSymbolsKeyboardWasShifted = false;
}
break;
}
@@ -587,6 +652,7 @@ public final class KeyboardState {
case SWITCH_STATE_SYMBOL_BEGIN: return "SYMBOL-BEGIN";
case SWITCH_STATE_SYMBOL: return "SYMBOL";
case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL: return "MOMENTARY-ALPHA-SYMBOL";
+ case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE: return "MOMENTARY-SYMBOL-MORE";
case SWITCH_STATE_MOMENTARY_ALPHA_SHIFT: return "MOMENTARY-ALPHA_SHIFT";
default: return null;
}
@@ -594,7 +660,8 @@ public final class KeyboardState {
@Override
public String toString() {
- return "[keyboard=" + (mIsAlphabetMode ? mAlphabetShiftState.toString() : "SYMBOLS")
+ return "[keyboard=" + (mIsAlphabetMode ? mAlphabetShiftState.toString()
+ : (mIsSymbolShifted ? "SYMBOLS_SHIFTED" : "SYMBOLS"))
+ " shift=" + mShiftKeyState
+ " symbol=" + mSymbolKeyState
+ " switch=" + switchStateToString(mSwitchState) + "]";
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
index 4d3bdb0ca..7008b0619 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
@@ -226,29 +226,31 @@ public final class KeyboardTextsSet {
/* 121 */ "shortcut_as_more_key",
/* 122 */ "action_next_as_more_key",
/* 123 */ "action_previous_as_more_key",
- /* 124 */ "label_tab_key",
- /* 125 */ "label_to_phone_numeric_key",
- /* 126 */ "label_to_phone_symbols_key",
- /* 127 */ "label_time_am",
- /* 128 */ "label_time_pm",
- /* 129 */ "keylabel_for_popular_domain",
- /* 130 */ "more_keys_for_popular_domain",
- /* 131 */ "more_keys_for_smiley",
- /* 132 */ "single_laqm_raqm",
- /* 133 */ "single_laqm_raqm_rtl",
- /* 134 */ "single_raqm_laqm",
- /* 135 */ "double_laqm_raqm",
- /* 136 */ "double_laqm_raqm_rtl",
- /* 137 */ "double_raqm_laqm",
- /* 138 */ "single_lqm_rqm",
- /* 139 */ "single_9qm_lqm",
- /* 140 */ "single_9qm_rqm",
- /* 141 */ "double_lqm_rqm",
- /* 142 */ "double_9qm_lqm",
- /* 143 */ "double_9qm_rqm",
- /* 144 */ "more_keys_for_single_quote",
- /* 145 */ "more_keys_for_double_quote",
- /* 146 */ "more_keys_for_tablet_double_quote",
+ /* 124 */ "label_to_more_symbol_key",
+ /* 125 */ "label_to_more_symbol_for_tablet_key",
+ /* 126 */ "label_tab_key",
+ /* 127 */ "label_to_phone_numeric_key",
+ /* 128 */ "label_to_phone_symbols_key",
+ /* 129 */ "label_time_am",
+ /* 130 */ "label_time_pm",
+ /* 131 */ "keylabel_for_popular_domain",
+ /* 132 */ "more_keys_for_popular_domain",
+ /* 133 */ "more_keys_for_smiley",
+ /* 134 */ "single_laqm_raqm",
+ /* 135 */ "single_laqm_raqm_rtl",
+ /* 136 */ "single_raqm_laqm",
+ /* 137 */ "double_laqm_raqm",
+ /* 138 */ "double_laqm_raqm_rtl",
+ /* 139 */ "double_raqm_laqm",
+ /* 140 */ "single_lqm_rqm",
+ /* 141 */ "single_9qm_lqm",
+ /* 142 */ "single_9qm_rqm",
+ /* 143 */ "double_lqm_rqm",
+ /* 144 */ "double_9qm_lqm",
+ /* 145 */ "double_9qm_rqm",
+ /* 146 */ "more_keys_for_single_quote",
+ /* 147 */ "more_keys_for_double_quote",
+ /* 148 */ "more_keys_for_tablet_double_quote",
};
private static final String EMPTY = "";
@@ -286,7 +288,8 @@ public final class KeyboardTextsSet {
// U+2666: "♦" BLACK DIAMOND SUIT
// U+2663: "♣" BLACK CLUB SUIT
/* 55 */ "\u266A,\u2665,\u2660,\u2666,\u2663",
- /* 56 */ EMPTY,
+ // U+00B1: "±" PLUS-MINUS SIGN
+ /* 56 */ "\u00B1",
// The all letters need to be mirrored are found at
// http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
/* 57 */ "!fixedColumnOrder!3,<,{,[",
@@ -358,8 +361,10 @@ public final class KeyboardTextsSet {
/* 103 */ "\u2030",
/* 104 */ ",",
/* 105~ */
- EMPTY, EMPTY, EMPTY, EMPTY,
- /* ~108 */
+ EMPTY, EMPTY, EMPTY,
+ /* ~107 */
+ // U+2026: "…" HORIZONTAL ELLIPSIS
+ /* 108 */ "\u2026",
/* 109 */ "\'",
/* 110 */ "\"",
/* 111 */ "\"",
@@ -375,22 +380,26 @@ public final class KeyboardTextsSet {
/* 121 */ "!icon/shortcut_key|!code/key_shortcut",
/* 122 */ "!hasLabels!,!text/label_next_key|!code/key_action_next",
/* 123 */ "!hasLabels!,!text/label_previous_key|!code/key_action_previous",
+ // Label for "switch to more symbol" modifier key. Must be short to fit on key!
+ /* 124 */ "= \\ <",
+ // Label for "switch to more symbol" modifier key on tablets. Must be short to fit on key!
+ /* 125 */ "~ [ {",
// Label for "Tab" key. Must be short to fit on key!
- /* 124 */ "Tab",
+ /* 126 */ "Tab",
// Label for "switch to phone numeric" key. Must be short to fit on key!
- /* 125 */ "123",
+ /* 127 */ "123",
// Label for "switch to phone symbols" key. Must be short to fit on key!
// U+FF0A: "*" FULLWIDTH ASTERISK
// U+FF03: "#" FULLWIDTH NUMBER SIGN
- /* 126 */ "\uFF0A\uFF03",
+ /* 128 */ "\uFF0A\uFF03",
// Key label for "ante meridiem"
- /* 127 */ "AM",
+ /* 129 */ "AM",
// Key label for "post meridiem"
- /* 128 */ "PM",
- /* 129 */ ".com",
+ /* 130 */ "PM",
+ /* 131 */ ".com",
// popular web domains for the locale - most popular, displayed on the keyboard
- /* 130 */ "!hasLabels!,.net,.org,.gov,.edu",
- /* 131 */ "!fixedColumnOrder!5,!hasLabels!,=-O|=-O ,:-P|:-P ,;-)|;-) ,:-(|:-( ,:-)|:-) ,:-!|:-! ,:-$|:-$ ,B-)|B-) ,:O|:O ,:-*|:-* ,:-D|:-D ,:\'(|:\'( ,:-\\\\|:-\\\\ ,O:-)|O:-) ,:-[|:-[ ",
+ /* 132 */ "!hasLabels!,.net,.org,.gov,.edu",
+ /* 133 */ "!fixedColumnOrder!5,!hasLabels!,=-O|=-O ,:-P|:-P ,;-)|;-) ,:-(|:-( ,:-)|:-) ,:-!|:-! ,:-$|:-$ ,B-)|B-) ,:O|:O ,:-*|:-* ,:-D|:-D ,:\'(|:\'( ,:-\\\\|:-\\\\ ,O:-)|O:-) ,:-[|:-[ ",
// U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
// U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
// U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
@@ -412,24 +421,24 @@ public final class KeyboardTextsSet {
// The following each quotation mark pair consist of
// <opening quotation mark>, <closing quotation mark>
// and is named after (single|double)_<opening quotation mark>_<closing quotation mark>.
- /* 132 */ "\u2039,\u203A",
- /* 133 */ "\u2039|\u203A,\u203A|\u2039",
- /* 134 */ "\u203A,\u2039",
- /* 135 */ "\u00AB,\u00BB",
- /* 136 */ "\u00AB|\u00BB,\u00BB|\u00AB",
- /* 137 */ "\u00BB,\u00AB",
+ /* 134 */ "\u2039,\u203A",
+ /* 135 */ "\u2039|\u203A,\u203A|\u2039",
+ /* 136 */ "\u203A,\u2039",
+ /* 137 */ "\u00AB,\u00BB",
+ /* 138 */ "\u00AB|\u00BB,\u00BB|\u00AB",
+ /* 139 */ "\u00BB,\u00AB",
// The following each quotation mark triplet consists of
// <another quotation mark>, <opening quotation mark>, <closing quotation mark>
// and is named after (single|double)_<opening quotation mark>_<closing quotation mark>.
- /* 138 */ "\u201A,\u2018,\u2019",
- /* 139 */ "\u2019,\u201A,\u2018",
- /* 140 */ "\u2018,\u201A,\u2019",
- /* 141 */ "\u201E,\u201C,\u201D",
- /* 142 */ "\u201D,\u201E,\u201C",
- /* 143 */ "\u201C,\u201E,\u201D",
- /* 144 */ "!fixedColumnOrder!5,!text/single_quotes,!text/single_angle_quotes",
- /* 145 */ "!fixedColumnOrder!5,!text/double_quotes,!text/double_angle_quotes",
- /* 146 */ "!fixedColumnOrder!6,!text/double_quotes,!text/single_quotes,!text/double_angle_quotes,!text/single_angle_quotes",
+ /* 140 */ "\u201A,\u2018,\u2019",
+ /* 141 */ "\u2019,\u201A,\u2018",
+ /* 142 */ "\u2018,\u201A,\u2019",
+ /* 143 */ "\u201E,\u201C,\u201D",
+ /* 144 */ "\u201D,\u201E,\u201C",
+ /* 145 */ "\u201C,\u201E,\u201D",
+ /* 146 */ "!fixedColumnOrder!5,!text/single_quotes,!text/single_angle_quotes",
+ /* 147 */ "!fixedColumnOrder!5,!text/double_quotes,!text/double_angle_quotes",
+ /* 148 */ "!fixedColumnOrder!6,!text/double_quotes,!text/single_quotes,!text/double_angle_quotes,!text/single_angle_quotes",
};
/* Language af: Afrikaans */
@@ -1916,8 +1925,9 @@ public final class KeyboardTextsSet {
// U+2605: "★" BLACK STAR
/* 54 */ "\u2605",
/* 55 */ null,
+ // U+00B1: "±" PLUS-MINUS SIGN
// U+FB29: "﬩" HEBREW LETTER ALTERNATIVE PLUS SIGN
- /* 56 */ "\uFB29",
+ /* 56 */ "\u00B1,\uFB29",
// The all letters need to be mirrored are found at
// http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
/* 57 */ "!fixedColumnOrder!3,<|>,{|},[|]",
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index b92283c5b..c884e7b1f 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -24,13 +24,14 @@ import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.personalization.DynamicPersonalizationDictionaryWriter;
+import com.android.inputmethod.latin.utils.AsyncResultHolder;
import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.PrioritizedSerialExecutor;
import java.io.File;
import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Abstract base class for an expandable dictionary that can be created and updated dynamically
@@ -48,19 +49,27 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
/** Whether to print debug output to log */
private static boolean DEBUG = false;
+ // TODO: Remove and enable dynamic update in native code.
+ /** Whether to call binary dictionary dynamically updating methods. */
+ private static boolean ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE = false;
+
+ private static final int TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS = 100;
+
/**
* The maximum length of a word in this dictionary.
*/
protected static final int MAX_WORD_LENGTH = Constants.DICTIONARY_MAX_WORD_LENGTH;
/**
- * A static map of locks, each of which controls access to a single binary dictionary file. They
- * ensure that only one instance can update the same dictionary at the same time. The key for
- * this map is the filename and the value is the shared dictionary controller associated with
- * that filename.
+ * A static map of time recorders, each of which records the time of accesses to a single binary
+ * dictionary file. The key for this map is the filename and the value is the shared dictionary
+ * time recorder associated with that filename.
*/
- private static final HashMap<String, DictionaryController> sSharedDictionaryControllers =
- CollectionUtils.newHashMap();
+ private static volatile ConcurrentHashMap<String, DictionaryTimeRecorder>
+ sFilenameDictionaryTimeRecorderMap = CollectionUtils.newConcurrentHashMap();
+
+ private static volatile ConcurrentHashMap<String, PrioritizedSerialExecutor>
+ sFilenameExecutorMap = CollectionUtils.newConcurrentHashMap();
/** The application context. */
protected final Context mContext;
@@ -71,30 +80,34 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
private BinaryDictionary mBinaryDictionary;
+ // TODO: Remove and handle dictionaries in native code.
/** The in-memory dictionary used to generate the binary dictionary. */
- private AbstractDictionaryWriter mDictionaryWriter;
+ protected AbstractDictionaryWriter mDictionaryWriter;
/**
* The name of this dictionary, used as the filename for storing the binary dictionary. Multiple
* dictionary instances with the same filename is supported, with access controlled by
- * DictionaryController.
+ * DictionaryTimeRecorder.
*/
private final String mFilename;
/** Whether to support dynamically updating the dictionary */
private final boolean mIsUpdatable;
- /** Controls access to the shared binary dictionary file across multiple instances. */
- private final DictionaryController mSharedDictionaryController;
+ // TODO: remove, once dynamic operations is serialized
+ /** Records access to the shared binary dictionary file across multiple instances. */
+ private final DictionaryTimeRecorder mFilenameDictionaryTimeRecorder;
- /** Controls access to the local binary dictionary for this instance. */
- private final DictionaryController mLocalDictionaryController = new DictionaryController();
+ // TODO: remove, once dynamic operations is serialized
+ /** Records access to the local binary dictionary for this instance. */
+ private final DictionaryTimeRecorder mPerInstanceDictionaryTimeRecorder =
+ new DictionaryTimeRecorder();
/* A extension for a binary dictionary file. */
public static final String DICT_FILE_EXTENSION = ".dict";
- private final AtomicReference<AsyncWriteBinaryDictionaryTask> mWaitingTask =
- new AtomicReference<AsyncWriteBinaryDictionaryTask>();
+ private final AtomicReference<Runnable> mUnfinishedFlushingTask =
+ new AtomicReference<Runnable>();
/**
* Abstract method for loading the unigrams and bigrams of a given dictionary in a background
@@ -110,16 +123,32 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
protected abstract boolean hasContentChanged();
/**
- * Gets the shared dictionary controller for the given filename.
+ * Gets the dictionary time recorder for the given filename.
*/
- private static synchronized DictionaryController getSharedDictionaryController(
+ private static DictionaryTimeRecorder getDictionaryTimeRecorder(
String filename) {
- DictionaryController controller = sSharedDictionaryControllers.get(filename);
- if (controller == null) {
- controller = new DictionaryController();
- sSharedDictionaryControllers.put(filename, controller);
+ DictionaryTimeRecorder recorder = sFilenameDictionaryTimeRecorderMap.get(filename);
+ if (recorder == null) {
+ synchronized(sFilenameDictionaryTimeRecorderMap) {
+ recorder = new DictionaryTimeRecorder();
+ sFilenameDictionaryTimeRecorderMap.put(filename, recorder);
+ }
+ }
+ return recorder;
+ }
+
+ /**
+ * Gets the executor for the given filename.
+ */
+ private static PrioritizedSerialExecutor getExecutor(final String filename) {
+ PrioritizedSerialExecutor executor = sFilenameExecutorMap.get(filename);
+ if (executor == null) {
+ synchronized(sFilenameExecutorMap) {
+ executor = new PrioritizedSerialExecutor();
+ sFilenameExecutorMap.put(filename, executor);
+ }
}
- return controller;
+ return executor;
}
private static AbstractDictionaryWriter getDictionaryWriter(final Context context,
@@ -148,7 +177,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
mContext = context;
mIsUpdatable = isUpdatable;
mBinaryDictionary = null;
- mSharedDictionaryController = getSharedDictionaryController(filename);
+ mFilenameDictionaryTimeRecorder = getDictionaryTimeRecorder(filename);
// Currently, only dynamic personalization dictionary is updatable.
mDictionaryWriter = getDictionaryWriter(context, dictType, isUpdatable);
}
@@ -162,35 +191,38 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
@Override
public void close() {
- closeBinaryDictionary();
- mLocalDictionaryController.writeLock().lock();
- try {
- mDictionaryWriter.close();
- } finally {
- mLocalDictionaryController.writeLock().unlock();
- }
+ getExecutor(mFilename).execute(new Runnable() {
+ @Override
+ public void run() {
+ if (mBinaryDictionary!= null) {
+ mBinaryDictionary.close();
+ mBinaryDictionary = null;
+ }
+ mDictionaryWriter.close();
+ }
+ });
}
protected void closeBinaryDictionary() {
// Ensure that no other threads are accessing the local binary dictionary.
- mLocalDictionaryController.writeLock().lock();
- try {
- if (mBinaryDictionary != null) {
- mBinaryDictionary.close();
- mBinaryDictionary = null;
+ getExecutor(mFilename).execute(new Runnable() {
+ @Override
+ public void run() {
+ if (mBinaryDictionary != null) {
+ mBinaryDictionary.close();
+ mBinaryDictionary = null;
+ }
}
- } finally {
- mLocalDictionaryController.writeLock().unlock();
- }
+ });
}
protected void clear() {
- mLocalDictionaryController.writeLock().lock();
- try {
- mDictionaryWriter.clear();
- } finally {
- mLocalDictionaryController.writeLock().unlock();
- }
+ getExecutor(mFilename).execute(new Runnable() {
+ @Override
+ public void run() {
+ mDictionaryWriter.clear();
+ }
+ });
}
/**
@@ -219,14 +251,17 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
Log.w(TAG, "addWordDynamically is called for non-updatable dictionary: " + mFilename);
return;
}
- // TODO: Use a queue to reflect what needs to be reflected.
- if (mLocalDictionaryController.writeLock().tryLock()) {
- try {
+
+ getExecutor(mFilename).execute(new Runnable() {
+ @Override
+ public void run() {
+ if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
+ mBinaryDictionary.addUnigramWord(word, frequency);
+ }
+ // TODO: Remove.
mDictionaryWriter.addUnigramWord(word, shortcutTarget, frequency, isNotAWord);
- } finally {
- mLocalDictionaryController.writeLock().unlock();
}
- }
+ });
}
/**
@@ -239,15 +274,18 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
+ mFilename);
return;
}
- // TODO: Use a queue to reflect what needs to be reflected.
- if (mLocalDictionaryController.writeLock().tryLock()) {
- try {
+
+ getExecutor(mFilename).execute(new Runnable() {
+ @Override
+ public void run() {
+ if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
+ mBinaryDictionary.addBigramWords(word0, word1, frequency);
+ }
+ // TODO: Remove.
mDictionaryWriter.addBigramWords(word0, word1, frequency, isValid,
0 /* lastTouchedTime */);
- } finally {
- mLocalDictionaryController.writeLock().unlock();
}
- }
+ });
}
/**
@@ -259,24 +297,30 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
+ mFilename);
return;
}
- // TODO: Use a queue to reflect what needs to be reflected.
- if (mLocalDictionaryController.writeLock().tryLock()) {
- try {
+
+ getExecutor(mFilename).execute(new Runnable() {
+ @Override
+ public void run() {
+ if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
+ mBinaryDictionary.removeBigramWords(word0, word1);
+ }
+ // TODO: Remove.
mDictionaryWriter.removeBigramWords(word0, word1);
- } finally {
- mLocalDictionaryController.writeLock().unlock();
}
- }
+ });
}
@Override
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
final String prevWord, final ProximityInfo proximityInfo,
final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
- asyncReloadDictionaryIfRequired();
- // Write lock because getSuggestions in native updates session status.
- if (mLocalDictionaryController.writeLock().tryLock()) {
- try {
+ reloadDictionaryIfRequired();
+ final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
+ final AsyncResultHolder<ArrayList<SuggestedWordInfo>> holder =
+ new AsyncResultHolder<ArrayList<SuggestedWordInfo>>();
+ getExecutor(mFilename).executePrioritized(new Runnable() {
+ @Override
+ public void run() {
final ArrayList<SuggestedWordInfo> inMemDictSuggestion =
mDictionaryWriter.getSuggestions(composer, prevWord, proximityInfo,
blockOffensiveWords, additionalFeaturesOptions);
@@ -286,38 +330,37 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
mBinaryDictionary.getSuggestions(composer, prevWord, proximityInfo,
blockOffensiveWords, additionalFeaturesOptions);
if (inMemDictSuggestion == null) {
- return binarySuggestion;
+ holder.set(binarySuggestion);
} else if (binarySuggestion == null) {
- return inMemDictSuggestion;
+ holder.set(inMemDictSuggestion);
} else {
binarySuggestion.addAll(inMemDictSuggestion);
- return binarySuggestion;
+ holder.set(binarySuggestion);
}
} else {
- return inMemDictSuggestion;
+ holder.set(inMemDictSuggestion);
}
- } finally {
- mLocalDictionaryController.writeLock().unlock();
}
- }
- return null;
+ });
+
+ return holder.get(null, TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS);
}
@Override
public boolean isValidWord(final String word) {
- asyncReloadDictionaryIfRequired();
+ reloadDictionaryIfRequired();
return isValidWordInner(word);
}
protected boolean isValidWordInner(final String word) {
- if (mLocalDictionaryController.readLock().tryLock()) {
- try {
- return isValidWordLocked(word);
- } finally {
- mLocalDictionaryController.readLock().unlock();
+ final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>();
+ getExecutor(mFilename).executePrioritized(new Runnable() {
+ @Override
+ public void run() {
+ holder.set(isValidWordLocked(word));
}
- }
- return false;
+ });
+ return holder.get(false, TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS);
}
protected boolean isValidWordLocked(final String word) {
@@ -335,8 +378,8 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
* dictionary exists, this method will generate one.
*/
protected void loadDictionary() {
- mLocalDictionaryController.mLastUpdateRequestTime = SystemClock.uptimeMillis();
- asyncReloadDictionaryIfRequired();
+ mPerInstanceDictionaryTimeRecorder.mLastUpdateRequestTime = SystemClock.uptimeMillis();
+ reloadDictionaryIfRequired();
}
/**
@@ -346,8 +389,8 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
private void loadBinaryDictionary() {
if (DEBUG) {
Log.d(TAG, "Loading binary dictionary: " + mFilename + " request="
- + mSharedDictionaryController.mLastUpdateRequestTime + " update="
- + mSharedDictionaryController.mLastUpdateTime);
+ + mFilenameDictionaryTimeRecorder.mLastUpdateRequestTime + " update="
+ + mFilenameDictionaryTimeRecorder.mLastUpdateTime);
}
final File file = new File(mContext.getFilesDir(), mFilename);
@@ -358,20 +401,18 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
final BinaryDictionary newBinaryDictionary = new BinaryDictionary(filename, 0, length,
true /* useFullEditDistance */, null, mDictType, mIsUpdatable);
- if (mBinaryDictionary != null) {
- // Ensure all threads accessing the current dictionary have finished before swapping in
- // the new one.
- final BinaryDictionary oldBinaryDictionary = mBinaryDictionary;
- mLocalDictionaryController.writeLock().lock();
- try {
+ // Ensure all threads accessing the current dictionary have finished before swapping in
+ // the new one.
+ final BinaryDictionary oldBinaryDictionary = mBinaryDictionary;
+ getExecutor(mFilename).executePrioritized(new Runnable() {
+ @Override
+ public void run() {
mBinaryDictionary = newBinaryDictionary;
- } finally {
- mLocalDictionaryController.writeLock().unlock();
+ if (oldBinaryDictionary != null) {
+ oldBinaryDictionary.close();
+ }
}
- oldBinaryDictionary.close();
- } else {
- mBinaryDictionary = newBinaryDictionary;
- }
+ });
}
/**
@@ -386,8 +427,8 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
private void writeBinaryDictionary() {
if (DEBUG) {
Log.d(TAG, "Generating binary dictionary: " + mFilename + " request="
- + mSharedDictionaryController.mLastUpdateRequestTime + " update="
- + mSharedDictionaryController.mLastUpdateTime);
+ + mFilenameDictionaryTimeRecorder.mLastUpdateRequestTime + " update="
+ + mFilenameDictionaryTimeRecorder.mLastUpdateTime);
}
if (needsToReloadBeforeWriting()) {
mDictionaryWriter.clear();
@@ -405,54 +446,42 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
protected void setRequiresReload(final boolean requiresRebuild) {
final long time = SystemClock.uptimeMillis();
- mLocalDictionaryController.mLastUpdateRequestTime = time;
- mSharedDictionaryController.mLastUpdateRequestTime = time;
+ mPerInstanceDictionaryTimeRecorder.mLastUpdateRequestTime = time;
+ mFilenameDictionaryTimeRecorder.mLastUpdateRequestTime = time;
if (DEBUG) {
Log.d(TAG, "Reload request: " + mFilename + ": request=" + time + " update="
- + mSharedDictionaryController.mLastUpdateTime);
- }
- }
-
- /**
- * Reloads the dictionary if required. Reload will occur asynchronously in a separate thread.
- */
- public void asyncReloadDictionaryIfRequired() {
- if (!isReloadRequired()) return;
- if (DEBUG) {
- Log.d(TAG, "Starting AsyncReloadDictionaryTask: " + mFilename);
+ + mFilenameDictionaryTimeRecorder.mLastUpdateTime);
}
- new AsyncReloadDictionaryTask().start();
}
/**
* Reloads the dictionary if required.
*/
- public final void syncReloadDictionaryIfRequired() {
+ public final void reloadDictionaryIfRequired() {
if (!isReloadRequired()) return;
- syncReloadDictionaryInternal();
+ reloadDictionary();
}
/**
* Returns whether a dictionary reload is required.
*/
private boolean isReloadRequired() {
- return mBinaryDictionary == null || mLocalDictionaryController.isOutOfDate();
+ return mBinaryDictionary == null || mPerInstanceDictionaryTimeRecorder.isOutOfDate();
}
/**
* Reloads the dictionary. Access is controlled on a per dictionary file basis and supports
* concurrent calls from multiple instances that share the same dictionary file.
*/
- private final void syncReloadDictionaryInternal() {
+ private final void reloadDictionary() {
// Ensure that only one thread attempts to read or write to the shared binary dictionary
// file at the same time.
- mLocalDictionaryController.writeLock().lock();
- try {
- mSharedDictionaryController.writeLock().lock();
- try {
+ getExecutor(mFilename).execute(new Runnable() {
+ @Override
+ public void run() {
final long time = SystemClock.uptimeMillis();
final boolean dictionaryFileExists = dictionaryFileExists();
- if (mSharedDictionaryController.isOutOfDate() || !dictionaryFileExists) {
+ if (mFilenameDictionaryTimeRecorder.isOutOfDate() || !dictionaryFileExists) {
// If the shared dictionary file does not exist or is out of date, the first
// instance that acquires the lock will generate a new one.
if (hasContentChanged() || !dictionaryFileExists) {
@@ -460,34 +489,31 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
// rebuild the binary dictionary. Empty dictionaries are supported (in the
// case where loadDictionaryAsync() adds nothing) in order to provide a
// uniform framework.
- mSharedDictionaryController.mLastUpdateTime = time;
+ mFilenameDictionaryTimeRecorder.mLastUpdateTime = time;
writeBinaryDictionary();
loadBinaryDictionary();
} else {
// If not, the reload request was unnecessary so revert
// LastUpdateRequestTime to LastUpdateTime.
- mSharedDictionaryController.mLastUpdateRequestTime =
- mSharedDictionaryController.mLastUpdateTime;
+ mFilenameDictionaryTimeRecorder.mLastUpdateRequestTime =
+ mFilenameDictionaryTimeRecorder.mLastUpdateTime;
}
- } else if (mBinaryDictionary == null || mLocalDictionaryController.mLastUpdateTime
- < mSharedDictionaryController.mLastUpdateTime) {
+ } else if (mBinaryDictionary == null ||
+ mPerInstanceDictionaryTimeRecorder.mLastUpdateTime
+ < mFilenameDictionaryTimeRecorder.mLastUpdateTime) {
// Otherwise, if the local dictionary is older than the shared dictionary, load
// the shared dictionary.
loadBinaryDictionary();
}
if (mBinaryDictionary != null && !mBinaryDictionary.isValidDictionary()) {
// Binary dictionary is not valid. Regenerate the dictionary file.
- mSharedDictionaryController.mLastUpdateTime = time;
+ mFilenameDictionaryTimeRecorder.mLastUpdateTime = time;
writeBinaryDictionary();
loadBinaryDictionary();
}
- mLocalDictionaryController.mLastUpdateTime = time;
- } finally {
- mSharedDictionaryController.writeLock().unlock();
+ mPerInstanceDictionaryTimeRecorder.mLastUpdateTime = time;
}
- } finally {
- mLocalDictionaryController.writeLock().unlock();
- }
+ });
}
// TODO: cache the file's existence so that we avoid doing a disk access each time.
@@ -497,83 +523,36 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
/**
- * Thread class for asynchronously reloading and rewriting the binary dictionary.
- */
- private class AsyncReloadDictionaryTask extends Thread {
- @Override
- public void run() {
- syncReloadDictionaryInternal();
- }
- }
-
- /**
* Load the dictionary to memory.
*/
protected void asyncLoadDictionaryToMemory() {
- new AsyncLoadDictionaryToMemoryTask().start();
- }
-
- /**
- * Thread class for asynchronously loading dictionary to memory.
- */
- private class AsyncLoadDictionaryToMemoryTask extends Thread {
- @Override
- public void run() {
- mLocalDictionaryController.writeLock().lock();
- try {
- mSharedDictionaryController.readLock().lock();
- try {
- loadDictionaryAsync();
- } finally {
- mSharedDictionaryController.readLock().unlock();
- }
- } finally {
- mLocalDictionaryController.writeLock().unlock();
+ getExecutor(mFilename).executePrioritized(new Runnable() {
+ @Override
+ public void run() {
+ loadDictionaryAsync();
}
- }
+ });
}
/**
* Generate binary dictionary using DictionaryWriter.
*/
protected void asyncWriteBinaryDictionary() {
- final AsyncWriteBinaryDictionaryTask newTask = new AsyncWriteBinaryDictionaryTask();
- newTask.start();
- final AsyncWriteBinaryDictionaryTask oldTask = mWaitingTask.getAndSet(newTask);
- if (oldTask != null) {
- oldTask.interrupt();
- }
- }
-
- /**
- * Thread class for asynchronously writing the binary dictionary.
- */
- private class AsyncWriteBinaryDictionaryTask extends Thread {
- @Override
- public void run() {
- mSharedDictionaryController.writeLock().lock();
- try {
- mLocalDictionaryController.writeLock().lock();
- try {
- if (isInterrupted()) {
- return;
- }
- writeBinaryDictionary();
- } finally {
- mLocalDictionaryController.writeLock().unlock();
- }
- } finally {
- mSharedDictionaryController.writeLock().unlock();
+ final Runnable newTask = new Runnable() {
+ @Override
+ public void run() {
+ writeBinaryDictionary();
}
- }
+ };
+ final Runnable oldTask = mUnfinishedFlushingTask.getAndSet(newTask);
+ getExecutor(mFilename).replaceAndExecute(oldTask, newTask);
}
/**
- * Lock for controlling access to a given binary dictionary and for tracking whether the
- * dictionary is out of date. Can be shared across multiple dictionary instances that access the
- * same filename.
+ * Time recorder for tracking whether the dictionary is out of date.
+ * Can be shared across multiple dictionary instances that access the same filename.
*/
- private static class DictionaryController extends ReentrantReadWriteLock {
+ private static class DictionaryTimeRecorder {
private volatile long mLastUpdateTime = 0;
private volatile long mLastUpdateRequestTime = 0;
@@ -588,12 +567,12 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
@UsedForTesting
protected void addWordDynamicallyForTests(final String word, final String shortcutTarget,
final int frequency, final boolean isNotAWord) {
- mLocalDictionaryController.writeLock().lock();
- try {
- addWordDynamically(word, shortcutTarget, frequency, isNotAWord);
- } finally {
- mLocalDictionaryController.writeLock().unlock();
- }
+ getExecutor(mFilename).executePrioritized(new Runnable() {
+ @Override
+ public void run() {
+ addWordDynamically(word, shortcutTarget, frequency, isNotAWord);
+ }
+ });
}
/**
@@ -602,12 +581,12 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
@UsedForTesting
protected void addBigramDynamicallyForTests(final String word0, final String word1,
final int frequency, final boolean isValid) {
- mLocalDictionaryController.writeLock().lock();
- try {
- addBigramDynamically(word0, word1, frequency, isValid);
- } finally {
- mLocalDictionaryController.writeLock().unlock();
- }
+ getExecutor(mFilename).executePrioritized(new Runnable() {
+ @Override
+ public void run() {
+ addBigramDynamically(word0, word1, frequency, isValid);
+ }
+ });
}
/**
@@ -615,11 +594,27 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
@UsedForTesting
protected void removeBigramDynamicallyForTests(final String word0, final String word1) {
- mLocalDictionaryController.writeLock().lock();
- try {
- removeBigramDynamically(word0, word1);
- } finally {
- mLocalDictionaryController.writeLock().unlock();
- }
+ 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) {
+ final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>();
+ getExecutor(mFilename).executePrioritized(new Runnable() {
+ @Override
+ public void run() {
+ if (mDictType == Dictionary.TYPE_USER_HISTORY) {
+ holder.set(((DynamicPersonalizationDictionaryWriter) mDictionaryWriter)
+ .isInDictionaryForTests(word));
+ }
+ }
+ });
+ return holder.get(false, TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS);
}
}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index 342dcfc5e..ba7d1a2b0 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -265,10 +265,10 @@ public class ExpandableDictionary extends Dictionary {
return (node == null) ? false : !node.mShortcutOnly;
}
- public boolean removeBigram(final String word1, final String word2) {
+ public boolean removeBigram(final String word0, final String word1) {
// Refer to addOrSetBigram() about word1.toLowerCase()
- final Node firstWord = searchWord(mRoots, word1.toLowerCase(), 0, null);
- final Node secondWord = searchWord(mRoots, word2, 0, null);
+ final Node firstWord = searchWord(mRoots, word0.toLowerCase(), 0, null);
+ final Node secondWord = searchWord(mRoots, word1, 0, null);
LinkedList<NextWord> bigrams = firstWord.mNGrams;
NextWord bigramNode = null;
if (bigrams == null || bigrams.size() == 0) {
@@ -297,10 +297,10 @@ public class ExpandableDictionary extends Dictionary {
return (node == null) ? -1 : node.mFrequency;
}
- public NextWord getBigramWord(final String word1, final String word2) {
- // Refer to addOrSetBigram() about word1.toLowerCase()
- final Node firstWord = searchWord(mRoots, word1.toLowerCase(), 0, null);
- final Node secondWord = searchWord(mRoots, word2, 0, null);
+ public NextWord getBigramWord(final String word0, final String word1) {
+ // Refer to addOrSetBigram() about word0.toLowerCase()
+ final Node firstWord = searchWord(mRoots, word0.toLowerCase(), 0, null);
+ final Node secondWord = searchWord(mRoots, word1, 0, null);
LinkedList<NextWord> bigrams = firstWord.mNGrams;
if (bigrams == null || bigrams.size() == 0) {
return null;
@@ -473,37 +473,41 @@ public class ExpandableDictionary extends Dictionary {
}
}
- public int setBigramAndGetFrequency(final String word1, final String word2,
+ public int setBigramAndGetFrequency(final String word0, final String word1,
final int frequency) {
- return setBigramAndGetFrequency(word1, word2, frequency, null /* unused */);
+ return setBigramAndGetFrequency(word0, word1, frequency, null /* unused */);
}
- public int setBigramAndGetFrequency(final String word1, final String word2,
+ public int setBigramAndGetFrequency(final String word0, final String word1,
final ForgettingCurveParams fcp) {
- return setBigramAndGetFrequency(word1, word2, 0 /* unused */, fcp);
+ return setBigramAndGetFrequency(word0, word1, 0 /* unused */, fcp);
}
/**
* Adds bigrams to the in-memory trie structure that is being used to retrieve any word
- * @param word1 the first word of this bigram
- * @param word2 the second word of this bigram
+ * @param word0 the first word of this bigram
+ * @param word1 the second word of this bigram
* @param frequency frequency for this bigram
* @param fcp an instance of ForgettingCurveParams to use for decay policy
* @return returns the final bigram frequency
*/
- private int setBigramAndGetFrequency(final String word1, final String word2,
+ private int setBigramAndGetFrequency(final String word0, final String word1,
final int frequency, final ForgettingCurveParams fcp) {
+ if (TextUtils.isEmpty(word0)) {
+ Log.e(TAG, "Invalid bigram previous word: " + word0);
+ return frequency;
+ }
// We don't want results to be different according to case of the looked up left hand side
// word. We do want however to return the correct case for the right hand side.
// So we want to squash the case of the left hand side, and preserve that of the right
// hand side word.
- final String word1Lower = word1.toLowerCase();
- if (TextUtils.isEmpty(word1Lower) || TextUtils.isEmpty(word2)) {
- Log.e(TAG, "Invalid bigram pair: " + word1 + ", " + word1Lower + ", " + word2);
+ final String word0Lower = word0.toLowerCase();
+ if (TextUtils.isEmpty(word0Lower) || TextUtils.isEmpty(word1)) {
+ Log.e(TAG, "Invalid bigram pair: " + word0 + ", " + word0Lower + ", " + word1);
return frequency;
}
- final Node firstWord = searchWord(mRoots, word1Lower, 0, null);
- final Node secondWord = searchWord(mRoots, word2, 0, null);
+ final Node firstWord = searchWord(mRoots, word0Lower, 0, null);
+ final Node secondWord = searchWord(mRoots, word1, 0, null);
LinkedList<NextWord> bigrams = firstWord.mNGrams;
if (bigrams == null || bigrams.size() == 0) {
firstWord.mNGrams = CollectionUtils.newLinkedList();
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 6be9ded5c..6c83ac7ed 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -86,6 +86,7 @@ import com.android.inputmethod.latin.settings.SettingsActivity;
import com.android.inputmethod.latin.settings.SettingsValues;
import com.android.inputmethod.latin.suggestions.SuggestionStripView;
import com.android.inputmethod.latin.utils.ApplicationUtils;
+import com.android.inputmethod.latin.utils.AsyncResultHolder;
import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
import com.android.inputmethod.latin.utils.CapsModeUtils;
import com.android.inputmethod.latin.utils.CollectionUtils;
@@ -107,8 +108,6 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Locale;
import java.util.TreeSet;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
/**
* Input method implementation for Qwerty'ish keyboard.
@@ -2218,7 +2217,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mKeyboardSwitcher.updateShiftState();
}
- // Returns true if we did an autocorrection, false otherwise.
+ // Returns true if we do an autocorrection, false otherwise.
private boolean handleSeparator(final int primaryCode, final int x, final int y,
final int spaceState) {
boolean didAutoCorrect = false;
@@ -2385,31 +2384,20 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return;
}
- final CountDownLatch latch = new CountDownLatch(1);
- final SuggestedWords[] suggestedWordsArray = new SuggestedWords[1];
+ final AsyncResultHolder<SuggestedWords> holder = new AsyncResultHolder<SuggestedWords>();
getSuggestedWordsOrOlderSuggestionsAsync(Suggest.SESSION_TYPING,
new OnGetSuggestedWordsCallback() {
@Override
public void onGetSuggestedWords(final SuggestedWords suggestedWords) {
- suggestedWordsArray[0] = suggestedWords;
- latch.countDown();
+ holder.set(suggestedWords);
}
}
);
- // TODO: Quit blocking the main thread.
- try {
- // Wait for the result of getSuggestedWords
- // We set the time out to avoid ANR.
- latch.await(GET_SUGGESTED_WORDS_TIMEOUT, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- // TODO: Cancel all pending "getSuggestedWords" tasks when it failed. We may want to add
- // "onGetSuggestionFailed" to "OnGetSuggestedWordsCallback".
- Log.e(TAG, "InterruptedException while waiting for getSuggestedWords.", e);
- return;
- }
- if (suggestedWordsArray[0] != null) {
- showSuggestionStrip(suggestedWordsArray[0]);
+ // This line may cause the current thread to wait.
+ final SuggestedWords suggestedWords = holder.get(null, GET_SUGGESTED_WORDS_TIMEOUT);
+ if (suggestedWords != null) {
+ showSuggestionStrip(suggestedWords);
}
}
@@ -2489,11 +2477,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
false /* isPrediction */);
}
- private void showSuggestionStrip(final SuggestedWords suggestedWords) {
- if (suggestedWords.isEmpty()) {
- clearSuggestionStrip();
- return;
- }
+ private void setAutoCorrection(final SuggestedWords suggestedWords) {
+ if (suggestedWords.isEmpty()) return;
final String autoCorrection;
if (suggestedWords.mWillAutoCorrect) {
autoCorrection = suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION);
@@ -2501,13 +2486,21 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
autoCorrection = suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD);
}
mWordComposer.setAutoCorrection(autoCorrection);
+ }
+
+ private void showSuggestionStrip(final SuggestedWords suggestedWords) {
+ if (suggestedWords.isEmpty()) {
+ clearSuggestionStrip();
+ return;
+ }
+ setAutoCorrection(suggestedWords);
final boolean isAutoCorrection = suggestedWords.willAutoCorrect();
setSuggestedWords(suggestedWords, isAutoCorrection);
setAutoCorrectionIndicator(isAutoCorrection);
setSuggestionStripShown(isSuggestionsStripVisible());
}
- private void commitCurrentAutoCorrection(final String separatorString) {
+ private void commitCurrentAutoCorrection(final String separator) {
// Complete any pending suggestions query first
if (mHandler.hasPendingUpdateSuggestions()) {
updateSuggestionStrip();
@@ -2523,16 +2516,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
if (mSettings.isInternal()) {
LatinImeLoggerUtils.onAutoCorrection(
- typedWord, autoCorrection, separatorString, mWordComposer);
+ typedWord, autoCorrection, separator, mWordComposer);
}
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
final SuggestedWords suggestedWords = mSuggestedWords;
ResearchLogger.latinIme_commitCurrentAutoCorrection(typedWord, autoCorrection,
- separatorString, mWordComposer.isBatchMode(), suggestedWords);
+ separator, mWordComposer.isBatchMode(), suggestedWords);
}
mExpectingUpdateSelection = true;
commitChosenWord(autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD,
- separatorString);
+ separator);
if (!typedWord.equals(autoCorrection)) {
// This will make the correction flash for a short while as a visual clue
// to the user that auto-correction happened. It has no other effect; in particular
diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java
index 67ef538ac..3213c92c7 100644
--- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java
@@ -35,14 +35,14 @@ public final class SynchronouslyLoadedContactsBinaryDictionary extends ContactsB
public synchronized ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer codes,
final String prevWordForBigrams, final ProximityInfo proximityInfo,
final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
- syncReloadDictionaryIfRequired();
+ reloadDictionaryIfRequired();
return super.getSuggestions(codes, prevWordForBigrams, proximityInfo, blockOffensiveWords,
additionalFeaturesOptions);
}
@Override
public synchronized boolean isValidWord(final String word) {
- syncReloadDictionaryIfRequired();
+ reloadDictionaryIfRequired();
return isValidWordInner(word);
}
diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
index bea522320..6405b5e46 100644
--- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
@@ -38,14 +38,14 @@ public final class SynchronouslyLoadedUserBinaryDictionary extends UserBinaryDic
public synchronized ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer codes,
final String prevWordForBigrams, final ProximityInfo proximityInfo,
final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
- syncReloadDictionaryIfRequired();
+ reloadDictionaryIfRequired();
return super.getSuggestions(codes, prevWordForBigrams, proximityInfo, blockOffensiveWords,
additionalFeaturesOptions);
}
@Override
public synchronized boolean isValidWord(final String word) {
- syncReloadDictionaryIfRequired();
+ reloadDictionaryIfRequired();
return isValidWordInner(word);
}
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
index 79f5ad8bd..5a213415a 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
@@ -27,7 +27,6 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
-import java.util.Iterator;
/**
* Encodes binary files for a FusionDictionary.
@@ -432,7 +431,7 @@ public class BinaryDictEncoderUtils {
* @param formatOptions file format options.
* @return the same array it was passed. The nodes have been updated for address and size.
*/
- private static ArrayList<PtNodeArray> computeAddresses(final FusionDictionary dict,
+ /* package */ static ArrayList<PtNodeArray> computeAddresses(final FusionDictionary dict,
final ArrayList<PtNodeArray> flatNodes, final FormatOptions formatOptions) {
// First get the worst possible sizes and offsets
for (final PtNodeArray n : flatNodes) calculatePtNodeArrayMaximumSize(n, formatOptions);
@@ -484,7 +483,7 @@ public class BinaryDictEncoderUtils {
*
* @param arrays the list of node arrays to check
*/
- private static void checkFlatPtNodeArrayList(final ArrayList<PtNodeArray> arrays) {
+ /* package */ static void checkFlatPtNodeArrayList(final ArrayList<PtNodeArray> arrays) {
int offset = 0;
int index = 0;
for (final PtNodeArray ptNodeArray : arrays) {
@@ -501,52 +500,53 @@ public class BinaryDictEncoderUtils {
}
/**
- * Helper method to write a variable-size address to a file.
+ * Helper method to write a children position to a file.
*
* @param buffer the buffer to write to.
* @param index the index in the buffer to write the address to.
- * @param address the address to write.
+ * @param position the position to write.
* @return the size in bytes the address actually took.
*/
- private static int writeVariableAddress(final byte[] buffer, int index, final int address) {
- switch (getByteSize(address)) {
+ /* package */ static int writeChildrenPosition(final byte[] buffer, int index,
+ final int position) {
+ switch (getByteSize(position)) {
case 1:
- buffer[index++] = (byte)address;
+ buffer[index++] = (byte)position;
return 1;
case 2:
- buffer[index++] = (byte)(0xFF & (address >> 8));
- buffer[index++] = (byte)(0xFF & address);
+ buffer[index++] = (byte)(0xFF & (position >> 8));
+ buffer[index++] = (byte)(0xFF & position);
return 2;
case 3:
- buffer[index++] = (byte)(0xFF & (address >> 16));
- buffer[index++] = (byte)(0xFF & (address >> 8));
- buffer[index++] = (byte)(0xFF & address);
+ buffer[index++] = (byte)(0xFF & (position >> 16));
+ buffer[index++] = (byte)(0xFF & (position >> 8));
+ buffer[index++] = (byte)(0xFF & position);
return 3;
case 0:
return 0;
default:
- throw new RuntimeException("Address " + address + " has a strange size");
+ throw new RuntimeException("Position " + position + " has a strange size");
}
}
/**
- * Helper method to write a variable-size signed address to a file.
+ * Helper method to write a signed children position to a file.
*
* @param buffer the buffer to write to.
* @param index the index in the buffer to write the address to.
- * @param address the address to write.
+ * @param position the position to write.
* @return the size in bytes the address actually took.
*/
- private static int writeVariableSignedAddress(final byte[] buffer, int index,
- final int address) {
- if (!BinaryDictIOUtils.hasChildrenAddress(address)) {
+ /* package */ static int writeSignedChildrenPosition(final byte[] buffer, int index,
+ final int position) {
+ if (!BinaryDictIOUtils.hasChildrenAddress(position)) {
buffer[index] = buffer[index + 1] = buffer[index + 2] = 0;
} else {
- final int absAddress = Math.abs(address);
+ final int absPosition = Math.abs(position);
buffer[index++] =
- (byte)((address < 0 ? FormatSpec.MSB8 : 0) | (0xFF & (absAddress >> 16)));
- buffer[index++] = (byte)(0xFF & (absAddress >> 8));
- buffer[index++] = (byte)(0xFF & absAddress);
+ (byte)((position < 0 ? FormatSpec.MSB8 : 0) | (0xFF & (absPosition >> 16)));
+ buffer[index++] = (byte)(0xFF & (absPosition >> 8));
+ buffer[index++] = (byte)(0xFF & absPosition);
}
return 3;
}
@@ -598,7 +598,7 @@ public class BinaryDictEncoderUtils {
return flags;
}
- private static byte makePtNodeFlags(final PtNode node, final int ptNodeAddress,
+ /* package */ static byte makePtNodeFlags(final PtNode node, final int ptNodeAddress,
final int childrenOffset, final FormatOptions formatOptions) {
return (byte) makePtNodeFlags(node.mChars.length > 1, node.mFrequency >= 0,
getByteSize(childrenOffset),
@@ -616,7 +616,7 @@ public class BinaryDictEncoderUtils {
* @param word the second bigram, for debugging purposes
* @return the flags
*/
- private static final int makeBigramFlags(final boolean more, final int offset,
+ /* package */ static final int makeBigramFlags(final boolean more, final int offset,
int bigramFrequency, final int unigramFrequency, final String word) {
int bigramFlags = (more ? FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT : 0)
+ (offset < 0 ? FormatSpec.FLAG_BIGRAM_ATTR_OFFSET_NEGATIVE : 0);
@@ -702,7 +702,7 @@ public class BinaryDictEncoderUtils {
+ (frequency & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY);
}
- private static final int writeParentAddress(final byte[] buffer, final int index,
+ /* package */ static final int writeParentAddress(final byte[] buffer, final int index,
final int address, final FormatOptions formatOptions) {
if (BinaryDictIOUtils.supportsDynamicUpdate(formatOptions)) {
if (address == FormatSpec.NO_PARENT_ADDRESS) {
@@ -721,137 +721,69 @@ public class BinaryDictEncoderUtils {
}
}
+ /* package */ static final int getChildrenPosition(final PtNode ptNode,
+ final FormatOptions formatOptions) {
+ int positionOfChildrenPosField = ptNode.mCachedAddressAfterUpdate
+ + getNodeHeaderSize(ptNode, formatOptions);
+ if (ptNode.mFrequency >= 0) {
+ positionOfChildrenPosField += FormatSpec.PTNODE_FREQUENCY_SIZE;
+ }
+ return null == ptNode.mChildren ? FormatSpec.NO_CHILDREN_ADDRESS
+ : ptNode.mChildren.mCachedAddressAfterUpdate - positionOfChildrenPosField;
+ }
+
/**
* Write a PtNodeArray to memory. The PtNodeArray is expected to have its final position cached.
*
* @param dict the dictionary the node array is a part of (for relative offsets).
- * @param buffer the memory buffer to write to.
+ * @param dictEncoder the dictionary encoder.
* @param ptNodeArray the node array to write.
* @param formatOptions file format options.
- * @return the address of the END of the node.
*/
@SuppressWarnings("unused")
- private static int writePlacedNode(final FusionDictionary dict, byte[] buffer,
- final PtNodeArray ptNodeArray, final FormatOptions formatOptions) {
+ /* package */ static void writePlacedNode(final FusionDictionary dict,
+ final DictEncoder dictEncoder, final PtNodeArray ptNodeArray,
+ final FormatOptions formatOptions) {
// TODO: Make the code in common with BinaryDictIOUtils#writePtNode
- int index = ptNodeArray.mCachedAddressAfterUpdate;
+ dictEncoder.setPosition(ptNodeArray.mCachedAddressAfterUpdate);
final int ptNodeCount = ptNodeArray.mData.size();
- final int countSize = getPtNodeCountSize(ptNodeArray);
- final int parentAddress = ptNodeArray.mCachedParentAddress;
- if (1 == countSize) {
- buffer[index++] = (byte)ptNodeCount;
- } else if (2 == countSize) {
- // We need to signal 2-byte size by setting the top bit of the MSB to 1, so
- // we | 0x80 to do this.
- buffer[index++] = (byte)((ptNodeCount >> 8) | 0x80);
- buffer[index++] = (byte)(ptNodeCount & 0xFF);
- } else {
- throw new RuntimeException("Strange size from getGroupCountSize : " + countSize);
- }
- int ptNodeAddress = index;
+ dictEncoder.writePtNodeCount(ptNodeCount);
+ final int parentPosition =
+ (ptNodeArray.mCachedParentAddress == FormatSpec.NO_PARENT_ADDRESS)
+ ? FormatSpec.NO_PARENT_ADDRESS
+ : ptNodeArray.mCachedParentAddress + ptNodeArray.mCachedAddressAfterUpdate;
for (int i = 0; i < ptNodeCount; ++i) {
final PtNode ptNode = ptNodeArray.mData.get(i);
- if (index != ptNode.mCachedAddressAfterUpdate) {
+ if (dictEncoder.getPosition() != ptNode.mCachedAddressAfterUpdate) {
throw new RuntimeException("Bug: write index is not the same as the cached address "
- + "of the node : " + index + " <> " + ptNode.mCachedAddressAfterUpdate);
+ + "of the node : " + dictEncoder.getPosition() + " <> "
+ + ptNode.mCachedAddressAfterUpdate);
}
- ptNodeAddress += getNodeHeaderSize(ptNode, formatOptions);
// Sanity checks.
if (DBG && ptNode.mFrequency > FormatSpec.MAX_TERMINAL_FREQUENCY) {
throw new RuntimeException("A node has a frequency > "
+ FormatSpec.MAX_TERMINAL_FREQUENCY
+ " : " + ptNode.mFrequency);
}
- if (ptNode.mFrequency >= 0) ptNodeAddress += FormatSpec.PTNODE_FREQUENCY_SIZE;
- final int childrenOffset = null == ptNode.mChildren
- ? FormatSpec.NO_CHILDREN_ADDRESS
- : ptNode.mChildren.mCachedAddressAfterUpdate - ptNodeAddress;
- buffer[index++] =
- makePtNodeFlags(ptNode, ptNodeAddress, childrenOffset, formatOptions);
-
- if (parentAddress == FormatSpec.NO_PARENT_ADDRESS) {
- index = writeParentAddress(buffer, index, parentAddress, formatOptions);
- } else {
- index = writeParentAddress(buffer, index, parentAddress
- + (ptNodeArray.mCachedAddressAfterUpdate
- - ptNode.mCachedAddressAfterUpdate),
- formatOptions);
- }
-
- index = CharEncoding.writeCharArray(ptNode.mChars, buffer, index);
- if (ptNode.hasSeveralChars()) {
- buffer[index++] = FormatSpec.PTNODE_CHARACTERS_TERMINATOR;
- }
- if (ptNode.mFrequency >= 0) {
- buffer[index++] = (byte) ptNode.mFrequency;
- }
-
- final int shift;
- if (formatOptions.mSupportsDynamicUpdate) {
- shift = writeVariableSignedAddress(buffer, index, childrenOffset);
- } else {
- shift = writeVariableAddress(buffer, index, childrenOffset);
- }
- index += shift;
- ptNodeAddress += shift;
-
- // Write shortcuts
- if (null != ptNode.mShortcutTargets && !ptNode.mShortcutTargets.isEmpty()) {
- final int indexOfShortcutByteSize = index;
- index += FormatSpec.PTNODE_SHORTCUT_LIST_SIZE_SIZE;
- ptNodeAddress += FormatSpec.PTNODE_SHORTCUT_LIST_SIZE_SIZE;
- final Iterator<WeightedString> shortcutIterator =
- ptNode.mShortcutTargets.iterator();
- while (shortcutIterator.hasNext()) {
- final WeightedString target = shortcutIterator.next();
- ++ptNodeAddress;
- int shortcutFlags = makeShortcutFlags(shortcutIterator.hasNext(),
- target.mFrequency);
- buffer[index++] = (byte)shortcutFlags;
- final int shortcutShift = CharEncoding.writeString(buffer, index, target.mWord);
- index += shortcutShift;
- ptNodeAddress += shortcutShift;
- }
- final int shortcutByteSize = index - indexOfShortcutByteSize;
- if (shortcutByteSize > 0xFFFF) {
- throw new RuntimeException("Shortcut list too large");
- }
- buffer[indexOfShortcutByteSize] = (byte)(shortcutByteSize >> 8);
- buffer[indexOfShortcutByteSize + 1] = (byte)(shortcutByteSize & 0xFF);
- }
- // Write bigrams
- if (null != ptNode.mBigrams) {
- final Iterator<WeightedString> bigramIterator = ptNode.mBigrams.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;
- ++ptNodeAddress;
- final int offset = addressOfBigram - ptNodeAddress;
- int bigramFlags = makeBigramFlags(bigramIterator.hasNext(), offset,
- bigram.mFrequency, unigramFrequencyForThisWord, bigram.mWord);
- buffer[index++] = (byte)bigramFlags;
- final int bigramShift = writeVariableAddress(buffer, index, Math.abs(offset));
- index += bigramShift;
- ptNodeAddress += bigramShift;
- }
- }
+ dictEncoder.writePtNodeFlags(ptNode, parentPosition, formatOptions);
+ dictEncoder.writeParentPosition(parentPosition, ptNode, formatOptions);
+ dictEncoder.writeCharacters(ptNode.mChars, ptNode.hasSeveralChars());
+ dictEncoder.writeFrequency(ptNode.mFrequency);
+ dictEncoder.writeChildrenPosition(ptNode, formatOptions);
+ dictEncoder.writeShortcuts(ptNode.mShortcutTargets);
+ dictEncoder.writeBigrams(ptNode.mBigrams, dict);
}
if (formatOptions.mSupportsDynamicUpdate) {
- buffer[index] = buffer[index + 1] = buffer[index + 2]
- = FormatSpec.NO_FORWARD_LINK_ADDRESS;
- index += FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
+ dictEncoder.writeForwardLinkAddress(FormatSpec.NO_FORWARD_LINK_ADDRESS);
}
- if (index != ptNodeArray.mCachedAddressAfterUpdate + ptNodeArray.mCachedSize) {
- throw new RuntimeException(
- "Not the same size : written " + (index - ptNodeArray.mCachedAddressAfterUpdate)
+ if (dictEncoder.getPosition() != ptNodeArray.mCachedAddressAfterUpdate
+ + ptNodeArray.mCachedSize) {
+ throw new RuntimeException("Not the same size : written "
+ + (dictEncoder.getPosition() - ptNodeArray.mCachedAddressAfterUpdate)
+ " bytes from a node that should have " + ptNodeArray.mCachedSize + " bytes");
}
- return index;
}
/**
@@ -862,7 +794,7 @@ public class BinaryDictEncoderUtils {
*
* @param ptNodeArrays the list of PtNode arrays.
*/
- private static void showStatistics(ArrayList<PtNodeArray> ptNodeArrays) {
+ /* package */ static void showStatistics(ArrayList<PtNodeArray> ptNodeArrays) {
int firstTerminalAddress = Integer.MAX_VALUE;
int lastTerminalAddress = Integer.MIN_VALUE;
int size = 0;
@@ -912,23 +844,15 @@ public class BinaryDictEncoderUtils {
}
/**
- * Dumps a FusionDictionary to a file.
+ * Writes a file header to an output stream.
*
- * @param destination the stream to write the binary data to.
+ * @param destination the stream to write the file header to.
* @param dict the dictionary to write.
* @param formatOptions file format options.
*/
- /* package */ static void writeDictionaryBinary(final OutputStream destination,
+ /* package */ static void writeDictionaryHeader(final OutputStream destination,
final FusionDictionary dict, final FormatOptions formatOptions)
- throws IOException, UnsupportedFormatException {
-
- // Addresses are limited to 3 bytes, but since addresses can be relative to each node
- // array, the structure itself is not limited to 16MB. However, if it is over 16MB deciding
- // the order of the PtNode arrays becomes a quite complicated problem, because though the
- // dictionary itself does not have a size limit, each node array must still be within 16MB
- // of all its children and parents. As long as this is ensured, the dictionary file may
- // grow to any size.
-
+ throws IOException, UnsupportedFormatException {
final int version = formatOptions.mVersion;
if (version < FormatSpec.MINIMUM_SUPPORTED_VERSION
|| version > FormatSpec.MAXIMUM_SUPPORTED_VERSION) {
@@ -975,33 +899,5 @@ public class BinaryDictEncoderUtils {
destination.write(bytes);
headerBuffer.close();
-
- // Leave the choice of the optimal node order to the flattenTree function.
- MakedictLog.i("Flattening the tree...");
- ArrayList<PtNodeArray> flatNodes = flattenTree(dict.mRootNodeArray);
-
- MakedictLog.i("Computing addresses...");
- computeAddresses(dict, flatNodes, formatOptions);
- MakedictLog.i("Checking PtNode array...");
- if (DBG) checkFlatPtNodeArrayList(flatNodes);
-
- // Create a buffer that matches the final dictionary size.
- final PtNodeArray lastNodeArray = flatNodes.get(flatNodes.size() - 1);
- final int bufferSize = lastNodeArray.mCachedAddressAfterUpdate + lastNodeArray.mCachedSize;
- final byte[] buffer = new byte[bufferSize];
- int index = 0;
-
- MakedictLog.i("Writing file...");
- int dataEndOffset = 0;
- for (PtNodeArray nodeArray : flatNodes) {
- dataEndOffset = writePlacedNode(dict, buffer, nodeArray, formatOptions);
- }
-
- if (DBG) showStatistics(flatNodes);
-
- destination.write(buffer, 0, dataEndOffset);
-
- destination.close();
- MakedictLog.i("Done");
}
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/DictEncoder.java
index 89c982e7b..d1589a30e 100644
--- a/java/src/com/android/inputmethod/latin/makedict/DictEncoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/DictEncoder.java
@@ -17,8 +17,11 @@
package com.android.inputmethod.latin.makedict;
import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
+import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
+import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
import java.io.IOException;
+import java.util.ArrayList;
/**
* An interface of binary dictionary encoder.
@@ -26,4 +29,32 @@ import java.io.IOException;
public interface DictEncoder {
public void writeDictionary(final FusionDictionary dict, final FormatOptions formatOptions)
throws IOException, UnsupportedFormatException;
+
+ public void setPosition(final int position);
+ public int getPosition();
+ public void writePtNodeCount(final int ptNodeCount);
+ public void writePtNodeFlags(final PtNode ptNode, final int parentAddress,
+ final FormatOptions formatOptions);
+ public void writeParentPosition(final int parentPosition, final PtNode ptNode,
+ final FormatOptions formatOptions);
+ public void writeCharacters(final int[] characters, final boolean hasSeveralChars);
+ public void writeFrequency(final int frequency);
+ public void writeChildrenPosition(final PtNode ptNode, final FormatOptions formatOptions);
+
+ /**
+ * Write a shortcut attributes list to memory.
+ *
+ * @param shortcuts the shortcut attributes list.
+ */
+ public void writeShortcuts(final ArrayList<WeightedString> shortcuts);
+
+ /**
+ * Write a bigram attributes list to memory.
+ *
+ * @param bigrams the bigram attributes list.
+ * @param dict the dictionary the node array is a part of (for relative offsets).
+ */
+ public void writeBigrams(final ArrayList<WeightedString> bigrams, final FusionDictionary dict);
+
+ public void writeForwardLinkAddress(final int forwardLinkAddress);
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index b8ef57696..bf35f6a8a 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -112,8 +112,10 @@ public final class FormatSpec {
* e | 1 byte = bbbbbbbb match
* n | case 1xxxxxxx => -((0xxxxxxx << 16) + (next byte << 8) + next byte)
* t | otherwise => (bbbbbbbb << 16) + (next byte << 8) + next byte
- * a |
- * ddress
+ * a | This address is relative to the head of the PtNode.
+ * d | If the node doesn't have a parent, this field is set to 0.
+ * d |
+ * ress
*
* c | IF FLAG_HAS_MULTIPLE_CHARS
* h | char, char, char, char n * (1 or 3 bytes) : use PtNodeInfo for i/o helpers
@@ -132,17 +134,18 @@ public final class FormatSpec {
* i | 1 byte = bbbbbbbb match
* l | case 1xxxxxxx => -((0xxxxxxx << 16) + (next byte << 8) + next byte)
* d | otherwise => (bbbbbbbb<<16) + (next byte << 8) + next byte
- * r | ELSIF 00 = FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS == addressType
- * e | // nothing
- * n | ELSIF 01 = FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE == addressType
- * A | children address, 1 byte
- * d | ELSIF 10 = FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES == addressType
- * d | children address, 2 bytes
- * r | ELSE // 11 = FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES = addressType
- * e | children address, 3 bytes
- * s | END
- * s
- * ress
+ * r | if this node doesn't have children, this field is set to 0.
+ * e | (see BinaryDictEncoderUtils#writeVariableSignedAddress)
+ * n | ELSIF 00 = FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS == addressType
+ * a | // nothing
+ * d | ELSIF 01 = FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE == addressType
+ * d | children address, 1 byte
+ * r | ELSIF 10 = FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES == addressType
+ * e | children address, 2 bytes
+ * s | ELSE // 11 = FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES = addressType
+ * s | children address, 3 bytes
+ * | END
+ * | This address is relative to the position of this field.
*
* | IF FLAG_IS_TERMINAL && FLAG_HAS_SHORTCUT_TARGETS
* | shortcut string list
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java
index e81fd4514..3f26ff378 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java
@@ -16,13 +16,19 @@
package com.android.inputmethod.latin.makedict;
+import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding;
import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
+import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
+import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
+import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
/**
* An implementation of DictEncoder for version 3 binary dictionary.
@@ -31,10 +37,13 @@ public class Ver3DictEncoder implements DictEncoder {
private final File mDictFile;
private OutputStream mOutStream;
+ private byte[] mBuffer;
+ private int mPosition;
public Ver3DictEncoder(final File dictFile) {
mDictFile = dictFile;
mOutStream = null;
+ mBuffer = null;
}
// This constructor is used only by BinaryDictOffdeviceUtilsTests.
@@ -57,12 +66,172 @@ public class Ver3DictEncoder implements DictEncoder {
}
@Override
- public void writeDictionary(FusionDictionary dict, FormatOptions formatOptions)
+ public void writeDictionary(final FusionDictionary dict, final FormatOptions formatOptions)
throws IOException, UnsupportedFormatException {
+ if (formatOptions.mVersion > 3) {
+ throw new UnsupportedFormatException(
+ "The given format options has wrong version number : "
+ + formatOptions.mVersion);
+ }
+
if (mOutStream == null) {
openStream();
}
- BinaryDictEncoderUtils.writeDictionaryBinary(mOutStream, dict, formatOptions);
+ BinaryDictEncoderUtils.writeDictionaryHeader(mOutStream, dict, formatOptions);
+
+ // Addresses are limited to 3 bytes, but since addresses can be relative to each node
+ // array, the structure itself is not limited to 16MB. However, if it is over 16MB deciding
+ // the order of the PtNode arrays becomes a quite complicated problem, because though the
+ // dictionary itself does not have a size limit, each node array must still be within 16MB
+ // of all its children and parents. As long as this is ensured, the dictionary file may
+ // grow to any size.
+
+ // Leave the choice of the optimal node order to the flattenTree function.
+ MakedictLog.i("Flattening the tree...");
+ ArrayList<PtNodeArray> flatNodes = BinaryDictEncoderUtils.flattenTree(dict.mRootNodeArray);
+
+ MakedictLog.i("Computing addresses...");
+ BinaryDictEncoderUtils.computeAddresses(dict, flatNodes, formatOptions);
+ MakedictLog.i("Checking PtNode array...");
+ if (MakedictLog.DBG) BinaryDictEncoderUtils.checkFlatPtNodeArrayList(flatNodes);
+
+ // Create a buffer that matches the final dictionary size.
+ final PtNodeArray lastNodeArray = flatNodes.get(flatNodes.size() - 1);
+ final int bufferSize = lastNodeArray.mCachedAddressAfterUpdate + lastNodeArray.mCachedSize;
+ mBuffer = new byte[bufferSize];
+
+ MakedictLog.i("Writing file...");
+
+ for (PtNodeArray nodeArray : flatNodes) {
+ BinaryDictEncoderUtils.writePlacedNode(dict, this, nodeArray, formatOptions);
+ }
+ if (MakedictLog.DBG) BinaryDictEncoderUtils.showStatistics(flatNodes);
+ mOutStream.write(mBuffer, 0, mPosition);
+
+ MakedictLog.i("Done");
close();
}
+
+ @Override
+ public void setPosition(final int position) {
+ if (mBuffer == null || position < 0 || position >= mBuffer.length) return;
+ mPosition = position;
+ }
+
+ @Override
+ public int getPosition() {
+ return mPosition;
+ }
+
+ @Override
+ public void writePtNodeCount(final int ptNodeCount) {
+ final int countSize = BinaryDictIOUtils.getPtNodeCountSize(ptNodeCount);
+ if (1 == countSize) {
+ mBuffer[mPosition++] = (byte) ptNodeCount;
+ } else if (2 == countSize) {
+ mBuffer[mPosition++] = (byte) ((ptNodeCount >> 8) & 0xFF);
+ mBuffer[mPosition++] = (byte) (ptNodeCount & 0xFF);
+ } else {
+ throw new RuntimeException("Strange size from getGroupCountSize : " + countSize);
+ }
+ }
+
+ @Override
+ public void writePtNodeFlags(final PtNode ptNode, final int parentAddress,
+ final FormatOptions formatOptions) {
+ final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode, formatOptions);
+ mBuffer[mPosition++] = BinaryDictEncoderUtils.makePtNodeFlags(ptNode, mPosition,
+ childrenPos, formatOptions);
+ }
+
+ @Override
+ public void writeParentPosition(final int parentPosition, final PtNode ptNode,
+ final FormatOptions formatOptions) {
+ if (parentPosition == FormatSpec.NO_PARENT_ADDRESS) {
+ mPosition = BinaryDictEncoderUtils.writeParentAddress(mBuffer, mPosition,
+ parentPosition, formatOptions);
+ } else {
+ mPosition = BinaryDictEncoderUtils.writeParentAddress(mBuffer, mPosition,
+ parentPosition - ptNode.mCachedAddressAfterUpdate, formatOptions);
+ }
+ }
+
+ @Override
+ public void writeCharacters(final int[] codePoints, final boolean hasSeveralChars) {
+ mPosition = CharEncoding.writeCharArray(codePoints, mBuffer, mPosition);
+ if (hasSeveralChars) {
+ mBuffer[mPosition++] = FormatSpec.PTNODE_CHARACTERS_TERMINATOR;
+ }
+ }
+
+ @Override
+ public void writeFrequency(final int frequency) {
+ if (frequency >= 0) {
+ mBuffer[mPosition++] = (byte) frequency;
+ }
+ }
+
+ @Override
+ public void writeChildrenPosition(final PtNode ptNode, final FormatOptions formatOptions) {
+ final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode, formatOptions);
+ if (formatOptions.mSupportsDynamicUpdate) {
+ mPosition += BinaryDictEncoderUtils.writeSignedChildrenPosition(mBuffer, mPosition,
+ childrenPos);
+ } else {
+ mPosition += BinaryDictEncoderUtils.writeChildrenPosition(mBuffer, mPosition,
+ childrenPos);
+ }
+ }
+
+ @Override
+ public void writeShortcuts(final ArrayList<WeightedString> shortcuts) {
+ if (null == shortcuts || shortcuts.isEmpty()) return;
+
+ final int indexOfShortcutByteSize = mPosition;
+ mPosition += FormatSpec.PTNODE_SHORTCUT_LIST_SIZE_SIZE;
+ final Iterator<WeightedString> shortcutIterator = shortcuts.iterator();
+ while (shortcutIterator.hasNext()) {
+ final WeightedString target = shortcutIterator.next();
+ final int shortcutFlags = BinaryDictEncoderUtils.makeShortcutFlags(
+ shortcutIterator.hasNext(),
+ target.mFrequency);
+ mBuffer[mPosition++] = (byte)shortcutFlags;
+ final int shortcutShift = CharEncoding.writeString(mBuffer, mPosition, target.mWord);
+ mPosition += shortcutShift;
+ }
+ final int shortcutByteSize = mPosition - indexOfShortcutByteSize;
+ if (shortcutByteSize > 0xFFFF) {
+ throw new RuntimeException("Shortcut list too large");
+ }
+ mBuffer[indexOfShortcutByteSize] = (byte)((shortcutByteSize >> 8) & 0xFF);
+ mBuffer[indexOfShortcutByteSize + 1] = (byte)(shortcutByteSize & 0xFF);
+ }
+
+ @Override
+ public void writeBigrams(final ArrayList<WeightedString> bigrams, final FusionDictionary dict) {
+ 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
+ - (mPosition + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE);
+ int bigramFlags = BinaryDictEncoderUtils.makeBigramFlags(bigramIterator.hasNext(),
+ offset, bigram.mFrequency, unigramFrequencyForThisWord, bigram.mWord);
+ mBuffer[mPosition++] = (byte) bigramFlags;
+ mPosition += BinaryDictEncoderUtils.writeChildrenPosition(mBuffer, mPosition,
+ Math.abs(offset));
+ }
+ }
+
+ @Override
+ public void writeForwardLinkAddress(final int forwardLinkAddress) {
+ mBuffer[mPosition++] = (byte) ((forwardLinkAddress >> 16) & 0xFF);
+ mBuffer[mPosition++] = (byte) ((forwardLinkAddress >> 8) & 0xFF);
+ mBuffer[mPosition++] = (byte) (forwardLinkAddress & 0xFF);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java b/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java
index d44660623..e43e74d87 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java
@@ -18,6 +18,7 @@ package com.android.inputmethod.latin.personalization;
import android.content.Context;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.AbstractDictionaryWriter;
import com.android.inputmethod.latin.ExpandableDictionary;
@@ -156,4 +157,10 @@ public class DynamicPersonalizationDictionaryWriter extends AbstractDictionaryWr
public boolean isValidWord(final String word) {
return mExpandableDictionary.isValidWord(word);
}
+
+ @UsedForTesting
+ public boolean isInDictionaryForTests(final String word) {
+ // TODO: Use native method to determine whether the word is in dictionary or not
+ return mBigramList.containsKey(word);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
index a08145b33..5b1d0647b 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
@@ -69,7 +69,7 @@ public abstract class DynamicPredictionDictionaryBase extends ExpandableBinaryDi
mPrefs = sp;
if (mLocale != null && mLocale.length() > 1) {
asyncLoadDictionaryToMemory();
- asyncReloadDictionaryIfRequired();
+ reloadDictionaryIfRequired();
}
}
@@ -196,12 +196,6 @@ public abstract class DynamicPredictionDictionaryBase extends ExpandableBinaryDi
return mLocale;
}
- @UsedForTesting
- /* package for test */ void forceAddWordForTest(
- final String word0, final String word1, final boolean isValid) {
- addToPersonalizationPredictionDictionary(word0, word1, isValid);
- }
-
public void registerUpdateSession(PersonalizationDictionaryUpdateSession session) {
session.setPredictionDictionary(this);
mSessions.add(session);
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
index c8deaf90d..5f702ee3f 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
@@ -52,7 +52,7 @@ public class PersonalizationHelper {
if (DEBUG) {
Log.w(TAG, "Use cached UserHistoryPredictionDictionary for " + locale);
}
- dict.asyncReloadDictionaryIfRequired();
+ dict.reloadDictionaryIfRequired();
return dict;
}
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java
index 6c2c9e26e..4c1803bdf 100644
--- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java
+++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java
@@ -97,6 +97,10 @@ public final class UserHistoryDictionaryBigramList {
return mBigramMap.isEmpty();
}
+ public boolean containsKey(String word) {
+ return mBigramMap.containsKey(word);
+ }
+
public Set<String> keySet() {
return mBigramMap.keySet();
}
diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java
index fd83865ba..8732a59f8 100644
--- a/java/src/com/android/inputmethod/latin/settings/Settings.java
+++ b/java/src/com/android/inputmethod/latin/settings/Settings.java
@@ -44,7 +44,9 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public static final String PREF_VIBRATE_ON = "vibrate_on";
public static final String PREF_SOUND_ON = "sound_on";
public static final String PREF_POPUP_ON = "popup_on";
- public static final String PREF_VOICE_MODE = "voice_mode";
+ // PREF_VOICE_MODE_OBSOLETE is obsolete. Use PREF_VOICE_INPUT_KEY instead.
+ public static final String PREF_VOICE_MODE_OBSOLETE = "voice_mode";
+ public static final String PREF_VOICE_INPUT_KEY = "pref_voice_input_key";
public static final String PREF_CORRECTION_SETTINGS = "correction_settings";
public static final String PREF_EDIT_PERSONAL_DICTIONARY = "edit_personal_dictionary";
public static final String PREF_CONFIGURE_DICTIONARIES_KEY = "configure_dictionaries_key";
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
index 1677e1828..cb7dda655 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
@@ -61,7 +61,7 @@ public final class SettingsFragment extends InputMethodSettingsFragment
DBG_USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS
|| Build.VERSION.SDK_INT <= 18 /* Build.VERSION.JELLY_BEAN_MR2 */;
- private ListPreference mVoicePreference;
+ private CheckBoxPreference mVoiceInputKeyPreference;
private ListPreference mShowCorrectionSuggestionsPreference;
private ListPreference mAutoCorrectionThresholdPreference;
private ListPreference mKeyPreviewPopupDismissDelay;
@@ -107,7 +107,8 @@ public final class SettingsFragment extends InputMethodSettingsFragment
SubtypeLocaleUtils.init(context);
AudioAndHapticFeedbackManager.init(context);
- mVoicePreference = (ListPreference) findPreference(Settings.PREF_VOICE_MODE);
+ mVoiceInputKeyPreference =
+ (CheckBoxPreference) findPreference(Settings.PREF_VOICE_INPUT_KEY);
mShowCorrectionSuggestionsPreference =
(ListPreference) findPreference(Settings.PREF_SHOW_SUGGESTIONS_SETTING);
final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
@@ -166,7 +167,7 @@ public final class SettingsFragment extends InputMethodSettingsFragment
final boolean showVoiceKeyOption = res.getBoolean(
R.bool.config_enable_show_voice_key_option);
if (!showVoiceKeyOption) {
- generalSettings.removePreference(mVoicePreference);
+ generalSettings.removePreference(mVoiceInputKeyPreference);
}
final PreferenceGroup advancedSettings =
@@ -243,10 +244,8 @@ public final class SettingsFragment extends InputMethodSettingsFragment
public void onResume() {
super.onResume();
final boolean isShortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled();
- if (isShortcutImeEnabled) {
- updateVoiceModeSummary();
- } else {
- getPreferenceScreen().removePreference(mVoicePreference);
+ if (!isShortcutImeEnabled) {
+ getPreferenceScreen().removePreference(mVoiceInputKeyPreference);
}
final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
final CheckBoxPreference showSetupWizardIcon =
@@ -287,7 +286,6 @@ public final class SettingsFragment extends InputMethodSettingsFragment
LauncherIconVisibilityManager.updateSetupWizardIconVisibility(getActivity());
}
ensureConsistencyOfAutoCorrectionSettings();
- updateVoiceModeSummary();
updateShowCorrectionSuggestionsSummary();
updateKeyPreviewPopupDelaySummary();
refreshEnablingsOfKeypressSoundAndVibrationSettings(prefs, getResources());
@@ -330,12 +328,6 @@ public final class SettingsFragment extends InputMethodSettingsFragment
lp.setSummary(entries[lp.findIndexOfValue(lp.getValue())]);
}
- private void updateVoiceModeSummary() {
- mVoicePreference.setSummary(
- getResources().getStringArray(R.array.voice_input_modes_summary)
- [mVoicePreference.findIndexOfValue(mVoicePreference.getValue())]);
- }
-
private void refreshEnablingsOfKeypressSoundAndVibrationSettings(
final SharedPreferences sp, final Resources res) {
setPreferenceEnabled(Settings.PREF_VIBRATION_DURATION_SETTINGS,
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index 32730d23f..072bb8731 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -64,7 +64,7 @@ public final class SettingsValues {
public final boolean mVibrateOn;
public final boolean mSoundOn;
public final boolean mKeyPreviewPopupOn;
- private final String mVoiceMode;
+ private final boolean mShowsVoiceInputKey;
public final boolean mIncludesOtherImesInLanguageSwitchList;
public final boolean mShowsLanguageSwitchKey;
public final boolean mUseContactsDict;
@@ -90,8 +90,6 @@ public final class SettingsValues {
public final float mAutoCorrectionThreshold;
public final boolean mCorrectionEnabled;
public final int mSuggestionVisibility;
- private final boolean mVoiceKeyEnabled;
- private final boolean mVoiceKeyOnMain;
public final boolean mBoostPersonalizationDictionaryForDebug;
public final boolean mUseOnlyPersonalizationDictionaryForDebug;
@@ -137,9 +135,7 @@ public final class SettingsValues {
mKeyPreviewPopupOn = Settings.readKeyPreviewPopupEnabled(prefs, res);
mSlidingKeyInputPreviewEnabled = prefs.getBoolean(
Settings.PREF_SLIDING_KEY_INPUT_PREVIEW, true);
- final String voiceModeMain = res.getString(R.string.voice_mode_main);
- final String voiceModeOff = res.getString(R.string.voice_mode_off);
- mVoiceMode = prefs.getString(Settings.PREF_VOICE_MODE, voiceModeMain);
+ mShowsVoiceInputKey = needsToShowVoiceInputKey(prefs, res);
final String autoCorrectionThresholdRawValue = prefs.getString(
Settings.PREF_AUTO_CORRECTION_THRESHOLD,
res.getString(R.string.auto_correction_threshold_mode_index_modest));
@@ -159,8 +155,6 @@ public final class SettingsValues {
mKeyPreviewPopupDismissDelay = Settings.readKeyPreviewPopupDismissDelay(prefs, res);
mAutoCorrectionThreshold = readAutoCorrectionThreshold(res,
autoCorrectionThresholdRawValue);
- mVoiceKeyEnabled = mVoiceMode != null && !mVoiceMode.equals(voiceModeOff);
- mVoiceKeyOnMain = mVoiceMode != null && mVoiceMode.equals(voiceModeMain);
mGestureInputEnabled = Settings.readGestureInputEnabled(prefs, res);
mGestureTrailEnabled = prefs.getBoolean(Settings.PREF_GESTURE_PREVIEW_TRAIL, true);
mGestureFloatingPreviewTextEnabled = prefs.getBoolean(
@@ -201,7 +195,7 @@ public final class SettingsValues {
mSoundOn = true;
mKeyPreviewPopupOn = true;
mSlidingKeyInputPreviewEnabled = true;
- mVoiceMode = "0";
+ mShowsVoiceInputKey = true;
mIncludesOtherImesInLanguageSwitchList = false;
mShowsLanguageSwitchKey = true;
mUseContactsDict = true;
@@ -214,8 +208,6 @@ public final class SettingsValues {
mKeypressSoundVolume = 1;
mKeyPreviewPopupDismissDelay = 70;
mAutoCorrectionThreshold = 1;
- mVoiceKeyEnabled = true;
- mVoiceKeyOnMain = true;
mGestureInputEnabled = true;
mGestureTrailEnabled = true;
mGestureFloatingPreviewTextEnabled = true;
@@ -274,14 +266,10 @@ public final class SettingsValues {
public boolean isVoiceKeyEnabled(final EditorInfo editorInfo) {
final boolean shortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled();
final int inputType = (editorInfo != null) ? editorInfo.inputType : 0;
- return shortcutImeEnabled && mVoiceKeyEnabled
+ return shortcutImeEnabled && mShowsVoiceInputKey
&& !InputTypeUtils.isPasswordInputType(inputType);
}
- public boolean isVoiceKeyOnMain() {
- return mVoiceKeyOnMain;
- }
-
public boolean isLanguageSwitchKeyEnabled() {
if (!mShowsLanguageSwitchKey) {
return false;
@@ -372,4 +360,18 @@ public final class SettingsValues {
}
return autoCorrectionThreshold;
}
+
+ private static boolean needsToShowVoiceInputKey(SharedPreferences prefs, Resources res) {
+ final String voiceModeMain = res.getString(R.string.voice_mode_main);
+ final String voiceMode = prefs.getString(Settings.PREF_VOICE_MODE_OBSOLETE, voiceModeMain);
+ final boolean showsVoiceInputKey = voiceMode == null || voiceMode.equals(voiceModeMain);
+ if (!showsVoiceInputKey) {
+ // Migrate settings from PREF_VOICE_MODE_OBSOLETE to PREF_VOICE_INPUT_KEY
+ // Set voiceModeMain as a value of obsolete voice mode settings.
+ prefs.edit().putString(Settings.PREF_VOICE_MODE_OBSOLETE, voiceModeMain).apply();
+ // Disable voice input key.
+ prefs.edit().putBoolean(Settings.PREF_VOICE_INPUT_KEY, false).apply();
+ }
+ return prefs.getBoolean(Settings.PREF_VOICE_INPUT_KEY, true);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java b/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java
new file mode 100644
index 000000000..c2e97a36f
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This class is a holder of a result of asynchronous computation.
+ *
+ * @param <E> the type of the result.
+ */
+public class AsyncResultHolder<E> {
+
+ private final Object mLock = new Object();
+
+ private E mResult;
+ private final CountDownLatch mLatch;
+
+ public AsyncResultHolder() {
+ mLatch = new CountDownLatch(1);
+ }
+
+ /**
+ * Sets the result value to this holder.
+ *
+ * @param result the value which is set.
+ */
+ public void set(final E result) {
+ synchronized(mLock) {
+ if (mLatch.getCount() > 0) {
+ mResult = result;
+ mLatch.countDown();
+ }
+ }
+ }
+
+ /**
+ * Gets the result value held in this holder.
+ * Causes the current thread to wait unless the value is set or the specified time is elapsed.
+ *
+ * @param defaultValue the default value.
+ * @param timeOut the time to wait.
+ * @return if the result is set until the time limit then the result, otherwise defaultValue.
+ */
+ public E get(final E defaultValue, final long timeOut) {
+ try {
+ if(mLatch.await(timeOut, TimeUnit.MILLISECONDS)) {
+ return mResult;
+ } else {
+ return defaultValue;
+ }
+ } catch (InterruptedException e) {
+ return defaultValue;
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/DebugLogUtils.java b/java/src/com/android/inputmethod/latin/utils/DebugLogUtils.java
index c4ead0ad1..ac654fa65 100644
--- a/java/src/com/android/inputmethod/latin/utils/DebugLogUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/DebugLogUtils.java
@@ -65,12 +65,12 @@ public final class DebugLogUtils {
/**
* Get the stack trace contained in an exception as a human-readable string.
- * @param e the exception
+ * @param t the throwable
* @return the human-readable stack trace
*/
- public static String getStackTrace(final Exception e) {
+ public static String getStackTrace(final Throwable t) {
final StringBuilder sb = new StringBuilder();
- final StackTraceElement[] frames = e.getStackTrace();
+ final StackTraceElement[] frames = t.getStackTrace();
for (int j = 0; j < frames.length; ++j) {
sb.append(frames[j].toString() + "\n");
}
diff --git a/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java b/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java
new file mode 100644
index 000000000..3c1db6529
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import java.util.ArrayDeque;
+import java.util.Queue;
+
+/**
+ * An object that executes submitted tasks using a thread.
+ */
+public class PrioritizedSerialExecutor {
+ public static final String TAG = PrioritizedSerialExecutor.class.getSimpleName();
+
+ private final Object mLock = new Object();
+
+ // The default value of capacities of task queues.
+ private static final int TASK_QUEUE_CAPACITY = 1000;
+ private final Queue<Runnable> mTasks;
+ private final Queue<Runnable> mPrioritizedTasks;
+
+ // The task which is running now.
+ private Runnable mActive;
+
+ public PrioritizedSerialExecutor() {
+ mTasks = new ArrayDeque<Runnable>(TASK_QUEUE_CAPACITY);
+ mPrioritizedTasks = new ArrayDeque<Runnable>(TASK_QUEUE_CAPACITY);
+ }
+
+ /**
+ * Clears all queued tasks.
+ */
+ public void clearAllTasks() {
+ synchronized(mLock) {
+ mTasks.clear();
+ mPrioritizedTasks.clear();
+ }
+ }
+
+ /**
+ * Enqueues the given task into the task queue.
+ * @param r the enqueued task
+ */
+ public void execute(final Runnable r) {
+ synchronized(mLock) {
+ mTasks.offer(r);
+ if (mActive == null) {
+ scheduleNext();
+ }
+ }
+ }
+
+ /**
+ * Enqueues the given task into the prioritized task queue.
+ * @param r the enqueued task
+ */
+ public void executePrioritized(final Runnable r) {
+ synchronized(mLock) {
+ mPrioritizedTasks.offer(r);
+ if (mActive == null) {
+ scheduleNext();
+ }
+ }
+ }
+
+ private boolean fetchNextTasks() {
+ synchronized(mLock) {
+ mActive = mPrioritizedTasks.poll();
+ if (mActive == null) {
+ mActive = mTasks.poll();
+ }
+ return mActive != null;
+ }
+ }
+
+ private void scheduleNext() {
+ synchronized(mLock) {
+ if (!fetchNextTasks()) {
+ return;
+ }
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ do {
+ synchronized(mLock) {
+ if (mActive != null) {
+ mActive.run();
+ }
+ }
+ } while (fetchNextTasks());
+ } finally {
+ scheduleNext();
+ }
+ }
+ }).start();
+ }
+ }
+
+ public void remove(final Runnable r) {
+ synchronized(mLock) {
+ mTasks.remove(r);
+ mPrioritizedTasks.remove(r);
+ }
+ }
+
+ public void replaceAndExecute(final Runnable oldTask, final Runnable newTask) {
+ synchronized(mLock) {
+ if (oldTask != null) remove(oldTask);
+ execute(newTask);
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
index 99788f6f2..05f3061a8 100644
--- a/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
@@ -34,6 +34,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.TreeMap;
+import java.util.concurrent.TimeUnit;
/**
* Reads and writes Binary files for a UserHistoryDictionary.
@@ -43,6 +44,9 @@ import java.util.TreeMap;
public final class UserHistoryDictIOUtils {
private static final String TAG = UserHistoryDictIOUtils.class.getSimpleName();
private static final boolean DEBUG = false;
+ private static final String USES_FORGETTING_CURVE_KEY = "USES_FORGETTING_CURVE";
+ private static final String USES_FORGETTING_CURVE_VALUE = "1";
+ private static final String LAST_UPDATED_TIME_KEY = "date";
public interface OnAddWordListener {
public void setUnigram(final String word, final String shortcutTarget, final int frequency);
@@ -61,6 +65,9 @@ public final class UserHistoryDictIOUtils {
final BigramDictionaryInterface dict, final UserHistoryDictionaryBigramList bigrams,
final FormatOptions formatOptions) {
final FusionDictionary fusionDict = constructFusionDictionary(dict, bigrams);
+ fusionDict.addOptionAttribute(USES_FORGETTING_CURVE_KEY, USES_FORGETTING_CURVE_VALUE);
+ fusionDict.addOptionAttribute(LAST_UPDATED_TIME_KEY,
+ String.valueOf(TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())));
try {
dictEncoder.writeDictionary(fusionDict, formatOptions);
Log.d(TAG, "end writing");