am 8a961759: (-s ours) am 86ade472: Merge "Initialize SubtypeLocale from Settings (DO NOT MERGE)" into jb-dev

* commit '8a96175936e6f4cc02e4fc3cd3ae6759f0b1f86e':
  Initialize SubtypeLocale from Settings (DO NOT MERGE)
diff --git a/dictionaries/en_gb_wordlist.xml b/dictionaries/en_gb_wordlist.xml
index c2af46e..09078ae 100644
--- a/dictionaries/en_gb_wordlist.xml
+++ b/dictionaries/en_gb_wordlist.xml
@@ -1,4 +1,4 @@
-<wordlist locale="en_GB" description="English (UK)" date="1340038724" version="16">
+<wordlist locale="en_GB" description="English (UK)" date="1340965760" version="17">
  <w f="222" flags="">the</w>
  <w f="214" flags="">of</w>
  <w f="212" flags="">and</w>
@@ -256,7 +256,6 @@
  <w f="155" flags="">national</w>
  <w f="155" flags="">nice</w>
  <w f="155" flags="">non</w>
- <w f="155" flags="">north</w>
  <w f="155" flags="">period</w>
  <w f="155" flags="">son</w>
  <w f="155" flags="">south</w>
@@ -283,7 +282,6 @@
  <w f="154" flags="">present</w>
  <w f="154" flags="">produced</w>
  <w f="154" flags="">record</w>
- <w f="154" flags="">role</w>
  <w f="154" flags="">six</w>
  <w f="154" flags="">species</w>
  <w f="154" flags="">started</w>
@@ -607,6 +605,7 @@
  <w f="145" flags="">move</w>
  <w f="145" flags="">natural</w>
  <w f="145" flags="">network</w>
+ <w f="145" flags="">north</w>
  <w f="145" flags="">northern</w>
  <w f="145" flags="">novel</w>
  <w f="145" flags="">numerous</w>
@@ -708,6 +707,7 @@
  <w f="144" flags="">rate</w>
  <w f="144" flags="">recent</w>
  <w f="144" flags="">remains</w>
+ <w f="144" flags="">role</w>
  <w f="144" flags="">seat</w>
  <w f="144" flags="">self</w>
  <w f="144" flags="">shown</w>
@@ -13520,7 +13520,6 @@
  <w f="97" flags="">Kay</w>
  <w f="97" flags="">Klein</w>
  <w f="97" flags="">Kuwait</w>
- <w f="97" flags="">Lang</w>
  <w f="97" flags="">Leigh</w>
  <w f="97" flags="">Leipzig</w>
  <w f="97" flags="">Lennon</w>
@@ -14169,7 +14168,6 @@
  <w f="96" flags="">MacLeod</w>
  <w f="96" flags="">Mafia</w>
  <w f="96" flags="">Maharashtra</w>
- <w f="96" flags="">Marina</w>
  <w f="96" flags="">McCall</w>
  <w f="96" flags="">McClellan</w>
  <w f="96" flags="">McGuire</w>
@@ -15902,7 +15900,6 @@
  <w f="94" flags="">leakage</w>
  <w f="94" flags="">leaks</w>
  <w f="94" flags="">lecturing</w>
- <w f="94" flags="">lesbians</w>
  <w f="94" flags="">licences</w>
  <w f="94" flags="">life's</w>
  <w f="94" flags="">lifeboat</w>
@@ -16780,7 +16777,6 @@
  <w f="93" flags="">sclerosis</w>
  <w f="93" flags="">scream</w>
  <w f="93" flags="">screenplays</w>
- <w f="93" flags="">screws</w>
  <w f="93" flags="">seaport</w>
  <w f="93" flags="">seawater</w>
  <w f="93" flags="">secretaries</w>
@@ -29553,7 +29549,6 @@
  <w f="79" flags="">Getty</w>
  <w f="79" flags="">Gleason</w>
  <w f="79" flags="">Godwin</w>
- <w f="79" flags="">Gotha</w>
  <w f="79" flags="">Grantham</w>
  <w f="79" flags="">Greenpeace</w>
  <w f="79" flags="">Grenoble</w>
@@ -32722,7 +32717,6 @@
  <w f="77" flags="">leeward</w>
  <w f="77" flags="">lengthwise</w>
  <w f="77" flags="">lentils</w>
- <w f="77" flags="">lesbianism</w>
  <w f="77" flags="">leveraging</w>
  <w f="77" flags="">lib</w>
  <w f="77" flags="">libertarianism</w>
@@ -49865,7 +49859,6 @@
  <w f="64" flags="abbreviation">ECW's</w>
  <w f="64" flags="abbreviation">EEOC</w>
  <w f="64" flags="abbreviation">EKG</w>
- <w f="64" flags="abbreviation">ENS</w>
  <w f="64" flags="abbreviation">ESOL</w>
  <w f="64" flags="">Edgeworth</w>
  <w f="64" flags="">Edirne</w>
@@ -54575,7 +54568,6 @@
  <w f="61" flags="">Hersey</w>
  <w f="61" flags="">Hiatt</w>
  <w f="61" flags="">Himmler's</w>
- <w f="61" flags="">Hoke</w>
  <w f="61" flags="">Hormuz</w>
  <w f="61" flags="">Hosea</w>
  <w f="61" flags="">Hubli</w>
@@ -89915,7 +89907,6 @@
  <w f="38" flags="">Hally</w>
  <w f="38" flags="">Harpo's</w>
  <w f="38" flags="">Higbee's</w>
- <w f="38" flags="">Hoke's</w>
  <w f="38" flags="">Honeywell's</w>
  <w f="38" flags="">Horatian</w>
  <w f="38" flags="">Iago's</w>
@@ -109454,7 +109445,6 @@
  <w f="25" flags="">leets</w>
  <w f="25" flags="">legitimism</w>
  <w f="25" flags="">leopardskin</w>
- <w f="25" flags="">lesbian's</w>
  <w f="25" flags="">lessee's</w>
  <w f="25" flags="">lethargically</w>
  <w f="25" flags="">libber</w>
@@ -112568,7 +112558,6 @@
  <w f="23" flags="">pickerels</w>
  <w f="23" flags="">piecers</w>
  <w f="23" flags="">pigmenting</w>
- <w f="23" flags="">pill's</w>
  <w f="23" flags="">pilled</w>
  <w f="23" flags="">pillorying</w>
  <w f="23" flags="">pinked</w>
@@ -117332,7 +117321,6 @@
  <w f="18" flags="">Hinsdale's</w>
  <w f="18" flags="">Hispaniola's</w>
  <w f="18" flags="">Hohenzollern's</w>
- <w f="18" flags="">Hokes</w>
  <w f="18" flags="">Horst's</w>
  <w f="18" flags="">Housecat's</w>
  <w f="18" flags="">Hughie's</w>
@@ -155568,6 +155556,7 @@
  <w f="0" flags="">XP</w>
  <w f="0" flags="offensive">Yoni's</w>
  <w f="0" flags="">acct</w>
+ <w f="0">admin</w>
  <w f="0" flags="n">adult</w>
  <w f="0" flags="medical">adulteress</w>
  <w f="0" flags="medical">adulteresses</w>
@@ -156092,6 +156081,9 @@
  <w f="0" flags="">lei</w>
  <w f="0" flags="">lem</w>
  <w f="0" flags="n">lesbian</w>
+ <w f="0" flags="">lesbian's</w>
+ <w f="0" flags="">lesbianism</w>
+ <w f="0" flags="">lesbians</w>
  <w f="0" flags="offensive">letch</w>
  <w f="0" flags="n">libido</w>
  <w f="0" flags="s">librium</w>
@@ -156267,6 +156259,7 @@
  <w f="0" flags="medical">phosphaturia</w>
  <w f="0" flags="medical">phosphaturic</w>
  <w f="0" flags="s">pill</w>
+ <w f="0" flags="">pill's</w>
  <w f="0" flags="s">pills</w>
  <w f="0" flags="offensive">pimp</w>
  <w f="0" flags="offensive">pimp's</w>
@@ -156428,6 +156421,7 @@
  <w f="0" flags="r">screw</w>
  <w f="0" flags="n">screwed</w>
  <w f="0" flags="n">screwing</w>
+ <w f="0" flags="">screws</w>
  <w f="0" flags="medical">scrota</w>
  <w f="0" flags="medical">scrotal</w>
  <w f="0" flags="medical">scrotum</w>
@@ -156641,6 +156635,7 @@
  <w f="0" flags="babytalk">twat</w>
  <w f="0" flags="babytalk">twats</w>
  <w f="0" flags="">twit</w>
+ <w f="0">ui</w>
  <w f="0">ull</w>
  <w f="0" flags="babytalk">underclothing</w>
  <w f="0" flags="babytalk">underwear</w>
@@ -156747,6 +156742,7 @@
  <w f="0" flags="medical">uterus</w>
  <w f="0" flags="medical">uterus's</w>
  <w f="0" flags="medical">uteruses</w>
+ <w f="0">ux</w>
  <w f="0" flags="medical">vagina</w>
  <w f="0" flags="medical">vagina's</w>
  <w f="0" flags="medical">vaginae</w>
diff --git a/dictionaries/en_us_wordlist.xml b/dictionaries/en_us_wordlist.xml
index 3cafbd7..0b158f5 100644
--- a/dictionaries/en_us_wordlist.xml
+++ b/dictionaries/en_us_wordlist.xml
@@ -1,4 +1,4 @@
-<wordlist locale="en_US" description="English (US)" date="1340038693" version="16">
+<wordlist locale="en_US" description="English (US)" date="1340965726" version="17">
  <w f="222" flags="">the</w>
  <w f="214" flags="">of</w>
  <w f="212" flags="">and</w>
@@ -255,7 +255,6 @@
  <w f="155" flags="">national</w>
  <w f="155" flags="">nice</w>
  <w f="155" flags="">non</w>
- <w f="155" flags="">north</w>
  <w f="155" flags="">period</w>
  <w f="155" flags="">son</w>
  <w f="155" flags="">south</w>
@@ -282,7 +281,6 @@
  <w f="154" flags="">present</w>
  <w f="154" flags="">produced</w>
  <w f="154" flags="">record</w>
- <w f="154" flags="">role</w>
  <w f="154" flags="">six</w>
  <w f="154" flags="">species</w>
  <w f="154" flags="">started</w>
@@ -587,7 +585,6 @@
  <w f="145" flags="">July</w>
  <w f="145" flags="">June</w>
  <w f="145" flags="">March</w>
- <w f="145" flags="">North</w>
  <w f="145" flags="">October</w>
  <w f="145" flags="">active</w>
  <w f="145" flags="">always</w>
@@ -614,6 +611,7 @@
  <w f="145" flags="">move</w>
  <w f="145" flags="">natural</w>
  <w f="145" flags="">network</w>
+ <w f="145" flags="">north</w>
  <w f="145" flags="">northern</w>
  <w f="145" flags="">novel</w>
  <w f="145" flags="">numerous</w>
@@ -717,6 +715,7 @@
  <w f="144" flags="">rate</w>
  <w f="144" flags="">recent</w>
  <w f="144" flags="">remains</w>
+ <w f="144" flags="">role</w>
  <w f="144" flags="">seat</w>
  <w f="144" flags="">self</w>
  <w f="144" flags="">shown</w>
@@ -12069,6 +12068,7 @@
  <w f="100" flags="">Nash</w>
  <w f="100" flags="">Newark</w>
  <w f="100" flags="">Norse</w>
+ <w f="100" flags="">North</w>
  <w f="100" flags="">Norton</w>
  <w f="100" flags="">Norwich</w>
  <w f="100" flags="">Okinawa</w>
@@ -13996,7 +13996,6 @@
  <w f="97" flags="">Klein</w>
  <w f="97" flags="">Kuwait</w>
  <w f="97" flags="">Ladies</w>
- <w f="97" flags="">Lang</w>
  <w f="97" flags="">Leigh</w>
  <w f="97" flags="">Leipzig</w>
  <w f="97" flags="">Lennon</w>
@@ -14677,7 +14676,6 @@
  <w f="96" flags="">Madras</w>
  <w f="96" flags="">Mafia</w>
  <w f="96" flags="">Maharashtra</w>
- <w f="96" flags="">Marina</w>
  <w f="96" flags="">Master's</w>
  <w f="96" flags="">McCall</w>
  <w f="96" flags="">McClellan</w>
@@ -16141,7 +16139,6 @@
  <w f="94" flags="">Stratford</w>
  <w f="94" flags="">Strauss</w>
  <w f="94" flags="">Stuttgart</w>
- <w f="94" flags="">Sunshine</w>
  <w f="94" flags="">Suzuki</w>
  <w f="94" flags="">Taiwanese</w>
  <w f="94" flags="">Talbot</w>
@@ -16154,7 +16151,6 @@
  <w f="94" flags="">Tyne</w>
  <w f="94" flags="abbreviation">UNC</w>
  <w f="94" flags="abbreviation">UNICEF</w>
- <w f="94" flags="">UNIX</w>
  <w f="94" flags="abbreviation">USDA</w>
  <w f="94" flags="">Unix</w>
  <w f="94" flags="">Valentine</w>
@@ -16488,7 +16484,6 @@
  <w f="94" flags="">leakage</w>
  <w f="94" flags="">leaks</w>
  <w f="94" flags="">lecturing</w>
- <w f="94" flags="">lesbians</w>
  <w f="94" flags="">leveled</w>
  <w f="94" flags="">life's</w>
  <w f="94" flags="">lifeboat</w>
@@ -17402,7 +17397,6 @@
  <w f="93" flags="">sclerosis</w>
  <w f="93" flags="">scream</w>
  <w f="93" flags="">screenplays</w>
- <w f="93" flags="">screws</w>
  <w f="93" flags="">seaport</w>
  <w f="93" flags="">seawater</w>
  <w f="93" flags="">secretaries</w>
@@ -30682,7 +30676,6 @@
  <w f="79" flags="">Ghats</w>
  <w f="79" flags="">Gleason</w>
  <w f="79" flags="">Godwin</w>
- <w f="79" flags="">Gotha</w>
  <w f="79" flags="">Grantham</w>
  <w f="79" flags="">Greenpeace</w>
  <w f="79" flags="">Grenoble</w>
@@ -33954,7 +33947,6 @@
  <w f="77" flags="">leeward</w>
  <w f="77" flags="">lengthwise</w>
  <w f="77" flags="">lentils</w>
- <w f="77" flags="">lesbianism</w>
  <w f="77" flags="">leveraging</w>
  <w f="77" flags="">lib</w>
  <w f="77" flags="">libertarianism</w>
@@ -92910,7 +92902,6 @@
  <w f="38" flags="">Harpo's</w>
  <w f="38" flags="">Higbee's</w>
  <w f="38">Hipparchus</w>
- <w f="38" flags="">Hoke's</w>
  <w f="38" flags="">Honeywell's</w>
  <w f="38" flags="">Horatian</w>
  <w f="38" flags="">Iago's</w>
@@ -112796,7 +112787,6 @@
  <w f="25" flags="">leets</w>
  <w f="25" flags="">legitimism</w>
  <w f="25" flags="">leopardskin</w>
- <w f="25" flags="">lesbian's</w>
  <w f="25" flags="">lessee's</w>
  <w f="25" flags="">lethargically</w>
  <w f="25" flags="">libber</w>
@@ -115952,7 +115942,6 @@
  <w f="23" flags="">pickerels</w>
  <w f="23" flags="">piecers</w>
  <w f="23" flags="">pigmenting</w>
- <w f="23" flags="">pill's</w>
  <w f="23" flags="">pilled</w>
  <w f="23" flags="">pillorying</w>
  <w f="23" flags="">pinked</w>
@@ -120765,7 +120754,6 @@
  <w f="18" flags="">Hinsdale's</w>
  <w f="18" flags="">Hispaniola's</w>
  <w f="18" flags="">Hohenzollern's</w>
- <w f="18" flags="">Hokes</w>
  <w f="18" flags="">Horst's</w>
  <w f="18" flags="">Housecat's</w>
  <w f="18" flags="">Hughie's</w>
@@ -159318,6 +159306,7 @@
  <w f="0" flags="">XP</w>
  <w f="0" flags="offensive">Yoni's</w>
  <w f="0" flags="">acct</w>
+ <w f="0">admin</w>
  <w f="0" flags="n">adult</w>
  <w f="0" flags="medical">adulteress</w>
  <w f="0" flags="medical">adulteresses</w>
@@ -159849,6 +159838,9 @@
  <w f="0" flags="medical">leching</w>
  <w f="0" flags="">lei</w>
  <w f="0" flags="n">lesbian</w>
+ <w f="0" flags="">lesbian's</w>
+ <w f="0" flags="">lesbianism</w>
+ <w f="0" flags="">lesbians</w>
  <w f="0" flags="offensive">letch</w>
  <w f="0" flags="n">libido</w>
  <w f="0" flags="n">lick</w>
@@ -160024,6 +160016,7 @@
  <w f="0" flags="medical">phosphaturia</w>
  <w f="0" flags="medical">phosphaturic</w>
  <w f="0" flags="s">pill</w>
+ <w f="0" flags="">pill's</w>
  <w f="0" flags="s">pills</w>
  <w f="0" flags="offensive">pimp</w>
  <w f="0" flags="offensive">pimp's</w>
@@ -160183,6 +160176,7 @@
  <w f="0" flags="r">screw</w>
  <w f="0" flags="n">screwed</w>
  <w f="0" flags="n">screwing</w>
+ <w f="0" flags="">screws</w>
  <w f="0" flags="medical">scrota</w>
  <w f="0" flags="medical">scrotal</w>
  <w f="0" flags="medical">scrotum</w>
@@ -160398,6 +160392,7 @@
  <w f="0" flags="babytalk">twat</w>
  <w f="0" flags="babytalk">twats</w>
  <w f="0" flags="">twit</w>
+ <w f="0">ui</w>
  <w f="0">ull</w>
  <w f="0" flags="">ump</w>
  <w f="0" flags="babytalk">underclothing</w>
@@ -160505,6 +160500,7 @@
  <w f="0" flags="medical">uterus</w>
  <w f="0" flags="medical">uterus's</w>
  <w f="0" flags="medical">uteruses</w>
+ <w f="0">ux</w>
  <w f="0" flags="medical">vagina</w>
  <w f="0" flags="medical">vagina's</w>
  <w f="0" flags="medical">vaginae</w>
diff --git a/dictionaries/en_wordlist.xml b/dictionaries/en_wordlist.xml
index 6f594d5..c6b3bdc 100644
--- a/dictionaries/en_wordlist.xml
+++ b/dictionaries/en_wordlist.xml
@@ -1,4 +1,4 @@
-<wordlist locale="en" description="English" date="1340038727" version="16">
+<wordlist locale="en" description="English" date="1340965763" version="17">
  <w f="222" flags="">the</w>
  <w f="214" flags="">of</w>
  <w f="212" flags="">and</w>
@@ -255,7 +255,6 @@
  <w f="155" flags="">national</w>
  <w f="155" flags="">nice</w>
  <w f="155" flags="">non</w>
- <w f="155" flags="">north</w>
  <w f="155" flags="">period</w>
  <w f="155" flags="">son</w>
  <w f="155" flags="">south</w>
@@ -282,7 +281,6 @@
  <w f="154" flags="">present</w>
  <w f="154" flags="">produced</w>
  <w f="154" flags="">record</w>
- <w f="154" flags="">role</w>
  <w f="154" flags="">six</w>
  <w f="154" flags="">species</w>
  <w f="154" flags="">started</w>
@@ -587,7 +585,6 @@
  <w f="145" flags="">July</w>
  <w f="145" flags="">June</w>
  <w f="145" flags="">March</w>
- <w f="145" flags="">North</w>
  <w f="145" flags="">October</w>
  <w f="145" flags="">active</w>
  <w f="145" flags="">always</w>
@@ -614,6 +611,7 @@
  <w f="145" flags="">move</w>
  <w f="145" flags="">natural</w>
  <w f="145" flags="">network</w>
+ <w f="145" flags="">north</w>
  <w f="145" flags="">northern</w>
  <w f="145" flags="">novel</w>
  <w f="145" flags="">numerous</w>
@@ -718,6 +716,7 @@
  <w f="144" flags="">rate</w>
  <w f="144" flags="">recent</w>
  <w f="144" flags="">remains</w>
+ <w f="144" flags="">role</w>
  <w f="144" flags="">seat</w>
  <w f="144" flags="">self</w>
  <w f="144" flags="">shown</w>
@@ -12162,6 +12161,7 @@
  <w f="100" flags="">Nash</w>
  <w f="100" flags="">Newark</w>
  <w f="100" flags="">Norse</w>
+ <w f="100" flags="">North</w>
  <w f="100" flags="">Norton</w>
  <w f="100" flags="">Norwich</w>
  <w f="100" flags="">Okinawa</w>
@@ -14103,7 +14103,6 @@
  <w f="97" flags="">Klein</w>
  <w f="97" flags="">Kuwait</w>
  <w f="97" flags="">Ladies</w>
- <w f="97" flags="">Lang</w>
  <w f="97" flags="">Leigh</w>
  <w f="97" flags="">Leipzig</w>
  <w f="97" flags="">Lennon</w>
@@ -14792,7 +14791,6 @@
  <w f="96" flags="">Madras</w>
  <w f="96" flags="">Mafia</w>
  <w f="96" flags="">Maharashtra</w>
- <w f="96" flags="">Marina</w>
  <w f="96" flags="">Master's</w>
  <w f="96" flags="">McCall</w>
  <w f="96" flags="">McClellan</w>
@@ -16273,7 +16271,6 @@
  <w f="94" flags="">Stratford</w>
  <w f="94" flags="">Strauss</w>
  <w f="94" flags="">Stuttgart</w>
- <w f="94" flags="">Sunshine</w>
  <w f="94" flags="">Suzuki</w>
  <w f="94" flags="abbreviation">TBS</w>
  <w f="94" flags="">Taiwanese</w>
@@ -16287,7 +16284,6 @@
  <w f="94" flags="">Tyne</w>
  <w f="94" flags="abbreviation">UNC</w>
  <w f="94" flags="abbreviation">UNICEF</w>
- <w f="94" flags="">UNIX</w>
  <w f="94" flags="abbreviation">USDA</w>
  <w f="94" flags="">Unix</w>
  <w f="94" flags="">Valentine</w>
@@ -16624,7 +16620,6 @@
  <w f="94" flags="">leakage</w>
  <w f="94" flags="">leaks</w>
  <w f="94" flags="">lecturing</w>
- <w f="94" flags="">lesbians</w>
  <w f="94" flags="">leveled</w>
  <w f="94" flags="">licences</w>
  <w f="94" flags="">life's</w>
@@ -17555,7 +17550,6 @@
  <w f="93" flags="">sclerosis</w>
  <w f="93" flags="">scream</w>
  <w f="93" flags="">screenplays</w>
- <w f="93" flags="">screws</w>
  <w f="93" flags="">seaport</w>
  <w f="93" flags="">seawater</w>
  <w f="93" flags="">secretaries</w>
@@ -31073,7 +31067,6 @@
  <w f="79" flags="">Ghats</w>
  <w f="79" flags="">Gleason</w>
  <w f="79" flags="">Godwin</w>
- <w f="79" flags="">Gotha</w>
  <w f="79" flags="">Grantham</w>
  <w f="79" flags="">Greenpeace</w>
  <w f="79" flags="">Grenoble</w>
@@ -34402,7 +34395,6 @@
  <w f="77" flags="">leeward</w>
  <w f="77" flags="">lengthwise</w>
  <w f="77" flags="">lentils</w>
- <w f="77" flags="">lesbianism</w>
  <w f="77" flags="">leveraging</w>
  <w f="77" flags="">lib</w>
  <w f="77" flags="">libertarianism</w>
@@ -52519,7 +52511,6 @@
  <w f="64" flags="abbreviation">ECW's</w>
  <w f="64" flags="abbreviation">EEOC</w>
  <w f="64" flags="abbreviation">EKG</w>
- <w f="64" flags="abbreviation">ENS</w>
  <w f="64" flags="abbreviation">ESOL</w>
  <w f="64" flags="">Edgeworth</w>
  <w f="64" flags="">Edirne</w>
@@ -57486,7 +57477,6 @@
  <w f="61" flags="">Hersey</w>
  <w f="61" flags="">Hiatt</w>
  <w f="61" flags="">Himmler's</w>
- <w f="61" flags="">Hoke</w>
  <w f="61" flags="">Hormuz</w>
  <w f="61" flags="">Hosea</w>
  <w f="61" flags="">Hubli</w>
@@ -94993,7 +94983,6 @@
  <w f="38" flags="">Harpo's</w>
  <w f="38" flags="">Higbee's</w>
  <w f="38">Hipparchus</w>
- <w f="38" flags="">Hoke's</w>
  <w f="38" flags="">Honeywell's</w>
  <w f="38" flags="">Horatian</w>
  <w f="38" flags="">Iago's</w>
@@ -115756,7 +115745,6 @@
  <w f="25" flags="">leets</w>
  <w f="25" flags="">legitimism</w>
  <w f="25" flags="">leopardskin</w>
- <w f="25" flags="">lesbian's</w>
  <w f="25" flags="">lessee's</w>
  <w f="25" flags="">lethargically</w>
  <w f="25" flags="">libber</w>
@@ -119045,7 +119033,6 @@
  <w f="23" flags="">pickerels</w>
  <w f="23" flags="">piecers</w>
  <w f="23" flags="">pigmenting</w>
- <w f="23" flags="">pill's</w>
  <w f="23" flags="">pilled</w>
  <w f="23" flags="">pillorying</w>
  <w f="23" flags="">pinked</w>
@@ -124070,7 +124057,6 @@
  <w f="18" flags="">Hinsdale's</w>
  <w f="18" flags="">Hispaniola's</w>
  <w f="18" flags="">Hohenzollern's</w>
- <w f="18" flags="">Hokes</w>
  <w f="18" flags="">Horst's</w>
  <w f="18" flags="">Housecat's</w>
  <w f="18" flags="">Hughie's</w>
@@ -164106,6 +164092,7 @@
  <w f="0" flags="">XP</w>
  <w f="0" flags="offensive">Yoni's</w>
  <w f="0" flags="">acct</w>
+ <w f="0">admin</w>
  <w f="0" flags="n">adult</w>
  <w f="0" flags="medical">adulteress</w>
  <w f="0" flags="medical">adulteresses</w>
@@ -164656,6 +164643,9 @@
  <w f="0" flags="">lei</w>
  <w f="0" flags="">lem</w>
  <w f="0" flags="n">lesbian</w>
+ <w f="0" flags="">lesbian's</w>
+ <w f="0" flags="">lesbianism</w>
+ <w f="0" flags="">lesbians</w>
  <w f="0" flags="offensive">letch</w>
  <w f="0" flags="n">libido</w>
  <w f="0" flags="s">librium</w>
@@ -164842,6 +164832,7 @@
  <w f="0" flags="medical">phosphaturia</w>
  <w f="0" flags="medical">phosphaturic</w>
  <w f="0" flags="s">pill</w>
+ <w f="0" flags="">pill's</w>
  <w f="0" flags="s">pills</w>
  <w f="0" flags="offensive">pimp</w>
  <w f="0" flags="offensive">pimp's</w>
@@ -165004,6 +164995,7 @@
  <w f="0" flags="r">screw</w>
  <w f="0" flags="n">screwed</w>
  <w f="0" flags="n">screwing</w>
+ <w f="0" flags="">screws</w>
  <w f="0" flags="medical">scrota</w>
  <w f="0" flags="medical">scrotal</w>
  <w f="0" flags="medical">scrotum</w>
@@ -165226,6 +165218,7 @@
  <w f="0" flags="babytalk">twat</w>
  <w f="0" flags="babytalk">twats</w>
  <w f="0" flags="">twit</w>
+ <w f="0">ui</w>
  <w f="0">ull</w>
  <w f="0" flags="">ump</w>
  <w f="0" flags="babytalk">underclothing</w>
@@ -165335,6 +165328,7 @@
  <w f="0" flags="medical">uterus</w>
  <w f="0" flags="medical">uterus's</w>
  <w f="0" flags="medical">uteruses</w>
+ <w f="0">ux</w>
  <w f="0" flags="medical">vagina</w>
  <w f="0" flags="medical">vagina's</w>
  <w f="0" flags="medical">vaginae</w>
diff --git a/dictionaries/fr_wordlist.xml b/dictionaries/fr_wordlist.xml
index 3990988..6053f48 100644
--- a/dictionaries/fr_wordlist.xml
+++ b/dictionaries/fr_wordlist.xml
@@ -1,4 +1,4 @@
-<wordlist locale="fr" description="Français" date="1339787661" version="15" options="french_ligature_processing">
+<wordlist locale="fr" description="Français" date="1340965148" version="17" options="french_ligature_processing">
  <w f="209" flags="">de</w>
  <w f="200" flags="">la</w>
  <w f="197" flags="">et</w>
@@ -1069,6 +1069,7 @@
  <w f="125" flags="">finit</w>
  <w f="125" flags="">fleuve</w>
  <w f="125" flags="">fondateur</w>
+ <w f="125">généralement</w>
  <w f="125" flags="">hors</w>
  <w f="125" flags="">ici</w>
  <w f="125" flags="">importantes</w>
@@ -13168,7 +13169,6 @@
  <w f="90" flags="">play-offs</w>
  <w f="90" flags="">plaça</w>
  <w f="90" flags="">plonger</w>
- <w f="90" flags="">pole</w>
  <w f="90" flags="">polonaises</w>
  <w f="90" flags="">polymères</w>
  <w f="90" flags="">popularisé</w>
@@ -15098,7 +15098,6 @@
  <w f="87" flags="">donneur</w>
  <w f="87" flags="">dorés</w>
  <w f="87" flags="">doucement</w>
- <w f="87" flags="">doyenné</w>
  <w f="87" flags="">drames</w>
  <w f="87" flags="">dualité</w>
  <w f="87" flags="">ducale</w>
@@ -20845,7 +20844,6 @@
  <w f="81" flags="">matérialisme</w>
  <w f="81" flags="">mauve</w>
  <w f="81" flags="">maximiser</w>
- <w f="81" flags="">media</w>
  <w f="81" flags="">melon</w>
  <w f="81" flags="">mendiants</w>
  <w f="81" flags="">menuiserie</w>
@@ -21702,7 +21700,6 @@
  <w f="80" flags="">gallo-romains</w>
  <w f="80" flags="">galop</w>
  <w f="80" flags="">gangster</w>
- <w f="80" flags="">general</w>
  <w f="80" flags="">gentilé</w>
  <w f="80" flags="">gewurztraminer</w>
  <w f="80" flags="">gisant</w>
@@ -24213,7 +24210,6 @@
  <w f="78" flags="">mythologies</w>
  <w f="78" flags="">mâchicoulis</w>
  <w f="78" flags="">méconnue</w>
- <w f="78" flags="">médina</w>
  <w f="78" flags="">mésopotamienne</w>
  <w f="78" flags="">métriques</w>
  <w f="78" flags="">n'apprécie</w>
@@ -26343,7 +26339,6 @@
  <w f="76" flags="">graphème</w>
  <w f="76" flags="">gravitationnelles</w>
  <w f="76" flags="">greffer</w>
- <w f="76" flags="">guérilleros</w>
  <w f="76" flags="">gèle</w>
  <w f="76" flags="">génitales</w>
  <w f="76" flags="">généalogiques</w>
@@ -31797,7 +31792,6 @@
  <w f="72" flags="">gouvernait</w>
  <w f="72" flags="">gouvernant</w>
  <w f="72" flags="">goéland</w>
- <w f="72" flags="">grace</w>
  <w f="72" flags="">groove</w>
  <w f="72" flags="">gymnastes</w>
  <w f="72" flags="">génomes</w>
@@ -32178,7 +32172,6 @@
  <w f="72" flags="">polders</w>
  <w f="72" flags="">poliomyélite</w>
  <w f="72" flags="">polychromie</w>
- <w f="72" flags="">polynomiale</w>
  <w f="72" flags="">polyvalents</w>
  <w f="72" flags="">pondus</w>
  <w f="72" flags="">pool</w>
@@ -33103,7 +33096,6 @@
  <w f="71" flags="">d'Austin</w>
  <w f="71" flags="">d'Avalon</w>
  <w f="71" flags="">d'Azov</w>
- <w f="71" flags="">d'Aïn</w>
  <w f="71" flags="">d'Emily</w>
  <w f="71" flags="">d'Esther</w>
  <w f="71" flags="">d'Halicarnasse</w>
@@ -33402,7 +33394,6 @@
  <w f="71" flags="">imaginés</w>
  <w f="71" flags="">immigrant</w>
  <w f="71" flags="">imparfait</w>
- <w f="71" flags="">imperium</w>
  <w f="71" flags="">implicites</w>
  <w f="71" flags="">importateur</w>
  <w f="71" flags="">imposable</w>
@@ -33808,7 +33799,6 @@
  <w f="71" flags="">rasées</w>
  <w f="71" flags="">rationaliser</w>
  <w f="71" flags="">rayés</w>
- <w f="71" flags="">real</w>
  <w f="71" flags="">rebaptiser</w>
  <w f="71" flags="">rebâti</w>
  <w f="71" flags="">recensant</w>
@@ -34659,7 +34649,6 @@
  <w f="70" flags="">cénotaphe</w>
  <w f="70" flags="">d'Achaïe</w>
  <w f="70" flags="">d'Agrippa</w>
- <w f="70" flags="">d'Al-Qaida</w>
  <w f="70" flags="">d'Alabama</w>
  <w f="70" flags="">d'Albon</w>
  <w f="70" flags="">d'Alessandro</w>
@@ -34674,7 +34663,6 @@
  <w f="70" flags="">d'Argos</w>
  <w f="70" flags="">d'Assouan</w>
  <w f="70" flags="">d'Avaugour</w>
- <w f="70" flags="">d'Avila</w>
  <w f="70" flags="">d'Azincourt</w>
  <w f="70" flags="">d'East</w>
  <w f="70" flags="">d'Edmund</w>
@@ -34788,7 +34776,6 @@
  <w f="70" flags="">diva</w>
  <w f="70" flags="">diverge</w>
  <w f="70" flags="">divergents</w>
- <w f="70" flags="">djebel</w>
  <w f="70" flags="">dominical</w>
  <w f="70" flags="">dopamine</w>
  <w f="70" flags="">dopé</w>
@@ -36252,7 +36239,6 @@
  <w f="69" flags="">concevant</w>
  <w f="69" flags="">concurrencée</w>
  <w f="69" flags="">condense</w>
- <w f="69" flags="">condottiere</w>
  <w f="69" flags="">confidente</w>
  <w f="69" flags="">confine</w>
  <w f="69" flags="">conjonctif</w>
@@ -36274,7 +36260,6 @@
  <w f="69" flags="">coqueluche</w>
  <w f="69" flags="">corda</w>
  <w f="69" flags="">cordiales</w>
- <w f="69" flags="">core</w>
  <w f="69" flags="">corpulence</w>
  <w f="69" flags="">corrosif</w>
  <w f="69" flags="">cottage</w>
@@ -40119,7 +40104,6 @@
  <w f="67" flags="">l'Estrie</w>
  <w f="67" flags="abbreviation">l'IFK</w>
  <w f="67" flags="abbreviation">l'INSEP</w>
- <w f="67" flags="">l'Imperial</w>
  <w f="67" flags="">l'Indien</w>
  <w f="67" flags="">l'Istrie</w>
  <w f="67" flags="abbreviation">l'OSCE</w>
@@ -40246,7 +40230,6 @@
  <w f="67" flags="">mayonnaise</w>
  <w f="67" flags="">maîtrisait</w>
  <w f="67" flags="">maîtrises</w>
- <w f="67" flags="">medium</w>
  <w f="67" flags="">melons</w>
  <w f="67" flags="">membranaires</w>
  <w f="67" flags="">menottes</w>
@@ -41573,7 +41556,6 @@
  <w f="66" flags="">d'Auxonne</w>
  <w f="66" flags="">d'Avellino</w>
  <w f="66" flags="">d'Eddy</w>
- <w f="66" flags="">d'Eden</w>
  <w f="66" flags="">d'Erich</w>
  <w f="66" flags="">d'Eschyle</w>
  <w f="66" flags="">d'Essen</w>
@@ -42181,7 +42163,6 @@
  <w f="66" flags="">mazout</w>
  <w f="66" flags="">maçonné</w>
  <w f="66" flags="">mec</w>
- <w f="66" flags="">medias</w>
  <w f="66" flags="">menaçants</w>
  <w f="66" flags="">mensurations</w>
  <w f="66" flags="">merchandising</w>
@@ -42343,7 +42324,6 @@
  <w f="66" flags="">plénipotentiaires</w>
  <w f="66" flags="">pléthore</w>
  <w f="66" flags="">poignarder</w>
- <w f="66" flags="">polynomial</w>
  <w f="66" flags="">polynésiens</w>
  <w f="66" flags="">pont-canal</w>
  <w f="66" flags="">popularisa</w>
@@ -44095,7 +44075,6 @@
  <w f="65" flags="">manipulent</w>
  <w f="65" flags="">manquèrent</w>
  <w f="65" flags="">manqués</w>
- <w f="65" flags="">maqam</w>
  <w f="65" flags="">marchaient</w>
  <w f="65" flags="">marginalisé</w>
  <w f="65" flags="">marre</w>
@@ -44231,7 +44210,6 @@
  <w f="65" flags="">pensais</w>
  <w f="65" flags="">pensante</w>
  <w f="65" flags="">perdrait</w>
- <w f="65" flags="">perestroïka</w>
  <w f="65" flags="">perfide</w>
  <w f="65" flags="">perméables</w>
  <w f="65" flags="">perpétuant</w>
@@ -45576,7 +45554,6 @@
  <w f="64" flags="">dormance</w>
  <w f="64" flags="">dormi</w>
  <w f="64" flags="">doublent</w>
- <w f="64" flags="">doyennés</w>
  <w f="64" flags="">dragueur</w>
  <w f="64" flags="">dragueurs</w>
  <w f="64" flags="">drainées</w>
@@ -45803,7 +45780,6 @@
  <w f="64" flags="">herbicide</w>
  <w f="64" flags="">heurtèrent</w>
  <w f="64" flags="">hiboux</w>
- <w f="64" flags="">hindî</w>
  <w f="64" flags="">hiragana</w>
  <w f="64" flags="">hivernaux</w>
  <w f="64" flags="">hivernent</w>
@@ -46826,6 +46802,7 @@
  <w f="63" flags="">Dyck</w>
  <w f="63" flags="">Délos</w>
  <w f="63" flags="">Eaton</w>
+ <w f="63" flags="">El-Kébir</w>
  <w f="63" flags="">Elgar</w>
  <w f="63" flags="">Emmanuel-Philibert</w>
  <w f="63" flags="">Erasmus</w>
@@ -47645,7 +47622,6 @@
  <w f="63" flags="">déterrer</w>
  <w f="63" flags="">efficiente</w>
  <w f="63" flags="">effroi</w>
- <w f="63" flags="">el-Kébir</w>
  <w f="63" flags="">embarcadère</w>
  <w f="63" flags="">embarquement</w>
  <w f="63" flags="">embusqués</w>
@@ -50498,7 +50474,6 @@
  <w f="62" flags="">pointures</w>
  <w f="62" flags="">politicienne</w>
  <w f="62" flags="">polyarthrite</w>
- <w f="62" flags="">polynomiales</w>
  <w f="62" flags="">polythéiste</w>
  <w f="62" flags="">ponctue</w>
  <w f="62" flags="">pondu</w>
@@ -52102,7 +52077,6 @@
  <w f="61" flags="">fécondée</w>
  <w f="61" flags="">fétichistes</w>
  <w f="61" flags="">fêta</w>
- <w f="61" flags="">galle</w>
  <w f="61" flags="">gallons</w>
  <w f="61" flags="">gamelan</w>
  <w f="61" flags="">gantois</w>
@@ -52406,7 +52380,6 @@
  <w f="61" flags="">lycanthropes</w>
  <w f="61" flags="">lymphe</w>
  <w f="61" flags="">lyrics</w>
- <w f="61" flags="">länder</w>
  <w f="61" flags="">m'appelle</w>
  <w f="61" flags="">madriers</w>
  <w f="61" flags="">mafieuses</w>
@@ -52462,7 +52435,6 @@
  <w f="61" flags="">modifiables</w>
  <w f="61" flags="">monade</w>
  <w f="61" flags="">monophysisme</w>
- <w f="61" flags="">mons</w>
  <w f="61" flags="">montgolfières</w>
  <w f="61" flags="">montreront</w>
  <w f="61" flags="">monégasques</w>
@@ -53890,7 +53862,6 @@
  <w f="60" flags="">couteuse</w>
  <w f="60" flags="">craignit</w>
  <w f="60" flags="">cramoisi</w>
- <w f="60" flags="">crane</w>
  <w f="60" flags="">cravates</w>
  <w f="60" flags="">cristallisée</w>
  <w f="60" flags="">crochu</w>
@@ -54259,7 +54230,6 @@
  <w f="60" flags="">gazettes</w>
  <w f="60" flags="">gaziers</w>
  <w f="60" flags="">gazéification</w>
- <w f="60" flags="">gaëlique</w>
  <w f="60" flags="">geai</w>
  <w f="60" flags="">gestions</w>
  <w f="60" flags="">glissée</w>
@@ -54422,7 +54392,6 @@
  <w f="60" flags="">l'Argens</w>
  <w f="60" flags="">l'Audiencia</w>
  <w f="60" flags="">l'Aéro-Club</w>
- <w f="60" flags="">l'Eden</w>
  <w f="60" flags="">l'Est-Anglie</w>
  <w f="60" flags="">l'Huveaune</w>
  <w f="60" flags="">l'Ibar</w>
@@ -54448,7 +54417,6 @@
  <w f="60" flags="">l'apposition</w>
  <w f="60" flags="">l'aristocrate</w>
  <w f="60" flags="">l'arrangeur</w>
- <w f="60" flags="">l'artefact</w>
  <w f="60" flags="">l'assignation</w>
  <w f="60" flags="">l'associer</w>
  <w f="60" flags="">l'attaquent</w>
@@ -54902,6 +54870,7 @@
  <w f="60" flags="">rustre</w>
  <w f="60" flags="">râpé</w>
  <w f="60" flags="">réaffirmée</w>
+ <w f="60" flags="">réapparaitre</w>
  <w f="60" flags="">rébus</w>
  <w f="60" flags="">réceptionner</w>
  <w f="60" flags="">réceptionniste</w>
@@ -56571,7 +56540,6 @@
  <w f="59" flags="">guinées</w>
  <w f="59" flags="">gustative</w>
  <w f="59" flags="">guyanaise</w>
- <w f="59" flags="">guérillero</w>
  <w f="59" flags="">gélatineuse</w>
  <w f="59" flags="">génocidaire</w>
  <w f="59" flags="">généra</w>
@@ -56739,7 +56707,6 @@
  <w f="59" flags="abbreviation">l'Ems</w>
  <w f="59" flags="">l'Erie</w>
  <w f="59" flags="">l'Espagnole</w>
- <w f="59" flags="">l'Esterel</w>
  <w f="59" flags="">l'Estrémadure</w>
  <w f="59" flags="abbreviation">l'HMS</w>
  <w f="59" flags="abbreviation">l'ICC</w>
@@ -56853,7 +56820,6 @@
  <w f="59" flags="">laminoirs</w>
  <w f="59" flags="">lasso</w>
  <w f="59" flags="">latentes</w>
- <w f="59" flags="">laure</w>
  <w f="59" flags="">leishmaniose</w>
  <w f="59" flags="">lemmings</w>
  <w f="59" flags="">lenticulaire</w>
@@ -57083,7 +57049,6 @@
  <w f="59" flags="">pathologiste</w>
  <w f="59" flags="">pattern</w>
  <w f="59" flags="">pectoraux</w>
- <w f="59" flags="">pedigree</w>
  <w f="59" flags="">pendage</w>
  <w f="59" flags="">pensez</w>
  <w f="59" flags="">perfectif</w>
@@ -57296,7 +57261,6 @@
  <w f="59" flags="">rotin</w>
  <w f="59" flags="">rouage</w>
  <w f="59" flags="">rougir</w>
- <w f="59" flags="">râga</w>
  <w f="59" flags="">râteau</w>
  <w f="59" flags="">règnera</w>
  <w f="59" flags="">récollets</w>
@@ -57476,7 +57440,6 @@
  <w f="59" flags="">survive</w>
  <w f="59" flags="">surélever</w>
  <w f="59" flags="">suspicieux</w>
- <w f="59" flags="">sutras</w>
  <w f="59" flags="">suève</w>
  <w f="59" flags="">swaps</w>
  <w f="59" flags="">sycomore</w>
@@ -58677,7 +58640,6 @@
  <w f="58" flags="">d'antiquité</w>
  <w f="58" flags="">d'appendices</w>
  <w f="58" flags="">d'arrivées</w>
- <w f="58" flags="">d'artefacts</w>
  <w f="58" flags="">d'artisan</w>
  <w f="58" flags="">d'arête</w>
  <w f="58" flags="">d'assauts</w>
@@ -59844,7 +59806,6 @@
  <w f="58" flags="">savate</w>
  <w f="58" flags="">scalde</w>
  <w f="58" flags="">scandaleuses</w>
- <w f="58" flags="">scenarii</w>
  <w f="58" flags="">schwa</w>
  <w f="58" flags="">scolastiques</w>
  <w f="58" flags="">scripte</w>
@@ -61680,7 +61641,6 @@
  <w f="57" flags="">l'avertir</w>
  <w f="57" flags="">l'aéroclub</w>
  <w f="57" flags="">l'effectivité</w>
- <w f="57" flags="">l'electro</w>
  <w f="57" flags="">l'ellipsoïde</w>
  <w f="57" flags="">l'en-but</w>
  <w f="57" flags="">l'encéphale</w>
@@ -61831,7 +61791,6 @@
  <w f="57" flags="">multifonctions</w>
  <w f="57" flags="">multilatérale</w>
  <w f="57" flags="">multilatérales</w>
- <w f="57" flags="">multimedia</w>
  <w f="57" flags="">multiplexes</w>
  <w f="57" flags="">multipolaire</w>
  <w f="57" flags="">muons</w>
@@ -62271,7 +62230,6 @@
  <w f="57" flags="">savoisienne</w>
  <w f="57" flags="">saxo</w>
  <w f="57" flags="">scellement</w>
- <w f="57" flags="">scenario</w>
  <w f="57" flags="">schizophrènes</w>
  <w f="57" flags="">scintillement</w>
  <w f="57" flags="">scié</w>
@@ -63875,7 +63833,6 @@
  <w f="56" flags="">efficience</w>
  <w f="56" flags="">efficient</w>
  <w f="56" flags="">el-Cheikh</w>
- <w f="56" flags="">el-Kebir</w>
  <w f="56" flags="">embaucha</w>
  <w f="56" flags="">embusqué</w>
  <w f="56" flags="">emmenèrent</w>
@@ -64412,7 +64369,6 @@
  <w f="56" flags="">mentorat</w>
  <w f="56" flags="">menues</w>
  <w f="56" flags="">mercantiliste</w>
- <w f="56" flags="">meta</w>
  <w f="56" flags="">mi-pente</w>
  <w f="56" flags="">microbiologiste</w>
  <w f="56" flags="">micrométéorites</w>
@@ -65477,7 +65433,6 @@
  <w f="55" flags="abbreviation">HBC</w>
  <w f="55" flags="abbreviation">HII</w>
  <w f="55" flags="abbreviation">HP-UX</w>
- <w f="55" flags="abbreviation">HTA</w>
  <w f="55" flags="">Haden</w>
  <w f="55" flags="">Hainan</w>
  <w f="55" flags="">Hallstatt</w>
@@ -66416,7 +66371,6 @@
  <w f="55" flags="">d'Alta</w>
  <w f="55" flags="">d'Aman</w>
  <w f="55" flags="">d'Anagni</w>
- <w f="55" flags="">d'Angoulème</w>
  <w f="55" flags="">d'Anhalt-Dessau</w>
  <w f="55" flags="">d'Anticosti</w>
  <w f="55" flags="">d'Antigua</w>
@@ -66587,7 +66541,6 @@
  <w f="55" flags="">denticules</w>
  <w f="55" flags="">dessécher</w>
  <w f="55" flags="">destitua</w>
- <w f="55" flags="">devanagari</w>
  <w f="55" flags="">diabolo</w>
  <w f="55" flags="">dialoguent</w>
  <w f="55" flags="">diastolique</w>
@@ -66599,7 +66552,6 @@
  <w f="55" flags="">digipack</w>
  <w f="55" flags="">dilapide</w>
  <w f="55" flags="">diluant</w>
- <w f="55" flags="">dime</w>
  <w f="55" flags="">diptère</w>
  <w f="55" flags="">directivité</w>
  <w f="55" flags="">disculpé</w>
@@ -66838,7 +66790,6 @@
  <w f="55" flags="">flavescence</w>
  <w f="55" flags="">fleurettiste</w>
  <w f="55" flags="">flexionnelle</w>
- <w f="55" flags="">florès</w>
  <w f="55" flags="">fluo</w>
  <w f="55" flags="">fléchi</w>
  <w f="55" flags="">fondirent</w>
@@ -67156,7 +67107,6 @@
  <w f="55" flags="">l'ainée</w>
  <w f="55" flags="">l'ajournement</w>
  <w f="55" flags="">l'allocution</w>
- <w f="55" flags="">l'ambigüité</w>
  <w f="55" flags="">l'ambitieuse</w>
  <w f="55" flags="">l'angine</w>
  <w f="55" flags="">l'antifascisme</w>
@@ -67694,6 +67644,7 @@
  <w f="55" flags="">qu'Eugène</w>
  <w f="55" flags="">qu'Henry</w>
  <w f="55" flags="">qu'Ibn</w>
+ <w f="55" flags="">qu'apparait</w>
  <w f="55" flags="">qu'association</w>
  <w f="55" flags="">qu'enfant</w>
  <w f="55" flags="">qu'entraîneur-joueur</w>
@@ -69943,7 +69894,6 @@
  <w f="54" flags="">mennonite</w>
  <w f="54" flags="">mentent</w>
  <w f="54" flags="">mercy</w>
- <w f="54" flags="">mesa</w>
  <w f="54" flags="">meseta</w>
  <w f="54" flags="">mesurera</w>
  <w f="54" flags="">meublées</w>
@@ -70487,7 +70437,6 @@
  <w f="54" flags="">survola</w>
  <w f="54" flags="">suscitaient</w>
  <w f="54" flags="">susvisé</w>
- <w f="54" flags="">sutra</w>
  <w f="54" flags="">synchronique</w>
  <w f="54" flags="">synchronisme</w>
  <w f="54" flags="">syncopé</w>
@@ -72014,7 +71963,6 @@
  <w f="53" flags="">d'Ayen</w>
  <w f="53" flags="">d'Eberhard</w>
  <w f="53" flags="">d'Echéry</w>
- <w f="53" flags="">d'Ecouen</w>
  <w f="53" flags="">d'Efteling</w>
  <w f="53" flags="">d'Eichmann</w>
  <w f="53" flags="">d'Elisa</w>
@@ -73120,7 +73068,6 @@
  <w f="53" flags="">parpaings</w>
  <w f="53" flags="">partiale</w>
  <w f="53" flags="">partirait</w>
- <w f="53" flags="">paseo</w>
  <w f="53" flags="">passacaille</w>
  <w f="53" flags="">pataphysique</w>
  <w f="53" flags="">patibulaire</w>
@@ -74939,6 +74886,7 @@
  <w f="52" flags="">congeler</w>
  <w f="52" flags="">congélateurs</w>
  <w f="52" flags="">conseillera</w>
+ <w f="52">conseillerais</w>
  <w f="52" flags="">conseillères</w>
  <w f="52" flags="">consolidations</w>
  <w f="52" flags="">consoude</w>
@@ -75209,7 +75157,6 @@
  <w f="52" flags="">divergeant</w>
  <w f="52" flags="">divisera</w>
  <w f="52" flags="">doline</w>
- <w f="52" flags="">dona</w>
  <w f="52" flags="">donnât</w>
  <w f="52" flags="">dope</w>
  <w f="52" flags="">dormeurs</w>
@@ -75428,7 +75375,6 @@
  <w f="52" flags="">félibre</w>
  <w f="52" flags="">férue</w>
  <w f="52" flags="">fût-elle</w>
- <w f="52" flags="">fœhn</w>
  <w f="52" flags="">gaité</w>
  <w f="52" flags="">gallicans</w>
  <w f="52" flags="">galope</w>
@@ -77912,10 +77858,8 @@
  <w f="51" flags="">gaspillé</w>
  <w f="51" flags="">gauchisme</w>
  <w f="51" flags="">gaves</w>
- <w f="51" flags="">genera</w>
  <w f="51" flags="">genette</w>
  <w f="51" flags="">gente</w>
- <w f="51" flags="">gentile</w>
  <w f="51" flags="">gentium</w>
  <w f="51" flags="">gibbons</w>
  <w f="51" flags="">gicleur</w>
@@ -80527,7 +80471,6 @@
  <w f="50" flags="">liner</w>
  <w f="50" flags="">lingue</w>
  <w f="50" flags="">linnéen</w>
- <w f="50" flags="">linoleum</w>
  <w f="50" flags="">linoléum</w>
  <w f="50" flags="">linéarisation</w>
  <w f="50" flags="">lipase</w>
@@ -80564,7 +80507,6 @@
  <w f="50" flags="">magnétisation</w>
  <w f="50" flags="">magnéto</w>
  <w f="50" flags="">magnétohydrodynamique</w>
- <w f="50" flags="">mahârâja</w>
  <w f="50" flags="">majorer</w>
  <w f="50" flags="">majorettes</w>
  <w f="50" flags="">makhzen</w>
@@ -83070,7 +83012,6 @@
  <w f="49" flags="">l'Ambigu</w>
  <w f="49" flags="">l'Astra</w>
  <w f="49" flags="">l'Ecclésia</w>
- <w f="49" flags="">l'Estérel</w>
  <w f="49" flags="">l'IDH</w>
  <w f="49" flags="abbreviation">l'ISA</w>
  <w f="49" flags="abbreviation">l'InVS</w>
@@ -83417,7 +83358,6 @@
  <w f="49" flags="">octosyllabiques</w>
  <w f="49" flags="">offensés</w>
  <w f="49" flags="">oisiveté</w>
- <w f="49" flags="">omega</w>
  <w f="49" flags="">omoplate</w>
  <w f="49" flags="">oncogène</w>
  <w f="49" flags="">oncologie</w>
@@ -85347,7 +85287,6 @@
  <w f="48" flags="">césarisme</w>
  <w f="48" flags="">césures</w>
  <w f="48" flags="">côtoiera</w>
- <w f="48" flags="">d'Ares</w>
  <w f="48" flags="">d'Axa</w>
  <w f="48" flags="">d'Hemingway</w>
  <w f="48" flags="abbreviation">d'URL</w>
@@ -85768,7 +85707,6 @@
  <w f="48" flags="">gaucherie</w>
  <w f="48" flags="">gelant</w>
  <w f="48" flags="">gendarmeries</w>
- <w f="48" flags="">gene</w>
  <w f="48" flags="">genin</w>
  <w f="48" flags="">genouillères</w>
  <w f="48" flags="">gentianes</w>
@@ -85806,7 +85744,6 @@
  <w f="48" flags="">gueuse</w>
  <w f="48" flags="">guinée</w>
  <w f="48" flags="">gujarati</w>
- <w f="48" flags="">gurû</w>
  <w f="48" flags="">guéries</w>
  <w f="48" flags="">guérira</w>
  <w f="48" flags="">guérisse</w>
@@ -86135,7 +86072,6 @@
  <w f="48" flags="">lamentant</w>
  <w f="48" flags="">lamenter</w>
  <w f="48" flags="">laminée</w>
- <w f="48" flags="">lander</w>
  <w f="48" flags="">lands</w>
  <w f="48" flags="">langoureux</w>
  <w f="48" flags="">laotiennes</w>
@@ -86293,7 +86229,6 @@
  <w f="48" flags="">mésosphère</w>
  <w f="48" flags="">métalangage</w>
  <w f="48" flags="">mît</w>
- <w f="48" flags="">môns</w>
  <w f="48" flags="">n'aboutiront</w>
  <w f="48" flags="">n'accorda</w>
  <w f="48" flags="">n'apprit</w>
@@ -87166,7 +87101,6 @@
  <w f="47" flags="">Avellino</w>
  <w f="47" flags="">Avesnes</w>
  <w f="47" flags="">Ayutthaya</w>
- <w f="47" flags="abbreviation">BOF</w>
  <w f="47" flags="">Babin</w>
  <w f="47" flags="">Bagotville</w>
  <w f="47" flags="">Bainville</w>
@@ -87876,6 +87810,7 @@
  <w f="47" flags="">blitzkrieg</w>
  <w f="47" flags="">boards</w>
  <w f="47" flags="">boas</w>
+ <w f="47" flags="abbreviation">bof</w>
  <w f="47" flags="">bomba</w>
  <w f="47" flags="">bomber</w>
  <w f="47" flags="">bombés</w>
@@ -87988,7 +87923,6 @@
  <w f="47" flags="">clinicienne</w>
  <w f="47" flags="">cloisonnements</w>
  <w f="47" flags="">closerie</w>
- <w f="47" flags="">clot</w>
  <w f="47" flags="">cloîtrées</w>
  <w f="47" flags="">clématite</w>
  <w f="47" flags="">clôturera</w>
@@ -88967,7 +88901,6 @@
  <w f="47" flags="">pesanteurs</w>
  <w f="47" flags="">petit-duc</w>
  <w f="47" flags="">peupla</w>
- <w f="47" flags="">peña</w>
  <w f="47" flags="">phalangiste</w>
  <w f="47" flags="">pharmacocinétique</w>
  <w f="47" flags="">phasmes</w>
@@ -91145,7 +91078,6 @@
  <w f="46" flags="">impudique</w>
  <w f="46" flags="">impulsa</w>
  <w f="46" flags="">impulsifs</w>
- <w f="46" flags="">imâm</w>
  <w f="46" flags="abbreviation">ina</w>
  <w f="46" flags="">inapplicables</w>
  <w f="46" flags="">incandescents</w>
@@ -91300,7 +91232,6 @@
  <w f="46" flags="">l'ex-directeur</w>
  <w f="46" flags="">l'ex-épouse</w>
  <w f="46" flags="">l'examine</w>
- <w f="46" flags="">l'exigüité</w>
  <w f="46" flags="">l'expatriation</w>
  <w f="46" flags="">l'expliquent</w>
  <w f="46" flags="">l'exploratrice</w>
@@ -91441,13 +91372,11 @@
  <w f="46" flags="">martyriser</w>
  <w f="46" flags="">masseuse</w>
  <w f="46" flags="">mastectomie</w>
- <w f="46" flags="">mega</w>
  <w f="46" flags="">membrée</w>
  <w f="46" flags="">mentant</w>
  <w f="46" flags="">mentionnèrent</w>
  <w f="46" flags="">menuets</w>
  <w f="46" flags="">mer-sol</w>
- <w f="46" flags="">metro</w>
  <w f="46" flags="">mi-championnat</w>
  <w f="46" flags="">micocoulier</w>
  <w f="46" flags="">micro-algues</w>
@@ -93469,7 +93398,6 @@
  <w f="45" flags="">concouraient</w>
  <w f="45" flags="">concourra</w>
  <w f="45" flags="">condensations</w>
- <w f="45" flags="">condottieres</w>
  <w f="45" flags="">confectionna</w>
  <w f="45" flags="">configurées</w>
  <w f="45" flags="">conflagration</w>
@@ -96795,7 +96723,6 @@
  <w f="44" flags="">eschatologie</w>
  <w f="44" flags="">escomptées</w>
  <w f="44" flags="">espar</w>
- <w f="44" flags="">esperanto</w>
  <w f="44" flags="">espionnait</w>
  <w f="44" flags="">esquivant</w>
  <w f="44" flags="">essaimage</w>
@@ -97285,9 +97212,7 @@
  <w f="44" flags="">laryngite</w>
  <w f="44" flags="">laryngé</w>
  <w f="44" flags="">latéralité</w>
- <w f="44" flags="">lauré</w>
  <w f="44" flags="">laze</w>
- <w f="44" flags="">leges</w>
  <w f="44" flags="">lestées</w>
  <w f="44" flags="">lettrages</w>
  <w f="44" flags="">levantine</w>
@@ -97372,7 +97297,6 @@
  <w f="44" flags="">micronoyaux</w>
  <w f="44" flags="">miette</w>
  <w f="44" flags="">militarisés</w>
- <w f="44" flags="">millenium</w>
  <w f="44" flags="">millerandage</w>
  <w f="44" flags="">minant</w>
  <w f="44" flags="">mini-jupe</w>
@@ -99977,7 +99901,6 @@
  <w f="43" flags="">mnémoniques</w>
  <w f="43" flags="">mobilisera</w>
  <w f="43" flags="">modifieront</w>
- <w f="43" flags="">moitie</w>
  <w f="43" flags="">molester</w>
  <w f="43" flags="">mona</w>
  <w f="43" flags="">monistes</w>
@@ -101868,7 +101791,6 @@
  <w f="42" flags="">dichotomies</w>
  <w f="42" flags="">dichotomiques</w>
  <w f="42" flags="">dilapidations</w>
- <w f="42" flags="">dimes</w>
  <w f="42" flags="">diplodocus</w>
  <w f="42" flags="">disait-elle</w>
  <w f="42" flags="">discréditées</w>
@@ -102899,7 +102821,6 @@
  <w f="42" flags="">restituait</w>
  <w f="42" flags="">retracés</w>
  <w f="42" flags="">retranscrivent</w>
- <w f="42" flags="">retro</w>
  <w f="42" flags="">retrouvez</w>
  <w f="42" flags="">reversant</w>
  <w f="42" flags="">revirent</w>
@@ -104901,7 +104822,6 @@
  <w f="41" flags="">garde-temps</w>
  <w f="41" flags="">gardes-pêche</w>
  <w f="41" flags="">gastroentérite</w>
- <w f="41" flags="">gates</w>
  <w f="41" flags="">gaufrier</w>
  <w f="41" flags="">gaules</w>
  <w f="41" flags="">gaussiens</w>
@@ -105532,7 +105452,6 @@
  <w f="41" flags="">plafonnées</w>
  <w f="41" flags="">plaisanté</w>
  <w f="41" flags="">planctons</w>
- <w f="41" flags="">plantagenêt</w>
  <w f="41" flags="">plantera</w>
  <w f="41" flags="">plaqueminier</w>
  <w f="41" flags="">playa</w>
@@ -108603,7 +108522,6 @@
  <w f="40" flags="">radiatives</w>
  <w f="40" flags="">radiophare</w>
  <w f="40" flags="">radiosondes</w>
- <w f="40" flags="">raga</w>
  <w f="40" flags="">rages</w>
  <w f="40" flags="">rainbow</w>
  <w f="40" flags="">rainuré</w>
@@ -108827,7 +108745,6 @@
  <w f="40" flags="">sarclage</w>
  <w f="40" flags="">satori</w>
  <w f="40" flags="">saturations</w>
- <w f="40" flags="">satî</w>
  <w f="40" flags="">saupoudrage</w>
  <w f="40" flags="">saurez</w>
  <w f="40" flags="">sauvez</w>
@@ -111688,7 +111605,6 @@
  <w f="39" flags="">monèmes</w>
  <w f="39" flags="">moralisant</w>
  <w f="39" flags="">moro</w>
- <w f="39" flags="">moré</w>
  <w f="39" flags="">mosaïsme</w>
  <w f="39" flags="">moteur-boîte</w>
  <w f="39" flags="">motivèrent</w>
@@ -111728,6 +111644,7 @@
  <w f="39" flags="">n'adopta</w>
  <w f="39" flags="">n'agissant</w>
  <w f="39" flags="">n'allèrent</w>
+ <w f="39">n'apportez</w>
  <w f="39" flags="">n'approcha</w>
  <w f="39" flags="">n'atteindrait</w>
  <w f="39" flags="">n'empêchaient</w>
@@ -115033,7 +114950,6 @@
  <w f="37" flags="">croyait-on</w>
  <w f="37" flags="">cryptogrammes</w>
  <w f="37" flags="">créancière</w>
- <w f="37" flags="">crémerie</w>
  <w f="37" flags="">crénelages</w>
  <w f="37" flags="">crépue</w>
  <w f="37" flags="">crétacées</w>
@@ -115728,7 +115644,6 @@
  <w f="37" flags="">harkas</w>
  <w f="37" flags="">harnachements</w>
  <w f="37" flags="">hast</w>
- <w f="37" flags="">haste</w>
  <w f="37" flags="">hellénisants</w>
  <w f="37" flags="">hennin</w>
  <w f="37" flags="">herbagers</w>
@@ -118031,7 +117946,6 @@
  <w f="36" flags="">doutons</w>
  <w f="36" flags="">doyennes</w>
  <w f="36" flags="">drives</w>
- <w f="36" flags="">drivé</w>
  <w f="36" flags="">droguées</w>
  <w f="36" flags="">droitières</w>
  <w f="36" flags="">ductus</w>
@@ -119687,6 +119601,7 @@
  <w f="35" flags="">appelleraient</w>
  <w f="35" flags="">appoggiatures</w>
  <w f="35" flags="">appointée</w>
+ <w f="35">apporteriez</w>
  <w f="35" flags="">apposèrent</w>
  <w f="35" flags="">apprendraient</w>
  <w f="35" flags="">approprie</w>
@@ -120848,7 +120763,6 @@
  <w f="35" flags="">fantasmatiques</w>
  <w f="35" flags="">fardage</w>
  <w f="35" flags="">faro</w>
- <w f="35" flags="">fasciculés</w>
  <w f="35" flags="">fascinaient</w>
  <w f="35" flags="">fascisation</w>
  <w f="35" flags="">fayard</w>
@@ -121985,7 +121899,6 @@
  <w f="35" flags="">pastore</w>
  <w f="35" flags="">pataude</w>
  <w f="35" flags="">patauge</w>
- <w f="35" flags="">pati</w>
  <w f="35" flags="">paturon</w>
  <w f="35" flags="">paturons</w>
  <w f="35" flags="">patènes</w>
@@ -123976,7 +123889,6 @@
  <w f="34" flags="">déviateur</w>
  <w f="34" flags="">dîna</w>
  <w f="34" flags="">ectopiques</w>
- <w f="34" flags="">eden</w>
  <w f="34" flags="">effectueraient</w>
  <w f="34" flags="">effraies</w>
  <w f="34" flags="">effritée</w>
@@ -124531,7 +124443,6 @@
  <w f="34" flags="">mestres</w>
  <w f="34" flags="">mesureront</w>
  <w f="34" flags="">meublent</w>
- <w f="34" flags="">mi-aout</w>
  <w f="34" flags="">mi-humaines</w>
  <w f="34" flags="">mi-voix</w>
  <w f="34" flags="">micelle</w>
@@ -127052,7 +126963,6 @@
  <w f="33" flags="">pavanes</w>
  <w f="33" flags="">pavant</w>
  <w f="33" flags="">pavimenteux</w>
- <w f="33" flags="">penelope</w>
  <w f="33" flags="">pensionna</w>
  <w f="33" flags="">pentacles</w>
  <w f="33" flags="">perborate</w>
@@ -131024,7 +130934,6 @@
  <w f="31" flags="">châtelaines</w>
  <w f="31" flags="">chènevières</w>
  <w f="31" flags="">cicatrisante</w>
- <w f="31" flags="">cicéro</w>
  <w f="31" flags="">cicéronienne</w>
  <w f="31" flags="">cimentant</w>
  <w f="31" flags="">cincles</w>
@@ -131800,7 +131709,6 @@
  <w f="31" flags="">grattés</w>
  <w f="31" flags="">gravissaient</w>
  <w f="31" flags="">gravois</w>
- <w f="31" flags="">greco</w>
  <w f="31" flags="">grecs-orthodoxes</w>
  <w f="31" flags="">greffera</w>
  <w f="31" flags="">grenadines</w>
@@ -131965,7 +131873,6 @@
  <w f="31" flags="">ivresses</w>
  <w f="31" flags="">j'écoutais</w>
  <w f="31" flags="">jaillie</w>
- <w f="31" flags="">jaina</w>
  <w f="31" flags="">jamaïquaines</w>
  <w f="31" flags="">janus</w>
  <w f="31" flags="">javelles</w>
@@ -132169,7 +132076,6 @@
  <w f="31" flags="">lattée</w>
  <w f="31" flags="">laudatives</w>
  <w f="31" flags="">lavandins</w>
- <w f="31" flags="">leger</w>
  <w f="31" flags="">levantines</w>
  <w f="31" flags="">lexicologiques</w>
  <w f="31" flags="">libéralisa</w>
@@ -132223,7 +132129,6 @@
  <w f="31" flags="">macro-algues</w>
  <w f="31" flags="">macrocéphale</w>
  <w f="31" flags="">macèrent</w>
- <w f="31" flags="">maelstrom</w>
  <w f="31" flags="">magnésiennes</w>
  <w f="31" flags="">magnétisées</w>
  <w f="31" flags="">mahométan</w>
@@ -134732,7 +134637,6 @@
  <w f="30" flags="">farcit</w>
  <w f="30" flags="">fardé</w>
  <w f="30" flags="">fasciation</w>
- <w f="30" flags="">fasciculé</w>
  <w f="30" flags="">fascinations</w>
  <w f="30" flags="">faucha</w>
  <w f="30" flags="">fausse-renoncule</w>
@@ -136328,6 +136232,8 @@
  <w f="30" flags="">sérails</w>
  <w f="30" flags="">sérialisée</w>
  <w f="30" flags="">séricigènes</w>
+ <w f="30">t'adresser</w>
+ <w f="30">t'adresses</w>
  <w f="30" flags="">t'aider</w>
  <w f="30" flags="">t'emmerde</w>
  <w f="30" flags="">taboues</w>
@@ -137547,7 +137453,6 @@
  <w f="28" flags="">d'arrangeurs</w>
  <w f="28" flags="">d'arrhes</w>
  <w f="28" flags="">d'arrière-plans</w>
- <w f="28" flags="">d'artefact</w>
  <w f="28" flags="">d'arythmies</w>
  <w f="28" flags="">d'assurance-crédit</w>
  <w f="28" flags="">d'astigmatisme</w>
@@ -138268,7 +138173,6 @@
  <w f="28" flags="">herbager</w>
  <w f="28" flags="">herborisa</w>
  <w f="28" flags="">herculéen</w>
- <w f="28" flags="">herpes</w>
  <w f="28" flags="">hiatale</w>
  <w f="28" flags="">histidines</w>
  <w f="28" flags="">hivernages</w>
@@ -146791,11 +146695,9 @@
  <w f="25" flags="">fédé</w>
  <w f="25" flags="">fédéral-provincial</w>
  <w f="25" flags="">félicitaient</w>
- <w f="25" flags="">féra</w>
  <w f="25" flags="">gagnais</w>
  <w f="25" flags="">gaillets</w>
  <w f="25" flags="">gainiers</w>
- <w f="25" flags="">gallé</w>
  <w f="25" flags="">galonnés</w>
  <w f="25" flags="">galoubet-tambourin</w>
  <w f="25" flags="">galoubets</w>
@@ -147598,7 +147500,6 @@
  <w f="25" flags="">maçonnes</w>
  <w f="25" flags="">maïzena</w>
  <w f="25" flags="">melba</w>
- <w f="25" flags="">melissa</w>
  <w f="25" flags="">menteuses</w>
  <w f="25" flags="">mentholée</w>
  <w f="25" flags="">mentionnerons</w>
@@ -147712,7 +147613,6 @@
  <w f="25" flags="">mouvaient</w>
  <w f="25" flags="">moyen-bavarois</w>
  <w f="25" flags="">moyen-néerlandais</w>
- <w f="25" flags="">moëres</w>
  <w f="25" flags="">mugir</w>
  <w f="25" flags="">multivariées</w>
  <w f="25" flags="">musé</w>
@@ -151397,7 +151297,6 @@
  <w f="23" flags="">dandiner</w>
  <w f="23" flags="">dansais</w>
  <w f="23" flags="">danse-contact</w>
- <w f="23" flags="">daphne</w>
  <w f="23" flags="">dardes</w>
  <w f="23" flags="">daring</w>
  <w f="23" flags="">dariques</w>
@@ -152474,7 +152373,6 @@
  <w f="23" flags="">kantiens</w>
  <w f="23" flags="">keno</w>
  <w f="23" flags="">khamsin</w>
- <w f="23" flags="">khân</w>
  <w f="23" flags="">kiki</w>
  <w f="23" flags="">kilkenny</w>
  <w f="23" flags="">kinder</w>
@@ -152886,7 +152784,6 @@
  <w f="23" flags="">lacèrent</w>
  <w f="23" flags="">lacée</w>
  <w f="23" flags="">lae</w>
- <w f="23" flags="">laiche</w>
  <w f="23" flags="">laideron</w>
  <w f="23" flags="">laisse-toi</w>
  <w f="23" flags="">laisseras</w>
@@ -153811,7 +153708,6 @@
  <w f="23" flags="">poissonnières</w>
  <w f="23" flags="">poissé</w>
  <w f="23" flags="">polacre</w>
- <w f="23" flags="">polaroid</w>
  <w f="23" flags="">polatouches</w>
  <w f="23" flags="">polissons</w>
  <w f="23" flags="">pollupostage</w>
@@ -154711,7 +154607,6 @@
  <w f="23" flags="">servocommande</w>
  <w f="23" flags="">servomécanismes</w>
  <w f="23" flags="">sexagésimaux</w>
- <w f="23" flags="">shâh</w>
  <w f="23" flags="">siccatifs</w>
  <w f="23" flags="">sifflaient</w>
  <w f="23" flags="">sillages</w>
@@ -155963,6 +155858,7 @@
  <w f="21" flags="">Motte-Saint-Valentin</w>
  <w f="21" flags="">Moulin-Brûlé</w>
  <w f="21" flags="">Musser</w>
+ <w f="21" flags="">Mélissa</w>
  <w f="21" flags="">Métro-Richelieu</w>
  <w f="21" flags="">Mézières-en-Gâtinais</w>
  <w f="21" flags="">Napoléon-Charles</w>
@@ -158839,7 +158735,6 @@
  <w f="21" flags="">hee</w>
  <w f="21" flags="">heller</w>
  <w f="21" flags="">hellénismes</w>
- <w f="21" flags="">herbés</w>
  <w f="21" flags="">herve</w>
  <w f="21" flags="">heurtons</w>
  <w f="21" flags="">hiberné</w>
@@ -159635,7 +159530,6 @@
  <w f="21" flags="">lactifères</w>
  <w f="21" flags="">lacéra</w>
  <w f="21" flags="">laguerre</w>
- <w f="21" flags="">laiches</w>
  <w f="21" flags="">laisserais</w>
  <w f="21" flags="">lamentos</w>
  <w f="21" flags="">lamentèrent</w>
@@ -160062,7 +159956,6 @@
  <w f="21" flags="">méga-corporation</w>
  <w f="21" flags="">mélamine-formaldéhyde</w>
  <w f="21" flags="">mélangeront</w>
- <w f="21" flags="">mélissa</w>
  <w f="21" flags="">mémorisait</w>
  <w f="21" flags="">ménologes</w>
  <w f="21" flags="">ménopausique</w>
@@ -160666,7 +160559,6 @@
  <w f="21" flags="">peinard</w>
  <w f="21" flags="">peinez</w>
  <w f="21" flags="">peinturer</w>
- <w f="21" flags="">pelagos</w>
  <w f="21" flags="">pelliculée</w>
  <w f="21" flags="">peltaste</w>
  <w f="21" flags="">pelté</w>
@@ -165431,7 +165323,6 @@
  <w f="18" flags="">crédit-carbone</w>
  <w f="18" flags="">créditera</w>
  <w f="18" flags="">crédulités</w>
- <w f="18" flags="">crémeries</w>
  <w f="18" flags="">crénelées-dentelées</w>
  <w f="18" flags="">créolisés</w>
  <w f="18" flags="">créosotes</w>
@@ -170056,7 +169947,6 @@
  <w f="18" flags="">position-impulsion</w>
  <w f="18" flags="">postulations</w>
  <w f="18" flags="">posât</w>
- <w f="18" flags="">potencé</w>
  <w f="18" flags="">potentialisé</w>
  <w f="18" flags="">potenza</w>
  <w f="18" flags="">potestative</w>
@@ -172484,7 +172374,6 @@
  <w f="18" flags="">écartes</w>
  <w f="18" flags="">échafaudant</w>
  <w f="18" flags="">échafaudèrent</w>
- <w f="18" flags="">échancre</w>
  <w f="18" flags="">échangeons</w>
  <w f="18" flags="">échangeraient</w>
  <w f="18" flags="">écharper</w>
@@ -172751,7 +172640,6 @@
  <w f="15" flags="">Antoinette-Élisabeth</w>
  <w f="15" flags="">Anvers-Bruxelles</w>
  <w f="15" flags="">Anvers-Wavre</w>
- <w f="15" flags="">Apportez-moi</w>
  <w f="15" flags="">Arbre-Saint-Pierre</w>
  <w f="15" flags="">Ardenne-Rives-de-Meuse</w>
  <w f="15" flags="">Argas</w>
@@ -173065,6 +172953,7 @@
  <w f="15" flags="">Château-Voué</w>
  <w f="15" flags="">Château-sur-Aisne</w>
  <w f="15" flags="">Chérasse</w>
+ <w f="15" flags="">Cicero</w>
  <w f="15" flags="">Cinq-Chemins</w>
  <w f="15" flags="">Cinq-Moulins</w>
  <w f="15" flags="">Cinq-Provinces</w>
@@ -174677,6 +174566,7 @@
  <w f="15" flags="">apporte-t-il</w>
  <w f="15" flags="">apporterez</w>
  <w f="15" flags="">apportes</w>
+ <w f="15" flags="">apportez-moi</w>
  <w f="15" flags="">apprend-t-on</w>
  <w f="15" flags="">apprenti-dessinateur</w>
  <w f="15" flags="">apprenti-pâtissier</w>
@@ -175926,8 +175816,6 @@
  <w f="15" flags="">ciboriums</w>
  <w f="15" flags="">cicatrisations</w>
  <w f="15" flags="">cicatrisés</w>
- <w f="15" flags="">cicero</w>
- <w f="15" flags="">cicéros</w>
  <w f="15" flags="">ciel-terre</w>
  <w f="15" flags="">cillées</w>
  <w f="15" flags="">cimentera</w>
@@ -179207,7 +179095,6 @@
  <w f="15" flags="">gemmologiste</w>
  <w f="15" flags="">gentillets</w>
  <w f="15" flags="">gentillettes</w>
- <w f="15" flags="">genépi</w>
  <w f="15" flags="">gercées</w>
  <w f="15" flags="">germanisa</w>
  <w f="15" flags="">germinaux</w>
@@ -179448,7 +179335,6 @@
  <w f="15" flags="">heng</w>
  <w f="15" flags="">hennissant</w>
  <w f="15" flags="">heptagones</w>
- <w f="15" flags="">herbé</w>
  <w f="15" flags="">herméticité</w>
  <w f="15" flags="">herri</w>
  <w f="15" flags="">hersages</w>
@@ -180957,7 +180843,6 @@
  <w f="15" flags="">limitez</w>
  <w f="15" flags="">limonites</w>
  <w f="15" flags="">limée</w>
- <w f="15" flags="">limón</w>
  <w f="15" flags="">lingotières</w>
  <w f="15" flags="">liniers</w>
  <w f="15" flags="">linotypie</w>
@@ -182796,7 +182681,6 @@
  <w f="15" flags="">passage-clef</w>
  <w f="15" flags="">passagers-kilomètres</w>
  <w f="15" flags="">passassent</w>
- <w f="15" flags="">passat</w>
  <w f="15" flags="">passe-plats</w>
  <w f="15" flags="">passe-rose</w>
  <w f="15" flags="">passementière</w>
@@ -182844,7 +182728,6 @@
  <w f="15" flags="">pelas</w>
  <w f="15" flags="">pelliculées</w>
  <w f="15" flags="">pelotonnée</w>
- <w f="15" flags="">peléen</w>
  <w f="15" flags="">peléennes</w>
  <w f="15" flags="">penchât</w>
  <w f="15" flags="">pense-il</w>
@@ -182868,7 +182751,6 @@
  <w f="15" flags="">perdit-il</w>
  <w f="15" flags="">perdureraient</w>
  <w f="15" flags="">perdîmes</w>
- <w f="15" flags="">peres</w>
  <w f="15" flags="">perfectionneront</w>
  <w f="15" flags="">perfoliées</w>
  <w f="15" flags="">perforera</w>
@@ -183005,7 +182887,6 @@
  <w f="15" flags="">planchera</w>
  <w f="15" flags="">planchéiée</w>
  <w f="15" flags="">planeuse</w>
- <w f="15" flags="">plantagenet</w>
  <w f="15" flags="">planteraient</w>
  <w f="15" flags="">planteuses</w>
  <w f="15" flags="">plastiqueurs</w>
@@ -187747,7 +187628,6 @@
  <w f="10" flags="">côtoyez</w>
  <w f="10" flags="">d'Agnat</w>
  <w f="10" flags="">d'Albert-le-Grand</w>
- <w f="10" flags="">d'Ales</w>
  <w f="10" flags="">d'Algérien</w>
  <w f="10" flags="">d'Angoumois-infanterie</w>
  <w f="10" flags="">d'Anonyme</w>
@@ -188046,7 +187926,6 @@
  <w f="10" flags="">ferryboat</w>
  <w f="10" flags="">feule</w>
  <w f="10" flags="">fichier-clef</w>
- <w f="10" flags="">fieffe</w>
  <w f="10" flags="">fieffer</w>
  <w f="10" flags="">fies</w>
  <w f="10" flags="">fieu</w>
@@ -188231,7 +188110,6 @@
  <w f="10" flags="">hava</w>
  <w f="10" flags="">havir</w>
  <w f="10" flags="">hebdomadiers</w>
- <w f="10" flags="">hela</w>
  <w f="10" flags="">helminthe</w>
  <w f="10" flags="">henni</w>
  <w f="10" flags="">herzégovine</w>
@@ -188729,7 +188607,6 @@
  <w f="10" flags="">malpolis</w>
  <w f="10" flags="">malta</w>
  <w f="10" flags="">malter</w>
- <w f="10" flags="">malé</w>
  <w f="10" flags="">mancha</w>
  <w f="10" flags="">mangano</w>
  <w f="10" flags="">mante-religieuse</w>
@@ -189012,7 +188889,6 @@
  <w f="10" flags="">pastelle</w>
  <w f="10" flags="">pasteurienne</w>
  <w f="10" flags="">pastillas</w>
- <w f="10" flags="">paséo</w>
  <w f="10" flags="">patachons</w>
  <w f="10" flags="">patay</w>
  <w f="10" flags="">patoche</w>
@@ -191229,7 +191105,6 @@
  <w f="1" flags="">miton</w>
  <w f="1" flags="">moblots</w>
  <w f="1" flags="">moch</w>
- <w f="1" flags="">moeres</w>
  <w f="1" flags="">moha</w>
  <w f="1" flags="">moisant</w>
  <w f="1" flags="">moise</w>
@@ -191271,7 +191146,6 @@
  <w f="1" flags="">mémère</w>
  <w f="1" flags="">ménine</w>
  <w f="1" flags="">ményanthe</w>
- <w f="1" flags="">mésa</w>
  <w f="1" flags="">métra</w>
  <w f="1" flags="">métromanie</w>
  <w f="1" flags="">mézeray</w>
@@ -191837,6 +191711,7 @@
  <w f="0" flags="e">gouine</w>
  <w f="0" flags="n">gousse</w>
  <w f="0" flags="n">gémissement</w>
+ <w f="0">haha</w>
  <w f="0" flags="n">hardcore</w>
  <w f="0" flags="n">hermaphrodite</w>
  <w f="0" flags="e">homo</w>
diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml
index 06d852b..6b82382 100644
--- a/java/AndroidManifest.xml
+++ b/java/AndroidManifest.xml
@@ -1,3 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 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.
+-->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         coreApp="true"
         package="com.android.inputmethod.latin">
diff --git a/java/proguard.flags b/java/proguard.flags
index 34e23aa..376a0e0 100644
--- a/java/proguard.flags
+++ b/java/proguard.flags
@@ -3,10 +3,6 @@
   <init>(...);
 }
 
--keep class com.android.inputmethod.latin.Flag {
-  *;
-}
-
 -keep class com.android.inputmethod.keyboard.ProximityInfo {
   <init>(com.android.inputmethod.keyboard.ProximityInfo);
 }
@@ -24,6 +20,10 @@
   boolean equalsIgnoreCase(...);
 }
 
+-keep class com.android.inputmethod.latin.InputPointers {
+  *;
+}
+
 -keep class com.android.inputmethod.latin.spellcheck.SpellCheckerSettingsFragment {
   *;
 }
@@ -41,13 +41,7 @@
 }
 
 -keep class com.android.inputmethod.latin.ResearchLogger {
-  void setLogFileManager(...);
-  void clearAll();
-  com.android.inputmethod.latin.ResearchLogger$LogFileManager getLogFileManager();
-}
-
--keep class com.android.inputmethod.latin.ResearchLogger$LogFileManager {
-  java.lang.String getContents();
+  void flush();
 }
 
 -keep class com.android.inputmethod.keyboard.KeyboardLayoutSet$Builder {
diff --git a/java/res/raw/main_en.dict b/java/res/raw/main_en.dict
index 98a9361..e02e300 100644
--- a/java/res/raw/main_en.dict
+++ b/java/res/raw/main_en.dict
Binary files differ
diff --git a/java/res/raw/main_fr.dict b/java/res/raw/main_fr.dict
index 717078c..8e61659 100644
--- a/java/res/raw/main_fr.dict
+++ b/java/res/raw/main_fr.dict
Binary files differ
diff --git a/java/res/values-af/strings.xml b/java/res/values-af/strings.xml
index 7431fce..30d5f5d 100644
--- a/java/res/values-af/strings.xml
+++ b/java/res/values-af/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Android-sleutelbord"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android-sleutelbord (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Android-sleutelbordinstellings"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Invoeropsies"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Android-speltoetser"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Navorsing-loglêerbevele"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Android-speltoetser (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Speltoetser se instellings"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Soek kontakname op"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Speltoetser gebruik inskrywings uit jou kontaklys"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibreer met sleuteldruk"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Verstek"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Stel kontakname voor"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Gebruik name van kontakte vir voorstelle en korreksies"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Aktiveer herkorrigerings"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Stel voorstelle vir herkorrigerings"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Outohoofletters"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Voeg woordeboeke by"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Hoofwoordeboek"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Matig"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Aggressief"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Baie aggressief"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Volgendewoordvoorstelle"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Gebruik vorige woord om voorstelle te verbeter"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Volgendewoordvoorspelling"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Gebruik vorige woord ook vir voorspelling"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Stel volgende woord voor"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Gebaseer op vorige woord"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Gestoor"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Gaan"</string>
     <string name="label_next_key" msgid="362972844525672568">"Volgende"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Steminvoer is gedeaktiveer"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Stel invoermetodes op"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Invoertale"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"let op die tydstempel in die loglêer"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Aangetekende tydstempel"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Moenie hierdie sessie aanteken nie"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Sessie se loglêer uitgevee"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Sessie se loglêer uitgevee"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Sessie se loglêer NIE uitgevee nie"</string>
     <string name="select_language" msgid="3693815588777926848">"Invoertale"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Raak weer om te stoor"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Woordeboek beskikbaar"</string>
diff --git a/java/res/values-am/strings-appname.xml b/java/res/values-am/strings-appname.xml
new file mode 100644
index 0000000..da0ab2f
--- /dev/null
+++ b/java/res/values-am/strings-appname.xml
@@ -0,0 +1,28 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (178705338187710493) -->
+    <skip />
+    <string name="spell_checker_service_name" msgid="6268342166872202903">"Android የፊደል አራሚ"</string>
+    <string name="english_ime_settings" msgid="7470027018752707691">"Android የቁልፍ ሰሌዳ ቅንብሮች"</string>
+    <string name="android_spell_checker_settings" msgid="8397842018475560441">"የፊደል አራሚ ቅንብሮች"</string>
+</resources>
diff --git a/java/res/values-am/strings.xml b/java/res/values-am/strings.xml
index d70c05d..69493b7 100644
--- a/java/res/values-am/strings.xml
+++ b/java/res/values-am/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"የAndroid ቁልፍሰሌዳ"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"የAndroid ቁልፍ ሰሌዳ (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"የAndroid ቁልፍሰሌዳ ቅንብሮች"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"ግቤት አማራጮች"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Android የፊደል ማረሚያ"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"የጥናት የምዝግብ ማስታወሻ ትዕዛዞች"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Android የፊደል ማረሚያ (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"የፊደል አራሚ ቅንብሮች"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"የእውቅያ ስሞችን ተመልከት"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"ፊደል አራሚ ከእውቅያ ዝርዝርህ የገቡትን ይጠቀማል"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"በቁልፍመጫንጊዜ አንዝር"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"ነባሪ"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"የዕውቂያ ስም ጠቁም"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"ከዕውቂያዎች ለጥቆማዎች እና ማስተካከያዎች ስሞች ተጠቀም"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"ድጋሚ ለማስተካከል አንቃ"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"ድጋሚ ለማስተካከል ጥቆማዎችን አዘጋጅ"</string>
     <string name="auto_cap" msgid="1719746674854628252">"ራስ-ሰር አቢይ ማድረግ"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"መዝገበ ቃላቶች ጨምር"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"ዋና መዝገበ ቃላት"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"መጠነኛ"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"ኃይለኛ"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"በጣም ቁጡ"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"የቀጣይ ቃል አስተያየቶች"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"ምክሮችን ለማሻሻል ቀዳሚ ቃል ተጠቀም"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"የቀጣይ ቃል ግምት"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"ለትንበያ የቀደመ ቃል እንዲሁ ተጠቀም"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"የቀጣይ ቃል አስተያየቶች"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"በቀዳሚው ቃል ላይ የተመሠረተ"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : ተቀምጧል"</string>
     <string name="label_go_key" msgid="1635148082137219148">"ሂድ"</string>
     <string name="label_next_key" msgid="362972844525672568">"በመቀጠል"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"የድምፅ ግቤት ቦዝኗል"</string>
     <string name="configure_input_method" msgid="373356270290742459">"ግቤት ሜተዶችን አዋቀር"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"ቋንቋዎች አግቤት"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"የምዝግብ ማስታወሻ ጊዜ ማህተም ማስታወሻ"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"የጊዜ ማህተም ተመዝግቧል"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"ይህን ክፍለ ጊዜ እንዳትመዘግበው"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"የክፍለጊዜ ምዝግብ ማስታወሻ በመሰረዝ ላይ"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"የክፍለ ጊዜ ምዝግብ ማስታወሻ ተሰርዟል"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"የክፍለጊዜ ምዝግብ ማስታወሻ አልተሰረዘም"</string>
     <string name="select_language" msgid="3693815588777926848">"ቋንቋዎች አግቤት"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"ለማስቀመጥ እንደገና ንካ"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"መዝገበ ቃላት አለ"</string>
diff --git a/java/res/values-ar/strings.xml b/java/res/values-ar/strings.xml
index 5678c40..eb7e306 100644
--- a/java/res/values-ar/strings.xml
+++ b/java/res/values-ar/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"لوحة مفاتيح Android"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"لوحة مفاتيح Android ‏(AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"إعدادات لوحة مفاتيح Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"خيارات الإرسال"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"التدقيق الإملائي في Android"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"أوامر سجلات البحث"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"التدقيق الإملائي في Android‏ (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"إعدادات التدقيق الإملائي"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"بحث في أسماء جهات الاتصال"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"يستخدم المدقق الإملائي إدخالات من قائمة جهات الاتصال"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"اهتزاز عند ضغط مفتاح"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"افتراضي"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"اقتراح أسماء جهات الاتصال"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"استخدام الأسماء من جهات الاتصال للاقتراحات والتصحيحات"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"تمكين عمليات إعادة التصحيح"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"تعيين اقتراحات لعمليات إعادة التصحيح"</string>
     <string name="auto_cap" msgid="1719746674854628252">"أحرف كبيرة تلقائيًا"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"القواميس الإضافية"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"القاموس الرئيسي"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"معتدل"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"حاد"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"شديد الصرامة"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"اقتراحات الكلمات التالية"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"استخدام الكلمة السابقة لتحسين الاقتراحات"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"تنبؤ الكلمات التالية"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"استخدام الكلمة السابقة أيضًا للتنبؤ"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"اقتراحات الكلمات التالية"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"استنادًا إلى الكلمة السابقة"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : تم الحفظ"</string>
     <string name="label_go_key" msgid="1635148082137219148">"تنفيذ"</string>
     <string name="label_next_key" msgid="362972844525672568">"التالي"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"الإدخال الصوتي مُعطل"</string>
     <string name="configure_input_method" msgid="373356270290742459">"تهيئة طرق الإدخال"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"لغات الإدخال"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"ملاحظة طابع زمني في سجل"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"تم تسجيل الطابع الزمني"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"عدم تسجيل هذه الجلسة"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"جارٍ حذف سجل الجلسة"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"تم حذف سجل الجلسة"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"لم يتم حذف سجل الجلسة"</string>
     <string name="select_language" msgid="3693815588777926848">"لغات الإدخال"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"المس مرة أخرى للحفظ"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"القاموس متاح"</string>
diff --git a/java/res/values-be/strings-appname.xml b/java/res/values-be/strings-appname.xml
new file mode 100644
index 0000000..226de7c
--- /dev/null
+++ b/java/res/values-be/strings-appname.xml
@@ -0,0 +1,28 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (178705338187710493) -->
+    <skip />
+    <string name="spell_checker_service_name" msgid="6268342166872202903">"Iнструмент праверкi правапiсу для Android"</string>
+    <string name="english_ime_settings" msgid="7470027018752707691">"Налады клавіятуры Android"</string>
+    <string name="android_spell_checker_settings" msgid="8397842018475560441">"Налады праверкі арфаграфіі"</string>
+</resources>
diff --git a/java/res/values-be/strings.xml b/java/res/values-be/strings.xml
index 4d14565..d67ddc0 100644
--- a/java/res/values-be/strings.xml
+++ b/java/res/values-be/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Клавіятура Android"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Клавіятура Android (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Налады клавіятуры Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Параметры ўводу"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Iнструмент праверкi правапiсу для Android"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Каманды гiсторыя даследаванняў"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Iнструмент праверкi правапiсу для Android (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Налады праверкі арфаграфіі"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Шукаць імёны кантактаў"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Модуль праверкі правапісу выкарыстоўвае запісы са спісу кантактаў"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Вібрацыя пры націску клавіш"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Па змаўчанні"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Прапан. імёны кантактаў"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Выкарыстоўваць імёны са спісу кантактаў для прапаноў і выпраўл."</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Уключыць карэкцiроўкі"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Задаць прапановы для карэкцiроўкі"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Аўтаматычна рабіць вялікія літары"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Дадатковыя слоўнікі"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Асноўны слоўнік"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Сціплы"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Агрэсіўны"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Вельмі агрэсіўны"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Падказкi для наступнага слова"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Выкарыстаць папярэдняе слова, каб палепшыць прапановы"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Падказка наступнага слова"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Выкарыстанне папярэдняга слова для падказак"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Падказкi для наступнага слова"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"На аснове папярэдняга слова"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Захаваныя"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Пачаць"</string>
     <string name="label_next_key" msgid="362972844525672568">"Далей"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Галасавы набор адкл."</string>
     <string name="configure_input_method" msgid="373356270290742459">"Налада метадаў уводу"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Мовы ўводу"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Пазначыць час у гiсторыi"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Запiсаныя пазнакі"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Не рэгістраваць гэты сеанс"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Выдаленне гiсторыi сеанса"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Гiсторыя сеанса выдалена"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Гiсторыя сеанса НЕ выдалена"</string>
     <string name="select_language" msgid="3693815588777926848">"Мовы ўводу"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Дакраніцеся зноў, каб захаваць"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Слоўнік даступны"</string>
diff --git a/java/res/values-bg/strings.xml b/java/res/values-bg/strings.xml
index 106a918..5cd6fca 100644
--- a/java/res/values-bg/strings.xml
+++ b/java/res/values-bg/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Клавиатура на Android"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Клавиатура на Android (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Настройки на клавиатурата на Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Опции за въвеждане"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Програма за правописна проверка за Android"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Команди за рег. файл за проучвания"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Програма за правописна проверка за Android (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Настройки за проверка на правописа"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Търсене на имена"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"За проверка на правописа се ползват записи от списъка с контакти"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Да вибрира при натискане на клавиш"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"По подразбиране"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Предложения за контакти"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Използване на имена от „Контакти“ за предложения и поправки"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Повторни поправки: Актив."</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Задаване на предложения за повторни поправки"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Автоматично поставяне на главни букви"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Добавени речници"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Основен речник"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Умерено"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Агресивно"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Много агресивно"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Предложения за следващата дума"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Използване на предишната дума за подобряване на предложенията"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Предвиждане на следващата дума"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Използване на предишната дума и за предвиждане"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Предложения за следващата дума"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Въз основа на предишната дума"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Запазено"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Старт"</string>
     <string name="label_next_key" msgid="362972844525672568">"Напред"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Глас. въвежд. е деакт."</string>
     <string name="configure_input_method" msgid="373356270290742459">"Конфигуриране на въвеждането"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Входни езици"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Отбелязване на часа в рег. файл"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Часът е записан"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Без регистр. на сесията"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Рег. файл на сесията се изтрива"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Рег. файл на сесията е изтрит"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Рег. файл на сесията НЕ Е изтрит"</string>
     <string name="select_language" msgid="3693815588777926848">"Езици за въвеждане"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Докоснете отново, за да запазите"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Има достъп до речник"</string>
diff --git a/java/res/values-ca/strings-appname.xml b/java/res/values-ca/strings-appname.xml
new file mode 100644
index 0000000..6a23c24
--- /dev/null
+++ b/java/res/values-ca/strings-appname.xml
@@ -0,0 +1,28 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (178705338187710493) -->
+    <skip />
+    <string name="spell_checker_service_name" msgid="6268342166872202903">"Corrector ortogràfic d\'Android"</string>
+    <string name="english_ime_settings" msgid="7470027018752707691">"Configuració del teclat d\'Android"</string>
+    <string name="android_spell_checker_settings" msgid="8397842018475560441">"Configuració de la correcció ortogràfica"</string>
+</resources>
diff --git a/java/res/values-ca/strings.xml b/java/res/values-ca/strings.xml
index e3adbcf..01cc422 100644
--- a/java/res/values-ca/strings.xml
+++ b/java/res/values-ca/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Teclat Android"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Teclat d\'Android (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Configuració del teclat d\'Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opcions d\'entrada"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Corrector ortogràfic d\'Android"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Recerca d\'ordres de reg."</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Corrector ortogràfic d\'Android (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Configuració de la correcció ortogràfica"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Cerca noms de contactes"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"El corrector ortogràfic utilitza entrades de la llista de cont."</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibra en prémer tecles"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Predeterminat"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Suggereix noms contactes"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Utilitza els noms de Contactes per a suggeriments i correccions"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Activa la capacitat de tornar a corregir"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Estableix suggeriments per tornar a corregir"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Majúscules automàtiques"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Diccionaris complementaris"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Diccionari principal"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Moderada"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Total"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Molt agressiu"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Suggeriments de paraula següent"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Utilitza la paraula anterior per millorar els suggeriments"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Predicció de paraula següent"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Utilitza també la paraula anterior per a la predicció"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Suggeriments de paraula següent"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"En funció de la paraula anterior"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: desada"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Vés"</string>
     <string name="label_next_key" msgid="362972844525672568">"Següent"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Entr. veu desactiv."</string>
     <string name="configure_input_method" msgid="373356270290742459">"Configura mètodes d\'entrada"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Idiomes d\'entrada"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Indica m. horària al reg."</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Marca horària enregistrada"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"No enregistris la sessió"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Suprimint registre de ses."</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Registre de ses. suprimit"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Registre de ses. NO sup."</string>
     <string name="select_language" msgid="3693815588777926848">"Idiomes d\'entrada"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Torna a tocar per desar"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Diccionari disponible"</string>
diff --git a/java/res/values-cs/strings.xml b/java/res/values-cs/strings.xml
index 90d840c..9764ce5 100644
--- a/java/res/values-cs/strings.xml
+++ b/java/res/values-cs/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Klávesnice Android"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Klávesnice Android (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Nastavení klávesnice Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Možnosti zadávání textu a dat"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Kontrola pravopisu Android"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Příkazy vývoj. protokolu"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Kontrola pravopisu Android (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Nastavení kontroly pravopisu"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Vyhledat kontakty"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Kontrola pravopisu používá záznamy z vašeho seznamu kontaktů."</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Při stisku klávesy vibrovat"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Výchozí"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Navrhovat jména kontaktů"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Použít jména ze seznamu kontaktů k návrhům a opravám"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Povolit opětovné opravy"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Nastavit návrhy pro opětovné opravy"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Velká písmena automaticky"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Doplňkové slovníky"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Hlavní slovník"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Mírné"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Agresivní"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Velmi agresivní"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Návrh dalšího slova"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Použít předchozí slovo ke zlepšení návrhů"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Odhad dalšího slova"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Použít předchozí slovo také pro odhad"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Návrhy dalšího slova"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Na základě předchozího slova"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: Uloženo"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Přejít"</string>
     <string name="label_next_key" msgid="362972844525672568">"Další"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Hlasový vstup vypnut"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Konfigurace metod zadávání"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Vstupní jazyky"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Uložit čas do protokolu"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Časové razítko vloženo"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Neprotokolovat relaci"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Mazání protokolu relace"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Protokol relace smazán"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Protokol relace nesmazán"</string>
     <string name="select_language" msgid="3693815588777926848">"Vstupní jazyky"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Opětovným dotykem provedete uložení"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Slovník k dispozici"</string>
diff --git a/java/res/values-da/strings-appname.xml b/java/res/values-da/strings-appname.xml
new file mode 100644
index 0000000..610d88a
--- /dev/null
+++ b/java/res/values-da/strings-appname.xml
@@ -0,0 +1,28 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (178705338187710493) -->
+    <skip />
+    <string name="spell_checker_service_name" msgid="6268342166872202903">"Android-stavekontrol"</string>
+    <string name="english_ime_settings" msgid="7470027018752707691">"Indstillinger for Android-tastatur"</string>
+    <string name="android_spell_checker_settings" msgid="8397842018475560441">"Indstillinger for stavekontrol"</string>
+</resources>
diff --git a/java/res/values-da/strings.xml b/java/res/values-da/strings.xml
index 50b0b0a..e669f71 100644
--- a/java/res/values-da/strings.xml
+++ b/java/res/values-da/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Android-tastatur"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android-tastatur (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Android-tastatur-indstillinger"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Indstillinger for input"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Android-stavekontrol"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Forskningslogkommandoer"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Android-stavekontrol (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Indstillinger for stavekontrol"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Slå kontaktnavne op"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Stavekontrollen bruger poster fra listen over kontaktpersoner"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibration ved tastetryk"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Standard"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Foreslå navne på kontakter"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Brug navne fra Kontaktpersoner til forslag og rettelser"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Aktivér fornyet rettelse"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Angiv forslag til fornyet rettelse"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Skriv aut. med stort"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Tillægsordbøger"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Hovedordbog"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Moderat"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Aggressiv"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Meget aggressiv"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Forslag til næste ord"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Brug forrige ord til at forbedre forslag"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Forudsigelse af næste ord"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Brug også tidligere ord til forudsigelse"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Forslag til næste ord"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Baseret på tidligere ord"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: Gemt"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Gå"</string>
     <string name="label_next_key" msgid="362972844525672568">"Næste"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Stemmeinput deaktiveret"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Konfigurer inputmetoder"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Inputsprog"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Notér tidsstempel i log"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Noteret tidsstempel"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Logfør ikke denne session"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Sletter sessionslogfil"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Sessionslogfil slettet"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Sessionslog IKKE slettet"</string>
     <string name="select_language" msgid="3693815588777926848">"Inputsprog"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Tryk igen for at gemme"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Ordbog er tilgængelig"</string>
diff --git a/java/res/values-de/strings-appname.xml b/java/res/values-de/strings-appname.xml
new file mode 100644
index 0000000..bc77f72
--- /dev/null
+++ b/java/res/values-de/strings-appname.xml
@@ -0,0 +1,28 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (178705338187710493) -->
+    <skip />
+    <string name="spell_checker_service_name" msgid="6268342166872202903">"Android-Rechtschreibprüfung"</string>
+    <string name="english_ime_settings" msgid="7470027018752707691">"Android-Tastatureinstellungen"</string>
+    <string name="android_spell_checker_settings" msgid="8397842018475560441">"Einstellungen für Rechtschreibprüfung"</string>
+</resources>
diff --git a/java/res/values-de/strings.xml b/java/res/values-de/strings.xml
index 216aac5..827ff60 100644
--- a/java/res/values-de/strings.xml
+++ b/java/res/values-de/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Android-Tastatur"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android-Tastatur (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Android-Tastatureinstellungen"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Eingabeoptionen"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Android-Rechtschreibprüfung"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Forschungsprotokollbefehle"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Android-Rechtschreibprüfung (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Einstellungen für Rechtschreibprüfung"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Kontaktnamen prüfen"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Rechtschreibprüfung verwendet Einträge aus Ihrer Kontaktliste."</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Bei Tastendruck vibrieren"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Standard"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Kontakte vorschlagen"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Namen aus \"Kontakte\" als Vorschläge und Korrekturmöglichkeiten anzeigen"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Korrekturen aktivieren"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Vorschläge für Korrekturen festlegen"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Autom. Groß-/Kleinschr."</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Erweiterte Wörterbücher"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Allgemeines Wörterbuch"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Mäßig"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Stark"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Sehr stark"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Vorschläge für nächstes Wort"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Zur Verbesserung von Vorschlägen vorheriges Wort verwenden"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Vervollständigung für nächstes Wort"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Vorheriges Wort auch für Vervollständigung verwenden"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Vorschläge für nächstes Wort"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Auf Grundlage des vorherigen Wortes"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: gespeichert"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Los"</string>
     <string name="label_next_key" msgid="362972844525672568">"Weiter"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Spracheingabe deaktiviert"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Eingabemethoden konfigurieren"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Eingabesprachen"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Zeitstempel im Protokoll"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Zeitstempel aufgenommen"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Nicht protokollieren"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Protokoll wird gelöscht..."</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Protokoll gelöscht"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Protokoll NICHT gelöscht"</string>
     <string name="select_language" msgid="3693815588777926848">"Eingabesprachen"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Zum Speichern erneut berühren"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Wörterbuch verfügbar"</string>
diff --git a/java/res/values-el/strings-appname.xml b/java/res/values-el/strings-appname.xml
new file mode 100644
index 0000000..6daf2d9
--- /dev/null
+++ b/java/res/values-el/strings-appname.xml
@@ -0,0 +1,28 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (178705338187710493) -->
+    <skip />
+    <string name="spell_checker_service_name" msgid="6268342166872202903">"Ορθογραφικός έλεγχος Android"</string>
+    <string name="english_ime_settings" msgid="7470027018752707691">"Ρυθμίσεις πληκτρολογίου Android"</string>
+    <string name="android_spell_checker_settings" msgid="8397842018475560441">"Ρυθμίσεις ορθογραφικού ελέγχου"</string>
+</resources>
diff --git a/java/res/values-el/strings.xml b/java/res/values-el/strings.xml
index 486346a..563e181 100644
--- a/java/res/values-el/strings.xml
+++ b/java/res/values-el/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Πληκτρολόγιο Android"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Πληκτρολόγιο Android (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Ρυθμίσεις πληκτρολογίου Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Επιλογές εισόδου"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Ορθογραφικός έλεγχος Android"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Έρευνα εντολών καταγραφής"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Ορθογραφικός έλεγχος Android (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Ρυθμίσεις ορθογραφικού ελέγχου"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Αναζήτηση ονομάτων επαφών"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Ο ορθογρ. έλεγχος χρησιμοπ. καταχωρίσεις από τη λίστα επαφών σας"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Δόνηση κατά το πάτημα πλήκτρων"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Προεπιλογή"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Πρόταση ονομάτων επαφών"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Χρησιμοποιήστε ονόματα από τις Επαφές για προτάσεις και διορθ."</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Ενεργ. επανάλ. διορθώσεων"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Ορισμός προτάσεων για επαναλήψεις διορθώσεων"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Αυτόματη χρήση κεφαλαίων"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Πρόσθετα λεξικά"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Κύριο λεξικό"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Μέτρια"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Υψηλή"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Πολύ επιθετική"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Προτάσεις επόμενων λέξεων"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Χρήση προηγούμενης λέξης για τη βελτίωση προτάσεων"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Πρόβλεψη επόμενης λέξης"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Χρησιμοποιήστε, επίσης, την προηγούμενη λέξη για πρόβλεψη"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Προτάσεις επόμενων λέξεων"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Βάσει προηγούμενης λέξης"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Αποθηκεύτηκε"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Μετ."</string>
     <string name="label_next_key" msgid="362972844525672568">"Επόμενο"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Απεν. φωνητ. είσοδος"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Διαμόρφωση μεθόδων εισαγωγής"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Γλώσσες εισόδου"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Χρόνος στο αρχείο καταγρ."</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Καταγεγραμμένος χρόνος"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Χωρίς αρχείο καταγραφής"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Διαγραφή αρχείου σύνδεσης"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Αρχείο καταγρ. διαγράφηκε"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Αρχείο καταγρ. ΔΕΝ διαγρ."</string>
     <string name="select_language" msgid="3693815588777926848">"Γλώσσες εισόδου"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Αγγίξτε ξανά για αποθήκευση"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Λεξικό διαθέσιμο"</string>
diff --git a/java/res/values-en-rGB/strings-appname.xml b/java/res/values-en-rGB/strings-appname.xml
new file mode 100644
index 0000000..53f9e9d
--- /dev/null
+++ b/java/res/values-en-rGB/strings-appname.xml
@@ -0,0 +1,28 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (178705338187710493) -->
+    <skip />
+    <string name="spell_checker_service_name" msgid="6268342166872202903">"Android spell checker"</string>
+    <string name="english_ime_settings" msgid="7470027018752707691">"Android keyboard settings"</string>
+    <string name="android_spell_checker_settings" msgid="8397842018475560441">"Spell checking settings"</string>
+</resources>
diff --git a/java/res/values-en-rGB/strings.xml b/java/res/values-en-rGB/strings.xml
index 5020076..513fae4 100644
--- a/java/res/values-en-rGB/strings.xml
+++ b/java/res/values-en-rGB/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Android keyboard"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android keyboard (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Android keyboard settings"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Input options"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Android spell checker"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Research Log Commands"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Android spell checker (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Spellchecking settings"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Look up contact names"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Spell checker uses entries from your contact list"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrate on key-press"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Default"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Suggest Contact names"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Use names from Contacts for suggestions and corrections"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Enable recorrections"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Set suggestions for recorrections"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Auto-capitalisation"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Add-on dictionaries"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Main dictionary"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Modest"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Aggressive"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Very aggressive"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Next word suggestions"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Use previous word to improve suggestion"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Next word prediction"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Use previous word also for prediction"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Next word suggestions"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Based on previous word"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Saved"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Go"</string>
     <string name="label_next_key" msgid="362972844525672568">"Next"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Voice input is disabled"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Configure input methods"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Input languages"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Note timestamp in log"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Recorded timestamp"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Do not log this session"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Deleting session log"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Session log deleted"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Session log NOT deleted"</string>
     <string name="select_language" msgid="3693815588777926848">"Input languages"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Touch again to save"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Dictionary available"</string>
diff --git a/java/res/values-es-rUS/strings.xml b/java/res/values-es-rUS/strings.xml
index 34ad0a4..3eb32c1 100644
--- a/java/res/values-es-rUS/strings.xml
+++ b/java/res/values-es-rUS/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Teclado de Android"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Teclado de Android (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Configuración de teclado de Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opciones de entrada"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Corrector ortográfico de Android"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Comandos registro invest."</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Corrector ortográfico de Android (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Configuración del corrector ortográfico"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Buscar nombres contactos"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"El corrector ortográfico usa entradas de tu lista de contactos."</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrar al pulsar teclas"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Predeterminada"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Sugerir nombres de contacto"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Usar nombres de los contactos para sugerencias y correcciones"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Activar correcciones"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Establecer sugerencias para realizar correcciones"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Mayúsculas automáticas"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Diccionarios complementarios"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Diccionario principal"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Moderado"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Total"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Muy agresivo"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Sugerencias para la palabra siguiente"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Usar la palabra anterior para mejorar las sugerencias"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Predicción de la palabra siguiente"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Usar la palabra anterior también para predicción."</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Sugerencias de palabra siguiente"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Según la palabra anterior"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: guardada"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Ir"</string>
     <string name="label_next_key" msgid="362972844525672568">"Siguiente"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"La entrada por voz está inhabilitada"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Configurar métodos de entrada"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Idiomas de entrada"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Marcar tiempo en registro"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Marca tiempo registrada"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"No registrar esta sesión"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Eliminando registro"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Registro sesión eliminado"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"NO se eliminó el registro"</string>
     <string name="select_language" msgid="3693815588777926848">"Idiomas de entrada"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Vuelve a tocar para guardar."</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Diccionario disponible"</string>
diff --git a/java/res/values-es/strings-appname.xml b/java/res/values-es/strings-appname.xml
new file mode 100644
index 0000000..e716e7d
--- /dev/null
+++ b/java/res/values-es/strings-appname.xml
@@ -0,0 +1,28 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (178705338187710493) -->
+    <skip />
+    <string name="spell_checker_service_name" msgid="6268342166872202903">"Corrector ortográfico de Android"</string>
+    <string name="english_ime_settings" msgid="7470027018752707691">"Ajustes del teclado de Android"</string>
+    <string name="android_spell_checker_settings" msgid="8397842018475560441">"Ajustes del corrector ortográfico"</string>
+</resources>
diff --git a/java/res/values-es/strings.xml b/java/res/values-es/strings.xml
index 3c60aa6..1eee5d9 100644
--- a/java/res/values-es/strings.xml
+++ b/java/res/values-es/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Teclado de Android"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Teclado Android (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Ajustes del teclado de Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opciones entrada texto"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Corrector de Android"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Comandos registro investigación"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Corrector de Android (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Ajustes del corrector ortográfico"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Nombres de contactos"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Añadir nombres de tu lista de contactos al corrector"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrar al pulsar tecla"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Predeterminado"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Sugerir contactos"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Utilizar nombres de contactos para sugerencias y correcciones"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Activar nuevas correcciones"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Establecer sugerencias para nuevas correcciones"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Mayúsculas automáticas"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Diccionarios complementarios"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Diccionario principal"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Parcial"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Total"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Muy agresiva"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Sugerir siguiente palabra"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Usar palabra anterior para mejorar las sugerencias"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Predecir siguiente palabra"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Utilizar también la palabra anterior para realizar la predicción"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Sugerir siguiente palabra"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Según la palabra anterior"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: guardada"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Ir"</string>
     <string name="label_next_key" msgid="362972844525672568">"Sig."</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Entrada de voz inhabilitada"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Configurar métodos de entrada"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Idiomas"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Anotar marca tiempo en registro"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Marca de tiempo registrada"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"No registrar esta sesión"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Eliminando registro..."</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Registro eliminado"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Registro no eliminado"</string>
     <string name="select_language" msgid="3693815588777926848">"Idiomas de entrada"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Toca otra vez para guardar."</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Hay un diccionario disponible"</string>
diff --git a/java/res/values-et/strings.xml b/java/res/values-et/strings.xml
index 4a592c3..a68e565 100644
--- a/java/res/values-et/strings.xml
+++ b/java/res/values-et/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Androidi klaviatuur"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android-klaviatuur (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Androidi klaviatuuriseaded"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Sisestusvalikud"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Androidi õigekirjakontroll"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Uuringulogi käsud"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Androidi õigekirjakontroll (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Õigekirjakontrolli seaded"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Kontakti nimede kontroll."</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Õigekirjakontroll kasutab teie kontaktisikute loendi sissekandeid"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibreeri klahvivajutusel"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Vaikeseade"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Soovita kontaktkirjeid"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Kasuta soovitusteks ja parandusteks nimesid kontaktiloendist"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Uute paranduste lubamine"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Soovituste seadmine uute paranduste jaoks"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Automaatne suurtähtede kasutamine"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Pistiksõnaraamatud"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Peamine sõnaraamat"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Mõõdukas"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Agressiivne"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Väga agressiivne"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Järgmise sõna soovitused"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Kasuta soovituste täiustamiseks eelmist sõna"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Järgmise sõna ennustus"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Kasuta ennustuseks ka eelmist sõna"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Järgmise sõna soovitused"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Eelmise sõna põhjal"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : salvestatud"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Mine"</string>
     <string name="label_next_key" msgid="362972844525672568">"Edasi"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Kõnesisend on keelatud"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Sisestusmeetodite seadistamine"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Sisestuskeeled"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Märgi ajatempel logisse"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Salvestatud ajatemplid"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Ära logi seda seanssi"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Seansi logi kustutamine"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Seansi logi kustutatud"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Seansi logi EI kustutatud"</string>
     <string name="select_language" msgid="3693815588777926848">"Sisestuskeeled"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Salvestamiseks puudutage uuesti"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Sõnastik saadaval"</string>
diff --git a/java/res/values-fa/strings-appname.xml b/java/res/values-fa/strings-appname.xml
new file mode 100644
index 0000000..263f166
--- /dev/null
+++ b/java/res/values-fa/strings-appname.xml
@@ -0,0 +1,28 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (178705338187710493) -->
+    <skip />
+    <string name="spell_checker_service_name" msgid="6268342166872202903">"غلط‌گیر املای Android"</string>
+    <string name="english_ime_settings" msgid="7470027018752707691">"تنظیمات صفحه کلید Android"</string>
+    <string name="android_spell_checker_settings" msgid="8397842018475560441">"تنظیمات غلط‌‌ گیر املا"</string>
+</resources>
diff --git a/java/res/values-fa/strings.xml b/java/res/values-fa/strings.xml
index bc0e85b..d61d7eb 100644
--- a/java/res/values-fa/strings.xml
+++ b/java/res/values-fa/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"صفحه کلید Android"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"صفحه کلید (Android (AOSP"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"تنظیمات صفحه کلید Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"گزینه های ورودی"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"غلط‌گیر املای Android"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"فرمان‌های گزارش‌گیری پژوهش"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"غلط‌گیر املای Android (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"تنظیمات غلط گیری املایی"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"جستجوی نام مخاطبین"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"غلط‌گیر املا از ورودی‌های لیست مخاطبین شما استفاده میکند"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"لرزش با فشار کلید"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"پیش فرض"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"پیشنهاد نام های مخاطب"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"برای پیشنهاد و تصحیح از نام مخاطبین استفاده شود"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"فعال کردن تصحیح مجدد"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"تنظیم پیشنهادات برای تصحیح مجدد"</string>
     <string name="auto_cap" msgid="1719746674854628252">"نوشتن با حروف بزرگ خودکار"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"فرهنگ‌های لغت افزودنی"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"فرهنگ‌ لغت اصلی"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"متوسط"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"فعال"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"بسیار پرخاشگرانه"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"پیشنهادات کلمه بعدی"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"برای بهبود پیشنهاد از کلمه قبلی استفاده شود"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"پیش‌بینی کلمه بعدی"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"استفاده از کلمه قبلی برای پیش بینی"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"پیشنهادات کلمه بعدی"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"بر اساس کلمه قبلی"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : ذخیره شد"</string>
     <string name="label_go_key" msgid="1635148082137219148">"برو"</string>
     <string name="label_next_key" msgid="362972844525672568">"بعدی"</string>
@@ -115,6 +108,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"ورودی صدا غیرفعال است"</string>
     <string name="configure_input_method" msgid="373356270290742459">"پیکربندی روش های ورودی"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"زبان های ورودی"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"یادداشت مهر زمان در گزارش"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"مهر زمان ثبت شده"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"از این جلسه گزارش‌گیری نشود"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"در حال حذف گزارش جلسه"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"گزارش جلسه حذف شد"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"گزارش جلسه حذف نشد"</string>
     <string name="select_language" msgid="3693815588777926848">"زبان‌های ورودی"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"برای ذخیره دوباره لمس کنید"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"دیکشنری موجود است"</string>
diff --git a/java/res/values-fi/strings-appname.xml b/java/res/values-fi/strings-appname.xml
new file mode 100644
index 0000000..fefb92e
--- /dev/null
+++ b/java/res/values-fi/strings-appname.xml
@@ -0,0 +1,28 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (178705338187710493) -->
+    <skip />
+    <string name="spell_checker_service_name" msgid="6268342166872202903">"Android-oikoluku"</string>
+    <string name="english_ime_settings" msgid="7470027018752707691">"Android-näppäimistön asetukset"</string>
+    <string name="android_spell_checker_settings" msgid="8397842018475560441">"Oikolukuasetukset"</string>
+</resources>
diff --git a/java/res/values-fi/strings.xml b/java/res/values-fi/strings.xml
index 97002ed..c036142 100644
--- a/java/res/values-fi/strings.xml
+++ b/java/res/values-fi/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Android-näppäimistö"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android-näppäimistö (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Android-näppäimistön asetukset"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Syöttövalinnat"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Android-oikoluku"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Tutkimuslokin komennot"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Android-oikoluku (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Oikoluvun asetukset"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Hae kontaktien nimiä"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Oikeinkirjoituksen tarkistus käyttää kontaktiluettelosi tietoja."</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Käytä värinää näppäimiä painettaessa"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Oletus"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Ehdota yhteystietojen nimiä"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Käytä yhteystietojen nimiä ehdotuksissa ja korjauksissa"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Ota korjaukset käyttöön"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Aseta korjausehdotuksia"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Automaattiset isot kirjaimet"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Lisäsanakirjat"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Pääsanakirja"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Osittainen"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Täysi"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Hyvin aggressiivinen"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Seuraavan sanan ehdotukset"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Paranna ehdotuksia aiempien sanojen avulla"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Seuraavan sanan ennakointi"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Käytä edellistä sanaa myös ennakointiin"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Seuraavan sanan ehdotukset"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Perustuu edelliseen sanan"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Tallennettu"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Siirry"</string>
     <string name="label_next_key" msgid="362972844525672568">"Seur."</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Ääniohjaus on pois käytöstä"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Määritä syöttötavat"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Syöttökielet"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Merkitse aikaleima lokiin"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Merkitty aikaleima"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Älä tallenna tätä käyttök."</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Poistetaan lokia"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Käyttökertaloki poistettu"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Lokia EI poistettu"</string>
     <string name="select_language" msgid="3693815588777926848">"Syöttökielet"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Tallenna koskettamalla uudelleen"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Sanakirja saatavilla"</string>
diff --git a/java/res/values-fr/strings-appname.xml b/java/res/values-fr/strings-appname.xml
new file mode 100644
index 0000000..da7671b
--- /dev/null
+++ b/java/res/values-fr/strings-appname.xml
@@ -0,0 +1,28 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (178705338187710493) -->
+    <skip />
+    <string name="spell_checker_service_name" msgid="6268342166872202903">"Correcteur orthographique Android"</string>
+    <string name="english_ime_settings" msgid="7470027018752707691">"Paramètres du clavier Android"</string>
+    <string name="android_spell_checker_settings" msgid="8397842018475560441">"Paramètres du correcteur orthographique"</string>
+</resources>
diff --git a/java/res/values-fr/strings.xml b/java/res/values-fr/strings.xml
index 285d222..b5b1eac 100644
--- a/java/res/values-fr/strings.xml
+++ b/java/res/values-fr/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Clavier Android"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Clavier Android (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Paramètres du clavier Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Options de saisie"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Correcteur orthographique Android"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Commandes journaux rech."</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Correcteur orthographique Android (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Paramètre du correcteur orthographique"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Rechercher noms contacts"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Correcteur orthographique utilise entrées de liste de contacts."</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrer à chaque touche"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Par défaut"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Proposer noms de contacts"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Utiliser des noms de contacts pour les suggestions et corrections"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Activer la recorrection"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Définir des suggestions de recorrection"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Majuscules auto"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Dictionnaires complémentaires"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Dictionnaire principal"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Simple"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Proactive"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Très exigeante"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Suggestions pour le mot suivant"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Améliorer les suggestions grâce au mot précédent"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Prédiction du mot suivant"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Utiliser le mot précédent pour la prédiction"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Suggestions pour le mot suivant"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Suggestions basées sur le mot précédent"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : enregistré"</string>
     <string name="label_go_key" msgid="1635148082137219148">"OK"</string>
     <string name="label_next_key" msgid="362972844525672568">"Suiv."</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Saisie vocale désactivée"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Configurer les modes de saisie"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Langues de saisie"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Noter heure dans journal"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Heure enregistrée."</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Ne pas enregistrer session"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Suppr. journal session…"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Journal session supprimé."</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Journal session PAS suppr."</string>
     <string name="select_language" msgid="3693815588777926848">"Langues de saisie"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Appuyer de nouveau pour enregistrer"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Dictionnaire disponible"</string>
diff --git a/java/res/values-hi/strings-appname.xml b/java/res/values-hi/strings-appname.xml
new file mode 100644
index 0000000..c8d34ec
--- /dev/null
+++ b/java/res/values-hi/strings-appname.xml
@@ -0,0 +1,28 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (178705338187710493) -->
+    <skip />
+    <string name="spell_checker_service_name" msgid="6268342166872202903">"Android वर्तनी परीक्षक"</string>
+    <string name="english_ime_settings" msgid="7470027018752707691">"Android कीबोर्ड सेटिंग"</string>
+    <string name="android_spell_checker_settings" msgid="8397842018475560441">"वर्तनी जांच सेटिंग"</string>
+</resources>
diff --git a/java/res/values-hi/strings.xml b/java/res/values-hi/strings.xml
index 2b18084..a567faf 100644
--- a/java/res/values-hi/strings.xml
+++ b/java/res/values-hi/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Android कीबोर्ड"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android कीबोर्ड (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Android कीबोर्ड सेटिंग"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"इनपुट विकल्‍प"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Android वर्तनी परीक्षक"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"लॉग आदेशों का शोध करें"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Android वर्तनी परीक्षक (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"वर्तनी जांच सेटिंग"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"संपर्क नामों को खोजें"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"वर्तनी परीक्षक आपकी संपर्क सूची की प्रविष्टियों का उपयोग करता है"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"कुंजी दबाने पर कंपन करता है"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"डिफ़ॉल्ट"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"संपर्क नाम सुझाएं"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"सुझाव और सुधार के लिए संपर्क से नामों का उपयोग करें"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"पुन: सुधार सक्षम करें"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"पुन: सुधार के लि‍ए सुझाव सेट करें"</string>
     <string name="auto_cap" msgid="1719746674854628252">"स्‍वत: अक्षर बड़े करना"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"एड-ऑन डिक्शनरी"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"मुख्‍य डिक्‍शनरी"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"साधारण"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"तीव्र"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"बहुत तीव्र"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"अगला शब्‍द सुझाव"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"सुझावों को बेहतर बनाने के लिए पिछले शब्‍द का उपयोग करें"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"अगला शब्‍द पूर्वानुमान"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"पूर्वानुमान के लिए पिछले शब्द का उपयोग करें"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"अगले शब्द सुझाव"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"पिछले शब्द के आधार पर"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: सहेजा गया"</string>
     <string name="label_go_key" msgid="1635148082137219148">"जाएं"</string>
     <string name="label_next_key" msgid="362972844525672568">"अगला"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"ध्‍वनि इनपुट अक्षम है"</string>
     <string name="configure_input_method" msgid="373356270290742459">"इनपुट पद्धति कॉन्‍फ़िगर करें"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"इनपुट भाषा"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"लॉग में टाइमस्‍टैम्‍प नोट करें"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"रिकॉर्ड किया गया टाइमस्टैम्प"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"इस सत्र को लॉग न करें"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"सत्र लॉग हटाया जा रहा है"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"सत्र लॉग हटाया गया"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"सत्र लॉग हटाया नहीं गया"</string>
     <string name="select_language" msgid="3693815588777926848">"इनपुट भाषाएं"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"सहेजने के लिए पुन: स्‍पर्श करें"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"शब्‍दकोश उपलब्‍ध है"</string>
diff --git a/java/res/values-hr/strings-appname.xml b/java/res/values-hr/strings-appname.xml
new file mode 100644
index 0000000..6457a21
--- /dev/null
+++ b/java/res/values-hr/strings-appname.xml
@@ -0,0 +1,28 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (178705338187710493) -->
+    <skip />
+    <string name="spell_checker_service_name" msgid="6268342166872202903">"Androidova provjera pravopisa"</string>
+    <string name="english_ime_settings" msgid="7470027018752707691">"Postavke Androidove tipkovnice"</string>
+    <string name="android_spell_checker_settings" msgid="8397842018475560441">"Postavke provjere pravopisa"</string>
+</resources>
diff --git a/java/res/values-hr/strings.xml b/java/res/values-hr/strings.xml
index a59d469..76f6edf 100644
--- a/java/res/values-hr/strings.xml
+++ b/java/res/values-hr/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Android tipkovnica"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android tipkovnica (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Postavke tipkovnice za Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opcije ulaza"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Androidova provjera pravopisa"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Istraživanje naredbi dnevnika"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Androidova provjera pravopisa (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Postavke provjere pravopisa"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Potražite imena kontakata"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Provjera pravopisa upotrebljava unose iz vašeg popisa kontakata"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibracija pri pritisku na tipku"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Zadano"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Predlaži imena kontakata"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Upotreba imena iz Kontakata za prijedloge i ispravke"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Omogući ponovne ispravke"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Postavite prijedloge za ponovne ispravke"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Automatsko pisanje velikih slova"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Rječnici-dodaci"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Glavni rječnik"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Skromno"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Agresivno"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Vrlo agresivno"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Prijedlozi za sljedeću riječ"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Upotrijebi prethodnu riječ radi poboljšanja prijedloga"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Predviđanje sljedeće riječi"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Upotrijebite prethodnu riječ i za predviđanje"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Prijedlozi za sljedeću riječ"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Na temelju prethodne riječi"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Spremljeno"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Idi"</string>
     <string name="label_next_key" msgid="362972844525672568">"Dalje"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Glas. unos onemog."</string>
     <string name="configure_input_method" msgid="373356270290742459">"Konfiguriraj načine ulaza"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Jezici unosa"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Zabilježi razdoblje u dnevniku"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Zabilježeno razdoblje"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Ne bilježi ovu sesiju"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Brisanje dnevnika sesije"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Izbrisan dnevnik sesije"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Dnevnik sesije NIJE izbrisan"</string>
     <string name="select_language" msgid="3693815588777926848">"Jezici unosa"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Dodirnite ponovo za spremanje"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Rječnik je dostupan"</string>
diff --git a/java/res/values-hu/strings.xml b/java/res/values-hu/strings.xml
index 0eac1a9..4bf9f42 100644
--- a/java/res/values-hu/strings.xml
+++ b/java/res/values-hu/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Android-billentyűzet"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android-billentyűzet (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Android billentyűzetbeállítások"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Beviteli beállítások"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Androidos helyesírás-ellenőrző"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Naplózási parancsok"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Androidos helyesírás-ellenőrző (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Helyesírás-ellenőrzés beállításai"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Névjegyek keresése"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"A helyesírás-ellenőrző használja a névjegyek bejegyzéseit"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Rezgés billentyű megnyomása esetén"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Alapbeállítás"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Javasolt névjegyek"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"A névjegyek használata a javaslatokhoz és javításokhoz"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Újbóli javítás engedélyezése"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Javaslatok beállítása az újbóli javításokhoz"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Automatikusan nagy kezdőbetű"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Bővítmények: szótárak"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Fő szótár"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Mérsékelt"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Agresszív"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Nagyon agresszív"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Következő szóra vonatkozó javaslatok"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Javaslatok fejlesztése az előző szó használatával"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Következő szó előrejelzése"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Az előző szó használata a prediktív bevitelhez is"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Következő szóra vonatkozó javaslatok"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Az előző szó alapján"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : mentve"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Ugrás"</string>
     <string name="label_next_key" msgid="362972844525672568">"Tovább"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Hangbevivel KI"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Beviteli módok beállítása"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Beviteli nyelvek"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Időbélyegző naplózáskor"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Rögzített időbélyegzők"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Ne naplózza"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Napló törlése folyamatban"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Napló törölve"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Napló NINCS törölve"</string>
     <string name="select_language" msgid="3693815588777926848">"Beviteli nyelvek"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Érintse meg újból a mentéshez"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Van elérhető szótár"</string>
diff --git a/java/res/values-in/strings.xml b/java/res/values-in/strings.xml
index e0e92b5..ee4651c 100644
--- a/java/res/values-in/strings.xml
+++ b/java/res/values-in/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Keyboard Android"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Keyboard Android (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Setelan keyboard Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opsi masukan"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Pemeriksa ejaan Android"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Riset Perintah Log"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Pemeriksa ejaan Android (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Setelan pemeriksaan ejaan"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Cari nama kontak"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Pemeriksa ejaan menggunakan entri dari daftar kontak Anda"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Getar jika tombol ditekan"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Default"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Sarankan nama Kontak"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Menggunakan nama dari Kontak untuk saran dan koreksi"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Aktifkan koreksi ulang"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Setel saran untuk koreksi ulang"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Kapitalisasi otomatis"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Kamus pengaya"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Kamus utama"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Sederhana"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Agresif"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Sangat agresif"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Saran kata berikutnya"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Gunakan kata sebelumnya untuk meningkatkan saran"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Prediksi kata berikutnya"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Gunakan kata sebelumnya juga untuk prediksi"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Saran kata berikutnya"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Berdasarkan kata sebelumnya"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Telah disimpan"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Buka"</string>
     <string name="label_next_key" msgid="362972844525672568">"Berikutnya"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Masukan suara dinonaktifkan"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Konfigurasikan metode masukan"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Bahasa masukan"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Catat cap waktu di log"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Cap waktu yang direkam"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Jangan simpan log sesi ini"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Menghapus log sesi"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Log sesi dihapus"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Log sesi BELUM dihapus"</string>
     <string name="select_language" msgid="3693815588777926848">"Bahasa masukan"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Sentuh lagi untuk menyimpan"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Kamus yang tersedia"</string>
diff --git a/java/res/values-it/strings-appname.xml b/java/res/values-it/strings-appname.xml
new file mode 100644
index 0000000..838daaf
--- /dev/null
+++ b/java/res/values-it/strings-appname.xml
@@ -0,0 +1,28 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (178705338187710493) -->
+    <skip />
+    <string name="spell_checker_service_name" msgid="6268342166872202903">"Controllo ortografico Android"</string>
+    <string name="english_ime_settings" msgid="7470027018752707691">"Impostazioni tastiera Android"</string>
+    <string name="android_spell_checker_settings" msgid="8397842018475560441">"Impostazioni di controllo ortografico"</string>
+</resources>
diff --git a/java/res/values-it/strings.xml b/java/res/values-it/strings.xml
index a54958c..e52e250 100644
--- a/java/res/values-it/strings.xml
+++ b/java/res/values-it/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Tastiera Android"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Tastiera Android (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Impostazioni tastiera Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opzioni inserimento"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Controllo ortografico Android"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Ricerca comandi di log"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Controllo ortografico Android (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Impostazioni di controllo ortografico"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Cerca in nomi contatti"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"La funzione di controllo ortografico usa voci dell\'elenco contatti"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrazione tasti"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Predefinito"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Suggerisci nomi di contatti"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Utilizza nomi di Contatti per suggerimenti e correzioni"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Attiva nuove correzioni"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Imposta suggerimenti per nuove correzioni"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Maiuscole automatiche"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Dizionari aggiuntivi"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Dizionario principale"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Media"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Massima"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Massima"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Suggerimenti parola successiva"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Usa parola precedente per migliorare suggerimenti"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Previsione parola successiva"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Usa anche la parola precedente per la previsione"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Suggerimenti parola successiva"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"In base alla parola precedente"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : parola salvata"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Vai"</string>
     <string name="label_next_key" msgid="362972844525672568">"Avanti"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Comandi vocali disatt."</string>
     <string name="configure_input_method" msgid="373356270290742459">"Configura metodi di immissione"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Lingue comandi"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Indicazione temporale log"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Indicazione temporale registrata"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Non registrare la sessione"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Eliminazione log sessione"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Log di sessione eliminato"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Log sessione non eliminato"</string>
     <string name="select_language" msgid="3693815588777926848">"Lingue comandi"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Tocca di nuovo per salvare"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Dizionario disponibile"</string>
diff --git a/java/res/values-iw/strings-appname.xml b/java/res/values-iw/strings-appname.xml
new file mode 100644
index 0000000..92bc240
--- /dev/null
+++ b/java/res/values-iw/strings-appname.xml
@@ -0,0 +1,28 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (178705338187710493) -->
+    <skip />
+    <string name="spell_checker_service_name" msgid="6268342166872202903">"בודק האיות של Android"</string>
+    <string name="english_ime_settings" msgid="7470027018752707691">"הגדרות מקלדת Android"</string>
+    <string name="android_spell_checker_settings" msgid="8397842018475560441">"הגדרות בדיקת איות"</string>
+</resources>
diff --git a/java/res/values-iw/strings.xml b/java/res/values-iw/strings.xml
index 323cac1..662f2f4 100644
--- a/java/res/values-iw/strings.xml
+++ b/java/res/values-iw/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"מקלדת Android"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"מקלדת Android ‏(AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"הגדרות מקלדת של Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"אפשרויות קלט"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"בודק האיות של Android"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"פקודות יומן מחקר"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"בודק האיות של Android ‏(AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"הגדרות בדיקת איות"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"חפש שמות של אנשי קשר"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"בודק האיות משתמש בערכים מרשימת אנשי הקשר שלך"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"רטט בלחיצה על מקשים"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"ברירת מחדל"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"הצע שמות של אנשי קשר"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"השתמש בשמות מרשימת אנשי הקשר עבור הצעות ותיקונים"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"הפוך תיקונים חוזרים לפעילים"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"הגדר הצעות עבור תיקונים חוזרים"</string>
     <string name="auto_cap" msgid="1719746674854628252">"הפיכת אותיות לרישיות באופן אוטומטי"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"הוספת מילונים"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"מילון ראשי"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"מצומצם"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"מחמיר"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"מחמיר מאוד"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"הצעות המילה הבאה"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"השתמש במילה הקודמת כדי לשפר את ההצעות"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"חיזוי המילה הבאה"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"השתמש במילה הקודמת גם עבור חיזוי"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"הצעות המילה הבאה"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"בהתבסס על המילה הקודמת"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : נשמרה"</string>
     <string name="label_go_key" msgid="1635148082137219148">"בצע"</string>
     <string name="label_next_key" msgid="362972844525672568">"הבא"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"הקלט הקולי מושבת"</string>
     <string name="configure_input_method" msgid="373356270290742459">"הגדרת שיטות קלט"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"שפות קלט"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"ציין חותמת זמן ביומן"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"חותמת זמן מתועדת"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"אל תרשום הפעלה זו ביומן"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"מוחק יומן הפעלה"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"יומן הפעלה נמחק"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"יומן הפעלה לא נמחק"</string>
     <string name="select_language" msgid="3693815588777926848">"שפות קלט"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"גע שוב כדי לשמור"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"מילון זמין"</string>
diff --git a/java/res/values-ja/strings.xml b/java/res/values-ja/strings.xml
index 3a86516..050ca66 100644
--- a/java/res/values-ja/strings.xml
+++ b/java/res/values-ja/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Androidキーボード"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Androidキーボード(AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Androidキーボードの設定"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"入力オプション"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Androidスペルチェッカー"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"ログコマンドの検索"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Androidスペルチェッカー(AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"スペルチェックの設定"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"連絡先名の検索"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"スペルチェッカーでは連絡先リストのエントリを使用します"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"キー操作バイブ"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"デフォルト"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"候補の連絡先名を表示"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"連絡先の名前を使用して候補表示や自動修正を行います"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"再修正を有効にする"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"再修正の候補を挿入する"</string>
     <string name="auto_cap" msgid="1719746674854628252">"自動大文字変換"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"アドオン辞書"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"メイン辞書"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"中"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"強"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"最も強い"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"次の入力候補"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"直前の単語から入力候補を予測します"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"次の入力候補を予測"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"前の語句も予測に使用"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"次の入力候補"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"前の語句に基づいた入力候補表示"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>:保存しました"</string>
     <string name="label_go_key" msgid="1635148082137219148">"実行"</string>
     <string name="label_next_key" msgid="362972844525672568">"次へ"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"音声入力は無効です"</string>
     <string name="configure_input_method" msgid="373356270290742459">"入力方法を設定"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"入力言語"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"タイムスタンプを記録"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"タイムスタンプ記録済み"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"セッションを記録しない"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"セッションログ削除中"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"セッションログ削除済み"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"セッションログ未削除"</string>
     <string name="select_language" msgid="3693815588777926848">"入力言語"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"保存するにはもう一度タップ"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"辞書を利用できます"</string>
diff --git a/java/res/values-ko/strings.xml b/java/res/values-ko/strings.xml
index 536f06d..516c8c3 100644
--- a/java/res/values-ko/strings.xml
+++ b/java/res/values-ko/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Android 키보드"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android 키보드(AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Android 키보드 설정"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"입력 옵션"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Android 맞춤법 검사기"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"로그 명령 탐색"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Android 맞춤법 검사기(AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"맞춤법 검사 설정"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"연락처 이름 조회"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"맞춤법 검사기가 주소록의 항목을 사용합니다."</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"키를 누를 때 진동 발생"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"기본값"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"주소록 이름 활용"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"추천 및 수정에 주소록의 이름 사용"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"재수정 가능 설정"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"재수정 추천어 사전 활성화"</string>
     <string name="auto_cap" msgid="1719746674854628252">"자동 대문자화"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"사전 추가"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"기본 사전"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"약"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"중"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"강"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"다음 추천 검색어"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"이전 단어를 사용하여 추천 검색어 개선"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"다음 예상 검색어"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"이전 단어를 사용하여 예상 검색어 표시"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"다음 검색어 추천"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"이전 단어에 기반한 추천"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: 저장됨"</string>
     <string name="label_go_key" msgid="1635148082137219148">"이동"</string>
     <string name="label_next_key" msgid="362972844525672568">"다음"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"음성 입력이 사용 중지됨"</string>
     <string name="configure_input_method" msgid="373356270290742459">"입력 방법 설정"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"입력 언어"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"로그에 타임스탬프를 기록"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"타임스탬프를 기록함"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"이 세션을 로그하지 마세요."</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"세션 로그 삭제"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"세션 로그가 삭제됨"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"세션 로그가 삭제되지 않음"</string>
     <string name="select_language" msgid="3693815588777926848">"입력 언어"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"저장하려면 다시 터치"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"사전 사용 가능"</string>
diff --git a/java/res/values-lt/strings.xml b/java/res/values-lt/strings.xml
index a0b8fc8..08d6659 100644
--- a/java/res/values-lt/strings.xml
+++ b/java/res/values-lt/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"„Android“ klaviatūra"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"„Android“ klaviatūra (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"„Android“ klaviatūros nustatymai"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Įvesties parinktys"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"„Android“ rašybos tikrinimo programa"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Tyrinėti žurnalo komandas"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"„Android“ rašybos tikrinimo programa (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Rašybos tikrinimo nustatymai"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Kontaktų vardų paieška"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Rašybos tikrinimo progr. naudoja įrašus, esančius kontaktų sąraše"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibruoti, kai paspaudžiami klavišai"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Numatytasis"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Siūlyti kontaktų vardus"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Siūlant ir taisant naudoti vardus iš „Kontaktų“"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Įdiegti pakartotinius pataisymus"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Nustatyti pakartotinio pataisymo pasiūlymus"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Automatinis didžiųjų raidžių rašymas"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Papildomi žodynai"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Pagrindinis žodynas"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Vidutinis"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Atkaklus"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Labai agresyviai"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Kito žodžio pasiūlymai"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Naudoti ankstesnį žodį pasiūlymams patobulinti"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Kito žodžio numatymas"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Numatant naudoti ir ankstesnį žodį"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Kito žodžio pasiūlymai"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Pagal ankstesnį žodį"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: išsaugota"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Pradėti"</string>
     <string name="label_next_key" msgid="362972844525672568">"Kitas"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Balso įv. neleidž."</string>
     <string name="configure_input_method" msgid="373356270290742459">"Konfigūruoti įvesties metodus"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Įvesties kalbos"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Pažym. laiko žymę žurnale"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Įrašyta laiko žymė"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Neįrašyti šios sesijos"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Ištrinam. sesijos žurnal."</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Sesijos žurnalas ištrint."</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Sesij. žurnal. NEIŠTRINT."</string>
     <string name="select_language" msgid="3693815588777926848">"Įvesties kalbos"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Jei norite išsaugoti, palieskite dar kartą"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Žodynas galimas"</string>
diff --git a/java/res/values-lv/strings-appname.xml b/java/res/values-lv/strings-appname.xml
new file mode 100644
index 0000000..fe2f336
--- /dev/null
+++ b/java/res/values-lv/strings-appname.xml
@@ -0,0 +1,28 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (178705338187710493) -->
+    <skip />
+    <string name="spell_checker_service_name" msgid="6268342166872202903">"Android pareizrakstības pārbaudītājs"</string>
+    <string name="english_ime_settings" msgid="7470027018752707691">"Android tastatūras iestatījumi"</string>
+    <string name="android_spell_checker_settings" msgid="8397842018475560441">"Pareizrakstības pārbaudes iestatījumi"</string>
+</resources>
diff --git a/java/res/values-lv/strings.xml b/java/res/values-lv/strings.xml
index 93727a8..3685ccc 100644
--- a/java/res/values-lv/strings.xml
+++ b/java/res/values-lv/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Android tastatūra"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android tastatūra (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Android tastatūras iestatījumi"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Ievades opcijas"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Android pareizrakstības pārbaudītājs"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Izpētes žurnāla komandas"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Android pareizrakstības pārbaudītājs (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Pareizrakstības pārbaudes iestatījumi"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Meklēt kontaktp. vārdus"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Pareizrakst. pārbaudītājs lieto ierakstus no kontaktp. saraksta."</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrēt, nospiežot taustiņu"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Noklusējums"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Ieteikt kontaktp. vārdus"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Izmantot kontaktpersonu vārdus kā ieteikumus un labojumus"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Iespējot atk. labojumus"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Iestatīt atkārtotu labojumu ieteikumus"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Automātiska lielo burtu lietošana"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Papildinājumu vārdnīcas"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Galvenā vārdnīca"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Mērena"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Agresīva"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Ļoti radikāla"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Nākamā vārda ieteikumi"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Ieteikumu uzlabošanai izmantot iepriekšējo vārdu"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Nākamā vārda prognozēšana"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Izmantot iepriekšējo vārdu arī prognozēm"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Nākamie vārdu ieteikumi"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Pamatojoties uz iepriekšējo vārdu"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: saglabāts"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Sākt"</string>
     <string name="label_next_key" msgid="362972844525672568">"Tālāk"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Balss iev. atspējota"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Ievades metožu konfigurēšana"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Ievades valodas"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Atzīmēt laiksp. žurnālā"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Laikspied. ir reģistrēts."</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Nereģistrēt šo sesiju"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Not. sesijas žurn. dzēš."</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Sesijas žurnāls ir dzēsts"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Sesijas žurn. NAV dzēsts"</string>
     <string name="select_language" msgid="3693815588777926848">"Ievades valodas"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Pieskarieties vēlreiz, lai saglabātu."</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Ir pieejama vārdnīca."</string>
diff --git a/java/res/values-ms/strings.xml b/java/res/values-ms/strings.xml
index a07c44f..59fec88 100644
--- a/java/res/values-ms/strings.xml
+++ b/java/res/values-ms/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Papan kekunci Android"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Papan kekunci Android (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Tetapan papan kekunci Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Pilihan input"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Penyemak ejaan Android"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Arahan Log Penyelidikan"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Penyemak ejaan Android (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Tetapan penyemakan ejaan"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Cari nama kenalan"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Penyemak ejaan menggunakan entri dari senarai kenalan anda"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Getar pada tekanan kekunci"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Lalai"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Cadangkan nama Kenalan"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Menggunakan nama daripada Kenalan untuk cadangan dan pembetulan"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Dayakan pembetulan semula"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Tetapkan cadangan untuk pembetulan semula"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Huruf besar auto"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Kamus tambahan"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Kamus utama"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Sederhana"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Agresif"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Sangat agresif"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Cadangan perkataan seterusnya"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Gunakan perkataan sebelumnya untuk memperbaik cadangan"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Ramalan perkataan seterusnya"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Gunakan juga perkataan sebelumnya untuk ramalan"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Cadangan perkataan seterusnya"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Berdasarkan perkataan sebelumnya"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Disimpan"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Pergi"</string>
     <string name="label_next_key" msgid="362972844525672568">"Seterusnya"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Input suara dilmphkn"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Konfigurasikan kaedah input"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Bahasa input"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Tanda cap waktu dalam log"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Cap waktu direkodkan"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Jangan log sesi ini"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Memadam log sesi"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Log sesi dipadam"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Log sesi TIDAK dipadam"</string>
     <string name="select_language" msgid="3693815588777926848">"Bahasa input"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Sentuh lagi untuk menyimpan"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Kamus tersedia"</string>
diff --git a/java/res/values-nb/strings-appname.xml b/java/res/values-nb/strings-appname.xml
new file mode 100644
index 0000000..b5ed18a
--- /dev/null
+++ b/java/res/values-nb/strings-appname.xml
@@ -0,0 +1,28 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (178705338187710493) -->
+    <skip />
+    <string name="spell_checker_service_name" msgid="6268342166872202903">"Android-stavekontroll"</string>
+    <string name="english_ime_settings" msgid="7470027018752707691">"Innstillinger for Android-tastatur"</string>
+    <string name="android_spell_checker_settings" msgid="8397842018475560441">"Innstillinger for stavekontroll"</string>
+</resources>
diff --git a/java/res/values-nb/strings.xml b/java/res/values-nb/strings.xml
index 75c1295..2f4194a 100644
--- a/java/res/values-nb/strings.xml
+++ b/java/res/values-nb/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Skjermtastatur"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android-tastatur (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Innstillinger for skjermtastatur"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Inndataalternativer"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Android-stavekontroll"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Kommandoer for undersøkelseslogging"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Android-stavekontroll (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Innstillinger for stavekontroll"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Slå opp kontaktnavn"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Stavekontrollen bruker oppføringer fra kontaktlisten din"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrer ved tastetrykk"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Standard"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Foreslå kontaktnavn"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Bruk navn fra Kontakter til forslag og korrigeringer"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Aktiver korrigeringer"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Angi forslag for korrigeringer"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Stor forbokstav"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Tilleggsordbøker"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Hovedordliste"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Moderat"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Omfattende"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Veldig aggressiv"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Forslag til rettelser av neste ord"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Bruk forrige ord til å forbedre forslagene"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Forslag til neste ord"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Bruk forrige ord også for forslag"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Forslag til neste ord"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Basert på det forrige ordet"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: Lagret"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Utfør"</string>
     <string name="label_next_key" msgid="362972844525672568">"Neste"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Taleinndata er deaktiv."</string>
     <string name="configure_input_method" msgid="373356270290742459">"Konfigurer inndatametoder"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Inndataspråk"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Notér tidsstempel i logg"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Registrerte tidsstempel"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Ikke loggfør denne økten"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Sletter øktloggen"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Øktloggen ble slettet"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Øktloggen ble IKKE slettet"</string>
     <string name="select_language" msgid="3693815588777926848">"Inndataspråk"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Trykk på nytt for å lagre"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Ordbok tilgjengelig"</string>
diff --git a/java/res/values-nl/strings-appname.xml b/java/res/values-nl/strings-appname.xml
new file mode 100644
index 0000000..f1cb252
--- /dev/null
+++ b/java/res/values-nl/strings-appname.xml
@@ -0,0 +1,28 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (178705338187710493) -->
+    <skip />
+    <string name="spell_checker_service_name" msgid="6268342166872202903">"Spellingcontrole van Android"</string>
+    <string name="english_ime_settings" msgid="7470027018752707691">"Instellingen voor Android-toetsenbord"</string>
+    <string name="android_spell_checker_settings" msgid="8397842018475560441">"Instellingen voor spellingcontrole"</string>
+</resources>
diff --git a/java/res/values-nl/strings.xml b/java/res/values-nl/strings.xml
index a7d499b..c42cd10 100644
--- a/java/res/values-nl/strings.xml
+++ b/java/res/values-nl/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Android-toetsenbord"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android-toetsenbord (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Instellingen voor Android-toetsenbord"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Invoeropties"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Spellingcontrole van Android"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Opdrachten in onderzoekslogbestand"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Spellingcontrole van Android (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Instellingen voor spellingcontrole"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Contactnamen opzoeken"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"De spellingcontrole gebruikt items uit uw contactenlijst"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Trillen bij toetsaanslag"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Standaard"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Contactnamen suggereren"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Namen uit Contacten gebruiken voor suggesties en correcties"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Verbeteringen inschakelen"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Suggesties instellen voor verbeteringen"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Auto-hoofdlettergebruik"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Woordenboeken toevoegen"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Algemeen woordenboek"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Normaal"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Agressief"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Zeer agressief"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Volgende woordsuggesties"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Vorig woord gebruiken om suggesties te verbeteren"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Volgende woordvoorspelling"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Het voorgaande woord ook voor voorspelling gebruiken"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Suggesties voor volgend woord"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Op basis van het vorige woord"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: opgeslagen"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Start"</string>
     <string name="label_next_key" msgid="362972844525672568">"Verder"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Spraakinvoer is uit"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Invoermethoden configureren"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Invoertalen"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Tijdstempel opnemen in logbestand"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Opgenomen tijdstempel"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Sessie niet registreren"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Sessielogbestand verwijderen"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Sessielogbestand verwijderd"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Sessielogbestand NIET verwijderd"</string>
     <string name="select_language" msgid="3693815588777926848">"Invoertalen"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Raak nogmaals aan om op te slaan"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Woordenboek beschikbaar"</string>
diff --git a/java/res/values-pl/strings.xml b/java/res/values-pl/strings.xml
index bf27782..7c86dbf 100644
--- a/java/res/values-pl/strings.xml
+++ b/java/res/values-pl/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Klawiatura Android"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Klawiatura Androida (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Ustawienia klawiatury Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opcje wprowadzania"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Słownik Androida"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Polecenia dziennika badań"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Sprawdzanie pisowni na Androidzie (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Ustawienia sprawdzania pisowni"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Przeszukaj kontakty"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Sprawdzanie pisowni bierze pod uwagę wpisy z listy kontaktów."</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Wibracja przy naciśnięciu"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Wartość domyślna"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Proponuj osoby z kontaktów"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"W propozycjach i poprawkach użyj nazwisk z kontaktów"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Włącz poprawki"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Ustaw sugestie poprawek"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Wstawiaj wielkie litery"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Dodatkowe słowniki"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Słownik główny"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Umiarkowana"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Agresywna"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Bardzo agresywna"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Sugestie kolejnych słów"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Używaj poprzedniego wyrazu, by polepszyć sugestie"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Przewidywanie następnego wyrazu"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Przewiduj również na podstawie poprzedniego słowa"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Sugestie kolejnych słów"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Na podstawie poprzedniego słowa"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Zapisano"</string>
     <string name="label_go_key" msgid="1635148082137219148">"OK"</string>
     <string name="label_next_key" msgid="362972844525672568">"Dalej"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Wprowadzanie głosowe jest wyłączone"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Konfiguruj metody wprowadzania"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Języki wprowadzania"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Znacznik czasu uwagi w dzienniku"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Zapisano znacznik czasu"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Nie rejestruj tej sesji"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Usuwanie dziennika sesji"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Usunięto dziennik sesji"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Dziennik sesji NIEUSUNIĘTY"</string>
     <string name="select_language" msgid="3693815588777926848">"Języki wprowadzania"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Dotknij ponownie, aby zapisać"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Słownik dostępny"</string>
diff --git a/java/res/values-pt-rPT/strings.xml b/java/res/values-pt-rPT/strings.xml
index ccb042e..06d7a78 100644
--- a/java/res/values-pt-rPT/strings.xml
+++ b/java/res/values-pt-rPT/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Teclado do Android"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Teclado Android (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Definições de teclado do Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opções de introdução"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Verificador ortográfico do Android"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Comandos de Reg. Invest."</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Verificador ortográfico do Android (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Definições da verificação ortográfica"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Procurar nomes de contac."</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"O corretor ortográfico utiliza entradas da sua lista de contactos"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrar ao primir as teclas"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Predefinido"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Sugerir nomes de Contactos"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Utilizar nomes dos Contactos para sugestões e correções"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Ativar correções"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Definir sugestões para correções"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Letras maiúsculas automáticas"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Dicionários extras"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Dicionário principal"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Moderada"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Agressiva"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Muito agressivo"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Sugestões da palavra seguinte"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Utilizar palavra anterior para melhorar sugestões"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Previsão da palavra seguinte"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Utilizar a palavra anterior também para predição"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Sugestões da palavra seguinte"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Com base na palavra anterior"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: guardada"</string>
     <string name="label_go_key" msgid="1635148082137219148">"OK"</string>
     <string name="label_next_key" msgid="362972844525672568">"Avançar"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Entr. voz desact."</string>
     <string name="configure_input_method" msgid="373356270290742459">"Configurar métodos de introdução"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Idiomas de entrada"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Anotar car. data no reg."</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Carimbo de data gravado"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Não registar esta sessão"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"A eliminar reg. da sessão"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Reg. de sessão eliminado"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Reg. de sessão NÃO elim."</string>
     <string name="select_language" msgid="3693815588777926848">"Idiomas de introdução"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Toque novamente para guardar"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Dicionário disponível"</string>
diff --git a/java/res/values-pt/strings.xml b/java/res/values-pt/strings.xml
index 7d64759..5b7c60f 100644
--- a/java/res/values-pt/strings.xml
+++ b/java/res/values-pt/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Teclado Android"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Teclado Android (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Configurações de teclado Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opções de entrada"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Corretor ortográfico do Android"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Pesq. comandos de reg."</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Corretor ortográfico do Android (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Configurações de verificação ortográfica"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Buscar nomes de contatos"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"O corretor ortográfico usa entradas de sua lista de contatos"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrar ao tocar a tecla"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Padrão"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Sugerir nomes de contato"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Usar nomes dos Contatos para sugestões e correções"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Ativar recorreções"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Definir sugestões para recorreções"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Capitaliz. automática"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Dicionários complementares"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Dicionário principal"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Moderado"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Agressivo"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Muito agressivo"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Sugestões p/ palavra seguinte"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Usar palavra anterior para melhorar as sugestões"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Previsão da palavra seguinte"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Use também a palavra anterior para prever"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Sugestões para a palavra seguinte"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Com base na palavra anterior"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Salvo"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Ir"</string>
     <string name="label_next_key" msgid="362972844525672568">"Avançar"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Texto por voz desat."</string>
     <string name="configure_input_method" msgid="373356270290742459">"Configurar métodos de entrada"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Idiomas de entrada"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Indicar data/hora no reg."</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Data/hora registrada"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Não registrar esta sessão"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Excluindo reg. de sessão"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Registro excluído"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Registro NÃO excluído"</string>
     <string name="select_language" msgid="3693815588777926848">"Idiomas de entrada"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Toque novamente para salvar"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Dicionário disponível"</string>
diff --git a/java/res/values-rm/strings.xml b/java/res/values-rm/strings.xml
index 18741f8..41be654 100644
--- a/java/res/values-rm/strings.xml
+++ b/java/res/values-rm/strings.xml
@@ -20,18 +20,14 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Tastatura Android"</string>
     <!-- no translation found for aosp_android_keyboard_ime_name (7877134937939182296) -->
     <skip />
-    <string name="english_ime_settings" msgid="6661589557206947774">"Parameters da la tastatura Android"</string>
     <!-- no translation found for english_ime_input_options (3909945612939668554) -->
     <skip />
-    <!-- no translation found for spell_checker_service_name (7338064335159755926) -->
+    <!-- no translation found for english_ime_research_log (8492602295696577851) -->
     <skip />
     <!-- no translation found for aosp_spell_checker_service_name (6985142605330377819) -->
     <skip />
-    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
-    <skip />
     <!-- no translation found for use_contacts_for_spellchecking_option_title (5374120998125353898) -->
     <skip />
     <!-- no translation found for use_contacts_for_spellchecking_option_summary (8754413382543307713) -->
@@ -65,10 +61,6 @@
     <skip />
     <!-- no translation found for use_contacts_dict_summary (6599983334507879959) -->
     <skip />
-    <!-- no translation found for enable_span_insert (7204653105667167620) -->
-    <skip />
-    <!-- no translation found for enable_span_insert_summary (2947317657871394467) -->
-    <skip />
     <string name="auto_cap" msgid="1719746674854628252">"Maiusclas automaticas"</string>
     <!-- no translation found for configure_dictionaries_title (4238652338556902049) -->
     <skip />
@@ -96,13 +88,9 @@
     <skip />
     <!-- no translation found for auto_correction_threshold_mode_very_aggeressive (3386782235540547678) -->
     <skip />
-    <!-- no translation found for bigram_suggestion (8169311444438922902) -->
+    <!-- no translation found for bigram_prediction (5809665643352206540) -->
     <skip />
-    <!-- no translation found for bigram_suggestion_summary (6635527607242625713) -->
-    <skip />
-    <!-- no translation found for bigram_prediction (3216364899483135294) -->
-    <skip />
-    <!-- no translation found for bigram_prediction_summary (1747261921174300098) -->
+    <!-- no translation found for bigram_prediction_summary (3253961591626441019) -->
     <skip />
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Memorisà"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Dai"</string>
@@ -190,6 +178,18 @@
     <!-- no translation found for configure_input_method (373356270290742459) -->
     <skip />
     <string name="language_selection_title" msgid="1651299598555326750">"Linguas da cumonds vocals"</string>
+    <!-- no translation found for note_timestamp_for_researchlog (1889446857977976026) -->
+    <skip />
+    <!-- no translation found for notify_recorded_timestamp (8036429032449612051) -->
+    <skip />
+    <!-- no translation found for do_not_log_this_session (413762473641146336) -->
+    <skip />
+    <!-- no translation found for notify_session_log_deleting (3299507647764414623) -->
+    <skip />
+    <!-- no translation found for notify_session_log_deleted (8687927130100934686) -->
+    <skip />
+    <!-- no translation found for notify_session_log_not_deleted (2592908998810755970) -->
+    <skip />
     <!-- no translation found for select_language (3693815588777926848) -->
     <skip />
     <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
diff --git a/java/res/values-ro/strings.xml b/java/res/values-ro/strings.xml
index 0c4d8bc..338024c 100644
--- a/java/res/values-ro/strings.xml
+++ b/java/res/values-ro/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Tastatură Android"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Tastatură Android (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Setările tastaturii Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opţiuni de introducere text"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Verificator ortografic Android"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Comenzi jurnal cercetare"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Verificator ortografic Android (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Setări de verificare ortografică"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Verificare nume în agendă"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Verificatorul ortografic utilizează intrări din lista de contacte"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrare la apăsarea tastei"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Prestabilit"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Sugeraţi nume din Agendă"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Utilizaţi numele din Agendă pentru sugestii şi corecţii"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Activaţi rectificările"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Setaţi sugestii pentru rectificări"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Auto-capitalizare"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Dicţionare suplimentare"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Dicţionar principal"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Moderată"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Agresivă"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Foarte exigentă"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Sugestii pentru cuvântul următor"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Utilizaţi cuvântul anterior pentru a îmbunătăţi sugestiile"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Predicţia cuvântului următor"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Se utilizează şi cuvântul precedent pentru predicţii"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Sugestii pentru cuvântul următor"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Bazate pe cuvântul precedent"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: salvat"</string>
     <string name="label_go_key" msgid="1635148082137219148">"OK"</string>
     <string name="label_next_key" msgid="362972844525672568">"Înainte"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Intr. vocală dezact."</string>
     <string name="configure_input_method" msgid="373356270290742459">"Configuraţi metodele de intrare"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Selectaţi limba"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Înreg. marc. temp. jurnal"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Marcaj temporal înregis."</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Nu înregistraţi sesiunea"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Se șterge jurnal sesiune"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Jurnal de sesiune șters"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Jurnal sesiune neşters"</string>
     <string name="select_language" msgid="3693815588777926848">"Limbi de intrare"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Atingeţi din nou pentru a salva"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Dicţionar disponibil"</string>
diff --git a/java/res/values-ru/strings.xml b/java/res/values-ru/strings.xml
index f76cc8e..4dd5b31 100644
--- a/java/res/values-ru/strings.xml
+++ b/java/res/values-ru/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Клавиатура Android"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Клавиатура Android (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Клавиатура Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Настройки"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Проверка правописания Android"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Все команды"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Проверка правописания Android (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Настройка проверки правописания"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Поиск контактов"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Обращаться к списку контактов при проверке правописания"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Виброотклик клавиш"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"По умолчанию"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Подсказывать имена"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Подсказывать исправления на основе имен из списка контактов"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Автоисправление"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Показывать варианты исправления"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Заглавные автоматически"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Дополнительные словари"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Основной словарь"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Умеренное"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Активное"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Очень активно"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Следующие варианты"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Использовать предыдущее слово, чтобы исправить предложенные варианты"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Следующая подсказка"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Использовать предыдущее слово для прогнозирования"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Подсказка для следующего слова"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Подсказки, основанные на предыдущих словах"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: сохранено"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Поиск"</string>
     <string name="label_next_key" msgid="362972844525672568">"Далее"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Голосовой ввод откл."</string>
     <string name="configure_input_method" msgid="373356270290742459">"Настройка способов ввода"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Языки ввода"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Закладка в журнале"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Закладка сохранена"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Не сохранять этот сеанс"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Удаление…"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Запись сеанса удалена"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Запись сеанса НЕ удалена"</string>
     <string name="select_language" msgid="3693815588777926848">"Языки ввода"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Нажмите, чтобы сохранить"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Доступен словарь"</string>
diff --git a/java/res/values-sk/strings.xml b/java/res/values-sk/strings.xml
index 44eecdc..1dced20 100644
--- a/java/res/values-sk/strings.xml
+++ b/java/res/values-sk/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Klávesnica Android"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Klávesnica Android (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Nastavenia klávesnice Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Možnosti zadávania textu a údajov"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Kontrola pravopisu Android"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Príkazy denníka výskumu"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Kontrola pravopisu Android (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Nastavenia kontroly pravopisu"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Vyhľadať kontakty"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Kontrola pravopisu používa záznamy z vášho zoznamu kontaktov"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Pri stlačení klávesu vibrovať"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Predvolená"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Navrhnúť mená kontaktov"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Používať mená z Kontaktov na návrhy a opravy"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Povoliť opätovné opravy"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Nastaviť návrhy pre opätovné opravy"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Veľké písmená automaticky"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Doplnkové slovníky"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Hlavný slovník"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Mierne"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Agresívne"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Veľmi agresívne"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Návrhy ďalšieho slova"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Na zlepšenie návrhov použiť predchádzajúce slovo"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Odhad ďalšieho slova"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Použiť predchádzajúce slovo aj pre predpoveď"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Návrhy ďalšieho slova"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Na základe predchádzajúceho slova"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Uložené"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Hľadať"</string>
     <string name="label_next_key" msgid="362972844525672568">"Ďalej"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Hlasový vstup je zakázaný"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Konfigurovať metódy vstupu"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Jazyky vstupu"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Časová pečiatka denníka"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Časová pečiatka zaznamenaná"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Neukl. reláciu do denníka"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Odstraň. denníka relácie"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Denník relácie odstránený"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Denník relácie NIE JE odstr."</string>
     <string name="select_language" msgid="3693815588777926848">"Jazyky vstupu"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Opätovným dotykom uložíte"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"K dispozícii je slovník"</string>
diff --git a/java/res/values-sl/strings-appname.xml b/java/res/values-sl/strings-appname.xml
new file mode 100644
index 0000000..aa8c816
--- /dev/null
+++ b/java/res/values-sl/strings-appname.xml
@@ -0,0 +1,28 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (178705338187710493) -->
+    <skip />
+    <string name="spell_checker_service_name" msgid="6268342166872202903">"Črkovalnik za Android"</string>
+    <string name="english_ime_settings" msgid="7470027018752707691">"Nastavitve tipkovnice Android"</string>
+    <string name="android_spell_checker_settings" msgid="8397842018475560441">"Nastavitve preverjanja črkovanja"</string>
+</resources>
diff --git a/java/res/values-sl/strings.xml b/java/res/values-sl/strings.xml
index ce7dc70..036802d 100644
--- a/java/res/values-sl/strings.xml
+++ b/java/res/values-sl/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Tipkovnica Android"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Tipkovnica Android (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Nastavitve tipkovnice Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Možnosti vnosa"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Črkovalnik za Android"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Ukazi za dnevnik raziskav"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Črkovalnik za Android (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Nastavitve preverjanja črkovanja"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Iskanje imen stikov"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Črkovalnik uporablja vnose s seznama stikov"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibriranje ob pritisku tipke"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Privzeto"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Predlagaj imena stikov"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Uporaba imen iz stikov za predloge in popravke"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Omogoči vnovične popravke"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Nastavitev predlogov za vnovične popravke"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Samod. velike začetnice"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Dodatni slovarji"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Glavni slovar"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Zmerno"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Strogo"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Zelo strogo"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Predlogi naslednje besede"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Predloge izboljšaj s prejšnjo besedo"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Predvidevanje naslednje besede"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Uporabi prejšnjo besedo tudi za predvidevanje"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Predlogi za naslednjo besedo"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Na podlagi prejšnje besede"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: shranjeno"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Pojdi"</string>
     <string name="label_next_key" msgid="362972844525672568">"Naprej"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Glas. vnos je onem."</string>
     <string name="configure_input_method" msgid="373356270290742459">"Nastavitev načinov vnosa"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Jeziki vnosa"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"V dnev. zabeleži čas. žig"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Časovni žig zabeležen"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Brez dnevnika za to sejo"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Brisanje seje dnevnika"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Dnevnik seje izbrisan"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Dnevnik seje NI izbrisan"</string>
     <string name="select_language" msgid="3693815588777926848">"Jeziki vnosa"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Dotaknite se še enkrat, da shranite"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Slovar je na voljo"</string>
diff --git a/java/res/values-sr/strings-appname.xml b/java/res/values-sr/strings-appname.xml
new file mode 100644
index 0000000..01da6d5
--- /dev/null
+++ b/java/res/values-sr/strings-appname.xml
@@ -0,0 +1,28 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (178705338187710493) -->
+    <skip />
+    <string name="spell_checker_service_name" msgid="6268342166872202903">"Android провера правописа"</string>
+    <string name="english_ime_settings" msgid="7470027018752707691">"Подешавања Android тастатуре"</string>
+    <string name="android_spell_checker_settings" msgid="8397842018475560441">"Подешавања провере правописа"</string>
+</resources>
diff --git a/java/res/values-sr/strings.xml b/java/res/values-sr/strings.xml
index 6d2899b..92f275f 100644
--- a/java/res/values-sr/strings.xml
+++ b/java/res/values-sr/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Android тастатура"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android тастатура (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Подешавања Android тастатуре"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Опције уноса"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Android провера правописа"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Команде евиденције истраживања"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Android провера правописа (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Подешавања провере правописа"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Потражи имена контаката"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Контролор правописа користи уносе са листе контаката"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Вибрирај на притисак тастера"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Подразумевано"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Предложи имена контаката"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Користи имена из Контаката за предлоге и исправке"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Омогући поновне исправке"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Подешавање предлога за поновне исправке"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Аутоматски унос великих слова"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Помоћни речници"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Главни речник"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Умерено"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Агресивно"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Веома агресивно"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Предлози за следећу реч"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Користи претходну реч за побољшање предлога"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Предвиђање следеће речи"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Користи претходну реч и за предвиђање"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Предлози за следећу реч"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"На основу претходне речи"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Сачувано"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Иди"</string>
     <string name="label_next_key" msgid="362972844525672568">"Следеће"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Гласовни унос је онемогућен"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Конфигурисање метода уноса"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Језици за унос"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Наведи временску ознаку у евиденцији"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Снимљена временска ознака"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Не евидентирај ову сесију"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Брисање евиденције сесије"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Евиденција сесије је обрисана"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Евиденција сесије НИЈЕ избрисана"</string>
     <string name="select_language" msgid="3693815588777926848">"Језици уноса"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Поново додирните да бисте сачували"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Речник је доступан"</string>
diff --git a/java/res/values-sv/strings-appname.xml b/java/res/values-sv/strings-appname.xml
new file mode 100644
index 0000000..edb2af2
--- /dev/null
+++ b/java/res/values-sv/strings-appname.xml
@@ -0,0 +1,28 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (178705338187710493) -->
+    <skip />
+    <string name="spell_checker_service_name" msgid="6268342166872202903">"Stavningskontroll i Android"</string>
+    <string name="english_ime_settings" msgid="7470027018752707691">"Inställningar för Androids tangentbord"</string>
+    <string name="android_spell_checker_settings" msgid="8397842018475560441">"Inställningar för stavningskontroll"</string>
+</resources>
diff --git a/java/res/values-sv/strings.xml b/java/res/values-sv/strings.xml
index 75e80d4..d460b5b 100644
--- a/java/res/values-sv/strings.xml
+++ b/java/res/values-sv/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Androids tangentbord"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Androids tangentbord (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Inställningar för Androids tangentbord"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Inmatningsalternativ"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Stavningskontroll i Android"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Loggkommandon"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Stavningskontroll i Android (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Inställningar för stavningskontroll"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Sök namn på kontakter"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"I stavningskontrollen används poster från kontaktlistan"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrera vid tangenttryck"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Standard"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Föreslå kontaktnamn"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Använd namn från Kontakter för förslag och korrigeringar"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Aktivera omkorrigeringar"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Ställ in förslag för omkorrigeringar"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Automatiska versaler"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Tilläggsordlistor"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Huvudordlistan"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Måttlig"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Aggressiv"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Mycket aggressivt"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Föreslå nästa ord"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Förbättra förslagen med föregående ord"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Förutspå nästa ord"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Använd även föregående ord för att ge förslag"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Föreslå nästa ord"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Baserat på föregående ord"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: sparat"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Kör"</string>
     <string name="label_next_key" msgid="362972844525672568">"Nästa"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Röstinmatning inaktiv"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Konfigurera inmatningsmetoder"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Inmatningsspråk"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Markera tidpunkt i loggen"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Tidpunkten har sparats"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Logga inte detta besök"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Besöksloggen tas bort"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Besöksloggen togs bort"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Besöksloggen togs EJ bort"</string>
     <string name="select_language" msgid="3693815588777926848">"Inmatningsspråk"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Spara genom att trycka igen"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"En ordlista är tillgänglig"</string>
diff --git a/java/res/values-sw/strings-appname.xml b/java/res/values-sw/strings-appname.xml
new file mode 100644
index 0000000..4a5b2a6
--- /dev/null
+++ b/java/res/values-sw/strings-appname.xml
@@ -0,0 +1,28 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (178705338187710493) -->
+    <skip />
+    <string name="spell_checker_service_name" msgid="6268342166872202903">"Kikagua tahajia cha Android"</string>
+    <string name="english_ime_settings" msgid="7470027018752707691">"Mipangilio ya kibodi ya Android"</string>
+    <string name="android_spell_checker_settings" msgid="8397842018475560441">"Mipangilio ya kukagua tahajia"</string>
+</resources>
diff --git a/java/res/values-sw/strings.xml b/java/res/values-sw/strings.xml
index da51cb3..9727fda 100644
--- a/java/res/values-sw/strings.xml
+++ b/java/res/values-sw/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Kibodi ya Android"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Kicharazio cha Android (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Mipangilio ya kibodi ya Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Chaguo za uingizaji"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Kikagua tahajia cha Android"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Amri za Kumbukumbu za Utafiti"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Kikagua tahajia cha Android (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Mipangilio ya kukagua sarufi"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Angalia majina ya wasiliani"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Kikagua tahajia hutumia ingizo kutoka kwa orodha yako ya anwani"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Tetema unabofya kitufe"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Chaguo-msingi"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Pendekeza majini ya Anwani"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Tumia majina kutoka kwa Anwani kwa mapendekezo na marekebisho"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Wezesha masahihisho mapya"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Weka mapendekezo kwa ajili ya kusahihisha upya"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Uwekaji wa herufi kubwa kiotomatiki"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Nyongeza za kamusi"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Kamusi kuu"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Ya wastani"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Ya hima"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Changamfu zaidi"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Mapendekezo ya neno lifuatalo"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Tumia neno la awali ili kuboresha mapendekezo"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Utabiri wa neno lifuatalo"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Tumia  neno la awali pia kwa udadisi"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Mapendekezo ya neno lifuatalo"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Kulingana na neno la awali"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Imehifadhiwa"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Nenda"</string>
     <string name="label_next_key" msgid="362972844525672568">"Ifuatayo"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Uingizaji sauti umelemazwa"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Sanidi mbinu za uingizaji"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Lugha za uingizaji"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Dokeza mhuri wa muda kwenye kumbukumbu"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Mhuri wa muda uliorekodiwa"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Usihifadhi kumbukumbu za kipindi hiki"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Inafuta kumbukumbu za kipindi"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Kumbukumbu za kipindi zimefutwa"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Kumbukumbu za kipindi HAZIJAFUTWA"</string>
     <string name="select_language" msgid="3693815588777926848">"Lugha zinazoruhusiwa"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Gusa tena ili kuhifadhi"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Kamusi inapatikana"</string>
diff --git a/java/res/values-th/strings.xml b/java/res/values-th/strings.xml
index da20d66..8c365fe 100644
--- a/java/res/values-th/strings.xml
+++ b/java/res/values-th/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"แป้นพิมพ์ Android"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android keyboard (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"การตั้งค่าแป้นพิมพ์ Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"ตัวเลือกการป้อนข้อมูล"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"แอนดรอยด์ตรวจสอบการสะกด"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"คำสั่งบันทึกการวิจัย"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"แอนดรอยด์ตรวจสอบการสะกด (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"การตั้งค่าการตรวจสอบการสะกด"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"ค้นหารายชื่อติดต่อ"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"เครื่องมือตรวจการสะกดใช้รายการจากรายชื่อติดต่อของคุณ"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"สั่นเมื่อกดปุ่ม"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"ค่าเริ่มต้น"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"แนะนำชื่อผู้ติดต่อ"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"ใช้ชื่อจากรายชื่อติดต่อสำหรับคำแนะนำและการแก้ไข"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"เปิดใช้งานการแก้ไขซ้ำ"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"ตั้งค่าคำแนะนำสำหรับการแก้ไขซ้ำ"</string>
     <string name="auto_cap" msgid="1719746674854628252">"ปรับเป็นตัวพิมพ์ใหญ่อัตโนมัติ"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"พจนานุกรม Add-On"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"พจนานุกรมหลัก"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"ปานกลาง"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"เข้มงวด"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"เข้มงวดมาก"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"คำแนะนำสำหรับคำถัดไป"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"ใช้คำก่อนหน้านี้เพื่อปรับปรุงคำแนะนำ"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"การคาดคะเนคำถัดไป"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"ใช้คำก่อนหน้านี้สำหรับการคาดคะเน"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"คำแนะนำสำหรับคำถัดไป"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"ตามคำก่อนหน้า"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : บันทึกแล้ว"</string>
     <string name="label_go_key" msgid="1635148082137219148">"ไป"</string>
     <string name="label_next_key" msgid="362972844525672568">"ถัดไป"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"ปิดใช้งานป้อนข้อมูลด้วยเสียง"</string>
     <string name="configure_input_method" msgid="373356270290742459">"กำหนดค่าวิธีการป้อนข้อมูล"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"ภาษาในการป้อนข้อมูล"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"จดเวลาบันทึกไว้ในบันทึก"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"บันทึกเวลาบันทึกแล้ว"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"อย่าบันทึกเซสชันนี้"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"กำลังลบบันทึกเซสชัน"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"ลบบันทึกเซสชันแล้ว"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"บันทึกเซสชันไม่ถูกลบ"</string>
     <string name="select_language" msgid="3693815588777926848">"ภาษาสำหรับการป้อนข้อมูล"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"แตะอีกครั้งเพื่อบัน​​ทึก"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"มีพจนานุกรมให้ใช้งาน"</string>
diff --git a/java/res/values-tl/strings.xml b/java/res/values-tl/strings.xml
index 7d365b2..832dc2b 100644
--- a/java/res/values-tl/strings.xml
+++ b/java/res/values-tl/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Android keyboard"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android keyboard (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Mga setting ng Android keyboard"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Mga pagpipilian sa input"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Pang-check ng pagbabaybay ng Android"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Cmmnd sa Log ng Pnnliksik"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Pang-check ng pagbabaybay ng Android (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Mga setting ng pang-check ng pagbabaybay"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Maghanap pangalan contact"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Gumagamit pang-check pagbabaybay entry sa iyong listahan contact"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Mag-vibrate sa keypress"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Default"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Mungkahi pangalan Contact"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Gamitin pangalan mula Mga Contact sa mga mungkahi\'t pagwawasto"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Paganahin ang mga muling pagtatama"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Magtakda ng mga suhestyon para sa mga muling pagtatama"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Auto-capitalization"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Mga diksyunaryo na add-on"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Pangunahing diksyunaryo"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Modest"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Agresibo"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Napaka-agresibo"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Mga paghuhula sa susunod na salita"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Gamitin ang naunang salita para mapahusay ang mga suhestiyon"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Paghuhula sa susunod na salita"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Gamitin ang nakaraang salita para din sa hula"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Mga suhestiyon sa susunod na salita"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Batay sa nakaraang salita"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Na-save"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Punta"</string>
     <string name="label_next_key" msgid="362972844525672568">"Susunod"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Hindi pinagana ang voice input"</string>
     <string name="configure_input_method" msgid="373356270290742459">"I-configure ang mga pamamaraan ng pag-input"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Mag-input ng mga wika"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Tandaan timestamp sa log"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Na-record na timestamp"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Huwag i-log ang session"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Tinatanggl log ng session"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Tinanggal log ng session"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"HND ntnggl log ng session"</string>
     <string name="select_language" msgid="3693815588777926848">"Mga wika ng input"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Pinduting muli upang i-save"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Available ang diksyunaryo"</string>
diff --git a/java/res/values-tr/strings.xml b/java/res/values-tr/strings.xml
index b2f7391..abfa736 100644
--- a/java/res/values-tr/strings.xml
+++ b/java/res/values-tr/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Android klavyesi"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android klavye (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Android klavye ayarları"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Giriş seçenekleri"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Android yazım denetleyici"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Araştırma Günlüğü Komutları"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Android yazım denetleyici (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Yazım denetimi ayarları"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Kişi adlarını denetle"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Yazım denetleyici, kişi listenizdeki girişleri kullanır"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Tuşa basıldığında titret"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Varsayılan"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Kişi Adları öner"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Öneri ve düzeltmeler için Kişiler\'deki adları kullan"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Düzeltmeleri etkinleştir"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Yeniden düzeltmeler için önerileri ayarla"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Otomatik olarak büyük fark yap"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Ek sözlükler"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Ana sözlük"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Ölçülü"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Agresif"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Çok geniş ölçekte"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Sonraki kelime önerileri"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Önerileri geliştirmek için önceki kelimeyi kullan"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Sonraki kelime tahmini"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Önceki kelimeyi de tahmin için kullan"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Sonraki kelime önerileri"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Önceki kelimeye dayanarak"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Kaydedildi"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Git"</string>
     <string name="label_next_key" msgid="362972844525672568">"İleri"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Sesle grş devre dışı"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Giriş yöntemlerini yapılandır"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Giriş dilleri"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Günlüğe zaman damgası koy"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Zaman damgası kaydedildi"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Bu oturumu günlüğe kaydetme"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Oturum günlüğü siliniyor"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Oturum günlüğü silindi"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Oturum günlüğü SİLİNMEDİ"</string>
     <string name="select_language" msgid="3693815588777926848">"Giriş dilleri"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Kaydetmek için tekrar dokunun"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Sözlük kullanılabilir"</string>
diff --git a/java/res/values-uk/strings-appname.xml b/java/res/values-uk/strings-appname.xml
new file mode 100644
index 0000000..d77b617
--- /dev/null
+++ b/java/res/values-uk/strings-appname.xml
@@ -0,0 +1,28 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (178705338187710493) -->
+    <skip />
+    <string name="spell_checker_service_name" msgid="6268342166872202903">"Засіб перевірки орфографії Android"</string>
+    <string name="english_ime_settings" msgid="7470027018752707691">"Налаштування клавіатури Android"</string>
+    <string name="android_spell_checker_settings" msgid="8397842018475560441">"Налаштування перевірки орфографії"</string>
+</resources>
diff --git a/java/res/values-uk/strings.xml b/java/res/values-uk/strings.xml
index e994e1a..9e4da5b 100644
--- a/java/res/values-uk/strings.xml
+++ b/java/res/values-uk/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Клавіатура Android"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Клавіатура Android (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Налашт-ня клавіат. Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Парам. введення"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Засіб перевірки орфографії Android"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Команди журналу дослідж."</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Засіб перевірки орфографії Android (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Налаштування перевірки орфографії"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Шукати імена контактів"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Програма перевірки правопису використ. записи зі списку контактів"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Вібр. при натисканні клавіш"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"За умовчанням"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Пропон. імена контактів"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Використ. імена зі списку контактів для пропозицій і виправлень"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Увімкнути виправлення"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Показувати варіанти автовиправлень"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Авто викор. вел. літер"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Додані словники"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Основний словник"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Помірне"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Активне"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Дуже активне"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Пропозиції наступного слова"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Використати попереднє слово для покращення пропозицій"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Передбачення наступного слова"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Використовувати попереднє слово також як передбачений запит"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Пропозиції наступного слова"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"На основі попереднього слова"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : збережено"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Іти"</string>
     <string name="label_next_key" msgid="362972844525672568">"Далі"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Голос. ввід вимкнено"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Налаштування методів введення"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Мови вводу"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Мітка часу в журналі"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Записана мітка часу"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Не реєструвати цю сесію"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Видалення журналу сесії"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Журнал сесії видалено"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Журнал сесії НЕ видалено"</string>
     <string name="select_language" msgid="3693815588777926848">"Мови введення"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Торкніться знову, щоб зберегти"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Словник доступний"</string>
diff --git a/java/res/values-vi/strings.xml b/java/res/values-vi/strings.xml
index 753af18..bf096c5 100644
--- a/java/res/values-vi/strings.xml
+++ b/java/res/values-vi/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Bàn phím Android"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Bàn phím Android (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Cài đặt bàn phím Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Tùy chọn nhập"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Trình kiểm tra chính tả Android"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Lệnh ghi nhật ký cho nghiên cứu"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Trình kiểm tra chính tả Android (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Cài đặt kiểm tra chính tả"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Tra cứu tên liên hệ"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Trình kiểm tra chính tả sử dụng các mục nhập từ danh sách liên hệ của bạn"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Rung khi nhấn phím"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Mặc định"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Đề xuất tên liên hệ"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Sử dụng tên từ Danh bạ cho các đề xuất và chỉnh sửa"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Bật sửa đổi lại"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Đặt đề xuất cho các sửa đổi lại"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Tự động viết hoa"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Thêm từ điển"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Từ điển chính"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Đơn giản"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Linh hoạt"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Rất linh hoạt"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Đề xuất từ tiếp theo"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Sử dụng từ trước đó để cải tiến đề xuất"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Dự đoán từ tiếp theo"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Cũng sử dụng từ trước đó để dự đoán"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Đề xuất từ tiếp theo"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Dựa trên từ trước đó"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Đã lưu"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Tìm"</string>
     <string name="label_next_key" msgid="362972844525672568">"Tiếp theo"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Nhập liệu bằng giọng nói đã bị tắt"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Định cấu hình phương thức nhập"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Ngôn ngữ nhập"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Dấu thời gian ghi chú trong nhật ký"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Dấu thời gian đã ghi"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Không ghi nhật ký phiên này"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Đang xóa nhật ký phiên"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Đã xóa nhật ký phiên"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Nhật ký phiên KHÔNG bị xóa"</string>
     <string name="select_language" msgid="3693815588777926848">"Ngôn ngữ nhập"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Chạm lại để lưu"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Có sẵn từ điển"</string>
diff --git a/java/res/values-zh-rCN/strings.xml b/java/res/values-zh-rCN/strings.xml
index efde541..a95d32a 100644
--- a/java/res/values-zh-rCN/strings.xml
+++ b/java/res/values-zh-rCN/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Android 键盘"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android 键盘 (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Android 键盘设置"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"输入选项"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Android 拼写检查工具"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"研究记录命令"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Android 拼写检查工具 (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"拼写检查设置"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"查找联系人姓名"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"拼写检查工具会使用您的联系人列表中的条目"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"按键振动"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"默认"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"联系人姓名建议"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"使用联系人中的姓名提供建议和更正"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"允许再次更正"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"设置建议以用于再次更正"</string>
     <string name="auto_cap" msgid="1719746674854628252">"自动大写"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"附加词典"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"主词典"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"小改"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"大改"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"改动极大"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"下一字词建议"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"使用上一字词改进建议"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"下一字词预测"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"结合前一个字词进行预测"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"下一字词建议"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"根据上一个字词提供建议"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>:已保存"</string>
     <string name="label_go_key" msgid="1635148082137219148">"开始"</string>
     <string name="label_next_key" msgid="362972844525672568">"下一步"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"语音输入功能已停用"</string>
     <string name="configure_input_method" msgid="373356270290742459">"配置输入法"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"输入语言"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"标记记录中的时间"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"已标记时间"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"不记录本次会话"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"正在删除会话记录"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"会话记录已删除"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"未能删除会话记录"</string>
     <string name="select_language" msgid="3693815588777926848">"输入语言"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"再次触摸即可保存"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"有可用词典"</string>
diff --git a/java/res/values-zh-rTW/strings.xml b/java/res/values-zh-rTW/strings.xml
index 51df022..b8bb3e5 100644
--- a/java/res/values-zh-rTW/strings.xml
+++ b/java/res/values-zh-rTW/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Android 鍵盤"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Android 鍵盤 (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Android 鍵盤設定"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"輸入選項"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Android 拼字檢查"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"研究紀錄指令"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Android 拼字檢查 (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"拼字檢查設定"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"查詢聯絡人姓名"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"拼字檢查程式使用您的聯絡人清單項目"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"按鍵時震動"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"預設"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"建議聯絡人名稱"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"根據「聯絡人」名稱提供建議與修正"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"啟用重新更正"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"設定建議供重新更正"</string>
     <string name="auto_cap" msgid="1719746674854628252">"自動大寫"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"外掛字典"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"主要字典"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"更正範圍小"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"更正範圍大"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"更正範圍極大"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"下一個字詞建議"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"根據前一個字詞找出更適合的建議"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"下一個字詞預測"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"同樣使用前一個字詞進行預測"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"下一個字詞建議"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"根據上一個字詞產生"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>:已儲存"</string>
     <string name="label_go_key" msgid="1635148082137219148">"開始"</string>
     <string name="label_next_key" msgid="362972844525672568">"繼續"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"語音輸入已停用"</string>
     <string name="configure_input_method" msgid="373356270290742459">"設定輸入法"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"輸入語言"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"在紀錄中加註時間戳記"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"已記錄時間戳記"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"不要記錄這個工作階段"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"正在刪除工作階段紀錄"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"已刪除工作階段紀錄"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"「未」刪除工作階段紀錄"</string>
     <string name="select_language" msgid="3693815588777926848">"輸入語言"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"再次輕觸即可儲存"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"可使用字典"</string>
diff --git a/java/res/values-zu/strings-appname.xml b/java/res/values-zu/strings-appname.xml
new file mode 100644
index 0000000..b5212a5
--- /dev/null
+++ b/java/res/values-zu/strings-appname.xml
@@ -0,0 +1,28 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_name (178705338187710493) -->
+    <skip />
+    <string name="spell_checker_service_name" msgid="6268342166872202903">"Isihloli sokupela se-Android"</string>
+    <string name="english_ime_settings" msgid="7470027018752707691">"Izilungiselelo zekhibhodi ye-Android"</string>
+    <string name="android_spell_checker_settings" msgid="8397842018475560441">"Izilungiselelo zokuhlola ukupela"</string>
+</resources>
diff --git a/java/res/values-zu/strings.xml b/java/res/values-zu/strings.xml
index d3f80e4..6984631 100644
--- a/java/res/values-zu/strings.xml
+++ b/java/res/values-zu/strings.xml
@@ -20,13 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Ikhibhodi ye-Android"</string>
     <string name="aosp_android_keyboard_ime_name" msgid="7877134937939182296">"Ikhibhodi ye-Android (AOSP)"</string>
-    <string name="english_ime_settings" msgid="6661589557206947774">"Izilungiselelo zekhibhodi ye-Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Okukhethwa kukho kokungenayo"</string>
-    <string name="spell_checker_service_name" msgid="7338064335159755926">"Isihloli sokupela se-Android"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Imiyalo yefayela lokungena lokucwaninga"</string>
     <string name="aosp_spell_checker_service_name" msgid="6985142605330377819">"Isihloli sokupela se-Android (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="5822324635435443689">"Izilungiselelo zokuhlola ukupela"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Bheka amagama woxhumana nabo"</string>
     <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Isihloli sokupela sisebenzisa okungenayo kusuka kuhlu lalabo oxhumana nabo"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Dlidlizelisa ngokucindezela inkinobho"</string>
@@ -45,8 +42,6 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Okuzenzakalelayo"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Sikisela amagama Othintana nabo"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Amagama abasebenzisi kusuka Kothintana nabo bokusikisela nokulungisa"</string>
-    <string name="enable_span_insert" msgid="7204653105667167620">"Vumela ukulungiswa kabusha"</string>
-    <string name="enable_span_insert_summary" msgid="2947317657871394467">"Setha iziphakamiso zokulungisa kabusha"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Ukwenza ofeleba okuzenzakalelayo"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Faka izichazamazwi"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Isichazamazwi sakho ngqangi"</string>
@@ -61,10 +56,8 @@
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Thobekile"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Bukhali"</string>
     <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Nobudlova kakhulu"</string>
-    <string name="bigram_suggestion" msgid="8169311444438922902">"Iziphakamiso zegama elilandelayo"</string>
-    <string name="bigram_suggestion_summary" msgid="6635527607242625713">"Sebenzisa igama elandulele ukuthuthukisa iziphakamiso"</string>
-    <string name="bigram_prediction" msgid="3216364899483135294">"Ukuqagela kwegama elilandelayo"</string>
-    <string name="bigram_prediction_summary" msgid="1747261921174300098">"Sebenzisa igama langaphambilini ukuze uqagele"</string>
+    <string name="bigram_prediction" msgid="5809665643352206540">"Iziphakamiso zegama elilandelayo"</string>
+    <string name="bigram_prediction_summary" msgid="3253961591626441019">"Ngokususela egameni langaphambilini"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Kulondoloziwe"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Iya"</string>
     <string name="label_next_key" msgid="362972844525672568">"Okulandelayo"</string>
@@ -111,6 +104,12 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Okufakwayo ngezwi kuvinjelwe"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Misa izindlela zokufakwayo"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Izilimi zokufakwayo"</string>
+    <string name="note_timestamp_for_researchlog" msgid="1889446857977976026">"Qaphela isitembu sesikhathi efayeleni lokungena"</string>
+    <string name="notify_recorded_timestamp" msgid="8036429032449612051">"Isitembu sesikhathi esirekhodiwe"</string>
+    <string name="do_not_log_this_session" msgid="413762473641146336">"Ungenzi ifayela lokungena lalesi sikhathi"</string>
+    <string name="notify_session_log_deleting" msgid="3299507647764414623">"Isusa ifayela lokungena lesikhathi"</string>
+    <string name="notify_session_log_deleted" msgid="8687927130100934686">"Ifayela lokungena lesikhathi lisusiwe"</string>
+    <string name="notify_session_log_not_deleted" msgid="2592908998810755970">"Ifayela lokungena lesikhathi alisusiwe"</string>
     <string name="select_language" msgid="3693815588777926848">"Izilimi zokufakwayo"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Thinta futhi ukuze ulondoloze"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Isichazamazwi siyatholakala"</string>
diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index 8d3319d..589830d 100644
--- a/java/res/values/config.xml
+++ b/java/res/values/config.xml
@@ -22,13 +22,10 @@
     <bool name="config_use_fullscreen_mode">false</bool>
     <bool name="config_enable_show_voice_key_option">true</bool>
     <bool name="config_enable_show_popup_on_keypress_option">true</bool>
-    <bool name="config_enable_next_word_suggestions_option">true</bool>
-    <bool name="config_enable_usability_study_mode_option">false</bool>
+    <!-- TODO: Disable the following configuration for production. -->
+    <bool name="config_enable_usability_study_mode_option">true</bool>
     <!-- Whether or not Popup on key press is enabled by default -->
     <bool name="config_default_popup_preview">true</bool>
-    <!-- Default value for next word suggestion: while showing suggestions for a word should we weigh
-         in the previous word? -->
-    <bool name="config_default_next_word_suggestions">true</bool>
     <!-- Default value for next word prediction: after entering a word and a space only, should we look
          at input history to suggest a hopefully helpful suggestions for the next word? -->
     <bool name="config_default_next_word_prediction">true</bool>
diff --git a/java/res/values/strings-appname.xml b/java/res/values/strings-appname.xml
new file mode 100644
index 0000000..19aaa25
--- /dev/null
+++ b/java/res/values/strings-appname.xml
@@ -0,0 +1,33 @@
+<?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>
+    <!-- Title for Latin Keyboard  -->
+    <string name="english_ime_name">Android keyboard</string>
+
+    <!-- Name of Android spell checker service -->
+    <string name="spell_checker_service_name">Android spell checker</string>
+
+    <!-- Title for Latin keyboard settings activity / dialog -->
+    <string name="english_ime_settings">Android keyboard settings</string>
+
+    <!-- Title for the spell checking service settings screen -->
+    <string name="android_spell_checker_settings">Spell checking settings</string>
+</resources>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index d51d378..5fd4af4 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -18,23 +18,16 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- Title for Latin keyboard  -->
-    <string name="english_ime_name">Android keyboard</string>
     <!-- Application name for opensource Android keyboard. AOSP(Android Open Source Project) should not be translated. -->
     <string name="aosp_android_keyboard_ime_name">Android keyboard (AOSP)</string>
-    <!-- Title for Latin keyboard settings activity / dialog -->
-    <string name="english_ime_settings">Android keyboard settings</string>
     <!-- Title for Latin keyboard input options dialog [CHAR LIMIT=25] -->
     <string name="english_ime_input_options">Input options</string>
+    <!-- Title for Latin keyboard research log dialog, which contains special commands for users that contribute data for research. [CHAR LIMIT=33] -->
+    <string name="english_ime_research_log">Research Log Commands</string>
 
-    <!-- Name of Android spell checker service -->
-    <string name="spell_checker_service_name">Android spell checker</string>
     <!-- Name of Android spell checker service. AOSP(Android Open Source Project) should not be translated. -->
     <string name="aosp_spell_checker_service_name">Android spell checker (AOSP)</string>
 
-    <!-- Title for the spell checking service settings screen -->
-    <string name="android_spell_checker_settings">Spell checking settings</string>
-
     <!-- Title for the spell checker option to turn on/off contact names lookup [CHAR LIMIT=25] -->
     <string name="use_contacts_for_spellchecking_option_title">Look up contact names</string>
 
@@ -83,11 +76,6 @@
     <!-- Description for option enabling or disabling the use of names of people in Contacts for suggestion and correction [CHAR LIMIT=65] -->
     <string name="use_contacts_dict_summary">Use names from Contacts for suggestions and corrections</string>
 
-    <!-- Option name for enabling insertion of suggestion spans (advanced option) [CHAR LIMIT=25] -->
-    <string name="enable_span_insert">Enable recorrections</string>
-    <!-- Option summary for enabling insertion of suggestion spans (advanced option) [CHAR LIMIT=65] -->
-    <string name="enable_span_insert_summary">Set suggestions for recorrections</string>
-
     <!-- Option to enable auto capitalization of sentences -->
     <string name="auto_cap">Auto-capitalization</string>
 
@@ -118,14 +106,10 @@
     <!-- Option to suggest auto correction suggestions very aggressively. Auto-corrects to a word which has even large edit distance from typed word. [CHAR LIMIT=20] -->
     <string name="auto_correction_threshold_mode_very_aggeressive">Very aggressive</string>
 
-    <!-- Option to enable next word correction -->
-    <string name="bigram_suggestion">Next word suggestions</string>
-    <!-- Option to enable next word suggestion. This uses the previous word in an attempt to improve the suggestions quality -->
-    <string name="bigram_suggestion_summary">Use previous word to improve suggestions</string>
-    <!-- Option to enable using next word prediction -->
-    <string name="bigram_prediction">Next word prediction</string>
-    <!-- Description for "next word prediction" option. This displays suggestions even when there is no input, based on the previous word. -->
-    <string name="bigram_prediction_summary">Use previous word also for prediction</string>
+    <!-- Option to enable using next word suggestions. After the user types a space, with this option on, the keyboard will try to predict the next word. -->
+    <string name="bigram_prediction">Next word suggestions</string>
+    <!-- Description for "next word suggestion" option. This displays suggestions even when there is no input, based on the previous word. -->
+    <string name="bigram_prediction_summary">Based on previous word</string>
 
     <!-- Indicates that a word has been added to the dictionary -->
     <string name="added_word"><xliff:g id="word">%s</xliff:g> : Saved</string>
@@ -233,6 +217,20 @@
     <!-- Title for input language selection screen -->
     <string name="language_selection_title">Input languages</string>
 
+    <!-- Title for dialog option that lets user mark a particular time in the log for later review by experts [CHAR LIMIT=38] -->
+    <string name="note_timestamp_for_researchlog">Note timestamp in log</string>
+    <!-- Toast notification message that the time has been marked for later review. [CHAR LIMIT=25] -->
+    <string name="notify_recorded_timestamp">Recorded timestamp</string>
+
+    <!-- Title for dialog option to let users cancel logging and delete log for this session [CHAR LIMIT=35] -->
+    <string name="do_not_log_this_session">Do not log this session</string>
+    <!-- Toast notification that the system is processing the request to delete the log for this session [CHAR LIMIT=35] -->
+    <string name="notify_session_log_deleting">Deleting session log</string>
+    <!-- Toast notification that the system has successfully deleted the log for this session [CHAR LIMIT=35] -->
+    <string name="notify_session_log_deleted">Session log deleted</string>
+    <!-- Toast notification that the system has failed to delete the log for this session [CHAR LIMIT=35] -->
+    <string name="notify_session_log_not_deleted">Session log NOT deleted</string>
+
     <!-- Preference for input language selection -->
     <string name="select_language">Input languages</string>
 
@@ -289,7 +287,7 @@
     <string name="subtype_locale">Language</string>
     <!-- Title of the spinner for choosing a keyboard layout of custom style in the settings dialog [CHAR LIMIT=15] -->
     <string name="keyboard_layout_set">Layout</string>
-    <!-- The message of the dialog to note that a custom input style needs to be enabled. [CHAR LIMIT=64] -->
+    <!-- The message of the dialog to note that a custom input style needs to be enabled. [CHAR LIMIT=130] -->
     <string name="custom_input_style_note_message">"Your custom input style needs to be enabled before you start using it. Do you want to enable it now?"</string>
     <!-- Title of the button to enable a custom input style entry in the settings dialog [CHAR LIMIT=15] -->
     <string name="enable">Enable</string>
diff --git a/java/res/xml-sw600dp/key_styles_common.xml b/java/res/xml-sw600dp/key_styles_common.xml
index a1b2eb4..bf2e76a 100644
--- a/java/res/xml-sw600dp/key_styles_common.xml
+++ b/java/res/xml-sw600dp/key_styles_common.xml
@@ -133,6 +133,17 @@
                 latin:keyIconPreview="!icon/tab_key_preview"
                 latin:backgroundType="functional" />
         </case>
+        <case
+            latin:keyboardLayoutSetElement="alphabet|alphabetAutomaticShifted|alphabetShiftLocked"
+            latin:navigateNext="true"
+        >
+            <key-style
+                latin:styleName="tabKeyStyle"
+                latin:code="!code/key_action_next"
+                latin:keyIcon="!icon/tab_key"
+                latin:keyIconPreview="!icon/tab_key_preview"
+                latin:backgroundType="functional" />
+        </case>
         <default>
             <key-style
                 latin:styleName="tabKeyStyle"
diff --git a/java/res/xml-sw768dp/key_styles_common.xml b/java/res/xml-sw768dp/key_styles_common.xml
index 40082ac..7afe584 100644
--- a/java/res/xml-sw768dp/key_styles_common.xml
+++ b/java/res/xml-sw768dp/key_styles_common.xml
@@ -117,6 +117,17 @@
                 latin:keyLabelFlags="fontNormal|preserveCase"
                 latin:backgroundType="functional" />
         </case>
+        <case
+            latin:keyboardLayoutSetElement="alphabet|alphabetAutomaticShifted|alphabetShiftLocked"
+            latin:navigateNext="true"
+        >
+            <key-style
+                latin:styleName="tabKeyStyle"
+                latin:code="!code/key_action_next"
+                latin:keyLabel="!text/label_tab_key"
+                latin:keyLabelFlags="fontNormal|preserveCase"
+                latin:backgroundType="functional" />
+        </case>
         <default>
             <key-style
                 latin:styleName="tabKeyStyle"
diff --git a/java/res/xml/key_styles_common.xml b/java/res/xml/key_styles_common.xml
index 622da21..162119d 100644
--- a/java/res/xml/key_styles_common.xml
+++ b/java/res/xml/key_styles_common.xml
@@ -22,23 +22,8 @@
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
     <!-- Base key style for the key which may have settings or tab key as popup key. -->
-    <switch>
-        <case
-            latin:clobberSettingsKey="true"
-        >
-            <key-style
-                latin:styleName="f1MoreKeysStyle"
-                latin:backgroundType="functional" />
-        </case>
-        <!-- clobberSettingsKey="false" -->
-        <default>
-            <key-style
-                latin:styleName="f1MoreKeysStyle"
-                latin:keyLabelFlags="hasPopupHint"
-                latin:moreKeys="!text/settings_as_more_key"
-                latin:backgroundType="functional" />
-        </default>
-    </switch>
+    <include
+        latin:keyboardLayout="@xml/key_styles_f1" />
     <!-- Functional key styles -->
     <switch>
         <case
diff --git a/java/res/xml/key_styles_f1.xml b/java/res/xml/key_styles_f1.xml
new file mode 100644
index 0000000..8dfc3cb
--- /dev/null
+++ b/java/res/xml/key_styles_f1.xml
@@ -0,0 +1,43 @@
+<?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.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- Base key style for the key which may have settings or tab key as popup key. -->
+    <!-- Kept as a separate file for cleaner overriding by an overlay.  -->
+    <switch>
+        <case
+            latin:clobberSettingsKey="true"
+        >
+            <key-style
+                latin:styleName="f1MoreKeysStyle"
+                latin:backgroundType="functional" />
+        </case>
+        <!-- clobberSettingsKey="false" -->
+        <default>
+            <key-style
+                latin:styleName="f1MoreKeysStyle"
+                latin:keyLabelFlags="hasPopupHint"
+                latin:moreKeys="!text/settings_as_more_key"
+                latin:backgroundType="functional" />
+        </default>
+    </switch>
+</merge>
diff --git a/java/res/xml/prefs.xml b/java/res/xml/prefs.xml
index 1379819..bf88058 100644
--- a/java/res/xml/prefs.xml
+++ b/java/res/xml/prefs.xml
@@ -81,6 +81,12 @@
         android:title="@string/misc_category"
         android:key="misc_settings">
         <CheckBoxPreference
+            android:key="next_word_prediction"
+            android:title="@string/bigram_prediction"
+            android:summary="@string/bigram_prediction_summary"
+            android:persistent="true"
+            android:defaultValue="true" />
+        <CheckBoxPreference
             android:key="usability_study_mode"
             android:title="@string/prefs_usability_study_mode"
             android:persistent="true"
@@ -114,24 +120,6 @@
                 android:summary="@string/use_contacts_dict_summary"
                 android:persistent="true"
                 android:defaultValue="true" />
-            <CheckBoxPreference
-                android:key="next_word_suggestion"
-                android:title="@string/bigram_suggestion"
-                android:summary="@string/bigram_suggestion_summary"
-                android:persistent="true"
-                android:defaultValue="true" />
-            <CheckBoxPreference
-                android:key="next_word_prediction"
-                android:title="@string/bigram_prediction"
-                android:summary="@string/bigram_prediction_summary"
-                android:persistent="true"
-                android:defaultValue="true" />
-            <CheckBoxPreference
-                android:key="enable_span_insert"
-                android:title="@string/enable_span_insert"
-                android:summary="@string/enable_span_insert_summary"
-                android:persistent="true"
-                android:defaultValue="true" />
             <PreferenceScreen
                 android:key="pref_vibration_duration_settings"
                 android:title="@string/prefs_keypress_vibration_duration_settings"/>
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
index 23acb8b..5ffd94a 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.SparseIntArray;
 import android.view.inputmethod.EditorInfo;
 
 import com.android.inputmethod.keyboard.Key;
@@ -39,8 +40,8 @@
     // Map of key labels to spoken description resource IDs
     private final HashMap<CharSequence, Integer> mKeyLabelMap;
 
-    // Map of key codes to spoken description resource IDs
-    private final HashMap<Integer, Integer> mKeyCodeMap;
+    // Sparse array of spoken description resource IDs indexed by key codes
+    private final SparseIntArray mKeyCodeMap;
 
     public static void init() {
         sInstance.initInternal();
@@ -52,7 +53,7 @@
 
     private KeyCodeDescriptionMapper() {
         mKeyLabelMap = new HashMap<CharSequence, Integer>();
-        mKeyCodeMap = new HashMap<Integer, Integer>();
+        mKeyCodeMap = new SparseIntArray();
     }
 
     private void initInternal() {
@@ -60,7 +61,7 @@
         mKeyLabelMap.put(":-)", R.string.spoken_description_smiley);
 
         // Symbols that most TTS engines can't speak
-        mKeyCodeMap.put((int) ' ', R.string.spoken_description_space);
+        mKeyCodeMap.put(' ', R.string.spoken_description_space);
 
         // Special non-character codes defined in Keyboard
         mKeyCodeMap.put(Keyboard.CODE_DELETE, R.string.spoken_description_delete);
@@ -273,7 +274,8 @@
             return context.getString(OBSCURED_KEY_RES_ID);
         }
 
-        if (mKeyCodeMap.containsKey(code)) {
+        final int resId = mKeyCodeMap.get(code);
+        if (resId != 0) {
             return context.getString(mKeyCodeMap.get(code));
         } else if (isDefinedNonCtrl) {
             return Character.toString((char) code);
diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
index 13e909c..9abc79d 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
@@ -53,6 +53,7 @@
         return x + mCorrectionX;
     }
 
+    // TODO: Remove vertical correction.
     public int getTouchY(int y) {
         return y + mCorrectionY;
     }
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index 21f175d..1aec001 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -23,6 +23,8 @@
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
 import android.util.TypedValue;
 import android.util.Xml;
 import android.view.InflateException;
@@ -44,7 +46,6 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Locale;
 
@@ -89,7 +90,8 @@
     private static final int MINIMUM_LETTER_CODE = CODE_TAB;
 
     /** Special keys code. Must be negative.
-     * These should be aligned with values/keycodes.xml
+     * These should be aligned with KeyboardCodesSet.ID_TO_NAME[],
+     * KeyboardCodesSet.DEFAULT[] and KeyboardCodesSet.RTL[]
      */
     public static final int CODE_SHIFT = -1;
     public static final int CODE_SWITCH_ALPHA_SYMBOL = -2;
@@ -101,8 +103,9 @@
     public static final int CODE_ACTION_NEXT = -8;
     public static final int CODE_ACTION_PREVIOUS = -9;
     public static final int CODE_LANGUAGE_SWITCH = -10;
+    public static final int CODE_RESEARCH = -11;
     // Code value representing the code is not specified.
-    public static final int CODE_UNSPECIFIED = -11;
+    public static final int CODE_UNSPECIFIED = -12;
 
     public final KeyboardId mId;
     public final int mThemeId;
@@ -132,7 +135,7 @@
     public final Key[] mAltCodeKeysWhileTyping;
     public final KeyboardIconsSet mIconsSet;
 
-    private final HashMap<Integer, Key> mKeyCache = new HashMap<Integer, Key>();
+    private final SparseArray<Key> mKeyCache = new SparseArray<Key>();
 
     private final ProximityInfo mProximityInfo;
     private final boolean mProximityCharsCorrectionEnabled;
@@ -182,23 +185,25 @@
         if (code == CODE_UNSPECIFIED) {
             return null;
         }
-        final Integer keyCode = code;
-        if (mKeyCache.containsKey(keyCode)) {
-            return mKeyCache.get(keyCode);
-        }
-
-        for (final Key key : mKeys) {
-            if (key.mCode == code) {
-                mKeyCache.put(keyCode, key);
-                return key;
+        synchronized (mKeyCache) {
+            final int index = mKeyCache.indexOfKey(code);
+            if (index >= 0) {
+                return mKeyCache.valueAt(index);
             }
+
+            for (final Key key : mKeys) {
+                if (key.mCode == code) {
+                    mKeyCache.put(code, key);
+                    return key;
+                }
+            }
+            mKeyCache.put(code, null);
+            return null;
         }
-        mKeyCache.put(keyCode, null);
-        return null;
     }
 
     public boolean hasKey(Key aKey) {
-        if (mKeyCache.containsKey(aKey)) {
+        if (mKeyCache.indexOfValue(aKey) >= 0) {
             return true;
         }
 
@@ -342,8 +347,8 @@
 
         private int mMaxHeightCount = 0;
         private int mMaxWidthCount = 0;
-        private final HashMap<Integer, Integer> mHeightHistogram = new HashMap<Integer, Integer>();
-        private final HashMap<Integer, Integer> mWidthHistogram = new HashMap<Integer, Integer>();
+        private final SparseIntArray mHeightHistogram = new SparseIntArray();
+        private final SparseIntArray mWidthHistogram = new SparseIntArray();
 
         private void clearHistogram() {
             mMostCommonKeyHeight = 0;
@@ -355,22 +360,22 @@
             mWidthHistogram.clear();
         }
 
-        private static int updateHistogramCounter(HashMap<Integer, Integer> histogram,
-                Integer key) {
-            final int count = (histogram.containsKey(key) ? histogram.get(key) : 0) + 1;
+        private static int updateHistogramCounter(SparseIntArray histogram, int key) {
+            final int index = histogram.indexOfKey(key);
+            final int count = (index >= 0 ? histogram.get(key) : 0) + 1;
             histogram.put(key, count);
             return count;
         }
 
         private void updateHistogram(Key key) {
-            final Integer height = key.mHeight + key.mVerticalGap;
+            final int height = key.mHeight + key.mVerticalGap;
             final int heightCount = updateHistogramCounter(mHeightHistogram, height);
             if (heightCount > mMaxHeightCount) {
                 mMaxHeightCount = heightCount;
                 mMostCommonKeyHeight = height;
             }
 
-            final Integer width = key.mWidth + key.mHorizontalGap;
+            final int width = key.mWidth + key.mHorizontalGap;
             final int widthCount = updateHistogramCounter(mWidthHistogram, width);
             if (widthCount > mMaxWidthCount) {
                 mMaxWidthCount = widthCount;
@@ -422,67 +427,67 @@
      * This class parses Keyboard XML file and eventually build a Keyboard.
      * The Keyboard XML file looks like:
      * <pre>
-     *   &gt;!-- xml/keyboard.xml --&lt;
-     *   &gt;Keyboard keyboard_attributes*&lt;
-     *     &gt;!-- Keyboard Content --&lt;
-     *     &gt;Row row_attributes*&lt;
-     *       &gt;!-- Row Content --&lt;
-     *       &gt;Key key_attributes* /&lt;
-     *       &gt;Spacer horizontalGap="32.0dp" /&lt;
-     *       &gt;include keyboardLayout="@xml/other_keys"&lt;
+     *   &lt;!-- xml/keyboard.xml --&gt;
+     *   &lt;Keyboard keyboard_attributes*&gt;
+     *     &lt;!-- Keyboard Content --&gt;
+     *     &lt;Row row_attributes*&gt;
+     *       &lt;!-- Row Content --&gt;
+     *       &lt;Key key_attributes* /&gt;
+     *       &lt;Spacer horizontalGap="32.0dp" /&gt;
+     *       &lt;include keyboardLayout="@xml/other_keys"&gt;
      *       ...
-     *     &gt;/Row&lt;
-     *     &gt;include keyboardLayout="@xml/other_rows"&lt;
+     *     &lt;/Row&gt;
+     *     &lt;include keyboardLayout="@xml/other_rows"&gt;
      *     ...
-     *   &gt;/Keyboard&lt;
+     *   &lt;/Keyboard&gt;
      * </pre>
-     * The XML file which is included in other file must have &gt;merge&lt; as root element,
+     * The XML file which is included in other file must have &lt;merge&gt; as root element,
      * such as:
      * <pre>
-     *   &gt;!-- xml/other_keys.xml --&lt;
-     *   &gt;merge&lt;
-     *     &gt;Key key_attributes* /&lt;
+     *   &lt;!-- xml/other_keys.xml --&gt;
+     *   &lt;merge&gt;
+     *     &lt;Key key_attributes* /&gt;
      *     ...
-     *   &gt;/merge&lt;
+     *   &lt;/merge&gt;
      * </pre>
      * and
      * <pre>
-     *   &gt;!-- xml/other_rows.xml --&lt;
-     *   &gt;merge&lt;
-     *     &gt;Row row_attributes*&lt;
-     *       &gt;Key key_attributes* /&lt;
-     *     &gt;/Row&lt;
+     *   &lt;!-- xml/other_rows.xml --&gt;
+     *   &lt;merge&gt;
+     *     &lt;Row row_attributes*&gt;
+     *       &lt;Key key_attributes* /&gt;
+     *     &lt;/Row&gt;
      *     ...
-     *   &gt;/merge&lt;
+     *   &lt;/merge&gt;
      * </pre>
      * You can also use switch-case-default tags to select Rows and Keys.
      * <pre>
-     *   &gt;switch&lt;
-     *     &gt;case case_attribute*&lt;
-     *       &gt;!-- Any valid tags at switch position --&lt;
-     *     &gt;/case&lt;
+     *   &lt;switch&gt;
+     *     &lt;case case_attribute*&gt;
+     *       &lt;!-- Any valid tags at switch position --&gt;
+     *     &lt;/case&gt;
      *     ...
-     *     &gt;default&lt;
-     *       &gt;!-- Any valid tags at switch position --&lt;
-     *     &gt;/default&lt;
-     *   &gt;/switch&lt;
+     *     &lt;default&gt;
+     *       &lt;!-- Any valid tags at switch position --&gt;
+     *     &lt;/default&gt;
+     *   &lt;/switch&gt;
      * </pre>
      * You can declare Key style and specify styles within Key tags.
      * <pre>
-     *     &gt;switch&lt;
-     *       &gt;case mode="email"&lt;
-     *         &gt;key-style styleName="f1-key" parentStyle="modifier-key"
+     *     &lt;switch&gt;
+     *       &lt;case mode="email"&gt;
+     *         &lt;key-style styleName="f1-key" parentStyle="modifier-key"
      *           keyLabel=".com"
-     *         /&lt;
-     *       &gt;/case&lt;
-     *       &gt;case mode="url"&lt;
-     *         &gt;key-style styleName="f1-key" parentStyle="modifier-key"
+     *         /&gt;
+     *       &lt;/case&gt;
+     *       &lt;case mode="url"&gt;
+     *         &lt;key-style styleName="f1-key" parentStyle="modifier-key"
      *           keyLabel="http://"
-     *         /&lt;
-     *       &gt;/case&lt;
-     *     &gt;/switch&lt;
+     *         /&gt;
+     *       &lt;/case&gt;
+     *     &lt;/switch&gt;
      *     ...
-     *     &gt;Key keyStyle="shift-key" ... /&lt;
+     *     &lt;Key keyStyle="shift-key" ... /&gt;
      * </pre>
      */
 
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
index 275aacf..dc27769 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
@@ -52,6 +52,7 @@
      */
     public void onCodeInput(int primaryCode, int x, int y);
 
+    // See {@link Adapter#isInvalidCoordinate(int)}.
     public static final int NOT_A_TOUCH_COORDINATE = -1;
     public static final int SUGGESTION_STRIP_COORDINATE = -2;
     public static final int SPELL_CHECKER_COORDINATE = -3;
@@ -63,6 +64,20 @@
      */
     public void onTextInput(CharSequence text);
 
+    // TODO: Should move this method to some more appropriate interface.
+    /**
+     * Called when user started batch input.
+     */
+    public void onStartBatchInput();
+
+    // TODO: Should move this method to some more appropriate interface.
+    /**
+     * Sends a sequence of characters to the listener as batch input.
+     *
+     * @param text the sequence of characters to be displayed as composing text.
+     */
+    public void onEndBatchInput(CharSequence text);
+
     /**
      * Called when user released a finger outside any key.
      */
@@ -84,10 +99,22 @@
         @Override
         public void onTextInput(CharSequence text) {}
         @Override
+        public void onStartBatchInput() {}
+        @Override
+        public void onEndBatchInput(CharSequence text) {}
+        @Override
         public void onCancelInput() {}
         @Override
         public boolean onCustomRequest(int requestCode) {
             return false;
         }
+
+        // TODO: Remove this method when the vertical correction is removed.
+        public static boolean isInvalidCoordinate(int coordinate) {
+            // Detect {@link KeyboardActionListener#NOT_A_TOUCH_COORDINATE},
+            // {@link KeyboardActionListener#SUGGESTION_STRIP_COORDINATE}, and
+            // {@link KeyboardActionListener#SPELL_CHECKER_COORDINATE}.
+            return coordinate < 0;
+        }
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index 233716a..b54c726 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -137,11 +137,13 @@
     }
 
     public boolean navigateNext() {
-        return (mEditorInfo.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0;
+        return (mEditorInfo.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0
+                || imeAction() == EditorInfo.IME_ACTION_NEXT;
     }
 
     public boolean navigatePrevious() {
-        return (mEditorInfo.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS) != 0;
+        return (mEditorInfo.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS) != 0
+                || imeAction() == EditorInfo.IME_ACTION_PREVIOUS;
     }
 
     public boolean passwordInput() {
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
index 8c72468..aab89a3 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
@@ -29,6 +29,7 @@
 import android.content.res.XmlResourceParser;
 import android.text.InputType;
 import android.util.Log;
+import android.util.SparseArray;
 import android.util.Xml;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodSubtype;
@@ -116,9 +117,9 @@
         InputMethodSubtype mSubtype;
         int mOrientation;
         int mWidth;
-        // KeyboardLayoutSet element id to element's parameters map.
-        final HashMap<Integer, ElementParams> mKeyboardLayoutSetElementIdToParamsMap =
-                new HashMap<Integer, ElementParams>();
+        // Sparse array of KeyboardLayoutSet element parameters indexed by element's id.
+        final SparseArray<ElementParams> mKeyboardLayoutSetElementIdToParamsMap =
+                new SparseArray<ElementParams>();
 
         static class ElementParams {
             int mKeyboardXmlId;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 51a0f53..fb98af3 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -30,6 +30,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Message;
 import android.util.AttributeSet;
+import android.util.SparseArray;
 import android.util.TypedValue;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -42,7 +43,6 @@
 import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
 import com.android.inputmethod.latin.StringUtils;
 
-import java.util.HashMap;
 import java.util.HashSet;
 
 /**
@@ -124,12 +124,10 @@
     private Canvas mCanvas;
     private final Paint mPaint = new Paint();
     private final Paint.FontMetrics mFontMetrics = new Paint.FontMetrics();
-    // This map caches key label text height in pixel as value and key label text size as map key.
-    private static final HashMap<Integer, Float> sTextHeightCache =
-            new HashMap<Integer, Float>();
-    // This map caches key label text width in pixel as value and key label text size as map key.
-    private static final HashMap<Integer, Float> sTextWidthCache =
-            new HashMap<Integer, Float>();
+    // This sparse array caches key label text height in pixel indexed by key label text size.
+    private static final SparseArray<Float> sTextHeightCache = new SparseArray<Float>();
+    // This sparse array caches key label text width in pixel indexed by key label text size.
+    private static final SparseArray<Float> sTextWidthCache = new SparseArray<Float>();
     private static final char[] KEY_LABEL_REFERENCE_CHAR = { 'M' };
     private static final char[] KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR = { '8' };
 
@@ -766,7 +764,7 @@
     private final Rect mTextBounds = new Rect();
 
     private float getCharHeight(char[] referenceChar, Paint paint) {
-        final Integer key = getCharGeometryCacheKey(referenceChar[0], paint);
+        final int key = getCharGeometryCacheKey(referenceChar[0], paint);
         final Float cachedValue = sTextHeightCache.get(key);
         if (cachedValue != null)
             return cachedValue;
@@ -778,7 +776,7 @@
     }
 
     private float getCharWidth(char[] referenceChar, Paint paint) {
-        final Integer key = getCharGeometryCacheKey(referenceChar[0], paint);
+        final int key = getCharGeometryCacheKey(referenceChar[0], paint);
         final Float cachedValue = sTextWidthCache.get(key);
         if (cachedValue != null)
             return cachedValue;
@@ -873,6 +871,7 @@
                 keyPreview, ViewLayoutUtils.newLayoutParam(mPreviewPlacer, 0, 0));
     }
 
+    @SuppressWarnings("deprecation") // setBackgroundDrawable is replaced by setBackground in API16
     @Override
     public void showKeyPreview(PointerTracker tracker) {
         if (!mShowKeyPreviewPopup) return;
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index 383298d..7714ba8 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -127,7 +127,6 @@
         private static final int MSG_TYPING_STATE_EXPIRED = 4;
 
         private final KeyTimerParams mParams;
-        private boolean mInKeyRepeat;
 
         public KeyTimerHandler(LatinKeyboardView outerInstance, KeyTimerParams params) {
             super(outerInstance);
@@ -140,8 +139,11 @@
             final PointerTracker tracker = (PointerTracker) msg.obj;
             switch (msg.what) {
             case MSG_REPEAT_KEY:
-                tracker.onRegisterKey(tracker.getKey());
-                startKeyRepeatTimer(tracker, mParams.mKeyRepeatInterval);
+                final Key currentKey = tracker.getKey();
+                if (currentKey != null && currentKey.mCode == msg.arg1) {
+                    tracker.onRegisterKey(currentKey);
+                    startKeyRepeatTimer(tracker, mParams.mKeyRepeatInterval);
+                }
                 break;
             case MSG_LONGPRESS_KEY:
                 if (tracker != null) {
@@ -158,22 +160,23 @@
         }
 
         private void startKeyRepeatTimer(PointerTracker tracker, long delay) {
-            sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, tracker), delay);
+            final Key key = tracker.getKey();
+            if (key == null) return;
+            sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, key.mCode, 0, tracker), delay);
         }
 
         @Override
         public void startKeyRepeatTimer(PointerTracker tracker) {
-            mInKeyRepeat = true;
             startKeyRepeatTimer(tracker, mParams.mKeyRepeatStartTimeout);
         }
 
         public void cancelKeyRepeatTimer() {
-            mInKeyRepeat = false;
             removeMessages(MSG_REPEAT_KEY);
         }
 
+        // TODO: Suppress layout changes in key repeat mode
         public boolean isInKeyRepeat() {
-            return mInKeyRepeat;
+            return hasMessages(MSG_REPEAT_KEY);
         }
 
         @Override
@@ -451,8 +454,8 @@
      */
     @Override
     public void setKeyboard(Keyboard keyboard) {
-        // Remove any pending messages, except dismissing preview
-        mKeyTimerHandler.cancelKeyTimers();
+        // Remove any pending messages, except dismissing preview and key repeat.
+        mKeyTimerHandler.cancelLongPressTimer();
         super.setKeyboard(keyboard);
         mKeyDetector.setKeyboard(
                 keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection);
@@ -755,15 +758,18 @@
                 final PointerTracker tracker = PointerTracker.getPointerTracker(
                         pointerId, this);
                 final int px, py;
+                final MotionEvent motionEvent;
                 if (mMoreKeysPanel != null
                         && tracker.mPointerId == mMoreKeysPanelPointerTrackerId) {
                     px = mMoreKeysPanel.translateX((int)me.getX(i));
                     py = mMoreKeysPanel.translateY((int)me.getY(i));
+                    motionEvent = null;
                 } else {
                     px = (int)me.getX(i);
                     py = (int)me.getY(i);
+                    motionEvent = me;
                 }
-                tracker.onMoveEvent(px, py, eventTime);
+                tracker.onMoveEvent(px, py, eventTime, motionEvent);
                 if (ENABLE_USABILITY_STUDY_LOG) {
                     final float pointerSize = me.getSize(i);
                     final float pointerPressure = me.getPressure(i);
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
index be7644f..9c80691 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
@@ -58,6 +58,16 @@
         }
 
         @Override
+        public void onStartBatchInput() {
+            mListener.onStartBatchInput();
+        }
+
+        @Override
+        public void onEndBatchInput(CharSequence text) {
+            mListener.onEndBatchInput(text);
+        }
+
+        @Override
         public void onCancelInput() {
             mListener.onCancelInput();
         }
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index babf6ec..1ae0020 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -148,9 +148,6 @@
     // true if this pointer has been long-pressed and is showing a more keys panel.
     private boolean mIsShowingMoreKeysPanel;
 
-    // true if this pointer is repeatable key
-    private boolean mIsRepeatableKey;
-
     // true if this pointer is in sliding key input
     boolean mIsInSlidingKeyInput;
 
@@ -242,10 +239,6 @@
                     + " ignoreModifier=" + ignoreModifierKey
                     + " enabled=" + key.isEnabled());
         }
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.pointerTracker_callListenerOnPressAndCheckKeyboardLayoutChange(key,
-                    ignoreModifierKey);
-        }
         if (ignoreModifierKey) {
             return false;
         }
@@ -323,6 +316,13 @@
     private void setKeyDetectorInner(KeyDetector keyDetector) {
         mKeyDetector = keyDetector;
         mKeyboard = keyDetector.getKeyboard();
+        final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY);
+        if (newKey != mCurrentKey) {
+            if (mDrawingProxy != null) {
+                setReleasedKeyGraphics(mCurrentKey);
+            }
+            mCurrentKey = newKey;
+        }
         final int keyQuarterWidth = mKeyboard.mMostCommonKeyWidth / 4;
         mKeyQuarterWidthSquared = keyQuarterWidth * keyQuarterWidth;
     }
@@ -469,7 +469,7 @@
             onUpEvent(x, y, eventTime);
             break;
         case MotionEvent.ACTION_MOVE:
-            onMoveEvent(x, y, eventTime);
+            onMoveEvent(x, y, eventTime, null);
             break;
         case MotionEvent.ACTION_CANCEL:
             onCancelEvent(x, y, eventTime);
@@ -525,7 +525,6 @@
                 || mKeyDetector.alwaysAllowsSlidingInput();
         mKeyboardLayoutHasBeenChanged = false;
         mKeyAlreadyProcessed = false;
-        mIsRepeatableKey = false;
         mIsInSlidingKeyInput = false;
         mIgnoreModifierKey = false;
         if (key != null) {
@@ -549,7 +548,7 @@
         mIsInSlidingKeyInput = true;
     }
 
-    public void onMoveEvent(int x, int y, long eventTime) {
+    public void onMoveEvent(int x, int y, long eventTime, MotionEvent me) {
         if (DEBUG_MOVE_EVENT)
             printTouchEvent("onMoveEvent:", x, y, eventTime);
         if (mKeyAlreadyProcessed)
@@ -611,6 +610,15 @@
                         onUpEventInternal();
                         onDownEventInternal(x, y, eventTime);
                     } else {
+                        // HACK: If there are currently multiple touches, register the key even if
+                        // the finger slides off the key. This defends against noise from some
+                        // touch panels when there are close multiple touches.
+                        // Caveat: When in chording input mode with a modifier key, we don't use
+                        // this hack.
+                        if (me != null && me.getPointerCount() > 1
+                                && !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) {
+                            onUpEventInternal();
+                        }
                         mKeyAlreadyProcessed = true;
                         setReleasedKeyGraphics(oldKey);
                     }
@@ -672,7 +680,7 @@
         }
         if (mKeyAlreadyProcessed)
             return;
-        if (!mIsRepeatableKey) {
+        if (mCurrentKey != null && !mCurrentKey.isRepeatable()) {
             detectAndSendKey(mCurrentKey, mKeyX, mKeyY);
         }
     }
@@ -718,9 +726,6 @@
         if (key != null && key.isRepeatable()) {
             onRegisterKey(key);
             mTimerProxy.startKeyRepeatTimer(this);
-            mIsRepeatableKey = true;
-        } else {
-            mIsRepeatableKey = false;
         }
     }
 
@@ -764,14 +769,10 @@
         callListenerOnRelease(key, code, false);
     }
 
-    private long mPreviousEventTime;
-
     private void printTouchEvent(String title, int x, int y, long eventTime) {
         final Key key = mKeyDetector.detectHitKey(x, y);
         final String code = KeyDetector.printableCode(key);
-        final long delta = eventTime - mPreviousEventTime;
         Log.d(TAG, String.format("%s%s[%d] %4d %4d %5d %s", title,
-                (mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, delta, code));
-        mPreviousEventTime = eventTime;
+                (mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, eventTime, code));
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index 1207c3f..ae123e2 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -24,9 +24,10 @@
 import com.android.inputmethod.latin.JniUtils;
 
 import java.util.Arrays;
-import java.util.HashMap;
 
 public class ProximityInfo {
+    /** MAX_PROXIMITY_CHARS_SIZE must be the same as MAX_PROXIMITY_CHARS_SIZE_INTERNAL
+     * in defines.h */
     public static final int MAX_PROXIMITY_CHARS_SIZE = 16;
     /** Number of key widths from current touch point to search for nearest keys. */
     private static float SEARCH_DISTANCE = 1.2f;
@@ -75,27 +76,6 @@
         mNativeProximityInfo = createNativeProximityInfo();
     }
 
-    // TODO: Remove this public constructor when the native part of the ProximityInfo becomes
-    // immutable.
-    // This public constructor aims only for test purpose.
-    public ProximityInfo(ProximityInfo o) {
-        mLocaleStr = o.mLocaleStr;
-        mGridWidth = o.mGridWidth;
-        mGridHeight = o.mGridHeight;
-        mGridSize = o.mGridSize;
-        mCellWidth = o.mCellWidth;
-        mCellHeight = o.mCellHeight;
-        mKeyboardMinWidth = o.mKeyboardMinWidth;
-        mKeyboardHeight = o.mKeyboardHeight;
-        mKeyHeight = o.mKeyHeight;
-        mMostCommonKeyWidth = o.mMostCommonKeyWidth;
-        mKeys = o.mKeys;
-        mTouchPositionCorrection = o.mTouchPositionCorrection;
-        mGridNeighbors = new Key[mGridSize][];
-        computeNearestNeighbors();
-        mNativeProximityInfo = createNativeProximityInfo();
-    }
-
     public static ProximityInfo createDummyProximityInfo() {
         return new ProximityInfo("", 1, 1, 1, 1, 1, 1, EMPTY_KEY_ARRAY, null);
     }
@@ -209,10 +189,6 @@
     private void computeNearestNeighbors() {
         final int defaultWidth = mMostCommonKeyWidth;
         final Key[] keys = mKeys;
-        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
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
index c4452a5..5326120 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
@@ -68,12 +68,20 @@
 
         public MoreKeySpec(final String moreKeySpec, boolean needsToUpperCase, Locale locale,
                 final KeyboardCodesSet codesSet) {
-            mCode = toUpperCaseOfCodeForLocale(getCode(moreKeySpec, codesSet),
-                    needsToUpperCase, locale);
             mLabel = toUpperCaseOfStringForLocale(getLabel(moreKeySpec),
                     needsToUpperCase, locale);
-            mOutputText = toUpperCaseOfStringForLocale(getOutputText(moreKeySpec),
+            final int code = toUpperCaseOfCodeForLocale(getCode(moreKeySpec, codesSet),
                     needsToUpperCase, locale);
+            if (code == Keyboard.CODE_UNSPECIFIED) {
+                // Some letter, for example German Eszett (U+00DF: "ß"), has multiple characters
+                // upper case representation ("SS").
+                mCode = Keyboard.CODE_OUTPUT_TEXT;
+                mOutputText = mLabel;
+            } else {
+                mCode = code;
+                mOutputText = toUpperCaseOfStringForLocale(getOutputText(moreKeySpec),
+                        needsToUpperCase, locale);
+            }
             mIconId = getIconId(moreKeySpec);
         }
     }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
index 80f4f25..291b3b9 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
@@ -18,6 +18,7 @@
 
 import android.content.res.TypedArray;
 import android.util.Log;
+import android.util.SparseArray;
 
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.latin.R;
@@ -89,7 +90,7 @@
 
     private class DeclaredKeyStyle extends KeyStyle {
         private final String mParentStyleName;
-        private final HashMap<Integer, Object> mStyleAttributes = new HashMap<Integer, Object>();
+        private final SparseArray<Object> mStyleAttributes = new SparseArray<Object>();
 
         public DeclaredKeyStyle(String parentStyleName) {
             mParentStyleName = parentStyleName;
@@ -100,8 +101,9 @@
             if (a.hasValue(index)) {
                 return parseStringArray(a, index);
             }
-            if (mStyleAttributes.containsKey(index)) {
-                return (String[])mStyleAttributes.get(index);
+            final Object value = mStyleAttributes.get(index);
+            if (value != null) {
+                return (String[])value;
             }
             final KeyStyle parentStyle = mStyles.get(mParentStyleName);
             return parentStyle.getStringArray(a, index);
@@ -112,8 +114,9 @@
             if (a.hasValue(index)) {
                 return parseString(a, index);
             }
-            if (mStyleAttributes.containsKey(index)) {
-                return (String)mStyleAttributes.get(index);
+            final Object value = mStyleAttributes.get(index);
+            if (value != null) {
+                return (String)value;
             }
             final KeyStyle parentStyle = mStyles.get(mParentStyleName);
             return parentStyle.getString(a, index);
@@ -124,8 +127,9 @@
             if (a.hasValue(index)) {
                 return a.getInt(index, defaultValue);
             }
-            if (mStyleAttributes.containsKey(index)) {
-                return (Integer)mStyleAttributes.get(index);
+            final Object value = mStyleAttributes.get(index);
+            if (value != null) {
+                return (Integer)value;
             }
             final KeyStyle parentStyle = mStyles.get(mParentStyleName);
             return parentStyle.getInt(a, index, defaultValue);
@@ -133,12 +137,13 @@
 
         @Override
         public int getFlag(TypedArray a, int index) {
-            int value = a.getInt(index, 0);
-            if (mStyleAttributes.containsKey(index)) {
-                value |= (Integer)mStyleAttributes.get(index);
+            int flags = a.getInt(index, 0);
+            final Object value = mStyleAttributes.get(index);
+            if (value != null) {
+                flags |= (Integer)value;
             }
             final KeyStyle parentStyle = mStyles.get(mParentStyleName);
-            return value | parentStyle.getFlag(a, index);
+            return flags | parentStyle.getFlag(a, index);
         }
 
         void readKeyAttributes(TypedArray keyAttr) {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
index 67cb74f..f7981a3 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
@@ -52,6 +52,7 @@
         "key_action_next",
         "key_action_previous",
         "key_language_switch",
+        "key_research",
         "key_unspecified",
         "key_left_parenthesis",
         "key_right_parenthesis",
@@ -86,6 +87,7 @@
         Keyboard.CODE_ACTION_NEXT,
         Keyboard.CODE_ACTION_PREVIOUS,
         Keyboard.CODE_LANGUAGE_SWITCH,
+        Keyboard.CODE_RESEARCH,
         Keyboard.CODE_UNSPECIFIED,
         CODE_LEFT_PARENTHESIS,
         CODE_RIGHT_PARENTHESIS,
@@ -112,6 +114,7 @@
         DEFAULT[11],
         DEFAULT[12],
         DEFAULT[13],
+        DEFAULT[14],
         CODE_RIGHT_PARENTHESIS,
         CODE_LEFT_PARENTHESIS,
         CODE_GREATER_THAN_SIGN,
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
index 540e63b..5155851 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
@@ -20,6 +20,7 @@
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
 import android.util.Log;
+import android.util.SparseIntArray;
 
 import com.android.inputmethod.latin.R;
 
@@ -31,8 +32,7 @@
     public static final int ICON_UNDEFINED = 0;
     private static final int ATTR_UNDEFINED = 0;
 
-    private static final HashMap<Integer, Integer> ATTR_ID_TO_ICON_ID
-            = new HashMap<Integer, Integer>();
+    private static final SparseIntArray ATTR_ID_TO_ICON_ID = new SparseIntArray();
 
     // Icon name to icon id map.
     private static final HashMap<String, Integer> sNameToIdsMap = new HashMap<String, Integer>();
@@ -76,7 +76,9 @@
     }
 
     public void loadIcons(final TypedArray keyboardAttrs) {
-        for (final Integer attrId : ATTR_ID_TO_ICON_ID.keySet()) {
+        final int size = ATTR_ID_TO_ICON_ID.size();
+        for (int index = 0; index < size; index++) {
+            final int attrId = ATTR_ID_TO_ICON_ID.keyAt(index);
             try {
                 final Drawable icon = keyboardAttrs.getDrawable(attrId);
                 setDefaultBounds(icon);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
index 43ffb85..4ab6832 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
@@ -21,8 +21,6 @@
 
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.ResearchLogger;
-import com.android.inputmethod.latin.define.ProductionFlag;
 
 /**
  * Keyboard state machine.
@@ -305,9 +303,6 @@
             Log.d(TAG, "onPressKey: code=" + Keyboard.printableCode(code)
                    + " single=" + isSinglePointer + " autoCaps=" + autoCaps + " " + this);
         }
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.keyboardState_onPressKey(code, this);
-        }
         if (code == Keyboard.CODE_SHIFT) {
             onPressShift();
         } else if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
@@ -341,9 +336,6 @@
             Log.d(TAG, "onReleaseKey: code=" + Keyboard.printableCode(code)
                     + " sliding=" + withSliding + " " + this);
         }
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.keyboardState_onReleaseKey(this, code, withSliding);
-        }
         if (code == Keyboard.CODE_SHIFT) {
             onReleaseShift(withSliding);
         } else if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
@@ -375,9 +367,6 @@
         if (DEBUG_EVENT) {
             Log.d(TAG, "onLongPressTimeout: code=" + Keyboard.printableCode(code) + " " + this);
         }
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.keyboardState_onLongPressTimeout(code, this);
-        }
         if (mIsAlphabetMode && code == Keyboard.CODE_SHIFT) {
             mLongPressShiftLockFired = true;
             mSwitchActions.hapticAndAudioFeedback(code);
@@ -509,9 +498,6 @@
         if (DEBUG_EVENT) {
             Log.d(TAG, "onCancelInput: single=" + isSinglePointer + " " + this);
         }
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.keyboardState_onCancelInput(isSinglePointer, this);
-        }
         // Switch back to the previous keyboard mode if the user cancels sliding input.
         if (isSinglePointer) {
             if (mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL) {
@@ -543,9 +529,6 @@
                     + " single=" + isSinglePointer
                     + " autoCaps=" + autoCaps + " " + this);
         }
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.keyboardState_onCodeInput(code, isSinglePointer, autoCaps, this);
-        }
 
         switch (mSwitchState) {
         case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL:
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
index 5db65c6..d3bb85d 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
@@ -28,6 +28,7 @@
     private static final String TAG = PointerTrackerQueue.class.getSimpleName();
     private static final boolean DEBUG = false;
 
+    // TODO: Use ring buffer instead of {@link LinkedList}.
     private final LinkedList<PointerTracker> mQueue = new LinkedList<PointerTracker>();
 
     public synchronized void add(PointerTracker tracker) {
@@ -81,6 +82,20 @@
         }
     }
 
+    public synchronized boolean hasModifierKeyOlderThan(PointerTracker tracker) {
+        final Iterator<PointerTracker> it = mQueue.iterator();
+        while (it.hasNext()) {
+            final PointerTracker t = it.next();
+            if (t == tracker) {
+                break;
+            }
+            if (t.isModifier()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     public synchronized boolean isAnyInSlidingKeyInput() {
         for (final PointerTracker tracker : mQueue) {
             if (tracker.isInSlidingKeyInput()) {
diff --git a/java/src/com/android/inputmethod/latin/AutoCorrection.java b/java/src/com/android/inputmethod/latin/AutoCorrection.java
index e0452483..c78974d 100644
--- a/java/src/com/android/inputmethod/latin/AutoCorrection.java
+++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java
@@ -21,34 +21,17 @@
 import android.text.TextUtils;
 import android.util.Log;
 
-import java.util.ArrayList;
 import java.util.concurrent.ConcurrentHashMap;
 
 public class AutoCorrection {
     private static final boolean DBG = LatinImeLogger.sDBG;
     private static final String TAG = AutoCorrection.class.getSimpleName();
+    private static final int MINIMUM_SAFETY_NET_CHAR_LENGTH = 4;
 
     private AutoCorrection() {
         // Purely static class: can't instantiate.
     }
 
-    public static CharSequence computeAutoCorrectionWord(
-            final ConcurrentHashMap<String, Dictionary> dictionaries,
-            final WordComposer wordComposer, final ArrayList<SuggestedWordInfo> suggestions,
-            final CharSequence consideredWord, final float autoCorrectionThreshold,
-            final CharSequence whitelistedWord) {
-        if (hasAutoCorrectionForWhitelistedWord(whitelistedWord)) {
-            return whitelistedWord;
-        } else if (hasAutoCorrectionForConsideredWord(
-                dictionaries, wordComposer, suggestions, consideredWord)) {
-            return consideredWord;
-        } else if (hasAutoCorrectionForBinaryDictionary(wordComposer, suggestions,
-                consideredWord, autoCorrectionThreshold)) {
-            return suggestions.get(0).mWord;
-        }
-        return null;
-    }
-
     public static boolean isValidWord(final ConcurrentHashMap<String, Dictionary> dictionaries,
             CharSequence word, boolean ignoreCase) {
         if (TextUtils.isEmpty(word)) {
@@ -56,7 +39,7 @@
         }
         final CharSequence lowerCasedWord = word.toString().toLowerCase();
         for (final String key : dictionaries.keySet()) {
-            if (key.equals(Suggest.DICT_KEY_WHITELIST)) continue;
+            if (key.equals(Dictionary.TYPE_WHITELIST)) continue;
             final Dictionary dictionary = dictionaries.get(key);
             // It's unclear how realistically 'dictionary' can be null, but the monkey is somehow
             // managing to get null in here. Presumably the language is changing to a language with
@@ -81,7 +64,7 @@
         }
         int maxFreq = -1;
         for (final String key : dictionaries.keySet()) {
-            if (key.equals(Suggest.DICT_KEY_WHITELIST)) continue;
+            if (key.equals(Dictionary.TYPE_WHITELIST)) continue;
             final Dictionary dictionary = dictionaries.get(key);
             if (null == dictionary) continue;
             final int tempFreq = dictionary.getFrequency(word);
@@ -92,11 +75,12 @@
         return maxFreq;
     }
 
-    public static boolean allowsToBeAutoCorrected(
+    // Returns true if this is a whitelist entry, or it isn't in any dictionary.
+    public static boolean isWhitelistedOrNotAWord(
             final ConcurrentHashMap<String, Dictionary> dictionaries,
             final CharSequence word, final boolean ignoreCase) {
         final WhitelistDictionary whitelistDictionary =
-                (WhitelistDictionary)dictionaries.get(Suggest.DICT_KEY_WHITELIST);
+                (WhitelistDictionary)dictionaries.get(Dictionary.TYPE_WHITELIST);
         // If "word" is in the whitelist dictionary, it should not be auto corrected.
         if (whitelistDictionary != null
                 && whitelistDictionary.shouldForciblyAutoCorrectFrom(word)) {
@@ -105,33 +89,18 @@
         return !isValidWord(dictionaries, word, ignoreCase);
     }
 
-    private static boolean hasAutoCorrectionForWhitelistedWord(CharSequence whiteListedWord) {
-        return whiteListedWord != null;
-    }
-
-    private static boolean hasAutoCorrectionForConsideredWord(
-            final ConcurrentHashMap<String, Dictionary> dictionaries,
-            final WordComposer wordComposer, final ArrayList<SuggestedWordInfo> suggestions,
-            final CharSequence consideredWord) {
-        if (TextUtils.isEmpty(consideredWord)) return false;
-        return wordComposer.size() > 1 && suggestions.size() > 0
-                && !allowsToBeAutoCorrected(dictionaries, consideredWord, false);
-    }
-
-    private static boolean hasAutoCorrectionForBinaryDictionary(WordComposer wordComposer,
-            ArrayList<SuggestedWordInfo> suggestions,
+    public static boolean suggestionExceedsAutoCorrectionThreshold(SuggestedWordInfo suggestion,
             CharSequence consideredWord, float autoCorrectionThreshold) {
-        if (wordComposer.size() > 1 && suggestions.size() > 0) {
-            final SuggestedWordInfo autoCorrectionSuggestion = suggestions.get(0);
+        if (null != suggestion) {
             //final int autoCorrectionSuggestionScore = sortedScores[0];
-            final int autoCorrectionSuggestionScore = autoCorrectionSuggestion.mScore;
+            final int autoCorrectionSuggestionScore = suggestion.mScore;
             // TODO: when the normalized score of the first suggestion is nearly equals to
             //       the normalized score of the second suggestion, behave less aggressive.
             final float normalizedScore = BinaryDictionary.calcNormalizedScore(
-                    consideredWord.toString(), autoCorrectionSuggestion.mWord.toString(),
+                    consideredWord.toString(), suggestion.mWord.toString(),
                     autoCorrectionSuggestionScore);
             if (DBG) {
-                Log.d(TAG, "Normalized " + consideredWord + "," + autoCorrectionSuggestion + ","
+                Log.d(TAG, "Normalized " + consideredWord + "," + suggestion + ","
                         + autoCorrectionSuggestionScore + ", " + normalizedScore
                         + "(" + autoCorrectionThreshold + ")");
             }
@@ -139,10 +108,43 @@
                 if (DBG) {
                     Log.d(TAG, "Auto corrected by S-threshold.");
                 }
-                return true;
+                return !shouldBlockAutoCorrectionBySafetyNet(consideredWord.toString(),
+                        suggestion.mWord);
             }
         }
         return false;
     }
 
+    // TODO: Resolve the inconsistencies between the native auto correction algorithms and
+    // this safety net
+    public static boolean shouldBlockAutoCorrectionBySafetyNet(final String typedWord,
+            final CharSequence suggestion) {
+        // Safety net for auto correction.
+        // Actually if we hit this safety net, it's a bug.
+        // If user selected aggressive auto correction mode, there is no need to use the safety
+        // net.
+        // If the length of typed word is less than MINIMUM_SAFETY_NET_CHAR_LENGTH,
+        // we should not use net because relatively edit distance can be big.
+        final int typedWordLength = typedWord.length();
+        if (typedWordLength < MINIMUM_SAFETY_NET_CHAR_LENGTH) {
+            return false;
+        }
+        final int maxEditDistanceOfNativeDictionary =
+                (typedWordLength < 5 ? 2 : typedWordLength / 2) + 1;
+        final int distance = BinaryDictionary.editDistance(typedWord, suggestion.toString());
+        if (DBG) {
+            Log.d(TAG, "Autocorrected edit distance = " + distance
+                    + ", " + maxEditDistanceOfNativeDictionary);
+        }
+        if (distance > maxEditDistanceOfNativeDictionary) {
+            if (DBG) {
+                Log.e(TAG, "Safety net: before = " + typedWord + ", after = " + suggestion);
+                Log.e(TAG, "(Error) The edit distance of this correction exceeds limit. "
+                        + "Turning off auto-correction.");
+            }
+            return true;
+        } else {
+            return false;
+        }
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index d0613bd..291d849 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -20,7 +20,9 @@
 import android.text.TextUtils;
 
 import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Locale;
 
@@ -40,19 +42,19 @@
      */
     public static final int MAX_WORD_LENGTH = 48;
     public static final int MAX_WORDS = 18;
+    public static final int MAX_SPACES = 16;
 
     private static final String TAG = "BinaryDictionary";
     private static final int MAX_BIGRAMS = 60;
+    private static final int MAX_RESULTS = MAX_BIGRAMS > MAX_WORDS ? MAX_BIGRAMS : MAX_WORDS;
 
     private static final int TYPED_LETTER_MULTIPLIER = 2;
 
-    private int mDicTypeId;
     private long mNativeDict;
     private final int[] mInputCodes = new int[MAX_WORD_LENGTH];
-    private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_WORDS];
-    private final char[] mOutputChars_bigrams = new char[MAX_WORD_LENGTH * MAX_BIGRAMS];
-    private final int[] mScores = new int[MAX_WORDS];
-    private final int[] mBigramScores = new int[MAX_BIGRAMS];
+    private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_RESULTS];
+    private final int[] mSpaceIndices = new int[MAX_SPACES];
+    private final int[] mOutputScores = new int[MAX_RESULTS];
 
     private final boolean mUseFullEditDistance;
 
@@ -65,14 +67,12 @@
      * @param offset the offset of the dictionary data within the file.
      * @param length the length of the binary data.
      * @param useFullEditDistance whether to use the full edit distance in suggestions
+     * @param dictType the dictionary type, as a human-readable string
      */
     public BinaryDictionary(final Context context,
             final String filename, final long offset, final long length,
-            final boolean useFullEditDistance, final Locale locale) {
-        // Note: at the moment a binary dictionary is always of the "main" type.
-        // Initializing this here will help transitioning out of the scheme where
-        // the Suggest class knows everything about every single dictionary.
-        mDicTypeId = Suggest.DIC_MAIN;
+            final boolean useFullEditDistance, final Locale locale, final String dictType) {
+        super(dictType);
         mUseFullEditDistance = useFullEditDistance;
         loadDictionary(filename, offset, length);
     }
@@ -87,8 +87,10 @@
     private native int getFrequencyNative(long dict, int[] word, int wordLength);
     private native boolean isValidBigramNative(long dict, int[] word1, int[] word2);
     private native int getSuggestionsNative(long dict, long proximityInfo, int[] xCoordinates,
-            int[] yCoordinates, int[] inputCodes, int codesSize, int[] prevWordForBigrams,
-            boolean useFullEditDistance, char[] outputChars, int[] scores);
+            int[] yCoordinates, int[] times, int[] pointerIds, int[] inputCodes, int codesSize,
+            int commitPoint, boolean isGesture,
+            int[] prevWordCodePointArray, boolean useFullEditDistance, char[] outputChars,
+            int[] scores, int[] outputIndices);
     private native int getBigramsNative(long dict, int[] prevWord, int prevWordLength,
             int[] inputCodes, int inputCodesLength, char[] outputChars, int[] scores,
             int maxWordLength, int maxBigrams);
@@ -103,13 +105,23 @@
     }
 
     @Override
-    public void getBigrams(final WordComposer codes, final CharSequence previousWord,
-            final WordCallback callback) {
-        if (mNativeDict == 0) return;
+    public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
+            final CharSequence prevWord, final ProximityInfo proximityInfo) {
+        if (composer.size() <= 1) {
+            return TextUtils.isEmpty(prevWord) ? null : getBigramsInternal(composer, prevWord);
+        } else {
+            return getWordsInternal(composer, prevWord, proximityInfo);
+        }
+    }
+
+    // TODO: move to native code
+    private ArrayList<SuggestedWordInfo> getBigramsInternal(final WordComposer codes,
+            final CharSequence previousWord) {
+        if (!isValidDictionary()) return null;
 
         int[] codePoints = StringUtils.toCodePointArray(previousWord.toString());
-        Arrays.fill(mOutputChars_bigrams, (char) 0);
-        Arrays.fill(mBigramScores, 0);
+        Arrays.fill(mOutputChars, (char) 0);
+        Arrays.fill(mOutputScores, 0);
 
         int codesSize = codes.size();
         Arrays.fill(mInputCodes, -1);
@@ -118,44 +130,53 @@
         }
 
         int count = getBigramsNative(mNativeDict, codePoints, codePoints.length, mInputCodes,
-                codesSize, mOutputChars_bigrams, mBigramScores, MAX_WORD_LENGTH, MAX_BIGRAMS);
+                codesSize, mOutputChars, mOutputScores, MAX_WORD_LENGTH, MAX_BIGRAMS);
         if (count > MAX_BIGRAMS) {
             count = MAX_BIGRAMS;
         }
 
+        final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>();
         for (int j = 0; j < count; ++j) {
-            if (codesSize > 0 && mBigramScores[j] < 1) break;
+            if (codesSize > 0 && mOutputScores[j] < 1) break;
             final int start = j * MAX_WORD_LENGTH;
             int len = 0;
-            while (len <  MAX_WORD_LENGTH && mOutputChars_bigrams[start + len] != 0) {
+            while (len <  MAX_WORD_LENGTH && mOutputChars[start + len] != 0) {
                 ++len;
             }
             if (len > 0) {
-                callback.addWord(mOutputChars_bigrams, start, len, mBigramScores[j],
-                        mDicTypeId, Dictionary.BIGRAM);
+                suggestions.add(new SuggestedWordInfo(
+                        new String(mOutputChars, start, len),
+                        mOutputScores[j], SuggestedWordInfo.KIND_CORRECTION, mDictType));
             }
         }
+        return suggestions;
     }
 
+    // TODO: move to native code
     // proximityInfo and/or prevWordForBigrams may not be null.
-    @Override
-    public void getWords(final WordComposer codes, final CharSequence prevWordForBigrams,
-            final WordCallback callback, final ProximityInfo proximityInfo) {
-        final int count = getSuggestions(codes, prevWordForBigrams, proximityInfo, mOutputChars,
-                mScores);
+    private ArrayList<SuggestedWordInfo> getWordsInternal(final WordComposer codes,
+            final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) {
+        if (!isValidDictionary()) return null;
 
+        final int count = getSuggestions(codes, prevWordForBigrams, proximityInfo, mOutputChars,
+                mOutputScores, mSpaceIndices);
+
+        final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>();
         for (int j = 0; j < count; ++j) {
-            if (mScores[j] < 1) break;
+            if (mOutputScores[j] < 1) break;
             final int start = j * MAX_WORD_LENGTH;
             int len = 0;
             while (len < MAX_WORD_LENGTH && mOutputChars[start + len] != 0) {
                 ++len;
             }
             if (len > 0) {
-                callback.addWord(mOutputChars, start, len, mScores[j], mDicTypeId,
-                        Dictionary.UNIGRAM);
+                // TODO: actually get the kind from native code
+                suggestions.add(new SuggestedWordInfo(
+                        new String(mOutputChars, start, len),
+                        mOutputScores[j], SuggestedWordInfo.KIND_CORRECTION, mDictType));
             }
         }
+        return suggestions;
     }
 
     /* package for test */ boolean isValidDictionary() {
@@ -163,30 +184,35 @@
     }
 
     // proximityInfo may not be null.
-    /* package for test */ int getSuggestions(final WordComposer codes,
+    private int getSuggestions(final WordComposer codes,
             final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo,
-            char[] outputChars, int[] scores) {
-        if (!isValidDictionary()) return -1;
-
-        final int codesSize = codes.size();
-        // Won't deal with really long words.
-        if (codesSize > MAX_WORD_LENGTH - 1) return -1;
-
+            char[] outputChars, int[] scores, int[] spaceIndices) {
         Arrays.fill(mInputCodes, WordComposer.NOT_A_CODE);
-        for (int i = 0; i < codesSize; i++) {
-            mInputCodes[i] = codes.getCodeAt(i);
-        }
         Arrays.fill(outputChars, (char) 0);
         Arrays.fill(scores, 0);
 
-        final int[] prevWordCodePointArray = null == prevWordForBigrams
+        final InputPointers ips = codes.getInputPointers();
+        final boolean isGesture = codes.isBatchMode();
+        final int codesSize;
+        if (isGesture) {
+            codesSize = ips.getPointerSize();
+        } else {
+            codesSize = codes.size();
+            // Won't deal with really long words.
+            if (codesSize > MAX_WORD_LENGTH - 1) return -1;
+            for (int i = 0; i < codesSize; i++) {
+                mInputCodes[i] = codes.getCodeAt(i);
+            }
+        }
+
+        // TODO: toLowerCase in the native code
+        final int[] prevWordCodePointArray = (null == prevWordForBigrams)
                 ? null : StringUtils.toCodePointArray(prevWordForBigrams.toString());
 
-        // TODO: pass the previous word to native code
-        return getSuggestionsNative(
-                mNativeDict, proximityInfo.getNativeProximityInfo(),
-                codes.getXCoordinates(), codes.getYCoordinates(), mInputCodes, codesSize,
-                prevWordCodePointArray, mUseFullEditDistance, outputChars, scores);
+        return getSuggestionsNative(mNativeDict, proximityInfo.getNativeProximityInfo(),
+            ips.getXCoordinates(), ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(),
+            mInputCodes, codesSize, 0 /* unused */, isGesture, prevWordCodePointArray,
+            mUseFullEditDistance, outputChars, scores, spaceIndices);
     }
 
     public static float calcNormalizedScore(String before, String after, int score) {
diff --git a/java/src/com/android/inputmethod/latin/BoundedTreeSet.java b/java/src/com/android/inputmethod/latin/BoundedTreeSet.java
new file mode 100644
index 0000000..cf97761
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/BoundedTreeSet.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.inputmethod.latin;
+
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.TreeSet;
+
+/**
+ * A TreeSet that is bounded in size and throws everything that's smaller than its limit
+ */
+public class BoundedTreeSet extends TreeSet<SuggestedWordInfo> {
+    private final int mCapacity;
+    public BoundedTreeSet(final Comparator<SuggestedWordInfo> comparator, final int capacity) {
+        super(comparator);
+        mCapacity = capacity;
+    }
+
+    @Override
+    public boolean add(final SuggestedWordInfo e) {
+        if (size() < mCapacity) return super.add(e);
+        if (comparator().compare(e, last()) > 0) return false;
+        super.add(e);
+        pollLast(); // removes the last element
+        return true;
+    }
+
+    @Override
+    public boolean addAll(final Collection<? extends SuggestedWordInfo> e) {
+        if (null == e) return false;
+        return super.addAll(e);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index 34308df..5edc431 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -52,6 +52,9 @@
     /** The number of contacts in the most recent dictionary rebuild. */
     static private int sContactCountAtLastRebuild = 0;
 
+    /** The locale for this contacts dictionary. Controls name bigram predictions. */
+    public final Locale mLocale;
+
     private ContentObserver mObserver;
 
     /**
@@ -59,8 +62,9 @@
      */
     private final boolean mUseFirstLastBigrams;
 
-    public ContactsBinaryDictionary(final Context context, final int dicTypeId, Locale locale) {
-        super(context, getFilenameWithLocale(NAME, locale.toString()), dicTypeId);
+    public ContactsBinaryDictionary(final Context context, Locale locale) {
+        super(context, getFilenameWithLocale(NAME, locale.toString()), Dictionary.TYPE_CONTACTS);
+        mLocale = locale;
         mUseFirstLastBigrams = useFirstLastBigramsForLocale(locale);
         registerObserver(context);
 
@@ -116,12 +120,6 @@
         }
     }
 
-    @Override
-    public void getBigrams(final WordComposer codes, final CharSequence previousWord,
-            final WordCallback callback) {
-        super.getBigrams(codes, previousWord, callback);
-    }
-
     private boolean useFirstLastBigramsForLocale(Locale locale) {
         // TODO: Add firstname/lastname bigram rules for other languages.
         if (locale != null && locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) {
@@ -163,7 +161,7 @@
      * bigrams depending on locale.
      */
     private void addName(String name) {
-        int len = name.codePointCount(0, name.length());
+        int len = StringUtils.codePointCount(name);
         String prevWord = null;
         // TODO: Better tokenization for non-Latin writing systems
         for (int i = 0; i < len; i++) {
@@ -173,7 +171,7 @@
                 i = end - 1;
                 // Don't add single letter words, possibly confuses
                 // capitalization of i.
-                final int wordLen = word.codePointCount(0, word.length());
+                final int wordLen = StringUtils.codePointCount(word);
                 if (wordLen < MAX_WORD_LENGTH && wordLen > 1) {
                     super.addWord(word, null /* shortcut */, FREQUENCY_FOR_CONTACTS);
                     if (!TextUtils.isEmpty(prevWord)) {
@@ -262,14 +260,14 @@
      * Checks if the words in a name are in the current binary dictionary.
      */
     private boolean isNameInDictionary(String name) {
-        int len = name.codePointCount(0, name.length());
+        int len = StringUtils.codePointCount(name);
         String prevWord = null;
         for (int i = 0; i < len; i++) {
             if (Character.isLetter(name.codePointAt(i))) {
                 int end = getWordEndPosition(name, len, i);
                 String word = name.substring(i, end);
                 i = end - 1;
-                final int wordLen = word.codePointCount(0, word.length());
+                final int wordLen = StringUtils.codePointCount(word);
                 if (wordLen < MAX_WORD_LENGTH && wordLen > 1) {
                     if (!TextUtils.isEmpty(prevWord) && mUseFirstLastBigrams) {
                         if (!super.isValidBigramLocked(prevWord, word)) {
diff --git a/java/src/com/android/inputmethod/latin/ContactsDictionary.java b/java/src/com/android/inputmethod/latin/ContactsDictionary.java
deleted file mode 100644
index cbfbd0e..0000000
--- a/java/src/com/android/inputmethod/latin/ContactsDictionary.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2009 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 android.content.ContentResolver;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.os.SystemClock;
-import android.provider.BaseColumns;
-import android.provider.ContactsContract.Contacts;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.inputmethod.keyboard.Keyboard;
-
-// TODO: This class is superseded by {@link ContactsBinaryDictionary}. Should be cleaned up.
-/**
- * An expandable dictionary that stores the words from Contacts provider.
- *
- * @deprecated Use {@link ContactsBinaryDictionary}.
- */
-@Deprecated
-public class ContactsDictionary extends ExpandableDictionary {
-
-    private static final String[] PROJECTION = {
-        BaseColumns._ID,
-        Contacts.DISPLAY_NAME,
-    };
-
-    private static final String TAG = "ContactsDictionary";
-
-    /**
-     * Frequency for contacts information into the dictionary
-     */
-    private static final int FREQUENCY_FOR_CONTACTS = 40;
-    private static final int FREQUENCY_FOR_CONTACTS_BIGRAM = 90;
-
-    private static final int INDEX_NAME = 1;
-
-    private ContentObserver mObserver;
-
-    private long mLastLoadedContacts;
-
-    public ContactsDictionary(final Context context, final int dicTypeId) {
-        super(context, dicTypeId);
-        registerObserver(context);
-        loadDictionary();
-    }
-
-    private synchronized void registerObserver(final Context context) {
-        // Perform a managed query. The Activity will handle closing and requerying the cursor
-        // when needed.
-        if (mObserver != null) return;
-        ContentResolver cres = context.getContentResolver();
-        cres.registerContentObserver(
-                Contacts.CONTENT_URI, true, mObserver = new ContentObserver(null) {
-                    @Override
-                    public void onChange(boolean self) {
-                        setRequiresReload(true);
-                    }
-                });
-    }
-
-    public void reopen(final Context context) {
-        registerObserver(context);
-    }
-
-    @Override
-    public synchronized void close() {
-        if (mObserver != null) {
-            getContext().getContentResolver().unregisterContentObserver(mObserver);
-            mObserver = null;
-        }
-        super.close();
-    }
-
-    @Override
-    public void startDictionaryLoadingTaskLocked() {
-        long now = SystemClock.uptimeMillis();
-        if (mLastLoadedContacts == 0
-                || now - mLastLoadedContacts > 30 * 60 * 1000 /* 30 minutes */) {
-            super.startDictionaryLoadingTaskLocked();
-        }
-    }
-
-    @Override
-    public void loadDictionaryAsync() {
-        try {
-            Cursor cursor = getContext().getContentResolver()
-                    .query(Contacts.CONTENT_URI, PROJECTION, null, null, null);
-            if (cursor != null) {
-                addWords(cursor);
-            }
-        } catch(IllegalStateException e) {
-            Log.e(TAG, "Contacts DB is having problems");
-        }
-        mLastLoadedContacts = SystemClock.uptimeMillis();
-    }
-
-    @Override
-    public void getBigrams(final WordComposer codes, final CharSequence previousWord,
-            final WordCallback callback) {
-        // Do not return bigrams from Contacts when nothing was typed.
-        if (codes.size() <= 0) return;
-        super.getBigrams(codes, previousWord, callback);
-    }
-
-    private void addWords(Cursor cursor) {
-        clearDictionary();
-
-        final int maxWordLength = getMaxWordLength();
-        try {
-            if (cursor.moveToFirst()) {
-                while (!cursor.isAfterLast()) {
-                    String name = cursor.getString(INDEX_NAME);
-
-                    if (name != null && -1 == name.indexOf('@')) {
-                        int len = name.length();
-                        String prevWord = null;
-
-                        // TODO: Better tokenization for non-Latin writing systems
-                        for (int i = 0; i < len; i++) {
-                            if (Character.isLetter(name.charAt(i))) {
-                                int j;
-                                for (j = i + 1; j < len; j++) {
-                                    char c = name.charAt(j);
-
-                                    if (!(c == Keyboard.CODE_DASH
-                                            || c == Keyboard.CODE_SINGLE_QUOTE
-                                            || Character.isLetter(c))) {
-                                        break;
-                                    }
-                                }
-
-                                String word = name.substring(i, j);
-                                i = j - 1;
-
-                                // Safeguard against adding really long words. Stack
-                                // may overflow due to recursion
-                                // Also don't add single letter words, possibly confuses
-                                // capitalization of i.
-                                final int wordLen = word.length();
-                                if (wordLen < maxWordLength && wordLen > 1) {
-                                    super.addWord(word, null /* shortcut */,
-                                            FREQUENCY_FOR_CONTACTS);
-                                    if (!TextUtils.isEmpty(prevWord)) {
-                                        super.setBigramAndGetFrequency(prevWord, word,
-                                                FREQUENCY_FOR_CONTACTS_BIGRAM);
-                                    }
-                                    prevWord = word;
-                                }
-                            }
-                        }
-                    }
-                    cursor.moveToNext();
-                }
-            }
-            cursor.close();
-        } catch(IllegalStateException e) {
-            Log.e(TAG, "Contacts DB is having problems");
-        }
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index 7cd9bc2..fd40aa6 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -16,7 +16,12 @@
 
 package com.android.inputmethod.latin;
 
+import android.text.TextUtils;
+
 import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+
+import java.util.ArrayList;
 
 /**
  * Abstract base class for a dictionary that can do a fuzzy search for words based on a set of key
@@ -28,55 +33,36 @@
      */
     protected static final int FULL_WORD_SCORE_MULTIPLIER = 2;
 
-    public static final int UNIGRAM = 0;
-    public static final int BIGRAM = 1;
-
     public static final int NOT_A_PROBABILITY = -1;
-    /**
-     * Interface to be implemented by classes requesting words to be fetched from the dictionary.
-     * @see #getWords(WordComposer, CharSequence, WordCallback, ProximityInfo)
-     */
-    public interface WordCallback {
-        /**
-         * Adds a word to a list of suggestions. The word is expected to be ordered based on
-         * the provided score.
-         * @param word the character array containing the word
-         * @param wordOffset starting offset of the word in the character array
-         * @param wordLength length of valid characters in the character array
-         * @param score the score of occurrence. This is normalized between 1 and 255, but
-         * can exceed those limits
-         * @param dicTypeId of the dictionary where word was from
-         * @param dataType tells type of this data, either UNIGRAM or BIGRAM
-         * @return true if the word was added, false if no more words are required
-         */
-        boolean addWord(char[] word, int wordOffset, int wordLength, int score, int dicTypeId,
-                int dataType);
+
+    public static final String TYPE_USER_TYPED = "user_typed";
+    public static final String TYPE_APPLICATION_DEFINED = "application_defined";
+    public static final String TYPE_HARDCODED = "hardcoded"; // punctuation signs and such
+    public static final String TYPE_MAIN = "main";
+    public static final String TYPE_CONTACTS = "contacts";
+    // User dictionary, the system-managed one.
+    public static final String TYPE_USER = "user";
+    // User history dictionary internal to LatinIME.
+    public static final String TYPE_USER_HISTORY = "history";
+    public static final String TYPE_WHITELIST ="whitelist";
+    protected final String mDictType;
+
+    public Dictionary(final String dictType) {
+        mDictType = dictType;
     }
 
     /**
-     * Searches for words in the dictionary that match the characters in the composer. Matched
-     * words are added through the callback object.
-     * @param composer the key sequence to match
-     * @param prevWordForBigrams the previous word, or null if none
-     * @param callback the callback object to send matched words to as possible candidates
+     * Searches for suggestions for a given context. For the moment the context is only the
+     * previous word.
+     * @param composer the key sequence to match with coordinate info, as a WordComposer
+     * @param prevWord the previous word, or null if none
      * @param proximityInfo the object for key proximity. May be ignored by some implementations.
-     * @see WordCallback#addWord(char[], int, int, int, int, int)
+     * @return the list of suggestions (possibly null if none)
      */
-    abstract public void getWords(final WordComposer composer,
-            final CharSequence prevWordForBigrams, final WordCallback callback,
-            final ProximityInfo proximityInfo);
-
-    /**
-     * Searches for pairs in the bigram dictionary that matches the previous word and all the
-     * possible words following are added through the callback object.
-     * @param composer the key sequence to match
-     * @param previousWord the word before
-     * @param callback the callback object to send possible word following previous word
-     */
-    public void getBigrams(final WordComposer composer, final CharSequence previousWord,
-            final WordCallback callback) {
-        // empty base implementation
-    }
+    // TODO: pass more context than just the previous word, to enable better suggestions (n-gram
+    // and more)
+    abstract public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
+            final CharSequence prevWord, final ProximityInfo proximityInfo);
 
     /**
      * Checks if the given word occurs in the dictionary
@@ -115,4 +101,12 @@
     public void close() {
         // empty base implementation
     }
+
+    /**
+     * Subclasses may override to indicate that this Dictionary is not yet properly initialized.
+     */
+
+    public boolean isInitialized() {
+        return true;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
index 1a05fcd..88ac07d 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -17,9 +17,11 @@
 package com.android.inputmethod.latin;
 
 import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 
 import android.util.Log;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -31,11 +33,13 @@
     private final String TAG = DictionaryCollection.class.getSimpleName();
     protected final CopyOnWriteArrayList<Dictionary> mDictionaries;
 
-    public DictionaryCollection() {
+    public DictionaryCollection(final String dictType) {
+        super(dictType);
         mDictionaries = new CopyOnWriteArrayList<Dictionary>();
     }
 
-    public DictionaryCollection(Dictionary... dictionaries) {
+    public DictionaryCollection(final String dictType, Dictionary... dictionaries) {
+        super(dictType);
         if (null == dictionaries) {
             mDictionaries = new CopyOnWriteArrayList<Dictionary>();
         } else {
@@ -44,23 +48,29 @@
         }
     }
 
-    public DictionaryCollection(Collection<Dictionary> dictionaries) {
+    public DictionaryCollection(final String dictType, Collection<Dictionary> dictionaries) {
+        super(dictType);
         mDictionaries = new CopyOnWriteArrayList<Dictionary>(dictionaries);
         mDictionaries.removeAll(Collections.singleton(null));
     }
 
     @Override
-    public void getWords(final WordComposer composer, final CharSequence prevWordForBigrams,
-            final WordCallback callback, final ProximityInfo proximityInfo) {
-        for (final Dictionary dict : mDictionaries)
-            dict.getWords(composer, prevWordForBigrams, callback, proximityInfo);
-    }
-
-    @Override
-    public void getBigrams(final WordComposer composer, final CharSequence previousWord,
-            final WordCallback callback) {
-        for (final Dictionary dict : mDictionaries)
-            dict.getBigrams(composer, previousWord, callback);
+    public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
+            final CharSequence prevWord, final ProximityInfo proximityInfo) {
+        final CopyOnWriteArrayList<Dictionary> dictionaries = mDictionaries;
+        if (dictionaries.isEmpty()) return null;
+        // To avoid creating unnecessary objects, we get the list out of the first
+        // dictionary and add the rest to it if not null, hence the get(0)
+        ArrayList<SuggestedWordInfo> suggestions = dictionaries.get(0).getSuggestions(composer,
+                prevWord, proximityInfo);
+        if (null == suggestions) suggestions = new ArrayList<SuggestedWordInfo>();
+        final int length = dictionaries.size();
+        for (int i = 0; i < length; ++ i) {
+            final ArrayList<SuggestedWordInfo> sugg = dictionaries.get(i).getSuggestions(composer,
+                    prevWord, proximityInfo);
+            if (null != sugg) suggestions.addAll(sugg);
+        }
+        return suggestions;
     }
 
     @Override
@@ -82,8 +92,9 @@
         return maxFreq;
     }
 
-    public boolean isEmpty() {
-        return mDictionaries.isEmpty();
+    @Override
+    public boolean isInitialized() {
+        return !mDictionaries.isEmpty();
     }
 
     @Override
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index a22d73a..06a5f4b 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -49,7 +49,8 @@
             final Locale locale, final boolean useFullEditDistance) {
         if (null == locale) {
             Log.e(TAG, "No locale defined for dictionary");
-            return new DictionaryCollection(createBinaryDictionary(context, locale));
+            return new DictionaryCollection(Dictionary.TYPE_MAIN,
+                    createBinaryDictionary(context, locale));
         }
 
         final LinkedList<Dictionary> dictList = new LinkedList<Dictionary>();
@@ -59,7 +60,7 @@
             for (final AssetFileAddress f : assetFileList) {
                 final BinaryDictionary binaryDictionary =
                         new BinaryDictionary(context, f.mFilename, f.mOffset, f.mLength,
-                                useFullEditDistance, locale);
+                                useFullEditDistance, locale, Dictionary.TYPE_MAIN);
                 if (binaryDictionary.isValidDictionary()) {
                     dictList.add(binaryDictionary);
                 }
@@ -69,7 +70,7 @@
         // If the list is empty, that means we should not use any dictionary (for example, the user
         // explicitly disabled the main dictionary), so the following is okay. dictList is never
         // null, but if for some reason it is, DictionaryCollection handles it gracefully.
-        return new DictionaryCollection(dictList);
+        return new DictionaryCollection(Dictionary.TYPE_MAIN, dictList);
     }
 
     /**
@@ -112,7 +113,7 @@
                 return null;
             }
             return new BinaryDictionary(context, sourceDir, afd.getStartOffset(), afd.getLength(),
-                    false /* useFullEditDistance */, locale);
+                    false /* useFullEditDistance */, locale, Dictionary.TYPE_MAIN);
         } catch (android.content.res.Resources.NotFoundException e) {
             Log.e(TAG, "Could not find the resource");
             return null;
@@ -140,7 +141,7 @@
             long startOffset, long length, final boolean useFullEditDistance, Locale locale) {
         if (dictionary.isFile()) {
             return new BinaryDictionary(context, dictionary.getAbsolutePath(), startOffset, length,
-                    useFullEditDistance, locale);
+                    useFullEditDistance, locale, Dictionary.TYPE_MAIN);
         } else {
             Log.e(TAG, "Could not find the file. path=" + dictionary.getAbsolutePath());
             return null;
diff --git a/java/src/com/android/inputmethod/latin/EditingUtils.java b/java/src/com/android/inputmethod/latin/EditingUtils.java
deleted file mode 100644
index 0f34d50..0000000
--- a/java/src/com/android/inputmethod/latin/EditingUtils.java
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright (C) 2009 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 android.view.inputmethod.ExtractedText;
-import android.view.inputmethod.ExtractedTextRequest;
-import android.view.inputmethod.InputConnection;
-
-import java.util.regex.Pattern;
-
-/**
- * Utility methods to deal with editing text through an InputConnection.
- */
-public class EditingUtils {
-    /**
-     * Number of characters we want to look back in order to identify the previous word
-     */
-    // Provision for a long word pair and a separator
-    private static final int LOOKBACK_CHARACTER_NUM = BinaryDictionary.MAX_WORD_LENGTH * 2 + 1;
-    private static final int INVALID_CURSOR_POSITION = -1;
-
-    private EditingUtils() {
-        // Unintentional empty constructor for singleton.
-    }
-
-    private static int getCursorPosition(InputConnection connection) {
-        if (null == connection) return INVALID_CURSOR_POSITION;
-        final ExtractedText extracted = connection.getExtractedText(new ExtractedTextRequest(), 0);
-        if (extracted == null) {
-            return INVALID_CURSOR_POSITION;
-        }
-        return extracted.startOffset + extracted.selectionStart;
-    }
-
-    /**
-     * @param connection connection to the current text field.
-     * @param separators characters which may separate words
-     * @return the word that surrounds the cursor, including up to one trailing
-     *   separator. For example, if the field contains "he|llo world", where |
-     *   represents the cursor, then "hello " will be returned.
-     */
-    public static String getWordAtCursor(InputConnection connection, String separators) {
-        // getWordRangeAtCursor returns null if the connection is null
-        Range r = getWordRangeAtCursor(connection, separators);
-        return (r == null) ? null : r.mWord;
-    }
-
-    /**
-     * Represents a range of text, relative to the current cursor position.
-     */
-    public static class Range {
-        /** Characters before selection start */
-        public final int mCharsBefore;
-
-        /**
-         * Characters after selection start, including one trailing word
-         * separator.
-         */
-        public final int mCharsAfter;
-
-        /** The actual characters that make up a word */
-        public final String mWord;
-
-        public Range(int charsBefore, int charsAfter, String word) {
-            if (charsBefore < 0 || charsAfter < 0) {
-                throw new IndexOutOfBoundsException();
-            }
-            this.mCharsBefore = charsBefore;
-            this.mCharsAfter = charsAfter;
-            this.mWord = word;
-        }
-    }
-
-    private static Range getWordRangeAtCursor(InputConnection connection, String sep) {
-        if (connection == null || sep == null) {
-            return null;
-        }
-        CharSequence before = connection.getTextBeforeCursor(1000, 0);
-        CharSequence after = connection.getTextAfterCursor(1000, 0);
-        if (before == null || after == null) {
-            return null;
-        }
-
-        // Find first word separator before the cursor
-        int start = before.length();
-        while (start > 0 && !isWhitespace(before.charAt(start - 1), sep)) start--;
-
-        // Find last word separator after the cursor
-        int end = -1;
-        while (++end < after.length() && !isWhitespace(after.charAt(end), sep)) {
-            // Nothing to do here.
-        }
-
-        int cursor = getCursorPosition(connection);
-        if (start >= 0 && cursor + end <= after.length() + before.length()) {
-            String word = before.toString().substring(start, before.length())
-                    + after.toString().substring(0, end);
-            return new Range(before.length() - start, end, word);
-        }
-
-        return null;
-    }
-
-    private static boolean isWhitespace(int code, String whitespace) {
-        return whitespace.contains(String.valueOf((char) code));
-    }
-
-    private static final Pattern spaceRegex = Pattern.compile("\\s+");
-
-    public static CharSequence getPreviousWord(InputConnection connection,
-            String sentenceSeperators) {
-        //TODO: Should fix this. This could be slow!
-        if (null == connection) return null;
-        CharSequence prev = connection.getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0);
-        return getPreviousWord(prev, sentenceSeperators);
-    }
-
-    // Get the word before the whitespace preceding the non-whitespace preceding the cursor.
-    // Also, it won't return words that end in a separator.
-    // Example :
-    // "abc def|" -> abc
-    // "abc def |" -> abc
-    // "abc def. |" -> abc
-    // "abc def . |" -> def
-    // "abc|" -> null
-    // "abc |" -> null
-    // "abc. def|" -> null
-    public static CharSequence getPreviousWord(CharSequence prev, String sentenceSeperators) {
-        if (prev == null) return null;
-        String[] w = spaceRegex.split(prev);
-
-        // If we can't find two words, or we found an empty word, return null.
-        if (w.length < 2 || w[w.length - 2].length() <= 0) return null;
-
-        // If ends in a separator, return null
-        char lastChar = w[w.length - 2].charAt(w[w.length - 2].length() - 1);
-        if (sentenceSeperators.contains(String.valueOf(lastChar))) return null;
-
-        return w[w.length - 2];
-    }
-
-    public static CharSequence getThisWord(InputConnection connection, String sentenceSeperators) {
-        if (null == connection) return null;
-        final CharSequence prev = connection.getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0);
-        return getThisWord(prev, sentenceSeperators);
-    }
-
-    // Get the word immediately before the cursor, even if there is whitespace between it and
-    // the cursor - but not if there is punctuation.
-    // Example :
-    // "abc def|" -> def
-    // "abc def |" -> def
-    // "abc def. |" -> null
-    // "abc def . |" -> null
-    public static CharSequence getThisWord(CharSequence prev, String sentenceSeperators) {
-        if (prev == null) return null;
-        String[] w = spaceRegex.split(prev);
-
-        // No word : return null
-        if (w.length < 1 || w[w.length - 1].length() <= 0) return null;
-
-        // If ends in a separator, return null
-        char lastChar = w[w.length - 1].charAt(w[w.length - 1].length() - 1);
-        if (sentenceSeperators.contains(String.valueOf(lastChar))) return null;
-
-        return w[w.length - 1];
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index c65404c..016530a 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -19,6 +19,7 @@
 import android.util.Log;
 
 import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
 import com.android.inputmethod.latin.makedict.FusionDictionary;
 import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
@@ -75,9 +76,6 @@
     /** The expandable fusion dictionary used to generate the binary dictionary. */
     private FusionDictionary mFusionDictionary;
 
-    /** The dictionary type id. */
-    public final int mDicTypeId;
-
     /**
      * The name of this dictionary, used as the filename for storing the binary dictionary. Multiple
      * dictionary instances with the same filename is supported, with access controlled by
@@ -123,11 +121,11 @@
      * @param context The application context of the parent.
      * @param filename The filename for this binary dictionary. Multiple dictionaries with the same
      *        filename is supported.
-     * @param dictType The type of this dictionary.
+     * @param dictType the dictionary type, as a human-readable string
      */
     public ExpandableBinaryDictionary(
-            final Context context, final String filename, final int dictType) {
-        mDicTypeId = dictType;
+            final Context context, final String filename, final String dictType) {
+        super(dictType);
         mFilename = filename;
         mContext = context;
         mBinaryDictionary = null;
@@ -194,46 +192,19 @@
     }
 
     @Override
-    public void getWords(final WordComposer codes, final CharSequence prevWordForBigrams,
-            final WordCallback callback, final ProximityInfo proximityInfo) {
+    public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
+            final CharSequence prevWord, final ProximityInfo proximityInfo) {
         asyncReloadDictionaryIfRequired();
-        getWordsInner(codes, prevWordForBigrams, callback, proximityInfo);
-    }
-
-    protected final void getWordsInner(final WordComposer codes,
-            final CharSequence prevWordForBigrams, final WordCallback callback,
-            final ProximityInfo proximityInfo) {
-        // Ensure that there are no concurrent calls to getWords. If there are, do nothing and
-        // return.
         if (mLocalDictionaryController.tryLock()) {
             try {
                 if (mBinaryDictionary != null) {
-                    mBinaryDictionary.getWords(codes, prevWordForBigrams, callback, proximityInfo);
+                    return mBinaryDictionary.getSuggestions(composer, prevWord, proximityInfo);
                 }
             } finally {
                 mLocalDictionaryController.unlock();
             }
         }
-    }
-
-    @Override
-    public void getBigrams(final WordComposer codes, final CharSequence previousWord,
-            final WordCallback callback) {
-        asyncReloadDictionaryIfRequired();
-        getBigramsInner(codes, previousWord, callback);
-    }
-
-    protected void getBigramsInner(final WordComposer codes, final CharSequence previousWord,
-            final WordCallback callback) {
-        if (mLocalDictionaryController.tryLock()) {
-            try {
-                if (mBinaryDictionary != null) {
-                    mBinaryDictionary.getBigrams(codes, previousWord, callback);
-                }
-            } finally {
-                mLocalDictionaryController.unlock();
-            }
-        }
+        return null;
     }
 
     @Override
@@ -306,7 +277,7 @@
         // Build the new binary dictionary
         final BinaryDictionary newBinaryDictionary =
                 new BinaryDictionary(mContext, filename, 0, length, true /* useFullEditDistance */,
-                        null);
+                        null, mDictType);
 
         if (mBinaryDictionary != null) {
             // Ensure all threads accessing the current dictionary have finished before swapping in
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index 34a92fd..5d7995d 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -17,10 +17,12 @@
 package com.android.inputmethod.latin;
 
 import android.content.Context;
+import android.text.TextUtils;
 
 import com.android.inputmethod.keyboard.KeyDetector;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.UserHistoryForgettingCurveUtils.ForgettingCurveParams;
 
 import java.util.ArrayList;
@@ -37,7 +39,6 @@
 
     private Context mContext;
     private char[] mWordBuilder = new char[BinaryDictionary.MAX_WORD_LENGTH];
-    private int mDicTypeId;
     private int mMaxDepth;
     private int mInputLength;
 
@@ -151,11 +152,11 @@
 
     private int[][] mCodes;
 
-    public ExpandableDictionary(Context context, int dicTypeId) {
+    public ExpandableDictionary(final Context context, final String dictType) {
+        super(dictType);
         mContext = context;
         clearDictionary();
         mCodes = new int[BinaryDictionary.MAX_WORD_LENGTH][];
-        mDicTypeId = dicTypeId;
     }
 
     public void loadDictionary() {
@@ -247,27 +248,43 @@
     }
 
     @Override
-    public void getWords(final WordComposer codes, final CharSequence prevWordForBigrams,
-            final WordCallback callback, final ProximityInfo proximityInfo) {
+    public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
+            final CharSequence prevWord, final ProximityInfo proximityInfo) {
+        if (reloadDictionaryIfRequired()) return null;
+        if (composer.size() <= 1) {
+            if (composer.size() >= BinaryDictionary.MAX_WORD_LENGTH) {
+                return null;
+            }
+            final ArrayList<SuggestedWordInfo> suggestions =
+                    getWordsInner(composer, prevWord, proximityInfo);
+            return suggestions;
+        } else {
+            if (TextUtils.isEmpty(prevWord)) return null;
+            final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>();
+            runBigramReverseLookUp(prevWord, suggestions);
+            return suggestions;
+        }
+    }
+
+    // This reloads the dictionary if required, and returns whether it's currently updating its
+    // contents or not.
+    // @VisibleForTesting
+    boolean reloadDictionaryIfRequired() {
         synchronized (mUpdatingLock) {
             // If we need to update, start off a background task
             if (mRequiresReload) startDictionaryLoadingTaskLocked();
-            // Currently updating contacts, don't return any results.
-            if (mUpdatingDictionary) return;
+            return mUpdatingDictionary;
         }
-        if (codes.size() >= BinaryDictionary.MAX_WORD_LENGTH) {
-            return;
-        }
-        getWordsInner(codes, prevWordForBigrams, callback, proximityInfo);
     }
 
-    protected final void getWordsInner(final WordComposer codes,
-            final CharSequence prevWordForBigrams, final WordCallback callback,
-            final ProximityInfo proximityInfo) {
+    protected ArrayList<SuggestedWordInfo> getWordsInner(final WordComposer codes,
+            final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) {
+        final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>();
         mInputLength = codes.size();
         if (mCodes.length < mInputLength) mCodes = new int[mInputLength][];
-        final int[] xCoordinates = codes.getXCoordinates();
-        final int[] yCoordinates = codes.getYCoordinates();
+        final InputPointers ips = codes.getInputPointers();
+        final int[] xCoordinates = ips.getXCoordinates();
+        final int[] yCoordinates = ips.getYCoordinates();
         // Cache the codes so that we don't have to lookup an array list
         for (int i = 0; i < mInputLength; i++) {
             // TODO: Calculate proximity info here.
@@ -281,10 +298,11 @@
             proximityInfo.fillArrayWithNearestKeyCodes(x, y, codes.getCodeAt(i), mCodes[i]);
         }
         mMaxDepth = mInputLength * 3;
-        getWordsRec(mRoots, codes, mWordBuilder, 0, false, 1, 0, -1, callback);
+        getWordsRec(mRoots, codes, mWordBuilder, 0, false, 1, 0, -1, suggestions);
         for (int i = 0; i < mInputLength; i++) {
-            getWordsRec(mRoots, codes, mWordBuilder, 0, false, 1, 0, i, callback);
+            getWordsRec(mRoots, codes, mWordBuilder, 0, false, 1, 0, i, suggestions);
         }
+        return suggestions;
     }
 
     @Override
@@ -368,24 +386,27 @@
      * @param word the word to insert, as an array of code points
      * @param depth the depth of the node in the tree
      * @param finalFreq the frequency for this word
+     * @param suggestions the suggestion collection to add the suggestions to
      * @return whether there is still space for more words.
-     * @see Dictionary.WordCallback#addWord(char[], int, int, int, int, int)
      */
     private boolean addWordAndShortcutsFromNode(final Node node, final char[] word, final int depth,
-            final int finalFreq, final WordCallback callback) {
+            final int finalFreq, final ArrayList<SuggestedWordInfo> suggestions) {
         if (finalFreq > 0 && !node.mShortcutOnly) {
-            if (!callback.addWord(word, 0, depth + 1, finalFreq, mDicTypeId, Dictionary.UNIGRAM)) {
-                return false;
-            }
+            // Use KIND_CORRECTION always. This dictionary does not really have a notion of
+            // COMPLETION against CORRECTION; we could artificially add one by looking at
+            // the respective size of the typed word and the suggestion if it matters sometime
+            // in the future.
+            suggestions.add(new SuggestedWordInfo(new String(word, 0, depth + 1), finalFreq,
+                    SuggestedWordInfo.KIND_CORRECTION, mDictType));
+            if (suggestions.size() >= Suggest.MAX_SUGGESTIONS) return false;
         }
         if (null != node.mShortcutTargets) {
             final int length = node.mShortcutTargets.size();
             for (int shortcutIndex = 0; shortcutIndex < length; ++shortcutIndex) {
                 final char[] shortcut = node.mShortcutTargets.get(shortcutIndex);
-                if (!callback.addWord(shortcut, 0, shortcut.length, finalFreq, mDicTypeId,
-                        Dictionary.UNIGRAM)) {
-                    return false;
-                }
+                suggestions.add(new SuggestedWordInfo(new String(shortcut, 0, shortcut.length),
+                        finalFreq, SuggestedWordInfo.KIND_SHORTCUT, mDictType));
+                if (suggestions.size() > Suggest.MAX_SUGGESTIONS) return false;
             }
         }
         return true;
@@ -408,12 +429,12 @@
      * case we skip over some punctuations such as apostrophe in the traversal. That is, if you type
      * "wouldve", it could be matching "would've", so the depth will be one more than the
      * inputIndex
-     * @param callback the callback class for adding a word
+     * @param suggestions the list in which to add suggestions
      */
     // TODO: Share this routine with the native code for BinaryDictionary
     protected void getWordsRec(NodeArray roots, final WordComposer codes, final char[] word,
             final int depth, final boolean completion, int snr, int inputIndex, int skipPos,
-            WordCallback callback) {
+            final ArrayList<SuggestedWordInfo> suggestions) {
         final int count = roots.mLength;
         final int codeSize = mInputLength;
         // Optimization: Prune out words that are too long compared to how much was typed.
@@ -443,14 +464,14 @@
                     } else {
                         finalFreq = computeSkippedWordFinalFreq(freq, snr, mInputLength);
                     }
-                    if (!addWordAndShortcutsFromNode(node, word, depth, finalFreq, callback)) {
+                    if (!addWordAndShortcutsFromNode(node, word, depth, finalFreq, suggestions)) {
                         // No space left in the queue, bail out
                         return;
                     }
                 }
                 if (children != null) {
                     getWordsRec(children, codes, word, depth + 1, true, snr, inputIndex,
-                            skipPos, callback);
+                            skipPos, suggestions);
                 }
             } else if ((c == Keyboard.CODE_SINGLE_QUOTE
                     && currentChars[0] != Keyboard.CODE_SINGLE_QUOTE) || depth == skipPos) {
@@ -458,7 +479,7 @@
                 word[depth] = c;
                 if (children != null) {
                     getWordsRec(children, codes, word, depth + 1, completion, snr, inputIndex,
-                            skipPos, callback);
+                            skipPos, suggestions);
                 }
             } else {
                 // Don't use alternatives if we're looking for missing characters
@@ -483,7 +504,7 @@
                                             snr * addedAttenuation, mInputLength);
                                 }
                                 if (!addWordAndShortcutsFromNode(node, word, depth, finalFreq,
-                                        callback)) {
+                                        suggestions)) {
                                     // No space left in the queue, bail out
                                     return;
                                 }
@@ -491,12 +512,12 @@
                             if (children != null) {
                                 getWordsRec(children, codes, word, depth + 1,
                                         true, snr * addedAttenuation, inputIndex + 1,
-                                        skipPos, callback);
+                                        skipPos, suggestions);
                             }
                         } else if (children != null) {
                             getWordsRec(children, codes, word, depth + 1,
                                     false, snr * addedAttenuation, inputIndex + 1,
-                                    skipPos, callback);
+                                    skipPos, suggestions);
                         }
                     }
                 }
@@ -514,8 +535,10 @@
 
     /**
      * Adds bigrams to the in-memory trie structure that is being used to retrieve any word
+     * @param word1 the first word of this bigram
+     * @param word2 the second word of this bigram
      * @param frequency frequency for this bigram
-     * @param addFrequency if true, it adds to current frequency, else it overwrites the old value
+     * @param fcp an instance of ForgettingCurveParams to use for decay policy
      * @return returns the final bigram frequency
      */
     private int setBigramAndGetFrequency(
@@ -580,32 +603,14 @@
         return searchWord(childNode.mChildren, word, depth + 1, childNode);
     }
 
-    // @VisibleForTesting
-    boolean reloadDictionaryIfRequired() {
-        synchronized (mUpdatingLock) {
-            // If we need to update, start off a background task
-            if (mRequiresReload) startDictionaryLoadingTaskLocked();
-            // Currently updating contacts, don't return any results.
-            return mUpdatingDictionary;
-        }
-    }
-
     private void runBigramReverseLookUp(final CharSequence previousWord,
-            final WordCallback callback) {
+            final ArrayList<SuggestedWordInfo> suggestions) {
         // Search for the lowercase version of the word only, because that's where bigrams
         // store their sons.
         Node prevWord = searchNode(mRoots, previousWord.toString().toLowerCase(), 0,
                 previousWord.length());
         if (prevWord != null && prevWord.mNGrams != null) {
-            reverseLookUp(prevWord.mNGrams, callback);
-        }
-    }
-
-    @Override
-    public void getBigrams(final WordComposer codes, final CharSequence previousWord,
-            final WordCallback callback) {
-        if (!reloadDictionaryIfRequired()) {
-            runBigramReverseLookUp(previousWord, callback);
+            reverseLookUp(prevWord.mNGrams, suggestions);
         }
     }
 
@@ -633,11 +638,12 @@
 
     /**
      * reverseLookUp retrieves the full word given a list of terminal nodes and adds those words
-     * through callback.
+     * to the suggestions list passed as an argument.
      * @param terminalNodes list of terminal nodes we want to add
+     * @param suggestions the suggestion collection to add the word to
      */
     private void reverseLookUp(LinkedList<NextWord> terminalNodes,
-            final WordCallback callback) {
+            final ArrayList<SuggestedWordInfo> suggestions) {
         Node node;
         int freq;
         for (NextWord nextWord : terminalNodes) {
@@ -651,8 +657,9 @@
             } while (node != null);
 
             if (freq >= 0) {
-                callback.addWord(mLookedUpString, index, BinaryDictionary.MAX_WORD_LENGTH - index,
-                        freq, mDicTypeId, Dictionary.BIGRAM);
+                suggestions.add(new SuggestedWordInfo(new String(mLookedUpString, index,
+                        BinaryDictionary.MAX_WORD_LENGTH - index),
+                        freq, SuggestedWordInfo.KIND_CORRECTION, mDictType));
             }
         }
     }
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
index 229ae2f..9c32f94 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -29,7 +29,6 @@
     final public boolean mInputTypeNoAutoCorrect;
     final public boolean mIsSettingsSuggestionStripOn;
     final public boolean mApplicationSpecifiedCompletionOn;
-    final public int mEditorAction;
 
     public InputAttributes(final EditorInfo editorInfo, final boolean isFullscreenMode) {
         final int inputType = null != editorInfo ? editorInfo.inputType : 0;
@@ -92,8 +91,6 @@
 
             mApplicationSpecifiedCompletionOn = flagAutoComplete && isFullscreenMode;
         }
-        mEditorAction = (editorInfo == null) ? EditorInfo.IME_ACTION_UNSPECIFIED
-                : editorInfo.imeOptions & EditorInfo.IME_MASK_ACTION;
     }
 
     @SuppressWarnings("unused")
diff --git a/java/src/com/android/inputmethod/latin/InputPointers.java b/java/src/com/android/inputmethod/latin/InputPointers.java
new file mode 100644
index 0000000..cd53bcd
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/InputPointers.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.inputmethod.latin;
+
+public class InputPointers {
+    private final ScalableIntArray mXCoordinates = new ScalableIntArray();
+    private final ScalableIntArray mYCoordinates = new ScalableIntArray();
+    private final ScalableIntArray mPointerIds = new ScalableIntArray();
+    private final ScalableIntArray mTimes = new ScalableIntArray();
+
+    public void addPointer(int index, int x, int y, int pointerId, int time) {
+        mXCoordinates.add(index, x);
+        mYCoordinates.add(index, y);
+        mPointerIds.add(index, pointerId);
+        mTimes.add(index, time);
+    }
+
+    public void addPointer(int x, int y, int pointerId, int time) {
+        mXCoordinates.add(x);
+        mYCoordinates.add(y);
+        mPointerIds.add(pointerId);
+        mTimes.add(time);
+    }
+
+    public void set(InputPointers ip) {
+        mXCoordinates.set(ip.mXCoordinates);
+        mYCoordinates.set(ip.mYCoordinates);
+        mPointerIds.set(ip.mPointerIds);
+        mTimes.set(ip.mTimes);
+    }
+
+    public void copy(InputPointers ip) {
+        mXCoordinates.copy(ip.mXCoordinates);
+        mYCoordinates.copy(ip.mYCoordinates);
+        mPointerIds.copy(ip.mPointerIds);
+        mTimes.copy(ip.mTimes);
+    }
+
+    /**
+     * Append the pointers in the specified {@link InputPointers} to the end of this.
+     * @param src the source {@link InputPointers} to append the pointers.
+     * @param startPos the starting index of the pointers in {@code src}.
+     * @param length the number of pointers to be appended.
+     */
+    public void append(InputPointers src, int startPos, int length) {
+        final int currentLength = getPointerSize();
+        final int newLength = currentLength + length;
+        mXCoordinates.ensureCapacity(newLength);
+        mYCoordinates.ensureCapacity(newLength);
+        mPointerIds.ensureCapacity(newLength);
+        mTimes.ensureCapacity(newLength);
+        System.arraycopy(src.getXCoordinates(), startPos, getXCoordinates(), currentLength, length);
+        System.arraycopy(src.getYCoordinates(), startPos, getYCoordinates(), currentLength, length);
+        System.arraycopy(src.getPointerIds(), startPos, getPointerIds(), currentLength, length);
+        System.arraycopy(src.getTimes(), startPos, getTimes(), currentLength, length);
+    }
+
+    public void reset() {
+        mXCoordinates.reset();
+        mYCoordinates.reset();
+        mPointerIds.reset();
+        mTimes.reset();
+    }
+
+    public int getPointerSize() {
+        return mXCoordinates.getLength();
+    }
+
+    public int[] getXCoordinates() {
+        return mXCoordinates.getPrimitiveArray();
+    }
+
+    public int[] getYCoordinates() {
+        return mYCoordinates.getPrimitiveArray();
+    }
+
+    public int[] getPointerIds() {
+        return mPointerIds.getPrimitiveArray();
+    }
+
+    public int[] getTimes() {
+        return mTimes.getPrimitiveArray();
+    }
+
+    private static class ScalableIntArray {
+        private static final int DEFAULT_SIZE = BinaryDictionary.MAX_WORD_LENGTH;
+        private int[] mArray;
+        private int mLength;
+
+        public ScalableIntArray() {
+            reset();
+        }
+
+        public void add(int index, int val) {
+            if (mLength < index + 1) {
+                mLength = index;
+                add(val);
+            } else {
+                mArray[index] = val;
+            }
+        }
+
+        public void add(int val) {
+            final int nextLength = mLength + 1;
+            ensureCapacity(nextLength);
+            mArray[mLength] = val;
+            mLength = nextLength;
+        }
+
+        public void ensureCapacity(int minimumCapacity) {
+            if (mArray.length < minimumCapacity) {
+                final int nextCapacity = mArray.length * 2;
+                grow(minimumCapacity > nextCapacity ? minimumCapacity : nextCapacity);
+            }
+        }
+
+        private void grow(int newCapacity) {
+            final int[] newArray = new int[newCapacity];
+            System.arraycopy(mArray, 0, newArray, 0, mArray.length);
+            mArray = newArray;
+        }
+
+        public int getLength() {
+            return mLength;
+        }
+
+        public void reset() {
+            mArray = new int[DEFAULT_SIZE];
+            mLength = 0;
+        }
+
+        public int[] getPrimitiveArray() {
+            return mArray;
+        }
+
+        public void copy(ScalableIntArray ip) {
+            ensureCapacity(ip.mLength);
+            System.arraycopy(ip.mArray, 0, mArray, 0, ip.mLength);
+            mLength = ip.mLength;
+        }
+
+        public void set(ScalableIntArray ip) {
+            mArray = ip.mArray;
+            mLength = ip.mLength;
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java
index 4e1f5fe..974af25 100644
--- a/java/src/com/android/inputmethod/latin/LastComposedWord.java
+++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java
@@ -41,26 +41,26 @@
     public static final int NOT_A_SEPARATOR = -1;
 
     public final int[] mPrimaryKeyCodes;
-    public final int[] mXCoordinates;
-    public final int[] mYCoordinates;
     public final String mTypedWord;
     public final String mCommittedWord;
     public final int mSeparatorCode;
     public final CharSequence mPrevWord;
+    public final InputPointers mInputPointers = new InputPointers();
 
     private boolean mActive;
 
     public static final LastComposedWord NOT_A_COMPOSED_WORD =
-            new LastComposedWord(null, null, null, "", "", NOT_A_SEPARATOR, null);
+            new LastComposedWord(null, null, "", "", NOT_A_SEPARATOR, null);
 
     // Warning: this is using the passed objects as is and fully expects them to be
     // immutable. Do not fiddle with their contents after you passed them to this constructor.
-    public LastComposedWord(final int[] primaryKeyCodes, final int[] xCoordinates,
-            final int[] yCoordinates, final String typedWord, final String committedWord,
+    public LastComposedWord(final int[] primaryKeyCodes, final InputPointers inputPointers,
+            final String typedWord, final String committedWord,
             final int separatorCode, final CharSequence prevWord) {
         mPrimaryKeyCodes = primaryKeyCodes;
-        mXCoordinates = xCoordinates;
-        mYCoordinates = yCoordinates;
+        if (inputPointers != null) {
+            mInputPointers.copy(inputPointers);
+        }
         mTypedWord = typedWord;
         mCommittedWord = committedWord;
         mSeparatorCode = separatorCode;
@@ -73,10 +73,10 @@
     }
 
     public boolean canRevertCommit() {
-        return mActive && !TextUtils.isEmpty(mCommittedWord);
+        return mActive && !TextUtils.isEmpty(mCommittedWord) && !didCommitTypedWord();
     }
 
-    public boolean didCommitTypedWord() {
+    private boolean didCommitTypedWord() {
         return TextUtils.equals(mTypedWord, mCommittedWord);
     }
 
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 97e898a..6d902b8 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -48,15 +48,12 @@
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
-import android.view.ViewParent;
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.CorrectionInfo;
 import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.inputmethod.accessibility.AccessibilityUtils;
@@ -64,6 +61,7 @@
 import com.android.inputmethod.compat.CompatUtils;
 import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
 import com.android.inputmethod.compat.SuggestionSpanUtils;
+import com.android.inputmethod.keyboard.KeyDetector;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardActionListener;
 import com.android.inputmethod.keyboard.KeyboardId;
@@ -103,27 +101,6 @@
      */
     private static final String SCHEME_PACKAGE = "package";
 
-    /** Whether to use the binary version of the contacts dictionary */
-    public static final boolean USE_BINARY_CONTACTS_DICTIONARY = true;
-
-    /** Whether to use the binary version of the user dictionary */
-    public static final boolean USE_BINARY_USER_DICTIONARY = true;
-
-    // TODO: migrate this to SettingsValues
-    private int mSuggestionVisibility;
-    private static final int SUGGESTION_VISIBILILTY_SHOW_VALUE
-            = R.string.prefs_suggestion_visibility_show_value;
-    private static final int SUGGESTION_VISIBILILTY_SHOW_ONLY_PORTRAIT_VALUE
-            = R.string.prefs_suggestion_visibility_show_only_portrait_value;
-    private static final int SUGGESTION_VISIBILILTY_HIDE_VALUE
-            = R.string.prefs_suggestion_visibility_hide_value;
-
-    private static final int[] SUGGESTION_VISIBILITY_VALUE_ARRAY = new int[] {
-        SUGGESTION_VISIBILILTY_SHOW_VALUE,
-        SUGGESTION_VISIBILILTY_SHOW_ONLY_PORTRAIT_VALUE,
-        SUGGESTION_VISIBILILTY_HIDE_VALUE
-    };
-
     private static final int SPACE_STATE_NONE = 0;
     // Double space: the state where the user pressed space twice quickly, which LatinIME
     // resolved as period-space. Undoing this converts the period to a space.
@@ -143,8 +120,7 @@
     // Current space state of the input method. This can be any of the above constants.
     private int mSpaceState;
 
-    private SettingsValues mSettingsValues;
-    private InputAttributes mInputAttributes;
+    private SettingsValues mCurrentSettings;
 
     private View mExtractArea;
     private View mKeyPreviewBackingView;
@@ -162,15 +138,13 @@
     private boolean mShouldSwitchToLastSubtype = true;
 
     private boolean mIsMainDictionaryAvailable;
-    // TODO: revert this back to the concrete class after transition.
-    private Dictionary mUserDictionary;
+    private UserBinaryDictionary mUserDictionary;
     private UserHistoryDictionary mUserHistoryDictionary;
     private boolean mIsUserDictionaryAvailable;
 
     private LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
     private WordComposer mWordComposer = new WordComposer();
-
-    private int mCorrectionMode;
+    private RichInputConnection mConnection = new RichInputConnection(this);
 
     // Keep track of the last selection range to decide if we need to show word alternatives
     private static final int NOT_A_CURSOR_POSITION = -1;
@@ -203,14 +177,13 @@
 
     public static class UIHandler extends StaticInnerHandlerWrapper<LatinIME> {
         private static final int MSG_UPDATE_SHIFT_STATE = 1;
-        private static final int MSG_SPACE_TYPED = 4;
-        private static final int MSG_SET_BIGRAM_PREDICTIONS = 5;
         private static final int MSG_PENDING_IMS_CALLBACK = 6;
-        private static final int MSG_UPDATE_SUGGESTIONS = 7;
+        private static final int MSG_UPDATE_SUGGESTION_STRIP = 7;
 
         private int mDelayUpdateSuggestions;
         private int mDelayUpdateShiftState;
         private long mDoubleSpacesTurnIntoPeriodTimeout;
+        private long mDoubleSpaceTimerStart;
 
         public UIHandler(LatinIME outerInstance) {
             super(outerInstance);
@@ -231,29 +204,25 @@
             final LatinIME latinIme = getOuterInstance();
             final KeyboardSwitcher switcher = latinIme.mKeyboardSwitcher;
             switch (msg.what) {
-            case MSG_UPDATE_SUGGESTIONS:
-                latinIme.updateSuggestions();
+            case MSG_UPDATE_SUGGESTION_STRIP:
+                latinIme.updateSuggestionsOrPredictions();
                 break;
             case MSG_UPDATE_SHIFT_STATE:
                 switcher.updateShiftState();
                 break;
-            case MSG_SET_BIGRAM_PREDICTIONS:
-                latinIme.updateBigramPredictions();
-                break;
             }
         }
 
-        public void postUpdateSuggestions() {
-            removeMessages(MSG_UPDATE_SUGGESTIONS);
-            sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTIONS), mDelayUpdateSuggestions);
+        public void postUpdateSuggestionStrip() {
+            sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTION_STRIP), mDelayUpdateSuggestions);
         }
 
-        public void cancelUpdateSuggestions() {
-            removeMessages(MSG_UPDATE_SUGGESTIONS);
+        public void cancelUpdateSuggestionStrip() {
+            removeMessages(MSG_UPDATE_SUGGESTION_STRIP);
         }
 
         public boolean hasPendingUpdateSuggestions() {
-            return hasMessages(MSG_UPDATE_SUGGESTIONS);
+            return hasMessages(MSG_UPDATE_SUGGESTION_STRIP);
         }
 
         public void postUpdateShiftState() {
@@ -265,26 +234,17 @@
             removeMessages(MSG_UPDATE_SHIFT_STATE);
         }
 
-        public void postUpdateBigramPredictions() {
-            removeMessages(MSG_SET_BIGRAM_PREDICTIONS);
-            sendMessageDelayed(obtainMessage(MSG_SET_BIGRAM_PREDICTIONS), mDelayUpdateSuggestions);
-        }
-
-        public void cancelUpdateBigramPredictions() {
-            removeMessages(MSG_SET_BIGRAM_PREDICTIONS);
-        }
-
         public void startDoubleSpacesTimer() {
-            removeMessages(MSG_SPACE_TYPED);
-            sendMessageDelayed(obtainMessage(MSG_SPACE_TYPED), mDoubleSpacesTurnIntoPeriodTimeout);
+            mDoubleSpaceTimerStart = SystemClock.uptimeMillis();
         }
 
         public void cancelDoubleSpacesTimer() {
-            removeMessages(MSG_SPACE_TYPED);
+            mDoubleSpaceTimerStart = 0;
         }
 
         public boolean isAcceptingDoubleSpaces() {
-            return hasMessages(MSG_SPACE_TYPED);
+            return SystemClock.uptimeMillis() - mDoubleSpaceTimerStart
+                    < mDoubleSpacesTurnIntoPeriodTimeout;
         }
 
         // Working variables for the following methods.
@@ -393,7 +353,7 @@
         mPrefs = prefs;
         LatinImeLogger.init(this, prefs);
         if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.init(this, prefs);
+            ResearchLogger.getInstance().init(this, prefs);
         }
         InputMethodManagerCompatWrapper.init(this);
         SubtypeSwitcher.init(this);
@@ -411,14 +371,12 @@
 
         loadSettings();
 
-        ImfUtils.setAdditionalInputMethodSubtypes(this, mSettingsValues.getAdditionalSubtypes());
-
-        // TODO: remove the following when it's not needed by updateCorrectionMode() any more
-        mInputAttributes = new InputAttributes(null, false /* isFullscreenMode */);
-        updateCorrectionMode();
+        ImfUtils.setAdditionalInputMethodSubtypes(this, mCurrentSettings.getAdditionalSubtypes());
 
         Utils.GCUtils.getInstance().reset();
         boolean tryGC = true;
+        // Shouldn't this be removed? I think that from Honeycomb on, the GC is now actually working
+        // as expected and this code is useless.
         for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
             try {
                 initSuggest();
@@ -454,14 +412,16 @@
         // Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged()
         // is not guaranteed. It may even be called at the same time on a different thread.
         if (null == mPrefs) mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
+        final InputAttributes inputAttributes =
+                new InputAttributes(getCurrentInputEditorInfo(), isFullscreenMode());
         final RunInLocale<SettingsValues> job = new RunInLocale<SettingsValues>() {
             @Override
             protected SettingsValues job(Resources res) {
-                return new SettingsValues(mPrefs, LatinIME.this);
+                return new SettingsValues(mPrefs, inputAttributes, LatinIME.this);
             }
         };
-        mSettingsValues = job.runInLocale(mResources, mSubtypeSwitcher.getCurrentSubtypeLocale());
-        mFeedbackManager = new AudioAndHapticFeedbackManager(this, mSettingsValues);
+        mCurrentSettings = job.runInLocale(mResources, mSubtypeSwitcher.getCurrentSubtypeLocale());
+        mFeedbackManager = new AudioAndHapticFeedbackManager(this, mCurrentSettings);
         resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary());
     }
 
@@ -469,7 +429,7 @@
         final Locale subtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
         final String localeStr = subtypeLocale.toString();
 
-        final Dictionary oldContactsDictionary;
+        final ContactsBinaryDictionary oldContactsDictionary;
         if (mSuggest != null) {
             oldContactsDictionary = mSuggest.getContactsDictionary();
             mSuggest.close();
@@ -477,19 +437,17 @@
             oldContactsDictionary = null;
         }
         mSuggest = new Suggest(this, subtypeLocale);
-        if (mSettingsValues.mAutoCorrectEnabled) {
-            mSuggest.setAutoCorrectionThreshold(mSettingsValues.mAutoCorrectionThreshold);
+        if (mCurrentSettings.mCorrectionEnabled) {
+            mSuggest.setAutoCorrectionThreshold(mCurrentSettings.mAutoCorrectionThreshold);
         }
 
         mIsMainDictionaryAvailable = DictionaryFactory.isDictionaryAvailable(this, subtypeLocale);
-
-        if (USE_BINARY_USER_DICTIONARY) {
-            mUserDictionary = new UserBinaryDictionary(this, localeStr);
-            mIsUserDictionaryAvailable = ((UserBinaryDictionary)mUserDictionary).isEnabled();
-        } else {
-            mUserDictionary = new UserDictionary(this, localeStr);
-            mIsUserDictionaryAvailable = ((UserDictionary)mUserDictionary).isEnabled();
+        if (ProductionFlag.IS_EXPERIMENTAL) {
+            ResearchLogger.getInstance().initSuggest(mSuggest);
         }
+
+        mUserDictionary = new UserBinaryDictionary(this, localeStr);
+        mIsUserDictionaryAvailable = mUserDictionary.isEnabled();
         mSuggest.setUserDictionary(mUserDictionary);
 
         resetContactsDictionary(oldContactsDictionary);
@@ -497,44 +455,43 @@
         // Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged()
         // is not guaranteed. It may even be called at the same time on a different thread.
         if (null == mPrefs) mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
-        mUserHistoryDictionary = UserHistoryDictionary.getInstance(
-                this, localeStr, Suggest.DIC_USER_HISTORY, mPrefs);
+        mUserHistoryDictionary = UserHistoryDictionary.getInstance(this, localeStr, mPrefs);
         mSuggest.setUserHistoryDictionary(mUserHistoryDictionary);
     }
 
     /**
      * Resets the contacts dictionary in mSuggest according to the user settings.
      *
-     * This method takes an optional contacts dictionary to use. Since the contacts dictionary
-     * does not depend on the locale, it can be reused across different instances of Suggest.
-     * The dictionary will also be opened or closed as necessary depending on the settings.
+     * This method takes an optional contacts dictionary to use when the locale hasn't changed
+     * since the contacts dictionary can be opened or closed as necessary depending on the settings.
      *
      * @param oldContactsDictionary an optional dictionary to use, or null
      */
-    private void resetContactsDictionary(final Dictionary oldContactsDictionary) {
-        final boolean shouldSetDictionary = (null != mSuggest && mSettingsValues.mUseContactsDict);
+    private void resetContactsDictionary(final ContactsBinaryDictionary oldContactsDictionary) {
+        final boolean shouldSetDictionary = (null != mSuggest && mCurrentSettings.mUseContactsDict);
 
-        final Dictionary dictionaryToUse;
+        final ContactsBinaryDictionary dictionaryToUse;
         if (!shouldSetDictionary) {
             // Make sure the dictionary is closed. If it is already closed, this is a no-op,
             // so it's safe to call it anyways.
             if (null != oldContactsDictionary) oldContactsDictionary.close();
             dictionaryToUse = null;
-        } else if (null != oldContactsDictionary) {
-            // Make sure the old contacts dictionary is opened. If it is already open, this is a
-            // no-op, so it's safe to call it anyways.
-            if (USE_BINARY_CONTACTS_DICTIONARY) {
-                ((ContactsBinaryDictionary)oldContactsDictionary).reopen(this);
-            } else {
-                ((ContactsDictionary)oldContactsDictionary).reopen(this);
-            }
-            dictionaryToUse = oldContactsDictionary;
         } else {
-            if (USE_BINARY_CONTACTS_DICTIONARY) {
-                dictionaryToUse = new ContactsBinaryDictionary(this, Suggest.DIC_CONTACTS,
-                        mSubtypeSwitcher.getCurrentSubtypeLocale());
+            final Locale locale = mSubtypeSwitcher.getCurrentSubtypeLocale();
+            if (null != oldContactsDictionary) {
+                if (!oldContactsDictionary.mLocale.equals(locale)) {
+                    // If the locale has changed then recreate the contacts dictionary. This
+                    // allows locale dependent rules for handling bigram name predictions.
+                    oldContactsDictionary.close();
+                    dictionaryToUse = new ContactsBinaryDictionary(this, locale);
+                } else {
+                    // Make sure the old contacts dictionary is opened. If it is already open,
+                    // this is a no-op, so it's safe to call it anyways.
+                    oldContactsDictionary.reopen(this);
+                    dictionaryToUse = oldContactsDictionary;
+                }
             } else {
-                dictionaryToUse = new ContactsDictionary(this, Suggest.DIC_CONTACTS);
+                dictionaryToUse = new ContactsBinaryDictionary(this, locale);
             }
         }
 
@@ -569,9 +526,10 @@
         if (mDisplayOrientation != conf.orientation) {
             mDisplayOrientation = conf.orientation;
             mHandler.startOrientationChanging();
-            final InputConnection ic = getCurrentInputConnection();
-            commitTyped(ic, LastComposedWord.NOT_A_SEPARATOR);
-            if (ic != null) ic.finishComposingText(); // For voice input
+            mConnection.beginBatchEdit();
+            commitTyped(LastComposedWord.NOT_A_SEPARATOR);
+            mConnection.finishComposingText();
+            mConnection.endBatchEdit();
             if (isShowingOptionDialog())
                 mOptionsDialog.dismiss();
         }
@@ -660,6 +618,7 @@
                     + ((editorInfo.inputType & InputType.TYPE_TEXT_FLAG_CAP_WORDS) != 0));
         }
         if (ProductionFlag.IS_EXPERIMENTAL) {
+            ResearchLogger.getInstance().start();
             ResearchLogger.latinIME_onStartInputViewInternal(editorInfo, mPrefs);
         }
         if (InputAttributes.inPrivateImeOptions(null, NO_MICROPHONE_COMPAT, editorInfo)) {
@@ -699,7 +658,6 @@
         updateFullscreenMode();
         mLastSelectionStart = editorInfo.initialSelStart;
         mLastSelectionEnd = editorInfo.initialSelEnd;
-        mInputAttributes = new InputAttributes(editorInfo, isFullscreenMode());
         mApplicationSpecifiedCompletions = null;
 
         inputView.closing();
@@ -709,36 +667,40 @@
         mSpaceState = SPACE_STATE_NONE;
 
         loadSettings();
-        updateCorrectionMode();
-        updateSuggestionVisibility(mResources);
 
-        if (mSuggest != null && mSettingsValues.mAutoCorrectEnabled) {
-            mSuggest.setAutoCorrectionThreshold(mSettingsValues.mAutoCorrectionThreshold);
+        if (mSuggest != null && mCurrentSettings.mCorrectionEnabled) {
+            mSuggest.setAutoCorrectionThreshold(mCurrentSettings.mAutoCorrectionThreshold);
         }
 
-        switcher.loadKeyboard(editorInfo, mSettingsValues);
+        switcher.loadKeyboard(editorInfo, mCurrentSettings);
 
         if (mSuggestionsView != null)
             mSuggestionsView.clear();
         setSuggestionStripShownInternal(
                 isSuggestionsStripVisible(), /* needsInputViewShown */ false);
-        // Delay updating suggestions because keyboard input view may not be shown at this point.
-        mHandler.postUpdateSuggestions();
+
+        mHandler.cancelUpdateSuggestionStrip();
         mHandler.cancelDoubleSpacesTimer();
 
-        inputView.setKeyPreviewPopupEnabled(mSettingsValues.mKeyPreviewPopupOn,
-                mSettingsValues.mKeyPreviewPopupDismissDelay);
+        inputView.setKeyPreviewPopupEnabled(mCurrentSettings.mKeyPreviewPopupOn,
+                mCurrentSettings.mKeyPreviewPopupDismissDelay);
         inputView.setProximityCorrectionEnabled(true);
 
         if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
     }
 
+    // Callback for the TargetApplicationGetter
+    @Override
     public void onTargetApplicationKnown(final ApplicationInfo info) {
         mTargetApplicationInfo = info;
     }
 
     @Override
     public void onWindowHidden() {
+        if (ProductionFlag.IS_EXPERIMENTAL) {
+            ResearchLogger.latinIME_onWindowHidden(mLastSelectionStart, mLastSelectionEnd,
+                    getCurrentInputConnection());
+        }
         super.onWindowHidden();
         KeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
         if (inputView != null) inputView.closing();
@@ -748,6 +710,9 @@
         super.onFinishInput();
 
         LatinImeLogger.commit();
+        if (ProductionFlag.IS_EXPERIMENTAL) {
+            ResearchLogger.getInstance().stop();
+        }
 
         KeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
         if (inputView != null) inputView.closing();
@@ -759,7 +724,7 @@
         KeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
         if (inputView != null) inputView.cancelAllMessages();
         // Remove pending messages related to update suggestions
-        mHandler.cancelUpdateSuggestions();
+        mHandler.cancelUpdateSuggestionStrip();
     }
 
     @Override
@@ -768,7 +733,6 @@
             int composingSpanStart, int composingSpanEnd) {
         super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
                 composingSpanStart, composingSpanEnd);
-
         if (DEBUG) {
             Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart
                     + ", ose=" + oldSelEnd
@@ -780,9 +744,16 @@
                     + ", ce=" + composingSpanEnd);
         }
         if (ProductionFlag.IS_EXPERIMENTAL) {
+            final boolean expectingUpdateSelectionFromLogger =
+                    ResearchLogger.getAndClearLatinIMEExpectingUpdateSelection();
             ResearchLogger.latinIME_onUpdateSelection(mLastSelectionStart, mLastSelectionEnd,
                     oldSelStart, oldSelEnd, newSelStart, newSelEnd, composingSpanStart,
-                    composingSpanEnd);
+                    composingSpanEnd, mExpectingUpdateSelection,
+                    expectingUpdateSelectionFromLogger, mConnection);
+            if (expectingUpdateSelectionFromLogger) {
+                // TODO: Investigate. Quitting now sounds wrong - we won't do the resetting work
+                return;
+            }
         }
 
         // TODO: refactor the following code to be less contrived.
@@ -841,7 +812,7 @@
      */
     @Override
     public void onExtractedTextClicked() {
-        if (isSuggestionsRequested()) return;
+        if (mCurrentSettings.isSuggestionsRequested(mDisplayOrientation)) return;
 
         super.onExtractedTextClicked();
     }
@@ -857,7 +828,7 @@
      */
     @Override
     public void onExtractedCursorMovement(int dx, int dy) {
-        if (isSuggestionsRequested()) return;
+        if (mCurrentSettings.isSuggestionsRequested(mDisplayOrientation)) return;
 
         super.onExtractedCursorMovement(dx, dy);
     }
@@ -888,33 +859,31 @@
         if (ProductionFlag.IS_EXPERIMENTAL) {
             ResearchLogger.latinIME_onDisplayCompletions(applicationSpecifiedCompletions);
         }
-        if (mInputAttributes.mApplicationSpecifiedCompletionOn) {
-            mApplicationSpecifiedCompletions = applicationSpecifiedCompletions;
-            if (applicationSpecifiedCompletions == null) {
-                clearSuggestions();
-                return;
-            }
-
-            final ArrayList<SuggestedWords.SuggestedWordInfo> applicationSuggestedWords =
-                    SuggestedWords.getFromApplicationSpecifiedCompletions(
-                            applicationSpecifiedCompletions);
-            final SuggestedWords suggestedWords = new SuggestedWords(
-                    applicationSuggestedWords,
-                    false /* typedWordValid */,
-                    false /* hasAutoCorrectionCandidate */,
-                    false /* allowsToBeAutoCorrected */,
-                    false /* isPunctuationSuggestions */,
-                    false /* isObsoleteSuggestions */,
-                    false /* isPrediction */);
-            // When in fullscreen mode, show completions generated by the application
-            final boolean isAutoCorrection = false;
-            setSuggestions(suggestedWords, isAutoCorrection);
-            setAutoCorrectionIndicator(isAutoCorrection);
-            // TODO: is this the right thing to do? What should we auto-correct to in
-            // this case? This says to keep whatever the user typed.
-            mWordComposer.setAutoCorrection(mWordComposer.getTypedWord());
-            setSuggestionStripShown(true);
+        if (!mCurrentSettings.isApplicationSpecifiedCompletionsOn()) return;
+        mApplicationSpecifiedCompletions = applicationSpecifiedCompletions;
+        if (applicationSpecifiedCompletions == null) {
+            clearSuggestions();
+            return;
         }
+
+        final ArrayList<SuggestedWords.SuggestedWordInfo> applicationSuggestedWords =
+                SuggestedWords.getFromApplicationSpecifiedCompletions(
+                        applicationSpecifiedCompletions);
+        final SuggestedWords suggestedWords = new SuggestedWords(
+                applicationSuggestedWords,
+                false /* typedWordValid */,
+                false /* hasAutoCorrectionCandidate */,
+                false /* isPunctuationSuggestions */,
+                false /* isObsoleteSuggestions */,
+                false /* isPrediction */);
+        // When in fullscreen mode, show completions generated by the application
+        final boolean isAutoCorrection = false;
+        setSuggestions(suggestedWords, isAutoCorrection);
+        setAutoCorrectionIndicator(isAutoCorrection);
+        // TODO: is this the right thing to do? What should we auto-correct to in
+        // this case? This says to keep whatever the user typed.
+        mWordComposer.setAutoCorrection(mWordComposer.getTypedWord());
+        setSuggestionStripShown(true);
     }
 
     private void setSuggestionStripShownInternal(boolean shown, boolean needsInputViewShown) {
@@ -1001,7 +970,7 @@
     public boolean onEvaluateFullscreenMode() {
         // Reread resource value here, because this method is called by framework anytime as needed.
         final boolean isFullscreenModeAllowed =
-                mSettingsValues.isFullscreenModeAllowed(getResources());
+                mCurrentSettings.isFullscreenModeAllowed(getResources());
         return super.onEvaluateFullscreenMode() && isFullscreenModeAllowed;
     }
 
@@ -1016,15 +985,11 @@
     }
 
     // This will reset the whole input state to the starting state. It will clear
-    // the composing word, reset the last composed word, tell the inputconnection
-    // and the composingStateManager about it.
+    // the composing word, reset the last composed word, tell the inputconnection about it.
     private void resetEntireInputState() {
         resetComposingState(true /* alsoResetLastComposedWord */);
-        updateSuggestions();
-        final InputConnection ic = getCurrentInputConnection();
-        if (ic != null) {
-            ic.finishComposingText();
-        }
+        clearSuggestions();
+        mConnection.finishComposingText();
     }
 
     private void resetComposingState(final boolean alsoResetLastComposedWord) {
@@ -1033,26 +998,26 @@
             mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
     }
 
-    public void commitTyped(final InputConnection ic, final int separatorCode) {
+    private void commitTyped(final int separatorCode) {
         if (!mWordComposer.isComposingWord()) return;
         final CharSequence typedWord = mWordComposer.getTypedWord();
         if (typedWord.length() > 0) {
-            if (ic != null) {
-                ic.commitText(typedWord, 1);
-                if (ProductionFlag.IS_EXPERIMENTAL) {
-                    ResearchLogger.latinIME_commitText(typedWord);
-                }
+            mConnection.commitText(typedWord, 1);
+            if (ProductionFlag.IS_EXPERIMENTAL) {
+                ResearchLogger.latinIME_commitText(typedWord);
             }
             final CharSequence prevWord = addToUserHistoryDictionary(typedWord);
             mLastComposedWord = mWordComposer.commitWord(
                     LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, typedWord.toString(),
                     separatorCode, prevWord);
         }
-        updateSuggestions();
+        updateSuggestionsOrPredictions();
     }
 
+    // Called from the KeyboardSwitcher which needs to know auto caps state to display
+    // the right layout.
     public int getCurrentAutoCapsState() {
-        if (!mSettingsValues.mAutoCap) return Constants.TextUtils.CAP_MODE_OFF;
+        if (!mCurrentSettings.mAutoCap) return Constants.TextUtils.CAP_MODE_OFF;
 
         final EditorInfo ei = getCurrentInputEditorInfo();
         if (ei == null) return Constants.TextUtils.CAP_MODE_OFF;
@@ -1070,27 +1035,23 @@
         // unless needed.
         if (mWordComposer.isComposingWord()) return Constants.TextUtils.CAP_MODE_OFF;
 
-        final InputConnection ic = getCurrentInputConnection();
-        if (ic == null) return Constants.TextUtils.CAP_MODE_OFF;
         // TODO: This blocking IPC call is heavy. Consider doing this without using IPC calls.
         // Note: getCursorCapsMode() returns the current capitalization mode that is any
         // combination of CAP_MODE_CHARACTERS, CAP_MODE_WORDS, and CAP_MODE_SENTENCES. 0 means none
         // of them.
-        return ic.getCursorCapsMode(inputType);
+        return mConnection.getCursorCapsMode(inputType);
     }
 
-    // "ic" may be null
-    private void swapSwapperAndSpaceWhileInBatchEdit(final InputConnection ic) {
-        if (null == ic) return;
-        CharSequence lastTwo = ic.getTextBeforeCursor(2, 0);
+    private void swapSwapperAndSpace() {
+        CharSequence lastTwo = mConnection.getTextBeforeCursor(2, 0);
         // It is guaranteed lastTwo.charAt(1) is a swapper - else this method is not called.
         if (lastTwo != null && lastTwo.length() == 2
                 && lastTwo.charAt(0) == Keyboard.CODE_SPACE) {
-            ic.deleteSurroundingText(2, 0);
+            mConnection.deleteSurroundingText(2, 0);
             if (ProductionFlag.IS_EXPERIMENTAL) {
                 ResearchLogger.latinIME_deleteSurroundingText(2);
             }
-            ic.commitText(lastTwo.charAt(1) + " ", 1);
+            mConnection.commitText(lastTwo.charAt(1) + " ", 1);
             if (ProductionFlag.IS_EXPERIMENTAL) {
                 ResearchLogger.latinIME_swapSwapperAndSpaceWhileInBatchEdit();
             }
@@ -1098,18 +1059,17 @@
         }
     }
 
-    private boolean maybeDoubleSpaceWhileInBatchEdit(final InputConnection ic) {
-        if (mCorrectionMode == Suggest.CORRECTION_NONE) return false;
-        if (ic == null) return false;
-        final CharSequence lastThree = ic.getTextBeforeCursor(3, 0);
+    private boolean maybeDoubleSpace() {
+        if (!mCurrentSettings.mCorrectionEnabled) return false;
+        if (!mHandler.isAcceptingDoubleSpaces()) return false;
+        final CharSequence lastThree = mConnection.getTextBeforeCursor(3, 0);
         if (lastThree != null && lastThree.length() == 3
                 && canBeFollowedByPeriod(lastThree.charAt(0))
                 && lastThree.charAt(1) == Keyboard.CODE_SPACE
-                && lastThree.charAt(2) == Keyboard.CODE_SPACE
-                && mHandler.isAcceptingDoubleSpaces()) {
+                && lastThree.charAt(2) == Keyboard.CODE_SPACE) {
             mHandler.cancelDoubleSpacesTimer();
-            ic.deleteSurroundingText(2, 0);
-            ic.commitText(". ", 1);
+            mConnection.deleteSurroundingText(2, 0);
+            mConnection.commitText(". ", 1);
             if (ProductionFlag.IS_EXPERIMENTAL) {
                 ResearchLogger.latinIME_doubleSpaceAutoPeriod();
             }
@@ -1131,29 +1091,10 @@
                 || codePoint == Keyboard.CODE_CLOSING_ANGLE_BRACKET;
     }
 
-    // "ic" may be null
-    private static void removeTrailingSpaceWhileInBatchEdit(final InputConnection ic) {
-        if (ic == null) return;
-        final CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
-        if (lastOne != null && lastOne.length() == 1
-                && lastOne.charAt(0) == Keyboard.CODE_SPACE) {
-            ic.deleteSurroundingText(1, 0);
-            if (ProductionFlag.IS_EXPERIMENTAL) {
-                ResearchLogger.latinIME_deleteSurroundingText(1);
-            }
-        }
-    }
-
+    // Callback for the SuggestionsView, to call when the "add to dictionary" hint is pressed.
     @Override
-    public boolean addWordToDictionary(String word) {
-        if (USE_BINARY_USER_DICTIONARY) {
-            ((UserBinaryDictionary)mUserDictionary).addWordToUserDictionary(word, 128);
-        } else {
-            ((UserDictionary)mUserDictionary).addWordToUserDictionary(word, 128);
-        }
-        // Suggestion strip should be updated after the operation of adding word to the
-        // user dictionary
-        mHandler.postUpdateSuggestions();
+    public boolean addWordToUserDictionary(String word) {
+        mUserDictionary.addWordToUserDictionary(word, 128);
         return true;
     }
 
@@ -1193,17 +1134,14 @@
     }
 
     private void performEditorAction(int actionId) {
-        final InputConnection ic = getCurrentInputConnection();
-        if (ic != null) {
-            ic.performEditorAction(actionId);
-            if (ProductionFlag.IS_EXPERIMENTAL) {
-                ResearchLogger.latinIME_performEditorAction(actionId);
-            }
+        mConnection.performEditorAction(actionId);
+        if (ProductionFlag.IS_EXPERIMENTAL) {
+            ResearchLogger.latinIME_performEditorAction(actionId);
         }
     }
 
     private void handleLanguageSwitchKey() {
-        final boolean includesOtherImes = mSettingsValues.mIncludesOtherImesInLanguageSwitchList;
+        final boolean includesOtherImes = mCurrentSettings.mIncludesOtherImesInLanguageSwitchList;
         final IBinder token = getWindow().getWindow().getAttributes().token;
         if (mShouldSwitchToLastSubtype) {
             final InputMethodSubtype lastSubtype = mImm.getLastInputMethodSubtype();
@@ -1221,12 +1159,12 @@
         }
     }
 
-    static private void sendUpDownEnterOrBackspace(final int code, final InputConnection ic) {
+    private void sendUpDownEnterOrBackspace(final int code) {
         final long eventTime = SystemClock.uptimeMillis();
-        ic.sendKeyEvent(new KeyEvent(eventTime, eventTime,
+        mConnection.sendKeyEvent(new KeyEvent(eventTime, eventTime,
                 KeyEvent.ACTION_DOWN, code, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
                 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE));
-        ic.sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
+        mConnection.sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
                 KeyEvent.ACTION_UP, code, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
                 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE));
     }
@@ -1239,24 +1177,21 @@
             return;
         }
 
-        final InputConnection ic = getCurrentInputConnection();
-        if (ic != null) {
-            // 16 is android.os.Build.VERSION_CODES.JELLY_BEAN but we can't write it because
-            // we want to be able to compile against the Ice Cream Sandwich SDK.
-            if (Keyboard.CODE_ENTER == code && mTargetApplicationInfo != null
-                    && mTargetApplicationInfo.targetSdkVersion < 16) {
-                // Backward compatibility mode. Before Jelly bean, the keyboard would simulate
-                // a hardware keyboard event on pressing enter or delete. This is bad for many
-                // reasons (there are race conditions with commits) but some applications are
-                // relying on this behavior so we continue to support it for older apps.
-                sendUpDownEnterOrBackspace(KeyEvent.KEYCODE_ENTER, ic);
-            } else {
-                final String text = new String(new int[] { code }, 0, 1);
-                ic.commitText(text, text.length());
-            }
-            if (ProductionFlag.IS_EXPERIMENTAL) {
-                ResearchLogger.latinIME_sendKeyCodePoint(code);
-            }
+        // 16 is android.os.Build.VERSION_CODES.JELLY_BEAN but we can't write it because
+        // we want to be able to compile against the Ice Cream Sandwich SDK.
+        if (Keyboard.CODE_ENTER == code && mTargetApplicationInfo != null
+                && mTargetApplicationInfo.targetSdkVersion < 16) {
+            // Backward compatibility mode. Before Jelly bean, the keyboard would simulate
+            // a hardware keyboard event on pressing enter or delete. This is bad for many
+            // reasons (there are race conditions with commits) but some applications are
+            // relying on this behavior so we continue to support it for older apps.
+            sendUpDownEnterOrBackspace(KeyEvent.KEYCODE_ENTER);
+        } else {
+            final String text = new String(new int[] { code }, 0, 1);
+            mConnection.commitText(text, text.length());
+        }
+        if (ProductionFlag.IS_EXPERIMENTAL) {
+            ResearchLogger.latinIME_sendKeyCodePoint(code);
         }
     }
 
@@ -1268,11 +1203,10 @@
             mDeleteCount = 0;
         }
         mLastKeyTime = when;
+        mConnection.beginBatchEdit();
 
         if (ProductionFlag.IS_EXPERIMENTAL) {
-            if (ResearchLogger.sIsLogging) {
-                ResearchLogger.getInstance().logKeyEvent(primaryCode, x, y);
-            }
+            ResearchLogger.latinIME_onCodeInput(primaryCode, x, y);
         }
 
         final KeyboardSwitcher switcher = mKeyboardSwitcher;
@@ -1321,16 +1255,19 @@
         case Keyboard.CODE_LANGUAGE_SWITCH:
             handleLanguageSwitchKey();
             break;
-        default:
-            if (primaryCode == Keyboard.CODE_TAB
-                    && mInputAttributes.mEditorAction == EditorInfo.IME_ACTION_NEXT) {
-                performEditorAction(EditorInfo.IME_ACTION_NEXT);
-                break;
+        case Keyboard.CODE_RESEARCH:
+            if (ProductionFlag.IS_EXPERIMENTAL) {
+                ResearchLogger.getInstance().presentResearchDialog(this);
             }
+            break;
+        default:
             mSpaceState = SPACE_STATE_NONE;
-            if (mSettingsValues.isWordSeparator(primaryCode)) {
+            if (mCurrentSettings.isWordSeparator(primaryCode)) {
                 didAutoCorrect = handleSeparator(primaryCode, x, y, spaceState);
             } else {
+                if (SPACE_STATE_PHANTOM == spaceState) {
+                    commitTyped(LastComposedWord.NOT_A_SEPARATOR);
+                }
                 final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
                 if (keyboard != null && keyboard.hasProximityCharsCorrection(primaryCode)) {
                     handleCharacter(primaryCode, x, y, spaceState);
@@ -1349,23 +1286,23 @@
                 && primaryCode != Keyboard.CODE_SWITCH_ALPHA_SYMBOL)
             mLastComposedWord.deactivate();
         mEnteredText = null;
+        mConnection.endBatchEdit();
     }
 
+    // Called from PointerTracker through the KeyboardActionListener interface
     @Override
     public void onTextInput(CharSequence text) {
-        final InputConnection ic = getCurrentInputConnection();
-        if (ic == null) return;
-        ic.beginBatchEdit();
-        commitTyped(ic, LastComposedWord.NOT_A_SEPARATOR);
-        text = specificTldProcessingOnTextInput(ic, text);
+        mConnection.beginBatchEdit();
+        commitTyped(LastComposedWord.NOT_A_SEPARATOR);
+        text = specificTldProcessingOnTextInput(text);
         if (SPACE_STATE_PHANTOM == mSpaceState) {
             sendKeyCodePoint(Keyboard.CODE_SPACE);
         }
-        ic.commitText(text, 1);
+        mConnection.commitText(text, 1);
         if (ProductionFlag.IS_EXPERIMENTAL) {
             ResearchLogger.latinIME_commitText(text);
         }
-        ic.endBatchEdit();
+        mConnection.endBatchEdit();
         mKeyboardSwitcher.updateShiftState();
         mKeyboardSwitcher.onCodeInput(Keyboard.CODE_OUTPUT_TEXT);
         mSpaceState = SPACE_STATE_NONE;
@@ -1373,9 +1310,32 @@
         resetComposingState(true /* alsoResetLastComposedWord */);
     }
 
-    // ic may not be null
-    private CharSequence specificTldProcessingOnTextInput(final InputConnection ic,
-            final CharSequence text) {
+    @Override
+    public void onStartBatchInput() {
+        mConnection.beginBatchEdit();
+        if (mWordComposer.isComposingWord()) {
+            commitTyped(LastComposedWord.NOT_A_SEPARATOR);
+            mExpectingUpdateSelection = true;
+            // TODO: Can we remove this?
+            mSpaceState = SPACE_STATE_PHANTOM;
+        }
+        mConnection.endBatchEdit();
+    }
+
+    @Override
+    public void onEndBatchInput(CharSequence text) {
+        mConnection.beginBatchEdit();
+        if (SPACE_STATE_PHANTOM == mSpaceState) {
+            sendKeyCodePoint(Keyboard.CODE_SPACE);
+        }
+        mConnection.setComposingText(text, 1);
+        mExpectingUpdateSelection = true;
+        mConnection.endBatchEdit();
+        mKeyboardSwitcher.updateShiftState();
+        mSpaceState = SPACE_STATE_PHANTOM;
+    }
+
+    private CharSequence specificTldProcessingOnTextInput(final CharSequence text) {
         if (text.length() <= 1 || text.charAt(0) != Keyboard.CODE_PERIOD
                 || !Character.isLetter(text.charAt(1))) {
             // Not a tld: do nothing.
@@ -1384,7 +1344,7 @@
         // We have a TLD (or something that looks like this): make sure we don't add
         // a space even if currently in phantom mode.
         mSpaceState = SPACE_STATE_NONE;
-        final CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
+        final CharSequence lastOne = mConnection.getTextBeforeCursor(1, 0);
         if (lastOne != null && lastOne.length() == 1
                 && lastOne.charAt(0) == Keyboard.CODE_PERIOD) {
             return text.subSequence(1, text.length());
@@ -1393,6 +1353,7 @@
         }
     }
 
+    // Called from PointerTracker through the KeyboardActionListener interface
     @Override
     public void onCancelInput() {
         // User released a finger outside any key
@@ -1400,24 +1361,15 @@
     }
 
     private void handleBackspace(final int spaceState) {
-        final InputConnection ic = getCurrentInputConnection();
-        if (ic == null) return;
-        ic.beginBatchEdit();
-        handleBackspaceWhileInBatchEdit(spaceState, ic);
-        ic.endBatchEdit();
-    }
-
-    // "ic" may not be null.
-    private void handleBackspaceWhileInBatchEdit(final int spaceState, final InputConnection ic) {
         // In many cases, we may have to put the keyboard in auto-shift state again.
         mHandler.postUpdateShiftState();
 
-        if (mEnteredText != null && sameAsTextBeforeCursor(ic, mEnteredText)) {
+        if (mEnteredText != null && mConnection.sameAsTextBeforeCursor(mEnteredText)) {
             // Cancel multi-character input: remove the text we just entered.
             // This is triggered on backspace after a key that inputs multiple characters,
             // like the smiley key or the .com key.
             final int length = mEnteredText.length();
-            ic.deleteSurroundingText(length, 0);
+            mConnection.deleteSurroundingText(length, 0);
             if (ProductionFlag.IS_EXPERIMENTAL) {
                 ResearchLogger.latinIME_deleteSurroundingText(length);
             }
@@ -1430,19 +1382,16 @@
         if (mWordComposer.isComposingWord()) {
             final int length = mWordComposer.size();
             if (length > 0) {
-                mWordComposer.deleteLast();
-                ic.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
-                // If we have deleted the last remaining character of a word, then we are not
-                // isComposingWord() any more.
-                if (!mWordComposer.isComposingWord()) {
-                    // Not composing word any more, so we can show bigrams.
-                    mHandler.postUpdateBigramPredictions();
+                // Immediately after a batch input.
+                if (SPACE_STATE_PHANTOM == spaceState) {
+                    mWordComposer.reset();
                 } else {
-                    // Still composing a word, so we still have letters to deduce a suggestion from.
-                    mHandler.postUpdateSuggestions();
+                    mWordComposer.deleteLast();
                 }
+                mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
+                mHandler.postUpdateSuggestionStrip();
             } else {
-                ic.deleteSurroundingText(1, 0);
+                mConnection.deleteSurroundingText(1, 0);
                 if (ProductionFlag.IS_EXPERIMENTAL) {
                     ResearchLogger.latinIME_deleteSurroundingText(1);
                 }
@@ -1450,17 +1399,18 @@
         } else {
             if (mLastComposedWord.canRevertCommit()) {
                 Utils.Stats.onAutoCorrectionCancellation();
-                revertCommit(ic);
+                revertCommit();
                 return;
             }
             if (SPACE_STATE_DOUBLE == spaceState) {
-                if (revertDoubleSpaceWhileInBatchEdit(ic)) {
+                mHandler.cancelDoubleSpacesTimer();
+                if (mConnection.revertDoubleSpace()) {
                     // No need to reset mSpaceState, it has already be done (that's why we
                     // receive it as a parameter)
                     return;
                 }
             } else if (SPACE_STATE_SWAP_PUNCTUATION == spaceState) {
-                if (revertSwapPunctuation(ic)) {
+                if (mConnection.revertSwapPunctuation()) {
                     // Likewise
                     return;
                 }
@@ -1471,8 +1421,8 @@
             if (mLastSelectionStart != mLastSelectionEnd) {
                 // If there is a selection, remove it.
                 final int lengthToDelete = mLastSelectionEnd - mLastSelectionStart;
-                ic.setSelection(mLastSelectionEnd, mLastSelectionEnd);
-                ic.deleteSurroundingText(lengthToDelete, 0);
+                mConnection.setSelection(mLastSelectionEnd, mLastSelectionEnd);
+                mConnection.deleteSurroundingText(lengthToDelete, 0);
                 if (ProductionFlag.IS_EXPERIMENTAL) {
                     ResearchLogger.latinIME_deleteSurroundingText(lengthToDelete);
                 }
@@ -1490,40 +1440,39 @@
                     // a hardware keyboard event on pressing enter or delete. This is bad for many
                     // reasons (there are race conditions with commits) but some applications are
                     // relying on this behavior so we continue to support it for older apps.
-                    sendUpDownEnterOrBackspace(KeyEvent.KEYCODE_DEL, ic);
+                    sendUpDownEnterOrBackspace(KeyEvent.KEYCODE_DEL);
                 } else {
-                    ic.deleteSurroundingText(1, 0);
+                    mConnection.deleteSurroundingText(1, 0);
                 }
                 if (ProductionFlag.IS_EXPERIMENTAL) {
                     ResearchLogger.latinIME_deleteSurroundingText(1);
                 }
                 if (mDeleteCount > DELETE_ACCELERATE_AT) {
-                    ic.deleteSurroundingText(1, 0);
+                    mConnection.deleteSurroundingText(1, 0);
                     if (ProductionFlag.IS_EXPERIMENTAL) {
                         ResearchLogger.latinIME_deleteSurroundingText(1);
                     }
                 }
             }
-            if (isSuggestionsRequested()) {
-                restartSuggestionsOnWordBeforeCursorIfAtEndOfWord(ic);
+            if (mCurrentSettings.isSuggestionsRequested(mDisplayOrientation)) {
+                restartSuggestionsOnWordBeforeCursorIfAtEndOfWord();
             }
         }
     }
 
-    // ic may be null
-    private boolean maybeStripSpaceWhileInBatchEdit(final InputConnection ic, final int code,
+    private boolean maybeStripSpace(final int code,
             final int spaceState, final boolean isFromSuggestionStrip) {
         if (Keyboard.CODE_ENTER == code && SPACE_STATE_SWAP_PUNCTUATION == spaceState) {
-            removeTrailingSpaceWhileInBatchEdit(ic);
+            mConnection.removeTrailingSpace();
             return false;
         } else if ((SPACE_STATE_WEAK == spaceState
                 || SPACE_STATE_SWAP_PUNCTUATION == spaceState)
                 && isFromSuggestionStrip) {
-            if (mSettingsValues.isWeakSpaceSwapper(code)) {
+            if (mCurrentSettings.isWeakSpaceSwapper(code)) {
                 return true;
             } else {
-                if (mSettingsValues.isWeakSpaceStripper(code)) {
-                    removeTrailingSpaceWhileInBatchEdit(ic);
+                if (mCurrentSettings.isWeakSpaceStripper(code)) {
+                    mConnection.removeTrailingSpace();
                 }
                 return false;
             }
@@ -1534,20 +1483,10 @@
 
     private void handleCharacter(final int primaryCode, final int x,
             final int y, final int spaceState) {
-        final InputConnection ic = getCurrentInputConnection();
-        if (null != ic) ic.beginBatchEdit();
-        // TODO: if ic is null, does it make any sense to call this?
-        handleCharacterWhileInBatchEdit(primaryCode, x, y, spaceState, ic);
-        if (null != ic) ic.endBatchEdit();
-    }
-
-    // "ic" may be null without this crashing, but the behavior will be really strange
-    private void handleCharacterWhileInBatchEdit(final int primaryCode,
-            final int x, final int y, final int spaceState, final InputConnection ic) {
         boolean isComposingWord = mWordComposer.isComposingWord();
 
         if (SPACE_STATE_PHANTOM == spaceState &&
-                !mSettingsValues.isSymbolExcludedFromWordSeparators(primaryCode)) {
+                !mCurrentSettings.isSymbolExcludedFromWordSeparators(primaryCode)) {
             if (isComposingWord) {
                 // Sanity check
                 throw new RuntimeException("Should not be composing here");
@@ -1559,8 +1498,9 @@
         // dozen milliseconds. Avoid calling it as much as possible, since we are on the UI
         // thread here.
         if (!isComposingWord && (isAlphabet(primaryCode)
-                || mSettingsValues.isSymbolExcludedFromWordSeparators(primaryCode))
-                && isSuggestionsRequested() && !isCursorTouchingWord()) {
+                || mCurrentSettings.isSymbolExcludedFromWordSeparators(primaryCode))
+                && mCurrentSettings.isSuggestionsRequested(mDisplayOrientation) &&
+                !mConnection.isCursorTouchingWord(mCurrentSettings)) {
             // Reset entirely the composing state anyway, then start composing a new word unless
             // the character is a single quote. The idea here is, single quote is not a
             // separator and it should be treated as a normal character, except in the first
@@ -1571,85 +1511,73 @@
             // it entirely and resume suggestions on the previous word, we'd like to still
             // have touch coordinates for it.
             resetComposingState(false /* alsoResetLastComposedWord */);
-            clearSuggestions();
         }
         if (isComposingWord) {
-            mWordComposer.add(
-                    primaryCode, x, y, mKeyboardSwitcher.getKeyboardView().getKeyDetector());
-            if (ic != null) {
-                // If it's the first letter, make note of auto-caps state
-                if (mWordComposer.size() == 1) {
-                    mWordComposer.setAutoCapitalized(
-                            getCurrentAutoCapsState() != Constants.TextUtils.CAP_MODE_OFF);
-                }
-                ic.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
+            final int keyX, keyY;
+            if (KeyboardActionListener.Adapter.isInvalidCoordinate(x)
+                    || KeyboardActionListener.Adapter.isInvalidCoordinate(y)) {
+                keyX = x;
+                keyY = y;
+            } else {
+                final KeyDetector keyDetector =
+                        mKeyboardSwitcher.getKeyboardView().getKeyDetector();
+                keyX = keyDetector.getTouchX(x);
+                keyY = keyDetector.getTouchY(y);
             }
-            mHandler.postUpdateSuggestions();
+            mWordComposer.add(primaryCode, keyX, keyY);
+            // If it's the first letter, make note of auto-caps state
+            if (mWordComposer.size() == 1) {
+                mWordComposer.setAutoCapitalized(
+                        getCurrentAutoCapsState() != Constants.TextUtils.CAP_MODE_OFF);
+            }
+            mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
         } else {
-            final boolean swapWeakSpace = maybeStripSpaceWhileInBatchEdit(ic, primaryCode,
+            final boolean swapWeakSpace = maybeStripSpace(primaryCode,
                     spaceState, KeyboardActionListener.SUGGESTION_STRIP_COORDINATE == x);
 
             sendKeyCodePoint(primaryCode);
 
             if (swapWeakSpace) {
-                swapSwapperAndSpaceWhileInBatchEdit(ic);
+                swapSwapperAndSpace();
                 mSpaceState = SPACE_STATE_WEAK;
             }
-            // Some characters are not word separators, yet they don't start a new
-            // composing span. For these, we haven't changed the suggestion strip, and
-            // if the "add to dictionary" hint is shown, we should do so now. Examples of
-            // such characters include single quote, dollar, and others; the exact list is
-            // the list of characters for which we enter handleCharacterWhileInBatchEdit
-            // that don't match the test if ((isAlphabet...)) at the top of this method.
-            if (null != mSuggestionsView && mSuggestionsView.dismissAddToDictionaryHint()) {
-                mHandler.postUpdateBigramPredictions();
-            }
+            // In case the "add to dictionary" hint was still displayed.
+            if (null != mSuggestionsView) mSuggestionsView.dismissAddToDictionaryHint();
         }
+        mHandler.postUpdateSuggestionStrip();
         Utils.Stats.onNonSeparator((char)primaryCode, x, y);
     }
 
     // Returns true if we did an autocorrection, false otherwise.
     private boolean handleSeparator(final int primaryCode, final int x, final int y,
             final int spaceState) {
-        // Should dismiss the "Touch again to save" message when handling separator
-        if (mSuggestionsView != null && mSuggestionsView.dismissAddToDictionaryHint()) {
-            mHandler.cancelUpdateBigramPredictions();
-            mHandler.postUpdateSuggestions();
-        }
-
         boolean didAutoCorrect = false;
         // Handle separator
-        final InputConnection ic = getCurrentInputConnection();
-        if (ic != null) {
-            ic.beginBatchEdit();
-        }
         if (mWordComposer.isComposingWord()) {
             // In certain languages where single quote is a separator, it's better
             // not to auto correct, but accept the typed word. For instance,
             // in Italian dov' should not be expanded to dove' because the elision
             // requires the last vowel to be removed.
-            final boolean shouldAutoCorrect = mSettingsValues.mAutoCorrectEnabled
-                    && !mInputAttributes.mInputTypeNoAutoCorrect;
-            if (shouldAutoCorrect && primaryCode != Keyboard.CODE_SINGLE_QUOTE) {
-                commitCurrentAutoCorrection(primaryCode, ic);
+            if (mCurrentSettings.mCorrectionEnabled && primaryCode != Keyboard.CODE_SINGLE_QUOTE) {
+                commitCurrentAutoCorrection(primaryCode);
                 didAutoCorrect = true;
             } else {
-                commitTyped(ic, primaryCode);
+                commitTyped(primaryCode);
             }
         }
 
-        final boolean swapWeakSpace = maybeStripSpaceWhileInBatchEdit(ic, primaryCode, spaceState,
+        final boolean swapWeakSpace = maybeStripSpace(primaryCode, spaceState,
                 KeyboardActionListener.SUGGESTION_STRIP_COORDINATE == x);
 
         if (SPACE_STATE_PHANTOM == spaceState &&
-                mSettingsValues.isPhantomSpacePromotingSymbol(primaryCode)) {
+                mCurrentSettings.isPhantomSpacePromotingSymbol(primaryCode)) {
             sendKeyCodePoint(Keyboard.CODE_SPACE);
         }
         sendKeyCodePoint(primaryCode);
 
         if (Keyboard.CODE_SPACE == primaryCode) {
-            if (isSuggestionsRequested()) {
-                if (maybeDoubleSpaceWhileInBatchEdit(ic)) {
+            if (mCurrentSettings.isSuggestionsRequested(mDisplayOrientation)) {
+                if (maybeDoubleSpace()) {
                     mSpaceState = SPACE_STATE_DOUBLE;
                 } else if (!isShowingPunctuationList()) {
                     mSpaceState = SPACE_STATE_WEAK;
@@ -1657,13 +1585,12 @@
             }
 
             mHandler.startDoubleSpacesTimer();
-            if (!isCursorTouchingWord()) {
-                mHandler.cancelUpdateSuggestions();
-                mHandler.postUpdateBigramPredictions();
+            if (!mConnection.isCursorTouchingWord(mCurrentSettings)) {
+                mHandler.postUpdateSuggestionStrip();
             }
         } else {
             if (swapWeakSpace) {
-                swapSwapperAndSpaceWhileInBatchEdit(ic);
+                swapSwapperAndSpace();
                 mSpaceState = SPACE_STATE_SWAP_PUNCTUATION;
             } else if (SPACE_STATE_PHANTOM == spaceState) {
                 // If we are in phantom space state, and the user presses a separator, we want to
@@ -1682,9 +1609,6 @@
 
         Utils.Stats.onSeparator((char)primaryCode, x, y);
 
-        if (ic != null) {
-            ic.endBatchEdit();
-        }
         return didAutoCorrect;
     }
 
@@ -1695,63 +1619,33 @@
     }
 
     private void handleClose() {
-        commitTyped(getCurrentInputConnection(), LastComposedWord.NOT_A_SEPARATOR);
+        commitTyped(LastComposedWord.NOT_A_SEPARATOR);
         requestHideSelf(0);
         LatinKeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
         if (inputView != null)
             inputView.closing();
     }
 
-    public boolean isSuggestionsRequested() {
-        return mInputAttributes.mIsSettingsSuggestionStripOn
-                && (mCorrectionMode > 0 || isShowingSuggestionsStrip());
-    }
-
-    public boolean isShowingPunctuationList() {
+    // TODO: make this private
+    // Outside LatinIME, only used by the test suite.
+    /* package for tests */ boolean isShowingPunctuationList() {
         if (mSuggestionsView == null) return false;
-        return mSettingsValues.mSuggestPuncList == mSuggestionsView.getSuggestions();
+        return mCurrentSettings.mSuggestPuncList == mSuggestionsView.getSuggestions();
     }
 
-    public boolean isShowingSuggestionsStrip() {
-        return (mSuggestionVisibility == SUGGESTION_VISIBILILTY_SHOW_VALUE)
-                || (mSuggestionVisibility == SUGGESTION_VISIBILILTY_SHOW_ONLY_PORTRAIT_VALUE
-                        && mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT);
-    }
-
-    public boolean isSuggestionsStripVisible() {
+    private boolean isSuggestionsStripVisible() {
         if (mSuggestionsView == null)
             return false;
         if (mSuggestionsView.isShowingAddToDictionaryHint())
             return true;
-        if (!isShowingSuggestionsStrip())
+        if (!mCurrentSettings.isSuggestionStripVisibleInOrientation(mDisplayOrientation))
             return false;
-        if (mInputAttributes.mApplicationSpecifiedCompletionOn)
+        if (mCurrentSettings.isApplicationSpecifiedCompletionsOn())
             return true;
-        return isSuggestionsRequested();
+        return mCurrentSettings.isSuggestionsRequested(mDisplayOrientation);
     }
 
-    public void switchToKeyboardView() {
-        if (DEBUG) {
-            Log.d(TAG, "Switch to keyboard view.");
-        }
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.latinIME_switchToKeyboardView();
-        }
-        View v = mKeyboardSwitcher.getKeyboardView();
-        if (v != null) {
-            // Confirms that the keyboard view doesn't have parent view.
-            ViewParent p = v.getParent();
-            if (p != null && p instanceof ViewGroup) {
-                ((ViewGroup) p).removeView(v);
-            }
-            setInputView(v);
-        }
-        setSuggestionStripShown(isSuggestionsStripVisible());
-        updateInputViewShown();
-        mHandler.postUpdateSuggestions();
-    }
-
-    public void clearSuggestions() {
+    private void clearSuggestions() {
         setSuggestions(SuggestedWords.EMPTY, false);
         setAutoCorrectionIndicator(false);
     }
@@ -1765,83 +1659,89 @@
 
     private void setAutoCorrectionIndicator(final boolean newAutoCorrectionIndicator) {
         // Put a blue underline to a word in TextView which will be auto-corrected.
-        final InputConnection ic = getCurrentInputConnection();
-        if (ic == null) return;
         if (mIsAutoCorrectionIndicatorOn != newAutoCorrectionIndicator
                 && mWordComposer.isComposingWord()) {
             mIsAutoCorrectionIndicatorOn = newAutoCorrectionIndicator;
             final CharSequence textWithUnderline =
                     getTextWithUnderline(mWordComposer.getTypedWord());
-            ic.setComposingText(textWithUnderline, 1);
+            mConnection.setComposingText(textWithUnderline, 1);
         }
     }
 
-    public void updateSuggestions() {
+    private void updateSuggestionsOrPredictions() {
+        mHandler.cancelUpdateSuggestionStrip();
+
         // Check if we have a suggestion engine attached.
-        if ((mSuggest == null || !isSuggestionsRequested())) {
+        if (mSuggest == null || !mCurrentSettings.isSuggestionsRequested(mDisplayOrientation)) {
             if (mWordComposer.isComposingWord()) {
-                Log.w(TAG, "Called updateSuggestions but suggestions were not requested!");
+                Log.w(TAG, "Called updateSuggestionsOrPredictions but suggestions were not "
+                        + "requested!");
                 mWordComposer.setAutoCorrection(mWordComposer.getTypedWord());
             }
             return;
         }
 
-        mHandler.cancelUpdateSuggestions();
-        mHandler.cancelUpdateBigramPredictions();
-
-        if (!mWordComposer.isComposingWord()) {
+        final String typedWord = mWordComposer.getTypedWord();
+        if (!mWordComposer.isComposingWord() && !mCurrentSettings.mBigramPredictionEnabled) {
             setPunctuationSuggestions();
             return;
         }
 
-        // TODO: May need a better way of retrieving previous word
-        final InputConnection ic = getCurrentInputConnection();
-        final CharSequence prevWord;
-        if (null == ic) {
-            prevWord = null;
-        } else {
-            prevWord = EditingUtils.getPreviousWord(ic, mSettingsValues.mWordSeparators);
-        }
+        // Get the word on which we should search the bigrams. If we are composing a word, it's
+        // whatever is *before* the half-committed word in the buffer, hence 2; if we aren't, we
+        // should just skip whitespace if any, so 1.
+        // TODO: this is slow (2-way IPC) - we should probably cache this instead.
+        final CharSequence prevWord =
+                mConnection.getNthPreviousWord(mCurrentSettings.mWordSeparators,
+                mWordComposer.isComposingWord() ? 2 : 1);
+        SuggestedWords suggestedWords = mSuggest.getSuggestedWords(mWordComposer,
+                prevWord, mKeyboardSwitcher.getKeyboard().getProximityInfo(),
+                mCurrentSettings.mCorrectionEnabled);
+        suggestedWords = maybeRetrieveOlderSuggestions(typedWord, suggestedWords);
 
-        final CharSequence typedWord = mWordComposer.getTypedWord();
-        // getSuggestedWords handles gracefully a null value of prevWord
-        final SuggestedWords suggestedWords = mSuggest.getSuggestedWords(mWordComposer,
-                prevWord, mKeyboardSwitcher.getKeyboard().getProximityInfo(), mCorrectionMode);
-
-        // 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
-        // is 1 or typed word is found in dictionary, regardless of suggestion count.  Actually,
-        // 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 (suggestedWords.size() > 1 || typedWord.length() == 1
-                || !suggestedWords.mAllowsToBeAutoCorrected
-                || mSuggestionsView.isShowingAddToDictionaryHint()) {
+        if (null != suggestedWords && suggestedWords.size() > 0) {
             showSuggestions(suggestedWords, typedWord);
         } else {
+            clearSuggestions();
+        }
+    }
+
+    private SuggestedWords maybeRetrieveOlderSuggestions(final CharSequence typedWord,
+            final SuggestedWords suggestedWords) {
+        // TODO: consolidate this into getSuggestedWords
+        // We update the suggestion strip only when we have some suggestions to show, i.e. when
+        // the suggestion count is > 1; else, we leave the old suggestions, with the typed word
+        // replaced with the new one. However, when the word is a dictionary word, or when the
+        // length of the typed word is 1 or 0 (after a deletion typically), we do want to remove the
+        // old suggestions. Also, if we are showing the "add to dictionary" hint, we need to
+        // revert to suggestions - although it is unclear how we can come here if it's displayed.
+        if (suggestedWords.size() > 1 || typedWord.length() <= 1
+                || !suggestedWords.mTypedWordValid
+                || mSuggestionsView.isShowingAddToDictionaryHint()) {
+            return suggestedWords;
+        } else {
             SuggestedWords previousSuggestions = mSuggestionsView.getSuggestions();
-            if (previousSuggestions == mSettingsValues.mSuggestPuncList) {
+            if (previousSuggestions == mCurrentSettings.mSuggestPuncList) {
                 previousSuggestions = SuggestedWords.EMPTY;
             }
             final ArrayList<SuggestedWords.SuggestedWordInfo> typedWordAndPreviousSuggestions =
                     SuggestedWords.getTypedWordAndPreviousSuggestions(
                             typedWord, previousSuggestions);
-            final SuggestedWords obsoleteSuggestedWords =
-                    new SuggestedWords(typedWordAndPreviousSuggestions,
+            return new SuggestedWords(typedWordAndPreviousSuggestions,
                             false /* typedWordValid */,
                             false /* hasAutoCorrectionCandidate */,
-                            false /* allowsToBeAutoCorrected */,
                             false /* isPunctuationSuggestions */,
                             true /* isObsoleteSuggestions */,
                             false /* isPrediction */);
-            showSuggestions(obsoleteSuggestedWords, typedWord);
         }
     }
 
-    public void showSuggestions(final SuggestedWords suggestedWords, final CharSequence typedWord) {
+    private void showSuggestions(final SuggestedWords suggestedWords,
+            final CharSequence typedWord) {
+        // This method is only ever called by updateSuggestions or updateBigramPredictions.
         final CharSequence autoCorrection;
         if (suggestedWords.size() > 0) {
-            if (suggestedWords.hasAutoCorrectionWord()) {
+            if (suggestedWords.mWillAutoCorrect) {
                 autoCorrection = suggestedWords.getWord(1);
             } else {
                 autoCorrection = typedWord;
@@ -1856,12 +1756,10 @@
         setSuggestionStripShown(isSuggestionsStripVisible());
     }
 
-    private void commitCurrentAutoCorrection(final int separatorCodePoint,
-            final InputConnection ic) {
+    private void commitCurrentAutoCorrection(final int separatorCodePoint) {
         // Complete any pending suggestions query first
         if (mHandler.hasPendingUpdateSuggestions()) {
-            mHandler.cancelUpdateSuggestions();
-            updateSuggestions();
+            updateSuggestionsOrPredictions();
         }
         final CharSequence autoCorrection = mWordComposer.getAutoCorrectionOrNull();
         if (autoCorrection != null) {
@@ -1878,26 +1776,20 @@
             mExpectingUpdateSelection = true;
             commitChosenWord(autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD,
                     separatorCodePoint);
-            if (!typedWord.equals(autoCorrection) && null != ic) {
+            if (!typedWord.equals(autoCorrection)) {
                 // This will make the correction flash for a short while as a visual clue
                 // to the user that auto-correction happened.
-                ic.commitCorrection(new CorrectionInfo(mLastSelectionEnd - typedWord.length(),
+                mConnection.commitCorrection(
+                        new CorrectionInfo(mLastSelectionEnd - typedWord.length(),
                         typedWord, autoCorrection));
             }
         }
     }
 
+    // Called from SuggestionsView through the SuggestionsView.Listener interface
     @Override
     public void pickSuggestionManually(final int index, final CharSequence suggestion,
-            int x, int y) {
-        final InputConnection ic = getCurrentInputConnection();
-        if (null != ic) ic.beginBatchEdit();
-        pickSuggestionManuallyWhileInBatchEdit(index, suggestion, x, y, ic);
-        if (null != ic) ic.endBatchEdit();
-    }
-
-    public void pickSuggestionManuallyWhileInBatchEdit(final int index,
-        final CharSequence suggestion, final int x, final int y, final InputConnection ic) {
+            final int x, final int y) {
         final SuggestedWords suggestedWords = mSuggestionsView.getSuggestions();
         // If this is a punctuation picked from the suggestion strip, pass it to onCodeInput
         if (suggestion.length() == 1 && isShowingPunctuationList()) {
@@ -1915,15 +1807,16 @@
             return;
         }
 
+        mConnection.beginBatchEdit();
         if (SPACE_STATE_PHANTOM == mSpaceState && suggestion.length() > 0) {
             int firstChar = Character.codePointAt(suggestion, 0);
-            if ((!mSettingsValues.isWeakSpaceStripper(firstChar))
-                    && (!mSettingsValues.isWeakSpaceSwapper(firstChar))) {
+            if ((!mCurrentSettings.isWeakSpaceStripper(firstChar))
+                    && (!mCurrentSettings.isWeakSpaceSwapper(firstChar))) {
                 sendKeyCodePoint(Keyboard.CODE_SPACE);
             }
         }
 
-        if (mInputAttributes.mApplicationSpecifiedCompletionOn
+        if (mCurrentSettings.isApplicationSpecifiedCompletionsOn()
                 && mApplicationSpecifiedCompletions != null
                 && index >= 0 && index < mApplicationSpecifiedCompletions.length) {
             if (mSuggestionsView != null) {
@@ -1931,13 +1824,12 @@
             }
             mKeyboardSwitcher.updateShiftState();
             resetComposingState(true /* alsoResetLastComposedWord */);
-            if (ic != null) {
-                final CompletionInfo completionInfo = mApplicationSpecifiedCompletions[index];
-                ic.commitCompletion(completionInfo);
-                if (ProductionFlag.IS_EXPERIMENTAL) {
-                    ResearchLogger.latinIME_pickApplicationSpecifiedCompletion(index,
-                            completionInfo.getText(), x, y);
-                }
+            final CompletionInfo completionInfo = mApplicationSpecifiedCompletions[index];
+            mConnection.commitCompletion(completionInfo);
+            mConnection.endBatchEdit();
+            if (ProductionFlag.IS_EXPERIMENTAL) {
+                ResearchLogger.latinIME_pickApplicationSpecifiedCompletion(index,
+                        completionInfo.getText(), x, y);
             }
             return;
         }
@@ -1953,6 +1845,7 @@
         mExpectingUpdateSelection = true;
         commitChosenWord(suggestion, LastComposedWord.COMMIT_TYPE_MANUAL_PICK,
                 LastComposedWord.NOT_A_SEPARATOR);
+        mConnection.endBatchEdit();
         // Don't allow cancellation of manual pick
         mLastComposedWord.deactivate();
         mSpaceState = SPACE_STATE_PHANTOM;
@@ -1966,8 +1859,6 @@
         // - There is a dictionary and the word is not in it
         // Please note that if mSuggest is null, it means that everything is off: suggestion
         // and correction, so we shouldn't try to show the hint
-        // We used to look at mCorrectionMode here, but showing the hint should have nothing
-        // to do with the autocorrection setting.
         final boolean showingAddToDictionaryHint = index == 0 && mSuggest != null
                 // If there is no dictionary the hint should be shown.
                 && (!mSuggest.hasMainDictionary()
@@ -1977,20 +1868,11 @@
 
         Utils.Stats.onSeparator((char)Keyboard.CODE_SPACE, WordComposer.NOT_A_COORDINATE,
                 WordComposer.NOT_A_COORDINATE);
-        if (!showingAddToDictionaryHint) {
-            // If we're not showing the "Touch again to save", then show corrections again.
-            // In case the cursor position doesn't change, make sure we show the suggestions again.
-            updateBigramPredictions();
-            // Updating the predictions right away may be slow and feel unresponsive on slower
-            // terminals. On the other hand if we just postUpdateBigramPredictions() it will
-            // take a noticeable delay to update them which may feel uneasy.
+        if (showingAddToDictionaryHint && mIsUserDictionaryAvailable) {
+            mSuggestionsView.showAddToDictionaryHint(suggestion, mCurrentSettings.mHintToSaveText);
         } else {
-            if (mIsUserDictionaryAvailable) {
-                mSuggestionsView.showAddToDictionaryHint(
-                        suggestion, mSettingsValues.mHintToSaveText);
-            } else {
-                mHandler.postUpdateSuggestions();
-            }
+            // If we're not showing the "Touch again to save", then update the suggestion strip.
+            mHandler.postUpdateSuggestionStrip();
         }
     }
 
@@ -1999,22 +1881,11 @@
      */
     private void commitChosenWord(final CharSequence chosenWord, final int commitType,
             final int separatorCode) {
-        final InputConnection ic = getCurrentInputConnection();
-        if (ic != null) {
-            if (mSettingsValues.mEnableSuggestionSpanInsertion) {
-                final SuggestedWords suggestedWords = mSuggestionsView.getSuggestions();
-                ic.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan(
-                        this, chosenWord, suggestedWords, mIsMainDictionaryAvailable),
-                        1);
-                if (ProductionFlag.IS_EXPERIMENTAL) {
-                    ResearchLogger.latinIME_commitText(chosenWord);
-                }
-            } else {
-                ic.commitText(chosenWord, 1);
-                if (ProductionFlag.IS_EXPERIMENTAL) {
-                    ResearchLogger.latinIME_commitText(chosenWord);
-                }
-            }
+        final SuggestedWords suggestedWords = mSuggestionsView.getSuggestions();
+        mConnection.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan(
+                this, chosenWord, suggestedWords, mIsMainDictionaryAvailable), 1);
+        if (ProductionFlag.IS_EXPERIMENTAL) {
+            ResearchLogger.latinIME_commitText(chosenWord);
         }
         // Add the word to the user history dictionary
         final CharSequence prevWord = addToUserHistoryDictionary(chosenWord);
@@ -2026,42 +1897,11 @@
                 separatorCode, prevWord);
     }
 
-    public void updateBigramPredictions() {
-        if (mSuggest == null || !isSuggestionsRequested())
-            return;
-
-        if (!mSettingsValues.mBigramPredictionEnabled) {
-            setPunctuationSuggestions();
-            return;
-        }
-
-        final SuggestedWords suggestedWords;
-        if (mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM) {
-            final CharSequence prevWord = EditingUtils.getThisWord(getCurrentInputConnection(),
-                    mSettingsValues.mWordSeparators);
-            if (!TextUtils.isEmpty(prevWord)) {
-                suggestedWords = mSuggest.getBigramPredictions(prevWord);
-            } else {
-                suggestedWords = null;
-            }
-        } else {
-            suggestedWords = null;
-        }
-
-        if (null != suggestedWords && suggestedWords.size() > 0) {
-            // Explicitly supply an empty typed word (the no-second-arg version of
-            // showSuggestions will retrieve the word near the cursor, we don't want that here)
-            showSuggestions(suggestedWords, "");
-        } else {
-            clearSuggestions();
-        }
-    }
-
-    public void setPunctuationSuggestions() {
-        if (mSettingsValues.mBigramPredictionEnabled) {
+    private void setPunctuationSuggestions() {
+        if (mCurrentSettings.mBigramPredictionEnabled) {
             clearSuggestions();
         } else {
-            setSuggestions(mSettingsValues.mSuggestPuncList, false);
+            setSuggestions(mCurrentSettings.mSuggestPuncList, false);
         }
         setAutoCorrectionIndicator(false);
         setSuggestionStripShown(isSuggestionsStripVisible());
@@ -2070,22 +1910,15 @@
     private CharSequence addToUserHistoryDictionary(final CharSequence suggestion) {
         if (TextUtils.isEmpty(suggestion)) return null;
 
-        // Only auto-add to dictionary if auto-correct is ON. Otherwise we'll be
-        // adding words in situations where the user or application really didn't
-        // want corrections enabled or learned.
-        if (!(mCorrectionMode == Suggest.CORRECTION_FULL
-                || mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM)) {
-            return null;
-        }
+        // If correction is not enabled, we don't add words to the user history dictionary.
+        // That's to avoid unintended additions in some sensitive fields, or fields that
+        // expect to receive non-words.
+        if (!mCurrentSettings.mCorrectionEnabled) return null;
 
-        if (mUserHistoryDictionary != null) {
-            final InputConnection ic = getCurrentInputConnection();
-            final CharSequence prevWord;
-            if (null != ic) {
-                prevWord = EditingUtils.getPreviousWord(ic, mSettingsValues.mWordSeparators);
-            } else {
-                prevWord = null;
-            }
+        final UserHistoryDictionary userHistoryDictionary = mUserHistoryDictionary;
+        if (userHistoryDictionary != null) {
+            final CharSequence prevWord
+                    = mConnection.getNthPreviousWord(mCurrentSettings.mWordSeparators, 2);
             final String secondWord;
             if (mWordComposer.isAutoCapitalized() && !mWordComposer.isMostlyCaps()) {
                 secondWord = suggestion.toString().toLowerCase(
@@ -2098,95 +1931,36 @@
             final int maxFreq = AutoCorrection.getMaxFrequency(
                     mSuggest.getUnigramDictionaries(), suggestion);
             if (maxFreq == 0) return null;
-            mUserHistoryDictionary.addToUserHistory(null == prevWord ? null : prevWord.toString(),
+            userHistoryDictionary.addToUserHistory(null == prevWord ? null : prevWord.toString(),
                     secondWord, maxFreq > 0);
             return prevWord;
         }
         return null;
     }
 
-    public boolean isCursorTouchingWord() {
-        final InputConnection ic = getCurrentInputConnection();
-        if (ic == null) return false;
-        CharSequence before = ic.getTextBeforeCursor(1, 0);
-        CharSequence after = ic.getTextAfterCursor(1, 0);
-        if (!TextUtils.isEmpty(before) && !mSettingsValues.isWordSeparator(before.charAt(0))
-                && !mSettingsValues.isSymbolExcludedFromWordSeparators(before.charAt(0))) {
-            return true;
-        }
-        if (!TextUtils.isEmpty(after) && !mSettingsValues.isWordSeparator(after.charAt(0))
-                && !mSettingsValues.isSymbolExcludedFromWordSeparators(after.charAt(0))) {
-            return true;
-        }
-        return false;
-    }
-
-    // "ic" must not be null
-    private static boolean sameAsTextBeforeCursor(final InputConnection ic,
-            final CharSequence text) {
-        final CharSequence beforeText = ic.getTextBeforeCursor(text.length(), 0);
-        return TextUtils.equals(text, beforeText);
-    }
-
-    // "ic" must not be null
     /**
      * Check if the cursor is actually at the end of a word. If so, restart suggestions on this
      * word, else do nothing.
      */
-    private void restartSuggestionsOnWordBeforeCursorIfAtEndOfWord(
-            final InputConnection ic) {
-        // Bail out if the cursor is not at the end of a word (cursor must be preceded by
-        // non-whitespace, non-separator, non-start-of-text)
-        // Example ("|" is the cursor here) : <SOL>"|a" " |a" " | " all get rejected here.
-        final CharSequence textBeforeCursor = ic.getTextBeforeCursor(1, 0);
-        if (TextUtils.isEmpty(textBeforeCursor)
-                || mSettingsValues.isWordSeparator(textBeforeCursor.charAt(0))) return;
-
-        // Bail out if the cursor is in the middle of a word (cursor must be followed by whitespace,
-        // separator or end of line/text)
-        // Example: "test|"<EOL> "te|st" get rejected here
-        final CharSequence textAfterCursor = ic.getTextAfterCursor(1, 0);
-        if (!TextUtils.isEmpty(textAfterCursor)
-                && !mSettingsValues.isWordSeparator(textAfterCursor.charAt(0))) return;
-
-        // Bail out if word before cursor is 0-length or a single non letter (like an apostrophe)
-        // Example: " -|" gets rejected here but "e-|" and "e|" are okay
-        CharSequence word = EditingUtils.getWordAtCursor(ic, mSettingsValues.mWordSeparators);
-        // We don't suggest on leading single quotes, so we have to remove them from the word if
-        // it starts with single quotes.
-        while (!TextUtils.isEmpty(word) && Keyboard.CODE_SINGLE_QUOTE == word.charAt(0)) {
-            word = word.subSequence(1, word.length());
+    private void restartSuggestionsOnWordBeforeCursorIfAtEndOfWord() {
+        final CharSequence word = mConnection.getWordBeforeCursorIfAtEndOfWord(mCurrentSettings);
+        if (null != word) {
+            restartSuggestionsOnWordBeforeCursor(word);
         }
-        if (TextUtils.isEmpty(word)) return;
-        final char firstChar = word.charAt(0); // we just tested that word is not empty
-        if (word.length() == 1 && !Character.isLetter(firstChar)) return;
-
-        // We only suggest on words that start with a letter or a symbol that is excluded from
-        // word separators (see #handleCharacterWhileInBatchEdit).
-        if (!(isAlphabet(firstChar)
-                || mSettingsValues.isSymbolExcludedFromWordSeparators(firstChar))) {
-            return;
-        }
-
-        // Okay, we are at the end of a word. Restart suggestions.
-        restartSuggestionsOnWordBeforeCursor(ic, word);
     }
 
-    // "ic" must not be null
-    private void restartSuggestionsOnWordBeforeCursor(final InputConnection ic,
-            final CharSequence word) {
+    private void restartSuggestionsOnWordBeforeCursor(final CharSequence word) {
         mWordComposer.setComposingWord(word, mKeyboardSwitcher.getKeyboard());
         final int length = word.length();
-        ic.deleteSurroundingText(length, 0);
+        mConnection.deleteSurroundingText(length, 0);
         if (ProductionFlag.IS_EXPERIMENTAL) {
             ResearchLogger.latinIME_deleteSurroundingText(length);
         }
-        ic.setComposingText(word, 1);
-        mHandler.postUpdateSuggestions();
+        mConnection.setComposingText(word, 1);
+        mHandler.postUpdateSuggestionStrip();
     }
 
-    // "ic" must not be null
-    private void revertCommit(final InputConnection ic) {
+    private void revertCommit() {
         final CharSequence previousWord = mLastComposedWord.mPrevWord;
         final String originallyTypedWord = mLastComposedWord.mTypedWord;
         final CharSequence committedWord = mLastComposedWord.mCommittedWord;
@@ -2200,7 +1974,7 @@
                 throw new RuntimeException("revertCommit, but we are composing a word");
             }
             final String wordBeforeCursor =
-                    ic.getTextBeforeCursor(deleteLength, 0)
+                    mConnection.getTextBeforeCursor(deleteLength, 0)
                             .subSequence(0, cancelLength).toString();
             if (!TextUtils.equals(committedWord, wordBeforeCursor)) {
                 throw new RuntimeException("revertCommit check failed: we thought we were "
@@ -2208,7 +1982,7 @@
                         + "\", but before the cursor we found \"" + wordBeforeCursor + "\"");
             }
         }
-        ic.deleteSurroundingText(deleteLength, 0);
+        mConnection.deleteSurroundingText(deleteLength, 0);
         if (ProductionFlag.IS_EXPERIMENTAL) {
             ResearchLogger.latinIME_deleteSurroundingText(deleteLength);
         }
@@ -2216,122 +1990,58 @@
             mUserHistoryDictionary.cancelAddingUserHistory(
                     previousWord.toString(), committedWord.toString());
         }
-        if (0 == separatorLength || mLastComposedWord.didCommitTypedWord()) {
-            // This is the case when we cancel a manual pick.
-            // We should restart suggestion on the word right away.
-            mWordComposer.resumeSuggestionOnLastComposedWord(mLastComposedWord);
-            ic.setComposingText(originallyTypedWord, 1);
-        } else {
-            ic.commitText(originallyTypedWord, 1);
-            // Re-insert the separator
-            sendKeyCodePoint(mLastComposedWord.mSeparatorCode);
-            Utils.Stats.onSeparator(mLastComposedWord.mSeparatorCode, WordComposer.NOT_A_COORDINATE,
-                    WordComposer.NOT_A_COORDINATE);
-            if (ProductionFlag.IS_EXPERIMENTAL) {
-                ResearchLogger.latinIME_revertCommit(originallyTypedWord);
-            }
-            // Don't restart suggestion yet. We'll restart if the user deletes the
-            // separator.
+        mConnection.commitText(originallyTypedWord, 1);
+        // Re-insert the separator
+        sendKeyCodePoint(mLastComposedWord.mSeparatorCode);
+        Utils.Stats.onSeparator(mLastComposedWord.mSeparatorCode, WordComposer.NOT_A_COORDINATE,
+                WordComposer.NOT_A_COORDINATE);
+        if (ProductionFlag.IS_EXPERIMENTAL) {
+            ResearchLogger.latinIME_revertCommit(originallyTypedWord);
         }
+        // Don't restart suggestion yet. We'll restart if the user deletes the
+        // separator.
         mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
-        mHandler.cancelUpdateBigramPredictions();
-        mHandler.postUpdateSuggestions();
+        // We have a separator between the word and the cursor: we should show predictions.
+        mHandler.postUpdateSuggestionStrip();
     }
 
-    // "ic" must not be null
-    private boolean revertDoubleSpaceWhileInBatchEdit(final InputConnection ic) {
-        mHandler.cancelDoubleSpacesTimer();
-        // Here we test whether we indeed have a period and a space before us. This should not
-        // be needed, but it's there just in case something went wrong.
-        final CharSequence textBeforeCursor = ic.getTextBeforeCursor(2, 0);
-        if (!". ".equals(textBeforeCursor)) {
-            // Theoretically we should not be coming here if there isn't ". " before the
-            // cursor, but the application may be changing the text while we are typing, so
-            // anything goes. We should not crash.
-            Log.d(TAG, "Tried to revert double-space combo but we didn't find "
-                    + "\". \" just before the cursor.");
-            return false;
-        }
-        ic.deleteSurroundingText(2, 0);
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.latinIME_deleteSurroundingText(2);
-        }
-        ic.commitText("  ", 1);
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.latinIME_revertDoubleSpaceWhileInBatchEdit();
-        }
-        return true;
-    }
-
-    private static boolean revertSwapPunctuation(final InputConnection ic) {
-        // Here we test whether we indeed have a space and something else before us. This should not
-        // be needed, but it's there just in case something went wrong.
-        final CharSequence textBeforeCursor = ic.getTextBeforeCursor(2, 0);
-        // NOTE: This does not work with surrogate pairs. Hopefully when the keyboard is able to
-        // enter surrogate pairs this code will have been removed.
-        if (TextUtils.isEmpty(textBeforeCursor)
-                || (Keyboard.CODE_SPACE != textBeforeCursor.charAt(1))) {
-            // We may only come here if the application is changing the text while we are typing.
-            // This is quite a broken case, but not logically impossible, so we shouldn't crash,
-            // but some debugging log may be in order.
-            Log.d(TAG, "Tried to revert a swap of punctuation but we didn't "
-                    + "find a space just before the cursor.");
-            return false;
-        }
-        ic.beginBatchEdit();
-        ic.deleteSurroundingText(2, 0);
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.latinIME_deleteSurroundingText(2);
-        }
-        ic.commitText(" " + textBeforeCursor.subSequence(0, 1), 1);
-        if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.latinIME_revertSwapPunctuation();
-        }
-        ic.endBatchEdit();
-        return true;
-    }
-
+    // Used by the RingCharBuffer
     public boolean isWordSeparator(int code) {
-        return mSettingsValues.isWordSeparator(code);
-    }
-
-    public boolean preferCapitalization() {
-        return mWordComposer.isFirstCharCapitalized();
+        return mCurrentSettings.isWordSeparator(code);
     }
 
     // Notify that language or mode have been changed and toggleLanguage will update KeyboardID
-    // according to new language or mode.
+    // according to new language or mode. Called from SubtypeSwitcher.
     public void onRefreshKeyboard() {
         // When the device locale is changed in SetupWizard etc., this method may get called via
         // onConfigurationChanged before SoftInputWindow is shown.
         if (mKeyboardSwitcher.getKeyboardView() != null) {
             // Reload keyboard because the current language has been changed.
-            mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mSettingsValues);
+            mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mCurrentSettings);
         }
         initSuggest();
-        updateCorrectionMode();
         loadSettings();
         // Since we just changed languages, we should re-evaluate suggestions with whatever word
         // we are currently composing. If we are not composing anything, we may want to display
-        // predictions or punctuation signs (which is done by updateBigramPredictions anyway).
-        if (isCursorTouchingWord()) {
-            mHandler.postUpdateSuggestions();
-        } else {
-            mHandler.postUpdateBigramPredictions();
-        }
+        // predictions or punctuation signs (which is done by the updateSuggestionStrip anyway).
+        mHandler.postUpdateSuggestionStrip();
     }
 
     // TODO: Remove this method from {@link LatinIME} and move {@link FeedbackManager} to
-    // {@link KeyboardSwitcher}.
+    // {@link KeyboardSwitcher}. Called from KeyboardSwitcher
     public void hapticAndAudioFeedback(final int primaryCode) {
         mFeedbackManager.hapticAndAudioFeedback(primaryCode, mKeyboardSwitcher.getKeyboardView());
     }
 
+    // Callback called by PointerTracker through the KeyboardActionListener. This is called when a
+    // key is depressed; release matching call is onReleaseKey below.
     @Override
     public void onPressKey(int primaryCode) {
         mKeyboardSwitcher.onPressKey(primaryCode);
     }
 
+    // Callback by PointerTracker through the KeyboardActionListener. This is called when a key
+    // is released; press matching call is onPressKey above.
     @Override
     public void onReleaseKey(int primaryCode, boolean withSliding) {
         mKeyboardSwitcher.onReleaseKey(primaryCode, withSliding);
@@ -2352,12 +2062,9 @@
             // This is a stopgap solution to avoid leaving a high surrogate alone in a text view.
             // In the future, we need to deprecate deteleSurroundingText() and have a surrogate
             // pair-friendly way of deleting characters in InputConnection.
-            final InputConnection ic = getCurrentInputConnection();
-            if (null != ic) {
-                final CharSequence lastChar = ic.getTextBeforeCursor(1, 0);
-                if (!TextUtils.isEmpty(lastChar) && Character.isHighSurrogate(lastChar.charAt(0))) {
-                    ic.deleteSurroundingText(1, 0);
-                }
+            final CharSequence lastChar = mConnection.getTextBeforeCursor(1, 0);
+            if (!TextUtils.isEmpty(lastChar) && Character.isHighSurrogate(lastChar.charAt(0))) {
+                mConnection.deleteSurroundingText(1, 0);
             }
         }
     }
@@ -2375,29 +2082,11 @@
         }
     };
 
-    private void updateCorrectionMode() {
-        // TODO: cleanup messy flags
-        final boolean shouldAutoCorrect = mSettingsValues.mAutoCorrectEnabled
-                && !mInputAttributes.mInputTypeNoAutoCorrect;
-        mCorrectionMode = shouldAutoCorrect ? Suggest.CORRECTION_FULL : Suggest.CORRECTION_NONE;
-        mCorrectionMode = (mSettingsValues.mBigramSuggestionEnabled && shouldAutoCorrect)
-                ? Suggest.CORRECTION_FULL_BIGRAM : mCorrectionMode;
-    }
-
-    private void updateSuggestionVisibility(final Resources res) {
-        final String suggestionVisiblityStr = mSettingsValues.mShowSuggestionsSetting;
-        for (int visibility : SUGGESTION_VISIBILITY_VALUE_ARRAY) {
-            if (suggestionVisiblityStr.equals(res.getString(visibility))) {
-                mSuggestionVisibility = visibility;
-                break;
-            }
-        }
-    }
-
     private void launchSettings() {
         launchSettingsClass(SettingsActivity.class);
     }
 
+    // Called from debug code only
     public void launchDebugSettings() {
         launchSettingsClass(DebugSettingsActivity.class);
     }
@@ -2440,10 +2129,10 @@
         final AlertDialog.Builder builder = new AlertDialog.Builder(this)
                 .setItems(items, listener)
                 .setTitle(title);
-        showOptionDialogInternal(builder.create());
+        showOptionDialog(builder.create());
     }
 
-    private void showOptionDialogInternal(AlertDialog dialog) {
+    /* package */ void showOptionDialog(AlertDialog dialog) {
         final IBinder windowToken = mKeyboardSwitcher.getKeyboardView().getWindowToken();
         if (windowToken == null) return;
 
@@ -2470,13 +2159,13 @@
         final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
         final int keyboardMode = keyboard != null ? keyboard.mId.mMode : -1;
         p.println("  Keyboard mode = " + keyboardMode);
-        p.println("  mIsSuggestionsRequested=" + mInputAttributes.mIsSettingsSuggestionStripOn);
-        p.println("  mCorrectionMode=" + mCorrectionMode);
+        p.println("  mIsSuggestionsSuggestionsRequested = "
+                + mCurrentSettings.isSuggestionsRequested(mDisplayOrientation));
+        p.println("  mCorrectionEnabled=" + mCurrentSettings.mCorrectionEnabled);
         p.println("  isComposingWord=" + mWordComposer.isComposingWord());
-        p.println("  mAutoCorrectEnabled=" + mSettingsValues.mAutoCorrectEnabled);
-        p.println("  mSoundOn=" + mSettingsValues.mSoundOn);
-        p.println("  mVibrateOn=" + mSettingsValues.mVibrateOn);
-        p.println("  mKeyPreviewPopupOn=" + mSettingsValues.mKeyPreviewPopupOn);
-        p.println("  mInputAttributes=" + mInputAttributes.toString());
+        p.println("  mSoundOn=" + mCurrentSettings.mSoundOn);
+        p.println("  mVibrateOn=" + mCurrentSettings.mVibrateOn);
+        p.println("  mKeyPreviewPopupOn=" + mCurrentSettings.mKeyPreviewPopupOn);
+        p.println("  inputAttributes=" + mCurrentSettings.getInputAttributesDebugString());
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/LatinImeLogger.java b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
index dc0868e..e843848 100644
--- a/java/src/com/android/inputmethod/latin/LatinImeLogger.java
+++ b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
@@ -71,7 +71,7 @@
     public static void onStartSuggestion(CharSequence previousWords) {
     }
 
-    public static void onAddSuggestedWord(String word, int typeId, int dataType) {
+    public static void onAddSuggestedWord(String word, String sourceDictionaryId) {
     }
 
     public static void onSetKeyboard(Keyboard kb) {
diff --git a/java/src/com/android/inputmethod/latin/NativeUtils.java b/java/src/com/android/inputmethod/latin/NativeUtils.java
new file mode 100644
index 0000000..9cc2bc0
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/NativeUtils.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.inputmethod.latin;
+
+public class NativeUtils {
+    static {
+        JniUtils.loadNativeLibrary();
+    }
+
+    private NativeUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    /**
+     * This method just calls up libm's powf() directly.
+     */
+    public static native float powf(float x, float y);
+}
diff --git a/java/src/com/android/inputmethod/latin/ResearchLogger.java b/java/src/com/android/inputmethod/latin/ResearchLogger.java
index 66d6d58..2de0194 100644
--- a/java/src/com/android/inputmethod/latin/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/latin/ResearchLogger.java
@@ -16,37 +16,47 @@
 
 package com.android.inputmethod.latin;
 
+import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
 import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
 import android.inputmethodservice.InputMethodService;
 import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Process;
 import android.os.SystemClock;
-import android.preference.PreferenceManager;
 import android.text.TextUtils;
+import android.util.JsonWriter;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.widget.Toast;
 
 import com.android.inputmethod.keyboard.Key;
-import com.android.inputmethod.keyboard.KeyDetector;
 import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.internal.KeyboardState;
+import com.android.inputmethod.keyboard.KeyboardId;
+import com.android.inputmethod.latin.RichInputConnection.Range;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.define.ProductionFlag;
 
 import java.io.BufferedWriter;
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.FileWriter;
 import java.io.IOException;
-import java.io.PrintWriter;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.channels.FileChannel;
-import java.nio.charset.Charset;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
 import java.util.Map;
+import java.util.UUID;
 
 /**
  * Logs the use of the LatinIME keyboard.
@@ -58,198 +68,84 @@
  */
 public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChangeListener {
     private static final String TAG = ResearchLogger.class.getSimpleName();
-    private static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode";
     private static final boolean DEBUG = false;
+    private static final boolean OUTPUT_ENTIRE_BUFFER = false;  // true may disclose private info
+    /* package */ static boolean sIsLogging = false;
+    private static final int OUTPUT_FORMAT_VERSION = 1;
+    private static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode";
+    private static final String FILENAME_PREFIX = "researchLog";
+    private static final String FILENAME_SUFFIX = ".txt";
+    private static final JsonWriter NULL_JSON_WRITER = new JsonWriter(
+            new OutputStreamWriter(new NullOutputStream()));
+    private static final SimpleDateFormat TIMESTAMP_DATEFORMAT =
+            new SimpleDateFormat("yyyyMMddHHmmss", Locale.US);
 
-    private static final ResearchLogger sInstance = new ResearchLogger(new LogFileManager());
-    public static boolean sIsLogging = false;
-    /* package */ final Handler mLoggingHandler;
-    private InputMethodService mIms;
+    // constants related to specific log points
+    private static final String WHITESPACE_SEPARATORS = " \t\n\r";
+    private static final int MAX_INPUTVIEW_LENGTH_TO_CAPTURE = 8192; // must be >=1
+    private static final String PREF_RESEARCH_LOGGER_UUID_STRING = "pref_research_logger_uuid";
 
-    /**
-     * Isolates management of files. This variable should never be null, but can be changed
-     * to support testing.
-     */
-    /* package */ LogFileManager mLogFileManager;
+    private static final ResearchLogger sInstance = new ResearchLogger();
+    private HandlerThread mHandlerThread;
+    /* package */ Handler mLoggingHandler;
+    // to write to a different filename, e.g., for testing, set mFile before calling start()
+    private File mFilesDir;
+    /* package */ File mFile;
+    private JsonWriter mJsonWriter = NULL_JSON_WRITER; // should never be null
 
-    /**
-     * Manages the file(s) that stores the logs.
-     *
-     * Handles creation, deletion, and provides Readers, Writers, and InputStreams to access
-     * the logs.
-     */
-    /* package */ static class LogFileManager {
-        public static final String RESEARCH_LOG_FILENAME_KEY = "RESEARCH_LOG_FILENAME";
+    private int mLoggingState;
+    private static final int LOGGING_STATE_OFF = 0;
+    private static final int LOGGING_STATE_ON = 1;
+    private static final int LOGGING_STATE_STOPPING = 2;
+    private boolean mIsPasswordView = false;
 
-        private static final String DEFAULT_FILENAME = "researchLog.txt";
-        private static final long LOGFILE_PURGE_INTERVAL = 1000 * 60 * 60 * 24;
+    // digits entered by the user are replaced with this codepoint.
+    /* package for test */ static final int DIGIT_REPLACEMENT_CODEPOINT =
+            Character.codePointAt("\uE000", 0);  // U+E000 is in the "private-use area"
+    // U+E001 is in the "private-use area"
+    /* package for test */ static final String WORD_REPLACEMENT_STRING = "\uE001";
+    // set when LatinIME should ignore an onUpdateSelection() callback that
+    // arises from operations in this class
+    private static boolean sLatinIMEExpectingUpdateSelection = false;
 
-        protected InputMethodService mIms;
-        protected File mFile;
-        protected PrintWriter mPrintWriter;
+    // used to check whether words are not unique
+    private Suggest mSuggest;
+    private Dictionary mDictionary;
 
-        /* package */ LogFileManager() {
+    private static class NullOutputStream extends OutputStream {
+        /** {@inheritDoc} */
+        @Override
+        public void write(byte[] buffer, int offset, int count) {
+            // nop
         }
 
-        public void init(final InputMethodService ims) {
-            mIms = ims;
+        /** {@inheritDoc} */
+        @Override
+        public void write(byte[] buffer) {
+            // nop
         }
 
-        public synchronized void createLogFile() throws IOException {
-            createLogFile(DEFAULT_FILENAME);
-        }
-
-        public synchronized void createLogFile(final SharedPreferences prefs)
-                throws IOException {
-            final String filename =
-                    prefs.getString(RESEARCH_LOG_FILENAME_KEY, DEFAULT_FILENAME);
-            createLogFile(filename);
-        }
-
-        public synchronized void createLogFile(final String filename)
-                throws IOException {
-            if (mIms == null) {
-                final String msg = "InputMethodService is not configured.  Logging is off.";
-                Log.w(TAG, msg);
-                throw new IOException(msg);
-            }
-            final File filesDir = mIms.getFilesDir();
-            if (filesDir == null || !filesDir.exists()) {
-                final String msg = "Storage directory does not exist.  Logging is off.";
-                Log.w(TAG, msg);
-                throw new IOException(msg);
-            }
-            close();
-            final File file = new File(filesDir, filename);
-            mFile = file;
-            boolean append = true;
-            if (file.exists() && file.lastModified() + LOGFILE_PURGE_INTERVAL <
-                    System.currentTimeMillis()) {
-                append = false;
-            }
-            mPrintWriter = new PrintWriter(new BufferedWriter(new FileWriter(file, append)), true);
-        }
-
-        public synchronized boolean append(final String s) {
-            PrintWriter printWriter = mPrintWriter;
-            if (printWriter == null || !mFile.exists()) {
-                if (DEBUG) {
-                    Log.w(TAG, "PrintWriter is null... attempting to create default log file");
-                }
-                try {
-                    createLogFile();
-                    printWriter = mPrintWriter;
-                } catch (IOException e) {
-                    Log.w(TAG, "Failed to create log file.  Not logging.");
-                    return false;
-                }
-            }
-            printWriter.print(s);
-            printWriter.flush();
-            return !printWriter.checkError();
-        }
-
-        public synchronized void reset() {
-            if (mPrintWriter != null) {
-                mPrintWriter.close();
-                mPrintWriter = null;
-                if (DEBUG) {
-                    Log.d(TAG, "logfile closed");
-                }
-            }
-            if (mFile != null) {
-                mFile.delete();
-                if (DEBUG) {
-                    Log.d(TAG, "logfile deleted");
-                }
-                mFile = null;
-            }
-        }
-
-        public synchronized void close() {
-            if (mPrintWriter != null) {
-                mPrintWriter.close();
-                mPrintWriter = null;
-                mFile = null;
-                if (DEBUG) {
-                    Log.d(TAG, "logfile closed");
-                }
-            }
-        }
-
-        /* package */ synchronized void flush() {
-            if (mPrintWriter != null) {
-                mPrintWriter.flush();
-            }
-        }
-
-        /* package */ synchronized String getContents() {
-            final File file = mFile;
-            if (file == null) {
-                return "";
-            }
-            if (mPrintWriter != null) {
-                mPrintWriter.flush();
-            }
-            FileInputStream stream = null;
-            FileChannel fileChannel = null;
-            String s = "";
-            try {
-                stream = new FileInputStream(file);
-                fileChannel = stream.getChannel();
-                final ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());
-                fileChannel.read(byteBuffer);
-                byteBuffer.rewind();
-                CharBuffer charBuffer = Charset.defaultCharset().decode(byteBuffer);
-                s = charBuffer.toString();
-            } catch (IOException e) {
-                e.printStackTrace();
-            } finally {
-                try {
-                    if (fileChannel != null) {
-                        fileChannel.close();
-                    }
-                } catch (IOException e) {
-                    e.printStackTrace();
-                } finally {
-                    try {
-                        if (stream != null) {
-                            stream.close();
-                        }
-                    } catch (IOException e) {
-                        e.printStackTrace();
-                    }
-                }
-            }
-            return s;
+        @Override
+        public void write(int oneByte) {
         }
     }
 
-    private ResearchLogger(final LogFileManager logFileManager) {
-        final HandlerThread handlerThread = new HandlerThread("ResearchLogger logging task",
-                Process.THREAD_PRIORITY_BACKGROUND);
-        handlerThread.start();
-        mLoggingHandler = new Handler(handlerThread.getLooper());
-        mLogFileManager = logFileManager;
+    private ResearchLogger() {
+        mLoggingState = LOGGING_STATE_OFF;
     }
 
     public static ResearchLogger getInstance() {
         return sInstance;
     }
 
-    public static void init(final InputMethodService ims, final SharedPreferences prefs) {
-        sInstance.initInternal(ims, prefs);
-    }
-
-    /* package */ void initInternal(final InputMethodService ims, final SharedPreferences prefs) {
-        mIms = ims;
-        final LogFileManager logFileManager = mLogFileManager;
-        if (logFileManager != null) {
-            logFileManager.init(ims);
-            try {
-                logFileManager.createLogFile(prefs);
-            } catch (IOException e) {
-                e.printStackTrace();
+    public void init(final InputMethodService ims, final SharedPreferences prefs) {
+        assert ims != null;
+        if (ims == null) {
+            Log.w(TAG, "IMS is null; logging is off");
+        } else {
+            mFilesDir = ims.getFilesDir();
+            if (mFilesDir == null || !mFilesDir.exists()) {
+                Log.w(TAG, "IME storage directory does not exist.");
             }
         }
         if (prefs != null) {
@@ -258,173 +154,125 @@
         }
     }
 
-    /**
-     * Represents a category of logging events that share the same subfield structure.
-     */
-    private static enum LogGroup {
-        MOTION_EVENT("m"),
-        KEY("k"),
-        CORRECTION("c"),
-        STATE_CHANGE("s"),
-        UNSTRUCTURED("u");
-
-        private final String mLogString;
-
-        private LogGroup(final String logString) {
-            mLogString = logString;
+    public synchronized void start() {
+        Log.d(TAG, "start called");
+        if (!sIsLogging) {
+            // Log.w(TAG, "not in usability mode; not logging");
+            return;
         }
-    }
-
-    public void logMotionEvent(final int action, final long eventTime, final int id,
-            final int x, final int y, final float size, final float pressure) {
-        final String eventTag;
-        switch (action) {
-            case MotionEvent.ACTION_CANCEL: eventTag = "[Cancel]"; break;
-            case MotionEvent.ACTION_UP: eventTag = "[Up]"; break;
-            case MotionEvent.ACTION_DOWN: eventTag = "[Down]"; break;
-            case MotionEvent.ACTION_POINTER_UP: eventTag = "[PointerUp]"; break;
-            case MotionEvent.ACTION_POINTER_DOWN: eventTag = "[PointerDown]"; break;
-            case MotionEvent.ACTION_MOVE: eventTag = "[Move]"; break;
-            case MotionEvent.ACTION_OUTSIDE: eventTag = "[Outside]"; break;
-            default: eventTag = "[Action" + action + "]"; break;
-        }
-        if (!TextUtils.isEmpty(eventTag)) {
-            final StringBuilder sb = new StringBuilder();
-            sb.append(eventTag);
-            sb.append('\t'); sb.append(eventTime);
-            sb.append('\t'); sb.append(id);
-            sb.append('\t'); sb.append(x);
-            sb.append('\t'); sb.append(y);
-            sb.append('\t'); sb.append(size);
-            sb.append('\t'); sb.append(pressure);
-            write(LogGroup.MOTION_EVENT, sb.toString());
-        }
-    }
-
-    public void logKeyEvent(final int code, final int x, final int y) {
-        final StringBuilder sb = new StringBuilder();
-        sb.append(Keyboard.printableCode(code));
-        sb.append('\t'); sb.append(x);
-        sb.append('\t'); sb.append(y);
-        write(LogGroup.KEY, sb.toString());
-    }
-
-    public void logCorrection(final String subgroup, final String before, final String after,
-            final int position) {
-        final StringBuilder sb = new StringBuilder();
-        sb.append(subgroup);
-        sb.append('\t'); sb.append(before);
-        sb.append('\t'); sb.append(after);
-        sb.append('\t'); sb.append(position);
-        write(LogGroup.CORRECTION, sb.toString());
-    }
-
-    public void logStateChange(final String subgroup, final String details) {
-        write(LogGroup.STATE_CHANGE, subgroup + "\t" + details);
-    }
-
-    public static class UnsLogGroup {
-        private static final boolean DEFAULT_ENABLED = true;
-
-        private static final boolean KEYBOARDSTATE_ONCANCELINPUT_ENABLED = DEFAULT_ENABLED;
-        private static final boolean KEYBOARDSTATE_ONCODEINPUT_ENABLED = DEFAULT_ENABLED;
-        private static final boolean KEYBOARDSTATE_ONLONGPRESSTIMEOUT_ENABLED = DEFAULT_ENABLED;
-        private static final boolean KEYBOARDSTATE_ONPRESSKEY_ENABLED = DEFAULT_ENABLED;
-        private static final boolean KEYBOARDSTATE_ONRELEASEKEY_ENABLED = DEFAULT_ENABLED;
-        private static final boolean LATINIME_COMMITCURRENTAUTOCORRECTION_ENABLED = DEFAULT_ENABLED;
-        private static final boolean LATINIME_COMMITTEXT_ENABLED = DEFAULT_ENABLED;
-        private static final boolean LATINIME_DELETESURROUNDINGTEXT_ENABLED = DEFAULT_ENABLED;
-        private static final boolean LATINIME_DOUBLESPACEAUTOPERIOD_ENABLED = DEFAULT_ENABLED;
-        private static final boolean LATINIME_ONDISPLAYCOMPLETIONS_ENABLED = DEFAULT_ENABLED;
-        private static final boolean LATINIME_ONSTARTINPUTVIEWINTERNAL_ENABLED = DEFAULT_ENABLED;
-        private static final boolean LATINIME_ONUPDATESELECTION_ENABLED = DEFAULT_ENABLED;
-        private static final boolean LATINIME_PERFORMEDITORACTION_ENABLED = DEFAULT_ENABLED;
-        private static final boolean LATINIME_PICKAPPLICATIONSPECIFIEDCOMPLETION_ENABLED
-                = DEFAULT_ENABLED;
-        private static final boolean LATINIME_PICKPUNCTUATIONSUGGESTION_ENABLED = DEFAULT_ENABLED;
-        private static final boolean LATINIME_PICKSUGGESTIONMANUALLY_ENABLED = DEFAULT_ENABLED;
-        private static final boolean LATINIME_REVERTCOMMIT_ENABLED = DEFAULT_ENABLED;
-        private static final boolean LATINIME_REVERTDOUBLESPACEWHILEINBATCHEDIT_ENABLED
-                = DEFAULT_ENABLED;
-        private static final boolean LATINIME_REVERTSWAPPUNCTUATION_ENABLED = DEFAULT_ENABLED;
-        private static final boolean LATINIME_SENDKEYCODEPOINT_ENABLED = DEFAULT_ENABLED;
-        private static final boolean LATINIME_SWAPSWAPPERANDSPACEWHILEINBATCHEDIT_ENABLED
-                = DEFAULT_ENABLED;
-        private static final boolean LATINIME_SWITCHTOKEYBOARDVIEW_ENABLED = DEFAULT_ENABLED;
-        private static final boolean LATINKEYBOARDVIEW_ONLONGPRESS_ENABLED = DEFAULT_ENABLED;
-        private static final boolean LATINKEYBOARDVIEW_ONPROCESSMOTIONEVENT_ENABLED
-                = DEFAULT_ENABLED;
-        private static final boolean LATINKEYBOARDVIEW_SETKEYBOARD_ENABLED = DEFAULT_ENABLED;
-        private static final boolean POINTERTRACKER_CALLLISTENERONCANCELINPUT_ENABLED
-                = DEFAULT_ENABLED;
-        private static final boolean POINTERTRACKER_CALLLISTENERONCODEINPUT_ENABLED
-                = DEFAULT_ENABLED;
-        private static final boolean
-                POINTERTRACKER_CALLLISTENERONPRESSANDCHECKKEYBOARDLAYOUTCHANGE_ENABLED
-                = DEFAULT_ENABLED;
-        private static final boolean POINTERTRACKER_CALLLISTENERONRELEASE_ENABLED = DEFAULT_ENABLED;
-        private static final boolean POINTERTRACKER_ONDOWNEVENT_ENABLED = DEFAULT_ENABLED;
-        private static final boolean POINTERTRACKER_ONMOVEEVENT_ENABLED = DEFAULT_ENABLED;
-        private static final boolean SUDDENJUMPINGTOUCHEVENTHANDLER_ONTOUCHEVENT_ENABLED
-                = DEFAULT_ENABLED;
-        private static final boolean SUGGESTIONSVIEW_SETSUGGESTIONS_ENABLED = DEFAULT_ENABLED;
-    }
-
-    public static void logUnstructured(String logGroup, final String details) {
-        // TODO: improve performance by making entire class static and/or implementing natively
-        getInstance().write(LogGroup.UNSTRUCTURED, logGroup + "\t" + details);
-    }
-
-    private void write(final LogGroup logGroup, final String log) {
-        // TODO: rewrite in native for better performance
-        mLoggingHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                final long currentTime = System.currentTimeMillis();
-                final long upTime = SystemClock.uptimeMillis();
-                final StringBuilder builder = new StringBuilder();
-                builder.append(currentTime);
-                builder.append('\t'); builder.append(upTime);
-                builder.append('\t'); builder.append(logGroup.mLogString);
-                builder.append('\t'); builder.append(log);
-                builder.append('\n');
-                if (DEBUG) {
-                    Log.d(TAG, "Write: " + '[' + logGroup.mLogString + ']' + log);
+        if (mFilesDir == null || !mFilesDir.exists()) {
+            Log.w(TAG, "IME storage directory does not exist.  Cannot start logging.");
+        } else {
+            if (mHandlerThread == null || !mHandlerThread.isAlive()) {
+                mHandlerThread = new HandlerThread("ResearchLogger logging task",
+                        Process.THREAD_PRIORITY_BACKGROUND);
+                mHandlerThread.start();
+                mLoggingHandler = null;
+                mLoggingState = LOGGING_STATE_OFF;
+            }
+            if (mLoggingHandler == null) {
+                mLoggingHandler = new Handler(mHandlerThread.getLooper());
+                mLoggingState = LOGGING_STATE_OFF;
+            }
+            if (mFile == null) {
+                final String timestampString = TIMESTAMP_DATEFORMAT.format(new Date());
+                mFile = new File(mFilesDir, FILENAME_PREFIX + timestampString + FILENAME_SUFFIX);
+            }
+            if (mLoggingState == LOGGING_STATE_OFF) {
+                try {
+                    mJsonWriter = new JsonWriter(new BufferedWriter(new FileWriter(mFile)));
+                    mJsonWriter.setLenient(true);
+                    mJsonWriter.beginArray();
+                    mLoggingState = LOGGING_STATE_ON;
+                } catch (IOException e) {
+                    Log.w(TAG, "cannot start JsonWriter");
+                    mJsonWriter = NULL_JSON_WRITER;
+                    e.printStackTrace();
                 }
-                final String s = builder.toString();
-                if (mLogFileManager.append(s)) {
-                    // success
-                } else {
-                    if (DEBUG) {
-                        Log.w(TAG, "Unable to write to log.");
-                    }
-                    // perhaps logfile was deleted.  try to recreate and relog.
+            }
+        }
+    }
+
+    public synchronized void stop() {
+        Log.d(TAG, "stop called");
+        if (mLoggingHandler != null && mLoggingState == LOGGING_STATE_ON) {
+            mLoggingState = LOGGING_STATE_STOPPING;
+            flushEventQueue(true);
+            // put this in the Handler queue so pending writes are processed first.
+            mLoggingHandler.post(new Runnable() {
+                @Override
+                public void run() {
                     try {
-                        mLogFileManager.createLogFile(PreferenceManager
-                                .getDefaultSharedPreferences(mIms));
-                        mLogFileManager.append(s);
+                        Log.d(TAG, "closing jsonwriter");
+                        mJsonWriter.endArray();
+                        mJsonWriter.flush();
+                        mJsonWriter.close();
+                    } catch (IllegalStateException e1) {
+                        // assume that this is just the json not being terminated properly.
+                        // ignore
+                        e1.printStackTrace();
                     } catch (IOException e) {
                         e.printStackTrace();
+                    } finally {
+                        mJsonWriter = NULL_JSON_WRITER;
+                        mFile = null;
+                        mLoggingState = LOGGING_STATE_OFF;
+                        if (DEBUG) {
+                            Log.d(TAG, "logfile closed");
+                        }
+                        Log.d(TAG, "finished stop(), notifying");
+                        synchronized (ResearchLogger.this) {
+                            ResearchLogger.this.notify();
+                        }
                     }
                 }
+            });
+            try {
+                wait();
+            } catch (InterruptedException e) {
+                e.printStackTrace();
             }
-        });
+        }
     }
 
-    public void clearAll() {
-        mLoggingHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                if (DEBUG) {
-                    Log.d(TAG, "Delete log file.");
+    public synchronized boolean abort() {
+        Log.d(TAG, "abort called");
+        boolean isLogFileDeleted = false;
+        if (mLoggingHandler != null && mLoggingState == LOGGING_STATE_ON) {
+            mLoggingState = LOGGING_STATE_STOPPING;
+            try {
+                Log.d(TAG, "closing jsonwriter");
+                mJsonWriter.endArray();
+                mJsonWriter.close();
+            } catch (IllegalStateException e1) {
+                // assume that this is just the json not being terminated properly.
+                // ignore
+                e1.printStackTrace();
+            } catch (IOException e) {
+                e.printStackTrace();
+            } finally {
+                mJsonWriter = NULL_JSON_WRITER;
+                // delete file
+                final boolean isDeleted = mFile.delete();
+                if (isDeleted) {
+                    isLogFileDeleted = true;
                 }
-                mLogFileManager.reset();
+                mFile = null;
+                mLoggingState = LOGGING_STATE_OFF;
+                if (DEBUG) {
+                    Log.d(TAG, "logfile closed");
+                }
             }
-        });
+        }
+        return isLogFileDeleted;
     }
 
-    /* package */ LogFileManager getLogFileManager() {
-        return mLogFileManager;
+    /* package */ synchronized void flush() {
+        try {
+            mJsonWriter.flush();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
     }
 
     @Override
@@ -433,325 +281,803 @@
             return;
         }
         sIsLogging = prefs.getBoolean(PREF_USABILITY_STUDY_MODE, false);
-    }
-
-    public static void keyboardState_onCancelInput(final boolean isSinglePointer,
-            final KeyboardState keyboardState) {
-        if (UnsLogGroup.KEYBOARDSTATE_ONCANCELINPUT_ENABLED) {
-            final String s = "onCancelInput: single=" + isSinglePointer + " " + keyboardState;
-            logUnstructured("KeyboardState_onCancelInput", s);
+        if (sIsLogging == false) {
+            abort();
         }
     }
 
-    public static void keyboardState_onCodeInput(
-            final int code, final boolean isSinglePointer, final int autoCaps,
-            final KeyboardState keyboardState) {
-        if (UnsLogGroup.KEYBOARDSTATE_ONCODEINPUT_ENABLED) {
-            final String s = "onCodeInput: code=" + Keyboard.printableCode(code)
-                    + " single=" + isSinglePointer
-                    + " autoCaps=" + autoCaps + " " + keyboardState;
-            logUnstructured("KeyboardState_onCodeInput", s);
-        }
-    }
-
-    public static void keyboardState_onLongPressTimeout(final int code,
-            final KeyboardState keyboardState) {
-        if (UnsLogGroup.KEYBOARDSTATE_ONLONGPRESSTIMEOUT_ENABLED) {
-            final String s = "onLongPressTimeout: code=" + Keyboard.printableCode(code) + " "
-                    + keyboardState;
-            logUnstructured("KeyboardState_onLongPressTimeout", s);
-        }
-    }
-
-    public static void keyboardState_onPressKey(final int code,
-            final KeyboardState keyboardState) {
-        if (UnsLogGroup.KEYBOARDSTATE_ONPRESSKEY_ENABLED) {
-            final String s = "onPressKey: code=" + Keyboard.printableCode(code) + " "
-                    + keyboardState;
-            logUnstructured("KeyboardState_onPressKey", s);
-        }
-    }
-
-    public static void keyboardState_onReleaseKey(final KeyboardState keyboardState, final int code,
-            final boolean withSliding) {
-        if (UnsLogGroup.KEYBOARDSTATE_ONRELEASEKEY_ENABLED) {
-            final String s = "onReleaseKey: code=" + Keyboard.printableCode(code)
-                    + " sliding=" + withSliding + " " + keyboardState;
-            logUnstructured("KeyboardState_onReleaseKey", s);
-        }
-    }
-
-    public static void latinIME_commitCurrentAutoCorrection(final String typedWord,
-            final String autoCorrection) {
-        if (UnsLogGroup.LATINIME_COMMITCURRENTAUTOCORRECTION_ENABLED) {
-            if (typedWord.equals(autoCorrection)) {
-                getInstance().logCorrection("[----]", typedWord, autoCorrection, -1);
-            } else {
-                getInstance().logCorrection("[Auto]", typedWord, autoCorrection, -1);
-            }
-        }
-    }
-
-    public static void latinIME_commitText(final CharSequence typedWord) {
-        if (UnsLogGroup.LATINIME_COMMITTEXT_ENABLED) {
-            logUnstructured("LatinIME_commitText", typedWord.toString());
-        }
-    }
-
-    public static void latinIME_deleteSurroundingText(final int length) {
-        if (UnsLogGroup.LATINIME_DELETESURROUNDINGTEXT_ENABLED) {
-            logUnstructured("LatinIME_deleteSurroundingText", String.valueOf(length));
-        }
-    }
-
-    public static void latinIME_doubleSpaceAutoPeriod() {
-        if (UnsLogGroup.LATINIME_DOUBLESPACEAUTOPERIOD_ENABLED) {
-            logUnstructured("LatinIME_doubleSpaceAutoPeriod", "");
-        }
-    }
-
-    public static void latinIME_onDisplayCompletions(
-            final CompletionInfo[] applicationSpecifiedCompletions) {
-        if (UnsLogGroup.LATINIME_ONDISPLAYCOMPLETIONS_ENABLED) {
-            final StringBuilder builder = new StringBuilder();
-            builder.append("Received completions:");
-            if (applicationSpecifiedCompletions != null) {
-                for (int i = 0; i < applicationSpecifiedCompletions.length; i++) {
-                    builder.append("  #");
-                    builder.append(i);
-                    builder.append(": ");
-                    builder.append(applicationSpecifiedCompletions[i]);
-                    builder.append("\n");
+    /* package */ void presentResearchDialog(final LatinIME latinIME) {
+        final CharSequence title = latinIME.getString(R.string.english_ime_research_log);
+        final CharSequence[] items = new CharSequence[] {
+                latinIME.getString(R.string.note_timestamp_for_researchlog),
+                latinIME.getString(R.string.do_not_log_this_session),
+        };
+        final DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface di, int position) {
+                di.dismiss();
+                switch (position) {
+                    case 0:
+                        ResearchLogger.getInstance().userTimestamp();
+                        Toast.makeText(latinIME, R.string.notify_recorded_timestamp,
+                                Toast.LENGTH_LONG).show();
+                        break;
+                    case 1:
+                        Toast toast = Toast.makeText(latinIME,
+                                R.string.notify_session_log_deleting, Toast.LENGTH_LONG);
+                        toast.show();
+                        final ResearchLogger logger = ResearchLogger.getInstance();
+                        boolean isLogDeleted = logger.abort();
+                        toast.cancel();
+                        if (isLogDeleted) {
+                            Toast.makeText(latinIME, R.string.notify_session_log_deleted,
+                                    Toast.LENGTH_LONG).show();
+                        } else {
+                            Toast.makeText(latinIME,
+                                    R.string.notify_session_log_not_deleted, Toast.LENGTH_LONG)
+                                    .show();
+                        }
+                        break;
                 }
             }
-            logUnstructured("LatinIME_onDisplayCompletions", builder.toString());
+        };
+        final AlertDialog.Builder builder = new AlertDialog.Builder(latinIME)
+                .setItems(items, listener)
+                .setTitle(title);
+        latinIME.showOptionDialog(builder.create());
+    }
+
+    public void initSuggest(Suggest suggest) {
+        mSuggest = suggest;
+    }
+
+    private void setIsPasswordView(boolean isPasswordView) {
+        mIsPasswordView = isPasswordView;
+    }
+
+    private boolean isAllowedToLog() {
+        return mLoggingState == LOGGING_STATE_ON && !mIsPasswordView;
+    }
+
+    private static final String CURRENT_TIME_KEY = "_ct";
+    private static final String UPTIME_KEY = "_ut";
+    private static final String EVENT_TYPE_KEY = "_ty";
+    private static final Object[] EVENTKEYS_NULLVALUES = {};
+
+    private LogUnit mCurrentLogUnit = new LogUnit();
+
+    /**
+     * Buffer a research log event, flagging it as privacy-sensitive.
+     *
+     * This event contains potentially private information.  If the word that this event is a part
+     * of is determined to be privacy-sensitive, then this event should not be included in the
+     * output log.  The system waits to output until the containing word is known.
+     *
+     * @param keys an array containing a descriptive name for the event, followed by the keys
+     * @param values an array of values, either a String or Number.  length should be one
+     * less than the keys array
+     */
+    private synchronized void enqueuePotentiallyPrivateEvent(final String[] keys,
+            final Object[] values) {
+        assert values.length + 1 == keys.length;
+        mCurrentLogUnit.addLogAtom(keys, values, true);
+    }
+
+    /**
+     * Buffer a research log event, flaggint it as not privacy-sensitive.
+     *
+     * This event contains no potentially private information.  Even if the word that this event
+     * is privacy-sensitive, this event can still safely be sent to the output log.  The system
+     * waits until the containing word is known so that this event can be written in the proper
+     * temporal order with other events that may be privacy sensitive.
+     *
+     * @param keys an array containing a descriptive name for the event, followed by the keys
+     * @param values an array of values, either a String or Number.  length should be one
+     * less than the keys array
+     */
+    private synchronized void enqueueEvent(final String[] keys, final Object[] values) {
+        assert values.length + 1 == keys.length;
+        mCurrentLogUnit.addLogAtom(keys, values, false);
+    }
+
+    // Used to track how often words are logged.  Too-frequent logging can leak
+    // semantics, disclosing private data.
+    /* package for test */ static class LoggingFrequencyState {
+        private static final int DEFAULT_WORD_LOG_FREQUENCY = 10;
+        private int mWordsRemainingToSkip;
+        private final int mFrequency;
+
+        /**
+         * Tracks how often words may be uploaded.
+         *
+         * @param frequency 1=Every word, 2=Every other word, etc.
+         */
+        public LoggingFrequencyState(int frequency) {
+            mFrequency = frequency;
+            mWordsRemainingToSkip = mFrequency;
+        }
+
+        public void onWordLogged() {
+            mWordsRemainingToSkip = mFrequency;
+        }
+
+        public void onWordNotLogged() {
+            if (mWordsRemainingToSkip > 1) {
+                mWordsRemainingToSkip--;
+            }
+        }
+
+        public boolean isSafeToLog() {
+            return mWordsRemainingToSkip <= 1;
         }
     }
 
+    /* package for test */ LoggingFrequencyState mLoggingFrequencyState =
+            new LoggingFrequencyState(LoggingFrequencyState.DEFAULT_WORD_LOG_FREQUENCY);
+
+    /* package for test */ boolean isPrivacyThreat(String word) {
+        // Current checks:
+        // - Word not in dictionary
+        // - Word contains numbers
+        // - Privacy-safe word not logged recently
+        if (TextUtils.isEmpty(word)) {
+            return false;
+        }
+        if (!mLoggingFrequencyState.isSafeToLog()) {
+            return true;
+        }
+        final int length = word.length();
+        boolean hasLetter = false;
+        for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
+            final int codePoint = Character.codePointAt(word, i);
+            if (Character.isDigit(codePoint)) {
+                return true;
+            }
+            if (Character.isLetter(codePoint)) {
+                hasLetter = true;
+                break; // Word may contain digits, but will only be allowed if in the dictionary.
+            }
+        }
+        if (hasLetter) {
+            if (mDictionary == null && mSuggest != null && mSuggest.hasMainDictionary()) {
+                mDictionary = mSuggest.getMainDictionary();
+            }
+            if (mDictionary == null) {
+                // Can't access dictionary.  Assume privacy threat.
+                return true;
+            }
+            return !(mDictionary.isValidWord(word));
+        }
+        // No letters, no numbers.  Punctuation, space, or something else.
+        return false;
+    }
+
+    private void onWordComplete(String word) {
+        final boolean isPrivacyThreat = isPrivacyThreat(word);
+        flushEventQueue(isPrivacyThreat);
+        if (isPrivacyThreat) {
+            mLoggingFrequencyState.onWordNotLogged();
+        } else {
+            mLoggingFrequencyState.onWordLogged();
+        }
+    }
+
+    /**
+     * Write out enqueued LogEvents to the log, possibly dropping privacy sensitive events.
+     */
+    /* package for test */ synchronized void flushEventQueue(
+            boolean removePotentiallyPrivateEvents) {
+        if (isAllowedToLog()) {
+            mCurrentLogUnit.setRemovePotentiallyPrivateEvents(removePotentiallyPrivateEvents);
+            mLoggingHandler.post(mCurrentLogUnit);
+        }
+        mCurrentLogUnit = new LogUnit();
+    }
+
+    private synchronized void outputEvent(final String[] keys, final Object[] values) {
+        try {
+            mJsonWriter.beginObject();
+            mJsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis());
+            mJsonWriter.name(UPTIME_KEY).value(SystemClock.uptimeMillis());
+            mJsonWriter.name(EVENT_TYPE_KEY).value(keys[0]);
+            final int length = values.length;
+            for (int i = 0; i < length; i++) {
+                mJsonWriter.name(keys[i + 1]);
+                Object value = values[i];
+                if (value instanceof String) {
+                    mJsonWriter.value((String) value);
+                } else if (value instanceof Number) {
+                    mJsonWriter.value((Number) value);
+                } else if (value instanceof Boolean) {
+                    mJsonWriter.value((Boolean) value);
+                } else if (value instanceof CompletionInfo[]) {
+                    CompletionInfo[] ci = (CompletionInfo[]) value;
+                    mJsonWriter.beginArray();
+                    for (int j = 0; j < ci.length; j++) {
+                        mJsonWriter.value(ci[j].toString());
+                    }
+                    mJsonWriter.endArray();
+                } else if (value instanceof SharedPreferences) {
+                    SharedPreferences prefs = (SharedPreferences) value;
+                    mJsonWriter.beginObject();
+                    for (Map.Entry<String,?> entry : prefs.getAll().entrySet()) {
+                        mJsonWriter.name(entry.getKey());
+                        final Object innerValue = entry.getValue();
+                        if (innerValue == null) {
+                            mJsonWriter.nullValue();
+                        } else if (innerValue instanceof Boolean) {
+                            mJsonWriter.value((Boolean) innerValue);
+                        } else if (innerValue instanceof Number) {
+                            mJsonWriter.value((Number) innerValue);
+                        } else {
+                            mJsonWriter.value(innerValue.toString());
+                        }
+                    }
+                    mJsonWriter.endObject();
+                } else if (value instanceof Key[]) {
+                    Key[] keyboardKeys = (Key[]) value;
+                    mJsonWriter.beginArray();
+                    for (Key keyboardKey : keyboardKeys) {
+                        mJsonWriter.beginObject();
+                        mJsonWriter.name("code").value(keyboardKey.mCode);
+                        mJsonWriter.name("altCode").value(keyboardKey.mAltCode);
+                        mJsonWriter.name("x").value(keyboardKey.mX);
+                        mJsonWriter.name("y").value(keyboardKey.mY);
+                        mJsonWriter.name("w").value(keyboardKey.mWidth);
+                        mJsonWriter.name("h").value(keyboardKey.mHeight);
+                        mJsonWriter.endObject();
+                    }
+                    mJsonWriter.endArray();
+                } else if (value instanceof SuggestedWords) {
+                    SuggestedWords words = (SuggestedWords) value;
+                    mJsonWriter.beginObject();
+                    mJsonWriter.name("typedWordValid").value(words.mTypedWordValid);
+                    mJsonWriter.name("willAutoCorrect").value(words.mWillAutoCorrect);
+                    mJsonWriter.name("isPunctuationSuggestions")
+                        .value(words.mIsPunctuationSuggestions);
+                    mJsonWriter.name("isObsoleteSuggestions")
+                        .value(words.mIsObsoleteSuggestions);
+                    mJsonWriter.name("isPrediction")
+                        .value(words.mIsPrediction);
+                    mJsonWriter.name("words");
+                    mJsonWriter.beginArray();
+                    final int size = words.size();
+                    for (int j = 0; j < size; j++) {
+                        SuggestedWordInfo wordInfo = words.getWordInfo(j);
+                        mJsonWriter.value(wordInfo.toString());
+                    }
+                    mJsonWriter.endArray();
+                    mJsonWriter.endObject();
+                } else if (value == null) {
+                    mJsonWriter.nullValue();
+                } else {
+                    Log.w(TAG, "Unrecognized type to be logged: " +
+                            (value == null ? "<null>" : value.getClass().getName()));
+                    mJsonWriter.nullValue();
+                }
+            }
+            mJsonWriter.endObject();
+        } catch (IOException e) {
+            e.printStackTrace();
+            Log.w(TAG, "Error in JsonWriter; disabling logging");
+            try {
+                mJsonWriter.close();
+            } catch (IllegalStateException e1) {
+                // assume that this is just the json not being terminated properly.
+                // ignore
+            } catch (IOException e1) {
+                e1.printStackTrace();
+            } finally {
+                mJsonWriter = NULL_JSON_WRITER;
+            }
+        }
+    }
+
+    private static class LogUnit implements Runnable {
+        private final List<String[]> mKeysList = new ArrayList<String[]>();
+        private final List<Object[]> mValuesList = new ArrayList<Object[]>();
+        private final List<Boolean> mIsPotentiallyPrivate = new ArrayList<Boolean>();
+        private boolean mRemovePotentiallyPrivateEvents = true;
+
+        private void addLogAtom(final String[] keys, final Object[] values,
+                final Boolean isPotentiallyPrivate) {
+            mKeysList.add(keys);
+            mValuesList.add(values);
+            mIsPotentiallyPrivate.add(isPotentiallyPrivate);
+        }
+
+        void setRemovePotentiallyPrivateEvents(boolean removePotentiallyPrivateEvents) {
+            mRemovePotentiallyPrivateEvents = removePotentiallyPrivateEvents;
+        }
+
+        @Override
+        public void run() {
+            final int numAtoms = mKeysList.size();
+            for (int atomIndex = 0; atomIndex < numAtoms; atomIndex++) {
+                if (mRemovePotentiallyPrivateEvents && mIsPotentiallyPrivate.get(atomIndex)) {
+                    continue;
+                }
+                final String[] keys = mKeysList.get(atomIndex);
+                final Object[] values = mValuesList.get(atomIndex);
+                ResearchLogger.getInstance().outputEvent(keys, values);
+            }
+        }
+    }
+
+    private static int scrubDigitFromCodePoint(int codePoint) {
+        return Character.isDigit(codePoint) ? DIGIT_REPLACEMENT_CODEPOINT : codePoint;
+    }
+
+    /* package for test */ static String scrubDigitsFromString(String s) {
+        StringBuilder sb = null;
+        final int length = s.length();
+        for (int i = 0; i < length; i = s.offsetByCodePoints(i, 1)) {
+            final int codePoint = Character.codePointAt(s, i);
+            if (Character.isDigit(codePoint)) {
+                if (sb == null) {
+                    sb = new StringBuilder(length);
+                    sb.append(s.substring(0, i));
+                }
+                sb.appendCodePoint(DIGIT_REPLACEMENT_CODEPOINT);
+            } else {
+                if (sb != null) {
+                    sb.appendCodePoint(codePoint);
+                }
+            }
+        }
+        if (sb == null) {
+            return s;
+        } else {
+            return sb.toString();
+        }
+    }
+
+    private String scrubWord(String word) {
+        if (mDictionary == null) {
+            return WORD_REPLACEMENT_STRING;
+        }
+        if (mDictionary.isValidWord(word)) {
+            return word;
+        }
+        return WORD_REPLACEMENT_STRING;
+    }
+
+    private static final String[] EVENTKEYS_LATINKEYBOARDVIEW_PROCESSMOTIONEVENT = {
+        "LatinKeyboardViewProcessMotionEvent", "action", "eventTime", "id", "x", "y", "size",
+        "pressure"
+    };
+    public static void latinKeyboardView_processMotionEvent(final MotionEvent me, final int action,
+            final long eventTime, final int index, final int id, final int x, final int y) {
+        if (me != null) {
+            final String actionString;
+            switch (action) {
+                case MotionEvent.ACTION_CANCEL: actionString = "CANCEL"; break;
+                case MotionEvent.ACTION_UP: actionString = "UP"; break;
+                case MotionEvent.ACTION_DOWN: actionString = "DOWN"; break;
+                case MotionEvent.ACTION_POINTER_UP: actionString = "POINTER_UP"; break;
+                case MotionEvent.ACTION_POINTER_DOWN: actionString = "POINTER_DOWN"; break;
+                case MotionEvent.ACTION_MOVE: actionString = "MOVE"; break;
+                case MotionEvent.ACTION_OUTSIDE: actionString = "OUTSIDE"; break;
+                default: actionString = "ACTION_" + action; break;
+            }
+            final float size = me.getSize(index);
+            final float pressure = me.getPressure(index);
+            final Object[] values = {
+                actionString, eventTime, id, x, y, size, pressure
+            };
+            getInstance().enqueuePotentiallyPrivateEvent(
+                    EVENTKEYS_LATINKEYBOARDVIEW_PROCESSMOTIONEVENT, values);
+        }
+    }
+
+    private static final String[] EVENTKEYS_LATINIME_ONCODEINPUT = {
+        "LatinIMEOnCodeInput", "code", "x", "y"
+    };
+    public static void latinIME_onCodeInput(final int code, final int x, final int y) {
+        final Object[] values = {
+            Keyboard.printableCode(scrubDigitFromCodePoint(code)), x, y
+        };
+        getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONCODEINPUT, values);
+    }
+
+    private static final String[] EVENTKEYS_CORRECTION = {
+        "LogCorrection", "subgroup", "before", "after", "position"
+    };
+    public static void logCorrection(final String subgroup, final String before, final String after,
+            final int position) {
+        final Object[] values = {
+            subgroup, scrubDigitsFromString(before), scrubDigitsFromString(after), position
+        };
+        getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_CORRECTION, values);
+    }
+
+    private static final String[] EVENTKEYS_LATINIME_COMMITCURRENTAUTOCORRECTION = {
+        "LatinIMECommitCurrentAutoCorrection", "typedWord", "autoCorrection"
+    };
+    public static void latinIME_commitCurrentAutoCorrection(final String typedWord,
+            final String autoCorrection) {
+        final Object[] values = {
+            scrubDigitsFromString(typedWord), scrubDigitsFromString(autoCorrection)
+        };
+        final ResearchLogger researchLogger = getInstance();
+        researchLogger.enqueuePotentiallyPrivateEvent(
+                EVENTKEYS_LATINIME_COMMITCURRENTAUTOCORRECTION, values);
+    }
+
+    private static final String[] EVENTKEYS_LATINIME_COMMITTEXT = {
+        "LatinIMECommitText", "typedWord"
+    };
+    public static void latinIME_commitText(final CharSequence typedWord) {
+        final String scrubbedWord = scrubDigitsFromString(typedWord.toString());
+        final Object[] values = {
+            scrubbedWord
+        };
+        final ResearchLogger researchLogger = getInstance();
+        researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_COMMITTEXT, values);
+        researchLogger.onWordComplete(scrubbedWord);
+    }
+
+    private static final String[] EVENTKEYS_LATINIME_DELETESURROUNDINGTEXT = {
+        "LatinIMEDeleteSurroundingText", "length"
+    };
+    public static void latinIME_deleteSurroundingText(final int length) {
+        final Object[] values = {
+            length
+        };
+        getInstance().enqueueEvent(EVENTKEYS_LATINIME_DELETESURROUNDINGTEXT, values);
+    }
+
+    private static final String[] EVENTKEYS_LATINIME_DOUBLESPACEAUTOPERIOD = {
+        "LatinIMEDoubleSpaceAutoPeriod"
+    };
+    public static void latinIME_doubleSpaceAutoPeriod() {
+        getInstance().enqueueEvent(EVENTKEYS_LATINIME_DOUBLESPACEAUTOPERIOD, EVENTKEYS_NULLVALUES);
+    }
+
+    private static final String[] EVENTKEYS_LATINIME_ONDISPLAYCOMPLETIONS = {
+        "LatinIMEOnDisplayCompletions", "applicationSpecifiedCompletions"
+    };
+    public static void latinIME_onDisplayCompletions(
+            final CompletionInfo[] applicationSpecifiedCompletions) {
+        final Object[] values = {
+            applicationSpecifiedCompletions
+        };
+        getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONDISPLAYCOMPLETIONS,
+                values);
+    }
+
+    /* package */ static boolean getAndClearLatinIMEExpectingUpdateSelection() {
+        boolean returnValue = sLatinIMEExpectingUpdateSelection;
+        sLatinIMEExpectingUpdateSelection = false;
+        return returnValue;
+    }
+
+    private static final String[] EVENTKEYS_LATINIME_ONWINDOWHIDDEN = {
+        "LatinIMEOnWindowHidden", "isTextTruncated", "text"
+    };
+    public static void latinIME_onWindowHidden(final int savedSelectionStart,
+            final int savedSelectionEnd, final InputConnection ic) {
+        if (ic != null) {
+            ic.beginBatchEdit();
+            ic.performContextMenuAction(android.R.id.selectAll);
+            CharSequence charSequence = ic.getSelectedText(0);
+            ic.setSelection(savedSelectionStart, savedSelectionEnd);
+            ic.endBatchEdit();
+            sLatinIMEExpectingUpdateSelection = true;
+            final Object[] values = new Object[2];
+            if (OUTPUT_ENTIRE_BUFFER) {
+                if (TextUtils.isEmpty(charSequence)) {
+                    values[0] = false;
+                    values[1] = "";
+                } else {
+                    if (charSequence.length() > MAX_INPUTVIEW_LENGTH_TO_CAPTURE) {
+                        int length = MAX_INPUTVIEW_LENGTH_TO_CAPTURE;
+                        // do not cut in the middle of a supplementary character
+                        final char c = charSequence.charAt(length - 1);
+                        if (Character.isHighSurrogate(c)) {
+                            length--;
+                        }
+                        final CharSequence truncatedCharSequence = charSequence.subSequence(0,
+                                length);
+                        values[0] = true;
+                        values[1] = truncatedCharSequence.toString();
+                    } else {
+                        values[0] = false;
+                        values[1] = charSequence.toString();
+                    }
+                }
+            } else {
+                values[0] = true;
+                values[1] = "";
+            }
+            final ResearchLogger researchLogger = getInstance();
+            researchLogger.enqueueEvent(EVENTKEYS_LATINIME_ONWINDOWHIDDEN, values);
+            researchLogger.flushEventQueue(true); // Play it safe.  Remove privacy-sensitive events.
+        }
+    }
+
+    private static final String[] EVENTKEYS_LATINIME_ONSTARTINPUTVIEWINTERNAL = {
+        "LatinIMEOnStartInputViewInternal", "uuid", "packageName", "inputType", "imeOptions",
+        "fieldId", "display", "model", "prefs", "outputFormatVersion"
+    };
     public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo,
             final SharedPreferences prefs) {
-        if (UnsLogGroup.LATINIME_ONSTARTINPUTVIEWINTERNAL_ENABLED) {
-            final StringBuilder builder = new StringBuilder();
-            builder.append("onStartInputView: editorInfo:");
-            builder.append("\tinputType=");
-            builder.append(Integer.toHexString(editorInfo.inputType));
-            builder.append("\timeOptions=");
-            builder.append(Integer.toHexString(editorInfo.imeOptions));
-            builder.append("\tdisplay="); builder.append(Build.DISPLAY);
-            builder.append("\tmodel="); builder.append(Build.MODEL);
-            for (Map.Entry<String,?> entry : prefs.getAll().entrySet()) {
-                builder.append("\t" + entry.getKey());
-                Object value = entry.getValue();
-                builder.append("=" + ((value == null) ? "<null>" : value.toString()));
-            }
-            logUnstructured("LatinIME_onStartInputViewInternal", builder.toString());
+        if (editorInfo != null) {
+            final Object[] values = {
+                getUUID(prefs), editorInfo.packageName, Integer.toHexString(editorInfo.inputType),
+                Integer.toHexString(editorInfo.imeOptions), editorInfo.fieldId, Build.DISPLAY,
+                Build.MODEL, prefs, OUTPUT_FORMAT_VERSION
+            };
+            getInstance().enqueueEvent(EVENTKEYS_LATINIME_ONSTARTINPUTVIEWINTERNAL, values);
         }
     }
 
+    private static String getUUID(final SharedPreferences prefs) {
+        String uuidString = prefs.getString(PREF_RESEARCH_LOGGER_UUID_STRING, null);
+        if (null == uuidString) {
+            UUID uuid = UUID.randomUUID();
+            uuidString = uuid.toString();
+            Editor editor = prefs.edit();
+            editor.putString(PREF_RESEARCH_LOGGER_UUID_STRING, uuidString);
+            editor.apply();
+        }
+        return uuidString;
+    }
+
+    private static final String[] EVENTKEYS_LATINIME_ONUPDATESELECTION = {
+        "LatinIMEOnUpdateSelection", "lastSelectionStart", "lastSelectionEnd", "oldSelStart",
+        "oldSelEnd", "newSelStart", "newSelEnd", "composingSpanStart", "composingSpanEnd",
+        "expectingUpdateSelection", "expectingUpdateSelectionFromLogger", "context"
+    };
     public static void latinIME_onUpdateSelection(final int lastSelectionStart,
             final int lastSelectionEnd, final int oldSelStart, final int oldSelEnd,
             final int newSelStart, final int newSelEnd, final int composingSpanStart,
-            final int composingSpanEnd) {
-        if (UnsLogGroup.LATINIME_ONUPDATESELECTION_ENABLED) {
-            final String s = "onUpdateSelection: oss=" + oldSelStart
-                    + ", ose=" + oldSelEnd
-                    + ", lss=" + lastSelectionStart
-                    + ", lse=" + lastSelectionEnd
-                    + ", nss=" + newSelStart
-                    + ", nse=" + newSelEnd
-                    + ", cs=" + composingSpanStart
-                    + ", ce=" + composingSpanEnd;
-            logUnstructured("LatinIME_onUpdateSelection", s);
+            final int composingSpanEnd, final boolean expectingUpdateSelection,
+            final boolean expectingUpdateSelectionFromLogger,
+            final RichInputConnection connection) {
+        String word = "";
+        if (connection != null) {
+            Range range = connection.getWordRangeAtCursor(WHITESPACE_SEPARATORS, 1);
+            if (range != null) {
+                word = range.mWord;
+            }
         }
+        final ResearchLogger researchLogger = getInstance();
+        final String scrubbedWord = researchLogger.scrubWord(word);
+        final Object[] values = {
+            lastSelectionStart, lastSelectionEnd, oldSelStart, oldSelEnd, newSelStart,
+            newSelEnd, composingSpanStart, composingSpanEnd, expectingUpdateSelection,
+            expectingUpdateSelectionFromLogger, scrubbedWord
+        };
+        researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONUPDATESELECTION, values);
     }
 
+    private static final String[] EVENTKEYS_LATINIME_PERFORMEDITORACTION = {
+        "LatinIMEPerformEditorAction", "imeActionNext"
+    };
     public static void latinIME_performEditorAction(final int imeActionNext) {
-        if (UnsLogGroup.LATINIME_PERFORMEDITORACTION_ENABLED) {
-            logUnstructured("LatinIME_performEditorAction", String.valueOf(imeActionNext));
-        }
+        final Object[] values = {
+            imeActionNext
+        };
+        getInstance().enqueueEvent(EVENTKEYS_LATINIME_PERFORMEDITORACTION, values);
     }
 
+    private static final String[] EVENTKEYS_LATINIME_PICKAPPLICATIONSPECIFIEDCOMPLETION = {
+        "LatinIMEPickApplicationSpecifiedCompletion", "index", "text", "x", "y"
+    };
     public static void latinIME_pickApplicationSpecifiedCompletion(final int index,
-            final CharSequence text, int x, int y) {
-        if (UnsLogGroup.LATINIME_PICKAPPLICATIONSPECIFIEDCOMPLETION_ENABLED) {
-            final String s = String.valueOf(index) + '\t' + text + '\t' + x + '\t' + y;
-            logUnstructured("LatinIME_pickApplicationSpecifiedCompletion", s);
-        }
+            final CharSequence cs, int x, int y) {
+        final Object[] values = {
+            index, cs, x, y
+        };
+        final ResearchLogger researchLogger = getInstance();
+        researchLogger.enqueuePotentiallyPrivateEvent(
+                EVENTKEYS_LATINIME_PICKAPPLICATIONSPECIFIEDCOMPLETION, values);
     }
 
+    private static final String[] EVENTKEYS_LATINIME_PICKSUGGESTIONMANUALLY = {
+        "LatinIMEPickSuggestionManually", "replacedWord", "index", "suggestion", "x", "y"
+    };
     public static void latinIME_pickSuggestionManually(final String replacedWord,
             final int index, CharSequence suggestion, int x, int y) {
-        if (UnsLogGroup.LATINIME_PICKSUGGESTIONMANUALLY_ENABLED) {
-            final String s = String.valueOf(index) + '\t' + suggestion + '\t' + x + '\t' + y;
-            logUnstructured("LatinIME_pickSuggestionManually", s);
-        }
+        final Object[] values = {
+            scrubDigitsFromString(replacedWord), index, suggestion == null ? null :
+                    scrubDigitsFromString(suggestion.toString()), x, y
+        };
+        final ResearchLogger researchLogger = getInstance();
+        researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_PICKSUGGESTIONMANUALLY,
+                values);
     }
 
+    private static final String[] EVENTKEYS_LATINIME_PUNCTUATIONSUGGESTION = {
+        "LatinIMEPunctuationSuggestion", "index", "suggestion", "x", "y"
+    };
     public static void latinIME_punctuationSuggestion(final int index,
             final CharSequence suggestion, int x, int y) {
-        if (UnsLogGroup.LATINIME_PICKPUNCTUATIONSUGGESTION_ENABLED) {
-            final String s = String.valueOf(index) + '\t' + suggestion + '\t' + x + '\t' + y;
-            logUnstructured("LatinIME_pickPunctuationSuggestion", s);
-        }
+        final Object[] values = {
+            index, suggestion, x, y
+        };
+        getInstance().enqueueEvent(EVENTKEYS_LATINIME_PUNCTUATIONSUGGESTION, values);
     }
 
+    private static final String[] EVENTKEYS_LATINIME_REVERTDOUBLESPACEWHILEINBATCHEDIT = {
+        "LatinIMERevertDoubleSpaceWhileInBatchEdit"
+    };
     public static void latinIME_revertDoubleSpaceWhileInBatchEdit() {
-        if (UnsLogGroup.LATINIME_REVERTDOUBLESPACEWHILEINBATCHEDIT_ENABLED) {
-            logUnstructured("LatinIME_revertDoubleSpaceWhileInBatchEdit", "");
-        }
+        getInstance().enqueueEvent(EVENTKEYS_LATINIME_REVERTDOUBLESPACEWHILEINBATCHEDIT,
+                EVENTKEYS_NULLVALUES);
     }
 
+    private static final String[] EVENTKEYS_LATINIME_REVERTSWAPPUNCTUATION = {
+        "LatinIMERevertSwapPunctuation"
+    };
     public static void latinIME_revertSwapPunctuation() {
-        if (UnsLogGroup.LATINIME_REVERTSWAPPUNCTUATION_ENABLED) {
-            logUnstructured("LatinIME_revertSwapPunctuation", "");
-        }
+        getInstance().enqueueEvent(EVENTKEYS_LATINIME_REVERTSWAPPUNCTUATION, EVENTKEYS_NULLVALUES);
     }
 
+    private static final String[] EVENTKEYS_LATINIME_SENDKEYCODEPOINT = {
+        "LatinIMESendKeyCodePoint", "code"
+    };
     public static void latinIME_sendKeyCodePoint(final int code) {
-        if (UnsLogGroup.LATINIME_SENDKEYCODEPOINT_ENABLED) {
-            logUnstructured("LatinIME_sendKeyCodePoint", String.valueOf(code));
-        }
+        final Object[] values = {
+            Keyboard.printableCode(scrubDigitFromCodePoint(code))
+        };
+        getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_SENDKEYCODEPOINT, values);
     }
 
+    private static final String[] EVENTKEYS_LATINIME_SWAPSWAPPERANDSPACEWHILEINBATCHEDIT = {
+        "LatinIMESwapSwapperAndSpaceWhileInBatchEdit"
+    };
     public static void latinIME_swapSwapperAndSpaceWhileInBatchEdit() {
-        if (UnsLogGroup.LATINIME_SWAPSWAPPERANDSPACEWHILEINBATCHEDIT_ENABLED) {
-            logUnstructured("latinIME_swapSwapperAndSpaceWhileInBatchEdit", "");
-        }
+        getInstance().enqueueEvent(EVENTKEYS_LATINIME_SWAPSWAPPERANDSPACEWHILEINBATCHEDIT,
+                EVENTKEYS_NULLVALUES);
     }
 
-    public static void latinIME_switchToKeyboardView() {
-        if (UnsLogGroup.LATINIME_SWITCHTOKEYBOARDVIEW_ENABLED) {
-            final String s = "Switch to keyboard view.";
-            logUnstructured("LatinIME_switchToKeyboardView", s);
-        }
-    }
-
+    private static final String[] EVENTKEYS_LATINKEYBOARDVIEW_ONLONGPRESS = {
+        "LatinKeyboardViewOnLongPress"
+    };
     public static void latinKeyboardView_onLongPress() {
-        if (UnsLogGroup.LATINKEYBOARDVIEW_ONLONGPRESS_ENABLED) {
-            final String s = "long press detected";
-            logUnstructured("LatinKeyboardView_onLongPress", s);
-        }
+        getInstance().enqueueEvent(EVENTKEYS_LATINKEYBOARDVIEW_ONLONGPRESS, EVENTKEYS_NULLVALUES);
     }
 
-    public static void latinKeyboardView_processMotionEvent(MotionEvent me, int action,
-            long eventTime, int index, int id, int x, int y) {
-        if (UnsLogGroup.LATINKEYBOARDVIEW_ONPROCESSMOTIONEVENT_ENABLED) {
-            final float size = me.getSize(index);
-            final float pressure = me.getPressure(index);
-            if (action != MotionEvent.ACTION_MOVE) {
-                getInstance().logMotionEvent(action, eventTime, id, x, y, size, pressure);
-            }
-        }
-    }
-
+    private static final String[] EVENTKEYS_LATINKEYBOARDVIEW_SETKEYBOARD = {
+        "LatinKeyboardViewSetKeyboard", "elementId", "locale", "orientation", "width",
+        "modeName", "action", "navigateNext", "navigatePrevious", "clobberSettingsKey",
+        "passwordInput", "shortcutKeyEnabled", "hasShortcutKey", "languageSwitchKeyEnabled",
+        "isMultiLine", "tw", "th", "keys"
+    };
     public static void latinKeyboardView_setKeyboard(final Keyboard keyboard) {
-        if (UnsLogGroup.LATINKEYBOARDVIEW_SETKEYBOARD_ENABLED) {
-            StringBuilder builder = new StringBuilder();
-            builder.append("id=");
-            builder.append(keyboard.mId);
-            builder.append("\tw=");
-            builder.append(keyboard.mOccupiedWidth);
-            builder.append("\th=");
-            builder.append(keyboard.mOccupiedHeight);
-            builder.append("\tkeys=[");
-            boolean first = true;
-            for (Key key : keyboard.mKeys) {
-                if (first) {
-                    first = false;
-                } else {
-                    builder.append(",");
-                }
-                builder.append("{code:");
-                builder.append(key.mCode);
-                builder.append(",altCode:");
-                builder.append(key.mAltCode);
-                builder.append(",x:");
-                builder.append(key.mX);
-                builder.append(",y:");
-                builder.append(key.mY);
-                builder.append(",w:");
-                builder.append(key.mWidth);
-                builder.append(",h:");
-                builder.append(key.mHeight);
-                builder.append("}");
-            }
-            builder.append("]");
-            logUnstructured("LatinKeyboardView_setKeyboard", builder.toString());
+        if (keyboard != null) {
+            final KeyboardId kid = keyboard.mId;
+            final boolean isPasswordView = kid.passwordInput();
+            final Object[] values = {
+                    KeyboardId.elementIdToName(kid.mElementId),
+                    kid.mLocale + ":" + kid.mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET),
+                    kid.mOrientation,
+                    kid.mWidth,
+                    KeyboardId.modeName(kid.mMode),
+                    kid.imeAction(),
+                    kid.navigateNext(),
+                    kid.navigatePrevious(),
+                    kid.mClobberSettingsKey,
+                    isPasswordView,
+                    kid.mShortcutKeyEnabled,
+                    kid.mHasShortcutKey,
+                    kid.mLanguageSwitchKeyEnabled,
+                    kid.isMultiLine(),
+                    keyboard.mOccupiedWidth,
+                    keyboard.mOccupiedHeight,
+                    keyboard.mKeys
+                };
+            getInstance().enqueueEvent(EVENTKEYS_LATINKEYBOARDVIEW_SETKEYBOARD, values);
+            getInstance().setIsPasswordView(isPasswordView);
         }
     }
 
+    private static final String[] EVENTKEYS_LATINIME_REVERTCOMMIT = {
+        "LatinIMERevertCommit", "originallyTypedWord"
+    };
     public static void latinIME_revertCommit(final String originallyTypedWord) {
-        if (UnsLogGroup.LATINIME_REVERTCOMMIT_ENABLED) {
-            logUnstructured("LatinIME_revertCommit", originallyTypedWord);
-        }
+        final Object[] values = {
+            originallyTypedWord
+        };
+        getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_REVERTCOMMIT, values);
     }
 
+    private static final String[] EVENTKEYS_POINTERTRACKER_CALLLISTENERONCANCELINPUT = {
+        "PointerTrackerCallListenerOnCancelInput"
+    };
     public static void pointerTracker_callListenerOnCancelInput() {
-        final String s = "onCancelInput";
-        if (UnsLogGroup.POINTERTRACKER_CALLLISTENERONCANCELINPUT_ENABLED) {
-            logUnstructured("PointerTracker_callListenerOnCancelInput", s);
-        }
+        getInstance().enqueueEvent(EVENTKEYS_POINTERTRACKER_CALLLISTENERONCANCELINPUT,
+                EVENTKEYS_NULLVALUES);
     }
 
+    private static final String[] EVENTKEYS_POINTERTRACKER_CALLLISTENERONCODEINPUT = {
+        "PointerTrackerCallListenerOnCodeInput", "code", "outputText", "x", "y",
+        "ignoreModifierKey", "altersCode", "isEnabled"
+    };
     public static void pointerTracker_callListenerOnCodeInput(final Key key, final int x,
             final int y, final boolean ignoreModifierKey, final boolean altersCode,
             final int code) {
-        if (UnsLogGroup.POINTERTRACKER_CALLLISTENERONCODEINPUT_ENABLED) {
-            final String s = "onCodeInput: " + Keyboard.printableCode(code)
-                    + " text=" + key.mOutputText + " x=" + x + " y=" + y
-                    + " ignoreModifier=" + ignoreModifierKey + " altersCode=" + altersCode
-                    + " enabled=" + key.isEnabled();
-            logUnstructured("PointerTracker_callListenerOnCodeInput", s);
+        if (key != null) {
+            CharSequence outputText = key.mOutputText;
+            final Object[] values = {
+                Keyboard.printableCode(scrubDigitFromCodePoint(code)), outputText == null ? null
+                        : scrubDigitsFromString(outputText.toString()),
+                x, y, ignoreModifierKey, altersCode, key.isEnabled()
+            };
+            getInstance().enqueuePotentiallyPrivateEvent(
+                    EVENTKEYS_POINTERTRACKER_CALLLISTENERONCODEINPUT, values);
         }
     }
 
-    public static void pointerTracker_callListenerOnPressAndCheckKeyboardLayoutChange(
-            final Key key, final boolean ignoreModifierKey) {
-        if (UnsLogGroup.POINTERTRACKER_CALLLISTENERONPRESSANDCHECKKEYBOARDLAYOUTCHANGE_ENABLED) {
-            final String s = "onPress    : " + KeyDetector.printableCode(key)
-                    + " ignoreModifier=" + ignoreModifierKey
-                    + " enabled=" + key.isEnabled();
-            logUnstructured("PointerTracker_callListenerOnPressAndCheckKeyboardLayoutChange", s);
-        }
-    }
-
+    private static final String[] EVENTKEYS_POINTERTRACKER_CALLLISTENERONRELEASE = {
+        "PointerTrackerCallListenerOnRelease", "code", "withSliding", "ignoreModifierKey",
+        "isEnabled"
+    };
     public static void pointerTracker_callListenerOnRelease(final Key key, final int primaryCode,
             final boolean withSliding, final boolean ignoreModifierKey) {
-        if (UnsLogGroup.POINTERTRACKER_CALLLISTENERONRELEASE_ENABLED) {
-            final String s = "onRelease  : " + Keyboard.printableCode(primaryCode)
-                    + " sliding=" + withSliding + " ignoreModifier=" + ignoreModifierKey
-                    + " enabled="+ key.isEnabled();
-            logUnstructured("PointerTracker_callListenerOnRelease", s);
+        if (key != null) {
+            final Object[] values = {
+                Keyboard.printableCode(scrubDigitFromCodePoint(primaryCode)), withSliding,
+                ignoreModifierKey, key.isEnabled()
+            };
+            getInstance().enqueuePotentiallyPrivateEvent(
+                    EVENTKEYS_POINTERTRACKER_CALLLISTENERONRELEASE, values);
         }
     }
 
+    private static final String[] EVENTKEYS_POINTERTRACKER_ONDOWNEVENT = {
+        "PointerTrackerOnDownEvent", "deltaT", "distanceSquared"
+    };
     public static void pointerTracker_onDownEvent(long deltaT, int distanceSquared) {
-        if (UnsLogGroup.POINTERTRACKER_ONDOWNEVENT_ENABLED) {
-            final String s = "onDownEvent: ignore potential noise: time=" + deltaT
-                    + " distance=" + distanceSquared;
-            logUnstructured("PointerTracker_onDownEvent", s);
-        }
+        final Object[] values = {
+            deltaT, distanceSquared
+        };
+        getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_POINTERTRACKER_ONDOWNEVENT, values);
     }
 
+    private static final String[] EVENTKEYS_POINTERTRACKER_ONMOVEEVENT = {
+        "PointerTrackerOnMoveEvent", "x", "y", "lastX", "lastY"
+    };
     public static void pointerTracker_onMoveEvent(final int x, final int y, final int lastX,
             final int lastY) {
-        if (UnsLogGroup.POINTERTRACKER_ONMOVEEVENT_ENABLED) {
-            final String s = String.format("onMoveEvent: sudden move is translated to "
-                    + "up[%d,%d]/down[%d,%d] events", lastX, lastY, x, y);
-            logUnstructured("PointerTracker_onMoveEvent", s);
-        }
+        final Object[] values = {
+            x, y, lastX, lastY
+        };
+        getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_POINTERTRACKER_ONMOVEEVENT, values);
     }
 
+    private static final String[] EVENTKEYS_SUDDENJUMPINGTOUCHEVENTHANDLER_ONTOUCHEVENT = {
+        "SuddenJumpingTouchEventHandlerOnTouchEvent", "motionEvent"
+    };
     public static void suddenJumpingTouchEventHandler_onTouchEvent(final MotionEvent me) {
-        if (UnsLogGroup.SUDDENJUMPINGTOUCHEVENTHANDLER_ONTOUCHEVENT_ENABLED) {
-            final String s = "onTouchEvent: ignore sudden jump " + me;
-            logUnstructured("SuddenJumpingTouchEventHandler_onTouchEvent", s);
+        if (me != null) {
+            final Object[] values = {
+                me.toString()
+            };
+            getInstance().enqueuePotentiallyPrivateEvent(
+                    EVENTKEYS_SUDDENJUMPINGTOUCHEVENTHANDLER_ONTOUCHEVENT, values);
         }
     }
 
-    public static void suggestionsView_setSuggestions(final SuggestedWords mSuggestedWords) {
-        if (UnsLogGroup.SUGGESTIONSVIEW_SETSUGGESTIONS_ENABLED) {
-            logUnstructured("SuggestionsView_setSuggestions", mSuggestedWords.toString());
+    private static final String[] EVENTKEYS_SUGGESTIONSVIEW_SETSUGGESTIONS = {
+        "SuggestionsViewSetSuggestions", "suggestedWords"
+    };
+    public static void suggestionsView_setSuggestions(final SuggestedWords suggestedWords) {
+        if (suggestedWords != null) {
+            final Object[] values = {
+                suggestedWords
+            };
+            getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_SUGGESTIONSVIEW_SETSUGGESTIONS,
+                    values);
         }
     }
-}
\ No newline at end of file
+
+    private static final String[] EVENTKEYS_USER_TIMESTAMP = {
+        "UserTimestamp"
+    };
+    public void userTimestamp() {
+        getInstance().enqueueEvent(EVENTKEYS_USER_TIMESTAMP, EVENTKEYS_NULLVALUES);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
new file mode 100644
index 0000000..5786978
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -0,0 +1,420 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.inputmethodservice.InputMethodService;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputConnection;
+
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.define.ProductionFlag;
+
+import java.util.regex.Pattern;
+
+/**
+ * Wrapper for InputConnection to simplify interaction
+ */
+public class RichInputConnection {
+    private static final String TAG = RichInputConnection.class.getSimpleName();
+    private static final boolean DBG = false;
+    // Provision for a long word pair and a separator
+    private static final int LOOKBACK_CHARACTER_NUM = BinaryDictionary.MAX_WORD_LENGTH * 2 + 1;
+    private static final Pattern spaceRegex = Pattern.compile("\\s+");
+    private static final int INVALID_CURSOR_POSITION = -1;
+
+    private final InputMethodService mParent;
+    InputConnection mIC;
+    int mNestLevel;
+    public RichInputConnection(final InputMethodService parent) {
+        mParent = parent;
+        mIC = null;
+        mNestLevel = 0;
+    }
+
+    public void beginBatchEdit() {
+        if (++mNestLevel == 1) {
+            mIC = mParent.getCurrentInputConnection();
+            if (null != mIC) mIC.beginBatchEdit();
+        } else {
+            if (DBG) {
+                throw new RuntimeException("Nest level too deep");
+            } else {
+                Log.e(TAG, "Nest level too deep : " + mNestLevel);
+            }
+        }
+    }
+    public void endBatchEdit() {
+        if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead
+        if (--mNestLevel == 0 && null != mIC) mIC.endBatchEdit();
+    }
+
+    private void checkBatchEdit() {
+        if (mNestLevel != 1) {
+            // TODO: exception instead
+            Log.e(TAG, "Batch edit level incorrect : " + mNestLevel);
+            Log.e(TAG, Utils.getStackTrace(4));
+        }
+    }
+
+    public void finishComposingText() {
+        checkBatchEdit();
+        if (null != mIC) mIC.finishComposingText();
+    }
+
+    public void commitText(final CharSequence text, final int i) {
+        checkBatchEdit();
+        if (null != mIC) mIC.commitText(text, i);
+    }
+
+    public int getCursorCapsMode(final int inputType) {
+        mIC = mParent.getCurrentInputConnection();
+        if (null == mIC) return Constants.TextUtils.CAP_MODE_OFF;
+        return mIC.getCursorCapsMode(inputType);
+    }
+
+    public CharSequence getTextBeforeCursor(final int i, final int j) {
+        mIC = mParent.getCurrentInputConnection();
+        if (null != mIC) return mIC.getTextBeforeCursor(i, j);
+        return null;
+    }
+
+    public CharSequence getTextAfterCursor(final int i, final int j) {
+        mIC = mParent.getCurrentInputConnection();
+        if (null != mIC) return mIC.getTextAfterCursor(i, j);
+        return null;
+    }
+
+    public void deleteSurroundingText(final int i, final int j) {
+        checkBatchEdit();
+        if (null != mIC) mIC.deleteSurroundingText(i, j);
+    }
+
+    public void performEditorAction(final int actionId) {
+        mIC = mParent.getCurrentInputConnection();
+        if (null != mIC) mIC.performEditorAction(actionId);
+    }
+
+    public void sendKeyEvent(final KeyEvent keyEvent) {
+        checkBatchEdit();
+        if (null != mIC) mIC.sendKeyEvent(keyEvent);
+    }
+
+    public void setComposingText(final CharSequence text, final int i) {
+        checkBatchEdit();
+        if (null != mIC) mIC.setComposingText(text, i);
+    }
+
+    public void setSelection(final int from, final int to) {
+        checkBatchEdit();
+        if (null != mIC) mIC.setSelection(from, to);
+    }
+
+    public void commitCorrection(final CorrectionInfo correctionInfo) {
+        checkBatchEdit();
+        if (null != mIC) mIC.commitCorrection(correctionInfo);
+    }
+
+    public void commitCompletion(final CompletionInfo completionInfo) {
+        checkBatchEdit();
+        if (null != mIC) mIC.commitCompletion(completionInfo);
+    }
+
+    public CharSequence getNthPreviousWord(final String sentenceSeperators, final int n) {
+        mIC = mParent.getCurrentInputConnection();
+        if (null == mIC) return null;
+        final CharSequence prev = mIC.getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0);
+        return getNthPreviousWord(prev, sentenceSeperators, n);
+    }
+
+    /**
+     * Represents a range of text, relative to the current cursor position.
+     */
+    public static class Range {
+        /** Characters before selection start */
+        public final int mCharsBefore;
+
+        /**
+         * Characters after selection start, including one trailing word
+         * separator.
+         */
+        public final int mCharsAfter;
+
+        /** The actual characters that make up a word */
+        public final String mWord;
+
+        public Range(int charsBefore, int charsAfter, String word) {
+            if (charsBefore < 0 || charsAfter < 0) {
+                throw new IndexOutOfBoundsException();
+            }
+            this.mCharsBefore = charsBefore;
+            this.mCharsAfter = charsAfter;
+            this.mWord = word;
+        }
+    }
+
+    private static boolean isSeparator(int code, String sep) {
+        return sep.indexOf(code) != -1;
+    }
+
+    // Get the nth word before cursor. n = 1 retrieves the word immediately before the cursor,
+    // n = 2 retrieves the word before that, and so on. This splits on whitespace only.
+    // Also, it won't return words that end in a separator (if the nth word before the cursor
+    // ends in a separator, it returns null).
+    // Example :
+    // (n = 1) "abc def|" -> def
+    // (n = 1) "abc def |" -> def
+    // (n = 1) "abc def. |" -> null
+    // (n = 1) "abc def . |" -> null
+    // (n = 2) "abc def|" -> abc
+    // (n = 2) "abc def |" -> abc
+    // (n = 2) "abc def. |" -> abc
+    // (n = 2) "abc def . |" -> def
+    // (n = 2) "abc|" -> null
+    // (n = 2) "abc |" -> null
+    // (n = 2) "abc. def|" -> null
+    public static CharSequence getNthPreviousWord(final CharSequence prev,
+            final String sentenceSeperators, final int n) {
+        if (prev == null) return null;
+        String[] w = spaceRegex.split(prev);
+
+        // If we can't find n words, or we found an empty word, return null.
+        if (w.length < n || w[w.length - n].length() <= 0) return null;
+
+        // If ends in a separator, return null
+        char lastChar = w[w.length - n].charAt(w[w.length - n].length() - 1);
+        if (sentenceSeperators.contains(String.valueOf(lastChar))) return null;
+
+        return w[w.length - n];
+    }
+
+    /**
+     * @param separators characters which may separate words
+     * @return the word that surrounds the cursor, including up to one trailing
+     *   separator. For example, if the field contains "he|llo world", where |
+     *   represents the cursor, then "hello " will be returned.
+     */
+    public String getWordAtCursor(String separators) {
+        // getWordRangeAtCursor returns null if the connection is null
+        Range r = getWordRangeAtCursor(separators, 0);
+        return (r == null) ? null : r.mWord;
+    }
+
+    private int getCursorPosition() {
+        mIC = mParent.getCurrentInputConnection();
+        if (null == mIC) return INVALID_CURSOR_POSITION;
+        final ExtractedText extracted = mIC.getExtractedText(new ExtractedTextRequest(), 0);
+        if (extracted == null) {
+            return INVALID_CURSOR_POSITION;
+        }
+        return extracted.startOffset + extracted.selectionStart;
+    }
+
+    /**
+     * Returns the text surrounding the cursor.
+     *
+     * @param sep a string of characters that split words.
+     * @param additionalPrecedingWordsCount the number of words before the current word that should
+     *   be included in the returned range
+     * @return a range containing the text surrounding the cursor
+     */
+    public Range getWordRangeAtCursor(String sep, int additionalPrecedingWordsCount) {
+        mIC = mParent.getCurrentInputConnection();
+        if (mIC == null || sep == null) {
+            return null;
+        }
+        CharSequence before = mIC.getTextBeforeCursor(1000, 0);
+        CharSequence after = mIC.getTextAfterCursor(1000, 0);
+        if (before == null || after == null) {
+            return null;
+        }
+
+        // Going backward, alternate skipping non-separators and separators until enough words
+        // have been read.
+        int start = before.length();
+        boolean isStoppingAtWhitespace = true;  // toggles to indicate what to stop at
+        while (true) { // see comments below for why this is guaranteed to halt
+            while (start > 0) {
+                final int codePoint = Character.codePointBefore(before, start);
+                if (isStoppingAtWhitespace == isSeparator(codePoint, sep)) {
+                    break;  // inner loop
+                }
+                --start;
+                if (Character.isSupplementaryCodePoint(codePoint)) {
+                    --start;
+                }
+            }
+            // isStoppingAtWhitespace is true every other time through the loop,
+            // so additionalPrecedingWordsCount is guaranteed to become < 0, which
+            // guarantees outer loop termination
+            if (isStoppingAtWhitespace && (--additionalPrecedingWordsCount < 0)) {
+                break;  // outer loop
+            }
+            isStoppingAtWhitespace = !isStoppingAtWhitespace;
+        }
+
+        // Find last word separator after the cursor
+        int end = -1;
+        while (++end < after.length()) {
+            final int codePoint = Character.codePointAt(after, end);
+            if (isSeparator(codePoint, sep)) {
+                break;
+            }
+            if (Character.isSupplementaryCodePoint(codePoint)) {
+                ++end;
+            }
+        }
+
+        int cursor = getCursorPosition();
+        if (start >= 0 && cursor + end <= after.length() + before.length()) {
+            String word = before.toString().substring(start, before.length())
+                    + after.toString().substring(0, end);
+            return new Range(before.length() - start, end, word);
+        }
+
+        return null;
+    }
+
+    public boolean isCursorTouchingWord(final SettingsValues settingsValues) {
+        CharSequence before = getTextBeforeCursor(1, 0);
+        CharSequence after = getTextAfterCursor(1, 0);
+        if (!TextUtils.isEmpty(before) && !settingsValues.isWordSeparator(before.charAt(0))
+                && !settingsValues.isSymbolExcludedFromWordSeparators(before.charAt(0))) {
+            return true;
+        }
+        if (!TextUtils.isEmpty(after) && !settingsValues.isWordSeparator(after.charAt(0))
+                && !settingsValues.isSymbolExcludedFromWordSeparators(after.charAt(0))) {
+            return true;
+        }
+        return false;
+    }
+
+    public void removeTrailingSpace() {
+        checkBatchEdit();
+        final CharSequence lastOne = getTextBeforeCursor(1, 0);
+        if (lastOne != null && lastOne.length() == 1
+                && lastOne.charAt(0) == Keyboard.CODE_SPACE) {
+            deleteSurroundingText(1, 0);
+            if (ProductionFlag.IS_EXPERIMENTAL) {
+                ResearchLogger.latinIME_deleteSurroundingText(1);
+            }
+        }
+    }
+
+    public boolean sameAsTextBeforeCursor(final CharSequence text) {
+        final CharSequence beforeText = getTextBeforeCursor(text.length(), 0);
+        return TextUtils.equals(text, beforeText);
+    }
+
+    /* (non-javadoc)
+     * Returns the word before the cursor if the cursor is at the end of a word, null otherwise
+     */
+    public CharSequence getWordBeforeCursorIfAtEndOfWord(final SettingsValues settings) {
+        // Bail out if the cursor is in the middle of a word (cursor must be followed by whitespace,
+        // separator or end of line/text)
+        // Example: "test|"<EOL> "te|st" get rejected here
+        final CharSequence textAfterCursor = getTextAfterCursor(1, 0);
+        if (!TextUtils.isEmpty(textAfterCursor)
+                && !settings.isWordSeparator(textAfterCursor.charAt(0))) return null;
+
+        // Bail out if word before cursor is 0-length or a single non letter (like an apostrophe)
+        // Example: " -|" gets rejected here but "e-|" and "e|" are okay
+        CharSequence word = getWordAtCursor(settings.mWordSeparators);
+        // We don't suggest on leading single quotes, so we have to remove them from the word if
+        // it starts with single quotes.
+        while (!TextUtils.isEmpty(word) && Keyboard.CODE_SINGLE_QUOTE == word.charAt(0)) {
+            word = word.subSequence(1, word.length());
+        }
+        if (TextUtils.isEmpty(word)) return null;
+        // Find the last code point of the string
+        final int lastCodePoint = Character.codePointBefore(word, word.length());
+        // If for some reason the text field contains non-unicode binary data, or if the
+        // charsequence is exactly one char long and the contents is a low surrogate, return null.
+        if (!Character.isDefined(lastCodePoint)) return null;
+        // Bail out if the cursor is not at the end of a word (cursor must be preceded by
+        // non-whitespace, non-separator, non-start-of-text)
+        // Example ("|" is the cursor here) : <SOL>"|a" " |a" " | " all get rejected here.
+        if (settings.isWordSeparator(lastCodePoint)) return null;
+        final char firstChar = word.charAt(0); // we just tested that word is not empty
+        if (word.length() == 1 && !Character.isLetter(firstChar)) return null;
+
+        // We only suggest on words that start with a letter or a symbol that is excluded from
+        // word separators (see #handleCharacterWhileInBatchEdit).
+        if (!(Character.isLetter(firstChar)
+                || settings.isSymbolExcludedFromWordSeparators(firstChar))) {
+            return null;
+        }
+
+        return word;
+    }
+
+    public boolean revertDoubleSpace() {
+        checkBatchEdit();
+        // Here we test whether we indeed have a period and a space before us. This should not
+        // be needed, but it's there just in case something went wrong.
+        final CharSequence textBeforeCursor = getTextBeforeCursor(2, 0);
+        if (!". ".equals(textBeforeCursor)) {
+            // Theoretically we should not be coming here if there isn't ". " before the
+            // cursor, but the application may be changing the text while we are typing, so
+            // anything goes. We should not crash.
+            Log.d(TAG, "Tried to revert double-space combo but we didn't find "
+                    + "\". \" just before the cursor.");
+            return false;
+        }
+        deleteSurroundingText(2, 0);
+        if (ProductionFlag.IS_EXPERIMENTAL) {
+            ResearchLogger.latinIME_deleteSurroundingText(2);
+        }
+        commitText("  ", 1);
+        if (ProductionFlag.IS_EXPERIMENTAL) {
+            ResearchLogger.latinIME_revertDoubleSpaceWhileInBatchEdit();
+        }
+        return true;
+    }
+
+    public boolean revertSwapPunctuation() {
+        checkBatchEdit();
+        // Here we test whether we indeed have a space and something else before us. This should not
+        // be needed, but it's there just in case something went wrong.
+        final CharSequence textBeforeCursor = getTextBeforeCursor(2, 0);
+        // NOTE: This does not work with surrogate pairs. Hopefully when the keyboard is able to
+        // enter surrogate pairs this code will have been removed.
+        if (TextUtils.isEmpty(textBeforeCursor)
+                || (Keyboard.CODE_SPACE != textBeforeCursor.charAt(1))) {
+            // We may only come here if the application is changing the text while we are typing.
+            // This is quite a broken case, but not logically impossible, so we shouldn't crash,
+            // but some debugging log may be in order.
+            Log.d(TAG, "Tried to revert a swap of punctuation but we didn't "
+                    + "find a space just before the cursor.");
+            return false;
+        }
+        deleteSurroundingText(2, 0);
+        if (ProductionFlag.IS_EXPERIMENTAL) {
+            ResearchLogger.latinIME_deleteSurroundingText(2);
+        }
+        commitText(" " + textBeforeCursor.subSequence(0, 1), 1);
+        if (ProductionFlag.IS_EXPERIMENTAL) {
+            ResearchLogger.latinIME_revertSwapPunctuation();
+        }
+        return true;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 4bb2172..70acdc7 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -69,9 +69,7 @@
     public static final String PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY =
             "pref_key_preview_popup_dismiss_delay";
     public static final String PREF_KEY_USE_CONTACTS_DICT = "pref_key_use_contacts_dict";
-    public static final String PREF_BIGRAM_SUGGESTION = "next_word_suggestion";
     public static final String PREF_BIGRAM_PREDICTIONS = "next_word_prediction";
-    public static final String PREF_KEY_ENABLE_SPAN_INSERT = "enable_span_insert";
     public static final String PREF_VIBRATION_DURATION_SETTINGS =
             "pref_vibration_duration_settings";
     public static final String PREF_KEYPRESS_SOUND_VOLUME =
@@ -87,9 +85,7 @@
     private ListPreference mShowCorrectionSuggestionsPreference;
     private ListPreference mAutoCorrectionThresholdPreference;
     private ListPreference mKeyPreviewPopupDismissDelay;
-    // Suggestion: use bigrams to adjust scores of suggestions obtained from unigram dictionary
-    private CheckBoxPreference mBigramSuggestion;
-    // Prediction: use bigrams to predict the next word when there is no input for it yet
+    // Use bigrams to predict the next word when there is no input for it yet
     private CheckBoxPreference mBigramPrediction;
     private Preference mDebugSettingsPreference;
 
@@ -100,7 +96,6 @@
         final String autoCorrectionOff = getResources().getString(
                 R.string.auto_correction_threshold_mode_index_off);
         final String currentSetting = mAutoCorrectionThresholdPreference.getValue();
-        mBigramSuggestion.setEnabled(!currentSetting.equals(autoCorrectionOff));
         if (null != mBigramPrediction) {
             mBigramPrediction.setEnabled(!currentSetting.equals(autoCorrectionOff));
         }
@@ -128,7 +123,6 @@
 
         mAutoCorrectionThresholdPreference =
                 (ListPreference) findPreference(PREF_AUTO_CORRECTION_THRESHOLD);
-        mBigramSuggestion = (CheckBoxPreference) findPreference(PREF_BIGRAM_SUGGESTION);
         mBigramPrediction = (CheckBoxPreference) findPreference(PREF_BIGRAM_PREDICTIONS);
         mDebugSettingsPreference = findPreference(PREF_DEBUG_SETTINGS);
         if (mDebugSettingsPreference != null) {
@@ -155,9 +149,6 @@
 
         final PreferenceGroup advancedSettings =
                 (PreferenceGroup) findPreference(PREF_ADVANCED_SETTINGS);
-        // Remove those meaningless options for now. TODO: delete them for good
-        advancedSettings.removePreference(findPreference(PREF_BIGRAM_SUGGESTION));
-        advancedSettings.removePreference(findPreference(PREF_KEY_ENABLE_SPAN_INSERT));
         if (!VibratorUtils.getInstance(context).hasVibrator()) {
             generalSettings.removePreference(findPreference(PREF_VIBRATE_ON));
             if (null != advancedSettings) { // Theoretically advancedSettings cannot be null
@@ -165,19 +156,30 @@
             }
         }
 
-        final boolean showPopupOption = res.getBoolean(
+        final boolean showKeyPreviewPopupOption = res.getBoolean(
                 R.bool.config_enable_show_popup_on_keypress_option);
-        if (!showPopupOption) {
+        mKeyPreviewPopupDismissDelay =
+                (ListPreference) findPreference(PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
+        if (!showKeyPreviewPopupOption) {
             generalSettings.removePreference(findPreference(PREF_POPUP_ON));
-        }
-
-        final boolean showBigramSuggestionsOption = res.getBoolean(
-                R.bool.config_enable_next_word_suggestions_option);
-        if (!showBigramSuggestionsOption) {
-            textCorrectionGroup.removePreference(mBigramSuggestion);
-            if (null != mBigramPrediction) {
-                textCorrectionGroup.removePreference(mBigramPrediction);
+            if (null != advancedSettings) { // Theoretically advancedSettings cannot be null
+                advancedSettings.removePreference(mKeyPreviewPopupDismissDelay);
             }
+        } else {
+            final String[] entries = new String[] {
+                    res.getString(R.string.key_preview_popup_dismiss_no_delay),
+                    res.getString(R.string.key_preview_popup_dismiss_default_delay),
+            };
+            final String popupDismissDelayDefaultValue = Integer.toString(res.getInteger(
+                    R.integer.config_key_preview_linger_timeout));
+            mKeyPreviewPopupDismissDelay.setEntries(entries);
+            mKeyPreviewPopupDismissDelay.setEntryValues(
+                    new String[] { "0", popupDismissDelayDefaultValue });
+            if (null == mKeyPreviewPopupDismissDelay.getValue()) {
+                mKeyPreviewPopupDismissDelay.setValue(popupDismissDelayDefaultValue);
+            }
+            mKeyPreviewPopupDismissDelay.setEnabled(
+                    SettingsValues.isKeyPreviewPopupEnabled(prefs, res));
         }
 
         final CheckBoxPreference includeOtherImesInLanguageSwitchList =
@@ -185,23 +187,6 @@
         includeOtherImesInLanguageSwitchList.setEnabled(
                 !SettingsValues.isLanguageSwitchKeySupressed(prefs));
 
-        mKeyPreviewPopupDismissDelay =
-                (ListPreference)findPreference(PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
-        final String[] entries = new String[] {
-                res.getString(R.string.key_preview_popup_dismiss_no_delay),
-                res.getString(R.string.key_preview_popup_dismiss_default_delay),
-        };
-        final String popupDismissDelayDefaultValue = Integer.toString(res.getInteger(
-                R.integer.config_key_preview_linger_timeout));
-        mKeyPreviewPopupDismissDelay.setEntries(entries);
-        mKeyPreviewPopupDismissDelay.setEntryValues(
-                new String[] { "0", popupDismissDelayDefaultValue });
-        if (null == mKeyPreviewPopupDismissDelay.getValue()) {
-            mKeyPreviewPopupDismissDelay.setValue(popupDismissDelayDefaultValue);
-        }
-        mKeyPreviewPopupDismissDelay.setEnabled(
-                SettingsValues.isKeyPreviewPopupEnabled(prefs, res));
-
         final PreferenceScreen dictionaryLink =
                 (PreferenceScreen) findPreference(PREF_CONFIGURE_DICTIONARIES_KEY);
         final Intent intent = dictionaryLink.getIntent();
@@ -327,13 +312,15 @@
 
     private void updateKeyPreviewPopupDelaySummary() {
         final ListPreference lp = mKeyPreviewPopupDismissDelay;
-        lp.setSummary(lp.getEntries()[lp.findIndexOfValue(lp.getValue())]);
+        final CharSequence[] entries = lp.getEntries();
+        if (entries == null || entries.length <= 0) return;
+        lp.setSummary(entries[lp.findIndexOfValue(lp.getValue())]);
     }
 
     private void updateVoiceModeSummary() {
         mVoicePreference.setSummary(
                 getResources().getStringArray(R.array.voice_input_modes_summary)
-                [mVoicePreference.findIndexOfValue(mVoicePreference.getValue())]);
+                        [mVoicePreference.findIndexOfValue(mVoicePreference.getValue())]);
     }
 
     private void refreshEnablingsOfKeypressSoundAndVibrationSettings(
diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java
index b07c3e5..10025da 100644
--- a/java/src/com/android/inputmethod/latin/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.content.SharedPreferences;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.util.Log;
 import android.view.inputmethod.EditorInfo;
@@ -29,7 +30,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
-import java.util.Map;
 
 /**
  * When you call the constructor of this class, you may want to change the current system locale by
@@ -38,6 +38,19 @@
 public class SettingsValues {
     private static final String TAG = SettingsValues.class.getSimpleName();
 
+    private static final int SUGGESTION_VISIBILITY_SHOW_VALUE
+            = R.string.prefs_suggestion_visibility_show_value;
+    private static final int SUGGESTION_VISIBILITY_SHOW_ONLY_PORTRAIT_VALUE
+            = R.string.prefs_suggestion_visibility_show_only_portrait_value;
+    private static final int SUGGESTION_VISIBILITY_HIDE_VALUE
+            = R.string.prefs_suggestion_visibility_hide_value;
+
+    private static final int[] SUGGESTION_VISIBILITY_VALUE_ARRAY = new int[] {
+        SUGGESTION_VISIBILITY_SHOW_VALUE,
+        SUGGESTION_VISIBILITY_SHOW_ONLY_PORTRAIT_VALUE,
+        SUGGESTION_VISIBILITY_HIDE_VALUE
+    };
+
     // From resources:
     public final int mDelayUpdateOldSuggestions;
     public final String mWeakSpaceStrippers;
@@ -63,27 +76,30 @@
     @SuppressWarnings("unused") // TODO: Use this
     private final String mKeyPreviewPopupDismissDelayRawValue;
     public final boolean mUseContactsDict;
-    // Suggestion: use bigrams to adjust scores of suggestions obtained from unigram dictionary
-    public final boolean mBigramSuggestionEnabled;
-    // Prediction: use bigrams to predict the next word when there is no input for it yet
+    // Use bigrams to predict the next word when there is no input for it yet
     public final boolean mBigramPredictionEnabled;
-    public final boolean mEnableSuggestionSpanInsertion;
     @SuppressWarnings("unused") // TODO: Use this
     private final int mVibrationDurationSettingsRawValue;
     @SuppressWarnings("unused") // TODO: Use this
     private final float mKeypressSoundVolumeRawValue;
     private final InputMethodSubtype[] mAdditionalSubtypes;
 
+    // From the input box
+    private final InputAttributes mInputAttributes;
+
     // Deduced settings
     public final int mKeypressVibrationDuration;
     public final float mFxVolume;
     public final int mKeyPreviewPopupDismissDelay;
-    public final boolean mAutoCorrectEnabled;
+    private final boolean mAutoCorrectEnabled;
     public final float mAutoCorrectionThreshold;
+    public final boolean mCorrectionEnabled;
+    public final int mSuggestionVisibility;
     private final boolean mVoiceKeyEnabled;
     private final boolean mVoiceKeyOnMain;
 
-    public SettingsValues(final SharedPreferences prefs, final Context context) {
+    public SettingsValues(final SharedPreferences prefs, final InputAttributes inputAttributes,
+            final Context context) {
         final Resources res = context.getResources();
 
         // Get the resources
@@ -109,6 +125,13 @@
                 mSymbolsExcludedFromWordSeparators, res);
         mHintToSaveText = context.getText(R.string.hint_add_to_dictionary);
 
+        // Store the input attributes
+        if (null == inputAttributes) {
+            mInputAttributes = new InputAttributes(null, false /* isFullscreenMode */);
+        } else {
+            mInputAttributes = inputAttributes;
+        }
+
         // Get the settings preferences
         mAutoCap = prefs.getBoolean(Settings.PREF_AUTO_CAP, true);
         mVibrateOn = isVibrateOn(context, prefs, res);
@@ -131,12 +154,7 @@
                 Integer.toString(res.getInteger(R.integer.config_key_preview_linger_timeout)));
         mUseContactsDict = prefs.getBoolean(Settings.PREF_KEY_USE_CONTACTS_DICT, true);
         mAutoCorrectEnabled = isAutoCorrectEnabled(res, mAutoCorrectionThresholdRawValue);
-        mBigramSuggestionEnabled = mAutoCorrectEnabled
-                && isBigramSuggestionEnabled(prefs, res, mAutoCorrectEnabled);
-        mBigramPredictionEnabled = mBigramSuggestionEnabled
-                && isBigramPredictionEnabled(prefs, res);
-        // TODO: remove mEnableSuggestionSpanInsertion. It's always true.
-        mEnableSuggestionSpanInsertion = true;
+        mBigramPredictionEnabled = isBigramPredictionEnabled(prefs, res);
         mVibrationDurationSettingsRawValue =
                 prefs.getInt(Settings.PREF_VIBRATION_DURATION_SETTINGS, -1);
         mKeypressSoundVolumeRawValue = prefs.getFloat(Settings.PREF_KEYPRESS_SOUND_VOLUME, -1.0f);
@@ -151,22 +169,23 @@
         mVoiceKeyOnMain = mVoiceMode != null && mVoiceMode.equals(voiceModeMain);
         mAdditionalSubtypes = AdditionalSubtype.createAdditionalSubtypesArray(
                 getPrefAdditionalSubtypes(prefs, res));
+        mCorrectionEnabled = mAutoCorrectEnabled && !mInputAttributes.mInputTypeNoAutoCorrect;
+        mSuggestionVisibility = createSuggestionVisibility(res);
     }
 
     // Helper functions to create member values.
     private static SuggestedWords createSuggestPuncList(final String[] puncs) {
-        final ArrayList<SuggestedWords.SuggestedWordInfo> puncList =
-                new ArrayList<SuggestedWords.SuggestedWordInfo>();
+        final ArrayList<SuggestedWordInfo> puncList = new ArrayList<SuggestedWordInfo>();
         if (puncs != null) {
             for (final String puncSpec : puncs) {
-                puncList.add(new SuggestedWords.SuggestedWordInfo(
-                        KeySpecParser.getLabel(puncSpec), SuggestedWordInfo.MAX_SCORE));
+                puncList.add(new SuggestedWordInfo(KeySpecParser.getLabel(puncSpec),
+                        SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_HARDCODED,
+                        Dictionary.TYPE_HARDCODED));
             }
         }
         return new SuggestedWords(puncList,
                 false /* typedWordValid */,
                 false /* hasAutoCorrectionCandidate */,
-                false /* allowsToBeAutoCorrected */,
                 true /* isPunctuationSuggestions */,
                 false /* isObsoleteSuggestions */,
                 false /* isPrediction */);
@@ -184,6 +203,16 @@
         return wordSeparators;
     }
 
+    private int createSuggestionVisibility(final Resources res) {
+        final String suggestionVisiblityStr = mShowSuggestionsSetting;
+        for (int visibility : SUGGESTION_VISIBILITY_VALUE_ARRAY) {
+            if (suggestionVisiblityStr.equals(res.getString(visibility))) {
+                return visibility;
+            }
+        }
+        throw new RuntimeException("Bug: visibility string is not configured correctly");
+    }
+
     private static boolean isVibrateOn(final Context context, final SharedPreferences prefs,
             final Resources res) {
         final boolean hasVibrator = VibratorUtils.getInstance(context).hasVibrator();
@@ -191,6 +220,22 @@
                 res.getBoolean(R.bool.config_default_vibration_enabled));
     }
 
+    public boolean isApplicationSpecifiedCompletionsOn() {
+        return mInputAttributes.mApplicationSpecifiedCompletionOn;
+    }
+
+    public boolean isSuggestionsRequested(final int displayOrientation) {
+        return mInputAttributes.mIsSettingsSuggestionStripOn
+                && (mCorrectionEnabled
+                        || isSuggestionStripVisibleInOrientation(displayOrientation));
+    }
+
+    public boolean isSuggestionStripVisibleInOrientation(final int orientation) {
+        return (mSuggestionVisibility == SUGGESTION_VISIBILITY_SHOW_VALUE)
+                || (mSuggestionVisibility == SUGGESTION_VISIBILITY_SHOW_ONLY_PORTRAIT_VALUE
+                        && orientation == Configuration.ORIENTATION_PORTRAIT);
+    }
+
     public boolean isWordSeparator(int code) {
         return mWordSeparators.contains(String.valueOf((char)code));
     }
@@ -240,12 +285,6 @@
                         R.integer.config_key_preview_linger_timeout))));
     }
 
-    private static boolean isBigramSuggestionEnabled(final SharedPreferences sp,
-            final Resources resources, final boolean autoCorrectEnabled) {
-        // TODO: remove this method. Bigram suggestion is always true.
-        return true;
-    }
-
     private static boolean isBigramPredictionEnabled(final SharedPreferences sp,
             final Resources resources) {
         return sp.getBoolean(Settings.PREF_BIGRAM_PREDICTIONS, resources.getBoolean(
@@ -367,4 +406,9 @@
         final String newStr = Utils.localeAndTimeHashMapToStr(map);
         prefs.edit().putString(Settings.PREF_LAST_USER_DICTIONARY_WRITE_TIME, newStr).apply();
     }
+
+    // For debug.
+    public String getInputAttributesDebugString() {
+        return mInputAttributes.toString();
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
index a43b905..6e7d985 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -184,6 +184,9 @@
         final char[] characters = string.toCharArray();
         final int length = characters.length;
         final int[] codePoints = new int[Character.codePointCount(characters, 0, length)];
+        if (length <= 0) {
+            return new int[0];
+        }
         int codePoint = Character.codePointAt(characters, 0);
         int dsti = 0;
         for (int srci = Character.charCount(codePoint);
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 336a76f..31c6000 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -18,7 +18,6 @@
 
 import android.content.Context;
 import android.text.TextUtils;
-import android.util.Log;
 
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.ProximityInfo;
@@ -26,6 +25,7 @@
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.HashSet;
 import java.util.Locale;
 import java.util.concurrent.ConcurrentHashMap;
@@ -34,79 +34,47 @@
  * This class loads a dictionary and provides a list of suggestions for a given sequence of
  * characters. This includes corrections and completions.
  */
-public class Suggest implements Dictionary.WordCallback {
+public class Suggest {
     public static final String TAG = Suggest.class.getSimpleName();
 
-    public static final int APPROX_MAX_WORD_LENGTH = 32;
-
+    // TODO: rename this to CORRECTION_OFF
     public static final int CORRECTION_NONE = 0;
+    // TODO: rename this to CORRECTION_ON
     public static final int CORRECTION_FULL = 1;
-    public static final int CORRECTION_FULL_BIGRAM = 2;
-
-    // It seems the following values are only used for logging.
-    public static final int DIC_USER_TYPED = 0;
-    public static final int DIC_MAIN = 1;
-    public static final int DIC_USER = 2;
-    public static final int DIC_USER_HISTORY = 3;
-    public static final int DIC_CONTACTS = 4;
-    public static final int DIC_WHITELIST = 6;
-    // If you add a type of dictionary, increment DIC_TYPE_LAST_ID
-    // TODO: this value seems unused. Remove it?
-    public static final int DIC_TYPE_LAST_ID = 6;
-    public static final String DICT_KEY_MAIN = "main";
-    public static final String DICT_KEY_CONTACTS = "contacts";
-    // User dictionary, the system-managed one.
-    public static final String DICT_KEY_USER = "user";
-    // User history dictionary for the unigram map, internal to LatinIME
-    public static final String DICT_KEY_USER_HISTORY_UNIGRAM = "history_unigram";
-    // User history dictionary for the bigram map, internal to LatinIME
-    public static final String DICT_KEY_USER_HISTORY_BIGRAM = "history_bigram";
-    public static final String DICT_KEY_WHITELIST ="whitelist";
 
     private static final boolean DBG = LatinImeLogger.sDBG;
 
-    private boolean mHasMainDictionary;
-    private Dictionary mContactsDict;
+    private Dictionary mMainDictionary;
+    private ContactsBinaryDictionary mContactsDict;
     private WhitelistDictionary mWhiteListDictionary;
-    private final ConcurrentHashMap<String, Dictionary> mUnigramDictionaries =
-            new ConcurrentHashMap<String, Dictionary>();
-    private final ConcurrentHashMap<String, Dictionary> mBigramDictionaries =
+    private final ConcurrentHashMap<String, Dictionary> mDictionaries =
             new ConcurrentHashMap<String, Dictionary>();
 
-    private int mPrefMaxSuggestions = 18;
-
-    private static final int PREF_MAX_BIGRAMS = 60;
+    public static final int MAX_SUGGESTIONS = 18;
 
     private float mAutoCorrectionThreshold;
 
-    private ArrayList<SuggestedWordInfo> mSuggestions = new ArrayList<SuggestedWordInfo>();
-    private ArrayList<SuggestedWordInfo> mBigramSuggestions = new ArrayList<SuggestedWordInfo>();
-    private CharSequence mConsideredWord;
-
-    // TODO: Remove these member variables by passing more context to addWord() callback method
-    private boolean mIsFirstCharCapitalized;
-    private boolean mIsAllUpperCase;
-    private int mTrailingSingleQuotesCount;
-
-    private static final int MINIMUM_SAFETY_NET_CHAR_LENGTH = 4;
+    // Locale used for upper- and title-casing words
+    final private Locale mLocale;
 
     public Suggest(final Context context, final Locale locale) {
         initAsynchronously(context, locale);
+        mLocale = locale;
     }
 
     /* package for test */ Suggest(final Context context, final File dictionary,
             final long startOffset, final long length, final Locale locale) {
         final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(context, dictionary,
                 startOffset, length /* useFullEditDistance */, false, locale);
-        mHasMainDictionary = null != mainDict;
-        addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_MAIN, mainDict);
-        addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_MAIN, mainDict);
+        mLocale = locale;
+        mMainDictionary = mainDict;
+        addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, mainDict);
         initWhitelistAndAutocorrectAndPool(context, locale);
     }
 
     private void initWhitelistAndAutocorrectAndPool(final Context context, final Locale locale) {
         mWhiteListDictionary = new WhitelistDictionary(context, locale);
-        addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_WHITELIST, mWhiteListDictionary);
+        addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_WHITELIST, mWhiteListDictionary);
     }
 
     private void initAsynchronously(final Context context, final Locale locale) {
@@ -129,15 +97,14 @@
     }
 
     public void resetMainDict(final Context context, final Locale locale) {
-        mHasMainDictionary = false;
+        mMainDictionary = null;
         new Thread("InitializeBinaryDictionary") {
             @Override
             public void run() {
                 final DictionaryCollection newMainDict =
                         DictionaryFactory.createMainDictionaryFromManager(context, locale);
-                mHasMainDictionary = null != newMainDict && !newMainDict.isEmpty();
-                addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_MAIN, newMainDict);
-                addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_MAIN, newMainDict);
+                addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, newMainDict);
+                mMainDictionary = newMainDict;
             }
         }.start();
     }
@@ -145,27 +112,27 @@
     // The main dictionary could have been loaded asynchronously.  Don't cache the return value
     // of this method.
     public boolean hasMainDictionary() {
-        return mHasMainDictionary;
+        return null != mMainDictionary && mMainDictionary.isInitialized();
     }
 
-    public Dictionary getContactsDictionary() {
+    public Dictionary getMainDictionary() {
+        return mMainDictionary;
+    }
+
+    public ContactsBinaryDictionary getContactsDictionary() {
         return mContactsDict;
     }
 
     public ConcurrentHashMap<String, Dictionary> getUnigramDictionaries() {
-        return mUnigramDictionaries;
-    }
-
-    public static int getApproxMaxWordLength() {
-        return APPROX_MAX_WORD_LENGTH;
+        return mDictionaries;
     }
 
     /**
      * Sets an optional user dictionary resource to be loaded. The user dictionary is consulted
      * before the main dictionary, if set. This refers to the system-managed user dictionary.
      */
-    public void setUserDictionary(Dictionary userDictionary) {
-        addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_USER, userDictionary);
+    public void setUserDictionary(UserBinaryDictionary userDictionary) {
+        addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_USER, userDictionary);
     }
 
     /**
@@ -173,238 +140,193 @@
      * the contacts dictionary by passing null to this method. In this case no contacts dictionary
      * won't be used.
      */
-    public void setContactsDictionary(Dictionary contactsDictionary) {
+    public void setContactsDictionary(ContactsBinaryDictionary contactsDictionary) {
         mContactsDict = contactsDictionary;
-        addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_CONTACTS, contactsDictionary);
-        addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_CONTACTS, contactsDictionary);
+        addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_CONTACTS, contactsDictionary);
     }
 
-    public void setUserHistoryDictionary(Dictionary userHistoryDictionary) {
-        addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_USER_HISTORY_UNIGRAM,
-                userHistoryDictionary);
-        addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_USER_HISTORY_BIGRAM,
-                userHistoryDictionary);
+    public void setUserHistoryDictionary(UserHistoryDictionary userHistoryDictionary) {
+        addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_USER_HISTORY, userHistoryDictionary);
     }
 
     public void setAutoCorrectionThreshold(float threshold) {
         mAutoCorrectionThreshold = threshold;
     }
 
-    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 = new StringBuilder(getApproxMaxWordLength());
-        // TODO: Must pay attention to locale when changing case.
-        if (all) {
-            sb.append(word.toString().toUpperCase());
-        } else if (first) {
-            sb.append(Character.toUpperCase(word.charAt(0)));
-            if (wordLength > 1) {
-                sb.append(word.subSequence(1, wordLength));
-            }
-        }
-        return sb;
-    }
-
-    protected void addBigramToSuggestions(SuggestedWordInfo bigram) {
-        mSuggestions.add(bigram);
-    }
-
-    private static final WordComposer sEmptyWordComposer = new WordComposer();
-    public SuggestedWords getBigramPredictions(CharSequence prevWordForBigram) {
-        LatinImeLogger.onStartSuggestion(prevWordForBigram);
-        mIsFirstCharCapitalized = false;
-        mIsAllUpperCase = false;
-        mTrailingSingleQuotesCount = 0;
-        mSuggestions = new ArrayList<SuggestedWordInfo>(mPrefMaxSuggestions);
-
-        // Treating USER_TYPED as UNIGRAM suggestion for logging now.
-        LatinImeLogger.onAddSuggestedWord("", Suggest.DIC_USER_TYPED, Dictionary.UNIGRAM);
-        mConsideredWord = "";
-
-        mBigramSuggestions = new ArrayList<SuggestedWordInfo>(PREF_MAX_BIGRAMS);
-
-        getAllBigrams(prevWordForBigram, sEmptyWordComposer);
-
-        // Nothing entered: return all bigrams for the previous word
-        int insertCount = Math.min(mBigramSuggestions.size(), mPrefMaxSuggestions);
-        for (int i = 0; i < insertCount; ++i) {
-            addBigramToSuggestions(mBigramSuggestions.get(i));
-        }
-
-        SuggestedWordInfo.removeDups(mSuggestions);
-
-        return new SuggestedWords(mSuggestions,
-                false /* typedWordValid */,
-                false /* hasAutoCorrectionCandidate */,
-                false /* allowsToBeAutoCorrected */,
-                false /* isPunctuationSuggestions */,
-                false /* isObsoleteSuggestions */,
-                true /* isPrediction */);
-    }
-
-    // TODO: cleanup dictionaries looking up and suggestions building with SuggestedWords.Builder
     public SuggestedWords getSuggestedWords(
             final WordComposer wordComposer, CharSequence prevWordForBigram,
-            final ProximityInfo proximityInfo, final int correctionMode) {
+            final ProximityInfo proximityInfo, final boolean isCorrectionEnabled) {
         LatinImeLogger.onStartSuggestion(prevWordForBigram);
-        mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
-        mIsAllUpperCase = wordComposer.isAllUpperCase();
-        mTrailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount();
-        mSuggestions = new ArrayList<SuggestedWordInfo>(mPrefMaxSuggestions);
+        if (wordComposer.isBatchMode()) {
+            return getSuggestedWordsForBatchInput(wordComposer, prevWordForBigram, proximityInfo);
+        } else {
+            return getSuggestedWordsForTypingInput(wordComposer, prevWordForBigram, proximityInfo,
+                    isCorrectionEnabled);
+        }
+    }
+
+    // Retrieves suggestions for the typing input.
+    private SuggestedWords getSuggestedWordsForTypingInput(
+            final WordComposer wordComposer, CharSequence prevWordForBigram,
+            final ProximityInfo proximityInfo, final boolean isCorrectionEnabled) {
+        final boolean isPrediction = !wordComposer.isComposingWord();
+        final boolean isFirstCharCapitalized =
+                !isPrediction && wordComposer.isFirstCharCapitalized();
+        final boolean isAllUpperCase = !isPrediction && wordComposer.isAllUpperCase();
+        final int trailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount();
+        final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator,
+                MAX_SUGGESTIONS);
 
         final String typedWord = wordComposer.getTypedWord();
-        final String consideredWord = mTrailingSingleQuotesCount > 0
-                ? typedWord.substring(0, typedWord.length() - mTrailingSingleQuotesCount)
+        final String consideredWord = trailingSingleQuotesCount > 0
+                ? typedWord.substring(0, typedWord.length() - trailingSingleQuotesCount)
                 : typedWord;
-        // Treating USER_TYPED as UNIGRAM suggestion for logging now.
-        LatinImeLogger.onAddSuggestedWord(typedWord, Suggest.DIC_USER_TYPED, Dictionary.UNIGRAM);
-        mConsideredWord = consideredWord;
+        LatinImeLogger.onAddSuggestedWord(typedWord, Dictionary.TYPE_USER_TYPED);
 
-        if (wordComposer.size() <= 1 && (correctionMode == CORRECTION_FULL_BIGRAM)) {
+        final WordComposer wordComposerForLookup;
+        if (trailingSingleQuotesCount > 0) {
+            wordComposerForLookup = new WordComposer(wordComposer);
+            for (int i = trailingSingleQuotesCount - 1; i >= 0; --i) {
+                wordComposerForLookup.deleteLast();
+            }
+        } else {
+            wordComposerForLookup = wordComposer;
+        }
+        if (wordComposerForLookup.size() <= 1) {
             // At first character typed, search only the bigrams
-            mBigramSuggestions = new ArrayList<SuggestedWordInfo>(PREF_MAX_BIGRAMS);
-
             if (!TextUtils.isEmpty(prevWordForBigram)) {
-                getAllBigrams(prevWordForBigram, wordComposer);
-                if (TextUtils.isEmpty(consideredWord)) {
-                    // Nothing entered: return all bigrams for the previous word
-                    int insertCount = Math.min(mBigramSuggestions.size(), mPrefMaxSuggestions);
-                    for (int i = 0; i < insertCount; ++i) {
-                        addBigramToSuggestions(mBigramSuggestions.get(i));
-                    }
-                } else {
-                    // Word entered: return only bigrams that match the first char of the typed word
-                    final char currentChar = consideredWord.charAt(0);
-                    // TODO: Must pay attention to locale when changing case.
-                    // TODO: Use codepoint instead of char
-                    final char currentCharUpper = Character.toUpperCase(currentChar);
-                    int count = 0;
-                    final int bigramSuggestionSize = mBigramSuggestions.size();
-                    for (int i = 0; i < bigramSuggestionSize; i++) {
-                        final SuggestedWordInfo bigramSuggestion = mBigramSuggestions.get(i);
-                        final char bigramSuggestionFirstChar =
-                                (char)bigramSuggestion.codePointAt(0);
-                        if (bigramSuggestionFirstChar == currentChar
-                                || bigramSuggestionFirstChar == currentCharUpper) {
-                            addBigramToSuggestions(bigramSuggestion);
-                            if (++count > mPrefMaxSuggestions) break;
-                        }
-                    }
+                for (final String key : mDictionaries.keySet()) {
+                    final Dictionary dictionary = mDictionaries.get(key);
+                    suggestionsSet.addAll(dictionary.getSuggestions(wordComposerForLookup,
+                            prevWordForBigram, proximityInfo));
                 }
             }
-
-        } else if (wordComposer.size() > 1) {
-            final WordComposer wordComposerForLookup;
-            if (mTrailingSingleQuotesCount > 0) {
-                wordComposerForLookup = new WordComposer(wordComposer);
-                for (int i = mTrailingSingleQuotesCount - 1; i >= 0; --i) {
-                    wordComposerForLookup.deleteLast();
-                }
-            } else {
-                wordComposerForLookup = wordComposer;
-            }
+        } else {
             // At second character typed, search the unigrams (scores being affected by bigrams)
-            for (final String key : mUnigramDictionaries.keySet()) {
-                // Skip UserUnigramDictionary and WhitelistDictionary to lookup
-                if (key.equals(DICT_KEY_USER_HISTORY_UNIGRAM) || key.equals(DICT_KEY_WHITELIST))
-                    continue;
-                final Dictionary dictionary = mUnigramDictionaries.get(key);
-                dictionary.getWords(wordComposerForLookup, prevWordForBigram, this, proximityInfo);
+            for (final String key : mDictionaries.keySet()) {
+                final Dictionary dictionary = mDictionaries.get(key);
+                suggestionsSet.addAll(dictionary.getSuggestions(
+                        wordComposerForLookup, prevWordForBigram, proximityInfo));
             }
         }
 
-        final CharSequence whitelistedWord = capitalizeWord(mIsAllUpperCase,
-                mIsFirstCharCapitalized, mWhiteListDictionary.getWhitelistedWord(consideredWord));
-
-        final boolean hasAutoCorrection;
-        if (CORRECTION_FULL == correctionMode || CORRECTION_FULL_BIGRAM == correctionMode) {
-            final CharSequence autoCorrection =
-                    AutoCorrection.computeAutoCorrectionWord(mUnigramDictionaries, wordComposer,
-                            mSuggestions, consideredWord, mAutoCorrectionThreshold,
-                            whitelistedWord);
-            hasAutoCorrection = (null != autoCorrection);
-        } else {
-            hasAutoCorrection = false;
-        }
-
-        if (whitelistedWord != null) {
-            if (mTrailingSingleQuotesCount > 0) {
-                final StringBuilder sb = new StringBuilder(whitelistedWord);
-                for (int i = mTrailingSingleQuotesCount - 1; i >= 0; --i) {
-                    sb.appendCodePoint(Keyboard.CODE_SINGLE_QUOTE);
-                }
-                mSuggestions.add(0, new SuggestedWordInfo(
-                        sb.toString(), SuggestedWordInfo.MAX_SCORE));
-            } else {
-                mSuggestions.add(0, new SuggestedWordInfo(
-                        whitelistedWord, SuggestedWordInfo.MAX_SCORE));
-            }
-        }
-
-        mSuggestions.add(0, new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE));
-        SuggestedWordInfo.removeDups(mSuggestions);
-
-        final ArrayList<SuggestedWordInfo> suggestionsList;
-        if (DBG) {
-            suggestionsList = getSuggestionsInfoListWithDebugInfo(typedWord, mSuggestions);
-        } else {
-            suggestionsList = mSuggestions;
-        }
-
         // TODO: Change this scheme - a boolean is not enough. A whitelisted word may be "valid"
         // but still autocorrected from - in the case the whitelist only capitalizes the word.
         // The whitelist should be case-insensitive, so it's not possible to be consistent with
         // a boolean flag. Right now this is handled with a slight hack in
         // WhitelistDictionary#shouldForciblyAutoCorrectFrom.
-        final boolean allowsToBeAutoCorrected = AutoCorrection.allowsToBeAutoCorrected(
-                getUnigramDictionaries(), consideredWord, wordComposer.isFirstCharCapitalized())
-        // If we don't have a main dictionary, we never want to auto-correct. The reason for this
-        // is, the user may have a contact whose name happens to match a valid word in their
-        // language, and it will unexpectedly auto-correct. For example, if the user types in
-        // English with no dictionary and has a "Will" in their contact list, "will" would
-        // always auto-correct to "Will" which is unwanted. Hence, no main dict => no auto-correct.
-                && mHasMainDictionary;
+        final boolean allowsToBeAutoCorrected = AutoCorrection.isWhitelistedOrNotAWord(
+                mDictionaries, consideredWord, wordComposer.isFirstCharCapitalized());
 
-        boolean autoCorrectionAvailable = hasAutoCorrection;
-        if (correctionMode == CORRECTION_FULL || correctionMode == CORRECTION_FULL_BIGRAM) {
-            autoCorrectionAvailable |= !allowsToBeAutoCorrected;
+        final CharSequence whitelistedWord =
+                mWhiteListDictionary.getWhitelistedWord(consideredWord);
+
+        final boolean hasAutoCorrection;
+        // TODO: using isCorrectionEnabled here is not very good. It's probably useless, because
+        // any attempt to do auto-correction is already shielded with a test for this flag; at the
+        // same time, it feels wrong that the SuggestedWord object includes information about
+        // the current settings. It may also be useful to know, when the setting is off, whether
+        // the word *would* have been auto-corrected.
+        if (!isCorrectionEnabled || !allowsToBeAutoCorrected || wordComposer.isMostlyCaps()
+                || wordComposer.isResumed() || !hasMainDictionary()) {
+            // If we don't have a main dictionary, we never want to auto-correct. The reason for
+            // this is, the user may have a contact whose name happens to match a valid word in
+            // their language, and it will unexpectedly auto-correct. For example, if the user
+            // types in English with no dictionary and has a "Will" in their contact list, "will"
+            // would always auto-correct to "Will" which is unwanted. Hence, no main dict => no
+            // auto-correct.
+            hasAutoCorrection = false;
+        } else if (null != whitelistedWord) {
+            hasAutoCorrection = true;
+        } else if (suggestionsSet.isEmpty()) {
+            hasAutoCorrection = false;
+        } else if (AutoCorrection.suggestionExceedsAutoCorrectionThreshold(suggestionsSet.first(),
+                consideredWord, mAutoCorrectionThreshold)) {
+            hasAutoCorrection = true;
+        } else {
+            hasAutoCorrection = false;
         }
-        // Don't auto-correct words with multiple capital letter
-        autoCorrectionAvailable &= !wordComposer.isMostlyCaps();
-        autoCorrectionAvailable &= !wordComposer.isResumed();
-        if (allowsToBeAutoCorrected && suggestionsList.size() > 1 && mAutoCorrectionThreshold > 0
-                && Suggest.shouldBlockAutoCorrectionBySafetyNet(typedWord,
-                        suggestionsList.get(1).mWord)) {
-            autoCorrectionAvailable = false;
+
+        if (whitelistedWord != null) {
+            suggestionsSet.add(new SuggestedWordInfo(whitelistedWord,
+                    SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_WHITELIST,
+                    Dictionary.TYPE_WHITELIST));
         }
+
+        final ArrayList<SuggestedWordInfo> suggestionsContainer =
+                new ArrayList<SuggestedWordInfo>(suggestionsSet);
+        final int suggestionsCount = suggestionsContainer.size();
+        if (isFirstCharCapitalized || isAllUpperCase || 0 != trailingSingleQuotesCount) {
+            for (int i = 0; i < suggestionsCount; ++i) {
+                final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
+                final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo(
+                        wordInfo, mLocale, isAllUpperCase, isFirstCharCapitalized,
+                        trailingSingleQuotesCount);
+                suggestionsContainer.set(i, transformedWordInfo);
+            }
+        }
+
+        for (int i = 0; i < suggestionsCount; ++i) {
+            final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
+            LatinImeLogger.onAddSuggestedWord(wordInfo.mWord.toString(), wordInfo.mSourceDict);
+        }
+
+        if (!TextUtils.isEmpty(typedWord)) {
+            suggestionsContainer.add(0, new SuggestedWordInfo(typedWord,
+                    SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_TYPED,
+                    Dictionary.TYPE_USER_TYPED));
+        }
+        SuggestedWordInfo.removeDups(suggestionsContainer);
+
+        final ArrayList<SuggestedWordInfo> suggestionsList;
+        if (DBG && !suggestionsContainer.isEmpty()) {
+            suggestionsList = getSuggestionsInfoListWithDebugInfo(typedWord, suggestionsContainer);
+        } else {
+            suggestionsList = suggestionsContainer;
+        }
+
         return new SuggestedWords(suggestionsList,
-                !allowsToBeAutoCorrected /* typedWordValid */,
-                autoCorrectionAvailable /* hasAutoCorrectionCandidate */,
-                allowsToBeAutoCorrected /* allowsToBeAutoCorrected */,
+                // TODO: this first argument is lying. If this is a whitelisted word which is an
+                // actual word, it says typedWordValid = false, which looks wrong. We should either
+                // rename the attribute or change the value.
+                !isPrediction && !allowsToBeAutoCorrected /* typedWordValid */,
+                !isPrediction && hasAutoCorrection, /* willAutoCorrect */
+                false /* isPunctuationSuggestions */,
+                false /* isObsoleteSuggestions */,
+                isPrediction);
+    }
+
+    // Retrieves suggestions for the batch input.
+    private SuggestedWords getSuggestedWordsForBatchInput(
+            final WordComposer wordComposer, CharSequence prevWordForBigram,
+            final ProximityInfo proximityInfo) {
+        final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator,
+                MAX_SUGGESTIONS);
+
+        // At second character typed, search the unigrams (scores being affected by bigrams)
+        for (final String key : mDictionaries.keySet()) {
+            // Skip UserUnigramDictionary and WhitelistDictionary to lookup
+            if (key.equals(Dictionary.TYPE_USER_HISTORY)
+                    || key.equals(Dictionary.TYPE_WHITELIST)) {
+                continue;
+            }
+            final Dictionary dictionary = mDictionaries.get(key);
+            suggestionsSet.addAll(dictionary.getSuggestions(
+                    wordComposer, prevWordForBigram, proximityInfo));
+        }
+
+        final ArrayList<SuggestedWordInfo> suggestionsContainer =
+                new ArrayList<SuggestedWordInfo>(suggestionsSet);
+
+        SuggestedWordInfo.removeDups(suggestionsContainer);
+        return new SuggestedWords(suggestionsContainer,
+                true /* typedWordValid */,
+                true /* willAutoCorrect */,
                 false /* isPunctuationSuggestions */,
                 false /* isObsoleteSuggestions */,
                 false /* isPrediction */);
     }
 
-    /**
-     * Adds all bigram predictions for prevWord. Also checks the lower case version of prevWord if
-     * it contains any upper case characters.
-     */
-    private void getAllBigrams(final CharSequence prevWord, final WordComposer wordComposer) {
-        if (StringUtils.hasUpperCase(prevWord)) {
-            // TODO: Must pay attention to locale when changing case.
-            final CharSequence lowerPrevWord = prevWord.toString().toLowerCase();
-            for (final Dictionary dictionary : mBigramDictionaries.values()) {
-                dictionary.getBigrams(wordComposer, lowerPrevWord, this);
-            }
-        }
-        for (final Dictionary dictionary : mBigramDictionaries.values()) {
-            dictionary.getBigrams(wordComposer, prevWord, this);
-        }
-    }
-
     private static ArrayList<SuggestedWordInfo> getSuggestionsInfoListWithDebugInfo(
             final String typedWord, final ArrayList<SuggestedWordInfo> suggestions) {
         final SuggestedWordInfo typedWordInfo = suggestions.get(0);
@@ -431,119 +353,44 @@
         return suggestionsList;
     }
 
-    // TODO: Use codepoint instead of char
-    @Override
-    public boolean addWord(final char[] word, final int offset, final int length, int score,
-            final int dicTypeId, final int dataType) {
-        int dataTypeForLog = dataType;
-        final ArrayList<SuggestedWordInfo> suggestions;
-        final int prefMaxSuggestions;
-        if (dataType == Dictionary.BIGRAM) {
-            suggestions = mBigramSuggestions;
-            prefMaxSuggestions = PREF_MAX_BIGRAMS;
-        } else {
-            suggestions = mSuggestions;
-            prefMaxSuggestions = mPrefMaxSuggestions;
+    private static class SuggestedWordInfoComparator implements Comparator<SuggestedWordInfo> {
+        // This comparator ranks the word info with the higher frequency first. That's because
+        // that's the order we want our elements in.
+        @Override
+        public int compare(final SuggestedWordInfo o1, final SuggestedWordInfo o2) {
+            if (o1.mScore > o2.mScore) return -1;
+            if (o1.mScore < o2.mScore) return 1;
+            if (o1.mCodePointCount < o2.mCodePointCount) return -1;
+            if (o1.mCodePointCount > o2.mCodePointCount) return 1;
+            return o1.mWord.toString().compareTo(o2.mWord.toString());
         }
+    }
+    private static final SuggestedWordInfoComparator sSuggestedWordInfoComparator =
+            new SuggestedWordInfoComparator();
 
-        int pos = 0;
-
-        // Check if it's the same word, only caps are different
-        if (StringUtils.equalsIgnoreCase(mConsideredWord, word, offset, length)) {
-            // TODO: remove this surrounding if clause and move this logic to
-            // getSuggestedWordBuilder.
-            if (suggestions.size() > 0) {
-                final SuggestedWordInfo currentHighestWord = suggestions.get(0);
-                // If the current highest word is also equal to typed word, we need to compare
-                // frequency to determine the insertion position. This does not ensure strictly
-                // correct ordering, but ensures the top score is on top which is enough for
-                // removing duplicates correctly.
-                if (StringUtils.equalsIgnoreCase(currentHighestWord.mWord, word, offset, length)
-                        && score <= currentHighestWord.mScore) {
-                    pos = 1;
-                }
-            }
+    private static SuggestedWordInfo getTransformedSuggestedWordInfo(
+            final SuggestedWordInfo wordInfo, final Locale locale, final boolean isAllUpperCase,
+            final boolean isFirstCharCapitalized, final int trailingSingleQuotesCount) {
+        final StringBuilder sb = new StringBuilder(wordInfo.mWord.length());
+        if (isAllUpperCase) {
+            sb.append(wordInfo.mWord.toString().toUpperCase(locale));
+        } else if (isFirstCharCapitalized) {
+            sb.append(StringUtils.toTitleCase(wordInfo.mWord.toString(), locale));
         } else {
-            // Check the last one's score and bail
-            if (suggestions.size() >= prefMaxSuggestions
-                    && suggestions.get(prefMaxSuggestions - 1).mScore >= score) return true;
-            while (pos < suggestions.size()) {
-                final int curScore = suggestions.get(pos).mScore;
-                if (curScore < score
-                        || (curScore == score && length < suggestions.get(pos).codePointCount())) {
-                    break;
-                }
-                pos++;
-            }
+            sb.append(wordInfo.mWord);
         }
-        if (pos >= prefMaxSuggestions) {
-            return true;
-        }
-
-        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());
-        } else if (mIsFirstCharCapitalized) {
-            sb.append(Character.toUpperCase(word[offset]));
-            if (length > 1) {
-                sb.append(word, offset + 1, length - 1);
-            }
-        } else {
-            sb.append(word, offset, length);
-        }
-        for (int i = mTrailingSingleQuotesCount - 1; i >= 0; --i) {
+        for (int i = trailingSingleQuotesCount - 1; i >= 0; --i) {
             sb.appendCodePoint(Keyboard.CODE_SINGLE_QUOTE);
         }
-        suggestions.add(pos, new SuggestedWordInfo(sb, score));
-        if (suggestions.size() > prefMaxSuggestions) {
-            suggestions.remove(prefMaxSuggestions);
-        } else {
-            LatinImeLogger.onAddSuggestedWord(sb.toString(), dicTypeId, dataTypeForLog);
-        }
-        return true;
+        return new SuggestedWordInfo(sb, wordInfo.mScore, wordInfo.mKind, wordInfo.mSourceDict);
     }
 
     public void close() {
         final HashSet<Dictionary> dictionaries = new HashSet<Dictionary>();
-        dictionaries.addAll(mUnigramDictionaries.values());
-        dictionaries.addAll(mBigramDictionaries.values());
+        dictionaries.addAll(mDictionaries.values());
         for (final Dictionary dictionary : dictionaries) {
             dictionary.close();
         }
-        mHasMainDictionary = false;
-    }
-
-    // TODO: Resolve the inconsistencies between the native auto correction algorithms and
-    // this safety net
-    public static boolean shouldBlockAutoCorrectionBySafetyNet(final String typedWord,
-            final CharSequence suggestion) {
-        // Safety net for auto correction.
-        // Actually if we hit this safety net, it's a bug.
-        // If user selected aggressive auto correction mode, there is no need to use the safety
-        // net.
-        // If the length of typed word is less than MINIMUM_SAFETY_NET_CHAR_LENGTH,
-        // we should not use net because relatively edit distance can be big.
-        final int typedWordLength = typedWord.length();
-        if (typedWordLength < Suggest.MINIMUM_SAFETY_NET_CHAR_LENGTH) {
-            return false;
-        }
-        final int maxEditDistanceOfNativeDictionary =
-                (typedWordLength < 5 ? 2 : typedWordLength / 2) + 1;
-        final int distance = BinaryDictionary.editDistance(typedWord, suggestion.toString());
-        if (DBG) {
-            Log.d(TAG, "Autocorrected edit distance = " + distance
-                    + ", " + maxEditDistanceOfNativeDictionary);
-        }
-        if (distance > maxEditDistanceOfNativeDictionary) {
-            if (DBG) {
-                Log.e(TAG, "Safety net: before = " + typedWord + ", after = " + suggestion);
-                Log.e(TAG, "(Error) The edit distance of this correction exceeds limit. "
-                        + "Turning off auto-correction.");
-            }
-            return true;
-        } else {
-            return false;
-        }
+        mMainDictionary = null;
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index 497fd3b..f079c21 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -25,27 +25,27 @@
 
 public class SuggestedWords {
     public static final SuggestedWords EMPTY = new SuggestedWords(
-            new ArrayList<SuggestedWordInfo>(0), false, false, false, false, false, false);
+            new ArrayList<SuggestedWordInfo>(0), false, false, false, false, false);
 
     public final boolean mTypedWordValid;
-    public final boolean mHasAutoCorrectionCandidate;
+    // Note: this INCLUDES cases where the word will auto-correct to itself. A good definition
+    // of what this flag means would be "the top suggestion is strong enough to auto-correct",
+    // whether this exactly matches the user entry or not.
+    public final boolean mWillAutoCorrect;
     public final boolean mIsPunctuationSuggestions;
-    public final boolean mAllowsToBeAutoCorrected;
     public final boolean mIsObsoleteSuggestions;
     public final boolean mIsPrediction;
     private final ArrayList<SuggestedWordInfo> mSuggestedWordInfoList;
 
     public SuggestedWords(final ArrayList<SuggestedWordInfo> suggestedWordInfoList,
             final boolean typedWordValid,
-            final boolean hasAutoCorrectionCandidate,
-            final boolean allowsToBeAutoCorrected,
+            final boolean willAutoCorrect,
             final boolean isPunctuationSuggestions,
             final boolean isObsoleteSuggestions,
             final boolean isPrediction) {
         mSuggestedWordInfoList = suggestedWordInfoList;
         mTypedWordValid = typedWordValid;
-        mHasAutoCorrectionCandidate = hasAutoCorrectionCandidate;
-        mAllowsToBeAutoCorrected = allowsToBeAutoCorrected;
+        mWillAutoCorrect = willAutoCorrect;
         mIsPunctuationSuggestions = isPunctuationSuggestions;
         mIsObsoleteSuggestions = isObsoleteSuggestions;
         mIsPrediction = isPrediction;
@@ -55,7 +55,7 @@
         return mSuggestedWordInfoList.size();
     }
 
-    public CharSequence getWord(int pos) {
+    public String getWord(int pos) {
         return mSuggestedWordInfoList.get(pos).mWord;
     }
 
@@ -67,12 +67,8 @@
         return mSuggestedWordInfoList.get(pos);
     }
 
-    public boolean hasAutoCorrectionWord() {
-        return mHasAutoCorrectionCandidate && size() > 1 && !mTypedWordValid;
-    }
-
     public boolean willAutoCorrect() {
-        return !mTypedWordValid && mHasAutoCorrectionCandidate;
+        return mWillAutoCorrect;
     }
 
     @Override
@@ -80,8 +76,7 @@
         // Pretty-print method to help debug
         return "SuggestedWords:"
                 + " mTypedWordValid=" + mTypedWordValid
-                + " mHasAutoCorrectionCandidate=" + mHasAutoCorrectionCandidate
-                + " mAllowsToBeAutoCorrected=" + mAllowsToBeAutoCorrected
+                + " mWillAutoCorrect=" + mWillAutoCorrect
                 + " mIsPunctuationSuggestions=" + mIsPunctuationSuggestions
                 + " words=" + Arrays.toString(mSuggestedWordInfoList.toArray());
     }
@@ -91,7 +86,8 @@
         final ArrayList<SuggestedWordInfo> result = new ArrayList<SuggestedWordInfo>();
         for (CompletionInfo info : infos) {
             if (null != info && info.getText() != null) {
-                result.add(new SuggestedWordInfo(info.getText(), SuggestedWordInfo.MAX_SCORE));
+                result.add(new SuggestedWordInfo(info.getText(), SuggestedWordInfo.MAX_SCORE,
+                        SuggestedWordInfo.KIND_APP_DEFINED, Dictionary.TYPE_APPLICATION_DEFINED));
             }
         }
         return result;
@@ -103,7 +99,8 @@
             final CharSequence typedWord, final SuggestedWords previousSuggestions) {
         final ArrayList<SuggestedWordInfo> suggestionsList = new ArrayList<SuggestedWordInfo>();
         final HashSet<String> alreadySeen = new HashSet<String>();
-        suggestionsList.add(new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE));
+        suggestionsList.add(new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE,
+                SuggestedWordInfo.KIND_TYPED, Dictionary.TYPE_USER_TYPED));
         alreadySeen.add(typedWord.toString());
         final int previousSize = previousSuggestions.size();
         for (int pos = 1; pos < previousSize; pos++) {
@@ -120,17 +117,28 @@
 
     public static class SuggestedWordInfo {
         public static final int MAX_SCORE = Integer.MAX_VALUE;
-        private final String mWordStr;
-        public final CharSequence mWord;
+        public static final int KIND_TYPED = 0; // What user typed
+        public static final int KIND_CORRECTION = 1; // Simple correction/suggestion
+        public static final int KIND_COMPLETION = 2; // Completion (suggestion with appended chars)
+        public static final int KIND_WHITELIST = 3; // Whitelisted word
+        public static final int KIND_BLACKLIST = 4; // Blacklisted word
+        public static final int KIND_HARDCODED = 5; // Hardcoded suggestion, e.g. punctuation
+        public static final int KIND_APP_DEFINED = 6; // Suggested by the application
+        public static final int KIND_SHORTCUT = 7; // A shortcut
+        public final String mWord;
         public final int mScore;
+        public final int mKind; // one of the KIND_* constants above
         public final int mCodePointCount;
+        public final String mSourceDict;
         private String mDebugString = "";
 
-        public SuggestedWordInfo(final CharSequence word, final int score) {
-            mWordStr = word.toString();
-            mWord = word;
+        public SuggestedWordInfo(final CharSequence word, final int score, final int kind,
+                final String sourceDict) {
+            mWord = word.toString();
             mScore = score;
-            mCodePointCount = mWordStr.codePointCount(0, mWordStr.length());
+            mKind = kind;
+            mSourceDict = sourceDict;
+            mCodePointCount = StringUtils.codePointCount(mWord);
         }
 
 
@@ -148,15 +156,15 @@
         }
 
         public int codePointAt(int i) {
-            return mWordStr.codePointAt(i);
+            return mWord.codePointAt(i);
         }
 
         @Override
         public String toString() {
             if (TextUtils.isEmpty(mDebugString)) {
-                return mWordStr;
+                return mWord;
             } else {
-                return mWordStr + " (" + mDebugString.toString() + ")";
+                return mWord + " (" + mDebugString.toString() + ")";
             }
         }
 
@@ -170,7 +178,7 @@
                 final SuggestedWordInfo cur = candidates.get(i);
                 for (int j = 0; j < i; ++j) {
                     final SuggestedWordInfo previous = candidates.get(j);
-                    if (TextUtils.equals(cur.mWord, previous.mWord)) {
+                    if (cur.mWord.equals(previous.mWord)) {
                         candidates.remove(cur.mScore < previous.mScore ? i : j);
                         --i;
                         break;
diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java
index 673b545..bdd988d 100644
--- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java
@@ -19,22 +19,23 @@
 import android.content.Context;
 
 import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 
+import java.util.ArrayList;
 import java.util.Locale;
 
 public class SynchronouslyLoadedContactsBinaryDictionary extends ContactsBinaryDictionary {
     private boolean mClosed;
 
     public SynchronouslyLoadedContactsBinaryDictionary(final Context context, final Locale locale) {
-        super(context, Suggest.DIC_CONTACTS, locale);
+        super(context, locale);
     }
 
     @Override
-    public synchronized void getWords(final WordComposer codes,
-            final CharSequence prevWordForBigrams, final WordCallback callback,
-            final ProximityInfo proximityInfo) {
+    public synchronized ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer codes,
+            final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) {
         syncReloadDictionaryIfRequired();
-        getWordsInner(codes, prevWordForBigrams, callback, proximityInfo);
+        return super.getSuggestions(codes, prevWordForBigrams, proximityInfo);
     }
 
     @Override
diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsDictionary.java
deleted file mode 100644
index a8b871c..0000000
--- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsDictionary.java
+++ /dev/null
@@ -1,54 +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 android.content.Context;
-
-import com.android.inputmethod.keyboard.ProximityInfo;
-
-public class SynchronouslyLoadedContactsDictionary extends ContactsDictionary {
-    private boolean mClosed;
-
-    public SynchronouslyLoadedContactsDictionary(final Context context) {
-        super(context, Suggest.DIC_CONTACTS);
-        mClosed = false;
-    }
-
-    @Override
-    public synchronized void getWords(final WordComposer codes,
-            final CharSequence prevWordForBigrams, final WordCallback callback,
-            final ProximityInfo proximityInfo) {
-        blockingReloadDictionaryIfRequired();
-        getWordsInner(codes, prevWordForBigrams, callback, proximityInfo);
-    }
-
-    @Override
-    public synchronized boolean isValidWord(CharSequence word) {
-        blockingReloadDictionaryIfRequired();
-        return getWordFrequency(word) > -1;
-    }
-
-    // Protect against multiple closing
-    @Override
-    public synchronized void close() {
-        // Actually with the current implementation of ContactsDictionary it's safe to close
-        // several times, so the following protection is really only for foolproofing
-        if (mClosed) return;
-        mClosed = true;
-        super.close();
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
index 1606a34..b8cfddd 100644
--- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
@@ -19,6 +19,9 @@
 import android.content.Context;
 
 import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+
+import java.util.ArrayList;
 
 public class SynchronouslyLoadedUserBinaryDictionary extends UserBinaryDictionary {
 
@@ -32,11 +35,10 @@
     }
 
     @Override
-    public synchronized void getWords(final WordComposer codes,
-            final CharSequence prevWordForBigrams, final WordCallback callback,
-            final ProximityInfo proximityInfo) {
+    public synchronized ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer codes,
+            final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) {
         syncReloadDictionaryIfRequired();
-        getWordsInner(codes, prevWordForBigrams, callback, proximityInfo);
+        return super.getSuggestions(codes, prevWordForBigrams, proximityInfo);
     }
 
     @Override
diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserDictionary.java
deleted file mode 100644
index 23a49c1..0000000
--- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserDictionary.java
+++ /dev/null
@@ -1,56 +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 android.content.Context;
-
-import com.android.inputmethod.keyboard.ProximityInfo;
-
-public class SynchronouslyLoadedUserDictionary extends UserDictionary {
-    private boolean mClosed;
-
-    public SynchronouslyLoadedUserDictionary(final Context context, final String locale) {
-        this(context, locale, false);
-    }
-
-    public SynchronouslyLoadedUserDictionary(final Context context, final String locale,
-            final boolean alsoUseMoreRestrictiveLocales) {
-        super(context, locale, alsoUseMoreRestrictiveLocales);
-    }
-
-    @Override
-    public synchronized void getWords(final WordComposer codes,
-            final CharSequence prevWordForBigrams, final WordCallback callback,
-            final ProximityInfo proximityInfo) {
-        blockingReloadDictionaryIfRequired();
-        getWordsInner(codes, prevWordForBigrams, callback, proximityInfo);
-    }
-
-    @Override
-    public synchronized boolean isValidWord(CharSequence word) {
-        blockingReloadDictionaryIfRequired();
-        return super.isValidWord(word);
-    }
-
-    // Protect against multiple closing
-    @Override
-    public synchronized void close() {
-        if (mClosed) return;
-        mClosed = true;
-        super.close();
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index 6fa1a25..60e6fa1 100644
--- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
@@ -34,13 +34,27 @@
  */
 public class UserBinaryDictionary extends ExpandableBinaryDictionary {
 
-    // TODO: use Words.SHORTCUT when it's public in the SDK
+    // The user dictionary provider uses an empty string to mean "all languages".
+    private static final String USER_DICTIONARY_ALL_LANGUAGES = "";
+
+    // TODO: use Words.SHORTCUT when we target JellyBean or above
     final static String SHORTCUT = "shortcut";
-    private static final String[] PROJECTION_QUERY = {
-        Words.WORD,
-        SHORTCUT,
-        Words.FREQUENCY,
-    };
+    private static final String[] PROJECTION_QUERY;
+    static {
+        // 16 is JellyBean, but we want this to compile against ICS.
+        if (android.os.Build.VERSION.SDK_INT >= 16) {
+            PROJECTION_QUERY = new String[] {
+                Words.WORD,
+                SHORTCUT,
+                Words.FREQUENCY,
+            };
+        } else {
+            PROJECTION_QUERY = new String[] {
+                Words.WORD,
+                Words.FREQUENCY,
+            };
+        }
+    }
 
     private static final String NAME = "userunigram";
 
@@ -58,9 +72,14 @@
 
     public UserBinaryDictionary(final Context context, final String locale,
             final boolean alsoUseMoreRestrictiveLocales) {
-        super(context, getFilenameWithLocale(NAME, locale), Suggest.DIC_USER);
+        super(context, getFilenameWithLocale(NAME, locale), Dictionary.TYPE_USER);
         if (null == locale) throw new NullPointerException(); // Catch the error earlier
-        mLocale = locale;
+        if (SubtypeLocale.NO_LANGUAGE.equals(locale)) {
+            // If we don't have a locale, insert into the "all locales" user dictionary.
+            mLocale = USER_DICTIONARY_ALL_LANGUAGES;
+        } else {
+            mLocale = locale;
+        }
         mAlsoUseMoreRestrictiveLocales = alsoUseMoreRestrictiveLocales;
         // Perform a managed query. The Activity will handle closing and re-querying the cursor
         // when needed.
@@ -136,7 +155,7 @@
             requestArguments = localeElements;
         }
         final Cursor cursor = mContext.getContentResolver().query(
-            Words.CONTENT_URI, PROJECTION_QUERY, request.toString(), requestArguments, null);
+                Words.CONTENT_URI, PROJECTION_QUERY, request.toString(), requestArguments, null);
         try {
             addWords(cursor);
         } finally {
@@ -182,16 +201,18 @@
     }
 
     private void addWords(Cursor cursor) {
+        // 16 is JellyBean, but we want this to compile against ICS.
+        final boolean hasShortcutColumn = android.os.Build.VERSION.SDK_INT >= 16;
         clearFusionDictionary();
         if (cursor == null) return;
         if (cursor.moveToFirst()) {
             final int indexWord = cursor.getColumnIndex(Words.WORD);
-            final int indexShortcut = cursor.getColumnIndex(SHORTCUT);
+            final int indexShortcut = hasShortcutColumn ? cursor.getColumnIndex(SHORTCUT) : 0;
             final int indexFrequency = cursor.getColumnIndex(Words.FREQUENCY);
             while (!cursor.isAfterLast()) {
-                String word = cursor.getString(indexWord);
-                String shortcut = cursor.getString(indexShortcut);
-                int frequency = cursor.getInt(indexFrequency);
+                final String word = cursor.getString(indexWord);
+                final String shortcut = hasShortcutColumn ? cursor.getString(indexShortcut) : null;
+                final int frequency = cursor.getInt(indexFrequency);
                 // Safeguard against adding really long words.
                 if (word.length() < MAX_WORD_LENGTH) {
                     super.addWord(word, null, frequency);
diff --git a/java/src/com/android/inputmethod/latin/UserDictionary.java b/java/src/com/android/inputmethod/latin/UserDictionary.java
deleted file mode 100644
index c1efadd..0000000
--- a/java/src/com/android/inputmethod/latin/UserDictionary.java
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Copyright (C) 2008 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 android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.provider.UserDictionary.Words;
-import android.text.TextUtils;
-
-import com.android.inputmethod.keyboard.ProximityInfo;
-
-import java.util.Arrays;
-
-// TODO: This class is superseded by {@link UserBinaryDictionary}. Should be cleaned up.
-/**
- * An expandable dictionary that stores the words in the user unigram dictionary.
- *
- * @deprecated Use {@link UserBinaryDictionary}.
- */
-@Deprecated
-public class UserDictionary extends ExpandableDictionary {
-
-    // TODO: use Words.SHORTCUT when it's public in the SDK
-    final static String SHORTCUT = "shortcut";
-    private static final String[] PROJECTION_QUERY = {
-        Words.WORD,
-        SHORTCUT,
-        Words.FREQUENCY,
-    };
-
-    // This is not exported by the framework so we pretty much have to write it here verbatim
-    private static final String ACTION_USER_DICTIONARY_INSERT =
-            "com.android.settings.USER_DICTIONARY_INSERT";
-
-    private ContentObserver mObserver;
-    final private String mLocale;
-    final private boolean mAlsoUseMoreRestrictiveLocales;
-
-    public UserDictionary(final Context context, final String locale) {
-        this(context, locale, false);
-    }
-
-    public UserDictionary(final Context context, final String locale,
-            final boolean alsoUseMoreRestrictiveLocales) {
-        super(context, Suggest.DIC_USER);
-        if (null == locale) throw new NullPointerException(); // Catch the error earlier
-        mLocale = locale;
-        mAlsoUseMoreRestrictiveLocales = alsoUseMoreRestrictiveLocales;
-        // Perform a managed query. The Activity will handle closing and re-querying the cursor
-        // when needed.
-        ContentResolver cres = context.getContentResolver();
-
-        mObserver = new ContentObserver(null) {
-            @Override
-            public void onChange(boolean self) {
-                setRequiresReload(true);
-            }
-        };
-        cres.registerContentObserver(Words.CONTENT_URI, true, mObserver);
-
-        loadDictionary();
-    }
-
-    @Override
-    public synchronized void close() {
-        if (mObserver != null) {
-            getContext().getContentResolver().unregisterContentObserver(mObserver);
-            mObserver = null;
-        }
-        super.close();
-    }
-
-    @Override
-    public void loadDictionaryAsync() {
-        // Split the locale. For example "en" => ["en"], "de_DE" => ["de", "DE"],
-        // "en_US_foo_bar_qux" => ["en", "US", "foo_bar_qux"] because of the limit of 3.
-        // This is correct for locale processing.
-        // For this example, we'll look at the "en_US_POSIX" case.
-        final String[] localeElements =
-                TextUtils.isEmpty(mLocale) ? new String[] {} : mLocale.split("_", 3);
-        final int length = localeElements.length;
-
-        final StringBuilder request = new StringBuilder("(locale is NULL)");
-        String localeSoFar = "";
-        // At start, localeElements = ["en", "US", "POSIX"] ; localeSoFar = "" ;
-        // and request = "(locale is NULL)"
-        for (int i = 0; i < length; ++i) {
-            // i | localeSoFar    | localeElements
-            // 0 | ""             | ["en", "US", "POSIX"]
-            // 1 | "en_"          | ["en", "US", "POSIX"]
-            // 2 | "en_US_"       | ["en", "en_US", "POSIX"]
-            localeElements[i] = localeSoFar + localeElements[i];
-            localeSoFar = localeElements[i] + "_";
-            // i | request
-            // 0 | "(locale is NULL)"
-            // 1 | "(locale is NULL) or (locale=?)"
-            // 2 | "(locale is NULL) or (locale=?) or (locale=?)"
-            request.append(" or (locale=?)");
-        }
-        // At the end, localeElements = ["en", "en_US", "en_US_POSIX"]; localeSoFar = en_US_POSIX_"
-        // and request = "(locale is NULL) or (locale=?) or (locale=?) or (locale=?)"
-
-        final String[] requestArguments;
-        // If length == 3, we already have all the arguments we need (common prefix is meaningless
-        // inside variants
-        if (mAlsoUseMoreRestrictiveLocales && length < 3) {
-            request.append(" or (locale like ?)");
-            // The following creates an array with one more (null) position
-            final String[] localeElementsWithMoreRestrictiveLocalesIncluded =
-                    Arrays.copyOf(localeElements, length + 1);
-            localeElementsWithMoreRestrictiveLocalesIncluded[length] =
-                    localeElements[length - 1] + "_%";
-            requestArguments = localeElementsWithMoreRestrictiveLocalesIncluded;
-            // If for example localeElements = ["en"]
-            // then requestArguments = ["en", "en_%"]
-            // and request = (locale is NULL) or (locale=?) or (locale like ?)
-            // If localeElements = ["en", "en_US"]
-            // then requestArguments = ["en", "en_US", "en_US_%"]
-        } else {
-            requestArguments = localeElements;
-        }
-        final Cursor cursor = getContext().getContentResolver()
-                .query(Words.CONTENT_URI, PROJECTION_QUERY, request.toString(),
-                        requestArguments, null);
-        try {
-            addWords(cursor);
-        } finally {
-            if (null != cursor) cursor.close();
-        }
-    }
-
-    public boolean isEnabled() {
-        final ContentResolver cr = getContext().getContentResolver();
-        final ContentProviderClient client = cr.acquireContentProviderClient(Words.CONTENT_URI);
-        if (client != null) {
-            client.release();
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * Adds a word to the user dictionary and makes it persistent.
-     *
-     * This will call upon the system interface to do the actual work through the intent
-     * readied by the system to this effect.
-     *
-     * @param word the word to add. If the word is capitalized, then the dictionary will
-     * recognize it as a capitalized word when searched.
-     * @param frequency the frequency of occurrence of the word. A frequency of 255 is considered
-     * the highest.
-     * @TODO use a higher or float range for frequency
-     */
-    public synchronized void addWordToUserDictionary(final String word, final int frequency) {
-        // Force load the dictionary here synchronously
-        if (getRequiresReload()) loadDictionaryAsync();
-        // TODO: do something for the UI. With the following, any sufficiently long word will
-        // look like it will go to the user dictionary but it won't.
-        // Safeguard against adding long words. Can cause stack overflow.
-        if (word.length() >= getMaxWordLength()) return;
-
-        // TODO: Add an argument to the intent to specify the frequency.
-        Intent intent = new Intent(ACTION_USER_DICTIONARY_INSERT);
-        intent.putExtra(Words.WORD, word);
-        intent.putExtra(Words.LOCALE, mLocale);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        getContext().startActivity(intent);
-    }
-
-    @Override
-    public synchronized void getWords(final WordComposer codes,
-            final CharSequence prevWordForBigrams, final WordCallback callback,
-            final ProximityInfo proximityInfo) {
-        super.getWords(codes, prevWordForBigrams, callback, proximityInfo);
-    }
-
-    @Override
-    public synchronized boolean isValidWord(CharSequence word) {
-        return super.isValidWord(word);
-    }
-
-    private void addWords(Cursor cursor) {
-        clearDictionary();
-        if (cursor == null) return;
-        final int maxWordLength = getMaxWordLength();
-        if (cursor.moveToFirst()) {
-            final int indexWord = cursor.getColumnIndex(Words.WORD);
-            final int indexShortcut = cursor.getColumnIndex(SHORTCUT);
-            final int indexFrequency = cursor.getColumnIndex(Words.FREQUENCY);
-            while (!cursor.isAfterLast()) {
-                String word = cursor.getString(indexWord);
-                String shortcut = cursor.getString(indexShortcut);
-                int frequency = cursor.getInt(indexFrequency);
-                // Safeguard against adding really long words. Stack may overflow due
-                // to recursion
-                if (word.length() < maxWordLength) {
-                    super.addWord(word, null, frequency);
-                }
-                if (null != shortcut && shortcut.length() < maxWordLength) {
-                    super.addWord(shortcut, word, frequency);
-                }
-                cursor.moveToNext();
-            }
-        }
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
index 5095f65..3bb670c 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
@@ -27,9 +27,12 @@
 import android.provider.BaseColumns;
 import android.util.Log;
 
+import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.UserHistoryForgettingCurveUtils.ForgettingCurveParams;
 
 import java.lang.ref.SoftReference;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.locks.ReentrantLock;
@@ -115,8 +118,7 @@
     }
 
     public synchronized static UserHistoryDictionary getInstance(
-            final Context context, final String locale,
-            final int dictTypeId, final SharedPreferences sp) {
+            final Context context, final String locale, final SharedPreferences sp) {
         if (sLangDictCache.containsKey(locale)) {
             final SoftReference<UserHistoryDictionary> ref = sLangDictCache.get(locale);
             final UserHistoryDictionary dict = ref == null ? null : ref.get();
@@ -128,14 +130,14 @@
             }
         }
         final UserHistoryDictionary dict =
-                new UserHistoryDictionary(context, locale, dictTypeId, sp);
+                new UserHistoryDictionary(context, locale, sp);
         sLangDictCache.put(locale, new SoftReference<UserHistoryDictionary>(dict));
         return dict;
     }
 
-    private UserHistoryDictionary(final Context context, final String locale, final int dicTypeId,
-            SharedPreferences sp) {
-        super(context, dicTypeId);
+    private UserHistoryDictionary(final Context context, final String locale,
+            final SharedPreferences sp) {
+        super(context, Dictionary.TYPE_USER_HISTORY);
         mLocale = locale;
         mPrefs = sp;
         if (sOpenHelper == null) {
@@ -158,6 +160,14 @@
         // super.close();
     }
 
+    @Override
+    protected ArrayList<SuggestedWordInfo> getWordsInner(final WordComposer composer,
+            final CharSequence prevWord, final ProximityInfo proximityInfo) {
+        // Inhibit suggestions (not predictions) for user history for now. Removing this method
+        // is enough to use it through the standard ExpandableDictionary way.
+        return null;
+    }
+
     /**
      * Return whether the passed charsequence is in the dictionary.
      */
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java
index 2884774..610652a 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java
@@ -98,11 +98,11 @@
     }
 
     public HashMap<String, Byte> getBigrams(String word1) {
-        if (!mBigramMap.containsKey(word1)) {
-            return EMPTY_BIGRAM_MAP;
-        } else {
-            return mBigramMap.get(word1);
-        }
+        if (mBigramMap.containsKey(word1)) return mBigramMap.get(word1);
+        // TODO: lower case according to locale
+        final String lowerWord1 = word1.toLowerCase();
+        if (mBigramMap.containsKey(lowerWord1)) return mBigramMap.get(lowerWord1);
+        return EMPTY_BIGRAM_MAP;
     }
 
     public boolean removeBigram(String word1, String word2) {
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java b/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java
index e5516dc..1de95d7 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java
@@ -50,7 +50,7 @@
         }
 
         private ForgettingCurveParams(long now, boolean isValid) {
-            this((int)pushCount((byte)0, isValid), now, now, isValid);
+            this(pushCount((byte)0, isValid), now, now, isValid);
         }
 
         /** This constructor is called when the user history bigram dictionary is being restored. */
@@ -199,20 +199,20 @@
         public static final int[][] SCORE_TABLE = new int[FC_LEVEL_MAX][ELAPSED_TIME_MAX + 1];
         static {
             for (int i = 0; i < FC_LEVEL_MAX; ++i) {
-                final double initialFreq;
+                final float initialFreq;
                 if (i >= 2) {
-                    initialFreq = (double)FC_FREQ_MAX;
+                    initialFreq = FC_FREQ_MAX;
                 } else if (i == 1) {
-                    initialFreq = (double)FC_FREQ_MAX / 2;
+                    initialFreq = FC_FREQ_MAX / 2;
                 } else if (i == 0) {
-                    initialFreq = (double)FC_FREQ_MAX / 4;
+                    initialFreq = FC_FREQ_MAX / 4;
                 } else {
                     continue;
                 }
                 for (int j = 0; j < ELAPSED_TIME_MAX; ++j) {
-                    final double elapsedHour = j * ELAPSED_TIME_INTERVAL_HOURS;
-                    final double freq =
-                            initialFreq * Math.pow(initialFreq, elapsedHour / HALF_LIFE_HOURS);
+                    final float elapsedHours = j * ELAPSED_TIME_INTERVAL_HOURS;
+                    final float freq = initialFreq
+                            * NativeUtils.powf(initialFreq, elapsedHours / HALF_LIFE_HOURS);
                     final int intFreq = Math.min(FC_FREQ_MAX, Math.max(0, (int)freq));
                     SCORE_TABLE[i][j] = intFreq;
                 }
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index 4178955..8f71de0 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -44,10 +44,9 @@
 import java.io.PrintWriter;
 import java.nio.channels.FileChannel;
 import java.text.SimpleDateFormat;
-import java.util.Collections;
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
-import java.util.Map;
 
 public class Utils {
     private Utils() {
@@ -206,18 +205,24 @@
     }
 
     // Get the current stack trace
-    public static String getStackTrace() {
+    public static String getStackTrace(final int limit) {
         StringBuilder sb = new StringBuilder();
         try {
             throw new RuntimeException();
         } catch (RuntimeException e) {
             StackTraceElement[] frames = e.getStackTrace();
             // Start at 1 because the first frame is here and we don't care about it
-            for (int j = 1; j < frames.length; ++j) sb.append(frames[j].toString() + "\n");
+            for (int j = 1; j < frames.length && j < limit + 1; ++j) {
+                sb.append(frames[j].toString() + "\n");
+            }
         }
         return sb.toString();
     }
 
+    public static String getStackTrace() {
+        return getStackTrace(Integer.MAX_VALUE);
+    }
+
     public static class UsabilityStudyLogUtils {
         // TODO: remove code duplication with ResearchLog class
         private static final String USABILITY_TAG = UsabilityStudyLogUtils.class.getSimpleName();
diff --git a/java/src/com/android/inputmethod/latin/WhitelistDictionary.java b/java/src/com/android/inputmethod/latin/WhitelistDictionary.java
index a0de2f9..14476dc 100644
--- a/java/src/com/android/inputmethod/latin/WhitelistDictionary.java
+++ b/java/src/com/android/inputmethod/latin/WhitelistDictionary.java
@@ -22,8 +22,11 @@
 import android.util.Log;
 import android.util.Pair;
 
+import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Locale;
 
@@ -37,7 +40,7 @@
 
     // TODO: Conform to the async load contact of ExpandableDictionary
     public WhitelistDictionary(final Context context, final Locale locale) {
-        super(context, Suggest.DIC_WHITELIST);
+        super(context, Dictionary.TYPE_WHITELIST);
         // TODO: Move whitelist dictionary into main dictionary.
         final RunInLocale<Void> job = new RunInLocale<Void>() {
             @Override
@@ -88,6 +91,13 @@
         return null;
     }
 
+    @Override
+    public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
+            final CharSequence prevWord, final ProximityInfo proximityInfo) {
+        // Whitelist does not supply any suggestions or predictions.
+        return null;
+    }
+
     // See LatinIME#updateSuggestions. This breaks in the (queer) case that the whitelist
     // lists that word a should autocorrect to word b, and word c would autocorrect to
     // an upper-cased version of a. In this case, the way this return value is used would
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index ca9caa1..46c892a 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -19,7 +19,6 @@
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.KeyDetector;
 import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.KeyboardActionListener;
 
 import java.util.Arrays;
 
@@ -34,11 +33,11 @@
     private static final int N = BinaryDictionary.MAX_WORD_LENGTH;
 
     private int[] mPrimaryKeyCodes;
-    private int[] mXCoordinates;
-    private int[] mYCoordinates;
-    private StringBuilder mTypedWord;
+    private final InputPointers mInputPointers = new InputPointers();
+    private final StringBuilder mTypedWord;
     private CharSequence mAutoCorrection;
     private boolean mIsResumed;
+    private boolean mIsBatchMode;
 
     // Cache these values for performance
     private int mCapsCount;
@@ -54,28 +53,23 @@
     public WordComposer() {
         mPrimaryKeyCodes = new int[N];
         mTypedWord = new StringBuilder(N);
-        mXCoordinates = new int[N];
-        mYCoordinates = new int[N];
         mAutoCorrection = null;
         mTrailingSingleQuotesCount = 0;
         mIsResumed = false;
+        mIsBatchMode = false;
         refreshSize();
     }
 
     public WordComposer(WordComposer source) {
-        init(source);
-    }
-
-    public void init(WordComposer source) {
         mPrimaryKeyCodes = Arrays.copyOf(source.mPrimaryKeyCodes, source.mPrimaryKeyCodes.length);
         mTypedWord = new StringBuilder(source.mTypedWord);
-        mXCoordinates = Arrays.copyOf(source.mXCoordinates, source.mXCoordinates.length);
-        mYCoordinates = Arrays.copyOf(source.mYCoordinates, source.mYCoordinates.length);
+        mInputPointers.copy(source.mInputPointers);
         mCapsCount = source.mCapsCount;
         mIsFirstCharCapitalized = source.mIsFirstCharCapitalized;
         mAutoCapitalized = source.mAutoCapitalized;
         mTrailingSingleQuotesCount = source.mTrailingSingleQuotesCount;
         mIsResumed = source.mIsResumed;
+        mIsBatchMode = source.mIsBatchMode;
         refreshSize();
     }
 
@@ -89,10 +83,11 @@
         mIsFirstCharCapitalized = false;
         mTrailingSingleQuotesCount = 0;
         mIsResumed = false;
+        mIsBatchMode = false;
         refreshSize();
     }
 
-    public final void refreshSize() {
+    private final void refreshSize() {
         mCodePointSize = mTypedWord.codePointCount(0, mTypedWord.length());
     }
 
@@ -116,12 +111,8 @@
         return mPrimaryKeyCodes[index];
     }
 
-    public int[] getXCoordinates() {
-        return mXCoordinates;
-    }
-
-    public int[] getYCoordinates() {
-        return mYCoordinates;
+    public InputPointers getInputPointers() {
+        return mInputPointers;
     }
 
     private static boolean isFirstCharCapitalized(int index, int codePoint, boolean previous) {
@@ -129,36 +120,18 @@
         return previous && !Character.isUpperCase(codePoint);
     }
 
-    // TODO: remove input keyDetector
-    public void add(int primaryCode, int x, int y, KeyDetector keyDetector) {
-        final int keyX;
-        final int keyY;
-        if (null == keyDetector
-                || x == KeyboardActionListener.SUGGESTION_STRIP_COORDINATE
-                || y == KeyboardActionListener.SUGGESTION_STRIP_COORDINATE
-                || x == KeyboardActionListener.NOT_A_TOUCH_COORDINATE
-                || y == KeyboardActionListener.NOT_A_TOUCH_COORDINATE) {
-            keyX = x;
-            keyY = y;
-        } else {
-            keyX = keyDetector.getTouchX(x);
-            keyY = keyDetector.getTouchY(y);
-        }
-        add(primaryCode, keyX, keyY);
-    }
-
     /**
      * Add a new keystroke, with the pressed key's code point with the touch point coordinates.
      */
-    private void add(int primaryCode, int keyX, int keyY) {
+    public void add(int primaryCode, int keyX, int keyY) {
         final int newIndex = size();
         mTypedWord.appendCodePoint(primaryCode);
         refreshSize();
         if (newIndex < BinaryDictionary.MAX_WORD_LENGTH) {
             mPrimaryKeyCodes[newIndex] = primaryCode >= Keyboard.CODE_SPACE
                     ? Character.toLowerCase(primaryCode) : primaryCode;
-            mXCoordinates[newIndex] = keyX;
-            mYCoordinates[newIndex] = keyY;
+            // TODO: Set correct pointer id and time
+            mInputPointers.addPointer(newIndex, keyX, keyY, 0, 0);
         }
         mIsFirstCharCapitalized = isFirstCharCapitalized(
                 newIndex, primaryCode, mIsFirstCharCapitalized);
@@ -171,17 +144,22 @@
         mAutoCorrection = null;
     }
 
+    // TODO: We may want to have appendBatchInputPointers() as well.
+    public void setBatchInputPointers(InputPointers batchPointers) {
+        mInputPointers.copy(batchPointers);
+        mIsBatchMode = true;
+    }
+
     /**
      * Internal method to retrieve reasonable proximity info for a character.
      */
     private void addKeyInfo(final int codePoint, final Keyboard keyboard) {
-        for (final Key key : keyboard.mKeys) {
-            if (key.mCode == codePoint) {
-                final int x = key.mX + key.mWidth / 2;
-                final int y = key.mY + key.mHeight / 2;
-                add(codePoint, x, y);
-                return;
-            }
+        final Key key = keyboard.getKey(codePoint);
+        if (key != null) {
+            final int x = key.mX + key.mWidth / 2;
+            final int y = key.mY + key.mHeight / 2;
+            add(codePoint, x, y);
+            return;
         }
         add(codePoint, WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
     }
@@ -318,19 +296,17 @@
         // or a DECIDED_WORD we may cancel the commit later; otherwise, we should deactivate
         // the last composed word to ensure this does not happen.
         final int[] primaryKeyCodes = mPrimaryKeyCodes;
-        final int[] xCoordinates = mXCoordinates;
-        final int[] yCoordinates = mYCoordinates;
         mPrimaryKeyCodes = new int[N];
-        mXCoordinates = new int[N];
-        mYCoordinates = new int[N];
         final LastComposedWord lastComposedWord = new LastComposedWord(primaryKeyCodes,
-                xCoordinates, yCoordinates, mTypedWord.toString(), committedWord, separatorCode,
+                mInputPointers, mTypedWord.toString(), committedWord, separatorCode,
                 prevWord);
+        mInputPointers.reset();
         if (type != LastComposedWord.COMMIT_TYPE_DECIDED_WORD
                 && type != LastComposedWord.COMMIT_TYPE_MANUAL_PICK) {
             lastComposedWord.deactivate();
         }
         mTypedWord.setLength(0);
+        mTrailingSingleQuotesCount = 0;
         refreshSize();
         mAutoCorrection = null;
         mIsResumed = false;
@@ -339,12 +315,15 @@
 
     public void resumeSuggestionOnLastComposedWord(final LastComposedWord lastComposedWord) {
         mPrimaryKeyCodes = lastComposedWord.mPrimaryKeyCodes;
-        mXCoordinates = lastComposedWord.mXCoordinates;
-        mYCoordinates = lastComposedWord.mYCoordinates;
+        mInputPointers.set(lastComposedWord.mInputPointers);
         mTypedWord.setLength(0);
         mTypedWord.append(lastComposedWord.mTypedWord);
         refreshSize();
         mAutoCorrection = null; // This will be filled by the next call to updateSuggestion.
         mIsResumed = true;
     }
+
+    public boolean isBatchMode() {
+        return mIsBatchMode;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
index 89c59f8..2c3eee7 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
@@ -787,9 +787,9 @@
         // (discretizedFrequency + 0.5) times this value to get the median value of the step,
         // which is the best approximation. This is how we get the most precise result with
         // only four bits.
-        final double stepSize =
-                (double)(MAX_TERMINAL_FREQUENCY - unigramFrequency) / (1.5 + MAX_BIGRAM_FREQUENCY);
-        final double firstStepStart = 1 + unigramFrequency + (stepSize / 2.0);
+        final float stepSize =
+                (MAX_TERMINAL_FREQUENCY - unigramFrequency) / (1.5f + MAX_BIGRAM_FREQUENCY);
+        final float firstStepStart = 1 + unigramFrequency + (stepSize / 2.0f);
         final int discretizedFrequency = (int)((bigramFrequency - firstStepStart) / stepSize);
         // If the bigram freq is less than half-a-step higher than the unigram freq, we get -1
         // here. The best approximation would be the unigram freq itself, so we should not
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 88efc5a..3bdfe1f 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -20,30 +20,22 @@
 import android.content.SharedPreferences;
 import android.preference.PreferenceManager;
 import android.service.textservice.SpellCheckerService;
-import android.text.TextUtils;
 import android.util.Log;
-import android.util.LruCache;
-import android.view.textservice.SentenceSuggestionsInfo;
 import android.view.textservice.SuggestionsInfo;
-import android.view.textservice.TextInfo;
 
-import com.android.inputmethod.compat.SuggestionsInfoCompatUtils;
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.BinaryDictionary;
+import com.android.inputmethod.latin.ContactsBinaryDictionary;
 import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.Dictionary.WordCallback;
 import com.android.inputmethod.latin.DictionaryCollection;
 import com.android.inputmethod.latin.DictionaryFactory;
-import com.android.inputmethod.latin.LatinIME;
 import com.android.inputmethod.latin.LocaleUtils;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.StringUtils;
 import com.android.inputmethod.latin.SynchronouslyLoadedContactsBinaryDictionary;
-import com.android.inputmethod.latin.SynchronouslyLoadedContactsDictionary;
 import com.android.inputmethod.latin.SynchronouslyLoadedUserBinaryDictionary;
-import com.android.inputmethod.latin.SynchronouslyLoadedUserDictionary;
+import com.android.inputmethod.latin.UserBinaryDictionary;
 import com.android.inputmethod.latin.WhitelistDictionary;
-import com.android.inputmethod.latin.WordComposer;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
@@ -66,18 +58,18 @@
 
     public static final String PREF_USE_CONTACTS_KEY = "pref_spellcheck_use_contacts";
 
-    private static final int CAPITALIZE_NONE = 0; // No caps, or mixed case
-    private static final int CAPITALIZE_FIRST = 1; // First only
-    private static final int CAPITALIZE_ALL = 2; // All caps
+    public static final int CAPITALIZE_NONE = 0; // No caps, or mixed case
+    public static final int CAPITALIZE_FIRST = 1; // First only
+    public static final int CAPITALIZE_ALL = 2; // All caps
 
     private final static String[] EMPTY_STRING_ARRAY = new String[0];
     private Map<String, DictionaryPool> mDictionaryPools =
             Collections.synchronizedMap(new TreeMap<String, DictionaryPool>());
-    private Map<String, Dictionary> mUserDictionaries =
-            Collections.synchronizedMap(new TreeMap<String, Dictionary>());
+    private Map<String, UserBinaryDictionary> mUserDictionaries =
+            Collections.synchronizedMap(new TreeMap<String, UserBinaryDictionary>());
     private Map<String, Dictionary> mWhitelistDictionaries =
             Collections.synchronizedMap(new TreeMap<String, Dictionary>());
-    private Dictionary mContactsDictionary;
+    private ContactsBinaryDictionary mContactsDictionary;
 
     // The threshold for a candidate to be offered as a suggestion.
     private float mSuggestionThreshold;
@@ -92,8 +84,8 @@
 
     public static final int SCRIPT_LATIN = 0;
     public static final int SCRIPT_CYRILLIC = 1;
-    private static final String SINGLE_QUOTE = "\u0027";
-    private static final String APOSTROPHE = "\u2019";
+    public static final String SINGLE_QUOTE = "\u0027";
+    public static final String APOSTROPHE = "\u2019";
     private static final TreeMap<String, Integer> mLanguageToScript;
     static {
         // List of the supported languages and their associated script. We won't check
@@ -130,7 +122,7 @@
         onSharedPreferenceChanged(prefs, PREF_USE_CONTACTS_KEY);
     }
 
-    private static int getScriptFromLocale(final Locale locale) {
+    public static int getScriptFromLocale(final Locale locale) {
         final Integer script = mLanguageToScript.get(locale.getLanguage());
         if (null == script) {
             throw new RuntimeException("We have been called with an unsupported language: \""
@@ -154,13 +146,9 @@
 
     private void startUsingContactsDictionaryLocked() {
         if (null == mContactsDictionary) {
-            if (LatinIME.USE_BINARY_CONTACTS_DICTIONARY) {
-                // TODO: use the right locale for each session
-                mContactsDictionary =
-                        new SynchronouslyLoadedContactsBinaryDictionary(this, Locale.getDefault());
-            } else {
-                mContactsDictionary = new SynchronouslyLoadedContactsDictionary(this);
-            }
+            // TODO: use the right locale for each session
+            mContactsDictionary =
+                    new SynchronouslyLoadedContactsBinaryDictionary(this, Locale.getDefault());
         }
         final Iterator<WeakReference<DictionaryCollection>> iterator =
                 mDictionaryCollectionsList.iterator();
@@ -196,19 +184,27 @@
 
     @Override
     public Session createSession() {
-        return new AndroidSpellCheckerSession(this);
+        // Should not refer to AndroidSpellCheckerSession directly considering
+        // that AndroidSpellCheckerSession may be overlaid.
+        return AndroidSpellCheckerSessionFactory.newInstance(this);
     }
 
-    private static SuggestionsInfo getNotInDictEmptySuggestions() {
+    public static SuggestionsInfo getNotInDictEmptySuggestions() {
         return new SuggestionsInfo(0, EMPTY_STRING_ARRAY);
     }
 
-    private static SuggestionsInfo getInDictEmptySuggestions() {
+    public static SuggestionsInfo getInDictEmptySuggestions() {
         return new SuggestionsInfo(SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY,
                 EMPTY_STRING_ARRAY);
     }
 
-    private static class SuggestionsGatherer implements WordCallback {
+    public SuggestionsGatherer newSuggestionsGatherer(final String text, int maxLength) {
+        return new SuggestionsGatherer(
+                text, mSuggestionThreshold, mRecommendedThreshold, maxLength);
+    }
+
+    // TODO: remove this class and replace it by storage local to the session.
+    public static class SuggestionsGatherer {
         public static class Result {
             public final String[] mSuggestions;
             public final boolean mHasRecommendedSuggestions;
@@ -242,9 +238,8 @@
             mScores = new int[mMaxLength];
         }
 
-        @Override
-        synchronized public boolean addWord(char[] word, int wordOffset, int wordLength, int score,
-                int dicTypeId, int dataType) {
+        synchronized public boolean addWord(char[] word, int[] spaceIndices, int wordOffset,
+                int wordLength, int score) {
             final int positionIndex = Arrays.binarySearch(mScores, 0, mLength, score);
             // binarySearch returns the index if the element exists, and -<insertion index> - 1
             // if it doesn't. See documentation for binarySearch.
@@ -368,8 +363,9 @@
     private void closeAllDictionaries() {
         final Map<String, DictionaryPool> oldPools = mDictionaryPools;
         mDictionaryPools = Collections.synchronizedMap(new TreeMap<String, DictionaryPool>());
-        final Map<String, Dictionary> oldUserDictionaries = mUserDictionaries;
-        mUserDictionaries = Collections.synchronizedMap(new TreeMap<String, Dictionary>());
+        final Map<String, UserBinaryDictionary> oldUserDictionaries = mUserDictionaries;
+        mUserDictionaries =
+                Collections.synchronizedMap(new TreeMap<String, UserBinaryDictionary>());
         final Map<String, Dictionary> oldWhitelistDictionaries = mWhitelistDictionaries;
         mWhitelistDictionaries = Collections.synchronizedMap(new TreeMap<String, Dictionary>());
         new Thread("spellchecker_close_dicts") {
@@ -389,7 +385,7 @@
                         // The synchronously loaded contacts dictionary should have been in one
                         // or several pools, but it is shielded against multiple closing and it's
                         // safe to call it several times.
-                        final Dictionary dictToClose = mContactsDictionary;
+                        final ContactsBinaryDictionary dictToClose = mContactsDictionary;
                         // TODO: revert to the concrete type when USE_BINARY_CONTACTS_DICTIONARY
                         // is no longer needed
                         mContactsDictionary = null;
@@ -400,7 +396,7 @@
         }.start();
     }
 
-    private DictionaryPool getDictionaryPool(final String locale) {
+    public DictionaryPool getDictionaryPool(final String locale) {
         DictionaryPool pool = mDictionaryPools.get(locale);
         if (null == pool) {
             final Locale localeObject = LocaleUtils.constructLocaleFromString(locale);
@@ -421,13 +417,9 @@
                 DictionaryFactory.createMainDictionaryFromManager(this, locale,
                         true /* useFullEditDistance */);
         final String localeStr = locale.toString();
-        Dictionary userDictionary = mUserDictionaries.get(localeStr);
+        UserBinaryDictionary userDictionary = mUserDictionaries.get(localeStr);
         if (null == userDictionary) {
-            if (LatinIME.USE_BINARY_USER_DICTIONARY) {
-                userDictionary = new SynchronouslyLoadedUserBinaryDictionary(this, localeStr, true);
-            } else {
-                userDictionary = new SynchronouslyLoadedUserDictionary(this, localeStr, true);
-            }
+            userDictionary = new SynchronouslyLoadedUserBinaryDictionary(this, localeStr, true);
             mUserDictionaries.put(localeStr, userDictionary);
         }
         dictionaryCollection.addDictionary(userDictionary);
@@ -440,17 +432,11 @@
         synchronized (mUseContactsLock) {
             if (mUseContactsDictionary) {
                 if (null == mContactsDictionary) {
-                    // TODO: revert to the concrete type when USE_BINARY_CONTACTS_DICTIONARY is no
-                    // longer needed
-                    if (LatinIME.USE_BINARY_CONTACTS_DICTIONARY) {
-                        // TODO: use the right locale. We can't do it right now because the
-                        // spell checker is reusing the contacts dictionary across sessions
-                        // without regard for their locale, so we need to fix that first.
-                        mContactsDictionary = new SynchronouslyLoadedContactsBinaryDictionary(this,
-                                Locale.getDefault());
-                    } else {
-                        mContactsDictionary = new SynchronouslyLoadedContactsDictionary(this);
-                    }
+                    // TODO: use the right locale. We can't do it right now because the
+                    // spell checker is reusing the contacts dictionary across sessions
+                    // without regard for their locale, so we need to fix that first.
+                    mContactsDictionary = new SynchronouslyLoadedContactsBinaryDictionary(this,
+                            Locale.getDefault());
                 }
             }
             dictionaryCollection.addDictionary(mContactsDictionary);
@@ -461,7 +447,7 @@
     }
 
     // This method assumes the text is not empty or null.
-    private static int getCapitalizationType(String text) {
+    public static int getCapitalizationType(String text) {
         // If the first char is not uppercase, then the word is either all lower case,
         // and in either case we return CAPITALIZE_NONE.
         if (!Character.isUpperCase(text.codePointAt(0))) return CAPITALIZE_NONE;
@@ -478,358 +464,4 @@
         if (1 == capsCount) return CAPITALIZE_FIRST;
         return (len == capsCount ? CAPITALIZE_ALL : CAPITALIZE_NONE);
     }
-
-    private static class AndroidSpellCheckerSession extends Session {
-        // Immutable, but need the locale which is not available in the constructor yet
-        private DictionaryPool mDictionaryPool;
-        // Likewise
-        private Locale mLocale;
-        // Cache this for performance
-        private int mScript; // One of SCRIPT_LATIN or SCRIPT_CYRILLIC for now.
-
-        private final AndroidSpellCheckerService mService;
-
-        private final SuggestionsCache mSuggestionsCache = new SuggestionsCache();
-
-        private static class SuggestionsParams {
-            public final String[] mSuggestions;
-            public final int mFlags;
-            public SuggestionsParams(String[] suggestions, int flags) {
-                mSuggestions = suggestions;
-                mFlags = flags;
-            }
-        }
-
-        private static class SuggestionsCache {
-            private static final int MAX_CACHE_SIZE = 50;
-            // TODO: support bigram
-            private final LruCache<String, SuggestionsParams> mUnigramSuggestionsInfoCache =
-                    new LruCache<String, SuggestionsParams>(MAX_CACHE_SIZE);
-
-            public SuggestionsParams getSuggestionsFromCache(String query) {
-                return mUnigramSuggestionsInfoCache.get(query);
-            }
-
-            public void putSuggestionsToCache(String query, String[] suggestions, int flags) {
-                if (suggestions == null || TextUtils.isEmpty(query)) {
-                    return;
-                }
-                mUnigramSuggestionsInfoCache.put(query, new SuggestionsParams(suggestions, flags));
-            }
-        }
-
-        AndroidSpellCheckerSession(final AndroidSpellCheckerService service) {
-            mService = service;
-        }
-
-        @Override
-        public void onCreate() {
-            final String localeString = getLocale();
-            mDictionaryPool = mService.getDictionaryPool(localeString);
-            mLocale = LocaleUtils.constructLocaleFromString(localeString);
-            mScript = getScriptFromLocale(mLocale);
-        }
-
-        /*
-         * Returns whether the code point is a letter that makes sense for the specified
-         * locale for this spell checker.
-         * The dictionaries supported by Latin IME are described in res/xml/spellchecker.xml
-         * and is limited to EFIGS languages and Russian.
-         * Hence at the moment this explicitly tests for Cyrillic characters or Latin characters
-         * as appropriate, and explicitly excludes CJK, Arabic and Hebrew characters.
-         */
-        private static boolean isLetterCheckableByLanguage(final int codePoint,
-                final int script) {
-            switch (script) {
-            case SCRIPT_LATIN:
-                // Our supported latin script dictionaries (EFIGS) at the moment only include
-                // characters in the C0, C1, Latin Extended A and B, IPA extensions unicode
-                // blocks. As it happens, those are back-to-back in the code range 0x40 to 0x2AF,
-                // so the below is a very efficient way to test for it. As for the 0-0x3F, it's
-                // excluded from isLetter anyway.
-                return codePoint <= 0x2AF && Character.isLetter(codePoint);
-            case SCRIPT_CYRILLIC:
-                // All Cyrillic characters are in the 400~52F block. There are some in the upper
-                // Unicode range, but they are archaic characters that are not used in modern
-                // russian and are not used by our dictionary.
-                return codePoint >= 0x400 && codePoint <= 0x52F && Character.isLetter(codePoint);
-            default:
-                // Should never come here
-                throw new RuntimeException("Impossible value of script: " + script);
-            }
-        }
-
-        /**
-         * Finds out whether a particular string should be filtered out of spell checking.
-         *
-         * This will loosely match URLs, numbers, symbols. To avoid always underlining words that
-         * we know we will never recognize, this accepts a script identifier that should be one
-         * of the SCRIPT_* constants defined above, to rule out quickly characters from very
-         * different languages.
-         *
-         * @param text the string to evaluate.
-         * @param script the identifier for the script this spell checker recognizes
-         * @return true if we should filter this text out, false otherwise
-         */
-        private static boolean shouldFilterOut(final String text, final int script) {
-            if (TextUtils.isEmpty(text) || text.length() <= 1) return true;
-
-            // TODO: check if an equivalent processing can't be done more quickly with a
-            // compiled regexp.
-            // Filter by first letter
-            final int firstCodePoint = text.codePointAt(0);
-            // Filter out words that don't start with a letter or an apostrophe
-            if (!isLetterCheckableByLanguage(firstCodePoint, script)
-                    && '\'' != firstCodePoint) return true;
-
-            // Filter contents
-            final int length = text.length();
-            int letterCount = 0;
-            for (int i = 0; i < length; i = text.offsetByCodePoints(i, 1)) {
-                final int codePoint = text.codePointAt(i);
-                // Any word containing a '@' is probably an e-mail address
-                // Any word containing a '/' is probably either an ad-hoc combination of two
-                // words or a URI - in either case we don't want to spell check that
-                if ('@' == codePoint || '/' == codePoint) return true;
-                if (isLetterCheckableByLanguage(codePoint, script)) ++letterCount;
-            }
-            // Guestimate heuristic: perform spell checking if at least 3/4 of the characters
-            // in this word are letters
-            return (letterCount * 4 < length * 3);
-        }
-
-        private SentenceSuggestionsInfo fixWronglyInvalidatedWordWithSingleQuote(
-                TextInfo ti, SentenceSuggestionsInfo ssi) {
-            final String typedText = ti.getText();
-            if (!typedText.contains(SINGLE_QUOTE)) {
-                return null;
-            }
-            final int N = ssi.getSuggestionsCount();
-            final ArrayList<Integer> additionalOffsets = new ArrayList<Integer>();
-            final ArrayList<Integer> additionalLengths = new ArrayList<Integer>();
-            final ArrayList<SuggestionsInfo> additionalSuggestionsInfos =
-                    new ArrayList<SuggestionsInfo>();
-            for (int i = 0; i < N; ++i) {
-                final SuggestionsInfo si = ssi.getSuggestionsInfoAt(i);
-                final int flags = si.getSuggestionsAttributes();
-                if ((flags & SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY) == 0) {
-                    continue;
-                }
-                final int offset = ssi.getOffsetAt(i);
-                final int length = ssi.getLengthAt(i);
-                final String subText = typedText.substring(offset, offset + length);
-                if (!subText.contains(SINGLE_QUOTE)) {
-                    continue;
-                }
-                final String[] splitTexts = subText.split(SINGLE_QUOTE, -1);
-                if (splitTexts == null || splitTexts.length <= 1) {
-                    continue;
-                }
-                final int splitNum = splitTexts.length;
-                for (int j = 0; j < splitNum; ++j) {
-                    final String splitText = splitTexts[j];
-                    if (TextUtils.isEmpty(splitText)) {
-                        continue;
-                    }
-                    if (mSuggestionsCache.getSuggestionsFromCache(splitText) == null) {
-                        continue;
-                    }
-                    final int newLength = splitText.length();
-                    // Neither RESULT_ATTR_IN_THE_DICTIONARY nor RESULT_ATTR_LOOKS_LIKE_TYPO
-                    final int newFlags = 0;
-                    final SuggestionsInfo newSi = new SuggestionsInfo(newFlags, EMPTY_STRING_ARRAY);
-                    newSi.setCookieAndSequence(si.getCookie(), si.getSequence());
-                    if (DBG) {
-                        Log.d(TAG, "Override and remove old span over: "
-                                + splitText + ", " + offset + "," + newLength);
-                    }
-                    additionalOffsets.add(offset);
-                    additionalLengths.add(newLength);
-                    additionalSuggestionsInfos.add(newSi);
-                }
-            }
-            final int additionalSize = additionalOffsets.size();
-            if (additionalSize <= 0) {
-                return null;
-            }
-            final int suggestionsSize = N + additionalSize;
-            final int[] newOffsets = new int[suggestionsSize];
-            final int[] newLengths = new int[suggestionsSize];
-            final SuggestionsInfo[] newSuggestionsInfos = new SuggestionsInfo[suggestionsSize];
-            int i;
-            for (i = 0; i < N; ++i) {
-                newOffsets[i] = ssi.getOffsetAt(i);
-                newLengths[i] = ssi.getLengthAt(i);
-                newSuggestionsInfos[i] = ssi.getSuggestionsInfoAt(i);
-            }
-            for (; i < suggestionsSize; ++i) {
-                newOffsets[i] = additionalOffsets.get(i - N);
-                newLengths[i] = additionalLengths.get(i - N);
-                newSuggestionsInfos[i] = additionalSuggestionsInfos.get(i - N);
-            }
-            return new SentenceSuggestionsInfo(newSuggestionsInfos, newOffsets, newLengths);
-        }
-
-        @Override
-        public SentenceSuggestionsInfo[] onGetSentenceSuggestionsMultiple(
-                TextInfo[] textInfos, int suggestionsLimit) {
-            final SentenceSuggestionsInfo[] retval = super.onGetSentenceSuggestionsMultiple(
-                    textInfos, suggestionsLimit);
-            if (retval == null || retval.length != textInfos.length) {
-                return retval;
-            }
-            for (int i = 0; i < retval.length; ++i) {
-                final SentenceSuggestionsInfo tempSsi =
-                        fixWronglyInvalidatedWordWithSingleQuote(textInfos[i], retval[i]);
-                if (tempSsi != null) {
-                    retval[i] = tempSsi;
-                }
-            }
-            return retval;
-        }
-
-        @Override
-        public SuggestionsInfo[] onGetSuggestionsMultiple(TextInfo[] textInfos,
-                int suggestionsLimit, boolean sequentialWords) {
-            final int length = textInfos.length;
-            final SuggestionsInfo[] retval = new SuggestionsInfo[length];
-            for (int i = 0; i < length; ++i) {
-                final String prevWord;
-                if (sequentialWords && i > 0) {
-                    final String prevWordCandidate = textInfos[i - 1].getText();
-                    // Note that an empty string would be used to indicate the initial word
-                    // in the future.
-                    prevWord = TextUtils.isEmpty(prevWordCandidate) ? null : prevWordCandidate;
-                } else {
-                    prevWord = null;
-                }
-                retval[i] = onGetSuggestions(textInfos[i], prevWord, suggestionsLimit);
-                retval[i].setCookieAndSequence(
-                        textInfos[i].getCookie(), textInfos[i].getSequence());
-            }
-            return retval;
-        }
-
-        // Note : this must be reentrant
-        /**
-         * Gets a list of suggestions for a specific string. This returns a list of possible
-         * corrections for the text passed as an argument. It may split or group words, and
-         * even perform grammatical analysis.
-         */
-        @Override
-        public SuggestionsInfo onGetSuggestions(final TextInfo textInfo,
-                final int suggestionsLimit) {
-            return onGetSuggestions(textInfo, null, suggestionsLimit);
-        }
-
-        private SuggestionsInfo onGetSuggestions(
-                final TextInfo textInfo, final String prevWord, final int suggestionsLimit) {
-            try {
-                final String inText = textInfo.getText();
-                final SuggestionsParams cachedSuggestionsParams =
-                        mSuggestionsCache.getSuggestionsFromCache(inText);
-                if (cachedSuggestionsParams != null) {
-                    if (DBG) {
-                        Log.d(TAG, "Cache hit: " + inText + ", " + cachedSuggestionsParams.mFlags);
-                    }
-                    return new SuggestionsInfo(
-                            cachedSuggestionsParams.mFlags, cachedSuggestionsParams.mSuggestions);
-                }
-
-                if (shouldFilterOut(inText, mScript)) {
-                    DictAndProximity dictInfo = null;
-                    try {
-                        dictInfo = mDictionaryPool.takeOrGetNull();
-                        if (null == dictInfo) return getNotInDictEmptySuggestions();
-                        return dictInfo.mDictionary.isValidWord(inText) ?
-                                getInDictEmptySuggestions() : getNotInDictEmptySuggestions();
-                    } finally {
-                        if (null != dictInfo) {
-                            if (!mDictionaryPool.offer(dictInfo)) {
-                                Log.e(TAG, "Can't re-insert a dictionary into its pool");
-                            }
-                        }
-                    }
-                }
-                final String text = inText.replaceAll(APOSTROPHE, SINGLE_QUOTE);
-
-                // TODO: Don't gather suggestions if the limit is <= 0 unless necessary
-                final SuggestionsGatherer suggestionsGatherer = new SuggestionsGatherer(text,
-                        mService.mSuggestionThreshold, mService.mRecommendedThreshold,
-                        suggestionsLimit);
-                final WordComposer composer = new WordComposer();
-                final int length = text.length();
-                for (int i = 0; i < length; i = text.offsetByCodePoints(i, 1)) {
-                    final int codePoint = text.codePointAt(i);
-                    // The getXYForCodePointAndScript method returns (Y << 16) + X
-                    final int xy = SpellCheckerProximityInfo.getXYForCodePointAndScript(
-                            codePoint, mScript);
-                    if (SpellCheckerProximityInfo.NOT_A_COORDINATE_PAIR == xy) {
-                        composer.add(codePoint, WordComposer.NOT_A_COORDINATE,
-                                WordComposer.NOT_A_COORDINATE, null);
-                    } else {
-                        composer.add(codePoint, xy & 0xFFFF, xy >> 16, null);
-                    }
-                }
-
-                final int capitalizeType = getCapitalizationType(text);
-                boolean isInDict = true;
-                DictAndProximity dictInfo = null;
-                try {
-                    dictInfo = mDictionaryPool.takeOrGetNull();
-                    if (null == dictInfo) return getNotInDictEmptySuggestions();
-                    dictInfo.mDictionary.getWords(composer, prevWord, suggestionsGatherer,
-                            dictInfo.mProximityInfo);
-                    isInDict = dictInfo.mDictionary.isValidWord(text);
-                    if (!isInDict && CAPITALIZE_NONE != capitalizeType) {
-                        // We want to test the word again if it's all caps or first caps only.
-                        // If it's fully down, we already tested it, if it's mixed case, we don't
-                        // want to test a lowercase version of it.
-                        isInDict = dictInfo.mDictionary.isValidWord(text.toLowerCase(mLocale));
-                    }
-                } finally {
-                    if (null != dictInfo) {
-                        if (!mDictionaryPool.offer(dictInfo)) {
-                            Log.e(TAG, "Can't re-insert a dictionary into its pool");
-                        }
-                    }
-                }
-
-                final SuggestionsGatherer.Result result = suggestionsGatherer.getResults(
-                        capitalizeType, mLocale);
-
-                if (DBG) {
-                    Log.i(TAG, "Spell checking results for " + text + " with suggestion limit "
-                            + suggestionsLimit);
-                    Log.i(TAG, "IsInDict = " + isInDict);
-                    Log.i(TAG, "LooksLikeTypo = " + (!isInDict));
-                    Log.i(TAG, "HasRecommendedSuggestions = " + result.mHasRecommendedSuggestions);
-                    if (null != result.mSuggestions) {
-                        for (String suggestion : result.mSuggestions) {
-                            Log.i(TAG, suggestion);
-                        }
-                    }
-                }
-
-                final int flags =
-                        (isInDict ? SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY
-                                : SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO)
-                        | (result.mHasRecommendedSuggestions
-                                ? SuggestionsInfoCompatUtils
-                                        .getValueOf_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS()
-                                : 0);
-                final SuggestionsInfo retval = new SuggestionsInfo(flags, result.mSuggestions);
-                mSuggestionsCache.putSuggestionsToCache(text, result.mSuggestions, flags);
-                return retval;
-            } catch (RuntimeException e) {
-                // Don't kill the keyboard if there is a bug in the spell checker
-                if (DBG) {
-                    throw e;
-                } else {
-                    Log.e(TAG, "Exception while spellcheking: " + e);
-                    return getNotInDictEmptySuggestions();
-                }
-            }
-        }
-    }
 }
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
new file mode 100644
index 0000000..501a0e2
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.inputmethod.latin.spellcheck;
+
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.textservice.SentenceSuggestionsInfo;
+import android.view.textservice.SuggestionsInfo;
+import android.view.textservice.TextInfo;
+
+import java.util.ArrayList;
+
+public class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheckerSession {
+    private static final String TAG = AndroidSpellCheckerSession.class.getSimpleName();
+    private static final boolean DBG = false;
+    private final static String[] EMPTY_STRING_ARRAY = new String[0];
+
+    public AndroidSpellCheckerSession(AndroidSpellCheckerService service) {
+        super(service);
+    }
+
+    private SentenceSuggestionsInfo fixWronglyInvalidatedWordWithSingleQuote(TextInfo ti,
+            SentenceSuggestionsInfo ssi) {
+        final String typedText = ti.getText();
+        if (!typedText.contains(AndroidSpellCheckerService.SINGLE_QUOTE)) {
+            return null;
+        }
+        final int N = ssi.getSuggestionsCount();
+        final ArrayList<Integer> additionalOffsets = new ArrayList<Integer>();
+        final ArrayList<Integer> additionalLengths = new ArrayList<Integer>();
+        final ArrayList<SuggestionsInfo> additionalSuggestionsInfos =
+                new ArrayList<SuggestionsInfo>();
+        String currentWord = null;
+        for (int i = 0; i < N; ++i) {
+            final SuggestionsInfo si = ssi.getSuggestionsInfoAt(i);
+            final int flags = si.getSuggestionsAttributes();
+            if ((flags & SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY) == 0) {
+                continue;
+            }
+            final int offset = ssi.getOffsetAt(i);
+            final int length = ssi.getLengthAt(i);
+            final String subText = typedText.substring(offset, offset + length);
+            final String prevWord = currentWord;
+            currentWord = subText;
+            if (!subText.contains(AndroidSpellCheckerService.SINGLE_QUOTE)) {
+                continue;
+            }
+            final String[] splitTexts =
+                    subText.split(AndroidSpellCheckerService.SINGLE_QUOTE, -1);
+            if (splitTexts == null || splitTexts.length <= 1) {
+                continue;
+            }
+            final int splitNum = splitTexts.length;
+            for (int j = 0; j < splitNum; ++j) {
+                final String splitText = splitTexts[j];
+                if (TextUtils.isEmpty(splitText)) {
+                    continue;
+                }
+                if (mSuggestionsCache.getSuggestionsFromCache(splitText, prevWord) == null) {
+                    continue;
+                }
+                final int newLength = splitText.length();
+                // Neither RESULT_ATTR_IN_THE_DICTIONARY nor RESULT_ATTR_LOOKS_LIKE_TYPO
+                final int newFlags = 0;
+                final SuggestionsInfo newSi =
+                        new SuggestionsInfo(newFlags, EMPTY_STRING_ARRAY);
+                newSi.setCookieAndSequence(si.getCookie(), si.getSequence());
+                if (DBG) {
+                    Log.d(TAG, "Override and remove old span over: " + splitText + ", "
+                            + offset + "," + newLength);
+                }
+                additionalOffsets.add(offset);
+                additionalLengths.add(newLength);
+                additionalSuggestionsInfos.add(newSi);
+            }
+        }
+        final int additionalSize = additionalOffsets.size();
+        if (additionalSize <= 0) {
+            return null;
+        }
+        final int suggestionsSize = N + additionalSize;
+        final int[] newOffsets = new int[suggestionsSize];
+        final int[] newLengths = new int[suggestionsSize];
+        final SuggestionsInfo[] newSuggestionsInfos = new SuggestionsInfo[suggestionsSize];
+        int i;
+        for (i = 0; i < N; ++i) {
+            newOffsets[i] = ssi.getOffsetAt(i);
+            newLengths[i] = ssi.getLengthAt(i);
+            newSuggestionsInfos[i] = ssi.getSuggestionsInfoAt(i);
+        }
+        for (; i < suggestionsSize; ++i) {
+            newOffsets[i] = additionalOffsets.get(i - N);
+            newLengths[i] = additionalLengths.get(i - N);
+            newSuggestionsInfos[i] = additionalSuggestionsInfos.get(i - N);
+        }
+        return new SentenceSuggestionsInfo(newSuggestionsInfos, newOffsets, newLengths);
+    }
+
+    @Override
+    public SentenceSuggestionsInfo[] onGetSentenceSuggestionsMultiple(TextInfo[] textInfos,
+            int suggestionsLimit) {
+        final SentenceSuggestionsInfo[] retval =
+                super.onGetSentenceSuggestionsMultiple(textInfos, suggestionsLimit);
+        if (retval == null || retval.length != textInfos.length) {
+            return retval;
+        }
+        for (int i = 0; i < retval.length; ++i) {
+            final SentenceSuggestionsInfo tempSsi =
+                    fixWronglyInvalidatedWordWithSingleQuote(textInfos[i], retval[i]);
+            if (tempSsi != null) {
+                retval[i] = tempSsi;
+            }
+        }
+        return retval;
+    }
+
+    @Override
+    public SuggestionsInfo[] onGetSuggestionsMultiple(TextInfo[] textInfos,
+            int suggestionsLimit, boolean sequentialWords) {
+        final int length = textInfos.length;
+        final SuggestionsInfo[] retval = new SuggestionsInfo[length];
+        for (int i = 0; i < length; ++i) {
+            final String prevWord;
+            if (sequentialWords && i > 0) {
+                final String prevWordCandidate = textInfos[i - 1].getText();
+                // Note that an empty string would be used to indicate the initial word
+                // in the future.
+                prevWord = TextUtils.isEmpty(prevWordCandidate) ? null : prevWordCandidate;
+            } else {
+                prevWord = null;
+            }
+            retval[i] = onGetSuggestions(textInfos[i], prevWord, suggestionsLimit);
+            retval[i].setCookieAndSequence(textInfos[i].getCookie(),
+                    textInfos[i].getSequence());
+        }
+        return retval;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSessionFactory.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSessionFactory.java
new file mode 100644
index 0000000..8eb1eb6
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSessionFactory.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.inputmethod.latin.spellcheck;
+
+import android.service.textservice.SpellCheckerService.Session;
+
+public abstract class AndroidSpellCheckerSessionFactory {
+    public static Session newInstance(AndroidSpellCheckerService service) {
+        return new AndroidSpellCheckerSession(service);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
new file mode 100644
index 0000000..0171dc0
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.inputmethod.latin.spellcheck;
+
+import android.service.textservice.SpellCheckerService.Session;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.LruCache;
+import android.view.textservice.SuggestionsInfo;
+import android.view.textservice.TextInfo;
+
+import com.android.inputmethod.compat.SuggestionsInfoCompatUtils;
+import com.android.inputmethod.latin.LocaleUtils;
+import com.android.inputmethod.latin.WordComposer;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.spellcheck.AndroidSpellCheckerService.SuggestionsGatherer;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+public abstract class AndroidWordLevelSpellCheckerSession extends Session {
+    private static final String TAG = AndroidWordLevelSpellCheckerSession.class.getSimpleName();
+    private static final boolean DBG = false;
+
+    // Immutable, but need the locale which is not available in the constructor yet
+    private DictionaryPool mDictionaryPool;
+    // Likewise
+    private Locale mLocale;
+    // Cache this for performance
+    private int mScript; // One of SCRIPT_LATIN or SCRIPT_CYRILLIC for now.
+    private final AndroidSpellCheckerService mService;
+    protected final SuggestionsCache mSuggestionsCache = new SuggestionsCache();
+
+    private static class SuggestionsParams {
+        public final String[] mSuggestions;
+        public final int mFlags;
+        public SuggestionsParams(String[] suggestions, int flags) {
+            mSuggestions = suggestions;
+            mFlags = flags;
+        }
+    }
+
+    protected static class SuggestionsCache {
+        private static final char CHAR_DELIMITER = '\uFFFC';
+        private static final int MAX_CACHE_SIZE = 50;
+        private final LruCache<String, SuggestionsParams> mUnigramSuggestionsInfoCache =
+                new LruCache<String, SuggestionsParams>(MAX_CACHE_SIZE);
+
+        // TODO: Support n-gram input
+        private static String generateKey(String query, String prevWord) {
+            if (TextUtils.isEmpty(query) || TextUtils.isEmpty(prevWord)) {
+                return query;
+            }
+            return query + CHAR_DELIMITER + prevWord;
+        }
+
+        // TODO: Support n-gram input
+        public SuggestionsParams getSuggestionsFromCache(String query, String prevWord) {
+            return mUnigramSuggestionsInfoCache.get(generateKey(query, prevWord));
+        }
+
+        // TODO: Support n-gram input
+        public void putSuggestionsToCache(
+                String query, String prevWord, String[] suggestions, int flags) {
+            if (suggestions == null || TextUtils.isEmpty(query)) {
+                return;
+            }
+            mUnigramSuggestionsInfoCache.put(
+                    generateKey(query, prevWord), new SuggestionsParams(suggestions, flags));
+        }
+    }
+
+    AndroidWordLevelSpellCheckerSession(final AndroidSpellCheckerService service) {
+        mService = service;
+    }
+
+    @Override
+    public void onCreate() {
+        final String localeString = getLocale();
+        mDictionaryPool = mService.getDictionaryPool(localeString);
+        mLocale = LocaleUtils.constructLocaleFromString(localeString);
+        mScript = AndroidSpellCheckerService.getScriptFromLocale(mLocale);
+    }
+
+    /*
+     * Returns whether the code point is a letter that makes sense for the specified
+     * locale for this spell checker.
+     * The dictionaries supported by Latin IME are described in res/xml/spellchecker.xml
+     * and is limited to EFIGS languages and Russian.
+     * Hence at the moment this explicitly tests for Cyrillic characters or Latin characters
+     * as appropriate, and explicitly excludes CJK, Arabic and Hebrew characters.
+     */
+    private static boolean isLetterCheckableByLanguage(final int codePoint,
+            final int script) {
+        switch (script) {
+        case AndroidSpellCheckerService.SCRIPT_LATIN:
+            // Our supported latin script dictionaries (EFIGS) at the moment only include
+            // characters in the C0, C1, Latin Extended A and B, IPA extensions unicode
+            // blocks. As it happens, those are back-to-back in the code range 0x40 to 0x2AF,
+            // so the below is a very efficient way to test for it. As for the 0-0x3F, it's
+            // excluded from isLetter anyway.
+            return codePoint <= 0x2AF && Character.isLetter(codePoint);
+        case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
+            // All Cyrillic characters are in the 400~52F block. There are some in the upper
+            // Unicode range, but they are archaic characters that are not used in modern
+            // russian and are not used by our dictionary.
+            return codePoint >= 0x400 && codePoint <= 0x52F && Character.isLetter(codePoint);
+        default:
+            // Should never come here
+            throw new RuntimeException("Impossible value of script: " + script);
+        }
+    }
+
+    /**
+     * Finds out whether a particular string should be filtered out of spell checking.
+     *
+     * This will loosely match URLs, numbers, symbols. To avoid always underlining words that
+     * we know we will never recognize, this accepts a script identifier that should be one
+     * of the SCRIPT_* constants defined above, to rule out quickly characters from very
+     * different languages.
+     *
+     * @param text the string to evaluate.
+     * @param script the identifier for the script this spell checker recognizes
+     * @return true if we should filter this text out, false otherwise
+     */
+    private static boolean shouldFilterOut(final String text, final int script) {
+        if (TextUtils.isEmpty(text) || text.length() <= 1) return true;
+
+        // TODO: check if an equivalent processing can't be done more quickly with a
+        // compiled regexp.
+        // Filter by first letter
+        final int firstCodePoint = text.codePointAt(0);
+        // Filter out words that don't start with a letter or an apostrophe
+        if (!isLetterCheckableByLanguage(firstCodePoint, script)
+                && '\'' != firstCodePoint) return true;
+
+        // Filter contents
+        final int length = text.length();
+        int letterCount = 0;
+        for (int i = 0; i < length; i = text.offsetByCodePoints(i, 1)) {
+            final int codePoint = text.codePointAt(i);
+            // Any word containing a '@' is probably an e-mail address
+            // Any word containing a '/' is probably either an ad-hoc combination of two
+            // words or a URI - in either case we don't want to spell check that
+            if ('@' == codePoint || '/' == codePoint) return true;
+            if (isLetterCheckableByLanguage(codePoint, script)) ++letterCount;
+        }
+        // Guestimate heuristic: perform spell checking if at least 3/4 of the characters
+        // in this word are letters
+        return (letterCount * 4 < length * 3);
+    }
+
+    // Note : this must be reentrant
+    /**
+     * Gets a list of suggestions for a specific string. This returns a list of possible
+     * corrections for the text passed as an argument. It may split or group words, and
+     * even perform grammatical analysis.
+     */
+    @Override
+    public SuggestionsInfo onGetSuggestions(final TextInfo textInfo,
+            final int suggestionsLimit) {
+        return onGetSuggestions(textInfo, null, suggestionsLimit);
+    }
+
+    protected SuggestionsInfo onGetSuggestions(
+            final TextInfo textInfo, final String prevWord, final int suggestionsLimit) {
+        try {
+            final String inText = textInfo.getText();
+            final SuggestionsParams cachedSuggestionsParams =
+                    mSuggestionsCache.getSuggestionsFromCache(inText, prevWord);
+            if (cachedSuggestionsParams != null) {
+                if (DBG) {
+                    Log.d(TAG, "Cache hit: " + inText + ", " + cachedSuggestionsParams.mFlags);
+                }
+                return new SuggestionsInfo(
+                        cachedSuggestionsParams.mFlags, cachedSuggestionsParams.mSuggestions);
+            }
+
+            if (shouldFilterOut(inText, mScript)) {
+                DictAndProximity dictInfo = null;
+                try {
+                    dictInfo = mDictionaryPool.takeOrGetNull();
+                    if (null == dictInfo) {
+                        return AndroidSpellCheckerService.getNotInDictEmptySuggestions();
+                    }
+                    return dictInfo.mDictionary.isValidWord(inText)
+                            ? AndroidSpellCheckerService.getInDictEmptySuggestions()
+                            : AndroidSpellCheckerService.getNotInDictEmptySuggestions();
+                } finally {
+                    if (null != dictInfo) {
+                        if (!mDictionaryPool.offer(dictInfo)) {
+                            Log.e(TAG, "Can't re-insert a dictionary into its pool");
+                        }
+                    }
+                }
+            }
+            final String text = inText.replaceAll(
+                    AndroidSpellCheckerService.APOSTROPHE, AndroidSpellCheckerService.SINGLE_QUOTE);
+
+            // TODO: Don't gather suggestions if the limit is <= 0 unless necessary
+            //final SuggestionsGatherer suggestionsGatherer = new SuggestionsGatherer(text,
+            //mService.mSuggestionThreshold, mService.mRecommendedThreshold,
+            //suggestionsLimit);
+            final SuggestionsGatherer suggestionsGatherer = mService.newSuggestionsGatherer(
+                    text, suggestionsLimit);
+            final WordComposer composer = new WordComposer();
+            final int length = text.length();
+            for (int i = 0; i < length; i = text.offsetByCodePoints(i, 1)) {
+                final int codePoint = text.codePointAt(i);
+                // The getXYForCodePointAndScript method returns (Y << 16) + X
+                final int xy = SpellCheckerProximityInfo.getXYForCodePointAndScript(
+                        codePoint, mScript);
+                if (SpellCheckerProximityInfo.NOT_A_COORDINATE_PAIR == xy) {
+                    composer.add(codePoint, WordComposer.NOT_A_COORDINATE,
+                            WordComposer.NOT_A_COORDINATE);
+                } else {
+                    composer.add(codePoint, xy & 0xFFFF, xy >> 16);
+                }
+            }
+
+            final int capitalizeType = AndroidSpellCheckerService.getCapitalizationType(text);
+            boolean isInDict = true;
+            DictAndProximity dictInfo = null;
+            try {
+                dictInfo = mDictionaryPool.takeOrGetNull();
+                if (null == dictInfo) {
+                    return AndroidSpellCheckerService.getNotInDictEmptySuggestions();
+                }
+                final ArrayList<SuggestedWordInfo> suggestions =
+                        dictInfo.mDictionary.getSuggestions(composer, prevWord,
+                                dictInfo.mProximityInfo);
+                for (final SuggestedWordInfo suggestion : suggestions) {
+                    final String suggestionStr = suggestion.mWord.toString();
+                    suggestionsGatherer.addWord(suggestionStr.toCharArray(), null, 0,
+                            suggestionStr.length(), suggestion.mScore);
+                }
+                isInDict = dictInfo.mDictionary.isValidWord(text);
+                if (!isInDict && AndroidSpellCheckerService.CAPITALIZE_NONE != capitalizeType) {
+                    // We want to test the word again if it's all caps or first caps only.
+                    // If it's fully down, we already tested it, if it's mixed case, we don't
+                    // want to test a lowercase version of it.
+                    isInDict = dictInfo.mDictionary.isValidWord(text.toLowerCase(mLocale));
+                }
+            } finally {
+                if (null != dictInfo) {
+                    if (!mDictionaryPool.offer(dictInfo)) {
+                        Log.e(TAG, "Can't re-insert a dictionary into its pool");
+                    }
+                }
+            }
+
+            final SuggestionsGatherer.Result result = suggestionsGatherer.getResults(
+                    capitalizeType, mLocale);
+
+            if (DBG) {
+                Log.i(TAG, "Spell checking results for " + text + " with suggestion limit "
+                        + suggestionsLimit);
+                Log.i(TAG, "IsInDict = " + isInDict);
+                Log.i(TAG, "LooksLikeTypo = " + (!isInDict));
+                Log.i(TAG, "HasRecommendedSuggestions = " + result.mHasRecommendedSuggestions);
+                if (null != result.mSuggestions) {
+                    for (String suggestion : result.mSuggestions) {
+                        Log.i(TAG, suggestion);
+                    }
+                }
+            }
+
+            final int flags =
+                    (isInDict ? SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY
+                            : SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO)
+                    | (result.mHasRecommendedSuggestions
+                            ? SuggestionsInfoCompatUtils
+                                    .getValueOf_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS()
+                            : 0);
+            final SuggestionsInfo retval = new SuggestionsInfo(flags, result.mSuggestions);
+            mSuggestionsCache.putSuggestionsToCache(text, prevWord, result.mSuggestions, flags);
+            return retval;
+        } catch (RuntimeException e) {
+            // Don't kill the keyboard if there is a bug in the spell checker
+            if (DBG) {
+                throw e;
+            } else {
+                Log.e(TAG, "Exception while spellcheking: " + e);
+                return AndroidSpellCheckerService.getNotInDictEmptySuggestions();
+            }
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java
index e86390b..4d33f4b 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java
@@ -57,11 +57,11 @@
 import com.android.inputmethod.keyboard.MoreKeysPanel;
 import com.android.inputmethod.keyboard.PointerTracker;
 import com.android.inputmethod.keyboard.ViewLayoutUtils;
+import com.android.inputmethod.latin.AutoCorrection;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.ResearchLogger;
 import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
-import com.android.inputmethod.latin.Suggest;
 import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.Utils;
 import com.android.inputmethod.latin.define.ProductionFlag;
@@ -71,7 +71,7 @@
 public class SuggestionsView extends RelativeLayout implements OnClickListener,
         OnLongClickListener {
     public interface Listener {
-        public boolean addWordToDictionary(String word);
+        public boolean addWordToUserDictionary(String word);
         public void pickSuggestionManually(int index, CharSequence word, int x, int y);
     }
 
@@ -336,8 +336,8 @@
             if (LatinImeLogger.sDBG && suggestedWords.size() > 1) {
                 // If we auto-correct, then the autocorrection is in slot 0 and the typed word
                 // is in slot 1.
-                if (index == mCenterSuggestionIndex && suggestedWords.mHasAutoCorrectionCandidate
-                        && Suggest.shouldBlockAutoCorrectionBySafetyNet(
+                if (index == mCenterSuggestionIndex
+                        && AutoCorrection.shouldBlockAutoCorrectionBySafetyNet(
                                 suggestedWords.getWord(1).toString(), suggestedWords.getWord(0))) {
                     return 0xFFFF0000;
                 }
@@ -718,10 +718,6 @@
         mPreviewPopup.dismiss();
     }
 
-    private void addToDictionary(CharSequence word) {
-        mListener.addWordToDictionary(word.toString());
-    }
-
     private final KeyboardActionListener mMoreSuggestionsListener =
             new KeyboardActionListener.Adapter() {
         @Override
@@ -863,7 +859,7 @@
     @Override
     public void onClick(View view) {
         if (mParams.isAddToDictionaryShowing(view)) {
-            addToDictionary(mParams.getAddToDictionaryWord());
+            mListener.addWordToUserDictionary(mParams.getAddToDictionaryWord().toString());
             clear();
             return;
         }
diff --git a/native/jni/Android.mk b/native/jni/Android.mk
index d53757f..54f61d9 100644
--- a/native/jni/Android.mk
+++ b/native/jni/Android.mk
@@ -17,15 +17,16 @@
 ############ some local flags
 # If you change any of those flags, you need to rebuild both libjni_latinime_static
 # and the shared library.
-#FLAG_DBG := true
-#FLAG_DO_PROFILE := true
+FLAG_DBG ?= false
+FLAG_DO_PROFILE ?= false
 
 ######################################
 include $(CLEAR_VARS)
 
 LATIN_IME_SRC_DIR := src
+LATIN_IME_SRC_FULLPATH_DIR := $(LOCAL_PATH)/$(LATIN_IME_SRC_DIR)
 
-LOCAL_C_INCLUDES += $(LOCAL_PATH)/$(LATIN_IME_SRC_DIR)
+LOCAL_C_INCLUDES += $(LATIN_IME_SRC_FULLPATH_DIR) $(LATIN_IME_SRC_FULLPATH_DIR)/gesture
 
 LOCAL_CFLAGS += -Werror -Wall
 
@@ -35,6 +36,7 @@
 LATIN_IME_JNI_SRC_FILES := \
     com_android_inputmethod_keyboard_ProximityInfo.cpp \
     com_android_inputmethod_latin_BinaryDictionary.cpp \
+    com_android_inputmethod_latin_NativeUtils.cpp \
     jni_common.cpp
 
 LATIN_IME_CORE_SRC_FILES := \
@@ -45,11 +47,13 @@
     correction.cpp \
     dictionary.cpp \
     proximity_info.cpp \
-    unigram_dictionary.cpp
+    proximity_info_state.cpp \
+    unigram_dictionary.cpp \
+    gesture/gesture_decoder_wrapper.cpp
 
 LOCAL_SRC_FILES := \
     $(LATIN_IME_JNI_SRC_FILES) \
-    $(addprefix $(LATIN_IME_SRC_DIR)/,$(LATIN_IME_CORE_SRC_FILES))
+    $(addprefix $(LATIN_IME_SRC_DIR)/, $(LATIN_IME_CORE_SRC_FILES))
 
 ifeq ($(FLAG_DO_PROFILE), true)
     $(warning Making profiling version of native library)
@@ -61,28 +65,24 @@
 endif # FLAG_DBG
 endif # FLAG_DO_PROFILE
 
-LOCAL_MODULE := libjni_latinime_static
+LOCAL_MODULE := libjni_latinime_common_static
 LOCAL_MODULE_TAGS := optional
 
-ifdef HISTORICAL_NDK_VERSIONS_ROOT # In the platform build system
+# TODO: Remove this conditional block once we have no issues with building against NDK
+ifndef TARGET_BUILD_APPS # A full system image build
 include external/stlport/libstlport.mk
-else # In the NDK build system
-LOCAL_C_INCLUDES += external/stlport/stlport bionic
+else # An unbundled build
+LOCAL_NDK_VERSION := 7
+LOCAL_SDK_VERSION := 14
+LOCAL_NDK_STL_VARIANT := stlport_static
 endif
 
 include $(BUILD_STATIC_LIBRARY)
-
 ######################################
 include $(CLEAR_VARS)
 
 # All code in LOCAL_WHOLE_STATIC_LIBRARIES will be built into this shared library.
-LOCAL_WHOLE_STATIC_LIBRARIES := libjni_latinime_static
-
-ifdef HISTORICAL_NDK_VERSIONS_ROOT # In the platform build system
-LOCAL_SHARED_LIBRARIES := libstlport
-else # In the NDK build system
-LOCAL_SHARED_LIBRARIES := libstlport_static
-endif
+LOCAL_WHOLE_STATIC_LIBRARIES := libjni_latinime_common_static
 
 ifeq ($(FLAG_DO_PROFILE), true)
     $(warning Making profiling version of native library)
@@ -97,8 +97,13 @@
 LOCAL_MODULE := libjni_latinime
 LOCAL_MODULE_TAGS := optional
 
-ifdef HISTORICAL_NDK_VERSIONS_ROOT # In the platform build system
-include external/stlport/libstlport.mk
+# TODO: Remove this conditional block once we have no issues with building against NDK
+ifndef TARGET_BUILD_APPS # A full system image build
+LOCAL_STATIC_LIBRARIES += libstlport_static
+else # An unbundled build
+LOCAL_NDK_VERSION := 7
+LOCAL_SDK_VERSION := 14
+LOCAL_NDK_STL_VARIANT := stlport_static
 endif
 
 include $(BUILD_SHARED_LIBRARY)
@@ -106,5 +111,6 @@
 #################### Clean up the tmp vars
 LATIN_IME_CORE_SRC_FILES :=
 LATIN_IME_JNI_SRC_FILES :=
+LATIN_IME_GESTURE_IMPL_SRC_FILES :=
 LATIN_IME_SRC_DIR :=
-TARGETING_UNBUNDLED_FROYO :=
+LATIN_IME_SRC_FULLPATH_DIR :=
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index d10dc96..bee0662 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -128,28 +128,37 @@
 
 static int latinime_BinaryDictionary_getSuggestions(JNIEnv *env, jobject object, jlong dict,
         jlong proximityInfo, jintArray xCoordinatesArray, jintArray yCoordinatesArray,
-        jintArray inputArray, jint arraySize, jintArray prevWordForBigrams,
-        jboolean useFullEditDistance, jcharArray outputArray, jintArray frequencyArray) {
-    Dictionary *dictionary = (Dictionary*)dict;
+        jintArray timesArray, jintArray pointerIdArray, jintArray inputArray, jint arraySize,
+        jint commitPoint, jboolean isGesture,
+        jintArray prevWordForBigrams, jboolean useFullEditDistance, jcharArray outputArray,
+        jintArray frequencyArray, jintArray spaceIndexArray) {
+    Dictionary *dictionary = (Dictionary*) dict;
     if (!dictionary) return 0;
     ProximityInfo *pInfo = (ProximityInfo*)proximityInfo;
     int *xCoordinates = env->GetIntArrayElements(xCoordinatesArray, 0);
     int *yCoordinates = env->GetIntArrayElements(yCoordinatesArray, 0);
+    int *times = env->GetIntArrayElements(timesArray, 0);
+    int *pointerIds = env->GetIntArrayElements(pointerIdArray, 0);
     int *frequencies = env->GetIntArrayElements(frequencyArray, 0);
     int *inputCodes = env->GetIntArrayElements(inputArray, 0);
     jchar *outputChars = env->GetCharArrayElements(outputArray, 0);
+    int *spaceIndices = env->GetIntArrayElements(spaceIndexArray, 0);
     jint *prevWordChars = prevWordForBigrams
             ? env->GetIntArrayElements(prevWordForBigrams, 0) : 0;
     jsize prevWordLength = prevWordChars ? env->GetArrayLength(prevWordForBigrams) : 0;
-    int count = dictionary->getSuggestions(pInfo, xCoordinates, yCoordinates, inputCodes,
-            arraySize, prevWordChars, prevWordLength, useFullEditDistance,
-            (unsigned short*) outputChars, frequencies);
+    int count = dictionary->getSuggestions(pInfo, xCoordinates, yCoordinates, times, pointerIds,
+            inputCodes, arraySize, prevWordChars, prevWordLength, commitPoint, isGesture,
+            useFullEditDistance, (unsigned short*) outputChars,
+            frequencies, spaceIndices);
     if (prevWordChars) {
         env->ReleaseIntArrayElements(prevWordForBigrams, prevWordChars, JNI_ABORT);
     }
+    env->ReleaseIntArrayElements(spaceIndexArray, spaceIndices, 0);
     env->ReleaseCharArrayElements(outputArray, outputChars, 0);
     env->ReleaseIntArrayElements(inputArray, inputCodes, JNI_ABORT);
     env->ReleaseIntArrayElements(frequencyArray, frequencies, 0);
+    env->ReleaseIntArrayElements(pointerIdArray, pointerIds, 0);
+    env->ReleaseIntArrayElements(timesArray, times, 0);
     env->ReleaseIntArrayElements(yCoordinatesArray, yCoordinates, 0);
     env->ReleaseIntArrayElements(xCoordinatesArray, xCoordinates, 0);
     return count;
@@ -251,8 +260,8 @@
 static JNINativeMethod sMethods[] = {
     {"openNative", "(Ljava/lang/String;JJIIII)J", (void*)latinime_BinaryDictionary_open},
     {"closeNative", "(J)V", (void*)latinime_BinaryDictionary_close},
-    {"getSuggestionsNative", "(JJ[I[I[II[IZ[C[I)I",
-            (void*)latinime_BinaryDictionary_getSuggestions},
+    {"getSuggestionsNative", "(JJ[I[I[I[I[IIIZ[IZ[C[I[I)I",
+            (void*) latinime_BinaryDictionary_getSuggestions},
     {"getFrequencyNative", "(J[II)I", (void*)latinime_BinaryDictionary_getFrequency},
     {"isValidBigramNative", "(J[I[I)Z", (void*)latinime_BinaryDictionary_isValidBigram},
     {"getBigramsNative", "(J[II[II[C[III)I", (void*)latinime_BinaryDictionary_getBigrams},
diff --git a/native/jni/com_android_inputmethod_latin_NativeUtils.cpp b/native/jni/com_android_inputmethod_latin_NativeUtils.cpp
new file mode 100644
index 0000000..c1e586a
--- /dev/null
+++ b/native/jni/com_android_inputmethod_latin_NativeUtils.cpp
@@ -0,0 +1,40 @@
+/*
+**
+** 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.
+*/
+
+#include "com_android_inputmethod_latin_NativeUtils.h"
+#include "jni.h"
+#include "jni_common.h"
+
+#include <math.h>
+
+namespace latinime {
+
+static float latinime_NativeUtils_powf(float x, float y) {
+    return powf(x, y);
+}
+
+static JNINativeMethod sMethods[] = {
+    {"powf", "(FF)F", (void*)latinime_NativeUtils_powf}
+};
+
+int register_NativeUtils(JNIEnv *env) {
+    const char* const kClassPathName = "com/android/inputmethod/latin/NativeUtils";
+    return registerNativeMethods(env, kClassPathName, sMethods,
+            sizeof(sMethods) / sizeof(sMethods[0]));
+}
+
+} // namespace latinime
diff --git a/native/jni/com_android_inputmethod_latin_NativeUtils.h b/native/jni/com_android_inputmethod_latin_NativeUtils.h
new file mode 100644
index 0000000..13a348a
--- /dev/null
+++ b/native/jni/com_android_inputmethod_latin_NativeUtils.h
@@ -0,0 +1,29 @@
+/*
+**
+** 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.
+*/
+
+#ifndef _COM_ANDROID_INPUTMETHOD_LATIN_NATIVEUTILS_H
+#define _COM_ANDROID_INPUTMETHOD_LATIN_NATIVEUTILS_H
+
+#include "jni.h"
+
+namespace latinime {
+
+int register_NativeUtils(JNIEnv *env);
+
+}
+
+#endif // _COM_ANDROID_INPUTMETHOD_LATIN_NATIVEUTILS_H
diff --git a/native/jni/jni_common.cpp b/native/jni/jni_common.cpp
index b9e2c32..1314bab 100644
--- a/native/jni/jni_common.cpp
+++ b/native/jni/jni_common.cpp
@@ -19,6 +19,7 @@
 
 #include "com_android_inputmethod_keyboard_ProximityInfo.h"
 #include "com_android_inputmethod_latin_BinaryDictionary.h"
+#include "com_android_inputmethod_latin_NativeUtils.h"
 #include "defines.h"
 #include "jni.h"
 #include "proximity_info.h"
@@ -52,6 +53,11 @@
         goto bail;
     }
 
+    if (!register_NativeUtils(env)) {
+        AKLOGE("ERROR: NativeUtils native registration failed");
+        goto bail;
+    }
+
     /* success -- return valid version number */
     result = JNI_VERSION_1_4;
 
diff --git a/native/jni/src/additional_proximity_chars.h b/native/jni/src/additional_proximity_chars.h
index e0ecc0e..82c31f8 100644
--- a/native/jni/src/additional_proximity_chars.h
+++ b/native/jni/src/additional_proximity_chars.h
@@ -26,6 +26,7 @@
 
 class AdditionalProximityChars {
  private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(AdditionalProximityChars);
     static const std::string LOCALE_EN_US;
     static const int EN_US_ADDITIONAL_A_SIZE = 4;
     static const int32_t EN_US_ADDITIONAL_A[];
diff --git a/native/jni/src/bigram_dictionary.cpp b/native/jni/src/bigram_dictionary.cpp
index 9ef024d..3bfbfad 100644
--- a/native/jni/src/bigram_dictionary.cpp
+++ b/native/jni/src/bigram_dictionary.cpp
@@ -27,9 +27,8 @@
 
 namespace latinime {
 
-BigramDictionary::BigramDictionary(const unsigned char *dict, int maxWordLength,
-        Dictionary *parentDictionary)
-    : DICT(dict), MAX_WORD_LENGTH(maxWordLength), mParentDictionary(parentDictionary) {
+BigramDictionary::BigramDictionary(const unsigned char *dict, int maxWordLength)
+        : DICT(dict), MAX_WORD_LENGTH(maxWordLength) {
     if (DEBUG_DICT) {
         AKLOGI("BigramDictionary - constructor");
     }
@@ -38,7 +37,8 @@
 BigramDictionary::~BigramDictionary() {
 }
 
-bool BigramDictionary::addWordBigram(unsigned short *word, int length, int frequency) {
+bool BigramDictionary::addWordBigram(unsigned short *word, int length, int frequency,
+        const int maxBigrams, int *bigramFreq, unsigned short *bigramChars) const {
     word[length] = 0;
     if (DEBUG_DICT) {
 #ifdef FLAG_DBG
@@ -50,25 +50,25 @@
 
     // Find the right insertion point
     int insertAt = 0;
-    while (insertAt < mMaxBigrams) {
-        if (frequency > mBigramFreq[insertAt] || (mBigramFreq[insertAt] == frequency
-                && length < Dictionary::wideStrLen(mBigramChars + insertAt * MAX_WORD_LENGTH))) {
+    while (insertAt < maxBigrams) {
+        if (frequency > bigramFreq[insertAt] || (bigramFreq[insertAt] == frequency
+                && length < Dictionary::wideStrLen(bigramChars + insertAt * MAX_WORD_LENGTH))) {
             break;
         }
         insertAt++;
     }
     if (DEBUG_DICT) {
-        AKLOGI("Bigram: InsertAt -> %d maxBigrams: %d", insertAt, mMaxBigrams);
+        AKLOGI("Bigram: InsertAt -> %d maxBigrams: %d", insertAt, maxBigrams);
     }
-    if (insertAt < mMaxBigrams) {
-        memmove((char*) mBigramFreq + (insertAt + 1) * sizeof(mBigramFreq[0]),
-               (char*) mBigramFreq + insertAt * sizeof(mBigramFreq[0]),
-               (mMaxBigrams - insertAt - 1) * sizeof(mBigramFreq[0]));
-        mBigramFreq[insertAt] = frequency;
-        memmove((char*) mBigramChars + (insertAt + 1) * MAX_WORD_LENGTH * sizeof(short),
-               (char*) mBigramChars + (insertAt    ) * MAX_WORD_LENGTH * sizeof(short),
-               (mMaxBigrams - insertAt - 1) * sizeof(short) * MAX_WORD_LENGTH);
-        unsigned short *dest = mBigramChars + (insertAt    ) * MAX_WORD_LENGTH;
+    if (insertAt < maxBigrams) {
+        memmove((char*) bigramFreq + (insertAt + 1) * sizeof(bigramFreq[0]),
+               (char*) bigramFreq + insertAt * sizeof(bigramFreq[0]),
+               (maxBigrams - insertAt - 1) * sizeof(bigramFreq[0]));
+        bigramFreq[insertAt] = frequency;
+        memmove((char*) bigramChars + (insertAt + 1) * MAX_WORD_LENGTH * sizeof(short),
+               (char*) bigramChars + (insertAt    ) * MAX_WORD_LENGTH * sizeof(short),
+               (maxBigrams - insertAt - 1) * sizeof(short) * MAX_WORD_LENGTH);
+        unsigned short *dest = bigramChars + (insertAt    ) * MAX_WORD_LENGTH;
         while (length--) {
             *dest++ = *word++;
         }
@@ -84,7 +84,7 @@
 /* Parameters :
  * prevWord: the word before, the one for which we need to look up bigrams.
  * prevWordLength: its length.
- * codes: what user typed, in the same format as for UnigramDictionary::getSuggestions.
+ * inputCodes: what user typed, in the same format as for UnigramDictionary::getSuggestions.
  * codesSize: the size of the codes array.
  * bigramChars: an array for output, at the same format as outwords for getSuggestions.
  * bigramFreq: an array to output frequencies.
@@ -98,19 +98,22 @@
  * and the bigrams are used to boost unigram result scores, it makes little sense to
  * reduce their scope to the ones that match the first letter.
  */
-int BigramDictionary::getBigrams(const int32_t *prevWord, int prevWordLength, int *codes,
+int BigramDictionary::getBigrams(const int32_t *prevWord, int prevWordLength, int *inputCodes,
         int codesSize, unsigned short *bigramChars, int *bigramFreq, int maxWordLength,
-        int maxBigrams) {
+        int maxBigrams) const {
     // TODO: remove unused arguments, and refrain from storing stuff in members of this class
     // TODO: have "in" arguments before "out" ones, and make out args explicit in the name
-    mBigramFreq = bigramFreq;
-    mBigramChars = bigramChars;
-    mInputCodes = codes;
-    mMaxBigrams = maxBigrams;
 
     const uint8_t* const root = DICT;
-    int pos = getBigramListPositionForWord(prevWord, prevWordLength);
+    int pos = getBigramListPositionForWord(prevWord, prevWordLength,
+            false /* forceLowerCaseSearch */);
     // getBigramListPositionForWord returns 0 if this word isn't in the dictionary or has no bigrams
+    if (0 == pos) {
+        // If no bigrams for this exact word, search again in lower case.
+        pos = getBigramListPositionForWord(prevWord, prevWordLength,
+                true /* forceLowerCaseSearch */);
+    }
+    // If still no bigrams, we really don't have them!
     if (0 == pos) return 0;
     int bigramFlags;
     int bigramCount = 0;
@@ -124,16 +127,17 @@
                 bigramBuffer, &unigramFreq);
 
         // codesSize == 0 means we are trying to find bigram predictions.
-        if (codesSize < 1 || checkFirstCharacter(bigramBuffer)) {
-            const int bigramFreq = UnigramDictionary::MASK_ATTRIBUTE_FREQUENCY & bigramFlags;
+        if (codesSize < 1 || checkFirstCharacter(bigramBuffer, inputCodes)) {
+            const int bigramFreqTemp = UnigramDictionary::MASK_ATTRIBUTE_FREQUENCY & bigramFlags;
             // Due to space constraints, the frequency for bigrams is approximate - the lower the
             // unigram frequency, the worse the precision. The theoritical maximum error in
             // resulting frequency is 8 - although in the practice it's never bigger than 3 or 4
             // in very bad cases. This means that sometimes, we'll see some bigrams interverted
             // here, but it can't get too bad.
             const int frequency =
-                    BinaryFormat::computeFrequencyForBigram(unigramFreq, bigramFreq);
-            if (addWordBigram(bigramBuffer, length, frequency)) {
+                    BinaryFormat::computeFrequencyForBigram(unigramFreq, bigramFreqTemp);
+            if (addWordBigram(
+                    bigramBuffer, length, frequency, maxBigrams, bigramFreq, bigramChars)) {
                 ++bigramCount;
             }
         }
@@ -144,10 +148,11 @@
 // Returns a pointer to the start of the bigram list.
 // If the word is not found or has no bigrams, this function returns 0.
 int BigramDictionary::getBigramListPositionForWord(const int32_t *prevWord,
-        const int prevWordLength) {
+        const int prevWordLength, const bool forceLowerCaseSearch) const {
     if (0 >= prevWordLength) return 0;
     const uint8_t* const root = DICT;
-    int pos = BinaryFormat::getTerminalPosition(root, prevWord, prevWordLength);
+    int pos = BinaryFormat::getTerminalPosition(root, prevWord, prevWordLength,
+            forceLowerCaseSearch);
 
     if (NOT_VALID_WORD == pos) return 0;
     const int flags = BinaryFormat::getFlagsAndForwardPointer(root, &pos);
@@ -164,10 +169,16 @@
 }
 
 void BigramDictionary::fillBigramAddressToFrequencyMapAndFilter(const int32_t *prevWord,
-        const int prevWordLength, std::map<int, int> *map, uint8_t *filter) {
+        const int prevWordLength, std::map<int, int> *map, uint8_t *filter) const {
     memset(filter, 0, BIGRAM_FILTER_BYTE_SIZE);
     const uint8_t* const root = DICT;
-    int pos = getBigramListPositionForWord(prevWord, prevWordLength);
+    int pos = getBigramListPositionForWord(prevWord, prevWordLength,
+            false /* forceLowerCaseSearch */);
+    if (0 == pos) {
+        // If no bigrams for this exact string, search again in lower case.
+        pos = getBigramListPositionForWord(prevWord, prevWordLength,
+                true /* forceLowerCaseSearch */);
+    }
     if (0 == pos) return;
 
     int bigramFlags;
@@ -181,11 +192,10 @@
     } while (0 != (UnigramDictionary::FLAG_ATTRIBUTE_HAS_NEXT & bigramFlags));
 }
 
-bool BigramDictionary::checkFirstCharacter(unsigned short *word) {
+bool BigramDictionary::checkFirstCharacter(unsigned short *word, int *inputCodes) const {
     // Checks whether this word starts with same character or neighboring characters of
     // what user typed.
 
-    int *inputCodes = mInputCodes;
     int maxAlt = MAX_ALTERNATIVES;
     const unsigned short firstBaseChar = toBaseLowerCase(*word);
     while (maxAlt > 0) {
@@ -199,12 +209,13 @@
 }
 
 bool BigramDictionary::isValidBigram(const int32_t *word1, int length1, const int32_t *word2,
-        int length2) {
+        int length2) const {
     const uint8_t* const root = DICT;
-    int pos = getBigramListPositionForWord(word1, length1);
+    int pos = getBigramListPositionForWord(word1, length1, false /* forceLowerCaseSearch */);
     // getBigramListPositionForWord returns 0 if this word isn't in the dictionary or has no bigrams
     if (0 == pos) return false;
-    int nextWordPos = BinaryFormat::getTerminalPosition(root, word2, length2);
+    int nextWordPos = BinaryFormat::getTerminalPosition(root, word2, length2,
+            false /* forceLowerCaseSearch */);
     if (NOT_VALID_WORD == nextWordPos) return false;
     int bigramFlags;
     do {
diff --git a/native/jni/src/bigram_dictionary.h b/native/jni/src/bigram_dictionary.h
index b8763a5..5372276 100644
--- a/native/jni/src/bigram_dictionary.h
+++ b/native/jni/src/bigram_dictionary.h
@@ -27,34 +27,30 @@
 class Dictionary;
 class BigramDictionary {
  public:
-    BigramDictionary(const unsigned char *dict, int maxWordLength, Dictionary *parentDictionary);
-    int getBigrams(const int32_t *word, int length, int *codes, int codesSize,
-            unsigned short *outWords, int *frequencies, int maxWordLength, int maxBigrams);
-    int getBigramListPositionForWord(const int32_t *prevWord, const int prevWordLength);
+    BigramDictionary(const unsigned char *dict, int maxWordLength);
+    int getBigrams(const int32_t *word, int length, int *inputCodes, int codesSize,
+            unsigned short *outWords, int *frequencies, int maxWordLength, int maxBigrams) const;
+    int getBigramListPositionForWord(const int32_t *prevWord, const int prevWordLength,
+            const bool forceLowerCaseSearch) const;
     void fillBigramAddressToFrequencyMapAndFilter(const int32_t *prevWord, const int prevWordLength,
-            std::map<int, int> *map, uint8_t *filter);
-    bool isValidBigram(const int32_t *word1, int length1, const int32_t *word2, int length2);
+            std::map<int, int> *map, uint8_t *filter) const;
+    bool isValidBigram(const int32_t *word1, int length1, const int32_t *word2, int length2) const;
     ~BigramDictionary();
  private:
-    bool addWordBigram(unsigned short *word, int length, int frequency);
+    DISALLOW_IMPLICIT_CONSTRUCTORS(BigramDictionary);
+    bool addWordBigram(unsigned short *word, int length, int frequency, const int maxBigrams,
+            int *bigramFreq, unsigned short *bigramChars) const;
     int getBigramAddress(int *pos, bool advance);
     int getBigramFreq(int *pos);
     void searchForTerminalNode(int addressLookingFor, int frequency);
     bool getFirstBitOfByte(int *pos) { return (DICT[*pos] & 0x80) > 0; }
     bool getSecondBitOfByte(int *pos) { return (DICT[*pos] & 0x40) > 0; }
-    bool checkFirstCharacter(unsigned short *word);
+    bool checkFirstCharacter(unsigned short *word, int *inputCodes) const;
 
     const unsigned char *DICT;
     const int MAX_WORD_LENGTH;
     // TODO: Re-implement proximity correction for bigram correction
     static const int MAX_ALTERNATIVES = 1;
-
-    Dictionary *mParentDictionary;
-    int *mBigramFreq;
-    int mMaxBigrams;
-    unsigned short *mBigramChars;
-    int *mInputCodes;
-    int mInputLength;
 };
 
 } // namespace latinime
diff --git a/native/jni/src/binary_format.h b/native/jni/src/binary_format.h
index 51bf8eb..474c854 100644
--- a/native/jni/src/binary_format.h
+++ b/native/jni/src/binary_format.h
@@ -19,12 +19,14 @@
 
 #include <limits>
 #include "bloom_filter.h"
+#include "char_utils.h"
 #include "unigram_dictionary.h"
 
 namespace latinime {
 
 class BinaryFormat {
  private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(BinaryFormat);
     const static int32_t MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
     const static int32_t CHARACTER_ARRAY_TERMINATOR = 0x1F;
     const static int MULTIPLE_BYTE_CHARACTER_ADDITIONAL_SIZE = 2;
@@ -64,7 +66,7 @@
     static int getAttributeAddressAndForwardPointer(const uint8_t* const dict, const uint8_t flags,
             int *pos);
     static int getTerminalPosition(const uint8_t* const root, const int32_t* const inWord,
-            const int length);
+            const int length, const bool forceLowerCaseSearch);
     static int getWordAtAddress(const uint8_t* const root, const int address, const int maxDepth,
             uint16_t* outWord, int* outUnigramFrequency);
     static int computeFrequencyForBigram(const int unigramFreq, const int bigramFreq);
@@ -308,7 +310,7 @@
 // This function gets the byte position of the last chargroup of the exact matching word in the
 // dictionary. If no match is found, it returns NOT_VALID_WORD.
 inline int BinaryFormat::getTerminalPosition(const uint8_t* const root,
-        const int32_t* const inWord, const int length) {
+        const int32_t* const inWord, const int length, const bool forceLowerCaseSearch) {
     int pos = 0;
     int wordPos = 0;
 
@@ -317,7 +319,7 @@
         // there was no match (or we would have found it).
         if (wordPos > length) return NOT_VALID_WORD;
         int charGroupCount = BinaryFormat::getGroupCountAndForwardPointer(root, &pos);
-        const int32_t wChar = inWord[wordPos];
+        const int32_t wChar = forceLowerCaseSearch ? toLowerCase(inWord[wordPos]) : inWord[wordPos];
         while (true) {
             // If there are no more character groups in this node, it means we could not
             // find a matching character for this depth, therefore there is no match.
diff --git a/native/jni/src/char_utils.h b/native/jni/src/char_utils.h
index 607dc51..21dca9a 100644
--- a/native/jni/src/char_utils.h
+++ b/native/jni/src/char_utils.h
@@ -50,8 +50,7 @@
     return c;
 }
 
-inline static unsigned short toBaseLowerCase(unsigned short c) {
-    c = toBaseChar(c);
+inline static unsigned short toLowerCase(const unsigned short c) {
     if (isAsciiUpper(c)) {
         return toAsciiLower(c);
     } else if (isAscii(c)) {
@@ -60,6 +59,10 @@
     return latin_tolower(c);
 }
 
+inline static unsigned short toBaseLowerCase(const unsigned short c) {
+    return toLowerCase(toBaseChar(c));
+}
+
 } // namespace latinime
 
 #endif // LATINIME_CHAR_UTILS_H
diff --git a/native/jni/src/correction.cpp b/native/jni/src/correction.cpp
index 99f5b92..827067b 100644
--- a/native/jni/src/correction.cpp
+++ b/native/jni/src/correction.cpp
@@ -27,6 +27,7 @@
 #include "defines.h"
 #include "dictionary.h"
 #include "proximity_info.h"
+#include "proximity_info_state.h"
 
 namespace latinime {
 
@@ -97,7 +98,7 @@
 static const char QUOTE = '\'';
 
 inline bool Correction::isQuote(const unsigned short c) {
-    const unsigned short userTypedChar = mProximityInfo->getPrimaryCharAt(mInputIndex);
+    const unsigned short userTypedChar = mProximityInfoState.getPrimaryCharAt(mInputIndex);
     return (c == QUOTE && userTypedChar != QUOTE);
 }
 
@@ -105,11 +106,6 @@
 // Correction //
 ////////////////
 
-Correction::Correction(const int typedLetterMultiplier, const int fullWordMultiplier)
-        : TYPED_LETTER_MULTIPLIER(typedLetterMultiplier), FULL_WORD_MULTIPLIER(fullWordMultiplier) {
-    initEditDistance(mEditDistanceTable);
-}
-
 void Correction::resetCorrection() {
     mTotalTraverseCount = 0;
 }
@@ -282,7 +278,7 @@
 
 void Correction::addCharToCurrentWord(const int32_t c) {
     mWord[mOutputIndex] = c;
-    const unsigned short *primaryInputWord = mProximityInfo->getPrimaryInputWord();
+    const unsigned short *primaryInputWord = mProximityInfoState.getPrimaryInputWord();
     calcEditDistanceOneStep(mEditDistanceTable, primaryInputWord, mInputLength,
             mWord, mOutputIndex + 1);
 }
@@ -308,13 +304,12 @@
     return UNRELATED;
 }
 
-inline bool isEquivalentChar(ProximityInfo::ProximityType type) {
-    return type == ProximityInfo::EQUIVALENT_CHAR;
+inline bool isEquivalentChar(ProximityType type) {
+    return type == EQUIVALENT_CHAR;
 }
 
-inline bool isProximityCharOrEquivalentChar(ProximityInfo::ProximityType type) {
-    return type == ProximityInfo::EQUIVALENT_CHAR
-            || type == ProximityInfo::NEAR_PROXIMITY_CHAR;
+inline bool isProximityCharOrEquivalentChar(ProximityType type) {
+    return type == EQUIVALENT_CHAR || type == NEAR_PROXIMITY_CHAR;
 }
 
 Correction::CorrectionType Correction::processCharAndCalcState(
@@ -335,19 +330,19 @@
         bool incremented = false;
         if (mLastCharExceeded && mInputIndex == mInputLength - 1) {
             // TODO: Do not check the proximity if EditDistance exceeds the threshold
-            const ProximityInfo::ProximityType matchId =
-                    mProximityInfo->getMatchedProximityId(mInputIndex, c, true, &proximityIndex);
+            const ProximityType matchId = mProximityInfoState.getMatchedProximityId(
+                    mInputIndex, c, true, &proximityIndex);
             if (isEquivalentChar(matchId)) {
                 mLastCharExceeded = false;
                 --mExcessiveCount;
                 mDistances[mOutputIndex] =
-                        mProximityInfo->getNormalizedSquaredDistance(mInputIndex, 0);
-            } else if (matchId == ProximityInfo::NEAR_PROXIMITY_CHAR) {
+                        mProximityInfoState.getNormalizedSquaredDistance(mInputIndex, 0);
+            } else if (matchId == NEAR_PROXIMITY_CHAR) {
                 mLastCharExceeded = false;
                 --mExcessiveCount;
                 ++mProximityCount;
-                mDistances[mOutputIndex] =
-                        mProximityInfo->getNormalizedSquaredDistance(mInputIndex, proximityIndex);
+                mDistances[mOutputIndex] = mProximityInfoState.getNormalizedSquaredDistance(
+                        mInputIndex, proximityIndex);
             }
             if (!isQuote(c)) {
                 incrementInputIndex();
@@ -388,7 +383,8 @@
 
     bool secondTransposing = false;
     if (mTransposedCount % 2 == 1) {
-        if (isEquivalentChar(mProximityInfo->getMatchedProximityId(mInputIndex - 1, c, false))) {
+        if (isEquivalentChar(mProximityInfoState.getMatchedProximityId(
+                mInputIndex - 1, c, false))) {
             ++mTransposedCount;
             secondTransposing = true;
         } else if (mCorrectionStates[mOutputIndex].mExceeding) {
@@ -417,17 +413,17 @@
             ? (noCorrectionsHappenedSoFar || mProximityCount == 0)
             : (noCorrectionsHappenedSoFar && mProximityCount == 0);
 
-    ProximityInfo::ProximityType matchedProximityCharId = secondTransposing
-            ? ProximityInfo::EQUIVALENT_CHAR
-            : mProximityInfo->getMatchedProximityId(
+    ProximityType matchedProximityCharId = secondTransposing
+            ? EQUIVALENT_CHAR
+            : mProximityInfoState.getMatchedProximityId(
                     mInputIndex, c, checkProximityChars, &proximityIndex);
 
-    if (ProximityInfo::UNRELATED_CHAR == matchedProximityCharId
-            || ProximityInfo::ADDITIONAL_PROXIMITY_CHAR == matchedProximityCharId) {
+    if (UNRELATED_CHAR == matchedProximityCharId
+            || ADDITIONAL_PROXIMITY_CHAR == matchedProximityCharId) {
         if (canTryCorrection && mOutputIndex > 0
                 && mCorrectionStates[mOutputIndex].mProximityMatching
                 && mCorrectionStates[mOutputIndex].mExceeding
-                && isEquivalentChar(mProximityInfo->getMatchedProximityId(
+                && isEquivalentChar(mProximityInfoState.getMatchedProximityId(
                         mInputIndex, mWord[mOutputIndex - 1], false))) {
             if (DEBUG_CORRECTION
                     && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
@@ -446,14 +442,14 @@
             // Here, we are doing something equivalent to matchedProximityCharId,
             // but we already know that "excessive char correction" just happened
             // so that we just need to check "mProximityCount == 0".
-            matchedProximityCharId = mProximityInfo->getMatchedProximityId(
+            matchedProximityCharId = mProximityInfoState.getMatchedProximityId(
                     mInputIndex, c, mProximityCount == 0, &proximityIndex);
         }
     }
 
-    if (ProximityInfo::UNRELATED_CHAR == matchedProximityCharId
-            || ProximityInfo::ADDITIONAL_PROXIMITY_CHAR == matchedProximityCharId) {
-        if (ProximityInfo::ADDITIONAL_PROXIMITY_CHAR == matchedProximityCharId) {
+    if (UNRELATED_CHAR == matchedProximityCharId
+            || ADDITIONAL_PROXIMITY_CHAR == matchedProximityCharId) {
+        if (ADDITIONAL_PROXIMITY_CHAR == matchedProximityCharId) {
             mAdditionalProximityMatching = true;
         }
         // TODO: Optimize
@@ -463,10 +459,10 @@
         if (mInputIndex < mInputLength - 1 && mOutputIndex > 0 && mTransposedCount > 0
                 && !mCorrectionStates[mOutputIndex].mTransposing
                 && mCorrectionStates[mOutputIndex - 1].mTransposing
-                && isEquivalentChar(mProximityInfo->getMatchedProximityId(
+                && isEquivalentChar(mProximityInfoState.getMatchedProximityId(
                         mInputIndex, mWord[mOutputIndex - 1], false))
                 && isEquivalentChar(
-                        mProximityInfo->getMatchedProximityId(mInputIndex + 1, c, false))) {
+                        mProximityInfoState.getMatchedProximityId(mInputIndex + 1, c, false))) {
             // Conversion t->e
             // Example:
             // occaisional -> occa   sional
@@ -478,7 +474,7 @@
                 && !mCorrectionStates[mOutputIndex].mTransposing
                 && mCorrectionStates[mOutputIndex - 1].mTransposing
                 && isEquivalentChar(
-                        mProximityInfo->getMatchedProximityId(mInputIndex - 1, c, false))) {
+                        mProximityInfoState.getMatchedProximityId(mInputIndex - 1, c, false))) {
             // Conversion t->s
             // Example:
             // chcolate -> chocolate
@@ -490,7 +486,7 @@
                 && mCorrectionStates[mOutputIndex].mProximityMatching
                 && mCorrectionStates[mOutputIndex].mSkipping
                 && isEquivalentChar(
-                        mProximityInfo->getMatchedProximityId(mInputIndex - 1, c, false))) {
+                        mProximityInfoState.getMatchedProximityId(mInputIndex - 1, c, false))) {
             // Conversion p->s
             // Note: This logic tries saving cases like contrst --> contrast -- "a" is one of
             // proximity chars of "s", but it should rather be handled as a skipped char.
@@ -502,7 +498,7 @@
                 && mCorrectionStates[mOutputIndex].mSkipping
                 && mCorrectionStates[mOutputIndex].mAdditionalProximityMatching
                 && isProximityCharOrEquivalentChar(
-                        mProximityInfo->getMatchedProximityId(mInputIndex + 1, c, false))) {
+                        mProximityInfoState.getMatchedProximityId(mInputIndex + 1, c, false))) {
             // Conversion s->a
             incrementInputIndex();
             --mSkippedCount;
@@ -511,7 +507,7 @@
             mDistances[mOutputIndex] = ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO;
         } else if ((mExceeding || mTransposing) && mInputIndex - 1 < mInputLength
                 && isEquivalentChar(
-                        mProximityInfo->getMatchedProximityId(mInputIndex + 1, c, false))) {
+                        mProximityInfoState.getMatchedProximityId(mInputIndex + 1, c, false))) {
             // 1.2. Excessive or transpose correction
             if (mTransposing) {
                 ++mTransposedCount;
@@ -543,7 +539,7 @@
                         mTransposedCount, mExcessiveCount, c);
             }
             return processSkipChar(c, isTerminal, false);
-        } else if (ProximityInfo::ADDITIONAL_PROXIMITY_CHAR == matchedProximityCharId) {
+        } else if (ADDITIONAL_PROXIMITY_CHAR == matchedProximityCharId) {
             // As a last resort, use additional proximity characters
             mProximityMatching = true;
             ++mProximityCount;
@@ -573,12 +569,12 @@
     } else if (isEquivalentChar(matchedProximityCharId)) {
         mMatching = true;
         ++mEquivalentCharCount;
-        mDistances[mOutputIndex] = mProximityInfo->getNormalizedSquaredDistance(mInputIndex, 0);
-    } else if (ProximityInfo::NEAR_PROXIMITY_CHAR == matchedProximityCharId) {
+        mDistances[mOutputIndex] = mProximityInfoState.getNormalizedSquaredDistance(mInputIndex, 0);
+    } else if (NEAR_PROXIMITY_CHAR == matchedProximityCharId) {
         mProximityMatching = true;
         ++mProximityCount;
         mDistances[mOutputIndex] =
-                mProximityInfo->getNormalizedSquaredDistance(mInputIndex, proximityIndex);
+                mProximityInfoState.getNormalizedSquaredDistance(mInputIndex, proximityIndex);
         if (DEBUG_CORRECTION
                 && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
                 && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
@@ -662,7 +658,7 @@
     const int excessivePos = correction->getExcessivePos();
     const int typedLetterMultiplier = correction->TYPED_LETTER_MULTIPLIER;
     const int fullWordMultiplier = correction->FULL_WORD_MULTIPLIER;
-    const ProximityInfo *proximityInfo = correction->mProximityInfo;
+    const ProximityInfoState *proximityInfoState = &correction->mProximityInfoState;
     const int skippedCount = correction->mSkippedCount;
     const int transposedCount = correction->mTransposedCount / 2;
     const int excessiveCount = correction->mExcessiveCount + correction->mTransposedCount % 2;
@@ -685,7 +681,7 @@
     const bool skipped = skippedCount > 0;
 
     const int quoteDiffCount = max(0, getQuoteCount(word, outputLength)
-            - getQuoteCount(proximityInfo->getPrimaryInputWord(), inputLength));
+            - getQuoteCount(proximityInfoState->getPrimaryInputWord(), inputLength));
 
     // TODO: Calculate edit distance for transposed and excessive
     int ed = 0;
@@ -737,8 +733,7 @@
         multiplyIntCapped(matchWeight, &finalFreq);
     }
 
-    if (proximityInfo->getMatchedProximityId(0, word[0], true)
-            == ProximityInfo::UNRELATED_CHAR) {
+    if (proximityInfoState->getMatchedProximityId(0, word[0], true) == UNRELATED_CHAR) {
         multiplyRate(FIRST_CHAR_DIFFERENT_DEMOTION_RATE, &finalFreq);
     }
 
@@ -764,7 +759,7 @@
     // Demotion for a word with excessive character
     if (excessiveCount > 0) {
         multiplyRate(WORDS_WITH_EXCESSIVE_CHARACTER_DEMOTION_RATE, &finalFreq);
-        if (!lastCharExceeded && !proximityInfo->existsAdjacentProximityChars(excessivePos)) {
+        if (!lastCharExceeded && !proximityInfoState->existsAdjacentProximityChars(excessivePos)) {
             if (DEBUG_DICT_FULL) {
                 AKLOGI("Double excessive demotion");
             }
@@ -775,8 +770,9 @@
     }
 
     const bool performTouchPositionCorrection =
-            CALIBRATE_SCORE_BY_TOUCH_COORDINATES && proximityInfo->touchPositionCorrectionEnabled()
-                        && skippedCount == 0 && excessiveCount == 0 && transposedCount == 0;
+            CALIBRATE_SCORE_BY_TOUCH_COORDINATES
+                    && proximityInfoState->touchPositionCorrectionEnabled()
+                    && skippedCount == 0 && excessiveCount == 0 && transposedCount == 0;
     // Score calibration by touch coordinates is being done only for pure-fat finger typing error
     // cases.
     int additionalProximityCount = 0;
@@ -796,7 +792,7 @@
                 static const float R1 = NEUTRAL_SCORE_SQUARED_RADIUS;
                 static const float R2 = HALF_SCORE_SQUARED_RADIUS;
                 const float x = (float)squaredDistance
-                        / ProximityInfo::NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR;
+                        / ProximityInfoState::NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR;
                 const float factor = max((x < R1)
                     ? (A * (R1 - x) + B * x) / R1
                     : (B * (R2 - x) + C * (x - R1)) / (R2 - R1), MIN);
@@ -907,7 +903,7 @@
 
     if (DEBUG_CORRECTION_FREQ
             && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == inputLength)) {
-        DUMP_WORD(proximityInfo->getPrimaryInputWord(), inputLength);
+        DUMP_WORD(correction->getPrimaryInputWord(), inputLength);
         DUMP_WORD(correction->mWord, outputLength);
         AKLOGI("FinalFreq: [P%d, S%d, T%d, E%d, A%d] %d, %d, %d, %d, %d, %d", proximityMatchedCount,
                 skippedCount, transposedCount, excessiveCount, additionalProximityCount,
@@ -1146,5 +1142,4 @@
     const float weight = 1.0 - (float) distance / afterLength;
     return (score / maxScore) * weight;
 }
-
 } // namespace latinime
diff --git a/native/jni/src/correction.h b/native/jni/src/correction.h
index 3300a84..ae7b3a5 100644
--- a/native/jni/src/correction.h
+++ b/native/jni/src/correction.h
@@ -19,9 +19,10 @@
 
 #include <assert.h>
 #include <stdint.h>
-#include "correction_state.h"
 
+#include "correction_state.h"
 #include "defines.h"
+#include "proximity_info_state.h"
 
 namespace latinime {
 
@@ -93,7 +94,7 @@
         }
     }
 
-    Correction(const int typedLetterMultiplier, const int fullWordMultiplier);
+    Correction() {};
     void resetCorrection();
     void initCorrection(
             const ProximityInfo *pi, const int inputLength, const int maxWordLength);
@@ -174,11 +175,25 @@
      private:
         static const int CODE_SPACE = ' ';
         static const int MAX_INITIAL_SCORE = 255;
-        static const int TYPED_LETTER_MULTIPLIER = 2;
-        static const int FULL_WORD_MULTIPLIER = 2;
     };
 
+    // proximity info state
+    void initInputParams(const ProximityInfo *proximityInfo, const int32_t *inputCodes,
+            const int inputLength, const int *xCoordinates, const int *yCoordinates) {
+        mProximityInfoState.initInputParams(
+                proximityInfo, inputCodes, inputLength, xCoordinates, yCoordinates);
+    }
+
+    const unsigned short* getPrimaryInputWord() const {
+        return mProximityInfoState.getPrimaryInputWord();
+    }
+
+    unsigned short getPrimaryCharAt(const int index) const {
+        return mProximityInfoState.getPrimaryCharAt(index);
+    }
+
  private:
+    DISALLOW_COPY_AND_ASSIGN(Correction);
     inline void incrementInputIndex();
     inline void incrementOutputIndex();
     inline void startToTraverseAllNodes();
@@ -190,8 +205,8 @@
     inline int getFinalProbabilityInternal(const int probability, unsigned short **word,
             int* wordLength, const int inputLength);
 
-    const int TYPED_LETTER_MULTIPLIER;
-    const int FULL_WORD_MULTIPLIER;
+    static const int TYPED_LETTER_MULTIPLIER = 2;
+    static const int FULL_WORD_MULTIPLIER = 2;
     const ProximityInfo *mProximityInfo;
 
     bool mUseFullEditDistance;
@@ -240,7 +255,7 @@
     bool mExceeding;
     bool mTransposing;
     bool mSkipping;
-
+    ProximityInfoState mProximityInfoState;
 };
 } // namespace latinime
 #endif // LATINIME_CORRECTION_H
diff --git a/native/jni/src/defines.h b/native/jni/src/defines.h
index cd2fc63..c7d3bf3 100644
--- a/native/jni/src/defines.h
+++ b/native/jni/src/defines.h
@@ -225,6 +225,9 @@
 // This is only used for the size of array. Not to be used in c functions.
 #define MAX_WORD_LENGTH_INTERNAL 48
 
+// This must be the same as ProximityInfo#MAX_PROXIMITY_CHARS_SIZE, currently it's 16.
+#define MAX_PROXIMITY_CHARS_SIZE_INTERNAL 16
+
 // This must be equal to ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE in KeyDetector.java
 #define ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE 2
 
@@ -252,6 +255,8 @@
 
 #define FIRST_WORD_INDEX 0
 
+#define MAX_SPACES_INTERNAL 16
+
 // TODO: Reduce this constant if possible; check the maximum number of digraphs in the same
 // word in the dictionary for languages with digraphs, like German and French
 #define DEFAULT_MAX_DIGRAPH_SEARCH_DEPTH 5
@@ -289,4 +294,24 @@
 #define INPUTLENGTH_FOR_DEBUG -1
 #define MIN_OUTPUT_INDEX_FOR_DEBUG -1
 
+#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
+  TypeName(const TypeName&);               \
+  void operator=(const TypeName&)
+
+#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \
+  TypeName();                                    \
+  DISALLOW_COPY_AND_ASSIGN(TypeName)
+
+// Used as a return value for character comparison
+typedef enum {
+    // Same char, possibly with different case or accent
+    EQUIVALENT_CHAR,
+    // It is a char located nearby on the keyboard
+    NEAR_PROXIMITY_CHAR,
+    // It is an unrelated char
+    UNRELATED_CHAR,
+    // Additional proximity char which can differ by language.
+    ADDITIONAL_PROXIMITY_CHAR
+} ProximityType;
+
 #endif // LATINIME_DEFINES_H
diff --git a/native/jni/src/dictionary.cpp b/native/jni/src/dictionary.cpp
index 1fb0247..628a169 100644
--- a/native/jni/src/dictionary.cpp
+++ b/native/jni/src/dictionary.cpp
@@ -22,6 +22,7 @@
 #include "binary_format.h"
 #include "defines.h"
 #include "dictionary.h"
+#include "gesture_decoder_wrapper.h"
 
 namespace latinime {
 
@@ -38,29 +39,28 @@
             AKLOGI("IN NATIVE SUGGEST Version: %d", (mDict[0] & 0xFF));
         }
     }
-    mCorrection = new Correction(typedLetterMultiplier, fullWordMultiplier);
-    mWordsPriorityQueuePool = new WordsPriorityQueuePool(
-            maxWords, SUB_QUEUE_MAX_WORDS, maxWordLength);
     const unsigned int headerSize = BinaryFormat::getHeaderSize(mDict);
     const unsigned int options = BinaryFormat::getFlags(mDict);
     mUnigramDictionary = new UnigramDictionary(mDict + headerSize, typedLetterMultiplier,
             fullWordMultiplier, maxWordLength, maxWords, options);
-    mBigramDictionary = new BigramDictionary(mDict + headerSize, maxWordLength, this);
+    mBigramDictionary = new BigramDictionary(mDict + headerSize, maxWordLength);
+    mGestureDecoder = new GestureDecoderWrapper(maxWordLength, maxWords);
+    mGestureDecoder->setDict(mUnigramDictionary, mBigramDictionary,
+            mDict + headerSize /* dict root */, 0 /* root pos */);
 }
 
 Dictionary::~Dictionary() {
-    delete mCorrection;
-    delete mWordsPriorityQueuePool;
     delete mUnigramDictionary;
     delete mBigramDictionary;
+    delete mGestureDecoder;
 }
 
-int Dictionary::getFrequency(const int32_t *word, int length) {
+int Dictionary::getFrequency(const int32_t *word, int length) const {
     return mUnigramDictionary->getFrequency(word, length);
 }
 
 bool Dictionary::isValidBigram(const int32_t *word1, int length1, const int32_t *word2,
-        int length2) {
+        int length2) const {
     return mBigramDictionary->isValidBigram(word1, length1, word2, length2);
 }
 
diff --git a/native/jni/src/dictionary.h b/native/jni/src/dictionary.h
index 9f23679..431f103 100644
--- a/native/jni/src/dictionary.h
+++ b/native/jni/src/dictionary.h
@@ -21,8 +21,8 @@
 
 #include "bigram_dictionary.h"
 #include "char_utils.h"
-#include "correction.h"
 #include "defines.h"
+#include "incremental_decoder_interface.h"
 #include "proximity_info.h"
 #include "unigram_dictionary.h"
 #include "words_priority_queue_pool.h"
@@ -35,29 +35,41 @@
             int fullWordMultiplier, int maxWordLength, int maxWords);
 
     int getSuggestions(ProximityInfo *proximityInfo, int *xcoordinates, int *ycoordinates,
-            int *codes, int codesSize, const int32_t* prevWordChars, const int prevWordLength,
-            bool useFullEditDistance, unsigned short *outWords, int *frequencies) {
-        std::map<int, int> bigramMap;
-        uint8_t bigramFilter[BIGRAM_FILTER_BYTE_SIZE];
-        mBigramDictionary->fillBigramAddressToFrequencyMapAndFilter(prevWordChars,
-                prevWordLength, &bigramMap, bigramFilter);
-        return mUnigramDictionary->getSuggestions(proximityInfo, mWordsPriorityQueuePool,
-                mCorrection, xcoordinates, ycoordinates, codes, codesSize, &bigramMap,
-                bigramFilter, useFullEditDistance, outWords, frequencies);
+            int *times, int *pointerIds, int *codes, int codesSize, int *prevWordChars,
+            int prevWordLength, int commitPoint, bool isGesture,
+            bool useFullEditDistance, unsigned short *outWords,
+            int *frequencies, int *spaceIndices) {
+        int result = 0;
+        if (isGesture) {
+            mGestureDecoder->setPrevWord(prevWordChars, prevWordLength);
+            result = mGestureDecoder->getSuggestions(proximityInfo, xcoordinates, ycoordinates,
+                    times, pointerIds, codes, codesSize, commitPoint,
+                    outWords, frequencies, spaceIndices);
+            return result;
+        } else {
+            std::map<int, int> bigramMap;
+            uint8_t bigramFilter[BIGRAM_FILTER_BYTE_SIZE];
+            mBigramDictionary->fillBigramAddressToFrequencyMapAndFilter(prevWordChars,
+                    prevWordLength, &bigramMap, bigramFilter);
+            result = mUnigramDictionary->getSuggestions(proximityInfo, xcoordinates,
+                    ycoordinates, codes, codesSize, &bigramMap, bigramFilter,
+                    useFullEditDistance, outWords, frequencies);
+            return result;
+        }
     }
 
     int getBigrams(const int32_t *word, int length, int *codes, int codesSize,
-            unsigned short *outWords, int *frequencies, int maxWordLength, int maxBigrams) {
+            unsigned short *outWords, int *frequencies, int maxWordLength, int maxBigrams) const {
         return mBigramDictionary->getBigrams(word, length, codes, codesSize, outWords, frequencies,
                 maxWordLength, maxBigrams);
     }
 
-    int getFrequency(const int32_t *word, int length);
-    bool isValidBigram(const int32_t *word1, int length1, const int32_t *word2, int length2);
-    void *getDict() { return (void *)mDict; }
-    int getDictSize() { return mDictSize; }
-    int getMmapFd() { return mMmapFd; }
-    int getDictBufAdjust() { return mDictBufAdjust; }
+    int getFrequency(const int32_t *word, int length) const;
+    bool isValidBigram(const int32_t *word1, int length1, const int32_t *word2, int length2) const;
+    void *getDict() const { return (void *)mDict; }
+    int getDictSize() const { return mDictSize; }
+    int getMmapFd() const { return mMmapFd; }
+    int getDictBufAdjust() const { return mDictBufAdjust; }
     ~Dictionary();
 
     // public static utility methods
@@ -65,6 +77,7 @@
     static int wideStrLen(unsigned short *str);
 
  private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(Dictionary);
     const unsigned char *mDict;
 
     // Used only for the mmap version of dictionary loading, but we use these as dummy variables
@@ -73,10 +86,9 @@
     const int mMmapFd;
     const int mDictBufAdjust;
 
-    UnigramDictionary *mUnigramDictionary;
-    BigramDictionary *mBigramDictionary;
-    WordsPriorityQueuePool *mWordsPriorityQueuePool;
-    Correction *mCorrection;
+    const UnigramDictionary *mUnigramDictionary;
+    const BigramDictionary *mBigramDictionary;
+    IncrementalDecoderInterface *mGestureDecoder;
 };
 
 // public static utility methods
diff --git a/native/jni/src/gesture/gesture_decoder_wrapper.cpp b/native/jni/src/gesture/gesture_decoder_wrapper.cpp
new file mode 100644
index 0000000..afbe0c5
--- /dev/null
+++ b/native/jni/src/gesture/gesture_decoder_wrapper.cpp
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#include "gesture_decoder_wrapper.h"
+
+namespace latinime {
+    IncrementalDecoderInterface *
+            (*GestureDecoderWrapper::sGestureDecoderFactoryMethod)(int, int) = 0;
+} // namespace latinime
diff --git a/native/jni/src/gesture/gesture_decoder_wrapper.h b/native/jni/src/gesture/gesture_decoder_wrapper.h
new file mode 100644
index 0000000..35982f0
--- /dev/null
+++ b/native/jni/src/gesture/gesture_decoder_wrapper.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#ifndef LATINIME_GESTURE_DECODER_WRAPPER_H
+#define LATINIME_GESTURE_DECODER_WRAPPER_H
+
+#include <stdint.h>
+#include "defines.h"
+#include "incremental_decoder_interface.h"
+
+namespace latinime {
+
+class UnigramDictionary;
+class BigramDictionary;
+class ProximityInfo;
+
+class GestureDecoderWrapper : public IncrementalDecoderInterface {
+ public:
+    GestureDecoderWrapper(const int maxWordLength, const int maxWords) {
+        mIncrementalDecoderInterface = getGestureDecoderInstance(maxWordLength, maxWords);
+    }
+
+    virtual ~GestureDecoderWrapper() {
+        delete mIncrementalDecoderInterface;
+    }
+
+    int getSuggestions(ProximityInfo *pInfo, int *inputXs, int *inputYs, int *times,
+            int *pointerIds, int *codes, int inputSize, int commitPoint,
+            unsigned short *outWords, int *frequencies, int *outputIndices) {
+        if (!mIncrementalDecoderInterface) {
+            return 0;
+        }
+        return mIncrementalDecoderInterface->getSuggestions(
+                pInfo, inputXs, inputYs, times, pointerIds, codes, inputSize, commitPoint,
+                outWords, frequencies, outputIndices);
+    }
+
+    void reset() {
+        if (!mIncrementalDecoderInterface) {
+            return;
+        }
+        mIncrementalDecoderInterface->reset();
+    }
+
+    void setDict(const UnigramDictionary *dict, const BigramDictionary *bigram,
+            const uint8_t *dictRoot, int rootPos) {
+        if (!mIncrementalDecoderInterface) {
+            return;
+        }
+        mIncrementalDecoderInterface->setDict(dict, bigram, dictRoot, rootPos);
+    }
+
+    void setPrevWord(const int32_t *prevWord, int prevWordLength) {
+        if (!mIncrementalDecoderInterface) {
+            return;
+        }
+        mIncrementalDecoderInterface->setPrevWord(prevWord, prevWordLength);
+    }
+
+    static void setGestureDecoderFactoryMethod(
+            IncrementalDecoderInterface *(*factoryMethod)(int, int)) {
+        sGestureDecoderFactoryMethod = factoryMethod;
+    }
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(GestureDecoderWrapper);
+    static IncrementalDecoderInterface *getGestureDecoderInstance(int maxWordLength, int maxWords) {
+        if (sGestureDecoderFactoryMethod) {
+            return sGestureDecoderFactoryMethod(maxWordLength, maxWords);
+        }
+        return 0;
+    }
+
+    static IncrementalDecoderInterface *(*sGestureDecoderFactoryMethod)(int, int);
+    IncrementalDecoderInterface *mIncrementalDecoderInterface;
+};
+} // namespace latinime
+#endif // LATINIME_GESTURE_DECODER_WRAPPER_H
diff --git a/native/jni/src/gesture/incremental_decoder_interface.h b/native/jni/src/gesture/incremental_decoder_interface.h
new file mode 100644
index 0000000..957f1eb
--- /dev/null
+++ b/native/jni/src/gesture/incremental_decoder_interface.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#ifndef LATINIME_INCREMENTAL_DECODER_INTERFACE_H
+#define LATINIME_INCREMENTAL_DECODER_INTERFACE_H
+
+#include <stdint.h>
+#include "defines.h"
+
+namespace latinime {
+
+class UnigramDictionary;
+class BigramDictionary;
+class ProximityInfo;
+
+class IncrementalDecoderInterface {
+ public:
+    virtual int getSuggestions(ProximityInfo *pInfo, int *inputXs, int *inputYs, int *times,
+            int *pointerIds, int *codes, int inputSize, int commitPoint,
+            unsigned short *outWords, int *frequencies, int *outputIndices) = 0;
+    virtual void reset() = 0;
+    virtual void setDict(const UnigramDictionary *dict, const BigramDictionary *bigram,
+            const uint8_t *dictRoot, int rootPos) = 0;
+    virtual void setPrevWord(const int32_t *prevWord, int prevWordLength) = 0;
+    virtual ~IncrementalDecoderInterface() { };
+};
+} // namespace latinime
+#endif // LATINIME_INCREMENTAL_DECODER_INTERFACE_H
diff --git a/native/jni/src/proximity_info.cpp b/native/jni/src/proximity_info.cpp
index 960d401..a4a6411 100644
--- a/native/jni/src/proximity_info.cpp
+++ b/native/jni/src/proximity_info.cpp
@@ -15,6 +15,7 @@
  */
 
 #include <assert.h>
+#include <math.h>
 #include <stdio.h>
 #include <string>
 
@@ -24,6 +25,7 @@
 #include "defines.h"
 #include "dictionary.h"
 #include "proximity_info.h"
+#include "proximity_info_state.h"
 
 namespace latinime {
 
@@ -51,23 +53,14 @@
           HAS_TOUCH_POSITION_CORRECTION_DATA(keyCount > 0 && keyXCoordinates && keyYCoordinates
                   && keyWidths && keyHeights && keyCharCodes && sweetSpotCenterXs
                   && sweetSpotCenterYs && sweetSpotRadii),
-          mLocaleStr(localeStr),
-          mInputXCoordinates(0), mInputYCoordinates(0),
-          mTouchPositionCorrectionEnabled(false) {
+          mLocaleStr(localeStr) {
     const int proximityGridLength = GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE;
-    mProximityCharsArray = new int32_t[proximityGridLength];
-    mInputCodes = new int32_t[MAX_PROXIMITY_CHARS_SIZE * MAX_WORD_LENGTH_INTERNAL];
     if (DEBUG_PROXIMITY_INFO) {
         AKLOGI("Create proximity info array %d", proximityGridLength);
     }
+    mProximityCharsArray = new int32_t[proximityGridLength];
     memcpy(mProximityCharsArray, proximityCharsArray,
             proximityGridLength * sizeof(mProximityCharsArray[0]));
-    const int normalizedSquaredDistancesLength =
-            MAX_PROXIMITY_CHARS_SIZE * MAX_WORD_LENGTH_INTERNAL;
-    mNormalizedSquaredDistances = new int[normalizedSquaredDistancesLength];
-    for (int i = 0; i < normalizedSquaredDistancesLength; ++i) {
-        mNormalizedSquaredDistances[i] = NOT_A_DISTANCE;
-    }
 
     copyOrFillZero(mKeyXCoordinates, keyXCoordinates, KEY_COUNT * sizeof(mKeyXCoordinates[0]));
     copyOrFillZero(mKeyYCoordinates, keyYCoordinates, KEY_COUNT * sizeof(mKeyYCoordinates[0]));
@@ -96,9 +89,7 @@
 }
 
 ProximityInfo::~ProximityInfo() {
-    delete[] mNormalizedSquaredDistances;
     delete[] mProximityCharsArray;
-    delete[] mInputCodes;
 }
 
 inline int ProximityInfo::getStartIndexFromCoordinates(const int x, const int y) const {
@@ -119,26 +110,18 @@
     if (DEBUG_PROXIMITY_INFO) {
         AKLOGI("hasSpaceProximity: index %d, %d, %d", startIndex, x, y);
     }
+    int32_t* proximityCharsArray = mProximityCharsArray;
     for (int i = 0; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
         if (DEBUG_PROXIMITY_INFO) {
             AKLOGI("Index: %d", mProximityCharsArray[startIndex + i]);
         }
-        if (mProximityCharsArray[startIndex + i] == KEYCODE_SPACE) {
+        if (proximityCharsArray[startIndex + i] == KEYCODE_SPACE) {
             return true;
         }
     }
     return false;
 }
 
-bool ProximityInfo::isOnKey(const int keyId, const int x, const int y) const {
-    if (keyId < 0) return true; // NOT_A_ID is -1, but return whenever < 0 just in case
-    const int left = mKeyXCoordinates[keyId];
-    const int top = mKeyYCoordinates[keyId];
-    const int right = left + mKeyWidths[keyId] + 1;
-    const int bottom = top + mKeyHeights[keyId];
-    return left < right && top < bottom && x >= left && x < right && y >= top && y < bottom;
-}
-
 int ProximityInfo::squaredDistanceToEdge(const int keyId, const int x, const int y) const {
     if (keyId < 0) return true; // NOT_A_ID is -1, but return whenever < 0 just in case
     const int left = mKeyXCoordinates[keyId];
@@ -154,12 +137,13 @@
 
 void ProximityInfo::calculateNearbyKeyCodes(
         const int x, const int y, const int32_t primaryKey, int *inputCodes) const {
+    int32_t *proximityCharsArray = mProximityCharsArray;
     int insertPos = 0;
     inputCodes[insertPos++] = primaryKey;
     const int startIndex = getStartIndexFromCoordinates(x, y);
     if (startIndex >= 0) {
         for (int i = 0; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
-            const int32_t c = mProximityCharsArray[startIndex + i];
+            const int32_t c = proximityCharsArray[startIndex + i];
             if (c < KEYCODE_SPACE || c == primaryKey) {
                 continue;
             }
@@ -216,115 +200,6 @@
     }
 }
 
-void ProximityInfo::setInputParams(const int32_t* inputCodes, const int inputLength,
-        const int* xCoordinates, const int* yCoordinates) {
-    memset(mInputCodes, 0,
-            MAX_WORD_LENGTH_INTERNAL * MAX_PROXIMITY_CHARS_SIZE * sizeof(mInputCodes[0]));
-
-    for (int i = 0; i < inputLength; ++i) {
-        const int32_t primaryKey = inputCodes[i];
-        const int x = xCoordinates[i];
-        const int y = yCoordinates[i];
-        int *proximities = &mInputCodes[i * MAX_PROXIMITY_CHARS_SIZE];
-        calculateNearbyKeyCodes(x, y, primaryKey, proximities);
-    }
-
-    if (DEBUG_PROXIMITY_CHARS) {
-        for (int i = 0; i < inputLength; ++i) {
-            AKLOGI("---");
-            for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE; ++j) {
-                int icc = mInputCodes[i * MAX_PROXIMITY_CHARS_SIZE + j];
-                int icfjc = inputCodes[i * MAX_PROXIMITY_CHARS_SIZE + j];
-                icc+= 0;
-                icfjc += 0;
-                AKLOGI("--- (%d)%c,%c", i, icc, icfjc);
-                AKLOGI("---             A<%d>,B<%d>", icc, icfjc);
-            }
-        }
-    }
-    //Keep for debug, sorry
-    //for (int i = 0; i < MAX_WORD_LENGTH_INTERNAL * MAX_PROXIMITY_CHARS_SIZE; ++i) {
-    //if (i < inputLength * MAX_PROXIMITY_CHARS_SIZE) {
-    //mInputCodes[i] = mInputCodesFromJava[i];
-    //} else {
-    // mInputCodes[i] = 0;
-    // }
-    //}
-    mInputXCoordinates = xCoordinates;
-    mInputYCoordinates = yCoordinates;
-    mTouchPositionCorrectionEnabled =
-            HAS_TOUCH_POSITION_CORRECTION_DATA && xCoordinates && yCoordinates;
-    mInputLength = inputLength;
-    for (int i = 0; i < inputLength; ++i) {
-        mPrimaryInputWord[i] = getPrimaryCharAt(i);
-    }
-    mPrimaryInputWord[inputLength] = 0;
-    if (DEBUG_PROXIMITY_CHARS) {
-        AKLOGI("--- setInputParams");
-    }
-    for (int i = 0; i < mInputLength; ++i) {
-        const int *proximityChars = getProximityCharsAt(i);
-        const int primaryKey = proximityChars[0];
-        const int x = xCoordinates[i];
-        const int y = yCoordinates[i];
-        if (DEBUG_PROXIMITY_CHARS) {
-            int a = x + y + primaryKey;
-            a += 0;
-            AKLOGI("--- Primary = %c, x = %d, y = %d", primaryKey, x, y);
-            // Keep debug code just in case
-            //int proximities[50];
-            //for (int m = 0; m < 50; ++m) {
-            //proximities[m] = 0;
-            //}
-            //calculateNearbyKeyCodes(x, y, primaryKey, proximities);
-            //for (int l = 0; l < 50 && proximities[l] > 0; ++l) {
-            //if (DEBUG_PROXIMITY_CHARS) {
-            //AKLOGI("--- native Proximity (%d) = %c", l, proximities[l]);
-            //}
-            //}
-        }
-        for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE && proximityChars[j] > 0; ++j) {
-            const int currentChar = proximityChars[j];
-            const float squaredDistance = hasInputCoordinates()
-                    ? calculateNormalizedSquaredDistance(getKeyIndex(currentChar), i)
-                    : NOT_A_DISTANCE_FLOAT;
-            if (squaredDistance >= 0.0f) {
-                mNormalizedSquaredDistances[i * MAX_PROXIMITY_CHARS_SIZE + j] =
-                        (int)(squaredDistance * NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR);
-            } else {
-                mNormalizedSquaredDistances[i * MAX_PROXIMITY_CHARS_SIZE + j] = (j == 0)
-                        ? EQUIVALENT_CHAR_WITHOUT_DISTANCE_INFO
-                        : PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO;
-            }
-            if (DEBUG_PROXIMITY_CHARS) {
-                AKLOGI("--- Proximity (%d) = %c", j, currentChar);
-            }
-        }
-    }
-}
-
-inline float square(const float x) { return x * x; }
-
-float ProximityInfo::calculateNormalizedSquaredDistance(
-        const int keyIndex, const int inputIndex) const {
-    if (keyIndex == NOT_AN_INDEX) {
-        return NOT_A_DISTANCE_FLOAT;
-    }
-    if (!hasSweetSpotData(keyIndex)) {
-        return NOT_A_DISTANCE_FLOAT;
-    }
-    if (NOT_A_COORDINATE == mInputXCoordinates[inputIndex]) {
-        return NOT_A_DISTANCE_FLOAT;
-    }
-    const float squaredDistance = calculateSquaredDistanceFromSweetSpotCenter(keyIndex, inputIndex);
-    const float squaredRadius = square(mSweetSpotRadii[keyIndex]);
-    return squaredDistance / squaredRadius;
-}
-
-bool ProximityInfo::hasInputCoordinates() const {
-    return mInputXCoordinates && mInputYCoordinates;
-}
-
 int ProximityInfo::getKeyIndex(const int c) const {
     if (KEY_COUNT == 0) {
         // We do not have the coordinate data
@@ -337,131 +212,24 @@
     return mCodeToKeyIndex[baseLowerC];
 }
 
-float ProximityInfo::calculateSquaredDistanceFromSweetSpotCenter(
-        const int keyIndex, const int inputIndex) const {
-    const float sweetSpotCenterX = mSweetSpotCenterXs[keyIndex];
-    const float sweetSpotCenterY = mSweetSpotCenterYs[keyIndex];
-    const float inputX = (float)mInputXCoordinates[inputIndex];
-    const float inputY = (float)mInputYCoordinates[inputIndex];
-    return square(inputX - sweetSpotCenterX) + square(inputY - sweetSpotCenterY);
-}
+// TODO: [Staging] Optimize
+void ProximityInfo::getCenters(int *centerXs, int *centerYs, int *codeToKeyIndex,
+        int *keyToCodeIndex, int *keyCount, int *keyWidth) const {
+    *keyCount = KEY_COUNT;
+    *keyWidth = sqrt((float)MOST_COMMON_KEY_WIDTH_SQUARE);
 
-inline const int* ProximityInfo::getProximityCharsAt(const int index) const {
-    return mInputCodes + (index * MAX_PROXIMITY_CHARS_SIZE);
-}
-
-unsigned short ProximityInfo::getPrimaryCharAt(const int index) const {
-    return getProximityCharsAt(index)[0];
-}
-
-inline bool ProximityInfo::existsCharInProximityAt(const int index, const int c) const {
-    const int *chars = getProximityCharsAt(index);
-    int i = 0;
-    while (chars[i] > 0 && i < MAX_PROXIMITY_CHARS_SIZE) {
-        if (chars[i++] == c) {
-            return true;
+    for (int i = 0; i < KEY_COUNT; ++i) {
+        const int code = mKeyCharCodes[i];
+        const int lowerCode = toBaseLowerCase(code);
+        centerXs[i] = mKeyXCoordinates[i] + mKeyWidths[i] / 2;
+        centerYs[i] = mKeyYCoordinates[i] + mKeyHeights[i] / 2;
+        codeToKeyIndex[code] = i;
+        if (code != lowerCode && lowerCode >= 0 && lowerCode <= MAX_CHAR_CODE) {
+            codeToKeyIndex[lowerCode] = i;
+            keyToCodeIndex[i] = lowerCode;
+        } else {
+            keyToCodeIndex[i] = code;
         }
     }
-    return false;
 }
-
-bool ProximityInfo::existsAdjacentProximityChars(const int index) const {
-    if (index < 0 || index >= mInputLength) return false;
-    const int currentChar = getPrimaryCharAt(index);
-    const int leftIndex = index - 1;
-    if (leftIndex >= 0 && existsCharInProximityAt(leftIndex, currentChar)) {
-        return true;
-    }
-    const int rightIndex = index + 1;
-    if (rightIndex < mInputLength && existsCharInProximityAt(rightIndex, currentChar)) {
-        return true;
-    }
-    return false;
-}
-
-// In the following function, c is the current character of the dictionary word
-// currently examined.
-// currentChars is an array containing the keys close to the character the
-// user actually typed at the same position. We want to see if c is in it: if so,
-// then the word contains at that position a character close to what the user
-// typed.
-// What the user typed is actually the first character of the array.
-// proximityIndex is a pointer to the variable where getMatchedProximityId returns
-// the index of c in the proximity chars of the input index.
-// Notice : accented characters do not have a proximity list, so they are alone
-// in their list. The non-accented version of the character should be considered
-// "close", but not the other keys close to the non-accented version.
-ProximityInfo::ProximityType ProximityInfo::getMatchedProximityId(const int index,
-        const unsigned short c, const bool checkProximityChars, int *proximityIndex) const {
-    const int *currentChars = getProximityCharsAt(index);
-    const int firstChar = currentChars[0];
-    const unsigned short baseLowerC = toBaseLowerCase(c);
-
-    // The first char in the array is what user typed. If it matches right away,
-    // that means the user typed that same char for this pos.
-    if (firstChar == baseLowerC || firstChar == c) {
-        return EQUIVALENT_CHAR;
-    }
-
-    if (!checkProximityChars) return UNRELATED_CHAR;
-
-    // If the non-accented, lowercased version of that first character matches c,
-    // then we have a non-accented version of the accented character the user
-    // typed. Treat it as a close char.
-    if (toBaseLowerCase(firstChar) == baseLowerC)
-        return NEAR_PROXIMITY_CHAR;
-
-    // Not an exact nor an accent-alike match: search the list of close keys
-    int j = 1;
-    while (j < MAX_PROXIMITY_CHARS_SIZE
-            && currentChars[j] > ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) {
-        const bool matched = (currentChars[j] == baseLowerC || currentChars[j] == c);
-        if (matched) {
-            if (proximityIndex) {
-                *proximityIndex = j;
-            }
-            return NEAR_PROXIMITY_CHAR;
-        }
-        ++j;
-    }
-    if (j < MAX_PROXIMITY_CHARS_SIZE
-            && currentChars[j] == ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) {
-        ++j;
-        while (j < MAX_PROXIMITY_CHARS_SIZE
-                && currentChars[j] > ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) {
-            const bool matched = (currentChars[j] == baseLowerC || currentChars[j] == c);
-            if (matched) {
-                if (proximityIndex) {
-                    *proximityIndex = j;
-                }
-                return ADDITIONAL_PROXIMITY_CHAR;
-            }
-            ++j;
-        }
-    }
-
-    // Was not included, signal this as an unrelated character.
-    return UNRELATED_CHAR;
-}
-
-bool ProximityInfo::sameAsTyped(const unsigned short *word, int length) const {
-    if (length != mInputLength) {
-        return false;
-    }
-    const int *inputCodes = mInputCodes;
-    while (length--) {
-        if ((unsigned int) *inputCodes != (unsigned int) *word) {
-            return false;
-        }
-        inputCodes += MAX_PROXIMITY_CHARS_SIZE;
-        word++;
-    }
-    return true;
-}
-
-const int ProximityInfo::NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR_LOG_2;
-const int ProximityInfo::NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR;
-const int ProximityInfo::MAX_KEY_COUNT_IN_A_KEYBOARD;
-const int ProximityInfo::MAX_CHAR_CODE;
-
 } // namespace latinime
diff --git a/native/jni/src/proximity_info.h b/native/jni/src/proximity_info.h
index feb0c94..d58935c 100644
--- a/native/jni/src/proximity_info.h
+++ b/native/jni/src/proximity_info.h
@@ -28,22 +28,6 @@
 
 class ProximityInfo {
  public:
-    static const int NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR_LOG_2 = 10;
-    static const int NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR =
-            1 << NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR_LOG_2;
-
-    // Used as a return value for character comparison
-    typedef enum {
-        // Same char, possibly with different case or accent
-        EQUIVALENT_CHAR,
-        // It is a char located nearby on the keyboard
-        NEAR_PROXIMITY_CHAR,
-        // It is an unrelated char
-        UNRELATED_CHAR,
-        // Additional proximity char which can differ by language.
-        ADDITIONAL_PROXIMITY_CHAR
-    } ProximityType;
-
     ProximityInfo(const std::string localeStr, const int maxProximityCharsSize,
             const int keyboardWidth, const int keyboardHeight, const int gridWidth,
             const int gridHeight, const int mostCommonkeyWidth,
@@ -53,26 +37,73 @@
             const float *sweetSpotCenterYs, const float *sweetSpotRadii);
     ~ProximityInfo();
     bool hasSpaceProximity(const int x, const int y) const;
-    void setInputParams(const int32_t *inputCodes, const int inputLength,
-            const int *xCoordinates, const int *yCoordinates);
-    const int* getProximityCharsAt(const int index) const;
-    unsigned short getPrimaryCharAt(const int index) const;
-    bool existsCharInProximityAt(const int index, const int c) const;
-    bool existsAdjacentProximityChars(const int index) const;
-    ProximityType getMatchedProximityId(const int index, const unsigned short c,
-            const bool checkProximityChars, int *proximityIndex = 0) const;
-    int getNormalizedSquaredDistance(const int inputIndex, const int proximityIndex) const {
-        return mNormalizedSquaredDistances[inputIndex * MAX_PROXIMITY_CHARS_SIZE + proximityIndex];
-    }
+    int getNormalizedSquaredDistance(const int inputIndex, const int proximityIndex) const;
     bool sameAsTyped(const unsigned short *word, int length) const;
-    const unsigned short* getPrimaryInputWord() const {
-        return mPrimaryInputWord;
+    int squaredDistanceToEdge(const int keyId, const int x, const int y) const;
+    bool isOnKey(const int keyId, const int x, const int y) const {
+        if (keyId < 0) return true; // NOT_A_ID is -1, but return whenever < 0 just in case
+        const int left = mKeyXCoordinates[keyId];
+        const int top = mKeyYCoordinates[keyId];
+        const int right = left + mKeyWidths[keyId] + 1;
+        const int bottom = top + mKeyHeights[keyId];
+        return left < right && top < bottom && x >= left && x < right && y >= top && y < bottom;
     }
-    bool touchPositionCorrectionEnabled() const {
-        return mTouchPositionCorrectionEnabled;
+    int getKeyIndex(const int c) const;
+    bool hasSweetSpotData(const int keyIndex) const {
+        // When there are no calibration data for a key,
+        // the radius of the key is assigned to zero.
+        return mSweetSpotRadii[keyIndex] > 0.0;
+    }
+    float getSweetSpotRadiiAt(int keyIndex) const {
+        return mSweetSpotRadii[keyIndex];
+    }
+    float getSweetSpotCenterXAt(int keyIndex) const {
+        return mSweetSpotCenterXs[keyIndex];
+    }
+    float getSweetSpotCenterYAt(int keyIndex) const {
+        return mSweetSpotCenterYs[keyIndex];
+    }
+    void calculateNearbyKeyCodes(
+            const int x, const int y, const int32_t primaryKey, int *inputCodes) const;
+
+    bool hasTouchPositionCorrectionData() const {
+        return HAS_TOUCH_POSITION_CORRECTION_DATA;
     }
 
+    int getMostCommonKeyWidthSquare() const {
+        return MOST_COMMON_KEY_WIDTH_SQUARE;
+    }
+
+    std::string getLocaleStr() const {
+        return mLocaleStr;
+    }
+
+    int getKeyCount() const {
+        return KEY_COUNT;
+    }
+
+    int getCellHeight() const {
+        return CELL_HEIGHT;
+    }
+
+    int getCellWidth() const {
+        return CELL_WIDTH;
+    }
+
+    int getGridWidth() const {
+        return GRID_WIDTH;
+    }
+
+    int getGridHeight() const {
+        return GRID_HEIGHT;
+    }
+
+    // Returns the keyboard key-center information.
+    void getCenters(int *centersX, int *centersY, int *codeToKeyIndex, int *keyToCodeIndex,
+            int *keyCount, int *keyWidth) const;
+
  private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(ProximityInfo);
     // The max number of the keys in one keyboard layout
     static const int MAX_KEY_COUNT_IN_A_KEYBOARD = 64;
     // The upper limit of the char code in mCodeToKeyIndex
@@ -86,16 +117,6 @@
     float calculateSquaredDistanceFromSweetSpotCenter(
             const int keyIndex, const int inputIndex) const;
     bool hasInputCoordinates() const;
-    int getKeyIndex(const int c) const;
-    bool hasSweetSpotData(const int keyIndex) const {
-        // When there are no calibration data for a key,
-        // the radius of the key is assigned to zero.
-        return mSweetSpotRadii[keyIndex] > 0.0;
-    }
-    bool isOnKey(const int keyId, const int x, const int y) const;
-    int squaredDistanceToEdge(const int keyId, const int x, const int y) const;
-    void calculateNearbyKeyCodes(
-            const int x, const int y, const int32_t primaryKey, int *inputCodes) const;
 
     const int MAX_PROXIMITY_CHARS_SIZE;
     const int KEYBOARD_WIDTH;
@@ -108,14 +129,7 @@
     const int KEY_COUNT;
     const bool HAS_TOUCH_POSITION_CORRECTION_DATA;
     const std::string mLocaleStr;
-    // TODO: remove this
-    const int *mInputCodesFromJava;
-    int32_t *mInputCodes;
-    const int *mInputXCoordinates;
-    const int *mInputYCoordinates;
-    bool mTouchPositionCorrectionEnabled;
     int32_t *mProximityCharsArray;
-    int *mNormalizedSquaredDistances;
     int32_t mKeyXCoordinates[MAX_KEY_COUNT_IN_A_KEYBOARD];
     int32_t mKeyYCoordinates[MAX_KEY_COUNT_IN_A_KEYBOARD];
     int32_t mKeyWidths[MAX_KEY_COUNT_IN_A_KEYBOARD];
@@ -124,9 +138,8 @@
     float mSweetSpotCenterXs[MAX_KEY_COUNT_IN_A_KEYBOARD];
     float mSweetSpotCenterYs[MAX_KEY_COUNT_IN_A_KEYBOARD];
     float mSweetSpotRadii[MAX_KEY_COUNT_IN_A_KEYBOARD];
-    int mInputLength;
-    unsigned short mPrimaryInputWord[MAX_WORD_LENGTH_INTERNAL];
     int mCodeToKeyIndex[MAX_CHAR_CODE + 1];
+    // TODO: move to correction.h
 };
 
 } // namespace latinime
diff --git a/native/jni/src/proximity_info_state.cpp b/native/jni/src/proximity_info_state.cpp
new file mode 100644
index 0000000..149299e
--- /dev/null
+++ b/native/jni/src/proximity_info_state.cpp
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#include <assert.h>
+#include <stdint.h>
+#include <string>
+
+#define LOG_TAG "LatinIME: proximity_info_state.cpp"
+
+#include "additional_proximity_chars.h"
+#include "defines.h"
+#include "dictionary.h"
+#include "proximity_info.h"
+#include "proximity_info_state.h"
+
+namespace latinime {
+void ProximityInfoState::initInputParams(
+        const ProximityInfo* proximityInfo, const int32_t* inputCodes, const int inputLength,
+        const int* xCoordinates, const int* yCoordinates) {
+    mProximityInfo = proximityInfo;
+    mHasTouchPositionCorrectionData = proximityInfo->hasTouchPositionCorrectionData();
+    mMostCommonKeyWidthSquare = proximityInfo->getMostCommonKeyWidthSquare();
+    mLocaleStr = proximityInfo->getLocaleStr();
+    mKeyCount = proximityInfo->getKeyCount();
+    mCellHeight = proximityInfo->getCellHeight();
+    mCellWidth = proximityInfo->getCellWidth();
+    mGridHeight = proximityInfo->getGridWidth();
+    mGridWidth = proximityInfo->getGridHeight();
+    const int normalizedSquaredDistancesLength =
+            MAX_PROXIMITY_CHARS_SIZE_INTERNAL * MAX_WORD_LENGTH_INTERNAL;
+    for (int i = 0; i < normalizedSquaredDistancesLength; ++i) {
+        mNormalizedSquaredDistances[i] = NOT_A_DISTANCE;
+    }
+
+    memset(mInputCodes, 0,
+            MAX_WORD_LENGTH_INTERNAL * MAX_PROXIMITY_CHARS_SIZE_INTERNAL * sizeof(mInputCodes[0]));
+
+    for (int i = 0; i < inputLength; ++i) {
+        const int32_t primaryKey = inputCodes[i];
+        const int x = xCoordinates[i];
+        const int y = yCoordinates[i];
+        int *proximities = &mInputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL];
+        mProximityInfo->calculateNearbyKeyCodes(x, y, primaryKey, proximities);
+    }
+
+    if (DEBUG_PROXIMITY_CHARS) {
+        for (int i = 0; i < inputLength; ++i) {
+            AKLOGI("---");
+            for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL; ++j) {
+                int icc = mInputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j];
+                int icfjc = inputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j];
+                icc += 0;
+                icfjc += 0;
+                AKLOGI("--- (%d)%c,%c", i, icc, icfjc); AKLOGI("--- A<%d>,B<%d>", icc, icfjc);
+            }
+        }
+    }
+    mInputXCoordinates = xCoordinates;
+    mInputYCoordinates = yCoordinates;
+    mTouchPositionCorrectionEnabled =
+            mHasTouchPositionCorrectionData && xCoordinates && yCoordinates;
+    mInputLength = inputLength;
+    for (int i = 0; i < inputLength; ++i) {
+        mPrimaryInputWord[i] = getPrimaryCharAt(i);
+    }
+    mPrimaryInputWord[inputLength] = 0;
+    if (DEBUG_PROXIMITY_CHARS) {
+        AKLOGI("--- initInputParams");
+    }
+    for (int i = 0; i < mInputLength; ++i) {
+        const int *proximityChars = getProximityCharsAt(i);
+        const int primaryKey = proximityChars[0];
+        const int x = xCoordinates[i];
+        const int y = yCoordinates[i];
+        if (DEBUG_PROXIMITY_CHARS) {
+            int a = x + y + primaryKey;
+            a += 0;
+            AKLOGI("--- Primary = %c, x = %d, y = %d", primaryKey, x, y);
+        }
+        for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL && proximityChars[j] > 0; ++j) {
+            const int currentChar = proximityChars[j];
+            const float squaredDistance =
+                    hasInputCoordinates() ? calculateNormalizedSquaredDistance(
+                            mProximityInfo->getKeyIndex(currentChar), i) :
+                            NOT_A_DISTANCE_FLOAT;
+            if (squaredDistance >= 0.0f) {
+                mNormalizedSquaredDistances[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j] =
+                        (int) (squaredDistance * NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR);
+            } else {
+                mNormalizedSquaredDistances[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j] =
+                        (j == 0) ? EQUIVALENT_CHAR_WITHOUT_DISTANCE_INFO :
+                                PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO;
+            }
+            if (DEBUG_PROXIMITY_CHARS) {
+                AKLOGI("--- Proximity (%d) = %c", j, currentChar);
+            }
+        }
+    }
+}
+
+float ProximityInfoState::calculateNormalizedSquaredDistance(
+        const int keyIndex, const int inputIndex) const {
+    if (keyIndex == NOT_AN_INDEX) {
+        return NOT_A_DISTANCE_FLOAT;
+    }
+    if (!mProximityInfo->hasSweetSpotData(keyIndex)) {
+        return NOT_A_DISTANCE_FLOAT;
+    }
+    if (NOT_A_COORDINATE == mInputXCoordinates[inputIndex]) {
+        return NOT_A_DISTANCE_FLOAT;
+    }
+    const float squaredDistance = calculateSquaredDistanceFromSweetSpotCenter(
+            keyIndex, inputIndex);
+    const float squaredRadius = square(mProximityInfo->getSweetSpotRadiiAt(keyIndex));
+    return squaredDistance / squaredRadius;
+}
+
+float ProximityInfoState::calculateSquaredDistanceFromSweetSpotCenter(
+        const int keyIndex, const int inputIndex) const {
+    const float sweetSpotCenterX = mProximityInfo->getSweetSpotCenterXAt(keyIndex);
+    const float sweetSpotCenterY = mProximityInfo->getSweetSpotCenterYAt(keyIndex);
+    const float inputX = (float)mInputXCoordinates[inputIndex];
+    const float inputY = (float)mInputYCoordinates[inputIndex];
+    return square(inputX - sweetSpotCenterX) + square(inputY - sweetSpotCenterY);
+}
+} // namespace latinime
diff --git a/native/jni/src/proximity_info_state.h b/native/jni/src/proximity_info_state.h
new file mode 100644
index 0000000..717871c
--- /dev/null
+++ b/native/jni/src/proximity_info_state.h
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#ifndef LATINIME_PROXIMITY_INFO_STATE_H
+#define LATINIME_PROXIMITY_INFO_STATE_H
+
+#include <assert.h>
+#include <stdint.h>
+#include <string>
+
+#include "additional_proximity_chars.h"
+#include "char_utils.h"
+#include "defines.h"
+
+namespace latinime {
+
+class ProximityInfo;
+
+class ProximityInfoState {
+ public:
+    static const int NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR_LOG_2 = 10;
+    static const int NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR =
+            1 << NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR_LOG_2;
+    // The upper limit of the char code in mCodeToKeyIndex
+    static const int MAX_CHAR_CODE = 127;
+    static const float NOT_A_DISTANCE_FLOAT = -1.0f;
+    static const int NOT_A_CODE = -1;
+
+    /////////////////////////////////////////
+    // Defined in proximity_info_state.cpp //
+    /////////////////////////////////////////
+    void initInputParams(
+            const ProximityInfo* proximityInfo, const int32_t* inputCodes, const int inputLength,
+            const int* xCoordinates, const int* yCoordinates);
+
+    /////////////////////////////////////////
+    // Defined here                        //
+    /////////////////////////////////////////
+    ProximityInfoState() {};
+    inline const int* getProximityCharsAt(const int index) const {
+        return mInputCodes + (index * MAX_PROXIMITY_CHARS_SIZE_INTERNAL);
+    }
+
+    inline unsigned short getPrimaryCharAt(const int index) const {
+        return getProximityCharsAt(index)[0];
+    }
+
+    inline bool existsCharInProximityAt(const int index, const int c) const {
+        const int *chars = getProximityCharsAt(index);
+        int i = 0;
+        while (chars[i] > 0 && i < MAX_PROXIMITY_CHARS_SIZE_INTERNAL) {
+            if (chars[i++] == c) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    inline bool existsAdjacentProximityChars(const int index) const {
+        if (index < 0 || index >= mInputLength) return false;
+        const int currentChar = getPrimaryCharAt(index);
+        const int leftIndex = index - 1;
+        if (leftIndex >= 0 && existsCharInProximityAt(leftIndex, currentChar)) {
+            return true;
+        }
+        const int rightIndex = index + 1;
+        if (rightIndex < mInputLength && existsCharInProximityAt(rightIndex, currentChar)) {
+            return true;
+        }
+        return false;
+    }
+
+    // In the following function, c is the current character of the dictionary word
+    // currently examined.
+    // currentChars is an array containing the keys close to the character the
+    // user actually typed at the same position. We want to see if c is in it: if so,
+    // then the word contains at that position a character close to what the user
+    // typed.
+    // What the user typed is actually the first character of the array.
+    // proximityIndex is a pointer to the variable where getMatchedProximityId returns
+    // the index of c in the proximity chars of the input index.
+    // Notice : accented characters do not have a proximity list, so they are alone
+    // in their list. The non-accented version of the character should be considered
+    // "close", but not the other keys close to the non-accented version.
+    inline ProximityType getMatchedProximityId(const int index,
+            const unsigned short c, const bool checkProximityChars, int *proximityIndex = 0) const {
+        const int *currentChars = getProximityCharsAt(index);
+        const int firstChar = currentChars[0];
+        const unsigned short baseLowerC = toBaseLowerCase(c);
+
+        // The first char in the array is what user typed. If it matches right away,
+        // that means the user typed that same char for this pos.
+        if (firstChar == baseLowerC || firstChar == c) {
+            return EQUIVALENT_CHAR;
+        }
+
+        if (!checkProximityChars) return UNRELATED_CHAR;
+
+        // If the non-accented, lowercased version of that first character matches c,
+        // then we have a non-accented version of the accented character the user
+        // typed. Treat it as a close char.
+        if (toBaseLowerCase(firstChar) == baseLowerC)
+            return NEAR_PROXIMITY_CHAR;
+
+        // Not an exact nor an accent-alike match: search the list of close keys
+        int j = 1;
+        while (j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL
+                && currentChars[j] > ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) {
+            const bool matched = (currentChars[j] == baseLowerC || currentChars[j] == c);
+            if (matched) {
+                if (proximityIndex) {
+                    *proximityIndex = j;
+                }
+                return NEAR_PROXIMITY_CHAR;
+            }
+            ++j;
+        }
+        if (j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL
+                && currentChars[j] == ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) {
+            ++j;
+            while (j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL
+                    && currentChars[j] > ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) {
+                const bool matched = (currentChars[j] == baseLowerC || currentChars[j] == c);
+                if (matched) {
+                    if (proximityIndex) {
+                        *proximityIndex = j;
+                    }
+                    return ADDITIONAL_PROXIMITY_CHAR;
+                }
+                ++j;
+            }
+        }
+
+        // Was not included, signal this as an unrelated character.
+        return UNRELATED_CHAR;
+    }
+
+    inline int getNormalizedSquaredDistance(
+            const int inputIndex, const int proximityIndex) const {
+        return mNormalizedSquaredDistances[
+                inputIndex * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + proximityIndex];
+    }
+
+    inline const unsigned short* getPrimaryInputWord() const {
+        return mPrimaryInputWord;
+    }
+
+    inline bool touchPositionCorrectionEnabled() const {
+        return mTouchPositionCorrectionEnabled;
+    }
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(ProximityInfoState);
+    /////////////////////////////////////////
+    // Defined in proximity_info_state.cpp //
+    /////////////////////////////////////////
+    float calculateNormalizedSquaredDistance(const int keyIndex, const int inputIndex) const;
+
+    float calculateSquaredDistanceFromSweetSpotCenter(
+            const int keyIndex, const int inputIndex) const;
+
+    /////////////////////////////////////////
+    // Defined here                        //
+    /////////////////////////////////////////
+    inline float square(const float x) const { return x * x; }
+
+    bool hasInputCoordinates() const {
+        return mInputXCoordinates && mInputYCoordinates;
+    }
+
+    bool sameAsTyped(const unsigned short *word, int length) const {
+        if (length != mInputLength) {
+            return false;
+        }
+        const int *inputCodes = mInputCodes;
+        while (length--) {
+            if ((unsigned int) *inputCodes != (unsigned int) *word) {
+                return false;
+            }
+            inputCodes += MAX_PROXIMITY_CHARS_SIZE_INTERNAL;
+            word++;
+        }
+        return true;
+    }
+
+    // const
+    const ProximityInfo *mProximityInfo;
+    bool mHasTouchPositionCorrectionData;
+    int mMostCommonKeyWidthSquare;
+    std::string mLocaleStr;
+    int mKeyCount;
+    int mCellHeight;
+    int mCellWidth;
+    int mGridHeight;
+    int mGridWidth;
+
+    const int *mInputXCoordinates;
+    const int *mInputYCoordinates;
+    bool mTouchPositionCorrectionEnabled;
+    int32_t mInputCodes[MAX_PROXIMITY_CHARS_SIZE_INTERNAL * MAX_WORD_LENGTH_INTERNAL];
+    int mNormalizedSquaredDistances[MAX_PROXIMITY_CHARS_SIZE_INTERNAL * MAX_WORD_LENGTH_INTERNAL];
+    int mInputLength;
+    unsigned short mPrimaryInputWord[MAX_WORD_LENGTH_INTERNAL];
+};
+
+} // namespace latinime
+
+#endif // LATINIME_PROXIMITY_INFO_STATE_H
diff --git a/native/jni/src/terminal_attributes.h b/native/jni/src/terminal_attributes.h
index 9a803cc..c712f50 100644
--- a/native/jni/src/terminal_attributes.h
+++ b/native/jni/src/terminal_attributes.h
@@ -62,6 +62,7 @@
     };
 
  private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(TerminalAttributes);
     const uint8_t* const mDict;
     const uint8_t mFlags;
     const int mStartPos;
diff --git a/native/jni/src/unigram_dictionary.cpp b/native/jni/src/unigram_dictionary.cpp
index ea9f11b..22f1657 100644
--- a/native/jni/src/unigram_dictionary.cpp
+++ b/native/jni/src/unigram_dictionary.cpp
@@ -103,7 +103,7 @@
         const bool useFullEditDistance, const int *codesSrc,
         const int codesRemain, const int currentDepth, int *codesDest, Correction *correction,
         WordsPriorityQueuePool *queuePool,
-        const digraph_t* const digraphs, const unsigned int digraphsSize) {
+        const digraph_t* const digraphs, const unsigned int digraphsSize) const {
 
     const int startIndex = codesDest - codesBuffer;
     if (currentDepth < MAX_DIGRAPH_SEARCH_DEPTH) {
@@ -170,14 +170,15 @@
 // bigramFilter is a bloom filter for fast rejection: see functions setInFilter and isInFilter
 // in bigram_dictionary.cpp
 int UnigramDictionary::getSuggestions(ProximityInfo *proximityInfo,
-        WordsPriorityQueuePool *queuePool, Correction *correction, const int *xcoordinates,
+        const int *xcoordinates,
         const int *ycoordinates, const int *codes, const int codesSize,
         const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
-        const bool useFullEditDistance, unsigned short *outWords, int *frequencies) {
+        const bool useFullEditDistance, unsigned short *outWords, int *frequencies) const {
 
-    queuePool->clearAll();
-    Correction* masterCorrection = correction;
-    correction->resetCorrection();
+    WordsPriorityQueuePool queuePool(MAX_WORDS, SUB_QUEUE_MAX_WORDS, MAX_WORD_LENGTH);
+    queuePool.clearAll();
+    Correction masterCorrection;
+    masterCorrection.resetCorrection();
     if (BinaryFormat::REQUIRES_GERMAN_UMLAUT_PROCESSING & FLAGS)
     { // Incrementally tune the word and try all possibilities
         int codesBuffer[getCodesBufferSize(codes, codesSize)];
@@ -185,8 +186,8 @@
         int yCoordinatesBuffer[codesSize];
         getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates, codesBuffer,
                 xCoordinatesBuffer, yCoordinatesBuffer, codesSize, bigramMap, bigramFilter,
-                useFullEditDistance, codes, codesSize, 0, codesBuffer, masterCorrection,
-                queuePool, GERMAN_UMLAUT_DIGRAPHS,
+                useFullEditDistance, codes, codesSize, 0, codesBuffer, &masterCorrection,
+                &queuePool, GERMAN_UMLAUT_DIGRAPHS,
                 sizeof(GERMAN_UMLAUT_DIGRAPHS) / sizeof(GERMAN_UMLAUT_DIGRAPHS[0]));
     } else if (BinaryFormat::REQUIRES_FRENCH_LIGATURES_PROCESSING & FLAGS) {
         int codesBuffer[getCodesBufferSize(codes, codesSize)];
@@ -194,28 +195,28 @@
         int yCoordinatesBuffer[codesSize];
         getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates, codesBuffer,
                 xCoordinatesBuffer, yCoordinatesBuffer, codesSize, bigramMap, bigramFilter,
-                useFullEditDistance, codes, codesSize, 0, codesBuffer, masterCorrection,
-                queuePool, FRENCH_LIGATURES_DIGRAPHS,
+                useFullEditDistance, codes, codesSize, 0, codesBuffer, &masterCorrection,
+                &queuePool, FRENCH_LIGATURES_DIGRAPHS,
                 sizeof(FRENCH_LIGATURES_DIGRAPHS) / sizeof(FRENCH_LIGATURES_DIGRAPHS[0]));
     } else { // Normal processing
         getWordSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, codesSize,
-                bigramMap, bigramFilter, useFullEditDistance, masterCorrection, queuePool);
+                bigramMap, bigramFilter, useFullEditDistance, &masterCorrection, &queuePool);
     }
 
     PROF_START(20);
     if (DEBUG_DICT) {
-        float ns = queuePool->getMasterQueue()->getHighestNormalizedScore(
-                proximityInfo->getPrimaryInputWord(), codesSize, 0, 0, 0);
+        float ns = queuePool.getMasterQueue()->getHighestNormalizedScore(
+                masterCorrection.getPrimaryInputWord(), codesSize, 0, 0, 0);
         ns += 0;
         AKLOGI("Max normalized score = %f", ns);
     }
     const int suggestedWordsCount =
-            queuePool->getMasterQueue()->outputSuggestions(
-                    proximityInfo->getPrimaryInputWord(), codesSize, frequencies, outWords);
+            queuePool.getMasterQueue()->outputSuggestions(
+                    masterCorrection.getPrimaryInputWord(), codesSize, frequencies, outWords);
 
     if (DEBUG_DICT) {
-        float ns = queuePool->getMasterQueue()->getHighestNormalizedScore(
-                proximityInfo->getPrimaryInputWord(), codesSize, 0, 0, 0);
+        float ns = queuePool.getMasterQueue()->getHighestNormalizedScore(
+                masterCorrection.getPrimaryInputWord(), codesSize, 0, 0, 0);
         ns += 0;
         AKLOGI("Returning %d words", suggestedWordsCount);
         /// Print the returned words
@@ -235,7 +236,8 @@
 void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo,
         const int *xcoordinates, const int *ycoordinates, const int *codes,
         const int inputLength, const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
-        const bool useFullEditDistance, Correction *correction, WordsPriorityQueuePool *queuePool) {
+        const bool useFullEditDistance, Correction *correction,
+        WordsPriorityQueuePool *queuePool) const {
 
     PROF_OPEN;
     PROF_START(0);
@@ -259,7 +261,7 @@
     WordsPriorityQueue* masterQueue = queuePool->getMasterQueue();
     if (masterQueue->size() > 0) {
         float nsForMaster = masterQueue->getHighestNormalizedScore(
-                proximityInfo->getPrimaryInputWord(), inputLength, 0, 0, 0);
+                correction->getPrimaryInputWord(), inputLength, 0, 0, 0);
         hasAutoCorrectionCandidate = (nsForMaster > START_TWO_WORDS_CORRECTION_THRESHOLD);
     }
     PROF_END(4);
@@ -288,11 +290,11 @@
                 const unsigned short* word = sw->mWord;
                 const int wordLength = sw->mWordLength;
                 float ns = Correction::RankingAlgorithm::calcNormalizedScore(
-                        proximityInfo->getPrimaryInputWord(), i, word, wordLength, score);
+                        correction->getPrimaryInputWord(), i, word, wordLength, score);
                 ns += 0;
                 AKLOGI("--- TOP SUB WORDS for %d --- %d %f [%d]", i, score, ns,
                         (ns > TWO_WORDS_CORRECTION_WITH_OTHER_ERROR_THRESHOLD));
-                DUMP_WORD(proximityInfo->getPrimaryInputWord(), i);
+                DUMP_WORD(correction->getPrimaryInputWord(), i);
                 DUMP_WORD(word, wordLength);
             }
         }
@@ -300,12 +302,13 @@
 }
 
 void UnigramDictionary::initSuggestions(ProximityInfo *proximityInfo, const int *xCoordinates,
-        const int *yCoordinates, const int *codes, const int inputLength, Correction *correction) {
+        const int *yCoordinates, const int *codes, const int inputLength,
+        Correction *correction) const {
     if (DEBUG_DICT) {
         AKLOGI("initSuggest");
         DUMP_WORD_INT(codes, inputLength);
     }
-    proximityInfo->setInputParams(codes, inputLength, xCoordinates, yCoordinates);
+    correction->initInputParams(proximityInfo, codes, inputLength, xCoordinates, yCoordinates);
     const int maxDepth = min(inputLength * MAX_DEPTH_MULTIPLIER, MAX_WORD_LENGTH);
     correction->initCorrection(proximityInfo, inputLength, maxDepth);
 }
@@ -317,7 +320,7 @@
         const int *xcoordinates, const int *ycoordinates, const int *codes,
         const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
         const bool useFullEditDistance, const int inputLength,
-        Correction *correction, WordsPriorityQueuePool *queuePool) {
+        Correction *correction, WordsPriorityQueuePool *queuePool) const {
     initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, inputLength, correction);
     getSuggestionCandidates(useFullEditDistance, inputLength, bigramMap, bigramFilter, correction,
             queuePool, true /* doAutoCompletion */, DEFAULT_MAX_ERRORS, FIRST_WORD_INDEX);
@@ -326,7 +329,7 @@
 void UnigramDictionary::getSuggestionCandidates(const bool useFullEditDistance,
         const int inputLength, const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
         Correction *correction, WordsPriorityQueuePool *queuePool,
-        const bool doAutoCompletion, const int maxErrors, const int currentWordIndex) {
+        const bool doAutoCompletion, const int maxErrors, const int currentWordIndex) const {
     uint8_t totalTraverseCount = correction->pushAndGetTotalTraverseCount();
     if (DEBUG_DICT) {
         AKLOGI("Traverse count %d", totalTraverseCount);
@@ -374,7 +377,7 @@
 inline void UnigramDictionary::onTerminal(const int probability,
         const TerminalAttributes& terminalAttributes, Correction *correction,
         WordsPriorityQueuePool *queuePool, const bool addToMasterQueue,
-        const int currentWordIndex) {
+        const int currentWordIndex) const {
     const int inputIndex = correction->getInputIndex();
     const bool addToSubQueue = inputIndex < SUB_QUEUE_MAX_COUNT;
 
@@ -430,7 +433,7 @@
         const bool hasAutoCorrectionCandidate, const int currentWordIndex,
         const int inputWordStartPos, const int inputWordLength,
         const int outputWordStartPos, const bool isSpaceProximity, int *freqArray,
-        int*wordLengthArray, unsigned short* outputWord, int *outputWordLength) {
+        int*wordLengthArray, unsigned short* outputWord, int *outputWordLength) const {
     if (inputWordLength > MULTIPLE_WORDS_SUGGESTION_MAX_WORD_LENGTH) {
         return FLAG_MULTIPLE_SUGGEST_ABORT;
     }
@@ -479,11 +482,12 @@
     initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes,
             inputLength, correction);
 
+    unsigned short word[MAX_WORD_LENGTH_INTERNAL];
     int freq = getMostFrequentWordLike(
-            inputWordStartPos, inputWordLength, proximityInfo, mWord);
+            inputWordStartPos, inputWordLength, correction, word);
     if (freq > 0) {
         nextWordLength = inputWordLength;
-        tempOutputWord = mWord;
+        tempOutputWord = word;
     } else if (!hasAutoCorrectionCandidate) {
         if (inputWordStartPos > 0) {
             const int offset = inputWordStartPos;
@@ -510,7 +514,7 @@
         }
         int score = 0;
         const float ns = queue->getHighestNormalizedScore(
-                proximityInfo->getPrimaryInputWord(), inputWordLength,
+                correction->getPrimaryInputWord(), inputWordLength,
                 &tempOutputWord, &score, &nextWordLength);
         if (DEBUG_DICT) {
             AKLOGI("NS(%d) = %f, Score = %d", currentWordIndex, ns, score);
@@ -577,7 +581,7 @@
         Correction *correction, WordsPriorityQueuePool* queuePool,
         const bool hasAutoCorrectionCandidate, const int startInputPos, const int startWordIndex,
         const int outputWordLength, int *freqArray, int* wordLengthArray,
-        unsigned short* outputWord) {
+        unsigned short* outputWord) const {
     if (startWordIndex >= (MULTIPLE_WORDS_SUGGESTION_MAX_WORDS - 1)) {
         // Return if the last word index
         return;
@@ -656,7 +660,7 @@
         const int *xcoordinates, const int *ycoordinates, const int *codes,
         const bool useFullEditDistance, const int inputLength,
         Correction *correction, WordsPriorityQueuePool* queuePool,
-        const bool hasAutoCorrectionCandidate) {
+        const bool hasAutoCorrectionCandidate) const {
     if (inputLength >= MAX_WORD_LENGTH) return;
     if (DEBUG_DICT) {
         AKLOGI("--- Suggest multiple words");
@@ -678,11 +682,11 @@
 // Wrapper for getMostFrequentWordLikeInner, which matches it to the previous
 // interface.
 inline int UnigramDictionary::getMostFrequentWordLike(const int startInputIndex,
-        const int inputLength, ProximityInfo *proximityInfo, unsigned short *word) {
+        const int inputLength, Correction *correction, unsigned short *word) const {
     uint16_t inWord[inputLength];
 
     for (int i = 0; i < inputLength; ++i) {
-        inWord[i] = (uint16_t)proximityInfo->getPrimaryCharAt(startInputIndex + i);
+        inWord[i] = (uint16_t)correction->getPrimaryCharAt(startInputIndex + i);
     }
     return getMostFrequentWordLikeInner(inWord, inputLength, word);
 }
@@ -751,21 +755,24 @@
 // Will find the highest frequency of the words like the one passed as an argument,
 // that is, everything that only differs by case/accents.
 int UnigramDictionary::getMostFrequentWordLikeInner(const uint16_t * const inWord,
-        const int length, short unsigned int* outWord) {
+        const int length, short unsigned int* outWord) const {
     int32_t newWord[MAX_WORD_LENGTH_INTERNAL];
     int depth = 0;
     int maxFreq = -1;
     const uint8_t* const root = DICT_ROOT;
+    int stackChildCount[MAX_WORD_LENGTH_INTERNAL];
+    int stackInputIndex[MAX_WORD_LENGTH_INTERNAL];
+    int stackSiblingPos[MAX_WORD_LENGTH_INTERNAL];
 
     int startPos = 0;
-    mStackChildCount[0] = BinaryFormat::getGroupCountAndForwardPointer(root, &startPos);
-    mStackInputIndex[0] = 0;
-    mStackSiblingPos[0] = startPos;
+    stackChildCount[0] = BinaryFormat::getGroupCountAndForwardPointer(root, &startPos);
+    stackInputIndex[0] = 0;
+    stackSiblingPos[0] = startPos;
     while (depth >= 0) {
-        const int charGroupCount = mStackChildCount[depth];
-        int pos = mStackSiblingPos[depth];
+        const int charGroupCount = stackChildCount[depth];
+        int pos = stackSiblingPos[depth];
         for (int charGroupIndex = charGroupCount - 1; charGroupIndex >= 0; --charGroupIndex) {
-            int inputIndex = mStackInputIndex[depth];
+            int inputIndex = stackInputIndex[depth];
             const uint8_t flags = BinaryFormat::getFlagsAndForwardPointer(root, &pos);
             // Test whether all chars in this group match with the word we are searching for. If so,
             // we want to traverse its children (or if the length match, evaluate its frequency).
@@ -785,15 +792,15 @@
             // anyway, so don't traverse unless inputIndex < length.
             if (isAlike && (-1 != childrenNodePos) && (inputIndex < length)) {
                 // Save position for this depth, to get back to this once children are done
-                mStackChildCount[depth] = charGroupIndex;
-                mStackSiblingPos[depth] = siblingPos;
+                stackChildCount[depth] = charGroupIndex;
+                stackSiblingPos[depth] = siblingPos;
                 // Prepare stack values for next depth
                 ++depth;
                 int childrenPos = childrenNodePos;
-                mStackChildCount[depth] =
+                stackChildCount[depth] =
                         BinaryFormat::getGroupCountAndForwardPointer(root, &childrenPos);
-                mStackSiblingPos[depth] = childrenPos;
-                mStackInputIndex[depth] = inputIndex;
+                stackSiblingPos[depth] = childrenPos;
+                stackInputIndex[depth] = inputIndex;
                 pos = childrenPos;
                 // Go to the next depth level.
                 ++depth;
@@ -810,7 +817,8 @@
 
 int UnigramDictionary::getFrequency(const int32_t* const inWord, const int length) const {
     const uint8_t* const root = DICT_ROOT;
-    int pos = BinaryFormat::getTerminalPosition(root, inWord, length);
+    int pos = BinaryFormat::getTerminalPosition(root, inWord, length,
+            false /* forceLowerCaseSearch */);
     if (NOT_VALID_WORD == pos) {
         return NOT_A_PROBABILITY;
     }
@@ -848,7 +856,7 @@
 inline bool UnigramDictionary::processCurrentNode(const int initialPos,
         const std::map<int, int> *bigramMap, const uint8_t *bigramFilter, Correction *correction,
         int *newCount, int *newChildrenPosition, int *nextSiblingPosition,
-        WordsPriorityQueuePool *queuePool, const int currentWordIndex) {
+        WordsPriorityQueuePool *queuePool, const int currentWordIndex) const {
     if (DEBUG_DICT) {
         correction->checkState();
     }
diff --git a/native/jni/src/unigram_dictionary.h b/native/jni/src/unigram_dictionary.h
index a1a8299..8352c54 100644
--- a/native/jni/src/unigram_dictionary.h
+++ b/native/jni/src/unigram_dictionary.h
@@ -77,19 +77,20 @@
             int fullWordMultiplier, int maxWordLength, int maxWords, const unsigned int flags);
     int getFrequency(const int32_t* const inWord, const int length) const;
     int getBigramPosition(int pos, unsigned short *word, int offset, int length) const;
-    int getSuggestions(ProximityInfo *proximityInfo, WordsPriorityQueuePool *queuePool,
-            Correction *correction, const int *xcoordinates, const int *ycoordinates,
+    int getSuggestions(
+            ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates,
             const int *codes, const int codesSize, const std::map<int, int> *bigramMap,
             const uint8_t *bigramFilter, const bool useFullEditDistance, unsigned short *outWords,
-            int *frequencies);
+            int *frequencies) const;
     virtual ~UnigramDictionary();
 
  private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(UnigramDictionary);
     void getWordSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
             const int *ycoordinates, const int *codes, const int inputLength,
             const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
             const bool useFullEditDistance, Correction *correction,
-            WordsPriorityQueuePool *queuePool);
+            WordsPriorityQueuePool *queuePool) const;
     int getDigraphReplacement(const int *codes, const int i, const int codesSize,
             const digraph_t* const digraphs, const unsigned int digraphsSize) const;
     void getWordWithDigraphSuggestionsRec(ProximityInfo *proximityInfo,
@@ -99,37 +100,36 @@
         const bool useFullEditDistance, const int* codesSrc, const int codesRemain,
         const int currentDepth, int* codesDest, Correction *correction,
         WordsPriorityQueuePool* queuePool, const digraph_t* const digraphs,
-        const unsigned int digraphsSize);
+        const unsigned int digraphsSize) const;
     void initSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
-            const int *ycoordinates, const int *codes, const int codesSize, Correction *correction);
+            const int *ycoordinates, const int *codes, const int codesSize,
+            Correction *correction) const;
     void getOneWordSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
             const int *ycoordinates, const int *codes, const std::map<int, int> *bigramMap,
             const uint8_t *bigramFilter, const bool useFullEditDistance, const int inputLength,
-            Correction *correction, WordsPriorityQueuePool* queuePool);
+            Correction *correction, WordsPriorityQueuePool* queuePool) const;
     void getSuggestionCandidates(
             const bool useFullEditDistance, const int inputLength,
             const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
             Correction *correction, WordsPriorityQueuePool* queuePool, const bool doAutoCompletion,
-            const int maxErrors, const int currentWordIndex);
+            const int maxErrors, const int currentWordIndex) const;
     void getSplitMultipleWordsSuggestions(ProximityInfo *proximityInfo,
             const int *xcoordinates, const int *ycoordinates, const int *codes,
             const bool useFullEditDistance, const int inputLength,
             Correction *correction, WordsPriorityQueuePool* queuePool,
-            const bool hasAutoCorrectionCandidate);
+            const bool hasAutoCorrectionCandidate) const;
     void onTerminal(const int freq, const TerminalAttributes& terminalAttributes,
             Correction *correction, WordsPriorityQueuePool *queuePool, const bool addToMasterQueue,
-            const int currentWordIndex);
-    bool needsToSkipCurrentNode(const unsigned short c,
-            const int inputIndex, const int skipPos, const int depth);
+            const int currentWordIndex) const;
     // Process a node by considering proximity, missing and excessive character
     bool processCurrentNode(const int initialPos, const std::map<int, int> *bigramMap,
             const uint8_t *bigramFilter, Correction *correction, int *newCount,
             int *newChildPosition, int *nextSiblingPosition, WordsPriorityQueuePool *queuePool,
-            const int currentWordIndex);
+            const int currentWordIndex) const;
     int getMostFrequentWordLike(const int startInputIndex, const int inputLength,
-            ProximityInfo *proximityInfo, unsigned short *word);
+            Correction *correction, unsigned short *word) const;
     int getMostFrequentWordLikeInner(const uint16_t* const inWord, const int length,
-            short unsigned int *outWord);
+            short unsigned int *outWord) const;
     int getSubStringSuggestion(
             ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates,
             const int *codes, const bool useFullEditDistance, Correction *correction,
@@ -137,14 +137,14 @@
             const bool hasAutoCorrectionCandidate, const int currentWordIndex,
             const int inputWordStartPos, const int inputWordLength,
             const int outputWordStartPos, const bool isSpaceProximity, int *freqArray,
-            int *wordLengthArray, unsigned short* outputWord, int *outputWordLength);
+            int *wordLengthArray, unsigned short* outputWord, int *outputWordLength) const;
     void getMultiWordsSuggestionRec(ProximityInfo *proximityInfo,
             const int *xcoordinates, const int *ycoordinates, const int *codes,
             const bool useFullEditDistance, const int inputLength,
             Correction *correction, WordsPriorityQueuePool* queuePool,
             const bool hasAutoCorrectionCandidate, const int startPos, const int startWordIndex,
             const int outputWordLength, int *freqArray, int* wordLengthArray,
-            unsigned short* outputWord);
+            unsigned short* outputWord) const;
 
     const uint8_t* const DICT_ROOT;
     const int MAX_WORD_LENGTH;
@@ -158,12 +158,6 @@
 
     static const digraph_t GERMAN_UMLAUT_DIGRAPHS[];
     static const digraph_t FRENCH_LIGATURES_DIGRAPHS[];
-
-    // Still bundled members
-    unsigned short mWord[MAX_WORD_LENGTH_INTERNAL];// TODO: remove
-    int mStackChildCount[MAX_WORD_LENGTH_INTERNAL];// TODO: remove
-    int mStackInputIndex[MAX_WORD_LENGTH_INTERNAL];// TODO: remove
-    int mStackSiblingPos[MAX_WORD_LENGTH_INTERNAL];// TODO: remove
 };
 } // namespace latinime
 
diff --git a/native/jni/src/words_priority_queue.h b/native/jni/src/words_priority_queue.h
index 7629251..9c6d28d 100644
--- a/native/jni/src/words_priority_queue.h
+++ b/native/jni/src/words_priority_queue.h
@@ -182,6 +182,7 @@
     }
 
  private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(WordsPriorityQueue);
     struct wordComparator {
         bool operator ()(SuggestedWord * left, SuggestedWord * right) {
             return left->mScore > right->mScore;
diff --git a/native/jni/src/words_priority_queue_pool.h b/native/jni/src/words_priority_queue_pool.h
index 210b5a8..b4e2bed 100644
--- a/native/jni/src/words_priority_queue_pool.h
+++ b/native/jni/src/words_priority_queue_pool.h
@@ -85,6 +85,7 @@
     }
 
  private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(WordsPriorityQueuePool);
     WordsPriorityQueue* mMasterQueue;
     WordsPriorityQueue* mSubQueues[SUB_QUEUE_MAX_COUNT * MULTIPLE_WORDS_SUGGESTION_MAX_WORDS];
     char mMasterQueueBuf[sizeof(WordsPriorityQueue)];
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java
index fa067f4..3dc4543 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java
@@ -16,25 +16,27 @@
 
 package com.android.inputmethod.keyboard.internal;
 
-import android.test.AndroidTestCase;
+import android.app.Instrumentation;
+import android.test.InstrumentationTestCase;
 
 import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Locale;
 
-public class KeySpecParserCsvTests extends AndroidTestCase {
+public class KeySpecParserCsvTests extends InstrumentationTestCase {
     private final KeyboardTextsSet mTextsSet = new KeyboardTextsSet();
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
 
+        final Instrumentation instrumentation = getInstrumentation();
         mTextsSet.setLanguage(Locale.ENGLISH.getLanguage());
-        mTextsSet.loadStringResources(getContext());
+        mTextsSet.loadStringResources(instrumentation.getTargetContext());
         final String[] testResourceNames = getAllResourceIdNames(
                 com.android.inputmethod.latin.tests.R.string.class);
-        mTextsSet.loadStringResourcesInternal(getTestContext(),
+        mTextsSet.loadStringResourcesInternal(instrumentation.getContext(),
                 testResourceNames,
                 com.android.inputmethod.latin.tests.R.string.empty_string);
     }
diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTests.java b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
index f1ccfdd..7790299 100644
--- a/tests/src/com/android/inputmethod/latin/InputLogicTests.java
+++ b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
@@ -126,6 +126,26 @@
                 mTextView.getText().toString());
     }
 
+    public void testAutoCorrectWithSpaceThenRevert() {
+        final String STRING_TO_TYPE = "tgis ";
+        final String EXPECTED_RESULT = "tgis ";
+        type(STRING_TO_TYPE);
+        mLatinIME.onUpdateSelection(0, 0, STRING_TO_TYPE.length(), STRING_TO_TYPE.length(), -1, -1);
+        type(Keyboard.CODE_DELETE);
+        assertEquals("auto-correct with space then revert", EXPECTED_RESULT,
+                mTextView.getText().toString());
+    }
+
+    public void testAutoCorrectToSelfDoesNotRevert() {
+        final String STRING_TO_TYPE = "this ";
+        final String EXPECTED_RESULT = "this";
+        type(STRING_TO_TYPE);
+        mLatinIME.onUpdateSelection(0, 0, STRING_TO_TYPE.length(), STRING_TO_TYPE.length(), -1, -1);
+        type(Keyboard.CODE_DELETE);
+        assertEquals("auto-correct with space does not revert", EXPECTED_RESULT,
+                mTextView.getText().toString());
+    }
+
     public void testDoubleSpace() {
         final String STRING_TO_TYPE = "this  ";
         final String EXPECTED_RESULT = "this. ";
diff --git a/tests/src/com/android/inputmethod/latin/InputPointersTests.java b/tests/src/com/android/inputmethod/latin/InputPointersTests.java
new file mode 100644
index 0000000..b60c2df
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/InputPointersTests.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.test.AndroidTestCase;
+
+public class InputPointersTests extends AndroidTestCase {
+    public void testNewInstance() {
+        final InputPointers src = new InputPointers();
+        assertEquals("newInstance size", 0, src.getPointerSize());
+        assertNotNull("new instance xCoordinates", src.getXCoordinates());
+        assertNotNull("new instance yCoordinates", src.getYCoordinates());
+        assertNotNull("new instance pointerIds", src.getPointerIds());
+        assertNotNull("new instance times", src.getTimes());
+    }
+
+    public void testReset() {
+        final InputPointers src = new InputPointers();
+        final int[] xCoordinates = src.getXCoordinates();
+        final int[] yCoordinates = src.getXCoordinates();
+        final int[] pointerIds = src.getXCoordinates();
+        final int[] times = src.getXCoordinates();
+
+        src.reset();
+        assertEquals("after reset size", 0, src.getPointerSize());
+        assertNotSame("after reset xCoordinates", xCoordinates, src.getXCoordinates());
+        assertNotSame("after reset yCoordinates", yCoordinates, src.getYCoordinates());
+        assertNotSame("after reset pointerIds", pointerIds, src.getPointerIds());
+        assertNotSame("after reset times", times, src.getTimes());
+    }
+
+    public void testAdd() {
+        final InputPointers src = new InputPointers();
+        final int limit = src.getXCoordinates().length * 2 + 10;
+        for (int i = 0; i < limit; i++) {
+            src.addPointer(i, i * 2, i * 3, i * 4);
+            assertEquals("after add " + i, i + 1, src.getPointerSize());
+        }
+        for (int i = 0; i < limit; i++) {
+            assertEquals("xCoordinates at " + i, i, src.getXCoordinates()[i]);
+            assertEquals("yCoordinates at " + i, i * 2, src.getYCoordinates()[i]);
+            assertEquals("pointerIds at " + i, i * 3, src.getPointerIds()[i]);
+            assertEquals("times at " + i, i * 4, src.getTimes()[i]);
+        }
+    }
+
+    public void testAddAt() {
+        final InputPointers src = new InputPointers();
+        final int limit = 1000, step = 100;
+        for (int i = 0; i < limit; i += step) {
+            src.addPointer(i, i, i * 2, i * 3, i * 4);
+            assertEquals("after add at " + i, i + 1, src.getPointerSize());
+        }
+        for (int i = 0; i < limit; i += step) {
+            assertEquals("xCoordinates at " + i, i, src.getXCoordinates()[i]);
+            assertEquals("yCoordinates at " + i, i * 2, src.getYCoordinates()[i]);
+            assertEquals("pointerIds at " + i, i * 3, src.getPointerIds()[i]);
+            assertEquals("times at " + i, i * 4, src.getTimes()[i]);
+        }
+    }
+
+    public void testSet() {
+        final InputPointers src = new InputPointers();
+        final int limit = src.getXCoordinates().length * 2 + 10;
+        for (int i = 0; i < limit; i++) {
+            src.addPointer(i, i * 2, i * 3, i * 4);
+        }
+        final InputPointers dst = new InputPointers();
+        dst.set(src);
+        assertEquals("after set size", dst.getPointerSize(), src.getPointerSize());
+        assertSame("after set xCoordinates", dst.getXCoordinates(), src.getXCoordinates());
+        assertSame("after set yCoordinates", dst.getYCoordinates(), src.getYCoordinates());
+        assertSame("after set pointerIds", dst.getPointerIds(), src.getPointerIds());
+        assertSame("after set times", dst.getTimes(), src.getTimes());
+    }
+
+    public void testCopy() {
+        final InputPointers src = new InputPointers();
+        final int limit = 100;
+        for (int i = 0; i < limit; i++) {
+            src.addPointer(i, i * 2, i * 3, i * 4);
+        }
+        final InputPointers dst = new InputPointers();
+        dst.copy(src);
+        assertEquals("after copy size", dst.getPointerSize(), src.getPointerSize());
+        assertNotSame("after copy xCoordinates", dst.getXCoordinates(), src.getXCoordinates());
+        assertNotSame("after copy yCoordinates", dst.getYCoordinates(), src.getYCoordinates());
+        assertNotSame("after copy pointerIds", dst.getPointerIds(), src.getPointerIds());
+        assertNotSame("after copy times", dst.getTimes(), src.getTimes());
+        final int size = dst.getPointerSize();
+        assertArrayEquals("after copy xCoordinates values",
+                dst.getXCoordinates(), 0, src.getXCoordinates(), 0, size);
+        assertArrayEquals("after copy yCoordinates values",
+                dst.getYCoordinates(), 0, src.getYCoordinates(), 0, size);
+        assertArrayEquals("after copy pointerIds values",
+                dst.getPointerIds(), 0, src.getPointerIds(), 0, size);
+        assertArrayEquals("after copy times values",
+                dst.getTimes(), 0, src.getTimes(), 0, size);
+    }
+
+    public void testAppend() {
+        final InputPointers src = new InputPointers();
+        final int limit = 100;
+        for (int i = 0; i < limit; i++) {
+            src.addPointer(i, i * 2, i * 3, i * 4);
+        }
+        final InputPointers dst = new InputPointers();
+        for (int i = 0; i < limit; i++) {
+            final int value = limit - i;
+            dst.addPointer(value * 4, value * 3, value * 2, value);
+        }
+        final InputPointers dstCopy = new InputPointers();
+        dstCopy.copy(dst);
+
+        dst.append(src, 0, 0);
+        assertEquals("after append zero size", limit, dst.getPointerSize());
+        assertArrayEquals("affer append zero xCoordinates", dstCopy.getXCoordinates(), 0,
+                dst.getXCoordinates(), 0, limit);
+        assertArrayEquals("affer append zero yCoordinates", dstCopy.getYCoordinates(), 0,
+                dst.getYCoordinates(), 0, limit);
+        assertArrayEquals("affer append zero pointerIds", dstCopy.getPointerIds(), 0,
+                dst.getPointerIds(), 0, limit);
+        assertArrayEquals("affer append zero times", dstCopy.getTimes(), 0,
+                dst.getTimes(), 0, limit);
+
+        dst.append(src, 0, src.getPointerSize());
+        assertEquals("after append size", limit * 2, dst.getPointerSize() + src.getPointerSize());
+        assertArrayEquals("affer append xCoordinates", dstCopy.getXCoordinates(), 0,
+                dst.getXCoordinates(), 0, limit);
+        assertArrayEquals("affer append yCoordinates", dstCopy.getYCoordinates(), 0,
+                dst.getYCoordinates(), 0, limit);
+        assertArrayEquals("affer append pointerIds", dstCopy.getPointerIds(), 0,
+                dst.getPointerIds(), 0, limit);
+        assertArrayEquals("affer append times", dstCopy.getTimes(), 0,
+                dst.getTimes(), 0, limit);
+        assertArrayEquals("after append xCoordinates", dst.getXCoordinates(), limit,
+                src.getXCoordinates(), 0, limit);
+        assertArrayEquals("after append yCoordinates", dst.getYCoordinates(), limit,
+                src.getYCoordinates(), 0, limit);
+        assertArrayEquals("after append pointerIds", dst.getPointerIds(), limit,
+                src.getPointerIds(), 0, limit);
+        assertArrayEquals("after append times", dst.getTimes(), limit,
+                src.getTimes(), 0, limit);
+    }
+
+    private static void assertArrayEquals(String message, int[] expecteds, int expectedPos,
+            int[] actuals, int actualPos, int length) {
+        if (expecteds == null && actuals == null) {
+            return;
+        }
+        if (expecteds == null || actuals == null) {
+            fail(message + ": expecteds=" + expecteds + " actuals=" + actuals);
+        }
+        for (int i = 0; i < length; i++) {
+            assertEquals(message + ": element at " + i,
+                    expecteds[i + expectedPos], actuals[i + actualPos]);
+        }
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/InputTestsBase.java b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
index eb47fd5..c672d51 100644
--- a/tests/src/com/android/inputmethod/latin/InputTestsBase.java
+++ b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
@@ -130,11 +130,11 @@
                 (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
         final ViewGroup vg = new FrameLayout(getContext());
         final View inputView = inflater.inflate(R.layout.input_view, vg);
+        mLatinIME.onCreateInputMethodInterface().startInput(ic, ei);
         mLatinIME.setInputView(inputView);
         mLatinIME.onBindInput();
         mLatinIME.onCreateInputView();
         mLatinIME.onStartInputView(ei, false);
-        mLatinIME.onCreateInputMethodInterface().startInput(ic, ei);
         mInputConnection = ic;
         mKeyboard = mLatinIME.mKeyboardSwitcher.getKeyboard();
         changeLanguage("en_US");
diff --git a/tests/src/com/android/inputmethod/latin/PunctuationTests.java b/tests/src/com/android/inputmethod/latin/PunctuationTests.java
index e1d4c46..0eb3ba4 100644
--- a/tests/src/com/android/inputmethod/latin/PunctuationTests.java
+++ b/tests/src/com/android/inputmethod/latin/PunctuationTests.java
@@ -27,7 +27,7 @@
         final String PUNCTUATION_FROM_STRIP = "!";
         final String EXPECTED_RESULT = "this!! ";
         final boolean defaultNextWordPredictionOption =
-                mLatinIME.getResources().getBoolean(R.bool.config_default_next_word_suggestions);
+                mLatinIME.getResources().getBoolean(R.bool.config_default_next_word_prediction);
         final boolean previousNextWordPredictionOption =
                 setBooleanPreference(NEXT_WORD_PREDICTION_OPTION, false,
                         defaultNextWordPredictionOption);
diff --git a/tests/src/com/android/inputmethod/latin/RichInputConnectionTests.java b/tests/src/com/android/inputmethod/latin/RichInputConnectionTests.java
new file mode 100644
index 0000000..ad99379
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/RichInputConnectionTests.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2010 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 android.inputmethodservice.InputMethodService;
+import android.test.AndroidTestCase;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputConnectionWrapper;
+
+import com.android.inputmethod.latin.RichInputConnection.Range;
+
+public class RichInputConnectionTests extends AndroidTestCase {
+
+    // The following is meant to be a reasonable default for
+    // the "word_separators" resource.
+    private static final String sSeparators = ".,:;!?-";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    private class MockConnection extends InputConnectionWrapper {
+        final String mTextBefore;
+        final String mTextAfter;
+        final ExtractedText mExtractedText;
+
+        public MockConnection(String textBefore, String textAfter, ExtractedText extractedText) {
+            super(null, false);
+            mTextBefore = textBefore;
+            mTextAfter = textAfter;
+            mExtractedText = extractedText;
+        }
+
+        /* (non-Javadoc)
+         * @see android.view.inputmethod.InputConnectionWrapper#getTextBeforeCursor(int, int)
+         */
+        @Override
+        public CharSequence getTextBeforeCursor(int n, int flags) {
+            return mTextBefore;
+        }
+
+        /* (non-Javadoc)
+         * @see android.view.inputmethod.InputConnectionWrapper#getTextAfterCursor(int, int)
+         */
+        @Override
+        public CharSequence getTextAfterCursor(int n, int flags) {
+            return mTextAfter;
+        }
+
+        /* (non-Javadoc)
+         * @see android.view.inputmethod.InputConnectionWrapper#getExtractedText(
+         *         ExtractedTextRequest, int)
+         */
+        @Override
+        public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
+            return mExtractedText;
+        }
+
+        @Override
+        public boolean beginBatchEdit() {
+            return true;
+        }
+
+        @Override
+        public boolean endBatchEdit() {
+            return true;
+        }
+    }
+
+    private class MockInputMethodService extends InputMethodService {
+        InputConnection mInputConnection;
+        public void setInputConnection(final InputConnection inputConnection) {
+            mInputConnection = inputConnection;
+        }
+        @Override
+        public InputConnection getCurrentInputConnection() {
+            return mInputConnection;
+        }
+    }
+
+    /************************** Tests ************************/
+
+    /**
+     * Test for getting previous word (for bigram suggestions)
+     */
+    public void testGetPreviousWord() {
+        // If one of the following cases breaks, the bigram suggestions won't work.
+        assertEquals(RichInputConnection.getNthPreviousWord("abc def", sSeparators, 2), "abc");
+        assertNull(RichInputConnection.getNthPreviousWord("abc", sSeparators, 2));
+        assertNull(RichInputConnection.getNthPreviousWord("abc. def", sSeparators, 2));
+
+        // The following tests reflect the current behavior of the function
+        // RichInputConnection#getNthPreviousWord.
+        // TODO: However at this time, the code does never go
+        // into such a path, so it should be safe to change the behavior of
+        // this function if needed - especially since it does not seem very
+        // logical. These tests are just there to catch any unintentional
+        // changes in the behavior of the RichInputConnection#getPreviousWord method.
+        assertEquals(RichInputConnection.getNthPreviousWord("abc def ", sSeparators, 2), "abc");
+        assertEquals(RichInputConnection.getNthPreviousWord("abc def.", sSeparators, 2), "abc");
+        assertEquals(RichInputConnection.getNthPreviousWord("abc def .", sSeparators, 2), "def");
+        assertNull(RichInputConnection.getNthPreviousWord("abc ", sSeparators, 2));
+
+        assertEquals(RichInputConnection.getNthPreviousWord("abc def", sSeparators, 1), "def");
+        assertEquals(RichInputConnection.getNthPreviousWord("abc def ", sSeparators, 1), "def");
+        assertNull(RichInputConnection.getNthPreviousWord("abc def.", sSeparators, 1));
+        assertNull(RichInputConnection.getNthPreviousWord("abc def .", sSeparators, 1));
+    }
+
+    /**
+     * Test logic in getting the word range at the cursor.
+     */
+    public void testGetWordRangeAtCursor() {
+        ExtractedText et = new ExtractedText();
+        final MockInputMethodService mockInputMethodService = new MockInputMethodService();
+        final RichInputConnection ic = new RichInputConnection(mockInputMethodService);
+        mockInputMethodService.setInputConnection(new MockConnection("word wo", "rd", et));
+        et.startOffset = 0;
+        et.selectionStart = 7;
+        Range r;
+
+        ic.beginBatchEdit();
+        // basic case
+        r = ic.getWordRangeAtCursor(" ", 0);
+        assertEquals("word", r.mWord);
+
+        // more than one word
+        r = ic.getWordRangeAtCursor(" ", 1);
+        assertEquals("word word", r.mWord);
+        ic.endBatchEdit();
+
+        // tab character instead of space
+        mockInputMethodService.setInputConnection(new MockConnection("one\tword\two", "rd", et));
+        ic.beginBatchEdit();
+        r = ic.getWordRangeAtCursor("\t", 1);
+        ic.endBatchEdit();
+        assertEquals("word\tword", r.mWord);
+
+        // only one word doesn't go too far
+        mockInputMethodService.setInputConnection(new MockConnection("one\tword\two", "rd", et));
+        ic.beginBatchEdit();
+        r = ic.getWordRangeAtCursor("\t", 1);
+        ic.endBatchEdit();
+        assertEquals("word\tword", r.mWord);
+
+        // tab or space
+        mockInputMethodService.setInputConnection(new MockConnection("one word\two", "rd", et));
+        ic.beginBatchEdit();
+        r = ic.getWordRangeAtCursor(" \t", 1);
+        ic.endBatchEdit();
+        assertEquals("word\tword", r.mWord);
+
+        // tab or space multiword
+        mockInputMethodService.setInputConnection(new MockConnection("one word\two", "rd", et));
+        ic.beginBatchEdit();
+        r = ic.getWordRangeAtCursor(" \t", 2);
+        ic.endBatchEdit();
+        assertEquals("one word\tword", r.mWord);
+
+        // splitting on supplementary character
+        final String supplementaryChar = "\uD840\uDC8A";
+        mockInputMethodService.setInputConnection(
+                new MockConnection("one word" + supplementaryChar + "wo", "rd", et));
+        ic.beginBatchEdit();
+        r = ic.getWordRangeAtCursor(supplementaryChar, 0);
+        ic.endBatchEdit();
+        assertEquals("word", r.mWord);
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/UtilsTests.java b/tests/src/com/android/inputmethod/latin/UtilsTests.java
deleted file mode 100644
index 2ef4e2f..0000000
--- a/tests/src/com/android/inputmethod/latin/UtilsTests.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2010,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 android.test.AndroidTestCase;
-
-public class UtilsTests extends AndroidTestCase {
-
-    // The following is meant to be a reasonable default for
-    // the "word_separators" resource.
-    private static final String sSeparators = ".,:;!?-";
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-    }
-
-    /************************** Tests ************************/
-
-    /**
-     * Test for getting previous word (for bigram suggestions)
-     */
-    public void testGetPreviousWord() {
-        // If one of the following cases breaks, the bigram suggestions won't work.
-        assertEquals(EditingUtils.getPreviousWord("abc def", sSeparators), "abc");
-        assertNull(EditingUtils.getPreviousWord("abc", sSeparators));
-        assertNull(EditingUtils.getPreviousWord("abc. def", sSeparators));
-
-        // The following tests reflect the current behavior of the function
-        // EditingUtils#getPreviousWord.
-        // TODO: However at this time, the code does never go
-        // into such a path, so it should be safe to change the behavior of
-        // this function if needed - especially since it does not seem very
-        // logical. These tests are just there to catch any unintentional
-        // changes in the behavior of the EditingUtils#getPreviousWord method.
-        assertEquals(EditingUtils.getPreviousWord("abc def ", sSeparators), "abc");
-        assertEquals(EditingUtils.getPreviousWord("abc def.", sSeparators), "abc");
-        assertEquals(EditingUtils.getPreviousWord("abc def .", sSeparators), "def");
-        assertNull(EditingUtils.getPreviousWord("abc ", sSeparators));
-    }
-
-    /**
-     * Test for getting the word before the cursor (for bigram)
-     */
-    public void testGetThisWord() {
-        assertEquals(EditingUtils.getThisWord("abc def", sSeparators), "def");
-        assertEquals(EditingUtils.getThisWord("abc def ", sSeparators), "def");
-        assertNull(EditingUtils.getThisWord("abc def.", sSeparators));
-        assertNull(EditingUtils.getThisWord("abc def .", sSeparators));
-    }
-}
diff --git a/tools/dicttool/Android.mk b/tools/dicttool/Android.mk
new file mode 100644
index 0000000..9e8dbe0
--- /dev/null
+++ b/tools/dicttool/Android.mk
@@ -0,0 +1,25 @@
+#
+# Copyright (C) 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+LOCAL_JAR_MANIFEST := etc/manifest.txt
+LOCAL_MODULE := dicttool
+LOCAL_MODULE_TAGS := eng
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+include $(LOCAL_PATH)/etc/Android.mk
diff --git a/tools/dicttool/etc/Android.mk b/tools/dicttool/etc/Android.mk
new file mode 100644
index 0000000..03d4a96
--- /dev/null
+++ b/tools/dicttool/etc/Android.mk
@@ -0,0 +1,20 @@
+# Copyright (C) 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := eng
+LOCAL_PREBUILT_EXECUTABLES := dicttool
+include $(BUILD_HOST_PREBUILT)
diff --git a/tools/dicttool/etc/dicttool b/tools/dicttool/etc/dicttool
new file mode 100755
index 0000000..8a39694
--- /dev/null
+++ b/tools/dicttool/etc/dicttool
@@ -0,0 +1,62 @@
+#!/bin/sh
+# Copyright 2011, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Set up prog to be the path of this script, including following symlinks,
+# and set up progdir to be the fully-qualified pathname of its directory.
+prog="$0"
+while [ -h "${prog}" ]; do
+    newProg=`/bin/ls -ld "${prog}"`
+    newProg=`expr "${newProg}" : ".* -> \(.*\)$"`
+    if expr "x${newProg}" : 'x/' >/dev/null; then
+        prog="${newProg}"
+    else
+        progdir=`dirname "${prog}"`
+        prog="${progdir}/${newProg}"
+    fi
+done
+oldwd=`pwd`
+progdir=`dirname "${prog}"`
+cd "${progdir}"
+progdir=`pwd`
+prog="${progdir}"/`basename "${prog}"`
+cd "${oldwd}"
+
+jarfile=dicttool.jar
+frameworkdir="$progdir"
+if [ ! -r "$frameworkdir/$jarfile" ]
+then
+    frameworkdir=`dirname "$progdir"`/tools/lib
+    libdir=`dirname "$progdir"`/tools/lib
+fi
+if [ ! -r "$frameworkdir/$jarfile" ]
+then
+    frameworkdir=`dirname "$progdir"`/framework
+    libdir=`dirname "$progdir"`/lib
+fi
+if [ ! -r "$frameworkdir/$jarfile" ]
+then
+    echo `basename "$prog"`": can't find $jarfile"
+    exit 1
+fi
+
+if [ "$OSTYPE" = "cygwin" ] ; then
+    jarpath=`cygpath -w  "$frameworkdir/$jarfile"`
+    progdir=`cygpath -w  "$progdir"`
+else
+    jarpath="$frameworkdir/$jarfile"
+fi
+
+# might need more memory, e.g. -Xmx128M
+exec java -ea -jar "$jarpath" "$@"
diff --git a/tools/dicttool/etc/manifest.txt b/tools/dicttool/etc/manifest.txt
new file mode 100644
index 0000000..67c8521
--- /dev/null
+++ b/tools/dicttool/etc/manifest.txt
@@ -0,0 +1 @@
+Main-Class: com.android.inputmethod.latin.dicttool.Dicttool
diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/Compress.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/Compress.java
new file mode 100644
index 0000000..307f596
--- /dev/null
+++ b/tools/dicttool/src/android/inputmethod/latin/dicttool/Compress.java
@@ -0,0 +1,94 @@
+/**
+ * Copyright (C) 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.
+ */
+
+package com.android.inputmethod.latin.dicttool;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+public class Compress {
+
+    private static OutputStream getCompressedStream(final OutputStream out)
+        throws java.io.IOException {
+        return new GZIPOutputStream(out);
+    }
+
+    private static InputStream getUncompressedStream(final InputStream in) throws IOException {
+        return new GZIPInputStream(in);
+    }
+
+    public static void copy(final InputStream input, final OutputStream output) throws IOException {
+        final byte[] buffer = new byte[1000];
+        for (int readBytes = input.read(buffer); readBytes >= 0; readBytes = input.read(buffer))
+            output.write(buffer, 0, readBytes);
+        input.close();
+        output.close();
+    }
+
+    static public class Compressor extends Dicttool.Command {
+        public static final String COMMAND = "compress";
+        private static final String SUFFIX = ".compressed";
+
+        public Compressor() {
+        }
+
+        public String getHelp() {
+            return "compress <filename>: Compresses a file using gzip compression";
+        }
+
+        public int getArity() {
+            return 1;
+        }
+
+        public void run() throws IOException {
+            final String inFilename = mArgs[0];
+            final String outFilename = inFilename + SUFFIX;
+            final FileInputStream input = new FileInputStream(new File(inFilename));
+            final FileOutputStream output = new FileOutputStream(new File(outFilename));
+            copy(input, new GZIPOutputStream(output));
+        }
+    }
+
+    static public class Uncompressor extends Dicttool.Command {
+        public static final String COMMAND = "uncompress";
+        private static final String SUFFIX = ".uncompressed";
+
+        public Uncompressor() {
+        }
+
+        public String getHelp() {
+            return "uncompress <filename>: Uncompresses a file compressed with gzip compression";
+        }
+
+        public int getArity() {
+            return 1;
+        }
+
+        public void run() throws IOException {
+            final String inFilename = mArgs[0];
+            final String outFilename = inFilename + SUFFIX;
+            final FileInputStream input = new FileInputStream(new File(inFilename));
+            final FileOutputStream output = new FileOutputStream(new File(outFilename));
+            copy(new GZIPInputStream(input), output);
+        }
+    }
+}
diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/Dicttool.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/Dicttool.java
new file mode 100644
index 0000000..b78be79
--- /dev/null
+++ b/tools/dicttool/src/android/inputmethod/latin/dicttool/Dicttool.java
@@ -0,0 +1,120 @@
+/**
+ * Copyright (C) 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.
+ */
+
+package com.android.inputmethod.latin.dicttool;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+
+public class Dicttool {
+
+    public static abstract class Command {
+        protected String[] mArgs;
+        public void setArgs(String[] args) throws IllegalArgumentException {
+            mArgs = args;
+        }
+        abstract public int getArity();
+        abstract public String getHelp();
+        abstract public void run() throws Exception;
+    }
+    static HashMap<String, Class<? extends Command>> sCommands =
+            new HashMap<String, Class<? extends Command>>();
+    static {
+        sCommands.put("info", Info.class);
+        sCommands.put("compress", Compress.Compressor.class);
+        sCommands.put("uncompress", Compress.Uncompressor.class);
+    }
+
+    private static Command getCommandInstance(final String commandName) {
+        try {
+            return sCommands.get(commandName).newInstance();
+        } catch (InstantiationException e) {
+            throw new RuntimeException(commandName + " is not installed");
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(commandName + " is not installed");
+        }
+    }
+
+    private static void help() {
+        System.out.println("Syntax: dicttool <command [arguments]>\nAvailable commands:\n");
+        for (final String commandName : sCommands.keySet()) {
+            System.out.println("*** " + commandName);
+            System.out.println(getCommandInstance(commandName).getHelp());
+            System.out.println("");
+        }
+    }
+
+    private static boolean isCommand(final String commandName) {
+        return sCommands.containsKey(commandName);
+    }
+
+    private String mPreviousCommand = null; // local to the getNextCommand function
+    private Command getNextCommand(final ArrayList<String> arguments) {
+        final String firstArgument = arguments.get(0);
+        final String commandName;
+        if (isCommand(firstArgument)) {
+            commandName = firstArgument;
+            arguments.remove(0);
+        } else if (isCommand(mPreviousCommand)) {
+            commandName = mPreviousCommand;
+        } else {
+            throw new RuntimeException("Unknown command : " + firstArgument);
+        }
+        final Command command = getCommandInstance(commandName);
+        final int arity = command.getArity();
+        if (arguments.size() < arity) {
+            throw new RuntimeException("Not enough arguments to command " + commandName);
+        }
+        final String[] argsArray = new String[arity];
+        arguments.subList(0, arity).toArray(argsArray);
+        for (int i = 0; i < arity; ++i) {
+            // For some reason, ArrayList#removeRange is protected
+            arguments.remove(0);
+        }
+        command.setArgs(argsArray);
+        mPreviousCommand = commandName;
+        return command;
+    }
+
+    private void execute(final ArrayList<String> arguments) {
+        ArrayList<Command> commandsToExecute = new ArrayList<Command>();
+        while (!arguments.isEmpty()) {
+            commandsToExecute.add(getNextCommand(arguments));
+        }
+        for (final Command command : commandsToExecute) {
+            try {
+                command.run();
+            } catch (Exception e) {
+                System.out.println("Exception while processing command "
+                        + command.getClass().getSimpleName() + " : " + e);
+                return;
+            }
+        }
+    }
+
+    public static void main(final String[] args) {
+        if (0 == args.length) {
+            help();
+            return;
+        }
+        if (!isCommand(args[0])) throw new RuntimeException("Unknown command : " + args[0]);
+
+        final ArrayList<String> arguments = new ArrayList<String>(args.length);
+        arguments.addAll(Arrays.asList(args));
+        new Dicttool().execute(arguments);
+    }
+}
diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/Info.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/Info.java
new file mode 100644
index 0000000..cb032dd
--- /dev/null
+++ b/tools/dicttool/src/android/inputmethod/latin/dicttool/Info.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (C) 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.
+ */
+
+package com.android.inputmethod.latin.dicttool;
+
+public class Info extends Dicttool.Command {
+    public Info() {
+    }
+
+    public String getHelp() {
+        return "info <filename>: prints various information about a dictionary file";
+    }
+
+    public int getArity() {
+        return 1;
+    }
+
+    public void run() {
+        // TODO: implement this
+        System.out.println("Not implemented yet");
+    }
+}
diff --git a/tools/maketext/src/com/android/inputmethod/latin/maketext/JarUtils.java b/tools/maketext/src/com/android/inputmethod/latin/maketext/JarUtils.java
index 366d73e..07a6c30 100644
--- a/tools/maketext/src/com/android/inputmethod/latin/maketext/JarUtils.java
+++ b/tools/maketext/src/com/android/inputmethod/latin/maketext/JarUtils.java
@@ -27,14 +27,13 @@
 import java.util.jar.JarFile;
 
 public class JarUtils {
-    private static final String MANIFEST = "META-INF/MANIFEST.MF";
-
     private JarUtils() {
         // This utility class is not publicly instantiable.
     }
 
-    public static JarFile getJarFile(final ClassLoader loader) {
-        final URL resUrl = loader.getResource(MANIFEST);
+    public static JarFile getJarFile(final Class<?> mainClass) {
+        final String mainClassPath = "/" + mainClass.getName().replace('.', '/') + ".class";
+        final URL resUrl = mainClass.getResource(mainClassPath);
         if (!resUrl.getProtocol().equals("jar")) {
             throw new RuntimeException("Should run as jar");
         }
diff --git a/tools/maketext/src/com/android/inputmethod/latin/maketext/LabelText.java b/tools/maketext/src/com/android/inputmethod/latin/maketext/LabelText.java
index a5abcf1..4a92369 100644
--- a/tools/maketext/src/com/android/inputmethod/latin/maketext/LabelText.java
+++ b/tools/maketext/src/com/android/inputmethod/latin/maketext/LabelText.java
@@ -58,7 +58,7 @@
 
     public static void main(final String[] args) {
         final Options options = new Options(args);
-        final JarFile jar = JarUtils.getJarFile(LabelText.class.getClassLoader());
+        final JarFile jar = JarUtils.getJarFile(LabelText.class);
         final MoreKeysResources resources = new MoreKeysResources(jar);
         resources.writeToJava(options.mJava);
     }
diff --git a/tools/maketext/src/com/android/inputmethod/latin/maketext/MoreKeysResources.java b/tools/maketext/src/com/android/inputmethod/latin/maketext/MoreKeysResources.java
index a483593..37ac0d0 100644
--- a/tools/maketext/src/com/android/inputmethod/latin/maketext/MoreKeysResources.java
+++ b/tools/maketext/src/com/android/inputmethod/latin/maketext/MoreKeysResources.java
@@ -100,7 +100,7 @@
                 final File outputFile = new File(outPackage,
                         JAVA_TEMPLATE.replace(".tmpl", ".java"));
                 outPackage.mkdirs();
-                ps = new PrintStream(outputFile);
+                ps = new PrintStream(outputFile, "UTF-8");
             }
             lnr = new LineNumberReader(new InputStreamReader(JarUtils.openResource(template)));
             inflateTemplate(lnr, ps);