aboutsummaryrefslogtreecommitdiffstats
path: root/java
diff options
context:
space:
mode:
Diffstat (limited to 'java')
-rw-r--r--java/res/drawable-hdpi/btn_keyboard_key_dark_active_holo.9.pngbin474 -> 463 bytes
-rw-r--r--java/res/drawable-hdpi/sym_keyboard_voice_holo_dark.pngbin1083 -> 1889 bytes
-rw-r--r--java/res/drawable-hdpi/sym_keyboard_voice_off_holo_dark.pngbin941 -> 1741 bytes
-rw-r--r--java/res/drawable-mdpi/btn_keyboard_key_dark_active_holo.9.pngbin338 -> 355 bytes
-rw-r--r--java/res/drawable-mdpi/sym_keyboard_voice_holo_dark.pngbin781 -> 1166 bytes
-rw-r--r--java/res/drawable-mdpi/sym_keyboard_voice_off_holo_dark.pngbin699 -> 1105 bytes
-rw-r--r--java/res/drawable-xhdpi/btn_keyboard_key_dark_active_holo.9.pngbin603 -> 589 bytes
-rw-r--r--java/res/drawable-xhdpi/sym_keyboard_voice_holo_dark.pngbin1421 -> 2393 bytes
-rw-r--r--java/res/drawable-xhdpi/sym_keyboard_voice_off_holo_dark.pngbin1248 -> 2196 bytes
-rw-r--r--java/res/drawable-xxhdpi/btn_keyboard_key_dark_active_holo.9.pngbin1803 -> 1718 bytes
-rw-r--r--java/res/drawable-xxhdpi/sym_keyboard_voice_holo_dark.pngbin1907 -> 3102 bytes
-rw-r--r--java/res/drawable-xxhdpi/sym_keyboard_voice_off_holo_dark.pngbin1791 -> 2749 bytes
-rw-r--r--java/res/raw/main_de.dictbin1605721 -> 1605727 bytes
-rw-r--r--java/res/raw/main_en.dictbin1069840 -> 1069846 bytes
-rw-r--r--java/res/raw/main_es.dictbin1138899 -> 1138908 bytes
-rw-r--r--java/res/raw/main_fr.dictbin1329179 -> 1329192 bytes
-rw-r--r--java/res/raw/main_it.dictbin1143332 -> 1143338 bytes
-rw-r--r--java/res/raw/main_pt_br.dictbin1091987 -> 1092054 bytes
-rw-r--r--java/res/values-af/strings.xml4
-rw-r--r--java/res/values-am/strings.xml4
-rw-r--r--java/res/values-ar/strings.xml4
-rw-r--r--java/res/values-be/strings.xml4
-rw-r--r--java/res/values-bg/strings.xml4
-rw-r--r--java/res/values-ca/strings.xml4
-rw-r--r--java/res/values-cs/strings.xml4
-rw-r--r--java/res/values-da/strings.xml4
-rw-r--r--java/res/values-de/strings.xml4
-rw-r--r--java/res/values-el/strings.xml4
-rw-r--r--java/res/values-en-rGB/strings.xml4
-rw-r--r--java/res/values-es-rUS/strings.xml4
-rw-r--r--java/res/values-es/strings.xml4
-rw-r--r--java/res/values-et-rEE/strings.xml4
-rw-r--r--java/res/values-fa/strings.xml4
-rw-r--r--java/res/values-fi/strings.xml4
-rw-r--r--java/res/values-fr/strings.xml4
-rw-r--r--java/res/values-hi/strings.xml4
-rw-r--r--java/res/values-hr/strings.xml4
-rw-r--r--java/res/values-hu/strings.xml4
-rw-r--r--java/res/values-in/strings.xml4
-rw-r--r--java/res/values-it/strings.xml4
-rw-r--r--java/res/values-iw/strings.xml4
-rw-r--r--java/res/values-ja/strings.xml4
-rw-r--r--java/res/values-ka-rGE/strings.xml4
-rw-r--r--java/res/values-ko/strings.xml4
-rw-r--r--java/res/values-land/dimens.xml2
-rw-r--r--java/res/values-lt/strings.xml4
-rw-r--r--java/res/values-lv/strings.xml4
-rw-r--r--java/res/values-mn-rMN/strings.xml4
-rw-r--r--java/res/values-ms-rMY/strings.xml4
-rw-r--r--java/res/values-nb/strings.xml4
-rw-r--r--java/res/values-nl/strings.xml4
-rw-r--r--java/res/values-pl/strings.xml4
-rw-r--r--java/res/values-pt-rPT/strings.xml4
-rw-r--r--java/res/values-pt/strings.xml4
-rw-r--r--java/res/values-rm/strings.xml4
-rw-r--r--java/res/values-ro/strings.xml4
-rw-r--r--java/res/values-ru/strings.xml4
-rw-r--r--java/res/values-sk/strings.xml4
-rw-r--r--java/res/values-sl/strings.xml4
-rw-r--r--java/res/values-sr/strings.xml4
-rw-r--r--java/res/values-sv/strings.xml4
-rw-r--r--java/res/values-sw/strings.xml4
-rw-r--r--java/res/values-sw600dp-land/dimens.xml2
-rw-r--r--java/res/values-sw600dp/dimens.xml2
-rw-r--r--java/res/values-sw768dp-land/dimens.xml2
-rw-r--r--java/res/values-sw768dp/dimens.xml2
-rw-r--r--java/res/values-th/strings.xml4
-rw-r--r--java/res/values-tl/strings.xml4
-rw-r--r--java/res/values-tr/strings.xml4
-rw-r--r--java/res/values-uk/strings.xml4
-rw-r--r--java/res/values-vi/strings.xml4
-rw-r--r--java/res/values-zh-rCN/strings.xml4
-rw-r--r--java/res/values-zh-rTW/strings.xml4
-rw-r--r--java/res/values-zu/strings.xml4
-rw-r--r--java/res/values/dimens.xml2
-rw-r--r--java/res/values/strings.xml5
-rw-r--r--java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java12
-rw-r--r--java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java53
-rw-r--r--java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java14
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/PointerTracker.java17
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java11
-rw-r--r--java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java9
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java69
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFactory.java31
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryWriter.java8
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java246
-rw-r--r--java/src/com/android/inputmethod/latin/InputAttributes.java4
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java47
-rw-r--r--java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java120
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputConnection.java6
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java8
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java38
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java6
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/FormatSpec.java19
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/SparseTable.java194
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java41
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java123
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java (renamed from java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java)42
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java13
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java22
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java16
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java2
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java (renamed from java/src/com/android/inputmethod/latin/personalization/UserHistoryPredictionDictionary.java)6
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java4
-rw-r--r--java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java70
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java110
-rw-r--r--java/src/com/android/inputmethod/latin/utils/StringUtils.java89
108 files changed, 1238 insertions, 435 deletions
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_dark_active_holo.9.png b/java/res/drawable-hdpi/btn_keyboard_key_dark_active_holo.9.png
index 87211a502..fa2cb8542 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_dark_active_holo.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_dark_active_holo.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_voice_holo_dark.png b/java/res/drawable-hdpi/sym_keyboard_voice_holo_dark.png
index c1e16a651..8a6336a57 100644
--- a/java/res/drawable-hdpi/sym_keyboard_voice_holo_dark.png
+++ b/java/res/drawable-hdpi/sym_keyboard_voice_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_voice_off_holo_dark.png b/java/res/drawable-hdpi/sym_keyboard_voice_off_holo_dark.png
index 26d068490..edf1379ab 100644
--- a/java/res/drawable-hdpi/sym_keyboard_voice_off_holo_dark.png
+++ b/java/res/drawable-hdpi/sym_keyboard_voice_off_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_dark_active_holo.9.png b/java/res/drawable-mdpi/btn_keyboard_key_dark_active_holo.9.png
index f98653ea1..8e9a34957 100644
--- a/java/res/drawable-mdpi/btn_keyboard_key_dark_active_holo.9.png
+++ b/java/res/drawable-mdpi/btn_keyboard_key_dark_active_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_voice_holo_dark.png b/java/res/drawable-mdpi/sym_keyboard_voice_holo_dark.png
index 16be37d05..0795fcc9b 100644
--- a/java/res/drawable-mdpi/sym_keyboard_voice_holo_dark.png
+++ b/java/res/drawable-mdpi/sym_keyboard_voice_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_voice_off_holo_dark.png b/java/res/drawable-mdpi/sym_keyboard_voice_off_holo_dark.png
index 95d718a46..f76da5797 100644
--- a/java/res/drawable-mdpi/sym_keyboard_voice_off_holo_dark.png
+++ b/java/res/drawable-mdpi/sym_keyboard_voice_off_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_dark_active_holo.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_dark_active_holo.9.png
index 738316d66..a2f6ac0e2 100644
--- a/java/res/drawable-xhdpi/btn_keyboard_key_dark_active_holo.9.png
+++ b/java/res/drawable-xhdpi/btn_keyboard_key_dark_active_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_voice_holo_dark.png b/java/res/drawable-xhdpi/sym_keyboard_voice_holo_dark.png
index 944a8524d..b2bb9b803 100644
--- a/java/res/drawable-xhdpi/sym_keyboard_voice_holo_dark.png
+++ b/java/res/drawable-xhdpi/sym_keyboard_voice_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_voice_off_holo_dark.png b/java/res/drawable-xhdpi/sym_keyboard_voice_off_holo_dark.png
index 2016caf40..23e75bfe7 100644
--- a/java/res/drawable-xhdpi/sym_keyboard_voice_off_holo_dark.png
+++ b/java/res/drawable-xhdpi/sym_keyboard_voice_off_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_active_holo.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_active_holo.9.png
index b35c29fe6..17f0a7a58 100644
--- a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_active_holo.9.png
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_active_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_voice_holo_dark.png b/java/res/drawable-xxhdpi/sym_keyboard_voice_holo_dark.png
index 6809f0711..f04cadf6f 100644
--- a/java/res/drawable-xxhdpi/sym_keyboard_voice_holo_dark.png
+++ b/java/res/drawable-xxhdpi/sym_keyboard_voice_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_voice_off_holo_dark.png b/java/res/drawable-xxhdpi/sym_keyboard_voice_off_holo_dark.png
index 6bd506a11..e74d523bc 100644
--- a/java/res/drawable-xxhdpi/sym_keyboard_voice_off_holo_dark.png
+++ b/java/res/drawable-xxhdpi/sym_keyboard_voice_off_holo_dark.png
Binary files differ
diff --git a/java/res/raw/main_de.dict b/java/res/raw/main_de.dict
index 5d35e64a2..69796bbaa 100644
--- a/java/res/raw/main_de.dict
+++ b/java/res/raw/main_de.dict
Binary files differ
diff --git a/java/res/raw/main_en.dict b/java/res/raw/main_en.dict
index 6564d47fa..bef6b1005 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_es.dict b/java/res/raw/main_es.dict
index f5906c2e2..261ab8c87 100644
--- a/java/res/raw/main_es.dict
+++ b/java/res/raw/main_es.dict
Binary files differ
diff --git a/java/res/raw/main_fr.dict b/java/res/raw/main_fr.dict
index 10adad092..18f529887 100644
--- a/java/res/raw/main_fr.dict
+++ b/java/res/raw/main_fr.dict
Binary files differ
diff --git a/java/res/raw/main_it.dict b/java/res/raw/main_it.dict
index 523f645c7..e161c2475 100644
--- a/java/res/raw/main_it.dict
+++ b/java/res/raw/main_it.dict
Binary files differ
diff --git a/java/res/raw/main_pt_br.dict b/java/res/raw/main_pt_br.dict
index f9ae9b561..21bbe7c67 100644
--- a/java/res/raw/main_pt_br.dict
+++ b/java/res/raw/main_pt_br.dict
Binary files differ
diff --git a/java/res/values-af/strings.xml b/java/res/values-af/strings.xml
index 01a57e7e0..a3c95b290 100644
--- a/java/res/values-af/strings.xml
+++ b/java/res/values-af/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Koppel \'n kopstuk om te hoor hoe wagwoordsleutels hardop gesê word."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Huidige teks is %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Geen teks ingevoer nie"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Sleutelkode %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift aan (tik om te deaktiveer)"</string>
diff --git a/java/res/values-am/strings.xml b/java/res/values-am/strings.xml
index 9d2993762..89e37b274 100644
--- a/java/res/values-am/strings.xml
+++ b/java/res/values-am/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"የይለፍቃል ቁልፎች ጮክ በለው ሲነገሩ ለመስማት የጆሮ ማዳመጫ ሰካ::"</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"የአሁኑ ፅሁፍ %s ነው"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"ምንም ፅሁፍ አልገባም"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"የቁልፍ ኮድ%d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"ቀይር"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"ቅያር በርቷል (ለማሰናክል ንካ)"</string>
diff --git a/java/res/values-ar/strings.xml b/java/res/values-ar/strings.xml
index 7e39b597e..9c3ff51c8 100644
--- a/java/res/values-ar/strings.xml
+++ b/java/res/values-ar/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"يمكنك توصيل سماعة رأس لسماع مفاتيح كلمة المرور منطوقة بصوت عالٍ."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"النص الحالي هو %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"لم يتم إدخال نص"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"رمز المفتاح %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"العالي"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift يعمل (انقر للتعطيل)"</string>
diff --git a/java/res/values-be/strings.xml b/java/res/values-be/strings.xml
index db40c8a72..9e591dea9 100644
--- a/java/res/values-be/strings.xml
+++ b/java/res/values-be/strings.xml
@@ -87,6 +87,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Каб праслухаць паролi, падключыце гарнiтуру."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Бягучы тэкст %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Тэкст не ўведзены"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Клавішны код %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Зрух"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift уключаны (націснiце, каб адключыць)"</string>
diff --git a/java/res/values-bg/strings.xml b/java/res/values-bg/strings.xml
index d3879cca5..c21b53490 100644
--- a/java/res/values-bg/strings.xml
+++ b/java/res/values-bg/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Включете слушалки, за да чуете клавишите за паролата на висок глас."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Текущият текст е %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Няма въведен текст"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Код на клавишa %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"„Shift“ е включен (докоснете за деактивиране)"</string>
diff --git a/java/res/values-ca/strings.xml b/java/res/values-ca/strings.xml
index 03e2f3fdf..d791452d4 100644
--- a/java/res/values-ca/strings.xml
+++ b/java/res/values-ca/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Connecta un auricular per escoltar les claus de la contrasenya en veu alta."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"El text actual és %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"No s\'ha introduït cap text"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Clau de codi %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Maj"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Maj activat (pica per desactivar)"</string>
diff --git a/java/res/values-cs/strings.xml b/java/res/values-cs/strings.xml
index a707f26a7..2cd159d0f 100644
--- a/java/res/values-cs/strings.xml
+++ b/java/res/values-cs/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Chcete-li slyšet, které klávesy jste při zadávání hesla stiskli, připojte sluchátka."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Aktuální text je %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Není zadán žádný text"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Kód klávesy %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Klávesa Shift je zapnutá (vypnete ji klepnutím)."</string>
diff --git a/java/res/values-da/strings.xml b/java/res/values-da/strings.xml
index 187689559..6c9d9d786 100644
--- a/java/res/values-da/strings.xml
+++ b/java/res/values-da/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Tilslut et headset for at høre indtastningen blive læst højt ved angivelse af adgangskode."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Nuværende tekst er %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Der er ingen indtastet tekst"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Tastekode %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Shift-tast"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Skift er slået til (tryk for at deaktivere)"</string>
diff --git a/java/res/values-de/strings.xml b/java/res/values-de/strings.xml
index 5f3e619ff..1a4e38542 100644
--- a/java/res/values-de/strings.xml
+++ b/java/res/values-de/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Schließen Sie ein Headset an, um das Passwort gesprochen zu hören."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Aktueller Text lautet %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Kein Text eingegeben"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Tastencode %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Umschalttaste"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Umschalttaste aktiviert (zum Deaktivieren berühren)"</string>
diff --git a/java/res/values-el/strings.xml b/java/res/values-el/strings.xml
index 27eb865ea..c2ee2d39b 100644
--- a/java/res/values-el/strings.xml
+++ b/java/res/values-el/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Συνδέστε ένα σετ ακουστικών για να ακούσετε τα πλήκτρα του κωδικού πρόσβασης να εκφωνούνται δυνατά."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Το τρέχον κείμενο είναι %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Δεν υπάρχει κείμενο"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Κωδικός πλήκτρου %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Το Shift είναι ενεργοποιημένο (πατήστε για απενεργοποίηση)"</string>
diff --git a/java/res/values-en-rGB/strings.xml b/java/res/values-en-rGB/strings.xml
index a577d4fbb..0586bcca4 100644
--- a/java/res/values-en-rGB/strings.xml
+++ b/java/res/values-en-rGB/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Plug in a headset to hear password keys spoken aloud."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Current text is %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"No text entered"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Key code %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift on (tap to disable)"</string>
diff --git a/java/res/values-es-rUS/strings.xml b/java/res/values-es-rUS/strings.xml
index b1a421475..42c3932f6 100644
--- a/java/res/values-es-rUS/strings.xml
+++ b/java/res/values-es-rUS/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Enchufa tus auriculares para escuchar en voz alta qué teclas presionas al ingresar una contraseña."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"El texto actual es %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"No se ingresó texto."</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Clave de código %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Mayús"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Se activó el modo Mayúscula (toca para desactivarlo)."</string>
diff --git a/java/res/values-es/strings.xml b/java/res/values-es/strings.xml
index 9ff9430de..4d72799f4 100644
--- a/java/res/values-es/strings.xml
+++ b/java/res/values-es/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Conecta un auricular para escuchar las contraseñas en voz alta."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"El texto actual es %s."</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"No se ha introducido texto."</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Código del teclado: %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Mayús"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Mayúsculas activadas (tocar para inhabilitar)"</string>
diff --git a/java/res/values-et-rEE/strings.xml b/java/res/values-et-rEE/strings.xml
index 62197d772..5706fa105 100644
--- a/java/res/values-et-rEE/strings.xml
+++ b/java/res/values-et-rEE/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Ühendage peakomplekt, et kuulata paroole."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Praegune tekst on %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Teksti ei ole sisestatud"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Klahvi kood: %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Tõstuklahv"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Tõstuklahv sees (puudutage keelamiseks)"</string>
diff --git a/java/res/values-fa/strings.xml b/java/res/values-fa/strings.xml
index 5d8951619..c03e440e4 100644
--- a/java/res/values-fa/strings.xml
+++ b/java/res/values-fa/strings.xml
@@ -86,6 +86,10 @@
<!-- no translation found for spoken_current_text_is (2485723011272583845) -->
<skip />
<string name="spoken_no_text_entered" msgid="7479685225597344496">"متنی وارد نشده است"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<!-- String.format failed for translation -->
<!-- no translation found for spoken_description_unknown (3197434010402179157) -->
<skip />
diff --git a/java/res/values-fi/strings.xml b/java/res/values-fi/strings.xml
index 4cdf6210e..744e60420 100644
--- a/java/res/values-fi/strings.xml
+++ b/java/res/values-fi/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Liitä kuulokkeet, niin kuulet mitä näppäimiä painat kirjoittaessasi salasanaa."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Nykyinen teksti on %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Ei kirjoitettua tekstiä"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Näppäimen koodi %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Vaihto"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Vaihto päällä (poista käytöstä napauttamalla)"</string>
diff --git a/java/res/values-fr/strings.xml b/java/res/values-fr/strings.xml
index e096a1fb8..5fedd4ebd 100644
--- a/java/res/values-fr/strings.xml
+++ b/java/res/values-fr/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Branchez des écouteurs pour entendre l\'énoncé à haute voix des touches lors de la saisie du mot de passe."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Le texte actuel est %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Aucun texte saisi"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Code touche %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Maj"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Touche Maj activée (appuyer pour désactiver)"</string>
diff --git a/java/res/values-hi/strings.xml b/java/res/values-hi/strings.xml
index dcbbcd97c..3b3228a9f 100644
--- a/java/res/values-hi/strings.xml
+++ b/java/res/values-hi/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"ज़ोर से बोली गई पासवर्ड कुंजियां सुनने के लिए हेडसेट प्‍लग इन करें."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"वर्तमान पाठ %s है"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"कोई पाठ दर्ज नहीं किया गया"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"कुंजी कोड %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"शिफ़्ट"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift चालू (अक्षम करने के लिए टैप करें)"</string>
diff --git a/java/res/values-hr/strings.xml b/java/res/values-hr/strings.xml
index 0dca64ef0..caef9541d 100644
--- a/java/res/values-hr/strings.xml
+++ b/java/res/values-hr/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Priključite slušalice da biste čuli tipke zaporke izgovorene naglas."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Trenutačni tekst je %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Nije unesen tekst"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Kôd tipke %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Uključena tipka Shift (dotaknite da onemogućite)"</string>
diff --git a/java/res/values-hu/strings.xml b/java/res/values-hu/strings.xml
index 2abed7401..3b0ee4798 100644
--- a/java/res/values-hu/strings.xml
+++ b/java/res/values-hu/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Csatlakoztasson egy headsetet, ha hallani szeretné a jelszót felolvasva."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"A jelenlegi szöveg: %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Szöveg nincs megadva"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Billentyűkód: %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift be van kapcsolva (érintse meg a kikapcsoláshoz)"</string>
diff --git a/java/res/values-in/strings.xml b/java/res/values-in/strings.xml
index c73c26c1a..bfbd2a700 100644
--- a/java/res/values-in/strings.xml
+++ b/java/res/values-in/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Pasang headset untuk mendengar tombol sandi yang diucapkan dengan keras."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Teks saat ini adalah %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Tidak ada teks yang dimasukkan"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Kode tombol %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift hidup (ketuk untuk mematikan)"</string>
diff --git a/java/res/values-it/strings.xml b/java/res/values-it/strings.xml
index eb8e1e959..6989643af 100644
--- a/java/res/values-it/strings.xml
+++ b/java/res/values-it/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Collega gli auricolari per ascoltare la pronuncia dei tasti premuti per la password."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Il testo attuale è %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Nessun testo inserito"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Codice tasto %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Maiuscolo"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Maiuscolo attivo (tocca per disattivare)"</string>
diff --git a/java/res/values-iw/strings.xml b/java/res/values-iw/strings.xml
index e71bd3190..b3ee01410 100644
--- a/java/res/values-iw/strings.xml
+++ b/java/res/values-iw/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"חבר אוזניות כדי לשמוע הקראה של מפתחות סיסמה."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"הטקסט הנוכחי הוא %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"לא הוזן טקסט"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"קוד מקש %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift פועל (הקש כדי להשבית)"</string>
diff --git a/java/res/values-ja/strings.xml b/java/res/values-ja/strings.xml
index 4d3f155bb..37f06377e 100644
--- a/java/res/values-ja/strings.xml
+++ b/java/res/values-ja/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"パスワードのキーが音声出力されるのでヘッドセットを接続してください。"</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"現在のテキスト:%s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"テキストが入力されていません"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"キーコード:%d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift有効(タップして解除)"</string>
diff --git a/java/res/values-ka-rGE/strings.xml b/java/res/values-ka-rGE/strings.xml
index 043c14c94..193f5115d 100644
--- a/java/res/values-ka-rGE/strings.xml
+++ b/java/res/values-ka-rGE/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"შეაერთეთ ყურსაცვამი, რათა მოისმინოთ აკრეფილი პაროლის კლავიშების სახელები."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"მიმდინარე ტექსტი არის %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"ტექსტი არ შეყვანილა"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"კლავიატურის კოდი %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift ჩართულია (შეეხეთ გამოსართავად)"</string>
diff --git a/java/res/values-ko/strings.xml b/java/res/values-ko/strings.xml
index a74b4f1f4..1497812e9 100644
--- a/java/res/values-ko/strings.xml
+++ b/java/res/values-ko/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"비밀번호 키를 음성으로 들으려면 헤드셋을 연결하세요."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"입력한 텍스트: %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"입력한 텍스트 없음"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"키 코드 %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"시프트 키"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift 사용(사용하지 않으려면 탭하세요.)"</string>
diff --git a/java/res/values-land/dimens.xml b/java/res/values-land/dimens.xml
index 42a746b60..b874d4881 100644
--- a/java/res/values-land/dimens.xml
+++ b/java/res/values-land/dimens.xml
@@ -77,7 +77,7 @@
<!-- Emoji keyboard -->
<fraction name="emoji_keyboard_key_width">10%p</fraction>
<fraction name="emoji_keyboard_row_height">50%p</fraction>
- <fraction name="emoji_keyboard_key_letter_size">60%p</fraction>
+ <fraction name="emoji_keyboard_key_letter_size">54%p</fraction>
<integer name="emoji_keyboard_max_key_count">20</integer>
</resources>
diff --git a/java/res/values-lt/strings.xml b/java/res/values-lt/strings.xml
index 6110858dd..88328db7d 100644
--- a/java/res/values-lt/strings.xml
+++ b/java/res/values-lt/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Prijunkite ausines, kad išgirstumėte sakomus slaptažodžio klavišus."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Dabartinis tekstas yra %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Nėra įvesto teksto"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Klavišo kodas %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Antrojo lygio klavišas"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Įjungtas antrasis lygis (palieskite, kad išjungtumėte)"</string>
diff --git a/java/res/values-lv/strings.xml b/java/res/values-lv/strings.xml
index ecd4e511d..069c48701 100644
--- a/java/res/values-lv/strings.xml
+++ b/java/res/values-lv/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Pievienojiet austiņas, lai dzirdētu paroles rakstzīmes."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Pašreizējais teksts ir %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Nav ievadīts teksts"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Taustiņu kods %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Pārslēgšanas taustiņš"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Pārslēgšanas taustiņš iespējots (pieskarieties, lai atspējotu)"</string>
diff --git a/java/res/values-mn-rMN/strings.xml b/java/res/values-mn-rMN/strings.xml
index 92c2befa2..3b039f120 100644
--- a/java/res/values-mn-rMN/strings.xml
+++ b/java/res/values-mn-rMN/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Нууц үгний товчнуудыг чангаар уншихыг сонсохын тулд чихэвчээ залгана уу."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Одоогийн текст %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Текст оруулаагүй"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Товчийн код %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Сэлгэх"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Сэлгэхийг идэвхжүүлсэн (товшиж идэвхгүйжүүлнэ үү)"</string>
diff --git a/java/res/values-ms-rMY/strings.xml b/java/res/values-ms-rMY/strings.xml
index 49efdf18f..ee241d64b 100644
--- a/java/res/values-ms-rMY/strings.xml
+++ b/java/res/values-ms-rMY/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Pasangkan set kepala untuk mendengar kekunci kata laluan disebut dengan kuat."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Teks semasa adalah %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Tiada teks dimasukkan"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Kod kunci %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Kunci anjak dihidupkan (ketik untuk melumpuhkan)"</string>
diff --git a/java/res/values-nb/strings.xml b/java/res/values-nb/strings.xml
index 9b30ea329..ae43d1078 100644
--- a/java/res/values-nb/strings.xml
+++ b/java/res/values-nb/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Koble til hodetelefoner for å høre opplesing av bokstavene i passordet."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Gjeldende tekst er %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Ingen tekst er skrevet inn"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Tastaturkode %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift er på (trykk for å deaktivere)"</string>
diff --git a/java/res/values-nl/strings.xml b/java/res/values-nl/strings.xml
index c61b090b6..de20da4f8 100644
--- a/java/res/values-nl/strings.xml
+++ b/java/res/values-nl/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Sluit een headset aan om wachtwoordtoetsen hardop te laten voorlezen."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Huidige tekst is %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Geen tekst ingevoerd"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Toetscode %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift aan (tik om uit te schakelen)"</string>
diff --git a/java/res/values-pl/strings.xml b/java/res/values-pl/strings.xml
index 31eace5cb..390837596 100644
--- a/java/res/values-pl/strings.xml
+++ b/java/res/values-pl/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Podłącz zestaw słuchawkowy, aby usłyszeć znaki hasła wypowiadane na głos."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Aktualny tekst: %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Nie wprowadzono tekstu"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Kod klawisza: %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift włączony (kliknij, by wyłączyć)"</string>
diff --git a/java/res/values-pt-rPT/strings.xml b/java/res/values-pt-rPT/strings.xml
index cd89a7765..4c81479e5 100644
--- a/java/res/values-pt-rPT/strings.xml
+++ b/java/res/values-pt-rPT/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Ligar auscultadores com microfone integrado para ouvir as teclas da palavra-passe."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"O texto atual é %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Nenhum texto digitado"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Código da tecla %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift ativado (tocar para desativar)"</string>
diff --git a/java/res/values-pt/strings.xml b/java/res/values-pt/strings.xml
index 820f577ad..ef533ae83 100644
--- a/java/res/values-pt/strings.xml
+++ b/java/res/values-pt/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Conecte um fone de ouvido para ouvir as chaves de senha em voz alta."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"O texto atual é %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Nenhum texto digitado"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Código de tecla %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift ativado (toque para desativar)"</string>
diff --git a/java/res/values-rm/strings.xml b/java/res/values-rm/strings.xml
index 1c509ad73..c68d30c0c 100644
--- a/java/res/values-rm/strings.xml
+++ b/java/res/values-rm/strings.xml
@@ -139,6 +139,10 @@
<skip />
<!-- no translation found for spoken_no_text_entered (7479685225597344496) -->
<skip />
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<!-- no translation found for spoken_description_unknown (3197434010402179157) -->
<skip />
<!-- no translation found for spoken_description_shift (244197883292549308) -->
diff --git a/java/res/values-ro/strings.xml b/java/res/values-ro/strings.xml
index 3caff1337..930b68bd6 100644
--- a/java/res/values-ro/strings.xml
+++ b/java/res/values-ro/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Conectaţi un set căşti-microfon pentru a auzi tastele apăsate când introduceţi parola."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Textul curent este %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Nu a fost introdus text"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Tasta cu codul %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Tasta Shift este activată (apăsaţi pentru a o dezactiva)"</string>
diff --git a/java/res/values-ru/strings.xml b/java/res/values-ru/strings.xml
index affe88fbe..8bfb011c5 100644
--- a/java/res/values-ru/strings.xml
+++ b/java/res/values-ru/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Подключите гарнитуру, чтобы услышать пароль."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Введенный текст: %s."</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Текст не введен"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Код клавиши:%d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Клавиша верхнего регистра"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Верхний регистр включен (нажмите, чтобы отключить)"</string>
diff --git a/java/res/values-sk/strings.xml b/java/res/values-sk/strings.xml
index 85d64c6c0..5c25f28ce 100644
--- a/java/res/values-sk/strings.xml
+++ b/java/res/values-sk/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Ak si chcete pri zadávaní hesla vypočuť nahlas vyslovené klávesy, pripojte náhlavnú súpravu."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Aktuálny text je %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Nie je zadaný žiadny text"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Kód klávesu %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Kláves Shift je zapnutý (zakážete ho klepnutím)"</string>
diff --git a/java/res/values-sl/strings.xml b/java/res/values-sl/strings.xml
index 7648d73b2..dde01dd68 100644
--- a/java/res/values-sl/strings.xml
+++ b/java/res/values-sl/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Priključite slušalke, če želite slišati izgovorjene tipke gesla."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Trenutno besedilo je %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Ni vnesenega besedila"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Koda tipke %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift je vklopljen (dotaknite se, da onemogočite)"</string>
diff --git a/java/res/values-sr/strings.xml b/java/res/values-sr/strings.xml
index db06bce7e..e7f6904c1 100644
--- a/java/res/values-sr/strings.xml
+++ b/java/res/values-sr/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Укључите слушалице да бисте чули наглас изговорене тастере за лозинку."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Тренутни текст је %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Текст није унет"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Кôд тастера %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift је укључен (додирните да бисте га онемогућили)"</string>
diff --git a/java/res/values-sv/strings.xml b/java/res/values-sv/strings.xml
index c6635165c..0fcca4837 100644
--- a/java/res/values-sv/strings.xml
+++ b/java/res/values-sv/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Anslut hörlurar om du vill att lösenordet ska läsas upp."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Nuvarande text är %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Ingen text har angetts"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Nyckelkod %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Skift"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Skift på (knacka lätt för att inaktivera)"</string>
diff --git a/java/res/values-sw/strings.xml b/java/res/values-sw/strings.xml
index 188bbca9c..fbf868f24 100644
--- a/java/res/values-sw/strings.xml
+++ b/java/res/values-sw/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Chomeka plagi ya kifaa cha kichwa cha kusikiza ili kusikiliza msimbo wa nenosiri inayozungumwa kwa sauti ya juu."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Maandishi ya sasa ni %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Hakuna maandishi yaliyoingizwa"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Msimbo wa kitufe %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Badilisha"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift imewashwa (gonga ili kulemaza)"</string>
diff --git a/java/res/values-sw600dp-land/dimens.xml b/java/res/values-sw600dp-land/dimens.xml
index 730b7d813..d067265e3 100644
--- a/java/res/values-sw600dp-land/dimens.xml
+++ b/java/res/values-sw600dp-land/dimens.xml
@@ -66,7 +66,7 @@
<!-- Emoji keyboard -->
<fraction name="emoji_keyboard_key_width">10%p</fraction>
<fraction name="emoji_keyboard_row_height">33%p</fraction>
- <fraction name="emoji_keyboard_key_letter_size">95%p</fraction>
+ <fraction name="emoji_keyboard_key_letter_size">85%p</fraction>
<integer name="emoji_keyboard_max_key_count">30</integer>
</resources>
diff --git a/java/res/values-sw600dp/dimens.xml b/java/res/values-sw600dp/dimens.xml
index 2bcf2fa84..591355d3c 100644
--- a/java/res/values-sw600dp/dimens.xml
+++ b/java/res/values-sw600dp/dimens.xml
@@ -92,7 +92,7 @@
<!-- Emoji keyboard -->
<fraction name="emoji_keyboard_key_width">12.5%p</fraction>
<fraction name="emoji_keyboard_row_height">33%p</fraction>
- <fraction name="emoji_keyboard_key_letter_size">85%p</fraction>
+ <fraction name="emoji_keyboard_key_letter_size">76%p</fraction>
<integer name="emoji_keyboard_max_key_count">24</integer>
</resources>
diff --git a/java/res/values-sw768dp-land/dimens.xml b/java/res/values-sw768dp-land/dimens.xml
index 1e2e1c651..664630b4f 100644
--- a/java/res/values-sw768dp-land/dimens.xml
+++ b/java/res/values-sw768dp-land/dimens.xml
@@ -67,7 +67,7 @@
<!-- Emoji keyboard -->
<fraction name="emoji_keyboard_key_width">7.69%p</fraction>
<fraction name="emoji_keyboard_row_height">33%p</fraction>
- <fraction name="emoji_keyboard_key_letter_size">75%p</fraction>
+ <fraction name="emoji_keyboard_key_letter_size">68%p</fraction>
<integer name="emoji_keyboard_max_key_count">39</integer>
</resources>
diff --git a/java/res/values-sw768dp/dimens.xml b/java/res/values-sw768dp/dimens.xml
index f62a536d7..1fd933abf 100644
--- a/java/res/values-sw768dp/dimens.xml
+++ b/java/res/values-sw768dp/dimens.xml
@@ -92,7 +92,7 @@
<!-- Emoji keyboard -->
<fraction name="emoji_keyboard_key_width">10%p</fraction>
<fraction name="emoji_keyboard_row_height">33%p</fraction>
- <fraction name="emoji_keyboard_key_letter_size">85%p</fraction>
+ <fraction name="emoji_keyboard_key_letter_size">76%p</fraction>
<integer name="emoji_keyboard_max_key_count">30</integer>
</resources>
diff --git a/java/res/values-th/strings.xml b/java/res/values-th/strings.xml
index 632f39a74..f84b32c62 100644
--- a/java/res/values-th/strings.xml
+++ b/java/res/values-th/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"เสียบชุดหูฟังเพื่อฟังเสียงเมื่อพิมพ์รหัสผ่าน"</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"ข้อความปัจจุบันคือ %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"ไม่มีข้อความ"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"รหัสคีย์ %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift เปิดอยู่ (แตะเพื่อปิดใช้งาน)"</string>
diff --git a/java/res/values-tl/strings.xml b/java/res/values-tl/strings.xml
index 3ec962b01..af1c68e01 100644
--- a/java/res/values-tl/strings.xml
+++ b/java/res/values-tl/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Mag-plug in ng headset upang marinig ang mga password key na binabanggit nang malakas."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Ang kasalukuyang teksto ay %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Walang tekstong inilagay"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Code ng key %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Naka-on ang shift (i-tap upang huwag paganahin)"</string>
diff --git a/java/res/values-tr/strings.xml b/java/res/values-tr/strings.xml
index 4d0a6174c..4c8e1a81d 100644
--- a/java/res/values-tr/strings.xml
+++ b/java/res/values-tr/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Şifre tuşlarının sesli okunmasını dinlemek için mikrofonlu kulaklık takın."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Mevcut metin: %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Hiç metin girilmedi"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Tuş kodu: %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Üst Karakter"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Üst karakter açık (devre dışı bırakmak için hafifçe vurun)"</string>
diff --git a/java/res/values-uk/strings.xml b/java/res/values-uk/strings.xml
index df62f3286..287bcb397 100644
--- a/java/res/values-uk/strings.xml
+++ b/java/res/values-uk/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Підключіть гарнітуру, щоб прослухати відтворені вголос символи пароля."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Поточний текст – %s."</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Текст не введено"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Код клавіші – %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Клавіша Shift"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift увімкнено (швидко торкніться, щоб вимкнути)"</string>
diff --git a/java/res/values-vi/strings.xml b/java/res/values-vi/strings.xml
index e08d1c6b9..8764df5e0 100644
--- a/java/res/values-vi/strings.xml
+++ b/java/res/values-vi/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Cắm tai nghe để nghe mật khẩu."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Ký tự hiện tại là %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Không có ký tự nào được nhập"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Mã phím %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift đang bật (bấm để tắt)"</string>
diff --git a/java/res/values-zh-rCN/strings.xml b/java/res/values-zh-rCN/strings.xml
index 5980cbf13..f7ceff44f 100644
--- a/java/res/values-zh-rCN/strings.xml
+++ b/java/res/values-zh-rCN/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"需要插入耳机才能听到密码的按键声。"</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"当前文本为%s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"未输入文字"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"键码为 %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift 模式已启用(点按即可停用)"</string>
diff --git a/java/res/values-zh-rTW/strings.xml b/java/res/values-zh-rTW/strings.xml
index b14a01a0a..b773ad21c 100644
--- a/java/res/values-zh-rTW/strings.xml
+++ b/java/res/values-zh-rTW/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"連接耳機即可聽取系統朗讀密碼按鍵。"</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"目前文字為 %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"未輸入文字"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"按鍵代碼 %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Shift 鍵"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift 鍵已開啟 (輕按即可停用)"</string>
diff --git a/java/res/values-zu/strings.xml b/java/res/values-zu/strings.xml
index 9ed88a00f..8f722cce3 100644
--- a/java/res/values-zu/strings.xml
+++ b/java/res/values-zu/strings.xml
@@ -84,6 +84,10 @@
<string name="spoken_use_headphones" msgid="896961781287283493">"Plaka ku-headset ukuze uzwe okhiye bephasiwedi ezindlebeni zakho bezwakala kakhulu."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"Umbhalo wamanje ngu %s"</string>
<string name="spoken_no_text_entered" msgid="7479685225597344496">"Awukho umbhalo ofakiwe"</string>
+ <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+ <skip />
+ <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+ <skip />
<string name="spoken_description_unknown" msgid="3197434010402179157">"Ikhodi yokhiye %d"</string>
<string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
<string name="spoken_description_shift_shifted" msgid="1681877323344195035">"U-Shift uvuliwe (thepha ukuwuvimbela)"</string>
diff --git a/java/res/values/dimens.xml b/java/res/values/dimens.xml
index 4e3b2f567..2d626dbcf 100644
--- a/java/res/values/dimens.xml
+++ b/java/res/values/dimens.xml
@@ -118,7 +118,7 @@
<!-- Emoji keyboard -->
<fraction name="emoji_keyboard_key_width">14.2857%p</fraction>
<fraction name="emoji_keyboard_row_height">33%p</fraction>
- <fraction name="emoji_keyboard_key_letter_size">90%p</fraction>
+ <fraction name="emoji_keyboard_key_letter_size">81%p</fraction>
<integer name="emoji_keyboard_max_key_count">21</integer>
<dimen name="emoji_category_page_id_height">3dp</dimen>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 69da1e862..06f4b4789 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -174,6 +174,11 @@
<!-- Spoken description when there is no text entered -->
<string name="spoken_no_text_entered">No text entered</string>
+ <!-- Spoken description to let the user know what auto-correction will be performed when a key is pressed. -->
+ <string name="spoken_auto_correct"><xliff:g id="key" example="Space">%1$s</xliff:g> corrects <xliff:g id="original">%2$s</xliff:g> to <xliff:g id="corrected">%3$s</xliff:g></string>
+ <!-- Spoken description used during obscured (e.g. password) entry to let the user know that auto-correction will be performed when a key is pressed. -->
+ <string name="spoken_auto_correct_obscured"><xliff:g id="key" example="Space">%1$s</xliff:g> has auto-correction</string>
+
<!-- Spoken description for unknown keyboard keys. -->
<string name="spoken_description_unknown">Key code %d</string>
<!-- Spoken description for the "Shift" keyboard key when "Shift" is off. -->
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
index 7639432aa..c628c5b09 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
@@ -35,6 +35,8 @@ import android.view.inputmethod.EditorInfo;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardView;
+import com.android.inputmethod.latin.settings.Settings;
+import com.android.inputmethod.latin.settings.SettingsValues;
import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.CoordinateUtils;
@@ -285,9 +287,15 @@ public final class AccessibilityEntityProvider extends AccessibilityNodeProvider
private String getKeyDescription(final Key key) {
final EditorInfo editorInfo = mInputMethodService.getCurrentInputEditorInfo();
final boolean shouldObscure = mAccessibilityUtils.shouldObscureInput(editorInfo);
- final String keyDescription = mKeyCodeDescriptionMapper.getDescriptionForKey(
+ final SettingsValues currentSettings = Settings.getInstance().getCurrent();
+ final String keyCodeDescription = mKeyCodeDescriptionMapper.getDescriptionForKey(
mKeyboardView.getContext(), mKeyboardView.getKeyboard(), key, shouldObscure);
- return keyDescription;
+ if (currentSettings.isWordSeparator(key.getCode())) {
+ return mAccessibilityUtils.getAutoCorrectionDescription(
+ keyCodeDescription, shouldObscure);
+ } else {
+ return keyCodeDescription;
+ }
}
/**
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
index 8929dc7e9..10fb9fef4 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
@@ -23,6 +23,7 @@ import android.os.Build;
import android.os.SystemClock;
import android.provider.Settings;
import android.support.v4.view.accessibility.AccessibilityEventCompat;
+import android.text.TextUtils;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
@@ -34,6 +35,7 @@ import android.view.inputmethod.EditorInfo;
import com.android.inputmethod.compat.SettingsSecureCompatUtils;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.utils.InputTypeUtils;
public final class AccessibilityUtils {
@@ -48,6 +50,12 @@ public final class AccessibilityUtils {
private AccessibilityManager mAccessibilityManager;
private AudioManager mAudioManager;
+ /** The most recent auto-correction. */
+ private String mAutoCorrectionWord;
+
+ /** The most recent typed word for auto-correction. */
+ private String mTypedWord;
+
/*
* Setting this constant to {@code false} will disable all keyboard
* accessibility code, regardless of whether Accessibility is turned on in
@@ -142,6 +150,51 @@ public final class AccessibilityUtils {
}
/**
+ * Sets the current auto-correction word and typed word. These may be used
+ * to provide the user with a spoken description of what auto-correction
+ * will occur when a key is typed.
+ *
+ * @param suggestedWords the list of suggested auto-correction words
+ * @param typedWord the currently typed word
+ */
+ public void setAutoCorrection(final SuggestedWords suggestedWords, final String typedWord) {
+ if (suggestedWords != null && suggestedWords.mWillAutoCorrect) {
+ mAutoCorrectionWord = suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION);
+ mTypedWord = typedWord;
+ } else {
+ mAutoCorrectionWord = null;
+ mTypedWord = null;
+ }
+ }
+
+ /**
+ * Obtains a description for an auto-correction key, taking into account the
+ * currently typed word and auto-correction.
+ *
+ * @param keyCodeDescription spoken description of the key that will insert
+ * an auto-correction
+ * @param shouldObscure whether the key should be obscured
+ * @return a description including a description of the auto-correction, if
+ * needed
+ */
+ public String getAutoCorrectionDescription(
+ final String keyCodeDescription, final boolean shouldObscure) {
+ if (!TextUtils.isEmpty(mAutoCorrectionWord)) {
+ if (!TextUtils.equals(mAutoCorrectionWord, mTypedWord)) {
+ if (shouldObscure) {
+ // This should never happen, but just in case...
+ return mContext.getString(R.string.spoken_auto_correct_obscured,
+ keyCodeDescription);
+ }
+ return mContext.getString(R.string.spoken_auto_correct, keyCodeDescription,
+ mTypedWord, mAutoCorrectionWord);
+ }
+ }
+
+ return keyCodeDescription;
+ }
+
+ /**
* Sends the specified text to the {@link AccessibilityManager} to be
* spoken.
*
diff --git a/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java b/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java
index 4e61edac2..eb48d01f6 100644
--- a/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java
@@ -153,7 +153,9 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
mCategoryNameToIdMap.put(sCategoryName[i], i);
}
addShownCategoryId(CATEGORY_ID_RECENTS);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2
+ || android.os.Build.VERSION.CODENAME.equalsIgnoreCase("KeyLimePie")
+ || android.os.Build.VERSION.CODENAME.equalsIgnoreCase("KitKat")) {
addShownCategoryId(CATEGORY_ID_PEOPLE);
addShownCategoryId(CATEGORY_ID_OBJECTS);
addShownCategoryId(CATEGORY_ID_NATURE);
@@ -716,12 +718,14 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
@Override
public void run() {
+ int repeatCount = 1;
int timeCount = 0;
while (timeCount < MAX_REPEAT_COUNT_TIME && !mAborted) {
if (timeCount > mKeyRepeatStartTimeout) {
- pressDelete();
+ pressDelete(repeatCount);
}
timeCount += mKeyRepeatInterval;
+ ++repeatCount;
try {
Thread.sleep(mKeyRepeatInterval);
} catch (InterruptedException e) {
@@ -734,9 +738,9 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
}
}
- public void pressDelete() {
+ public void pressDelete(int repeatCount) {
mKeyboardActionListener.onPressKey(
- Constants.CODE_DELETE, 0 /* repeatCount */, true /* isSinglePointer */);
+ Constants.CODE_DELETE, repeatCount, true /* isSinglePointer */);
mKeyboardActionListener.onCodeInput(
Constants.CODE_DELETE, NOT_A_COORDINATE, NOT_A_COORDINATE);
mKeyboardActionListener.onReleaseKey(
@@ -752,7 +756,7 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
v.setBackgroundColor(mDeleteKeyPressedBackgroundColor);
- pressDelete();
+ pressDelete(0 /* repeatCount */);
startRepeat();
return true;
case MotionEvent.ACTION_UP:
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index cc1ffd183..74edd87cf 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -315,7 +315,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
}
public boolean isShowingEmojiKeyboard() {
- return mEmojiKeyboardView.getVisibility() == View.VISIBLE;
+ return mEmojiKeyboardView != null && mEmojiKeyboardView.getVisibility() == View.VISIBLE;
}
public boolean isShowingMoreKeysPanel() {
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index d4d0d8718..ee4ac950c 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -346,6 +346,8 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
// true if this pointer is in a sliding key input from a modifier key,
// so that further modifier keys should be ignored.
boolean mIsInSlidingKeyInputFromModifier;
+ // if not a NOT_A_CODE, the key of this code is repeating
+ private int mCurrentRepeatingKeyCode = Constants.NOT_A_CODE;
// true if a sliding key input is allowed.
private boolean mIsAllowedSlidingKeyInput;
@@ -937,9 +939,10 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
if (!sShouldHandleGesture) {
return;
}
- // A gesture should start only from a non-modifier key.
+ // A gesture should start only from a non-modifier key. Note that the gesture detection is
+ // disabled when the key is repeating.
mIsDetectingGesture = (mKeyboard != null) && mKeyboard.mId.isAlphabetKeyboard()
- && key != null && !key.isModifier() && !key.isRepeatable();
+ && key != null && !key.isModifier();
if (mIsDetectingGesture) {
if (getActivePointerTrackerCount() == 1) {
sGestureFirstDownTime = eventTime;
@@ -1247,6 +1250,8 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
mIsDetectingGesture = false;
final Key currentKey = mCurrentKey;
mCurrentKey = null;
+ final int currentRepeatingKeyCode = mCurrentRepeatingKeyCode;
+ mCurrentRepeatingKeyCode = Constants.NOT_A_CODE;
// Release the last pressed key.
setReleasedKeyGraphics(currentKey);
@@ -1272,8 +1277,8 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
if (mIsTrackingForActionDisabled) {
return;
}
- if (currentKey != null && currentKey.isRepeatable() && !isInSlidingKeyInput) {
- // Repeatable key has been registered in {@link #onDownEventInternal(int,int,long)}.
+ if (currentKey != null && currentKey.isRepeatable()
+ && (currentKey.getCode() == currentRepeatingKeyCode) && !isInSlidingKeyInput) {
return;
}
detectAndSendKey(currentKey, mKeyX, mKeyY, eventTime);
@@ -1412,7 +1417,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
if (!key.isRepeatable()) return;
// Don't start key repeat when we are in sliding input mode.
if (mIsInSlidingKeyInput) return;
- detectAndSendKey(key, key.getX(), key.getY(), SystemClock.uptimeMillis());
final int startRepeatCount = 1;
mTimerProxy.startKeyRepeatTimer(this, startRepeatCount, sParams.mKeyRepeatStartTimeout);
}
@@ -1420,8 +1424,11 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
public void onKeyRepeat(final int code, final int repeatCount) {
final Key key = getKey();
if (key == null || key.getCode() != code) {
+ mCurrentRepeatingKeyCode = Constants.NOT_A_CODE;
return;
}
+ mCurrentRepeatingKeyCode = code;
+ mIsDetectingGesture = false;
final int nextRepeatCount = repeatCount + 1;
mTimerProxy.startKeyRepeatTimer(this, nextRepeatCount, sParams.mKeyRepeatInterval);
callListenerOnPressAndCheckKeyboardLayoutChange(key, repeatCount);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
index b3491d807..9f9fdaa6f 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
@@ -585,7 +585,7 @@ public final class KeyboardState {
}
}
- private static boolean isSpaceCharacter(final int c) {
+ private static boolean isSpaceOrEnter(final int c) {
return c == Constants.CODE_SPACE || c == Constants.CODE_ENTER;
}
@@ -614,7 +614,12 @@ public final class KeyboardState {
}
break;
case SWITCH_STATE_SYMBOL_BEGIN:
- if (!isSpaceCharacter(code) && (Constants.isLetterCode(code)
+ if (mIsEmojiMode) {
+ // When in the Emoji keyboard, we don't want to switch back to the main layout even
+ // after the user hits an emoji letter followed by an enter or a space.
+ break;
+ }
+ if (!isSpaceOrEnter(code) && (Constants.isLetterCode(code)
|| code == Constants.CODE_OUTPUT_TEXT)) {
mSwitchState = SWITCH_STATE_SYMBOL;
}
@@ -622,7 +627,7 @@ public final class KeyboardState {
case SWITCH_STATE_SYMBOL:
// Switch back to alpha keyboard mode if user types one or more non-space/enter
// characters followed by a space/enter.
- if (isSpaceCharacter(code)) {
+ if (isSpaceOrEnter(code)) {
toggleAlphabetAndSymbols();
mPrevSymbolsKeyboardWasShifted = false;
}
diff --git a/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java b/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
index 845a9b987..4a0ce3735 100644
--- a/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
+++ b/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
@@ -25,6 +25,7 @@ import com.android.inputmethod.latin.makedict.Ver3DictEncoder;
import java.io.File;
import java.io.IOException;
+import java.util.Map;
// TODO: Quit extending Dictionary after implementing dynamic binary dictionary.
abstract public class AbstractDictionaryWriter extends Dictionary {
@@ -50,16 +51,16 @@ abstract public class AbstractDictionaryWriter extends Dictionary {
abstract public void removeBigramWords(final String word0, final String word1);
- abstract protected void writeDictionary(final DictEncoder dictEncoder)
- throws IOException, UnsupportedFormatException;
+ abstract protected void writeDictionary(final DictEncoder dictEncoder,
+ final Map<String, String> attributeMap) throws IOException, UnsupportedFormatException;
- public void write(final String fileName) {
+ public void write(final String fileName, final Map<String, String> attributeMap) {
final String tempFileName = fileName + ".temp";
final File file = new File(mContext.getFilesDir(), fileName);
final File tempFile = new File(mContext.getFilesDir(), tempFileName);
try {
final DictEncoder dictEncoder = new Ver3DictEncoder(tempFile);
- writeDictionary(dictEncoder);
+ writeDictionary(dictEncoder, attributeMap);
tempFile.renameTo(file);
} catch (IOException e) {
Log.e(TAG, "IO exception while writing file", e);
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 61ccfcfad..29c6c0451 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -31,10 +31,12 @@ import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Locale;
+import java.util.Map;
/**
* Implements a static, compacted, binary dictionary of standard words.
*/
+// TODO: All methods which should be locked need to have a suffix "Locked".
public final class BinaryDictionary extends Dictionary {
private static final String TAG = BinaryDictionary.class.getSimpleName();
@@ -46,6 +48,11 @@ public final class BinaryDictionary extends Dictionary {
// TODO: Remove this heuristic.
private static final int SPACE_COUNT_FOR_AUTO_COMMIT = 3;
+ @UsedForTesting
+ public static final String UNIGRAM_COUNT_QUERY = "UNIGRAM_COUNT";
+ @UsedForTesting
+ public static final String BIGRAM_COUNT_QUERY = "BIGRAM_COUNT";
+
private long mNativeDict;
private final Locale mLocale;
private final long mDictSize;
@@ -103,10 +110,12 @@ public final class BinaryDictionary extends Dictionary {
JniUtils.loadNativeLibrary();
}
+ private static native boolean createEmptyDictFileNative(String filePath, long dictVersion,
+ String[] attributeKeyStringArray, String[] attributeValueStringArray);
private static native long openNative(String sourceDir, long dictOffset, long dictSize,
boolean isUpdatable);
private static native void flushNative(long dict, String filePath);
- private static native boolean needsToRunGCNative(long dict);
+ private static native boolean needsToRunGCNative(long dict, boolean mindsBlockByGC);
private static native void flushWithGCNative(long dict, String filePath);
private static native void closeNative(long dict);
private static native int getProbabilityNative(long dict, int[] word);
@@ -125,6 +134,21 @@ public final class BinaryDictionary extends Dictionary {
private static native void removeBigramWordsNative(long dict, int[] word0, int[] word1);
private static native int calculateProbabilityNative(long dict, int unigramProbability,
int bigramProbability);
+ private static native String getPropertyNative(long dict, String query);
+
+ @UsedForTesting
+ public static boolean createEmptyDictFile(final String filePath, final long dictVersion,
+ final Map<String, String> attributeMap) {
+ final String[] keyArray = new String[attributeMap.size()];
+ final String[] valueArray = new String[attributeMap.size()];
+ int index = 0;
+ for (final String key : attributeMap.keySet()) {
+ keyArray[index] = key;
+ valueArray[index] = attributeMap.get(key);
+ index++;
+ }
+ return createEmptyDictFileNative(filePath, dictVersion, keyArray, valueArray);
+ }
// TODO: Move native dict into session
private final void loadDictionary(final String path, final long startOffset,
@@ -246,7 +270,7 @@ public final class BinaryDictionary extends Dictionary {
}
private void runGCIfRequired() {
- if (needsToRunGCNative(mNativeDict)) {
+ if (needsToRunGC(true /* mindsBlockByGC */)) {
flushWithGC();
}
}
@@ -283,27 +307,34 @@ public final class BinaryDictionary extends Dictionary {
removeBigramWordsNative(mNativeDict, codePoints0, codePoints1);
}
- public void flush() {
- if (!isValidDictionary()) return;
- flushNative(mNativeDict, mDictFilePath);
- closeNative(mNativeDict);
+ private void reopen() {
+ close();
final File dictFile = new File(mDictFilePath);
mNativeDict = openNative(dictFile.getAbsolutePath(), 0 /* startOffset */,
dictFile.length(), true /* isUpdatable */);
}
+ public void flush() {
+ if (!isValidDictionary()) return;
+ flushNative(mNativeDict, mDictFilePath);
+ reopen();
+ }
+
public void flushWithGC() {
if (!isValidDictionary()) return;
flushWithGCNative(mNativeDict, mDictFilePath);
- closeNative(mNativeDict);
- final File dictFile = new File(mDictFilePath);
- mNativeDict = openNative(dictFile.getAbsolutePath(), 0 /* startOffset */,
- dictFile.length(), true /* isUpdatable */);
+ reopen();
}
- public boolean needsToRunGC() {
+ /**
+ * Checks whether GC is needed to run or not.
+ * @param mindsBlockByGC Whether to mind operations blocked by GC. We don't need to care about
+ * the blocking in some situations such as in idle time or just before closing.
+ * @return whether GC is needed to run or not.
+ */
+ public boolean needsToRunGC(final boolean mindsBlockByGC) {
if (!isValidDictionary()) return false;
- return needsToRunGCNative(mNativeDict);
+ return needsToRunGCNative(mNativeDict, mindsBlockByGC);
}
@UsedForTesting
@@ -312,6 +343,12 @@ public final class BinaryDictionary extends Dictionary {
return calculateProbabilityNative(mNativeDict, unigramProbability, bigramProbability);
}
+ @UsedForTesting
+ public String getPropertyForTests(String query) {
+ if (!isValidDictionary()) return "";
+ return getPropertyNative(mNativeDict, query);
+ }
+
@Override
public boolean shouldAutoCommit(final SuggestedWordInfo candidate) {
// TODO: actually use the confidence rather than use this completely broken heuristic
@@ -338,21 +375,23 @@ public final class BinaryDictionary extends Dictionary {
traverseSession.close();
}
}
+ mDicTraverseSessions.clear();
}
- closeInternal();
+ closeInternalLocked();
}
- private synchronized void closeInternal() {
+ private synchronized void closeInternalLocked() {
if (mNativeDict != 0) {
closeNative(mNativeDict);
mNativeDict = 0;
}
}
+ // TODO: Manage BinaryDictionary instances without using WeakReference or something.
@Override
protected void finalize() throws Throwable {
try {
- closeInternal();
+ closeInternalLocked();
} finally {
super.finalize();
}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index 3721132c5..828e54f14 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -51,7 +51,7 @@ public final class DictionaryFactory {
if (null == locale) {
Log.e(TAG, "No locale defined for dictionary");
return new DictionaryCollection(Dictionary.TYPE_MAIN,
- createBinaryDictionary(context, locale));
+ createReadOnlyBinaryDictionary(context, locale));
}
final LinkedList<Dictionary> dictList = CollectionUtils.newLinkedList();
@@ -59,11 +59,11 @@ public final class DictionaryFactory {
BinaryDictionaryGetter.getDictionaryFiles(locale, context);
if (null != assetFileList) {
for (final AssetFileAddress f : assetFileList) {
- final BinaryDictionary binaryDictionary = new BinaryDictionary(f.mFilename,
- f.mOffset, f.mLength, useFullEditDistance, locale, Dictionary.TYPE_MAIN,
- false /* isUpdatable */);
- if (binaryDictionary.isValidDictionary()) {
- dictList.add(binaryDictionary);
+ final ReadOnlyBinaryDictionary readOnlyBinaryDictionary =
+ new ReadOnlyBinaryDictionary(f.mFilename, f.mOffset, f.mLength,
+ useFullEditDistance, locale, Dictionary.TYPE_MAIN);
+ if (readOnlyBinaryDictionary.isValidDictionary()) {
+ dictList.add(readOnlyBinaryDictionary);
}
}
}
@@ -89,12 +89,12 @@ public final class DictionaryFactory {
}
/**
- * Initializes a dictionary from a raw resource file
+ * Initializes a read-only binary dictionary from a raw resource file
* @param context application context for reading resources
* @param locale the locale to use for the resource
- * @return an initialized instance of BinaryDictionary
+ * @return an initialized instance of ReadOnlyBinaryDictionary
*/
- protected static BinaryDictionary createBinaryDictionary(final Context context,
+ protected static ReadOnlyBinaryDictionary createReadOnlyBinaryDictionary(final Context context,
final Locale locale) {
AssetFileDescriptor afd = null;
try {
@@ -113,9 +113,8 @@ public final class DictionaryFactory {
Log.e(TAG, "sourceDir is not a file: " + sourceDir);
return null;
}
- return new BinaryDictionary(sourceDir, afd.getStartOffset(), afd.getLength(),
- false /* useFullEditDistance */, locale, Dictionary.TYPE_MAIN,
- false /* isUpdatable */);
+ return new ReadOnlyBinaryDictionary(sourceDir, afd.getStartOffset(), afd.getLength(),
+ false /* useFullEditDistance */, locale, Dictionary.TYPE_MAIN);
} catch (android.content.res.Resources.NotFoundException e) {
Log.e(TAG, "Could not find the resource");
return null;
@@ -142,10 +141,10 @@ public final class DictionaryFactory {
final DictionaryCollection dictionaryCollection =
new DictionaryCollection(Dictionary.TYPE_MAIN);
for (final AssetFileAddress address : dictionaryList) {
- final BinaryDictionary binaryDictionary = new BinaryDictionary(address.mFilename,
- address.mOffset, address.mLength, useFullEditDistance, locale,
- Dictionary.TYPE_MAIN, false /* isUpdatable */);
- dictionaryCollection.addDictionary(binaryDictionary);
+ final ReadOnlyBinaryDictionary readOnlyBinaryDictionary = new ReadOnlyBinaryDictionary(
+ address.mFilename, address.mOffset, address.mLength, useFullEditDistance,
+ locale, Dictionary.TYPE_MAIN);
+ dictionaryCollection.addDictionary(readOnlyBinaryDictionary);
}
return dictionaryCollection;
}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryWriter.java b/java/src/com/android/inputmethod/latin/DictionaryWriter.java
index 5a453dde5..84abfa66d 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryWriter.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryWriter.java
@@ -31,6 +31,7 @@ import com.android.inputmethod.latin.utils.CollectionUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Map;
/**
* An in memory dictionary for memorizing entries and writing a binary dictionary.
@@ -84,8 +85,11 @@ public class DictionaryWriter extends AbstractDictionaryWriter {
}
@Override
- protected void writeDictionary(final DictEncoder dictEncoder)
- throws IOException, UnsupportedFormatException {
+ protected void writeDictionary(final DictEncoder dictEncoder,
+ final Map<String, String> attributeMap) throws IOException, UnsupportedFormatException {
+ for (final Map.Entry<String, String> entry : attributeMap.entrySet()) {
+ mFusionDictionary.addOptionAttribute(entry.getKey(), entry.getValue());
+ }
dictEncoder.writeDictionary(mFusionDictionary, FORMAT_OPTIONS);
}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 0774ce203..2d1ca51e2 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -22,12 +22,7 @@ import android.util.Log;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.ProximityInfo;
-import com.android.inputmethod.latin.makedict.DictEncoder;
import com.android.inputmethod.latin.makedict.FormatSpec;
-import com.android.inputmethod.latin.makedict.FusionDictionary;
-import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
-import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
-import com.android.inputmethod.latin.makedict.Ver3DictEncoder;
import com.android.inputmethod.latin.personalization.DynamicPersonalizationDictionaryWriter;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.utils.AsyncResultHolder;
@@ -35,10 +30,11 @@ import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.PrioritizedSerialExecutor;
import java.io.File;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
/**
@@ -68,18 +64,21 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
protected static final int MAX_WORD_LENGTH = Constants.DICTIONARY_MAX_WORD_LENGTH;
- private static final FormatSpec.FormatOptions FORMAT_OPTIONS =
- new FormatSpec.FormatOptions(3 /* version */, true /* supportsDynamicUpdate */);
+ private static final int DICTIONARY_FORMAT_VERSION = 3;
+
+ private static final String SUPPORTS_DYNAMIC_UPDATE =
+ FormatSpec.FileHeader.ATTRIBUTE_VALUE_TRUE;
/**
- * A static map of time recorders, each of which records the time of accesses to a single binary
- * dictionary file. The key for this map is the filename and the value is the shared dictionary
- * time recorder associated with that filename.
+ * A static map of update controllers, each of which records the time of accesses to a single
+ * binary dictionary file and tracks whether the file is regenerating. The key for this map is
+ * the filename and the value is the shared dictionary time recorder associated with that
+ * filename.
*/
- private static volatile ConcurrentHashMap<String, DictionaryTimeRecorder>
- sFilenameDictionaryTimeRecorderMap = CollectionUtils.newConcurrentHashMap();
+ private static final ConcurrentHashMap<String, DictionaryUpdateController>
+ sFilenameDictionaryUpdateControllerMap = CollectionUtils.newConcurrentHashMap();
- private static volatile ConcurrentHashMap<String, PrioritizedSerialExecutor>
+ private static final ConcurrentHashMap<String, PrioritizedSerialExecutor>
sFilenameExecutorMap = CollectionUtils.newConcurrentHashMap();
/** The application context. */
@@ -106,13 +105,13 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
private final boolean mIsUpdatable;
// TODO: remove, once dynamic operations is serialized
- /** Records access to the shared binary dictionary file across multiple instances. */
- private final DictionaryTimeRecorder mFilenameDictionaryTimeRecorder;
+ /** Controls updating the shared binary dictionary file across multiple instances. */
+ private final DictionaryUpdateController mFilenameDictionaryUpdateController;
// TODO: remove, once dynamic operations is serialized
- /** Records access to the local binary dictionary for this instance. */
- private final DictionaryTimeRecorder mPerInstanceDictionaryTimeRecorder =
- new DictionaryTimeRecorder();
+ /** Controls updating the local binary dictionary for this instance. */
+ private final DictionaryUpdateController mPerInstanceDictionaryUpdateController =
+ new DictionaryUpdateController();
/* A extension for a binary dictionary file. */
public static final String DICT_FILE_EXTENSION = ".dict";
@@ -134,15 +133,15 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
protected abstract boolean hasContentChanged();
/**
- * Gets the dictionary time recorder for the given filename.
+ * Gets the dictionary update controller for the given filename.
*/
- private static DictionaryTimeRecorder getDictionaryTimeRecorder(
+ private static DictionaryUpdateController getDictionaryUpdateController(
String filename) {
- DictionaryTimeRecorder recorder = sFilenameDictionaryTimeRecorderMap.get(filename);
+ DictionaryUpdateController recorder = sFilenameDictionaryUpdateControllerMap.get(filename);
if (recorder == null) {
- synchronized(sFilenameDictionaryTimeRecorderMap) {
- recorder = new DictionaryTimeRecorder();
- sFilenameDictionaryTimeRecorderMap.put(filename, recorder);
+ synchronized(sFilenameDictionaryUpdateControllerMap) {
+ recorder = new DictionaryUpdateController();
+ sFilenameDictionaryUpdateControllerMap.put(filename, recorder);
}
}
return recorder;
@@ -192,7 +191,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
mContext = context;
mIsUpdatable = isUpdatable;
mBinaryDictionary = null;
- mFilenameDictionaryTimeRecorder = getDictionaryTimeRecorder(filename);
+ mFilenameDictionaryUpdateController = getDictionaryUpdateController(filename);
// Currently, only dynamic personalization dictionary is updatable.
mDictionaryWriter = getDictionaryWriter(context, dictType, isUpdatable);
}
@@ -233,6 +232,14 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
});
}
+ protected Map<String, String> getHeaderAttributeMap() {
+ HashMap<String, String> attributeMap = new HashMap<String, String>();
+ attributeMap.put(FormatSpec.FileHeader.SUPPORTS_DYNAMIC_UPDATE_ATTRIBUTE,
+ SUPPORTS_DYNAMIC_UPDATE);
+ attributeMap.put(FormatSpec.FileHeader.DICTIONARY_ID_ATTRIBUTE, mFilename);
+ return attributeMap;
+ }
+
protected void clear() {
getExecutor(mFilename).execute(new Runnable() {
@Override
@@ -240,17 +247,8 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE && mDictionaryWriter == null) {
mBinaryDictionary.close();
final File file = new File(mContext.getFilesDir(), mFilename);
- final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
- new FusionDictionary.DictionaryOptions(new HashMap<String,String>(),
- false, false));
- final DictEncoder dictEncoder = new Ver3DictEncoder(file);
- try {
- dictEncoder.writeDictionary(dict, FORMAT_OPTIONS);
- } catch (IOException e) {
- Log.e(TAG, "Exception in creating new dictionary file.", e);
- } catch (UnsupportedFormatException e) {
- Log.e(TAG, "Exception in creating new dictionary file.", e);
- }
+ BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(),
+ DICTIONARY_FORMAT_VERSION, getHeaderAttributeMap());
} else {
mDictionaryWriter.clear();
}
@@ -352,6 +350,9 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
final int sessionId) {
reloadDictionaryIfRequired();
+ if (isRegenerating()) {
+ return null;
+ }
final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
final AsyncResultHolder<ArrayList<SuggestedWordInfo>> holder =
new AsyncResultHolder<ArrayList<SuggestedWordInfo>>();
@@ -412,6 +413,9 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
protected boolean isValidWordInner(final String word) {
+ if (isRegenerating()) {
+ return false;
+ }
final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>();
getExecutor(mFilename).executePrioritized(new Runnable() {
@Override
@@ -437,7 +441,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
* dictionary exists, this method will generate one.
*/
protected void loadDictionary() {
- mPerInstanceDictionaryTimeRecorder.mLastUpdateRequestTime = SystemClock.uptimeMillis();
+ mPerInstanceDictionaryUpdateController.mLastUpdateRequestTime = SystemClock.uptimeMillis();
reloadDictionaryIfRequired();
}
@@ -448,8 +452,8 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
private void loadBinaryDictionary() {
if (DEBUG) {
Log.d(TAG, "Loading binary dictionary: " + mFilename + " request="
- + mFilenameDictionaryTimeRecorder.mLastUpdateRequestTime + " update="
- + mFilenameDictionaryTimeRecorder.mLastUpdateTime);
+ + mFilenameDictionaryUpdateController.mLastUpdateRequestTime + " update="
+ + mFilenameDictionaryUpdateController.mLastUpdateTime);
}
final File file = new File(mContext.getFilesDir(), mFilename);
@@ -487,37 +491,28 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
private void writeBinaryDictionary() {
if (DEBUG) {
Log.d(TAG, "Generating binary dictionary: " + mFilename + " request="
- + mFilenameDictionaryTimeRecorder.mLastUpdateRequestTime + " update="
- + mFilenameDictionaryTimeRecorder.mLastUpdateTime);
+ + mFilenameDictionaryUpdateController.mLastUpdateRequestTime + " update="
+ + mFilenameDictionaryUpdateController.mLastUpdateTime);
}
if (needsToReloadBeforeWriting()) {
mDictionaryWriter.clear();
loadDictionaryAsync();
- mDictionaryWriter.write(mFilename);
+ mDictionaryWriter.write(mFilename, getHeaderAttributeMap());
} else {
if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
if (mBinaryDictionary == null || !mBinaryDictionary.isValidDictionary()) {
final File file = new File(mContext.getFilesDir(), mFilename);
- final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
- new FusionDictionary.DictionaryOptions(new HashMap<String,String>(),
- false, false));
- final DictEncoder dictEncoder = new Ver3DictEncoder(file);
- try {
- dictEncoder.writeDictionary(dict, FORMAT_OPTIONS);
- } catch (IOException e) {
- Log.e(TAG, "Exception in creating new dictionary file.", e);
- } catch (UnsupportedFormatException e) {
- Log.e(TAG, "Exception in creating new dictionary file.", e);
- }
+ BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(),
+ DICTIONARY_FORMAT_VERSION, getHeaderAttributeMap());
} else {
- if (mBinaryDictionary.needsToRunGC()) {
+ if (mBinaryDictionary.needsToRunGC(false /* mindsBlockByGC */)) {
mBinaryDictionary.flushWithGC();
} else {
mBinaryDictionary.flush();
}
}
} else {
- mDictionaryWriter.write(mFilename);
+ mDictionaryWriter.write(mFilename, getHeaderAttributeMap());
}
}
}
@@ -531,11 +526,11 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
protected void setRequiresReload(final boolean requiresRebuild) {
final long time = SystemClock.uptimeMillis();
- mPerInstanceDictionaryTimeRecorder.mLastUpdateRequestTime = time;
- mFilenameDictionaryTimeRecorder.mLastUpdateRequestTime = time;
+ mPerInstanceDictionaryUpdateController.mLastUpdateRequestTime = time;
+ mFilenameDictionaryUpdateController.mLastUpdateRequestTime = time;
if (DEBUG) {
Log.d(TAG, "Reload request: " + mFilename + ": request=" + time + " update="
- + mFilenameDictionaryTimeRecorder.mLastUpdateTime);
+ + mFilenameDictionaryUpdateController.mLastUpdateTime);
}
}
@@ -544,14 +539,26 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
public final void reloadDictionaryIfRequired() {
if (!isReloadRequired()) return;
- reloadDictionary();
+ if (setIsRegeneratingIfNotRegenerating()) {
+ reloadDictionary();
+ }
}
/**
* Returns whether a dictionary reload is required.
*/
private boolean isReloadRequired() {
- return mBinaryDictionary == null || mPerInstanceDictionaryTimeRecorder.isOutOfDate();
+ return mBinaryDictionary == null || mPerInstanceDictionaryUpdateController.isOutOfDate();
+ }
+
+ private boolean isRegenerating() {
+ return mFilenameDictionaryUpdateController.mIsRegenerating.get();
+ }
+
+ // Returns whether the dictionary can be regenerated.
+ private boolean setIsRegeneratingIfNotRegenerating() {
+ return mFilenameDictionaryUpdateController.mIsRegenerating.compareAndSet(
+ false /* expect */ , true /* update */);
}
/**
@@ -564,39 +571,44 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
getExecutor(mFilename).execute(new Runnable() {
@Override
public void run() {
- final long time = SystemClock.uptimeMillis();
- final boolean dictionaryFileExists = dictionaryFileExists();
- if (mFilenameDictionaryTimeRecorder.isOutOfDate() || !dictionaryFileExists) {
- // If the shared dictionary file does not exist or is out of date, the first
- // instance that acquires the lock will generate a new one.
- if (hasContentChanged() || !dictionaryFileExists) {
- // If the source content has changed or the dictionary does not exist,
- // rebuild the binary dictionary. Empty dictionaries are supported (in the
- // case where loadDictionaryAsync() adds nothing) in order to provide a
- // uniform framework.
- mFilenameDictionaryTimeRecorder.mLastUpdateTime = time;
+ try {
+ final long time = SystemClock.uptimeMillis();
+ final boolean dictionaryFileExists = dictionaryFileExists();
+ if (mFilenameDictionaryUpdateController.isOutOfDate()
+ || !dictionaryFileExists) {
+ // If the shared dictionary file does not exist or is out of date, the
+ // first instance that acquires the lock will generate a new one.
+ if (hasContentChanged() || !dictionaryFileExists) {
+ // If the source content has changed or the dictionary does not exist,
+ // rebuild the binary dictionary. Empty dictionaries are supported (in
+ // the case where loadDictionaryAsync() adds nothing) in order to
+ // provide a uniform framework.
+ mFilenameDictionaryUpdateController.mLastUpdateTime = time;
+ writeBinaryDictionary();
+ loadBinaryDictionary();
+ } else {
+ // If not, the reload request was unnecessary so revert
+ // LastUpdateRequestTime to LastUpdateTime.
+ mFilenameDictionaryUpdateController.mLastUpdateRequestTime =
+ mFilenameDictionaryUpdateController.mLastUpdateTime;
+ }
+ } else if (mBinaryDictionary == null ||
+ mPerInstanceDictionaryUpdateController.mLastUpdateTime
+ < mFilenameDictionaryUpdateController.mLastUpdateTime) {
+ // Otherwise, if the local dictionary is older than the shared dictionary,
+ // load the shared dictionary.
+ loadBinaryDictionary();
+ }
+ if (mBinaryDictionary != null && !mBinaryDictionary.isValidDictionary()) {
+ // Binary dictionary is not valid. Regenerate the dictionary file.
+ mFilenameDictionaryUpdateController.mLastUpdateTime = time;
writeBinaryDictionary();
loadBinaryDictionary();
- } else {
- // If not, the reload request was unnecessary so revert
- // LastUpdateRequestTime to LastUpdateTime.
- mFilenameDictionaryTimeRecorder.mLastUpdateRequestTime =
- mFilenameDictionaryTimeRecorder.mLastUpdateTime;
}
- } else if (mBinaryDictionary == null ||
- mPerInstanceDictionaryTimeRecorder.mLastUpdateTime
- < mFilenameDictionaryTimeRecorder.mLastUpdateTime) {
- // Otherwise, if the local dictionary is older than the shared dictionary, load
- // the shared dictionary.
- loadBinaryDictionary();
- }
- if (mBinaryDictionary != null && !mBinaryDictionary.isValidDictionary()) {
- // Binary dictionary is not valid. Regenerate the dictionary file.
- mFilenameDictionaryTimeRecorder.mLastUpdateTime = time;
- writeBinaryDictionary();
- loadBinaryDictionary();
+ mPerInstanceDictionaryUpdateController.mLastUpdateTime = time;
+ } finally {
+ mFilenameDictionaryUpdateController.mIsRegenerating.set(false);
}
- mPerInstanceDictionaryTimeRecorder.mLastUpdateTime = time;
}
});
}
@@ -636,59 +648,19 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
/**
- * Time recorder for tracking whether the dictionary is out of date.
+ * For tracking whether the dictionary is out of date and the dictionary is regenerating.
* Can be shared across multiple dictionary instances that access the same filename.
*/
- private static class DictionaryTimeRecorder {
- private volatile long mLastUpdateTime = 0;
- private volatile long mLastUpdateRequestTime = 0;
+ private static class DictionaryUpdateController {
+ public volatile long mLastUpdateTime = 0;
+ public volatile long mLastUpdateRequestTime = 0;
+ public volatile AtomicBoolean mIsRegenerating = new AtomicBoolean();
- private boolean isOutOfDate() {
+ public boolean isOutOfDate() {
return (mLastUpdateRequestTime > mLastUpdateTime);
}
}
- /**
- * Dynamically adds a word unigram to the dictionary for testing with blocking-lock.
- */
- @UsedForTesting
- protected void addWordDynamicallyForTests(final String word, final String shortcutTarget,
- final int frequency, final boolean isNotAWord) {
- getExecutor(mFilename).executePrioritized(new Runnable() {
- @Override
- public void run() {
- addWordDynamically(word, shortcutTarget, frequency, isNotAWord);
- }
- });
- }
-
- /**
- * Dynamically adds a word bigram in the dictionary for testing with blocking-lock.
- */
- @UsedForTesting
- protected void addBigramDynamicallyForTests(final String word0, final String word1,
- final int frequency, final boolean isValid) {
- getExecutor(mFilename).executePrioritized(new Runnable() {
- @Override
- public void run() {
- addBigramDynamically(word0, word1, frequency, isValid);
- }
- });
- }
-
- /**
- * Dynamically remove a word bigram in the dictionary for testing with blocking-lock.
- */
- @UsedForTesting
- protected void removeBigramDynamicallyForTests(final String word0, final String word1) {
- getExecutor(mFilename).executePrioritized(new Runnable() {
- @Override
- public void run() {
- removeBigramDynamically(word0, word1);
- }
- });
- }
-
// TODO: Implement native binary methods once the dynamic dictionary implementation is done.
@UsedForTesting
public boolean isInDictionaryForTests(final String word) {
@@ -701,7 +673,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
holder.set(mBinaryDictionary.isValidWord(word));
} else {
holder.set(((DynamicPersonalizationDictionaryWriter) mDictionaryWriter)
- .isInDictionaryForTests(word));
+ .isInBigramListForTests(word));
}
}
}
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
index 21b103e5a..8caf6f17f 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -103,6 +103,10 @@ public final class InputAttributes {
}
}
+ public boolean isTypeNull() {
+ return InputType.TYPE_NULL == mInputType;
+ }
+
public boolean isSameInputType(final EditorInfo editorInfo) {
return editorInfo.inputType == mInputType;
}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 270dc4c06..96e16de0d 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -81,7 +81,7 @@ import com.android.inputmethod.latin.personalization.PersonalizationDictionary;
import com.android.inputmethod.latin.personalization.PersonalizationDictionarySessionRegister;
import com.android.inputmethod.latin.personalization.PersonalizationHelper;
import com.android.inputmethod.latin.personalization.PersonalizationPredictionDictionary;
-import com.android.inputmethod.latin.personalization.UserHistoryPredictionDictionary;
+import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.settings.SettingsActivity;
import com.android.inputmethod.latin.settings.SettingsValues;
@@ -179,7 +179,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private boolean mIsMainDictionaryAvailable;
private UserBinaryDictionary mUserDictionary;
- private UserHistoryPredictionDictionary mUserHistoryPredictionDictionary;
+ private UserHistoryDictionary mUserHistoryDictionary;
private PersonalizationPredictionDictionary mPersonalizationPredictionDictionary;
private PersonalizationDictionary mPersonalizationDictionary;
private boolean mIsUserDictionaryAvailable;
@@ -623,9 +623,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
- mUserHistoryPredictionDictionary = PersonalizationHelper
- .getUserHistoryPredictionDictionary(this, localeStr, prefs);
- newSuggest.setUserHistoryPredictionDictionary(mUserHistoryPredictionDictionary);
+ mUserHistoryDictionary = PersonalizationHelper.getUserHistoryDictionary(
+ this, localeStr, prefs);
+ newSuggest.setUserHistoryDictionary(mUserHistoryDictionary);
mPersonalizationDictionary = PersonalizationHelper
.getPersonalizationDictionary(this, localeStr, prefs);
newSuggest.setPersonalizationDictionary(mPersonalizationDictionary);
@@ -1521,7 +1521,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mSubtypeState.switchSubtype(token, mRichImm);
}
- private void sendDownUpKeyEventForBackwardCompatibility(final int code) {
+ private void sendDownUpKeyEvent(final int code) {
final long eventTime = SystemClock.uptimeMillis();
mConnection.sendKeyEvent(new KeyEvent(eventTime, eventTime,
KeyEvent.ACTION_DOWN, code, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
@@ -1538,7 +1538,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// TODO: Remove this special handling of digit letters.
// For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}.
if (code >= '0' && code <= '9') {
- sendDownUpKeyEventForBackwardCompatibility(code - '0' + KeyEvent.KEYCODE_0);
+ sendDownUpKeyEvent(code - '0' + KeyEvent.KEYCODE_0);
return;
}
@@ -1547,7 +1547,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// 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.
- sendDownUpKeyEventForBackwardCompatibility(KeyEvent.KEYCODE_ENTER);
+ sendDownUpKeyEvent(KeyEvent.KEYCODE_ENTER);
} else {
final String text = new String(new int[] { code }, 0, 1);
mConnection.commitText(text, text.length());
@@ -2104,12 +2104,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
final int lengthToDelete = Character.isSupplementaryCodePoint(
mConnection.getCodePointBeforeCursor()) ? 2 : 1;
- if (mAppWorkAroundsUtils.isBeforeJellyBean()) {
- // 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.
- sendDownUpKeyEventForBackwardCompatibility(KeyEvent.KEYCODE_DEL);
+ if (mAppWorkAroundsUtils.isBeforeJellyBean() ||
+ currentSettings.mInputAttributes.isTypeNull()) {
+ // There are two possible reasons to send a key event: either the field has
+ // type TYPE_NULL, in which case the keyboard should send events, or we are
+ // running in 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, so we retain this behavior if the app has target SDK < JellyBean.
+ sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);
} else {
mConnection.deleteSurroundingText(lengthToDelete, 0);
}
@@ -2553,6 +2557,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private void showSuggestionStripWithTypedWord(final SuggestedWords suggestedWords,
final String typedWord) {
if (suggestedWords.isEmpty()) {
+ // No auto-correction is available, clear the cached values.
+ AccessibilityUtils.getInstance().setAutoCorrection(null, null);
clearSuggestionStrip();
return;
}
@@ -2561,6 +2567,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
setSuggestedWords(suggestedWords, isAutoCorrection);
setAutoCorrectionIndicator(isAutoCorrection);
setSuggestionStripShown(isSuggestionsStripVisible());
+ // An auto-correction is available, cache it in accessibility code so
+ // we can be speak it if the user touches a key that will insert it.
+ AccessibilityUtils.getInstance().setAutoCorrection(suggestedWords, typedWord);
}
private void showSuggestionStrip(final SuggestedWords suggestedWords) {
@@ -2746,9 +2755,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final SettingsValues currentSettings = mSettings.getCurrent();
if (!currentSettings.mCorrectionEnabled) return null;
- final UserHistoryPredictionDictionary userHistoryPredictionDictionary =
- mUserHistoryPredictionDictionary;
- if (userHistoryPredictionDictionary == null) return null;
+ final UserHistoryDictionary userHistoryDictionary = mUserHistoryDictionary;
+ if (userHistoryDictionary == null) return null;
final String prevWord = mConnection.getNthPreviousWord(currentSettings.mWordSeparators, 2);
final String secondWord;
@@ -2762,8 +2770,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final int maxFreq = AutoCorrectionUtils.getMaxFrequency(
suggest.getUnigramDictionaries(), suggestion);
if (maxFreq == 0) return null;
- userHistoryPredictionDictionary
- .addToPersonalizationPredictionDictionary(prevWord, secondWord, maxFreq > 0);
+ userHistoryDictionary.addToDictionary(prevWord, secondWord, maxFreq > 0);
return prevWord;
}
@@ -2949,7 +2956,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
mConnection.deleteSurroundingText(deleteLength, 0);
if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) {
- mUserHistoryPredictionDictionary.cancelAddingUserHistory(previousWord, committedWord);
+ mUserHistoryDictionary.cancelAddingUserHistory(previousWord, committedWord);
}
final String stringToCommit = originallyTypedWord + mLastComposedWord.mSeparatorString;
if (mSettings.getCurrent().mCurrentLanguageHasSpaces) {
diff --git a/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java
new file mode 100644
index 000000000..68505ce38
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2013 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.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * This class provides binary dictionary reading operations with locking. An instance of this class
+ * can be used by multiple threads. Note that different session IDs must be used when multiple
+ * threads get suggestions using this class.
+ */
+public final class ReadOnlyBinaryDictionary extends Dictionary {
+ /**
+ * A lock for accessing binary dictionary. Only closing binary dictionary is the operation
+ * that change the state of dictionary.
+ */
+ private final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
+
+ private final BinaryDictionary mBinaryDictionary;
+
+ public ReadOnlyBinaryDictionary(final String filename, final long offset, final long length,
+ final boolean useFullEditDistance, final Locale locale, final String dictType) {
+ super(dictType);
+ mBinaryDictionary = new BinaryDictionary(filename, offset, length, useFullEditDistance,
+ locale, dictType, false /* isUpdatable */);
+ }
+
+ public boolean isValidDictionary() {
+ return mBinaryDictionary.isValidDictionary();
+ }
+
+ @Override
+ public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
+ final String prevWord, final ProximityInfo proximityInfo,
+ final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
+ return getSuggestionsWithSessionId(composer, prevWord, proximityInfo, blockOffensiveWords,
+ additionalFeaturesOptions, 0 /* sessionId */);
+ }
+
+ @Override
+ public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
+ final String prevWord, final ProximityInfo proximityInfo,
+ final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
+ final int sessionId) {
+ if (mLock.readLock().tryLock()) {
+ try {
+ return mBinaryDictionary.getSuggestions(composer, prevWord, proximityInfo,
+ blockOffensiveWords, additionalFeaturesOptions);
+ } finally {
+ mLock.readLock().unlock();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean isValidWord(final String word) {
+ if (mLock.readLock().tryLock()) {
+ try {
+ return mBinaryDictionary.isValidWord(word);
+ } finally {
+ mLock.readLock().unlock();
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean shouldAutoCommit(final SuggestedWordInfo candidate) {
+ if (mLock.readLock().tryLock()) {
+ try {
+ return mBinaryDictionary.shouldAutoCommit(candidate);
+ } finally {
+ mLock.readLock().unlock();
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public int getFrequency(final String word) {
+ if (mLock.readLock().tryLock()) {
+ try {
+ return mBinaryDictionary.getFrequency(word);
+ } finally {
+ mLock.readLock().unlock();
+ }
+ }
+ return NOT_A_PROBABILITY;
+ }
+
+ @Override
+ public void close() {
+ mLock.writeLock().lock();
+ try {
+ mBinaryDictionary.close();
+ } finally {
+ mLock.writeLock().unlock();
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 925381b50..8580a6e54 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -30,6 +30,7 @@ import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.settings.SettingsValues;
import com.android.inputmethod.latin.utils.CapsModeUtils;
import com.android.inputmethod.latin.utils.DebugLogUtils;
+import com.android.inputmethod.latin.utils.SpannableStringUtils;
import com.android.inputmethod.latin.utils.StringUtils;
import com.android.inputmethod.latin.utils.TextRange;
import com.android.inputmethod.research.ResearchLogger;
@@ -610,8 +611,9 @@ public final class RichInputConnection {
// We don't use TextUtils#concat because it copies all spans without respect to their
// nature. If the text includes a PARAGRAPH span and it has been split, then
// TextUtils#concat will crash when it tries to concat both sides of it.
- return new TextRange(StringUtils.concatWithNonParagraphSuggestionSpansOnly(before, after),
- startIndexInBefore, before.length() + endIndexInAfter, before.length());
+ return new TextRange(
+ SpannableStringUtils.concatWithNonParagraphSuggestionSpansOnly(before, after),
+ startIndexInBefore, before.length() + endIndexInAfter, before.length());
}
public boolean isCursorTouchingWord(final SettingsValues settingsValues) {
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 6c18c948f..9fd1f53a2 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -26,7 +26,7 @@ import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.personalization.PersonalizationDictionary;
import com.android.inputmethod.latin.personalization.PersonalizationPredictionDictionary;
-import com.android.inputmethod.latin.personalization.UserHistoryPredictionDictionary;
+import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
import com.android.inputmethod.latin.utils.BoundedTreeSet;
@@ -190,10 +190,8 @@ public final class Suggest {
addOrReplaceDictionaryInternal(Dictionary.TYPE_CONTACTS, contactsDictionary);
}
- public void setUserHistoryPredictionDictionary(
- final UserHistoryPredictionDictionary userHistoryPredictionDictionary) {
- addOrReplaceDictionaryInternal(Dictionary.TYPE_USER_HISTORY,
- userHistoryPredictionDictionary);
+ public void setUserHistoryDictionary(final UserHistoryDictionary userHistoryDictionary) {
+ addOrReplaceDictionaryInternal(Dictionary.TYPE_USER_HISTORY, userHistoryDictionary);
}
public void setPersonalizationPredictionDictionary(
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
index 4dba8e5cf..af61f2979 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
@@ -225,6 +225,26 @@ public class BinaryDictEncoderUtils {
return position;
}
+ static void writeUIntToStream(final OutputStream stream, final int value, final int size)
+ throws IOException {
+ switch(size) {
+ case 4:
+ stream.write((value >> 24) & 0xFF);
+ /* fall through */
+ case 3:
+ stream.write((value >> 16) & 0xFF);
+ /* fall through */
+ case 2:
+ stream.write((value >> 8) & 0xFF);
+ /* fall through */
+ case 1:
+ stream.write(value & 0xFF);
+ break;
+ default:
+ /* nop */
+ }
+ }
+
// End utility methods
// This method is responsible for finding a nice ordering of the nodes that favors run-time
@@ -365,12 +385,14 @@ public class BinaryDictEncoderUtils {
nodeSize + size, ptNode.mChildren));
}
nodeSize += getShortcutListSize(ptNode.mShortcutTargets);
- if (null != ptNode.mBigrams) {
- for (WeightedString bigram : ptNode.mBigrams) {
- final int offset = getOffsetToTargetPtNodeDuringUpdate(ptNodeArray,
- nodeSize + size + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE,
- FusionDictionary.findWordInTree(dict.mRootNodeArray, bigram.mWord));
- nodeSize += getByteSize(offset) + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE;
+ if (formatOptions.mVersion < FormatSpec.FIRST_VERSION_WITH_TERMINAL_ID) {
+ if (null != ptNode.mBigrams) {
+ for (WeightedString bigram : ptNode.mBigrams) {
+ final int offset = getOffsetToTargetPtNodeDuringUpdate(ptNodeArray,
+ nodeSize + size + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE,
+ FusionDictionary.findWordInTree(dict.mRootNodeArray, bigram.mWord));
+ nodeSize += getByteSize(offset) + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE;
+ }
}
}
ptNode.mCachedSize = nodeSize;
@@ -882,8 +904,9 @@ public class BinaryDictEncoderUtils {
* @param destination the stream to write the file header to.
* @param dict the dictionary to write.
* @param formatOptions file format options.
+ * @return the size of the header.
*/
- /* package */ static void writeDictionaryHeader(final OutputStream destination,
+ /* package */ static int writeDictionaryHeader(final OutputStream destination,
final FusionDictionary dict, final FormatOptions formatOptions)
throws IOException, UnsupportedFormatException {
final int version = formatOptions.mVersion;
@@ -932,5 +955,6 @@ public class BinaryDictEncoderUtils {
destination.write(bytes);
headerBuffer.close();
+ return size;
}
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java
index bf3d19101..411e265b3 100644
--- a/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java
@@ -77,7 +77,7 @@ public final class DynamicBinaryDictIOUtils {
* @param newParentAddress the absolute address of the parent.
* @param formatOptions file format options.
*/
- public static void updateParentAddress(final DictBuffer dictBuffer,
+ private static void updateParentAddress(final DictBuffer dictBuffer,
final int ptNodeOriginAddress, final int newParentAddress,
final FormatOptions formatOptions) {
final int originalPosition = dictBuffer.position();
@@ -109,7 +109,7 @@ public final class DynamicBinaryDictIOUtils {
* @param newParentAddress the address to be written.
* @param formatOptions file format options.
*/
- public static void updateParentAddresses(final DictBuffer dictBuffer,
+ private static void updateParentAddresses(final DictBuffer dictBuffer,
final int ptNodeOriginAddress, final int newParentAddress,
final FormatOptions formatOptions) {
final int originalPosition = dictBuffer.position();
@@ -136,7 +136,7 @@ public final class DynamicBinaryDictIOUtils {
* @param newChildrenAddress the absolute address of the child.
* @param formatOptions file format options.
*/
- public static void updateChildrenAddress(final DictBuffer dictBuffer,
+ private static void updateChildrenAddress(final DictBuffer dictBuffer,
final int ptNodeOriginAddress, final int newChildrenAddress,
final FormatOptions formatOptions) {
final int originalPosition = dictBuffer.position();
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index 51b89a02a..9481a8c14 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -263,7 +263,14 @@ public final class FormatSpec {
// These values are used only by version 4 or later.
static final String TRIE_FILE_EXTENSION = ".trie";
static final String FREQ_FILE_EXTENSION = ".freq";
+ // tat = Terminal Address Table
+ static final String TERMINAL_ADDRESS_TABLE_FILE_EXTENSION = ".tat";
+ static final String BIGRAM_FILE_EXTENSION = ".bigram";
+ static final String BIGRAM_LOOKUP_TABLE_FILE_EXTENSION = ".bigram_lookup";
+ static final String BIGRAM_ADDRESS_TABLE_FILE_EXTENSION = ".bigram_index";
static final int FREQUENCY_AND_FLAGS_SIZE = 2;
+ static final int TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE = 3;
+ static final int BIGRAM_ADDRESS_TABLE_BLOCK_SIZE = 4;
static final int NO_CHILDREN_ADDRESS = Integer.MIN_VALUE;
static final int NO_PARENT_ADDRESS = 0;
@@ -322,9 +329,15 @@ public final class FormatSpec {
public final int mHeaderSize;
public final DictionaryOptions mDictionaryOptions;
public final FormatOptions mFormatOptions;
- private static final String DICTIONARY_VERSION_ATTRIBUTE = "version";
- private static final String DICTIONARY_LOCALE_ATTRIBUTE = "locale";
- private static final String DICTIONARY_ID_ATTRIBUTE = "dictionary";
+ // Note that these are corresponding definitions in native code in latinime::HeaderPolicy
+ // and latinime::HeaderReadWriteUtils.
+ public static final String SUPPORTS_DYNAMIC_UPDATE_ATTRIBUTE = "SUPPORTS_DYNAMIC_UPDATE";
+ public static final String USES_FORGETTING_CURVE_ATTRIBUTE = "USES_FORGETTING_CURVE";
+ public static final String ATTRIBUTE_VALUE_TRUE = "1";
+
+ public static final String DICTIONARY_VERSION_ATTRIBUTE = "version";
+ public static final String DICTIONARY_LOCALE_ATTRIBUTE = "locale";
+ public static final String DICTIONARY_ID_ATTRIBUTE = "dictionary";
private static final String DICTIONARY_DESCRIPTION_ATTRIBUTE = "description";
public FileHeader(final int headerSize, final DictionaryOptions dictionaryOptions,
final FormatOptions formatOptions) {
diff --git a/java/src/com/android/inputmethod/latin/makedict/SparseTable.java b/java/src/com/android/inputmethod/latin/makedict/SparseTable.java
new file mode 100644
index 000000000..96d057a44
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/SparseTable.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2013 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.makedict;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * SparseTable is an extensible map from integer to integer.
+ * This holds one value for every mBlockSize keys, so it uses 1/mBlockSize'th of the full index
+ * memory.
+ */
+@UsedForTesting
+public class SparseTable {
+
+ /**
+ * mLookupTable is indexed by terminal ID, containing exactly one entry for every mBlockSize
+ * terminals.
+ * It contains at index i = j / mBlockSize the index in mContentsTable where the values for
+ * terminals with IDs j to j + mBlockSize - 1 are stored as an mBlockSize-sized integer array.
+ */
+ private final ArrayList<Integer> mLookupTable;
+ private final ArrayList<Integer> mContentTable;
+
+ private final int mBlockSize;
+ public static final int NOT_EXIST = -1;
+
+ @UsedForTesting
+ public SparseTable(final int initialCapacity, final int blockSize) {
+ mBlockSize = blockSize;
+ final int lookupTableSize = initialCapacity / mBlockSize
+ + (initialCapacity % mBlockSize > 0 ? 1 : 0);
+ mLookupTable = new ArrayList<Integer>(Collections.nCopies(lookupTableSize, NOT_EXIST));
+ mContentTable = new ArrayList<Integer>();
+ }
+
+ @UsedForTesting
+ public SparseTable(final int[] lookupTable, final int[] contentTable, final int blockSize) {
+ mBlockSize = blockSize;
+ mLookupTable = new ArrayList<Integer>(lookupTable.length);
+ for (int i = 0; i < lookupTable.length; ++i) {
+ mLookupTable.add(lookupTable[i]);
+ }
+ mContentTable = new ArrayList<Integer>(contentTable.length);
+ for (int i = 0; i < contentTable.length; ++i) {
+ mContentTable.add(contentTable[i]);
+ }
+ }
+
+ /**
+ * Converts an byte array to an int array considering each set of 4 bytes is an int stored in
+ * big-endian.
+ * The length of byteArray must be a multiple of four.
+ * Otherwise, IndexOutOfBoundsException will be raised.
+ */
+ @UsedForTesting
+ private static void convertByteArrayToIntegerArray(final byte[] byteArray,
+ final ArrayList<Integer> integerArray) {
+ for (int i = 0; i < byteArray.length; i += 4) {
+ int value = 0;
+ for (int j = i; j < i + 4; ++j) {
+ value <<= 8;
+ value |= byteArray[j] & 0xFF;
+ }
+ integerArray.add(value);
+ }
+ }
+
+ @UsedForTesting
+ public SparseTable(final byte[] lookupTable, final byte[] contentTable, final int blockSize) {
+ mBlockSize = blockSize;
+ mLookupTable = new ArrayList<Integer>(lookupTable.length / 4);
+ mContentTable = new ArrayList<Integer>(contentTable.length / 4);
+ convertByteArrayToIntegerArray(lookupTable, mLookupTable);
+ convertByteArrayToIntegerArray(contentTable, mContentTable);
+ }
+
+ @UsedForTesting
+ public int get(final int index) {
+ if (index < 0 || index / mBlockSize >= mLookupTable.size()
+ || mLookupTable.get(index / mBlockSize) == NOT_EXIST) {
+ return NOT_EXIST;
+ }
+ return mContentTable.get(mLookupTable.get(index / mBlockSize) + (index % mBlockSize));
+ }
+
+ @UsedForTesting
+ public void set(final int index, final int value) {
+ if (mLookupTable.get(index / mBlockSize) == NOT_EXIST) {
+ mLookupTable.set(index / mBlockSize, mContentTable.size());
+ for (int i = 0; i < mBlockSize; ++i) {
+ mContentTable.add(NOT_EXIST);
+ }
+ }
+ mContentTable.set(mLookupTable.get(index / mBlockSize) + (index % mBlockSize), value);
+ }
+
+ public void remove(final int index) {
+ set(index, NOT_EXIST);
+ }
+
+ @UsedForTesting
+ public int size() {
+ return mLookupTable.size() * mBlockSize;
+ }
+
+ @UsedForTesting
+ /* package */ int getContentTableSize() {
+ return mContentTable.size();
+ }
+
+ @UsedForTesting
+ /* package */ int getLookupTableSize() {
+ return mLookupTable.size();
+ }
+
+ public boolean contains(final int index) {
+ return get(index) != NOT_EXIST;
+ }
+
+ @UsedForTesting
+ public void write(final OutputStream lookupOutStream, final OutputStream contentOutStream)
+ throws IOException {
+ for (final int index : mLookupTable) {
+ BinaryDictEncoderUtils.writeUIntToStream(lookupOutStream, index, 4);
+ }
+
+ for (final int index : mContentTable) {
+ BinaryDictEncoderUtils.writeUIntToStream(contentOutStream, index, 4);
+ }
+ }
+
+ @UsedForTesting
+ public void writeToFiles(final File lookupTableFile, final File contentFile)
+ throws IOException {
+ FileOutputStream lookupTableOutStream = null;
+ FileOutputStream contentOutStream = null;
+ try {
+ lookupTableOutStream = new FileOutputStream(lookupTableFile);
+ contentOutStream = new FileOutputStream(contentFile);
+ write(lookupTableOutStream, contentOutStream);
+ } finally {
+ if (lookupTableOutStream != null) {
+ lookupTableOutStream.close();
+ }
+ if (contentOutStream != null) {
+ contentOutStream.close();
+ }
+ }
+ }
+
+ private static byte[] readFileToByteArray(final File file) throws IOException {
+ final byte[] contents = new byte[(int) file.length()];
+ FileInputStream inStream = null;
+ try {
+ inStream = new FileInputStream(file);
+ inStream.read(contents);
+ } finally {
+ if (inStream != null) {
+ inStream.close();
+ }
+ }
+ return contents;
+ }
+
+ @UsedForTesting
+ public static SparseTable readFromFiles(final File lookupTableFile, final File contentFile,
+ final int blockSize) throws IOException {
+ final byte[] lookupTable = readFileToByteArray(lookupTableFile);
+ final byte[] content = readFileToByteArray(contentFile);
+ return new SparseTable(lookupTable, content, blockSize);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java
index 36c5a2720..0aa431966 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java
@@ -41,11 +41,16 @@ public class Ver4DictDecoder extends DictDecoder {
private static final int FILETYPE_TRIE = 1;
private static final int FILETYPE_FREQUENCY = 2;
+ private static final int FILETYPE_TERMINAL_ADDRESS_TABLE = 3;
+ private static final int FILETYPE_BIGRAM = 4;
private final File mDictDirectory;
private final DictionaryBufferFactory mBufferFactory;
private DictBuffer mDictBuffer;
private DictBuffer mFrequencyBuffer;
+ private DictBuffer mTerminalAddressTableBuffer;
+ private DictBuffer mBigramBuffer;
+ private SparseTable mBigramAddressTable;
@UsedForTesting
/* package */ Ver4DictDecoder(final File dictDirectory, final int factoryFlag) {
@@ -77,6 +82,12 @@ public class Ver4DictDecoder extends DictDecoder {
} else if (fileType == FILETYPE_FREQUENCY) {
return new File(mDictDirectory,
mDictDirectory.getName() + FormatSpec.FREQ_FILE_EXTENSION);
+ } else if (fileType == FILETYPE_TERMINAL_ADDRESS_TABLE) {
+ return new File(mDictDirectory,
+ mDictDirectory.getName() + FormatSpec.TERMINAL_ADDRESS_TABLE_FILE_EXTENSION);
+ } else if (fileType == FILETYPE_BIGRAM) {
+ return new File(mDictDirectory,
+ mDictDirectory.getName() + FormatSpec.BIGRAM_FILE_EXTENSION);
} else {
throw new RuntimeException("Unsupported kind of file : " + fileType);
}
@@ -87,6 +98,10 @@ public class Ver4DictDecoder extends DictDecoder {
final String filename = mDictDirectory.getName();
mDictBuffer = mBufferFactory.getDictionaryBuffer(getFile(FILETYPE_TRIE));
mFrequencyBuffer = mBufferFactory.getDictionaryBuffer(getFile(FILETYPE_FREQUENCY));
+ mTerminalAddressTableBuffer = mBufferFactory.getDictionaryBuffer(
+ getFile(FILETYPE_TERMINAL_ADDRESS_TABLE));
+ mBigramBuffer = mBufferFactory.getDictionaryBuffer(getFile(FILETYPE_BIGRAM));
+ loadBigramAddressSparseTable();
}
@Override
@@ -111,6 +126,15 @@ public class Ver4DictDecoder extends DictDecoder {
return header;
}
+ private void loadBigramAddressSparseTable() throws IOException {
+ final File lookupIndexFile = new File(mDictDirectory,
+ mDictDirectory.getName() + FormatSpec.BIGRAM_LOOKUP_TABLE_FILE_EXTENSION);
+ final File contentFile = new File(mDictDirectory,
+ mDictDirectory.getName() + FormatSpec.BIGRAM_ADDRESS_TABLE_FILE_EXTENSION);
+ mBigramAddressTable = SparseTable.readFromFiles(lookupIndexFile, contentFile,
+ FormatSpec.BIGRAM_ADDRESS_TABLE_BLOCK_SIZE);
+ }
+
protected static class PtNodeReader extends DictDecoder.PtNodeReader {
protected static int readFrequency(final DictBuffer frequencyBuffer, final int terminalId) {
frequencyBuffer.position(terminalId * FormatSpec.FREQUENCY_AND_FLAGS_SIZE + 1);
@@ -184,8 +208,21 @@ public class Ver4DictDecoder extends DictDecoder {
final ArrayList<PendingAttribute> bigrams;
if (0 != (flags & FormatSpec.FLAG_HAS_BIGRAMS)) {
bigrams = new ArrayList<PendingAttribute>();
- addressPointer += PtNodeReader.readBigramAddresses(mDictBuffer, bigrams,
- addressPointer);
+ final int posOfBigrams = mBigramAddressTable.get(terminalId);
+ mBigramBuffer.position(posOfBigrams);
+ while (bigrams.size() < FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
+ // If bigrams.size() reaches FormatSpec.MAX_BIGRAMS_IN_A_PTNODE,
+ // remaining bigram entries are ignored.
+ final int bigramFlags = mBigramBuffer.readUnsignedByte();
+ final int targetTerminalId = mBigramBuffer.readUnsignedInt24();
+ mTerminalAddressTableBuffer.position(
+ targetTerminalId * FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE);
+ final int targetAddress = mTerminalAddressTableBuffer.readUnsignedInt24();
+ bigrams.add(new PendingAttribute(
+ bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY,
+ targetAddress));
+ if (0 == (bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) break;
+ }
if (bigrams.size() >= FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
MakedictLog.d("too many bigrams in a node.");
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
index 75b75ae2e..4c25faf88 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
@@ -26,6 +26,7 @@ import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
@@ -41,10 +42,15 @@ import java.util.Iterator;
public class Ver4DictEncoder implements DictEncoder {
private final File mDictPlacedDir;
private byte[] mTrieBuf;
- private byte[] mFreqBuf;
private int mTriePos;
+ private int mHeaderSize;
+ private SparseTable mBigramAddressTable;
private OutputStream mTrieOutStream;
private OutputStream mFreqOutStream;
+ private OutputStream mTerminalAddressTableOutStream;
+ private OutputStream mBigramOutStream;
+ private File mDictDir;
+ private String mBaseFilename;
@UsedForTesting
public Ver4DictEncoder(final File dictPlacedDir) {
@@ -54,18 +60,25 @@ public class Ver4DictEncoder implements DictEncoder {
private void openStreams(final FormatOptions formatOptions, final DictionaryOptions dictOptions)
throws FileNotFoundException, IOException {
final FileHeader header = new FileHeader(0, dictOptions, formatOptions);
- final String filename = header.getId() + "." + header.getVersion();
- final File mDictDir = new File(mDictPlacedDir, filename);
- final File trieFile = new File(mDictDir, filename + FormatSpec.TRIE_FILE_EXTENSION);
- final File freqFile = new File(mDictDir, filename + FormatSpec.FREQ_FILE_EXTENSION);
+ mBaseFilename = header.getId() + "." + header.getVersion();
+ mDictDir = new File(mDictPlacedDir, mBaseFilename);
+ final File trieFile = new File(mDictDir, mBaseFilename + FormatSpec.TRIE_FILE_EXTENSION);
+ final File freqFile = new File(mDictDir, mBaseFilename + FormatSpec.FREQ_FILE_EXTENSION);
+ final File terminalAddressTableFile = new File(mDictDir,
+ mBaseFilename + FormatSpec.TERMINAL_ADDRESS_TABLE_FILE_EXTENSION);
+ final File bigramFile = new File(mDictDir,
+ mBaseFilename + FormatSpec.BIGRAM_FILE_EXTENSION);
if (!mDictDir.isDirectory()) {
if (mDictDir.exists()) mDictDir.delete();
mDictDir.mkdirs();
}
if (!trieFile.exists()) trieFile.createNewFile();
if (!freqFile.exists()) freqFile.createNewFile();
+ if (!terminalAddressTableFile.exists()) terminalAddressTableFile.createNewFile();
mTrieOutStream = new FileOutputStream(trieFile);
mFreqOutStream = new FileOutputStream(freqFile);
+ mTerminalAddressTableOutStream = new FileOutputStream(terminalAddressTableFile);
+ mBigramOutStream = new FileOutputStream(bigramFile);
}
private void close() throws IOException {
@@ -76,9 +89,17 @@ public class Ver4DictEncoder implements DictEncoder {
if (mFreqOutStream != null) {
mFreqOutStream.close();
}
+ if (mTerminalAddressTableOutStream != null) {
+ mTerminalAddressTableOutStream.close();
+ }
+ if (mBigramOutStream != null) {
+ mBigramOutStream.close();
+ }
} finally {
mTrieOutStream = null;
mFreqOutStream = null;
+ mTerminalAddressTableOutStream = null;
+ mBigramOutStream = null;
}
}
@@ -97,7 +118,8 @@ public class Ver4DictEncoder implements DictEncoder {
openStreams(formatOptions, dict.mOptions);
}
- BinaryDictEncoderUtils.writeDictionaryHeader(mTrieOutStream, dict, formatOptions);
+ mHeaderSize = BinaryDictEncoderUtils.writeDictionaryHeader(mTrieOutStream, dict,
+ formatOptions);
MakedictLog.i("Flattening the tree...");
ArrayList<PtNodeArray> flatNodes = BinaryDictEncoderUtils.flattenTree(dict.mRootNodeArray);
@@ -112,10 +134,15 @@ public class Ver4DictEncoder implements DictEncoder {
BinaryDictEncoderUtils.computeAddresses(dict, flatNodes, formatOptions);
if (MakedictLog.DBG) BinaryDictEncoderUtils.checkFlatPtNodeArrayList(flatNodes);
+ writeTerminalData(flatNodes, terminalCount);
+ mBigramAddressTable = new SparseTable(terminalCount,
+ FormatSpec.BIGRAM_ADDRESS_TABLE_BLOCK_SIZE);
+ writeBigrams(flatNodes, dict);
+ writeBigramAddressSparseTable();
+
final PtNodeArray lastNodeArray = flatNodes.get(flatNodes.size() - 1);
final int bufferSize = lastNodeArray.mCachedAddressAfterUpdate + lastNodeArray.mCachedSize;
mTrieBuf = new byte[bufferSize];
- mFreqBuf = new byte[terminalCount * FormatSpec.FREQUENCY_AND_FLAGS_SIZE];
MakedictLog.i("Writing file...");
for (PtNodeArray nodeArray : flatNodes) {
@@ -126,7 +153,6 @@ public class Ver4DictEncoder implements DictEncoder {
MakedictLog.i("has " + terminalCount + " terminals.");
}
mTrieOutStream.write(mTrieBuf);
- mFreqOutStream.write(mFreqBuf);
MakedictLog.i("Done");
close();
@@ -185,12 +211,6 @@ public class Ver4DictEncoder implements DictEncoder {
FormatSpec.PTNODE_TERMINAL_ID_SIZE);
}
- private void writeFrequency(final int frequency, final int terminalId) {
- final int freqPos = terminalId * FormatSpec.FREQUENCY_AND_FLAGS_SIZE;
- BinaryDictEncoderUtils.writeUIntToBuffer(mFreqBuf, freqPos, frequency,
- FormatSpec.FREQUENCY_AND_FLAGS_SIZE);
- }
-
private void writeChildrenPosition(PtNode ptNode, FormatOptions formatOptions) {
final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode, formatOptions);
if (formatOptions.mSupportsDynamicUpdate) {
@@ -226,24 +246,41 @@ public class Ver4DictEncoder implements DictEncoder {
shortcutByteSize, FormatSpec.PTNODE_SHORTCUT_LIST_SIZE_SIZE);
}
- private void writeBigrams(ArrayList<WeightedString> bigrams, FusionDictionary dict) {
- if (bigrams == null) return;
-
- final Iterator<WeightedString> bigramIterator = bigrams.iterator();
- while (bigramIterator.hasNext()) {
- final WeightedString bigram = bigramIterator.next();
- final PtNode target =
- FusionDictionary.findWordInTree(dict.mRootNodeArray, bigram.mWord);
- final int addressOfBigram = target.mCachedAddressAfterUpdate;
- final int unigramFrequencyForThisWord = target.mFrequency;
- final int offset = addressOfBigram
- - (mTriePos + FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE);
- int bigramFlags = BinaryDictEncoderUtils.makeBigramFlags(bigramIterator.hasNext(),
- offset, bigram.mFrequency, unigramFrequencyForThisWord, bigram.mWord);
- mTrieBuf[mTriePos++] = (byte) bigramFlags;
- mTriePos += BinaryDictEncoderUtils.writeChildrenPosition(mTrieBuf,
- mTriePos, Math.abs(offset));
+ private void writeBigrams(final ArrayList<PtNodeArray> flatNodes, final FusionDictionary dict)
+ throws IOException {
+ final ByteArrayOutputStream bigramBuffer = new ByteArrayOutputStream();
+
+ for (final PtNodeArray nodeArray : flatNodes) {
+ for (final PtNode ptNode : nodeArray.mData) {
+ if (ptNode.mBigrams != null) {
+ final int startPos = bigramBuffer.size();
+ mBigramAddressTable.set(ptNode.mTerminalId, startPos);
+ final Iterator<WeightedString> bigramIterator = ptNode.mBigrams.iterator();
+ while (bigramIterator.hasNext()) {
+ final WeightedString bigram = bigramIterator.next();
+ final PtNode target =
+ FusionDictionary.findWordInTree(dict.mRootNodeArray, bigram.mWord);
+ final int unigramFrequencyForThisWord = target.mFrequency;
+ final int bigramFlags = BinaryDictEncoderUtils.makeBigramFlags(
+ bigramIterator.hasNext(), 0, bigram.mFrequency,
+ unigramFrequencyForThisWord, bigram.mWord);
+ BinaryDictEncoderUtils.writeUIntToStream(bigramBuffer, bigramFlags,
+ FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE);
+ BinaryDictEncoderUtils.writeUIntToStream(bigramBuffer, target.mTerminalId,
+ FormatSpec.PTNODE_ATTRIBUTE_MAX_ADDRESS_SIZE);
+ }
+ }
+ }
}
+ bigramBuffer.writeTo(mBigramOutStream);
+ }
+
+ private void writeBigramAddressSparseTable() throws IOException {
+ final File lookupIndexFile =
+ new File(mDictDir, mBaseFilename + FormatSpec.BIGRAM_LOOKUP_TABLE_FILE_EXTENSION);
+ final File contentFile =
+ new File(mDictDir, mBaseFilename + FormatSpec.BIGRAM_ADDRESS_TABLE_FILE_EXTENSION);
+ mBigramAddressTable.writeToFiles(lookupIndexFile, contentFile);
}
@Override
@@ -260,10 +297,30 @@ public class Ver4DictEncoder implements DictEncoder {
writeCharacters(ptNode.mChars, ptNode.hasSeveralChars());
if (ptNode.isTerminal()) {
writeTerminalId(ptNode.mTerminalId);
- writeFrequency(ptNode.mFrequency, ptNode.mTerminalId);
}
writeChildrenPosition(ptNode, formatOptions);
writeShortcuts(ptNode.mShortcutTargets);
- writeBigrams(ptNode.mBigrams, dict);
+ }
+
+ private void writeTerminalData(final ArrayList<PtNodeArray> flatNodes,
+ final int terminalCount) throws IOException {
+ final byte[] freqBuf = new byte[terminalCount * FormatSpec.FREQUENCY_AND_FLAGS_SIZE];
+ final byte[] terminalAddressTableBuf =
+ new byte[terminalCount * FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE];
+ for (final PtNodeArray nodeArray : flatNodes) {
+ for (final PtNode ptNode : nodeArray.mData) {
+ if (ptNode.isTerminal()) {
+ BinaryDictEncoderUtils.writeUIntToBuffer(freqBuf,
+ ptNode.mTerminalId * FormatSpec.FREQUENCY_AND_FLAGS_SIZE,
+ ptNode.mFrequency, FormatSpec.FREQUENCY_AND_FLAGS_SIZE);
+ BinaryDictEncoderUtils.writeUIntToBuffer(terminalAddressTableBuf,
+ ptNode.mTerminalId * FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE,
+ ptNode.mCachedAddressAfterUpdate + mHeaderSize,
+ FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE);
+ }
+ }
+ }
+ mFreqOutStream.write(freqBuf);
+ mTerminalAddressTableOutStream.write(terminalAddressTableBuf);
}
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
index 075d7e3c3..7cf4f0c88 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
@@ -22,6 +22,7 @@ import android.util.Log;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.ExpandableBinaryDictionary;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.makedict.DictDecoder;
@@ -34,12 +35,15 @@ import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils.OnAddWordListe
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
/**
- * This class is a base class of a dictionary for the personalized prediction language model.
+ * This class is a base class of a dictionary that supports decaying for the personalized language
+ * model.
*/
-public abstract class DynamicPredictionDictionaryBase extends ExpandableBinaryDictionary {
- private static final String TAG = DynamicPredictionDictionaryBase.class.getSimpleName();
+public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableBinaryDictionary {
+ private static final String TAG = DecayingExpandableBinaryDictionaryBase.class.getSimpleName();
public static final boolean DBG_SAVE_RESTORE = false;
private static final boolean DBG_STRESS_TEST = false;
private static final boolean PROFILE_SAVE_RESTORE = LatinImeLogger.sDBG;
@@ -47,6 +51,9 @@ public abstract class DynamicPredictionDictionaryBase extends ExpandableBinaryDi
/** Any pair being typed or picked */
public static final int FREQUENCY_FOR_TYPED = 2;
+ public static final int FREQUENCY_FOR_WORDS_IN_DICTS = FREQUENCY_FOR_TYPED;
+ public static final int FREQUENCY_FOR_WORDS_NOT_IN_DICTS = Dictionary.NOT_A_PROBABILITY;
+
/** Locale for which this user history dictionary is storing words */
private final String mLocale;
@@ -60,8 +67,9 @@ public abstract class DynamicPredictionDictionaryBase extends ExpandableBinaryDi
// Should always be false except when we use this class for test
@UsedForTesting boolean mIsTest = false;
- /* package */ DynamicPredictionDictionaryBase(final Context context, final String locale,
- final SharedPreferences sp, final String dictionaryType, final String fileName) {
+ /* package */ DecayingExpandableBinaryDictionaryBase(final Context context,
+ final String locale, final SharedPreferences sp, final String dictionaryType,
+ final String fileName) {
super(context, fileName, dictionaryType, true);
mLocale = locale;
mFileName = fileName;
@@ -84,6 +92,18 @@ public abstract class DynamicPredictionDictionaryBase extends ExpandableBinaryDi
}
@Override
+ protected Map<String, String> getHeaderAttributeMap() {
+ HashMap<String, String> attributeMap = new HashMap<String, String>();
+ attributeMap.put(FormatSpec.FileHeader.SUPPORTS_DYNAMIC_UPDATE_ATTRIBUTE,
+ FormatSpec.FileHeader.ATTRIBUTE_VALUE_TRUE);
+ attributeMap.put(FormatSpec.FileHeader.USES_FORGETTING_CURVE_ATTRIBUTE,
+ FormatSpec.FileHeader.ATTRIBUTE_VALUE_TRUE);
+ attributeMap.put(FormatSpec.FileHeader.DICTIONARY_ID_ATTRIBUTE, mFileName);
+ attributeMap.put(FormatSpec.FileHeader.DICTIONARY_LOCALE_ATTRIBUTE, mLocale);
+ return attributeMap;
+ }
+
+ @Override
protected boolean hasContentChanged() {
return false;
}
@@ -103,27 +123,29 @@ public abstract class DynamicPredictionDictionaryBase extends ExpandableBinaryDi
}
/**
- * Pair will be added to the personalization prediction dictionary.
+ * Pair will be added to the decaying dictionary.
*
* The first word may be null. That means we don't know the context, in other words,
* it's only a unigram. The first word may also be an empty string : this means start
* context, as in beginning of a sentence for example.
* The second word may not be null (a NullPointerException would be thrown).
*/
- public void addToPersonalizationPredictionDictionary(
- final String word0, final String word1, final boolean isValid) {
+ public void addToDictionary(final String word0, final String word1, final boolean isValid) {
if (word1.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH ||
(word0 != null && word0.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH)) {
return;
}
- addWordDynamically(word1, null /* the "shortcut" parameter is null */, FREQUENCY_FOR_TYPED,
+ final int frequency = ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE ?
+ (isValid ? FREQUENCY_FOR_WORDS_IN_DICTS : FREQUENCY_FOR_WORDS_NOT_IN_DICTS) :
+ FREQUENCY_FOR_TYPED;
+ addWordDynamically(word1, null /* the "shortcut" parameter is null */, frequency,
false /* isNotAWord */);
// Do not insert a word as a bigram of itself
if (word1.equals(word0)) {
return;
}
if (null != word0) {
- addBigramDynamically(word0, word1, FREQUENCY_FOR_TYPED, isValid);
+ addBigramDynamically(word0, word1, frequency, isValid);
}
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java b/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java
index 0af028a9e..039b25337 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java
@@ -36,6 +36,7 @@ import com.android.inputmethod.latin.utils.UserHistoryForgettingCurveUtils.Forge
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Map;
// Currently this class is used to implement dynamic prodiction dictionary.
// TODO: Move to native code.
@@ -79,7 +80,7 @@ public class DynamicPersonalizationDictionaryWriter extends AbstractDictionaryWr
public void addUnigramWord(final String word, final String shortcutTarget, final int frequency,
final boolean isNotAWord) {
if (mBigramList.size() > mMaxHistoryBigrams * 2) {
- // Too many entries: just stop adding new vocabrary and wait next refresh.
+ // Too many entries: just stop adding new vocabulary and wait next refresh.
return;
}
mExpandableDictionary.addWord(word, shortcutTarget, frequency);
@@ -90,7 +91,7 @@ public class DynamicPersonalizationDictionaryWriter extends AbstractDictionaryWr
public void addBigramWords(final String word0, final String word1, final int frequency,
final boolean isValid, final long lastModifiedTime) {
if (mBigramList.size() > mMaxHistoryBigrams * 2) {
- // Too many entries: just stop adding new vocabrary and wait next refresh.
+ // Too many entries: just stop adding new vocabulary and wait next refresh.
return;
}
if (lastModifiedTime > 0) {
@@ -113,8 +114,8 @@ public class DynamicPersonalizationDictionaryWriter extends AbstractDictionaryWr
}
@Override
- protected void writeDictionary(final DictEncoder dictEncoder)
- throws IOException, UnsupportedFormatException {
+ protected void writeDictionary(final DictEncoder dictEncoder,
+ final Map<String, String> attributeMap) throws IOException, UnsupportedFormatException {
UserHistoryDictIOUtils.writeDictionary(dictEncoder,
new FrequencyProvider(mBigramList, mExpandableDictionary, mMaxHistoryBigrams),
mBigramList, FORMAT_OPTIONS);
@@ -176,8 +177,8 @@ public class DynamicPersonalizationDictionaryWriter extends AbstractDictionaryWr
}
@UsedForTesting
- public boolean isInDictionaryForTests(final String word) {
+ public boolean isInBigramListForTests(final String word) {
// TODO: Use native method to determine whether the word is in dictionary or not
- return mBigramList.containsKey(word);
+ return mBigramList.containsKey(word) || mBigramList.getBigrams(null).containsKey(word);
}
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java
index ab3de801c..a86f6e584 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java
@@ -46,7 +46,7 @@ public abstract class PersonalizationDictionaryUpdateSession {
// TODO: Use a dynamic binary dictionary instead
public WeakReference<PersonalizationDictionary> mDictionary;
- public WeakReference<DynamicPredictionDictionaryBase> mPredictionDictionary;
+ public WeakReference<DecayingExpandableBinaryDictionaryBase> mPredictionDictionary;
public final String mSystemLocale;
public PersonalizationDictionaryUpdateSession(String locale) {
mSystemLocale = locale;
@@ -60,15 +60,16 @@ public abstract class PersonalizationDictionaryUpdateSession {
mDictionary = new WeakReference<PersonalizationDictionary>(dictionary);
}
- public void setPredictionDictionary(DynamicPredictionDictionaryBase dictionary) {
- mPredictionDictionary = new WeakReference<DynamicPredictionDictionaryBase>(dictionary);
+ public void setPredictionDictionary(DecayingExpandableBinaryDictionaryBase dictionary) {
+ mPredictionDictionary =
+ new WeakReference<DecayingExpandableBinaryDictionaryBase>(dictionary);
}
protected PersonalizationDictionary getDictionary() {
return mDictionary == null ? null : mDictionary.get();
}
- protected DynamicPredictionDictionaryBase getPredictionDictionary() {
+ protected DecayingExpandableBinaryDictionaryBase getPredictionDictionary() {
return mPredictionDictionary == null ? null : mPredictionDictionary.get();
}
@@ -81,7 +82,7 @@ public abstract class PersonalizationDictionaryUpdateSession {
}
private void unsetPredictionDictionary() {
- final DynamicPredictionDictionaryBase dictionary = getPredictionDictionary();
+ final DecayingExpandableBinaryDictionaryBase dictionary = getPredictionDictionary();
if (dictionary == null) {
return;
}
@@ -89,7 +90,7 @@ public abstract class PersonalizationDictionaryUpdateSession {
}
public void clearAndFlushPredictionDictionary(Context context) {
- final DynamicPredictionDictionaryBase dictionary = getPredictionDictionary();
+ final DecayingExpandableBinaryDictionaryBase dictionary = getPredictionDictionary();
if (dictionary == null) {
return;
}
@@ -105,24 +106,23 @@ public abstract class PersonalizationDictionaryUpdateSession {
// TODO: Support multi locale to add bigram
public void addBigramToPersonalizationDictionary(String word0, String word1, boolean isValid,
int frequency) {
- final DynamicPredictionDictionaryBase dictionary = getPredictionDictionary();
+ final DecayingExpandableBinaryDictionaryBase dictionary = getPredictionDictionary();
if (dictionary == null) {
return;
}
- dictionary.addToPersonalizationPredictionDictionary(word0, word1, isValid);
+ dictionary.addToDictionary(word0, word1, isValid);
}
// Bulk import
// TODO: Support multi locale to add bigram
public void addBigramsToPersonalizationDictionary(
final ArrayList<PersonalizationLanguageModelParam> lmParams) {
- final DynamicPredictionDictionaryBase dictionary = getPredictionDictionary();
+ final DecayingExpandableBinaryDictionaryBase dictionary = getPredictionDictionary();
if (dictionary == null) {
return;
}
for (final PersonalizationLanguageModelParam lmParam : lmParams) {
- dictionary.addToPersonalizationPredictionDictionary(
- lmParam.mWord0, lmParam.mWord1, lmParam.mIsValid);
+ dictionary.addToDictionary(lmParam.mWord0, lmParam.mWord1, lmParam.mIsValid);
}
}
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
index 5f702ee3f..8c9484b12 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
@@ -30,7 +30,7 @@ public class PersonalizationHelper {
private static final String TAG = PersonalizationHelper.class.getSimpleName();
private static final boolean DEBUG = false;
- private static final ConcurrentHashMap<String, SoftReference<UserHistoryPredictionDictionary>>
+ private static final ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>
sLangUserHistoryDictCache = CollectionUtils.newConcurrentHashMap();
private static final ConcurrentHashMap<String, SoftReference<PersonalizationDictionary>>
@@ -41,25 +41,23 @@ public class PersonalizationHelper {
sLangPersonalizationPredictionDictCache =
CollectionUtils.newConcurrentHashMap();
- public static UserHistoryPredictionDictionary getUserHistoryPredictionDictionary(
+ public static UserHistoryDictionary getUserHistoryDictionary(
final Context context, final String locale, final SharedPreferences sp) {
synchronized (sLangUserHistoryDictCache) {
if (sLangUserHistoryDictCache.containsKey(locale)) {
- final SoftReference<UserHistoryPredictionDictionary> ref =
+ final SoftReference<UserHistoryDictionary> ref =
sLangUserHistoryDictCache.get(locale);
- final UserHistoryPredictionDictionary dict = ref == null ? null : ref.get();
+ final UserHistoryDictionary dict = ref == null ? null : ref.get();
if (dict != null) {
if (DEBUG) {
- Log.w(TAG, "Use cached UserHistoryPredictionDictionary for " + locale);
+ Log.w(TAG, "Use cached UserHistoryDictionary for " + locale);
}
dict.reloadDictionaryIfRequired();
return dict;
}
}
- final UserHistoryPredictionDictionary dict =
- new UserHistoryPredictionDictionary(context, locale, sp);
- sLangUserHistoryDictCache.put(
- locale, new SoftReference<UserHistoryPredictionDictionary>(dict));
+ final UserHistoryDictionary dict = new UserHistoryDictionary(context, locale, sp);
+ sLangUserHistoryDictCache.put(locale, new SoftReference<UserHistoryDictionary>(dict));
return dict;
}
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java
index e80953c05..432954453 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java
@@ -22,7 +22,7 @@ import com.android.inputmethod.latin.ExpandableBinaryDictionary;
import android.content.Context;
import android.content.SharedPreferences;
-public class PersonalizationPredictionDictionary extends DynamicPredictionDictionaryBase {
+public class PersonalizationPredictionDictionary extends DecayingExpandableBinaryDictionaryBase {
private static final String NAME = PersonalizationPredictionDictionary.class.getSimpleName();
/* package */ PersonalizationPredictionDictionary(final Context context, final String locale,
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryPredictionDictionary.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
index b140c919b..a60226d7e 100644
--- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryPredictionDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
@@ -26,10 +26,10 @@ import android.content.SharedPreferences;
* Locally gathers stats about the words user types and various other signals like auto-correction
* cancellation or manual picks. This allows the keyboard to adapt to the typist over time.
*/
-public class UserHistoryPredictionDictionary extends DynamicPredictionDictionaryBase {
+public class UserHistoryDictionary extends DecayingExpandableBinaryDictionaryBase {
/* package for tests */ static final String NAME =
- UserHistoryPredictionDictionary.class.getSimpleName();
- /* package */ UserHistoryPredictionDictionary(final Context context, final String locale,
+ UserHistoryDictionary.class.getSimpleName();
+ /* package */ UserHistoryDictionary(final Context context, final String locale,
final SharedPreferences sp) {
super(context, locale, sp, Dictionary.TYPE_USER_HISTORY, getDictionaryFileName(locale));
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java
index 4c1803bdf..55a90ee51 100644
--- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java
+++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java
@@ -54,7 +54,7 @@ public final class UserHistoryDictionaryBigramList {
* Called when loaded from the SQL DB.
*/
public void addBigram(String word1, String word2, byte fcValue) {
- if (DynamicPredictionDictionaryBase.DBG_SAVE_RESTORE) {
+ if (DecayingExpandableBinaryDictionaryBase.DBG_SAVE_RESTORE) {
Log.d(TAG, "--- add bigram: " + word1 + ", " + word2 + ", " + fcValue);
}
final HashMap<String, Byte> map;
@@ -74,7 +74,7 @@ public final class UserHistoryDictionaryBigramList {
* Called when inserted to the SQL DB.
*/
public void updateBigram(String word1, String word2, byte fcValue) {
- if (DynamicPredictionDictionaryBase.DBG_SAVE_RESTORE) {
+ if (DecayingExpandableBinaryDictionaryBase.DBG_SAVE_RESTORE) {
Log.d(TAG, "--- update bigram: " + word1 + ", " + word2 + ", " + fcValue);
}
final HashMap<String, Byte> map;
diff --git a/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java b/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java
index 5dc0b5893..201a70d42 100644
--- a/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java
+++ b/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java
@@ -16,8 +16,11 @@
package com.android.inputmethod.latin.utils;
-import java.util.ArrayDeque;
import java.util.Queue;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
/**
* An object that executes submitted tasks using a thread.
@@ -27,19 +30,20 @@ public class PrioritizedSerialExecutor {
private final Object mLock = new Object();
- // The default value of capacities of task queues.
- private static final int TASK_QUEUE_CAPACITY = 1000;
private final Queue<Runnable> mTasks;
private final Queue<Runnable> mPrioritizedTasks;
private boolean mIsShutdown;
+ private final ThreadPoolExecutor mThreadPoolExecutor;
// The task which is running now.
private Runnable mActive;
public PrioritizedSerialExecutor() {
- mTasks = new ArrayDeque<Runnable>(TASK_QUEUE_CAPACITY);
- mPrioritizedTasks = new ArrayDeque<Runnable>(TASK_QUEUE_CAPACITY);
+ mTasks = new ConcurrentLinkedQueue<Runnable>();
+ mPrioritizedTasks = new ConcurrentLinkedQueue<Runnable>();
mIsShutdown = false;
+ mThreadPoolExecutor = new ThreadPoolExecutor(1 /* corePoolSize */, 1 /* maximumPoolSize */,
+ 0 /* keepAliveTime */, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(1));
}
/**
@@ -59,7 +63,16 @@ public class PrioritizedSerialExecutor {
public void execute(final Runnable r) {
synchronized(mLock) {
if (!mIsShutdown) {
- mTasks.offer(r);
+ mTasks.offer(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ r.run();
+ } finally {
+ scheduleNext();
+ }
+ }
+ });
if (mActive == null) {
scheduleNext();
}
@@ -74,45 +87,36 @@ public class PrioritizedSerialExecutor {
public void executePrioritized(final Runnable r) {
synchronized(mLock) {
if (!mIsShutdown) {
- mPrioritizedTasks.offer(r);
- if (mActive == null) {
+ mPrioritizedTasks.offer(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ r.run();
+ } finally {
+ scheduleNext();
+ }
+ }
+ });
+ if (mActive == null) {
scheduleNext();
}
}
}
}
- private boolean fetchNextTasks() {
- synchronized(mLock) {
- mActive = mPrioritizedTasks.poll();
- if (mActive == null) {
- mActive = mTasks.poll();
- }
- return mActive != null;
+ private boolean fetchNextTasksLocked() {
+ mActive = mPrioritizedTasks.poll();
+ if (mActive == null) {
+ mActive = mTasks.poll();
}
+ return mActive != null;
}
private void scheduleNext() {
synchronized(mLock) {
- if (!fetchNextTasks()) {
- return;
+ if (fetchNextTasksLocked()) {
+ mThreadPoolExecutor.execute(mActive);
}
- new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- do {
- synchronized(mLock) {
- if (mActive != null) {
- mActive.run();
- }
- }
- } while (fetchNextTasks());
- } finally {
- scheduleNext();
- }
- }
- }).start();
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java b/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java
new file mode 100644
index 000000000..b51fd9377
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2013 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.utils;
+
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.SpannedString;
+import android.text.TextUtils;
+import android.text.style.SuggestionSpan;
+
+public final class SpannableStringUtils {
+ /**
+ * Copies the spans from the region <code>start...end</code> in
+ * <code>source</code> to the region
+ * <code>destoff...destoff+end-start</code> in <code>dest</code>.
+ * Spans in <code>source</code> that begin before <code>start</code>
+ * or end after <code>end</code> but overlap this range are trimmed
+ * as if they began at <code>start</code> or ended at <code>end</code>.
+ * Only SuggestionSpans that don't have the SPAN_PARAGRAPH span are copied.
+ *
+ * This code is almost entirely taken from {@link TextUtils#copySpansFrom}, except for the
+ * kind of span that is copied.
+ *
+ * @throws IndexOutOfBoundsException if any of the copied spans
+ * are out of range in <code>dest</code>.
+ */
+ public static void copyNonParagraphSuggestionSpansFrom(Spanned source, int start, int end,
+ Spannable dest, int destoff) {
+ Object[] spans = source.getSpans(start, end, SuggestionSpan.class);
+
+ for (int i = 0; i < spans.length; i++) {
+ int fl = source.getSpanFlags(spans[i]);
+ if (0 != (fl & Spannable.SPAN_PARAGRAPH)) continue;
+
+ int st = source.getSpanStart(spans[i]);
+ int en = source.getSpanEnd(spans[i]);
+
+ if (st < start)
+ st = start;
+ if (en > end)
+ en = end;
+
+ dest.setSpan(spans[i], st - start + destoff, en - start + destoff,
+ fl);
+ }
+ }
+
+ /**
+ * Returns a CharSequence concatenating the specified CharSequences, retaining their
+ * SuggestionSpans that don't have the PARAGRAPH flag, but not other spans.
+ *
+ * This code is almost entirely taken from {@link TextUtils#concat(CharSequence...)}, except
+ * it calls copyNonParagraphSuggestionSpansFrom instead of {@link TextUtils#copySpansFrom}.
+ */
+ public static CharSequence concatWithNonParagraphSuggestionSpansOnly(CharSequence... text) {
+ if (text.length == 0) {
+ return "";
+ }
+
+ if (text.length == 1) {
+ return text[0];
+ }
+
+ boolean spanned = false;
+ for (int i = 0; i < text.length; i++) {
+ if (text[i] instanceof Spanned) {
+ spanned = true;
+ break;
+ }
+ }
+
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < text.length; i++) {
+ sb.append(text[i]);
+ }
+
+ if (!spanned) {
+ return sb.toString();
+ }
+
+ SpannableString ss = new SpannableString(sb);
+ int off = 0;
+ for (int i = 0; i < text.length; i++) {
+ int len = text[i].length();
+
+ if (text[i] instanceof Spanned) {
+ copyNonParagraphSuggestionSpansFrom((Spanned) text[i], 0, len, ss, off);
+ }
+
+ off += len;
+ }
+
+ return new SpannedString(ss);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
index 327780ad0..121aecf0f 100644
--- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
@@ -20,12 +20,7 @@ import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.settings.SettingsValues;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.Spanned;
-import android.text.SpannedString;
import android.text.TextUtils;
-import android.text.style.SuggestionSpan;
import android.util.JsonReader;
import android.util.JsonWriter;
import android.util.Log;
@@ -467,88 +462,4 @@ public final class StringUtils {
}
return "";
}
-
- /**
- * Copies the spans from the region <code>start...end</code> in
- * <code>source</code> to the region
- * <code>destoff...destoff+end-start</code> in <code>dest</code>.
- * Spans in <code>source</code> that begin before <code>start</code>
- * or end after <code>end</code> but overlap this range are trimmed
- * as if they began at <code>start</code> or ended at <code>end</code>.
- * Only SuggestionSpans that don't have the SPAN_PARAGRAPH span are copied.
- *
- * This code is almost entirely taken from {@link TextUtils#copySpansFrom}, except for the
- * kind of span that is copied.
- *
- * @throws IndexOutOfBoundsException if any of the copied spans
- * are out of range in <code>dest</code>.
- */
- public static void copyNonParagraphSuggestionSpansFrom(Spanned source, int start, int end,
- Spannable dest, int destoff) {
- Object[] spans = source.getSpans(start, end, SuggestionSpan.class);
-
- for (int i = 0; i < spans.length; i++) {
- int fl = source.getSpanFlags(spans[i]);
- if (0 != (fl & Spannable.SPAN_PARAGRAPH)) continue;
-
- int st = source.getSpanStart(spans[i]);
- int en = source.getSpanEnd(spans[i]);
-
- if (st < start)
- st = start;
- if (en > end)
- en = end;
-
- dest.setSpan(spans[i], st - start + destoff, en - start + destoff,
- fl);
- }
- }
-
- /**
- * Returns a CharSequence concatenating the specified CharSequences, retaining their
- * SuggestionSpans that don't have the PARAGRAPH flag, but not other spans.
- *
- * This code is almost entirely taken from {@link TextUtils#concat(CharSequence...)}, except
- * it calls copyNonParagraphSuggestionSpansFrom instead of {@link TextUtils#copySpansFrom}.
- */
- public static CharSequence concatWithNonParagraphSuggestionSpansOnly(CharSequence... text) {
- if (text.length == 0) {
- return "";
- }
-
- if (text.length == 1) {
- return text[0];
- }
-
- boolean spanned = false;
- for (int i = 0; i < text.length; i++) {
- if (text[i] instanceof Spanned) {
- spanned = true;
- break;
- }
- }
-
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < text.length; i++) {
- sb.append(text[i]);
- }
-
- if (!spanned) {
- return sb.toString();
- }
-
- SpannableString ss = new SpannableString(sb);
- int off = 0;
- for (int i = 0; i < text.length; i++) {
- int len = text[i].length();
-
- if (text[i] instanceof Spanned) {
- copyNonParagraphSuggestionSpansFrom((Spanned) text[i], 0, len, ss, off);
- }
-
- off += len;
- }
-
- return new SpannedString(ss);
- }
}