Merge "Fix additional proximity in the native code"
diff --git a/java/res/values-fa/donottranslate-more-keys.xml b/java/res/values-fa/donottranslate-more-keys.xml
new file mode 100644
index 0000000..3e13c79
--- /dev/null
+++ b/java/res/values-fa/donottranslate-more-keys.xml
@@ -0,0 +1,160 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, 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.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- U+061F: "؟" ARABIC QUESTION MARK
+         U+060C: "،" ARABIC COMMA
+         U+061B: "؛" ARABIC SEMICOLON -->
+    <!-- U+0650: "ِ" ARABIC KASRA
+         U+064E: "َ" ARABIC FATHA
+         U+064D: "ٍ" ARABIC KASRATAN
+         U+064B: "ً" ARABIC FATHATAN
+         U+0656: "ٖ" ARABIC SUBSCRIPT ALEF
+         U+0670: "ٰ" ARABIC LETTER SUPERSCRIPT ALEF
+         U+0655: "ٕ" ARABIC HAMZA BELOW
+         U+0654: "ٔ" ARABIC HAMZA ABOVE -->
+    <!-- U+064F: "ُ" ARABIC DAMMA
+         U+064C: "ٌ" ARABIC DAMMATAN
+         U+0651: "ّ" ARABIC SHADDA
+         U+0652: "ْ" ARABIC SUKUN
+         U+0653: "ٓ" ARABIC MADDAH ABOVE
+         U+0640: "ـ" ARABIC TATWEEL -->
+    <!-- In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label. -->
+    <string name="more_keys_for_punctuation">"!fixedColumnOrder!8,\",\',-,:,!,&#x061F;,&#x060C;,&#x061B;,&#x0650;,&#x064E;,&#x064D;,&#x064B;,&#x0656;,&#x0670;,&#x0655;,&#x0654;,&#x064F;,&#x064C;,&#x0651;,&#x0652;,&#x0653;,&#x0640;&#x0640;&#x0640;|&#x0640;,/"</string>
+    <string name="keyhintlabel_for_punctuation">&#x064B;</string>
+    <!-- U+0661: "١" ARABIC-INDIC DIGIT ONE -->
+    <string name="keylabel_for_symbols_1">&#x0661;</string>
+    <!-- U+0662: "٢" ARABIC-INDIC DIGIT TWO -->
+    <string name="keylabel_for_symbols_2">&#x0662;</string>
+    <!-- U+0663: "٣" ARABIC-INDIC DIGIT THREE -->
+    <string name="keylabel_for_symbols_3">&#x0663;</string>
+    <!-- U+0664: "٤" ARABIC-INDIC DIGIT FOUR -->
+    <string name="keylabel_for_symbols_4">&#x0664;</string>
+    <!-- U+0665: "٥" ARABIC-INDIC DIGIT FIVE -->
+    <string name="keylabel_for_symbols_5">&#x0665;</string>
+    <!-- U+0666: "٦" ARABIC-INDIC DIGIT SIX -->
+    <string name="keylabel_for_symbols_6">&#x0666;</string>
+    <!-- U+0667: "٧" ARABIC-INDIC DIGIT SEVEN -->
+    <string name="keylabel_for_symbols_7">&#x0667;</string>
+    <!-- U+0668: "٨" ARABIC-INDIC DIGIT EIGHT -->
+    <string name="keylabel_for_symbols_8">&#x0668;</string>
+    <!-- U+0669: "٩" ARABIC-INDIC DIGIT NINE -->
+    <string name="keylabel_for_symbols_9">&#x0669;</string>
+    <!-- U+0660: "٠" ARABIC-INDIC DIGIT ZERO -->
+    <string name="keylabel_for_symbols_0">&#x0660;</string>
+    <string name="additional_more_keys_for_symbols_1">1</string>
+    <string name="additional_more_keys_for_symbols_2">2</string>
+    <string name="additional_more_keys_for_symbols_3">3</string>
+    <string name="additional_more_keys_for_symbols_4">4</string>
+    <string name="additional_more_keys_for_symbols_5">5</string>
+    <string name="additional_more_keys_for_symbols_6">6</string>
+    <string name="additional_more_keys_for_symbols_7">7</string>
+    <string name="additional_more_keys_for_symbols_8">8</string>
+    <string name="additional_more_keys_for_symbols_9">9</string>
+    <!-- U+066B: "٫" ARABIC DECIMAL SEPARATOR
+         U+066C: "٬" ARABIC THOUSANDS SEPARATOR -->
+    <string name="additional_more_keys_for_symbols_0">0,&#x066B;,&#x066C;</string>
+    <!-- U+060C: "،" ARABIC COMMA -->
+    <string name="keylabel_for_comma">&#x060C;</string>
+    <string name="more_keys_for_comma">"\\,"</string>
+    <string name="keylabel_for_symbols_question">&#x061F;</string>
+    <string name="keylabel_for_symbols_semicolon">&#x061B;</string>
+    <!-- U+066A: "٪" ARABIC PERCENT SIGN -->
+    <string name="keylabel_for_symbols_percent">&#x066A;</string>
+    <string name="more_keys_for_symbols_question">\?</string>
+    <string name="more_keys_for_symbols_semicolon">;</string>
+    <!-- U+2030: "‰" PER MILLE SIGN -->
+    <string name="more_keys_for_symbols_percent">%,&#x2030;</string>
+    <!-- U+060C: "،" ARABIC COMMA
+         U+061B: "؛" ARABIC SEMICOLON
+         U+061F: "؟" ARABIC QUESTION MARK -->
+    <string name="keylabel_for_apostrophe">&#x060C;</string>
+    <string name="keylabel_for_dash">"."</string>
+    <string name="keyhintlabel_for_apostrophe">&#x061F;</string>
+    <string name="keyhintlabel_for_dash">&#x064B;</string>
+    <string name="more_keys_for_apostrophe">"&#x061F;,&#x061B;,!,:,-,/,\',\""</string>
+    <!-- U+0651: "ّ" ARABIC SHADDA
+         U+0652: "ْ" ARABIC SUKUN
+         U+064C: "ٌ" ARABIC DAMMATAN
+         U+0653: "ٓ" ARABIC MADDAH ABOVE
+         U+064F: "ُ" ARABIC DAMMA -->
+    <!-- U+0650: "ِ" ARABIC KASRA
+         U+064E: "َ" ARABIC FATHA
+         U+064B: "ً" ARABIC FATHATAN
+         U+0640: "ـ" ARABIC TATWEEL
+         U+064D: "ٍ" ARABIC KASRATAN -->
+    <!-- U+0670: "ٰ" ARABIC LETTER SUPERSCRIPT ALEF
+         U+0656: "ٖ" ARABIC SUBSCRIPT ALEF
+         U+0654: "ٔ" ARABIC HAMZA ABOVE
+         U+0655: "ٕ" ARABIC HAMZA BELOW -->
+    <!-- In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label. -->
+    <string name="more_keys_for_dash">"&#x0651;,&#x0652;,&#x064C;,&#x0653;,&#x064F;,&#x0650;,&#x064E;,&#x064B;,&#x0640;&#x0640;&#x0640;|&#x0640;,&#x064D;,&#x0654;,&#x0656;,&#x0655;,&#x0670;"</string>
+    <!-- U+266A: "♪" EIGHTH NOTE -->
+    <string name="more_keys_for_bullet">&#x266A;</string>
+    <!-- U+2605: "★" BLACK STAR
+         U+066D: "٭" ARABIC FIVE POINTED STAR -->
+    <string name="more_keys_for_star">&#x2605;,&#x066D;</string>
+    <!-- The all letters need to be mirrored are found at
+         http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt -->
+    <!-- U+0029: ")" RIGHT PARENTHESIS -->
+    <integer name="keycode_for_left_parenthesis">0x0029</integer>
+    <!-- U+0028: "(" LEFT PARENTHESIS -->
+    <integer name="keycode_for_right_parenthesis">0x0028</integer>
+    <!-- U+FD3E: "﴾" ORNATE LEFT PARENTHESIS
+         U+FD3F: "﴿" ORNATE RIGHT PARENTHESIS -->
+    <!-- TODO: DroidSansArabic lacks the glyph of U+FD3E ORNATE LEFT PARENTHESIS -->
+    <!-- TODO: DroidSansArabic lacks the glyph of U+FD3F ORNATE RIGHT PARENTHESIS -->
+    <string name="more_keys_for_left_parenthesis">!fixedColumnOrder!4,&#xFD3E;|&#xFD3F;,&lt;|&gt;,{|},[|]</string>
+    <string name="more_keys_for_right_parenthesis">!fixedColumnOrder!4,&#xFD3F;|&#xFD3E;,&gt;|&lt;,}|{,]|[</string>
+    <!-- U+003E: ">" GREATER-THAN SIGN -->
+    <integer name="keycode_for_less_than">0x003E</integer>
+    <!-- U+003C: "<" LESS-THAN SIGN -->
+    <integer name="keycode_for_greater_than">0x003C</integer>
+    <!-- U+2264: "≤" LESS-THAN OR EQUAL TO
+         U+2265: "≥" GREATER-THAN EQUAL TO
+         U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+         U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+         U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+         U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+         The following characters don't need BIDI mirroring.
+         U+2018: "‘" LEFT SINGLE QUOTATION MARK
+         U+2019: "’" RIGHT SINGLE QUOTATION MARK
+         U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
+         U+201B: "‛" SINGLE HIGH-REVERSED-9 QUOTATION MARK
+         U+201C: "“" LEFT DOUBLE QUOTATION MARK
+         U+201D: "”" RIGHT DOUBLE QUOTATION MARK
+         U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
+         U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
+    <string name="more_keys_for_less_than">!fixedColumnOrder!3,&#x2039;|&#x203A;,&#x2264;|&#x2265;,&#x00AB;|&#x00BB;</string>
+    <string name="more_keys_for_greater_than">!fixedColumnOrder!3,&#x203A;|&#x2039;,&#x2265;|&#x2264;,&#x00BB;|&#x00AB;</string>
+    <!-- U+005D: "]" RIGHT SQUARE BRACKET -->
+    <integer name="keycode_for_left_square_bracket">0x005D</integer>
+    <!-- U+005B: "[" LEFT SQUARE BRACKET -->
+    <integer name="keycode_for_right_square_bracket">0x005B</integer>
+    <!-- U+007D: "}" RIGHT CURLY BRACKET -->
+    <integer name="keycode_for_left_curly_bracket">0x007D</integer>
+    <!-- U+007B: "{" LEFT CURLY BRACKET -->
+    <integer name="keycode_for_right_curly_bracket">0x007B</integer>
+    <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
+    <!-- <string name="more_keys_for_double_quote">&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;|&#x00BB;,&#x00BB;|&#x00AB;</string> -->
+    <string name="more_keys_for_double_quote">!fixedColumnOrder!4,&#x201C;,&#x201D;,&#x00AB;|&#x00BB;,&#x00BB;|&#x00AB;</string>
+    <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
+    <!-- <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;|&#x00BB;,&#x00BB|&#x00AB;;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string> -->
+    <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!4,&#x201C;,&#x201D;,&#x00AB;|&#x00BB;,&#x00BB;|&#x00AB;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string>
+</resources>
diff --git a/java/res/values-fa/donottranslate.xml b/java/res/values-fa/donottranslate.xml
new file mode 100644
index 0000000..57de253
--- /dev/null
+++ b/java/res/values-fa/donottranslate.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, 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.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- The all letters need to be mirrored are found at
+         http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt -->
+    <!-- Symbols that are suggested between words -->
+    <string name="suggested_punctuations">!,?,\\,,:,;,\",(|),)|(,\',-,/,@,_</string>
+</resources>
diff --git a/java/res/values-in/strings.xml b/java/res/values-in/strings.xml
index 1f03a05..f10d613 100644
--- a/java/res/values-in/strings.xml
+++ b/java/res/values-in/strings.xml
@@ -45,7 +45,7 @@
     <skip />
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Tundaan singkir munculan kunci"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Tanpa penundaan"</string>
-    <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Bawaan"</string>
+    <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Default"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Sarankan nama Kenalan"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Menggunakan nama dari Kenalan untuk saran dan koreksi"</string>
     <string name="enable_span_insert" msgid="7204653105667167620">"Aktifkan koreksi ulang"</string>
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index d4a50b6..8df51d6 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -114,7 +114,7 @@
 
         <attr name="shadowColor" format="color" />
         <attr name="shadowRadius" format="float" />
-        <attr name="backgroundDimAmount" format="float" />
+        <attr name="backgroundDimAlpha" format="integer" />
 
         <attr name="keyTextStyle" format="enum">
             <!-- This should be aligned with Typeface.NORMAL etc. -->
@@ -139,7 +139,7 @@
             <enum name="alwaysDisplay" value="-1" />
         </attr>
         <attr name="delayBeforeFadeoutLangageOnSpacebar" format="integer" />
-        <attr name="finalFadeoutFactorOfLanguageOnSpacebar" format="float" />
+        <attr name="finalAlphaOfLanguageOnSpacebar" format="integer" />
         <!-- Key detection hysteresis distance. -->
         <attr name="keyHysteresisDistance" format="dimension" />
         <!-- Touch noise threshold time in millisecond -->
diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml
index eb2f9bb..eaca642 100644
--- a/java/res/values/styles.xml
+++ b/java/res/values/styles.xml
@@ -65,7 +65,7 @@
         <item name="verticalCorrection">@dimen/keyboard_vertical_correction</item>
         <item name="shadowColor">#BB000000</item>
         <item name="shadowRadius">2.75</item>
-        <item name="backgroundDimAmount">0.5</item>
+        <item name="backgroundDimAlpha">128</item>
         <!-- Common attributes of LatinKeyboardView -->
         <item name="keyHysteresisDistance">@dimen/config_key_hysteresis_distance</item>
         <item name="touchNoiseThresholdTime">@integer/config_touch_noise_threshold_time</item>
@@ -80,7 +80,7 @@
         <item name="showMoreKeysKeyboardAtTouchedPoint">@bool/config_show_more_keys_keyboard_at_touched_point</item>
         <item name="durationOfFadeoutLanguageOnSpacebar">200</item>
         <item name="delayBeforeFadeoutLangageOnSpacebar">1200</item>
-        <item name="finalFadeoutFactorOfLanguageOnSpacebar">0.5</item>
+        <item name="finalAlphaOfLanguageOnSpacebar">128</item>
     </style>
     <style
         name="LatinKeyboardView"
diff --git a/java/res/xml-fa/keyboard_set.xml b/java/res/xml-fa/keyboard_set.xml
new file mode 100644
index 0000000..9bd1050
--- /dev/null
+++ b/java/res/xml-fa/keyboard_set.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, 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.
+*/
+-->
+
+<KeyboardSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+    latin:keyboardLocale="fa" >
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_arabic" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardSet>
diff --git a/java/res/xml-sw600dp/key_styles_common.xml b/java/res/xml-sw600dp/key_styles_common.xml
index 2fa8b38..b206f0e 100644
--- a/java/res/xml-sw600dp/key_styles_common.xml
+++ b/java/res/xml-sw600dp/key_styles_common.xml
@@ -79,9 +79,12 @@
         latin:code="@integer/key_space"
         latin:keyActionFlags="noKeyPreview" />
     <key-style
-        latin:styleName="nonSpecialBackgroundSpaceKeyStyle"
-        latin:code="@integer/key_space"
-        latin:keyActionFlags="noKeyPreview" />
+        latin:styleName="zwnjKeyStyle"
+        latin:keyLabel="&#x200C;"
+        latin:moreKeys="&#x200D;"
+        latin:keyLabelFlags="hasPopupHint"
+        latin:altCode="@integer/key_space"
+        latin:keyActionFlags="altCodeWhileTyping" />
     <key-style
         latin:styleName="smileyKeyStyle"
         latin:keyLabel=":-)"
diff --git a/java/res/xml-sw600dp/row_qwerty4.xml b/java/res/xml-sw600dp/row_qwerty4.xml
index 3c2f06d..eec35b0 100644
--- a/java/res/xml-sw600dp/row_qwerty4.xml
+++ b/java/res/xml-sw600dp/row_qwerty4.xml
@@ -50,10 +50,24 @@
                     latin:keyStyle="hasShiftedLetterHintStyle" />
             </default>
         </switch>
-        <Key
-            latin:keyStyle="spaceKeyStyle"
-            latin:keyXPos="30.750%p"
-            latin:keyWidth="39.750%p" />
+        <switch>
+            <case
+                latin:languageCode="fa"
+            >
+                <Key
+                    latin:keyStyle="spaceKeyStyle"
+                    latin:keyXPos="30.750%p"
+                    latin:keyWidth="30.850%p" />
+                <Key
+                    latin:keyStyle="zwnjKeyStyle" />
+            </case>
+            <default>
+                <Key
+                    latin:keyStyle="spaceKeyStyle"
+                    latin:keyXPos="30.750%p"
+                    latin:keyWidth="39.750%p" />
+            </default>
+        </switch>
         <switch>
             <case
                 latin:languageCode="iw"
diff --git a/java/res/xml-sw600dp/rows_number_normal.xml b/java/res/xml-sw600dp/rows_number_normal.xml
index 3704c52..be5776b 100644
--- a/java/res/xml-sw600dp/rows_number_normal.xml
+++ b/java/res/xml-sw600dp/rows_number_normal.xml
@@ -143,7 +143,7 @@
             latin:keyStyle="numTabKeyStyle"
             latin:keyWidth="11.00%p" />
         <Key
-            latin:keyStyle="nonSpecialBackgroundSpaceKeyStyle"
+            latin:keyStyle="spaceKeyStyle"
             latin:keyWidth="27.75%p"
             latin:keyXPos="12.75%p" />
         <Key
diff --git a/java/res/xml-sw600dp/rows_phone.xml b/java/res/xml-sw600dp/rows_phone.xml
index 4c896e9..e892693 100644
--- a/java/res/xml-sw600dp/rows_phone.xml
+++ b/java/res/xml-sw600dp/rows_phone.xml
@@ -112,7 +112,7 @@
             latin:keyStyle="numTabKeyStyle"
             latin:keyWidth="11.00%p" />
         <Key
-            latin:keyStyle="nonSpecialBackgroundSpaceKeyStyle"
+            latin:keyStyle="spaceKeyStyle"
             latin:keyWidth="27.75%p"
             latin:keyXPos="12.75%p" />
         <Key
diff --git a/java/res/xml-sw768dp/key_styles_common.xml b/java/res/xml-sw768dp/key_styles_common.xml
index 5c35f1c..069fc63 100644
--- a/java/res/xml-sw768dp/key_styles_common.xml
+++ b/java/res/xml-sw768dp/key_styles_common.xml
@@ -78,9 +78,12 @@
         latin:code="@integer/key_space"
         latin:keyActionFlags="noKeyPreview" />
     <key-style
-        latin:styleName="nonSpecialBackgroundSpaceKeyStyle"
-        latin:code="@integer/key_space"
-        latin:keyActionFlags="noKeyPreview" />
+        latin:styleName="zwnjKeyStyle"
+        latin:keyLabel="&#x200C;"
+        latin:moreKeys="&#x200D;"
+        latin:keyLabelFlags="hasPopupHint"
+        latin:altCode="@integer/key_space"
+        latin:keyActionFlags="altCodeWhileTyping" />
     <key-style
         latin:styleName="smileyKeyStyle"
         latin:keyLabel=":-)"
diff --git a/java/res/xml-sw768dp/row_qwerty4.xml b/java/res/xml-sw768dp/row_qwerty4.xml
index 24c081e..0aa04f8 100644
--- a/java/res/xml-sw768dp/row_qwerty4.xml
+++ b/java/res/xml-sw768dp/row_qwerty4.xml
@@ -76,10 +76,30 @@
                 </switch>
             </default>
         </switch>
-        <Key
-            latin:keyStyle="spaceKeyStyle"
-            latin:keyXPos="31.250%p"
-            latin:keyWidth="37.500%p" />
+        <switch>
+            <case
+                latin:languageCode="fa"
+            >
+                <Key
+                    latin:keyStyle="spaceKeyStyle"
+                    latin:keyXPos="31.250%p"
+                    latin:keyWidth="29.453%p" />
+                <!-- U+200C: "" ZERO WIDTH NON-JOINER
+                     U+200D: "" ZERO WIDTH JOINER -->
+                <Key
+                    latin:keyLabel="&#x200C;"
+                    latin:moreKeys="&#x200D;"
+                    latin:keyLabelFlags="hasPopupHint"
+                    latin:altCode="@integer/key_space"
+                    latin:keyActionFlags="altCodeWhileTyping" />
+            </case>
+            <default>
+                <Key
+                    latin:keyStyle="spaceKeyStyle"
+                    latin:keyXPos="31.250%p"
+                    latin:keyWidth="37.500%p" />
+            </default>
+        </switch>
         <switch>
             <case
                 latin:languageCode="iw"
diff --git a/java/res/xml-sw768dp/rows_number_normal.xml b/java/res/xml-sw768dp/rows_number_normal.xml
index 8bf1a17..2eeb6c9 100644
--- a/java/res/xml-sw768dp/rows_number_normal.xml
+++ b/java/res/xml-sw768dp/rows_number_normal.xml
@@ -145,7 +145,7 @@
             latin:keyboardLayout="@xml/key_settings"
             latin:keyWidth="8.047%p" />
         <Key
-            latin:keyStyle="nonSpecialBackgroundSpaceKeyStyle"
+            latin:keyStyle="spaceKeyStyle"
             latin:keyXPos="13.829%p"
             latin:keyWidth="24.140%p" />
         <Key
diff --git a/java/res/xml-sw768dp/rows_phone.xml b/java/res/xml-sw768dp/rows_phone.xml
index 69c9e86..216fbed 100644
--- a/java/res/xml-sw768dp/rows_phone.xml
+++ b/java/res/xml-sw768dp/rows_phone.xml
@@ -114,7 +114,7 @@
             latin:keyboardLayout="@xml/key_settings"
             latin:keyWidth="8.047%p" />
         <Key
-            latin:keyStyle="nonSpecialBackgroundSpaceKeyStyle"
+            latin:keyStyle="spaceKeyStyle"
             latin:keyXPos="13.829%p"
             latin:keyWidth="24.140%p" />
         <Key
diff --git a/java/res/xml/key_styles_common.xml b/java/res/xml/key_styles_common.xml
index f153a7d..7b526c0 100644
--- a/java/res/xml/key_styles_common.xml
+++ b/java/res/xml/key_styles_common.xml
@@ -108,6 +108,14 @@
         latin:keyActionFlags="noKeyPreview|enableLongPress"
         latin:backgroundType="functional" />
     <key-style
+        latin:styleName="zwnjKeyStyle"
+        latin:keyLabel="&#x200C;"
+        latin:moreKeys="&#x200D;"
+        latin:keyLabelFlags="hasPopupHint"
+        latin:altCode="@integer/key_space"
+        latin:keyActionFlags="altCodeWhileTyping"
+        latin:backgroundType="functional" />
+    <key-style
         latin:styleName="shortcutKeyStyle"
         latin:code="@integer/key_shortcut"
         latin:keyIcon="iconShortcutKey"
@@ -123,6 +131,8 @@
         latin:keyActionFlags="noKeyPreview|altCodeWhileTyping|enableLongPress"
         latin:altCode="@integer/key_space"
         latin:backgroundType="functional" />
+    <!-- U+200C: "" ZERO WIDTH NON-JOINER
+         U+200D: "" ZERO WIDTH JOINER -->
     <key-style
         latin:styleName="tabKeyStyle"
         latin:code="@integer/key_tab"
diff --git a/java/res/xml/method.xml b/java/res/xml/method.xml
index f330b26..cdcea0e 100644
--- a/java/res/xml/method.xml
+++ b/java/res/xml/method.xml
@@ -33,6 +33,7 @@
     en_GB: English Great Britain/qwerty
     es: Spanish/spanish
     et: Estonian/nordic
+    fa: Persian/arabic
     fi: Finnish/nordic
     fr: French/azerty
     fr_CA: French Canada/qwerty
@@ -140,6 +141,12 @@
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
+            android:imeSubtypeLocale="fa"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="SupportTouchPositionCorrection"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
             android:imeSubtypeLocale="fi"
             android:imeSubtypeMode="keyboard"
             android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
diff --git a/java/res/xml/row_qwerty4.xml b/java/res/xml/row_qwerty4.xml
index 0dd2177..b2b47e9 100644
--- a/java/res/xml/row_qwerty4.xml
+++ b/java/res/xml/row_qwerty4.xml
@@ -59,6 +59,28 @@
         </switch>
         <switch>
             <case
+                latin:languageCode="fa"
+                latin:languageSwitchKeyEnabled="true"
+            >
+                <Key
+                    latin:keyStyle="languageSwitchKeyStyle" />
+                <Key
+                    latin:keyStyle="spaceKeyStyle"
+                    latin:keyWidth="30%p" />
+                <Key
+                    latin:keyStyle="zwnjKeyStyle" />
+            </case>
+            <case
+                latin:languageCode="fa"
+                latin:languageSwitchKeyEnabled="false"
+            >
+                <Key
+                    latin:keyStyle="spaceKeyStyle"
+                    latin:keyWidth="40%p" />
+                <Key
+                    latin:keyStyle="zwnjKeyStyle" />
+            </case>
+            <case
                 latin:languageSwitchKeyEnabled="true"
             >
                 <Key
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
index dc7c12b..dd43166 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
@@ -36,7 +36,6 @@
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Set;
 
 /**
  * Exposes a virtual view sub-tree for {@link KeyboardView} and generates
@@ -135,9 +134,9 @@
             ViewCompat.onInitializeAccessibilityNodeInfo(mKeyboardView, info);
 
             // Add the virtual children of the root View.
-            // TODO(alanv): Need to assign a unique ID to each key.
+            // TODO: Need to assign a unique ID to each key.
             final Keyboard keyboard = mKeyboardView.getKeyboard();
-            final Set<Key> keys = keyboard.mKeys;
+            final Key[] keys = keyboard.mKeys;
             for (Key key : keys) {
                 final int childVirtualViewId = generateVirtualViewIdForKey(key);
                 info.addChild(mKeyboardView, childVirtualViewId);
@@ -342,8 +341,8 @@
 
         mVirtualViewIdToKey.clear();
 
-        final Set<Key> keySet = keyboard.mKeys;
-        for (Key key : keySet) {
+        final Key[] keys = keyboard.mKeys;
+        for (Key key : keys) {
             final int virtualViewId = generateVirtualViewIdForKey(key);
             mVirtualViewIdToKey.put(virtualViewId, key);
         }
diff --git a/java/src/com/android/inputmethod/compat/MotionEventCompatUtils.java b/java/src/com/android/inputmethod/compat/MotionEventCompatUtils.java
index eca922e..9a52301 100644
--- a/java/src/com/android/inputmethod/compat/MotionEventCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/MotionEventCompatUtils.java
@@ -17,7 +17,7 @@
 package com.android.inputmethod.compat;
 
 public class MotionEventCompatUtils {
-    // TODO(alanv): Remove after these are added to MotionEventCompat.
+    // TODO: Remove after these are added to MotionEventCompat.
     public static final int ACTION_HOVER_ENTER = 0x9;
     public static final int ACTION_HOVER_EXIT = 0xA;
 }
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index b7f1ddd..44c6a49 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -40,12 +40,9 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 /**
  * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard
@@ -125,16 +122,17 @@
     /** Maximum column for more keys keyboard */
     public final int mMaxMoreKeysKeyboardColumn;
 
-    /** List of keys and icons in this keyboard */
-    public final Set<Key> mKeys;
-    public final Set<Key> mShiftKeys;
+    /** Array of keys and icons in this keyboard */
+    public final Key[] mKeys;
+    public final Key[] mShiftKeys;
+    public final Key[] mAltCodeKeysWhileTyping;
     public final KeyboardIconsSet mIconsSet;
 
-    private final Map<Integer, Key> mKeyCache = new HashMap<Integer, Key>();
+    private final HashMap<Integer, Key> mKeyCache = new HashMap<Integer, Key>();
 
     private final ProximityInfo mProximityInfo;
 
-    public final Map<Integer, List<Integer>> mAdditionalProximityChars;
+    private final Map<Integer, List<Integer>> mAdditionalProximityChars;
 
     public Keyboard(Params params) {
         mId = params.mId;
@@ -149,8 +147,10 @@
         mTopPadding = params.mTopPadding;
         mVerticalGap = params.mVerticalGap;
 
-        mKeys = Collections.unmodifiableSet(params.mKeys);
-        mShiftKeys = Collections.unmodifiableSet(params.mShiftKeys);
+        mKeys = params.mKeys.toArray(new Key[params.mKeys.size()]);
+        mShiftKeys = params.mShiftKeys.toArray(new Key[params.mShiftKeys.size()]);
+        mAltCodeKeysWhileTyping = params.mAltCodeKeysWhileTyping.toArray(
+                new Key[params.mAltCodeKeysWhileTyping.size()]);
         mIconsSet = params.mIconsSet;
         mAdditionalProximityChars = params.mAdditionalProximityChars;
 
@@ -225,8 +225,9 @@
         public int GRID_WIDTH;
         public int GRID_HEIGHT;
 
-        public final Set<Key> mKeys = new HashSet<Key>();
-        public final Set<Key> mShiftKeys = new HashSet<Key>();
+        public final ArrayList<Key> mKeys = new ArrayList<Key>();
+        public final ArrayList<Key> mShiftKeys = new ArrayList<Key>();
+        public final ArrayList<Key> mAltCodeKeysWhileTyping = new ArrayList<Key>();
         public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet();
         // TODO: Should be in Key instead of Keyboard.Params?
         public final Map<Integer, List<Integer>> mAdditionalProximityChars =
@@ -308,12 +309,15 @@
             if (key.mCode == Keyboard.CODE_SHIFT) {
                 mShiftKeys.add(key);
             }
+            if (key.altCodeWhileTyping()) {
+                mAltCodeKeysWhileTyping.add(key);
+            }
         }
 
         private int mMaxHeightCount = 0;
         private int mMaxWidthCount = 0;
-        private final Map<Integer, Integer> mHeightHistogram = new HashMap<Integer, Integer>();
-        private final Map<Integer, Integer> mWidthHistogram = new HashMap<Integer, Integer>();
+        private final HashMap<Integer, Integer> mHeightHistogram = new HashMap<Integer, Integer>();
+        private final HashMap<Integer, Integer> mWidthHistogram = new HashMap<Integer, Integer>();
 
         private void clearHistogram() {
             mMostCommonKeyHeight = 0;
@@ -325,7 +329,8 @@
             mWidthHistogram.clear();
         }
 
-        private static int updateHistogramCounter(Map<Integer, Integer> histogram, Integer key) {
+        private static int updateHistogramCounter(HashMap<Integer, Integer> histogram,
+                Integer key) {
             final int count = (histogram.containsKey(key) ? histogram.get(key) : 0) + 1;
             histogram.put(key, count);
             return count;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index decd73d..4c65522 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -44,11 +44,12 @@
 import com.android.inputmethod.latin.StringUtils;
 
 import java.util.HashMap;
+import java.util.HashSet;
 
 /**
  * A view that renders a virtual {@link Keyboard}.
  *
- * @attr ref R.styleable#KeyboardView_backgroundDimAmount
+ * @attr ref R.styleable#KeyboardView_backgroundDimAlpha
  * @attr ref R.styleable#KeyboardView_keyBackground
  * @attr ref R.styleable#KeyboardView_keyLetterRatio
  * @attr ref R.styleable#KeyboardView_keyLargeLetterRatio
@@ -81,7 +82,7 @@
     // XML attributes
     protected final float mVerticalCorrection;
     protected final int mMoreKeysLayout;
-    private final float mBackgroundDimAmount;
+    private final int mBackgroundDimAlpha;
 
     // HORIZONTAL ELLIPSIS "...", character for popup hint.
     private static final String POPUP_HINT_CHAR = "\u2026";
@@ -110,12 +111,12 @@
     private boolean mNeedsToDimBackground;
     /** Whether the keyboard bitmap buffer needs to be redrawn before it's blitted. **/
     private boolean mBufferNeedsUpdate;
-    /** The dirty region in the keyboard bitmap */
-    private final Rect mDirtyRect = new Rect();
-    /** The key to invalidate. */
-    private Key mInvalidatedKey;
-    /** The dirty region for single key drawing */
-    private final Rect mInvalidatedKeyRect = new Rect();
+    /** True if all keys should be drawn */
+    private boolean mInvalidateAllKeys;
+    /** The keys that should be drawn */
+    private final HashSet<Key> mInvalidatedKeys = new HashSet<Key>();
+    /** The region of invalidated keys */
+    private final Rect mInvalidatedKeysRect = new Rect();
     /** The keyboard bitmap buffer for faster updates */
     private Bitmap mBuffer;
     /** The canvas for the above mutable keyboard bitmap */
@@ -335,7 +336,7 @@
         mVerticalCorrection = a.getDimensionPixelOffset(
                 R.styleable.KeyboardView_verticalCorrection, 0);
         mMoreKeysLayout = a.getResourceId(R.styleable.KeyboardView_moreKeysLayout, 0);
-        mBackgroundDimAmount = a.getFloat(R.styleable.KeyboardView_backgroundDimAmount, 0.5f);
+        mBackgroundDimAlpha = a.getInt(R.styleable.KeyboardView_backgroundDimAlpha, 0);
         a.recycle();
 
         mDelayAfterPreview = mKeyPreviewDrawParams.mLingerTimeout;
@@ -366,8 +367,6 @@
         mKeyboard = keyboard;
         LatinImeLogger.onSetKeyboard(keyboard);
         requestLayout();
-        mDirtyRect.set(0, 0, getWidth(), getHeight());
-        mBufferNeedsUpdate = true;
         invalidateAllKeys();
         final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
         mKeyDrawParams.updateKeyHeight(keyHeight);
@@ -434,47 +433,50 @@
             if (mBuffer != null)
                 mBuffer.recycle();
             mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-            mDirtyRect.union(0, 0, width, height);
+            mInvalidateAllKeys = true;
             if (mCanvas != null) {
                 mCanvas.setBitmap(mBuffer);
             } else {
                 mCanvas = new Canvas(mBuffer);
             }
         }
-        final Canvas canvas = mCanvas;
-        canvas.clipRect(mDirtyRect, Op.REPLACE);
-        canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
 
         if (mKeyboard == null) return;
 
+        final Canvas canvas = mCanvas;
+        final Paint paint = mPaint;
         final KeyDrawParams params = mKeyDrawParams;
-        if (mInvalidatedKey != null && mInvalidatedKeyRect.contains(mDirtyRect)) {
-            // Draw a single key.
-            final int keyDrawX = mInvalidatedKey.mX + mInvalidatedKey.mVisualInsetsLeft
-                    + getPaddingLeft();
-            final int keyDrawY = mInvalidatedKey.mY + getPaddingTop();
-            canvas.translate(keyDrawX, keyDrawY);
-            onDrawKey(mInvalidatedKey, canvas, mPaint, params);
-            canvas.translate(-keyDrawX, -keyDrawY);
-        } else {
+
+        if (mInvalidateAllKeys || mInvalidatedKeys.isEmpty()) {
+            mInvalidatedKeysRect.set(0, 0, getWidth(), getHeight());
+            canvas.clipRect(mInvalidatedKeysRect, Op.REPLACE);
+            canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
             // Draw all keys.
             for (final Key key : mKeyboard.mKeys) {
-                final int keyDrawX = key.mX + key.mVisualInsetsLeft + getPaddingLeft();
-                final int keyDrawY = key.mY + getPaddingTop();
-                canvas.translate(keyDrawX, keyDrawY);
-                onDrawKey(key, canvas, mPaint, params);
-                canvas.translate(-keyDrawX, -keyDrawY);
+                onDrawKey(key, canvas, paint, params);
+            }
+        } else {
+            // Draw invalidated keys.
+            for (final Key key : mInvalidatedKeys) {
+                final int x = key.mX + getPaddingLeft();
+                final int y = key.mY + getPaddingTop();
+                mInvalidatedKeysRect.set(x, y, x + key.mWidth, y + key.mHeight);
+                canvas.clipRect(mInvalidatedKeysRect, Op.REPLACE);
+                canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
+                onDrawKey(key, canvas, paint, params);
             }
         }
 
         // Overlay a dark rectangle to dim the entire keyboard
         if (mNeedsToDimBackground) {
-            mPaint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24);
-            canvas.drawRect(0, 0, width, height, mPaint);
+            paint.setColor(Color.BLACK);
+            paint.setAlpha(mBackgroundDimAlpha);
+            canvas.drawRect(0, 0, width, height, paint);
         }
 
-        mInvalidatedKey = null;
-        mDirtyRect.setEmpty();
+        mInvalidatedKeys.clear();
+        mInvalidatedKeysRect.setEmpty();
+        mInvalidateAllKeys = false;
     }
 
     public void dimEntireKeyboard(boolean dimmed) {
@@ -486,10 +488,16 @@
     }
 
     private void onDrawKey(Key key, Canvas canvas, Paint paint, KeyDrawParams params) {
+        final int keyDrawX = key.mX + key.mVisualInsetsLeft + getPaddingLeft();
+        final int keyDrawY = key.mY + getPaddingTop();
+        canvas.translate(keyDrawX, keyDrawY);
+
         if (!key.isSpacer()) {
             onDrawKeyBackground(key, canvas, params);
         }
         onDrawKeyTopVisuals(key, canvas, paint, params);
+
+        canvas.translate(-keyDrawX, -keyDrawY);
     }
 
     // Draw key background.
@@ -905,9 +913,9 @@
      * @see #invalidateKey(Key)
      */
     public void invalidateAllKeys() {
-        mDirtyRect.union(0, 0, getWidth(), getHeight());
+        mInvalidatedKeys.clear();
+        mInvalidateAllKeys = true;
         mBufferNeedsUpdate = true;
-        mInvalidatedKey = null;
         invalidate();
     }
 
@@ -920,22 +928,21 @@
      */
     @Override
     public void invalidateKey(Key key) {
-        if (key == null)
-            return;
-        mInvalidatedKey = key;
+        if (mInvalidateAllKeys) return;
+        if (key == null) return;
+        mInvalidatedKeys.add(key);
         final int x = key.mX + getPaddingLeft();
         final int y = key.mY + getPaddingTop();
-        mInvalidatedKeyRect.set(x, y, x + key.mWidth, y + key.mHeight);
-        mDirtyRect.union(mInvalidatedKeyRect);
+        mInvalidatedKeysRect.union(x, y, x + key.mWidth, y + key.mHeight);
         mBufferNeedsUpdate = true;
-        invalidate(mInvalidatedKeyRect);
+        invalidate(mInvalidatedKeysRect);
     }
 
     public void closing() {
         PointerTracker.dismissAllKeyPreviews();
         cancelAllMessages();
 
-        mDirtyRect.union(0, 0, getWidth(), getHeight());
+        mInvalidateAllKeys = true;
         requestLayout();
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index 9168d07..f4e766c 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -21,7 +21,6 @@
 import android.content.pm.PackageManager;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
-import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Paint.Align;
 import android.graphics.Typeface;
@@ -77,13 +76,14 @@
     private Drawable mSpaceIcon;
     // Stuff to draw language name on spacebar.
     private ValueAnimator mLanguageOnSpacebarAnimator;
-    private float mFinalFadeoutFactorOfLanguageOnSpacebar;
+    private int mFinalAlphaOfLanguageOnSpacebar;
     private int mDurationOfFadeoutLanguageOnSpacebar;
+    private static final int ALPHA_OPAQUE = 255;
     private static final int LANGUAGE_ON_SPACEBAR_NEVER_DISPLAY = 0;
     private static final int LANGUAGE_ON_SPACEBAR_ALWAYS_DISPLAY = -1;
     private boolean mNeedsToDisplayLanguage;
     private Locale mSpacebarLocale;
-    private float mSpacebarTextFadeFactor = 0.0f;
+    private int mSpacebarTextAlpha;
     private final float mSpacebarTextRatio;
     private float mSpacebarTextSize;
     private final int mSpacebarTextColor;
@@ -344,8 +344,8 @@
                 LANGUAGE_ON_SPACEBAR_NEVER_DISPLAY);
         final int delayBeforeFadeoutLanguageOnSpacebar = a.getInt(
                 R.styleable.LatinKeyboardView_delayBeforeFadeoutLangageOnSpacebar, 0);
-        mFinalFadeoutFactorOfLanguageOnSpacebar = a.getFloat(
-                R.styleable.LatinKeyboardView_finalFadeoutFactorOfLanguageOnSpacebar, 0.0f);
+        mFinalAlphaOfLanguageOnSpacebar = a.getInt(
+                R.styleable.LatinKeyboardView_finalAlphaOfLanguageOnSpacebar, 0);
 
         final KeyTimerParams keyTimerParams = new KeyTimerParams(a);
         mPointerTrackerParams = new PointerTrackerParams(a);
@@ -361,8 +361,8 @@
 
         PointerTracker.setParameters(mPointerTrackerParams);
 
-        mLanguageOnSpacebarAnimator = ValueAnimator.ofFloat(
-                1.0f, mFinalFadeoutFactorOfLanguageOnSpacebar);
+        mLanguageOnSpacebarAnimator = ValueAnimator.ofInt(
+                ALPHA_OPAQUE, mFinalAlphaOfLanguageOnSpacebar);
         mLanguageOnSpacebarAnimator.setStartDelay(delayBeforeFadeoutLanguageOnSpacebar);
         if (mDurationOfFadeoutLanguageOnSpacebar > 0) {
             mLanguageOnSpacebarAnimator.setDuration(mDurationOfFadeoutLanguageOnSpacebar);
@@ -370,7 +370,7 @@
         mLanguageOnSpacebarAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
             @Override
             public void onAnimationUpdate(ValueAnimator animation) {
-                mSpacebarTextFadeFactor = (Float)animation.getAnimatedValue();
+                mSpacebarTextAlpha = (Integer)animation.getAnimatedValue();
                 invalidateKey(mSpaceKey);
             }
         });
@@ -794,15 +794,15 @@
         mLanguageOnSpacebarAnimator.cancel();
         mNeedsToDisplayLanguage = needsToDisplayLanguage;
         if (mDurationOfFadeoutLanguageOnSpacebar == LANGUAGE_ON_SPACEBAR_NEVER_DISPLAY) {
-            mSpacebarTextFadeFactor = 0.0f;
+            mNeedsToDisplayLanguage = false;
         } else if (mDurationOfFadeoutLanguageOnSpacebar == LANGUAGE_ON_SPACEBAR_ALWAYS_DISPLAY) {
-            mSpacebarTextFadeFactor = 1.0f;
+            mSpacebarTextAlpha = ALPHA_OPAQUE;
         } else {
             if (subtypeChanged && needsToDisplayLanguage) {
-                mSpacebarTextFadeFactor = 1.0f;
+                mSpacebarTextAlpha = ALPHA_OPAQUE;
                 mLanguageOnSpacebarAnimator.start();
             } else {
-                mSpacebarTextFadeFactor = mFinalFadeoutFactorOfLanguageOnSpacebar;
+                mSpacebarTextAlpha = mFinalAlphaOfLanguageOnSpacebar;
             }
         }
         invalidateKey(mSpaceKey);
@@ -835,12 +835,6 @@
         }
     }
 
-    private static int getSpacebarTextColor(int color, float fadeFactor) {
-        final int newColor = Color.argb((int)(Color.alpha(color) * fadeFactor),
-                Color.red(color), Color.green(color), Color.blue(color));
-        return newColor;
-    }
-
     // Compute width of text with specified text size using paint.
     private int getTextWidth(Paint paint, String text, float textSize) {
         paint.setTextSize(textSize);
@@ -888,7 +882,7 @@
         final int width = key.mWidth;
         final int height = key.mHeight;
 
-        // If application locales are explicitly selected.
+        // If input subtypes are explicitly selected.
         if (mNeedsToDisplayLanguage) {
             final String language = layoutLanguageOnSpacebar(paint, mSpacebarLocale, width,
                     mSpacebarTextSize);
@@ -898,9 +892,11 @@
             final float descent = paint.descent();
             final float textHeight = -paint.ascent() + descent;
             final float baseline = height / 2 + textHeight / 2;
-            paint.setColor(getSpacebarTextColor(mSpacebarTextShadowColor, mSpacebarTextFadeFactor));
+            paint.setColor(mSpacebarTextShadowColor);
+            paint.setAlpha(mSpacebarTextAlpha);
             canvas.drawText(language, width / 2, baseline - descent - 1, paint);
-            paint.setColor(getSpacebarTextColor(mSpacebarTextColor, mSpacebarTextFadeFactor));
+            paint.setColor(mSpacebarTextColor);
+            paint.setAlpha(mSpacebarTextAlpha);
             canvas.drawText(language, width / 2, baseline - descent, paint);
         }
 
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 7a9915b..607b33b 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -25,8 +25,6 @@
 import com.android.inputmethod.latin.LatinImeLogger;
 
 import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
 
 public class PointerTracker {
     private static final String TAG = PointerTracker.class.getSimpleName();
@@ -109,7 +107,7 @@
     private static LatinKeyboardView.PointerTrackerParams sParams;
     private static int sTouchNoiseThresholdDistanceSquared;
 
-    private static final List<PointerTracker> sTrackers = new ArrayList<PointerTracker>();
+    private static final ArrayList<PointerTracker> sTrackers = new ArrayList<PointerTracker>();
     private static PointerTrackerQueue sPointerTrackerQueue;
 
     public final int mPointerId;
@@ -120,7 +118,6 @@
     private KeyboardActionListener mListener = EMPTY_LISTENER;
 
     private Keyboard mKeyboard;
-    private Set<Key> mKeys;
     private int mKeyQuarterWidthSquared;
     private final TextView mKeyPreviewText;
 
@@ -180,7 +177,7 @@
     }
 
     public static PointerTracker getPointerTracker(final int id, KeyEventHandler handler) {
-        final List<PointerTracker> trackers = sTrackers;
+        final ArrayList<PointerTracker> trackers = sTrackers;
 
         // Create pointer trackers until we can get 'id+1'-th tracker, if needed.
         for (int i = trackers.size(); i <= id; i++) {
@@ -303,7 +300,6 @@
     private void setKeyDetectorInner(KeyDetector keyDetector) {
         mKeyDetector = keyDetector;
         mKeyboard = keyDetector.getKeyboard();
-        mKeys = mKeyboard.mKeys;
         final int keyQuarterWidth = mKeyboard.mMostCommonKeyWidth / 4;
         mKeyQuarterWidthSquared = keyQuarterWidth * keyQuarterWidth;
     }
@@ -326,55 +322,74 @@
 
     private void setReleasedKeyGraphics(Key key) {
         mDrawingProxy.dismissKeyPreview(this);
-        if (key != null && key.isEnabled()) {
-            key.onReleased();
-            mDrawingProxy.invalidateKey(key);
+        if (key == null || !key.isEnabled()) {
+            return;
+        }
 
-            if (key.isShift()) {
-                for (final Key shiftKey : mKeyboard.mShiftKeys) {
-                    if (shiftKey != key) {
-                        shiftKey.onReleased();
-                        mDrawingProxy.invalidateKey(shiftKey);
-                    }
+        updateReleaseKeyGraphics(key);
+
+        if (key.isShift()) {
+            for (final Key shiftKey : mKeyboard.mShiftKeys) {
+                if (shiftKey != key) {
+                    updateReleaseKeyGraphics(shiftKey);
                 }
             }
+        }
 
-            if (key.altCodeWhileTyping()) {
-                final Key altKey = mKeyboard.getKey(key.mAltCode);
-                if (altKey != null) {
-                    altKey.onReleased();
-                    mDrawingProxy.invalidateKey(altKey);
+        if (key.altCodeWhileTyping()) {
+            final int altCode = key.mAltCode;
+            final Key altKey = mKeyboard.getKey(altCode);
+            if (altKey != null) {
+                updateReleaseKeyGraphics(altKey);
+            }
+            for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) {
+                if (k != key && k.mAltCode == altCode) {
+                    updateReleaseKeyGraphics(k);
                 }
             }
         }
     }
 
     private void setPressedKeyGraphics(Key key) {
-        if (key != null && key.isEnabled()) {
-            if (!key.noKeyPreview()) {
-                mDrawingProxy.showKeyPreview(this);
-            }
-            key.onPressed();
-            mDrawingProxy.invalidateKey(key);
+        if (key == null || !key.isEnabled()) {
+            return;
+        }
 
-            if (key.isShift()) {
-                for (final Key shiftKey : mKeyboard.mShiftKeys) {
-                    if (shiftKey != key) {
-                        shiftKey.onPressed();
-                        mDrawingProxy.invalidateKey(shiftKey);
-                    }
-                }
-            }
+        if (!key.noKeyPreview()) {
+            mDrawingProxy.showKeyPreview(this);
+        }
+        updatePressKeyGraphics(key);
 
-            if (key.altCodeWhileTyping() && mTimerProxy.isTyping()) {
-                final Key altKey = mKeyboard.getKey(key.mAltCode);
-                if (altKey != null) {
-                    // TODO: Show altKey's preview.
-                    altKey.onPressed();
-                    mDrawingProxy.invalidateKey(altKey);
+        if (key.isShift()) {
+            for (final Key shiftKey : mKeyboard.mShiftKeys) {
+                if (shiftKey != key) {
+                    updatePressKeyGraphics(shiftKey);
                 }
             }
         }
+
+        if (key.altCodeWhileTyping() && mTimerProxy.isTyping()) {
+            final int altCode = key.mAltCode;
+            final Key altKey = mKeyboard.getKey(altCode);
+            if (altKey != null) {
+                updatePressKeyGraphics(altKey);
+            }
+            for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) {
+                if (k != key && k.mAltCode == altCode) {
+                    updatePressKeyGraphics(k);
+                }
+            }
+        }
+    }
+
+    private void updateReleaseKeyGraphics(Key key) {
+        key.onReleased();
+        mDrawingProxy.invalidateKey(key);
+    }
+
+    private void updatePressKeyGraphics(Key key) {
+        key.onPressed();
+        mDrawingProxy.invalidateKey(key);
     }
 
     public int getLastX() {
@@ -691,7 +706,7 @@
     }
 
     private boolean isMajorEnoughMoveToBeOnNewKey(int x, int y, Key newKey) {
-        if (mKeys == null || mKeyDetector == null)
+        if (mKeyDetector == null)
             throw new NullPointerException("keyboard and/or key detector not set");
         Key curKey = mCurrentKey;
         if (newKey == curKey) {
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index 8a65a5f..e2a4830 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -28,7 +28,6 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 public class ProximityInfo {
     public static final int MAX_PROXIMITY_CHARS_SIZE = 16;
@@ -50,8 +49,8 @@
     private final String mLocaleStr;
 
     ProximityInfo(String localeStr, int gridWidth, int gridHeight, int minWidth, int height,
-            int mostCommonKeyWidth,
-            int mostCommonKeyHeight, Set<Key> keys, TouchPositionCorrection touchPositionCorrection,
+            int mostCommonKeyWidth, int mostCommonKeyHeight, final Key[] keys,
+            TouchPositionCorrection touchPositionCorrection,
             Map<Integer, List<Integer>> additionalProximityChars) {
         if (TextUtils.isEmpty(localeStr)) {
             mLocaleStr = "";
@@ -77,8 +76,8 @@
     }
 
     public static ProximityInfo createDummyProximityInfo() {
-        return new ProximityInfo("", 1, 1, 1, 1, 1, 1, Collections.<Key> emptySet(),
-                null, Collections.<Integer, List<Integer>> emptyMap());
+        return new ProximityInfo("", 1, 1, 1, 1, 1, 1, EMPTY_KEY_ARRAY, null,
+                Collections.<Integer, List<Integer>> emptyMap());
     }
 
     public static ProximityInfo createSpellCheckerProximityInfo(final int[] proximity) {
@@ -106,8 +105,7 @@
     private native void releaseProximityInfoNative(long nativeProximityInfo);
 
     private final void setProximityInfo(Key[][] gridNeighborKeys, int keyboardWidth,
-            int keyboardHeight, Set<Key> keys,
-            TouchPositionCorrection touchPositionCorrection) {
+            int keyboardHeight, final Key[] keys, TouchPositionCorrection touchPositionCorrection) {
         final int[] proximityCharsArray = new int[mGridSize * MAX_PROXIMITY_CHARS_SIZE];
         Arrays.fill(proximityCharsArray, KeyDetector.NOT_A_CODE);
         for (int i = 0; i < mGridSize; ++i) {
@@ -117,7 +115,7 @@
                         gridNeighborKeys[i][j].mCode;
             }
         }
-        final int keyCount = keys.size();
+        final int keyCount = keys.length;
         final int[] keyXCoordinates = new int[keyCount];
         final int[] keyYCoordinates = new int[keyCount];
         final int[] keyWidths = new int[keyCount];
@@ -132,8 +130,8 @@
             sweetSpotCenterYs = new float[keyCount];
             sweetSpotRadii = new float[keyCount];
             calculateSweetSpotParams = true;
-            int i = 0;
-            for (final Key key : keys) {
+            for (int i = 0; i < keyCount; i++) {
+                final Key key = keys[i];
                 keyXCoordinates[i] = key.mX;
                 keyYCoordinates[i] = key.mY;
                 keyWidths[i] = key.mWidth;
@@ -156,7 +154,6 @@
                                 hitBoxWidth * hitBoxWidth + hitBoxHeight * hitBoxHeight);
                     }
                 }
-                i++;
             }
         } else {
             sweetSpotCenterXs = sweetSpotCenterYs = sweetSpotRadii = null;
@@ -186,17 +183,17 @@
         }
     }
 
-    private void computeNearestNeighbors(int defaultWidth, Set<Key> keys,
+    private void computeNearestNeighbors(int defaultWidth, final Key[] keys,
             TouchPositionCorrection touchPositionCorrection,
             Map<Integer, List<Integer>> additionalProximityChars) {
-        final Map<Integer, Key> keyCodeMap = new HashMap<Integer, Key>();
+        final HashMap<Integer, Key> keyCodeMap = new HashMap<Integer, Key>();
         for (final Key key : keys) {
             keyCodeMap.put(key.mCode, key);
         }
         final int thresholdBase = (int) (defaultWidth * SEARCH_DISTANCE);
         final int threshold = thresholdBase * thresholdBase;
         // Round-up so we don't have any pixels outside the grid
-        final Key[] neighborKeys = new Key[keys.size()];
+        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) {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
index ca711ec..9b9c861 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
@@ -24,7 +24,6 @@
 import com.android.inputmethod.latin.R;
 
 import java.util.HashMap;
-import java.util.Map;
 
 public class KeyboardIconsSet {
     private static final String TAG = KeyboardIconsSet.class.getSimpleName();
@@ -35,8 +34,9 @@
 
     private final Drawable[] mIcons = new Drawable[NUM_ICONS + 1];
 
-    private static final Map<Integer, Integer> ATTR_ID_TO_ICON_ID = new HashMap<Integer, Integer>();
-    private static final Map<String, Integer> NAME_TO_ICON_ID = new HashMap<String, Integer>();
+    private static final HashMap<Integer, Integer> ATTR_ID_TO_ICON_ID
+            = new HashMap<Integer, Integer>();
+    private static final HashMap<String, Integer> NAME_TO_ICON_ID = new HashMap<String, Integer>();
     private static final String[] ICON_NAMES = new String[NUM_ICONS + 1];
 
     private static final int ATTR_UNDEFINED = 0;
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index dc5bd2e..def639d 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -72,6 +72,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
 
@@ -925,10 +926,11 @@
             final List<SuggestedWords.SuggestedWordInfo> applicationSuggestedWords =
                     SuggestedWords.Builder.getFromApplicationSpecifiedCompletions(
                             applicationSpecifiedCompletions);
-            SuggestedWords.Builder builder = new SuggestedWords.Builder()
-                    .addWords(applicationSuggestedWords)
-                    .setTypedWordValid(false)
-                    .setHasMinimalSuggestion(false);
+            SuggestedWords.Builder builder = new SuggestedWords.Builder(applicationSuggestedWords,
+                    false /* typedWordValid */,
+                    false /* hasMinimalSuggestion */,
+                    false /* allowsToBeAutoCorrected */,
+                    false /* isPunctuationSuggestions */);
             // When in fullscreen mode, show completions generated by the application
             final SuggestedWords words = builder.build();
             final boolean isAutoCorrection = false;
@@ -1767,6 +1769,7 @@
         // getSuggestedWordBuilder handles gracefully a null value of prevWord
         final SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder(mWordComposer,
                 prevWord, mKeyboardSwitcher.getKeyboard().getProximityInfo(), mCorrectionMode);
+        final SuggestedWords suggestions = builder.build();
 
         // Basically, we update the suggestion strip only when suggestion count > 1.  However,
         // there is an exception: We update the suggestion strip whenever typed word's length
@@ -1774,16 +1777,24 @@
         // in most cases, suggestion count is 1 when typed word's length is 1, but we do always
         // need to clear the previous state when the user starts typing a word (i.e. typed word's
         // length == 1).
-        if (builder.size() > 1 || typedWord.length() == 1 || !builder.allowsToBeAutoCorrected()
+        if (suggestions.size() > 1 || typedWord.length() == 1
+                || !suggestions.mAllowsToBeAutoCorrected
                 || mSuggestionsView.isShowingAddToDictionaryHint()) {
-            showSuggestions(builder.build(), typedWord);
+            showSuggestions(suggestions, typedWord);
         } else {
             SuggestedWords previousSuggestions = mSuggestionsView.getSuggestions();
             if (previousSuggestions == mSettingsValues.mSuggestPuncList) {
                 previousSuggestions = SuggestedWords.EMPTY;
             }
-            final SuggestedWords.Builder obsoleteSuggestionsBuilder = new SuggestedWords.Builder()
-                    .addTypedWordAndPreviousSuggestions(typedWord, previousSuggestions);
+            final ArrayList<SuggestedWords.SuggestedWordInfo> typedWordAndPreviousSuggestions =
+                    SuggestedWords.Builder.getTypedWordAndPreviousSuggestions(
+                            typedWord, previousSuggestions);
+            final SuggestedWords.Builder obsoleteSuggestionsBuilder =
+                    new SuggestedWords.Builder(typedWordAndPreviousSuggestions,
+                            false /* typedWordValid */,
+                            false /* hasMinimalSuggestion */,
+                            false /* allowsToBeAutoCorrected */,
+                            false /* isPunctuationSuggestions */);
             showSuggestions(obsoleteSuggestionsBuilder.build(), typedWord);
         }
     }
diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java
index 7e77020..0a4aea1 100644
--- a/java/src/com/android/inputmethod/latin/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -183,9 +183,11 @@
                         KeySpecParser.getLabel(puncSpec)));
             }
         }
-        final SuggestedWords.Builder builder = new SuggestedWords.Builder()
-                .addWords(puncList)
-                .setIsPunctuationSuggestions();
+        final SuggestedWords.Builder builder = new SuggestedWords.Builder(puncList,
+                false /* typedWordValid */,
+                false /* hasMinimalSuggestion */,
+                false /* allowsToBeAutoCorrected */,
+                true /* isPunctuationSuggestions */);
         return builder.build();
     }
 
@@ -203,9 +205,11 @@
                 }
             }
         }
-        final SuggestedWords.Builder builder = new SuggestedWords.Builder()
-                .addWords(puncOutputTextList)
-                .setIsPunctuationSuggestions();
+        final SuggestedWords.Builder builder = new SuggestedWords.Builder(puncOutputTextList,
+                false /* typedWordValid */,
+                false /* hasMinimalSuggestion */,
+                false /* allowsToBeAutoCorrected */,
+                true /* isPunctuationSuggestions */);
         return builder.build();
     }
 
diff --git a/java/src/com/android/inputmethod/latin/StringBuilderPool.java b/java/src/com/android/inputmethod/latin/StringBuilderPool.java
deleted file mode 100644
index a663ed4..0000000
--- a/java/src/com/android/inputmethod/latin/StringBuilderPool.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-package com.android.inputmethod.latin;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * A pool of string builders to be used from anywhere.
- */
-public class StringBuilderPool {
-    // Singleton
-    private static final StringBuilderPool sInstance = new StringBuilderPool();
-    private static final boolean DEBUG = false;
-    private StringBuilderPool() {}
-    // TODO: Make this a normal array with a size of 20, or a ConcurrentQueue
-    private final List<StringBuilder> mPool =
-            Collections.synchronizedList(new ArrayList<StringBuilder>());
-
-    public static StringBuilder getStringBuilder(final int initialSize) {
-        // TODO: although the pool is synchronized, the following is not thread-safe.
-        // Two threads entering this at the same time could take the same size of the pool and the
-        // second to attempt removing this index from the pool would crash with an
-        // IndexOutOfBoundsException.
-        // At the moment this pool is only used in Suggest.java and only in one thread so it's
-        // okay. The simplest thing to do here is probably to replace the ArrayList with a
-        // ConcurrentQueue.
-        final int poolSize = sInstance.mPool.size();
-        final StringBuilder sb = poolSize > 0 ? (StringBuilder) sInstance.mPool.remove(poolSize - 1)
-                : new StringBuilder(initialSize);
-        sb.setLength(0);
-        return sb;
-    }
-
-    public static void recycle(final StringBuilder garbage) {
-        if (DEBUG) {
-            final int gid = garbage.hashCode();
-            for (final StringBuilder q : sInstance.mPool) {
-                if (gid == q.hashCode()) throw new RuntimeException("Duplicate id " + gid);
-            }
-        }
-        sInstance.mPool.add(garbage);
-    }
-
-    public static void ensureCapacity(final int capacity, final int initialSize) {
-        for (int i = sInstance.mPool.size(); i < capacity; ++i) {
-            final StringBuilder sb = new StringBuilder(initialSize);
-            sInstance.mPool.add(sb);
-        }
-    }
-
-    public static int getSize() {
-        return sInstance.mPool.size();
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
index 81c3b4e..7b34cae 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -142,7 +142,7 @@
             for (int j = 0; j < i; j++) {
                 CharSequence previous = suggestions.get(j);
                 if (TextUtils.equals(cur, previous)) {
-                    removeFromSuggestions(suggestions, i);
+                    suggestions.remove(i);
                     i--;
                     break;
                 }
@@ -151,14 +151,6 @@
         }
     }
 
-    private static void removeFromSuggestions(final ArrayList<CharSequence> suggestions,
-            final int index) {
-        final CharSequence garbage = suggestions.remove(index);
-        if (garbage instanceof StringBuilder) {
-            StringBuilderPool.recycle((StringBuilder)garbage);
-        }
-    }
-
     public static String getFullDisplayName(Locale locale, boolean returnsNameInThisLocale) {
         if (returnsNameInThisLocale) {
             return toTitleCase(SubtypeLocale.getFullDisplayName(locale), locale);
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index e04a4e8..4dee4f3 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -98,7 +98,7 @@
     private int[] mBigramScores = new int[PREF_MAX_BIGRAMS];
 
     private ArrayList<CharSequence> mSuggestions = new ArrayList<CharSequence>();
-    ArrayList<CharSequence> mBigramSuggestions  = new ArrayList<CharSequence>();
+    private ArrayList<CharSequence> mBigramSuggestions = new ArrayList<CharSequence>();
     private CharSequence mConsideredWord;
 
     // TODO: Remove these member variables by passing more context to addWord() callback method
@@ -122,7 +122,6 @@
     private void initWhitelistAndAutocorrectAndPool(final Context context, final Locale locale) {
         mWhiteListDictionary = new WhitelistDictionary(context, locale);
         addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_WHITELIST, mWhiteListDictionary);
-        StringBuilderPool.ensureCapacity(mPrefMaxSuggestions, getApproxMaxWordLength());
     }
 
     private void initAsynchronously(final Context context, final int dictionaryResId,
@@ -181,7 +180,7 @@
         return mUnigramDictionaries;
     }
 
-    public int getApproxMaxWordLength() {
+    public static int getApproxMaxWordLength() {
         return APPROX_MAX_WORD_LENGTH;
     }
 
@@ -216,27 +215,11 @@
         mAutoCorrectionThreshold = threshold;
     }
 
-    /**
-     * Number of suggestions to generate from the input key sequence. This has
-     * to be a number between 1 and 100 (inclusive).
-     * @param maxSuggestions
-     * @throws IllegalArgumentException if the number is out of range
-     */
-    public void setMaxSuggestions(int maxSuggestions) {
-        if (maxSuggestions < 1 || maxSuggestions > 100) {
-            throw new IllegalArgumentException("maxSuggestions must be between 1 and 100");
-        }
-        mPrefMaxSuggestions = maxSuggestions;
-        mScores = new int[mPrefMaxSuggestions];
-        mBigramScores = new int[PREF_MAX_BIGRAMS];
-        collectGarbage(mSuggestions, mPrefMaxSuggestions);
-        StringBuilderPool.ensureCapacity(mPrefMaxSuggestions, getApproxMaxWordLength());
-    }
-
-    private CharSequence capitalizeWord(boolean all, boolean first, CharSequence word) {
+    private static CharSequence capitalizeWord(final boolean all, final boolean first,
+            final CharSequence word) {
         if (TextUtils.isEmpty(word) || !(all || first)) return word;
         final int wordLength = word.length();
-        final StringBuilder sb = StringBuilderPool.getStringBuilder(getApproxMaxWordLength());
+        final StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
         // TODO: Must pay attention to locale when changing case.
         if (all) {
             sb.append(word.toString().toUpperCase());
@@ -250,12 +233,7 @@
     }
 
     protected void addBigramToSuggestions(CharSequence bigram) {
-        // TODO: Try to be a little more shrewd with resource allocation.
-        // At the moment we copy this object because the StringBuilders are pooled (see
-        // StringBuilderPool.java) and when we are finished using mSuggestions and
-        // mBigramSuggestions we will take everything from both and insert them back in the
-        // pool, so we can't allow the same object to be in both lists at the same time.
-        final StringBuilder sb = StringBuilderPool.getStringBuilder(getApproxMaxWordLength());
+        final StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
         sb.append(bigram);
         mSuggestions.add(sb);
     }
@@ -266,7 +244,7 @@
         mIsFirstCharCapitalized = false;
         mIsAllUpperCase = false;
         mTrailingSingleQuotesCount = 0;
-        collectGarbage(mSuggestions, mPrefMaxSuggestions);
+        mSuggestions = new ArrayList<CharSequence>(mPrefMaxSuggestions);
         Arrays.fill(mScores, 0);
 
         // Treating USER_TYPED as UNIGRAM suggestion for logging now.
@@ -274,7 +252,7 @@
         mConsideredWord = "";
 
         Arrays.fill(mBigramScores, 0);
-        collectGarbage(mBigramSuggestions, PREF_MAX_BIGRAMS);
+        mBigramSuggestions = new ArrayList<CharSequence>(PREF_MAX_BIGRAMS);
 
         CharSequence lowerPrevWord = prevWordForBigram.toString().toLowerCase();
         if (mMainDict != null && mMainDict.isValidWord(lowerPrevWord)) {
@@ -291,10 +269,12 @@
 
         StringUtils.removeDupes(mSuggestions);
 
-        return new SuggestedWords.Builder()
-                .addWords(SuggestedWords.Builder.getFromCharSequenceList(mSuggestions))
-                .setAllowsToBeAutoCorrected(false)
-                .setHasAutoCorrection(false);
+        return new SuggestedWords.Builder(
+                SuggestedWords.Builder.getFromCharSequenceList(mSuggestions),
+                false /* typedWordValid */,
+                false /* hasMinimalSuggestion */,
+                false /* allowsToBeAutoCorrected */,
+                false /* isPunctuationSuggestions */);
     }
 
     // TODO: cleanup dictionaries looking up and suggestions building with SuggestedWords.Builder
@@ -305,7 +285,7 @@
         mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
         mIsAllUpperCase = wordComposer.isAllUpperCase();
         mTrailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount();
-        collectGarbage(mSuggestions, mPrefMaxSuggestions);
+        mSuggestions = new ArrayList<CharSequence>(mPrefMaxSuggestions);
         Arrays.fill(mScores, 0);
 
         final String typedWord = wordComposer.getTypedWord();
@@ -328,7 +308,7 @@
         if (wordComposer.size() <= 1 && (correctionMode == CORRECTION_FULL_BIGRAM)) {
             // At first character typed, search only the bigrams
             Arrays.fill(mBigramScores, 0);
-            collectGarbage(mBigramSuggestions, PREF_MAX_BIGRAMS);
+            mBigramSuggestions = new ArrayList<CharSequence>(PREF_MAX_BIGRAMS);
 
             if (!TextUtils.isEmpty(prevWordForBigram)) {
                 CharSequence lowerPrevWord = prevWordForBigram.toString().toLowerCase();
@@ -413,6 +393,7 @@
         StringUtils.removeDupes(mSuggestions);
 
         final SuggestedWords.Builder builder;
+        final ArrayList<SuggestedWords.SuggestedWordInfo> scoreInfoList;
         if (DBG) {
             // TODO: this doesn't take into account the fact that removing dupes from mSuggestions
             // may have made mScores[] and mSuggestions out of sync.
@@ -421,8 +402,7 @@
             double normalizedScore = BinaryDictionary.calcNormalizedScore(
                     typedWord, autoCorrectionSuggestion.toString(),
                     autoCorrectionSuggestionScore);
-            ArrayList<SuggestedWords.SuggestedWordInfo> scoreInfoList =
-                    new ArrayList<SuggestedWords.SuggestedWordInfo>();
+            scoreInfoList = new ArrayList<SuggestedWords.SuggestedWordInfo>();
             scoreInfoList.add(new SuggestedWords.SuggestedWordInfo(autoCorrectionSuggestion, "+",
                     false));
             final int suggestionsSize = mSuggestions.size();
@@ -446,14 +426,8 @@
                 scoreInfoList.add(new SuggestedWords.SuggestedWordInfo(mSuggestions.get(i),
                         "--", false));
             }
-            builder = new SuggestedWords.Builder().addWords(scoreInfoList)
-                    .setAllowsToBeAutoCorrected(allowsToBeAutoCorrected)
-                    .setHasAutoCorrection(hasAutoCorrection);
         } else {
-            builder = new SuggestedWords.Builder()
-                    .addWords(SuggestedWords.Builder.getFromCharSequenceList(mSuggestions))
-                    .setAllowsToBeAutoCorrected(allowsToBeAutoCorrected)
-                    .setHasAutoCorrection(hasAutoCorrection);
+            scoreInfoList = SuggestedWords.Builder.getFromCharSequenceList(mSuggestions);
         }
 
         boolean autoCorrectionAvailable = hasAutoCorrection;
@@ -463,10 +437,20 @@
         }
         // Don't auto-correct words with multiple capital letter
         autoCorrectionAvailable &= !wordComposer.isMostlyCaps();
-        builder.setTypedWordValid(!allowsToBeAutoCorrected).setHasMinimalSuggestion(
-                autoCorrectionAvailable);
-        if (allowsToBeAutoCorrected && builder.size() > 1 && mAutoCorrectionThreshold > 0
-                && Suggest.shouldBlockAutoCorrectionBySafetyNet(typedWord, builder.getWord(1))) {
+        final boolean shouldBlockAutoCorrectionBySatefyNet;
+        if (allowsToBeAutoCorrected && scoreInfoList.size() > 1 && mAutoCorrectionThreshold > 0
+                && Suggest.shouldBlockAutoCorrectionBySafetyNet(typedWord,
+                        scoreInfoList.get(1).mWord)) {
+            shouldBlockAutoCorrectionBySatefyNet = true;
+        } else {
+            shouldBlockAutoCorrectionBySatefyNet = false;
+        }
+        builder = new SuggestedWords.Builder(scoreInfoList,
+                !allowsToBeAutoCorrected /* typedWordValid */,
+                autoCorrectionAvailable /* hasMinimalSuggestion */,
+                allowsToBeAutoCorrected /* allowsToBeAutoCorrected */,
+                false /* isPunctuationSuggestions */);
+        if (shouldBlockAutoCorrectionBySatefyNet) {
             builder.setShouldBlockAutoCorrectionBySafetyNet();
         }
         return builder;
@@ -542,7 +526,7 @@
 
         System.arraycopy(sortedScores, pos, sortedScores, pos + 1, prefMaxSuggestions - pos - 1);
         sortedScores[pos] = score;
-        final StringBuilder sb = StringBuilderPool.getStringBuilder(getApproxMaxWordLength());
+        final StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
         // TODO: Must pay attention to locale when changing case.
         if (mIsAllUpperCase) {
             sb.append(new String(word, offset, length).toUpperCase());
@@ -559,10 +543,7 @@
         }
         suggestions.add(pos, sb);
         if (suggestions.size() > prefMaxSuggestions) {
-            final CharSequence garbage = suggestions.remove(prefMaxSuggestions);
-            if (garbage instanceof StringBuilder) {
-                StringBuilderPool.recycle((StringBuilder)garbage);
-            }
+            suggestions.remove(prefMaxSuggestions);
         } else {
             LatinImeLogger.onAddSuggestedWord(sb.toString(), dicTypeId, dataTypeForLog);
         }
@@ -573,40 +554,22 @@
         // TODO This is almost O(n^2). Might need fix.
         // search whether the word appeared in bigram data
         int bigramSuggestSize = mBigramSuggestions.size();
-        for(int i = 0; i < bigramSuggestSize; i++) {
-            if(mBigramSuggestions.get(i).length() == length) {
+        for (int i = 0; i < bigramSuggestSize; i++) {
+            if (mBigramSuggestions.get(i).length() == length) {
                 boolean chk = true;
-                for(int j = 0; j < length; j++) {
-                    if(mBigramSuggestions.get(i).charAt(j) != word[offset+j]) {
+                for (int j = 0; j < length; j++) {
+                    if (mBigramSuggestions.get(i).charAt(j) != word[offset+j]) {
                         chk = false;
                         break;
                     }
                 }
-                if(chk) return i;
+                if (chk) return i;
             }
         }
 
         return -1;
     }
 
-    private static void collectGarbage(ArrayList<CharSequence> suggestions,
-            int prefMaxSuggestions) {
-        int poolSize = StringBuilderPool.getSize();
-        int garbageSize = suggestions.size();
-        while (poolSize < prefMaxSuggestions && garbageSize > 0) {
-            final CharSequence garbage = suggestions.get(garbageSize - 1);
-            if (garbage instanceof StringBuilder) {
-                StringBuilderPool.recycle((StringBuilder)garbage);
-                poolSize++;
-            }
-            garbageSize--;
-        }
-        if (poolSize == prefMaxSuggestions + 1) {
-            Log.w("Suggest", "String pool got too big: " + poolSize);
-        }
-        suggestions.clear();
-    }
-
     public void close() {
         final Set<Dictionary> dictionaries = new HashSet<Dictionary>();
         dictionaries.addAll(mUnigramDictionaries.values());
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index 7dd85f6..bc89941 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -16,7 +16,6 @@
 
 package com.android.inputmethod.latin;
 
-import android.text.TextUtils;
 import android.view.inputmethod.CompletionInfo;
 
 import java.util.ArrayList;
@@ -26,21 +25,25 @@
 
 public class SuggestedWords {
     public static final SuggestedWords EMPTY = new SuggestedWords(false, false, false, false,
-            Collections.<SuggestedWordInfo>emptyList());
+            false, Collections.<SuggestedWordInfo>emptyList());
 
     public final boolean mTypedWordValid;
     public final boolean mHasAutoCorrectionCandidate;
     public final boolean mIsPunctuationSuggestions;
+    public final boolean mAllowsToBeAutoCorrected;
     private final List<SuggestedWordInfo> mSuggestedWordInfoList;
 
-    SuggestedWords(boolean typedWordValid,
-            boolean hasAutoCorrectionCandidate, boolean isPunctuationSuggestions,
-            boolean shouldBlockAutoCorrectionBySafetyNet,
-            List<SuggestedWordInfo> suggestedWordInfoList) {
+    SuggestedWords(final boolean typedWordValid,
+            final boolean hasAutoCorrectionCandidate,
+            final boolean isPunctuationSuggestions,
+            final boolean shouldBlockAutoCorrectionBySafetyNet,
+            final boolean allowsToBeAutoCorrected,
+            final List<SuggestedWordInfo> suggestedWordInfoList) {
         mTypedWordValid = typedWordValid;
         mHasAutoCorrectionCandidate = hasAutoCorrectionCandidate
                 && !shouldBlockAutoCorrectionBySafetyNet;
         mIsPunctuationSuggestions = isPunctuationSuggestions;
+        mAllowsToBeAutoCorrected = allowsToBeAutoCorrected;
         mSuggestedWordInfoList = suggestedWordInfoList;
     }
 
@@ -74,44 +77,26 @@
     }
 
     public static class Builder {
-        private boolean mTypedWordValid;
-        private boolean mHasMinimalSuggestion;
-        private boolean mIsPunctuationSuggestions;
+        private final boolean mTypedWordValid;
+        private final boolean mHasMinimalSuggestion;
+        private final boolean mIsPunctuationSuggestions;
         private boolean mShouldBlockAutoCorrectionBySafetyNet;
-        private boolean mAllowsToBeAutoCorrected;
-        private boolean mHasAutoCorrection;
-        private List<SuggestedWordInfo> mSuggestedWordInfoList =
-                new ArrayList<SuggestedWordInfo>();
+        private final boolean mAllowsToBeAutoCorrected;
+        private final List<SuggestedWordInfo> mSuggestedWordInfoList;
 
-        public Builder() {
-            // Nothing to do here.
+        public Builder(final List<SuggestedWordInfo> suggestedWordInfoList,
+                final boolean typedWordValid,
+                final boolean hasMinimalSuggestion,
+                final boolean allowsToBeAutoCorrected,
+                final boolean isPunctuationSuggestions) {
+            mSuggestedWordInfoList = suggestedWordInfoList;
+            mTypedWordValid = typedWordValid;
+            mHasMinimalSuggestion = hasMinimalSuggestion;
+            mAllowsToBeAutoCorrected = allowsToBeAutoCorrected;
+            mIsPunctuationSuggestions = isPunctuationSuggestions;
         }
 
-        // TODO: the following method is a wrapper to satisfy tests. Update tests and remove it.
-        public Builder addWords(final List<CharSequence> words,
-                final List<SuggestedWordInfo> suggestedWordInfoList) {
-            return addWords(suggestedWordInfoList);
-        }
-
-        public Builder addWords(List<SuggestedWordInfo> suggestedWordInfoList) {
-            final int N = suggestedWordInfoList.size();
-            for (int i = 0; i < N; ++i) {
-                SuggestedWordInfo suggestedWordInfo = suggestedWordInfoList.get(i);
-                addWord(suggestedWordInfo.mWord, suggestedWordInfo);
-            }
-            return this;
-        }
-
-        /* package for tests */
-        Builder addWord(CharSequence word, SuggestedWordInfo suggestedWordInfo) {
-            if (!TextUtils.isEmpty(suggestedWordInfo.mWord)) {
-                // It's okay if suggestedWordInfo is null since it's checked where it's used.
-                mSuggestedWordInfoList.add(suggestedWordInfo);
-            }
-            return this;
-        }
-
-        public static List<SuggestedWordInfo> getFromCharSequenceList(
+        public static ArrayList<SuggestedWordInfo> getFromCharSequenceList(
                 final List<CharSequence> wordList) {
             final ArrayList<SuggestedWordInfo> result = new ArrayList<SuggestedWordInfo>();
             for (CharSequence word : wordList) {
@@ -129,62 +114,35 @@
             return result;
         }
 
-        public Builder setTypedWordValid(boolean typedWordValid) {
-            mTypedWordValid = typedWordValid;
-            return this;
-        }
-
-        public Builder setHasMinimalSuggestion(boolean hasMinimalSuggestion) {
-            mHasMinimalSuggestion = hasMinimalSuggestion;
-            return this;
-        }
-
-        public Builder setIsPunctuationSuggestions() {
-            mIsPunctuationSuggestions = true;
-            return this;
-        }
-
         public Builder setShouldBlockAutoCorrectionBySafetyNet() {
             mShouldBlockAutoCorrectionBySafetyNet = true;
             return this;
         }
 
-        public Builder setAllowsToBeAutoCorrected(final boolean allowsToBeAutoCorrected) {
-            mAllowsToBeAutoCorrected = allowsToBeAutoCorrected;
-            return this;
-        }
-
-        public Builder setHasAutoCorrection(final boolean hasAutoCorrection) {
-            mHasAutoCorrection = hasAutoCorrection;
-            return this;
-        }
-
         // Should get rid of the first one (what the user typed previously) from suggestions
         // and replace it with what the user currently typed.
-        public Builder addTypedWordAndPreviousSuggestions(CharSequence typedWord,
-                SuggestedWords previousSuggestions) {
-            mSuggestedWordInfoList.clear();
+        public static ArrayList<SuggestedWordInfo> getTypedWordAndPreviousSuggestions(
+                final CharSequence typedWord, final SuggestedWords previousSuggestions) {
+            final ArrayList<SuggestedWordInfo> suggestionsList = new ArrayList<SuggestedWordInfo>();
             final HashSet<String> alreadySeen = new HashSet<String>();
-            addWord(typedWord, new SuggestedWordInfo(typedWord, null, false));
+            suggestionsList.add(new SuggestedWordInfo(typedWord, null, false));
             alreadySeen.add(typedWord.toString());
             final int previousSize = previousSuggestions.size();
             for (int pos = 1; pos < previousSize; pos++) {
                 final String prevWord = previousSuggestions.getWord(pos).toString();
                 // Filter out duplicate suggestion.
                 if (!alreadySeen.contains(prevWord)) {
-                    addWord(prevWord, new SuggestedWordInfo(prevWord, null, true));
+                    suggestionsList.add(new SuggestedWordInfo(prevWord, null, true));
                     alreadySeen.add(prevWord);
                 }
             }
-            mTypedWordValid = false;
-            mHasMinimalSuggestion = false;
-            return this;
+            return suggestionsList;
         }
 
         public SuggestedWords build() {
             return new SuggestedWords(mTypedWordValid, mHasMinimalSuggestion,
                     mIsPunctuationSuggestions, mShouldBlockAutoCorrectionBySafetyNet,
-                    mSuggestedWordInfoList);
+                    mAllowsToBeAutoCorrected, mSuggestedWordInfoList);
         }
 
         public int size() {
@@ -199,10 +157,6 @@
             return mAllowsToBeAutoCorrected;
         }
 
-        public boolean hasAutoCorrection() {
-            return mHasAutoCorrection;
-        }
-
         @Override
         public String toString() {
             // Pretty-print method to help debug
@@ -216,7 +170,7 @@
     }
 
     public static class SuggestedWordInfo {
-        private final CharSequence mWord;
+        public final CharSequence mWord;
         private final CharSequence mDebugString;
         private final boolean mPreviousSuggestedWord;