Merge "Passing options to native suggestion method."
diff --git a/java/res/layout/research_feedback_fragment_layout.xml b/java/res/layout/research_feedback_fragment_layout.xml
index 2915a77..505a1e8 100644
--- a/java/res/layout/research_feedback_fragment_layout.xml
+++ b/java/res/layout/research_feedback_fragment_layout.xml
@@ -18,11 +18,10 @@
    want a dialog, but it must be its own activity so we can launch the soft
    keyboard on it.  A regular dialog will not work since it would be launched from
    the IME. -->
-<ScrollView>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android">
     <LinearLayout
-         xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="match_parent"
-         android:layout_height="match_parent"
+         android:layout_height="wrap_content"
          android:layout_marginStart="8dip"
          android:layout_marginEnd="8dip"
          android:orientation="vertical">
diff --git a/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java b/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java
index d0e8446..77f67b8 100644
--- a/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java
+++ b/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java
@@ -144,7 +144,7 @@
     public static String getMatchLevelSortedString(final int matchLevel) {
         // This works because the match levels are 0~99 (actually 0~30)
         // Ideally this should use a number of digits equals to the 1og10 of the greater matchLevel
-        return String.format("%02d", MATCH_LEVEL_MAX - matchLevel);
+        return String.format(Locale.ROOT, "%02d", MATCH_LEVEL_MAX - matchLevel);
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index 1550e77..ae72b4a 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -453,7 +453,7 @@
         } else {
             label = "/" + mLabel;
         }
-        return String.format("%s%s %d,%d %dx%d %s/%s/%s",
+        return String.format(Locale.ROOT, "%s%s %d,%d %dx%d %s/%s/%s",
                 Constants.printableCode(mCode), label, mX, mY, mWidth, mHeight, mHintLabel,
                 KeyboardIconsSet.getIconName(mIconId), backgroundName(mBackgroundType));
     }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index aa27067..4c5dd25 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -187,7 +187,7 @@
     public String toString() {
         final String orientation = (mOrientation == Configuration.ORIENTATION_PORTRAIT)
                 ? "port" : "land";
-        return String.format("[%s %s:%s %s:%dx%d %s %s %s%s%s%s%s%s%s%s%s]",
+        return String.format(Locale.ROOT, "[%s %s:%s %s:%dx%d %s %s %s%s%s%s%s%s%s%s%s]",
                 elementIdToName(mElementId),
                 mLocale,
                 mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET),
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
index 1fe23a3..d4051f7 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
@@ -36,6 +36,7 @@
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
+import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.compat.EditorInfoCompatUtils;
 import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
 import com.android.inputmethod.keyboard.internal.KeyboardParams;
@@ -424,6 +425,7 @@
                 SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT, false);
     }
 
+    @UsedForTesting
     public static KeyboardLayoutSet createKeyboardSetForTest(final Context context,
             final InputMethodSubtype subtype, final int orientation,
             final boolean testCasesHaveTouchCoordinates) {
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index ad08d64..c5bd624 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -32,6 +32,7 @@
 import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
 import com.android.inputmethod.keyboard.internal.KeyboardState;
 import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
+import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.InputView;
 import com.android.inputmethod.latin.LatinIME;
 import com.android.inputmethod.latin.LatinImeLogger;
@@ -68,8 +69,6 @@
         new KeyboardTheme(5, R.style.KeyboardTheme_IceCreamSandwich),
     };
 
-    private final AudioAndHapticFeedbackManager mFeedbackManager =
-            AudioAndHapticFeedbackManager.getInstance();
     private SubtypeSwitcher mSubtypeSwitcher;
     private SharedPreferences mPrefs;
 
@@ -151,7 +150,6 @@
         mKeyboardLayoutSet = builder.build();
         try {
             mState.onLoadKeyboard();
-            mFeedbackManager.onSettingsChanged(settingsValues);
         } catch (KeyboardLayoutSetException e) {
             Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause());
             LatinImeLogger.logOnException(e.mKeyboardId.toString(), e.getCause());
@@ -159,10 +157,6 @@
         }
     }
 
-    public void onRingerModeChanged() {
-        mFeedbackManager.onRingerModeChanged();
-    }
-
     public void saveKeyboardState() {
         if (getKeyboard() != null) {
             mState.onSaveKeyboardState();
@@ -217,9 +211,7 @@
     }
 
     public void onPressKey(final int code, final boolean isSinglePointer) {
-        if (isVibrateAndSoundFeedbackRequired()) {
-            mFeedbackManager.hapticAndAudioFeedback(code, mKeyboardView);
-        }
+        hapticAndAudioFeedback(code);
         mState.onPressKey(code, isSinglePointer, mLatinIME.getCurrentAutoCapsState());
     }
 
@@ -328,24 +320,25 @@
         }
     }
 
