Merge "Add a method to import one bigram and string utilities"
diff --git a/java/res/drawable-hdpi/btn_center_default.9.png b/java/res/drawable-hdpi/btn_center_default.9.png
deleted file mode 100644
index 4f5f01c..0000000
--- a/java/res/drawable-hdpi/btn_center_default.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_center_pressed.9.png b/java/res/drawable-hdpi/btn_center_pressed.9.png
deleted file mode 100644
index 213b482..0000000
--- a/java/res/drawable-hdpi/btn_center_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_center_selected.9.png b/java/res/drawable-hdpi/btn_center_selected.9.png
deleted file mode 100644
index 213b482..0000000
--- a/java/res/drawable-hdpi/btn_center_selected.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_center_default.9.png b/java/res/drawable-mdpi/btn_center_default.9.png
deleted file mode 100644
index d5ec36b..0000000
--- a/java/res/drawable-mdpi/btn_center_default.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_center_pressed.9.png b/java/res/drawable-mdpi/btn_center_pressed.9.png
deleted file mode 100644
index 593a679..0000000
--- a/java/res/drawable-mdpi/btn_center_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_center_selected.9.png b/java/res/drawable-mdpi/btn_center_selected.9.png
deleted file mode 100644
index f1914a8..0000000
--- a/java/res/drawable-mdpi/btn_center_selected.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_center_default.9.png b/java/res/drawable-xhdpi/btn_center_default.9.png
deleted file mode 100644
index e847425..0000000
--- a/java/res/drawable-xhdpi/btn_center_default.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_center_pressed.9.png b/java/res/drawable-xhdpi/btn_center_pressed.9.png
deleted file mode 100644
index facfd43..0000000
--- a/java/res/drawable-xhdpi/btn_center_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_center_selected.9.png b/java/res/drawable-xhdpi/btn_center_selected.9.png
deleted file mode 100644
index facfd43..0000000
--- a/java/res/drawable-xhdpi/btn_center_selected.9.png
+++ /dev/null
Binary files differ
diff --git a/java/res/drawable/btn_center.xml b/java/res/drawable/btn_center.xml
deleted file mode 100644
index 3ac2129..0000000
--- a/java/res/drawable/btn_center.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<selector
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:exitFadeDuration="@android:integer/config_mediumAnimTime">
-    <item
-        android:state_window_focused="false"
-        android:state_enabled="true"
-        android:drawable="@drawable/btn_center_default" />
-    <item
-        android:state_pressed="true"
-        android:drawable="@drawable/btn_center_pressed" />
-    <item
-        android:state_focused="true"
-        android:state_enabled="true"
-        android:drawable="@drawable/btn_center_selected" />
-    <item
-        android:state_enabled="true"
-        android:drawable="@drawable/btn_center_default" />
-    <item
-        android:drawable="@drawable/btn_center_default" />
-</selector>
diff --git a/java/res/drawable/btn_keyboard_key.xml b/java/res/drawable/btn_keyboard_key.xml
index 797bc10..112ac26 100644
--- a/java/res/drawable/btn_keyboard_key.xml
+++ b/java/res/drawable/btn_keyboard_key.xml
@@ -15,9 +15,7 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-
     <!-- Toggle keys. Use checkable/checked state. -->
-
     <item android:state_checkable="true" android:state_checked="true"
           android:state_pressed="true"
           android:drawable="@drawable/btn_keyboard_key_pressed_on" />
@@ -28,11 +26,12 @@
     <item android:state_checkable="true"
           android:drawable="@drawable/btn_keyboard_key_normal_off" />
 
-    <!-- Normal keys -->
+    <!-- Empty background keys. -->
+    <item android:state_empty="true"
+          android:drawable="@drawable/transparent" />
 
+    <!-- Normal keys. -->
     <item android:state_pressed="true"
           android:drawable="@drawable/btn_keyboard_key_pressed" />
-    <item
-          android:drawable="@drawable/btn_keyboard_key_normal" />
-
+    <item android:drawable="@drawable/btn_keyboard_key_normal" />
 </selector>
diff --git a/java/res/drawable/btn_keyboard_key3.xml b/java/res/drawable/btn_keyboard_key3.xml
index dbe82d5..080b1f3 100644
--- a/java/res/drawable/btn_keyboard_key3.xml
+++ b/java/res/drawable/btn_keyboard_key3.xml
@@ -15,9 +15,7 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-
     <!-- Toggle keys. Use checkable/checked state. -->
-
     <item android:state_checkable="true" android:state_checked="true"
           android:state_pressed="true"
           android:drawable="@drawable/btn_keyboard_key_pressed_on" />
@@ -28,8 +26,11 @@
     <item android:state_checkable="true"
           android:drawable="@drawable/btn_keyboard_key_fulltrans_pressed" />
 
