Merge "Add hindi_compact keyboard"
diff --git a/java/src/com/android/inputmethod/event/Event.java b/java/src/com/android/inputmethod/event/Event.java
index 6465909..6535d2d 100644
--- a/java/src/com/android/inputmethod/event/Event.java
+++ b/java/src/com/android/inputmethod/event/Event.java
@@ -36,25 +36,25 @@
     // Should the types below be represented by separate classes instead? It would be cleaner
     // but probably a bit too much
     // An event we don't handle in Latin IME, for example pressing Ctrl on a hardware keyboard.
-    final public static int EVENT_NOT_HANDLED = 0;
+    final public static int EVENT_TYPE_NOT_HANDLED = 0;
     // A key press that is part of input, for example pressing an alphabetic character on a
     // hardware qwerty keyboard. It may be part of a sequence that will be re-interpreted later
     // through combination.
-    final public static int EVENT_INPUT_KEYPRESS = 1;
+    final public static int EVENT_TYPE_INPUT_KEYPRESS = 1;
     // A toggle event is triggered by a key that affects the previous character. An example would
     // be a numeric key on a 10-key keyboard, which would toggle between 1 - a - b - c with
     // repeated presses.
-    final public static int EVENT_TOGGLE = 2;
+    final public static int EVENT_TYPE_TOGGLE = 2;
     // A mode event instructs the combiner to change modes. The canonical example would be the
     // hankaku/zenkaku key on a Japanese keyboard, or even the caps lock key on a qwerty keyboard
     // if handled at the combiner level.
-    final public static int EVENT_MODE_KEY = 3;
+    final public static int EVENT_TYPE_MODE_KEY = 3;
     // An event corresponding to a gesture.
-    final public static int EVENT_GESTURE = 4;
+    final public static int EVENT_TYPE_GESTURE = 4;
     // An event corresponding to the manual pick of a suggestion.
-    final public static int EVENT_SUGGESTION_PICKED = 5;
+    final public static int EVENT_TYPE_SUGGESTION_PICKED = 5;
     // An event corresponding to a string generated by some software process.
-    final public static int EVENT_SOFTWARE_GENERATED_STRING = 6;
+    final public static int EVENT_TYPE_SOFTWARE_GENERATED_STRING = 6;
 
     // 0 is a valid code point, so we use -1 here.
     final public static int NOT_A_CODE_POINT = -1;
@@ -66,7 +66,7 @@
     // or dead-abovering.
     final private static int FLAG_DEAD = 0x1;
 
-    final private int mType; // The type of event - one of the constants above
+    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
     // has nothing to do with other representations of the key. It is only relevant if this event
     // is of KEYPRESS type, but for a mode key like hankaku/zenkaku or ctrl, there is no code point
@@ -94,7 +94,7 @@
     // Some flags that can't go into the key code. It's a bit field of FLAG_*
     final private int mFlags;
 
-    // If this is of type EVENT_SUGGESTION_PICKED, this must not be null (and must be null in
+    // If this is of type EVENT_TYPE_SUGGESTION_PICKED, this must not be null (and must be null in
     // other cases).
     final public SuggestedWordInfo mSuggestedWordInfo;
 