-    // Implements {@link KeyboardState.SwitchActions}.
-    @Override
-    public void hapticAndAudioFeedback(final int code) {
-        mFeedbackManager.hapticAndAudioFeedback(code, mKeyboardView);
+    private void hapticAndAudioFeedback(final int code) {
+        if (mKeyboardView == null || mKeyboardView.isInSlidingKeyInput()) {
+            return;
+        }
+        AudioAndHapticFeedbackManager.getInstance().hapticAndAudioFeedback(code, mKeyboardView);
     }
 
     public void onLongPressTimeout(final int code) {
         mState.onLongPressTimeout(code);
+        final Keyboard keyboard = getKeyboard();
+        if (keyboard != null && keyboard.mId.isAlphabetKeyboard() && code == Constants.CODE_SHIFT) {
+            hapticAndAudioFeedback(code);
+        }
     }
 
     public boolean isInMomentarySwitchState() {
         return mState.isInMomentarySwitchState();
     }
 
-    private boolean isVibrateAndSoundFeedbackRequired() {
-        return mKeyboardView != null && !mKeyboardView.isInSlidingKeyInput();
-    }
-
     /**
      * Updates state machine to figure out when to automatically switch back to the previous mode.
      */
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 6c6fc61..34464f6 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -57,6 +57,7 @@
 import com.android.inputmethod.keyboard.internal.PreviewPlacerView;
 import com.android.inputmethod.keyboard.internal.SlidingKeyInputPreview;
 import com.android.inputmethod.keyboard.internal.TouchScreenRegulator;
+import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
 import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.CoordinateUtils;
@@ -240,7 +241,9 @@
             case MSG_REPEAT_KEY:
                 final Key currentKey = tracker.getKey();
                 if (currentKey != null && currentKey.mCode == msg.arg1) {
-                    tracker.onRegisterKey(currentKey);
+                    tracker.onRepeatKey(currentKey);
+                    AudioAndHapticFeedbackManager.getInstance().hapticAndAudioFeedback(
+                            currentKey.mCode, keyboardView);
                     startKeyRepeatTimer(tracker, mKeyRepeatInterval);
                 }
                 break;
@@ -987,16 +990,14 @@
     /**
      * Called when a key is long pressed.
      * @param tracker the pointer tracker which pressed the parent key
-     * @return true if the long press is handled, false otherwise. Subclasses should call the
-     * method on the base class if the subclass doesn't wish to handle the call.
      */
-    private boolean onLongPress(final PointerTracker tracker) {
+    private void onLongPress(final PointerTracker tracker) {
         if (isShowingMoreKeysPanel()) {
-            return false;
+            return;
         }
         final Key key = tracker.getKey();
         if (key == null) {
-            return false;
+            return;
         }
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
             ResearchLogger.mainKeyboardView_onLongPress();
@@ -1007,18 +1008,18 @@
             tracker.onLongPressed();
             invokeCodeInput(embeddedCode);
             invokeReleaseKey(code);
-            KeyboardSwitcher.getInstance().hapticAndAudioFeedback(code);
-            return true;
+            AudioAndHapticFeedbackManager.getInstance().hapticAndAudioFeedback(code, this);
+            return;
         }
         if (code == Constants.CODE_SPACE || code == Constants.CODE_LANGUAGE_SWITCH) {
             // Long pressing the space key invokes IME switcher dialog.
             if (invokeCustomRequest(LatinIME.CODE_SHOW_INPUT_METHOD_PICKER)) {
                 tracker.onLongPressed();
                 invokeReleaseKey(code);
-                return true;
+                return;
             }
         }
-        return openMoreKeysPanel(key, tracker);
+        openMoreKeysPanel(key, tracker);
     }
 
     private boolean invokeCustomRequest(final int requestCode) {
@@ -1034,10 +1035,10 @@
         mKeyboardActionListener.onReleaseKey(code, false);
     }
 
-    private boolean openMoreKeysPanel(final Key key, final PointerTracker tracker) {
+    private void openMoreKeysPanel(final Key key, final PointerTracker tracker) {
         final MoreKeysPanel moreKeysPanel = onCreateMoreKeysPanel(key, getContext());
         if (moreKeysPanel == null) {
-            return false;
+            return;
         }
 
         final int[] lastCoords = CoordinateUtils.newInstance();
@@ -1059,7 +1060,6 @@
         final int translatedX = moreKeysPanel.translateX(CoordinateUtils.x(lastCoords));
         final int translatedY = moreKeysPanel.translateY(CoordinateUtils.y(lastCoords));
         tracker.onShowMoreKeysPanel(translatedX, translatedY, moreKeysPanel);
-        return true;
     }
 
     public boolean isInSlidingKeyInput() {
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 1742393..5df7011 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -1266,15 +1266,13 @@
         if (!key.isRepeatable()) return;
         // Don't start key repeat when we are in sliding input mode.
         if (mIsInSlidingKeyInput) return;
-        onRegisterKey(key);
+        onRepeatKey(key);
         mTimerProxy.startKeyRepeatTimer(this);
     }
 
-    public void onRegisterKey(final Key key) {
-        if (key != null) {
-            detectAndSendKey(key, key.mX, key.mY, SystemClock.uptimeMillis());
-            mTimerProxy.startTypingStateTimer(key);
-        }
+    public void onRepeatKey(final Key key) {
+        detectAndSendKey(key, key.mX, key.mY, SystemClock.uptimeMillis());
+        mTimerProxy.startTypingStateTimer(key);
     }
 
     private boolean isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final long eventTime,
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
index b31f00b..8deadbf 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
@@ -58,7 +58,7 @@
         }
 
         private static double degreeToRadian(final int degree) {
-            return (double)degree / 180.0d * Math.PI;
+            return degree / 180.0d * Math.PI;
         }
 
         public GestureStrokePreviewParams(final TypedArray mainKeyboardViewAttr) {
@@ -125,8 +125,18 @@
 
     }
 
+    /**
+     * Append sampled preview points.
+     *
+     * @param eventTimes the event time array of gesture trail to be drawn.
+     * @param xCoords the x-coordinates array of gesture trail to be drawn.
+     * @param yCoords the y-coordinates array of gesture trail to be drawn.
+     * @param types the point types array of gesture trail. This is valid only when
+     * {@link GestureTrail#DEBUG_SHOW_POINTS} is true.
+     */
     public void appendPreviewStroke(final ResizableIntArray eventTimes,
-            final ResizableIntArray xCoords, final ResizableIntArray yCoords) {
+            final ResizableIntArray xCoords, final ResizableIntArray yCoords,
+            final ResizableIntArray types) {
         final int length = mPreviewEventTimes.getLength() - mLastPreviewSize;
         if (length <= 0) {
             return;
@@ -134,6 +144,9 @@
         eventTimes.append(mPreviewEventTimes, mLastPreviewSize, length);
         xCoords.append(mPreviewXCoordinates, mLastPreviewSize, length);
         yCoords.append(mPreviewYCoordinates, mLastPreviewSize, length);
+        if (GestureTrail.DEBUG_SHOW_POINTS) {
+            types.fill(GestureTrail.POINT_TYPE_SAMPLED, types.getLength(), length);
+        }
         mLastPreviewSize = mPreviewEventTimes.getLength();
     }
 
@@ -148,6 +161,8 @@
      * @param eventTimes the event time array of gesture trail to be drawn.
      * @param xCoords the x-coordinates array of gesture trail to be drawn.
      * @param yCoords the y-coordinates array of gesture trail to be drawn.
+     * @param types the point types array of gesture trail. This is valid only when
+     * {@link GestureTrail#DEBUG_SHOW_POINTS} is true.
      * @return the start index of the last interpolated segment of input arrays.
      */
     public int interpolateStrokeAndReturnStartIndexOfLastSegment(final int lastInterpolatedIndex,
@@ -189,7 +204,7 @@
                 eventTimes.add(d1, (int)(dt * t) + t1);
                 xCoords.add(d1, (int)mInterpolator.mInterpolatedX);
                 yCoords.add(d1, (int)mInterpolator.mInterpolatedY);
-                if (GestureTrail.DBG_SHOW_POINTS) {
+                if (GestureTrail.DEBUG_SHOW_POINTS) {
                     types.add(d1, GestureTrail.POINT_TYPE_INTERPOLATED);
                 }
                 d1++;
@@ -197,7 +212,7 @@
             eventTimes.add(d1, pt[p2]);
             xCoords.add(d1, px[p2]);
             yCoords.add(d1, py[p2]);
-            if (GestureTrail.DBG_SHOW_POINTS) {
+            if (GestureTrail.DEBUG_SHOW_POINTS) {
                 types.add(d1, GestureTrail.POINT_TYPE_SAMPLED);
             }
         }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java
index 03dd1c3..0f3cd78 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java
@@ -36,10 +36,11 @@
  * @attr ref R.styleable#MainKeyboardView_gestureTrailWidth
  */
 final class GestureTrail {
-    public static final boolean DBG_SHOW_POINTS = false;
-    public static final int POINT_TYPE_SAMPLED = 0;
-    public static final int POINT_TYPE_INTERPOLATED = 1;
-    public static final int POINT_TYPE_COMPROMISED = 2;
+    public static final boolean DEBUG_SHOW_POINTS = false;
+    public static final int POINT_TYPE_SAMPLED = 1;
+    public static final int POINT_TYPE_INTERPOLATED = 2;
+    private static final int FADEOUT_START_DELAY_FOR_DEBUG = 2000; // millisecond
+    private static final int FADEOUT_DURATION_FOR_DEBUG = 200; // millisecond
 
     private static final int DEFAULT_CAPACITY = GestureStrokeWithPreviewPoints.PREVIEW_CAPACITY;
 
@@ -48,7 +49,7 @@
     private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
     private final ResizableIntArray mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY);
     private final ResizableIntArray mPointTypes = new ResizableIntArray(
-            DBG_SHOW_POINTS ? DEFAULT_CAPACITY : 0);
+            DEBUG_SHOW_POINTS ? DEFAULT_CAPACITY : 0);
     private int mCurrentStrokeId = -1;
     // The wall time of the zero value in {@link #mEventTimes}
     private long mCurrentTimeBase;
@@ -83,10 +84,12 @@
                     R.styleable.MainKeyboardView_gestureTrailShadowRatio, 0);
             mTrailShadowEnabled = (trailShadowRatioInt > 0);
             mTrailShadowRatio = (float)trailShadowRatioInt / (float)PERCENTAGE_INT;
-            mFadeoutStartDelay = DBG_SHOW_POINTS ? 2000 : mainKeyboardViewAttr.getInt(
-                    R.styleable.MainKeyboardView_gestureTrailFadeoutStartDelay, 0);
-            mFadeoutDuration = DBG_SHOW_POINTS ? 200 : mainKeyboardViewAttr.getInt(
-                    R.styleable.MainKeyboardView_gestureTrailFadeoutDuration, 0);
+            mFadeoutStartDelay = DEBUG_SHOW_POINTS ? FADEOUT_START_DELAY_FOR_DEBUG
+                    : mainKeyboardViewAttr.getInt(
+                            R.styleable.MainKeyboardView_gestureTrailFadeoutStartDelay, 0);
+            mFadeoutDuration = DEBUG_SHOW_POINTS ? FADEOUT_DURATION_FOR_DEBUG
+                    : mainKeyboardViewAttr.getInt(
+                            R.styleable.MainKeyboardView_gestureTrailFadeoutDuration, 0);
             mTrailLingerDuration = mFadeoutStartDelay + mFadeoutDuration;
             mUpdateInterval = mainKeyboardViewAttr.getInt(
                     R.styleable.MainKeyboardView_gestureTrailUpdateInterval, 0);
@@ -117,7 +120,7 @@
 
     private void addStrokeLocked(final GestureStrokeWithPreviewPoints stroke, final long downTime) {
         final int trailSize = mEventTimes.getLength();
-        stroke.appendPreviewStroke(mEventTimes, mXCoordinates, mYCoordinates);
+        stroke.appendPreviewStroke(mEventTimes, mXCoordinates, mYCoordinates, mPointTypes);
         if (mEventTimes.getLength() == trailSize) {
             return;
         }
@@ -255,23 +258,15 @@
                         final int alpha = getAlpha(elapsedTime, params);
                         paint.setAlpha(alpha);
                         canvas.drawPath(path, paint);
-                        if (DBG_SHOW_POINTS) {
-                            if (pointTypes[i] == POINT_TYPE_INTERPOLATED) {
-                                paint.setColor(Color.RED);
-                            } else if (pointTypes[i] == POINT_TYPE_SAMPLED) {
-                                paint.setColor(0xFFA000FF);
-                            } else {
-                                paint.setColor(Color.GREEN);
-                            }
-                            canvas.drawCircle(p1x - 1, p1y - 1, 2, paint);
-                            paint.setColor(params.mTrailColor);
-                        }
                     }
                 }
                 p1x = p2x;
                 p1y = p2y;
                 r1 = r2;
             }
+            if (DEBUG_SHOW_POINTS) {
+                debugDrawPoints(canvas, startIndex, trailSize, paint);
+            }
         }
 
         final int newSize = trailSize - startIndex;
@@ -281,11 +276,14 @@
                 System.arraycopy(eventTimes, startIndex, eventTimes, 0, newSize);
                 System.arraycopy(xCoords, startIndex, xCoords, 0, newSize);
                 System.arraycopy(yCoords, startIndex, yCoords, 0, newSize);
+                if (DEBUG_SHOW_POINTS) {
+                    System.arraycopy(pointTypes, startIndex, pointTypes, 0, newSize);
+                }
             }
             mEventTimes.setLength(newSize);
             mXCoordinates.setLength(newSize);
             mYCoordinates.setLength(newSize);