-    <!-- Normal keys -->
+    <!-- Empty background keys. -->
+    <item android:state_empty="true"
+          android:drawable="@drawable/transparent" />
 
+    <!-- Normal keys. -->
     <item android:state_pressed="true"
           android:drawable="@drawable/btn_keyboard_key_fulltrans_normal" />
     <item android:drawable="@drawable/btn_keyboard_key_fulltrans_pressed" />
diff --git a/java/res/drawable/btn_keyboard_key_gingerbread.xml b/java/res/drawable/btn_keyboard_key_gingerbread.xml
index 5b4399e..3fc253e 100644
--- a/java/res/drawable/btn_keyboard_key_gingerbread.xml
+++ b/java/res/drawable/btn_keyboard_key_gingerbread.xml
@@ -15,23 +15,19 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-
     <!-- Functional keys. -->
-
     <item android:state_single="true" android:state_pressed="true"
           android:drawable="@drawable/btn_keyboard_key_dark_pressed" />
     <item android:state_single="true"
           android:drawable="@drawable/btn_keyboard_key_dark_normal" />
 
     <!-- Action keys. -->
-
     <item android:state_active="true" android:state_pressed="true"
           android:drawable="@drawable/btn_keyboard_key_dark_pressed" />
     <item android:state_active="true"
           android:drawable="@drawable/btn_keyboard_key_dark_normal" />
 
     <!-- Toggle keys. Use checkable/checked state. -->
-
     <item android:state_checkable="true" android:state_checked="true" android:state_pressed="true"
           android:drawable="@drawable/btn_keyboard_key_dark_pressed_on" />
     <item android:state_checkable="true" android:state_pressed="true"
@@ -41,8 +37,11 @@
     <item android:state_checkable="true"
           android:drawable="@drawable/btn_keyboard_key_dark_normal_off" />
 
-    <!-- Normal keys. -->
+    <!-- Empty background keys. -->
+    <item android:state_empty="true"
+          android:drawable="@drawable/transparent" />
 
+    <!-- Normal keys. -->
     <item android:state_pressed="true"
           android:drawable="@drawable/btn_keyboard_key_light_pressed" />
     <item android:drawable="@drawable/btn_keyboard_key_light_normal" />
diff --git a/java/res/drawable/btn_keyboard_key_ics.xml b/java/res/drawable/btn_keyboard_key_ics.xml
index e893da1..0c86e16 100644
--- a/java/res/drawable/btn_keyboard_key_ics.xml
+++ b/java/res/drawable/btn_keyboard_key_ics.xml
@@ -15,23 +15,19 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-
     <!-- Functional keys. -->
-
     <item android:state_single="true" android:state_pressed="true"
           android:drawable="@drawable/btn_keyboard_key_dark_pressed_holo" />
     <item android:state_single="true"
           android:drawable="@drawable/btn_keyboard_key_dark_normal_holo" />
 
     <!-- Action keys. -->
-
     <item android:state_active="true" android:state_pressed="true"
           android:drawable="@drawable/btn_keyboard_key_dark_pressed_holo" />
     <item android:state_active="true"
           android:drawable="@drawable/btn_keyboard_key_dark_active_holo" />
 
     <!-- Toggle keys. Use checkable/checked state. -->
-
     <item android:state_checkable="true" android:state_checked="true" android:state_pressed="true"
           android:drawable="@drawable/btn_keyboard_key_dark_pressed_on_holo" />
     <item android:state_checkable="true" android:state_pressed="true"
@@ -41,8 +37,11 @@
     <item android:state_checkable="true"
           android:drawable="@drawable/btn_keyboard_key_dark_normal_off_holo" />
 
-    <!-- Normal keys. -->
+    <!-- Empty background keys. -->
+    <item android:state_empty="true"
+          android:drawable="@drawable/transparent" />
 
+    <!-- Normal keys. -->
     <item android:state_pressed="true"
           android:drawable="@drawable/btn_keyboard_key_light_pressed_holo" />
     <item android:drawable="@drawable/btn_keyboard_key_light_normal_holo" />
diff --git a/java/res/drawable/btn_keyboard_key_stone.xml b/java/res/drawable/btn_keyboard_key_stone.xml
index 9bc3f18..70a2ad4 100644
--- a/java/res/drawable/btn_keyboard_key_stone.xml
+++ b/java/res/drawable/btn_keyboard_key_stone.xml
@@ -15,23 +15,19 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-
     <!-- Functional keys. -->
-
     <item android:state_single="true" android:state_pressed="true"
           android:drawable="@drawable/btn_keyboard_key_fulltrans_pressed" />
     <item android:state_single="true"
           android:drawable="@drawable/btn_keyboard_key_normal_stone" />
 
     <!-- Action keys. -->
