Merge "Add an initial test command to dicttool."
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index 80bf704..c318317 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -439,7 +439,6 @@
     </declare-styleable>
 
     <declare-styleable name="SeekBarDialogPreference">
-        <attr name="valueFormatText" format="reference" />
         <attr name="maxValue" format="integer" />
         <attr name="minValue" format="integer" />
         <attr name="stepValue" format="integer" />
diff --git a/java/res/values/keypress-vibration-durations.xml b/java/res/values/keypress-vibration-durations.xml
index e8d9225..e2f2b56 100644
--- a/java/res/values/keypress-vibration-durations.xml
+++ b/java/res/values/keypress-vibration-durations.xml
@@ -56,7 +56,7 @@
         <item>MODEL=XT907:MANUFACTURER=motorola,30</item>
         <!-- Sony Xperia Z -->
         <item>MODEL=C6603:MANUFACTURER=Sony,35</item>
-        <!-- Default value for unknown device -->
-        <item>,20</item>
+        <!-- Default value for unknown device. The negative value means system default. -->
+        <item>,-1</item>
     </string-array>
 </resources>
diff --git a/java/res/values/keypress-volumes.xml b/java/res/values/keypress-volumes.xml
index a096c34..d359055 100644
--- a/java/res/values/keypress-volumes.xml
+++ b/java/res/values/keypress-volumes.xml
@@ -26,7 +26,7 @@
         <item>HARDWARE=grouper,0.3f</item>
         <item>HARDWARE=mako,0.3f</item>
         <item>HARDWARE=manta,0.2f</item>
-        <!-- Default value for unknown device -->
-        <item>,0.2f</item>
+        <!-- Default value for unknown device. The negative value means system default. -->
+        <item>,-1.0f</item>
     </string-array>
 </resources>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 85abb08..cdc2c3a 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -79,6 +79,8 @@
 
     <!-- Units abbreviation for the duration (milliseconds) [CHAR LIMIT=10] -->
     <string name="abbreviation_unit_milliseconds"><xliff:g id="milliseconds">%s</xliff:g>ms</string>
+    <!-- The text that represents the current settings value is the system default [CHAR LIMIT=20] -->
+    <string name="settings_system_default">System default</string>
 
     <!-- Option name for enabling or disabling the use of names of people in Contacts for suggestion and correction [CHAR LIMIT=25] -->
     <string name="use_contacts_dict">Suggest Contact names</string>
diff --git a/java/res/xml/prefs.xml b/java/res/xml/prefs.xml
index 51e3420..d77adce 100644
--- a/java/res/xml/prefs.xml
+++ b/java/res/xml/prefs.xml
@@ -170,14 +170,12 @@
             <com.android.inputmethod.latin.SeekBarDialogPreference
                 android:key="pref_key_longpress_timeout"
                 android:title="@string/prefs_key_longpress_timeout_settings"
-                latin:valueFormatText="@string/abbreviation_unit_milliseconds"
                 latin:minValue="@integer/config_min_longpress_timeout"
                 latin:maxValue="@integer/config_max_longpress_timeout"
                 latin:stepValue="@integer/config_longpress_timeout_step" />
             <com.android.inputmethod.latin.SeekBarDialogPreference
                 android:key="pref_vibration_duration_settings"
                 android:title="@string/prefs_keypress_vibration_duration_settings"
-                latin:valueFormatText="@string/abbreviation_unit_milliseconds"
                 latin:maxValue="@integer/config_max_vibration_duration" />
             <com.android.inputmethod.latin.SeekBarDialogPreference
                 android:key="pref_keypress_sound_volume"
