Merge "Add cloemak keyboard test"
diff --git a/java/src/com/android/inputmethod/event/Event.java b/java/src/com/android/inputmethod/event/Event.java
index 41bf36b..bd4143d 100644
--- a/java/src/com/android/inputmethod/event/Event.java
+++ b/java/src/com/android/inputmethod/event/Event.java
@@ -17,6 +17,7 @@
 package com.android.inputmethod.event;
 
 import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 
 /**
  * Class representing a generic input event as handled by Latin IME.
@@ -49,6 +50,8 @@
     final public static int EVENT_MODE_KEY = 3;
     // An event corresponding to a gesture.
     final public static int EVENT_GESTURE = 4;
+    // An event corresponding to the manual pick of a suggestion.
+    final public static int EVENT_SUGGESTION_PICKED = 5;
 
     // 0 is a valid code point, so we use -1 here.
     final public static int NOT_A_CODE_POINT = -1;
@@ -85,31 +88,50 @@
     // 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
+    // other cases).
+    final public SuggestedWordInfo mSuggestedWordInfo;
+
     // The next event, if any. Null if there is no next event yet.
     final public Event mNextEvent;
 
     // This method is private - to create a new event, use one of the create* utility methods.
     private Event(final int type, final int codePoint, final int keyCode, final int x, final int y,
-            final int flags, final Event next) {
+            final SuggestedWordInfo suggestedWordInfo, final int flags, final Event next) {
         mType = type;
         mCodePoint = codePoint;
         mKeyCode = keyCode;
         mX = x;
         mY = y;
+        mSuggestedWordInfo = suggestedWordInfo;
         mFlags = flags;
         mNextEvent = next;
+        // Sanity checks
+        // mSuggestedWordInfo is non-null if and only if the type is SUGGESTION_PICKED
+        if (EVENT_SUGGESTION_PICKED == mType) {
+            if (null == mSuggestedWordInfo) {
+                throw new RuntimeException("Wrong event: SUGGESTION_PICKED event must have a "
+                        + "non-null SuggestedWordInfo");
+            }
+        } else {
+            if (null != mSuggestedWordInfo) {
+                throw new RuntimeException("Wrong event: only SUGGESTION_PICKED events may have " +
+                        "a non-null SuggestedWordInfo");
+            }
+        }
     }
 
     public static Event createSoftwareKeypressEvent(final int codePoint, final int keyCode,
             final int x, final int y) {
-        return new Event(EVENT_INPUT_KEYPRESS, codePoint, keyCode, x, y, FLAG_NONE, null);
+        return new Event(EVENT_INPUT_KEYPRESS, codePoint, keyCode, x, y,
+                null /* suggestedWordInfo */, FLAG_NONE, null);
     }
 
     public static Event createHardwareKeypressEvent(final int codePoint, final int keyCode,
             final Event next) {
         return new Event(EVENT_INPUT_KEYPRESS, codePoint, keyCode,
                 Constants.EXTERNAL_KEYBOARD_COORDINATE, Constants.EXTERNAL_KEYBOARD_COORDINATE,
-                FLAG_NONE, next);
+                null /* suggestedWordInfo */, FLAG_NONE, next);
     }
 
     // This creates an input event for a dead character. @see {@link #FLAG_DEAD}
@@ -117,7 +139,7 @@
         // TODO: add an argument or something if we ever create a software layout with dead keys.
         return new Event(EVENT_INPUT_KEYPRESS, codePoint, keyCode,
                 Constants.EXTERNAL_KEYBOARD_COORDINATE, Constants.EXTERNAL_KEYBOARD_COORDINATE,
-                FLAG_DEAD, next);
+                null /* suggestedWordInfo */, FLAG_DEAD, next);
     }
 
     /**
@@ -130,7 +152,8 @@
     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, codePoint, NOT_A_KEY_CODE,
-                Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, FLAG_NONE, null /* next */);
+                Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
+                null /* suggestedWordInfo */, FLAG_NONE, null /* next */);
     }
 
     /**
@@ -144,13 +167,24 @@
     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, codePoint, NOT_A_KEY_CODE, x, y, FLAG_NONE,
-                null /* next */);
+        return new Event(EVENT_INPUT_KEYPRESS, codePoint, NOT_A_KEY_CODE, x, y,
+                null /* suggestedWordInfo */, FLAG_NONE, null /* next */);
+    }
+
+    /**
+     * Creates an input event representing the manual pick of a suggestion.
+     * @return an event for this suggestion pick.
+     */
+    public static Event createSuggestionPickedEvent(final SuggestedWordInfo suggestedWordInfo) {
+        return new Event(EVENT_SUGGESTION_PICKED, NOT_A_CODE_POINT, NOT_A_KEY_CODE,
+                Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE,
+                suggestedWordInfo, FLAG_NONE, null);
     }
 
     public static Event createNotHandledEvent() {
         return new Event(EVENT_NOT_HANDLED, NOT_A_CODE_POINT, NOT_A_KEY_CODE,
-                Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, FLAG_NONE, null);
+                Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
+                null /* suggestedWordInfo */, FLAG_NONE, null);
     }
 
     // Returns whether this event is for a dead character. @see {@link #FLAG_DEAD}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