-
     <item android:state_active="true" android:state_pressed="true"
           android:drawable="@drawable/btn_keyboard_key_fulltrans_pressed" />
     <item android:state_active="true"
           android:drawable="@drawable/btn_keyboard_key_normal_stone" />
 
     <!-- Toggle keys. Use checkable/checked state. -->
-
     <item android:state_checkable="true" android:state_checked="true"
           android:state_pressed="true"
           android:drawable="@drawable/btn_keyboard_key_normal_on_stone" />
@@ -42,8 +38,11 @@
     <item android:state_checkable="true"
           android:drawable="@drawable/btn_keyboard_key_normal_off_stone" />
 
-    <!-- Normal keys. -->
+    <!-- Empty background keys. -->
+    <item android:state_empty="true"
+          android:drawable="@drawable/transparent" />
 
+    <!-- Normal keys. -->
     <item android:state_pressed="true"
           android:drawable="@drawable/btn_keyboard_key_fulltrans_pressed" />
     <item android:drawable="@drawable/btn_keyboard_key_normal_stone" />
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index eef9116..5c59f5f 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -240,11 +240,12 @@
         <attr name="maxMoreKeysColumn" format="integer" />
         <attr name="backgroundType" format="enum">
             <!-- This should be aligned with Key.BACKGROUND_TYPE_* -->
-            <enum name="normal" value="0" />
-            <enum name="functional" value="1" />
-            <enum name="action" value="2" />
-            <enum name="stickyOff" value="3" />
-            <enum name="stickyOn" value="4" />
+            <enum name="empty" value="0" />
+            <enum name="normal" value="1" />
+            <enum name="functional" value="2" />
+            <enum name="action" value="3" />
+            <enum name="stickyOff" value="4" />
+            <enum name="stickyOn" value="5" />
         </attr>
         <!-- The key action flags. -->
         <attr name="keyActionFlags" format="integer">
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index 6180528..16c79eb 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -122,11 +122,12 @@
 
     /** Background type that represents different key background visual than normal one. */
     public final int mBackgroundType;
-    public static final int BACKGROUND_TYPE_NORMAL = 0;
-    public static final int BACKGROUND_TYPE_FUNCTIONAL = 1;
-    public static final int BACKGROUND_TYPE_ACTION = 2;
-    public static final int BACKGROUND_TYPE_STICKY_OFF = 3;
-    public static final int BACKGROUND_TYPE_STICKY_ON = 4;
+    public static final int BACKGROUND_TYPE_EMPTY = 0;
+    public static final int BACKGROUND_TYPE_NORMAL = 1;
+    public static final int BACKGROUND_TYPE_FUNCTIONAL = 2;
+    public static final int BACKGROUND_TYPE_ACTION = 3;
+    public static final int BACKGROUND_TYPE_STICKY_OFF = 4;
+    public static final int BACKGROUND_TYPE_STICKY_ON = 5;
 
     private final int mActionFlags;
     private static final int ACTION_FLAGS_IS_REPEATABLE = 0x01;
