Merge "Add TrieMap.save()." into lmp-dev
diff --git a/java/Android.mk b/java/Android.mk
index b580624..9b8b2b4 100644
--- a/java/Android.mk
+++ b/java/Android.mk
@@ -25,7 +25,7 @@
 
 LOCAL_JNI_SHARED_LIBRARIES := libjni_latinime
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-common inputmethod-common android-support-v4
+LOCAL_STATIC_JAVA_LIBRARIES := android-common inputmethod-common android-support-v4 jsr305
 
 # Do not compress dictionary files to mmap dict data runtime
 LOCAL_AAPT_FLAGS := -0 .dict
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index 5434106..003b011 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -481,13 +481,24 @@
     <declare-styleable name="KeyboardLayoutSet_Feature">
         <!-- This should be aligned with ScriptUtils.SCRIPT_* -->
         <attr name="supportedScript" format="enum">
-            <enum name="latin" value="0" />
-            <enum name="cyrillic" value="1" />
-            <enum name="greek" value="2" />
-            <enum name="arabic" value="3" />
-            <enum name="hebrew" value="4" />
-            <enum name="armenian" value="5" />
-            <enum name="georgian" value="6" />
+            <enum name="arabic" value="0" />
+            <enum name="armenian" value="1" />
+            <enum name="bengali" value="2" />
+            <enum name="cyrillic" value="3" />
+            <enum name="devanagari" value="4" />
+            <enum name="georgian" value="5" />
+            <enum name="greek" value="6" />
+            <enum name="hebrew" value="7" />
+            <enum name="kannada" value="8" />
+            <enum name="khmer" value="9" />
+            <enum name="lao" value="10" />
+            <enum name="latin" value="11" />
+            <enum name="malayalam" value="12" />
+            <enum name="myanmar" value="13" />
+            <enum name="sinhala" value="14" />
+            <enum name="tamil" value="15" />
+            <enum name="telugu" value="16" />
+            <enum name="thai" value="17" />
         </attr>
     </declare-styleable>
 
diff --git a/java/res/xml/keyboard_layout_set_bengali.xml b/java/res/xml/keyboard_layout_set_bengali.xml
index 6e40e6d..de48a0c 100644
--- a/java/res/xml/keyboard_layout_set_bengali.xml
+++ b/java/res/xml/keyboard_layout_set_bengali.xml
@@ -20,6 +20,8 @@
 
 <KeyboardLayoutSet
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Feature
+        latin:supportedScript="bengali" />
     <Element
         latin:elementName="alphabet"
         latin:elementKeyboard="@xml/kbd_bengali"
diff --git a/java/res/xml/keyboard_layout_set_hindi.xml b/java/res/xml/keyboard_layout_set_hindi.xml
index e850c7e..ee41b2a 100644
--- a/java/res/xml/keyboard_layout_set_hindi.xml
+++ b/java/res/xml/keyboard_layout_set_hindi.xml
@@ -20,6 +20,8 @@
 
 <KeyboardLayoutSet
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Feature
+        latin:supportedScript="devanagari" />
     <Element
         latin:elementName="alphabet"
         latin:elementKeyboard="@xml/kbd_hindi"
diff --git a/java/res/xml/keyboard_layout_set_hindi_compact.xml b/java/res/xml/keyboard_layout_set_hindi_compact.xml
index 77d02fb..147b299 100644
--- a/java/res/xml/keyboard_layout_set_hindi_compact.xml
+++ b/java/res/xml/keyboard_layout_set_hindi_compact.xml
@@ -20,6 +20,8 @@
 
 <KeyboardLayoutSet
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Feature
+        latin:supportedScript="devanagari" />
     <Element
         latin:elementName="alphabet"
         latin:elementKeyboard="@xml/kbd_hindi_compact"
diff --git a/java/res/xml/keyboard_layout_set_kannada.xml b/java/res/xml/keyboard_layout_set_kannada.xml
index 8dcf996..14323e7 100644
--- a/java/res/xml/keyboard_layout_set_kannada.xml
+++ b/java/res/xml/keyboard_layout_set_kannada.xml
@@ -20,6 +20,8 @@
 
 <KeyboardLayoutSet
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Feature
+        latin:supportedScript="kannada" />
     <Element
         latin:elementName="alphabet"
         latin:elementKeyboard="@xml/kbd_kannada"
diff --git a/java/res/xml/keyboard_layout_set_khmer.xml b/java/res/xml/keyboard_layout_set_khmer.xml
index 181f98b..752c852 100644
--- a/java/res/xml/keyboard_layout_set_khmer.xml
+++ b/java/res/xml/keyboard_layout_set_khmer.xml
@@ -20,6 +20,8 @@
 
 <KeyboardLayoutSet
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Feature
+        latin:supportedScript="khmer" />
     <Element
         latin:elementName="alphabet"
         latin:elementKeyboard="@xml/kbd_khmer"
diff --git a/java/res/xml/keyboard_layout_set_lao.xml b/java/res/xml/keyboard_layout_set_lao.xml
index 2ffde45..6285f87 100644
--- a/java/res/xml/keyboard_layout_set_lao.xml
+++ b/java/res/xml/keyboard_layout_set_lao.xml
@@ -20,6 +20,8 @@
 
 <KeyboardLayoutSet
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Feature
+        latin:supportedScript="lao" />
     <Element
         latin:elementName="alphabet"
         latin:elementKeyboard="@xml/kbd_lao"
diff --git a/java/res/xml/keyboard_layout_set_malayalam.xml b/java/res/xml/keyboard_layout_set_malayalam.xml
index 14c76ba..f6086f6 100644
--- a/java/res/xml/keyboard_layout_set_malayalam.xml
+++ b/java/res/xml/keyboard_layout_set_malayalam.xml
@@ -20,6 +20,8 @@
 
 <KeyboardLayoutSet
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Feature
+        latin:supportedScript="malayalam" />
     <Element
         latin:elementName="alphabet"
         latin:elementKeyboard="@xml/kbd_malayalam"
diff --git a/java/res/xml/keyboard_layout_set_marathi.xml b/java/res/xml/keyboard_layout_set_marathi.xml
index e5c68e7..6aea175 100644
--- a/java/res/xml/keyboard_layout_set_marathi.xml
+++ b/java/res/xml/keyboard_layout_set_marathi.xml
@@ -20,6 +20,8 @@
 
 <KeyboardLayoutSet
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Feature
+        latin:supportedScript="devanagari" />
     <Element
         latin:elementName="alphabet"
         latin:elementKeyboard="@xml/kbd_marathi"
diff --git a/java/res/xml/keyboard_layout_set_myanmar.xml b/java/res/xml/keyboard_layout_set_myanmar.xml
index 5c823b2..5af8c06 100644
--- a/java/res/xml/keyboard_layout_set_myanmar.xml
+++ b/java/res/xml/keyboard_layout_set_myanmar.xml
@@ -20,6 +20,8 @@
 
 <KeyboardLayoutSet
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Feature
+        latin:supportedScript="myanmar" />
     <Element
         latin:elementName="alphabet"
         latin:elementKeyboard="@xml/kbd_myanmar"
diff --git a/java/res/xml/keyboard_layout_set_nepali_romanized.xml b/java/res/xml/keyboard_layout_set_nepali_romanized.xml
index fbbc6a5..5bad835 100644
--- a/java/res/xml/keyboard_layout_set_nepali_romanized.xml
+++ b/java/res/xml/keyboard_layout_set_nepali_romanized.xml
@@ -20,6 +20,8 @@
 
 <KeyboardLayoutSet
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Feature
+        latin:supportedScript="devanagari" />
     <Element
         latin:elementName="alphabet"
         latin:elementKeyboard="@xml/kbd_nepali_romanized"
diff --git a/java/res/xml/keyboard_layout_set_nepali_traditional.xml b/java/res/xml/keyboard_layout_set_nepali_traditional.xml
index 4a3b601..6f11dee 100644
--- a/java/res/xml/keyboard_layout_set_nepali_traditional.xml
+++ b/java/res/xml/keyboard_layout_set_nepali_traditional.xml
@@ -20,6 +20,8 @@
 
 <KeyboardLayoutSet
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Feature
+        latin:supportedScript="devanagari" />
     <Element
         latin:elementName="alphabet"
         latin:elementKeyboard="@xml/kbd_nepali_traditional"
diff --git a/java/res/xml/keyboard_layout_set_sinhala.xml b/java/res/xml/keyboard_layout_set_sinhala.xml
index 8e6e619..1955d21 100644
--- a/java/res/xml/keyboard_layout_set_sinhala.xml
+++ b/java/res/xml/keyboard_layout_set_sinhala.xml
@@ -20,6 +20,8 @@
 
 <KeyboardLayoutSet
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Feature
+        latin:supportedScript="sinhala" />
     <Element
         latin:elementName="alphabet"
         latin:elementKeyboard="@xml/kbd_sinhala"
diff --git a/java/res/xml/keyboard_layout_set_tamil.xml b/java/res/xml/keyboard_layout_set_tamil.xml
index 5c04915..27f4a32 100644
--- a/java/res/xml/keyboard_layout_set_tamil.xml
+++ b/java/res/xml/keyboard_layout_set_tamil.xml
@@ -20,6 +20,8 @@
 
 <KeyboardLayoutSet
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Feature
+        latin:supportedScript="tamil" />
     <Element
         latin:elementName="alphabet"
         latin:elementKeyboard="@xml/kbd_tamil"
