am 86ce72a6: (-s ours) am e0211363: (-s ours) am c507c66c: Import revised translations. DO NOT MERGE
* commit '86ce72a64d5646efb2c7edf123844fc83a6b4342':
Import revised translations. DO NOT MERGE
diff --git a/java/res/drawable-mdpi/key_hint_comma_large_holo.9.png b/java/res/drawable-mdpi/key_hint_comma_large_holo.9.png
new file mode 100644
index 0000000..82e4a93
--- /dev/null
+++ b/java/res/drawable-mdpi/key_hint_comma_large_holo.9.png
Binary files differ
diff --git a/java/res/values-en/donottranslate-altchars.xml b/java/res/values-en/donottranslate-altchars.xml
index 3950d7d..29582c9 100644
--- a/java/res/values-en/donottranslate-altchars.xml
+++ b/java/res/values-en/donottranslate-altchars.xml
@@ -22,6 +22,7 @@
<string name="alternates_for_e">3,è,é,ê,ë,ē</string>
<string name="alternates_for_i">8,î,ï,í,ī,ì</string>
<string name="alternates_for_o">9,ô,ö,ò,ó,œ,ø,ō,õ</string>
+ <string name="alternates_for_s">ß</string>
<string name="alternates_for_u">7,û,ü,ù,ú,ū</string>
<string name="alternates_for_n">ñ</string>
<string name="alternates_for_c">ç</string>
diff --git a/java/res/values-es-rUS-xlarge/strings.xml b/java/res/values-es-rUS-xlarge/strings.xml
new file mode 100644
index 0000000..24d2b4f
--- /dev/null
+++ b/java/res/values-es-rUS-xlarge/strings.xml
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- XL -->
+ <string name="sound_on_keypress" msgid="5115009797011251176">"Sonar al pulsar teclas"</string>
+ <!-- XL -->
+ <string name="auto_cap" msgid="6033382411344449470">"Uso de mayúsculas automático"</string>
+ <!-- XL -->
+ <string name="auto_correction" msgid="7961335093790493671">"Corrección automática"</string>
+ <!-- XL -->
+ <string name="auto_correction_summary" msgid="6260001790426244084">"La barra espaciadora o la puntuación insertan automáticamente la palabra resaltada."</string>
+ <!-- XL -->
+ <string name="bigram_suggestion" msgid="7146707435859263625">"Sugerencias de bigramas"</string>
+ <!-- XL -->
+ <string name="label_done_key" msgid="5392116476778838314">"Listo"</string>
+ <!-- XL -->
+ <string name="voice_warning_title" msgid="7559175513146431282">"Entrada de voz"</string>
+ <!-- XL -->
+ <string name="voice_warning_may_not_understand" msgid="5450473727606344027">"La entrada de voz utiliza el reconocimiento de voz de Google. "<a href="http://m.google.com/privacy">"Aplica la Política de privacidad de Google para celulares"</a>"."</string>
+ <!-- XL -->
+ <string name="voice_warning_how_to_turn_off" msgid="8461922898209345270">"Para desactivar la entrada por voz, ve a la configuración de métodos de entrada."</string>
+ <!-- XL -->
+ <string name="voice_hint_dialog_message" msgid="6099357096490592798">"Para utilizar entrada de voz, presiona el botón micrófono."</string>
+ <!-- XL -->
+ <string name="voice_input" msgid="6634874497844843576">"Entrada de voz"</string>
+ <!-- XL -->
+ <string name="prefs_enable_recorrection_summary" msgid="3119549956172710725">"Toca las palabras ingresadas que desees corregir, solo cuando las sugerencias estén visibles."</string>
+ <!-- XL -->
+ <string name="prefs_show_suggestions" msgid="1375526087676269770">"Mostrar sugerencias"</string>
+ <!-- XL -->
+ <string name="prefs_show_suggestions_summary" msgid="2564386479780335351">"Mostrar palabras sugeridas al escribir"</string>
+ <!-- XL -->
+ <string name="prefs_suggestion_visibility_show_name" msgid="8350173747634837929">"Mostrar siempre"</string>
+ <!-- XL -->
+ <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="670278993111469619">"Mostrar en modo retrato"</string>
+ <!-- XL -->
+ <string name="prefs_suggestion_visibility_hide_name" msgid="2750493093338023345">"Ocultar siempre"</string>
+ <!-- XL -->
+ <string name="auto_correction_threshold_mode_off" msgid="4899978031827517261">"Apagado"</string>
+ <!-- XL -->
+ <string name="auto_correction_threshold_mode_modest" msgid="3316394123368070951">"Moderado"</string>
+ <!-- XL -->
+ <string name="auto_correction_threshold_mode_aggeressive" msgid="6091003457668724585">"Intenso"</string>
+ <!-- XL -->
+ <string name="label_to_alpha_key" msgid="3103719164112604010">"ABC"</string>
+ <!-- XL -->
+ <string name="voice_input_modes_main_keyboard" msgid="1403596961453846365">"En el teclado principal"</string>
+ <!-- XL -->
+ <string name="voice_input_modes_symbols_keyboard" msgid="5916050323076830126">"En el teclado de símbolos"</string>
+ <!-- XL -->
+ <string name="voice_input_modes_off" msgid="1577817314089496020">"Apagado"</string>
+ <!-- XL -->
+ <string name="voice_input_modes_summary_main_keyboard" msgid="5118121899312172508">"Micrófono en el teclado principal"</string>
+ <!-- XL -->
+ <string name="voice_input_modes_summary_symbols_keyboard" msgid="8181616553734217736">"Micrófono en el teclado de símbolos"</string>
+ <!-- XL -->
+ <string name="voice_input_modes_summary_off" msgid="3854831353403775554">"La entrada por voz está inhabilitada."</string>
+ <!-- XL -->
+ <string name="subtype_mode_cs_keyboard" msgid="1186679497674833204">"Teclado en checo"</string>
+ <!-- XL -->
+ <string name="subtype_mode_da_keyboard" msgid="1395637124037817510">"Teclado en danés"</string>
+ <!-- XL -->
+ <string name="subtype_mode_de_keyboard" msgid="1145552122692431122">"Teclado en alemán"</string>
+ <!-- XL -->
+ <string name="subtype_mode_en_GB_keyboard" msgid="5050923189634470413">"Teclado en inglés (Reino Unido)"</string>
+ <!-- XL -->
+ <string name="subtype_mode_en_US_keyboard" msgid="3435344903704397043">"Teclado en inglés (EE.UU.)"</string>
+ <!-- XL -->
+ <string name="subtype_mode_es_keyboard" msgid="1030419781157491328">"Teclado en español"</string>
+ <!-- XL -->
+ <string name="subtype_mode_es_US_keyboard" msgid="5792199241357098918">"Teclado en español (EE.UU.)"</string>
+ <!-- XL -->
+ <string name="subtype_mode_fr_keyboard" msgid="4855416218650524164">"Teclado en francés"</string>
+ <!-- XL -->
+ <string name="subtype_mode_fr_CA_keyboard" msgid="6458285776720480201">"Teclado en francés (Canadá)"</string>
+ <!-- XL -->
+ <string name="subtype_mode_fr_CH_keyboard" msgid="5966960427086795964">"Teclado en francés (Suiza)"</string>
+ <!-- XL -->
+ <string name="subtype_mode_it_keyboard" msgid="6927754583816493555">"Teclado en italiano"</string>
+ <!-- XL -->
+ <string name="subtype_mode_nb_keyboard" msgid="771634025467668613">"Teclado en noruego"</string>
+ <!-- XL -->
+ <string name="subtype_mode_nl_keyboard" msgid="3397048533451717478">"Teclado en holandés"</string>
+ <!-- XL -->
+ <string name="subtype_mode_ru_keyboard" msgid="3812694929448916712">"Teclado en ruso"</string>
+ <!-- XL -->
+ <string name="subtype_mode_sr_keyboard" msgid="7947963963114184275">"Teclado en serbio"</string>
+ <!-- XL -->
+ <string name="subtype_mode_sv_keyboard" msgid="3874083866564515371">"Teclado en sueco"</string>
+ <!-- XL -->
+ <string name="subtype_mode_af_voice">"Voz en Afrikáans"</string>
+ <!-- XL -->
+ <string name="subtype_mode_cs_voice" msgid="8290007904951946296">"Voz en checo"</string>
+ <!-- XL -->
+ <string name="subtype_mode_de_voice" msgid="672328729666823853">"Voz en alemán"</string>
+ <!-- XL -->
+ <string name="subtype_mode_en_voice">"Voz en inglés"</string>
+ <!-- XL -->
+ <string name="subtype_mode_es_voice" msgid="1243071504878834350">"Voz en español"</string>
+ <!-- XL -->
+ <string name="subtype_mode_fr_voice" msgid="2048805677248981105">"Voz en francés"</string>
+ <!-- XL -->
+ <string name="subtype_mode_ja_voice" msgid="1855513591711108481">"Voz en japonés"</string>
+ <!-- XL -->
+ <string name="subtype_mode_ko_voice" msgid="3453153041889151316">"Voz en coreano"</string>
+ <!-- XL -->
+ <string name="subtype_mode_pl_voice" msgid="6730658974157645735">"Voz en polaco"</string>
+ <!-- XL -->
+ <string name="subtype_mode_pt_voice" msgid="4508062762756741654">"Voz en portugués"</string>
+ <!-- XL -->
+ <string name="subtype_mode_ru_voice" msgid="554299262138845594">"Voz en ruso"</string>
+ <!-- XL -->
+ <string name="subtype_mode_tr_voice" msgid="5242644971865917801">"Voz en turco"</string>
+ <!-- XL -->
+ <string name="subtype_mode_yue_voice">"Voz en chino, yue"</string>
+ <!-- XL -->
+ <string name="subtype_mode_zh_voice">"Voz en chino, mandarín"</string>
+ <!-- XL -->
+ <string name="subtype_mode_zu_voice">"Voz en isiZulu"</string>
+ <!-- XL -->
+ <string name="prefs_usability_study_mode" msgid="8423000345880575687">"Modo estudio de usabilidad"</string>
+</resources>
diff --git a/java/res/values-xlarge/config.xml b/java/res/values-xlarge/config.xml
index 40fdce0..f075b1b 100644
--- a/java/res/values-xlarge/config.xml
+++ b/java/res/values-xlarge/config.xml
@@ -32,6 +32,7 @@
<bool name="config_digit_popup_characters_enabled">false</bool>
<!-- Whether or not Popup on key press is enabled by default -->
<bool name="config_default_popup_preview">false</bool>
+ <bool name="config_default_sound_enabled">true</bool>
<bool name="config_use_spacebar_language_switcher">false</bool>
<!-- Showing mini keyboard, just above the touched point if true, aligned to the key if false -->
<bool name="config_show_mini_keyboard_at_touched_point">true</bool>
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index 9759e0e..6589278 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -140,6 +140,8 @@
<attr name="keyStyle" format="string" />
<!-- Shift key icon for shifted state -->
<attr name="shiftedIcon" format="reference" />
+ <!-- The key is enabled and responds on press. -->
+ <attr name="enabled" format="boolean" />
</declare-styleable>
<declare-styleable name="Keyboard_Row">
@@ -166,10 +168,11 @@
<enum name="web" value="4" />
<enum name="phone" value="5" />
</attr>
+ <attr name="passwordInput" format="boolean" />
<attr name="hasSettingsKey" format="string" />
<attr name="voiceKeyEnabled" format="string" />
<attr name="hasVoiceKey" format="string" />
- <attr name="imeOptions">
+ <attr name="imeAction">
<!-- This should be aligned with EditorInfo.IME_ACTION_* -->
<flag name="actionUnspecified" value="0" />
<flag name="actionNone" value="1" />
@@ -180,6 +183,7 @@
<flag name="actionDone" value="6" />
<flag name="actionPrevious" value="7" />
</attr>
+ <attr name="languageCode" format="string" />
</declare-styleable>
<declare-styleable name="Keyboard_KeyStyle">
diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index ceb4f12..bf42589 100644
--- a/java/res/values/config.xml
+++ b/java/res/values/config.xml
@@ -20,7 +20,6 @@
<resources>
<bool name="config_swipeDisambiguation">true</bool>
- <bool name="default_recorrection_enabled">true</bool>
<bool name="config_long_press_comma_for_settings_enabled">true</bool>
<bool name="config_enable_show_settings_key_option">true</bool>
<bool name="config_enable_show_subtype_settings">true</bool>
@@ -39,6 +38,8 @@
<!-- Default values for whether quick fixes and bigram suggestions are activated -->
<bool name="config_default_quick_fixes">true</bool>
<bool name="config_default_bigram_suggestions">true</bool>
+ <bool name="config_default_recorrection_enabled">true</bool>
+ <bool name="config_default_sound_enabled">false</bool>
<bool name="config_use_spacebar_language_switcher">true</bool>
<!-- Showing mini keyboard, just above the touched point if true, aligned to the key if false -->
<bool name="config_show_mini_keyboard_at_touched_point">false</bool>
diff --git a/java/res/xml-xlarge/kbd_key_styles.xml b/java/res/xml-xlarge/kbd_key_styles.xml
index d211e5e..fc06d00 100644
--- a/java/res/xml-xlarge/kbd_key_styles.xml
+++ b/java/res/xml-xlarge/kbd_key_styles.xml
@@ -165,4 +165,19 @@
latin:keyOutputText="@string/keylabel_for_popular_domain"
latin:keyHintIcon="@drawable/hint_popup_holo"
latin:popupCharacters="@string/alternates_for_popular_domain" />
+ <switch>
+ <case
+ latin:passwordInput="true"
+ >
+ <key-style
+ latin:styleName="nonPasswordSymbolKeyStyle"
+ latin:enabled="false" />
+ </case>
+ <!-- latin:passwordInput="false" -->
+ <default>
+ <key-style
+ latin:styleName="nonPasswordSymbolKeyStyle"
+ latin:enabled="true" />
+ </default>
+ </switch>
</merge>
diff --git a/java/res/xml-xlarge/kbd_number.xml b/java/res/xml-xlarge/kbd_number.xml
index 875548b..152ec82 100644
--- a/java/res/xml-xlarge/kbd_number.xml
+++ b/java/res/xml-xlarge/kbd_number.xml
@@ -31,120 +31,189 @@
>
<include
latin:keyboardLayout="@xml/kbd_key_styles" />
- <!-- This row is intentionally not marked as a top row -->
- <Row>
- <Key
- latin:keyStyle="tabKeyStyle"
- latin:keyLabelOption="alignLeft"
- latin:keyEdgeFlags="left" />
- <Spacer
- latin:horizontalGap="4.458%p" />
- <Key
- latin:keyLabel="-"
- latin:keyWidth="8.042%p" />
- <Key
- latin:keyLabel="+"
- latin:keyWidth="8.042%p" />
- <Key
- latin:keyLabel="."
- latin:keyWidth="8.042%p" />
- <Spacer
- latin:horizontalGap="4.458%p" />
- <Key
- latin:keyLabel="1" />
- <Key
- latin:keyLabel="2" />
- <Key
- latin:keyLabel="3" />
- <Spacer
- latin:horizontalGap="9.360%p" />
- <Key
- latin:keyStyle="deleteKeyStyle"
- latin:keyWidth="9.804%p"
- latin:keyEdgeFlags="right" />
- </Row>
- <Row>
- <Spacer
- latin:horizontalGap="16.406%p" />
- <Key
- latin:keyLabel="*"
- latin:keyWidth="8.042%p" />
- <Key
- latin:keyLabel="/"
- latin:keyWidth="8.042%p" />
- <Key
- latin:keyLabel=","
- latin:keyWidth="8.042%p" />
- <Spacer
- latin:horizontalGap="4.458%p" />
- <Key
- latin:keyLabel="4" />
- <Key
- latin:keyLabel="5" />
- <Key
- latin:keyLabel="6" />
- <Spacer
- latin:horizontalGap="4.458%p" />
- <Key
- latin:keyStyle="returnKeyStyle"
- latin:keyWidth="14.706%p"
- latin:keyEdgeFlags="right" />
- </Row>
- <Row>
- <!-- There is an empty area bellow the "More" key and left of the "(" key. To ignore
- the touch event on the area, "(" is intentionally not marked as a left edge key. -->
- <Spacer
- latin:horizontalGap="16.406%p" />
- <Key
- latin:keyLabel="("
- latin:keyWidth="8.042%p" />
- <Key
- latin:keyLabel=")"
- latin:keyWidth="8.042%p" />
- <Key
- latin:keyLabel="="
- latin:keyWidth="8.042%p" />
- <Spacer
- latin:horizontalGap="4.458%p" />
- <Key
- latin:keyLabel="7" />
- <Key
- latin:keyLabel="8" />
- <Key
- latin:keyLabel="9" />
- <!-- There is an empty area bellow the "Enter" key and right of the "9" key. To ignore
- the touch event on the area, "9" is intentionally not marked as a right edge key. -->
- </Row>
- <!-- This row is intentionally not marked as a bottom row -->
- <Row>
- <!-- There is an empty area bellow the "More" key and left of the "space" key. To ignore
- the touch event on the area, "space" is intentionally not marked as a left edge key. -->
- <Spacer
- latin:horizontalGap="8.362%p" />
- <Key
- latin:keyStyle="settingsKeyStyle"
- latin:keyWidth="8.042%p" />
- <Key
- latin:keyStyle="nonSpecialBackgroundSpaceKeyStyle"
- latin:keyWidth="24.127%p" />
- <Spacer
- latin:horizontalGap="4.458%p" />
- <Key
- latin:keyLabel="*" />
- <Key
- latin:keyLabel="0" />
- <Key
- latin:keyLabel="#" />
- <switch>
- <case
- latin:voiceKeyEnabled="true"
- >
+ <switch>
+ <case
+ latin:passwordInput="true"
+ >
+ <!-- This row is intentionally not marked as a top row -->
+ <Row>
+ <Spacer
+ latin:horizontalGap="32.076%p" />
<Key
- latin:keyStyle="micKeyStyle"
+ latin:keyLabel="1" />
+ <Key
+ latin:keyLabel="2" />
+ <Key
+ latin:keyLabel="3" />
+ <Spacer
+ latin:horizontalGap="22.272%p" />
+ <Key
+ latin:keyStyle="deleteKeyStyle"
+ latin:keyWidth="9.804%p"
+ latin:keyEdgeFlags="right" />
+ </Row>
+ <Row>
+ <Spacer
+ latin:horizontalGap="32.076%p" />
+ <Key
+ latin:keyLabel="4" />
+ <Key
+ latin:keyLabel="5" />
+ <Key
+ latin:keyLabel="6" />
+ <Spacer
+ latin:horizontalGap="17.371%p" />
+ <Key
+ latin:keyStyle="returnKeyStyle"
+ latin:keyWidth="14.706%p"
+ latin:keyEdgeFlags="right" />
+ </Row>
+ <Row>
+ <Spacer
+ latin:horizontalGap="32.076%p" />
+ <Key
+ latin:keyLabel="7" />
+ <Key
+ latin:keyLabel="8" />
+ <Key
+ latin:keyLabel="9" />
+ <!-- There is an empty area below the "Enter" key and right of the "9" key. To
+ ignore the touch event on the area, "9" is intentionally not marked as a right
+ edge key. -->
+ </Row>
+ <!-- This row is intentionally not marked as a bottom row -->
+ <Row>
+ <Spacer
+ latin:horizontalGap="44.026%p" />
+ <Key
+ latin:keyLabel="0" />
+ <!-- There is an empty area below the "Enter" key and right of the "#" key. To
+ ignore the touch event on the area, "#" is intentionally not marked as a right
+ edge key. -->
+ </Row>
+ </case>
+ <!-- latin:passwordInput="false" -->
+ <default>
+ <!-- This row is intentionally not marked as a top row -->
+ <Row>
+ <Key
+ latin:keyStyle="tabKeyStyle"
+ latin:keyLabelOption="alignLeft"
+ latin:keyEdgeFlags="left" />
+ <Spacer
+ latin:horizontalGap="4.458%p" />
+ <Key
+ latin:keyLabel="-"
latin:keyWidth="8.042%p" />
- </case>
- </switch>
- <!-- There is an empty area bellow the "Enter" key and right of the "#" key. To ignore
- the touch event on the area, "#" is intentionally not marked as a right edge key. -->
- </Row>
+ <Key
+ latin:keyLabel="+"
+ latin:keyWidth="8.042%p" />
+ <Key
+ latin:keyLabel="."
+ latin:keyWidth="8.042%p" />
+ <Spacer
+ latin:horizontalGap="4.458%p" />
+ <Key
+ latin:keyLabel="1" />
+ <Key
+ latin:keyLabel="2" />
+ <Key
+ latin:keyLabel="3" />
+ <Spacer
+ latin:horizontalGap="9.360%p" />
+ <Key
+ latin:keyStyle="deleteKeyStyle"
+ latin:keyWidth="9.804%p"
+ latin:keyEdgeFlags="right" />
+ </Row>
+ <Row>
+ <Spacer
+ latin:horizontalGap="16.406%p" />
+ <Key
+ latin:keyLabel="*"
+ latin:keyWidth="8.042%p" />
+ <Key
+ latin:keyLabel="/"
+ latin:keyWidth="8.042%p" />
+ <Key
+ latin:keyLabel=","
+ latin:keyWidth="8.042%p" />
+ <Spacer
+ latin:horizontalGap="4.458%p" />
+ <Key
+ latin:keyLabel="4" />
+ <Key
+ latin:keyLabel="5" />
+ <Key
+ latin:keyLabel="6" />
+ <Spacer
+ latin:horizontalGap="4.458%p" />
+ <Key
+ latin:keyStyle="returnKeyStyle"
+ latin:keyWidth="14.706%p"
+ latin:keyEdgeFlags="right" />
+ </Row>
+ <Row>
+ <!-- There is an empty area below the "More" key and left of the "(" key. To
+ ignore the touch event on the area, "(" is intentionally not marked as a left
+ edge key. -->
+ <Spacer
+ latin:horizontalGap="16.406%p" />
+ <Key
+ latin:keyLabel="("
+ latin:keyWidth="8.042%p" />
+ <Key
+ latin:keyLabel=")"
+ latin:keyWidth="8.042%p" />
+ <Key
+ latin:keyLabel="="
+ latin:keyWidth="8.042%p" />
+ <Spacer
+ latin:horizontalGap="4.458%p" />
+ <Key
+ latin:keyLabel="7" />
+ <Key
+ latin:keyLabel="8" />
+ <Key
+ latin:keyLabel="9" />
+ <!-- There is an empty area below the "Enter" key and right of the "9" key. To
+ ignore the touch event on the area, "9" is intentionally not marked as a right
+ edge key. -->
+ </Row>
+ <!-- This row is intentionally not marked as a bottom row -->
+ <Row>
+ <!-- There is an empty area below the "More" key and left of the "space" key. To
+ ignore the touch event on the area, "space" is intentionally not marked as a
+ left edge key. -->
+ <Spacer
+ latin:horizontalGap="8.362%p" />
+ <Key
+ latin:keyStyle="settingsKeyStyle"
+ latin:keyWidth="8.042%p" />
+ <Key
+ latin:keyStyle="nonSpecialBackgroundSpaceKeyStyle"
+ latin:keyWidth="24.127%p" />
+ <Spacer
+ latin:horizontalGap="4.458%p" />
+ <Key
+ latin:keyLabel="*" />
+ <Key
+ latin:keyLabel="0" />
+ <Key
+ latin:keyLabel="#" />
+ <switch>
+ <case
+ latin:voiceKeyEnabled="true"
+ >
+ <Key
+ latin:keyStyle="micKeyStyle"
+ latin:keyWidth="8.042%p" />
+ </case>
+ </switch>
+ <!-- There is an empty area below the "Enter" key and right of the "#" key. To
+ ignore the touch event on the area, "#" is intentionally not marked as a right
+ edge key. -->
+ </Row>
+ </default>
+ </switch>
</Keyboard>
diff --git a/java/res/xml-xlarge/kbd_qwerty_row4.xml b/java/res/xml-xlarge/kbd_qwerty_row4.xml
index 9d0fd81..8011064 100644
--- a/java/res/xml-xlarge/kbd_qwerty_row4.xml
+++ b/java/res/xml-xlarge/kbd_qwerty_row4.xml
@@ -31,25 +31,24 @@
latin:keyStyle="settingsKeyStyle" />
<switch>
<case
- latin:mode="email"
+ latin:languageCode="ru"
>
- <Key
- latin:keyStyle="comKeyStyle" />
- <Key
- latin:keyLabel="\@" />
- </case>
- <!-- TODO: implement logical OR for <case> attribute -->
- <case
- latin:mode="url"
- >
- <Key
- latin:keyStyle="comKeyStyle"
- latin:keyWidth="16.084%p" />
- </case>
- <default>
<switch>
+ <!-- TODO: implement logical OR for <case> attribute -->
<case
- latin:imeOptions="actionSearch"
+ latin:mode="email"
+ >
+ <Key
+ latin:keyStyle="comKeyStyle" />
+ </case>
+ <case
+ latin:mode="url"
+ >
+ <Key
+ latin:keyStyle="comKeyStyle" />
+ </case>
+ <case
+ latin:imeAction="actionSearch"
>
<Key
latin:keyLabel=":"
@@ -63,12 +62,84 @@
latin:keyStyle="smileyKeyStyle" />
</default>
</switch>
- <Key
- latin:keyLabel="/"
- latin:manualTemporaryUpperCaseCode="64"
- latin:keyHintIcon="@drawable/key_hint_at_holo"
- latin:manualTemporaryUpperCaseHintIcon="@drawable/key_hint_at_large_holo"
- latin:popupCharacters="\@" />
+ <switch>
+ <case
+ latin:mode="email"
+ >
+ <Key
+ latin:keyLabel="\@" />
+ </case>
+ <case
+ latin:mode="url"
+ >
+ <Key
+ latin:keyLabel="-"
+ latin:manualTemporaryUpperCaseCode="95"
+ latin:keyHintIcon="@drawable/key_hint_underline_holo"
+ latin:manualTemporaryUpperCaseHintIcon="@drawable/key_hint_underline_large_holo"
+ latin:popupCharacters="_" />
+ </case>
+ <default>
+ <Key
+ latin:keyLabel="/"
+ latin:manualTemporaryUpperCaseCode="64"
+ latin:keyHintIcon="@drawable/key_hint_at_holo"
+ latin:manualTemporaryUpperCaseHintIcon="@drawable/key_hint_at_large_holo"
+ latin:popupCharacters="\@" />
+ </default>
+ </switch>
+ </case>
+ <!-- not languageCode="ru" -->
+ <default>
+ <switch>
+ <case
+ latin:mode="url"
+ >
+ <Key
+ latin:keyStyle="comKeyStyle"
+ latin:keyWidth="16.084%p" />
+ </case>
+ <default>
+ <switch>
+ <case
+ latin:mode="email"
+ >
+ <Key
+ latin:keyStyle="comKeyStyle" />
+ </case>
+ <case
+ latin:imeAction="actionSearch"
+ >
+ <Key
+ latin:keyLabel=":"
+ latin:manualTemporaryUpperCaseCode="43"
+ latin:keyHintIcon="@drawable/key_hint_plus_holo"
+ latin:manualTemporaryUpperCaseHintIcon="@drawable/key_hint_plus_large_holo"
+ latin:popupCharacters="+" />
+ </case>
+ <default>
+ <Key
+ latin:keyStyle="smileyKeyStyle" />
+ </default>
+ </switch>
+ <switch>
+ <case
+ latin:mode="email"
+ >
+ <Key
+ latin:keyLabel="\@" />
+ </case>
+ <default>
+ <Key
+ latin:keyLabel="/"
+ latin:manualTemporaryUpperCaseCode="64"
+ latin:keyHintIcon="@drawable/key_hint_at_holo"
+ latin:manualTemporaryUpperCaseHintIcon="@drawable/key_hint_at_large_holo"
+ latin:popupCharacters="\@" />
+ </default>
+ </switch>
+ </default>
+ </switch>
</default>
</switch>
<Key
@@ -76,44 +147,95 @@
latin:keyWidth="37.454%p" />
<switch>
<case
- latin:mode="email"
+ latin:languageCode="ru"
>
- <Key
- latin:keyLabel="-" />
+ <switch>
+ <case
+ latin:mode="email"
+ >
+ <Key
+ latin:keyLabel="-" />
+ </case>
+ <case
+ latin:mode="url"
+ >
+ <Key
+ latin:keyLabel="/"
+ latin:manualTemporaryUpperCaseCode="58"
+ latin:keyHintIcon="@drawable/key_hint_colon_holo"
+ latin:manualTemporaryUpperCaseHintIcon="@drawable/key_hint_colon_large_holo"
+ latin:popupCharacters=":" />
+ </case>
+ <default>
+ <Key
+ latin:keyLabel="\?"
+ latin:manualTemporaryUpperCaseCode="95"
+ latin:keyHintIcon="@drawable/key_hint_underline_holo"
+ latin:manualTemporaryUpperCaseHintIcon="@drawable/key_hint_underline_large_holo"
+ latin:popupCharacters="_" />
+ </default>
+ </switch>
+ <switch>
+ <case
+ latin:mode="email"
+ >
+ <Key
+ latin:keyLabel="_" />
+ </case>
+ <default>
+ <Key
+ latin:keyLabel="!"
+ latin:manualTemporaryUpperCaseCode="39"
+ latin:keyHintIcon="@drawable/key_hint_quote_holo"
+ latin:manualTemporaryUpperCaseHintIcon="@drawable/key_hint_quote_large_holo"
+ latin:popupCharacters="\'" />
+ </default>
+ </switch>
</case>
- <case
- latin:mode="url"
- >
- <Key
- latin:keyLabel="/"
- latin:manualTemporaryUpperCaseCode="58"
- latin:keyHintIcon="@drawable/key_hint_colon_holo"
- latin:manualTemporaryUpperCaseHintIcon="@drawable/key_hint_colon_large_holo"
- latin:popupCharacters=":" />
- </case>
+ <!-- not languageCode="ru" -->
<default>
- <Key
- latin:keyLabel="\'"
- latin:manualTemporaryUpperCaseCode="34"
- latin:keyHintIcon="@drawable/key_hint_quote_holo"
- latin:manualTemporaryUpperCaseHintIcon="@drawable/key_hint_quote_large_holo"
- latin:popupCharacters=""" />
- </default>
- </switch>
- <switch>
- <case
- latin:mode="email"
- >
- <Key
- latin:keyLabel="_" />
- </case>
- <default>
- <Key
- latin:keyLabel="-"
- latin:manualTemporaryUpperCaseCode="95"
- latin:keyHintIcon="@drawable/key_hint_underline_holo"
- latin:manualTemporaryUpperCaseHintIcon="@drawable/key_hint_underline_large_holo"
- latin:popupCharacters="_" />
+ <switch>
+ <case
+ latin:mode="email"
+ >
+ <Key
+ latin:keyLabel="-" />
+ </case>
+ <case
+ latin:mode="url"
+ >
+ <Key
+ latin:keyLabel="/"
+ latin:manualTemporaryUpperCaseCode="58"
+ latin:keyHintIcon="@drawable/key_hint_colon_holo"
+ latin:manualTemporaryUpperCaseHintIcon="@drawable/key_hint_colon_large_holo"
+ latin:popupCharacters=":" />
+ </case>
+ <default>
+ <Key
+ latin:keyLabel="\'"
+ latin:manualTemporaryUpperCaseCode="34"
+ latin:keyHintIcon="@drawable/key_hint_quote_holo"
+ latin:manualTemporaryUpperCaseHintIcon="@drawable/key_hint_quote_large_holo"
+ latin:popupCharacters=""" />
+ </default>
+ </switch>
+ <switch>
+ <case
+ latin:mode="email"
+ >
+ <Key
+ latin:keyLabel="_" />
+ </case>
+ <default>
+ <Key
+ latin:keyLabel="-"
+ latin:manualTemporaryUpperCaseCode="95"
+ latin:keyHintIcon="@drawable/key_hint_underline_holo"
+ latin:manualTemporaryUpperCaseHintIcon="@drawable/key_hint_underline_large_holo"
+ latin:popupCharacters="_" />
+ </default>
+ </switch>
</default>
</switch>
<switch>
diff --git a/java/res/xml-xlarge/kbd_ru_rows.xml b/java/res/xml-xlarge/kbd_ru_rows.xml
index 008988a..c5cd043 100644
--- a/java/res/xml-xlarge/kbd_ru_rows.xml
+++ b/java/res/xml-xlarge/kbd_ru_rows.xml
@@ -105,11 +105,11 @@
latin:keyEdgeFlags="right" />
</Row>
<Row
- latin:keyWidth="8.042%p"
+ latin:keyWidth="7.520%p"
>
<Key
latin:keyStyle="shiftKeyStyle"
- latin:keyWidth="15.192%p"
+ latin:keyWidth="12.400%p"
latin:keyEdgeFlags="left" />
<Key
latin:keyLabel="я" />
@@ -131,8 +131,14 @@
<Key
latin:keyLabel="ю" />
<Key
+ latin:keyLabel="."
+ latin:manualTemporaryUpperCaseCode="44"
+ latin:keyHintIcon="@drawable/key_hint_comma_holo"
+ latin:manualTemporaryUpperCaseHintIcon="@drawable/key_hint_comma_large_holo"
+ latin:popupCharacters="," />
+ <Key
latin:keyStyle="shiftKeyStyle"
- latin:keyWidth="12.530%p"
+ latin:keyWidth="12.400%p"
latin:keyEdgeFlags="right" />
</Row>
<include
diff --git a/java/res/xml-xlarge/kbd_symbols.xml b/java/res/xml-xlarge/kbd_symbols.xml
index e56cc92..640dd09 100644
--- a/java/res/xml-xlarge/kbd_symbols.xml
+++ b/java/res/xml-xlarge/kbd_symbols.xml
@@ -129,16 +129,33 @@
latin:keyLabel=":" />
<Key
latin:keyLabel=";" />
- <Key
- latin:keyLabel="," />
- <Key
- latin:keyLabel="." />
- <Key
- latin:keyLabel="!"
- latin:popupCharacters="¡" />
- <Key
- latin:keyLabel="\?"
- latin:popupCharacters="¿" />
+ <switch>
+ <case
+ latin:languageCode="ru"
+ >
+ <Key
+ latin:keyLabel="\'" />
+ <Key
+ latin:keyLabel="""
+ latin:popupCharacters="“,”,«,»,˝" />
+ <Key
+ latin:keyLabel="." />
+ <Key
+ latin:keyLabel="," />
+ </case>
+ <default>
+ <Key
+ latin:keyLabel="," />
+ <Key
+ latin:keyLabel="." />
+ <Key
+ latin:keyLabel="!"
+ latin:popupCharacters="¡" />
+ <Key
+ latin:keyLabel="\?"
+ latin:popupCharacters="¿" />
+ </default>
+ </switch>
<Key
latin:keyStyle="moreKeyStyle"
latin:keyWidth="12.530%p"
@@ -159,11 +176,23 @@
<Key
latin:keyStyle="spaceKeyStyle"
latin:keyWidth="37.454%p" />
- <Key
- latin:keyLabel="""
- latin:popupCharacters="“,”,«,»,˝" />
- <Key
- latin:keyLabel="_" />
+ <switch>
+ <case
+ latin:languageCode="ru"
+ >
+ <Key
+ latin:keyLabel="_" />
+ <Key
+ latin:keyLabel="-" />
+ </case>
+ <default>
+ <Key
+ latin:keyLabel="""
+ latin:popupCharacters="“,”,«,»,˝" />
+ <Key
+ latin:keyLabel="_" />
+ </default>
+ </switch>
<switch>
<case
latin:voiceKeyEnabled="true"
diff --git a/java/res/xml-xlarge/kbd_symbols_shift.xml b/java/res/xml-xlarge/kbd_symbols_shift.xml
index f7cf24a..1f5513b 100644
--- a/java/res/xml-xlarge/kbd_symbols_shift.xml
+++ b/java/res/xml-xlarge/kbd_symbols_shift.xml
@@ -46,21 +46,28 @@
<Key
latin:keyLabel="|" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="•"
latin:popupCharacters="♪,♥,♠,♦,♣" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="√" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="π"
latin:popupCharacters="Π" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="÷" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="×" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="§"
latin:popupCharacters="¶" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="Δ" />
<Key
latin:keyStyle="deleteKeyStyle"
@@ -76,19 +83,25 @@
latin:keyWidth="11.167%p"
latin:keyEdgeFlags="left" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="£" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="¢" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="€" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="¥" />
<Key
latin:keyLabel="^"
latin:popupCharacters="↑,↓,←,→" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="°" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="±"
latin:popupCharacters="∞" />
<Key
@@ -110,20 +123,26 @@
<Key
latin:keyLabel="\\" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="©" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="®" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="™" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="℅" />
<Key
latin:keyLabel="[" />
<Key
latin:keyLabel="]" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="¡" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="¿" />
<Key
latin:keyStyle="moreKeyStyle"
diff --git a/java/res/xml/kbd_key_styles.xml b/java/res/xml/kbd_key_styles.xml
index 3b35f35..473510e 100644
--- a/java/res/xml/kbd_key_styles.xml
+++ b/java/res/xml/kbd_key_styles.xml
@@ -182,7 +182,7 @@
<!-- Return key style -->
<switch>
<case
- latin:imeOptions="actionGo"
+ latin:imeAction="actionGo"
>
<key-style
latin:styleName="returnKeyStyle"
@@ -191,7 +191,7 @@
latin:parentStyle="functionalKeyStyle" />
</case>
<case
- latin:imeOptions="actionNext"
+ latin:imeAction="actionNext"
>
<key-style
latin:styleName="returnKeyStyle"
@@ -200,7 +200,7 @@
latin:parentStyle="functionalKeyStyle" />
</case>
<case
- latin:imeOptions="actionDone"
+ latin:imeAction="actionDone"
>
<key-style
latin:styleName="returnKeyStyle"
@@ -209,7 +209,7 @@
latin:parentStyle="functionalKeyStyle" />
</case>
<case
- latin:imeOptions="actionSend"
+ latin:imeAction="actionSend"
>
<key-style
latin:styleName="returnKeyStyle"
@@ -218,7 +218,7 @@
latin:parentStyle="functionalKeyStyle" />
</case>
<case
- latin:imeOptions="actionSearch"
+ latin:imeAction="actionSearch"
>
<switch>
<case
diff --git a/java/res/xml/prefs.xml b/java/res/xml/prefs.xml
index 9ea801e..d031415 100644
--- a/java/res/xml/prefs.xml
+++ b/java/res/xml/prefs.xml
@@ -38,6 +38,7 @@
<CheckBoxPreference
android:key="sound_on"
android:title="@string/sound_on_keypress"
+ android:defaultValue="@bool/config_default_sound_enabled"
android:persistent="true"
/>
@@ -53,7 +54,7 @@
android:title="@string/prefs_enable_recorrection"
android:summary="@string/prefs_enable_recorrection_summary"
android:persistent="true"
- android:defaultValue="@bool/default_recorrection_enabled"
+ android:defaultValue="@bool/config_default_recorrection_enabled"
/>
<ListPreference
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index 23886ad..7396f05 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -95,7 +95,7 @@
public boolean mPressed;
/** If this is a sticky key, is it on? */
public boolean mOn;
- /** Key is enabled or not. */
+ /** Key is enabled and responds on press */
public boolean mEnabled = true;
private final static int[] KEY_STATE_NORMAL_ON = {
@@ -226,6 +226,7 @@
mRepeatable = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isRepeatable, false);
mModifier = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isModifier, false);
mSticky = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isSticky, false);
+ mEnabled = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_enabled, true);
mEdgeFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyEdgeFlags, 0)
| row.mRowEdgeFlags;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
index e7a9d85..1a4f901 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
@@ -17,10 +17,12 @@
package com.android.inputmethod.keyboard;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
public abstract class KeyDetector {
public static final int NOT_A_KEY = -1;
+ public static final int NOT_A_CODE = -1;
protected Keyboard mKeyboard;
@@ -104,8 +106,35 @@
*
* @param x The x-coordinate of a touch point
* @param y The y-coordinate of a touch point
- * @param allKeys All nearby key indices are returned in this array
+ * @param allCodes All nearby key code except functional key are returned in this array
* @return The nearest key index
*/
- abstract public int getKeyIndexAndNearbyCodes(int x, int y, int[] allKeys);
+ abstract public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes);
+
+ /**
+ * Compute the most common key width in order to use it as proximity key detection threshold.
+ *
+ * @param keyboard The keyboard to compute the most common key width
+ * @return The most common key width in the keyboard
+ */
+ public static int getMostCommonKeyWidth(final Keyboard keyboard) {
+ if (keyboard == null) return 0;
+ final List<Key> keys = keyboard.getKeys();
+ if (keys == null || keys.size() == 0) return 0;
+ final HashMap<Integer, Integer> histogram = new HashMap<Integer, Integer>();
+ int maxCount = 0;
+ int mostCommonWidth = 0;
+ for (final Key key : keys) {
+ final Integer width = key.mWidth + key.mGap;
+ Integer count = histogram.get(width);
+ if (count == null)
+ count = 0;
+ histogram.put(width, ++count);
+ if (count > maxCount) {
+ maxCount = count;
+ mostCommonWidth = width;
+ }
+ }
+ return mostCommonWidth;
+ }
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyStyles.java b/java/src/com/android/inputmethod/keyboard/KeyStyles.java
index 44ec531..169f2e6 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyStyles.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyStyles.java
@@ -188,6 +188,7 @@
readBoolean(keyAttr, R.styleable.Keyboard_Key_isModifier);
readBoolean(keyAttr, R.styleable.Keyboard_Key_isSticky);
readBoolean(keyAttr, R.styleable.Keyboard_Key_isRepeatable);
+ readBoolean(keyAttr, R.styleable.Keyboard_Key_enabled);
}
private void readDrawable(TypedArray a, int index) {
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index db86740..d09f678 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -17,6 +17,7 @@
package com.android.inputmethod.keyboard;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.Utils;
import android.view.inputmethod.EditorInfo;
@@ -41,29 +42,35 @@
public final int mMode;
public final int mXmlId;
public final int mColorScheme;
+ public final boolean mPasswordInput;
public final boolean mHasSettingsKey;
public final boolean mVoiceKeyEnabled;
public final boolean mHasVoiceKey;
- public final int mImeOptions;
+ public final int mImeAction;
public final boolean mEnableShiftLock;
public final String mXmlName;
private final int mHashCode;
- public KeyboardId(String xmlName, int xmlId, Locale locale, int orientation, int mode,
- int colorScheme, boolean hasSettingsKey, boolean voiceKeyEnabled, boolean hasVoiceKey,
- int imeOptions, boolean enableShiftLock) {
+ public KeyboardId(String xmlName, int xmlId, int colorScheme, Locale locale, int orientation,
+ int mode, EditorInfo attribute, boolean hasSettingsKey, boolean voiceKeyEnabled,
+ boolean hasVoiceKey, boolean enableShiftLock) {
+ final int inputType = (attribute != null) ? attribute.inputType : 0;
+ final int imeOptions = (attribute != null) ? attribute.imeOptions : 0;
this.mLocale = locale;
this.mOrientation = orientation;
this.mMode = mode;
this.mXmlId = xmlId;
this.mColorScheme = colorScheme;
+ this.mPasswordInput = Utils.isPasswordInputType(inputType)
+ || Utils.isVisiblePasswordInputType(inputType);
this.mHasSettingsKey = hasSettingsKey;
this.mVoiceKeyEnabled = voiceKeyEnabled;
this.mHasVoiceKey = hasVoiceKey;
- // We are interested only in IME_MASK_ACTION enum value and IME_FLAG_NO_ENTER_ACTION.
- this.mImeOptions = imeOptions
- & (EditorInfo.IME_MASK_ACTION | EditorInfo.IME_FLAG_NO_ENTER_ACTION);
+ // We are interested only in {@link EditorInfo#IME_MASK_ACTION} enum value and
+ // {@link EditorInfo#IME_FLAG_NO_ENTER_ACTION}.
+ this.mImeAction = imeOptions & (
+ EditorInfo.IME_MASK_ACTION | EditorInfo.IME_FLAG_NO_ENTER_ACTION);
this.mEnableShiftLock = enableShiftLock;
this.mXmlName = xmlName;
@@ -73,10 +80,11 @@
mode,
xmlId,
colorScheme,
+ mPasswordInput,
hasSettingsKey,
voiceKeyEnabled,
hasVoiceKey,
- imeOptions,
+ mImeAction,
enableShiftLock,
});
}
@@ -112,10 +120,11 @@
&& other.mMode == this.mMode
&& other.mXmlId == this.mXmlId
&& other.mColorScheme == this.mColorScheme
+ && other.mPasswordInput == this.mPasswordInput
&& other.mHasSettingsKey == this.mHasSettingsKey
&& other.mVoiceKeyEnabled == this.mVoiceKeyEnabled
&& other.mHasVoiceKey == this.mHasVoiceKey
- && other.mImeOptions == this.mImeOptions
+ && other.mImeAction == this.mImeAction
&& other.mEnableShiftLock == this.mEnableShiftLock;
}
@@ -126,17 +135,19 @@
@Override
public String toString() {
- return String.format("[%s.xml %s %s %s imeOptions=%s %s%s%s%s%s]",
+ return String.format("[%s.xml %s %s %s imeAction=%s %s%s%s%s%s%s]",
mXmlName,
mLocale,
(mOrientation == 1 ? "port" : "land"),
modeName(mMode),
- imeOptionsName(mImeOptions),
- colorSchemeName(mColorScheme),
+ imeOptionsName(mImeAction),
+ (mPasswordInput ? " passwordInput" : ""),
(mHasSettingsKey ? " hasSettingsKey" : ""),
(mVoiceKeyEnabled ? " voiceKeyEnabled" : ""),
(mHasVoiceKey ? " hasVoiceKey" : ""),
- (mEnableShiftLock ? " enableShiftLock" : ""));
+ (mEnableShiftLock ? " enableShiftLock" : ""),
+ colorSchemeName(mColorScheme)
+ );
}
public static String modeName(int mode) {
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardParser.java b/java/src/com/android/inputmethod/keyboard/KeyboardParser.java
index e8324e5..df64ad5 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardParser.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardParser.java
@@ -103,7 +103,7 @@
*/
public class KeyboardParser {
- private static final String TAG = "KeyboardParser";
+ private static final String TAG = KeyboardParser.class.getSimpleName();
private static final boolean DEBUG = false;
// Keyboard XML Tags
@@ -279,8 +279,8 @@
checkEndTag(TAG_KEY, parser);
} else {
Key key = new Key(mResources, row, mCurrentX, mCurrentY, parser, mKeyStyles);
- if (DEBUG) Log.d(TAG, String.format("<%s keyLabel=%s code=%d popupCharacters=%s />",
- TAG_KEY, key.mLabel, key.mCode,
+ if (DEBUG) Log.d(TAG, String.format("<%s%s keyLabel=%s code=%d popupCharacters=%s />",
+ TAG_KEY, (key.mEnabled ? "" : " disabled"), key.mLabel, key.mCode,
Arrays.toString(key.mPopupCharacters)));
checkEndTag(TAG_KEY, parser);
keys.add(key);
@@ -419,6 +419,8 @@
try {
final boolean modeMatched = matchInteger(a,
R.styleable.Keyboard_Case_mode, id.mMode);
+ final boolean passwordInputMatched = matchBoolean(a,
+ R.styleable.Keyboard_Case_passwordInput, id.mPasswordInput);
final boolean settingsKeyMatched = matchBoolean(a,
R.styleable.Keyboard_Case_hasSettingsKey, id.mHasSettingsKey);
final boolean voiceEnabledMatched = matchBoolean(a,
@@ -427,24 +429,30 @@
R.styleable.Keyboard_Case_hasVoiceKey, id.mHasVoiceKey);
final boolean colorSchemeMatched = matchInteger(viewAttr,
R.styleable.KeyboardView_colorScheme, id.mColorScheme);
- // As noted at KeyboardSwitcher.KeyboardId class, we are interested only in
- // enum value masked by IME_MASK_ACTION and IME_FLAG_NO_ENTER_ACTION. So matching
+ // As noted at {@link KeyboardId} class, we are interested only in enum value masked by
+ // {@link android.view.inputmethod.EditorInfo#IME_MASK_ACTION} and
+ // {@link android.view.inputmethod.EditorInfo#IME_FLAG_NO_ENTER_ACTION}. So matching
// this attribute with id.mImeOptions as integer value is enough for our purpose.
- final boolean imeOptionsMatched = matchInteger(a,
- R.styleable.Keyboard_Case_imeOptions, id.mImeOptions);
- final boolean selected = modeMatched && settingsKeyMatched && voiceEnabledMatched
- && voiceKeyMatched && colorSchemeMatched && imeOptionsMatched;
+ final boolean imeActionMatched = matchInteger(a,
+ R.styleable.Keyboard_Case_imeAction, id.mImeAction);
+ final boolean languageCodeMatched = matchString(a,
+ R.styleable.Keyboard_Case_languageCode, id.mLocale.getLanguage());
+ final boolean selected = modeMatched && passwordInputMatched && settingsKeyMatched
+ && voiceEnabledMatched && voiceKeyMatched && colorSchemeMatched
+ && imeActionMatched && languageCodeMatched;
- if (DEBUG) Log.d(TAG, String.format("<%s%s%s%s%s%s%s> %s", TAG_CASE,
+ if (DEBUG) Log.d(TAG, String.format("<%s%s%s%s%s%s%s%s%s> %s", TAG_CASE,
textAttr(KeyboardId.modeName(
a.getInt(R.styleable.Keyboard_Case_mode, -1)), "mode"),
textAttr(KeyboardId.colorSchemeName(
a.getInt(R.styleable.KeyboardView_colorScheme, -1)), "colorSchemeName"),
+ booleanAttr(a, R.styleable.Keyboard_Case_passwordInput, "passwordInput"),
booleanAttr(a, R.styleable.Keyboard_Case_hasSettingsKey, "hasSettingsKey"),
booleanAttr(a, R.styleable.Keyboard_Case_voiceKeyEnabled, "voiceKeyEnabled"),
booleanAttr(a, R.styleable.Keyboard_Case_hasVoiceKey, "hasVoiceKey"),
textAttr(KeyboardId.imeOptionsName(
- a.getInt(R.styleable.Keyboard_Case_imeOptions, -1)), "imeOptions"),
+ a.getInt(R.styleable.Keyboard_Case_imeAction, -1)), "imeAction"),
+ textAttr(a.getString(R.styleable.Keyboard_Case_languageCode), "languageCode"),
Boolean.toString(selected)));
return selected;
@@ -466,6 +474,12 @@
return !a.hasValue(index) || a.getBoolean(index, false) == value;
}
+ private static boolean matchString(TypedArray a, int index, String value) {
+ // If <case> does not have "index" attribute, that means this <case> is wild-card for the
+ // attribute.
+ return !a.hasValue(index) || a.getString(index).equals(value);
+ }
+
private boolean parseDefault(XmlResourceParser parser, Row row, List<Key> keys)
throws XmlPullParserException, IOException {
if (DEBUG) Log.d(TAG, String.format("<%s>", TAG_DEFAULT));
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 2648ff3..3297dc3 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -28,6 +28,7 @@
import android.content.res.Resources;
import android.util.Log;
import android.view.InflateException;
+import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import java.lang.ref.SoftReference;
@@ -66,8 +67,9 @@
private final HashMap<KeyboardId, SoftReference<LatinKeyboard>> mKeyboardCache =
new HashMap<KeyboardId, SoftReference<LatinKeyboard>>();
+ // TODO: clean mMode up and use mAttribute instead.
private int mMode = KeyboardId.MODE_TEXT; /* default value */
- private int mImeOptions;
+ private EditorInfo mAttribute;
private boolean mIsSymbols;
/** mIsAutoCorrectionActive indicates that auto corrected word will be input instead of
* what user actually typed. */
@@ -128,10 +130,7 @@
final int orientation = res.getConfiguration().orientation;
final int mode = mMode;
final int colorScheme = getColorScheme();
- final boolean hasSettingsKey = mHasSettingsKey;
- final boolean voiceKeyEnabled = mVoiceKeyEnabled;
- final boolean hasVoiceKey = voiceKeyEnabled && !mVoiceButtonOnPrimary;
- final int imeOptions = mImeOptions;
+ final boolean hasVoiceKey = mVoiceKeyEnabled && !mVoiceButtonOnPrimary;
// Note: This comment is only applied for phone number keyboard layout.
// On non-xlarge device, "@integer/key_switch_alpha_symbol" key code is used to switch
// between "phone keyboard" and "phone symbols keyboard". But on xlarge device,
@@ -140,50 +139,43 @@
// mSymbolsId and mSymbolsShiftedId to "phone keyboard" and "phone symbols keyboard"
// respectively here for xlarge device's layout switching.
int xmlId = mode == KeyboardId.MODE_PHONE ? R.xml.kbd_phone : R.xml.kbd_symbols;
- mSymbolsId = new KeyboardId(
- res.getResourceEntryName(xmlId), xmlId, locale, orientation, mode, colorScheme,
- hasSettingsKey, voiceKeyEnabled, hasVoiceKey, imeOptions, true);
+ final String xmlName = res.getResourceEntryName(xmlId);
+ mSymbolsId = new KeyboardId(xmlName, xmlId, colorScheme, locale, orientation, mode,
+ mAttribute, mHasSettingsKey, mVoiceKeyEnabled, hasVoiceKey, true);
xmlId = mode == KeyboardId.MODE_PHONE ? R.xml.kbd_phone_symbols : R.xml.kbd_symbols_shift;
- mSymbolsShiftedId = new KeyboardId(
- res.getResourceEntryName(xmlId), xmlId, locale, orientation, mode, colorScheme,
- hasSettingsKey, voiceKeyEnabled, hasVoiceKey, imeOptions, true);
+ mSymbolsShiftedId = new KeyboardId(xmlName, xmlId, colorScheme, locale, orientation, mode,
+ mAttribute, mHasSettingsKey, mVoiceKeyEnabled, hasVoiceKey, true);
}
private boolean hasVoiceKey(boolean isSymbols) {
return mVoiceKeyEnabled && (isSymbols != mVoiceButtonOnPrimary);
}
- public void loadKeyboard(int mode, int imeOptions, boolean voiceKeyEnabled,
+ public void loadKeyboard(int mode, EditorInfo attribute, boolean voiceKeyEnabled,
boolean voiceButtonOnPrimary) {
mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_ALPHA;
try {
- if (mInputView == null) return;
- final Keyboard oldKeyboard = mInputView.getKeyboard();
- loadKeyboardInternal(mode, imeOptions, voiceKeyEnabled, voiceButtonOnPrimary, false);
- final Keyboard newKeyboard = mInputView.getKeyboard();
- if (newKeyboard.isAlphaKeyboard()) {
- final boolean localeChanged = (oldKeyboard == null)
- || !newKeyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale);
- mInputMethodService.mHandler.startDisplayLanguageOnSpacebar(localeChanged);
- }
+ loadKeyboardInternal(mode, attribute, voiceKeyEnabled, voiceButtonOnPrimary, false);
} catch (RuntimeException e) {
- Log.w(TAG, e);
- LatinImeLogger.logOnException(mode + "," + imeOptions, e);
+ // Get KeyboardId to record which keyboard has been failed to load.
+ final KeyboardId id = getKeyboardId(mode, attribute, false);
+ Log.w(TAG, "loading keyboard failed: " + id, e);
+ LatinImeLogger.logOnException(id.toString(), e);
}
}
- private void loadKeyboardInternal(int mode, int imeOptions, boolean voiceButtonEnabled,
+ private void loadKeyboardInternal(int mode, EditorInfo attribute, boolean voiceButtonEnabled,
boolean voiceButtonOnPrimary, boolean isSymbols) {
if (mInputView == null) return;
mMode = mode;
- mImeOptions = imeOptions;
+ mAttribute = attribute;
mVoiceKeyEnabled = voiceButtonEnabled;
mVoiceButtonOnPrimary = voiceButtonOnPrimary;
mIsSymbols = isSymbols;
// Update the settings key state because number of enabled IMEs could have been changed
mHasSettingsKey = getSettingsKeyMode(mPrefs, mInputMethodService);
- final KeyboardId id = getKeyboardId(mode, imeOptions, isSymbols);
+ final KeyboardId id = getKeyboardId(mode, attribute, isSymbols);
final Keyboard oldKeyboard = mInputView.getKeyboard();
if (oldKeyboard != null && oldKeyboard.mId.equals(id))
@@ -192,7 +184,15 @@
makeSymbolsKeyboardIds();
mCurrentId = id;
mInputView.setPreviewEnabled(mInputMethodService.getPopupOn());
- mInputView.setKeyboard(getKeyboard(id));
+ setKeyboard(getKeyboard(id));
+ }
+
+ private void setKeyboard(final Keyboard newKeyboard) {
+ final Keyboard oldKeyboard = mInputView.getKeyboard();
+ mInputView.setKeyboard(newKeyboard);
+ final boolean localeChanged = (oldKeyboard == null)
+ || !newKeyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale);
+ mInputMethodService.mHandler.startDisplayLanguageOnSpacebar(localeChanged);
}
private LatinKeyboard getKeyboard(KeyboardId id) {
@@ -228,7 +228,7 @@
return keyboard;
}
- private KeyboardId getKeyboardId(int mode, int imeOptions, boolean isSymbols) {
+ private KeyboardId getKeyboardId(int mode, EditorInfo attribute, boolean isSymbols) {
final boolean hasVoiceKey = hasVoiceKey(isSymbols);
final int charColorId = getColorScheme();
final int xmlId;
@@ -260,8 +260,8 @@
final int orientation = res.getConfiguration().orientation;
final Locale locale = mSubtypeSwitcher.getInputLocale();
return new KeyboardId(
- res.getResourceEntryName(xmlId), xmlId, locale, orientation, mode, charColorId,
- mHasSettingsKey, mVoiceKeyEnabled, hasVoiceKey, imeOptions, enableShiftLock);
+ res.getResourceEntryName(xmlId), xmlId, charColorId, locale, orientation, mode,
+ attribute, mHasSettingsKey, mVoiceKeyEnabled, hasVoiceKey, enableShiftLock);
}
public int getKeyboardMode() {
@@ -278,22 +278,19 @@
public boolean isKeyboardAvailable() {
if (mInputView != null)
- return mInputView.getLatinKeyboard() != null;
+ return mInputView.getKeyboard() != null;
return false;
}
- private LatinKeyboard getLatinKeyboard() {
- if (mInputView != null)
- return mInputView.getLatinKeyboard();
+ public LatinKeyboard getLatinKeyboard() {
+ if (mInputView != null) {
+ final Keyboard keyboard = mInputView.getKeyboard();
+ if (keyboard instanceof LatinKeyboard)
+ return (LatinKeyboard)keyboard;
+ }
return null;
}
- public void setPreferredLetters(int[] frequencies) {
- LatinKeyboard latinKeyboard = getLatinKeyboard();
- if (latinKeyboard != null)
- latinKeyboard.setPreferredLetters(frequencies);
- }
-
public void keyReleased() {
LatinKeyboard latinKeyboard = getLatinKeyboard();
if (latinKeyboard != null)
@@ -556,7 +553,7 @@
// indicator, we need to call enableShiftLock() and setShiftLocked(false).
keyboard.setShifted(false);
}
- mInputView.setKeyboard(keyboard);
+ setKeyboard(keyboard);
}
public boolean isInMomentaryAutoModeSwitchState() {
@@ -572,7 +569,7 @@
}
private void toggleKeyboardMode() {
- loadKeyboardInternal(mMode, mImeOptions, mVoiceKeyEnabled, mVoiceButtonOnPrimary,
+ loadKeyboardInternal(mMode, mAttribute, mVoiceKeyEnabled, mVoiceButtonOnPrimary,
!mIsSymbols);
if (mIsSymbols) {
mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_SYMBOL_BEGIN;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 19f1fa8..7fee022 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -26,6 +26,7 @@
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.PorterDuff;
@@ -68,8 +69,7 @@
* @attr ref R.styleable#KeyboardView_popupLayout
*/
public class KeyboardView extends View implements PointerTracker.UIProxy {
- private static final String TAG = "KeyboardView";
- private static final boolean DEBUG = false;
+ private static final String TAG = KeyboardView.class.getSimpleName();
private static final boolean DEBUG_SHOW_ALIGN = false;
private static final boolean DEBUG_KEYBOARD_GRID = false;
@@ -115,7 +115,6 @@
private int[] mOffsetInWindow;
private int mOldPreviewKeyIndex = KeyDetector.NOT_A_KEY;
private boolean mShowPreview = true;
- private boolean mShowTouchPoints = true;
private int mPopupPreviewOffsetX;
private int mPopupPreviewOffsetY;
private int mWindowY;
@@ -158,18 +157,20 @@
// Drawing
/** Whether the keyboard bitmap needs to be redrawn before it's blitted. **/
private boolean mDrawPending;
- /** The dirty region in the keyboard bitmap */
- private final Rect mDirtyRect = new Rect();
- /** The keyboard bitmap for faster updates */
- private Bitmap mBuffer;
/** Notes if the keyboard just changed, so that we could possibly reallocate the mBuffer. */
private boolean mKeyboardChanged;
+ /** The dirty region in the keyboard bitmap */
+ private final Rect mDirtyRect = new Rect();
+ /** The key to invalidate. */
private Key mInvalidatedKey;
+ /** The dirty region for single key drawing */
+ private final Rect mInvalidatedKeyRect = new Rect();
+ /** The keyboard bitmap for faster updates */
+ private Bitmap mBuffer;
/** The canvas for the above mutable keyboard bitmap */
private Canvas mCanvas;
private final Paint mPaint;
private final Rect mPadding;
- private final Rect mClipRegion = new Rect(0, 0, 0, 0);
// This map caches key label text height in pixel as value and key label text size as map key.
private final HashMap<Integer, Integer> mTextHeightCache = new HashMap<Integer, Integer>();
// Distance from horizontal center of the key, proportional to key label text height and width.
@@ -506,10 +507,9 @@
tracker.setKeyboard(keyboard, mKeys, mKeyHysteresisDistance);
}
requestLayout();
- // Hint to reallocate the buffer if the size changed
mKeyboardChanged = true;
invalidateAllKeys();
- computeProximityThreshold(keyboard, mKeys);
+ mKeyDetector.setProximityThreshold(KeyDetector.getMostCommonKeyWidth(keyboard));
mMiniKeyboardCache.clear();
}
@@ -601,37 +601,6 @@
}
}
- /**
- * Compute the most common key width and use it as proximity key detection threshold.
- * @param keyboard
- * @param keys
- */
- private void computeProximityThreshold(Keyboard keyboard, Key[] keys) {
- if (keyboard == null || keys == null || keys.length == 0) return;
- final HashMap<Integer, Integer> histogram = new HashMap<Integer, Integer>();
- int maxCount = 0;
- int mostCommonWidth = 0;
- for (Key key : keys) {
- final Integer width = key.mWidth + key.mGap;
- Integer count = histogram.get(width);
- if (count == null)
- count = 0;
- histogram.put(width, ++count);
- if (count > maxCount) {
- maxCount = count;
- mostCommonWidth = width;
- }
- }
- mKeyDetector.setProximityThreshold(mostCommonWidth);
- }
-
- @Override
- public void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
- // Release the buffer, if any and it will be reallocated on the next draw
- mBuffer = null;
- }
-
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
@@ -641,19 +610,18 @@
canvas.drawBitmap(mBuffer, 0, 0, null);
}
- @SuppressWarnings("unused")
private void onBufferDraw() {
+ final int width = getWidth();
+ final int height = getHeight();
+ if (width == 0 || height == 0)
+ return;
if (mBuffer == null || mKeyboardChanged) {
- if (mBuffer == null || mKeyboardChanged &&
- (mBuffer.getWidth() != getWidth() || mBuffer.getHeight() != getHeight())) {
- // Make sure our bitmap is at least 1x1
- final int width = Math.max(1, getWidth());
- final int height = Math.max(1, getHeight());
- mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- mCanvas = new Canvas(mBuffer);
- }
- invalidateAllKeys();
mKeyboardChanged = false;
+ mDirtyRect.union(0, 0, width, height);
+ }
+ if (mBuffer == null || mBuffer.getWidth() != width || mBuffer.getHeight() != height) {
+ mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ mCanvas = new Canvas(mBuffer);
}
final Canvas canvas = mCanvas;
canvas.clipRect(mDirtyRect, Op.REPLACE);
@@ -662,30 +630,19 @@
final Paint paint = mPaint;
final Drawable keyBackground = mKeyBackground;
- final Rect clipRegion = mClipRegion;
final Rect padding = mPadding;
final int kbdPaddingLeft = getPaddingLeft();
final int kbdPaddingTop = getPaddingTop();
final Key[] keys = mKeys;
- final Key invalidKey = mInvalidatedKey;
final boolean isManualTemporaryUpperCase = mKeyboard.isManualTemporaryUpperCase();
+ final boolean drawSingleKey = (mInvalidatedKey != null
+ && mInvalidatedKeyRect.contains(mDirtyRect));
- boolean drawSingleKey = false;
- if (invalidKey != null && canvas.getClipBounds(clipRegion)) {
- // TODO we should use Rect.inset and Rect.contains here.
- // Is clipRegion completely contained within the invalidated key?
- if (invalidKey.mX + kbdPaddingLeft - 1 <= clipRegion.left &&
- invalidKey.mY + kbdPaddingTop - 1 <= clipRegion.top &&
- invalidKey.mX + invalidKey.mWidth + kbdPaddingLeft + 1 >= clipRegion.right &&
- invalidKey.mY + invalidKey.mHeight + kbdPaddingTop + 1 >= clipRegion.bottom) {
- drawSingleKey = true;
- }
- }
canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
final int keyCount = keys.length;
for (int i = 0; i < keyCount; i++) {
final Key key = keys[i];
- if (drawSingleKey && invalidKey != key) {
+ if (drawSingleKey && key != mInvalidatedKey) {
continue;
}
int[] drawableState = key.getCurrentDrawableState();
@@ -739,16 +696,23 @@
} else {
positionX = (key.mWidth + padding.left - padding.right) / 2;
paint.setTextAlign(Align.CENTER);
- if (DEBUG_SHOW_ALIGN && label.length() > 1)
- drawVerticalLine(canvas, positionX, rowHeight, 0xc0008080, new Paint());
+ if (DEBUG_SHOW_ALIGN) {
+ if (label.length() > 1)
+ drawVerticalLine(canvas, positionX, rowHeight, 0xc0008080, new Paint());
+ }
}
if (key.mManualTemporaryUpperCaseHintIcon != null && isManualTemporaryUpperCase) {
paint.setColor(mKeyTextColorDisabled);
} else {
paint.setColor(mKeyTextColor);
}
- // Set a drop shadow for the text
- paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor);
+ if (key.mEnabled) {
+ // Set a drop shadow for the text
+ paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor);
+ } else {
+ // Make label invisible
+ paint.setColor(Color.TRANSPARENT);
+ }
canvas.drawText(label, positionX, baseline, paint);
// Turn off drop shadow
paint.setShadowLayer(0, 0, 0, 0);
@@ -811,32 +775,13 @@
canvas.drawLine(0, i * ch, cw * mKeyboard.GRID_WIDTH, i * ch, p);
}
- mInvalidatedKey = null;
// Overlay a dark rectangle to dim the keyboard
if (mMiniKeyboardView != null) {
paint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24);
- canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
+ canvas.drawRect(0, 0, width, height, paint);
}
- if (DEBUG) {
- if (mShowTouchPoints) {
- for (PointerTracker tracker : mPointerTrackers) {
- int startX = tracker.getStartX();
- int startY = tracker.getStartY();
- int lastX = tracker.getLastX();
- int lastY = tracker.getLastY();
- paint.setAlpha(128);
- paint.setColor(0xFFFF0000);
- canvas.drawCircle(startX, startY, 3, paint);
- canvas.drawLine(startX, startY, lastX, lastY, paint);
- paint.setColor(0xFF0000FF);
- canvas.drawCircle(lastX, lastY, 3, paint);
- paint.setColor(0xFF00FF00);
- canvas.drawCircle((startX + lastX) / 2, (startY + lastY) / 2, 2, paint);
- }
- }
- }
-
+ mInvalidatedKey = null;
mDrawPending = false;
mDirtyRect.setEmpty();
}
@@ -1050,12 +995,11 @@
if (key == null)
return;
mInvalidatedKey = key;
- // TODO we should clean up this and record key's region to use in onBufferDraw.
- mDirtyRect.union(key.mX + getPaddingLeft(), key.mY + getPaddingTop(),
- key.mX + key.mWidth + getPaddingLeft(), key.mY + key.mHeight + getPaddingTop());
+ mInvalidatedKeyRect.set(0, 0, key.mWidth, key.mHeight);
+ mInvalidatedKeyRect.offset(key.mX + getPaddingLeft(), key.mY + getPaddingTop());
+ mDirtyRect.union(mInvalidatedKeyRect);
onBufferDraw();
- invalidate(key.mX + getPaddingLeft(), key.mY + getPaddingTop(),
- key.mX + key.mWidth + getPaddingLeft(), key.mY + key.mHeight + getPaddingTop());
+ invalidate(mInvalidatedKeyRect);
}
private boolean openPopupIfRequired(int keyIndex, PointerTracker tracker) {
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
index ffb8d64..5820049 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -31,17 +32,12 @@
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
-import android.util.Log;
import java.util.List;
import java.util.Locale;
// TODO: We should remove this class
public class LatinKeyboard extends Keyboard {
-
- private static final boolean DEBUG_PREFERRED_LETTER = false;
- private static final String TAG = "LatinKeyboard";
-
public static final int OPACITY_FULLY_OPAQUE = 255;
private static final int SPACE_LED_LENGTH_PERCENT = 80;
@@ -69,15 +65,7 @@
private final Drawable mEnabledShortcutIcon;
private final Drawable mDisabledShortcutIcon;
- private int[] mPrefLetterFrequencies;
- private int mPrefLetter;
- private int mPrefLetterX;
- private int mPrefLetterY;
- private int mPrefDistance;
-
private static final float SPACEBAR_DRAG_THRESHOLD = 0.8f;
- private static final float OVERLAP_PERCENTAGE_LOW_PROB = 0.70f;
- private static final float OVERLAP_PERCENTAGE_HIGH_PROB = 0.85f;
// Minimum width of space key preview (proportional to keyboard width)
private static final float SPACEBAR_POPUP_MIN_RATIO = 0.4f;
// Height in space key the language name will be drawn. (proportional to space key height)
@@ -167,6 +155,8 @@
}
private void updateSpacebarForLocale(boolean isAutoCorrection) {
+ if (mSpaceKey == null)
+ return;
final Resources res = mContext.getResources();
// If application locales are explicitly selected.
if (SubtypeSwitcher.getInstance().needsToDisplayLanguage()) {
@@ -265,7 +255,7 @@
final boolean allowVariableTextSize = true;
final String language = layoutSpacebar(paint, subtypeSwitcher.getInputLocale(),
mButtonArrowLeftIcon, mButtonArrowRightIcon, width, height,
- getTextSizeFromTheme(textStyle, defaultTextSize),
+ getTextSizeFromTheme(mContext.getTheme(), textStyle, defaultTextSize),
allowVariableTextSize);
// Draw language text with shadow
@@ -334,18 +324,9 @@
return mSpaceDragLastDiff > 0 ? 1 : -1;
}
- public void setPreferredLetters(int[] frequencies) {
- mPrefLetterFrequencies = frequencies;
- mPrefLetter = 0;
- }
-
public void keyReleased() {
mCurrentlyInSpace = false;
mSpaceDragLastDiff = 0;
- mPrefLetter = 0;
- mPrefLetterX = 0;
- mPrefLetterY = 0;
- mPrefDistance = Integer.MAX_VALUE;
if (mSpaceKey != null) {
updateLocaleDrag(Integer.MAX_VALUE);
}
@@ -381,80 +362,6 @@
return isOnSpace;
}
}
- } else if (mPrefLetterFrequencies != null) {
- // New coordinate? Reset
- if (mPrefLetterX != x || mPrefLetterY != y) {
- mPrefLetter = 0;
- mPrefDistance = Integer.MAX_VALUE;
- }
- // Handle preferred next letter
- final int[] pref = mPrefLetterFrequencies;
- if (mPrefLetter > 0) {
- if (DEBUG_PREFERRED_LETTER) {
- if (mPrefLetter == code && !key.isOnKey(x, y)) {
- Log.d(TAG, "CORRECTED !!!!!!");
- }
- }
- return mPrefLetter == code;
- } else {
- final boolean isOnKey = key.isOnKey(x, y);
- int[] nearby = getNearestKeys(x, y);
- List<Key> nearbyKeys = getKeys();
- if (isOnKey) {
- // If it's a preferred letter
- if (inPrefList(code, pref)) {
- // Check if its frequency is much lower than a nearby key
- mPrefLetter = code;
- mPrefLetterX = x;
- mPrefLetterY = y;
- for (int i = 0; i < nearby.length; i++) {
- Key k = nearbyKeys.get(nearby[i]);
- if (k != key && inPrefList(k.mCode, pref)) {
- final int dist = distanceFrom(k, x, y);
- if (dist < (int) (k.mWidth * OVERLAP_PERCENTAGE_LOW_PROB) &&
- (pref[k.mCode] > pref[mPrefLetter] * 3)) {
- mPrefLetter = k.mCode;
- mPrefDistance = dist;
- if (DEBUG_PREFERRED_LETTER) {
- Log.d(TAG, "CORRECTED ALTHOUGH PREFERRED !!!!!!");
- }
- break;
- }
- }
- }
-
- return mPrefLetter == code;
- }
- }
-
- // Get the surrounding keys and intersect with the preferred list
- // For all in the intersection
- // if distance from touch point is within a reasonable distance
- // make this the pref letter
- // If no pref letter
- // return inside;
- // else return thiskey == prefletter;
-
- for (int i = 0; i < nearby.length; i++) {
- Key k = nearbyKeys.get(nearby[i]);
- if (inPrefList(k.mCode, pref)) {
- final int dist = distanceFrom(k, x, y);
- if (dist < (int) (k.mWidth * OVERLAP_PERCENTAGE_HIGH_PROB)
- && dist < mPrefDistance) {
- mPrefLetter = k.mCode;
- mPrefLetterX = x;
- mPrefLetterY = y;
- mPrefDistance = dist;
- }
- }
- }
- // Didn't find any
- if (mPrefLetter == 0) {
- return isOnKey;
- } else {
- return mPrefLetter == code;
- }
- }
}
// Lock into the spacebar
@@ -463,19 +370,6 @@
return key.isOnKey(x, y);
}
- private boolean inPrefList(int code, int[] pref) {
- if (code < pref.length && code >= 0) return pref[code] > 0;
- return false;
- }
-
- private int distanceFrom(Key k, int x, int y) {
- if (y > k.mY && y < k.mY + k.mHeight) {
- return Math.abs(k.mX + k.mWidth / 2 - x);
- } else {
- return Integer.MAX_VALUE;
- }
- }
-
@Override
public int[] getNearestKeys(int x, int y) {
if (mCurrentlyInSpace) {
@@ -487,8 +381,8 @@
}
}
- private int getTextSizeFromTheme(int style, int defValue) {
- TypedArray array = mContext.getTheme().obtainStyledAttributes(
+ private static int getTextSizeFromTheme(Theme theme, int style, int defValue) {
+ TypedArray array = theme.obtainStyledAttributes(
style, new int[] { android.R.attr.textSize });
int textSize = array.getDimensionPixelSize(array.getResourceId(0, 0), defValue);
return textSize;
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index af2fd5c..6c3ad5e 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -66,7 +66,8 @@
}
}
- public void setLatinKeyboard(LatinKeyboard newKeyboard) {
+ @Override
+ public void setKeyboard(Keyboard newKeyboard) {
final LatinKeyboard oldKeyboard = getLatinKeyboard();
if (oldKeyboard != null) {
// Reset old keyboard state before switching to new keyboard.
@@ -80,7 +81,7 @@
mLastRowY = (newKeyboard.getHeight() * 3) / 4;
}
- public LatinKeyboard getLatinKeyboard() {
+ private LatinKeyboard getLatinKeyboard() {
Keyboard keyboard = getKeyboard();
if (keyboard instanceof LatinKeyboard) {
return (LatinKeyboard)keyboard;
@@ -141,6 +142,9 @@
* KeyboardView.
*/
private boolean handleSuddenJump(MotionEvent me) {
+ // If device has distinct multi touch panel, there is no need to check sudden jump.
+ if (hasDistinctMultitouch())
+ return false;
final int action = me.getAction();
final int x = (int) me.getX();
final int y = (int) me.getY();
diff --git a/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java b/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java
index f04991e..a8750d3 100644
--- a/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java
@@ -35,24 +35,24 @@
}
@Override
- public int getKeyIndexAndNearbyCodes(int x, int y, int[] allKeys) {
+ public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) {
final Key[] keys = getKeys();
final int touchX = getTouchX(x);
final int touchY = getTouchY(y);
- int closestKeyIndex = NOT_A_KEY;
- int closestKeyDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare;
+ int nearestIndex = NOT_A_KEY;
+ int nearestDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare;
final int keyCount = keys.length;
for (int index = 0; index < keyCount; index++) {
final int dist = keys[index].squaredDistanceToEdge(touchX, touchY);
- if (dist < closestKeyDist) {
- closestKeyIndex = index;
- closestKeyDist = dist;
+ if (dist < nearestDist) {
+ nearestIndex = index;
+ nearestDist = dist;
}
}
- if (allKeys != null && closestKeyIndex != NOT_A_KEY)
- allKeys[0] = keys[closestKeyIndex].mCode;
- return closestKeyIndex;
+ if (allCodes != null && nearestIndex != NOT_A_KEY)
+ allCodes[0] = keys[nearestIndex].mCode;
+ return nearestIndex;
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index a981f72..ac37547 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -129,32 +129,42 @@
}
// Returns true if keyboard has been changed by this callback.
- private boolean callListenerOnPressAndCheckKeyboardLayoutChange(int primaryCode) {
+ private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key) {
if (DEBUG_LISTENER)
- Log.d(TAG, "onPress : " + keyCodePrintable(primaryCode));
- mListener.onPress(primaryCode);
- final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged;
- mKeyboardLayoutHasBeenChanged = false;
- return keyboardLayoutHasBeenChanged;
+ Log.d(TAG, "onPress : " + keyCodePrintable(key.mCode));
+ if (key.mEnabled) {
+ mListener.onPress(key.mCode);
+ final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged;
+ mKeyboardLayoutHasBeenChanged = false;
+ return keyboardLayoutHasBeenChanged;
+ }
+ return false;
}
- private void callListenerOnCodeInput(int primaryCode, int[] keyCodes, int x, int y) {
+ // Note that we need primaryCode argument because the keyboard may in shifted state and the
+ // primaryCode is different from {@link Key#mCode}.
+ private void callListenerOnCodeInput(Key key, int primaryCode, int[] keyCodes, int x, int y) {
if (DEBUG_LISTENER)
Log.d(TAG, "onCodeInput: " + keyCodePrintable(primaryCode)
+ " codes="+ Arrays.toString(keyCodes) + " x=" + x + " y=" + y);
- mListener.onCodeInput(primaryCode, keyCodes, x, y);
+ if (key.mEnabled)
+ mListener.onCodeInput(primaryCode, keyCodes, x, y);
}
- private void callListenerOnTextInput(CharSequence text) {
+ private void callListenerOnTextInput(Key key) {
if (DEBUG_LISTENER)
- Log.d(TAG, "onTextInput: text=" + text);
- mListener.onTextInput(text);
+ Log.d(TAG, "onTextInput: text=" + key.mOutputText);
+ if (key.mEnabled)
+ mListener.onTextInput(key.mOutputText);
}
- private void callListenerOnRelease(int primaryCode) {
+ // Note that we need primaryCode argument because the keyboard may in shifted state and the
+ // primaryCode is different from {@link Key#mCode}.
+ private void callListenerOnRelease(Key key, int primaryCode) {
if (DEBUG_LISTENER)
Log.d(TAG, "onRelease : " + keyCodePrintable(primaryCode));
- mListener.onRelease(primaryCode);
+ if (key.mEnabled)
+ mListener.onRelease(primaryCode);
}
private void callListenerOnCancelInput() {
@@ -313,7 +323,7 @@
// This onPress call may have changed keyboard layout. Those cases are detected at
// {@link #setKeyboard}. In those cases, we should update keyIndex according to the new
// keyboard layout.
- if (callListenerOnPressAndCheckKeyboardLayoutChange(mKeys[keyIndex].mCode))
+ if (callListenerOnPressAndCheckKeyboardLayoutChange(mKeys[keyIndex]))
keyIndex = mKeyState.onDownKey(x, y, eventTime);
}
if (isValidKeyIndex(keyIndex)) {
@@ -346,7 +356,7 @@
// This onPress call may have changed keyboard layout. Those cases are detected at
// {@link #setKeyboard}. In those cases, we should update keyIndex according to the
// new keyboard layout.
- if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex).mCode))
+ if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex)))
keyIndex = keyState.onMoveKey(x, y);
keyState.onMoveToNewKey(keyIndex, x, y);
startLongPressTimer(keyIndex);
@@ -355,13 +365,13 @@
// onRelease() first to notify that the previous key has been released, then call
// onPress() to notify that the new key is being pressed.
mIsInSlidingKeyInput = true;
- callListenerOnRelease(oldKey.mCode);
+ callListenerOnRelease(oldKey, oldKey.mCode);
mHandler.cancelLongPressTimers();
if (mIsAllowedSlidingKeyInput) {
// This onPress call may have changed keyboard layout. Those cases are detected
// at {@link #setKeyboard}. In those cases, we should update keyIndex according
// to the new keyboard layout.
- if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex).mCode))
+ if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex)))
keyIndex = keyState.onMoveKey(x, y);
keyState.onMoveToNewKey(keyIndex, x, y);
startLongPressTimer(keyIndex);
@@ -390,7 +400,7 @@
// The pointer has been slid out from the previous key, we must call onRelease() to
// notify that the previous key has been released.
mIsInSlidingKeyInput = true;
- callListenerOnRelease(oldKey.mCode);
+ callListenerOnRelease(oldKey, oldKey.mCode);
mHandler.cancelLongPressTimers();
if (mIsAllowedSlidingKeyInput) {
keyState.onMoveToNewKey(keyIndex, x ,y);
@@ -490,15 +500,6 @@
return mKeyState.getDownTime();
}
- // These package scope methods are only for debugging purpose.
- /* package */ int getStartX() {
- return mKeyState.getStartX();
- }
-
- /* package */ int getStartY() {
- return mKeyState.getStartY();
- }
-
private boolean isMinorMoveBounce(int x, int y, int newKey) {
if (mKeys == null || mKeyHysteresisDistanceSquared < 0)
throw new IllegalStateException("keyboard and/or hysteresis not set");
@@ -548,8 +549,8 @@
return;
}
if (key.mOutputText != null) {
- callListenerOnTextInput(key.mOutputText);
- callListenerOnRelease(key.mCode);
+ callListenerOnTextInput(key);
+ callListenerOnRelease(key, key.mCode);
} else {
int code = key.mCode;
final int[] codes = mKeyDetector.newCodeArray();
@@ -570,9 +571,8 @@
codes[1] = codes[0];
codes[0] = code;
}
- if (key.mEnabled)
- callListenerOnCodeInput(code, codes, x, y);
- callListenerOnRelease(code);
+ callListenerOnCodeInput(key, code, codes, x, y);
+ callListenerOnRelease(key, code);
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java b/java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java
index 250bb95..a62ed96 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java
@@ -23,8 +23,6 @@
private final KeyDetector mKeyDetector;
// The position and time at which first down event occurred.
- private int mStartX;
- private int mStartY;
private long mDownTime;
private long mUpTime;
@@ -54,14 +52,6 @@
return mKeyY;
}
- public int getStartX() {
- return mStartX;
- }
-
- public int getStartY() {
- return mStartY;
- }
-
public long getDownTime() {
return mDownTime;
}
@@ -79,8 +69,6 @@
}
public int onDownKey(int x, int y, long eventTime) {
- mStartX = x;
- mStartY = y;
mDownTime = eventTime;
return onMoveToNewKey(onMoveKeyInternal(x, y), x, y);
}
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityKeyDetector.java b/java/src/com/android/inputmethod/keyboard/ProximityKeyDetector.java
index 0920da2..c3fd198 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityKeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityKeyDetector.java
@@ -16,49 +16,106 @@
package com.android.inputmethod.keyboard;
+import android.util.Log;
+
import java.util.Arrays;
public class ProximityKeyDetector extends KeyDetector {
+ private static final String TAG = ProximityKeyDetector.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
private static final int MAX_NEARBY_KEYS = 12;
// working area
- private int[] mDistances = new int[MAX_NEARBY_KEYS];
+ private final int[] mDistances = new int[MAX_NEARBY_KEYS];
+ private final int[] mIndices = new int[MAX_NEARBY_KEYS];
@Override
protected int getMaxNearbyKeys() {
return MAX_NEARBY_KEYS;
}
+ private void initializeNearbyKeys() {
+ Arrays.fill(mDistances, Integer.MAX_VALUE);
+ Arrays.fill(mIndices, NOT_A_KEY);
+ }
+
+ /**
+ * Insert the key into nearby keys buffer and sort nearby keys by ascending order of distance.
+ *
+ * @param keyIndex index of the key.
+ * @param distance distance between the key's edge and user touched point.
+ * @return order of the key in the nearby buffer, 0 if it is the nearest key.
+ */
+ private int sortNearbyKeys(int keyIndex, int distance) {
+ final int[] distances = mDistances;
+ final int[] indices = mIndices;
+ for (int insertPos = 0; insertPos < distances.length; insertPos++) {
+ if (distance < distances[insertPos]) {
+ final int nextPos = insertPos + 1;
+ if (nextPos < distances.length) {
+ System.arraycopy(distances, insertPos, distances, nextPos,
+ distances.length - nextPos);
+ System.arraycopy(indices, insertPos, indices, nextPos,
+ indices.length - nextPos);
+ }
+ distances[insertPos] = distance;
+ indices[insertPos] = keyIndex;
+ return insertPos;
+ }
+ }
+ return distances.length;
+ }
+
+ private void getNearbyKeyCodes(final int[] allCodes) {
+ final Key[] keys = getKeys();
+ final int[] indices = mIndices;
+
+ // allCodes[0] should always have the key code even if it is a non-letter key.
+ if (indices[0] == NOT_A_KEY) {
+ allCodes[0] = NOT_A_CODE;
+ return;
+ }
+
+ int numCodes = 0;
+ for (int j = 0; j < indices.length && numCodes < allCodes.length; j++) {
+ final int index = indices[j];
+ if (index == NOT_A_KEY)
+ break;
+ final int code = keys[index].mCode;
+ // filter out a non-letter key from nearby keys
+ if (code < Keyboard.CODE_SPACE)
+ continue;
+ allCodes[numCodes++] = code;
+ }
+ }
+
@Override
- public int getKeyIndexAndNearbyCodes(int x, int y, int[] allKeys) {
+ public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) {
final Key[] keys = getKeys();
final int touchX = getTouchX(x);
final int touchY = getTouchY(y);
+ initializeNearbyKeys();
int primaryIndex = NOT_A_KEY;
- final int[] distances = mDistances;
- Arrays.fill(distances, Integer.MAX_VALUE);
for (final int index : mKeyboard.getNearestKeys(touchX, touchY)) {
final Key key = keys[index];
final boolean isInside = key.isInside(touchX, touchY);
- if (isInside)
- primaryIndex = index;
- final int dist = key.squaredDistanceToEdge(touchX, touchY);
- if (isInside || (mProximityCorrectOn && dist < mProximityThresholdSquare)) {
- if (allKeys == null) continue;
- // Find insertion point
- for (int j = 0; j < distances.length; j++) {
- if (distances[j] > dist) {
- final int nextPos = j + 1;
- System.arraycopy(distances, j, distances, nextPos,
- distances.length - nextPos);
- System.arraycopy(allKeys, j, allKeys, nextPos,
- allKeys.length - nextPos);
- distances[j] = dist;
- allKeys[j] = key.mCode;
- break;
- }
- }
+ final int distance = key.squaredDistanceToEdge(touchX, touchY);
+ if (isInside || (mProximityCorrectOn && distance < mProximityThresholdSquare)) {
+ final int insertedPosition = sortNearbyKeys(index, distance);
+ if (insertedPosition == 0 && isInside)
+ primaryIndex = index;
+ }
+ }
+
+ if (allCodes != null && allCodes.length > 0) {
+ getNearbyKeyCodes(allCodes);
+ if (DEBUG) {
+ Log.d(TAG, "x=" + x + " y=" + y
+ + " primary="
+ + (primaryIndex == NOT_A_KEY ? "none" : keys[primaryIndex].mCode)
+ + " codes=" + Arrays.toString(allCodes));
}
}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 813f7d3..ff7e2b8 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -20,6 +20,7 @@
import android.content.res.AssetFileDescriptor;
import android.util.Log;
+import java.io.File;
import java.util.Arrays;
/**
@@ -72,9 +73,40 @@
public static BinaryDictionary initDictionary(Context context, int resId, int dicTypeId) {
synchronized (sInstance) {
sInstance.closeInternal();
- if (resId != 0) {
- sInstance.loadDictionary(context, resId);
+ try {
+ final AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId);
+ if (afd == null) {
+ Log.e(TAG, "Found the resource but it is compressed. resId=" + resId);
+ return null;
+ }
+ final String sourceDir = context.getApplicationInfo().sourceDir;
+ final File packagePath = new File(sourceDir);
+ // TODO: Come up with a way to handle a directory.
+ if (!packagePath.isFile()) {
+ Log.e(TAG, "sourceDir is not a file: " + sourceDir);
+ return null;
+ }
+ sInstance.loadDictionary(sourceDir, afd.getStartOffset(), afd.getLength());
sInstance.mDicTypeId = dicTypeId;
+ } catch (android.content.res.Resources.NotFoundException e) {
+ Log.e(TAG, "Could not find the resource. resId=" + resId);
+ return null;
+ }
+ }
+ return sInstance;
+ }
+
+ // For unit test
+ /* package */ static BinaryDictionary initDictionary(File dictionary, long startOffset,
+ long length, int dicTypeId) {
+ synchronized (sInstance) {
+ sInstance.closeInternal();
+ if (dictionary.isFile()) {
+ sInstance.loadDictionary(dictionary.getAbsolutePath(), startOffset, length);
+ sInstance.mDicTypeId = dicTypeId;
+ } else {
+ Log.e(TAG, "Could not find the file. path=" + dictionary.getAbsolutePath());
+ return null;
}
}
return sInstance;
@@ -86,33 +118,21 @@
private native void closeNative(int dict);
private native boolean isValidWordNative(int nativeData, char[] word, int wordLength);
private native int getSuggestionsNative(int dict, int[] inputCodes, int codesSize,
- char[] outputChars, int[] frequencies,
- int[] nextLettersFrequencies, int nextLettersSize);
+ char[] outputChars, int[] frequencies);
private native int getBigramsNative(int dict, char[] prevWord, int prevWordLength,
int[] inputCodes, int inputCodesLength, char[] outputChars, int[] frequencies,
int maxWordLength, int maxBigrams, int maxAlternatives);
- private final void loadDictionary(Context context, int resId) {
- try {
- final AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId);
- if (afd == null) {
- Log.e(TAG, "Found the resource but it is compressed. resId=" + resId);
- return;
- }
- mNativeDict = openNative(context.getApplicationInfo().sourceDir,
- afd.getStartOffset(), afd.getLength(),
+ private final void loadDictionary(String path, long startOffset, long length) {
+ mNativeDict = openNative(path, startOffset, length,
TYPED_LETTER_MULTIPLIER, FULL_WORD_FREQ_MULTIPLIER,
MAX_WORD_LENGTH, MAX_WORDS, MAX_ALTERNATIVES);
- mDictLength = afd.getLength();
- } catch (android.content.res.Resources.NotFoundException e) {
- Log.e(TAG, "Could not find the resource. resId=" + resId);
- return;
- }
+ mDictLength = length;
}
@Override
public void getBigrams(final WordComposer codes, final CharSequence previousWord,
- final WordCallback callback, int[] nextLettersFrequencies) {
+ final WordCallback callback) {
if (mNativeDict == 0) return;
char[] chars = previousWord.toString().toCharArray();
@@ -144,15 +164,14 @@
}
@Override
- public void getWords(final WordComposer codes, final WordCallback callback,
- int[] nextLettersFrequencies) {
+ public void getWords(final WordComposer codes, final WordCallback callback) {
if (mNativeDict == 0) return;
final int codesSize = codes.size();
// Won't deal with really long words.
if (codesSize > MAX_WORD_LENGTH - 1) return;
- Arrays.fill(mInputCodes, -1);
+ Arrays.fill(mInputCodes, WordComposer.NOT_A_CODE);
for (int i = 0; i < codesSize; i++) {
int[] alternatives = codes.getCodesAt(i);
System.arraycopy(alternatives, 0, mInputCodes, i * MAX_ALTERNATIVES,
@@ -162,8 +181,7 @@
Arrays.fill(mFrequencies, 0);
int count = getSuggestionsNative(mNativeDict, mInputCodes, codesSize, mOutputChars,
- mFrequencies, nextLettersFrequencies,
- nextLettersFrequencies != null ? nextLettersFrequencies.length : 0);
+ mFrequencies);
for (int j = 0; j < count; ++j) {
if (mFrequencies[j] < 1) break;
diff --git a/java/src/com/android/inputmethod/latin/CandidateView.java b/java/src/com/android/inputmethod/latin/CandidateView.java
index fc45c7c..9699ad1 100644
--- a/java/src/com/android/inputmethod/latin/CandidateView.java
+++ b/java/src/com/android/inputmethod/latin/CandidateView.java
@@ -54,7 +54,7 @@
private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan();
private static final int MAX_SUGGESTIONS = 16;
- private static boolean DBG = LatinImeLogger.sDBG;
+ private static final boolean DBG = LatinImeLogger.sDBG;
private final ArrayList<View> mWords = new ArrayList<View>();
private final boolean mConfigCandidateHighlightFontColorEnabled;
@@ -226,10 +226,14 @@
}
final String debugString = info.getDebugString();
if (DBG) {
- if (!TextUtils.isEmpty(debugString)) {
+ if (TextUtils.isEmpty(debugString)) {
+ dv.setVisibility(GONE);
+ } else {
dv.setText(debugString);
dv.setVisibility(VISIBLE);
}
+ } else {
+ dv.setVisibility(GONE);
}
} else {
dv.setVisibility(GONE);
@@ -249,8 +253,10 @@
final TextView tv = (TextView)mWords.get(1).findViewById(R.id.candidate_word);
final Spannable word = new SpannableString(autoCorrectedWord);
final int wordLength = word.length();
- word.setSpan(mInvertedBackgroundColorSpan, 0, wordLength, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
- word.setSpan(mInvertedForegroundColorSpan, 0, wordLength, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ word.setSpan(mInvertedBackgroundColorSpan, 0, wordLength,
+ Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ word.setSpan(mInvertedForegroundColorSpan, 0, wordLength,
+ Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
tv.setText(word);
mShowingAutoCorrectionInverted = true;
}
diff --git a/java/src/com/android/inputmethod/latin/DebugSettings.java b/java/src/com/android/inputmethod/latin/DebugSettings.java
index 03211f3..2f1e7c2 100644
--- a/java/src/com/android/inputmethod/latin/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/DebugSettings.java
@@ -20,6 +20,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
+import android.os.Process;
import android.preference.CheckBoxPreference;
import android.preference.PreferenceActivity;
import android.util.Log;
@@ -30,6 +31,7 @@
private static final String TAG = "DebugSettings";
private static final String DEBUG_MODE_KEY = "debug_mode";
+ private boolean mServiceNeedsRestart = false;
private CheckBoxPreference mDebugMode;
@Override
@@ -39,16 +41,24 @@
SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
prefs.registerOnSharedPreferenceChangeListener(this);
+ mServiceNeedsRestart = false;
mDebugMode = (CheckBoxPreference) findPreference(DEBUG_MODE_KEY);
updateDebugMode();
}
@Override
+ protected void onStop() {
+ super.onStop();
+ if (mServiceNeedsRestart) Process.killProcess(Process.myPid());
+ }
+
+ @Override
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
if (key.equals(DEBUG_MODE_KEY)) {
if (mDebugMode != null) {
mDebugMode.setChecked(prefs.getBoolean(DEBUG_MODE_KEY, false));
updateDebugMode();
+ mServiceNeedsRestart = true;
}
}
}
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index 7493359..56f0cc5 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -61,14 +61,9 @@
* words are added through the callback object.
* @param composer the key sequence to match
* @param callback the callback object to send matched words to as possible candidates
- * @param nextLettersFrequencies array of frequencies of next letters that could follow the
- * word so far. For instance, "bracke" can be followed by "t", so array['t'] will have
- * a non-zero value on returning from this method.
- * Pass in null if you don't want the dictionary to look up next letters.
* @see WordCallback#addWord(char[], int, int)
*/
- abstract public void getWords(final WordComposer composer, final WordCallback callback,
- int[] nextLettersFrequencies);
+ abstract public void getWords(final WordComposer composer, final WordCallback callback);
/**
* Searches for pairs in the bigram dictionary that matches the previous word and all the
@@ -76,13 +71,9 @@
* @param composer the key sequence to match
* @param previousWord the word before
* @param callback the callback object to send possible word following previous word
- * @param nextLettersFrequencies array of frequencies of next letters that could follow the
- * word so far. For instance, "bracke" can be followed by "t", so array['t'] will have
- * a non-zero value on returning from this method.
- * Pass in null if you don't want the dictionary to look up next letters.
*/
public void getBigrams(final WordComposer composer, final CharSequence previousWord,
- final WordCallback callback, int[] nextLettersFrequencies) {
+ final WordCallback callback) {
// empty base implementation
}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index 0fc86c3..b10e7a6 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -37,7 +37,6 @@
private int mDicTypeId;
private int mMaxDepth;
private int mInputLength;
- private int[] mNextLettersFrequencies;
private StringBuilder sb = new StringBuilder(MAX_WORD_LENGTH);
private static final char QUOTE = '\'';
@@ -191,8 +190,7 @@
}
@Override
- public void getWords(final WordComposer codes, final WordCallback callback,
- int[] nextLettersFrequencies) {
+ public void getWords(final WordComposer codes, final WordCallback callback) {
synchronized (mUpdatingLock) {
// If we need to update, start off a background task
if (mRequiresReload) startDictionaryLoadingTaskLocked();
@@ -201,7 +199,6 @@
}
mInputLength = codes.size();
- mNextLettersFrequencies = nextLettersFrequencies;
if (mCodes.length < mInputLength) mCodes = new int[mInputLength][];
// Cache the codes so that we don't have to lookup an array list
for (int i = 0; i < mInputLength; i++) {
@@ -282,11 +279,6 @@
DataType.UNIGRAM)) {
return;
}
- // Add to frequency of next letters for predictive correction
- if (mNextLettersFrequencies != null && depth >= inputIndex && skipPos < 0
- && mNextLettersFrequencies.length > word[inputIndex]) {
- mNextLettersFrequencies[word[inputIndex]]++;
- }
}
if (children != null) {
getWordsRec(children, codes, word, depth + 1, completion, snr, inputIndex,
@@ -427,7 +419,7 @@
@Override
public void getBigrams(final WordComposer codes, final CharSequence previousWord,
- final WordCallback callback, int[] nextLettersFrequencies) {
+ final WordCallback callback) {
if (!reloadDictionaryIfRequired()) {
runReverseLookUp(previousWord, callback);
}
@@ -516,7 +508,7 @@
}
}
- static char toLowerCase(char c) {
+ private static char toLowerCase(char c) {
char baseChar = c;
if (c < BASE_CHARS.length) {
baseChar = BASE_CHARS[c];
@@ -535,7 +527,7 @@
* if c is not a combined character, or the base character if it
* is combined.
*/
- static final char BASE_CHARS[] = {
+ private static final char BASE_CHARS[] = {
0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 5ce1b7e..c9e7f8b 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -81,8 +81,7 @@
/**
* Input method implementation for Qwerty'ish keyboard.
*/
-public class LatinIME extends InputMethodService implements KeyboardActionListener,
- SharedPreferences.OnSharedPreferenceChangeListener {
+public class LatinIME extends InputMethodService implements KeyboardActionListener {
private static final String TAG = "LatinIME";
private static final boolean PERF_DEBUG = false;
private static final boolean TRACE = false;
@@ -186,7 +185,6 @@
// Keeps track of most recently inserted text (multi-character key) for reverting
private CharSequence mEnteredText;
- private boolean mRefreshKeyboardRequired;
private final ArrayList<WordAlternatives> mWordHistory = new ArrayList<WordAlternatives>();
@@ -323,7 +321,7 @@
removeMessages(MSG_DISMISS_LANGUAGE_ON_SPACEBAR);
final LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
if (inputView != null) {
- final LatinKeyboard keyboard = inputView.getLatinKeyboard();
+ final LatinKeyboard keyboard = mKeyboardSwitcher.getLatinKeyboard();
// The language is never displayed when the delay is zero.
if (mConfigDelayBeforeFadeoutLanguageOnSpacebar != 0)
inputView.setSpacebarTextFadeFactor(localeChanged ? 1.0f
@@ -359,9 +357,9 @@
// but always use the default setting defined in the resources.
if (res.getBoolean(R.bool.config_enable_show_recorrection_option)) {
mReCorrectionEnabled = prefs.getBoolean(Settings.PREF_RECORRECTION_ENABLED,
- res.getBoolean(R.bool.default_recorrection_enabled));
+ res.getBoolean(R.bool.config_default_recorrection_enabled));
} else {
- mReCorrectionEnabled = res.getBoolean(R.bool.default_recorrection_enabled);
+ mReCorrectionEnabled = res.getBoolean(R.bool.config_default_recorrection_enabled);
}
mConfigEnableShowSubtypeSettings = res.getBoolean(
@@ -395,7 +393,6 @@
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
registerReceiver(mReceiver, filter);
mVoiceConnector = VoiceIMEConnector.init(this, prefs, mHandler);
- prefs.registerOnSharedPreferenceChangeListener(this);
}
/**
@@ -497,12 +494,6 @@
return container;
}
- private static boolean isPasswordVariation(int variation) {
- return variation == InputType.TYPE_TEXT_VARIATION_PASSWORD
- || variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
- || variation == InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD;
- }
-
private static boolean isEmailVariation(int variation) {
return variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
|| variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
@@ -523,20 +514,15 @@
mSubtypeSwitcher.updateParametersOnStartInputView();
- if (mRefreshKeyboardRequired) {
- mRefreshKeyboardRequired = false;
- onRefreshKeyboard();
- }
-
TextEntryState.newSession(this);
// Most such things we decide below in initializeInputAttributesAndGetMode, but we need to
// know now whether this is a password text field, because we need to know now whether we
// want to enable the voice button.
- mVoiceConnector.resetVoiceStates(isPasswordVariation(
- attribute.inputType & InputType.TYPE_MASK_VARIATION));
+ mVoiceConnector.resetVoiceStates(Utils.isPasswordInputType(attribute.inputType)
+ || Utils.isVisiblePasswordInputType(attribute.inputType));
- final int mode = initializeInputAttributesAndGetMode(attribute.inputType);
+ final int mode = initializeInputAttributesAndGetMode(attribute);
inputView.closing();
mEnteredText = null;
@@ -547,7 +533,7 @@
loadSettings(attribute);
if (mSubtypeSwitcher.isKeyboardMode()) {
- switcher.loadKeyboard(mode, attribute.imeOptions,
+ switcher.loadKeyboard(mode, attribute,
mVoiceConnector.isVoiceButtonEnabled(),
mVoiceConnector.isVoiceButtonOnPrimary());
switcher.updateShiftState();
@@ -571,7 +557,11 @@
if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
}
- private int initializeInputAttributesAndGetMode(int inputType) {
+ // TODO: Separate calculating keyboard mode from initializing attributes, and make it an
+ // utility method in {@link Utils}.
+ private int initializeInputAttributesAndGetMode(EditorInfo attribute) {
+ if (attribute == null) return KeyboardId.MODE_TEXT;
+ final int inputType = attribute.inputType;
final int variation = inputType & InputType.TYPE_MASK_VARIATION;
mAutoSpace = false;
mInputTypeNoAutoCorrect = false;
@@ -591,16 +581,17 @@
case InputType.TYPE_CLASS_TEXT:
mIsSettingsSuggestionStripOn = true;
// Make sure that passwords are not displayed in candidate view
- if (isPasswordVariation(variation)) {
+ if (Utils.isPasswordInputType(inputType)
+ || Utils.isVisiblePasswordInputType(inputType)) {
mIsSettingsSuggestionStripOn = false;
}
- if (isEmailVariation(variation)
+ if (LatinIME.isEmailVariation(variation)
|| variation == InputType.TYPE_TEXT_VARIATION_PERSON_NAME) {
mAutoSpace = false;
} else {
mAutoSpace = true;
}
- if (isEmailVariation(variation)) {
+ if (LatinIME.isEmailVariation(variation)) {
mIsSettingsSuggestionStripOn = false;
mode = KeyboardId.MODE_EMAIL;
} else if (variation == InputType.TYPE_TEXT_VARIATION_URI) {
@@ -1467,26 +1458,21 @@
}
public void switchToKeyboardView() {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- if (DEBUG) {
- Log.d(TAG, "Switch to keyboard view.");
- }
- View v = mKeyboardSwitcher.getInputView();
- if (v != null) {
- // Confirms that the keyboard view doesn't have parent view.
- ViewParent p = v.getParent();
- if (p != null && p instanceof ViewGroup) {
- ((ViewGroup) p).removeView(v);
- }
- setInputView(v);
- }
- setCandidatesViewShown(isCandidateStripVisible());
- updateInputViewShown();
- mHandler.postUpdateSuggestions();
+ if (DEBUG) {
+ Log.d(TAG, "Switch to keyboard view.");
+ }
+ View v = mKeyboardSwitcher.getInputView();
+ if (v != null) {
+ // Confirms that the keyboard view doesn't have parent view.
+ ViewParent p = v.getParent();
+ if (p != null && p instanceof ViewGroup) {
+ ((ViewGroup) p).removeView(v);
}
- });
+ setInputView(v);
+ }
+ setCandidatesViewShown(isCandidateStripVisible());
+ updateInputViewShown();
+ mHandler.postUpdateSuggestions();
}
public void clearSuggestions() {
@@ -1508,8 +1494,6 @@
}
public void updateSuggestions() {
- mKeyboardSwitcher.setPreferredLetters(null);
-
// Check if we have a suggestion engine attached.
if ((mSuggest == null || !isSuggestionsRequested())
&& !mVoiceConnector.isVoiceInputHighlighted()) {
@@ -1528,7 +1512,6 @@
}
private void showCorrections(WordAlternatives alternatives) {
- mKeyboardSwitcher.setPreferredLetters(null);
SuggestedWords.Builder builder = alternatives.getAlternatives();
builder.setTypedWordValid(false).setHasMinimalSuggestion(false);
showSuggestions(builder.build(), alternatives.getOriginalWord());
@@ -1541,9 +1524,6 @@
SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder(
mKeyboardSwitcher.getInputView(), word, prevWord);
- int[] nextLettersFrequencies = mSuggest.getNextLettersFrequencies();
- mKeyboardSwitcher.setPreferredLetters(nextLettersFrequencies);
-
boolean correctionAvailable = !mInputTypeNoAutoCorrect && !mJustReverted
&& mSuggest.hasAutoCorrection();
final CharSequence typedWord = word.getTypedWord();
@@ -1712,7 +1692,6 @@
saveWordInHistory(suggestion);
mHasValidSuggestions = false;
mCommittedLength = suggestion.length();
- switcher.setPreferredLetters(null);
}
/**
@@ -1930,28 +1909,12 @@
mSubtypeSwitcher.toggleLanguage(reset, next);
}
// Reload keyboard because the current language has been changed.
- KeyboardSwitcher switcher = mKeyboardSwitcher;
final EditorInfo attribute = getCurrentInputEditorInfo();
- final int mode = initializeInputAttributesAndGetMode((attribute != null)
- ? attribute.inputType : 0);
- final int imeOptions = (attribute != null) ? attribute.imeOptions : 0;
- switcher.loadKeyboard(mode, imeOptions, mVoiceConnector.isVoiceButtonEnabled(),
- mVoiceConnector.isVoiceButtonOnPrimary());
+ final int mode = initializeInputAttributesAndGetMode(attribute);
+ mKeyboardSwitcher.loadKeyboard(mode, attribute,
+ mVoiceConnector.isVoiceButtonEnabled(), mVoiceConnector.isVoiceButtonOnPrimary());
initSuggest();
- switcher.updateShiftState();
- }
-
- @Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
- String key) {
- mSubtypeSwitcher.onSharedPreferenceChanged(sharedPreferences, key);
- if (Settings.PREF_SELECTED_LANGUAGES.equals(key)) {
- mRefreshKeyboardRequired = true;
- } else if (Settings.PREF_RECORRECTION_ENABLED.equals(key)) {
- mReCorrectionEnabled = sharedPreferences.getBoolean(
- Settings.PREF_RECORRECTION_ENABLED,
- mResources.getBoolean(R.bool.default_recorrection_enabled));
- }
+ mKeyboardSwitcher.updateShiftState();
}
@Override
@@ -2082,7 +2045,7 @@
private void updateAutoTextEnabled() {
if (mSuggest == null) return;
- mSuggest.setAutoTextEnabled(mQuickFixes
+ mSuggest.setQuickFixesEnabled(mQuickFixes
&& SubtypeSwitcher.getInstance().isSystemLanguageSameAsInputLanguage());
}
@@ -2121,7 +2084,8 @@
Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
mVibrateOn = vibrator != null && vibrator.hasVibrator()
&& prefs.getBoolean(Settings.PREF_VIBRATE_ON, false);
- mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON, false);
+ mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON,
+ mResources.getBoolean(R.bool.config_default_sound_enabled));
mPopupOn = isPopupEnabled(prefs);
mAutoCap = prefs.getBoolean(Settings.PREF_AUTO_CAP, true);
@@ -2272,10 +2236,10 @@
di.dismiss();
switch (position) {
case 0:
- launchSettings();
+ mImm.showInputMethodPicker();
break;
case 1:
- mImm.showInputMethodPicker();
+ launchSettings();
break;
}
}
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 12338ce..341d5ad 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -225,7 +225,9 @@
final String action;
if (android.os.Build.VERSION.SDK_INT
>= /* android.os.Build.VERSION_CODES.HONEYCOMB */ 11) {
- action = "android.settings.INPUT_METHOD_AND_SUBTYPE_ENABLER";
+ // Refer to android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS
+ // TODO: Can this be a constant instead of literal String constant?
+ action = "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS";
} else {
action = "com.android.inputmethod.latin.INPUT_LANGUAGE_SELECTION";
}
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index f4262cc..ee9dab3 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -371,12 +371,10 @@
ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
mIsNetworkConnected = !noConnection;
- final LatinKeyboardView inputView = KeyboardSwitcher.getInstance().getInputView();
- if (inputView != null) {
- final LatinKeyboard keyboard = inputView.getLatinKeyboard();
- if (keyboard != null) {
- keyboard.updateShortcutKey(isShortcutAvailable(), inputView);
- }
+ final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance();
+ final LatinKeyboard keyboard = switcher.getLatinKeyboard();
+ if (keyboard != null) {
+ keyboard.updateShortcutKey(isShortcutAvailable(), switcher.getInputView());
}
}
@@ -426,8 +424,15 @@
if (mConfigUseSpacebarLanguageSwitcher) {
return mLanguageSwitcher.getEnabledLanguages();
} else {
+ int enabledLanguageCount = mEnabledLanguagesOfCurrentInputMethod.size();
+ // Workaround for explicitly specifying the voice language
+ if (enabledLanguageCount == 1) {
+ mEnabledLanguagesOfCurrentInputMethod.add(
+ mEnabledLanguagesOfCurrentInputMethod.get(0));
+ ++enabledLanguageCount;
+ }
return mEnabledLanguagesOfCurrentInputMethod.toArray(
- new String[mEnabledLanguagesOfCurrentInputMethod.size()]);
+ new String[enabledLanguageCount]);
}
}
@@ -465,14 +470,6 @@
}
}
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
- if (mConfigUseSpacebarLanguageSwitcher) {
- if (Settings.PREF_SELECTED_LANGUAGES.equals(key)) {
- mLanguageSwitcher.loadLocales(sharedPreferences);
- }
- }
- }
-
/**
* Change system locale for this application
* @param newLocale
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index ced355b..6466f79 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -22,6 +22,7 @@
import android.util.Log;
import android.view.View;
+import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
@@ -64,7 +65,7 @@
static final int LARGE_DICTIONARY_THRESHOLD = 200 * 1000;
- private static boolean DBG = LatinImeLogger.sDBG;
+ private static final boolean DBG = LatinImeLogger.sDBG;
private BinaryDictionary mMainDict;
@@ -80,18 +81,12 @@
private static final int PREF_MAX_BIGRAMS = 60;
- private boolean mAutoTextEnabled;
+ private boolean mQuickFixesEnabled;
private double mAutoCorrectionThreshold;
private int[] mPriorities = new int[mPrefMaxSuggestions];
private int[] mBigramPriorities = new int[PREF_MAX_BIGRAMS];
- // Handle predictive correction for only the first 1280 characters for performance reasons
- // If we support scripts that need latin characters beyond that, we should probably use some
- // kind of a sparse array or language specific list with a mapping lookup table.
- // 1280 is the size of the BASE_CHARS array in ExpandableDictionary, which is a basic set of
- // latin characters.
- private int[] mNextLettersFrequencies = new int[1280];
private ArrayList<CharSequence> mSuggestions = new ArrayList<CharSequence>();
ArrayList<CharSequence> mBigramSuggestions = new ArrayList<CharSequence>();
private ArrayList<CharSequence> mStringPool = new ArrayList<CharSequence>();
@@ -109,6 +104,12 @@
initPool();
}
+ // For unit test
+ /* package */ Suggest(File dictionary, long startOffset, long length) {
+ mMainDict = BinaryDictionary.initDictionary(dictionary, startOffset, length, DIC_MAIN);
+ initPool();
+ }
+
private void initPool() {
for (int i = 0; i < mPrefMaxSuggestions; i++) {
StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
@@ -116,8 +117,8 @@
}
}
- public void setAutoTextEnabled(boolean enabled) {
- mAutoTextEnabled = enabled;
+ public void setQuickFixesEnabled(boolean enabled) {
+ mQuickFixesEnabled = enabled;
}
public int getCorrectionMode() {
@@ -209,7 +210,6 @@
mIsAllUpperCase = wordComposer.isAllUpperCase();
collectGarbage(mSuggestions, mPrefMaxSuggestions);
Arrays.fill(mPriorities, 0);
- Arrays.fill(mNextLettersFrequencies, 0);
// Save a lowercase version of the original word
CharSequence typedWord = wordComposer.getTypedWord();
@@ -224,6 +224,7 @@
mLowerOriginalWord = "";
}
+ double normalizedScore = Integer.MIN_VALUE;
if (wordComposer.size() == 1 && (mCorrectionMode == CORRECTION_FULL_BIGRAM
|| mCorrectionMode == CORRECTION_BASIC)) {
// At first character typed, search only the bigrams
@@ -236,16 +237,13 @@
prevWordForBigram = lowerPrevWord;
}
if (mUserBigramDictionary != null) {
- mUserBigramDictionary.getBigrams(wordComposer, prevWordForBigram, this,
- mNextLettersFrequencies);
+ mUserBigramDictionary.getBigrams(wordComposer, prevWordForBigram, this);
}
if (mContactsDictionary != null) {
- mContactsDictionary.getBigrams(wordComposer, prevWordForBigram, this,
- mNextLettersFrequencies);
+ mContactsDictionary.getBigrams(wordComposer, prevWordForBigram, this);
}
if (mMainDict != null) {
- mMainDict.getBigrams(wordComposer, prevWordForBigram, this,
- mNextLettersFrequencies);
+ mMainDict.getBigrams(wordComposer, prevWordForBigram, this);
}
char currentChar = wordComposer.getTypedWord().charAt(0);
char currentCharUpper = Character.toUpperCase(currentChar);
@@ -270,10 +268,10 @@
// At second character typed, search the unigrams (scores being affected by bigrams)
if (mUserDictionary != null || mContactsDictionary != null) {
if (mUserDictionary != null) {
- mUserDictionary.getWords(wordComposer, this, mNextLettersFrequencies);
+ mUserDictionary.getWords(wordComposer, this);
}
if (mContactsDictionary != null) {
- mContactsDictionary.getWords(wordComposer, this, mNextLettersFrequencies);
+ mContactsDictionary.getWords(wordComposer, this);
}
if (mSuggestions.size() > 0 && isValidWord(typedWord)
@@ -285,14 +283,14 @@
mHasAutoCorrection = true;
}
}
- if (mMainDict != null) mMainDict.getWords(wordComposer, this, mNextLettersFrequencies);
+ if (mMainDict != null) mMainDict.getWords(wordComposer, this);
if ((mCorrectionMode == CORRECTION_FULL || mCorrectionMode == CORRECTION_FULL_BIGRAM)
&& mSuggestions.size() > 0 && mPriorities.length > 0) {
// TODO: when the normalized score of the first suggestion is nearly equals to
// the normalized score of the second suggestion, behave less aggressive.
- final double normalizedScore = Utils.calcNormalizedScore(
+ normalizedScore = Utils.calcNormalizedScore(
typedWord, mSuggestions.get(0), mPriorities[0]);
- if (LatinImeLogger.sDBG) {
+ if (DBG) {
Log.d(TAG, "Normalized " + typedWord + "," + mSuggestions.get(0) + ","
+ mPriorities[0] + ", " + normalizedScore
+ "(" + mAutoCorrectionThreshold + ")");
@@ -308,7 +306,7 @@
if (typedWord != null) {
mSuggestions.add(0, typedWord.toString());
}
- if (mAutoTextEnabled) {
+ if (mQuickFixesEnabled) {
int i = 0;
int max = 6;
// Don't autotext the suggestions from the dictionaries
@@ -354,11 +352,30 @@
}
}
removeDupes();
- return new SuggestedWords.Builder().addWords(mSuggestions, null);
- }
-
- public int[] getNextLettersFrequencies() {
- return mNextLettersFrequencies;
+ if (DBG) {
+ ArrayList<SuggestedWords.SuggestedWordInfo> frequencyInfoList =
+ new ArrayList<SuggestedWords.SuggestedWordInfo>();
+ frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo("+", false));
+ final int priorityLength = mPriorities.length;
+ for (int i = 0; i < priorityLength; ++i) {
+ if (normalizedScore > 0) {
+ final String priorityThreshold = Integer.toString(mPriorities[i]) + " (" +
+ normalizedScore + ")";
+ frequencyInfoList.add(
+ new SuggestedWords.SuggestedWordInfo(priorityThreshold, false));
+ normalizedScore = 0.0;
+ } else {
+ final String priority = Integer.toString(mPriorities[i]);
+ frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo(priority, false));
+ }
+ }
+ for (int i = priorityLength; i < mSuggestions.size(); ++i) {
+ frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo("--", false));
+ }
+ return new SuggestedWords.Builder().addWords(mSuggestions, frequencyInfoList);
+ } else {
+ return new SuggestedWords.Builder().addWords(mSuggestions, null);
+ }
}
private void removeDupes() {
@@ -392,12 +409,12 @@
return mHasAutoCorrection;
}
- private boolean compareCaseInsensitive(final String mLowerOriginalWord,
+ private static boolean compareCaseInsensitive(final String lowerOriginalWord,
final char[] word, final int offset, final int length) {
- final int originalLength = mLowerOriginalWord.length();
+ final int originalLength = lowerOriginalWord.length();
if (originalLength == length && Character.isUpperCase(word[offset])) {
for (int i = 0; i < originalLength; i++) {
- if (mLowerOriginalWord.charAt(i) != Character.toLowerCase(word[offset+i])) {
+ if (lowerOriginalWord.charAt(i) != Character.toLowerCase(word[offset+i])) {
return false;
}
}
diff --git a/java/src/com/android/inputmethod/latin/UserDictionary.java b/java/src/com/android/inputmethod/latin/UserDictionary.java
index 56ee5b9..c06bd73 100644
--- a/java/src/com/android/inputmethod/latin/UserDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserDictionary.java
@@ -126,9 +126,8 @@
}
@Override
- public synchronized void getWords(final WordComposer codes, final WordCallback callback,
- int[] nextLettersFrequencies) {
- super.getWords(codes, callback, nextLettersFrequencies);
+ public synchronized void getWords(final WordComposer codes, final WordCallback callback) {
+ super.getWords(codes, callback);
}
@Override
@@ -138,7 +137,7 @@
private void addWords(Cursor cursor) {
clearDictionary();
-
+ if (cursor == null) return;
final int maxWordLength = getMaxWordLength();
if (cursor.moveToFirst()) {
final int indexWord = cursor.getColumnIndex(Words.WORD);
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index e980d3a..149c5ca 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -16,13 +16,17 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.keyboard.KeyboardId;
+
import android.inputmethodservice.InputMethodService;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
+import android.text.InputType;
import android.text.format.DateUtils;
import android.util.Log;
+import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
@@ -55,7 +59,7 @@
}
public static class GCUtils {
- private static final String TAG = "GCUtils";
+ private static final String GC_TAG = GCUtils.class.getSimpleName();
public static final int GC_TRY_COUNT = 2;
// GC_TRY_LOOP_MAX is used for the hard limit of GC wait,
// GC_TRY_LOOP_MAX should be greater than GC_TRY_COUNT.
@@ -84,7 +88,7 @@
Thread.sleep(GC_INTERVAL);
return true;
} catch (InterruptedException e) {
- Log.e(TAG, "Sleep was interrupted.");
+ Log.e(GC_TAG, "Sleep was interrupted.");
LatinImeLogger.logOnException(metaData, t);
return false;
}
@@ -261,6 +265,19 @@
return dp[sl][tl];
}
+ // Get the current stack trace
+ public static String getStackTrace() {
+ StringBuilder sb = new StringBuilder();
+ try {
+ throw new RuntimeException();
+ } catch (RuntimeException e) {
+ StackTraceElement[] frames = e.getStackTrace();
+ // Start at 1 because the first frame is here and we don't care about it
+ for (int j = 1; j < frames.length; ++j) sb.append(frames[j].toString() + "\n");
+ }
+ return sb.toString();
+ }
+
// In dictionary.cpp, getSuggestion() method,
// suggestion scores are computed using the below formula.
// original score (called 'frequency')
@@ -268,13 +285,22 @@
// (the number of matched characters between typed word and suggested word))
// * (individual word's score which defined in the unigram dictionary,
// and this score is defined in range [0, 255].)
- // * (when before.length() == after.length(),
- // mFullWordMultiplier (this is defined 2))
- // So, maximum original score is pow(2, before.length()) * 255 * 2
- // So, we can normalize original score by dividing this value.
+ // Then, the following processing is applied.
+ // - If the dictionary word is matched up to the point of the user entry
+ // (full match up to min(before.length(), after.length())
+ // => Then multiply by FULL_MATCHED_WORDS_PROMOTION_RATE (this is defined 1.2)
+ // - If the word is a true full match except for differences in accents or
+ // capitalization, then treat it as if the frequency was 255.
+ // - If before.length() == after.length()
+ // => multiply by mFullWordMultiplier (this is defined 2))
+ // So, maximum original score is pow(2, min(before.length(), after.length())) * 255 * 2 * 1.2
+ // For historical reasons we ignore the 1.2 modifier (because the measure for a good
+ // autocorrection threshold was done at a time when it didn't exist). This doesn't change
+ // the result.
+ // So, we can normalize original score by dividing pow(2, min(b.l(),a.l())) * 255 * 2.
private static final int MAX_INITIAL_SCORE = 255;
private static final int TYPED_LETTER_MULTIPLIER = 2;
- private static final int FULL_WORD_MULTIPLYER = 2;
+ private static final int FULL_WORD_MULTIPLIER = 2;
public static double calcNormalizedScore(CharSequence before, CharSequence after, int score) {
final int beforeLength = before.length();
final int afterLength = after.length();
@@ -284,7 +310,7 @@
// correction.
final double maximumScore = MAX_INITIAL_SCORE
* Math.pow(TYPED_LETTER_MULTIPLIER, Math.min(beforeLength, afterLength))
- * FULL_WORD_MULTIPLYER;
+ * FULL_WORD_MULTIPLIER;
// add a weight based on edit distance.
// distance <= max(afterLength, beforeLength) == afterLength,
// so, 0 <= distance / afterLength <= 1
@@ -439,4 +465,24 @@
return new PrintWriter(new FileOutputStream(mFile), true /* autoFlush */);
}
}
+
+ // Please refer to TextView.isPasswordInputType
+ public static boolean isPasswordInputType(int inputType) {
+ final int variation =
+ inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION);
+ return (variation
+ == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD))
+ || (variation
+ == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD))
+ || (variation
+ == (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD));
+ }
+
+ // Please refer to TextView.isVisiblePasswordInputType
+ public static boolean isVisiblePasswordInputType(int inputType) {
+ final int variation =
+ inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION);
+ return variation
+ == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 2e415b7..e003dcd 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -16,12 +16,16 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.keyboard.KeyDetector;
+
import java.util.ArrayList;
/**
* A place to store the currently composing word with information such as adjacent key codes as well
*/
public class WordComposer {
+ public static final int NOT_A_CODE = KeyDetector.NOT_A_CODE;
+
/**
* The list of unicode values for each keystroke (including surrounding keys)
*/
diff --git a/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java b/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java
index 61a194a..277ef7e 100644
--- a/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java
+++ b/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java
@@ -38,16 +38,13 @@
import android.preference.PreferenceManager;
import android.provider.Browser;
import android.speech.SpeechRecognizer;
-import android.text.Layout;
-import android.text.Selection;
-import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
-import android.text.style.ClickableSpan;
import android.text.style.URLSpan;
import android.util.Log;
import android.view.LayoutInflater;
-import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
@@ -83,9 +80,8 @@
private static final String IME_OPTION_NO_MICROPHONE = "nm";
private static final int RECOGNITIONVIEW_HEIGHT_THRESHOLD_RATIO = 6;
- @SuppressWarnings("unused")
- private static final String TAG = "VoiceIMEConnector";
- private static boolean DEBUG = LatinImeLogger.sDBG;
+ private static final String TAG = VoiceIMEConnector.class.getSimpleName();
+ private static final boolean DEBUG = LatinImeLogger.sDBG;
private boolean mAfterVoiceInput;
private boolean mHasUsedVoiceInput;
@@ -177,7 +173,7 @@
if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) {
return;
}
- AlertDialog.Builder builder = new AlertDialog.Builder(mService);
+ AlertDialog.Builder builder = new UrlLinkAlertDialogBuilder(mService);
builder.setCancelable(true);
builder.setIcon(R.drawable.ic_mic_dialog);
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@@ -215,90 +211,80 @@
mService.getText(R.string.voice_warning_how_to_turn_off));
}
builder.setMessage(message);
-
builder.setTitle(R.string.voice_warning_title);
mVoiceWarningDialog = builder.create();
- Window window = mVoiceWarningDialog.getWindow();
- WindowManager.LayoutParams lp = window.getAttributes();
+ final Window window = mVoiceWarningDialog.getWindow();
+ final WindowManager.LayoutParams lp = window.getAttributes();
lp.token = token;
lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
window.setAttributes(lp);
window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
mVoiceInput.logKeyboardWarningDialogShown();
mVoiceWarningDialog.show();
- // Make URL in the dialog message clickable
- TextView textView = (TextView) mVoiceWarningDialog.findViewById(android.R.id.message);
- if (textView != null) {
- final CustomLinkMovementMethod method = CustomLinkMovementMethod.getInstance();
- method.setVoiceWarningDialog(mVoiceWarningDialog);
- textView.setMovementMethod(method);
- }
}
- private static class CustomLinkMovementMethod extends LinkMovementMethod {
- private static CustomLinkMovementMethod sLinkMovementMethodInstance =
- new CustomLinkMovementMethod();
+ private static class UrlLinkAlertDialogBuilder extends AlertDialog.Builder {
private AlertDialog mAlertDialog;
- public void setVoiceWarningDialog(AlertDialog alertDialog) {
- mAlertDialog = alertDialog;
+ public UrlLinkAlertDialogBuilder(Context context) {
+ super(context);
}
- public static CustomLinkMovementMethod getInstance() {
- return sLinkMovementMethodInstance;
- }
-
- // Almost the same as LinkMovementMethod.onTouchEvent(), but overrides it for
- // FLAG_ACTIVITY_NEW_TASK and mAlertDialog.cancel().
@Override
- public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
- int action = event.getAction();
+ public AlertDialog.Builder setMessage(CharSequence message) {
+ return super.setMessage(replaceURLSpan(message));
+ }
- if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
- int x = (int) event.getX();
- int y = (int) event.getY();
-
- x -= widget.getTotalPaddingLeft();
- y -= widget.getTotalPaddingTop();
-
- x += widget.getScrollX();
- y += widget.getScrollY();
-
- Layout layout = widget.getLayout();
- int line = layout.getLineForVertical(y);
- int off = layout.getOffsetForHorizontal(line, x);
-
- ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);
-
- if (link.length != 0) {
- if (action == MotionEvent.ACTION_UP) {
- if (link[0] instanceof URLSpan) {
- URLSpan urlSpan = (URLSpan) link[0];
- Uri uri = Uri.parse(urlSpan.getURL());
- Context context = widget.getContext();
- Intent intent = new Intent(Intent.ACTION_VIEW, uri);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
- if (mAlertDialog != null) {
- // Go back to the previous IME for now.
- // TODO: If we can find a way to bring the new activity to front
- // while keeping the warning dialog, we don't need to cancel here.
- mAlertDialog.cancel();
- }
- context.startActivity(intent);
- } else {
- link[0].onClick(widget);
- }
- } else if (action == MotionEvent.ACTION_DOWN) {
- Selection.setSelection(buffer, buffer.getSpanStart(link[0]),
- buffer.getSpanEnd(link[0]));
- }
- return true;
- } else {
- Selection.removeSelection(buffer);
- }
+ private Spanned replaceURLSpan(CharSequence message) {
+ // Replace all spans with the custom span
+ final SpannableStringBuilder ssb = new SpannableStringBuilder(message);
+ for (URLSpan span : ssb.getSpans(0, ssb.length(), URLSpan.class)) {
+ int spanStart = ssb.getSpanStart(span);
+ int spanEnd = ssb.getSpanEnd(span);
+ int spanFlags = ssb.getSpanFlags(span);
+ ssb.removeSpan(span);
+ ssb.setSpan(new ClickableSpan(span.getURL()), spanStart, spanEnd, spanFlags);
}
- return super.onTouchEvent(widget, buffer, event);
+ return ssb;
+ }
+
+ @Override
+ public AlertDialog create() {
+ final AlertDialog dialog = super.create();
+
+ dialog.setOnShowListener(new DialogInterface.OnShowListener() {
+ @Override
+ public void onShow(DialogInterface dialogInterface) {
+ // Make URL in the dialog message click-able.
+ TextView textView = (TextView) mAlertDialog.findViewById(android.R.id.message);
+ if (textView != null) {
+ textView.setMovementMethod(LinkMovementMethod.getInstance());
+ }
+ }
+ });
+ mAlertDialog = dialog;
+ return dialog;
+ }
+
+ class ClickableSpan extends URLSpan {
+ public ClickableSpan(String url) {
+ super(url);
+ }
+
+ @Override
+ public void onClick(View widget) {
+ Uri uri = Uri.parse(getURL());
+ Context context = widget.getContext();
+ Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ // Add this flag to start an activity from service
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
+ // Dismiss the warning dialog and go back to the previous IME.
+ // TODO: If we can find a way to bring the new activity to front while keeping
+ // the warning dialog, we don't need to dismiss it here.
+ mAlertDialog.cancel();
+ context.startActivity(intent);
+ }
}
}
@@ -729,7 +715,7 @@
mHandler.updateVoiceResults();
}
- public FieldContext makeFieldContext() {
+ private FieldContext makeFieldContext() {
SubtypeSwitcher switcher = SubtypeSwitcher.getInstance();
return new FieldContext(mService.getCurrentInputConnection(),
mService.getCurrentInputEditorInfo(), switcher.getInputLocaleStr(),
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index 25580f4..4660103 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -123,26 +123,20 @@
}
static int latinime_BinaryDictionary_getSuggestions(JNIEnv *env, jobject object, jint dict,
- jintArray inputArray, jint arraySize, jcharArray outputArray, jintArray frequencyArray,
- jintArray nextLettersArray, jint nextLettersSize) {
+ jintArray inputArray, jint arraySize, jcharArray outputArray, jintArray frequencyArray) {
Dictionary *dictionary = (Dictionary*)dict;
if (!dictionary) return 0;
int *frequencies = env->GetIntArrayElements(frequencyArray, NULL);
int *inputCodes = env->GetIntArrayElements(inputArray, NULL);
jchar *outputChars = env->GetCharArrayElements(outputArray, NULL);
- int *nextLetters = nextLettersArray != NULL ? env->GetIntArrayElements(nextLettersArray, NULL)
- : NULL;
int count = dictionary->getSuggestions(inputCodes, arraySize, (unsigned short*) outputChars,
- frequencies, nextLetters, nextLettersSize);
+ frequencies);
env->ReleaseIntArrayElements(frequencyArray, frequencies, 0);
env->ReleaseIntArrayElements(inputArray, inputCodes, JNI_ABORT);
env->ReleaseCharArrayElements(outputArray, outputChars, 0);
- if (nextLetters) {
- env->ReleaseIntArrayElements(nextLettersArray, nextLetters, 0);
- }
return count;
}
@@ -209,7 +203,7 @@
static JNINativeMethod gMethods[] = {
{"openNative", "(Ljava/lang/String;JJIIIII)I", (void*)latinime_BinaryDictionary_open},
{"closeNative", "(I)V", (void*)latinime_BinaryDictionary_close},
- {"getSuggestionsNative", "(I[II[C[I[II)I", (void*)latinime_BinaryDictionary_getSuggestions},
+ {"getSuggestionsNative", "(I[II[C[I)I", (void*)latinime_BinaryDictionary_getSuggestions},
{"isValidWordNative", "(I[CI)Z", (void*)latinime_BinaryDictionary_isValidWord},
{"getBigramsNative", "(I[CI[II[C[IIII)I", (void*)latinime_BinaryDictionary_getBigrams}
};
diff --git a/native/src/debug.h b/native/src/debug.h
new file mode 100644
index 0000000..e5572e1
--- /dev/null
+++ b/native/src/debug.h
@@ -0,0 +1,58 @@
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#ifndef LATINIME_DEBUG_H
+#define LATINIME_DEBUG_H
+
+#include "defines.h"
+
+static inline unsigned char* convertToUnibyteString(unsigned short* input, unsigned char* output,
+ const unsigned int length) {
+ int i = 0;
+ for (; i <= length && input[i] != 0; ++i)
+ output[i] = input[i] & 0xFF;
+ output[i] = 0;
+ return output;
+}
+static inline unsigned char* convertToUnibyteStringAndReplaceLastChar(unsigned short* input,
+ unsigned char* output, const unsigned int length, unsigned char c) {
+ int i = 0;
+ for (; i <= length && input[i] != 0; ++i)
+ output[i] = input[i] & 0xFF;
+ output[i-1] = c;
+ output[i] = 0;
+ return output;
+}
+static inline void LOGI_S16(unsigned short* string, const unsigned int length) {
+ unsigned char tmp_buffer[length];
+ convertToUnibyteString(string, tmp_buffer, length);
+ LOGI(">> %s", tmp_buffer);
+ // The log facility is throwing out log that comes too fast. The following
+ // is a dirty way of slowing down processing so that we can see all log.
+ // TODO : refactor this in a blocking log or something.
+ // usleep(10);
+}
+static inline void LOGI_S16_PLUS(unsigned short* string, const unsigned int length,
+ unsigned char c) {
+ unsigned char tmp_buffer[length+1];
+ convertToUnibyteStringAndReplaceLastChar(string, tmp_buffer, length, c);
+ LOGI(">> %s", tmp_buffer);
+ // Likewise
+ // usleep(10);
+}
+
+#endif // LATINIME_DEBUG_H
diff --git a/native/src/defines.h b/native/src/defines.h
index c1eaf0d..918028a 100644
--- a/native/src/defines.h
+++ b/native/src/defines.h
@@ -100,6 +100,9 @@
#ifndef U_SHORT_MAX
#define U_SHORT_MAX 1 << 16
#endif
+#ifndef S_INT_MAX
+#define S_INT_MAX ((1 << 31) - 1)
+#endif
// Define this to use mmap() for dictionary loading. Undefine to use malloc() instead of mmap().
// We measured and compared performance of both, and found mmap() is fairly good in terms of
@@ -137,9 +140,6 @@
#define WORDS_WITH_TRANSPOSED_CHARACTERS_DEMOTION_RATE 60
#define FULL_MATCHED_WORDS_PROMOTION_RATE 120
-// This is used as a bare multiplier (not subject to /100)
-#define FULL_MATCH_ACCENTS_OR_CAPITALIZATION_DIFFER_MULTIPLIER 2
-
// This should be greater than or equal to MAX_WORD_LENGTH defined in BinaryDictionary.java
// This is only used for the size of array. Not to be used in c functions.
#define MAX_WORD_LENGTH_INTERNAL 48
@@ -151,6 +151,9 @@
#define MIN_USER_TYPED_LENGTH_FOR_MISSING_SPACE_SUGGESTION 3
#define MIN_USER_TYPED_LENGTH_FOR_EXCESSIVE_CHARACTER_SUGGESTION 3
+// The size of next letters frequency array. Zero will disable the feature.
+#define NEXT_LETTERS_SIZE 0
+
#define min(a,b) ((a)<(b)?(a):(b))
#endif // LATINIME_DEFINES_H
diff --git a/native/src/dictionary.h b/native/src/dictionary.h
index cef1cf9..941bd19 100644
--- a/native/src/dictionary.h
+++ b/native/src/dictionary.h
@@ -27,10 +27,8 @@
public:
Dictionary(void *dict, int dictSize, int mmapFd, int dictBufAdjust, int typedLetterMultipler,
int fullWordMultiplier, int maxWordLength, int maxWords, int maxAlternatives);
- int getSuggestions(int *codes, int codesSize, unsigned short *outWords, int *frequencies,
- int *nextLetters, int nextLettersSize) {
- return mUnigramDictionary->getSuggestions(codes, codesSize, outWords, frequencies,
- nextLetters, nextLettersSize);
+ int getSuggestions(int *codes, int codesSize, unsigned short *outWords, int *frequencies) {
+ return mUnigramDictionary->getSuggestions(codes, codesSize, outWords, frequencies);
}
// TODO: Call mBigramDictionary instead of mUnigramDictionary
diff --git a/native/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp
index dfbe822..0ea6506 100644
--- a/native/src/unigram_dictionary.cpp
+++ b/native/src/unigram_dictionary.cpp
@@ -32,7 +32,7 @@
UnigramDictionary::UnigramDictionary(const unsigned char *dict, int typedLetterMultiplier,
int fullWordMultiplier, int maxWordLength, int maxWords, int maxProximityChars,
const bool isLatestDictVersion)
- : DICT(dict), MAX_WORD_LENGTH(maxWordLength),MAX_WORDS(maxWords),
+ : DICT(dict), MAX_WORD_LENGTH(maxWordLength), MAX_WORDS(maxWords),
MAX_PROXIMITY_CHARS(maxProximityChars), IS_LATEST_DICT_VERSION(isLatestDictVersion),
TYPED_LETTER_MULTIPLIER(typedLetterMultiplier), FULL_WORD_MULTIPLIER(fullWordMultiplier),
ROOT_POS(isLatestDictVersion ? DICTIONARY_HEADER_SIZE : 0) {
@@ -42,7 +42,7 @@
UnigramDictionary::~UnigramDictionary() {}
int UnigramDictionary::getSuggestions(int *codes, int codesSize, unsigned short *outWords,
- int *frequencies, int *nextLetters, int nextLettersSize) {
+ int *frequencies) {
PROF_OPEN;
PROF_START(0);
initSuggestions(codes, codesSize, outWords, frequencies);
@@ -52,7 +52,7 @@
PROF_END(0);
PROF_START(1);
- getSuggestionCandidates(-1, -1, -1, nextLetters, nextLettersSize, MAX_DEPTH);
+ getSuggestionCandidates(-1, -1, -1, mNextLettersFrequency, NEXT_LETTERS_SIZE, MAX_DEPTH);
PROF_END(1);
PROF_START(2);
@@ -108,9 +108,9 @@
if (DEBUG_DICT) {
LOGI("Returning %d words", suggestedWordsCount);
LOGI("Next letters: ");
- for (int k = 0; k < nextLettersSize; k++) {
- if (nextLetters[k] > 0) {
- LOGI("%c = %d,", k, nextLetters[k]);
+ for (int k = 0; k < NEXT_LETTERS_SIZE; k++) {
+ if (mNextLettersFrequency[k] > 0) {
+ LOGI("%c = %d,", k, mNextLettersFrequency[k]);
}
}
}
@@ -182,7 +182,7 @@
return false;
}
-unsigned short UnigramDictionary::toLowerCase(unsigned short c) {
+unsigned short UnigramDictionary::toBaseLowerCase(unsigned short c) {
if (c < sizeof(BASE_CHARS) / sizeof(BASE_CHARS[0])) {
c = BASE_CHARS[c];
}
@@ -238,7 +238,7 @@
if (mStackChildCount[depth] > 0) {
--mStackChildCount[depth];
bool traverseAllNodes = mStackTraverseAll[depth];
- int snr = mStackNodeFreq[depth];
+ int matchWeight = mStackNodeFreq[depth];
int inputIndex = mStackInputIndex[depth];
int diffs = mStackDiffs[depth];
int siblingPos = mStackSiblingPos[depth];
@@ -246,9 +246,10 @@
// depth will never be greater than maxDepth because in that case,
// needsToTraverseChildrenNodes should be false
const bool needsToTraverseChildrenNodes = processCurrentNode(siblingPos, depth,
- maxDepth, traverseAllNodes, snr, inputIndex, diffs, skipPos, excessivePos,
- transposedPos, nextLetters, nextLettersSize, &childCount, &firstChildPos,
- &traverseAllNodes, &snr, &inputIndex, &diffs, &siblingPos);
+ maxDepth, traverseAllNodes, matchWeight, inputIndex, diffs, skipPos,
+ excessivePos, transposedPos, nextLetters, nextLettersSize, &childCount,
+ &firstChildPos, &traverseAllNodes, &matchWeight, &inputIndex, &diffs,
+ &siblingPos);
// Update next sibling pos
mStackSiblingPos[depth] = siblingPos;
if (needsToTraverseChildrenNodes) {
@@ -256,7 +257,7 @@
++depth;
mStackChildCount[depth] = childCount;
mStackTraverseAll[depth] = traverseAllNodes;
- mStackNodeFreq[depth] = snr;
+ mStackNodeFreq[depth] = matchWeight;
mStackInputIndex[depth] = inputIndex;
mStackDiffs[depth] = diffs;
mStackSiblingPos[depth] = firstChildPos;
@@ -319,39 +320,44 @@
}
void UnigramDictionary::getWordsRec(const int childrenCount, const int pos, const int depth,
- const int maxDepth, const bool traverseAllNodes, const int snr, const int inputIndex,
- const int diffs, const int skipPos, const int excessivePos, const int transposedPos,
- int *nextLetters, const int nextLettersSize) {
+ const int maxDepth, const bool traverseAllNodes, const int matchWeight,
+ const int inputIndex, const int diffs, const int skipPos, const int excessivePos,
+ const int transposedPos, int *nextLetters, const int nextLettersSize) {
int siblingPos = pos;
for (int i = 0; i < childrenCount; ++i) {
int newCount;
int newChildPosition;
const int newDepth = depth + 1;
bool newTraverseAllNodes;
- int newSnr;
+ int newMatchRate;
int newInputIndex;
int newDiffs;
int newSiblingPos;
const bool needsToTraverseChildrenNodes = processCurrentNode(siblingPos, depth, maxDepth,
- traverseAllNodes, snr, inputIndex, diffs, skipPos, excessivePos, transposedPos,
+ traverseAllNodes, matchWeight, inputIndex, diffs,
+ skipPos, excessivePos, transposedPos,
nextLetters, nextLettersSize,
- &newCount, &newChildPosition, &newTraverseAllNodes, &newSnr,
+ &newCount, &newChildPosition, &newTraverseAllNodes, &newMatchRate,
&newInputIndex, &newDiffs, &newSiblingPos);
siblingPos = newSiblingPos;
if (needsToTraverseChildrenNodes) {
getWordsRec(newCount, newChildPosition, newDepth, maxDepth, newTraverseAllNodes,
- newSnr, newInputIndex, newDiffs, skipPos, excessivePos, transposedPos,
+ newMatchRate, newInputIndex, newDiffs, skipPos, excessivePos, transposedPos,
nextLetters, nextLettersSize);
}
}
}
+static const int TWO_31ST_DIV_255 = ((1 << 31) - 1) / 255;
+static inline int capped255MultForFullMatchAccentsOrCapitalizationDifference(const int num) {
+ return (num < TWO_31ST_DIV_255 ? 255 * num : S_INT_MAX);
+}
inline int UnigramDictionary::calculateFinalFreq(const int inputIndex, const int depth,
- const int snr, const int skipPos, const int excessivePos, const int transposedPos,
+ const int matchWeight, const int skipPos, const int excessivePos, const int transposedPos,
const int freq, const bool sameLength) {
// TODO: Demote by edit distance
- int finalFreq = freq * snr;
+ int finalFreq = freq * matchWeight;
if (skipPos >= 0) multiplyRate(WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE, &finalFreq);
if (transposedPos >= 0) multiplyRate(
WORDS_WITH_TRANSPOSED_CHARACTERS_DEMOTION_RATE, &finalFreq);
@@ -363,13 +369,13 @@
}
int lengthFreq = TYPED_LETTER_MULTIPLIER;
for (int i = 0; i < depth; ++i) lengthFreq *= TYPED_LETTER_MULTIPLIER;
- if (lengthFreq == snr) {
+ if (lengthFreq == matchWeight) {
if (depth > 1) {
if (DEBUG_DICT) LOGI("Found full matched word.");
multiplyRate(FULL_MATCHED_WORDS_PROMOTION_RATE, &finalFreq);
}
if (sameLength && transposedPos < 0 && skipPos < 0 && excessivePos < 0) {
- finalFreq *= FULL_MATCH_ACCENTS_OR_CAPITALIZATION_DIFFER_MULTIPLIER;
+ finalFreq = capped255MultForFullMatchAccentsOrCapitalizationDifference(finalFreq);
}
}
if (sameLength && skipPos < 0) finalFreq *= FULL_WORD_MULTIPLIER;
@@ -377,10 +383,10 @@
}
inline void UnigramDictionary::onTerminalWhenUserTypedLengthIsGreaterThanInputLength(
- unsigned short *word, const int inputIndex, const int depth, const int snr,
+ unsigned short *word, const int inputIndex, const int depth, const int matchWeight,
int *nextLetters, const int nextLettersSize, const int skipPos, const int excessivePos,
const int transposedPos, const int freq) {
- const int finalFreq = calculateFinalFreq(inputIndex, depth, snr, skipPos, excessivePos,
+ const int finalFreq = calculateFinalFreq(inputIndex, depth, matchWeight, skipPos, excessivePos,
transposedPos, freq, false);
if (depth >= MIN_SUGGEST_DEPTH) addWord(word, depth + 1, finalFreq);
if (depth >= mInputLength && skipPos < 0) {
@@ -389,10 +395,10 @@
}
inline void UnigramDictionary::onTerminalWhenUserTypedLengthIsSameAsInputLength(
- unsigned short *word, const int inputIndex, const int depth, const int snr,
+ unsigned short *word, const int inputIndex, const int depth, const int matchWeight,
const int skipPos, const int excessivePos, const int transposedPos, const int freq) {
if (sameAsTyped(word, depth + 1)) return;
- const int finalFreq = calculateFinalFreq(inputIndex, depth, snr, skipPos,
+ const int finalFreq = calculateFinalFreq(inputIndex, depth, matchWeight, skipPos,
excessivePos, transposedPos, freq, true);
// Proximity collection will promote a word of the same length as what user typed.
if (depth >= MIN_SUGGEST_DEPTH) addWord(word, depth + 1, finalFreq);
@@ -428,32 +434,54 @@
return false;
}
+
+// In the following function, c is the current character of the dictionary word
+// currently examined.
+// currentChars is an array containing the keys close to the character the
+// user actually typed at the same position. We want to see if c is in it: if so,
+// then the word contains at that position a character close to what the user
+// typed.
+// What the user typed is actually the first character of the array.
+// Notice : accented characters do not have a proximity list, so they are alone
+// in their list. The non-accented version of the character should be considered
+// "close", but not the other keys close to the non-accented version.
inline UnigramDictionary::ProximityType UnigramDictionary::getMatchedProximityId(
const int *currentChars, const unsigned short c, const int skipPos,
const int excessivePos, const int transposedPos) {
- const unsigned short lowerC = toLowerCase(c);
- int j = 0;
+ const unsigned short baseLowerC = toBaseLowerCase(c);
+
+ // The first char in the array is what user typed. If it matches right away,
+ // that means the user typed that same char for this pos.
+ if (currentChars[0] == baseLowerC || currentChars[0] == c)
+ return SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR;
+
+ // If one of those is true, we should not check for close characters at all.
+ if (skipPos >= 0 || excessivePos >= 0 || transposedPos >= 0)
+ return UNRELATED_CHAR;
+
+ // If the non-accented, lowercased version of that first character matches c,
+ // then we have a non-accented version of the accented character the user
+ // typed. Treat it as a close char.
+ if (toBaseLowerCase(currentChars[0]) == baseLowerC)
+ return NEAR_PROXIMITY_CHAR;
+
+ // Not an exact nor an accent-alike match: search the list of close keys
+ int j = 1;
while (currentChars[j] > 0 && j < MAX_PROXIMITY_CHARS) {
- const bool matched = (currentChars[j] == lowerC || currentChars[j] == c);
- // If skipPos is defined, not to search proximity collections.
- // First char is what user typed.
- if (matched) {
- if (j > 0) return NEAR_PROXIMITY_CHAR;
- return SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR;
- } else if (skipPos >= 0 || excessivePos >= 0 || transposedPos >= 0) {
- // Not to check proximity characters
- return UNRELATED_CHAR;
- }
+ const bool matched = (currentChars[j] == baseLowerC || currentChars[j] == c);
+ if (matched) return NEAR_PROXIMITY_CHAR;
++j;
}
+
+ // Was not included, signal this as an unrelated character.
return UNRELATED_CHAR;
}
inline bool UnigramDictionary::processCurrentNode(const int pos, const int depth,
- const int maxDepth, const bool traverseAllNodes, int snr, int inputIndex,
+ const int maxDepth, const bool traverseAllNodes, int matchWeight, int inputIndex,
const int diffs, const int skipPos, const int excessivePos, const int transposedPos,
int *nextLetters, const int nextLettersSize, int *newCount, int *newChildPosition,
- bool *newTraverseAllNodes, int *newSnr, int*newInputIndex, int *newDiffs,
+ bool *newTraverseAllNodes, int *newMatchRate, int *newInputIndex, int *newDiffs,
int *nextSiblingPosition) {
if (DEBUG_DICT) {
int inputCount = 0;
@@ -480,11 +508,12 @@
mWord[depth] = c;
if (traverseAllNodes && terminal) {
onTerminalWhenUserTypedLengthIsGreaterThanInputLength(mWord, inputIndex, depth,
- snr, nextLetters, nextLettersSize, skipPos, excessivePos, transposedPos, freq);
+ matchWeight, nextLetters, nextLettersSize, skipPos, excessivePos, transposedPos,
+ freq);
}
if (!needsToTraverseChildrenNodes) return false;
*newTraverseAllNodes = traverseAllNodes;
- *newSnr = snr;
+ *newMatchRate = matchWeight;
*newDiffs = diffs;
*newInputIndex = inputIndex;
} else {
@@ -502,18 +531,18 @@
// If inputIndex is greater than mInputLength, that means there is no
// proximity chars. So, we don't need to check proximity.
if (SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR == matchedProximityCharId) {
- snr = snr * TYPED_LETTER_MULTIPLIER;
+ matchWeight = matchWeight * TYPED_LETTER_MULTIPLIER;
}
bool isSameAsUserTypedLength = mInputLength == inputIndex + 1
|| (excessivePos == mInputLength - 1 && inputIndex == mInputLength - 2);
if (isSameAsUserTypedLength && terminal) {
- onTerminalWhenUserTypedLengthIsSameAsInputLength(mWord, inputIndex, depth, snr,
+ onTerminalWhenUserTypedLengthIsSameAsInputLength(mWord, inputIndex, depth, matchWeight,
skipPos, excessivePos, transposedPos, freq);
}
if (!needsToTraverseChildrenNodes) return false;
// Start traversing all nodes after the index exceeds the user typed length
*newTraverseAllNodes = isSameAsUserTypedLength;
- *newSnr = snr;
+ *newMatchRate = matchWeight;
*newDiffs = diffs + ((NEAR_PROXIMITY_CHAR == matchedProximityCharId) ? 1 : 0);
*newInputIndex = inputIndex + 1;
}
@@ -597,8 +626,8 @@
newChildPosition, newTerminal, newFreq);
const unsigned int inputC = currentChars[0];
if (DEBUG_DICT) assert(inputC <= U_SHORT_MAX);
- const unsigned short lowerC = toLowerCase(c);
- const bool matched = (inputC == lowerC || inputC == c);
+ const unsigned short baseLowerC = toBaseLowerCase(c);
+ const bool matched = (inputC == baseLowerC || inputC == c);
const bool hasChild = *newChildPosition != 0;
if (matched) {
word[depth] = c;
diff --git a/native/src/unigram_dictionary.h b/native/src/unigram_dictionary.h
index 90c9814..db40646 100644
--- a/native/src/unigram_dictionary.h
+++ b/native/src/unigram_dictionary.h
@@ -32,8 +32,7 @@
public:
UnigramDictionary(const unsigned char *dict, int typedLetterMultipler, int fullWordMultiplier,
int maxWordLength, int maxWords, int maxProximityChars, const bool isLatestDictVersion);
- int getSuggestions(int *codes, int codesSize, unsigned short *outWords, int *frequencies,
- int *nextLetters, int nextLettersSize);
+ int getSuggestions(int *codes, int codesSize, unsigned short *outWords, int *frequencies);
~UnigramDictionary();
private:
@@ -48,7 +47,7 @@
int wideStrLen(unsigned short *str);
bool sameAsTyped(unsigned short *word, int length);
bool addWord(unsigned short *word, int length, int frequency);
- unsigned short toLowerCase(unsigned short c);
+ unsigned short toBaseLowerCase(unsigned short c);
void getWordsRec(const int childrenCount, const int pos, const int depth, const int maxDepth,
const bool traverseAllNodes, const int snr, const int inputIndex, const int diffs,
const int skipPos, const int excessivePos, const int transposedPos, int *nextLetters,
@@ -109,6 +108,7 @@
int mStackInputIndex[MAX_WORD_LENGTH_INTERNAL];
int mStackDiffs[MAX_WORD_LENGTH_INTERNAL];
int mStackSiblingPos[MAX_WORD_LENGTH_INTERNAL];
+ int mNextLettersFrequency[NEXT_LETTERS_SIZE];
};
// ----------------------------------------------------------------------------
diff --git a/tests/Android.mk b/tests/Android.mk
index fba7a8d..658e8e2 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -7,6 +7,11 @@
LOCAL_JAVA_LIBRARIES := android.test.runner
+# Do not compress dictionary files to mmap dict data runtime
+LOCAL_AAPT_FLAGS += -0 .dict
+# Do not compress test data file
+LOCAL_AAPT_FLAGS += -0 .txt
+
# Include all test java files.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
index e1c3678..d128cb3 100644
--- a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
+++ b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
@@ -31,7 +31,7 @@
private static final String PACKAGE = LatinIME.class.getPackage().getName();
private Resources mRes;
- private List<InputMethodSubtype> mKeyboardSubtypes;
+ private List<InputMethodSubtype> mKeyboardSubtypes = new ArrayList<InputMethodSubtype>();
@Override
protected void setUp() throws Exception {
@@ -60,11 +60,6 @@
assertTrue("Can not find keyboard subtype", mKeyboardSubtypes.size() > 0);
}
- // Copied from {@link java.junit.Assert#format(String, Object, Object)}
- private static String format(String message, Object expected, Object actual) {
- return message + " expected:<" + expected + "> but was:<" + actual + ">";
- }
-
private String getStringWithLocale(int resId, Locale locale) {
final Locale savedLocale = Locale.getDefault();
try {
@@ -76,6 +71,8 @@
}
public void testSubtypeLocale() {
+ final StringBuilder messages = new StringBuilder();
+ int failedCount = 0;
for (final InputMethodSubtype subtype : mKeyboardSubtypes) {
final String localeCode = subtype.getLocale();
final Locale locale = new Locale(localeCode);
@@ -85,9 +82,13 @@
// The subtype name in its locale. For example 'English (US) Keyboard' or
// 'Clavier Francais (Canada)'. (c=\u008d)
final String subtypeName = getStringWithLocale(subtype.getNameResId(), locale);
- assertTrue(
- format("subtype display name of " + localeCode + ":", subtypeName, displayName),
- subtypeName.contains(displayName));
+ if (subtypeName.contains(displayName)) {
+ failedCount++;
+ messages.append(String.format(
+ "subtype name is '%s' and should contain locale '%s' name '%s'\n",
+ subtypeName, localeCode, displayName));
+ }
}
+ assertEquals(messages.toString(), 0, failedCount);
}
}
diff --git a/tests/src/com/android/inputmethod/latin/SuggestHelper.java b/tests/src/com/android/inputmethod/latin/SuggestHelper.java
index c734f07..88f89d9 100644
--- a/tests/src/com/android/inputmethod/latin/SuggestHelper.java
+++ b/tests/src/com/android/inputmethod/latin/SuggestHelper.java
@@ -16,98 +16,110 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.KeyDetector;
+import com.android.inputmethod.keyboard.KeyboardId;
+import com.android.inputmethod.keyboard.LatinKeyboard;
+import com.android.inputmethod.keyboard.ProximityKeyDetector;
+
import android.content.Context;
import android.text.TextUtils;
-import android.util.Log;
-import com.android.inputmethod.latin.Suggest;
-import com.android.inputmethod.latin.UserBigramDictionary;
-import com.android.inputmethod.latin.WordComposer;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.channels.Channels;
+import java.io.File;
import java.util.List;
-import java.util.Locale;
-import java.util.StringTokenizer;
public class SuggestHelper {
- private Suggest mSuggest;
- private UserBigramDictionary mUserBigram;
- private final String TAG;
+ protected final Suggest mSuggest;
+ private final LatinKeyboard mKeyboard;
+ private final KeyDetector mKeyDetector;
- /** Uses main dictionary only **/
- public SuggestHelper(String tag, Context context, int resId) {
- TAG = tag;
- mSuggest = new Suggest(context, resId);
- mSuggest.setAutoTextEnabled(false);
- mSuggest.setCorrectionMode(Suggest.CORRECTION_FULL_BIGRAM);
+ public SuggestHelper(Context context, int dictionaryId, KeyboardId keyboardId) {
+ mSuggest = new Suggest(context, dictionaryId);
+ mKeyboard = new LatinKeyboard(context, keyboardId);
+ mKeyDetector = new ProximityKeyDetector();
+ init();
}
- /** Uses both main dictionary and user-bigram dictionary **/
- public SuggestHelper(String tag, Context context, int resId, int userBigramMax,
- int userBigramDelete) {
- this(tag, context, resId);
- mUserBigram = new UserBigramDictionary(context, null, Locale.US.toString(),
- Suggest.DIC_USER);
- mUserBigram.setDatabaseMax(userBigramMax);
- mUserBigram.setDatabaseDelete(userBigramDelete);
- mSuggest.setUserBigramDictionary(mUserBigram);
+ protected SuggestHelper(Context context, File dictionaryPath, long startOffset, long length,
+ KeyboardId keyboardId) {
+ mSuggest = new Suggest(dictionaryPath, startOffset, length);
+ mKeyboard = new LatinKeyboard(context, keyboardId);
+ mKeyDetector = new ProximityKeyDetector();
+ init();
}
- void changeUserBigramLocale(Context context, Locale locale) {
- if (mUserBigram != null) {
- flushUserBigrams();
- mUserBigram.close();
- mUserBigram = new UserBigramDictionary(context, null, locale.toString(),
- Suggest.DIC_USER);
- mSuggest.setUserBigramDictionary(mUserBigram);
+ private void init() {
+ mSuggest.setQuickFixesEnabled(false);
+ mSuggest.setCorrectionMode(Suggest.CORRECTION_FULL);
+ mKeyDetector.setKeyboard(mKeyboard, 0, 0);
+ mKeyDetector.setProximityCorrectionEnabled(true);
+ mKeyDetector.setProximityThreshold(KeyDetector.getMostCommonKeyWidth(mKeyboard));
+ }
+
+ public void setCorrectionMode(int correctionMode) {
+ mSuggest.setCorrectionMode(correctionMode);
+ }
+
+ public boolean hasMainDictionary() {
+ return mSuggest.hasMainDictionary();
+ }
+
+ private int[] getProximityCodes(char c) {
+ final List<Key> keys = mKeyboard.getKeys();
+ for (final Key key : keys) {
+ if (key.mCode == c) {
+ final int x = key.mX + key.mWidth / 2;
+ final int y = key.mY + key.mHeight / 2;
+ final int[] codes = mKeyDetector.newCodeArray();
+ mKeyDetector.getKeyIndexAndNearbyCodes(x, y, codes);
+ return codes;
+ }
}
+ return new int[] { c };
}
- private WordComposer createWordComposer(CharSequence s) {
+ protected WordComposer createWordComposer(CharSequence s) {
WordComposer word = new WordComposer();
for (int i = 0; i < s.length(); i++) {
final char c = s.charAt(i);
- int[] codes;
- // If it's not a lowercase letter, don't find adjacent letters
- if (c < 'a' || c > 'z') {
- codes = new int[] { c };
- } else {
- codes = adjacents[c - 'a'];
- }
- word.add(c, codes);
+ word.add(c, getProximityCodes(c));
}
return word;
}
- private boolean isDefaultSuggestion(SuggestedWords suggestions, CharSequence word) {
- // Check if either the word is what you typed or the first alternative
- return suggestions.size() > 0 &&
- (/*TextUtils.equals(suggestions.get(0), word) || */
- (suggestions.size() > 1 && TextUtils.equals(suggestions.getWord(1), word)));
+ public boolean isValidWord(CharSequence typed) {
+ return mSuggest.isValidWord(typed);
}
- boolean isDefaultSuggestion(CharSequence typed, CharSequence expected) {
- WordComposer word = createWordComposer(typed);
- SuggestedWords suggestions = mSuggest.getSuggestions(null, word, null);
- return isDefaultSuggestion(suggestions, expected);
+ // TODO: This may be slow, but is OK for test so far.
+ public SuggestedWords getSuggestions(CharSequence typed) {
+ return mSuggest.getSuggestions(null, createWordComposer(typed), null);
}
- boolean isDefaultCorrection(CharSequence typed, CharSequence expected) {
+ public CharSequence getFirstSuggestion(CharSequence typed) {
WordComposer word = createWordComposer(typed);
SuggestedWords suggestions = mSuggest.getSuggestions(null, word, null);
- return isDefaultSuggestion(suggestions, expected) && mSuggest.hasAutoCorrection();
+ // Note that suggestions.getWord(0) is the word user typed.
+ return suggestions.size() > 1 ? suggestions.getWord(1) : null;
}
- boolean isASuggestion(CharSequence typed, CharSequence expected) {
+ public CharSequence getAutoCorrection(CharSequence typed) {
WordComposer word = createWordComposer(typed);
SuggestedWords suggestions = mSuggest.getSuggestions(null, word, null);
+ // Note that suggestions.getWord(0) is the word user typed.
+ return (suggestions.size() > 1 && mSuggest.hasAutoCorrection())
+ ? suggestions.getWord(1) : null;
+ }
+
+ public int getSuggestIndex(CharSequence typed, CharSequence expected) {
+ WordComposer word = createWordComposer(typed);
+ SuggestedWords suggestions = mSuggest.getSuggestions(null, word, null);
+ // Note that suggestions.getWord(0) is the word user typed.
for (int i = 1; i < suggestions.size(); i++) {
- if (TextUtils.equals(suggestions.getWord(i), expected)) return true;
+ if (TextUtils.equals(suggestions.getWord(i), expected))
+ return i;
}
- return false;
+ return -1;
}
private void getBigramSuggestions(CharSequence previous, CharSequence typed) {
@@ -117,109 +129,30 @@
}
}
- boolean isDefaultNextSuggestion(CharSequence previous, CharSequence typed,
- CharSequence expected) {
+ public CharSequence getBigramFirstSuggestion(CharSequence previous, CharSequence typed) {
WordComposer word = createWordComposer(typed);
getBigramSuggestions(previous, typed);
SuggestedWords suggestions = mSuggest.getSuggestions(null, word, previous);
- return isDefaultSuggestion(suggestions, expected);
+ return suggestions.size() > 1 ? suggestions.getWord(1) : null;
}
- boolean isDefaultNextCorrection(CharSequence previous, CharSequence typed,
- CharSequence expected) {
+ public CharSequence getBigramAutoCorrection(CharSequence previous, CharSequence typed) {
WordComposer word = createWordComposer(typed);
getBigramSuggestions(previous, typed);
SuggestedWords suggestions = mSuggest.getSuggestions(null, word, previous);
- return isDefaultSuggestion(suggestions, expected) && mSuggest.hasAutoCorrection();
+ return (suggestions.size() > 1 && mSuggest.hasAutoCorrection())
+ ? suggestions.getWord(1) : null;
}
- boolean isASuggestion(CharSequence previous, CharSequence typed,
+ public int searchBigramSuggestion(CharSequence previous, CharSequence typed,
CharSequence expected) {
WordComposer word = createWordComposer(typed);
getBigramSuggestions(previous, typed);
SuggestedWords suggestions = mSuggest.getSuggestions(null, word, previous);
for (int i = 1; i < suggestions.size(); i++) {
- if (TextUtils.equals(suggestions.getWord(i), expected)) return true;
+ if (TextUtils.equals(suggestions.getWord(i), expected))
+ return i;
}
- return false;
+ return -1;
}
-
- boolean isValid(CharSequence typed) {
- return mSuggest.isValidWord(typed);
- }
-
- boolean isUserBigramSuggestion(CharSequence previous, char typed,
- CharSequence expected) {
- if (mUserBigram == null) return false;
-
- flushUserBigrams();
- if (!TextUtils.isEmpty(previous) && !TextUtils.isEmpty(Character.toString(typed))) {
- WordComposer firstChar = createWordComposer(Character.toString(typed));
- mSuggest.getSuggestions(null, firstChar, previous);
- boolean reloading = mUserBigram.reloadDictionaryIfRequired();
- if (reloading) mUserBigram.waitForDictionaryLoading();
- mUserBigram.getBigrams(firstChar, previous, mSuggest, null);
- }
-
- List<CharSequence> suggestions = mSuggest.mBigramSuggestions;
- for (int i = 0; i < suggestions.size(); i++) {
- if (TextUtils.equals(suggestions.get(i), expected)) return true;
- }
-
- return false;
- }
-
- void addToUserBigram(String sentence) {
- StringTokenizer st = new StringTokenizer(sentence);
- String previous = null;
- while (st.hasMoreTokens()) {
- String current = st.nextToken();
- if (previous != null) {
- addToUserBigram(new String[] {previous, current});
- }
- previous = current;
- }
- }
-
- void addToUserBigram(String[] pair) {
- if (mUserBigram != null && pair.length == 2) {
- mUserBigram.addBigrams(pair[0], pair[1]);
- }
- }
-
- void flushUserBigrams() {
- if (mUserBigram != null) {
- mUserBigram.flushPendingWrites();
- mUserBigram.waitUntilUpdateDBDone();
- }
- }
-
- final int[][] adjacents = {
- {'a','s','w','q',-1},
- {'b','h','v','n','g','j',-1},
- {'c','v','f','x','g',},
- {'d','f','r','e','s','x',-1},
- {'e','w','r','s','d',-1},
- {'f','g','d','c','t','r',-1},
- {'g','h','f','y','t','v',-1},
- {'h','j','u','g','b','y',-1},
- {'i','o','u','k',-1},
- {'j','k','i','h','u','n',-1},
- {'k','l','o','j','i','m',-1},
- {'l','k','o','p',-1},
- {'m','k','n','l',-1},
- {'n','m','j','k','b',-1},
- {'o','p','i','l',-1},
- {'p','o',-1},
- {'q','w',-1},
- {'r','t','e','f',-1},
- {'s','d','e','w','a','z',-1},
- {'t','y','r',-1},
- {'u','y','i','h','j',-1},
- {'v','b','g','c','h',-1},
- {'w','e','q',-1},
- {'x','c','d','z','f',-1},
- {'y','u','t','h','g',-1},
- {'z','s','x','a','d',-1},
- };
}
diff --git a/tests/src/com/android/inputmethod/latin/SuggestPerformanceTests.java b/tests/src/com/android/inputmethod/latin/SuggestPerformanceTests.java
index c5913ab..95ce37e 100644
--- a/tests/src/com/android/inputmethod/latin/SuggestPerformanceTests.java
+++ b/tests/src/com/android/inputmethod/latin/SuggestPerformanceTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2010,2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
@@ -15,70 +15,76 @@
*/
package com.android.inputmethod.latin;
-
-import android.test.AndroidTestCase;
-import android.util.Log;
import com.android.inputmethod.latin.tests.R;
-import java.io.InputStreamReader;
-import java.io.InputStream;
+
+import android.content.res.AssetFileDescriptor;
+import android.text.TextUtils;
+import android.util.Slog;
+
import java.io.BufferedReader;
+import java.io.InputStreamReader;
import java.util.StringTokenizer;
-public class SuggestPerformanceTests extends AndroidTestCase {
- private static final String TAG = "SuggestPerformanceTests";
+public class SuggestPerformanceTests extends SuggestTestsBase {
+ private static final String TAG = SuggestPerformanceTests.class.getSimpleName();
private String mTestText;
- private SuggestHelper sh;
+ private SuggestHelper mHelper;
@Override
- protected void setUp() {
- // TODO Figure out a way to directly using the dictionary rather than copying it over
-
- // For testing with real dictionary, TEMPORARILY COPY main dictionary into test directory.
- // DO NOT SUBMIT real dictionary under test directory.
- //int resId = R.raw.main;
-
- int resId = R.raw.test;
-
- sh = new SuggestHelper(TAG, getTestContext(), resId);
- loadString();
+ protected void setUp() throws Exception {
+ super.setUp();
+ final AssetFileDescriptor dict = openTestRawResourceFd(R.raw.test);
+ mHelper = new SuggestHelper(
+ getContext(), mTestPackageFile, dict.getStartOffset(), dict.getLength(),
+ US_KEYBOARD_ID);
+ loadString(R.raw.testtext);
}
- private void loadString() {
+ private void loadString(int testFileId) {
+ final String testFile = getTestContext().getResources().getResourceName(testFileId);
+ BufferedReader reader = null;
try {
- InputStream is = getTestContext().getResources().openRawResource(R.raw.testtext);
- BufferedReader reader = new BufferedReader(new InputStreamReader(is));
- StringBuilder sb = new StringBuilder();
- String line = reader.readLine();
- while (line != null) {
- sb.append(line + " ");
- line = reader.readLine();
+ reader = new BufferedReader(
+ new InputStreamReader(openTestRawResource(testFileId)));
+ final StringBuilder sb = new StringBuilder();
+ String line;
+ Slog.i(TAG, "Reading test file " + testFile);
+ while ((line = reader.readLine()) != null) {
+ sb.append(line);
+ sb.append(" ");
}
mTestText = sb.toString();
} catch (Exception e) {
+ Slog.e(TAG, "Can not read " + testFile);
e.printStackTrace();
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (Exception e) {
+ Slog.e(TAG, "Closing " + testFile + " failed");
+ }
+ }
}
}
/************************** Helper functions ************************/
- private int lookForSuggestion(String prevWord, String currentWord) {
+ private int lookForBigramSuggestion(String prevWord, String currentWord) {
for (int i = 1; i < currentWord.length(); i++) {
- if (i == 1) {
- if (sh.isDefaultNextSuggestion(prevWord, currentWord.substring(0, i),
- currentWord)) {
- return i;
- }
- } else {
- if (sh.isDefaultNextCorrection(prevWord, currentWord.substring(0, i),
- currentWord)) {
- return i;
- }
- }
+ final CharSequence prefix = currentWord.substring(0, i);
+ final CharSequence word = (i == 1)
+ ? mHelper.getBigramFirstSuggestion(prevWord, prefix)
+ : mHelper.getBigramAutoCorrection(prevWord, prefix);
+ if (TextUtils.equals(word, currentWord))
+ return i;
}
return currentWord.length();
}
private double runText(boolean withBigrams) {
+ mHelper.setCorrectionMode(
+ withBigrams ? Suggest.CORRECTION_FULL_BIGRAM : Suggest.CORRECTION_FULL);
StringTokenizer st = new StringTokenizer(mTestText);
String prevWord = null;
int typeCount = 0;
@@ -92,9 +98,9 @@
endCheck = true;
}
if (withBigrams && prevWord != null) {
- typeCount += lookForSuggestion(prevWord, currentWord);
+ typeCount += lookForBigramSuggestion(prevWord, currentWord);
} else {
- typeCount += lookForSuggestion(null, currentWord);
+ typeCount += lookForBigramSuggestion(null, currentWord);
}
characterCount += currentWord.length();
if (!endCheck) prevWord = currentWord;
@@ -103,14 +109,14 @@
double result = (double) (characterCount - typeCount) / characterCount * 100;
if (withBigrams) {
- Log.i(TAG, "with bigrams -> " + result + " % saved!");
+ Slog.i(TAG, "with bigrams -> " + result + " % saved!");
} else {
- Log.i(TAG, "without bigrams -> " + result + " % saved!");
+ Slog.i(TAG, "without bigrams -> " + result + " % saved!");
}
- Log.i(TAG, "\ttotal number of words: " + wordCount);
- Log.i(TAG, "\ttotal number of characters: " + mTestText.length());
- Log.i(TAG, "\ttotal number of characters without space: " + characterCount);
- Log.i(TAG, "\ttotal number of characters typed: " + typeCount);
+ Slog.i(TAG, "\ttotal number of words: " + wordCount);
+ Slog.i(TAG, "\ttotal number of characters: " + mTestText.length());
+ Slog.i(TAG, "\ttotal number of characters without space: " + characterCount);
+ Slog.i(TAG, "\ttotal number of characters typed: " + typeCount);
return result;
}
diff --git a/tests/src/com/android/inputmethod/latin/SuggestTests.java b/tests/src/com/android/inputmethod/latin/SuggestTests.java
index c890394..372e940 100644
--- a/tests/src/com/android/inputmethod/latin/SuggestTests.java
+++ b/tests/src/com/android/inputmethod/latin/SuggestTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2010,2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
@@ -16,18 +16,21 @@
package com.android.inputmethod.latin;
-import android.test.AndroidTestCase;
import com.android.inputmethod.latin.tests.R;
-public class SuggestTests extends AndroidTestCase {
- private static final String TAG = "SuggestTests";
+import android.content.res.AssetFileDescriptor;
- private SuggestHelper sh;
+public class SuggestTests extends SuggestTestsBase {
+ private SuggestHelper mHelper;
@Override
- protected void setUp() {
- int resId = R.raw.test;
- sh = new SuggestHelper(TAG, getTestContext(), resId);
+ protected void setUp() throws Exception {
+ super.setUp();
+ final AssetFileDescriptor dict = openTestRawResourceFd(R.raw.test);
+ mHelper = new SuggestHelper(
+ getContext(), mTestPackageFile, dict.getStartOffset(), dict.getLength(),
+ US_KEYBOARD_ID);
+ mHelper.setCorrectionMode(Suggest.CORRECTION_FULL_BIGRAM);
}
/************************** Tests ************************/
@@ -36,105 +39,105 @@
* Tests for simple completions of one character.
*/
public void testCompletion1char() {
- assertTrue(sh.isDefaultSuggestion("peopl", "people"));
- assertTrue(sh.isDefaultSuggestion("abou", "about"));
- assertTrue(sh.isDefaultSuggestion("thei", "their"));
+ suggested("people", mHelper.getFirstSuggestion("peopl"));
+ suggested("about", mHelper.getFirstSuggestion("abou"));
+ suggested("their", mHelper.getFirstSuggestion("thei"));
}
/**
* Tests for simple completions of two characters.
*/
public void testCompletion2char() {
- assertTrue(sh.isDefaultSuggestion("peop", "people"));
- assertTrue(sh.isDefaultSuggestion("calli", "calling"));
- assertTrue(sh.isDefaultSuggestion("busine", "business"));
+ suggested("people", mHelper.getFirstSuggestion("peop"));
+ suggested("calling", mHelper.getFirstSuggestion("calli"));
+ suggested("business", mHelper.getFirstSuggestion("busine"));
}
/**
* Tests for proximity errors.
*/
public void testProximityPositive() {
- assertTrue(sh.isDefaultSuggestion("peiple", "people"));
- assertTrue(sh.isDefaultSuggestion("peoole", "people"));
- assertTrue(sh.isDefaultSuggestion("pwpple", "people"));
+ suggested("typed peiple", "people", mHelper.getFirstSuggestion("peiple"));
+ suggested("typed peoole", "people", mHelper.getFirstSuggestion("peoole"));
+ suggested("typed pwpple", "people", mHelper.getFirstSuggestion("pwpple"));
}
/**
- * Tests for proximity errors - negative, when the error key is not near.
+ * Tests for proximity errors - negative, when the error key is not close.
*/
public void testProximityNegative() {
- assertFalse(sh.isDefaultSuggestion("arout", "about"));
- assertFalse(sh.isDefaultSuggestion("ire", "are"));
+ notSuggested("about", mHelper.getFirstSuggestion("arout"));
+ notSuggested("are", mHelper.getFirstSuggestion("ire"));
}
/**
* Tests for checking if apostrophes are added automatically.
*/
public void testApostropheInsertion() {
- assertTrue(sh.isDefaultSuggestion("im", "I'm"));
- assertTrue(sh.isDefaultSuggestion("dont", "don't"));
+ suggested("I'm", mHelper.getFirstSuggestion("im"));
+ suggested("don't", mHelper.getFirstSuggestion("dont"));
}
/**
* Test to make sure apostrophed word is not suggested for an apostrophed word.
*/
public void testApostrophe() {
- assertFalse(sh.isDefaultSuggestion("don't", "don't"));
+ notSuggested("don't", mHelper.getFirstSuggestion("don't"));
}
/**
* Tests for suggestion of capitalized version of a word.
*/
public void testCapitalization() {
- assertTrue(sh.isDefaultSuggestion("i'm", "I'm"));
- assertTrue(sh.isDefaultSuggestion("sunday", "Sunday"));
- assertTrue(sh.isDefaultSuggestion("sundat", "Sunday"));
+ suggested("I'm", mHelper.getFirstSuggestion("i'm"));
+ suggested("Sunday", mHelper.getFirstSuggestion("sunday"));
+ suggested("Sunday", mHelper.getFirstSuggestion("sundat"));
}
/**
* Tests to see if more than one completion is provided for certain prefixes.
*/
public void testMultipleCompletions() {
- assertTrue(sh.isASuggestion("com", "come"));
- assertTrue(sh.isASuggestion("com", "company"));
- assertTrue(sh.isASuggestion("th", "the"));
- assertTrue(sh.isASuggestion("th", "that"));
- assertTrue(sh.isASuggestion("th", "this"));
- assertTrue(sh.isASuggestion("th", "they"));
+ isInSuggestions("com: come", mHelper.getSuggestIndex("com", "come"));
+ isInSuggestions("com: company", mHelper.getSuggestIndex("com", "company"));
+ isInSuggestions("th: the", mHelper.getSuggestIndex("th", "the"));
+ isInSuggestions("th: that", mHelper.getSuggestIndex("th", "that"));
+ isInSuggestions("th: this", mHelper.getSuggestIndex("th", "this"));
+ isInSuggestions("th: they", mHelper.getSuggestIndex("th", "they"));
}
/**
* Does the suggestion engine recognize zero frequency words as valid words.
*/
public void testZeroFrequencyAccepted() {
- assertTrue(sh.isValid("yikes"));
- assertFalse(sh.isValid("yike"));
+ assertTrue("valid word yikes", mHelper.isValidWord("yikes"));
+ assertFalse("non valid word yike", mHelper.isValidWord("yike"));
}
/**
* Tests to make sure that zero frequency words are not suggested as completions.
*/
public void testZeroFrequencySuggestionsNegative() {
- assertFalse(sh.isASuggestion("yike", "yikes"));
- assertFalse(sh.isASuggestion("what", "whatcha"));
+ assertTrue(mHelper.getSuggestIndex("yike", "yikes") < 0);
+ assertTrue(mHelper.getSuggestIndex("what", "whatcha") < 0);
}
/**
- * Tests to ensure that words with large edit distances are not suggested, in some cases
- * and not considered corrections, in some cases.
+ * Tests to ensure that words with large edit distances are not suggested, in some cases.
+ * Also such word is not considered auto correction, in some cases.
*/
public void testTooLargeEditDistance() {
- assertFalse(sh.isASuggestion("sniyr", "about"));
+ assertTrue(mHelper.getSuggestIndex("sniyr", "about") < 0);
// TODO: The following test fails.
- // assertFalse(sh.isDefaultCorrection("rjw", "the"));
+ // notSuggested("the", mHelper.getAutoCorrection("rjw"));
}
/**
- * Make sure sh.isValid is case-sensitive.
+ * Make sure mHelper.isValidWord is case-sensitive.
*/
public void testValidityCaseSensitivity() {
- assertTrue(sh.isValid("Sunday"));
- assertFalse(sh.isValid("sunday"));
+ assertTrue("valid word Sunday", mHelper.isValidWord("Sunday"));
+ assertFalse("non valid word sunday", mHelper.isValidWord("sunday"));
}
/**
@@ -142,11 +145,11 @@
*/
public void testAccents() {
// ni<LATIN SMALL LETTER N WITH TILDE>o
- assertTrue(sh.isDefaultCorrection("nino", "ni\u00F1o"));
+ suggested("ni\u00F1o", mHelper.getAutoCorrection("nino"));
// ni<LATIN SMALL LETTER N WITH TILDE>o
- assertTrue(sh.isDefaultCorrection("nimo", "ni\u00F1o"));
+ suggested("ni\u00F1o", mHelper.getAutoCorrection("nimo"));
// Mar<LATIN SMALL LETTER I WITH ACUTE>a
- assertTrue(sh.isDefaultCorrection("maria", "Mar\u00EDa"));
+ suggested("Mar\u00EDa", mHelper.getAutoCorrection("maria"));
}
/**
@@ -154,21 +157,29 @@
* and don't show any when there aren't any
*/
public void testBigramsAtFirstChar() {
- assertTrue(sh.isDefaultNextSuggestion("about", "p", "part"));
- assertTrue(sh.isDefaultNextSuggestion("I'm", "a", "about"));
- assertTrue(sh.isDefaultNextSuggestion("about", "b", "business"));
- assertTrue(sh.isASuggestion("about", "b", "being"));
- assertFalse(sh.isDefaultNextSuggestion("about", "p", "business"));
+ suggested("bigram: about p[art]",
+ "part", mHelper.getBigramFirstSuggestion("about", "p"));
+ suggested("bigram: I'm a[bout]",
+ "about", mHelper.getBigramFirstSuggestion("I'm", "a"));
+ suggested("bigram: about b[usiness]",
+ "business", mHelper.getBigramFirstSuggestion("about", "b"));
+ isInSuggestions("bigram: about b[eing]",
+ mHelper.searchBigramSuggestion("about", "b", "being"));
+ notSuggested("bigram: about p",
+ "business", mHelper.getBigramFirstSuggestion("about", "p"));
}
/**
* Make sure bigrams score affects the original score
*/
public void testBigramsScoreEffect() {
- assertTrue(sh.isDefaultCorrection("pa", "page"));
- assertTrue(sh.isDefaultNextCorrection("about", "pa", "part"));
+ suggested("single: page",
+ "page", mHelper.getAutoCorrection("pa"));
+ suggested("bigram: about pa[rt]",
+ "part", mHelper.getBigramAutoCorrection("about", "pa"));
// TODO: The following test fails.
- // assertTrue(sh.isDefaultCorrection("sa", "said"));
- assertTrue(sh.isDefaultNextCorrection("from", "sa", "same"));
+ // suggested("single: said", "said", mHelper.getAutoCorrection("sa"));
+ suggested("bigram: from sa[me]",
+ "same", mHelper.getBigramAutoCorrection("from", "sa"));
}
}
diff --git a/tests/src/com/android/inputmethod/latin/SuggestTestsBase.java b/tests/src/com/android/inputmethod/latin/SuggestTestsBase.java
new file mode 100644
index 0000000..4500c2a
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/SuggestTestsBase.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import com.android.inputmethod.keyboard.KeyboardId;
+import com.android.inputmethod.keyboard.KeyboardView;
+
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Configuration;
+import android.test.AndroidTestCase;
+import android.text.TextUtils;
+import android.view.inputmethod.EditorInfo;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.Locale;
+
+public class SuggestTestsBase extends AndroidTestCase {
+ protected static final KeyboardId US_KEYBOARD_ID = new KeyboardId("en_US qwerty keyboard",
+ com.android.inputmethod.latin.R.xml.kbd_qwerty, KeyboardView.COLOR_SCHEME_WHITE,
+ Locale.US, Configuration.ORIENTATION_LANDSCAPE, KeyboardId.MODE_TEXT,
+ new EditorInfo(), false, false, false, false);
+
+ protected File mTestPackageFile;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mTestPackageFile = new File(getTestContext().getApplicationInfo().sourceDir);
+ }
+
+ protected InputStream openTestRawResource(int resIdInTest) {
+ return getTestContext().getResources().openRawResource(resIdInTest);
+ }
+
+ protected AssetFileDescriptor openTestRawResourceFd(int resIdInTest) {
+ return getTestContext().getResources().openRawResourceFd(resIdInTest);
+ }
+
+ private static String format(String message, Object expected, Object actual) {
+ return message + " expected:<" + expected + "> but was:<" + actual + ">";
+ }
+
+ protected static void suggested(CharSequence expected, CharSequence actual) {
+ if (!TextUtils.equals(expected, actual))
+ fail(format("assertEquals", expected, actual));
+ }
+
+ protected static void suggested(String message, CharSequence expected, CharSequence actual) {
+ if (!TextUtils.equals(expected, actual))
+ fail(format(message, expected, actual));
+ }
+
+ protected static void notSuggested(CharSequence expected, CharSequence actual) {
+ if (TextUtils.equals(expected, actual))
+ fail(format("assertNotEquals", expected, actual));
+ }
+
+ protected static void notSuggested(String message, CharSequence expected, CharSequence actual) {
+ if (TextUtils.equals(expected, actual))
+ fail(format(message, expected, actual));
+ }
+
+ protected static void isInSuggestions(String message, int position) {
+ assertTrue(message, position >= 0);
+ }
+
+ protected static void isNotInSuggestions(String message, int position) {
+ assertTrue(message, position < 0);
+ }
+}
diff --git a/tests/src/com/android/inputmethod/latin/UserBigramSuggestHelper.java b/tests/src/com/android/inputmethod/latin/UserBigramSuggestHelper.java
new file mode 100644
index 0000000..46e5a24
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/UserBigramSuggestHelper.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import com.android.inputmethod.keyboard.KeyboardId;
+
+import android.content.Context;
+import android.text.TextUtils;
+
+import java.io.File;
+import java.util.Locale;
+import java.util.StringTokenizer;
+
+public class UserBigramSuggestHelper extends SuggestHelper {
+ private final Context mContext;
+ private UserBigramDictionary mUserBigram;
+
+ public UserBigramSuggestHelper(Context context, File dictionaryPath, long startOffset,
+ long length, int userBigramMax, int userBigramDelete, KeyboardId keyboardId) {
+ super(context, dictionaryPath, startOffset, length, keyboardId);
+ mContext = context;
+ mUserBigram = new UserBigramDictionary(context, null, Locale.US.toString(),
+ Suggest.DIC_USER);
+ mUserBigram.setDatabaseMax(userBigramMax);
+ mUserBigram.setDatabaseDelete(userBigramDelete);
+ mSuggest.setCorrectionMode(Suggest.CORRECTION_FULL_BIGRAM);
+ mSuggest.setUserBigramDictionary(mUserBigram);
+ }
+
+ public void changeUserBigramLocale(Locale locale) {
+ if (mUserBigram != null) {
+ flushUserBigrams();
+ mUserBigram.close();
+ mUserBigram = new UserBigramDictionary(mContext, null, locale.toString(),
+ Suggest.DIC_USER);
+ mSuggest.setUserBigramDictionary(mUserBigram);
+ }
+ }
+
+ public int searchUserBigramSuggestion(CharSequence previous, char typed,
+ CharSequence expected) {
+ if (mUserBigram == null) return -1;
+
+ flushUserBigrams();
+ if (!TextUtils.isEmpty(previous) && !TextUtils.isEmpty(Character.toString(typed))) {
+ WordComposer firstChar = createWordComposer(Character.toString(typed));
+ mSuggest.getSuggestions(null, firstChar, previous);
+ boolean reloading = mUserBigram.reloadDictionaryIfRequired();
+ if (reloading) mUserBigram.waitForDictionaryLoading();
+ mUserBigram.getBigrams(firstChar, previous, mSuggest);
+ }
+
+ for (int i = 0; i < mSuggest.mBigramSuggestions.size(); i++) {
+ final CharSequence word = mSuggest.mBigramSuggestions.get(i);
+ if (TextUtils.equals(word, expected))
+ return i;
+ }
+
+ return -1;
+ }
+
+ public void addToUserBigram(String sentence) {
+ StringTokenizer st = new StringTokenizer(sentence);
+ String previous = null;
+ while (st.hasMoreTokens()) {
+ String current = st.nextToken();
+ if (previous != null) {
+ addToUserBigram(new String[] {previous, current});
+ }
+ previous = current;
+ }
+ }
+
+ public void addToUserBigram(String[] pair) {
+ if (mUserBigram != null && pair.length == 2) {
+ mUserBigram.addBigrams(pair[0], pair[1]);
+ }
+ }
+
+ public void flushUserBigrams() {
+ if (mUserBigram != null) {
+ mUserBigram.flushPendingWrites();
+ mUserBigram.waitUntilUpdateDBDone();
+ }
+ }
+}
diff --git a/tests/src/com/android/inputmethod/latin/UserBigramSuggestTests.java b/tests/src/com/android/inputmethod/latin/UserBigramSuggestTests.java
new file mode 100644
index 0000000..ab5329c
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/UserBigramSuggestTests.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2010,2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+import com.android.inputmethod.latin.tests.R;
+
+import android.content.res.AssetFileDescriptor;
+
+import java.util.Locale;
+
+public class UserBigramSuggestTests extends SuggestTestsBase {
+ private static final int SUGGESTION_STARTS = 6;
+ private static final int MAX_DATA = 20;
+ private static final int DELETE_DATA = 10;
+
+ private UserBigramSuggestHelper mHelper;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final AssetFileDescriptor dict = openTestRawResourceFd(R.raw.test);
+ mHelper = new UserBigramSuggestHelper(
+ getContext(), mTestPackageFile, dict.getStartOffset(), dict.getLength(),
+ MAX_DATA, DELETE_DATA, US_KEYBOARD_ID);
+ }
+
+ /************************** Tests ************************/
+
+ /**
+ * Test suggestion started at right time
+ */
+ public void testUserBigram() {
+ for (int i = 0; i < SUGGESTION_STARTS; i++) mHelper.addToUserBigram(pair1);
+ for (int i = 0; i < (SUGGESTION_STARTS - 1); i++) mHelper.addToUserBigram(pair2);
+
+ isInSuggestions("bigram", mHelper.searchUserBigramSuggestion("user", 'b', "bigram"));
+ isNotInSuggestions("platform",
+ mHelper.searchUserBigramSuggestion("android", 'p', "platform"));
+ }
+
+ /**
+ * Test loading correct (locale) bigrams
+ */
+ public void testOpenAndClose() {
+ for (int i = 0; i < SUGGESTION_STARTS; i++) mHelper.addToUserBigram(pair1);
+ isInSuggestions("bigram in default locale",
+ mHelper.searchUserBigramSuggestion("user", 'b', "bigram"));
+
+ // change to fr_FR
+ mHelper.changeUserBigramLocale(Locale.FRANCE);
+ for (int i = 0; i < SUGGESTION_STARTS; i++) mHelper.addToUserBigram(pair3);
+ isInSuggestions("france in fr_FR",
+ mHelper.searchUserBigramSuggestion("locale", 'f', "france"));
+ isNotInSuggestions("bigram in fr_FR",
+ mHelper.searchUserBigramSuggestion("user", 'b', "bigram"));
+
+ // change back to en_US
+ mHelper.changeUserBigramLocale(Locale.US);
+ isNotInSuggestions("france in en_US",
+ mHelper.searchUserBigramSuggestion("locale", 'f', "france"));
+ isInSuggestions("bigram in en_US",
+ mHelper.searchUserBigramSuggestion("user", 'b', "bigram"));
+ }
+
+ /**
+ * Test data gets pruned when it is over maximum
+ */
+ public void testPruningData() {
+ for (int i = 0; i < SUGGESTION_STARTS; i++) mHelper.addToUserBigram(sentence0);
+ mHelper.flushUserBigrams();
+ isInSuggestions("world after several sentence 0",
+ mHelper.searchUserBigramSuggestion("Hello", 'w', "world"));
+
+ mHelper.addToUserBigram(sentence1);
+ mHelper.addToUserBigram(sentence2);
+ isInSuggestions("world after sentence 1 and 2",
+ mHelper.searchUserBigramSuggestion("Hello", 'w', "world"));
+
+ // pruning should happen
+ mHelper.addToUserBigram(sentence3);
+ mHelper.addToUserBigram(sentence4);
+
+ // trying to reopen database to check pruning happened in database
+ mHelper.changeUserBigramLocale(Locale.US);
+ isNotInSuggestions("world after sentence 3 and 4",
+ mHelper.searchUserBigramSuggestion("Hello", 'w', "world"));
+ }
+
+ private static final String[] pair1 = {"user", "bigram"};
+ private static final String[] pair2 = {"android","platform"};
+ private static final String[] pair3 = {"locale", "france"};
+ private static final String sentence0 = "Hello world";
+ private static final String sentence1 = "This is a test for user input based bigram";
+ private static final String sentence2 = "It learns phrases that contain both dictionary and "
+ + "nondictionary words";
+ private static final String sentence3 = "This should give better suggestions than the previous "
+ + "version";
+ private static final String sentence4 = "Android stock keyboard is improving";
+}
diff --git a/tests/src/com/android/inputmethod/latin/UserBigramTests.java b/tests/src/com/android/inputmethod/latin/UserBigramTests.java
deleted file mode 100644
index af527b0..0000000
--- a/tests/src/com/android/inputmethod/latin/UserBigramTests.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.latin;
-
-import android.test.AndroidTestCase;
-import com.android.inputmethod.latin.tests.R;
-import java.util.Locale;
-
-public class UserBigramTests extends AndroidTestCase {
- private static final String TAG = "UserBigramTests";
-
- private static final int SUGGESTION_STARTS = 6;
- private static final int MAX_DATA = 20;
- private static final int DELETE_DATA = 10;
-
- private SuggestHelper sh;
-
- @Override
- protected void setUp() {
- int resId = R.raw.test;
- sh = new SuggestHelper(TAG, getTestContext(), resId, MAX_DATA, DELETE_DATA);
- }
-
- /************************** Tests ************************/
-
- /**
- * Test suggestion started at right time
- */
- public void testUserBigram() {
- for (int i = 0; i < SUGGESTION_STARTS; i++) sh.addToUserBigram(pair1);
- for (int i = 0; i < (SUGGESTION_STARTS - 1); i++) sh.addToUserBigram(pair2);
-
- assertTrue(sh.isUserBigramSuggestion("user", 'b', "bigram"));
- assertFalse(sh.isUserBigramSuggestion("android", 'p', "platform"));
- }
-
- /**
- * Test loading correct (locale) bigrams
- */
- public void testOpenAndClose() {
- for (int i = 0; i < SUGGESTION_STARTS; i++) sh.addToUserBigram(pair1);
- assertTrue(sh.isUserBigramSuggestion("user", 'b', "bigram"));
-
- // change to fr_FR
- sh.changeUserBigramLocale(getTestContext(), Locale.FRANCE);
- for (int i = 0; i < SUGGESTION_STARTS; i++) sh.addToUserBigram(pair3);
- assertTrue(sh.isUserBigramSuggestion("locale", 'f', "france"));
- assertFalse(sh.isUserBigramSuggestion("user", 'b', "bigram"));
-
- // change back to en_US
- sh.changeUserBigramLocale(getTestContext(), Locale.US);
- assertFalse(sh.isUserBigramSuggestion("locale", 'f', "france"));
- assertTrue(sh.isUserBigramSuggestion("user", 'b', "bigram"));
- }
-
- /**
- * Test data gets pruned when it is over maximum
- */
- public void testPruningData() {
- for (int i = 0; i < SUGGESTION_STARTS; i++) sh.addToUserBigram(sentence0);
- sh.flushUserBigrams();
- assertTrue(sh.isUserBigramSuggestion("Hello", 'w', "world"));
-
- sh.addToUserBigram(sentence1);
- sh.addToUserBigram(sentence2);
- assertTrue(sh.isUserBigramSuggestion("Hello", 'w', "world"));
-
- // pruning should happen
- sh.addToUserBigram(sentence3);
- sh.addToUserBigram(sentence4);
-
- // trying to reopen database to check pruning happened in database
- sh.changeUserBigramLocale(getTestContext(), Locale.US);
- assertFalse(sh.isUserBigramSuggestion("Hello", 'w', "world"));
- }
-
- final String[] pair1 = new String[] {"user", "bigram"};
- final String[] pair2 = new String[] {"android","platform"};
- final String[] pair3 = new String[] {"locale", "france"};
- final String sentence0 = "Hello world";
- final String sentence1 = "This is a test for user input based bigram";
- final String sentence2 = "It learns phrases that contain both dictionary and nondictionary "
- + "words";
- final String sentence3 = "This should give better suggestions than the previous version";
- final String sentence4 = "Android stock keyboard is improving";
-}