@@ -175,7 +176,7 @@
     public Key(final KeyboardParams params, final MoreKeySpec moreKeySpec, final int x, final int y,
             final int width, final int height, final int labelFlags) {
         this(params, moreKeySpec.mLabel, null, moreKeySpec.mIconId, moreKeySpec.mCode,
-                moreKeySpec.mOutputText, x, y, width, height, labelFlags);
+                moreKeySpec.mOutputText, x, y, width, height, labelFlags, BACKGROUND_TYPE_NORMAL);
     }
 
     /**
@@ -183,12 +184,12 @@
      */
     public Key(final KeyboardParams params, final String label, final String hintLabel,
             final int iconId, final int code, final String outputText, final int x, final int y,
-            final int width, final int height, final int labelFlags) {
+            final int width, final int height, final int labelFlags, final int backgroundType) {
         mHeight = height - params.mVerticalGap;
         mWidth = width - params.mHorizontalGap;
         mHintLabel = hintLabel;
         mLabelFlags = labelFlags;
-        mBackgroundType = BACKGROUND_TYPE_NORMAL;
+        mBackgroundType = backgroundType;
         mActionFlags = 0;
         mMoreKeys = null;
         mMoreKeysColumnAndFlags = 0;
@@ -465,6 +466,7 @@
 
     private static String backgroundName(final int backgroundType) {
         switch (backgroundType) {
+        case BACKGROUND_TYPE_EMPTY: return "empty";
         case BACKGROUND_TYPE_NORMAL: return "normal";
         case BACKGROUND_TYPE_FUNCTIONAL: return "functional";
         case BACKGROUND_TYPE_ACTION: return "action";
@@ -788,6 +790,10 @@
         android.R.attr.state_pressed
     };
 
+    private final static int[] KEY_STATE_EMPTY = {
+        android.R.attr.state_empty
+    };
+
     // functional normal state (with properties)
     private static final int[] KEY_STATE_FUNCTIONAL_NORMAL = {
             android.R.attr.state_single
@@ -825,6 +831,8 @@
             return mPressed ? KEY_STATE_PRESSED_HIGHLIGHT_OFF : KEY_STATE_NORMAL_HIGHLIGHT_OFF;
         case BACKGROUND_TYPE_STICKY_ON:
             return mPressed ? KEY_STATE_PRESSED_HIGHLIGHT_ON : KEY_STATE_NORMAL_HIGHLIGHT_ON;
+        case BACKGROUND_TYPE_EMPTY:
+            return mPressed ? KEY_STATE_PRESSED : KEY_STATE_EMPTY;
         default: /* BACKGROUND_TYPE_NORMAL */
             return mPressed ? KEY_STATE_PRESSED : KEY_STATE_NORMAL;
         }
@@ -842,7 +850,7 @@
         protected Spacer(final KeyboardParams params, final int x, final int y, final int width,
                 final int height) {
             super(params, null, null, ICON_UNDEFINED, CODE_UNSPECIFIED,
-                    null, x, y, width, height, 0);
+                    null, x, y, width, height, 0, BACKGROUND_TYPE_EMPTY);
         }
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
index b266986..dc760e6 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
@@ -26,10 +26,10 @@
      *
      * @param primaryCode the unicode of the key being pressed. If the touch is not on a valid key,
      *            the value will be zero.
-     * @param isRepeatKey true if pressing has occurred while key repeat input.
+     * @param repeatCount how many times the key was repeated. Zero if it is the first press.
      * @param isSinglePointer true if pressing has occurred while no other key is being pressed.
      */
-    public void onPressKey(int primaryCode, boolean isRepeatKey, boolean isSinglePointer);
+    public void onPressKey(int primaryCode, int repeatCount, boolean isSinglePointer);
 
     /**
      * Called when the user releases a key. This is sent after the {@link #onCodeInput} is called.
@@ -103,7 +103,7 @@
 
     public static class Adapter implements KeyboardActionListener {
         @Override
-        public void onPressKey(int primaryCode, boolean isRepeatKey, boolean isSinglePointer) {}
+        public void onPressKey(int primaryCode, int repeatCount, boolean isSinglePointer) {}
         @Override
         public void onReleaseKey(int primaryCode, boolean withSliding) {}
         @Override
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 526c2f1..f3d0ead 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -217,7 +217,7 @@
                 startWhileTypingFadeinAnimation(keyboardView);
                 break;
             case MSG_REPEAT_KEY:
-                tracker.onKeyRepeat(msg.arg1);
+                tracker.onKeyRepeat(msg.arg1 /* code */, msg.arg2 /* repeatCount */);
                 break;
             case MSG_LONGPRESS_KEY:
                 keyboardView.onLongPress(tracker);
@@ -230,12 +230,14 @@
         }
 
         @Override