diff --git a/java/res/xml/keyboard_layout_set_telugu.xml b/java/res/xml/keyboard_layout_set_telugu.xml
index aca47b9..2bf65bc 100644
--- a/java/res/xml/keyboard_layout_set_telugu.xml
+++ b/java/res/xml/keyboard_layout_set_telugu.xml
@@ -20,6 +20,8 @@
 
 <KeyboardLayoutSet
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Feature
+        latin:supportedScript="telugu" />
     <Element
         latin:elementName="alphabet"
         latin:elementKeyboard="@xml/kbd_telugu"
diff --git a/java/res/xml/keyboard_layout_set_thai.xml b/java/res/xml/keyboard_layout_set_thai.xml
index b8f9997..f69dfa6 100644
--- a/java/res/xml/keyboard_layout_set_thai.xml
+++ b/java/res/xml/keyboard_layout_set_thai.xml
@@ -20,6 +20,8 @@
 
 <KeyboardLayoutSet
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Feature
+        latin:supportedScript="thai" />
     <Element
         latin:elementName="alphabet"
         latin:elementKeyboard="@xml/kbd_thai"
diff --git a/java/src/com/android/inputmethod/event/Combiner.java b/java/src/com/android/inputmethod/event/Combiner.java
index 8b808c6..fee93f0 100644
--- a/java/src/com/android/inputmethod/event/Combiner.java
+++ b/java/src/com/android/inputmethod/event/Combiner.java
@@ -18,6 +18,8 @@
 
 import java.util.ArrayList;
 
+import javax.annotation.Nonnull;
+
 /**
  * A generic interface for combiners. Combiners are objects that transform chains of input events
  * into committable strings and manage feedback to show to the user on the combining state.
@@ -33,6 +35,7 @@
      * @param event the event to combine with the existing state.
      * @return the resulting event.
      */
+    @Nonnull
     Event processEvent(ArrayList<Event> previousEvents, Event event);
 
     /**
diff --git a/java/src/com/android/inputmethod/event/CombinerChain.java b/java/src/com/android/inputmethod/event/CombinerChain.java
index 61bc11b..f69bf4f 100644
--- a/java/src/com/android/inputmethod/event/CombinerChain.java
+++ b/java/src/com/android/inputmethod/event/CombinerChain.java
@@ -24,6 +24,8 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 
+import javax.annotation.Nonnull;
+
 /**
  * This class implements the logic chain between receiving events and generating code points.
  *
@@ -81,22 +83,29 @@
     }
 
     /**
-     * Pass a new event through the whole chain.
+     * Process an event through the combining chain, and return a processed event to apply.
      * @param previousEvents the list of previous events in this composition
      * @param newEvent the new event to process
+     * @return the processed event. It may be the same event, or a consumed event, or a completely
+     *   new event. However it may never be null.
      */