index e9ecd1e..d9cb51f 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
@@ -1669,7 +1669,7 @@
         // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
         // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
         // U+010D: "č" LATIN SMALL LETTER C WITH CARON
-        /* more_keys_for_c */ "\u00E7,\u0107,\u010D",
+        /* more_keys_for_c */ "\u00E7,%,\u0107,\u010D",
         /* label_to_alpha_key ~ */
         null, null, null,
         /* ~ more_keys_for_n */
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 4a18c2b..fc5c7f7 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -169,7 +169,6 @@
 
         private int mDelayUpdateSuggestions;
         private int mDelayUpdateShiftState;
-        private long mDoubleSpacePeriodTimeout;
         private long mDoubleSpacePeriodTimerStart;
 
         public UIHandler(final LatinIME ownerInstance) {
@@ -184,8 +183,6 @@
             final Resources res = latinIme.getResources();
             mDelayUpdateSuggestions = res.getInteger(R.integer.config_delay_update_suggestions);
             mDelayUpdateShiftState = res.getInteger(R.integer.config_delay_update_shift_state);
-            mDoubleSpacePeriodTimeout =
-                    res.getInteger(R.integer.config_double_space_period_timeout);
         }
 
         @Override
@@ -327,7 +324,7 @@
 
         public boolean isAcceptingDoubleSpacePeriod() {
             return SystemClock.uptimeMillis() - mDoubleSpacePeriodTimerStart
-                    < mDoubleSpacePeriodTimeout;
+                    < getOwnerInstance().mSettings.getCurrent().mDoubleSpacePeriodTimeout;
         }
 
         // Working variables for the following methods.
@@ -1276,15 +1273,7 @@
         final InputTransaction completeInputTransaction =
                 mInputLogic.onCodeInput(mSettings.getCurrent(), event,
                         mKeyboardSwitcher.getKeyboardShiftMode(), mHandler);
-        switch (completeInputTransaction.getRequiredShiftUpdate()) {
-            case InputTransaction.SHIFT_UPDATE_LATER:
-                mHandler.postUpdateShiftState();
-                break;
-            case InputTransaction.SHIFT_UPDATE_NOW:
-                mKeyboardSwitcher.updateShiftState();
-                break;
-            default: // SHIFT_NO_UPDATE
-        }
+        updateShiftModeAfterInputTransaction(completeInputTransaction.getRequiredShiftUpdate());
         mKeyboardSwitcher.onCodeInput(codePoint);
     }
 
@@ -1500,8 +1489,10 @@
     // interface
     @Override
     public void pickSuggestionManually(final int index, final SuggestedWordInfo suggestionInfo) {
-        mInputLogic.onPickSuggestionManually(mSettings.getCurrent(), index, suggestionInfo,
-                mHandler, mKeyboardSwitcher);
+        final InputTransaction completeInputTransaction = mInputLogic.onPickSuggestionManually(
+                mSettings.getCurrent(), index, suggestionInfo,
+                mKeyboardSwitcher.getKeyboardShiftMode(), mHandler);
+        updateShiftModeAfterInputTransaction(completeInputTransaction.getRequiredShiftUpdate());
     }
 
     @Override
@@ -1539,6 +1530,18 @@
         }
     }
 
