Merge "[SD10] Add script checks for Lao and Khmer" 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/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/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..bb2d304 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -762,7 +762,8 @@
             resetComposingState(false /* alsoResetLastComposedWord */);
         }
         if (isComposingWord) {
-            mWordComposer.processEvent(inputTransaction.mEvent);
+            final Event processedEvent = 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);
@@ -933,7 +934,8 @@
                     mDictionaryFacilitator.removeWordFromPersonalizedDicts(rejectedSuggestion);
                 }
             } else {
-                mWordComposer.processEvent(inputTransaction.mEvent);
+                final Event processedEvent = mWordComposer.processEvent(inputTransaction.mEvent);
+                mWordComposer.applyProcessedEvent(processedEvent);
             }
             if (mWordComposer.isComposingWord()) {
                 mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
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/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/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() {