-    public void processEvent(final ArrayList<Event> previousEvents, final Event newEvent) {
+    @Nonnull
+    public Event processEvent(final ArrayList<Event> previousEvents, final Event newEvent) {
         final ArrayList<Event> modifiablePreviousEvents = new ArrayList<>(previousEvents);
         Event event = newEvent;
         for (final Combiner combiner : mCombiners) {
             // A combiner can never return more than one event; it can return several
             // code points, but they should be encapsulated within one event.
             event = combiner.processEvent(modifiablePreviousEvents, event);
-            if (null == event) {
-                // Combiners return null if they eat the event.
-                break;
-            }
         }
+        return event;
+    }
+
+    /**
+     * Apply a processed event.
+     * @param event the event to be applied
+     */
+    public void applyProcessedEvent(final Event event) {
         if (null != event) {
             // TODO: figure out the generic way of doing this
             if (Constants.CODE_DELETE == event.mKeyCode) {
diff --git a/java/src/com/android/inputmethod/event/DeadKeyCombiner.java b/java/src/com/android/inputmethod/event/DeadKeyCombiner.java
index bef4d85..d816247 100644
--- a/java/src/com/android/inputmethod/event/DeadKeyCombiner.java
+++ b/java/src/com/android/inputmethod/event/DeadKeyCombiner.java
@@ -23,6 +23,8 @@
 
 import java.util.ArrayList;
 
+import javax.annotation.Nonnull;
+
 /**
  * A combiner that handles dead keys.
  */
@@ -31,13 +33,13 @@
     final StringBuilder mDeadSequence = new StringBuilder();
 
     @Override
+    @Nonnull
     public Event processEvent(final ArrayList<Event> previousEvents, final Event event) {
-        if (null == event) return null; // Just in case some combiner is broken
         if (TextUtils.isEmpty(mDeadSequence)) {
             if (event.isDead()) {
                 mDeadSequence.appendCodePoint(event.mCodePoint);
             }
-            return event;
+            return Event.createConsumedEvent(event);
         } else {
             // TODO: Allow combining for several dead chars rather than only the first one.
             // The framework doesn't know how to do this now.
diff --git a/java/src/com/android/inputmethod/event/Event.java b/java/src/com/android/inputmethod/event/Event.java
index d257441..98c8274 100644
--- a/java/src/com/android/inputmethod/event/Event.java
+++ b/java/src/com/android/inputmethod/event/Event.java
@@ -67,6 +67,8 @@
     final private static int FLAG_DEAD = 0x1;
     // This event is coming from a key repeat, software or hardware.
     final private static int FLAG_REPEAT = 0x2;
+    // This event has already been consumed.
+    final private static int FLAG_CONSUMED = 0x4;
 
     final private int mEventType; // The type of event - one of the constants above
     // The code point associated with the event, if relevant. This is a unicode code point, and
@@ -219,6 +221,17 @@
                 null /* next */);
     }
 
+    /**
+     * Creates an event identical to the passed event, but that has already been consumed.
+     * @param source the event to copy the properties of.
+     * @return an identical event marked as consumed.
+     */
+    public static Event createConsumedEvent(final Event source) {
+        return new Event(source.mEventType, source.mText, source.mCodePoint, source.mKeyCode,
+                source.mX, source.mY, source.mSuggestedWordInfo, source.mFlags | FLAG_CONSUMED,
+                source.mNextEvent);
+    }
+
     public static Event createNotHandledEvent() {
         return new Event(EVENT_TYPE_NOT_HANDLED, null /* text */, NOT_A_CODE_POINT, NOT_A_KEY_CODE,
                 Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
@@ -241,6 +254,8 @@
         return 0 != (FLAG_REPEAT & mFlags);
     }
 
+    public boolean isConsumed() { return 0 != (FLAG_CONSUMED & mFlags); }
+
     // Returns whether this is a fake key press from the suggestion strip. This happens with
     // punctuation signs selected from the suggestion strip.
     public boolean isSuggestionStripPress() {
diff --git a/java/src/com/android/inputmethod/event/MyanmarReordering.java b/java/src/com/android/inputmethod/event/MyanmarReordering.java
index 3291993..dcd06c8 100644
--- a/java/src/com/android/inputmethod/event/MyanmarReordering.java
+++ b/java/src/com/android/inputmethod/event/MyanmarReordering.java
@@ -21,6 +21,8 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 
+import javax.annotation.Nonnull;
+
 /**
  * A combiner that reorders input for Myanmar.
  */
@@ -111,7 +113,7 @@
      * 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. Null if none.
+     * @return the resulting software text event. Never null.
      */
     private Event clearAndGetResultingEvent(final Event newEvent) {
         final CharSequence combinedText;
@@ -124,18 +126,19 @@
         if (null != newEvent) {
             mCurrentEvents.add(newEvent);
         }
-        return null == combinedText ? null
+        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 null;
+                return Event.createConsumedEvent(newEvent);
             } else if (isConsonantOrMedial(lastEvent.mCodePoint)) {
                 final Event resultingEvent = clearAndGetResultingEvent(null);
                 mCurrentEvents.add(Event.createSoftwareKeypressEvent(ZERO_WIDTH_NON_JOINER,
@@ -151,7 +154,7 @@
             final Event lastEvent = getLastEvent();
             if (null == lastEvent) {
                 mCurrentEvents.add(newEvent);
-                return null;
+                return Event.createConsumedEvent(newEvent);
             } else if (VOWEL_E == lastEvent.mCodePoint) {
                 final int eventSize = mCurrentEvents.size();
                 if (eventSize >= 2
@@ -162,7 +165,7 @@
                     mCurrentEvents.remove(eventSize - 2);
                     mCurrentEvents.add(newEvent);
                     mCurrentEvents.add(lastEvent);
-                    return null;
+                    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) {
@@ -174,7 +177,7 @@
                 mCurrentEvents.remove(eventSize - 1);
                 mCurrentEvents.add(newEvent);
                 mCurrentEvents.add(lastEvent);
-                return null;
+                return Event.createConsumedEvent(newEvent);
             } else { // lastCodePoint is a consonant/medial. But if it's something else it's fine
                 return clearAndGetResultingEvent(newEvent);
             }
@@ -182,7 +185,7 @@
             final Event lastEvent = getLastEvent();
             if (null == lastEvent) {
                 mCurrentEvents.add(newEvent);
-                return null;
+                return Event.createConsumedEvent(newEvent);
             } else 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
@@ -198,7 +201,7 @@
                     mCurrentEvents.remove(eventSize - 1);
                     mCurrentEvents.add(newEvent);
                     mCurrentEvents.add(lastEvent);
-                    return null;
+                    return Event.createConsumedEvent(newEvent);
                 }
                 // Otherwise, we just commit everything.
                 return clearAndGetResultingEvent(null);
@@ -228,10 +231,10 @@
                             mCurrentEvents.remove(eventSize - 1);
                         }
                     }
-                    return null;
+                    return Event.createConsumedEvent(newEvent);
                 } else if (eventSize > 0) {
                     mCurrentEvents.remove(eventSize - 1);
-                    return null;
+                    return Event.createConsumedEvent(newEvent);
                 }
             }
         }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
index 870ac86..d6d0b21 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
@@ -113,7 +113,7 @@
         boolean mIsSpellChecker;
         int mKeyboardWidth;
         int mKeyboardHeight;
-        int mScriptId;
+        int mScriptId = ScriptUtils.SCRIPT_LATIN;
         // Sparse array of KeyboardLayoutSet element parameters indexed by element's id.
         final SparseArray<ElementParams> mKeyboardLayoutSetElementIdToParamsMap =
                 new SparseArray<>();
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index cdd7822..3b69048 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -25,6 +25,8 @@
 import java.util.ArrayList;
 import java.util.Collections;
 
+import javax.annotation.Nonnull;
+
 /**
  * A place to store the currently composing word with information such as adjacent key codes as well
  */
@@ -175,20 +177,31 @@
     }
 
     /**
-     * Process an input event.
+     * Process an event and return an event, and return a processed event to apply.
+     * @param event the unprocessed event.
+     * @return the processed event. Never null, but may be marked as consumed.
+     */
+    @Nonnull
+    public Event processEvent(final Event event) {
+        final Event processedEvent = mCombinerChain.processEvent(mEvents, event);
+        mEvents.add(event);
+        return processedEvent;
+    }
+
+    /**
+     * Apply a processed input event.
      *
      * All input events should be supported, including software/hardware events, characters as well
      * as deletions, multiple inputs and gestures.
      *
-     * @param event the event to process.
+     * @param event the event to apply. Must not be null.
      */
-    public void processEvent(final Event event) {
+    public void applyProcessedEvent(final Event event) {
+        mCombinerChain.applyProcessedEvent(event);
         final int primaryCode = event.mCodePoint;
         final int keyX = event.mX;
         final int keyY = event.mY;
         final int newIndex = size();
-        mCombinerChain.processEvent(mEvents, event);
-        mEvents.add(event);
         refreshTypedWordCache();
         mCursorPositionWithinWord = mCodePointSize;
         // We may have deleted the last one.
@@ -281,7 +294,9 @@
             final int codePoint = Character.codePointAt(word, i);
             // We don't want to override the batch input points that are held in mInputPointers
             // (See {@link #add(int,int,int)}).
-            processEvent(Event.createEventForCodePointFromUnknownSource(codePoint));
+            final Event processedEvent =
+                    processEvent(Event.createEventForCodePointFromUnknownSource(codePoint));
+            applyProcessedEvent(processedEvent);
         }
     }
 
@@ -295,9 +310,11 @@
         reset();
         final int length = codePoints.length;
         for (int i = 0; i < length; ++i) {
-            processEvent(Event.createEventForCodePointFromAlreadyTypedText(codePoints[i],
+            final Event processedEvent =
+                    processEvent(Event.createEventForCodePointFromAlreadyTypedText(codePoints[i],
                     CoordinateUtils.xFromArray(coordinates, i),
                     CoordinateUtils.yFromArray(coordinates, i)));
+            applyProcessedEvent(processedEvent);
         }
         mIsResumed = true;
     }
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index 74d8799..f2a6f3b 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -712,7 +712,8 @@
      */
     private void handleNonSeparator(final SettingsValues settingsValues,
             final InputTransaction inputTransaction) {
-        final int codePoint = inputTransaction.mEvent.mCodePoint;
+        final Event processedEvent = mWordComposer.processEvent(inputTransaction.mEvent);
+        final int codePoint = processedEvent.mCodePoint;
         // TODO: refactor this method to stop flipping isComposingWord around all the time, and
         // make it shorter (possibly cut into several pieces). Also factor handleNonSpecialCharacter
         // which has the same name as other handle* methods but is not the same.
@@ -762,7 +763,7 @@
             resetComposingState(false /* alsoResetLastComposedWord */);
         }
         if (isComposingWord) {
-            mWordComposer.processEvent(inputTransaction.mEvent);
+            mWordComposer.applyProcessedEvent(processedEvent);
             // If it's the first letter, make note of auto-caps state
             if (mWordComposer.isSingleLetter()) {
                 mWordComposer.setCapitalizedModeAtStartComposingTime(inputTransaction.mShiftState);
@@ -771,7 +772,7 @@
                     mWordComposer.getTypedWord()), 1);
         } else {
             final boolean swapWeakSpace = tryStripSpaceAndReturnWhetherShouldSwapInstead(
-                    inputTransaction, inputTransaction.mEvent.isSuggestionStripPress());
+                    inputTransaction, processedEvent.isSuggestionStripPress());
 
             if (swapWeakSpace && trySwapSwapperAndSpace(inputTransaction)) {
                 mSpaceState = SpaceState.WEAK;
@@ -902,6 +903,7 @@
     private void handleBackspace(final InputTransaction inputTransaction,
             // TODO: remove this argument, put it into settingsValues
             final int currentKeyboardScriptId) {
+        final Event processedEvent = mWordComposer.processEvent(inputTransaction.mEvent);
         mSpaceState = SpaceState.NONE;
         mDeleteCount++;
 
@@ -913,7 +915,7 @@
         // Then again, even in the case of a key repeat, if the cursor is at start of text, it
         // can't go any further back, so we can update right away even if it's a key repeat.
         final int shiftUpdateKind =
-                inputTransaction.mEvent.isKeyRepeat() && mConnection.getExpectedSelectionStart() > 0
+                processedEvent.isKeyRepeat() && mConnection.getExpectedSelectionStart() > 0
                 ? InputTransaction.SHIFT_UPDATE_LATER : InputTransaction.SHIFT_UPDATE_NOW;
         inputTransaction.requireShiftUpdate(shiftUpdateKind);
 
@@ -933,7 +935,7 @@
                     mDictionaryFacilitator.removeWordFromPersonalizedDicts(rejectedSuggestion);
                 }
             } else {
-                mWordComposer.processEvent(inputTransaction.mEvent);
+                mWordComposer.applyProcessedEvent(processedEvent);
             }
             if (mWordComposer.isComposingWord()) {
                 mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index d151e40..6c4d80e 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -38,6 +38,7 @@
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
+import com.android.inputmethod.accessibility.AccessibilityUtils;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.MainKeyboardView;
 import com.android.inputmethod.keyboard.MoreKeysPanel;
@@ -368,13 +369,15 @@
         return true;
     }
 
-    // Working variables for {@link #onLongClick(View)} and
-    // {@link onInterceptTouchEvent(MotionEvent)}.
+    // Working variables for {@link onInterceptTouchEvent(MotionEvent)} and
+    // {@link onTouchEvent(MotionEvent)}.
     private int mLastX;
     private int mLastY;
     private int mOriginX;
     private int mOriginY;
     private final int mMoreSuggestionsModalTolerance;
+    private boolean mNeedsToTransformTouchEventToHoverEvent;
+    private boolean mIsDispatchingHoverEventToMoreSuggestions;
     private final GestureDetector mMoreSuggestionsSlidingDetector;
     private final GestureDetector.OnGestureListener mMoreSuggestionsSlidingListener =
             new GestureDetector.SimpleOnGestureListener() {
@@ -402,9 +405,12 @@
         final int y = (int)me.getY(index);
         if (Math.abs(x - mOriginX) >= mMoreSuggestionsModalTolerance
                 || mOriginY - y >= mMoreSuggestionsModalTolerance) {
-            // Decided to be in the sliding input mode only when the touch point has been moved
+            // Decided to be in the sliding suggestion mode only when the touch point has been moved
             // upward. Further {@link MotionEvent}s will be delivered to
             // {@link #onTouchEvent(MotionEvent)}.
+            mNeedsToTransformTouchEventToHoverEvent = AccessibilityUtils.getInstance()
+                    .isTouchExplorationEnabled();
+            mIsDispatchingHoverEventToMoreSuggestions = false;
             return true;
         }
 
@@ -426,10 +432,41 @@
         // In the sliding input mode. {@link MotionEvent} should be forwarded to
         // {@link MoreSuggestionsView}.
         final int index = me.getActionIndex();
-        final int x = (int)me.getX(index);
-        final int y = (int)me.getY(index);
-        me.setLocation(mMoreSuggestionsView.translateX(x), mMoreSuggestionsView.translateY(y));
-        mMoreSuggestionsView.onTouchEvent(me);
+        final int x = mMoreSuggestionsView.translateX((int)me.getX(index));
+        final int y = mMoreSuggestionsView.translateY((int)me.getY(index));
+        me.setLocation(x, y);
+        if (!mNeedsToTransformTouchEventToHoverEvent) {
+            mMoreSuggestionsView.onTouchEvent(me);
+            return true;
+        }
+        // In sliding suggestion mode with accessibility mode on, a touch event should be
+        // transformed to a hover event.
+        final int width = mMoreSuggestionsView.getWidth();
+        final int height = mMoreSuggestionsView.getHeight();
+        final boolean onMoreSuggestions = (x >= 0 && x < width && y >= 0 && y < height);
+        if (!onMoreSuggestions && !mIsDispatchingHoverEventToMoreSuggestions) {
+            // Just drop this touch event because dispatching hover event isn't started yet and
+            // the touch event isn't on {@link MoreSuggestionsView}.
+            return true;
+        }
+        final int hoverAction;
+        if (onMoreSuggestions && !mIsDispatchingHoverEventToMoreSuggestions) {
+            // Transform this touch event to a hover enter event and start dispatching a hover
+            // event to {@link MoreSuggestionsView}.
+            mIsDispatchingHoverEventToMoreSuggestions = true;
+            hoverAction = MotionEvent.ACTION_HOVER_ENTER;
+        } else if (me.getActionMasked() == MotionEvent.ACTION_UP) {
+            // Transform this touch event to a hover exit event and stop dispatching a hover event
+            // after this.
+            mIsDispatchingHoverEventToMoreSuggestions = false;
+            mNeedsToTransformTouchEventToHoverEvent = false;
+            hoverAction = MotionEvent.ACTION_HOVER_EXIT;
+        } else {
+            // Transform this touch event to a hover move event.
+            hoverAction = MotionEvent.ACTION_HOVER_MOVE;
+        }
+        me.setAction(hoverAction);
+        mMoreSuggestionsView.onHoverEvent(me);
         return true;
     }
 
diff --git a/java/src/com/android/inputmethod/latin/utils/ScriptUtils.java b/java/src/com/android/inputmethod/latin/utils/ScriptUtils.java
index a76a6df..0e24466 100644
--- a/java/src/com/android/inputmethod/latin/utils/ScriptUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ScriptUtils.java
@@ -26,13 +26,24 @@
     // Used for hardware keyboards
     public static final int SCRIPT_UNKNOWN = -1;
     // TODO: should we use ISO 15924 identifiers instead?
-    public static final int SCRIPT_LATIN = 0;
-    public static final int SCRIPT_CYRILLIC = 1;
-    public static final int SCRIPT_GREEK = 2;
-    public static final int SCRIPT_ARABIC = 3;
-    public static final int SCRIPT_HEBREW = 4;
-    public static final int SCRIPT_ARMENIAN = 5;
-    public static final int SCRIPT_GEORGIAN = 6;
+    public static final int SCRIPT_ARABIC = 0;
+    public static final int SCRIPT_ARMENIAN = 1;
+    public static final int SCRIPT_BENGALI = 2;
+    public static final int SCRIPT_CYRILLIC = 3;
+    public static final int SCRIPT_DEVANAGARI = 4;
+    public static final int SCRIPT_GEORGIAN = 5;
+    public static final int SCRIPT_GREEK = 6;
+    public static final int SCRIPT_HEBREW = 7;
+    public static final int SCRIPT_KANNADA = 8;
+    public static final int SCRIPT_KHMER = 9;
+    public static final int SCRIPT_LAO = 10;
+    public static final int SCRIPT_LATIN = 11;
+    public static final int SCRIPT_MALAYALAM = 12;
+    public static final int SCRIPT_MYANMAR = 13;
+    public static final int SCRIPT_SINHALA = 14;
+    public static final int SCRIPT_TAMIL = 15;
+    public static final int SCRIPT_TELUGU = 16;
+    public static final int SCRIPT_THAI = 17;
     public static final TreeMap<String, Integer> mSpellCheckerLanguageToScript;
     static {
         // List of the supported languages and their associated script. We won't check
@@ -72,18 +83,40 @@
      */
     public static boolean isLetterPartOfScript(final int codePoint, final int scriptId) {
         switch (scriptId) {
-        case SCRIPT_LATIN:
-            // Our supported latin script dictionaries (EFIGS) at the moment only include
-            // characters in the C0, C1, Latin Extended A and B, IPA extensions unicode
-            // blocks. As it happens, those are back-to-back in the code range 0x40 to 0x2AF,
-            // so the below is a very efficient way to test for it. As for the 0-0x3F, it's
-            // excluded from isLetter anyway.
-            return codePoint <= 0x2AF && Character.isLetter(codePoint);
+        case SCRIPT_ARABIC:
+            // Arabic letters can be in any of the following blocks:
+            // Arabic U+0600..U+06FF
+            // Arabic Supplement, Thaana U+0750..U+077F, U+0780..U+07BF
+            // Arabic Extended-A U+08A0..U+08FF
+            // Arabic Presentation Forms-A U+FB50..U+FDFF
+            // Arabic Presentation Forms-B U+FE70..U+FEFF
+            return (codePoint >= 0x600 && codePoint <= 0x6FF)
+                    || (codePoint >= 0x750 && codePoint <= 0x7BF)
+                    || (codePoint >= 0x8A0 && codePoint <= 0x8FF)
+                    || (codePoint >= 0xFB50 && codePoint <= 0xFDFF)
+                    || (codePoint >= 0xFE70 && codePoint <= 0xFEFF);
+        case SCRIPT_ARMENIAN:
+            // Armenian letters are in the Armenian unicode block, U+0530..U+058F and
+            // Alphabetic Presentation Forms block, U+FB00..U+FB4F, but only in the Armenian part
+            // of that block, which is U+FB13..U+FB17.
+            return (codePoint >= 0x530 && codePoint <= 0x58F
+                    || codePoint >= 0xFB13 && codePoint <= 0xFB17);
+        case SCRIPT_BENGALI:
+            // Bengali unicode block is U+0980..U+09FF
+            return (codePoint >= 0x980 && codePoint <= 0x9FF);
         case SCRIPT_CYRILLIC:
             // All Cyrillic characters are in the 400~52F block. There are some in the upper
             // Unicode range, but they are archaic characters that are not used in modern
             // Russian and are not used by our dictionary.
             return codePoint >= 0x400 && codePoint <= 0x52F && Character.isLetter(codePoint);
+        case SCRIPT_DEVANAGARI:
+            // Devanagari unicode block is +0900..U+097F
+            return (codePoint >= 0x900 && codePoint <= 0x97F);
+        case SCRIPT_GEORGIAN:
+            // Georgian letters are in the Georgian unicode block, U+10A0..U+10FF,
+            // or Georgian supplement block, U+2D00..U+2D2F
+            return (codePoint >= 0x10A0 && codePoint <= 0x10FF
+                    || codePoint >= 0x2D00 && codePoint <= 0x2D2F);
         case SCRIPT_GREEK:
             // Greek letters are either in the 370~3FF range (Greek & Coptic), or in the
             // 1F00~1FFF range (Greek extended). Our dictionary contains both sort of characters.
@@ -93,35 +126,53 @@
             return (codePoint >= 0x370 && codePoint <= 0x3FF)
                     || (codePoint >= 0x1F00 && codePoint <= 0x1FFF)
                     || codePoint == 0xF2;
-        case SCRIPT_ARABIC:
-            // Arabic letters can be in any of the following blocks:
-            // Arabic U+0600..U+06FF
-            // Arabic Supplement U+0750..U+077F
-            // Arabic Extended-A U+08A0..U+08FF
-            // Arabic Presentation Forms-A U+FB50..U+FDFF
-            // Arabic Presentation Forms-B U+FE70..U+FEFF
-            return (codePoint >= 0x600 && codePoint <= 0x6FF)
-                    || (codePoint >= 0x750 && codePoint <= 0x77F)
-                    || (codePoint >= 0x8A0 && codePoint <= 0x8FF)
-                    || (codePoint >= 0xFB50 && codePoint <= 0xFDFF)
-                    || (codePoint >= 0xFE70 && codePoint <= 0xFEFF);
         case SCRIPT_HEBREW:
             // Hebrew letters are in the Hebrew unicode block, which spans from U+0590 to U+05FF,
             // or in the Alphabetic Presentation Forms block, U+FB00..U+FB4F, but only in the
             // Hebrew part of that block, which is U+FB1D..U+FB4F.
             return (codePoint >= 0x590 && codePoint <= 0x5FF
                     || codePoint >= 0xFB1D && codePoint <= 0xFB4F);
-        case SCRIPT_ARMENIAN:
-            // Armenian letters are in the Armenian unicode block, U+0530..U+058F and
-            // Alphabetic Presentation Forms block, U+FB00..U+FB4F, but only in the Armenian part
-            // of that block, which is U+FB13..U+FB17.
-            return (codePoint >= 0x530 && codePoint <= 0x58F
-                    || codePoint >= 0xFB13 && codePoint <= 0xFB17);
-        case SCRIPT_GEORGIAN:
-            // Georgian letters are in the Georgian unicode block, U+10A0..U+10FF,
-            // or Georgian supplement block, U+2D00..U+2D2F
-            return (codePoint >= 0x10A0 && codePoint <= 0x10FF
-                    || codePoint >= 0x2D00 && codePoint <= 0x2D2F);
+        case SCRIPT_KANNADA:
+            // Kannada unicode block is U+0C80..U+0CFF
+            return (codePoint >= 0xC80 && codePoint <= 0xCFF);
+        case SCRIPT_KHMER:
+            // Khmer letters are in unicode block U+1780..U+17FF, and the Khmer symbols block
+            // is U+19E0..U+19FF
+            return (codePoint >= 0x1780 && codePoint <= 0x17FF
+                    || codePoint >= 0x19E0 && codePoint <= 0x19FF);
+        case SCRIPT_LAO:
+            // The Lao block is U+0E80..U+0EFF
+            return (codePoint >= 0xE80 && codePoint <= 0xEFF);
+        case SCRIPT_LATIN:
+            // Our supported latin script dictionaries (EFIGS) at the moment only include
+            // characters in the C0, C1, Latin Extended A and B, IPA extensions unicode
+            // blocks. As it happens, those are back-to-back in the code range 0x40 to 0x2AF,
+            // so the below is a very efficient way to test for it. As for the 0-0x3F, it's
+            // excluded from isLetter anyway.
+            return codePoint <= 0x2AF && Character.isLetter(codePoint);
+        case SCRIPT_MALAYALAM:
+            // Malayalam unicode block is U+0D00..U+0D7F
+            return (codePoint >= 0xD00 && codePoint <= 0xD7F);
+        case SCRIPT_MYANMAR:
+            // Myanmar has three unicode blocks :
+            // Myanmar U+1000..U+109F
+            // Myanmar extended-A U+AA60..U+AA7F
+            // Myanmar extended-B U+A9E0..U+A9FF
+            return (codePoint >= 0x1000 && codePoint <= 0x109F
+                    || codePoint >= 0xAA60 && codePoint <= 0xAA7F
+                    || codePoint >= 0xA9E0 && codePoint <= 0xA9FF);
+        case SCRIPT_SINHALA:
+            // Sinhala unicode block is U+0D80..U+0DFF
+            return (codePoint >= 0xD80 && codePoint <= 0xDFF);
+        case SCRIPT_TAMIL:
+            // Tamil unicode block is U+0B80..U+0BFF
+            return (codePoint >= 0xB80 && codePoint <= 0xBFF);
+        case SCRIPT_TELUGU:
+            // Telugu unicode block is U+0C00..U+0C7F
+            return (codePoint >= 0xC00 && codePoint <= 0xC7F);
+        case SCRIPT_THAI:
+            // Thai unicode block is U+0E00..U+0E7F
+            return (codePoint >= 0xE00 && codePoint <= 0xE7F);
         case SCRIPT_UNKNOWN:
             return true;
         default:
diff --git a/native/jni/src/suggest/core/dictionary/dictionary.cpp b/native/jni/src/suggest/core/dictionary/dictionary.cpp
index bf917d6..d625739 100644
--- a/native/jni/src/suggest/core/dictionary/dictionary.cpp
+++ b/native/jni/src/suggest/core/dictionary/dictionary.cpp
@@ -59,32 +59,44 @@
     }
 }
 
+Dictionary::NgramListenerForPrediction::NgramListenerForPrediction(
+        const PrevWordsInfo *const prevWordsInfo, SuggestionResults *const suggestionResults,
+        const DictionaryStructureWithBufferPolicy *const dictStructurePolicy)
+    : mPrevWordsInfo(prevWordsInfo), mSuggestionResults(suggestionResults),
+      mDictStructurePolicy(dictStructurePolicy) {}
+
+void Dictionary::NgramListenerForPrediction::onVisitEntry(const int ngramProbability,
+        const int targetPtNodePos) {
+    if (targetPtNodePos == NOT_A_DICT_POS) {
+        return;
+    }
+    if (mPrevWordsInfo->isNthPrevWordBeginningOfSentence(1 /* n */)
+            && ngramProbability == NOT_A_PROBABILITY) {
+        return;
+    }
+    int targetWordCodePoints[MAX_WORD_LENGTH];
+    int unigramProbability = 0;
+    const int codePointCount = mDictStructurePolicy->
+            getCodePointsAndProbabilityAndReturnCodePointCount(targetPtNodePos,
+                    MAX_WORD_LENGTH, targetWordCodePoints, &unigramProbability);
+    if (codePointCount <= 0) {
+        return;
+    }
+    const int probability = mDictStructurePolicy->getProbability(
+            unigramProbability, ngramProbability);
+    mSuggestionResults->addPrediction(targetWordCodePoints, codePointCount, probability);
+}
+
 void Dictionary::getPredictions(const PrevWordsInfo *const prevWordsInfo,
         SuggestionResults *const outSuggestionResults) const {
     TimeKeeper::setCurrentTime();
-    int unigramProbability = 0;
-    int bigramCodePoints[MAX_WORD_LENGTH];
-    BinaryDictionaryBigramsIterator bigramsIt = prevWordsInfo->getBigramsIteratorForPrediction(
+    NgramListenerForPrediction listener(prevWordsInfo, outSuggestionResults,
             mDictionaryStructureWithBufferPolicy.get());
-    while (bigramsIt.hasNext()) {
-        bigramsIt.next();
-        if (bigramsIt.getBigramPos() == NOT_A_DICT_POS) {
-            continue;
-        }
-        if (prevWordsInfo->isNthPrevWordBeginningOfSentence(1 /* n */)
-                && bigramsIt.getProbability() == NOT_A_PROBABILITY) {
-            continue;
-        }
-        const int codePointCount = mDictionaryStructureWithBufferPolicy->
-                getCodePointsAndProbabilityAndReturnCodePointCount(bigramsIt.getBigramPos(),
-                        MAX_WORD_LENGTH, bigramCodePoints, &unigramProbability);
-        if (codePointCount <= 0) {
-            continue;
-        }
-        const int probability = mDictionaryStructureWithBufferPolicy->getProbability(
-                unigramProbability, bigramsIt.getProbability());
-        outSuggestionResults->addPrediction(bigramCodePoints, codePointCount, probability);
-    }
+    int prevWordsPtNodePos[MAX_PREV_WORD_COUNT_FOR_N_GRAM];
+    prevWordsInfo->getPrevWordsTerminalPtNodePos(
+            mDictionaryStructureWithBufferPolicy.get(), prevWordsPtNodePos,
+            true /* tryLowerCaseSearch */);
+    mDictionaryStructureWithBufferPolicy->iterateNgramEntries(prevWordsPtNodePos, &listener);
 }
 
 int Dictionary::getProbability(const int *word, int length) const {
@@ -103,7 +115,15 @@
     int nextWordPos = mDictionaryStructureWithBufferPolicy->getTerminalPtNodePositionOfWord(word,
             length, false /* forceLowerCaseSearch */);
     if (NOT_A_DICT_POS == nextWordPos) return NOT_A_PROBABILITY;
-    return getDictionaryStructurePolicy()->getProbabilityOfPtNode(prevWordsInfo, nextWordPos);
+    if (!prevWordsInfo) {
+        return getDictionaryStructurePolicy()->getProbabilityOfPtNode(
+                nullptr /* prevWordsPtNodePos */, nextWordPos);
+    }
+    int prevWordsPtNodePos[MAX_PREV_WORD_COUNT_FOR_N_GRAM];
+    prevWordsInfo->getPrevWordsTerminalPtNodePos(
+            mDictionaryStructureWithBufferPolicy.get(), prevWordsPtNodePos,
+            true /* tryLowerCaseSearch */);
+    return getDictionaryStructurePolicy()->getProbabilityOfPtNode(prevWordsPtNodePos, nextWordPos);
 }
 
 bool Dictionary::addUnigramEntry(const int *const word, const int length,
diff --git a/native/jni/src/suggest/core/dictionary/dictionary.h b/native/jni/src/suggest/core/dictionary/dictionary.h
index 3b41088..732d3b1 100644
--- a/native/jni/src/suggest/core/dictionary/dictionary.h
+++ b/native/jni/src/suggest/core/dictionary/dictionary.h
@@ -21,6 +21,7 @@
 
 #include "defines.h"
 #include "jni.h"
+#include "suggest/core/dictionary/ngram_listener.h"
 #include "suggest/core/dictionary/property/word_property.h"
 #include "suggest/core/policy/dictionary_header_structure_policy.h"
 #include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
@@ -114,6 +115,21 @@
 
     typedef std::unique_ptr<SuggestInterface> SuggestInterfacePtr;
 
+    class NgramListenerForPrediction : public NgramListener {
+     public:
+        NgramListenerForPrediction(const PrevWordsInfo *const prevWordsInfo,
+                SuggestionResults *const suggestionResults,
+                const DictionaryStructureWithBufferPolicy *const dictStructurePolicy);
+        virtual void onVisitEntry(const int ngramProbability, const int targetPtNodePos);
+
+     private:
+        DISALLOW_IMPLICIT_CONSTRUCTORS(NgramListenerForPrediction);
+
+        const PrevWordsInfo *const mPrevWordsInfo;
+        SuggestionResults *const mSuggestionResults;
+        const DictionaryStructureWithBufferPolicy *const mDictStructurePolicy;
+    };
+
     static const int HEADER_ATTRIBUTE_BUFFER_SIZE;
 
     const DictionaryStructureWithBufferPolicy::StructurePolicyPtr
diff --git a/native/jni/src/suggest/core/dictionary/ngram_listener.h b/native/jni/src/suggest/core/dictionary/ngram_listener.h
new file mode 100644
index 0000000..88b88ba
--- /dev/null
+++ b/native/jni/src/suggest/core/dictionary/ngram_listener.h
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+#ifndef LATINIME_NGRAM_LISTENER_H
+#define LATINIME_NGRAM_LISTENER_H
+
+#include "defines.h"
+
+namespace latinime {
+
+/**
+ * Interface to iterate ngram entries.
+ */
+class NgramListener {
+ public:
+    virtual void onVisitEntry(const int ngramProbability, const int targetPtNodePos) = 0;
+    virtual ~NgramListener() {};
+
+ protected:
+    NgramListener() {}
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(NgramListener);
+
+};
+} // namespace latinime
+#endif /* LATINIME_NGRAM_LISTENER_H */
diff --git a/native/jni/src/suggest/core/layout/proximity_info_state.h b/native/jni/src/suggest/core/layout/proximity_info_state.h
index 6b1a319..e6180fe 100644
--- a/native/jni/src/suggest/core/layout/proximity_info_state.h
+++ b/native/jni/src/suggest/core/layout/proximity_info_state.h
@@ -215,13 +215,13 @@
     std::vector<float> mSpeedRates;
     std::vector<float> mDirections;
     // probabilities of skipping or mapping to a key for each point.
-    std::vector<std::unordered_map<int, float> > mCharProbabilities;
+    std::vector<std::unordered_map<int, float>> mCharProbabilities;
     // The vector for the key code set which holds nearby keys of some trailing sampled input points
     // for each sampled input point. These nearby keys contain the next characters which can be in
     // the dictionary. Specifically, currently we are looking for keys nearby trailing sampled
     // inputs including the current input point.
     std::vector<ProximityInfoStateUtils::NearKeycodesSet> mSampledSearchKeySets;
-    std::vector<std::vector<int> > mSampledSearchKeyVectors;
+    std::vector<std::vector<int>> mSampledSearchKeyVectors;
     bool mTouchPositionCorrectionEnabled;
     int mInputProximities[MAX_PROXIMITY_CHARS_SIZE * MAX_WORD_LENGTH];
     int mSampledInputSize;
diff --git a/native/jni/src/suggest/core/layout/proximity_info_state_utils.cpp b/native/jni/src/suggest/core/layout/proximity_info_state_utils.cpp
index ea3b022..0aeb36a 100644
--- a/native/jni/src/suggest/core/layout/proximity_info_state_utils.cpp
+++ b/native/jni/src/suggest/core/layout/proximity_info_state_utils.cpp
@@ -621,7 +621,7 @@
         const std::vector<int> *const sampledLengthCache,
         const std::vector<float> *const sampledNormalizedSquaredLengthCache,
         const ProximityInfo *const proximityInfo,
-        std::vector<std::unordered_map<int, float> > *charProbabilities) {
+        std::vector<std::unordered_map<int, float>> *charProbabilities) {
     charProbabilities->resize(sampledInputSize);
     // Calculates probabilities of using a point as a correlated point with the character
     // for each point.
@@ -822,9 +822,9 @@
 /* static */ void ProximityInfoStateUtils::updateSampledSearchKeySets(
         const ProximityInfo *const proximityInfo, const int sampledInputSize,
         const int lastSavedInputSize, const std::vector<int> *const sampledLengthCache,
-        const std::vector<std::unordered_map<int, float> > *const charProbabilities,
+        const std::vector<std::unordered_map<int, float>> *const charProbabilities,
         std::vector<NearKeycodesSet> *sampledSearchKeySets,
-        std::vector<std::vector<int> > *sampledSearchKeyVectors) {
+        std::vector<std::vector<int>> *sampledSearchKeyVectors) {
     sampledSearchKeySets->resize(sampledInputSize);
     sampledSearchKeyVectors->resize(sampledInputSize);
     const int readForwordLength = static_cast<int>(
@@ -868,7 +868,7 @@
 /* static */ bool ProximityInfoStateUtils::suppressCharProbabilities(const int mostCommonKeyWidth,
         const int sampledInputSize, const std::vector<int> *const lengthCache,
         const int index0, const int index1,
-        std::vector<std::unordered_map<int, float> > *charProbabilities) {
+        std::vector<std::unordered_map<int, float>> *charProbabilities) {
     ASSERT(0 <= index0 && index0 < sampledInputSize);
     ASSERT(0 <= index1 && index1 < sampledInputSize);
     const float keyWidthFloat = static_cast<float>(mostCommonKeyWidth);
@@ -933,7 +933,7 @@
 // returns probability of generating the word.
 /* static */ float ProximityInfoStateUtils::getMostProbableString(
         const ProximityInfo *const proximityInfo, const int sampledInputSize,
-        const std::vector<std::unordered_map<int, float> > *const charProbabilities,
+        const std::vector<std::unordered_map<int, float>> *const charProbabilities,
         int *const codePointBuf) {
     ASSERT(sampledInputSize >= 0);
     memset(codePointBuf, 0, sizeof(codePointBuf[0]) * MAX_WORD_LENGTH);
diff --git a/native/jni/src/suggest/core/layout/proximity_info_state_utils.h b/native/jni/src/suggest/core/layout/proximity_info_state_utils.h
index 211a797..4043334 100644
--- a/native/jni/src/suggest/core/layout/proximity_info_state_utils.h
+++ b/native/jni/src/suggest/core/layout/proximity_info_state_utils.h
@@ -72,13 +72,13 @@
             const std::vector<int> *const sampledLengthCache,
             const std::vector<float> *const sampledNormalizedSquaredLengthCache,
             const ProximityInfo *const proximityInfo,
-            std::vector<std::unordered_map<int, float> > *charProbabilities);
+            std::vector<std::unordered_map<int, float>> *charProbabilities);
     static void updateSampledSearchKeySets(const ProximityInfo *const proximityInfo,
             const int sampledInputSize, const int lastSavedInputSize,
             const std::vector<int> *const sampledLengthCache,
-            const std::vector<std::unordered_map<int, float> > *const charProbabilities,
+            const std::vector<std::unordered_map<int, float>> *const charProbabilities,
             std::vector<NearKeycodesSet> *sampledSearchKeySets,
-            std::vector<std::vector<int> > *sampledSearchKeyVectors);
+            std::vector<std::vector<int>> *sampledSearchKeyVectors);
     static float getPointToKeyByIdLength(const float maxPointToKeyLength,
             const std::vector<float> *const sampledNormalizedSquaredLengthCache, const int keyCount,
             const int inputIndex, const int keyId);
@@ -105,7 +105,7 @@
     // TODO: Move to most_probable_string_utils.h
     static float getMostProbableString(const ProximityInfo *const proximityInfo,
             const int sampledInputSize,
-            const std::vector<std::unordered_map<int, float> > *const charProbabilities,
+            const std::vector<std::unordered_map<int, float>> *const charProbabilities,
             int *const codePointBuf);
 
  private:
@@ -147,7 +147,7 @@
             const int index2);
     static bool suppressCharProbabilities(const int mostCommonKeyWidth,
             const int sampledInputSize, const std::vector<int> *const lengthCache, const int index0,
-            const int index1, std::vector<std::unordered_map<int, float> > *charProbabilities);
+            const int index1, std::vector<std::unordered_map<int, float>> *charProbabilities);
     static float calculateSquaredDistanceFromSweetSpotCenter(
             const ProximityInfo *const proximityInfo, const std::vector<int> *const sampledInputXs,
             const std::vector<int> *const sampledInputYs, const int keyIndex,
diff --git a/native/jni/src/suggest/core/policy/dictionary_header_structure_policy.h b/native/jni/src/suggest/core/policy/dictionary_header_structure_policy.h
index a612276..6da390e 100644
--- a/native/jni/src/suggest/core/policy/dictionary_header_structure_policy.h
+++ b/native/jni/src/suggest/core/policy/dictionary_header_structure_policy.h
@@ -30,7 +30,7 @@
  */
 class DictionaryHeaderStructurePolicy {
  public:
-    typedef std::map<std::vector<int>, std::vector<int> > AttributeMap;
+    typedef std::map<std::vector<int>, std::vector<int>> AttributeMap;
 
     virtual ~DictionaryHeaderStructurePolicy() {}
 
diff --git a/native/jni/src/suggest/core/policy/dictionary_structure_with_buffer_policy.h b/native/jni/src/suggest/core/policy/dictionary_structure_with_buffer_policy.h
index 7ad20e7..7e3bf3f 100644
--- a/native/jni/src/suggest/core/policy/dictionary_structure_with_buffer_policy.h
+++ b/native/jni/src/suggest/core/policy/dictionary_structure_with_buffer_policy.h
@@ -30,6 +30,7 @@
 class DictionaryBigramsStructurePolicy;
 class DictionaryHeaderStructurePolicy;
 class DictionaryShortcutsStructurePolicy;
+class NgramListener;
 class PrevWordsInfo;
 class UnigramProperty;
 
@@ -58,9 +59,12 @@
     virtual int getProbability(const int unigramProbability,
             const int bigramProbability) const = 0;
 
-    virtual int getProbabilityOfPtNode(const PrevWordsInfo *const prevWordsInfo,
+    virtual int getProbabilityOfPtNode(const int *const prevWordsPtNodePos,
             const int nodePos) const = 0;
 
+    virtual void iterateNgramEntries(const int *const prevWordsPtNodePos,
+            NgramListener *const listener) const = 0;
+
     virtual int getShortcutPositionOfPtNode(const int nodePos) const = 0;
 
     virtual BinaryDictionaryBigramsIterator getBigramsIteratorOfPtNode(const int nodePos) const = 0;
diff --git a/native/jni/src/suggest/core/policy/scoring.h b/native/jni/src/suggest/core/policy/scoring.h
index 292194bf..9e75cac 100644
--- a/native/jni/src/suggest/core/policy/scoring.h
+++ b/native/jni/src/suggest/core/policy/scoring.h
@@ -37,7 +37,6 @@
             DicNode *const terminals, const int size) const = 0;
     virtual float getDoubleLetterDemotionDistanceCost(
             const DicNode *const terminalDicNode) const = 0;
-    virtual bool doesAutoCorrectValidWord() const = 0;
     virtual bool autoCorrectsToMultiWordSuggestionIfTop() const = 0;
     virtual bool sameAsTyped(const DicTraverseSession *const traverseSession,
             const DicNode *const dicNode) const = 0;
diff --git a/native/jni/src/suggest/core/result/suggestions_output_utils.cpp b/native/jni/src/suggest/core/result/suggestions_output_utils.cpp
index 7b0e7e1..0b99b75 100644
--- a/native/jni/src/suggest/core/result/suggestions_output_utils.cpp
+++ b/native/jni/src/suggest/core/result/suggestions_output_utils.cpp
@@ -117,8 +117,7 @@
     const int finalScore = scoringPolicy->calculateFinalScore(
             compoundDistance, traverseSession->getInputSize(),
             terminalDicNode->getContainedErrorTypes(),
-            (forceCommitMultiWords && terminalDicNode->hasMultipleWords())
-                     || (isValidWord && scoringPolicy->doesAutoCorrectValidWord()),
+            (forceCommitMultiWords && terminalDicNode->hasMultipleWords()),
             boostExactMatches);
 
     // Don't output invalid or blocked offensive words. However, we still need to submit their
@@ -145,12 +144,7 @@
                 traverseSession->getDictionaryStructurePolicy()
                         ->getShortcutPositionOfPtNode(terminalDicNode->getPtNodePos()));
         const bool sameAsTyped = scoringPolicy->sameAsTyped(traverseSession, terminalDicNode);
-        const int shortcutBaseScore = scoringPolicy->doesAutoCorrectValidWord() ?
-                 scoringPolicy->calculateFinalScore(compoundDistance,
-                         traverseSession->getInputSize(),
-                         terminalDicNode->getContainedErrorTypes(),
-                         true /* forceCommit */, boostExactMatches) : finalScore;
-        outputShortcuts(&shortcutIt, shortcutBaseScore, sameAsTyped, outSuggestionResults);
+        outputShortcuts(&shortcutIt, finalScore, sameAsTyped, outSuggestionResults);
     }
 }
 
diff --git a/native/jni/src/suggest/core/session/prev_words_info.h b/native/jni/src/suggest/core/session/prev_words_info.h
index 76276f5..e44e876 100644
--- a/native/jni/src/suggest/core/session/prev_words_info.h
+++ b/native/jni/src/suggest/core/session/prev_words_info.h
@@ -90,13 +90,6 @@
         }
     }
 
-    BinaryDictionaryBigramsIterator getBigramsIteratorForPrediction(
-            const DictionaryStructureWithBufferPolicy *const dictStructurePolicy) const {
-        return getBigramsIteratorForWordWithTryingLowerCaseSearch(
-                dictStructurePolicy, mPrevWordCodePoints[0], mPrevWordCodePointCount[0],
-                mIsBeginningOfSentence[0]);
-    }
-
     // n is 1-indexed.
     const int *getNthPrevWordCodePoints(const int n) const {
         if (n <= 0 || n > MAX_PREV_WORD_COUNT_FOR_N_GRAM) {
@@ -154,46 +147,6 @@
                 codePoints, codePointCount, true /* forceLowerCaseSearch */);
     }
 
-    static BinaryDictionaryBigramsIterator getBigramsIteratorForWordWithTryingLowerCaseSearch(
-            const DictionaryStructureWithBufferPolicy *const dictStructurePolicy,
-            const int *const wordCodePoints, const int wordCodePointCount,
-            const bool isBeginningOfSentence) {
-        if (!dictStructurePolicy || !wordCodePoints || wordCodePointCount > MAX_WORD_LENGTH) {
-            return BinaryDictionaryBigramsIterator();
-        }
-        int codePoints[MAX_WORD_LENGTH];
-        int codePointCount = wordCodePointCount;
-        memmove(codePoints, wordCodePoints, sizeof(int) * codePointCount);
-        if (isBeginningOfSentence) {
-            codePointCount = CharUtils::attachBeginningOfSentenceMarker(codePoints,
-                    codePointCount, MAX_WORD_LENGTH);
-            if (codePointCount <= 0) {
-                return BinaryDictionaryBigramsIterator();
-            }
-        }
-        BinaryDictionaryBigramsIterator bigramsIt = getBigramsIteratorForWord(dictStructurePolicy,
-                codePoints, codePointCount, false /* forceLowerCaseSearch */);
-        // getBigramsIteratorForWord returns an empty iterator if this word isn't in the dictionary
-        // or has no bigrams.
-        if (bigramsIt.hasNext()) {
-            return bigramsIt;
-        }
-        // If no bigrams for this exact word, search again in lower case.
-        return getBigramsIteratorForWord(dictStructurePolicy, codePoints,
-                codePointCount, true /* forceLowerCaseSearch */);
-    }
-
-    static BinaryDictionaryBigramsIterator getBigramsIteratorForWord(
-            const DictionaryStructureWithBufferPolicy *const dictStructurePolicy,
-            const int *wordCodePoints, const int wordCodePointCount,
-            const bool forceLowerCaseSearch) {
-        if (!wordCodePoints || wordCodePointCount <= 0) return BinaryDictionaryBigramsIterator();
-        const int terminalPtNodePos = dictStructurePolicy->getTerminalPtNodePositionOfWord(
-                wordCodePoints, wordCodePointCount, forceLowerCaseSearch);
-        if (NOT_A_DICT_POS == terminalPtNodePos) return BinaryDictionaryBigramsIterator();
-        return dictStructurePolicy->getBigramsIteratorOfPtNode(terminalPtNodePos);
-    }
-
     void clear() {
         for (size_t i = 0; i < NELEMS(mPrevWordCodePoints); ++i) {
             mPrevWordCodePointCount[i] = 0;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.cpp
index 3277410..994c425 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.cpp
@@ -28,6 +28,7 @@
 
 #include "suggest/core/dicnode/dic_node.h"
 #include "suggest/core/dicnode/dic_node_vector.h"
+#include "suggest/core/dictionary/ngram_listener.h"
 #include "suggest/core/dictionary/property/bigram_property.h"
 #include "suggest/core/dictionary/property/unigram_property.h"
 #include "suggest/core/dictionary/property/word_property.h"
@@ -131,7 +132,7 @@
     }
 }
 
-int Ver4PatriciaTriePolicy::getProbabilityOfPtNode(const PrevWordsInfo *const prevWordsInfo,
+int Ver4PatriciaTriePolicy::getProbabilityOfPtNode(const int *const prevWordsPtNodePos,
         const int ptNodePos) const {
     if (ptNodePos == NOT_A_DICT_POS) {
         return NOT_A_PROBABILITY;
@@ -140,9 +141,9 @@
     if (ptNodeParams.isDeleted() || ptNodeParams.isBlacklisted() || ptNodeParams.isNotAWord()) {
         return NOT_A_PROBABILITY;
     }
-    if (prevWordsInfo) {
+    if (prevWordsPtNodePos) {
         BinaryDictionaryBigramsIterator bigramsIt =
-                prevWordsInfo->getBigramsIteratorForPrediction(this /* dictStructurePolicy */);
+                getBigramsIteratorOfPtNode(prevWordsPtNodePos[0]);
         while (bigramsIt.hasNext()) {
             bigramsIt.next();
             if (bigramsIt.getBigramPos() == ptNodePos
@@ -155,6 +156,18 @@
     return getProbability(ptNodeParams.getProbability(), NOT_A_PROBABILITY);
 }
 
+void Ver4PatriciaTriePolicy::iterateNgramEntries(const int *const prevWordsPtNodePos,
+        NgramListener *const listener) const {
+    if (!prevWordsPtNodePos) {
+        return;
+    }
+    BinaryDictionaryBigramsIterator bigramsIt = getBigramsIteratorOfPtNode(prevWordsPtNodePos[0]);
+    while (bigramsIt.hasNext()) {
+        bigramsIt.next();
+        listener->onVisitEntry(bigramsIt.getProbability(), bigramsIt.getBigramPos());
+    }
+}
+
 int Ver4PatriciaTriePolicy::getShortcutPositionOfPtNode(const int ptNodePos) const {
     if (ptNodePos == NOT_A_DICT_POS) {
         return NOT_A_DICT_POS;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.h b/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.h
index c80a73a..ff69de7 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.h
@@ -90,8 +90,10 @@
 
     int getProbability(const int unigramProbability, const int bigramProbability) const;
 
-    int getProbabilityOfPtNode(const PrevWordsInfo *const prevWordsInfo,
-            const int ptNodePos) const;
+    int getProbabilityOfPtNode(const int *const prevWordsPtNodePos, const int ptNodePos) const;
+
+    void iterateNgramEntries(const int *const prevWordsPtNodePos,
+            NgramListener *const listener) const;
 
     int getShortcutPositionOfPtNode(const int ptNodePos) const;
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp
index b909e82..53415ae 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp
@@ -21,6 +21,7 @@
 #include "suggest/core/dicnode/dic_node.h"
 #include "suggest/core/dicnode/dic_node_vector.h"
 #include "suggest/core/dictionary/binary_dictionary_bigrams_iterator.h"
+#include "suggest/core/dictionary/ngram_listener.h"
 #include "suggest/core/session/prev_words_info.h"
 #include "suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_reading_helper.h"
 #include "suggest/policyimpl/dictionary/structure/pt_common/patricia_trie_reading_utils.h"
@@ -296,7 +297,7 @@
     }
 }
 
-int PatriciaTriePolicy::getProbabilityOfPtNode(const PrevWordsInfo *const prevWordsInfo,
+int PatriciaTriePolicy::getProbabilityOfPtNode(const int *const prevWordsPtNodePos,
         const int ptNodePos) const {
     if (ptNodePos == NOT_A_DICT_POS) {
         return NOT_A_PROBABILITY;
@@ -309,9 +310,9 @@
         // for shortcuts).
         return NOT_A_PROBABILITY;
     }
-    if (prevWordsInfo) {
+    if (prevWordsPtNodePos) {
         BinaryDictionaryBigramsIterator bigramsIt =
-                prevWordsInfo->getBigramsIteratorForPrediction(this /* dictStructurePolicy */);
+                getBigramsIteratorOfPtNode(prevWordsPtNodePos[0]);
         while (bigramsIt.hasNext()) {
             bigramsIt.next();
             if (bigramsIt.getBigramPos() == ptNodePos
@@ -324,6 +325,18 @@
     return getProbability(ptNodeParams.getProbability(), NOT_A_PROBABILITY);
 }
 
+void PatriciaTriePolicy::iterateNgramEntries(const int *const prevWordsPtNodePos,
+        NgramListener *const listener) const {
+    if (!prevWordsPtNodePos) {
+        return;
+    }
+    BinaryDictionaryBigramsIterator bigramsIt = getBigramsIteratorOfPtNode(prevWordsPtNodePos[0]);
+    while (bigramsIt.hasNext()) {
+        bigramsIt.next();
+        listener->onVisitEntry(bigramsIt.getProbability(), bigramsIt.getBigramPos());
+    }
+}
+
 int PatriciaTriePolicy::getShortcutPositionOfPtNode(const int ptNodePos) const {
     if (ptNodePos == NOT_A_DICT_POS) {
         return NOT_A_DICT_POS;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h
index 1dd5705..07cb72b 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h
@@ -63,7 +63,10 @@
 
     int getProbability(const int unigramProbability, const int bigramProbability) const;
 
-    int getProbabilityOfPtNode(const PrevWordsInfo *const prevWordsInfo, const int ptNodePos) const;
+    int getProbabilityOfPtNode(const int *const prevWordsPtNodePos, const int ptNodePos) const;
+
+    void iterateNgramEntries(const int *const prevWordsPtNodePos,
+            NgramListener *const listener) const;
 
     int getShortcutPositionOfPtNode(const int ptNodePos) const;
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.cpp
index 36ab996..ac95403 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.cpp
@@ -118,7 +118,7 @@
     }
     FILE *const file = fdopen(fd, "wb");
     if (!file) {
-        AKLOGE("fdopen failed for the file %s. errno: %d", filePath, errno);
+        AKLOGE("fdopen failed for the file %s. errno: %d", bodyFilePath, errno);
         ASSERT(false);
         return false;
     }
@@ -146,27 +146,27 @@
 bool Ver4DictBuffers::flushDictBuffers(FILE *const file) const {
     // Write trie.
     if (!DictFileWritingUtils::writeBufferToFileTail(file, &mExpandableTrieBuffer)) {
-        AKLOGE("Trie cannot be written. %s", tmpDirPath);
+        AKLOGE("Trie cannot be written.");
         return false;
     }
     // Write terminal position lookup table.
     if (!mTerminalPositionLookupTable.flushToFile(file)) {
-        AKLOGE("Terminal position lookup table cannot be written. %s", tmpDirPath);
+        AKLOGE("Terminal position lookup table cannot be written.");
         return false;
     }
     // Write probability dict content.
     if (!mProbabilityDictContent.flushToFile(file)) {
-        AKLOGE("Probability dict content cannot be written. %s", tmpDirPath);
+        AKLOGE("Probability dict content cannot be written.");
         return false;
     }
     // Write bigram dict content.
     if (!mBigramDictContent.flushToFile(file)) {
-        AKLOGE("Bigram dict content cannot be written. %s", tmpDirPath);
+        AKLOGE("Bigram dict content cannot be written.");
         return false;
     }
     // Write shortcut dict content.
     if (!mShortcutDictContent.flushToFile(file)) {
-        AKLOGE("Shortcut dict content cannot be written. %s", tmpDirPath);
+        AKLOGE("Shortcut dict content cannot be written.");
         return false;
     }
     return true;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp
index cada3d1..22f7e11 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp
@@ -20,6 +20,7 @@
 
 #include "suggest/core/dicnode/dic_node.h"
 #include "suggest/core/dicnode/dic_node_vector.h"
+#include "suggest/core/dictionary/ngram_listener.h"
 #include "suggest/core/dictionary/property/bigram_property.h"
 #include "suggest/core/dictionary/property/unigram_property.h"
 #include "suggest/core/dictionary/property/word_property.h"
@@ -121,7 +122,7 @@
     }
 }
 
-int Ver4PatriciaTriePolicy::getProbabilityOfPtNode(const PrevWordsInfo *const prevWordsInfo,
+int Ver4PatriciaTriePolicy::getProbabilityOfPtNode(const int *const prevWordsPtNodePos,
         const int ptNodePos) const {
     if (ptNodePos == NOT_A_DICT_POS) {
         return NOT_A_PROBABILITY;
@@ -130,9 +131,9 @@
     if (ptNodeParams.isDeleted() || ptNodeParams.isBlacklisted() || ptNodeParams.isNotAWord()) {
         return NOT_A_PROBABILITY;
     }
-    if (prevWordsInfo) {
+    if (prevWordsPtNodePos) {
         BinaryDictionaryBigramsIterator bigramsIt =
-                prevWordsInfo->getBigramsIteratorForPrediction(this /* dictStructurePolicy */);
+                getBigramsIteratorOfPtNode(prevWordsPtNodePos[0]);
         while (bigramsIt.hasNext()) {
             bigramsIt.next();
             if (bigramsIt.getBigramPos() == ptNodePos
@@ -145,6 +146,18 @@
     return getProbability(ptNodeParams.getProbability(), NOT_A_PROBABILITY);
 }
 
+void Ver4PatriciaTriePolicy::iterateNgramEntries(const int *const prevWordsPtNodePos,
+        NgramListener *const listener) const {
+    if (!prevWordsPtNodePos) {
+        return;
+    }
+    BinaryDictionaryBigramsIterator bigramsIt = getBigramsIteratorOfPtNode(prevWordsPtNodePos[0]);
+    while (bigramsIt.hasNext()) {
+        bigramsIt.next();
+        listener->onVisitEntry(bigramsIt.getProbability(), bigramsIt.getBigramPos());
+    }
+}
+
 int Ver4PatriciaTriePolicy::getShortcutPositionOfPtNode(const int ptNodePos) const {
     if (ptNodePos == NOT_A_DICT_POS) {
         return NOT_A_DICT_POS;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.h
index b0f16cd..c5b6a80 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.h
@@ -72,7 +72,10 @@
 
     int getProbability(const int unigramProbability, const int bigramProbability) const;
 
-    int getProbabilityOfPtNode(const PrevWordsInfo *const prevWordsInfo, const int ptNodePos) const;
+    int getProbabilityOfPtNode(const int *const prevWordsPtNodePos, const int ptNodePos) const;
+
+    void iterateNgramEntries(const int *const prevWordsPtNodePos,
+            NgramListener *const listener) const;
 
     int getShortcutPositionOfPtNode(const int ptNodePos) const;
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/forgetting_curve_utils.h b/native/jni/src/suggest/policyimpl/dictionary/utils/forgetting_curve_utils.h
index 3ff80ae..9910777 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/forgetting_curve_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/forgetting_curve_utils.h
@@ -84,7 +84,7 @@
         static const int STRONG_BASE_PROBABILITY;
         static const int AGGRESSIVE_BASE_PROBABILITY;
 
-        std::vector<std::vector<std::vector<int> > > mTables;
+        std::vector<std::vector<std::vector<int>>> mTables;
 
         static int getBaseProbabilityForLevel(const int tableId, const int level);
     };
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_scoring.h b/native/jni/src/suggest/policyimpl/typing/typing_scoring.h
index 66ea624..04cb660 100644
--- a/native/jni/src/suggest/policyimpl/typing/typing_scoring.h
+++ b/native/jni/src/suggest/policyimpl/typing/typing_scoring.h
@@ -69,10 +69,6 @@
         return 0.0f;
     }
 
-    AK_FORCE_INLINE bool doesAutoCorrectValidWord() const {
-        return false;
-    }
-
     AK_FORCE_INLINE bool autoCorrectsToMultiWordSuggestionIfTop() const {
         return true;
     }
diff --git a/tools/dicttool/Android.mk b/tools/dicttool/Android.mk
index e4de59d..0f1f344 100644
--- a/tools/dicttool/Android.mk
+++ b/tools/dicttool/Android.mk
@@ -88,6 +88,7 @@
         $(call all-java-files-under, $(DICTTOOL_ONDEVICE_TESTS_DIRECTORY))
 
 LOCAL_JAVA_LIBRARIES := junit
+LOCAL_STATIC_JAVA_LIBRARIES := jsr305lib
 LOCAL_REQUIRED_MODULES := $(LATINIME_HOST_NATIVE_LIBNAME)
 LOCAL_JAR_MANIFEST := etc/manifest.txt
 LOCAL_MODULE := dicttool_aosp
diff --git a/tools/dicttool/compat/com/android/inputmethod/event/CombinerChain.java b/tools/dicttool/compat/com/android/inputmethod/event/CombinerChain.java
index 458f22c..c4457a1 100644
--- a/tools/dicttool/compat/com/android/inputmethod/event/CombinerChain.java
+++ b/tools/dicttool/compat/com/android/inputmethod/event/CombinerChain.java
@@ -31,8 +31,12 @@
         mComposingWord = new StringBuilder(initialText);
     }
 
-    public void processEvent(final ArrayList<Event> previousEvents, final Event newEvent) {
-        mComposingWord.append(newEvent.getTextToCommit());
+    public Event processEvent(final ArrayList<Event> previousEvents, final Event newEvent) {
+        return newEvent;
+    }
+
+    public void applyProcessedEvent(final Event event) {
+        mComposingWord.append(event.getTextToCommit());
     }
 
     public CharSequence getComposingWordWithCombiningFeedback() {