+    private void updateShiftModeAfterInputTransaction(final int requiredShiftUpdate) {
+        switch (requiredShiftUpdate) {
+        case InputTransaction.SHIFT_UPDATE_LATER:
+            mHandler.postUpdateShiftState();
+            break;
+        case InputTransaction.SHIFT_UPDATE_NOW:
+            mKeyboardSwitcher.updateShiftState();
+            break;
+        default: // SHIFT_NO_UPDATE
+        }
+    }
+
     private void hapticAndAudioFeedback(final int code, final int repeatCount) {
         final MainKeyboardView keyboardView = mKeyboardSwitcher.getMainKeyboardView();
         if (keyboardView != null && keyboardView.isInDraggingFinger()) {
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index fa7c4b4..1eff427 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -196,13 +196,16 @@
      * @param settingsValues the current values of the settings.
      * @param index the index of the suggestion.
      * @param suggestionInfo the suggestion info.
+     * @param keyboardShiftState the shift state of the keyboard, as returned by
+     *     {@link com.android.inputmethod.keyboard.KeyboardSwitcher#getKeyboardShiftMode()}
+     * @return the complete transaction object
      */
     // Called from {@link SuggestionStripView} through the {@link SuggestionStripView#Listener}
     // interface
-    public void onPickSuggestionManually(final SettingsValues settingsValues,
-            final int index, final SuggestedWordInfo suggestionInfo,
-            // TODO: remove these two arguments
-            final LatinIME.UIHandler handler, final KeyboardSwitcher keyboardSwitcher) {
+    public InputTransaction onPickSuggestionManually(final SettingsValues settingsValues,
+            final int index, final SuggestedWordInfo suggestionInfo, final int keyboardShiftState,
+            // TODO: remove this argument
+            final LatinIME.UIHandler handler) {
         final SuggestedWords suggestedWords = mSuggestedWords;
         final String suggestion = suggestionInfo.mWord;
         // If this is a punctuation picked from the suggestion strip, pass it to onCodeInput
@@ -212,16 +215,26 @@
             LatinImeLogger.logOnManualSuggestion("", suggestion, index, suggestedWords);
             // Rely on onCodeInput to do the complicated swapping/stripping logic consistently.
             final int primaryCode = suggestion.charAt(0);
-            final Event event = Event.createSoftwareKeypressEvent(primaryCode, Event.NOT_A_KEY_CODE,
-                    Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE);
-            onCodeInput(settingsValues, event, keyboardSwitcher.getKeyboardShiftMode(), handler);
+            // 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 InputTransaction completeTransaction = onCodeInput(settingsValues, event,
+                    keyboardShiftState, handler);
             if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
                 ResearchLogger.latinIME_punctuationSuggestion(index, suggestion,
                         false /* isBatchMode */, suggestedWords.mIsPrediction);
             }
-            return;
+            return completeTransaction;
         }
 
+        final Event event = Event.createSuggestionPickedEvent(suggestionInfo);
+        final InputTransaction inputTransaction = new InputTransaction(settingsValues,
+                event, SystemClock.uptimeMillis(), mSpaceState, keyboardShiftState);
         mConnection.beginBatchEdit();
         if (SpaceState.PHANTOM == mSpaceState && suggestion.length() > 0
                 // In the batch input mode, a manually picked suggested word should just replace
@@ -241,11 +254,11 @@
         if (SuggestedWordInfo.KIND_APP_DEFINED == suggestionInfo.mKind) {
             mSuggestedWords = SuggestedWords.EMPTY;
             mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
-            keyboardSwitcher.updateShiftState();
+            inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
             resetComposingState(true /* alsoResetLastComposedWord */);
             mConnection.commitCompletion(suggestionInfo.mApplicationSpecifiedCompletionInfo);
             mConnection.endBatchEdit();
-            return;
+            return inputTransaction;
         }
 
         // We need to log before we commit, because the word composer will store away the user
@@ -264,7 +277,7 @@
         mLastComposedWord.deactivate();
         // Space state must be updated before calling updateShiftState
         mSpaceState = SpaceState.PHANTOM;
-        keyboardSwitcher.updateShiftState();
+        inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
 
         // We should show the "Touch again to save" hint if the user pressed the first entry
         // AND it's in none of our current dictionaries (main, user or otherwise).
@@ -290,6 +303,7 @@
             // If we're not showing the "Touch again to save", then update the suggestion strip.
             handler.postUpdateSuggestionStrip();
         }
+        return inputTransaction;
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index 5a652a5..d47a61e 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -50,6 +50,7 @@
     // From resources:
     public final SpacingAndPunctuations mSpacingAndPunctuations;
     public final int mDelayUpdateOldSuggestions;
+    public final long mDoubleSpacePeriodTimeout;
 
     // From preferences, in the same order as xml/prefs.xml:
     public final boolean mAutoCap;
@@ -132,6 +133,7 @@
         mBlockPotentiallyOffensive = Settings.readBlockPotentiallyOffensive(prefs, res);
         mAutoCorrectEnabled = Settings.readAutoCorrectEnabled(autoCorrectionThresholdRawValue, res);
         mBigramPredictionEnabled = readBigramPredictionEnabled(prefs, res);
+        mDoubleSpacePeriodTimeout = res.getInteger(R.integer.config_double_space_period_timeout);
 
         // Compute other readable settings
         mKeyLongpressTimeout = Settings.readKeyLongpressTimeout(prefs, res);
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/FrenchCustomizer.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/FrenchCustomizer.java
index b10b368..ab90267 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/tests/FrenchCustomizer.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/FrenchCustomizer.java
@@ -83,6 +83,7 @@
                 // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
                 // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
                 // U+010D: "č" LATIN SMALL LETTER C WITH CARON
-                .setMoreKeysOf("c", "\u00E7", "\u0107", "\u010D");
+                .setMoreKeysOf("c", "\u00E7", "\u0107", "\u010D")
+                .setAdditionalMoreKeysPositionOf("c", 2);
     }
 }
diff --git a/tools/make-keyboard-text/res/values-fr/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-fr/donottranslate-more-keys.xml
index 6656776..47b1c9e 100644
--- a/tools/make-keyboard-text/res/values-fr/donottranslate-more-keys.xml
+++ b/tools/make-keyboard-text/res/values-fr/donottranslate-more-keys.xml
@@ -62,7 +62,7 @@
     <!-- U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
          U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
          U+010D: "č" LATIN SMALL LETTER C WITH CARON -->
-    <string name="more_keys_for_c">&#x00E7;,&#x0107;,&#x010D;</string>
+    <string name="more_keys_for_c">&#x00E7;,%,&#x0107;,&#x010D;</string>
     <!-- U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS -->
     <string name="more_keys_for_y">%,&#x00FF;</string>
     <!-- U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE -->