aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/event/CombinerChain.java40
-rw-r--r--java/src/com/android/inputmethod/event/MyanmarReordering.java265
-rw-r--r--java/src/com/android/inputmethod/keyboard/Keyboard.java11
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardLayout.java124
-rw-r--r--java/src/com/android/inputmethod/keyboard/ProximityInfo.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java36
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java2
-rw-r--r--java/src/com/android/inputmethod/latin/DicTraverseSession.java4
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFacilitator.java886
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java947
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java2
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java4
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputMethodManager.java18
-rw-r--r--java/src/com/android/inputmethod/latin/WordComposer.java3
-rw-r--r--java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java3
-rw-r--r--java/src/com/android/inputmethod/latin/settings/AdvancedSettingsFragment.java2
-rw-r--r--java/src/com/android/inputmethod/latin/settings/DebugSettingsFragment.java4
-rw-r--r--java/src/com/android/inputmethod/latin/settings/MultiLingualSettingsFragment.java41
-rw-r--r--java/src/com/android/inputmethod/latin/settings/Settings.java20
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsFragment.java5
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsValues.java8
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsValuesForSuggestion.java4
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java3
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java10
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java3
-rw-r--r--java/src/com/android/inputmethod/latin/utils/FragmentUtils.java2
26 files changed, 1159 insertions, 1290 deletions
diff --git a/java/src/com/android/inputmethod/event/CombinerChain.java b/java/src/com/android/inputmethod/event/CombinerChain.java
index 5858faa09..d77ece8e6 100644
--- a/java/src/com/android/inputmethod/event/CombinerChain.java
+++ b/java/src/com/android/inputmethod/event/CombinerChain.java
@@ -22,7 +22,6 @@ import android.text.TextUtils;
import com.android.inputmethod.latin.common.Constants;
import java.util.ArrayList;
-import java.util.HashMap;
import javax.annotation.Nonnull;
@@ -45,13 +44,6 @@ public class CombinerChain {
private SpannableStringBuilder mStateFeedback;
private final ArrayList<Combiner> mCombiners;
- private static final HashMap<String, Class<? extends Combiner>> IMPLEMENTED_COMBINERS =
- new HashMap<>();
- static {
- IMPLEMENTED_COMBINERS.put("MyanmarReordering", MyanmarReordering.class);
- }
- private static final String COMBINER_SPEC_SEPARATOR = ";";
-
/**
* Create an combiner chain.
*
@@ -61,15 +53,11 @@ public class CombinerChain {
* cursor: we'll start after this.
*
* @param initialText The text that has already been combined so far.
- * @param combinerList A list of combiners to be applied in order.
*/
- public CombinerChain(final String initialText, final Combiner... combinerList) {
+ public CombinerChain(final String initialText) {
mCombiners = new ArrayList<>();
// The dead key combiner is always active, and always first
mCombiners.add(new DeadKeyCombiner());
- for (final Combiner combiner : combinerList) {
- mCombiners.add(combiner);
- }
mCombinedText = new StringBuilder(initialText);
mStateFeedback = new SpannableStringBuilder();
}
@@ -146,30 +134,4 @@ public class CombinerChain {
final SpannableStringBuilder s = new SpannableStringBuilder(mCombinedText);
return s.append(mStateFeedback);
}
-
- public static Combiner[] createCombiners(final String spec) {
- if (TextUtils.isEmpty(spec)) {
- return new Combiner[0];
- }
- final String[] combinerDescriptors = spec.split(COMBINER_SPEC_SEPARATOR);
- final Combiner[] combiners = new Combiner[combinerDescriptors.length];
- int i = 0;
- for (final String combinerDescriptor : combinerDescriptors) {
- final Class<? extends Combiner> combinerClass =
- IMPLEMENTED_COMBINERS.get(combinerDescriptor);
- if (null == combinerClass) {
- throw new RuntimeException("Unknown combiner descriptor: " + combinerDescriptor);
- }
- try {
- combiners[i++] = combinerClass.newInstance();
- } catch (InstantiationException e) {
- throw new RuntimeException("Unable to instantiate combiner: " + combinerDescriptor,
- e);
- } catch (IllegalAccessException e) {
- throw new RuntimeException("Unable to instantiate combiner: " + combinerDescriptor,
- e);
- }
- }
- return combiners;
- }
}
diff --git a/java/src/com/android/inputmethod/event/MyanmarReordering.java b/java/src/com/android/inputmethod/event/MyanmarReordering.java
deleted file mode 100644
index 7bc1630f5..000000000
--- a/java/src/com/android/inputmethod/event/MyanmarReordering.java
+++ /dev/null
@@ -1,265 +0,0 @@
-/*
- * Copyright (C) 2014 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.event;
-
-import com.android.inputmethod.latin.common.Constants;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-
-import javax.annotation.Nonnull;
-
-/**
- * A combiner that reorders input for Myanmar.
- */
-public class MyanmarReordering implements Combiner {
- // U+1031 MYANMAR VOWEL SIGN E
- private final static int VOWEL_E = 0x1031; // Code point for vowel E that we need to reorder
- // U+200C ZERO WIDTH NON-JOINER
- // U+200B ZERO WIDTH SPACE
- private final static int ZERO_WIDTH_NON_JOINER = 0x200B; // should be 0x200C
-
- private final ArrayList<Event> mCurrentEvents = new ArrayList<>();
-
- // List of consonants :
- // U+1000 MYANMAR LETTER KA
- // U+1001 MYANMAR LETTER KHA
- // U+1002 MYANMAR LETTER GA
- // U+1003 MYANMAR LETTER GHA
- // U+1004 MYANMAR LETTER NGA
- // U+1005 MYANMAR LETTER CA
- // U+1006 MYANMAR LETTER CHA
- // U+1007 MYANMAR LETTER JA
- // U+1008 MYANMAR LETTER JHA
- // U+1009 MYANMAR LETTER NYA
- // U+100A MYANMAR LETTER NNYA
- // U+100B MYANMAR LETTER TTA
- // U+100C MYANMAR LETTER TTHA
- // U+100D MYANMAR LETTER DDA
- // U+100E MYANMAR LETTER DDHA
- // U+100F MYANMAR LETTER NNA
- // U+1010 MYANMAR LETTER TA
- // U+1011 MYANMAR LETTER THA
- // U+1012 MYANMAR LETTER DA
- // U+1013 MYANMAR LETTER DHA
- // U+1014 MYANMAR LETTER NA
- // U+1015 MYANMAR LETTER PA
- // U+1016 MYANMAR LETTER PHA
- // U+1017 MYANMAR LETTER BA
- // U+1018 MYANMAR LETTER BHA
- // U+1019 MYANMAR LETTER MA
- // U+101A MYANMAR LETTER YA
- // U+101B MYANMAR LETTER RA
- // U+101C MYANMAR LETTER LA
- // U+101D MYANMAR LETTER WA
- // U+101E MYANMAR LETTER SA
- // U+101F MYANMAR LETTER HA
- // U+1020 MYANMAR LETTER LLA
- // U+103F MYANMAR LETTER GREAT SA
- private static boolean isConsonant(final int codePoint) {
- return (codePoint >= 0x1000 && codePoint <= 0x1020) || 0x103F == codePoint;
- }
-
- // List of medials :
- // U+103B MYANMAR CONSONANT SIGN MEDIAL YA
- // U+103C MYANMAR CONSONANT SIGN MEDIAL RA
- // U+103D MYANMAR CONSONANT SIGN MEDIAL WA
- // U+103E MYANMAR CONSONANT SIGN MEDIAL HA
- // U+105E MYANMAR CONSONANT SIGN MON MEDIAL NA
- // U+105F MYANMAR CONSONANT SIGN MON MEDIAL MA
- // U+1060 MYANMAR CONSONANT SIGN MON MEDIAL LA
- // U+1082 MYANMAR CONSONANT SIGN SHAN MEDIAL WA
- private static int[] MEDIAL_LIST = { 0x103B, 0x103C, 0x103D, 0x103E,
- 0x105E, 0x105F, 0x1060, 0x1082};
- private static boolean isMedial(final int codePoint) {
- return Arrays.binarySearch(MEDIAL_LIST, codePoint) >= 0;
- }
-
- private static boolean isConsonantOrMedial(final int codePoint) {
- return isConsonant(codePoint) || isMedial(codePoint);
- }
-
- private Event getLastEvent() {
- final int size = mCurrentEvents.size();
- if (size <= 0) {
- return null;
- }
- return mCurrentEvents.get(size - 1);
- }
-
- private CharSequence getCharSequence() {
- final StringBuilder s = new StringBuilder();
- for (final Event e : mCurrentEvents) {
- s.appendCodePoint(e.mCodePoint);
- }
- return s;
- }
-
- /**
- * Clears the currently combining stream of events and returns the resulting software text
- * event corresponding to the stream. Optionally adds a new event to the cleared stream.
- * @param newEvent the new event to add to the stream. null if none.
- * @return the resulting software text event. Never null.
- */
- @Nonnull
- private Event clearAndGetResultingEvent(final Event newEvent) {
- final CharSequence combinedText;
- if (mCurrentEvents.size() > 0) {
- combinedText = getCharSequence();
- mCurrentEvents.clear();
- } else {
- combinedText = null;
- }
- if (null != newEvent) {
- mCurrentEvents.add(newEvent);
- }
- return null == combinedText ? Event.createConsumedEvent(newEvent)
- : Event.createSoftwareTextEvent(combinedText, Event.NOT_A_KEY_CODE);
- }
-
- @Override
- @Nonnull
- public Event processEvent(ArrayList<Event> previousEvents, Event newEvent) {
- final int codePoint = newEvent.mCodePoint;
- if (VOWEL_E == codePoint) {
- final Event lastEvent = getLastEvent();
- if (null == lastEvent) {
- mCurrentEvents.add(newEvent);
- return Event.createConsumedEvent(newEvent);
- }
- if (isConsonantOrMedial(lastEvent.mCodePoint)) {
- final Event resultingEvent = clearAndGetResultingEvent(null);
- mCurrentEvents.add(Event.createSoftwareKeypressEvent(ZERO_WIDTH_NON_JOINER,
- Event.NOT_A_KEY_CODE,
- Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
- false /* isKeyRepeat */));
- mCurrentEvents.add(newEvent);
- return resultingEvent;
- }
- // VOWEL_E == lastCodePoint. But if that was anything else this is correct too.
- return clearAndGetResultingEvent(newEvent);
- }
- if (isConsonant(codePoint)) {
- final Event lastEvent = getLastEvent();
- if (null == lastEvent) {
- mCurrentEvents.add(newEvent);
- return Event.createConsumedEvent(newEvent);
- }
- if (VOWEL_E == lastEvent.mCodePoint) {
- final int eventSize = mCurrentEvents.size();
- if (eventSize >= 2
- && mCurrentEvents.get(eventSize - 2).mCodePoint == ZERO_WIDTH_NON_JOINER) {
- // We have a ZWJN before a vowel E. We need to remove the ZWNJ and then
- // reorder the vowel with respect to the consonant.
- mCurrentEvents.remove(eventSize - 1);
- mCurrentEvents.remove(eventSize - 2);
- mCurrentEvents.add(newEvent);
- mCurrentEvents.add(lastEvent);
- return Event.createConsumedEvent(newEvent);
- }
- // If there is already a consonant, then we are starting a new syllable.
- for (int i = eventSize - 2; i >= 0; --i) {
- if (isConsonant(mCurrentEvents.get(i).mCodePoint)) {
- return clearAndGetResultingEvent(newEvent);
- }
- }
- // If we come here, we didn't have a consonant so we reorder
- mCurrentEvents.remove(eventSize - 1);
- mCurrentEvents.add(newEvent);
- mCurrentEvents.add(lastEvent);
- return Event.createConsumedEvent(newEvent);
- }
- // lastCodePoint is a consonant/medial. But if it's something else it's fine
- return clearAndGetResultingEvent(newEvent);
- }
- if (isMedial(codePoint)) {
- final Event lastEvent = getLastEvent();
- if (null == lastEvent) {
- mCurrentEvents.add(newEvent);
- return Event.createConsumedEvent(newEvent);
- }
- if (VOWEL_E == lastEvent.mCodePoint) {
- final int eventSize = mCurrentEvents.size();
- // If there is already a consonant, then we are in the middle of a syllable, and we
- // need to reorder.
- boolean hasConsonant = false;
- for (int i = eventSize - 2; i >= 0; --i) {
- if (isConsonant(mCurrentEvents.get(i).mCodePoint)) {
- hasConsonant = true;
- break;
- }
- }
- if (hasConsonant) {
- mCurrentEvents.remove(eventSize - 1);
- mCurrentEvents.add(newEvent);
- mCurrentEvents.add(lastEvent);
- return Event.createConsumedEvent(newEvent);
- }
- // Otherwise, we just commit everything.
- return clearAndGetResultingEvent(null);
- }
- // lastCodePoint is a consonant/medial. But if it's something else it's fine
- return clearAndGetResultingEvent(newEvent);
- }
- final Event lastEvent = getLastEvent();
- if (Constants.CODE_DELETE == newEvent.mKeyCode && null != lastEvent) {
- final int eventSize = mCurrentEvents.size();
- if (VOWEL_E == lastEvent.mCodePoint) {
- // We have a VOWEL_E at the end. There are four cases.
- // - The vowel is the only code point in the buffer. Remove it.
- // - The vowel is preceded by a ZWNJ. Remove both vowel E and ZWNJ.
- // - The vowel is preceded by a consonant/medial, remove the consonant/medial.
- // - In all other cases, it's strange, so just remove the last code point.
- if (eventSize <= 1) {
- mCurrentEvents.clear();
- } else { // eventSize >= 2
- final int previousCodePoint = mCurrentEvents.get(eventSize - 2).mCodePoint;
- if (previousCodePoint == ZERO_WIDTH_NON_JOINER) {
- mCurrentEvents.remove(eventSize - 1);
- mCurrentEvents.remove(eventSize - 2);
- } else if (isConsonantOrMedial(previousCodePoint)) {
- mCurrentEvents.remove(eventSize - 2);
- } else {
- mCurrentEvents.remove(eventSize - 1);
- }
- }
- return Event.createConsumedEvent(newEvent);
- } else if (eventSize > 0) {
- mCurrentEvents.remove(eventSize - 1);
- return Event.createConsumedEvent(newEvent);
- }
- }
- // This character is not part of the combining scheme, so we should reset everything.
- if (mCurrentEvents.size() > 0) {
- // If we have events in flight, then add the new event and return the resulting event.
- mCurrentEvents.add(newEvent);
- return clearAndGetResultingEvent(null);
- }
- // If we don't have any events in flight, then just pass this one through.
- return newEvent;
- }
-
- @Override
- public CharSequence getCombiningStateFeedback() {
- return getCharSequence();
- }
-
- @Override
- public void reset() {
- mCurrentEvents.clear();
- }
-}
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index 2055a59bb..7318d4738 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -95,6 +95,9 @@ public class Keyboard {
@Nonnull
private final ProximityInfo mProximityInfo;
+ @Nonnull
+ private final KeyboardLayout mKeyboardLayout;
+
private final boolean mProximityCharsCorrectionEnabled;
public Keyboard(@Nonnull final KeyboardParams params) {
@@ -121,6 +124,8 @@ public class Keyboard {
mOccupiedWidth, mOccupiedHeight, mMostCommonKeyWidth, mMostCommonKeyHeight,
mSortedKeys, params.mTouchPositionCorrection);
mProximityCharsCorrectionEnabled = params.mProximityCharsCorrectionEnabled;
+ mKeyboardLayout = KeyboardLayout.newKeyboardLayout(mSortedKeys, mMostCommonKeyWidth,
+ mMostCommonKeyHeight, mOccupiedWidth, mOccupiedHeight);
}
protected Keyboard(@Nonnull final Keyboard keyboard) {
@@ -145,6 +150,7 @@ public class Keyboard {
mProximityInfo = keyboard.mProximityInfo;
mProximityCharsCorrectionEnabled = keyboard.mProximityCharsCorrectionEnabled;
+ mKeyboardLayout = keyboard.mKeyboardLayout;
}
public boolean hasProximityCharsCorrection(final int code) {
@@ -164,6 +170,11 @@ public class Keyboard {
return mProximityInfo;
}
+ @Nonnull
+ public KeyboardLayout getKeyboardLayout() {
+ return mKeyboardLayout;
+ }
+
/**
* Return the sorted list of keys of this keyboard.
* The keys are sorted from top-left to bottom-right order.
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayout.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayout.java
new file mode 100644
index 000000000..d0f32078e
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayout.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.inputmethod.keyboard;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+/**
+ * KeyboardLayout maintains the keyboard layout information.
+ */
+public class KeyboardLayout {
+
+ private final int[] mKeyCodes;
+
+ private final int[] mKeyXCoordinates;
+ private final int[] mKeyYCoordinates;
+
+ private final int[] mKeyWidths;
+ private final int[] mKeyHeights;
+
+ public final int mMostCommonKeyWidth;
+ public final int mMostCommonKeyHeight;
+
+ public final int mKeyboardWidth;
+ public final int mKeyboardHeight;
+
+ public KeyboardLayout(ArrayList<Key> layoutKeys, int mostCommonKeyWidth,
+ int mostCommonKeyHeight, int keyboardWidth, int keyboardHeight) {
+ mMostCommonKeyWidth = mostCommonKeyWidth;
+ mMostCommonKeyHeight = mostCommonKeyHeight;
+ mKeyboardWidth = keyboardWidth;
+ mKeyboardHeight = keyboardHeight;
+
+ mKeyCodes = new int[layoutKeys.size()];
+ mKeyXCoordinates = new int[layoutKeys.size()];
+ mKeyYCoordinates = new int[layoutKeys.size()];
+ mKeyWidths = new int[layoutKeys.size()];
+ mKeyHeights = new int[layoutKeys.size()];
+
+ for (int i = 0; i < layoutKeys.size(); i++) {
+ Key key = layoutKeys.get(i);
+ mKeyCodes[i] = Character.toLowerCase(key.getCode());
+ mKeyXCoordinates[i] = key.getX();
+ mKeyYCoordinates[i] = key.getY();
+ mKeyWidths[i] = key.getWidth();
+ mKeyHeights[i] = key.getHeight();
+ }
+ }
+
+ @UsedForTesting
+ public int[] getKeyCodes() {
+ return mKeyCodes;
+ }
+
+ /**
+ * The x-coordinate for the top-left corner of the keys.
+ *
+ */
+ public int[] getKeyXCoordinates() {
+ return mKeyXCoordinates;
+ }
+
+ /**
+ * The y-coordinate for the top-left corner of the keys.
+ */
+ public int[] getKeyYCoordinates() {
+ return mKeyYCoordinates;
+ }
+
+ /**
+ * The widths of the keys which are smaller than the true hit-area due to the gaps
+ * between keys. The mostCommonKey(Width/Height) represents the true key width/height
+ * including the gaps.
+ */
+ public int[] getKeyWidths() {
+ return mKeyWidths;
+ }
+
+ /**
+ * The heights of the keys which are smaller than the true hit-area due to the gaps
+ * between keys. The mostCommonKey(Width/Height) represents the true key width/height
+ * including the gaps.
+ */
+ public int[] getKeyHeights() {
+ return mKeyHeights;
+ }
+
+ /**
+ * Factory method to create {@link KeyboardLayout} objects.
+ */
+ public static KeyboardLayout newKeyboardLayout(@Nonnull final List<Key> sortedKeys,
+ int mostCommonKeyWidth, int mostCommonKeyHeight,
+ int occupiedWidth, int occupiedHeight) {
+ final ArrayList<Key> layoutKeys = new ArrayList<Key>();
+ for (final Key key : sortedKeys) {
+ if (!ProximityInfo.needsProximityInfo(key)) {
+ continue;
+ }
+ if (key.getCode() != ',') {
+ layoutKeys.add(key);
+ }
+ }
+ return new KeyboardLayout(layoutKeys, mostCommonKeyWidth,
+ mostCommonKeyHeight, occupiedWidth, occupiedHeight);
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index 228b964ea..b9a5eaefb 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -95,7 +95,7 @@ public class ProximityInfo {
private static native void releaseProximityInfoNative(long nativeProximityInfo);
- private static boolean needsProximityInfo(final Key key) {
+ static boolean needsProximityInfo(final Key key) {
// Don't include special keys into ProximityInfo.
return key.getCode() >= Constants.CODE_SPACE;
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
index a81d7ea2a..b50c0a86a 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
@@ -2781,41 +2781,6 @@ public final class KeyboardTextsTable {
/* additional_morekeys_symbols_0 */ "0",
};
- /* Locale my_MM: Burmese (Myanmar) */
- private static final String[] TEXTS_my_MM = {
- /* morekeys_a ~ */
- null, null, null, null,
- /* ~ morekeys_u */
- // Label for "switch to alphabetic" key.
- // U+1000: "က" MYANMAR LETTER KA
- // U+1001: "ခ" MYANMAR LETTER KHA
- // U+1002: "ဂ" MYANMAR LETTER GA
- /* keylabel_to_alpha */ "\u1000\u1001\u1002",
- /* morekeys_i ~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null,
- /* ~ morekeys_nordic_row2_11 */
- /* morekeys_punctuation */ "!autoColumnOrder!9,\u104A,.,?,!,#,),(,/,;,...,',@,:,-,\",+,\\%,&",
- // U+104A: "၊" MYANMAR SIGN LITTLE SECTION
- // U+104B: "။" MYANMAR SIGN SECTION
- /* keyspec_tablet_comma */ "\u104A",
- /* keyspec_period */ "\u104B",
- /* morekeys_period */ null,
- /* keyspec_tablet_period */ "\u104B",
- /* keyspec_swiss_row1_11 ~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null,
- /* ~ keyspec_comma */
- /* morekeys_tablet_comma */ "\\,",
- /* keyhintlabel_period */ "\u104A",
- /* morekeys_question ~ */
- null, null, null, null, null, null, null, null, null, null, null, null, null,
- /* ~ keyspec_south_slavic_row3_8 */
- /* morekeys_tablet_punctuation */ "!autoColumnOrder!8,.,',#,),(,/,;,@,...,:,-,\",+,\\%,&",
- };
-
/* Locale nb: Norwegian Bokmål */
private static final String[] TEXTS_nb = {
// U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
@@ -4189,7 +4154,6 @@ public final class KeyboardTextsTable {
"ml_IN" , TEXTS_ml_IN, /* 2/ 12 Malayalam (India) */
"mn_MN" , TEXTS_mn_MN, /* 2/ 12 Mongolian (Mongolia) */
"mr_IN" , TEXTS_mr_IN, /* 23/ 53 Marathi (India) */
- "my_MM" , TEXTS_my_MM, /* 8/ 98 Burmese (Myanmar) */
"nb" , TEXTS_nb, /* 11/ 55 Norwegian Bokmål */
"ne_NP" , TEXTS_ne_NP, /* 27/ 60 Nepali (Nepal) */
"nl" , TEXTS_nl, /* 9/ 13 Dutch */
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 46cd3b8b2..b0eae0832 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -293,8 +293,6 @@ public final class BinaryDictionary extends Dictionary {
settingsValuesForSuggestion.mBlockPotentiallyOffensive);
session.mNativeSuggestOptions.setSpaceAwareGestureEnabled(
settingsValuesForSuggestion.mSpaceAwareGestureEnabled);
- session.mNativeSuggestOptions.setAdditionalFeaturesOptions(
- settingsValuesForSuggestion.mAdditionalFeaturesSettingValues);
session.mNativeSuggestOptions.setWeightForLocale(weightForLocale);
if (inOutWeightOfLangModelVsSpatialModel != null) {
session.mInputOutputWeightOfLangModelVsSpatialModel[0] =
diff --git a/java/src/com/android/inputmethod/latin/DicTraverseSession.java b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
index aefefd305..e7fd99ee8 100644
--- a/java/src/com/android/inputmethod/latin/DicTraverseSession.java
+++ b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
@@ -18,7 +18,6 @@ package com.android.inputmethod.latin;
import com.android.inputmethod.latin.common.Constants;
import com.android.inputmethod.latin.common.NativeSuggestOptions;
-import com.android.inputmethod.latin.settings.AdditionalFeaturesSettingUtils;
import com.android.inputmethod.latin.utils.JniUtils;
import java.util.Locale;
@@ -44,8 +43,7 @@ public final class DicTraverseSession {
public final int[] mOutputAutoCommitFirstWordConfidence = new int[1];
public final float[] mInputOutputWeightOfLangModelVsSpatialModel = new float[1];
- public final NativeSuggestOptions mNativeSuggestOptions = new NativeSuggestOptions(
- AdditionalFeaturesSettingUtils.ADDITIONAL_FEATURES_SETTINGS_SIZE);
+ public final NativeSuggestOptions mNativeSuggestOptions = new NativeSuggestOptions();
private static native long setDicTraverseSessionNative(String locale, long dictSize);
private static native void initDicTraverseSessionNative(long nativeDicTraverseSession,
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
index b8893a5d8..a0a1d939e 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
@@ -17,272 +17,58 @@
package com.android.inputmethod.latin;
import android.content.Context;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Pair;
import android.view.inputmethod.InputMethodSubtype;
+import android.util.Pair;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.ExpandableBinaryDictionary.UpdateEntriesForInputEventsCallback;
-import com.android.inputmethod.latin.NgramContext.WordInfo;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.common.Constants;
-import com.android.inputmethod.latin.personalization.ContextualDictionary;
import com.android.inputmethod.latin.personalization.PersonalizationDataChunk;
-import com.android.inputmethod.latin.personalization.PersonalizationDictionary;
-import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
-import com.android.inputmethod.latin.utils.DistracterFilter;
-import com.android.inputmethod.latin.utils.DistracterFilterCheckingExactMatchesAndSuggestions;
-import com.android.inputmethod.latin.utils.DistracterFilterCheckingIsInDictionary;
-import com.android.inputmethod.latin.utils.ExecutorUtils;
import com.android.inputmethod.latin.utils.SuggestionResults;
import java.io.File;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
import java.util.Locale;
+import java.util.List;
import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
- * Facilitates interaction with different kinds of dictionaries. Provides APIs
- * to instantiate and select the correct dictionaries (based on language or account),
- * update entries and fetch suggestions.
- *
- * Currently AndroidSpellCheckerService and LatinIME both use DictionaryFacilitator as
- * a client for interacting with dictionaries.
+ * Interface that facilitates interaction with different kinds of dictionaries. Provides APIs to
+ * instantiate and select the correct dictionaries (based on language or account), update entries
+ * and fetch suggestions. Currently AndroidSpellCheckerService and LatinIME both use
+ * DictionaryFacilitator as a client for interacting with dictionaries.
*/
-public class DictionaryFacilitator {
- // TODO: Consolidate dictionaries in native code.
- public static final String TAG = DictionaryFacilitator.class.getSimpleName();
-
- // HACK: This threshold is being used when adding a capitalized entry in the User History
- // dictionary.
- private static final int CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT = 140;
- // How many words we need to type in a row ({@see mConfidenceInMostProbableLanguage}) to
- // declare we are confident the user is typing in the most probable language.
- private static final int CONFIDENCE_THRESHOLD = 3;
-
- private DictionaryGroup[] mDictionaryGroups = new DictionaryGroup[] { new DictionaryGroup() };
- private DictionaryGroup mMostProbableDictionaryGroup = mDictionaryGroups[0];
- private boolean mIsUserDictEnabled = false;
- private volatile CountDownLatch mLatchForWaitingLoadingMainDictionaries = new CountDownLatch(0);
- // To synchronize assigning mDictionaryGroup to ensure closing dictionaries.
- private final Object mLock = new Object();
- private final DistracterFilter mDistracterFilter;
- private final PersonalizationHelperForDictionaryFacilitator mPersonalizationHelper;
-
- private static final String[] DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS =
- new String[] {
- Dictionary.TYPE_MAIN,
- Dictionary.TYPE_USER_HISTORY,
- Dictionary.TYPE_PERSONALIZATION,
- Dictionary.TYPE_USER,
- Dictionary.TYPE_CONTACTS,
- Dictionary.TYPE_CONTEXTUAL
- };
-
- public static final Map<String, Class<? extends ExpandableBinaryDictionary>>
- DICT_TYPE_TO_CLASS = new HashMap<>();
-
- static {
- DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_USER_HISTORY, UserHistoryDictionary.class);
- DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_PERSONALIZATION, PersonalizationDictionary.class);
- DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_USER, UserBinaryDictionary.class);
- DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_CONTACTS, ContactsBinaryDictionary.class);
- DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_CONTEXTUAL, ContextualDictionary.class);
- }
-
- private static final String DICT_FACTORY_METHOD_NAME = "getDictionary";
- private static final Class<?>[] DICT_FACTORY_METHOD_ARG_TYPES =
- new Class[] { Context.class, Locale.class, File.class, String.class, String.class };
-
- private static final String[] SUB_DICT_TYPES =
- Arrays.copyOfRange(DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS, 1 /* start */,
- DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS.length);
-
+public interface DictionaryFacilitator {
/**
* Returns whether this facilitator is exactly for this list of locales.
*
* @param locales the list of locales to test against
*/
- public boolean isForLocales(final Locale[] locales) {
- if (locales.length != mDictionaryGroups.length) {
- return false;
- }
- for (final Locale locale : locales) {
- boolean found = false;
- for (final DictionaryGroup group : mDictionaryGroups) {
- if (locale.equals(group.mLocale)) {
- found = true;
- break;
- }
- }
- if (!found) {
- return false;
- }
- }
- return true;
- }
+ boolean isForLocales(final Locale[] locales);
/**
* Returns whether this facilitator is exactly for this account.
*
* @param account the account to test against.
*/
- public boolean isForAccount(@Nullable final String account) {
- for (final DictionaryGroup group : mDictionaryGroups) {
- if (!TextUtils.equals(group.mAccount, account)) {
- return false;
- }
- }
- return true;
- }
+ boolean isForAccount(@Nullable final String account);
- /**
- * A group of dictionaries that work together for a single language.
- */
- private static class DictionaryGroup {
- // TODO: Add null analysis annotations.
- // TODO: Run evaluation to determine a reasonable value for these constants. The current
- // values are ad-hoc and chosen without any particular care or methodology.
- public static final float WEIGHT_FOR_MOST_PROBABLE_LANGUAGE = 1.0f;
- public static final float WEIGHT_FOR_GESTURING_IN_NOT_MOST_PROBABLE_LANGUAGE = 0.95f;
- public static final float WEIGHT_FOR_TYPING_IN_NOT_MOST_PROBABLE_LANGUAGE = 0.6f;
-
- /**
- * The locale associated with the dictionary group.
- */
- @Nullable public final Locale mLocale;
-
- /**
- * The user account associated with the dictionary group.
- */
- @Nullable public final String mAccount;
-
- @Nullable private Dictionary mMainDict;
- // Confidence that the most probable language is actually the language the user is
- // typing in. For now, this is simply the number of times a word from this language
- // has been committed in a row.
- private int mConfidence = 0;
-
- public float mWeightForTypingInLocale = WEIGHT_FOR_MOST_PROBABLE_LANGUAGE;
- public float mWeightForGesturingInLocale = WEIGHT_FOR_MOST_PROBABLE_LANGUAGE;
- public final ConcurrentHashMap<String, ExpandableBinaryDictionary> mSubDictMap =
- new ConcurrentHashMap<>();
-
- public DictionaryGroup() {
- this(null /* locale */, null /* mainDict */, null /* account */,
- Collections.<String, ExpandableBinaryDictionary>emptyMap() /* subDicts */);
- }
-
- public DictionaryGroup(@Nullable final Locale locale,
- @Nullable final Dictionary mainDict,
- @Nullable final String account,
- final Map<String, ExpandableBinaryDictionary> subDicts) {
- mLocale = locale;
- mAccount = account;
- // The main dictionary can be asynchronously loaded.
- setMainDict(mainDict);
- for (final Map.Entry<String, ExpandableBinaryDictionary> entry : subDicts.entrySet()) {
- setSubDict(entry.getKey(), entry.getValue());
- }
- }
-
- private void setSubDict(final String dictType, final ExpandableBinaryDictionary dict) {
- if (dict != null) {
- mSubDictMap.put(dictType, dict);
- }
- }
-
- public void setMainDict(final Dictionary mainDict) {
- // Close old dictionary if exists. Main dictionary can be assigned multiple times.
- final Dictionary oldDict = mMainDict;
- mMainDict = mainDict;
- if (oldDict != null && mainDict != oldDict) {
- oldDict.close();
- }
- }
-
- public Dictionary getDict(final String dictType) {
- if (Dictionary.TYPE_MAIN.equals(dictType)) {
- return mMainDict;
- }
- return getSubDict(dictType);
- }
-
- public ExpandableBinaryDictionary getSubDict(final String dictType) {
- return mSubDictMap.get(dictType);
- }
-
- public boolean hasDict(final String dictType, @Nullable final String account) {
- if (Dictionary.TYPE_MAIN.equals(dictType)) {
- return mMainDict != null;
- }
- if (Dictionary.TYPE_USER_HISTORY.equals(dictType) &&
- !TextUtils.equals(account, mAccount)) {
- // If the dictionary type is user history, & if the account doesn't match,
- // return immediately. If the account matches, continue looking it up in the
- // sub dictionary map.
- return false;
- }
- return mSubDictMap.containsKey(dictType);
- }
-
- public void closeDict(final String dictType) {
- final Dictionary dict;
- if (Dictionary.TYPE_MAIN.equals(dictType)) {
- dict = mMainDict;
- } else {
- dict = mSubDictMap.remove(dictType);
- }
- if (dict != null) {
- dict.close();
- }
- }
+ interface DictionaryInitializationListener {
+ void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable);
}
- public interface DictionaryInitializationListener {
- public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable);
- }
-
- public DictionaryFacilitator() {
- mDistracterFilter = DistracterFilter.EMPTY_DISTRACTER_FILTER;
- mPersonalizationHelper = null;
- }
-
- public DictionaryFacilitator(final Context context) {
- mDistracterFilter = new DistracterFilterCheckingExactMatchesAndSuggestions(context);
- mPersonalizationHelper =
- new PersonalizationHelperForDictionaryFacilitator(context, mDistracterFilter);
- }
-
- public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes) {
- mDistracterFilter.updateEnabledSubtypes(enabledSubtypes);
- mPersonalizationHelper.updateEnabledSubtypes(enabledSubtypes);
- }
+ void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes);
// TODO: remove this, it's confusing with seamless multiple language switching
- public void setIsMonolingualUser(final boolean isMonolingualUser) {
- mPersonalizationHelper.setIsMonolingualUser(isMonolingualUser);
- }
+ void setIsMonolingualUser(final boolean isMonolingualUser);
- public boolean isActive() {
- return null != mDictionaryGroups[0].mLocale;
- }
+ boolean isActive();
/**
* Returns the most probable locale among all currently active locales. BE CAREFUL using this.
@@ -292,660 +78,96 @@ public class DictionaryFacilitator {
* string.
* @return the most probable locale
*/
- public Locale getMostProbableLocale() {
- return getDictionaryGroupForMostProbableLanguage().mLocale;
- }
+ Locale getMostProbableLocale();
- public Locale[] getLocales() {
- final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
- final Locale[] locales = new Locale[dictionaryGroups.length];
- for (int i = 0; i < dictionaryGroups.length; ++i) {
- locales[i] = dictionaryGroups[i].mLocale;
- }
- return locales;
- }
+ Locale[] getLocales();
- private DictionaryGroup getDictionaryGroupForMostProbableLanguage() {
- return mMostProbableDictionaryGroup;
- }
+ void switchMostProbableLanguage(@Nullable final Locale locale);
- public void switchMostProbableLanguage(@Nullable final Locale locale) {
- if (null == locale) {
- // In many cases, there is no locale to a committed word. For example, a typed word
- // that is in none of the currently active dictionaries but still does not
- // auto-correct to anything has no locale. In this case we simply do not change
- // the most probable language and do not touch confidence.
- return;
- }
- final DictionaryGroup newMostProbableDictionaryGroup =
- findDictionaryGroupWithLocale(mDictionaryGroups, locale);
- if (null == newMostProbableDictionaryGroup) {
- // It seems this may happen as a race condition; pressing the globe key and space
- // in quick succession could commit a word out of a dictionary that's not in the
- // facilitator any more. In this case, just not changing things is fine.
- return;
- }
- if (newMostProbableDictionaryGroup == mMostProbableDictionaryGroup) {
- ++newMostProbableDictionaryGroup.mConfidence;
- } else {
- mMostProbableDictionaryGroup.mWeightForTypingInLocale =
- DictionaryGroup.WEIGHT_FOR_TYPING_IN_NOT_MOST_PROBABLE_LANGUAGE;
- mMostProbableDictionaryGroup.mWeightForGesturingInLocale =
- DictionaryGroup.WEIGHT_FOR_GESTURING_IN_NOT_MOST_PROBABLE_LANGUAGE;
- mMostProbableDictionaryGroup.mConfidence = 0;
- newMostProbableDictionaryGroup.mWeightForTypingInLocale =
- DictionaryGroup.WEIGHT_FOR_MOST_PROBABLE_LANGUAGE;
- newMostProbableDictionaryGroup.mWeightForGesturingInLocale =
- DictionaryGroup.WEIGHT_FOR_MOST_PROBABLE_LANGUAGE;
- mMostProbableDictionaryGroup = newMostProbableDictionaryGroup;
- }
- }
+ boolean isConfidentAboutCurrentLanguageBeing(final Locale mLocale);
- public boolean isConfidentAboutCurrentLanguageBeing(final Locale mLocale) {
- final DictionaryGroup mostProbableDictionaryGroup = mMostProbableDictionaryGroup;
- if (!mostProbableDictionaryGroup.mLocale.equals(mLocale)) {
- return false;
- }
- if (mDictionaryGroups.length <= 1) {
- return true;
- }
- return mostProbableDictionaryGroup.mConfidence >= CONFIDENCE_THRESHOLD;
- }
-
- @Nullable
- private static ExpandableBinaryDictionary getSubDict(final String dictType,
- final Context context, final Locale locale, final File dictFile,
- final String dictNamePrefix, @Nullable final String account) {
- final Class<? extends ExpandableBinaryDictionary> dictClass =
- DICT_TYPE_TO_CLASS.get(dictType);
- if (dictClass == null) {
- return null;
- }
- try {
- final Method factoryMethod = dictClass.getMethod(DICT_FACTORY_METHOD_NAME,
- DICT_FACTORY_METHOD_ARG_TYPES);
- final Object dict = factoryMethod.invoke(null /* obj */,
- new Object[] { context, locale, dictFile, dictNamePrefix, account });
- return (ExpandableBinaryDictionary) dict;
- } catch (final NoSuchMethodException | SecurityException | IllegalAccessException
- | IllegalArgumentException | InvocationTargetException e) {
- Log.e(TAG, "Cannot create dictionary: " + dictType, e);
- return null;
- }
- }
-
- public void resetDictionaries(final Context context, final Locale[] newLocales,
+ void resetDictionaries(final Context context, final Locale[] newLocales,
final boolean useContactsDict, final boolean usePersonalizedDicts,
final boolean forceReloadMainDictionary,
@Nullable final String account,
- final DictionaryInitializationListener listener) {
- resetDictionariesWithDictNamePrefix(context, newLocales, useContactsDict,
- usePersonalizedDicts, forceReloadMainDictionary, listener, "" /* dictNamePrefix */,
- account);
- }
+ final DictionaryInitializationListener listener);
- @Nullable
- static DictionaryGroup findDictionaryGroupWithLocale(final DictionaryGroup[] dictionaryGroups,
- final Locale locale) {
- for (DictionaryGroup dictionaryGroup : dictionaryGroups) {
- if (locale.equals(dictionaryGroup.mLocale)) {
- return dictionaryGroup;
- }
- }
- return null;
- }
-
- public void resetDictionariesWithDictNamePrefix(final Context context,
+ void resetDictionariesWithDictNamePrefix(final Context context,
final Locale[] newLocales,
final boolean useContactsDict,
final boolean usePersonalizedDicts,
final boolean forceReloadMainDictionary,
@Nullable final DictionaryInitializationListener listener,
final String dictNamePrefix,
- @Nullable final String account) {
- final HashMap<Locale, ArrayList<String>> existingDictionariesToCleanup = new HashMap<>();
- // TODO: Make subDictTypesToUse configurable by resource or a static final list.
- final HashSet<String> subDictTypesToUse = new HashSet<>();
- subDictTypesToUse.add(Dictionary.TYPE_USER);
- if (useContactsDict) {
- subDictTypesToUse.add(Dictionary.TYPE_CONTACTS);
- }
- if (usePersonalizedDicts) {
- subDictTypesToUse.add(Dictionary.TYPE_USER_HISTORY);
- subDictTypesToUse.add(Dictionary.TYPE_PERSONALIZATION);
- subDictTypesToUse.add(Dictionary.TYPE_CONTEXTUAL);
- }
-
- // Gather all dictionaries. We'll remove them from the list to clean up later.
- for (final Locale newLocale : newLocales) {
- final ArrayList<String> dictTypeForLocale = new ArrayList<>();
- existingDictionariesToCleanup.put(newLocale, dictTypeForLocale);
- final DictionaryGroup currentDictionaryGroupForLocale =
- findDictionaryGroupWithLocale(mDictionaryGroups, newLocale);
- if (null == currentDictionaryGroupForLocale) {
- continue;
- }
- for (final String dictType : SUB_DICT_TYPES) {
- if (currentDictionaryGroupForLocale.hasDict(dictType, account)) {
- dictTypeForLocale.add(dictType);
- }
- }
- if (currentDictionaryGroupForLocale.hasDict(Dictionary.TYPE_MAIN, account)) {
- dictTypeForLocale.add(Dictionary.TYPE_MAIN);
- }
- }
-
- final DictionaryGroup[] newDictionaryGroups = new DictionaryGroup[newLocales.length];
- for (int i = 0; i < newLocales.length; ++i) {
- final Locale newLocale = newLocales[i];
- final DictionaryGroup dictionaryGroupForLocale =
- findDictionaryGroupWithLocale(mDictionaryGroups, newLocale);
- final ArrayList<String> dictTypesToCleanupForLocale =
- existingDictionariesToCleanup.get(newLocale);
- final boolean noExistingDictsForThisLocale = (null == dictionaryGroupForLocale);
-
- final Dictionary mainDict;
- if (forceReloadMainDictionary || noExistingDictsForThisLocale
- || !dictionaryGroupForLocale.hasDict(Dictionary.TYPE_MAIN, account)) {
- mainDict = null;
- } else {
- mainDict = dictionaryGroupForLocale.getDict(Dictionary.TYPE_MAIN);
- dictTypesToCleanupForLocale.remove(Dictionary.TYPE_MAIN);
- }
-
- final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>();
- for (final String subDictType : subDictTypesToUse) {
- final ExpandableBinaryDictionary subDict;
- if (noExistingDictsForThisLocale
- || !dictionaryGroupForLocale.hasDict(subDictType, account)) {
- // Create a new dictionary.
- subDict = getSubDict(subDictType, context, newLocale, null /* dictFile */,
- dictNamePrefix, account);
- } else {
- // Reuse the existing dictionary, and don't close it at the end
- subDict = dictionaryGroupForLocale.getSubDict(subDictType);
- dictTypesToCleanupForLocale.remove(subDictType);
- }
- subDicts.put(subDictType, subDict);
- }
- newDictionaryGroups[i] = new DictionaryGroup(newLocale, mainDict, account, subDicts);
- }
-
- // Replace Dictionaries.
- final DictionaryGroup[] oldDictionaryGroups;
- synchronized (mLock) {
- oldDictionaryGroups = mDictionaryGroups;
- mDictionaryGroups = newDictionaryGroups;
- mMostProbableDictionaryGroup = newDictionaryGroups[0];
- mIsUserDictEnabled = UserBinaryDictionary.isEnabled(context);
- if (hasAtLeastOneUninitializedMainDictionary()) {
- asyncReloadUninitializedMainDictionaries(context, newLocales, listener);
- }
- }
- if (listener != null) {
- listener.onUpdateMainDictionaryAvailability(hasAtLeastOneInitializedMainDictionary());
- }
-
- // Clean up old dictionaries.
- for (final Locale localeToCleanUp : existingDictionariesToCleanup.keySet()) {
- final ArrayList<String> dictTypesToCleanUp =
- existingDictionariesToCleanup.get(localeToCleanUp);
- final DictionaryGroup dictionarySetToCleanup =
- findDictionaryGroupWithLocale(oldDictionaryGroups, localeToCleanUp);
- for (final String dictType : dictTypesToCleanUp) {
- dictionarySetToCleanup.closeDict(dictType);
- }
- }
- }
-
- private void asyncReloadUninitializedMainDictionaries(final Context context,
- final Locale[] locales, final DictionaryInitializationListener listener) {
- final CountDownLatch latchForWaitingLoadingMainDictionary = new CountDownLatch(1);
- mLatchForWaitingLoadingMainDictionaries = latchForWaitingLoadingMainDictionary;
- ExecutorUtils.getExecutor("InitializeBinaryDictionary").execute(new Runnable() {
- @Override
- public void run() {
- doReloadUninitializedMainDictionaries(
- context, locales, listener, latchForWaitingLoadingMainDictionary);
- }
- });
- }
-
- void doReloadUninitializedMainDictionaries(final Context context, final Locale[] locales,
- final DictionaryInitializationListener listener,
- final CountDownLatch latchForWaitingLoadingMainDictionary) {
- for (final Locale locale : locales) {
- final DictionaryGroup dictionaryGroup =
- findDictionaryGroupWithLocale(mDictionaryGroups, locale);
- if (null == dictionaryGroup) {
- // This should never happen, but better safe than crashy
- Log.w(TAG, "Expected a dictionary group for " + locale + " but none found");
- continue;
- }
- final Dictionary mainDict =
- DictionaryFactory.createMainDictionaryFromManager(context, locale);
- synchronized (mLock) {
- if (locale.equals(dictionaryGroup.mLocale)) {
- dictionaryGroup.setMainDict(mainDict);
- } else {
- // Dictionary facilitator has been reset for another locale.
- mainDict.close();
- }
- }
- }
- if (listener != null) {
- listener.onUpdateMainDictionaryAvailability(
- hasAtLeastOneInitializedMainDictionary());
- }
- latchForWaitingLoadingMainDictionary.countDown();
- }
+ @Nullable final String account);
@UsedForTesting
- public void resetDictionariesForTesting(final Context context, final Locale[] locales,
+ void resetDictionariesForTesting(final Context context, final Locale[] locales,
final ArrayList<String> dictionaryTypes, final HashMap<String, File> dictionaryFiles,
final Map<String, Map<String, String>> additionalDictAttributes,
- @Nullable final String account) {
- Dictionary mainDictionary = null;
- final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>();
-
- final DictionaryGroup[] dictionaryGroups = new DictionaryGroup[locales.length];
- for (int i = 0; i < locales.length; ++i) {
- final Locale locale = locales[i];
- for (final String dictType : dictionaryTypes) {
- if (dictType.equals(Dictionary.TYPE_MAIN)) {
- mainDictionary = DictionaryFactory.createMainDictionaryFromManager(context,
- locale);
- } else {
- final File dictFile = dictionaryFiles.get(dictType);
- final ExpandableBinaryDictionary dict = getSubDict(
- dictType, context, locale, dictFile, "" /* dictNamePrefix */, account);
- if (additionalDictAttributes.containsKey(dictType)) {
- dict.clearAndFlushDictionaryWithAdditionalAttributes(
- additionalDictAttributes.get(dictType));
- }
- if (dict == null) {
- throw new RuntimeException("Unknown dictionary type: " + dictType);
- }
- dict.reloadDictionaryIfRequired();
- dict.waitAllTasksForTests();
- subDicts.put(dictType, dict);
- }
- }
- dictionaryGroups[i] = new DictionaryGroup(locale, mainDictionary, account, subDicts);
- }
- mDictionaryGroups = dictionaryGroups;
- mMostProbableDictionaryGroup = dictionaryGroups[0];
- }
+ @Nullable final String account);
- public void closeDictionaries() {
- final DictionaryGroup[] dictionaryGroups;
- synchronized (mLock) {
- dictionaryGroups = mDictionaryGroups;
- mMostProbableDictionaryGroup = new DictionaryGroup();
- mDictionaryGroups = new DictionaryGroup[] { mMostProbableDictionaryGroup };
- }
- for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
- for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
- dictionaryGroup.closeDict(dictType);
- }
- }
- mDistracterFilter.close();
- if (mPersonalizationHelper != null) {
- mPersonalizationHelper.close();
- }
- }
+ void closeDictionaries();
@UsedForTesting
- public ExpandableBinaryDictionary getSubDictForTesting(final String dictName) {
- return mMostProbableDictionaryGroup.getSubDict(dictName);
- }
+ ExpandableBinaryDictionary getSubDictForTesting(final String dictName);
- // The main dictionaries are loaded asynchronously. Don't cache the return value
+ // The main dictionaries are loaded asynchronously. Don't cache the return value
// of these methods.
- public boolean hasAtLeastOneInitializedMainDictionary() {
- final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
- for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
- final Dictionary mainDict = dictionaryGroup.getDict(Dictionary.TYPE_MAIN);
- if (mainDict != null && mainDict.isInitialized()) {
- return true;
- }
- }
- return false;
- }
+ boolean hasAtLeastOneInitializedMainDictionary();
- public boolean hasAtLeastOneUninitializedMainDictionary() {
- final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
- for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
- final Dictionary mainDict = dictionaryGroup.getDict(Dictionary.TYPE_MAIN);
- if (mainDict == null || !mainDict.isInitialized()) {
- return true;
- }
- }
- return false;
- }
+ boolean hasAtLeastOneUninitializedMainDictionary();
- public boolean hasPersonalizationDictionary() {
- final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
- for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
- if (dictionaryGroup.hasDict(Dictionary.TYPE_PERSONALIZATION, null /* account */)) {
- return true;
- }
- }
- return false;
- }
+ boolean hasPersonalizationDictionary();
- public void flushPersonalizationDictionary() {
- final HashSet<ExpandableBinaryDictionary> personalizationDictsUsedForSuggestion =
- new HashSet<>();
- final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
- for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
- final ExpandableBinaryDictionary personalizationDictUsedForSuggestion =
- dictionaryGroup.getSubDict(Dictionary.TYPE_PERSONALIZATION);
- personalizationDictsUsedForSuggestion.add(personalizationDictUsedForSuggestion);
- }
- mPersonalizationHelper.flushPersonalizationDictionariesToUpdate(
- personalizationDictsUsedForSuggestion);
- mDistracterFilter.close();
- }
+ void flushPersonalizationDictionary();
- public void waitForLoadingMainDictionaries(final long timeout, final TimeUnit unit)
- throws InterruptedException {
- mLatchForWaitingLoadingMainDictionaries.await(timeout, unit);
- }
+ void waitForLoadingMainDictionaries(final long timeout, final TimeUnit unit)
+ throws InterruptedException;
@UsedForTesting
- public void waitForLoadingDictionariesForTesting(final long timeout, final TimeUnit unit)
- throws InterruptedException {
- waitForLoadingMainDictionaries(timeout, unit);
- final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
- for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
- for (final ExpandableBinaryDictionary dict : dictionaryGroup.mSubDictMap.values()) {
- dict.waitAllTasksForTests();
- }
- }
- }
+ void waitForLoadingDictionariesForTesting(final long timeout, final TimeUnit unit)
+ throws InterruptedException;
- public boolean isUserDictionaryEnabled() {
- return mIsUserDictEnabled;
- }
+ boolean isUserDictionaryEnabled();
- public void addWordToUserDictionary(final Context context, final String word) {
- final Locale locale = getMostProbableLocale();
- if (locale == null) {
- return;
- }
- // TODO: add a toast telling what language this is being added to?
- UserBinaryDictionary.addWordToUserDictionary(context, locale, word);
- }
+ void addWordToUserDictionary(final Context context, final String word);
- public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized,
+ void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized,
@Nonnull final NgramContext ngramContext, final int timeStampInSeconds,
- final boolean blockPotentiallyOffensive) {
- final DictionaryGroup dictionaryGroup = getDictionaryGroupForMostProbableLanguage();
- final String[] words = suggestion.split(Constants.WORD_SEPARATOR);
- NgramContext ngramContextForCurrentWord = ngramContext;
- for (int i = 0; i < words.length; i++) {
- final String currentWord = words[i];
- final boolean wasCurrentWordAutoCapitalized = (i == 0) ? wasAutoCapitalized : false;
- addWordToUserHistory(dictionaryGroup, ngramContextForCurrentWord, currentWord,
- wasCurrentWordAutoCapitalized, timeStampInSeconds, blockPotentiallyOffensive);
- ngramContextForCurrentWord =
- ngramContextForCurrentWord.getNextNgramContext(new WordInfo(currentWord));
- }
- }
-
- private void addWordToUserHistory(final DictionaryGroup dictionaryGroup,
- final NgramContext ngramContext, final String word, final boolean wasAutoCapitalized,
- final int timeStampInSeconds, final boolean blockPotentiallyOffensive) {
- final ExpandableBinaryDictionary userHistoryDictionary =
- dictionaryGroup.getSubDict(Dictionary.TYPE_USER_HISTORY);
- if (userHistoryDictionary == null
- || !isConfidentAboutCurrentLanguageBeing(userHistoryDictionary.mLocale)) {
- return;
- }
- final int maxFreq = getFrequency(word);
- if (maxFreq == 0 && blockPotentiallyOffensive) {
- return;
- }
- final String lowerCasedWord = word.toLowerCase(dictionaryGroup.mLocale);
- final String secondWord;
- if (wasAutoCapitalized) {
- if (isValidWord(word, false /* ignoreCase */)
- && !isValidWord(lowerCasedWord, false /* ignoreCase */)) {
- // If the word was auto-capitalized and exists only as a capitalized word in the
- // dictionary, then we must not downcase it before registering it. For example,
- // the name of the contacts in start-of-sentence position would come here with the
- // wasAutoCapitalized flag: if we downcase it, we'd register a lower-case version
- // of that contact's name which would end up popping in suggestions.
- secondWord = word;
- } else {
- // If however the word is not in the dictionary, or exists as a lower-case word
- // only, then we consider that was a lower-case word that had been auto-capitalized.
- secondWord = lowerCasedWord;
- }
- } else {
- // HACK: We'd like to avoid adding the capitalized form of common words to the User
- // History dictionary in order to avoid suggesting them until the dictionary
- // consolidation is done.
- // TODO: Remove this hack when ready.
- final int lowerCaseFreqInMainDict = dictionaryGroup.hasDict(Dictionary.TYPE_MAIN,
- null /* account */) ?
- dictionaryGroup.getDict(Dictionary.TYPE_MAIN).getFrequency(lowerCasedWord) :
- Dictionary.NOT_A_PROBABILITY;
- if (maxFreq < lowerCaseFreqInMainDict
- && lowerCaseFreqInMainDict >= CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT) {
- // Use lower cased word as the word can be a distracter of the popular word.
- secondWord = lowerCasedWord;
- } else {
- secondWord = word;
- }
- }
- // We demote unrecognized words (frequency < 0, below) by specifying them as "invalid".
- // We don't add words with 0-frequency (assuming they would be profanity etc.).
- final boolean isValid = maxFreq > 0;
- UserHistoryDictionary.addToDictionary(userHistoryDictionary, ngramContext, secondWord,
- isValid, timeStampInSeconds,
- new DistracterFilterCheckingIsInDictionary(
- mDistracterFilter, userHistoryDictionary));
- }
+ final boolean blockPotentiallyOffensive);
- private void removeWord(final String dictName, final String word) {
- final ExpandableBinaryDictionary dictionary =
- getDictionaryGroupForMostProbableLanguage().getSubDict(dictName);
- if (dictionary != null) {
- dictionary.removeUnigramEntryDynamically(word);
- }
- }
-
- public void removeWordFromPersonalizedDicts(final String word) {
- removeWord(Dictionary.TYPE_USER_HISTORY, word);
- removeWord(Dictionary.TYPE_PERSONALIZATION, word);
- removeWord(Dictionary.TYPE_CONTEXTUAL, word);
- }
+ void removeWordFromPersonalizedDicts(final String word);
// TODO: Revise the way to fusion suggestion results.
- public SuggestionResults getSuggestionResults(final WordComposer composer,
+ SuggestionResults getSuggestionResults(final WordComposer composer,
final NgramContext ngramContext, final long proximityInfoHandle,
- final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId) {
- final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
- final SuggestionResults suggestionResults = new SuggestionResults(
- SuggestedWords.MAX_SUGGESTIONS, ngramContext.isBeginningOfSentenceContext());
- final float[] weightOfLangModelVsSpatialModel =
- new float[] { Dictionary.NOT_A_WEIGHT_OF_LANG_MODEL_VS_SPATIAL_MODEL };
- for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
- for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
- final Dictionary dictionary = dictionaryGroup.getDict(dictType);
- if (null == dictionary) continue;
- final float weightForLocale = composer.isBatchMode()
- ? dictionaryGroup.mWeightForGesturingInLocale
- : dictionaryGroup.mWeightForTypingInLocale;
- final ArrayList<SuggestedWordInfo> dictionarySuggestions =
- dictionary.getSuggestions(composer.getComposedDataSnapshot(), ngramContext,
- proximityInfoHandle, settingsValuesForSuggestion, sessionId,
- weightForLocale, weightOfLangModelVsSpatialModel);
- if (null == dictionarySuggestions) continue;
- suggestionResults.addAll(dictionarySuggestions);
- if (null != suggestionResults.mRawSuggestions) {
- suggestionResults.mRawSuggestions.addAll(dictionarySuggestions);
- }
- }
- }
- return suggestionResults;
- }
+ final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId);
- public boolean isValidWord(final String word, final boolean ignoreCase) {
- if (TextUtils.isEmpty(word)) {
- return false;
- }
- final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
- for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
- if (dictionaryGroup.mLocale == null) {
- continue;
- }
- final String lowerCasedWord = word.toLowerCase(dictionaryGroup.mLocale);
- for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
- final Dictionary dictionary = dictionaryGroup.getDict(dictType);
- // Ideally the passed map would come out of a {@link java.util.concurrent.Future} and
- // would be immutable once it's finished initializing, but concretely a null test is
- // probably good enough for the time being.
- if (null == dictionary) continue;
- if (dictionary.isValidWord(word)
- || (ignoreCase && dictionary.isValidWord(lowerCasedWord))) {
- return true;
- }
- }
- }
- return false;
- }
+ boolean isValidWord(final String word, final boolean ignoreCase);
- private int getFrequencyInternal(final String word,
- final boolean isGettingMaxFrequencyOfExactMatches) {
- if (TextUtils.isEmpty(word)) {
- return Dictionary.NOT_A_PROBABILITY;
- }
- int maxFreq = Dictionary.NOT_A_PROBABILITY;
- final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
- for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
- for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
- final Dictionary dictionary = dictionaryGroup.getDict(dictType);
- if (dictionary == null) continue;
- final int tempFreq;
- if (isGettingMaxFrequencyOfExactMatches) {
- tempFreq = dictionary.getMaxFrequencyOfExactMatches(word);
- } else {
- tempFreq = dictionary.getFrequency(word);
- }
- if (tempFreq >= maxFreq) {
- maxFreq = tempFreq;
- }
- }
- }
- return maxFreq;
- }
+ int getFrequency(final String word);
- public int getFrequency(final String word) {
- return getFrequencyInternal(word, false /* isGettingMaxFrequencyOfExactMatches */);
- }
-
- public int getMaxFrequencyOfExactMatches(final String word) {
- return getFrequencyInternal(word, true /* isGettingMaxFrequencyOfExactMatches */);
- }
+ int getMaxFrequencyOfExactMatches(final String word);
- private void clearSubDictionary(final String dictName) {
- final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
- for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
- final ExpandableBinaryDictionary dictionary = dictionaryGroup.getSubDict(dictName);
- if (dictionary != null) {
- dictionary.clear();
- }
- }
- }
-
- public void clearUserHistoryDictionary() {
- clearSubDictionary(Dictionary.TYPE_USER_HISTORY);
- }
+ void clearUserHistoryDictionary();
// This method gets called only when the IME receives a notification to remove the
// personalization dictionary.
- public void clearPersonalizationDictionary() {
- clearSubDictionary(Dictionary.TYPE_PERSONALIZATION);
- mPersonalizationHelper.clearDictionariesToUpdate();
- }
+ void clearPersonalizationDictionary();
- public void clearContextualDictionary() {
- clearSubDictionary(Dictionary.TYPE_CONTEXTUAL);
- }
+ void clearContextualDictionary();
- public void addEntriesToPersonalizationDictionary(
+ void addEntriesToPersonalizationDictionary(
final PersonalizationDataChunk personalizationDataChunk,
final SpacingAndPunctuations spacingAndPunctuations,
- final UpdateEntriesForInputEventsCallback callback) {
- mPersonalizationHelper.updateEntriesOfPersonalizationDictionaries(
- getMostProbableLocale(), personalizationDataChunk, spacingAndPunctuations,
- callback);
- }
+ final UpdateEntriesForInputEventsCallback callback);
@UsedForTesting
- public void addPhraseToContextualDictionary(final String[] phrase, final int probability,
- final int bigramProbabilityForWords, final int bigramProbabilityForPhrases) {
- // TODO: we're inserting the phrase into the dictionary for the active language. Rethink
- // this a bit from a theoretical point of view.
- final ExpandableBinaryDictionary contextualDict =
- getDictionaryGroupForMostProbableLanguage().getSubDict(Dictionary.TYPE_CONTEXTUAL);
- if (contextualDict == null) {
- return;
- }
- NgramContext ngramContext = NgramContext.BEGINNING_OF_SENTENCE;
- for (int i = 0; i < phrase.length; i++) {
- final String[] subPhrase = Arrays.copyOfRange(phrase, i /* start */, phrase.length);
- final String subPhraseStr = TextUtils.join(Constants.WORD_SEPARATOR, subPhrase);
- contextualDict.addUnigramEntryWithCheckingDistracter(
- subPhraseStr, probability, null /* shortcutTarget */,
- Dictionary.NOT_A_PROBABILITY /* shortcutFreq */,
- false /* isNotAWord */, false /* isPossiblyOffensive */,
- BinaryDictionary.NOT_A_VALID_TIMESTAMP,
- DistracterFilter.EMPTY_DISTRACTER_FILTER);
- contextualDict.addNgramEntry(ngramContext, subPhraseStr,
- bigramProbabilityForPhrases, BinaryDictionary.NOT_A_VALID_TIMESTAMP);
-
- if (i < phrase.length - 1) {
- contextualDict.addUnigramEntryWithCheckingDistracter(
- phrase[i], probability, null /* shortcutTarget */,
- Dictionary.NOT_A_PROBABILITY /* shortcutFreq */,
- false /* isNotAWord */, false /* isPossiblyOffensive */,
- BinaryDictionary.NOT_A_VALID_TIMESTAMP,
- DistracterFilter.EMPTY_DISTRACTER_FILTER);
- contextualDict.addNgramEntry(ngramContext, phrase[i],
- bigramProbabilityForWords, BinaryDictionary.NOT_A_VALID_TIMESTAMP);
- }
- ngramContext =
- ngramContext.getNextNgramContext(new NgramContext.WordInfo(phrase[i]));
- }
- }
+ void addPhraseToContextualDictionary(final String[] phrase, final int probability,
+ final int bigramProbabilityForWords, final int bigramProbabilityForPhrases);
- public void dumpDictionaryForDebug(final String dictName) {
- final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
- for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
- final ExpandableBinaryDictionary dictToDump = dictionaryGroup.getSubDict(dictName);
- if (dictToDump == null) {
- Log.e(TAG, "Cannot dump " + dictName + ". "
- + "The dictionary is not being used for suggestion or cannot be dumped.");
- return;
- }
- dictToDump.dumpAllWordsForDebug();
- }
- }
+ void dumpDictionaryForDebug(final String dictName);
- public ArrayList<Pair<String, DictionaryStats>> getStatsOfEnabledSubDicts() {
- final ArrayList<Pair<String, DictionaryStats>> statsOfEnabledSubDicts = new ArrayList<>();
- final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
- for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
- for (final String dictType : SUB_DICT_TYPES) {
- final ExpandableBinaryDictionary dictionary = dictionaryGroup.getSubDict(dictType);
- if (dictionary == null) continue;
- statsOfEnabledSubDicts.add(new Pair<>(dictType, dictionary.getDictionaryStats()));
- }
- }
- return statsOfEnabledSubDicts;
- }
+ ArrayList<Pair<String, DictionaryStats>> getStatsOfEnabledSubDicts();
}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java
new file mode 100644
index 000000000..167501118
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java
@@ -0,0 +1,947 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.ExpandableBinaryDictionary.UpdateEntriesForInputEventsCallback;
+import com.android.inputmethod.latin.NgramContext.WordInfo;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.personalization.ContextualDictionary;
+import com.android.inputmethod.latin.personalization.PersonalizationDataChunk;
+import com.android.inputmethod.latin.personalization.PersonalizationDictionary;
+import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
+import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
+import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
+import com.android.inputmethod.latin.utils.DistracterFilter;
+import com.android.inputmethod.latin.utils.DistracterFilterCheckingExactMatchesAndSuggestions;
+import com.android.inputmethod.latin.utils.DistracterFilterCheckingIsInDictionary;
+import com.android.inputmethod.latin.utils.ExecutorUtils;
+import com.android.inputmethod.latin.utils.SuggestionResults;
+
+import java.io.File;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * Facilitates interaction with different kinds of dictionaries. Provides APIs
+ * to instantiate and select the correct dictionaries (based on language or account),
+ * update entries and fetch suggestions.
+ *
+ * Currently AndroidSpellCheckerService and LatinIME both use DictionaryFacilitator as
+ * a client for interacting with dictionaries.
+ */
+public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
+ // TODO: Consolidate dictionaries in native code.
+ public static final String TAG = DictionaryFacilitatorImpl.class.getSimpleName();
+
+ // HACK: This threshold is being used when adding a capitalized entry in the User History
+ // dictionary.
+ private static final int CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT = 140;
+ // How many words we need to type in a row ({@see mConfidenceInMostProbableLanguage}) to
+ // declare we are confident the user is typing in the most probable language.
+ private static final int CONFIDENCE_THRESHOLD = 3;
+
+ private DictionaryGroup[] mDictionaryGroups = new DictionaryGroup[] { new DictionaryGroup() };
+ private DictionaryGroup mMostProbableDictionaryGroup = mDictionaryGroups[0];
+ private boolean mIsUserDictEnabled = false;
+ private volatile CountDownLatch mLatchForWaitingLoadingMainDictionaries = new CountDownLatch(0);
+ // To synchronize assigning mDictionaryGroup to ensure closing dictionaries.
+ private final Object mLock = new Object();
+ private final DistracterFilter mDistracterFilter;
+ private final PersonalizationHelperForDictionaryFacilitator mPersonalizationHelper;
+
+ private static final String[] DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS =
+ new String[] {
+ Dictionary.TYPE_MAIN,
+ Dictionary.TYPE_USER_HISTORY,
+ Dictionary.TYPE_PERSONALIZATION,
+ Dictionary.TYPE_USER,
+ Dictionary.TYPE_CONTACTS,
+ Dictionary.TYPE_CONTEXTUAL
+ };
+
+ public static final Map<String, Class<? extends ExpandableBinaryDictionary>>
+ DICT_TYPE_TO_CLASS = new HashMap<>();
+
+ static {
+ DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_USER_HISTORY, UserHistoryDictionary.class);
+ DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_PERSONALIZATION, PersonalizationDictionary.class);
+ DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_USER, UserBinaryDictionary.class);
+ DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_CONTACTS, ContactsBinaryDictionary.class);
+ DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_CONTEXTUAL, ContextualDictionary.class);
+ }
+
+ private static final String DICT_FACTORY_METHOD_NAME = "getDictionary";
+ private static final Class<?>[] DICT_FACTORY_METHOD_ARG_TYPES =
+ new Class[] { Context.class, Locale.class, File.class, String.class, String.class };
+
+ private static final String[] SUB_DICT_TYPES =
+ Arrays.copyOfRange(DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS, 1 /* start */,
+ DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS.length);
+
+ /**
+ * Returns whether this facilitator is exactly for this list of locales.
+ *
+ * @param locales the list of locales to test against
+ */
+ public boolean isForLocales(final Locale[] locales) {
+ if (locales.length != mDictionaryGroups.length) {
+ return false;
+ }
+ for (final Locale locale : locales) {
+ boolean found = false;
+ for (final DictionaryGroup group : mDictionaryGroups) {
+ if (locale.equals(group.mLocale)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns whether this facilitator is exactly for this account.
+ *
+ * @param account the account to test against.
+ */
+ public boolean isForAccount(@Nullable final String account) {
+ for (final DictionaryGroup group : mDictionaryGroups) {
+ if (!TextUtils.equals(group.mAccount, account)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * A group of dictionaries that work together for a single language.
+ */
+ private static class DictionaryGroup {
+ // TODO: Add null analysis annotations.
+ // TODO: Run evaluation to determine a reasonable value for these constants. The current
+ // values are ad-hoc and chosen without any particular care or methodology.
+ public static final float WEIGHT_FOR_MOST_PROBABLE_LANGUAGE = 1.0f;
+ public static final float WEIGHT_FOR_GESTURING_IN_NOT_MOST_PROBABLE_LANGUAGE = 0.95f;
+ public static final float WEIGHT_FOR_TYPING_IN_NOT_MOST_PROBABLE_LANGUAGE = 0.6f;
+
+ /**
+ * The locale associated with the dictionary group.
+ */
+ @Nullable public final Locale mLocale;
+
+ /**
+ * The user account associated with the dictionary group.
+ */
+ @Nullable public final String mAccount;
+
+ @Nullable private Dictionary mMainDict;
+ // Confidence that the most probable language is actually the language the user is
+ // typing in. For now, this is simply the number of times a word from this language
+ // has been committed in a row.
+ private int mConfidence = 0;
+
+ public float mWeightForTypingInLocale = WEIGHT_FOR_MOST_PROBABLE_LANGUAGE;
+ public float mWeightForGesturingInLocale = WEIGHT_FOR_MOST_PROBABLE_LANGUAGE;
+ public final ConcurrentHashMap<String, ExpandableBinaryDictionary> mSubDictMap =
+ new ConcurrentHashMap<>();
+
+ public DictionaryGroup() {
+ this(null /* locale */, null /* mainDict */, null /* account */,
+ Collections.<String, ExpandableBinaryDictionary>emptyMap() /* subDicts */);
+ }
+
+ public DictionaryGroup(@Nullable final Locale locale,
+ @Nullable final Dictionary mainDict,
+ @Nullable final String account,
+ final Map<String, ExpandableBinaryDictionary> subDicts) {
+ mLocale = locale;
+ mAccount = account;
+ // The main dictionary can be asynchronously loaded.
+ setMainDict(mainDict);
+ for (final Map.Entry<String, ExpandableBinaryDictionary> entry : subDicts.entrySet()) {
+ setSubDict(entry.getKey(), entry.getValue());
+ }
+ }
+
+ private void setSubDict(final String dictType, final ExpandableBinaryDictionary dict) {
+ if (dict != null) {
+ mSubDictMap.put(dictType, dict);
+ }
+ }
+
+ public void setMainDict(final Dictionary mainDict) {
+ // Close old dictionary if exists. Main dictionary can be assigned multiple times.
+ final Dictionary oldDict = mMainDict;
+ mMainDict = mainDict;
+ if (oldDict != null && mainDict != oldDict) {
+ oldDict.close();
+ }
+ }
+
+ public Dictionary getDict(final String dictType) {
+ if (Dictionary.TYPE_MAIN.equals(dictType)) {
+ return mMainDict;
+ }
+ return getSubDict(dictType);
+ }
+
+ public ExpandableBinaryDictionary getSubDict(final String dictType) {
+ return mSubDictMap.get(dictType);
+ }
+
+ public boolean hasDict(final String dictType, @Nullable final String account) {
+ if (Dictionary.TYPE_MAIN.equals(dictType)) {
+ return mMainDict != null;
+ }
+ if (Dictionary.TYPE_USER_HISTORY.equals(dictType) &&
+ !TextUtils.equals(account, mAccount)) {
+ // If the dictionary type is user history, & if the account doesn't match,
+ // return immediately. If the account matches, continue looking it up in the
+ // sub dictionary map.
+ return false;
+ }
+ return mSubDictMap.containsKey(dictType);
+ }
+
+ public void closeDict(final String dictType) {
+ final Dictionary dict;
+ if (Dictionary.TYPE_MAIN.equals(dictType)) {
+ dict = mMainDict;
+ } else {
+ dict = mSubDictMap.remove(dictType);
+ }
+ if (dict != null) {
+ dict.close();
+ }
+ }
+ }
+
+ public DictionaryFacilitatorImpl() {
+ mDistracterFilter = DistracterFilter.EMPTY_DISTRACTER_FILTER;
+ mPersonalizationHelper = null;
+ }
+
+ public DictionaryFacilitatorImpl(final Context context) {
+ mDistracterFilter = new DistracterFilterCheckingExactMatchesAndSuggestions(context);
+ mPersonalizationHelper =
+ new PersonalizationHelperForDictionaryFacilitator(context, mDistracterFilter);
+ }
+
+ public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes) {
+ mDistracterFilter.updateEnabledSubtypes(enabledSubtypes);
+ mPersonalizationHelper.updateEnabledSubtypes(enabledSubtypes);
+ }
+
+ // TODO: remove this, it's confusing with seamless multiple language switching
+ public void setIsMonolingualUser(final boolean isMonolingualUser) {
+ mPersonalizationHelper.setIsMonolingualUser(isMonolingualUser);
+ }
+
+ public boolean isActive() {
+ return null != mDictionaryGroups[0].mLocale;
+ }
+
+ /**
+ * Returns the most probable locale among all currently active locales. BE CAREFUL using this.
+ *
+ * DO NOT USE THIS just because it's convenient. Use it when it's correct, for example when
+ * choosing what dictionary to put a word in, or when changing the capitalization of a typed
+ * string.
+ * @return the most probable locale
+ */
+ public Locale getMostProbableLocale() {
+ return getDictionaryGroupForMostProbableLanguage().mLocale;
+ }
+
+ public Locale[] getLocales() {
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ final Locale[] locales = new Locale[dictionaryGroups.length];
+ for (int i = 0; i < dictionaryGroups.length; ++i) {
+ locales[i] = dictionaryGroups[i].mLocale;
+ }
+ return locales;
+ }
+
+ private DictionaryGroup getDictionaryGroupForMostProbableLanguage() {
+ return mMostProbableDictionaryGroup;
+ }
+
+ public void switchMostProbableLanguage(@Nullable final Locale locale) {
+ if (null == locale) {
+ // In many cases, there is no locale to a committed word. For example, a typed word
+ // that is in none of the currently active dictionaries but still does not
+ // auto-correct to anything has no locale. In this case we simply do not change
+ // the most probable language and do not touch confidence.
+ return;
+ }
+ final DictionaryGroup newMostProbableDictionaryGroup =
+ findDictionaryGroupWithLocale(mDictionaryGroups, locale);
+ if (null == newMostProbableDictionaryGroup) {
+ // It seems this may happen as a race condition; pressing the globe key and space
+ // in quick succession could commit a word out of a dictionary that's not in the
+ // facilitator any more. In this case, just not changing things is fine.
+ return;
+ }
+ if (newMostProbableDictionaryGroup == mMostProbableDictionaryGroup) {
+ ++newMostProbableDictionaryGroup.mConfidence;
+ } else {
+ mMostProbableDictionaryGroup.mWeightForTypingInLocale =
+ DictionaryGroup.WEIGHT_FOR_TYPING_IN_NOT_MOST_PROBABLE_LANGUAGE;
+ mMostProbableDictionaryGroup.mWeightForGesturingInLocale =
+ DictionaryGroup.WEIGHT_FOR_GESTURING_IN_NOT_MOST_PROBABLE_LANGUAGE;
+ mMostProbableDictionaryGroup.mConfidence = 0;
+ newMostProbableDictionaryGroup.mWeightForTypingInLocale =
+ DictionaryGroup.WEIGHT_FOR_MOST_PROBABLE_LANGUAGE;
+ newMostProbableDictionaryGroup.mWeightForGesturingInLocale =
+ DictionaryGroup.WEIGHT_FOR_MOST_PROBABLE_LANGUAGE;
+ mMostProbableDictionaryGroup = newMostProbableDictionaryGroup;
+ }
+ }
+
+ public boolean isConfidentAboutCurrentLanguageBeing(final Locale mLocale) {
+ final DictionaryGroup mostProbableDictionaryGroup = mMostProbableDictionaryGroup;
+ if (!mostProbableDictionaryGroup.mLocale.equals(mLocale)) {
+ return false;
+ }
+ if (mDictionaryGroups.length <= 1) {
+ return true;
+ }
+ return mostProbableDictionaryGroup.mConfidence >= CONFIDENCE_THRESHOLD;
+ }
+
+ @Nullable
+ private static ExpandableBinaryDictionary getSubDict(final String dictType,
+ final Context context, final Locale locale, final File dictFile,
+ final String dictNamePrefix, @Nullable final String account) {
+ final Class<? extends ExpandableBinaryDictionary> dictClass =
+ DICT_TYPE_TO_CLASS.get(dictType);
+ if (dictClass == null) {
+ return null;
+ }
+ try {
+ final Method factoryMethod = dictClass.getMethod(DICT_FACTORY_METHOD_NAME,
+ DICT_FACTORY_METHOD_ARG_TYPES);
+ final Object dict = factoryMethod.invoke(null /* obj */,
+ new Object[] { context, locale, dictFile, dictNamePrefix, account });
+ return (ExpandableBinaryDictionary) dict;
+ } catch (final NoSuchMethodException | SecurityException | IllegalAccessException
+ | IllegalArgumentException | InvocationTargetException e) {
+ Log.e(TAG, "Cannot create dictionary: " + dictType, e);
+ return null;
+ }
+ }
+
+ public void resetDictionaries(final Context context, final Locale[] newLocales,
+ final boolean useContactsDict, final boolean usePersonalizedDicts,
+ final boolean forceReloadMainDictionary,
+ @Nullable final String account,
+ final DictionaryInitializationListener listener) {
+ resetDictionariesWithDictNamePrefix(context, newLocales, useContactsDict,
+ usePersonalizedDicts, forceReloadMainDictionary, listener, "" /* dictNamePrefix */,
+ account);
+ }
+
+ @Nullable
+ static DictionaryGroup findDictionaryGroupWithLocale(final DictionaryGroup[] dictionaryGroups,
+ final Locale locale) {
+ for (DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ if (locale.equals(dictionaryGroup.mLocale)) {
+ return dictionaryGroup;
+ }
+ }
+ return null;
+ }
+
+ public void resetDictionariesWithDictNamePrefix(final Context context,
+ final Locale[] newLocales,
+ final boolean useContactsDict,
+ final boolean usePersonalizedDicts,
+ final boolean forceReloadMainDictionary,
+ @Nullable final DictionaryInitializationListener listener,
+ final String dictNamePrefix,
+ @Nullable final String account) {
+ final HashMap<Locale, ArrayList<String>> existingDictionariesToCleanup = new HashMap<>();
+ // TODO: Make subDictTypesToUse configurable by resource or a static final list.
+ final HashSet<String> subDictTypesToUse = new HashSet<>();
+ subDictTypesToUse.add(Dictionary.TYPE_USER);
+ if (useContactsDict) {
+ subDictTypesToUse.add(Dictionary.TYPE_CONTACTS);
+ }
+ if (usePersonalizedDicts) {
+ subDictTypesToUse.add(Dictionary.TYPE_USER_HISTORY);
+ subDictTypesToUse.add(Dictionary.TYPE_PERSONALIZATION);
+ subDictTypesToUse.add(Dictionary.TYPE_CONTEXTUAL);
+ }
+
+ // Gather all dictionaries. We'll remove them from the list to clean up later.
+ for (final Locale newLocale : newLocales) {
+ final ArrayList<String> dictTypeForLocale = new ArrayList<>();
+ existingDictionariesToCleanup.put(newLocale, dictTypeForLocale);
+ final DictionaryGroup currentDictionaryGroupForLocale =
+ findDictionaryGroupWithLocale(mDictionaryGroups, newLocale);
+ if (null == currentDictionaryGroupForLocale) {
+ continue;
+ }
+ for (final String dictType : SUB_DICT_TYPES) {
+ if (currentDictionaryGroupForLocale.hasDict(dictType, account)) {
+ dictTypeForLocale.add(dictType);
+ }
+ }
+ if (currentDictionaryGroupForLocale.hasDict(Dictionary.TYPE_MAIN, account)) {
+ dictTypeForLocale.add(Dictionary.TYPE_MAIN);
+ }
+ }
+
+ final DictionaryGroup[] newDictionaryGroups = new DictionaryGroup[newLocales.length];
+ for (int i = 0; i < newLocales.length; ++i) {
+ final Locale newLocale = newLocales[i];
+ final DictionaryGroup dictionaryGroupForLocale =
+ findDictionaryGroupWithLocale(mDictionaryGroups, newLocale);
+ final ArrayList<String> dictTypesToCleanupForLocale =
+ existingDictionariesToCleanup.get(newLocale);
+ final boolean noExistingDictsForThisLocale = (null == dictionaryGroupForLocale);
+
+ final Dictionary mainDict;
+ if (forceReloadMainDictionary || noExistingDictsForThisLocale
+ || !dictionaryGroupForLocale.hasDict(Dictionary.TYPE_MAIN, account)) {
+ mainDict = null;
+ } else {
+ mainDict = dictionaryGroupForLocale.getDict(Dictionary.TYPE_MAIN);
+ dictTypesToCleanupForLocale.remove(Dictionary.TYPE_MAIN);
+ }
+
+ final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>();
+ for (final String subDictType : subDictTypesToUse) {
+ final ExpandableBinaryDictionary subDict;
+ if (noExistingDictsForThisLocale
+ || !dictionaryGroupForLocale.hasDict(subDictType, account)) {
+ // Create a new dictionary.
+ subDict = getSubDict(subDictType, context, newLocale, null /* dictFile */,
+ dictNamePrefix, account);
+ } else {
+ // Reuse the existing dictionary, and don't close it at the end
+ subDict = dictionaryGroupForLocale.getSubDict(subDictType);
+ dictTypesToCleanupForLocale.remove(subDictType);
+ }
+ subDicts.put(subDictType, subDict);
+ }
+ newDictionaryGroups[i] = new DictionaryGroup(newLocale, mainDict, account, subDicts);
+ }
+
+ // Replace Dictionaries.
+ final DictionaryGroup[] oldDictionaryGroups;
+ synchronized (mLock) {
+ oldDictionaryGroups = mDictionaryGroups;
+ mDictionaryGroups = newDictionaryGroups;
+ mMostProbableDictionaryGroup = newDictionaryGroups[0];
+ mIsUserDictEnabled = UserBinaryDictionary.isEnabled(context);
+ if (hasAtLeastOneUninitializedMainDictionary()) {
+ asyncReloadUninitializedMainDictionaries(context, newLocales, listener);
+ }
+ }
+ if (listener != null) {
+ listener.onUpdateMainDictionaryAvailability(hasAtLeastOneInitializedMainDictionary());
+ }
+
+ // Clean up old dictionaries.
+ for (final Locale localeToCleanUp : existingDictionariesToCleanup.keySet()) {
+ final ArrayList<String> dictTypesToCleanUp =
+ existingDictionariesToCleanup.get(localeToCleanUp);
+ final DictionaryGroup dictionarySetToCleanup =
+ findDictionaryGroupWithLocale(oldDictionaryGroups, localeToCleanUp);
+ for (final String dictType : dictTypesToCleanUp) {
+ dictionarySetToCleanup.closeDict(dictType);
+ }
+ }
+ }
+
+ private void asyncReloadUninitializedMainDictionaries(final Context context,
+ final Locale[] locales, final DictionaryInitializationListener listener) {
+ final CountDownLatch latchForWaitingLoadingMainDictionary = new CountDownLatch(1);
+ mLatchForWaitingLoadingMainDictionaries = latchForWaitingLoadingMainDictionary;
+ ExecutorUtils.getExecutor("InitializeBinaryDictionary").execute(new Runnable() {
+ @Override
+ public void run() {
+ doReloadUninitializedMainDictionaries(
+ context, locales, listener, latchForWaitingLoadingMainDictionary);
+ }
+ });
+ }
+
+ void doReloadUninitializedMainDictionaries(final Context context, final Locale[] locales,
+ final DictionaryInitializationListener listener,
+ final CountDownLatch latchForWaitingLoadingMainDictionary) {
+ for (final Locale locale : locales) {
+ final DictionaryGroup dictionaryGroup =
+ findDictionaryGroupWithLocale(mDictionaryGroups, locale);
+ if (null == dictionaryGroup) {
+ // This should never happen, but better safe than crashy
+ Log.w(TAG, "Expected a dictionary group for " + locale + " but none found");
+ continue;
+ }
+ final Dictionary mainDict =
+ DictionaryFactory.createMainDictionaryFromManager(context, locale);
+ synchronized (mLock) {
+ if (locale.equals(dictionaryGroup.mLocale)) {
+ dictionaryGroup.setMainDict(mainDict);
+ } else {
+ // Dictionary facilitator has been reset for another locale.
+ mainDict.close();
+ }
+ }
+ }
+ if (listener != null) {
+ listener.onUpdateMainDictionaryAvailability(
+ hasAtLeastOneInitializedMainDictionary());
+ }
+ latchForWaitingLoadingMainDictionary.countDown();
+ }
+
+ @UsedForTesting
+ public void resetDictionariesForTesting(final Context context, final Locale[] locales,
+ final ArrayList<String> dictionaryTypes, final HashMap<String, File> dictionaryFiles,
+ final Map<String, Map<String, String>> additionalDictAttributes,
+ @Nullable final String account) {
+ Dictionary mainDictionary = null;
+ final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>();
+
+ final DictionaryGroup[] dictionaryGroups = new DictionaryGroup[locales.length];
+ for (int i = 0; i < locales.length; ++i) {
+ final Locale locale = locales[i];
+ for (final String dictType : dictionaryTypes) {
+ if (dictType.equals(Dictionary.TYPE_MAIN)) {
+ mainDictionary = DictionaryFactory.createMainDictionaryFromManager(context,
+ locale);
+ } else {
+ final File dictFile = dictionaryFiles.get(dictType);
+ final ExpandableBinaryDictionary dict = getSubDict(
+ dictType, context, locale, dictFile, "" /* dictNamePrefix */, account);
+ if (additionalDictAttributes.containsKey(dictType)) {
+ dict.clearAndFlushDictionaryWithAdditionalAttributes(
+ additionalDictAttributes.get(dictType));
+ }
+ if (dict == null) {
+ throw new RuntimeException("Unknown dictionary type: " + dictType);
+ }
+ dict.reloadDictionaryIfRequired();
+ dict.waitAllTasksForTests();
+ subDicts.put(dictType, dict);
+ }
+ }
+ dictionaryGroups[i] = new DictionaryGroup(locale, mainDictionary, account, subDicts);
+ }
+ mDictionaryGroups = dictionaryGroups;
+ mMostProbableDictionaryGroup = dictionaryGroups[0];
+ }
+
+ public void closeDictionaries() {
+ final DictionaryGroup[] dictionaryGroups;
+ synchronized (mLock) {
+ dictionaryGroups = mDictionaryGroups;
+ mMostProbableDictionaryGroup = new DictionaryGroup();
+ mDictionaryGroups = new DictionaryGroup[] { mMostProbableDictionaryGroup };
+ }
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
+ dictionaryGroup.closeDict(dictType);
+ }
+ }
+ mDistracterFilter.close();
+ if (mPersonalizationHelper != null) {
+ mPersonalizationHelper.close();
+ }
+ }
+
+ @UsedForTesting
+ public ExpandableBinaryDictionary getSubDictForTesting(final String dictName) {
+ return mMostProbableDictionaryGroup.getSubDict(dictName);
+ }
+
+ // The main dictionaries are loaded asynchronously. Don't cache the return value
+ // of these methods.
+ public boolean hasAtLeastOneInitializedMainDictionary() {
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ final Dictionary mainDict = dictionaryGroup.getDict(Dictionary.TYPE_MAIN);
+ if (mainDict != null && mainDict.isInitialized()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean hasAtLeastOneUninitializedMainDictionary() {
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ final Dictionary mainDict = dictionaryGroup.getDict(Dictionary.TYPE_MAIN);
+ if (mainDict == null || !mainDict.isInitialized()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean hasPersonalizationDictionary() {
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ if (dictionaryGroup.hasDict(Dictionary.TYPE_PERSONALIZATION, null /* account */)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void flushPersonalizationDictionary() {
+ final HashSet<ExpandableBinaryDictionary> personalizationDictsUsedForSuggestion =
+ new HashSet<>();
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ final ExpandableBinaryDictionary personalizationDictUsedForSuggestion =
+ dictionaryGroup.getSubDict(Dictionary.TYPE_PERSONALIZATION);
+ personalizationDictsUsedForSuggestion.add(personalizationDictUsedForSuggestion);
+ }
+ mPersonalizationHelper.flushPersonalizationDictionariesToUpdate(
+ personalizationDictsUsedForSuggestion);
+ mDistracterFilter.close();
+ }
+
+ public void waitForLoadingMainDictionaries(final long timeout, final TimeUnit unit)
+ throws InterruptedException {
+ mLatchForWaitingLoadingMainDictionaries.await(timeout, unit);
+ }
+
+ @UsedForTesting
+ public void waitForLoadingDictionariesForTesting(final long timeout, final TimeUnit unit)
+ throws InterruptedException {
+ waitForLoadingMainDictionaries(timeout, unit);
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ for (final ExpandableBinaryDictionary dict : dictionaryGroup.mSubDictMap.values()) {
+ dict.waitAllTasksForTests();
+ }
+ }
+ }
+
+ public boolean isUserDictionaryEnabled() {
+ return mIsUserDictEnabled;
+ }
+
+ public void addWordToUserDictionary(final Context context, final String word) {
+ final Locale locale = getMostProbableLocale();
+ if (locale == null) {
+ return;
+ }
+ // TODO: add a toast telling what language this is being added to?
+ UserBinaryDictionary.addWordToUserDictionary(context, locale, word);
+ }
+
+ public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized,
+ @Nonnull final NgramContext ngramContext, final int timeStampInSeconds,
+ final boolean blockPotentiallyOffensive) {
+ final DictionaryGroup dictionaryGroup = getDictionaryGroupForMostProbableLanguage();
+ final String[] words = suggestion.split(Constants.WORD_SEPARATOR);
+ NgramContext ngramContextForCurrentWord = ngramContext;
+ for (int i = 0; i < words.length; i++) {
+ final String currentWord = words[i];
+ final boolean wasCurrentWordAutoCapitalized = (i == 0) ? wasAutoCapitalized : false;
+ addWordToUserHistory(dictionaryGroup, ngramContextForCurrentWord, currentWord,
+ wasCurrentWordAutoCapitalized, timeStampInSeconds, blockPotentiallyOffensive);
+ ngramContextForCurrentWord =
+ ngramContextForCurrentWord.getNextNgramContext(new WordInfo(currentWord));
+ }
+ }
+
+ private void addWordToUserHistory(final DictionaryGroup dictionaryGroup,
+ final NgramContext ngramContext, final String word, final boolean wasAutoCapitalized,
+ final int timeStampInSeconds, final boolean blockPotentiallyOffensive) {
+ final ExpandableBinaryDictionary userHistoryDictionary =
+ dictionaryGroup.getSubDict(Dictionary.TYPE_USER_HISTORY);
+ if (userHistoryDictionary == null
+ || !isConfidentAboutCurrentLanguageBeing(userHistoryDictionary.mLocale)) {
+ return;
+ }
+ final int maxFreq = getFrequency(word);
+ if (maxFreq == 0 && blockPotentiallyOffensive) {
+ return;
+ }
+ final String lowerCasedWord = word.toLowerCase(dictionaryGroup.mLocale);
+ final String secondWord;
+ if (wasAutoCapitalized) {
+ if (isValidWord(word, false /* ignoreCase */)
+ && !isValidWord(lowerCasedWord, false /* ignoreCase */)) {
+ // If the word was auto-capitalized and exists only as a capitalized word in the
+ // dictionary, then we must not downcase it before registering it. For example,
+ // the name of the contacts in start-of-sentence position would come here with the
+ // wasAutoCapitalized flag: if we downcase it, we'd register a lower-case version
+ // of that contact's name which would end up popping in suggestions.
+ secondWord = word;
+ } else {
+ // If however the word is not in the dictionary, or exists as a lower-case word
+ // only, then we consider that was a lower-case word that had been auto-capitalized.
+ secondWord = lowerCasedWord;
+ }
+ } else {
+ // HACK: We'd like to avoid adding the capitalized form of common words to the User
+ // History dictionary in order to avoid suggesting them until the dictionary
+ // consolidation is done.
+ // TODO: Remove this hack when ready.
+ final int lowerCaseFreqInMainDict = dictionaryGroup.hasDict(Dictionary.TYPE_MAIN,
+ null /* account */) ?
+ dictionaryGroup.getDict(Dictionary.TYPE_MAIN).getFrequency(lowerCasedWord) :
+ Dictionary.NOT_A_PROBABILITY;
+ if (maxFreq < lowerCaseFreqInMainDict
+ && lowerCaseFreqInMainDict >= CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT) {
+ // Use lower cased word as the word can be a distracter of the popular word.
+ secondWord = lowerCasedWord;
+ } else {
+ secondWord = word;
+ }
+ }
+ // We demote unrecognized words (frequency < 0, below) by specifying them as "invalid".
+ // We don't add words with 0-frequency (assuming they would be profanity etc.).
+ final boolean isValid = maxFreq > 0;
+ UserHistoryDictionary.addToDictionary(userHistoryDictionary, ngramContext, secondWord,
+ isValid, timeStampInSeconds,
+ new DistracterFilterCheckingIsInDictionary(
+ mDistracterFilter, userHistoryDictionary));
+ }
+
+ private void removeWord(final String dictName, final String word) {
+ final ExpandableBinaryDictionary dictionary =
+ getDictionaryGroupForMostProbableLanguage().getSubDict(dictName);
+ if (dictionary != null) {
+ dictionary.removeUnigramEntryDynamically(word);
+ }
+ }
+
+ public void removeWordFromPersonalizedDicts(final String word) {
+ removeWord(Dictionary.TYPE_USER_HISTORY, word);
+ removeWord(Dictionary.TYPE_PERSONALIZATION, word);
+ removeWord(Dictionary.TYPE_CONTEXTUAL, word);
+ }
+
+ // TODO: Revise the way to fusion suggestion results.
+ public SuggestionResults getSuggestionResults(final WordComposer composer,
+ final NgramContext ngramContext, final long proximityInfoHandle,
+ final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId) {
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ final SuggestionResults suggestionResults = new SuggestionResults(
+ SuggestedWords.MAX_SUGGESTIONS, ngramContext.isBeginningOfSentenceContext());
+ final float[] weightOfLangModelVsSpatialModel =
+ new float[] { Dictionary.NOT_A_WEIGHT_OF_LANG_MODEL_VS_SPATIAL_MODEL };
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
+ final Dictionary dictionary = dictionaryGroup.getDict(dictType);
+ if (null == dictionary) continue;
+ final float weightForLocale = composer.isBatchMode()
+ ? dictionaryGroup.mWeightForGesturingInLocale
+ : dictionaryGroup.mWeightForTypingInLocale;
+ final ArrayList<SuggestedWordInfo> dictionarySuggestions =
+ dictionary.getSuggestions(composer.getComposedDataSnapshot(), ngramContext,
+ proximityInfoHandle, settingsValuesForSuggestion, sessionId,
+ weightForLocale, weightOfLangModelVsSpatialModel);
+ if (null == dictionarySuggestions) continue;
+ suggestionResults.addAll(dictionarySuggestions);
+ if (null != suggestionResults.mRawSuggestions) {
+ suggestionResults.mRawSuggestions.addAll(dictionarySuggestions);
+ }
+ }
+ }
+ return suggestionResults;
+ }
+
+ public boolean isValidWord(final String word, final boolean ignoreCase) {
+ if (TextUtils.isEmpty(word)) {
+ return false;
+ }
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ if (dictionaryGroup.mLocale == null) {
+ continue;
+ }
+ final String lowerCasedWord = word.toLowerCase(dictionaryGroup.mLocale);
+ for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
+ final Dictionary dictionary = dictionaryGroup.getDict(dictType);
+ // Ideally the passed map would come out of a {@link java.util.concurrent.Future} and
+ // would be immutable once it's finished initializing, but concretely a null test is
+ // probably good enough for the time being.
+ if (null == dictionary) continue;
+ if (dictionary.isValidWord(word)
+ || (ignoreCase && dictionary.isValidWord(lowerCasedWord))) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private int getFrequencyInternal(final String word,
+ final boolean isGettingMaxFrequencyOfExactMatches) {
+ if (TextUtils.isEmpty(word)) {
+ return Dictionary.NOT_A_PROBABILITY;
+ }
+ int maxFreq = Dictionary.NOT_A_PROBABILITY;
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
+ final Dictionary dictionary = dictionaryGroup.getDict(dictType);
+ if (dictionary == null) continue;
+ final int tempFreq;
+ if (isGettingMaxFrequencyOfExactMatches) {
+ tempFreq = dictionary.getMaxFrequencyOfExactMatches(word);
+ } else {
+ tempFreq = dictionary.getFrequency(word);
+ }
+ if (tempFreq >= maxFreq) {
+ maxFreq = tempFreq;
+ }
+ }
+ }
+ return maxFreq;
+ }
+
+ public int getFrequency(final String word) {
+ return getFrequencyInternal(word, false /* isGettingMaxFrequencyOfExactMatches */);
+ }
+
+ public int getMaxFrequencyOfExactMatches(final String word) {
+ return getFrequencyInternal(word, true /* isGettingMaxFrequencyOfExactMatches */);
+ }
+
+ private void clearSubDictionary(final String dictName) {
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ final ExpandableBinaryDictionary dictionary = dictionaryGroup.getSubDict(dictName);
+ if (dictionary != null) {
+ dictionary.clear();
+ }
+ }
+ }
+
+ public void clearUserHistoryDictionary() {
+ clearSubDictionary(Dictionary.TYPE_USER_HISTORY);
+ }
+
+ // This method gets called only when the IME receives a notification to remove the
+ // personalization dictionary.
+ public void clearPersonalizationDictionary() {
+ clearSubDictionary(Dictionary.TYPE_PERSONALIZATION);
+ mPersonalizationHelper.clearDictionariesToUpdate();
+ }
+
+ public void clearContextualDictionary() {
+ clearSubDictionary(Dictionary.TYPE_CONTEXTUAL);
+ }
+
+ public void addEntriesToPersonalizationDictionary(
+ final PersonalizationDataChunk personalizationDataChunk,
+ final SpacingAndPunctuations spacingAndPunctuations,
+ final UpdateEntriesForInputEventsCallback callback) {
+ mPersonalizationHelper.updateEntriesOfPersonalizationDictionaries(
+ getMostProbableLocale(), personalizationDataChunk, spacingAndPunctuations,
+ callback);
+ }
+
+ @UsedForTesting
+ public void addPhraseToContextualDictionary(final String[] phrase, final int probability,
+ final int bigramProbabilityForWords, final int bigramProbabilityForPhrases) {
+ // TODO: we're inserting the phrase into the dictionary for the active language. Rethink
+ // this a bit from a theoretical point of view.
+ final ExpandableBinaryDictionary contextualDict =
+ getDictionaryGroupForMostProbableLanguage().getSubDict(Dictionary.TYPE_CONTEXTUAL);
+ if (contextualDict == null) {
+ return;
+ }
+ NgramContext ngramContext = NgramContext.BEGINNING_OF_SENTENCE;
+ for (int i = 0; i < phrase.length; i++) {
+ final String[] subPhrase = Arrays.copyOfRange(phrase, i /* start */, phrase.length);
+ final String subPhraseStr = TextUtils.join(Constants.WORD_SEPARATOR, subPhrase);
+ contextualDict.addUnigramEntryWithCheckingDistracter(
+ subPhraseStr, probability, null /* shortcutTarget */,
+ Dictionary.NOT_A_PROBABILITY /* shortcutFreq */,
+ false /* isNotAWord */, false /* isPossiblyOffensive */,
+ BinaryDictionary.NOT_A_VALID_TIMESTAMP,
+ DistracterFilter.EMPTY_DISTRACTER_FILTER);
+ contextualDict.addNgramEntry(ngramContext, subPhraseStr,
+ bigramProbabilityForPhrases, BinaryDictionary.NOT_A_VALID_TIMESTAMP);
+
+ if (i < phrase.length - 1) {
+ contextualDict.addUnigramEntryWithCheckingDistracter(
+ phrase[i], probability, null /* shortcutTarget */,
+ Dictionary.NOT_A_PROBABILITY /* shortcutFreq */,
+ false /* isNotAWord */, false /* isPossiblyOffensive */,
+ BinaryDictionary.NOT_A_VALID_TIMESTAMP,
+ DistracterFilter.EMPTY_DISTRACTER_FILTER);
+ contextualDict.addNgramEntry(ngramContext, phrase[i],
+ bigramProbabilityForWords, BinaryDictionary.NOT_A_VALID_TIMESTAMP);
+ }
+ ngramContext =
+ ngramContext.getNextNgramContext(new NgramContext.WordInfo(phrase[i]));
+ }
+ }
+
+ public void dumpDictionaryForDebug(final String dictName) {
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ final ExpandableBinaryDictionary dictToDump = dictionaryGroup.getSubDict(dictName);
+ if (dictToDump == null) {
+ Log.e(TAG, "Cannot dump " + dictName + ". "
+ + "The dictionary is not being used for suggestion or cannot be dumped.");
+ return;
+ }
+ dictToDump.dumpAllWordsForDebug();
+ }
+ }
+
+ public ArrayList<Pair<String, DictionaryStats>> getStatsOfEnabledSubDicts() {
+ final ArrayList<Pair<String, DictionaryStats>> statsOfEnabledSubDicts = new ArrayList<>();
+ final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
+ for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
+ for (final String dictType : SUB_DICT_TYPES) {
+ final ExpandableBinaryDictionary dictionary = dictionaryGroup.getSubDict(dictType);
+ if (dictionary == null) continue;
+ statsOfEnabledSubDicts.add(new Pair<>(dictType, dictionary.getDictionaryStats()));
+ }
+ }
+ return statsOfEnabledSubDicts;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java
index 3119ff82f..13bd15101 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java
@@ -136,7 +136,7 @@ public class DictionaryFacilitatorLruCache {
if (dictionaryFacilitator != null) {
return dictionaryFacilitator;
}
- dictionaryFacilitator = new DictionaryFacilitator();
+ dictionaryFacilitator = DictionaryFacilitatorProvider.newDictionaryFacilitator();
resetDictionariesForLocaleLocked(dictionaryFacilitator, locale);
waitForLoadingMainDictionary(dictionaryFacilitator);
mLruCache.put(locale, dictionaryFacilitator);
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 2e6541ff1..622cdb0a6 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -139,8 +139,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private static final String SCHEME_PACKAGE = "package";
final Settings mSettings;
- private final DictionaryFacilitator mDictionaryFacilitator =
- new DictionaryFacilitator(this /* context */);
+ private final DictionaryFacilitator mDictionaryFacilitator =
+ DictionaryFacilitatorProvider.newDictionaryFacilitator(this /* context */);
// TODO: Move from LatinIME.
private final PersonalizationDictionaryUpdater mPersonalizationDictionaryUpdater =
new PersonalizationDictionaryUpdater(this /* context */, mDictionaryFacilitator);
diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
index 811af4bd7..64a7cf347 100644
--- a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
+++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
@@ -33,7 +33,6 @@ import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
-import com.android.inputmethod.latin.settings.AdditionalFeaturesSettingUtils;
import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
import com.android.inputmethod.latin.utils.LanguageOnSpacebarUtils;
@@ -324,22 +323,6 @@ public class RichInputMethodManager {
return INDEX_NOT_FOUND;
}
- public boolean checkIfSubtypeBelongsToThisIme(final InputMethodSubtype subtype) {
- return getSubtypeIndexInIme(subtype, getInputMethodInfoOfThisIme()) != INDEX_NOT_FOUND;
- }
-
- private static int getSubtypeIndexInIme(final InputMethodSubtype subtype,
- final InputMethodInfo imi) {
- final int count = imi.getSubtypeCount();
- for (int index = 0; index < count; index++) {
- final InputMethodSubtype ims = imi.getSubtypeAt(index);
- if (ims.equals(subtype)) {
- return index;
- }
- }
- return INDEX_NOT_FOUND;
- }
-
public void onSubtypeChanged(@Nonnull final InputMethodSubtype newSubtype) {
updateCurrentSubtype(newSubtype);
updateShortcutIme();
@@ -416,7 +399,6 @@ public class RichInputMethodManager {
// subtypes should be counted as well.
if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
++filteredImisCount;
- continue;
}
}
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index e605bfe78..e80e3628f 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -103,8 +103,7 @@ public final class WordComposer {
final String nonNullCombiningSpec = null == combiningSpec ? "" : combiningSpec;
if (!nonNullCombiningSpec.equals(mCombiningSpec)) {
mCombinerChain = new CombinerChain(
- mCombinerChain.getComposingWordWithCombiningFeedback().toString(),
- CombinerChain.createCombiners(nonNullCombiningSpec));
+ mCombinerChain.getComposingWordWithCombiningFeedback().toString());
mCombiningSpec = nonNullCombiningSpec;
}
}
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index 0185a04ef..4842438c8 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -2204,8 +2204,7 @@ public final class InputLogic {
mWordComposer.isComposingWord() ? 2 : 1),
proximityInfo,
new SettingsValuesForSuggestion(settingsValues.mBlockPotentiallyOffensive,
- settingsValues.mPhraseGestureEnabled,
- settingsValues.mAdditionalFeaturesSettingValues),
+ settingsValues.mPhraseGestureEnabled),
settingsValues.mAutoCorrectionEnabledPerUserSettings,
inputStyle, sequenceNumber, callback);
}
diff --git a/java/src/com/android/inputmethod/latin/settings/AdvancedSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/AdvancedSettingsFragment.java
index 3dfc743f8..9366726e2 100644
--- a/java/src/com/android/inputmethod/latin/settings/AdvancedSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/AdvancedSettingsFragment.java
@@ -103,8 +103,6 @@ public final class AdvancedSettingsFragment extends SubScreenFragment {
removePreference(Settings.PREF_ENABLE_METRICS_LOGGING);
}
- AdditionalFeaturesSettingUtils.addAdditionalFeaturesPreferences(context, this);
-
setupKeypressVibrationDurationSettings();
setupKeypressSoundVolumeSettings();
setupKeyLongpressTimeoutSettings();
diff --git a/java/src/com/android/inputmethod/latin/settings/DebugSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/DebugSettingsFragment.java
index b788d7fcf..a56de1f69 100644
--- a/java/src/com/android/inputmethod/latin/settings/DebugSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/DebugSettingsFragment.java
@@ -28,7 +28,7 @@ import android.preference.PreferenceGroup;
import android.preference.TwoStatePreference;
import com.android.inputmethod.latin.DictionaryDumpBroadcastReceiver;
-import com.android.inputmethod.latin.DictionaryFacilitator;
+import com.android.inputmethod.latin.DictionaryFacilitatorImpl;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.debug.ExternalDictionaryGetterForDebug;
import com.android.inputmethod.latin.utils.ApplicationUtils;
@@ -67,7 +67,7 @@ public final class DebugSettingsFragment extends SubScreenFragment
final PreferenceGroup dictDumpPreferenceGroup =
(PreferenceGroup)findPreference(PREF_KEY_DUMP_DICTS);
- for (final String dictName : DictionaryFacilitator.DICT_TYPE_TO_CLASS.keySet()) {
+ for (final String dictName : DictionaryFacilitatorImpl.DICT_TYPE_TO_CLASS.keySet()) {
final Preference pref = new DictDumpPreference(getActivity(), dictName);
pref.setOnPreferenceClickListener(this);
dictDumpPreferenceGroup.addPreference(pref);
diff --git a/java/src/com/android/inputmethod/latin/settings/MultiLingualSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/MultiLingualSettingsFragment.java
deleted file mode 100644
index c5930db1e..000000000
--- a/java/src/com/android/inputmethod/latin/settings/MultiLingualSettingsFragment.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2014 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.settings;
-
-import android.os.Bundle;
-
-import com.android.inputmethod.latin.R;
-
-/**
- * "Multilingual options" settings sub screen.
- *
- * This settings sub screen handles the following input preferences.
- * - Language switch key
- * - Switch to other input methods
- */
-public final class MultiLingualSettingsFragment extends SubScreenFragment {
- @Override
- public void onCreate(final Bundle icicle) {
- super.onCreate(icicle);
- addPreferencesFromResource(R.xml.prefs_screen_multilingual);
- if (!Settings.ENABLE_SHOW_LANGUAGE_SWITCH_KEY_SETTINGS) {
- removePreference(Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY);
- removePreference(Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST);
- }
- AdditionalFeaturesSettingUtils.addAdditionalFeaturesPreferences(getActivity(), this);
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java
index 77996405b..0ac19f76b 100644
--- a/java/src/com/android/inputmethod/latin/settings/Settings.java
+++ b/java/src/com/android/inputmethod/latin/settings/Settings.java
@@ -44,14 +44,8 @@ import javax.annotation.Nonnull;
public final class Settings implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = Settings.class.getSimpleName();
// Settings screens
- public static final String SCREEN_PREFERENCES = "screen_preferences";
public static final String SCREEN_ACCOUNTS = "screen_accounts";
- public static final String SCREEN_APPEARANCE = "screen_appearance";
public static final String SCREEN_THEME = "screen_theme";
- public static final String SCREEN_MULTILINGUAL = "screen_multilingual";
- public static final String SCREEN_GESTURE = "screen_gesture";
- public static final String SCREEN_CORRECTION = "screen_correction";
- public static final String SCREEN_ADVANCED = "screen_advanced";
public static final String SCREEN_DEBUG = "screen_debug";
// In the same order as xml/prefs.xml
public static final String PREF_AUTO_CAP = "auto_cap";
@@ -73,9 +67,6 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
"pref_key_use_double_space_period";
public static final String PREF_BLOCK_POTENTIALLY_OFFENSIVE =
"pref_key_block_potentially_offensive";
- // No multilingual options in Android L and above for now.
- public static final boolean SHOW_MULTILINGUAL_SETTINGS =
- BuildCompatUtils.EFFECTIVE_SDK_INT <= Build.VERSION_CODES.KITKAT;
public static final boolean ENABLE_SHOW_LANGUAGE_SWITCH_KEY_SETTINGS =
BuildCompatUtils.EFFECTIVE_SDK_INT <= Build.VERSION_CODES.KITKAT;
public static final boolean SHOULD_SHOW_LXX_SUGGESTION_UI =
@@ -84,7 +75,6 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
"pref_show_language_switch_key";
public static final String PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST =
"pref_include_other_imes_in_language_switch_list";
- public static final String PREF_KEYBOARD_THEME = "pref_keyboard_theme";
public static final String PREF_CUSTOM_INPUT_STYLES = "custom_input_styles";
public static final String PREF_ENABLE_SPLIT_KEYBOARD = "pref_split_keyboard";
// TODO: consolidate key preview dismiss delay with the key preview animation parameters.
@@ -103,8 +93,6 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
"pref_gesture_floating_preview_text";
public static final String PREF_PHRASE_GESTURE_ENABLED = "pref_gesture_space_aware";
- public static final String PREF_INPUT_LANGUAGE = "input_language";
- public static final String PREF_SELECTED_LANGUAGES = "selected_languages";
public static final String PREF_KEY_IS_INTERNAL = "pref_key_is_internal";
public static final String PREF_ENABLE_METRICS_LOGGING = "pref_enable_metrics_logging";
@@ -203,14 +191,6 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
return mSettingsValues.mIsInternal;
}
- public boolean isWordSeparator(final int code) {
- return mSettingsValues.isWordSeparator(code);
- }
-
- public boolean getBlockPotentiallyOffensive() {
- return mSettingsValues.mBlockPotentiallyOffensive;
- }
-
public static int readScreenMetrics(final Resources res) {
return res.getInteger(R.integer.config_screen_metrics);
}
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
index 6c21accf6..b98c53af4 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
@@ -48,15 +48,10 @@ public final class SettingsFragment extends InputMethodSettingsFragment {
final PreferenceScreen preferenceScreen = getPreferenceScreen();
preferenceScreen.setTitle(
ApplicationUtils.getActivityTitleResId(getActivity(), SettingsActivity.class));
- if (!Settings.SHOW_MULTILINGUAL_SETTINGS) {
- final Preference multilingualOptions = findPreference(Settings.SCREEN_MULTILINGUAL);
- preferenceScreen.removePreference(multilingualOptions);
- }
if (!ProductionFlags.ENABLE_ACCOUNT_SIGN_IN) {
final Preference accountsPreference = findPreference(Settings.SCREEN_ACCOUNTS);
preferenceScreen.removePreference(accountsPreference);
}
- AdditionalFeaturesSettingUtils.addAdditionalFeaturesPreferences(getActivity(), this);
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index 0669026d8..a080515dd 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -102,10 +102,6 @@ public class SettingsValues {
private final boolean mSuggestionsEnabledPerUserSettings;
private final AsyncResultHolder<AppWorkaroundsUtils> mAppWorkarounds;
- // Setting values for additional features
- public final int[] mAdditionalFeaturesSettingValues =
- new int[AdditionalFeaturesSettingUtils.ADDITIONAL_FEATURES_SETTINGS_SIZE];
-
// TextDecorator
public final int mTextHighlightColorForAddToDictionaryIndicator;
@@ -187,8 +183,6 @@ public class SettingsValues {
mAutoCorrectionEnabledPerUserSettings = mAutoCorrectEnabled
&& !mInputAttributes.mInputTypeNoAutoCorrect;
mSuggestionsEnabledPerUserSettings = readSuggestionsEnabled(prefs);
- AdditionalFeaturesSettingUtils.readAdditionalFeaturesPreferencesIntoArray(context,
- prefs, mAdditionalFeaturesSettingValues);
mTextHighlightColorForAddToDictionaryIndicator = res.getColor(
R.color.text_decorator_add_to_dictionary_indicator_text_highlight_color);
mIsInternal = Settings.isInternal(prefs);
@@ -437,8 +431,6 @@ public class SettingsValues {
sb.append("\n mAppWorkarounds = ");
final AppWorkaroundsUtils awu = mAppWorkarounds.get(null, 0);
sb.append("" + (null == awu ? "null" : awu.toString()));
- sb.append("\n mAdditionalFeaturesSettingValues = ");
- sb.append("" + Arrays.toString(mAdditionalFeaturesSettingValues));
sb.append("\n mTextHighlightColorForAddToDictionaryIndicator = ");
sb.append("" + mTextHighlightColorForAddToDictionaryIndicator);
sb.append("\n mIsInternal = ");
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValuesForSuggestion.java b/java/src/com/android/inputmethod/latin/settings/SettingsValuesForSuggestion.java
index d80af4ba7..56e6fac71 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValuesForSuggestion.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValuesForSuggestion.java
@@ -19,12 +19,10 @@ package com.android.inputmethod.latin.settings;
public class SettingsValuesForSuggestion {
public final boolean mBlockPotentiallyOffensive;
public final boolean mSpaceAwareGestureEnabled;
- public final int[] mAdditionalFeaturesSettingValues;
public SettingsValuesForSuggestion(final boolean blockPotentiallyOffensive,
- final boolean spaceAwareGestureEnabled, final int[] additionalFeaturesSettingValues) {
+ final boolean spaceAwareGestureEnabled) {
mBlockPotentiallyOffensive = blockPotentiallyOffensive;
mSpaceAwareGestureEnabled = spaceAwareGestureEnabled;
- mAdditionalFeaturesSettingValues = additionalFeaturesSettingValues;
}
}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index bcf7bbfdc..8744020b1 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -76,8 +76,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
// TODO: make a spell checker option to block offensive words or not
private final SettingsValuesForSuggestion mSettingsValuesForSuggestion =
new SettingsValuesForSuggestion(true /* blockPotentiallyOffensive */,
- true /* spaceAwareGestureEnabled */,
- null /* additionalFeaturesSettingValues */);
+ true /* spaceAwareGestureEnabled */);
public static final String SINGLE_QUOTE = "\u0027";
public static final String APOSTROPHE = "\u2019";
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index 4b8d2a3f9..832bfd066 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -39,6 +39,7 @@ import com.android.inputmethod.latin.common.LocaleUtils;
import com.android.inputmethod.latin.common.StringUtils;
import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
import com.android.inputmethod.latin.utils.ScriptUtils;
+import com.android.inputmethod.latin.utils.StatsUtils;
import com.android.inputmethod.latin.utils.SuggestionResults;
import java.util.ArrayList;
@@ -297,6 +298,15 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
}
}
}
+ // Handle word not in dictionary.
+ // This is called only once per unique word, so entering multiple
+ // instances of the same word does not result in more than one call
+ // to this method.
+ // Also, upon changing the orientation of the device, this is called
+ // again for every unique invalid word in the text box.
+ if (!isInDict) {
+ StatsUtils.onInvalidWordIdentification(text);
+ }
final int flags =
(isInDict ? SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
index 9c6a94810..56a04a856 100644
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
@@ -238,8 +238,7 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr
}
final SettingsValuesForSuggestion settingsValuesForSuggestion =
new SettingsValuesForSuggestion(false /* blockPotentiallyOffensive */,
- false /* spaceAwareGestureEnabled */,
- null /* additionalFeaturesSettingValues */);
+ false /* spaceAwareGestureEnabled */);
final int trailingSingleQuotesCount = StringUtils.getTrailingSingleQuotesCount(testedWord);
final String consideredWord = trailingSingleQuotesCount > 0 ?
testedWord.substring(0, testedWord.length() - trailingSingleQuotesCount) :
diff --git a/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java b/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java
index ae2de44c7..147e57b13 100644
--- a/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java
@@ -25,7 +25,6 @@ import com.android.inputmethod.latin.settings.CorrectionSettingsFragment;
import com.android.inputmethod.latin.settings.CustomInputStyleSettingsFragment;
import com.android.inputmethod.latin.settings.DebugSettingsFragment;
import com.android.inputmethod.latin.settings.GestureSettingsFragment;
-import com.android.inputmethod.latin.settings.MultiLingualSettingsFragment;
import com.android.inputmethod.latin.settings.PreferencesSettingsFragment;
import com.android.inputmethod.latin.settings.SettingsFragment;
import com.android.inputmethod.latin.settings.ThemeSettingsFragment;
@@ -46,7 +45,6 @@ public class FragmentUtils {
sLatinImeFragments.add(AccountsSettingsFragment.class.getName());
sLatinImeFragments.add(AppearanceSettingsFragment.class.getName());
sLatinImeFragments.add(ThemeSettingsFragment.class.getName());
- sLatinImeFragments.add(MultiLingualSettingsFragment.class.getName());
sLatinImeFragments.add(CustomInputStyleSettingsFragment.class.getName());
sLatinImeFragments.add(GestureSettingsFragment.class.getName());
sLatinImeFragments.add(CorrectionSettingsFragment.class.getName());