-            if (DBG_SHOW_POINTS) {
+            if (DEBUG_SHOW_POINTS) {
                 mPointTypes.setLength(newSize);
             }
             // The start index of the last segment of the stroke
@@ -295,4 +293,26 @@
         }
         return newSize > 0;
     }
+
+    private void debugDrawPoints(final Canvas canvas, final int startIndex, final int endIndex,
+            final Paint paint) {
+        final int[] xCoords = mXCoordinates.getPrimitiveArray();
+        final int[] yCoords = mYCoordinates.getPrimitiveArray();
+        final int[] pointTypes = mPointTypes.getPrimitiveArray();
+        // {@link Paint} that is zero width stroke and anti alias off draws exactly 1 pixel.
+        paint.setAntiAlias(false);
+        paint.setStrokeWidth(0);
+        for (int i = startIndex; i < endIndex; i++) {
+            final int pointType = pointTypes[i];
+            if (pointType == POINT_TYPE_INTERPOLATED) {
+                paint.setColor(Color.RED);
+            } else if (pointType == POINT_TYPE_SAMPLED) {
+                paint.setColor(0xFFA000FF);
+            } else {
+                paint.setColor(Color.GREEN);
+            }
+            canvas.drawPoint(getXCoordValue(xCoords[i]), yCoords[i], paint);
+        }
+        paint.setAntiAlias(true);
+    }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
index 6af1bd7..f18d5ed 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
@@ -58,7 +58,6 @@
         public void cancelDoubleTapTimer();
         public void startLongPressTimer(int code);
         public void cancelLongPressTimer();