-        public void startKeyRepeatTimer(final PointerTracker tracker, final int delay) {
+        public void startKeyRepeatTimer(final PointerTracker tracker, final int repeatCount,
+                final int delay) {
             final Key key = tracker.getKey();
             if (key == null || delay == 0) {
                 return;
             }
-            sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, key.mCode, 0, tracker), delay);
+            sendMessageDelayed(
+                    obtainMessage(MSG_REPEAT_KEY, key.mCode, repeatCount, tracker), delay);
         }
 
         public void cancelKeyRepeatTimer() {
@@ -938,7 +940,7 @@
         if (key.hasNoPanelAutoMoreKey()) {
             final int moreKeyCode = key.mMoreKeys[0].mCode;
             tracker.onLongPressed();
-            listener.onPressKey(moreKeyCode, false /* isRepeatKey */, true /* isSinglePointer */);
+            listener.onPressKey(moreKeyCode, 0 /* repeatCount */, true /* isSinglePointer */);
             listener.onCodeInput(moreKeyCode,
                     Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
             listener.onReleaseKey(moreKeyCode, false /* withSliding */);
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index b66ee2a..5387ddb 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -64,7 +64,7 @@
 
         /**
          * Get KeyboardActionListener object that is used to register key code and so on.
-         * @return the KeyboardActionListner for this PointerTracker
+         * @return the KeyboardActionListner for this PointerTracke
          */
         public KeyboardActionListener getKeyboardActionListener();
 
@@ -94,7 +94,7 @@
     public interface TimerProxy {
         public void startTypingStateTimer(Key typedKey);
         public boolean isTypingState();
-        public void startKeyRepeatTimer(PointerTracker tracker, int delay);
+        public void startKeyRepeatTimer(PointerTracker tracker, int repeatCount, int delay);
         public void startLongPressTimer(PointerTracker tracker, int delay);
         public void cancelLongPressTimer();
         public void startDoubleTapShiftKeyTimer();
@@ -111,7 +111,7 @@
             @Override
             public boolean isTypingState() { return false; }
             @Override
-            public void startKeyRepeatTimer(PointerTracker tracker, int delay) {}
+            public void startKeyRepeatTimer(PointerTracker tracker, int repeatCount, int delay) {}
             @Override
             public void startLongPressTimer(PointerTracker tracker, int delay) {}
             @Override
@@ -490,7 +490,7 @@
 
     // Returns true if keyboard has been changed by this callback.
     private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key,
-            final boolean isRepeatKey) {
+            final int repeatCount) {
         // While gesture input is going on, this method should be a no-operation. But when gesture
         // input has been canceled, <code>sInGesture</code> and <code>mIsDetectingGesture</code>
         // are set to false. To keep this method is a no-operation,
@@ -504,13 +504,13 @@
                     KeyDetector.printableCode(key),
                     ignoreModifierKey ? " ignoreModifier" : "",
                     key.isEnabled() ? "" : " disabled",
-                    isRepeatKey ? " repeat" : ""));
+                    repeatCount > 0 ? " repeatCount=" + repeatCount : ""));
         }
         if (ignoreModifierKey) {
             return false;
         }
         if (key.isEnabled()) {
-            mListener.onPressKey(key.mCode, isRepeatKey, getActivePointerTrackerCount() == 1);
+            mListener.onPressKey(key.mCode, repeatCount, getActivePointerTrackerCount() == 1);
             final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged;
             mKeyboardLayoutHasBeenChanged = false;
             mTimerProxy.startTypingStateTimer(key);
@@ -967,7 +967,7 @@
             // This onPress call may have changed keyboard layout. Those cases are detected at
             // {@link #setKeyboard}. In those cases, we should update key according to the new
             // keyboard layout.
-            if (callListenerOnPressAndCheckKeyboardLayoutChange(key, false /* isRepeatKey */)) {
+            if (callListenerOnPressAndCheckKeyboardLayoutChange(key, 0 /* repeatCount */)) {
                 key = onDownKey(x, y, eventTime);
             }
 
@@ -1057,7 +1057,7 @@
         // at {@link #setKeyboard}. In those cases, we should update key according
         // to the new keyboard layout.
         Key key = newKey;
-        if (callListenerOnPressAndCheckKeyboardLayoutChange(key, false /* isRepeatKey */)) {
+        if (callListenerOnPressAndCheckKeyboardLayoutChange(key, 0 /* repeatCount */)) {
             key = onMoveKey(x, y);
         }
         onMoveToNewKey(key, x, y);
@@ -1413,16 +1413,18 @@
         // Don't start key repeat when we are in sliding input mode.
         if (mIsInSlidingKeyInput) return;
         detectAndSendKey(key, key.mX, key.mY, SystemClock.uptimeMillis());
-        mTimerProxy.startKeyRepeatTimer(this, sParams.mKeyRepeatStartTimeout);
+        final int startRepeatCount = 1;
+        mTimerProxy.startKeyRepeatTimer(this, startRepeatCount, sParams.mKeyRepeatStartTimeout);
     }
 
-    public void onKeyRepeat(final int code) {
+    public void onKeyRepeat(final int code, final int repeatCount) {
         final Key key = getKey();
         if (key == null || key.mCode != code) {
             return;
         }
-        mTimerProxy.startKeyRepeatTimer(this, sParams.mKeyRepeatInterval);
-        callListenerOnPressAndCheckKeyboardLayoutChange(key, true /* isRepeatKey */);
+        final int nextRepeatCount = repeatCount + 1;
+        mTimerProxy.startKeyRepeatTimer(this, nextRepeatCount, sParams.mKeyRepeatInterval);
+        callListenerOnPressAndCheckKeyboardLayoutChange(key, repeatCount);
         callListenerOnCodeInput(key, code, mKeyX, mKeyY, SystemClock.uptimeMillis());
     }
 
diff --git a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
index 42c5794..54bc295 100644
--- a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
+++ b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
@@ -57,10 +57,10 @@
         mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
     }
 
-    public void hapticAndAudioFeedback(final int primaryCode,
+    public void performHapticAndAudioFeedback(final int code,
             final View viewToPerformHapticFeedbackOn) {
-        vibrateInternal(viewToPerformHapticFeedbackOn);
-        playKeyClick(primaryCode);
+        performHapticFeedback(viewToPerformHapticFeedbackOn);
+        performAudioFeedback(code);
     }
 
     public boolean hasVibrator() {
@@ -81,14 +81,14 @@
         return mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_NORMAL;
     }
 
-    private void playKeyClick(final int primaryCode) {
+    public void performAudioFeedback(final int code) {
         // if mAudioManager is null, we can't play a sound anyway, so return
         if (mAudioManager == null) {
             return;
         }
         if (mSoundOn) {
             final int sound;
-            switch (primaryCode) {
+            switch (code) {
             case Constants.CODE_DELETE:
                 sound = AudioManager.FX_KEYPRESS_DELETE;
                 break;
@@ -106,7 +106,7 @@
         }
     }
 
-    private void vibrateInternal(final View viewToPerformHapticFeedbackOn) {
+    public void performHapticFeedback(final View viewToPerformHapticFeedbackOn) {
         if (!mSettingsValues.mVibrateOn) {
             return;
         }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index fa9f466..ffe3171 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -122,6 +122,8 @@
 
     private static final int PENDING_IMS_CALLBACK_DURATION = 800;
 
+    private static final int PERIOD_FOR_AUDIO_AND_HAPTIC_FEEDBACK_IN_KEY_REPEAT = 2;
+
     /**
      * The name of the scheme used by the Package Manager to warn of a new package installation,
      * replacement or removal.
@@ -1462,7 +1464,7 @@
             break;
         case Constants.CODE_SHIFT:
             // Note: Calling back to the keyboard on Shift key is handled in
-            // {@link #onPressKey(int,boolean)} and {@link #onReleaseKey(int,boolean)}.
+            // {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}.
             final Keyboard currentKeyboard = switcher.getKeyboard();
             if (null != currentKeyboard && currentKeyboard.mId.isAlphabetKeyboard()) {
                 // TODO: Instead of checking for alphabetic keyboard here, separate keycodes for
@@ -1476,7 +1478,7 @@
             break;
         case Constants.CODE_SWITCH_ALPHA_SYMBOL:
             // Note: Calling back to the keyboard on symbol key is handled in
-            // {@link #onPressKey(int,boolean)} and {@link #onReleaseKey(int,boolean)}.
+            // {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}.
             break;
         case Constants.CODE_SETTINGS:
             onSettingsKeyPressed();
@@ -2698,30 +2700,43 @@
         }
     }
 
-    private void hapticAndAudioFeedback(final int code, final boolean isRepeatKey) {
+    private void hapticAndAudioFeedback(final int code, final int repeatCount) {
         final MainKeyboardView keyboardView = mKeyboardSwitcher.getMainKeyboardView();
         if (keyboardView != null && keyboardView.isInSlidingKeyInput()) {
             // No need to feedback while sliding input.
             return;
         }
-        if (isRepeatKey) {
-            // No need to feedback when repeating key.
-            return;
+        if (repeatCount > 0) {
+            if (code == Constants.CODE_DELETE && !mConnection.canDeleteCharacters()) {
+                // No need to feedback when repeat delete key will have no effect.
+                return;
+            }
+            // TODO: Use event time that the last feedback has been generated instead of relying on
+            // a repeat count to thin out feedback.
+            if (repeatCount % PERIOD_FOR_AUDIO_AND_HAPTIC_FEEDBACK_IN_KEY_REPEAT == 0) {
+                return;
+            }
         }
-        AudioAndHapticFeedbackManager.getInstance().hapticAndAudioFeedback(code, keyboardView);
+        final AudioAndHapticFeedbackManager feedbackManager =
+                AudioAndHapticFeedbackManager.getInstance();
+        if (repeatCount == 0) {
+            // TODO: Reconsider how to perform haptic feedback when repeating key.
+            feedbackManager.performHapticFeedback(keyboardView);
+        }
+        feedbackManager.performAudioFeedback(code);
     }
 
     // Callback of the {@link KeyboardActionListener}. This is called when a key is depressed;
     // release matching call is {@link #onReleaseKey(int,boolean)} below.
     @Override
-    public void onPressKey(final int primaryCode, final boolean isRepeatKey,
+    public void onPressKey(final int primaryCode, final int repeatCount,
             final boolean isSinglePointer) {
         mKeyboardSwitcher.onPressKey(primaryCode, isSinglePointer);
-        hapticAndAudioFeedback(primaryCode, isRepeatKey);
+        hapticAndAudioFeedback(primaryCode, repeatCount);
     }
 
     // Callback of the {@link KeyboardActionListener}. This is called when a key is released;
-    // press matching call is {@link #onPressKey(int,boolean,boolean)} above.
+    // press matching call is {@link #onPressKey(int,int,boolean)} above.
     @Override
     public void onReleaseKey(final int primaryCode, final boolean withSliding) {
         mKeyboardSwitcher.onReleaseKey(primaryCode, withSliding);
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
index c7b063d..7c7064e 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
@@ -39,8 +39,6 @@
 
 public final class BinaryDictIOUtils {
     private static final boolean DBG = false;
-    private static final int MSB24 = 0x800000;
-    private static final int SINT24_MAX = 0x7FFFFF;
     private static final int MAX_JUMPS = 10000;
 
     private BinaryDictIOUtils() {
@@ -921,8 +919,8 @@
             // reached the end of the array.
             final int linkAddressPosition = buffer.position();
             int nextLink = buffer.readUnsignedInt24();
-            if ((nextLink & MSB24) != 0) {
-                nextLink = -(nextLink & SINT24_MAX);
+            if ((nextLink & FormatSpec.MSB24) != 0) {
+                nextLink = -(nextLink & FormatSpec.SINT24_MAX);
             }
             if (nextLink == FormatSpec.NO_FORWARD_LINK_ADDRESS) {
                 /*
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
index 504349a..5710e14 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
@@ -449,10 +449,6 @@
         }
     }
 
-    private static final int UINT8_MAX = 0xFF;
-    private static final int UINT16_MAX = 0xFFFF;
-    private static final int UINT24_MAX = 0xFFFFFF;
-
     /**
      * Compute the size, in bytes, that an address will occupy.
      *
@@ -464,22 +460,18 @@
      * @return the byte size.
      */
     static int getByteSize(final int address) {
-        assert(address <= UINT24_MAX);
+        assert(address <= FormatSpec.UINT24_MAX);
         if (!hasChildrenAddress(address)) {
             return 0;
-        } else if (Math.abs(address) <= UINT8_MAX) {
+        } else if (Math.abs(address) <= FormatSpec.UINT8_MAX) {
             return 1;
-        } else if (Math.abs(address) <= UINT16_MAX) {
+        } else if (Math.abs(address) <= FormatSpec.UINT16_MAX) {
             return 2;
         } else {
             return 3;
         }
     }
 
-    private static final int SINT24_MAX = 0x7FFFFF;
-    private static final int MSB8 = 0x80;
-    private static final int MSB24 = 0x800000;
-
     // End utility methods.
 
     // This method is responsible for finding a nice ordering of the nodes that favors run-time
@@ -814,7 +806,8 @@
             buffer[index] = buffer[index + 1] = buffer[index + 2] = 0;
         } else {
             final int absAddress = Math.abs(address);
-            buffer[index++] = (byte)((address < 0 ? MSB8 : 0) | (0xFF & (absAddress >> 16)));
+            buffer[index++] =
+                    (byte)((address < 0 ? FormatSpec.MSB8 : 0) | (0xFF & (absAddress >> 16)));
             buffer[index++] = (byte)(0xFF & (absAddress >> 8));
             buffer[index++] = (byte)(0xFF & absAddress);
         }
@@ -978,8 +971,8 @@
                 buffer[index] = buffer[index + 1] = buffer[index + 2] = 0;
             } else {
                 final int absAddress = Math.abs(address);
-                assert(absAddress <= SINT24_MAX);
-                buffer[index] = (byte)((address < 0 ? MSB8 : 0)
+                assert(absAddress <= FormatSpec.SINT24_MAX);
+                buffer[index] = (byte)((address < 0 ? FormatSpec.MSB8 : 0)
                         | ((absAddress >> 16) & 0xFF));
                 buffer[index + 1] = (byte)((absAddress >> 8) & 0xFF);
                 buffer[index + 2] = (byte)(absAddress & 0xFF);
@@ -1300,8 +1293,8 @@
         if (options.mSupportsDynamicUpdate) {
             final int address = buffer.readUnsignedInt24();
             if (address == 0) return FormatSpec.NO_CHILDREN_ADDRESS;
-            if ((address & MSB24) != 0) {
-                return -(address & SINT24_MAX);
+            if ((address & FormatSpec.MSB24) != 0) {
+                return -(address & FormatSpec.SINT24_MAX);
             } else {
                 return address;
             }
@@ -1324,8 +1317,8 @@
             final FormatOptions formatOptions) {
         if (supportsDynamicUpdate(formatOptions)) {
             final int parentAddress = buffer.readUnsignedInt24();
-            final int sign = ((parentAddress & MSB24) != 0) ? -1 : 1;
-            return sign * (parentAddress & SINT24_MAX);
+            final int sign = ((parentAddress & FormatSpec.MSB24) != 0) ? -1 : 1;
+            return sign * (parentAddress & FormatSpec.SINT24_MAX);
         } else {
             return FormatSpec.NO_PARENT_ADDRESS;
         }
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictReader.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictReader.java
index 57a5832..a4a7ce4 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictReader.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictReader.java
@@ -24,6 +24,7 @@
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.RandomAccessFile;
 import java.nio.ByteBuffer;
 import java.nio.channels.FileChannel;
 
@@ -82,6 +83,32 @@
         }
     }
 
+    /**
+     * Creates FusionDictionaryBuffer from a RandomAccessFile.
+     */
+    @UsedForTesting
+    public static final class FusionDictionaryBufferFromWritableByteBufferFactory
+            implements FusionDictionaryBufferFactory {
+        @Override
+        public FusionDictionaryBufferInterface getFusionDictionaryBuffer(final File file)
+                throws FileNotFoundException, IOException {
+            RandomAccessFile raFile = null;
+            ByteBuffer buffer = null;
+            try {
+                raFile = new RandomAccessFile(file, "rw");
+                buffer = raFile.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, file.length());
+            } finally {
+                if (raFile != null) {
+                    raFile.close();
+                }
+            }
+            if (buffer != null) {
+                return new BinaryDictInputOutput.ByteBufferWrapper(buffer);
+            }
+            return null;
+        }
+    }
+
     private final File mDictionaryBinaryFile;
     private FusionDictionaryBufferInterface mFusionDictionaryBuffer;
 
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index 2bb5d8b..9af66ed 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -263,6 +263,13 @@
     static final int NOT_VALID_WORD = -99;
     static final int SIGNED_CHILDREN_ADDRESS_SIZE = 3;
 
+    static final int UINT8_MAX = 0xFF;
+    static final int UINT16_MAX = 0xFFFF;
+    static final int UINT24_MAX = 0xFFFFFF;
+    static final int SINT24_MAX = 0x7FFFFF;
+    static final int MSB8 = 0x80;
+    static final int MSB24 = 0x800000;
+
     /**
      * Options about file format.
      */
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index e97069d..acd4745 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -210,7 +210,8 @@
                 final int indexInMoreSuggestions = index + SUGGESTION_CODE_BASE;
                 final Key key = new Key(
                         params, word, info, KeyboardIconsSet.ICON_UNDEFINED, indexInMoreSuggestions,
-                        null, x, y, width, params.mDefaultRowHeight, 0);
+                        null /* outputText */, x, y, width, params.mDefaultRowHeight,
+                        0 /* labelFlags */, Key.BACKGROUND_TYPE_NORMAL);
                 params.markAsEdgeKey(key, index);
                 params.onAddKey(key);
                 final int columnNumber = params.getColumnNumber(index);
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index 2644f3c..badc942 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -198,7 +198,7 @@
 
     @Override
     public boolean onLongClick(final View view) {
-        AudioAndHapticFeedbackManager.getInstance().hapticAndAudioFeedback(
+        AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(
                 Constants.NOT_A_CODE, this);
         return showMoreSuggestions();
     }
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictReaderTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictReaderTests.java
index 5f6950a..a46e583 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictReaderTests.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictReaderTests.java
@@ -18,8 +18,12 @@
 
 import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.FusionDictionaryBufferInterface;
 import com.android.inputmethod.latin.makedict.BinaryDictReader.FusionDictionaryBufferFactory;
-import com.android.inputmethod.latin.makedict.BinaryDictReader.FusionDictionaryBufferFromByteArrayFactory;
-import com.android.inputmethod.latin.makedict.BinaryDictReader.FusionDictionaryBufferFromByteBufferFactory;
+import com.android.inputmethod.latin.makedict.BinaryDictReader.
+        FusionDictionaryBufferFromByteArrayFactory;
+import com.android.inputmethod.latin.makedict.BinaryDictReader.
+        FusionDictionaryBufferFromByteBufferFactory;
+import com.android.inputmethod.latin.makedict.BinaryDictReader.
+        FusionDictionaryBufferFromWritableByteBufferFactory;
 
 import android.test.AndroidTestCase;
 import android.util.Log;
@@ -94,6 +98,11 @@
                 new FusionDictionaryBufferFromByteArrayFactory());
     }
 
+    public void testOpenBufferWithWritableByteBuffer() {
+        runTestOpenBuffer("testOpenBufferWithWritableByteBuffer",
+                new FusionDictionaryBufferFromWritableByteBufferFactory());
+    }
+
     @SuppressWarnings("null")
     public void runTestGetBuffer(final String testName,
             final FusionDictionaryBufferFactory factory) {
@@ -135,4 +144,9 @@
         runTestGetBuffer("testGetBufferWithByteArray",
                 new FusionDictionaryBufferFromByteArrayFactory());
     }
+
+    public void testGetBufferWithWritableByteBuffer() {
+        runTestGetBuffer("testGetBufferWithWritableByteBuffer",
+                new FusionDictionaryBufferFromWritableByteBufferFactory());
+    }
 }