@@ -105,7 +105,7 @@
     private Event(final int type, final CharSequence text, final int codePoint, final int keyCode,
             final int x, final int y, final SuggestedWordInfo suggestedWordInfo, final int flags,
             final Event next) {
-        mType = type;
+        mEventType = type;
         mText = text;
         mCodePoint = codePoint;
         mKeyCode = keyCode;
@@ -116,7 +116,7 @@
         mNextEvent = next;
         // Sanity checks
         // mSuggestedWordInfo is non-null if and only if the type is SUGGESTION_PICKED
-        if (EVENT_SUGGESTION_PICKED == mType) {
+        if (EVENT_TYPE_SUGGESTION_PICKED == mEventType) {
             if (null == mSuggestedWordInfo) {
                 throw new RuntimeException("Wrong event: SUGGESTION_PICKED event must have a "
                         + "non-null SuggestedWordInfo");
@@ -131,13 +131,13 @@
 
     public static Event createSoftwareKeypressEvent(final int codePoint, final int keyCode,
             final int x, final int y) {
-        return new Event(EVENT_INPUT_KEYPRESS, null /* text */, codePoint, keyCode, x, y,
-                null /* suggestedWordInfo */, FLAG_NONE, null);
+        return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, keyCode, x, y,
+                null /* suggestedWordInfo */, FLAG_NONE, null /* next */);
     }
 
     public static Event createHardwareKeypressEvent(final int codePoint, final int keyCode,
             final Event next) {
-        return new Event(EVENT_INPUT_KEYPRESS, null /* text */, codePoint, keyCode,
+        return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, keyCode,
                 Constants.EXTERNAL_KEYBOARD_COORDINATE, Constants.EXTERNAL_KEYBOARD_COORDINATE,
                 null /* suggestedWordInfo */, FLAG_NONE, next);
     }
@@ -145,7 +145,7 @@
     // This creates an input event for a dead character. @see {@link #FLAG_DEAD}
     public static Event createDeadEvent(final int codePoint, final int keyCode, final Event next) {
         // TODO: add an argument or something if we ever create a software layout with dead keys.
-        return new Event(EVENT_INPUT_KEYPRESS, null /* text */, codePoint, keyCode,
+        return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, keyCode,
                 Constants.EXTERNAL_KEYBOARD_COORDINATE, Constants.EXTERNAL_KEYBOARD_COORDINATE,
                 null /* suggestedWordInfo */, FLAG_DEAD, next);
     }
@@ -159,7 +159,7 @@
      */
     public static Event createEventForCodePointFromUnknownSource(final int codePoint) {
         // TODO: should we have a different type of event for this? After all, it's not a key press.
-        return new Event(EVENT_INPUT_KEYPRESS, null /* text */, codePoint, NOT_A_KEY_CODE,
+        return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, NOT_A_KEY_CODE,
                 Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
                 null /* suggestedWordInfo */, FLAG_NONE, null /* next */);
     }
@@ -175,8 +175,8 @@
     public static Event createEventForCodePointFromAlreadyTypedText(final int codePoint,
             final int x, final int y) {
         // TODO: should we have a different type of event for this? After all, it's not a key press.
-        return new Event(EVENT_INPUT_KEYPRESS, null /* text */, codePoint, NOT_A_KEY_CODE, x, y,
-                null /* suggestedWordInfo */, FLAG_NONE, null /* next */);
+        return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, NOT_A_KEY_CODE,
+                x, y, null /* suggestedWordInfo */, FLAG_NONE, null /* next */);
     }
 
     /**
@@ -184,10 +184,10 @@
      * @return an event for this suggestion pick.
      */
     public static Event createSuggestionPickedEvent(final SuggestedWordInfo suggestedWordInfo) {
-        return new Event(EVENT_SUGGESTION_PICKED, suggestedWordInfo.mWord,
+        return new Event(EVENT_TYPE_SUGGESTION_PICKED, suggestedWordInfo.mWord,
                 NOT_A_CODE_POINT, NOT_A_KEY_CODE,
                 Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE,
-                suggestedWordInfo, FLAG_NONE, null);
+                suggestedWordInfo, FLAG_NONE, null /* next */);
     }
 
     /**
@@ -199,13 +199,26 @@
      * @return an event for this text.
      */
     public static Event createSoftwareTextEvent(final CharSequence text, final int keyCode) {
-        return new Event(EVENT_SOFTWARE_GENERATED_STRING, text, NOT_A_CODE_POINT, keyCode,
+        return new Event(EVENT_TYPE_SOFTWARE_GENERATED_STRING, text, NOT_A_CODE_POINT, keyCode,
                 Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
                 null /* suggestedWordInfo */, FLAG_NONE, null /* next */);
     }
 
+    /**
+     * Creates an input event representing the manual pick of a punctuation suggestion.
+     * @return an event for this suggestion pick.
+     */
+    public static Event createPunctuationSuggestionPickedEvent(
+            final SuggestedWordInfo suggestedWordInfo) {
+        final int primaryCode = suggestedWordInfo.mWord.charAt(0);
+        return new Event(EVENT_TYPE_SUGGESTION_PICKED, suggestedWordInfo.mWord, primaryCode,
+                NOT_A_KEY_CODE, Constants.SUGGESTION_STRIP_COORDINATE,
+                Constants.SUGGESTION_STRIP_COORDINATE, suggestedWordInfo, FLAG_NONE,
+                null /* next */);
+    }
+
     public static Event createNotHandledEvent() {
-        return new Event(EVENT_NOT_HANDLED, null /* text */, NOT_A_CODE_POINT, NOT_A_KEY_CODE,
+        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,
                 null /* suggestedWordInfo */, FLAG_NONE, null);
     }
@@ -218,25 +231,26 @@
     // 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() {
-        return EVENT_INPUT_KEYPRESS == mType && Constants.SUGGESTION_STRIP_COORDINATE == mX;
+        return EVENT_TYPE_SUGGESTION_PICKED == mEventType;
     }
 
     public boolean isHandled() {
-        return EVENT_NOT_HANDLED != mType;
+        return EVENT_TYPE_NOT_HANDLED != mEventType;
     }
 
     public CharSequence getTextToCommit() {
-        switch (mType) {
-        case EVENT_MODE_KEY:
-        case EVENT_NOT_HANDLED:
-        case EVENT_TOGGLE:
+        switch (mEventType) {
+        case EVENT_TYPE_MODE_KEY:
+        case EVENT_TYPE_NOT_HANDLED:
+        case EVENT_TYPE_TOGGLE:
             return "";
-        case EVENT_INPUT_KEYPRESS:
+        case EVENT_TYPE_INPUT_KEYPRESS:
             return StringUtils.newSingleCodePointString(mCodePoint);
-        case EVENT_GESTURE:
-        case EVENT_SOFTWARE_GENERATED_STRING:
+        case EVENT_TYPE_GESTURE:
+        case EVENT_TYPE_SOFTWARE_GENERATED_STRING:
+        case EVENT_TYPE_SUGGESTION_PICKED:
             return mText;
         }
-        throw new RuntimeException("Unknown event type: " + mType);
+        throw new RuntimeException("Unknown event type: " + mEventType);
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 43a4422..30c2dfe 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -89,6 +89,8 @@
     private final long mDictSize;
     private final String mDictFilePath;
     private final boolean mIsUpdatable;
+    private boolean mHasUpdated;
+
     private final int[] mInputCodePoints = new int[MAX_WORD_LENGTH];
     private final int[] mOutputSuggestionCount = new int[1];
     private final int[] mOutputCodePoints = new int[MAX_WORD_LENGTH * MAX_RESULTS];
@@ -138,6 +140,7 @@
         mDictSize = length;
         mDictFilePath = filename;
         mIsUpdatable = isUpdatable;
+        mHasUpdated = false;
         mNativeSuggestOptions.setUseFullEditDistance(useFullEditDistance);
         loadDictionary(filename, offset, length, isUpdatable);
     }
@@ -185,6 +188,7 @@
     // TODO: Move native dict into session
     private final void loadDictionary(final String path, final long startOffset,
             final long length, final boolean isUpdatable) {
+        mHasUpdated = false;
         mNativeDict = openNative(path, startOffset, length, isUpdatable);
     }
 
@@ -401,6 +405,7 @@
                 StringUtils.toCodePointArray(shortcutTarget) : null;
         addUnigramWordNative(mNativeDict, codePoints, probability, shortcutTargetCodePoints,
                 shortcutProbability, isNotAWord, isBlacklisted, timestamp);
+        mHasUpdated = true;
     }
 
     // Add a bigram entry to binary dictionary with timestamp in native code.
@@ -412,6 +417,7 @@
         final int[] codePoints0 = StringUtils.toCodePointArray(word0);
         final int[] codePoints1 = StringUtils.toCodePointArray(word1);
         addBigramWordsNative(mNativeDict, codePoints0, codePoints1, probability, timestamp);
+        mHasUpdated = true;
     }
 
     // Remove a bigram entry form binary dictionary in native code.
@@ -422,6 +428,7 @@
         final int[] codePoints0 = StringUtils.toCodePointArray(word0);
         final int[] codePoints1 = StringUtils.toCodePointArray(word1);
         removeBigramWordsNative(mNativeDict, codePoints0, codePoints1);
+        mHasUpdated = true;
     }
 
     public void addMultipleDictionaryEntries(final LanguageModelParam[] languageModelParams) {
@@ -433,6 +440,7 @@
             }
             processedParamCount = addMultipleDictionaryEntriesNative(mNativeDict,
                     languageModelParams, processedParamCount);
+            mHasUpdated = true;
             if (processedParamCount <= 0) {
                 return;
             }
@@ -451,8 +459,10 @@
 
     public void flush() {
         if (!isValidDictionary()) return;
-        flushNative(mNativeDict, mDictFilePath);
-        reopen();
+        if (mHasUpdated) {
+            flushNative(mNativeDict, mDictFilePath);
+            reopen();
+        }
     }
 
     public void flushWithGC() {
diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index 4e17f83..d5873d7 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -100,10 +100,6 @@
                 });
     }
 
-    public void reopen(final Context context) {
-        registerObserver(context);
-    }
-
     @Override
     public synchronized void close() {
         if (mObserver != null) {
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index aea4811..64e9d2b 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -262,6 +262,9 @@
         ExecutorUtils.getExecutor(mDictName).execute(new Runnable() {
             @Override
             public void run() {
+                if (mBinaryDictionary == null) {
+                    return;
+                }
                 runGCAfterAllPrioritizedTasksIfRequiredLocked(mindsBlockByGC);
             }
         });
@@ -274,9 +277,6 @@
     }
 
     private void runGCAfterAllPrioritizedTasksIfRequiredLocked(final boolean mindsBlockByGC) {
-        if (mBinaryDictionary == null) {
-            return;
-        }
         // needsToRunGC() have to be called with lock.
         if (mBinaryDictionary.needsToRunGC(mindsBlockByGC)) {
             if (setProcessingLargeTaskIfNot()) {
@@ -301,9 +301,13 @@
     protected void addWordDynamically(final String word, final int frequency,
             final String shortcutTarget, final int shortcutFreq, final boolean isNotAWord,
             final boolean isBlacklisted, final int timestamp) {
+        reloadDictionaryIfRequired();
         ExecutorUtils.getExecutor(mDictName).execute(new Runnable() {
             @Override
             public void run() {
+                if (mBinaryDictionary == null) {
+                    return;
+                }
                 runGCAfterAllPrioritizedTasksIfRequiredLocked(true /* mindsBlockByGC */);
                 addWordDynamicallyLocked(word, frequency, shortcutTarget, shortcutFreq,
                         isNotAWord, isBlacklisted, timestamp);
@@ -323,9 +327,13 @@
      */
     protected void addBigramDynamically(final String word0, final String word1,
             final int frequency, final int timestamp) {
+        reloadDictionaryIfRequired();
         ExecutorUtils.getExecutor(mDictName).execute(new Runnable() {
             @Override
             public void run() {
+                if (mBinaryDictionary == null) {
+                    return;
+                }
                 runGCAfterAllPrioritizedTasksIfRequiredLocked(true /* mindsBlockByGC */);
                 addBigramDynamicallyLocked(word0, word1, frequency, timestamp);
             }
@@ -341,9 +349,13 @@
      * Dynamically remove a word bigram in the dictionary.
      */
     protected void removeBigramDynamically(final String word0, final String word1) {
+        reloadDictionaryIfRequired();
         ExecutorUtils.getExecutor(mDictName).execute(new Runnable() {
             @Override
             public void run() {
+                if (mBinaryDictionary == null) {
+                    return;
+                }
                 runGCAfterAllPrioritizedTasksIfRequiredLocked(true /* mindsBlockByGC */);
                 mBinaryDictionary.removeBigramWords(word0, word1);
             }
@@ -360,14 +372,15 @@
     protected void addMultipleDictionaryEntriesDynamically(
             final ArrayList<LanguageModelParam> languageModelParams,
             final AddMultipleDictionaryEntriesCallback callback) {
+        reloadDictionaryIfRequired();
         ExecutorUtils.getExecutor(mDictName).execute(new Runnable() {
             @Override
             public void run() {
+                if (mBinaryDictionary == null) {
+                    return;
+                }
                 final boolean locked = setProcessingLargeTaskIfNot();
                 try {
-                    if (mBinaryDictionary == null) {
-                        return;
-                    }
                     mBinaryDictionary.addMultipleDictionaryEntries(
                             languageModelParams.toArray(
                                     new LanguageModelParam[languageModelParams.size()]));
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index 2fd8ace..c89be35 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -210,15 +210,7 @@
             // So, LatinImeLogger logs "" as a user's input.
             LatinImeLogger.logOnManualSuggestion("", suggestion, index, suggestedWords);
             // Rely on onCodeInput to do the complicated swapping/stripping logic consistently.
-            final int primaryCode = suggestion.charAt(0);
-            // TODO: we should be using createSuggestionPickedEvent here, but for legacy reasons,
-            // onCodeInput is expected a software keypress event for a suggested punctuation
-            // because the current code is descended from a time where this information used not
-            // to be available. Fix this.
-            final Event event = Event.createSoftwareKeypressEvent(primaryCode,
-                    Event.NOT_A_KEY_CODE /* keyCode*/,
-                    Constants.SUGGESTION_STRIP_COORDINATE /* x */,
-                    Constants.SUGGESTION_STRIP_COORDINATE /* y */);
+            final Event event = Event.createPunctuationSuggestionPickedEvent(suggestionInfo);
             if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
                 ResearchLogger.latinIME_punctuationSuggestion(index, suggestion,
                         false /* isBatchMode */, suggestedWords.mIsPrediction);
diff --git a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
index 6f84e1f..712e314 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
@@ -66,7 +66,7 @@
         }
         // Flush pending writes.
         flush();
-        // TODO: Quit depending on finalize() and really close the dictionary file.
+        super.close();
     }
 
     public void flush() {