-        public void hapticAndAudioFeedback(int code);
     }
 
     private final SwitchActions mSwitchActions;
@@ -387,7 +386,6 @@
         }
         if (mIsAlphabetMode && code == Constants.CODE_SHIFT) {
             mLongPressShiftLockFired = true;
-            mSwitchActions.hapticAndAudioFeedback(code);
         }
     }
 
diff --git a/java/src/com/android/inputmethod/latin/AutoCorrection.java b/java/src/com/android/inputmethod/latin/AutoCorrection.java
index fa35922..86be429 100644
--- a/java/src/com/android/inputmethod/latin/AutoCorrection.java
+++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java
@@ -32,12 +32,13 @@
         // Purely static class: can't instantiate.
     }
 
-    public static boolean isValidWord(final ConcurrentHashMap<String, Dictionary> dictionaries,
-            final String word, final boolean ignoreCase) {
+    public static boolean isValidWord(final Suggest suggest, final String word,
+            final boolean ignoreCase) {
         if (TextUtils.isEmpty(word)) {
             return false;
         }
-        final String lowerCasedWord = word.toLowerCase();
+        final ConcurrentHashMap<String, Dictionary> dictionaries = suggest.getUnigramDictionaries();
+        final String lowerCasedWord = word.toLowerCase(suggest.mLocale);
         for (final String key : dictionaries.keySet()) {
             final Dictionary dictionary = dictionaries.get(key);
             // It's unclear how realistically 'dictionary' can be null, but the monkey is somehow
@@ -73,13 +74,6 @@
         return maxFreq;
     }
 
-    // Returns true if this is in any of the dictionaries.
-    public static boolean isInTheDictionary(
-            final ConcurrentHashMap<String, Dictionary> dictionaries,
-            final String word, final boolean ignoreCase) {
-        return isValidWord(dictionaries, word, ignoreCase);
-    }
-
     public static boolean suggestionExceedsAutoCorrectionThreshold(
             final SuggestedWordInfo suggestion, final String consideredWord,
             final float autoCorrectionThreshold) {
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index f85c16b..592db35 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -480,6 +480,7 @@
         final InputAttributes inputAttributes =
                 new InputAttributes(getCurrentInputEditorInfo(), isFullscreenMode());
         mSettings.loadSettings(locale, inputAttributes);
+        AudioAndHapticFeedbackManager.getInstance().onSettingsChanged(mSettings.getCurrent());
         // May need to reset the contacts dictionary depending on the user settings.
         resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary());
     }
@@ -2370,7 +2371,7 @@
         final boolean showingAddToDictionaryHint =
                 SuggestedWordInfo.KIND_TYPED == suggestionInfo.mKind && mSuggest != null
                 // If the suggestion is not in the dictionary, the hint should be shown.
-                && !AutoCorrection.isValidWord(mSuggest.getUnigramDictionaries(), suggestion, true);
+                && !AutoCorrection.isValidWord(mSuggest, suggestion, true);
 
         if (mSettings.isInternal()) {
             Stats.onSeparator((char)Constants.CODE_SPACE,
@@ -2701,7 +2702,7 @@
             if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
                 mSubtypeSwitcher.onNetworkStateChanged(intent);
             } else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
-                mKeyboardSwitcher.onRingerModeChanged();
+                AudioAndHapticFeedbackManager.getInstance().onRingerModeChanged();
             }
         }
     };