diff --git a/java/res/xml/rowkeys_hebrew1.xml b/java/res/xml/rowkeys_hebrew1.xml
index 396da78..81a00e3 100644
--- a/java/res/xml/rowkeys_hebrew1.xml
+++ b/java/res/xml/rowkeys_hebrew1.xml
@@ -21,28 +21,70 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
+    <switch>
+        <case
+            latin:mode="email|url"
+        >
+            <Key
+                latin:keyLabel="-"
+                latin:keyHintLabel="1"
+                latin:additionalMoreKeys="1" />
+            <Key
+                latin:keyLabel="_"
+                latin:keyHintLabel="2"
+                latin:additionalMoreKeys="2" />
+        </case>
+        <default>
+            <Key
+                latin:keyLabel="\'"
+                latin:keyHintLabel="1"
+                latin:additionalMoreKeys="1"
+                latin:moreKeys="&quot;" />
+            <Key
+                latin:keyLabel="-"
+                latin:keyHintLabel="2"
+                latin:additionalMoreKeys="2"
+                latin:moreKeys="_" />
+        </default>
+    </switch>
     <!-- U+05E7: "ק" HEBREW LETTER QOF -->
     <Key
-        latin:keyLabel="&#x05E7;" />
+        latin:keyLabel="&#x05E7;"
+        latin:keyHintLabel="3"
+        latin:additionalMoreKeys="3" />
     <!-- U+05E8: "ר" HEBREW LETTER RESH -->
     <Key
-        latin:keyLabel="&#x05E8;" />
+        latin:keyLabel="&#x05E8;"
+        latin:keyHintLabel="4"
+        latin:additionalMoreKeys="4" />
     <!-- U+05D0: "א" HEBREW LETTER ALEF -->
     <Key
-        latin:keyLabel="&#x05D0;" />
+        latin:keyLabel="&#x05D0;"
+        latin:keyHintLabel="5"
+        latin:additionalMoreKeys="5" />
     <!-- U+05D8: "ט" HEBREW LETTER TET -->
     <Key
-        latin:keyLabel="&#x05D8;" />
+        latin:keyLabel="&#x05D8;"
+        latin:keyHintLabel="6"
+        latin:additionalMoreKeys="6" />
     <!-- U+05D5: "ו" HEBREW LETTER VAV -->
     <Key
-        latin:keyLabel="&#x05D5;" />
+        latin:keyLabel="&#x05D5;"
+        latin:keyHintLabel="7"
+        latin:additionalMoreKeys="7" />
     <!-- U+05DF: "ן" HEBREW LETTER FINAL NUN -->
     <Key
-        latin:keyLabel="&#x05DF;" />
+        latin:keyLabel="&#x05DF;"
+        latin:keyHintLabel="8"
+        latin:additionalMoreKeys="8" />
     <!-- U+05DD: "ם" HEBREW LETTER FINAL MEM -->
     <Key
-        latin:keyLabel="&#x05DD;" />
+        latin:keyLabel="&#x05DD;"
+        latin:keyHintLabel="9"
+        latin:additionalMoreKeys="9" />
     <!-- U+05E4: "פ" HEBREW LETTER PE -->
     <Key
-        latin:keyLabel="&#x05E4;" />
+        latin:keyLabel="&#x05E4;"
+        latin:keyHintLabel="0"
+        latin:additionalMoreKeys="0" />
 </merge>
diff --git a/java/res/xml/rows_hebrew.xml b/java/res/xml/rows_hebrew.xml
index 2d513df..f12380a 100644
--- a/java/res/xml/rows_hebrew.xml
+++ b/java/res/xml/rows_hebrew.xml
@@ -27,12 +27,7 @@
         latin:keyWidth="10%p"
     >
         <include
-            latin:keyboardLayout="@xml/rowkeys_hebrew1"
-            latin:keyXPos="5%p" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight"
-            latin:visualInsetsLeft="1%p" />
+            latin:keyboardLayout="@xml/rowkeys_hebrew1" />
     </Row>
     <Row
         latin:keyWidth="10%p"
@@ -44,8 +39,10 @@
         latin:keyWidth="10%p"
     >
         <include
-            latin:keyboardLayout="@xml/rowkeys_hebrew3"
-            latin:keyXPos="5%p" />
+            latin:keyboardLayout="@xml/rowkeys_hebrew3" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillRight" />
     </Row>
     <include
         latin:keyboardLayout="@xml/row_qwerty4" />
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index 57d3fed..0e86b05 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -240,28 +240,121 @@
 
     private void computeNearestNeighbors() {
         final int defaultWidth = mMostCommonKeyWidth;
-        final Key[] keys = mKeys;
-        final int thresholdBase = (int) (defaultWidth * SEARCH_DISTANCE);
-        final int threshold = thresholdBase * thresholdBase;
+        final int keyCount = mKeys.length;
+        final int gridSize = mGridNeighbors.length;
+        final int threshold = (int) (defaultWidth * SEARCH_DISTANCE);
+        final int thresholdSquared = threshold * threshold;
         // Round-up so we don't have any pixels outside the grid
-        final Key[] neighborKeys = new Key[keys.length];
-        final int gridWidth = mGridWidth * mCellWidth;
-        final int gridHeight = mGridHeight * mCellHeight;
-        for (int x = 0; x < gridWidth; x += mCellWidth) {
-            for (int y = 0; y < gridHeight; y += mCellHeight) {
-                final int centerX = x + mCellWidth / 2;
-                final int centerY = y + mCellHeight / 2;
-                int count = 0;
-                for (final Key key : keys) {
-                    if (key.isSpacer()) continue;
-                    if (key.squaredDistanceToEdge(centerX, centerY) < threshold) {
-                        neighborKeys[count++] = key;
+        final int fullGridWidth = mGridWidth * mCellWidth;
+        final int fullGridHeight = mGridHeight * mCellHeight;
+
+        // For large layouts, 'neighborsFlatBuffer' is about 80k of memory: gridSize is usually 512,
+        // keycount is about 40 and a pointer to a Key is 4 bytes. This contains, for each cell,
+        // enough space for as many keys as there are on the keyboard. Hence, every
+        // keycount'th element is the start of a new cell, and each of these virtual subarrays
+        // start empty with keycount spaces available. This fills up gradually in the loop below.
+        // Since in the practice each cell does not have a lot of neighbors, most of this space is
+        // actually just empty padding in this fixed-size buffer.
+        final Key[] neighborsFlatBuffer = new Key[gridSize * keyCount];
+        final int[] neighborCountPerCell = new int[gridSize];
+        final int halfCellWidth = mCellWidth / 2;
+        final int halfCellHeight = mCellHeight / 2;
+        for (final Key key : mKeys) {
+            if (key.isSpacer()) continue;
+
+/* HOW WE PRE-SELECT THE CELLS (iterate over only the relevant cells, instead of all of them)
+
+  We want to compute the distance for keys that are in the cells that are close enough to the
+  key border, as this method is performance-critical. These keys are represented with 'star'
+  background on the diagram below. Let's consider the Y case first.
+
+  We want to select the cells which center falls between the top of the key minus the threshold,
+  and the bottom of the key plus the threshold.
+  topPixelWithinThreshold is key.mY - threshold, and bottomPixelWithinThreshold is
+  key.mY + key.mHeight + threshold.
+
+  Then we need to compute the center of the top row that we need to evaluate, as we'll iterate
+  from there.
+
+(0,0)----> x
+| .-------------------------------------------.
+| |   |   |   |   |   |   |   |   |   |   |   |
+| |---+---+---+---+---+---+---+---+---+---+---|   .- top of top cell (aligned on the grid)
+| |   |   |   |   |   |   |   |   |   |   |   |   |
+| |-----------+---+---+---+---+---+---+---+---|---'                          v
+| |   |   |   |***|***|*_________________________ topPixelWithinThreshold    | yDeltaToGrid
+| |---+---+---+-----^-+-|-+---+---+---+---+---|                              ^
+| |   |   |   |***|*|*|*|*|***|***|   |   |   |           ______________________________________
+v |---+---+--threshold--|-+---+---+---+---+---|          |
+  |   |   |   |***|*|*|*|*|***|***|   |   |   |          | Starting from key.mY, we substract
+y |---+---+---+---+-v-+-|-+---+---+---+---+---|          | thresholdBase and get the top pixel
+  |   |   |   |***|**########------------------- key.mY  | within the threshold. We align that on
+  |---+---+---+---+--#+---+-#-+---+---+---+---|          | the grid by computing the delta to the
+  |   |   |   |***|**#|***|*#*|***|   |   |   |          | grid, and get the top of the top cell.
+  |---+---+---+---+--#+---+-#-+---+---+---+---|          |
+  |   |   |   |***|**########*|***|   |   |   |          | Adding half the cell height to the top
+  |---+---+---+---+---+-|-+---+---+---+---+---|          | of the top cell, we get the middle of
+  |   |   |   |***|***|*|*|***|***|   |   |   |          | the top cell (yMiddleOfTopCell).
+  |---+---+---+---+---+-|-+---+---+---+---+---|          |
+  |   |   |   |***|***|*|*|***|***|   |   |   |          |
+  |---+---+---+---+---+-|________________________ yEnd   | Since we only want to add the key to
+  |   |   |   |   |   |   | (bottomPixelWithinThreshold) | the proximity if it's close enough to
+  |---+---+---+---+---+---+---+---+---+---+---|          | the center of the cell, we only need
+  |   |   |   |   |   |   |   |   |   |   |   |          | to compute for these cells where
+  '---'---'---'---'---'---'---'---'---'---'---'          | topPixelWithinThreshold is above the
+                                        (positive x,y)   | center of the cell. This is the case
+                                                         | when yDeltaToGrid is less than half
+  [Zoomed in diagram]                                    | the height of the cell.
+  +-------+-------+-------+-------+-------+              |
+  |       |       |       |       |       |              | On the zoomed in diagram, on the right
+  |       |       |       |       |       |              | the topPixelWithinThreshold (represented
+  |       |       |       |       |       |      top of  | with an = sign) is below and we can skip
+  +-------+-------+-------+--v----+-------+ .. top cell  | this cell, while on the left it's above
+  |       | = topPixelWT  |  |  yDeltaToGrid             | and we need to compute for this cell.
+  |..yStart.|.....|.......|..|....|.......|... y middle  | Thus, if yDeltaToGrid is more than half
+  |   (left)|     |       |  ^ =  |       | of top cell  | the height of the cell, we start the
+  +-------+-|-----+-------+----|--+-------+              | iteration one cell below the top cell,
+  |       | |     |       |    |  |       |              | else we start it on the top cell. This
+  |.......|.|.....|.......|....|..|.....yStart (right)   | is stored in yStart.
+
+  Since we only want to go up to bottomPixelWithinThreshold, and we only iterate on the center
+  of the keys, we can stop as soon as the y value exceeds bottomPixelThreshold, so we don't
+  have to align this on the center of the key. Hence, we don't need a separate value for
+  bottomPixelWithinThreshold and call this yEnd right away.
+*/
+            final int topPixelWithinThreshold = key.mY - threshold;
+            final int yDeltaToGrid = topPixelWithinThreshold % mCellHeight;
+            final int yMiddleOfTopCell = topPixelWithinThreshold - yDeltaToGrid + halfCellHeight;
+            final int yStart = Math.max(halfCellHeight,
+                    yMiddleOfTopCell + (yDeltaToGrid <= halfCellHeight ? 0 : mCellHeight));
+            final int yEnd = Math.min(fullGridHeight, key.mY + key.mHeight + threshold);
+
+            final int leftPixelWithinThreshold = key.mX - threshold;
+            final int xDeltaToGrid = leftPixelWithinThreshold % mCellWidth;
+            final int xMiddleOfLeftCell = leftPixelWithinThreshold - xDeltaToGrid + halfCellWidth;
+            final int xStart = Math.max(halfCellWidth,
+                    xMiddleOfLeftCell + (xDeltaToGrid <= halfCellWidth ? 0 : mCellWidth));
+            final int xEnd = Math.min(fullGridWidth, key.mX + key.mWidth + threshold);
+
+            int baseIndexOfCurrentRow = (yStart / mCellHeight) * mGridWidth + (xStart / mCellWidth);
+            for (int centerY = yStart; centerY <= yEnd; centerY += mCellHeight) {
+                int index = baseIndexOfCurrentRow;
+                for (int centerX = xStart; centerX <= xEnd; centerX += mCellWidth) {
+                    if (key.squaredDistanceToEdge(centerX, centerY) < thresholdSquared) {
+                        neighborsFlatBuffer[index * keyCount + neighborCountPerCell[index]] = key;
+                        ++neighborCountPerCell[index];
                     }
+                    ++index;
                 }
-                mGridNeighbors[(y / mCellHeight) * mGridWidth + (x / mCellWidth)] =
-                        Arrays.copyOfRange(neighborKeys, 0, count);
+                baseIndexOfCurrentRow += mGridWidth;
             }
         }
+
+        for (int i = 0; i < gridSize; ++i) {
+            final int base = i * keyCount;
+            mGridNeighbors[i] =
+                    Arrays.copyOfRange(neighborsFlatBuffer, base, base + neighborCountPerCell[i]);
+        }
     }
 
     public void fillArrayWithNearestKeyCodes(final int x, final int y, final int primaryKeyCode,
diff --git a/java/src/com/android/inputmethod/latin/SeekBarDialogPreference.java b/java/src/com/android/inputmethod/latin/SeekBarDialogPreference.java
index 3ea9fed..44065ff 100644
--- a/java/src/com/android/inputmethod/latin/SeekBarDialogPreference.java
+++ b/java/src/com/android/inputmethod/latin/SeekBarDialogPreference.java
@@ -33,10 +33,10 @@
         public int readDefaultValue(final String key);
         public void writeValue(final int value, final String key);
         public void writeDefaultValue(final String key);
+        public String getValueText(final int value);
         public void feedbackValue(final int value);
     }
 
-    private final int mValueFormatResId;
     private final int mMaxValue;
     private final int mMinValue;
     private final int mStepValue;
@@ -50,7 +50,6 @@
         super(context, attrs);
         final TypedArray a = context.obtainStyledAttributes(
                 attrs, R.styleable.SeekBarDialogPreference, 0, 0);
-        mValueFormatResId = a.getResourceId(R.styleable.SeekBarDialogPreference_valueFormatText, 0);
         mMaxValue = a.getInt(R.styleable.SeekBarDialogPreference_maxValue, 0);
         mMinValue = a.getInt(R.styleable.SeekBarDialogPreference_minValue, 0);
         mStepValue = a.getInt(R.styleable.SeekBarDialogPreference_stepValue, 0);
@@ -60,15 +59,8 @@
 
     public void setInterface(final ValueProxy proxy) {
         mValueProxy = proxy;
-        setSummary(getValueText(clipValue(proxy.readValue(getKey()))));
-    }
-
-    private String getValueText(final int value) {
-        if (mValueFormatResId == 0) {
-            return Integer.toString(value);
-        } else {
-            return getContext().getString(mValueFormatResId, value);
-        }
+        final int value = mValueProxy.readValue(getKey());
+        setSummary(mValueProxy.getValueText(value));
     }
 
     @Override
@@ -101,16 +93,11 @@
         return clipValue(getValueFromProgress(progress));
     }
 
-    private void setValue(final int value, final boolean fromUser) {
-        mValueView.setText(getValueText(value));
-        if (!fromUser) {
-            mSeekBar.setProgress(getProgressFromValue(value));
-        }
-    }
-
     @Override
     protected void onBindDialogView(final View view) {
-        setValue(clipValue(mValueProxy.readValue(getKey())), false /* fromUser */);
+        final int value = mValueProxy.readValue(getKey());
+        mValueView.setText(mValueProxy.getValueText(value));
+        mSeekBar.setProgress(getProgressFromValue(clipValue(value)));
     }
 
     @Override
@@ -125,13 +112,15 @@
         super.onClick(dialog, which);
         final String key = getKey();
         if (which == DialogInterface.BUTTON_NEUTRAL) {
-            setValue(clipValue(mValueProxy.readDefaultValue(key)), false /* fromUser */);
+            final int value = mValueProxy.readDefaultValue(key);
+            setSummary(mValueProxy.getValueText(value));
             mValueProxy.writeDefaultValue(key);
             return;
         }
         if (which == DialogInterface.BUTTON_POSITIVE) {
-            setSummary(mValueView.getText());
-            mValueProxy.writeValue(getClippedValueFromProgress(mSeekBar.getProgress()), key);
+            final int value = getClippedValueFromProgress(mSeekBar.getProgress());
+            setSummary(mValueProxy.getValueText(value));
+            mValueProxy.writeValue(value, key);
             return;
         }
     }
@@ -139,7 +128,11 @@
     @Override
     public void onProgressChanged(final SeekBar seekBar, final int progress,
             final boolean fromUser) {
-        setValue(getClippedValueFromProgress(progress), fromUser);
+        final int value = getClippedValueFromProgress(progress);
+        mValueView.setText(mValueProxy.getValueText(value));
+        if (!fromUser) {
+            mSeekBar.setProgress(getProgressFromValue(value));
+        }
     }
 
     @Override
diff --git a/java/src/com/android/inputmethod/latin/SettingsFragment.java b/java/src/com/android/inputmethod/latin/SettingsFragment.java
index 1fad765..8017ce1 100644
--- a/java/src/com/android/inputmethod/latin/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/SettingsFragment.java
@@ -364,6 +364,14 @@
             public void feedbackValue(final int value) {
                 AudioAndHapticFeedbackManager.getInstance().vibrate(value);
             }
+
+            @Override
+            public String getValueText(final int value) {
+                if (value < 0) {
+                    return res.getString(R.string.settings_system_default);
+                }
+                return res.getString(R.string.abbreviation_unit_milliseconds, value);
+            }
         });
     }
 