diff --git a/java/src/com/android/inputmethod/latin/LocaleUtils.java b/java/src/com/android/inputmethod/latin/LocaleUtils.java
index 5fde815..a1e4050 100644
--- a/java/src/com/android/inputmethod/latin/LocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/LocaleUtils.java
@@ -148,7 +148,7 @@
     public static String getMatchLevelSortedString(int matchLevel) {
         // This works because the match levels are 0~99 (actually 0~30)
         // Ideally this should use a number of digits equals to the 1og10 of the greater matchLevel
-        return String.format("%02d", MATCH_LEVEL_MAX - matchLevel);
+        return String.format(Locale.ROOT, "%02d", MATCH_LEVEL_MAX - matchLevel);
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index dc9bef2..5d580f2 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -229,7 +229,7 @@
         // or if it's a 2+ characters non-word (i.e. it's not in the dictionary).
         final boolean allowsToBeAutoCorrected = (null != whitelistedWord
                 && !whitelistedWord.equals(consideredWord))
-                || (consideredWord.length() > 1 && !AutoCorrection.isInTheDictionary(mDictionaries,
+                || (consideredWord.length() > 1 && !AutoCorrection.isValidWord(this,
                         consideredWord, wordComposer.isFirstCharCapitalized()));
 
         final boolean hasAutoCorrection;
@@ -379,7 +379,8 @@
                     typedWord, cur.toString(), cur.mScore);
             final String scoreInfoString;
             if (normalizedScore > 0) {
-                scoreInfoString = String.format("%d (%4.2f)", cur.mScore, normalizedScore);
+                scoreInfoString = String.format(
+                        Locale.ROOT, "%d (%4.2f)", cur.mScore, normalizedScore);
             } else {
                 scoreInfoString = Integer.toString(cur.mScore);
             }
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index ad350a0..1113939 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -54,6 +54,7 @@
 import com.android.inputmethod.keyboard.MainKeyboardView;
 import com.android.inputmethod.keyboard.MoreKeysPanel;
 import com.android.inputmethod.keyboard.ViewLayoutUtils;
+import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
 import com.android.inputmethod.latin.AutoCorrection;
 import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.Constants;
@@ -689,7 +690,8 @@
 
     @Override
     public boolean onLongClick(final View view) {
-        KeyboardSwitcher.getInstance().hapticAndAudioFeedback(Constants.NOT_A_CODE);
+        AudioAndHapticFeedbackManager.getInstance().hapticAndAudioFeedback(
+                Constants.NOT_A_CODE, this);
         return showMoreSuggestions();
     }
 
diff --git a/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java
index 7e8f166..3482153 100644
--- a/java/src/com/android/inputmethod/research/MainLogBuffer.java
+++ b/java/src/com/android/inputmethod/research/MainLogBuffer.java
@@ -63,6 +63,15 @@
     private static final boolean DEBUG = false
             && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
 
+    // Keep consistent with switch statement in Statistics.recordPublishabilityResultCode()
+    public static final int PUBLISHABILITY_PUBLISHABLE = 0;
+    public static final int PUBLISHABILITY_UNPUBLISHABLE_STOPPING = 1;
+    public static final int PUBLISHABILITY_UNPUBLISHABLE_INCORRECT_WORD_COUNT = 2;
+    public static final int PUBLISHABILITY_UNPUBLISHABLE_SAMPLED_TOO_RECENTLY = 3;
+    public static final int PUBLISHABILITY_UNPUBLISHABLE_DICTIONARY_UNAVAILABLE = 4;
+    public static final int PUBLISHABILITY_UNPUBLISHABLE_MAY_CONTAIN_DIGIT = 5;
+    public static final int PUBLISHABILITY_UNPUBLISHABLE_NOT_IN_DICTIONARY = 6;
+
     // The size of the n-grams logged.  E.g. N_GRAM_SIZE = 2 means to sample bigrams.
     public static final int N_GRAM_SIZE = 2;
 
@@ -105,21 +114,24 @@
     }
 
     /**
-     * Determines whether uploading the n words at the front the MainLogBuffer will not violate
-     * user privacy.
+     * Determines whether the string determined by a series of LogUnits will not violate user
+     * privacy if published.
      *
-     * The size of the MainLogBuffer is just enough to hold one n-gram, its corrections, and any
-     * non-character data that is typed between words.  The decision about privacy is made based on
-     * the buffer's entire content.  If it is decided that the privacy risks are too great to upload
-     * the contents of this buffer, a censored version of the LogItems may still be uploaded.  E.g.,
-     * the screen orientation and other characteristics about the device can be uploaded without
-     * revealing much about the user.
+     * @param logUnits a LogUnit list to check for publishability
+     * @param nGramSize the smallest n-gram acceptable to be published.  if
+     * {@link ResearchLogger.IS_LOGGING_EVERYTHING} is true, then publish if there are more than
+     * {@code minNGramSize} words in the logUnits, otherwise wait.  if {@link
+     * ResearchLogger.IS_LOGGING_EVERYTHING} is false, then ensure that there are exactly nGramSize
+     * words in the LogUnits.
+     *
+     * @return one of the {@code PUBLISHABILITY_*} result codes defined in this class.
      */
-    private boolean isSafeNGram(final ArrayList<LogUnit> logUnits, final int minNGramSize) {
+    private int getPublishabilityResultCode(final ArrayList<LogUnit> logUnits,
+            final int nGramSize) {
         // Bypass privacy checks when debugging.
         if (ResearchLogger.IS_LOGGING_EVERYTHING) {
             if (mIsStopping) {
-                return true;
+                return PUBLISHABILITY_UNPUBLISHABLE_STOPPING;
             }
             // Only check that it is the right length.  If not, wait for later words to make
             // complete n-grams.
@@ -129,13 +141,17 @@
                 final LogUnit logUnit = logUnits.get(i);
                 numWordsInLogUnitList += logUnit.getNumWords();
             }
-            return numWordsInLogUnitList >= minNGramSize;
+            if (numWordsInLogUnitList >= nGramSize) {
+                return PUBLISHABILITY_PUBLISHABLE;
+            } else {
+                return PUBLISHABILITY_UNPUBLISHABLE_INCORRECT_WORD_COUNT;
+            }
         }
 
         // Check that we are not sampling too frequently.  Having sampled recently might disclose
         // too much of the user's intended meaning.
         if (mNumWordsUntilSafeToSample > 0) {
-            return false;
+            return PUBLISHABILITY_UNPUBLISHABLE_SAMPLED_TOO_RECENTLY;
         }
         // Reload the dictionary in case it has changed (e.g., because the user has changed
         // languages).
@@ -144,7 +160,7 @@
             // Main dictionary is unavailable.  Since we cannot check it, we cannot tell if a
             // word is out-of-vocabulary or not.  Therefore, we must judge the entire buffer
             // contents to potentially pose a privacy risk.
-            return false;
+            return PUBLISHABILITY_UNPUBLISHABLE_DICTIONARY_UNAVAILABLE;
         }
 
         // Check each word in the buffer.  If any word poses a privacy threat, we cannot upload
@@ -155,7 +171,7 @@
             if (!logUnit.hasOneOrMoreWords()) {
                 // Digits outside words are a privacy threat.
                 if (logUnit.mayContainDigit()) {
-                    return false;
+                    return PUBLISHABILITY_UNPUBLISHABLE_MAY_CONTAIN_DIGIT;
                 }
             } else {
                 numWordsInLogUnitList += logUnit.getNumWords();
@@ -168,14 +184,18 @@
                                     + ResearchLogger.hasLetters(word)
                                     + ", isValid: " + (dictionary.isValidWord(word)));
                         }
-                        return false;
+                        return PUBLISHABILITY_UNPUBLISHABLE_NOT_IN_DICTIONARY;
                     }
                 }
             }
         }
 
         // Finally, only return true if the ngram is the right size.
-        return numWordsInLogUnitList == minNGramSize;
+        if (numWordsInLogUnitList == nGramSize) {
+            return PUBLISHABILITY_PUBLISHABLE;
+        } else {
+            return PUBLISHABILITY_UNPUBLISHABLE_INCORRECT_WORD_COUNT;
+        }
     }
 
     public void shiftAndPublishAll() throws IOException {
@@ -216,7 +236,9 @@
         // TODO: Refactor this method to require fewer passes through the LogUnits.  Should really
         // require only one pass.
         ArrayList<LogUnit> logUnits = peekAtFirstNWords(N_GRAM_SIZE);
-        if (isSafeNGram(logUnits, N_GRAM_SIZE)) {
+        final int publishabilityResultCode = getPublishabilityResultCode(logUnits, N_GRAM_SIZE);
+        ResearchLogger.recordPublishabilityResultCode(publishabilityResultCode);
+        if (publishabilityResultCode == MainLogBuffer.PUBLISHABILITY_PUBLISHABLE) {
             // Good n-gram at the front of the buffer.  Publish it, disclosing details.
             publish(logUnits, true /* canIncludePrivateData */);
             shiftOutWords(N_GRAM_SIZE);
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index 0220e20..64f0349 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -1660,12 +1660,24 @@
     }
 
     /**
-     * Shared event for logging committed text.
+     * Shared events for logging committed text.
+     *
+     * The "CommitTextEventHappened" LogStatement is written to the log even if privacy rules
+     * indicate that the word contents should not be logged.  It has no contents, and only serves to
+     * record the event and thereby make it easier to calculate word-level statistics even when the
+     * word contents are unknown.
      */
     private static final LogStatement LOGSTATEMENT_COMMITTEXT =
-            new LogStatement("CommitText", true, false, "committedText", "isBatchMode");
+            new LogStatement("CommitText", true /* isPotentiallyPrivate */,
+                    false /* isPotentiallyRevealing */, "committedText", "isBatchMode");
+    private static final LogStatement LOGSTATEMENT_COMMITTEXT_EVENT_HAPPENED =
+            new LogStatement("CommitTextEventHappened", false /* isPotentiallyPrivate */,
+                    false /* isPotentiallyRevealing */);
     private void enqueueCommitText(final String word, final boolean isBatchMode) {
+        // Event containing the word; will be published only if privacy checks pass
         enqueueEvent(LOGSTATEMENT_COMMITTEXT, word, isBatchMode);
+        // Event not containing the word; will always be published
+        enqueueEvent(LOGSTATEMENT_COMMITTEXT_EVENT_HAPPENED);
     }
 
     /**
@@ -1884,6 +1896,20 @@
     }
 
     /**
+     * Call this method when the logging system has attempted publication of an n-gram.
+     *
+     * Statistics are gathered about the success or failure.
+     *
+     * @param publishabilityResultCode a result code as defined by
+     * {@code MainLogBuffer.PUBLISHABILITY_*}
+     */
+    static void recordPublishabilityResultCode(final int publishabilityResultCode) {
+        final ResearchLogger researchLogger = getInstance();
+        final Statistics statistics = researchLogger.mStatistics;
+        statistics.recordPublishabilityResultCode(publishabilityResultCode);
+    }
+
+    /**
      * Log statistics.
      *
      * ContextualData, recorded at the end of a session.
@@ -1895,7 +1921,11 @@
                     "averageTimeDuringRepeatedDelete", "averageTimeAfterDelete",
                     "dictionaryWordCount", "splitWordsCount", "gestureInputCount",
                     "gestureCharsCount", "gesturesDeletedCount", "manualSuggestionsCount",
-                    "revertCommitsCount", "correctedWordsCount", "autoCorrectionsCount");
+                    "revertCommitsCount", "correctedWordsCount", "autoCorrectionsCount",
+                    "publishableCount", "unpublishableStoppingCount",
+                    "unpublishableIncorrectWordCount", "unpublishableSampledTooRecentlyCount",
+                    "unpublishableDictionaryUnavailableCount", "unpublishableMayContainDigitCount",
+                    "unpublishableNotInDictionaryCount");
     private static void logStatistics() {
         final ResearchLogger researchLogger = getInstance();
         final Statistics statistics = researchLogger.mStatistics;
@@ -1910,6 +1940,10 @@
                 statistics.mGesturesInputCount, statistics.mGesturesCharsCount,
                 statistics.mGesturesDeletedCount, statistics.mManualSuggestionsCount,
                 statistics.mRevertCommitsCount, statistics.mCorrectedWordsCount,
-                statistics.mAutoCorrectionsCount);
+                statistics.mAutoCorrectionsCount, statistics.mPublishableCount,
+                statistics.mUnpublishableStoppingCount, statistics.mUnpublishableIncorrectWordCount,
+                statistics.mUnpublishableSampledTooRecently,
+                statistics.mUnpublishableDictionaryUnavailable,
+                statistics.mUnpublishableMayContainDigit, statistics.mUnpublishableNotInDictionary);
     }
 }
diff --git a/java/src/com/android/inputmethod/research/Statistics.java b/java/src/com/android/inputmethod/research/Statistics.java
index 7f6c851..e573ca0 100644
--- a/java/src/com/android/inputmethod/research/Statistics.java
+++ b/java/src/com/android/inputmethod/research/Statistics.java
@@ -61,6 +61,16 @@
     boolean mIsEmptyUponStarting;
     boolean mIsEmptinessStateKnown;
 
+    // Counts of how often an n-gram is collected or not, and the reasons for the decision.
+    // Keep consistent with publishability result code list in MainLogBuffer
+    int mPublishableCount;
+    int mUnpublishableStoppingCount;
+    int mUnpublishableIncorrectWordCount;
+    int mUnpublishableSampledTooRecently;
+    int mUnpublishableDictionaryUnavailable;
+    int mUnpublishableMayContainDigit;
+    int mUnpublishableNotInDictionary;
+
     // Timers to count average time to enter a key, first press a delete key,
     // between delete keys, and then to return typing after a delete key.
     final AverageTimeCounter mKeyCounter = new AverageTimeCounter();
@@ -133,6 +143,13 @@
         mAfterDeleteKeyCounter.reset();
         mGesturesCharsCount = 0;
         mGesturesDeletedCount = 0;
+        mPublishableCount = 0;
+        mUnpublishableStoppingCount = 0;
+        mUnpublishableIncorrectWordCount = 0;
+        mUnpublishableSampledTooRecently = 0;
+        mUnpublishableDictionaryUnavailable = 0;
+        mUnpublishableMayContainDigit = 0;
+        mUnpublishableNotInDictionary = 0;
 
         mLastTapTime = 0;
         mIsLastKeyDeleteKey = false;
@@ -230,4 +247,31 @@
         mIsLastKeyDeleteKey = isDeletion;
         mLastTapTime = time;
     }
+
+    public void recordPublishabilityResultCode(final int publishabilityResultCode) {
+        // Keep consistent with publishability result code list in MainLogBuffer
+        switch (publishabilityResultCode) {
+        case MainLogBuffer.PUBLISHABILITY_PUBLISHABLE:
+            mPublishableCount++;
+            break;
+        case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_STOPPING:
+            mUnpublishableStoppingCount++;
+            break;
+        case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_INCORRECT_WORD_COUNT:
+            mUnpublishableIncorrectWordCount++;
+            break;
+        case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_SAMPLED_TOO_RECENTLY:
+            mUnpublishableSampledTooRecently++;
+            break;
+        case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_DICTIONARY_UNAVAILABLE:
+            mUnpublishableDictionaryUnavailable++;
+            break;
+        case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_MAY_CONTAIN_DIGIT:
+            mUnpublishableMayContainDigit++;
+            break;
+        case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_NOT_IN_DICTIONARY:
+            mUnpublishableNotInDictionary++;
+            break;
+        }
+    }
 }
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java
index b1ae6f5..861abe8 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java
@@ -35,8 +35,8 @@
 
     private static final String CODE_SETTINGS = "!code/key_settings";
     private static final String ICON_SETTINGS = "!icon/settings_key";
-    private static final String CODE_SETTINGS_UPPERCASE = CODE_SETTINGS.toUpperCase();
-    private static final String ICON_SETTINGS_UPPERCASE = ICON_SETTINGS.toUpperCase();
+    private static final String CODE_SETTINGS_UPPERCASE = CODE_SETTINGS.toUpperCase(Locale.ROOT);
+    private static final String ICON_SETTINGS_UPPERCASE = ICON_SETTINGS.toUpperCase(Locale.ROOT);
     private static final String CODE_NON_EXISTING = "!code/non_existing";
     private static final String ICON_NON_EXISTING = "!icon/non_existing";
 
@@ -587,7 +587,7 @@
                 new String[] { null, "a", "b", "c" }, true);
         // Upper case specification will not work.
         assertGetBooleanValue("HAS LABEL", HAS_LABEL,
-                new String[] { HAS_LABEL.toUpperCase(), "a", "b", "c" },
+                new String[] { HAS_LABEL.toUpperCase(Locale.ROOT), "a", "b", "c" },
                 new String[] { "!HASLABEL!", "a", "b", "c" }, false);
 
         assertGetBooleanValue("No has label", HAS_LABEL,
@@ -600,13 +600,13 @@
         // Upper case specification will not work.
         assertGetBooleanValue("Multiple has label", HAS_LABEL,
                 new String[] {
-                    "a", HAS_LABEL.toUpperCase(), "b", "c", HAS_LABEL, "d" },
+                    "a", HAS_LABEL.toUpperCase(Locale.ROOT), "b", "c", HAS_LABEL, "d" },
                 new String[] {
                     "a", "!HASLABEL!", "b", "c", null, "d" }, true);
         // Upper case specification will not work.
         assertGetBooleanValue("Multiple has label with needs dividers", HAS_LABEL,
                 new String[] {
-                    "a", HAS_LABEL, "b", NEEDS_DIVIDER, HAS_LABEL.toUpperCase(), "d" },
+                    "a", HAS_LABEL, "b", NEEDS_DIVIDER, HAS_LABEL.toUpperCase(Locale.ROOT), "d" },
                 new String[] {
                     "a", null, "b", NEEDS_DIVIDER, "!HASLABEL!", "d" }, true);
     }
@@ -625,7 +625,7 @@
                 new String[] { null, "a", "b", "c" }, 3);
         // Upper case specification will not work.
         assertGetIntValue("FIXED COLUMN ORDER 3", FIXED_COLUMN_ORDER, -1,
-                new String[] { FIXED_COLUMN_ORDER.toUpperCase() + "3", "a", "b", "c" },
+                new String[] { FIXED_COLUMN_ORDER.toUpperCase(Locale.ROOT) + "3", "a", "b", "c" },
                 new String[] { "!FIXEDCOLUMNORDER!3", "a", "b", "c" }, -1);
 
         assertGetIntValue("No fixed column order", FIXED_COLUMN_ORDER, -1,
@@ -641,7 +641,7 @@
         // Upper case specification will not work.
         assertGetIntValue("Multiple fixed column order 5,3 with has label", FIXED_COLUMN_ORDER, -1,
                 new String[] {
-                    FIXED_COLUMN_ORDER.toUpperCase() + "5", HAS_LABEL, "a",
+                    FIXED_COLUMN_ORDER.toUpperCase(Locale.ROOT) + "5", HAS_LABEL, "a",
                     FIXED_COLUMN_ORDER + "3", "b" },
                 new String[] { "!FIXEDCOLUMNORDER!5", HAS_LABEL, "a", null, "b" }, 3);
     }
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java b/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java
index 2544b6c..2cb9648 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java
@@ -149,11 +149,6 @@
         mLongPressTimeoutCode = 0;
     }
 
-    @Override
-    public void hapticAndAudioFeedback(final int code) {
-        // Nothing to do.
-    }
-
     public void onLongPressTimeout(final int code) {
         // TODO: Handle simultaneous long presses.
         if (mLongPressTimeoutCode == code) {