@@ -396,6 +404,11 @@
             }
 
             @Override
+            public String getValueText(final int value) {
+                return res.getString(R.string.abbreviation_unit_milliseconds, value);
+            }
+
+            @Override
             public void feedbackValue(final int value) {}
         });
     }
@@ -439,6 +452,14 @@
             }
 
             @Override
+            public String getValueText(final int value) {
+                if (value < 0) {
+                    return res.getString(R.string.settings_system_default);
+                }
+                return Integer.toString(value);
+            }
+
+            @Override
             public void feedbackValue(final int value) {
                 am.playSoundEffect(
                         AudioManager.FX_KEYPRESS_STANDARD, getValueFromPercentage(value));
diff --git a/native/jni/src/suggest/core/suggest.cpp b/native/jni/src/suggest/core/suggest.cpp
index 6c4a6c1..a8f16c8 100644
--- a/native/jni/src/suggest/core/suggest.cpp
+++ b/native/jni/src/suggest/core/suggest.cpp
@@ -530,6 +530,12 @@
             CT_NEW_WORD_SPACE_SUBSTITUTION : CT_NEW_WORD_SPACE_OMITTION;
     Weighting::addCostAndForwardInputIndex(WEIGHTING, correctionType, traverseSession, dicNode,
             &newDicNode, traverseSession->getMultiBigramMap());
-    traverseSession->getDicTraverseCache()->copyPushNextActive(&newDicNode);
+    if (newDicNode.getCompoundDistance() < static_cast<float>(MAX_VALUE_FOR_WEIGHTING)) {
+        // newDicNode is worth continuing to traverse.
+        // CAVEAT: This pruning is important for speed. Remove this when we can afford not to prune
+        // here because here is not the right place to do pruning. Pruning should take place only
+        // in DicNodePriorityQueue.
+        traverseSession->getDicTraverseCache()->copyPushNextActive(&newDicNode);
+    }
 }
 } // namespace latinime