Merge "Fix key info reported by keyboard accessibility node provider." into jb-mr1-dev
diff --git a/java/res/layout/research_splash.xml b/java/res/layout/research_splash.xml
deleted file mode 100644
index 56fd702..0000000
--- a/java/res/layout/research_splash.xml
+++ /dev/null
@@ -1,88 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:id="@+id/research_splash_screen_layout">
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
- <com.android.internal.widget.DialogTitle
- style="?android:attr/windowTitleStyle"
- android:singleLine="true"
- android:ellipsize="end"
- android:layout_width="match_parent"
- android:layout_height="64dip"
- android:layout_marginLeft="16dip"
- android:layout_marginRight="16dip"
- android:gravity="center_vertical|left"
- android:text="@string/research_splash_title" />
- <View android:layout_width="match_parent"
- android:layout_height="2dip"
- android:background="@android:color/holo_blue_light" />
- </LinearLayout>
-
- <TextView
- android:text="@string/research_splash_content"
- android:layout_height="fill_parent"
- android:layout_width="match_parent"
- android:layout_gravity="fill_horizontal|center_vertical"
- android:layout_marginLeft="16dip"
- android:layout_marginRight="16dip"
- android:layout_marginBottom="16dip"
- android:layout_marginTop="16dip"/>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:divider="?android:attr/dividerHorizontal"
- android:showDividers="beginning"
- android:dividerPadding="0dip">
- <LinearLayout
- style="?android:attr/buttonBarStyle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:measureWithLargestChild="true">
- <Button
- android:layout_width="0dip"
- android:layout_gravity="left"
- android:layout_weight="1"
- android:maxLines="2"
- stype="?android:attr/buttonBarButtonStyle"
- android:textSize="14sp"
- android:text="@string/research_dont_send_usage_info"
- android:layout_height="wrap_content"
- android:id="@+id/research_do_not_log_button" />
- <Button
- android:layout_width="0dip"
- android:layout_gravity="right"
- android:layout_weight="1"
- android:maxLines="2"
- style="?android:attr/buttonBarButtonStyle"
- android:textSize="14sp"
- android:text="@string/research_send_usage_info"
- android:layout_height="wrap_content"
- android:id="@+id/research_do_log_button" />
- </LinearLayout>
- </LinearLayout>
-</LinearLayout>
diff --git a/java/res/values-en/whitelist.xml b/java/res/values-en/whitelist.xml
deleted file mode 100644
index 2620179..0000000
--- a/java/res/values-en/whitelist.xml
+++ /dev/null
@@ -1,411 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** 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.
-*/
--->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!--
- An entry of the whitelist word should be:
- 1. (int)frequency
- 2. (String)before
- 3. (String)after
- -->
- <string-array name="wordlist_whitelist" translatable="false">
-
- <item>255</item>
- <item>ill</item>
- <item>I\'ll</item>
-
- <!-- TODO: Trim down more entries by removing ones that get auto-corrected by the
- Android keyboard's own typing error correction algorithms. -->
-
- <item>255</item>
- <item>acomodate</item>
- <item>accommodate</item>
-
- <item>255</item>
- <item>aint</item>
- <item>ain\'t</item>
-
- <item>255</item>
- <item>alot</item>
- <item>a lot</item>
-
- <item>255</item>
- <item>andteh</item>
- <item>and the</item>
-
- <item>255</item>
- <item>arent</item>
- <item>aren\'t</item>
-
- <item>255</item>
- <item>bot</item>
- <item>not</item>
-
- <item>255</item>
- <item>bern</item>
- <item>been</item>
-
- <item>255</item>
- <item>bot</item>
- <item>not</item>
-
- <item>255</item>
- <item>bur</item>
- <item>but</item>
-
- <item>255</item>
- <item>cam</item>
- <item>can</item>
-
- <item>255</item>
- <item>cant</item>
- <item>can\'t</item>
-
- <item>255</item>
- <item>dame</item>
- <item>same</item>
-
- <item>255</item>
- <item>didint</item>
- <item>didn\'t</item>
-
- <item>255</item>
- <item>dormer</item>
- <item>former</item>
-
- <item>255</item>
- <item>dud</item>
- <item>did</item>
-
- <item>255</item>
- <item>fay</item>
- <item>day</item>
-
- <item>255</item>
- <item>fife</item>
- <item>five</item>
-
- <item>255</item>
- <item>foo</item>
- <item>for</item>
-
- <item>255</item>
- <item>fora</item>
- <item>for a</item>
-
- <item>255</item>
- <item>galled</item>
- <item>called</item>
-
- <item>255</item>
- <item>goo</item>
- <item>too</item>
-
- <item>255</item>
- <item>hed</item>
- <item>he\'d</item>
-
- <item>255</item>
- <item>hel</item>
- <item>he\'ll</item>
-
- <item>255</item>
- <item>heres</item>
- <item>here\'s</item>
-
- <item>255</item>
- <item>hew</item>
- <item>new</item>
-
- <item>255</item>
- <item>hoe</item>
- <item>how</item>
-
- <item>255</item>
- <item>hoes</item>
- <item>how\'s</item>
-
- <item>255</item>
- <item>howd</item>
- <item>how\'d</item>
-
- <item>255</item>
- <item>howll</item>
- <item>how\'ll</item>
-
- <item>255</item>
- <item>hows</item>
- <item>how\'s</item>
-
- <item>255</item>
- <item>howve</item>
- <item>how\'ve</item>
-
- <item>255</item>
- <item>hum</item>
- <item>him</item>
-
- <item>255</item>
- <item>i</item>
- <item>I</item>
-
- <item>255</item>
- <item>ifs</item>
- <item>its</item>
-
- <item>255</item>
- <item>il</item>
- <item>I\'ll</item>
-
- <item>255</item>
- <item>im</item>
- <item>I\'m</item>
-
- <item>255</item>
- <item>inteh</item>
- <item>in the</item>
-
- <item>255</item>
- <item>itd</item>
- <item>it\'d</item>
-
- <item>255</item>
- <item>itsa</item>
- <item>it\'s a</item>
-
- <item>255</item>
- <item>lets</item>
- <item>let\'s</item>
-
- <item>255</item>
- <item>maam</item>
- <item>ma\'am</item>
-
- <item>255</item>
- <item>manu</item>
- <item>many</item>
-
- <item>255</item>
- <item>mare</item>
- <item>made</item>
-
- <item>255</item>
- <item>mew</item>
- <item>new</item>
-
- <item>255</item>
- <item>mire</item>
- <item>more</item>
-
- <item>255</item>
- <item>moat</item>
- <item>most</item>
-
- <item>255</item>
- <item>mot</item>
- <item>not</item>
-
- <item>255</item>
- <item>mote</item>
- <item>note</item>
-
- <item>255</item>
- <item>motes</item>
- <item>notes</item>
-
- <item>255</item>
- <item>mow</item>
- <item>now</item>
-
- <item>255</item>
- <item>namer</item>
- <item>named</item>
-
- <item>255</item>
- <item>nave</item>
- <item>have</item>
-
- <item>255</item>
- <item>nee</item>
- <item>new</item>
-
- <item>255</item>
- <item>nigh</item>
- <item>high</item>
-
- <item>255</item>
- <item>nit</item>
- <item>not</item>
-
- <item>255</item>
- <item>oft</item>
- <item>off</item>
-
- <item>255</item>
- <item>os</item>
- <item>is</item>
-
- <item>255</item>
- <item>pater</item>
- <item>later</item>
-
- <item>255</item>
- <item>rook</item>
- <item>took</item>
-
- <item>255</item>
- <item>shel</item>
- <item>she\'ll</item>
-
- <item>255</item>
- <item>shouldent</item>
- <item>shouldn\'t</item>
-
- <item>255</item>
- <item>sill</item>
- <item>will</item>
-
- <item>255</item>
- <item>sown</item>
- <item>down</item>
-
- <item>255</item>
- <item>thatd</item>
- <item>that\'d</item>
-
- <item>255</item>
- <item>tine</item>
- <item>time</item>
-
- <item>255</item>
- <item>thong</item>
- <item>thing</item>
-
- <item>255</item>
- <item>tome</item>
- <item>time</item>
-
- <!-- through additional proximity, 'uf' becomes 'of'. 'o' is not next to 'u' so anyone
- typing 'uf' probably meant 'if', but 'of' is much more common and should be left
- higher than 'if', hence the need for this entry. -->
- <item>255</item>
- <item>uf</item>
- <item>if</item>
-
- <!-- 'un' becomes 'UN' because of perfect match ; even if we remove 'UN', then 'un'
- will become 'on' for the same reason as above. So list this here. -->
- <item>255</item>
- <item>un</item>
- <item>in</item>
-
- <!-- does it really make any sense to have the following here? -->
- <item>255</item>
- <item>UnitedStates</item>
- <item>United States</item>
-
- <item>255</item>
- <item>unitedstates</item>
- <item>United States</item>
-
- <item>255</item>
- <item>visavis</item>
- <item>vis-a-vis</item>
-
- <item>255</item>
- <item>wierd</item>
- <item>weird</item>
-
- <item>255</item>
- <item>wel</item>
- <item>we\'ll</item>
-
- <item>255</item>
- <item>wer</item>
- <item>we\'re</item>
-
- <item>255</item>
- <item>whatd</item>
- <item>what\'d</item>
-
- <item>255</item>
- <item>whatm</item>
- <item>what\'m</item>
-
- <item>255</item>
- <item>whatre</item>
- <item>what\'re</item>
-
- <item>255</item>
- <item>whats</item>
- <item>what\'s</item>
-
- <item>255</item>
- <item>whens</item>
- <item>when\'s</item>
-
- <item>255</item>
- <item>whered</item>
- <item>where\'d</item>
-
- <item>255</item>
- <item>wherell</item>
- <item>where\'ll</item>
-
- <item>255</item>
- <item>wheres</item>
- <item>where\'s</item>
-
- <item>255</item>
- <item>wholl</item>
- <item>who\'ll</item>
-
- <item>255</item>
- <item>whove</item>
- <item>who\'ve</item>
-
- <item>255</item>
- <item>whyd</item>
- <item>why\'d</item>
-
- <item>255</item>
- <item>whyll</item>
- <item>why\'ll</item>
-
- <item>255</item>
- <item>whys</item>
- <item>why\'s</item>
-
- <item>255</item>
- <item>whyve</item>
- <item>why\'ve</item>
-
- <item>255</item>
- <item>wont</item>
- <item>won\'t</item>
-
- <item>255</item>
- <item>yall</item>
- <item>y\'all</item>
-
- <item>255</item>
- <item>youd</item>
- <item>you\'d</item>
-
- </string-array>
-</resources>
diff --git a/java/res/values-hi/strings.xml b/java/res/values-hi/strings.xml
index 56642c4..57e9ff4 100644
--- a/java/res/values-hi/strings.xml
+++ b/java/res/values-hi/strings.xml
@@ -72,7 +72,7 @@
<string name="label_to_alpha_key" msgid="4793983863798817523">"कखग"</string>
<string name="label_to_symbol_key" msgid="8516904117128967293">"?१२३"</string>
<string name="label_to_symbol_with_microphone_key" msgid="9035925553010061906">"१२३"</string>
- <string name="label_pause_key" msgid="181098308428035340">"रोकें"</string>
+ <string name="label_pause_key" msgid="181098308428035340">"पॉज़ करें"</string>
<string name="label_wait_key" msgid="6402152600878093134">"प्रतीक्षा करें"</string>
<string name="spoken_use_headphones" msgid="896961781287283493">"ज़ोर से बोली गई पासवर्ड कुंजियां सुनने के लिए हेडसेट प्लग इन करें."</string>
<string name="spoken_current_text_is" msgid="2485723011272583845">"वर्तमान पाठ %s है"</string>
@@ -116,7 +116,7 @@
<string name="hint_add_to_dictionary" msgid="573678656946085380">"सहेजने के लिए पुन: स्पर्श करें"</string>
<string name="has_dictionary" msgid="6071847973466625007">"शब्दकोश उपलब्ध है"</string>
<string name="prefs_enable_log" msgid="6620424505072963557">"उपयोगकर्ता फ़ीडबैक सक्षम करें"</string>
- <string name="prefs_description_log" msgid="5827825607258246003">"उपयोग के आंकड़े और क्रैश रिपोर्ट Google को स्वचालित रूप से भेज कर इस इनपुट पद्धति संपादक को बेहतर बनाने में सहायता करें."</string>
+ <string name="prefs_description_log" msgid="5827825607258246003">"उपयोग के आंकड़े और क्रैश रिपोर्ट Google को अपने आप भेज कर इस इनपुट पद्धति संपादक को बेहतर बनाने में सहायता करें."</string>
<string name="keyboard_layout" msgid="8451164783510487501">"कीबोर्ड थीम"</string>
<string name="subtype_en_GB" msgid="88170601942311355">"अंग्रेज़ी (यूके)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"अंग्रेज़ी (यूएस)"</string>
diff --git a/java/res/values-is/strings.xml b/java/res/values-is/strings.xml
new file mode 100644
index 0000000..8d5b007
--- /dev/null
+++ b/java/res/values-is/strings.xml
@@ -0,0 +1,263 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for aosp_android_keyboard_ime_name (7877134937939182296) -->
+ <skip />
+ <!-- no translation found for english_ime_input_options (3909945612939668554) -->
+ <skip />
+ <!-- no translation found for english_ime_research_log (8492602295696577851) -->
+ <skip />
+ <!-- no translation found for aosp_spell_checker_service_name (6985142605330377819) -->
+ <skip />
+ <!-- no translation found for use_contacts_for_spellchecking_option_title (5374120998125353898) -->
+ <skip />
+ <!-- no translation found for use_contacts_for_spellchecking_option_summary (8754413382543307713) -->
+ <skip />
+ <!-- no translation found for vibrate_on_keypress (5258079494276955460) -->
+ <skip />
+ <!-- no translation found for sound_on_keypress (6093592297198243644) -->
+ <skip />
+ <!-- no translation found for popup_on_keypress (123894815723512944) -->
+ <skip />
+ <!-- no translation found for general_category (1859088467017573195) -->
+ <skip />
+ <!-- no translation found for correction_category (2236750915056607613) -->
+ <skip />
+ <!-- no translation found for misc_category (6894192814868233453) -->
+ <skip />
+ <!-- no translation found for advanced_settings (362895144495591463) -->
+ <skip />
+ <!-- no translation found for advanced_settings_summary (4487980456152830271) -->
+ <skip />
+ <!-- no translation found for include_other_imes_in_language_switch_list (4533689960308565519) -->
+ <skip />
+ <!-- no translation found for include_other_imes_in_language_switch_list_summary (840637129103317635) -->
+ <skip />
+ <!-- no translation found for suppress_language_switch_key (8003788410354806368) -->
+ <skip />
+ <!-- no translation found for key_preview_popup_dismiss_delay (6213164897443068248) -->
+ <skip />
+ <!-- no translation found for key_preview_popup_dismiss_no_delay (2096123151571458064) -->
+ <skip />
+ <!-- no translation found for key_preview_popup_dismiss_default_delay (2166964333903906734) -->
+ <skip />
+ <!-- no translation found for use_contacts_dict (4435317977804180815) -->
+ <skip />
+ <!-- no translation found for use_contacts_dict_summary (6599983334507879959) -->
+ <skip />
+ <!-- no translation found for auto_cap (1719746674854628252) -->
+ <skip />
+ <!-- no translation found for configure_dictionaries_title (4238652338556902049) -->
+ <skip />
+ <!-- no translation found for main_dictionary (4798763781818361168) -->
+ <skip />
+ <!-- no translation found for prefs_show_suggestions (8026799663445531637) -->
+ <skip />
+ <!-- no translation found for prefs_show_suggestions_summary (1583132279498502825) -->
+ <skip />
+ <!-- no translation found for prefs_suggestion_visibility_show_name (3219916594067551303) -->
+ <skip />
+ <!-- no translation found for prefs_suggestion_visibility_show_only_portrait_name (3551821800439659812) -->
+ <skip />
+ <!-- no translation found for prefs_suggestion_visibility_hide_name (6309143926422234673) -->
+ <skip />
+ <!-- no translation found for auto_correction (4979925752001319458) -->
+ <skip />
+ <!-- no translation found for auto_correction_summary (5625751551134658006) -->
+ <skip />
+ <!-- no translation found for auto_correction_threshold_mode_off (8470882665417944026) -->
+ <skip />
+ <!-- no translation found for auto_correction_threshold_mode_modest (8788366690620799097) -->
+ <skip />
+ <!-- no translation found for auto_correction_threshold_mode_aggeressive (3524029103734923819) -->
+ <skip />
+ <!-- no translation found for auto_correction_threshold_mode_very_aggeressive (3386782235540547678) -->
+ <skip />
+ <!-- no translation found for bigram_prediction (5809665643352206540) -->
+ <skip />
+ <!-- no translation found for bigram_prediction_summary (3253961591626441019) -->
+ <skip />
+ <!-- no translation found for gesture_input (3310827802759290774) -->
+ <skip />
+ <!-- no translation found for gesture_input_summary (7019742443455085809) -->
+ <skip />
+ <!-- no translation found for gesture_preview_trail (3802333369335722221) -->
+ <skip />
+ <!-- no translation found for gesture_floating_preview_text (6859416520117939680) -->
+ <skip />
+ <!-- no translation found for gesture_floating_preview_text_summary (3333754126434989709) -->
+ <skip />
+ <!-- no translation found for added_word (8993883354622484372) -->
+ <skip />
+ <string name="label_go_key" msgid="1635148082137219148">"Áfram"</string>
+ <string name="label_next_key" msgid="362972844525672568">"Næsta"</string>
+ <string name="label_previous_key" msgid="1211868118071386787">"Fyrra"</string>
+ <string name="label_done_key" msgid="2441578748772529288">"Lokið"</string>
+ <string name="label_send_key" msgid="2815056534433717444">"Senda"</string>
+ <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
+ <!-- no translation found for label_to_symbol_key (8516904117128967293) -->
+ <skip />
+ <!-- no translation found for label_to_symbol_with_microphone_key (9035925553010061906) -->
+ <skip />
+ <!-- no translation found for label_pause_key (181098308428035340) -->
+ <skip />
+ <!-- no translation found for label_wait_key (6402152600878093134) -->
+ <skip />
+ <!-- no translation found for spoken_use_headphones (896961781287283493) -->
+ <skip />
+ <!-- no translation found for spoken_current_text_is (2485723011272583845) -->
+ <skip />
+ <!-- no translation found for spoken_no_text_entered (7479685225597344496) -->
+ <skip />
+ <!-- no translation found for spoken_description_unknown (3197434010402179157) -->
+ <skip />
+ <!-- no translation found for spoken_description_shift (244197883292549308) -->
+ <skip />
+ <!-- no translation found for spoken_description_shift_shifted (1681877323344195035) -->
+ <skip />
+ <!-- no translation found for spoken_description_caps_lock (3276478269526304432) -->
+ <skip />
+ <!-- no translation found for spoken_description_delete (8740376944276199801) -->
+ <skip />
+ <!-- no translation found for spoken_description_to_symbol (5486340107500448969) -->
+ <skip />
+ <!-- no translation found for spoken_description_to_alpha (23129338819771807) -->
+ <skip />
+ <!-- no translation found for spoken_description_to_numeric (591752092685161732) -->
+ <skip />
+ <!-- no translation found for spoken_description_settings (4627462689603838099) -->
+ <skip />
+ <!-- no translation found for spoken_description_tab (2667716002663482248) -->
+ <skip />
+ <!-- no translation found for spoken_description_space (2582521050049860859) -->
+ <skip />
+ <!-- no translation found for spoken_description_mic (615536748882611950) -->
+ <skip />
+ <!-- no translation found for spoken_description_smiley (2256309826200113918) -->
+ <skip />
+ <!-- no translation found for spoken_description_return (8178083177238315647) -->
+ <skip />
+ <!-- no translation found for spoken_description_search (1247236163755920808) -->
+ <skip />
+ <!-- no translation found for spoken_description_dot (40711082435231673) -->
+ <skip />
+ <!-- no translation found for spoken_description_language_switch (5507091328222331316) -->
+ <skip />
+ <!-- no translation found for spoken_description_action_next (8636078276664150324) -->
+ <skip />
+ <!-- no translation found for spoken_description_action_previous (800872415009336208) -->
+ <skip />
+ <!-- no translation found for spoken_description_shiftmode_on (5700440798609574589) -->
+ <skip />
+ <!-- no translation found for spoken_description_shiftmode_locked (593175803181701830) -->
+ <skip />
+ <!-- no translation found for spoken_description_shiftmode_off (657219998449174808) -->
+ <skip />
+ <!-- no translation found for spoken_description_mode_symbol (7183343879909747642) -->
+ <skip />
+ <!-- no translation found for spoken_description_mode_alpha (3528307674390156956) -->
+ <skip />
+ <!-- no translation found for spoken_description_mode_phone (6520207943132026264) -->
+ <skip />
+ <!-- no translation found for spoken_description_mode_phone_shift (5499629753962641227) -->
+ <skip />
+ <!-- no translation found for voice_input (3583258583521397548) -->
+ <skip />
+ <!-- no translation found for voice_input_modes_main_keyboard (3360660341121083174) -->
+ <skip />
+ <!-- no translation found for voice_input_modes_symbols_keyboard (7203213240786084067) -->
+ <skip />
+ <!-- no translation found for voice_input_modes_off (3745699748218082014) -->
+ <skip />
+ <!-- no translation found for voice_input_modes_summary_main_keyboard (6586544292900314339) -->
+ <skip />
+ <!-- no translation found for voice_input_modes_summary_symbols_keyboard (5233725927281932391) -->
+ <skip />
+ <!-- no translation found for voice_input_modes_summary_off (63875609591897607) -->
+ <skip />
+ <!-- no translation found for configure_input_method (373356270290742459) -->
+ <skip />
+ <!-- no translation found for language_selection_title (1651299598555326750) -->
+ <skip />
+ <!-- no translation found for select_language (3693815588777926848) -->
+ <skip />
+ <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+ <skip />
+ <!-- no translation found for has_dictionary (6071847973466625007) -->
+ <skip />
+ <!-- no translation found for prefs_enable_log (6620424505072963557) -->
+ <skip />
+ <!-- no translation found for prefs_description_log (5827825607258246003) -->
+ <skip />
+ <!-- no translation found for keyboard_layout (8451164783510487501) -->
+ <skip />
+ <!-- no translation found for subtype_en_GB (88170601942311355) -->
+ <skip />
+ <!-- no translation found for subtype_en_US (6160452336634534239) -->
+ <skip />
+ <!-- no translation found for subtype_with_layout_en_GB (2179097748724725906) -->
+ <skip />
+ <!-- no translation found for subtype_with_layout_en_US (1362581347576714579) -->
+ <skip />
+ <!-- no translation found for subtype_no_language (141420857808801746) -->
+ <skip />
+ <!-- no translation found for subtype_no_language_qwerty (2956121451616633133) -->
+ <skip />
+ <!-- no translation found for subtype_no_language_qwertz (1177848172397202890) -->
+ <skip />
+ <!-- no translation found for subtype_no_language_azerty (8721460968141187394) -->
+ <skip />
+ <!-- no translation found for subtype_no_language_dvorak (3122976737669823935) -->
+ <skip />
+ <!-- no translation found for subtype_no_language_colemak (4205992994906097244) -->
+ <skip />
+ <!-- no translation found for subtype_no_language_pcqwerty (8840928374394180189) -->
+ <skip />
+ <!-- no translation found for custom_input_styles_title (8429952441821251512) -->
+ <skip />
+ <!-- no translation found for add_style (6163126614514489951) -->
+ <skip />
+ <!-- no translation found for add (8299699805688017798) -->
+ <skip />
+ <!-- no translation found for remove (4486081658752944606) -->
+ <skip />
+ <!-- no translation found for save (7646738597196767214) -->
+ <skip />
+ <!-- no translation found for subtype_locale (8576443440738143764) -->
+ <skip />
+ <!-- no translation found for keyboard_layout_set (4309233698194565609) -->
+ <skip />
+ <!-- no translation found for custom_input_style_note_message (8826731320846363423) -->
+ <skip />
+ <!-- no translation found for enable (5031294444630523247) -->
+ <skip />
+ <!-- no translation found for not_now (6172462888202790482) -->
+ <skip />
+ <!-- no translation found for custom_input_style_already_exists (8008728952215449707) -->
+ <skip />
+ <!-- no translation found for prefs_usability_study_mode (1261130555134595254) -->
+ <skip />
+ <!-- no translation found for prefs_keypress_vibration_duration_settings (1829950405285211668) -->
+ <skip />
+ <!-- no translation found for prefs_keypress_sound_volume_settings (5875933757082305040) -->
+ <skip />
+</resources>
diff --git a/java/res/values-ka/strings.xml b/java/res/values-ka/strings.xml
new file mode 100644
index 0000000..fcb666d
--- /dev/null
+++ b/java/res/values-ka/strings.xml
@@ -0,0 +1,263 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for aosp_android_keyboard_ime_name (7877134937939182296) -->
+ <skip />
+ <!-- no translation found for english_ime_input_options (3909945612939668554) -->
+ <skip />
+ <!-- no translation found for english_ime_research_log (8492602295696577851) -->
+ <skip />
+ <!-- no translation found for aosp_spell_checker_service_name (6985142605330377819) -->
+ <skip />
+ <!-- no translation found for use_contacts_for_spellchecking_option_title (5374120998125353898) -->
+ <skip />
+ <!-- no translation found for use_contacts_for_spellchecking_option_summary (8754413382543307713) -->
+ <skip />
+ <!-- no translation found for vibrate_on_keypress (5258079494276955460) -->
+ <skip />
+ <!-- no translation found for sound_on_keypress (6093592297198243644) -->
+ <skip />
+ <!-- no translation found for popup_on_keypress (123894815723512944) -->
+ <skip />
+ <!-- no translation found for general_category (1859088467017573195) -->
+ <skip />
+ <!-- no translation found for correction_category (2236750915056607613) -->
+ <skip />
+ <!-- no translation found for misc_category (6894192814868233453) -->
+ <skip />
+ <!-- no translation found for advanced_settings (362895144495591463) -->
+ <skip />
+ <!-- no translation found for advanced_settings_summary (4487980456152830271) -->
+ <skip />
+ <!-- no translation found for include_other_imes_in_language_switch_list (4533689960308565519) -->
+ <skip />
+ <!-- no translation found for include_other_imes_in_language_switch_list_summary (840637129103317635) -->
+ <skip />
+ <!-- no translation found for suppress_language_switch_key (8003788410354806368) -->
+ <skip />
+ <!-- no translation found for key_preview_popup_dismiss_delay (6213164897443068248) -->
+ <skip />
+ <!-- no translation found for key_preview_popup_dismiss_no_delay (2096123151571458064) -->
+ <skip />
+ <!-- no translation found for key_preview_popup_dismiss_default_delay (2166964333903906734) -->
+ <skip />
+ <!-- no translation found for use_contacts_dict (4435317977804180815) -->
+ <skip />
+ <!-- no translation found for use_contacts_dict_summary (6599983334507879959) -->
+ <skip />
+ <!-- no translation found for auto_cap (1719746674854628252) -->
+ <skip />
+ <!-- no translation found for configure_dictionaries_title (4238652338556902049) -->
+ <skip />
+ <!-- no translation found for main_dictionary (4798763781818361168) -->
+ <skip />
+ <!-- no translation found for prefs_show_suggestions (8026799663445531637) -->
+ <skip />
+ <!-- no translation found for prefs_show_suggestions_summary (1583132279498502825) -->
+ <skip />
+ <!-- no translation found for prefs_suggestion_visibility_show_name (3219916594067551303) -->
+ <skip />
+ <!-- no translation found for prefs_suggestion_visibility_show_only_portrait_name (3551821800439659812) -->
+ <skip />
+ <!-- no translation found for prefs_suggestion_visibility_hide_name (6309143926422234673) -->
+ <skip />
+ <!-- no translation found for auto_correction (4979925752001319458) -->
+ <skip />
+ <!-- no translation found for auto_correction_summary (5625751551134658006) -->
+ <skip />
+ <!-- no translation found for auto_correction_threshold_mode_off (8470882665417944026) -->
+ <skip />
+ <!-- no translation found for auto_correction_threshold_mode_modest (8788366690620799097) -->
+ <skip />
+ <!-- no translation found for auto_correction_threshold_mode_aggeressive (3524029103734923819) -->
+ <skip />
+ <!-- no translation found for auto_correction_threshold_mode_very_aggeressive (3386782235540547678) -->
+ <skip />
+ <!-- no translation found for bigram_prediction (5809665643352206540) -->
+ <skip />
+ <!-- no translation found for bigram_prediction_summary (3253961591626441019) -->
+ <skip />
+ <!-- no translation found for gesture_input (3310827802759290774) -->
+ <skip />
+ <!-- no translation found for gesture_input_summary (7019742443455085809) -->
+ <skip />
+ <!-- no translation found for gesture_preview_trail (3802333369335722221) -->
+ <skip />
+ <!-- no translation found for gesture_floating_preview_text (6859416520117939680) -->
+ <skip />
+ <!-- no translation found for gesture_floating_preview_text_summary (3333754126434989709) -->
+ <skip />
+ <!-- no translation found for added_word (8993883354622484372) -->
+ <skip />
+ <string name="label_go_key" msgid="1635148082137219148">"გადასვლა"</string>
+ <string name="label_next_key" msgid="362972844525672568">"შემდეგი"</string>
+ <string name="label_previous_key" msgid="1211868118071386787">"წინა"</string>
+ <string name="label_done_key" msgid="2441578748772529288">"შესრულებულია"</string>
+ <string name="label_send_key" msgid="2815056534433717444">"გაგზავნა"</string>
+ <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
+ <!-- no translation found for label_to_symbol_key (8516904117128967293) -->
+ <skip />
+ <!-- no translation found for label_to_symbol_with_microphone_key (9035925553010061906) -->
+ <skip />
+ <!-- no translation found for label_pause_key (181098308428035340) -->
+ <skip />
+ <!-- no translation found for label_wait_key (6402152600878093134) -->
+ <skip />
+ <!-- no translation found for spoken_use_headphones (896961781287283493) -->
+ <skip />
+ <!-- no translation found for spoken_current_text_is (2485723011272583845) -->
+ <skip />
+ <!-- no translation found for spoken_no_text_entered (7479685225597344496) -->
+ <skip />
+ <!-- no translation found for spoken_description_unknown (3197434010402179157) -->
+ <skip />
+ <!-- no translation found for spoken_description_shift (244197883292549308) -->
+ <skip />
+ <!-- no translation found for spoken_description_shift_shifted (1681877323344195035) -->
+ <skip />
+ <!-- no translation found for spoken_description_caps_lock (3276478269526304432) -->
+ <skip />
+ <!-- no translation found for spoken_description_delete (8740376944276199801) -->
+ <skip />
+ <!-- no translation found for spoken_description_to_symbol (5486340107500448969) -->
+ <skip />
+ <!-- no translation found for spoken_description_to_alpha (23129338819771807) -->
+ <skip />
+ <!-- no translation found for spoken_description_to_numeric (591752092685161732) -->
+ <skip />
+ <!-- no translation found for spoken_description_settings (4627462689603838099) -->
+ <skip />
+ <!-- no translation found for spoken_description_tab (2667716002663482248) -->
+ <skip />
+ <!-- no translation found for spoken_description_space (2582521050049860859) -->
+ <skip />
+ <!-- no translation found for spoken_description_mic (615536748882611950) -->
+ <skip />
+ <!-- no translation found for spoken_description_smiley (2256309826200113918) -->
+ <skip />
+ <!-- no translation found for spoken_description_return (8178083177238315647) -->
+ <skip />
+ <!-- no translation found for spoken_description_search (1247236163755920808) -->
+ <skip />
+ <!-- no translation found for spoken_description_dot (40711082435231673) -->
+ <skip />
+ <!-- no translation found for spoken_description_language_switch (5507091328222331316) -->
+ <skip />
+ <!-- no translation found for spoken_description_action_next (8636078276664150324) -->
+ <skip />
+ <!-- no translation found for spoken_description_action_previous (800872415009336208) -->
+ <skip />
+ <!-- no translation found for spoken_description_shiftmode_on (5700440798609574589) -->
+ <skip />
+ <!-- no translation found for spoken_description_shiftmode_locked (593175803181701830) -->
+ <skip />
+ <!-- no translation found for spoken_description_shiftmode_off (657219998449174808) -->
+ <skip />
+ <!-- no translation found for spoken_description_mode_symbol (7183343879909747642) -->
+ <skip />
+ <!-- no translation found for spoken_description_mode_alpha (3528307674390156956) -->
+ <skip />
+ <!-- no translation found for spoken_description_mode_phone (6520207943132026264) -->
+ <skip />
+ <!-- no translation found for spoken_description_mode_phone_shift (5499629753962641227) -->
+ <skip />
+ <!-- no translation found for voice_input (3583258583521397548) -->
+ <skip />
+ <!-- no translation found for voice_input_modes_main_keyboard (3360660341121083174) -->
+ <skip />
+ <!-- no translation found for voice_input_modes_symbols_keyboard (7203213240786084067) -->
+ <skip />
+ <!-- no translation found for voice_input_modes_off (3745699748218082014) -->
+ <skip />
+ <!-- no translation found for voice_input_modes_summary_main_keyboard (6586544292900314339) -->
+ <skip />
+ <!-- no translation found for voice_input_modes_summary_symbols_keyboard (5233725927281932391) -->
+ <skip />
+ <!-- no translation found for voice_input_modes_summary_off (63875609591897607) -->
+ <skip />
+ <!-- no translation found for configure_input_method (373356270290742459) -->
+ <skip />
+ <!-- no translation found for language_selection_title (1651299598555326750) -->
+ <skip />
+ <!-- no translation found for select_language (3693815588777926848) -->
+ <skip />
+ <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+ <skip />
+ <!-- no translation found for has_dictionary (6071847973466625007) -->
+ <skip />
+ <!-- no translation found for prefs_enable_log (6620424505072963557) -->
+ <skip />
+ <!-- no translation found for prefs_description_log (5827825607258246003) -->
+ <skip />
+ <!-- no translation found for keyboard_layout (8451164783510487501) -->
+ <skip />
+ <!-- no translation found for subtype_en_GB (88170601942311355) -->
+ <skip />
+ <!-- no translation found for subtype_en_US (6160452336634534239) -->
+ <skip />
+ <!-- no translation found for subtype_with_layout_en_GB (2179097748724725906) -->
+ <skip />
+ <!-- no translation found for subtype_with_layout_en_US (1362581347576714579) -->
+ <skip />
+ <!-- no translation found for subtype_no_language (141420857808801746) -->
+ <skip />
+ <!-- no translation found for subtype_no_language_qwerty (2956121451616633133) -->
+ <skip />
+ <!-- no translation found for subtype_no_language_qwertz (1177848172397202890) -->
+ <skip />
+ <!-- no translation found for subtype_no_language_azerty (8721460968141187394) -->
+ <skip />
+ <!-- no translation found for subtype_no_language_dvorak (3122976737669823935) -->
+ <skip />
+ <!-- no translation found for subtype_no_language_colemak (4205992994906097244) -->
+ <skip />
+ <!-- no translation found for subtype_no_language_pcqwerty (8840928374394180189) -->
+ <skip />
+ <!-- no translation found for custom_input_styles_title (8429952441821251512) -->
+ <skip />
+ <!-- no translation found for add_style (6163126614514489951) -->
+ <skip />
+ <!-- no translation found for add (8299699805688017798) -->
+ <skip />
+ <!-- no translation found for remove (4486081658752944606) -->
+ <skip />
+ <!-- no translation found for save (7646738597196767214) -->
+ <skip />
+ <!-- no translation found for subtype_locale (8576443440738143764) -->
+ <skip />
+ <!-- no translation found for keyboard_layout_set (4309233698194565609) -->
+ <skip />
+ <!-- no translation found for custom_input_style_note_message (8826731320846363423) -->
+ <skip />
+ <!-- no translation found for enable (5031294444630523247) -->
+ <skip />
+ <!-- no translation found for not_now (6172462888202790482) -->
+ <skip />
+ <!-- no translation found for custom_input_style_already_exists (8008728952215449707) -->
+ <skip />
+ <!-- no translation found for prefs_usability_study_mode (1261130555134595254) -->
+ <skip />
+ <!-- no translation found for prefs_keypress_vibration_duration_settings (1829950405285211668) -->
+ <skip />
+ <!-- no translation found for prefs_keypress_sound_volume_settings (5875933757082305040) -->
+ <skip />
+</resources>
diff --git a/java/res/values-land/dimens.xml b/java/res/values-land/dimens.xml
index 6259725..a1546f1 100644
--- a/java/res/values-land/dimens.xml
+++ b/java/res/values-land/dimens.xml
@@ -55,6 +55,11 @@
<fraction name="spacebar_text_ratio">40.000%</fraction>
<dimen name="key_preview_offset">0.0dp</dimen>
+ <!-- For 5-row keyboard -->
+ <fraction name="key_bottom_gap_5row">3.20%p</fraction>
+ <fraction name="key_letter_ratio_5row">78%</fraction>
+ <fraction name="key_uppercase_letter_ratio_5row">48%</fraction>
+
<dimen name="key_preview_offset_ics">1.6dp</dimen>
<!-- popup_key_height x -0.5 -->
<dimen name="more_keys_keyboard_vertical_correction_ics">-22.4dp</dimen>
diff --git a/java/res/values-mk/strings.xml b/java/res/values-mk/strings.xml
new file mode 100644
index 0000000..7f293e4
--- /dev/null
+++ b/java/res/values-mk/strings.xml
@@ -0,0 +1,263 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for aosp_android_keyboard_ime_name (7877134937939182296) -->
+ <skip />
+ <!-- no translation found for english_ime_input_options (3909945612939668554) -->
+ <skip />
+ <!-- no translation found for english_ime_research_log (8492602295696577851) -->
+ <skip />
+ <!-- no translation found for aosp_spell_checker_service_name (6985142605330377819) -->
+ <skip />
+ <!-- no translation found for use_contacts_for_spellchecking_option_title (5374120998125353898) -->
+ <skip />
+ <!-- no translation found for use_contacts_for_spellchecking_option_summary (8754413382543307713) -->
+ <skip />
+ <!-- no translation found for vibrate_on_keypress (5258079494276955460) -->
+ <skip />
+ <!-- no translation found for sound_on_keypress (6093592297198243644) -->
+ <skip />
+ <!-- no translation found for popup_on_keypress (123894815723512944) -->
+ <skip />
+ <!-- no translation found for general_category (1859088467017573195) -->
+ <skip />
+ <!-- no translation found for correction_category (2236750915056607613) -->
+ <skip />
+ <!-- no translation found for misc_category (6894192814868233453) -->
+ <skip />
+ <!-- no translation found for advanced_settings (362895144495591463) -->
+ <skip />
+ <!-- no translation found for advanced_settings_summary (4487980456152830271) -->
+ <skip />
+ <!-- no translation found for include_other_imes_in_language_switch_list (4533689960308565519) -->
+ <skip />
+ <!-- no translation found for include_other_imes_in_language_switch_list_summary (840637129103317635) -->
+ <skip />
+ <!-- no translation found for suppress_language_switch_key (8003788410354806368) -->
+ <skip />
+ <!-- no translation found for key_preview_popup_dismiss_delay (6213164897443068248) -->
+ <skip />
+ <!-- no translation found for key_preview_popup_dismiss_no_delay (2096123151571458064) -->
+ <skip />
+ <!-- no translation found for key_preview_popup_dismiss_default_delay (2166964333903906734) -->
+ <skip />
+ <!-- no translation found for use_contacts_dict (4435317977804180815) -->
+ <skip />
+ <!-- no translation found for use_contacts_dict_summary (6599983334507879959) -->
+ <skip />
+ <!-- no translation found for auto_cap (1719746674854628252) -->
+ <skip />
+ <!-- no translation found for configure_dictionaries_title (4238652338556902049) -->
+ <skip />
+ <!-- no translation found for main_dictionary (4798763781818361168) -->
+ <skip />
+ <!-- no translation found for prefs_show_suggestions (8026799663445531637) -->
+ <skip />
+ <!-- no translation found for prefs_show_suggestions_summary (1583132279498502825) -->
+ <skip />
+ <!-- no translation found for prefs_suggestion_visibility_show_name (3219916594067551303) -->
+ <skip />
+ <!-- no translation found for prefs_suggestion_visibility_show_only_portrait_name (3551821800439659812) -->
+ <skip />
+ <!-- no translation found for prefs_suggestion_visibility_hide_name (6309143926422234673) -->
+ <skip />
+ <!-- no translation found for auto_correction (4979925752001319458) -->
+ <skip />
+ <!-- no translation found for auto_correction_summary (5625751551134658006) -->
+ <skip />
+ <!-- no translation found for auto_correction_threshold_mode_off (8470882665417944026) -->
+ <skip />
+ <!-- no translation found for auto_correction_threshold_mode_modest (8788366690620799097) -->
+ <skip />
+ <!-- no translation found for auto_correction_threshold_mode_aggeressive (3524029103734923819) -->
+ <skip />
+ <!-- no translation found for auto_correction_threshold_mode_very_aggeressive (3386782235540547678) -->
+ <skip />
+ <!-- no translation found for bigram_prediction (5809665643352206540) -->
+ <skip />
+ <!-- no translation found for bigram_prediction_summary (3253961591626441019) -->
+ <skip />
+ <!-- no translation found for gesture_input (3310827802759290774) -->
+ <skip />
+ <!-- no translation found for gesture_input_summary (7019742443455085809) -->
+ <skip />
+ <!-- no translation found for gesture_preview_trail (3802333369335722221) -->
+ <skip />
+ <!-- no translation found for gesture_floating_preview_text (6859416520117939680) -->
+ <skip />
+ <!-- no translation found for gesture_floating_preview_text_summary (3333754126434989709) -->
+ <skip />
+ <!-- no translation found for added_word (8993883354622484372) -->
+ <skip />
+ <string name="label_go_key" msgid="1635148082137219148">"Оди"</string>
+ <string name="label_next_key" msgid="362972844525672568">"Следно"</string>
+ <string name="label_previous_key" msgid="1211868118071386787">"Претходно"</string>
+ <string name="label_done_key" msgid="2441578748772529288">"Готово"</string>
+ <string name="label_send_key" msgid="2815056534433717444">"Испрати"</string>
+ <string name="label_to_alpha_key" msgid="4793983863798817523">"АБВ"</string>
+ <!-- no translation found for label_to_symbol_key (8516904117128967293) -->
+ <skip />
+ <!-- no translation found for label_to_symbol_with_microphone_key (9035925553010061906) -->
+ <skip />
+ <!-- no translation found for label_pause_key (181098308428035340) -->
+ <skip />
+ <!-- no translation found for label_wait_key (6402152600878093134) -->
+ <skip />
+ <!-- no translation found for spoken_use_headphones (896961781287283493) -->
+ <skip />
+ <!-- no translation found for spoken_current_text_is (2485723011272583845) -->
+ <skip />
+ <!-- no translation found for spoken_no_text_entered (7479685225597344496) -->
+ <skip />
+ <!-- no translation found for spoken_description_unknown (3197434010402179157) -->
+ <skip />
+ <!-- no translation found for spoken_description_shift (244197883292549308) -->
+ <skip />
+ <!-- no translation found for spoken_description_shift_shifted (1681877323344195035) -->
+ <skip />
+ <!-- no translation found for spoken_description_caps_lock (3276478269526304432) -->
+ <skip />
+ <!-- no translation found for spoken_description_delete (8740376944276199801) -->
+ <skip />
+ <!-- no translation found for spoken_description_to_symbol (5486340107500448969) -->
+ <skip />
+ <!-- no translation found for spoken_description_to_alpha (23129338819771807) -->
+ <skip />
+ <!-- no translation found for spoken_description_to_numeric (591752092685161732) -->
+ <skip />
+ <!-- no translation found for spoken_description_settings (4627462689603838099) -->
+ <skip />
+ <!-- no translation found for spoken_description_tab (2667716002663482248) -->
+ <skip />
+ <!-- no translation found for spoken_description_space (2582521050049860859) -->
+ <skip />
+ <!-- no translation found for spoken_description_mic (615536748882611950) -->
+ <skip />
+ <!-- no translation found for spoken_description_smiley (2256309826200113918) -->
+ <skip />
+ <!-- no translation found for spoken_description_return (8178083177238315647) -->
+ <skip />
+ <!-- no translation found for spoken_description_search (1247236163755920808) -->
+ <skip />
+ <!-- no translation found for spoken_description_dot (40711082435231673) -->
+ <skip />
+ <!-- no translation found for spoken_description_language_switch (5507091328222331316) -->
+ <skip />
+ <!-- no translation found for spoken_description_action_next (8636078276664150324) -->
+ <skip />
+ <!-- no translation found for spoken_description_action_previous (800872415009336208) -->
+ <skip />
+ <!-- no translation found for spoken_description_shiftmode_on (5700440798609574589) -->
+ <skip />
+ <!-- no translation found for spoken_description_shiftmode_locked (593175803181701830) -->
+ <skip />
+ <!-- no translation found for spoken_description_shiftmode_off (657219998449174808) -->
+ <skip />
+ <!-- no translation found for spoken_description_mode_symbol (7183343879909747642) -->
+ <skip />
+ <!-- no translation found for spoken_description_mode_alpha (3528307674390156956) -->
+ <skip />
+ <!-- no translation found for spoken_description_mode_phone (6520207943132026264) -->
+ <skip />
+ <!-- no translation found for spoken_description_mode_phone_shift (5499629753962641227) -->
+ <skip />
+ <!-- no translation found for voice_input (3583258583521397548) -->
+ <skip />
+ <!-- no translation found for voice_input_modes_main_keyboard (3360660341121083174) -->
+ <skip />
+ <!-- no translation found for voice_input_modes_symbols_keyboard (7203213240786084067) -->
+ <skip />
+ <!-- no translation found for voice_input_modes_off (3745699748218082014) -->
+ <skip />
+ <!-- no translation found for voice_input_modes_summary_main_keyboard (6586544292900314339) -->
+ <skip />
+ <!-- no translation found for voice_input_modes_summary_symbols_keyboard (5233725927281932391) -->
+ <skip />
+ <!-- no translation found for voice_input_modes_summary_off (63875609591897607) -->
+ <skip />
+ <!-- no translation found for configure_input_method (373356270290742459) -->
+ <skip />
+ <!-- no translation found for language_selection_title (1651299598555326750) -->
+ <skip />
+ <!-- no translation found for select_language (3693815588777926848) -->
+ <skip />
+ <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+ <skip />
+ <!-- no translation found for has_dictionary (6071847973466625007) -->
+ <skip />
+ <!-- no translation found for prefs_enable_log (6620424505072963557) -->
+ <skip />
+ <!-- no translation found for prefs_description_log (5827825607258246003) -->
+ <skip />
+ <!-- no translation found for keyboard_layout (8451164783510487501) -->
+ <skip />
+ <!-- no translation found for subtype_en_GB (88170601942311355) -->
+ <skip />
+ <!-- no translation found for subtype_en_US (6160452336634534239) -->
+ <skip />
+ <!-- no translation found for subtype_with_layout_en_GB (2179097748724725906) -->
+ <skip />
+ <!-- no translation found for subtype_with_layout_en_US (1362581347576714579) -->
+ <skip />
+ <!-- no translation found for subtype_no_language (141420857808801746) -->
+ <skip />
+ <!-- no translation found for subtype_no_language_qwerty (2956121451616633133) -->
+ <skip />
+ <!-- no translation found for subtype_no_language_qwertz (1177848172397202890) -->
+ <skip />
+ <!-- no translation found for subtype_no_language_azerty (8721460968141187394) -->
+ <skip />
+ <!-- no translation found for subtype_no_language_dvorak (3122976737669823935) -->
+ <skip />
+ <!-- no translation found for subtype_no_language_colemak (4205992994906097244) -->
+ <skip />
+ <!-- no translation found for subtype_no_language_pcqwerty (8840928374394180189) -->
+ <skip />
+ <!-- no translation found for custom_input_styles_title (8429952441821251512) -->
+ <skip />
+ <!-- no translation found for add_style (6163126614514489951) -->
+ <skip />
+ <!-- no translation found for add (8299699805688017798) -->
+ <skip />
+ <!-- no translation found for remove (4486081658752944606) -->
+ <skip />
+ <!-- no translation found for save (7646738597196767214) -->
+ <skip />
+ <!-- no translation found for subtype_locale (8576443440738143764) -->
+ <skip />
+ <!-- no translation found for keyboard_layout_set (4309233698194565609) -->
+ <skip />
+ <!-- no translation found for custom_input_style_note_message (8826731320846363423) -->
+ <skip />
+ <!-- no translation found for enable (5031294444630523247) -->
+ <skip />
+ <!-- no translation found for not_now (6172462888202790482) -->
+ <skip />
+ <!-- no translation found for custom_input_style_already_exists (8008728952215449707) -->
+ <skip />
+ <!-- no translation found for prefs_usability_study_mode (1261130555134595254) -->
+ <skip />
+ <!-- no translation found for prefs_keypress_vibration_duration_settings (1829950405285211668) -->
+ <skip />
+ <!-- no translation found for prefs_keypress_sound_volume_settings (5875933757082305040) -->
+ <skip />
+</resources>
diff --git a/java/res/values-sw600dp-land/dimens.xml b/java/res/values-sw600dp-land/dimens.xml
index a478df8..9664bf9 100644
--- a/java/res/values-sw600dp-land/dimens.xml
+++ b/java/res/values-sw600dp-land/dimens.xml
@@ -53,6 +53,11 @@
<fraction name="spacebar_text_ratio">30.0%</fraction>
<dimen name="key_uppercase_letter_padding">4dp</dimen>
+ <!-- For 5-row keyboard -->
+ <fraction name="key_bottom_gap_5row">3.20%p</fraction>
+ <fraction name="key_letter_ratio_5row">62%</fraction>
+ <fraction name="key_uppercase_letter_ratio_5row">36%</fraction>
+
<dimen name="suggestions_strip_padding">252.0dp</dimen>
<integer name="max_more_suggestions_row">5</integer>
<fraction name="min_more_suggestions_width">50%</fraction>
diff --git a/java/res/values-sw600dp/dimens.xml b/java/res/values-sw600dp/dimens.xml
index 5596ba4..e608f7d 100644
--- a/java/res/values-sw600dp/dimens.xml
+++ b/java/res/values-sw600dp/dimens.xml
@@ -66,6 +66,11 @@
<dimen name="key_preview_height">94.5dp</dimen>
<dimen name="key_preview_offset">16.0dp</dimen>
+ <!-- For 5-row keyboard -->
+ <fraction name="key_bottom_gap_5row">3.20%p</fraction>
+ <fraction name="key_letter_ratio_5row">52%</fraction>
+ <fraction name="key_uppercase_letter_ratio_5row">27%</fraction>
+
<dimen name="key_preview_offset_ics">8.0dp</dimen>
<!-- popup_key_height x -0.5 -->
<dimen name="more_keys_keyboard_vertical_correction_ics">-31.5dp</dimen>
diff --git a/java/res/values-sw768dp-land/dimens.xml b/java/res/values-sw768dp-land/dimens.xml
index b95c858..5112170 100644
--- a/java/res/values-sw768dp-land/dimens.xml
+++ b/java/res/values-sw768dp-land/dimens.xml
@@ -55,6 +55,11 @@
<fraction name="spacebar_text_ratio">24.00%</fraction>
<dimen name="key_preview_height">107.1dp</dimen>
+ <!-- For 5-row keyboard -->
+ <fraction name="key_bottom_gap_5row">2.65%p</fraction>
+ <fraction name="key_letter_ratio_5row">53%</fraction>
+ <fraction name="key_uppercase_letter_ratio_5row">30%</fraction>
+
<dimen name="key_preview_offset_ics">8.0dp</dimen>
<dimen name="suggestions_strip_padding">252.0dp</dimen>
diff --git a/java/res/values-sw768dp/dimens.xml b/java/res/values-sw768dp/dimens.xml
index ce33b73..ec9d759 100644
--- a/java/res/values-sw768dp/dimens.xml
+++ b/java/res/values-sw768dp/dimens.xml
@@ -67,6 +67,11 @@
<dimen name="key_preview_height">94.5dp</dimen>
<dimen name="key_preview_offset">16.0dp</dimen>
+ <!-- For 5-row keyboard -->
+ <fraction name="key_bottom_gap_5row">2.95%p</fraction>
+ <fraction name="key_letter_ratio_5row">51%</fraction>
+ <fraction name="key_uppercase_letter_ratio_5row">33%</fraction>
+
<dimen name="key_preview_offset_ics">8.0dp</dimen>
<!-- popup_key_height x -0.5 -->
<dimen name="more_keys_keyboard_vertical_correction_ics">-31.5dp</dimen>
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index 4975d65..c8f6435 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -41,26 +41,6 @@
checkable+checked+pressed. -->
<attr name="keyBackground" format="reference" />
- <!-- Size of the text for one letter keys. If not defined, keyLetterRatio takes effect. -->
- <attr name="keyLetterSize" format="dimension" />
- <!-- Size of the text for keys with multiple letters. If not defined, keyLabelRatio takes
- effect. -->
- <attr name="keyLabelSize" format="dimension" />
- <!-- Size of the text for one letter keys, in the proportion of key height. -->
- <attr name="keyLetterRatio" format="float" />
- <!-- Large size of the text for one letter keys, in the proportion of key height. -->
- <attr name="keyLargeLetterRatio" format="float" />
- <!-- Size of the text for keys with multiple letters, in the proportion of key height. -->
- <attr name="keyLabelRatio" format="float" />
- <!-- Large size of the text for keys with multiple letters, in the proportion of key height. -->
- <attr name="keyLargeLabelRatio" format="float" />
- <!-- Size of the text for hint letter (= one character hint label), in the proportion of
- key height. -->
- <attr name="keyHintLetterRatio" format="float" />
- <!-- Size of the text for hint label, in the proportion of key height. -->
- <attr name="keyHintLabelRatio" format="float" />
- <!-- Size of the text for shifted letter hint, in the proportion of key height. -->
- <attr name="keyShiftedLetterHintRatio" format="float" />
<!-- Horizontal padding of left/right aligned key label to the edge of the key. -->
<attr name="keyLabelHorizontalPadding" format="dimension" />
<!-- Right padding of hint letter to the edge of the key.-->
@@ -69,18 +49,8 @@
<attr name="keyPopupHintLetterPadding" format="dimension" />
<!-- Right padding of shifted letter hint to the edge of the key.-->
<attr name="keyShiftedLetterHintPadding" format="dimension" />
-
- <!-- Color to use for the label in a key. -->
- <attr name="keyTextColor" format="color" />
- <!-- Color to use for the label in a key when in inactivated state. -->
- <attr name="keyTextInactivatedColor" format="color" />
- <!-- Key hint letter (= one character hint label) color -->
- <attr name="keyHintLetterColor" format="color" />
- <!-- Key hint label color -->
- <attr name="keyHintLabelColor" format="color" />
- <!-- Shifted letter hint colors -->
- <attr name="keyShiftedLetterHintInactivatedColor" format="color" />
- <attr name="keyShiftedLetterHintActivatedColor" format="color" />
+ <!-- Blur radius of key text shadow. -->
+ <attr name="keyTextShadowRadius" format="float" />
<!-- Layout resource for key press feedback.-->
<attr name="keyPreviewLayout" format="reference" />
@@ -90,14 +60,10 @@
<attr name="keyPreviewLeftBackground" format="reference" />
<!-- The background for the right edge key press feedback. -->
<attr name="keyPreviewRightBackground" format="reference" />
- <!-- The text color for key press feedback. -->
- <attr name="keyPreviewTextColor" format="color" />
<!-- Vertical offset of the key press feedback from the key. -->
<attr name="keyPreviewOffset" format="dimension" />
<!-- Height of the key press feedback popup. -->
<attr name="keyPreviewHeight" format="dimension" />
- <!-- Size of the text for key press feedback popup, int the proportion of key height -->
- <attr name="keyPreviewTextRatio" format="float" />
<!-- Delay after key releasing and key press feedback dismissing in millisecond -->
<attr name="keyPreviewLingerTimeout" format="integer" />
@@ -107,18 +73,8 @@
<!-- Layout resource for more keys panel -->
<attr name="moreKeysLayout" format="reference" />
- <attr name="shadowColor" format="color" />
- <attr name="shadowRadius" format="float" />
<attr name="backgroundDimAlpha" format="integer" />
- <attr name="keyTextStyle" format="enum">
- <!-- This should be aligned with Typeface.NORMAL etc. -->
- <enum name="normal" value="0" />
- <enum name="bold" value="1" />
- <enum name="italic" value="2" />
- <enum name="boldItalic" value="3" />
- </attr>
-
<!-- Attributes for PreviewPlacerView -->
<attr name="gestureFloatingPreviewTextSize" format="dimension" />
<attr name="gestureFloatingPreviewTextColor" format="color" />
@@ -131,6 +87,12 @@
<attr name="gestureFloatingPreviewTextConnectorWidth" format="dimension" />
<!-- Delay after gesture input and gesture floating preview text dismissing in millisecond -->
<attr name="gestureFloatingPreviewTextLingerTimeout" format="integer" />
+ <!-- Delay after gesture trail starts fading out in millisecond. -->
+ <attr name="gesturePreviewTrailFadeoutStartDelay" format="integer" />
+ <!-- Duration while gesture preview trail is fading out in millisecond. -->
+ <attr name="gesturePreviewTrailFadeoutDuration" format="integer" />
+ <!-- Interval of updating gesture preview trail in millisecond. -->
+ <attr name="gesturePreviewTrailUpdateInterval" format="integer" />
<attr name="gesturePreviewTrailColor" format="color" />
<attr name="gesturePreviewTrailWidth" format="dimension" />
</declare-styleable>
@@ -181,13 +143,13 @@
<attr name="colorTypedWord" format="color" />
<attr name="colorAutoCorrect" format="color" />
<attr name="colorSuggested" format="color" />
- <attr name="alphaValidTypedWord" format="integer" />
- <attr name="alphaTypedWord" format="integer" />
- <attr name="alphaAutoCorrect" format="integer" />
- <attr name="alphaSuggested" format="integer" />
- <attr name="alphaObsoleted" format="integer" />
+ <attr name="alphaValidTypedWord" format="fraction" />
+ <attr name="alphaTypedWord" format="fraction" />
+ <attr name="alphaAutoCorrect" format="fraction" />
+ <attr name="alphaSuggested" format="fraction" />
+ <attr name="alphaObsoleted" format="fraction" />
<attr name="suggestionsCountInStrip" format="integer" />
- <attr name="centerSuggestionPercentile" format="integer" />
+ <attr name="centerSuggestionPercentile" format="fraction" />
<attr name="maxMoreSuggestionsRow" format="integer" />
<attr name="minMoreSuggestionsWidth" format="float" />
</declare-styleable>
@@ -331,6 +293,50 @@
<!-- The X-coordinate of upper right corner of this key including horizontal gap.
If the value is negative, the origin is the right edge of the keyboard. -->
<attr name="keyXPos" format="dimension|fraction" />
+
+ <!-- Key top visual attributes -->
+ <attr name="keyTypeface" format="enum">
+ <!-- This should be aligned with Typeface.NORMAL etc. -->
+ <enum name="normal" value="0" />
+ <enum name="bold" value="1" />
+ <enum name="italic" value="2" />
+ <enum name="boldItalic" value="3" />
+ </attr>
+ <!-- Size of the text for one letter keys. If specified as fraction, the text size is
+ measured in the proportion of key height. -->
+ <attr name="keyLetterSize" format="dimension|fraction" />
+ <!-- Size of the text for keys with multiple letters. If specified as fraction, the text
+ size is measured in the proportion of key height. -->
+ <attr name="keyLabelSize" format="dimension|fraction" />
+ <!-- Large size of the text for one letter keys, in the proportion of key height. -->
+ <attr name="keyLargeLetterRatio" format="fraction" />
+ <!-- Large size of the text for keys with multiple letters, in the proportion of key height. -->
+ <attr name="keyLargeLabelRatio" format="fraction" />
+ <!-- Size of the text for hint letter (= one character hint label), in the proportion of
+ key height. -->
+ <attr name="keyHintLetterRatio" format="fraction" />
+ <!-- Size of the text for hint label, in the proportion of key height. -->
+ <attr name="keyHintLabelRatio" format="fraction" />
+ <!-- Size of the text for shifted letter hint, in the proportion of key height. -->
+ <attr name="keyShiftedLetterHintRatio" format="fraction" />
+ <!-- Color to use for the label in a key. -->
+ <attr name="keyTextColor" format="color" />
+ <attr name="keyTextShadowColor" format="color" />
+ <!-- Color to use for the label in a key when in inactivated state. -->
+ <attr name="keyTextInactivatedColor" format="color" />
+ <!-- Key hint letter (= one character hint label) color -->
+ <attr name="keyHintLetterColor" format="color" />
+ <!-- Key hint label color -->
+ <attr name="keyHintLabelColor" format="color" />
+ <!-- Shifted letter hint colors -->
+ <attr name="keyShiftedLetterHintInactivatedColor" format="color" />
+ <attr name="keyShiftedLetterHintActivatedColor" format="color" />
+
+ <!-- Key preview visual parameters -->
+ <!-- The text color for key press feedback. -->
+ <attr name="keyPreviewTextColor" format="color" />
+ <!-- Size of the text for key press feedback popup, in the proportion of key height. -->
+ <attr name="keyPreviewTextRatio" format="fraction" />
</declare-styleable>
<declare-styleable name="Keyboard_Include">
diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index 54a6687..8e2d43e 100644
--- a/java/res/values/config.xml
+++ b/java/res/values/config.xml
@@ -50,6 +50,9 @@
-->
<integer name="config_key_preview_linger_timeout">70</integer>
<integer name="config_gesture_floating_preview_text_linger_timeout">200</integer>
+ <integer name="config_gesture_preview_trail_fadeout_start_delay">100</integer>
+ <integer name="config_gesture_preview_trail_fadeout_duration">800</integer>
+ <integer name="config_gesture_preview_trail_update_interval">20</integer>
<!--
Configuration for MainKeyboardView
-->
diff --git a/java/res/values/dimens.xml b/java/res/values/dimens.xml
index c59bad3..aa16c77 100644
--- a/java/res/values/dimens.xml
+++ b/java/res/values/dimens.xml
@@ -73,6 +73,11 @@
<dimen name="key_popup_hint_letter_padding">2dp</dimen>
<dimen name="key_uppercase_letter_padding">2dp</dimen>
+ <!-- For 5-row keyboard -->
+ <fraction name="key_bottom_gap_5row">3.20%p</fraction>
+ <fraction name="key_letter_ratio_5row">64%</fraction>
+ <fraction name="key_uppercase_letter_ratio_5row">41%</fraction>
+
<dimen name="key_preview_offset_ics">8.0dp</dimen>
<!-- popup_key_height x -0.5 -->
<dimen name="more_keys_keyboard_vertical_correction_ics">-26.4dp</dimen>
@@ -92,7 +97,7 @@
<dimen name="suggestion_text_size">18dp</dimen>
<dimen name="more_suggestions_hint_text_size">27dp</dimen>
<integer name="suggestions_count_in_strip">3</integer>
- <integer name="center_suggestion_percentile">36</integer>
+ <fraction name="center_suggestion_percentile">36%</fraction>
<!-- Gesture preview parameters -->
<dimen name="gesture_preview_trail_width">2.5dp</dimen>
@@ -101,4 +106,7 @@
<dimen name="gesture_floating_preview_text_shadow_border">17.5dp</dimen>
<dimen name="gesture_floating_preview_text_shading_border">7.5dp</dimen>
<dimen name="gesture_floating_preview_text_connector_width">1.0dp</dimen>
+
+ <!-- Inset used in Accessibility mode to avoid accidental key presses when a finger slides off the screen. -->
+ <dimen name="accessibility_edge_slop">8dp</dimen>
</resources>
diff --git a/java/res/values/keypress-vibration-durations.xml b/java/res/values/keypress-vibration-durations.xml
index 2569f23..1d7e57b 100644
--- a/java/res/values/keypress-vibration-durations.xml
+++ b/java/res/values/keypress-vibration-durations.xml
@@ -22,5 +22,7 @@
<!-- Build.HARDWARE,duration_in_milliseconds -->
<item>herring,5</item>
<item>tuna,5</item>
+ <item>mako,20</item>
+ <item>manta,16</item>
</string-array>
</resources>
diff --git a/java/res/values/keypress-volumes.xml b/java/res/values/keypress-volumes.xml
index 3b433e4..d112069 100644
--- a/java/res/values/keypress-volumes.xml
+++ b/java/res/values/keypress-volumes.xml
@@ -24,5 +24,7 @@
<item>tuna,0.5</item>
<item>stingray,0.4</item>
<item>grouper,0.3</item>
+ <item>mako,0.3</item>
+ <item>manta,0.2</item>
</string-array>
</resources>
diff --git a/java/res/values/whitelist.xml b/java/res/values/research_strings.xml
similarity index 71%
rename from java/res/values/whitelist.xml
rename to java/res/values/research_strings.xml
index d4ecbfa..2cad15e 100644
--- a/java/res/values/whitelist.xml
+++ b/java/res/values/research_strings.xml
@@ -2,7 +2,7 @@
<!--
/*
**
-** Copyright 2011, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
@@ -18,12 +18,7 @@
*/
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!--
- An entry of the whitelist word should be:
- 1. (int)frequency
- 2. (String)before
- 3. (String)after
- -->
- <string-array name="wordlist_whitelist">
- </string-array>
+ <!-- Contents of note explaining what data is collected and how. -->
+ <!-- TODO: remove translatable=false attribute once text is stable -->
+ <string name="research_splash_content" translatable="false"></string>
</resources>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 07b3f31..bd60844 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -261,7 +261,8 @@
<string name="research_feedback_dialog_title" translatable="false">Send feedback</string>
<!-- Text for checkbox option to include user data in feedback for research purposes [CHAR LIMIT=50] -->
<!-- TODO: remove translatable=false attribute once text is stable -->
- <string name="research_feedback_include_history_label" translatable="false">Include last 5 words entered</string>
+ <!-- TODO: handle multilingual plurals -->
+ <string name="research_feedback_include_history_label" translatable="false">Include last <xliff:g id="word">%d</xliff:g> words entered</string>
<!-- Hint to user about the text entry field where they should enter research feedback [CHAR LIMIT=40] -->
<!-- TODO: remove translatable=false attribute once text is stable -->
<string name="research_feedback_hint" translatable="false">Enter your feedback here.</string>
@@ -277,16 +278,15 @@
<!-- Title of dialog shown at start informing users about contributing research usage data-->
<!-- TODO: remove translatable=false attribute once text is stable -->
- <string name="research_splash_title" translatable="false">Usage Participation</string>
- <!-- Contents of note explaining what data is collected and how. -->
+ <string name="research_splash_title" translatable="false">Warning</string>
+
+ <!-- Toast message informing users that logging has been disabled -->
<!-- TODO: remove translatable=false attribute once text is stable -->
- <string name="research_splash_content" translatable="false">Thank you for dogfooding this keyboard.\n\nIf you like it, please help us make it better by sending us usage information. When enabled, the keyboard uploads general statistics, such as how fast you type, and also occasional samples of how you type words.\n\nNo passwords or non-dictionary words are ever automatically uploaded, and words are sampled infrequently enough so that reconstructing the meaning of what you typed is highly unlikely.\n\nYou can disable and reenable logging through the RLog menu by long-pressing on the microphone or settings key.\n</string>
- <!-- Button label text for opting out of research usage data collection [CHAR LIMIT=50] -->
+ <string name="research_logging_disabled" translatable="false">Logging Disabled</string>
+
+ <!-- Name for the research uploading service to be displayed to users. [CHAR LIMIT=50] -->
<!-- TODO: remove translatable=false attribute once text is stable -->
- <string name="research_dont_send_usage_info" translatable="false">Do not send\nusage info</string>
- <!-- Button label text for opting into research usage data collection [CHAR LIMIT=50] -->
- <!-- TODO: remove translatable=false attribute once text is stable -->
- <string name="research_send_usage_info" translatable="false">Send usage info</string>
+ <string name="research_log_uploader_name" translatable="false">Research Uploader Service</string>
<!-- Preference for input language selection -->
<string name="select_language">Input languages</string>
diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml
index ae67c43..634b32a 100644
--- a/java/res/values/styles.xml
+++ b/java/res/values/styles.xml
@@ -35,14 +35,14 @@
<style name="KeyboardView">
<item name="android:background">@drawable/keyboard_background</item>
<item name="keyBackground">@drawable/btn_keyboard_key</item>
- <item name="keyLetterRatio">@fraction/key_letter_ratio</item>
+ <item name="keyLetterSize">@fraction/key_letter_ratio</item>
<item name="keyLargeLetterRatio">@fraction/key_large_letter_ratio</item>
- <item name="keyLabelRatio">@fraction/key_label_ratio</item>
+ <item name="keyLabelSize">@fraction/key_label_ratio</item>
<item name="keyLargeLabelRatio">@fraction/key_large_label_ratio</item>
<item name="keyHintLetterRatio">@fraction/key_hint_letter_ratio</item>
<item name="keyHintLabelRatio">@fraction/key_hint_label_ratio</item>
<item name="keyShiftedLetterHintRatio">@fraction/key_uppercase_letter_ratio</item>
- <item name="keyTextStyle">normal</item>
+ <item name="keyTypeface">normal</item>
<item name="keyTextColor">#FFFFFFFF</item>
<item name="keyTextInactivatedColor">#FFFFFFFF</item>
<item name="keyHintLetterColor">#80000000</item>
@@ -64,8 +64,8 @@
<item name="keyPreviewLingerTimeout">@integer/config_key_preview_linger_timeout</item>
<item name="moreKeysLayout">@layout/more_keys_keyboard</item>
<item name="verticalCorrection">@dimen/keyboard_vertical_correction</item>
- <item name="shadowColor">#BB000000</item>
- <item name="shadowRadius">2.75</item>
+ <item name="keyTextShadowColor">#BB000000</item>
+ <item name="keyTextShadowRadius">2.75</item>
<item name="backgroundDimAlpha">128</item>
<!-- android:color/holo_blue_light=#FF33B5E5 -->
<item name="gestureFloatingPreviewTextSize">@dimen/gesture_floating_preview_text_size</item>
@@ -78,6 +78,9 @@
<item name="gestureFloatingPreviewTextConnectorColor">@android:color/white</item>
<item name="gestureFloatingPreviewTextConnectorWidth">@dimen/gesture_floating_preview_text_connector_width</item>
<item name="gestureFloatingPreviewTextLingerTimeout">@integer/config_gesture_floating_preview_text_linger_timeout</item>
+ <item name="gesturePreviewTrailFadeoutStartDelay">@integer/config_gesture_preview_trail_fadeout_start_delay</item>
+ <item name="gesturePreviewTrailFadeoutDuration">@integer/config_gesture_preview_trail_fadeout_duration</item>
+ <item name="gesturePreviewTrailUpdateInterval">@integer/config_gesture_preview_trail_update_interval</item>
<item name="gesturePreviewTrailColor">@android:color/holo_blue_light</item>
<item name="gesturePreviewTrailWidth">@dimen/gesture_preview_trail_width</item>
<!-- Common attributes of MainKeyboardView -->
@@ -135,9 +138,9 @@
<item name="colorTypedWord">@android:color/white</item>
<item name="colorAutoCorrect">#FFFCAE00</item>
<item name="colorSuggested">#FFFCAE00</item>
- <item name="alphaObsoleted">50</item>
+ <item name="alphaObsoleted">50%</item>
<item name="suggestionsCountInStrip">@integer/suggestions_count_in_strip</item>
- <item name="centerSuggestionPercentile">@integer/center_suggestion_percentile</item>
+ <item name="centerSuggestionPercentile">@fraction/center_suggestion_percentile</item>
<item name="maxMoreSuggestionsRow">@integer/max_more_suggestions_row</item>
<item name="minMoreSuggestionsWidth">@fraction/min_more_suggestions_width</item>
</style>
@@ -200,7 +203,7 @@
<item name="keyHintLabelColor">#E0000000</item>
<item name="keyShiftedLetterHintInactivatedColor">#66000000</item>
<item name="keyShiftedLetterHintActivatedColor">#CC000000</item>
- <item name="shadowColor">#FFFFFFFF</item>
+ <item name="keyTextShadowColor">#FFFFFFFF</item>
</style>
<style
name="MainKeyboardView.Stone"
@@ -226,7 +229,7 @@
>
<item name="keyBackground">@drawable/btn_keyboard_key_stone</item>
<item name="keyTextColor">#FF000000</item>
- <item name="shadowColor">#FFFFFFFF</item>
+ <item name="keyTextShadowColor">#FFFFFFFF</item>
</style>
<!-- Theme "Stone bold" -->
<style
@@ -240,7 +243,7 @@
name="KeyboardView.Stone.Bold"
parent="KeyboardView.Stone"
>
- <item name="keyTextStyle">bold</item>
+ <item name="keyTypeface">bold</item>
</style>
<style
name="MainKeyboardView.Stone.Bold"
@@ -269,7 +272,7 @@
>
<item name="android:background">@drawable/keyboard_dark_background</item>
<item name="keyBackground">@drawable/btn_keyboard_key_gingerbread</item>
- <item name="keyTextStyle">bold</item>
+ <item name="keyTypeface">bold</item>
</style>
<style
name="MainKeyboardView.Gingerbread"
@@ -314,7 +317,7 @@
>
<item name="android:background">@drawable/keyboard_background_holo</item>
<item name="keyBackground">@drawable/btn_keyboard_key_ics</item>
- <item name="keyTextStyle">bold</item>
+ <item name="keyTypeface">bold</item>
<item name="keyTextInactivatedColor">#66E0E4E5</item>
<item name="keyHintLetterColor">#80000000</item>
<item name="keyHintLabelColor">#A0FFFFFF</item>
@@ -325,8 +328,8 @@
<item name="keyPreviewRightBackground">@drawable/keyboard_key_feedback_right_ics</item>
<item name="keyPreviewTextColor">#FFFFFFFF</item>
<item name="keyPreviewOffset">@dimen/key_preview_offset_ics</item>
- <item name="shadowColor">#00000000</item>
- <item name="shadowRadius">0.0</item>
+ <item name="keyTextShadowColor">#00000000</item>
+ <item name="keyTextShadowRadius">0.0</item>
</style>
<style
name="MainKeyboardView.IceCreamSandwich"
@@ -370,12 +373,12 @@
<item name="colorTypedWord">@android:color/holo_blue_light</item>
<item name="colorAutoCorrect">@android:color/holo_blue_light</item>
<item name="colorSuggested">@android:color/holo_blue_light</item>
- <item name="alphaValidTypedWord">85</item>
- <item name="alphaTypedWord">85</item>
- <item name="alphaSuggested">70</item>
- <item name="alphaObsoleted">70</item>
+ <item name="alphaValidTypedWord">85%</item>
+ <item name="alphaTypedWord">85%</item>
+ <item name="alphaSuggested">70%</item>
+ <item name="alphaObsoleted">70%</item>
<item name="suggestionsCountInStrip">@integer/suggestions_count_in_strip</item>
- <item name="centerSuggestionPercentile">@integer/center_suggestion_percentile</item>
+ <item name="centerSuggestionPercentile">@fraction/center_suggestion_percentile</item>
<item name="maxMoreSuggestionsRow">@integer/max_more_suggestions_row</item>
<item name="minMoreSuggestionsWidth">@fraction/min_more_suggestions_width</item>
</style>
diff --git a/java/res/xml-sw600dp-land/kbd_thai.xml b/java/res/xml-sw600dp-land/kbd_thai.xml
deleted file mode 100644
index b75980f..0000000
--- a/java/res/xml-sw600dp-land/kbd_thai.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<Keyboard
- xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
- latin:rowHeight="20%p"
- latin:verticalGap="3.20%p"
- latin:touchPositionCorrectionData="@null"
->
- <include
- latin:keyboardLayout="@xml/rows_thai" />
-</Keyboard>
diff --git a/java/res/xml-sw600dp/kbd_thai.xml b/java/res/xml-sw600dp/kbd_thai.xml
deleted file mode 100644
index b75980f..0000000
--- a/java/res/xml-sw600dp/kbd_thai.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<Keyboard
- xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
- latin:rowHeight="20%p"
- latin:verticalGap="3.20%p"
- latin:touchPositionCorrectionData="@null"
->
- <include
- latin:keyboardLayout="@xml/rows_thai" />
-</Keyboard>
diff --git a/java/res/xml-sw600dp/rowkeys_arabic1.xml b/java/res/xml-sw600dp/rowkeys_arabic1.xml
index 44fdc67..6a0e257 100644
--- a/java/res/xml-sw600dp/rowkeys_arabic1.xml
+++ b/java/res/xml-sw600dp/rowkeys_arabic1.xml
@@ -23,19 +23,23 @@
>
<!-- U+0636: "ض" ARABIC LETTER DAD -->
<Key
- latin:keyLabel="ض" />
+ latin:keyLabel="ض"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0635: "ص" ARABIC LETTER SAD -->
<Key
- latin:keyLabel="ص" />
+ latin:keyLabel="ص"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+062B: "ث" ARABIC LETTER THEH -->
<Key
- latin:keyLabel="ث" />
+ latin:keyLabel="ث"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0642: "ق" ARABIC LETTER QAF
U+06A8: "ڨ" ARABIC LETTER QAF WITH THREE DOTS ABOVE -->
<!-- TODO: DroidSansArabic lacks the glyph of U+06A8 ARABIC LETTER QAF WITH THREE DOTS ABOVE -->
<Key
latin:keyLabel="ق"
- latin:moreKeys="ڨ" />
+ latin:moreKeys="ڨ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0641: "ف" ARABIC LETTER FEH
U+06A4: "ڤ" ARABIC LETTER VEH
U+06A2: "ڢ" ARABIC LETTER FEH WITH DOT MOVED BELOW
@@ -44,28 +48,35 @@
<!-- TODO: DroidSansArabic lacks the glyph of U+06A5 ARABIC LETTER FEH WITH THREE DOTS BELOW -->
<Key
latin:keyLabel="ف"
- latin:moreKeys="ڤ,ڢ,ڥ" />
+ latin:moreKeys="ڤ,ڢ,ڥ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+063A: "غ" ARABIC LETTER GHAIN -->
<Key
- latin:keyLabel="غ" />
+ latin:keyLabel="غ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0639: "ع" ARABIC LETTER AIN -->
<Key
- latin:keyLabel="ع" />
+ latin:keyLabel="ع"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0647: "ه" ARABIC LETTER HEH
U+FEEB: "ﻫ" ARABIC LETTER HEH INITIAL FORM
U+0647 U+200D: ARABIC LETTER HEH + ZERO WIDTH JOINER -->
<Key
latin:keyLabel="ه"
- latin:moreKeys="ﻫ|ه‍" />
+ latin:moreKeys="ﻫ|ه‍"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+062E: "خ" ARABIC LETTER KHAH -->
<Key
- latin:keyLabel="خ" />
+ latin:keyLabel="خ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+062D: "ح" ARABIC LETTER HAH -->
<Key
- latin:keyLabel="ح" />
+ latin:keyLabel="ح"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+062C: "ج" ARABIC LETTER JEEM
U+0686: "چ" ARABIC LETTER TCHEH -->
<Key
latin:keyLabel="ج"
- latin:moreKeys="چ" />
+ latin:moreKeys="چ"
+ latin:keyLabelFlags="fontNormal" />
</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_arabic2.xml b/java/res/xml-sw600dp/rowkeys_arabic2.xml
index 3eba2fb..00e69ac 100644
--- a/java/res/xml-sw600dp/rowkeys_arabic2.xml
+++ b/java/res/xml-sw600dp/rowkeys_arabic2.xml
@@ -26,21 +26,25 @@
<!-- TODO: DroidSansArabic lacks the glyph of U+069C ARABIC LETTER SEEN WITH THREE DOTS BELOW AND THREE DOTS ABOVE -->
<Key
latin:keyLabel="ش"
- latin:moreKeys="ڜ" />
+ latin:moreKeys="ڜ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0633: "س" ARABIC LETTER SEEN -->
<Key
- latin:keyLabel="س" />
+ latin:keyLabel="س"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+064A: "ي" ARABIC LETTER YEH
U+0626: "ئ" ARABIC LETTER YEH WITH HAMZA ABOVE
U+0649: "ى" ARABIC LETTER ALEF MAKSURA -->
<Key
latin:keyLabel="ي"
- latin:moreKeys="ئ,ى" />
+ latin:moreKeys="ئ,ى"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0628: "ب" ARABIC LETTER BEH
U+067E: "پ" ARABIC LETTER PEH -->
<Key
latin:keyLabel="ب"
- latin:moreKeys="پ" />
+ latin:moreKeys="پ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0644: "ل" ARABIC LETTER LAM
U+FEFB: "ﻻ" ARABIC LIGATURE LAM WITH ALEF ISOLATED FORM
U+0627: "ا" ARABIC LETTER ALEF
@@ -52,7 +56,8 @@
U+0622: "آ" ARABIC LETTER ALEF WITH MADDA ABOVE -->
<Key
latin:keyLabel="ل"
- latin:moreKeys="ﻻ|لا,ﻷ|لأ,ﻹ|لإ,ﻵ|لآ" />
+ latin:moreKeys="ﻻ|لا,ﻷ|لأ,ﻹ|لإ,ﻵ|لآ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0627: "ا" ARABIC LETTER ALEF
U+0621: "ء" ARABIC LETTER HAMZA
U+0671: "ٱ" ARABIC LETTER ALEF WASLA
@@ -61,23 +66,29 @@
U+0622: "آ" ARABIC LETTER ALEF WITH MADDA ABOVE -->
<Key
latin:keyLabel="ا"
- latin:moreKeys="ء,ٱ,أ,إ,آ" />
+ latin:moreKeys="ء,ٱ,أ,إ,آ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+062A: "ت" ARABIC LETTER TEH -->
<Key
- latin:keyLabel="ت" />
+ latin:keyLabel="ت"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0646: "ن" ARABIC LETTER NOON -->
<Key
- latin:keyLabel="ن" />
+ latin:keyLabel="ن"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0645: "م" ARABIC LETTER MEEM -->
<Key
- latin:keyLabel="م" />
+ latin:keyLabel="م"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0643: "ك" ARABIC LETTER KAF
U+06AF: "گ" ARABIC LETTER GAF
U+06A9: "ک" ARABIC LETTER KEHEH -->
<Key
latin:keyLabel="ك"
- latin:moreKeys="گ,ک" />
+ latin:moreKeys="گ,ک"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0637: "ط" ARABIC LETTER TAH -->
<Key
- latin:keyLabel="ط" />
+ latin:keyLabel="ط"
+ latin:keyLabelFlags="fontNormal" />
</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_arabic3.xml b/java/res/xml-sw600dp/rowkeys_arabic3.xml
index 911550f..b0bcd78 100644
--- a/java/res/xml-sw600dp/rowkeys_arabic3.xml
+++ b/java/res/xml-sw600dp/rowkeys_arabic3.xml
@@ -23,37 +23,48 @@
>
<!-- U+0626: "ئ" ARABIC LETTER YEH WITH HAMZA ABOVE -->
<Key
- latin:keyLabel="ئ" />
+ latin:keyLabel="ئ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0621: "ء" ARABIC LETTER HAMZA -->
<Key
- latin:keyLabel="ء" />
+ latin:keyLabel="ء"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0624: "ؤ" ARABIC LETTER WAW WITH HAMZA ABOVE -->
<Key
- latin:keyLabel="ؤ" />
+ latin:keyLabel="ؤ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0631: "ر" ARABIC LETTER REH -->
<Key
- latin:keyLabel="ر" />
+ latin:keyLabel="ر"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0630: "ذ" ARABIC LETTER THAL -->
<Key
- latin:keyLabel="ذ" />
+ latin:keyLabel="ذ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0649: "ى" ARABIC LETTER ALEF MAKSURA -->
<Key
- latin:keyLabel="ى" />
+ latin:keyLabel="ى"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0629: "ة" ARABIC LETTER TEH MARBUTA -->
<Key
- latin:keyLabel="ة" />
+ latin:keyLabel="ة"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0648: "و" ARABIC LETTER WAW -->
<Key
- latin:keyLabel="و" />
+ latin:keyLabel="و"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0632: "ز" ARABIC LETTER ZAIN
U+0698: "ژ" ARABIC LETTER JEH -->
<Key
latin:keyLabel="ز"
- latin:moreKeys="ژ" />
+ latin:moreKeys="ژ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0638: "ظ" ARABIC LETTER ZAH -->
<Key
- latin:keyLabel="ظ" />
+ latin:keyLabel="ظ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+062F: "د" ARABIC LETTER DAL -->
<Key
- latin:keyLabel="د" />
+ latin:keyLabel="د"
+ latin:keyLabelFlags="fontNormal" />
</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_farsi1.xml b/java/res/xml-sw600dp/rowkeys_farsi1.xml
index 53208f2..7b31240 100644
--- a/java/res/xml-sw600dp/rowkeys_farsi1.xml
+++ b/java/res/xml-sw600dp/rowkeys_farsi1.xml
@@ -23,25 +23,32 @@
>
<!-- U+0636: "ض" ARABIC LETTER DAD -->
<Key
- latin:keyLabel="ض" />
+ latin:keyLabel="ض"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0635: "ص" ARABIC LETTER SAD -->
<Key
- latin:keyLabel="ص" />
+ latin:keyLabel="ص"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+062B: "ث" ARABIC LETTER THEH -->
<Key
- latin:keyLabel="ث" />
+ latin:keyLabel="ث"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0642: "ق" ARABIC LETTER QAF -->
<Key
- latin:keyLabel="ق" />
+ latin:keyLabel="ق"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0641: "ف" ARABIC LETTER FEH -->
<Key
- latin:keyLabel="ف" />
+ latin:keyLabel="ف"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+063A: "غ" ARABIC LETTER GHAIN -->
<Key
- latin:keyLabel="غ" />
+ latin:keyLabel="غ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0639: "ع" ARABIC LETTER AIN -->
<Key
- latin:keyLabel="ع" />
+ latin:keyLabel="ع"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0647: "ه" ARABIC LETTER HEH
U+FEEB: "ﻫ" ARABIC LETTER HEH INITIAL FORM
U+0647/U+200D: ARABIC LETTER HEH + ZERO WIDTH JOINER
@@ -49,17 +56,22 @@
U+0629: "ة" ARABIC LETTER TEH MARBUTA -->
<Key
latin:keyLabel="ه"
- latin:moreKeys="ﻫ|ه‍,هٔ,ة,%" />
+ latin:moreKeys="ﻫ|ه‍,هٔ,ة,%"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+062E: "خ" ARABIC LETTER KHAH -->
<Key
- latin:keyLabel="خ" />
+ latin:keyLabel="خ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+062D: "ح" ARABIC LETTER HAH -->
<Key
- latin:keyLabel="ح" />
+ latin:keyLabel="ح"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+062C: "ج" ARABIC LETTER JEEM -->
<Key
- latin:keyLabel="ج" />
+ latin:keyLabel="ج"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0686: "چ" ARABIC LETTER TCHEH -->
<Key
- latin:keyLabel="چ" />
+ latin:keyLabel="چ"
+ latin:keyLabelFlags="fontNormal" />
</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_farsi2.xml b/java/res/xml-sw600dp/rowkeys_farsi2.xml
index 234f984..3b759b6 100644
--- a/java/res/xml-sw600dp/rowkeys_farsi2.xml
+++ b/java/res/xml-sw600dp/rowkeys_farsi2.xml
@@ -23,10 +23,12 @@
>
<!-- U+0634: "ش" ARABIC LETTER SHEEN -->
<Key
- latin:keyLabel="ش" />
+ latin:keyLabel="ش"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0633: "س" ARABIC LETTER SEEN -->
<Key
- latin:keyLabel="س" />
+ latin:keyLabel="س"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+06CC: "ی" ARABIC LETTER FARSI YEH
U+0626: "ئ" ARABIC LETTER YEH WITH HAMZA ABOVE
U+064A: "ي" ARABIC LETTER YEH
@@ -34,13 +36,16 @@
U+0649: "ى" ARABIC LETTER ALEF MAKSURA -->
<Key
latin:keyLabel="ی"
- latin:moreKeys="ئ,ي,ﯨ|ى" />
+ latin:moreKeys="ئ,ي,ﯨ|ى"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0628: "ب" ARABIC LETTER BEH -->
<Key
- latin:keyLabel="ب" />
+ latin:keyLabel="ب"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0644: "ل" ARABIC LETTER LAM -->
<Key
- latin:keyLabel="ل" />
+ latin:keyLabel="ل"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0627: "ا" ARABIC LETTER ALEF
U+0621: "ء" ARABIC LETTER HAMZA
U+0622: "آ" ARABIC LETTER ALEF WITH MADDA ABOVE
@@ -49,25 +54,31 @@
U+0625: "إ" ARABIC LETTER ALEF WITH HAMZA BELOW -->
<Key
latin:keyLabel="ا"
- latin:moreKeys="ء,آ,أ,ٱ,إ" />
+ latin:moreKeys="ء,آ,أ,ٱ,إ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+062A: "ت" ARABIC LETTER TEH
U+062B: "ﺙ" ARABIC LETTER THEH
U+0629: "ة": ARABIC LETTER TEH MARBUTA -->
<Key
latin:keyLabel="ت"
- latin:moreKeys="ث,ة" />
+ latin:moreKeys="ث,ة"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0646: "ن" ARABIC LETTER NOON -->
<Key
- latin:keyLabel="ن" />
+ latin:keyLabel="ن"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0645: "م" ARABIC LETTER MEEM -->
<Key
- latin:keyLabel="م" />
+ latin:keyLabel="م"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+06A9: "ک" ARABIC LETTER KEHEH
U+0643: "ك" ARABIC LETTER KAF -->
<Key
latin:keyLabel="ک"
- latin:moreKeys="ك" />
+ latin:moreKeys="ك"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+06AF: "گ" ARABIC LETTER GAF -->
<Key
- latin:keyLabel="گ" />
+ latin:keyLabel="گ"
+ latin:keyLabelFlags="fontNormal" />
</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_farsi3.xml b/java/res/xml-sw600dp/rowkeys_farsi3.xml
index 998ba72..3597618 100644
--- a/java/res/xml-sw600dp/rowkeys_farsi3.xml
+++ b/java/res/xml-sw600dp/rowkeys_farsi3.xml
@@ -23,34 +23,44 @@
>
<!-- U+0638: "ظ" ARABIC LETTER ZAH -->
<Key
- latin:keyLabel="ظ" />
+ latin:keyLabel="ظ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0637: "ط" ARABIC LETTER TAH -->
<Key
- latin:keyLabel="ط" />
+ latin:keyLabel="ط"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0698: "ژ" ARABIC LETTER JEH -->
<Key
- latin:keyLabel="ژ" />
+ latin:keyLabel="ژ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0632: "ز" ARABIC LETTER ZAIN -->
<Key
- latin:keyLabel="ز" />
+ latin:keyLabel="ز"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0631: "ر" ARABIC LETTER REH -->
<Key
- latin:keyLabel="ر" />
+ latin:keyLabel="ر"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0630: "ذ" ARABIC LETTER THAL -->
<Key
- latin:keyLabel="ذ" />
+ latin:keyLabel="ذ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+062F: "د" ARABIC LETTER DAL -->
<Key
- latin:keyLabel="د" />
+ latin:keyLabel="د"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+067E: "پ" ARABIC LETTER PEH -->
<Key
- latin:keyLabel="پ" />
+ latin:keyLabel="پ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0648: "و" ARABIC LETTER WAW
U+0624: "ؤ" ARABIC LETTER WAW WITH HAMZA ABOVE -->
<Key
latin:keyLabel="و"
- latin:moreKeys="ؤ" />
+ latin:moreKeys="ؤ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0622: "آ" ARABIC LETTER ALEF WITH MADDA ABOVE -->
<Key
- latin:keyLabel="آ" />
+ latin:keyLabel="آ"
+ latin:keyLabelFlags="fontNormal" />
</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_thai1.xml b/java/res/xml-sw600dp/rowkeys_thai1.xml
deleted file mode 100644
index 6aec7c2..0000000
--- a/java/res/xml-sw600dp/rowkeys_thai1.xml
+++ /dev/null
@@ -1,97 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
- xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
- <switch>
- <case
- latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
- >
- <!-- U+0E51: "๑" THAI DIGIT ONE -->
- <Key
- latin:keyLabel="๑" />
- <!-- U+0E52: "๒" THAI DIGIT TWO -->
- <Key
- latin:keyLabel="๒" />
- <!-- U+0E53: "๓" THAI DIGIT THREE -->
- <Key
- latin:keyLabel="๓" />
- <!-- U+0E54: "๔" THAI DIGIT FOUR -->
- <Key
- latin:keyLabel="๔" />
- <!-- U+0E39: " ู" THAI CHARACTER SARA UU -->
- <Key
- latin:keyLabel="ู" />
- <!-- U+0E3F: "฿" THAI CURRENCY SYMBOL BAHT -->
- <Key
- latin:keyLabel="฿" />
- <!-- U+0E55: "๕" THAI DIGIT FIVE -->
- <Key
- latin:keyLabel="๕" />
- <!-- U+0E56: "๖" THAI DIGIT SIX -->
- <Key
- latin:keyLabel="๖" />
- <!-- U+0E57: "๗" THAI DIGIT SEVEN -->
- <Key
- latin:keyLabel="๗" />
- <!-- U+0E58: "๘" THAI DIGIT EIGHT -->
- <Key
- latin:keyLabel="๘" />
- <!-- U+0E59: "๙" THAI DIGIT NINE -->
- <Key
- latin:keyLabel="๙" />
- </case>
- <default>
- <!-- U+0E45: "ๅ" THAI CHARACTER LAKKHANGYAO -->
- <Key
- latin:keyLabel="ๅ" />
- <Key
- latin:keyLabel="/" />
- <!-- U+0E20: "ภ" THAI CHARACTER PHO SAMPHAO -->
- <Key
- latin:keyLabel="ภ" />
- <!-- U+0E16: "ถ" THAI CHARACTER THO THUNG -->
- <Key
- latin:keyLabel="ถ" />
- <!-- U+0E38: " ุ" THAI CHARACTER SARA U -->
- <Key
- latin:keyLabel="ุ" />
- <!-- U+0E36: " ึ" THAI CHARACTER SARA UE -->
- <Key
- latin:keyLabel="ึ" />
- <!-- U+0E04: "ค" THAI CHARACTER KHO KHWAI -->
- <Key
- latin:keyLabel="ค" />
- <!-- U+0E15: "ต" THAI CHARACTER TO TAO -->
- <Key
- latin:keyLabel="ต" />
- <!-- U+0E08: "จ" THAI CHARACTER CHO CHAN -->
- <Key
- latin:keyLabel="จ" />
- <!-- U+0E02: "ข" THAI CHARACTER KHO KHAI -->
- <Key
- latin:keyLabel="ข" />
- <!-- U+0E0A: "ช" THAI CHARACTER CHO CHANG -->
- <Key
- latin:keyLabel="ช" />
- </default>
- </switch>
-</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_thai2.xml b/java/res/xml-sw600dp/rowkeys_thai2.xml
deleted file mode 100644
index edb759a..0000000
--- a/java/res/xml-sw600dp/rowkeys_thai2.xml
+++ /dev/null
@@ -1,108 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
- xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
- <switch>
- <case
- latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
- >
- <!-- U+0E50: "๐" THAI DIGIT ZERO -->
- <Key
- latin:keyLabel="๐" />
- <Key
- latin:keyLabel=""" />
- <!-- U+0E0E: "ฎ" THAI CHARACTER DO CHADA -->
- <Key
- latin:keyLabel="ฎ" />
- <!-- U+0E11: "ฑ" THAI CHARACTER THO NANGMONTHO -->
- <Key
- latin:keyLabel="ฑ" />
- <!-- U+0E18: "ธ" THAI CHARACTER THO THONG -->
- <Key
- latin:keyLabel="ธ" />
- <!-- U+0E4D: " ํ" THAI CHARACTER THANTHAKHAT -->
- <Key
- latin:keyLabel="ํ" />
- <!-- U+0E4A: " ๊" THAI CHARACTER MAI TRI -->
- <Key
- latin:keyLabel="๊" />
- <!-- U+0E13: "ณ" THAI CHARACTER NO NEN -->
- <Key
- latin:keyLabel="ณ" />
- <!-- U+0E2F: "ฯ" THAI CHARACTER PAIYANNOI -->
- <Key
- latin:keyLabel="ฯ" />
- <!-- U+0E0D: "ญ" THAI CHARACTER YO YING -->
- <Key
- latin:keyLabel="ญ" />
- <!-- U+0E10: "ฐ" THAI CHARACTER THO THAN -->
- <Key
- latin:keyLabel="ฐ" />
- <Key
- latin:keyLabel="," />
- <!-- U+0E05: "ฅ" THAI CHARACTER KHO KHON -->
- <Key
- latin:keyLabel="ฅ" />
- </case>
- <default>
- <!-- U+0E46: "ๆ" THAI CHARACTER MAIYAMOK -->
- <Key
- latin:keyLabel="ๆ" />
- <!-- U+0E44: "ไ" THAI CHARACTER SARA AI MAIMALAI -->
- <Key
- latin:keyLabel="ไ" />
- <!-- U+0E33: "ำ" THAI CHARACTER SARA AM -->
- <Key
- latin:keyLabel="ำ" />
- <!-- U+0E1E: "พ" THAI CHARACTER PHO PHAN -->
- <Key
- latin:keyLabel="พ" />
- <!-- U+0E30: "ะ" THAI CHARACTER SARA A -->
- <Key
- latin:keyLabel="ะ" />
- <!-- U+0E31: " ั" THAI CHARACTER MAI HAN-AKAT -->
- <Key
- latin:keyLabel="ั" />
- <!-- U+0E35: " ี" HAI CHARACTER SARA II -->
- <Key
- latin:keyLabel="ี" />
- <!-- U+0E23: "ร" THAI CHARACTER RO RUA -->
- <Key
- latin:keyLabel="ร" />
- <!-- U+0E19: "น" THAI CHARACTER NO NU -->
- <Key
- latin:keyLabel="น" />
- <!-- U+0E22: "ย" THAI CHARACTER YO YAK -->
- <Key
- latin:keyLabel="ย" />
- <!-- U+0E1A: "บ" THAI CHARACTER BO BAIMAI -->
- <Key
- latin:keyLabel="บ" />
- <!-- U+0E25: "ล" THAI CHARACTER LO LING -->
- <Key
- latin:keyLabel="ล" />
- <!-- U+0E03: "ฃ" THAI CHARACTER KHO KHUAT -->
- <Key
- latin:keyLabel="ฃ" />
- </default>
- </switch>
-</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_thai3.xml b/java/res/xml-sw600dp/rowkeys_thai3.xml
deleted file mode 100644
index 7507dde..0000000
--- a/java/res/xml-sw600dp/rowkeys_thai3.xml
+++ /dev/null
@@ -1,97 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
- xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
- <switch>
- <case
- latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
- >
- <!-- U+0E24: "ฤ" THAI CHARACTER RU -->
- <Key
- latin:keyLabel="ฤ" />
- <!-- U+0E06: "ฆ" THAI CHARACTER KHO RAKHANG -->
- <Key
- latin:keyLabel="ฆ" />
- <!-- U+0E0F: "ฏ" THAI CHARACTER TO PATAK -->
- <Key
- latin:keyLabel="ฏ" />
- <!-- U+0E42: "โ" THAI CHARACTER SARA O -->
- <Key
- latin:keyLabel="โ" />
- <!-- U+0E0C: "ฌ" THAI CHARACTER CHO CHOE -->
- <Key
- latin:keyLabel="ฌ" />
- <!-- U+0E47: " ็" THAI CHARACTER MAITAIKHU -->
- <Key
- latin:keyLabel="็" />
- <!-- U+0E4B: " ๋" THAI CHARACTER MAI CHATTAWA -->
- <Key
- latin:keyLabel="๋" />
- <!-- U+0E29: "ษ" THAI CHARACTER SO RUSI -->
- <Key
- latin:keyLabel="ษ" />
- <!-- U+0E28: "ศ" THAI CHARACTER SO SALA -->
- <Key
- latin:keyLabel="ศ" />
- <!-- U+0E0B: "ซ" THAI CHARACTER SO SO -->
- <Key
- latin:keyLabel="ซ" />
- <Key
- latin:keyLabel="." />
- </case>
- <default>
- <!-- U+0E1F: "ฟ" THAI CHARACTER FO FAN -->
- <Key
- latin:keyLabel="ฟ" />
- <!-- U+0E2B: "ห" THAI CHARACTER HO HIP -->
- <Key
- latin:keyLabel="ห" />
- <!-- U+0E01: "ก" THAI CHARACTER KO KAI -->
- <Key
- latin:keyLabel="ก" />
- <!-- U+0E14: "ด" THAI CHARACTER DO DEK -->
- <Key
- latin:keyLabel="ด" />
- <!-- U+0E40: "เ" THAI CHARACTER SARA E -->
- <Key
- latin:keyLabel="เ" />
- <!-- U+0E49: " ้" THAI CHARACTER MAI THO -->
- <Key
- latin:keyLabel="้" />
- <!-- U+0E48: " ฺ" THAI CHARACTER MAI EK -->
- <Key
- latin:keyLabel="่" />
- <!-- U+0E32: "า" THAI CHARACTER SARA AA -->
- <Key
- latin:keyLabel="า" />
- <!-- U+0E2A: "ส" THAI CHARACTER SO SUA -->
- <Key
- latin:keyLabel="ส" />
- <!-- U+0E27: "ว" THAI CHARACTER WO WAEN -->
- <Key
- latin:keyLabel="ว" />
- <!-- U+0E07: "ง" THAI CHARACTER NGO NGU -->
- <Key
- latin:keyLabel="ง" />
- </default>
- </switch>
-</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_thai4.xml b/java/res/xml-sw600dp/rowkeys_thai4.xml
deleted file mode 100644
index 64549bd..0000000
--- a/java/res/xml-sw600dp/rowkeys_thai4.xml
+++ /dev/null
@@ -1,89 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
- xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
- <switch>
- <case
- latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
- >
- <Key
- latin:keyLabel="(" />
- <Key
- latin:keyLabel=")" />
- <!-- U+0E09: "ฉ" THAI CHARACTER CHO CHING -->
- <Key
- latin:keyLabel="ฉ" />
- <!-- U+0E2E: "ฮ" THAI CHARACTER HO NOKHUK -->
- <Key
- latin:keyLabel="ฮ" />
- <!-- U+0E3A: " ฺ" THAI CHARACTER PHINTHU -->
- <Key
- latin:keyLabel="ฺ" />
- <!-- U+0E4C: " ์" THAI CHARACTER THANTHAKHAT -->
- <Key
- latin:keyLabel="์" />
- <Key
- latin:keyLabel="\?" />
- <!-- U+0E12: "ฒ" THAI CHARACTER THO PHUTHAO -->
- <Key
- latin:keyLabel="ฒ" />
- <!-- U+0E2C: "ฬ" THAI CHARACTER LO CHULA -->
- <Key
- latin:keyLabel="ฬ" />
- <!-- U+0E26: "ฦ" THAI CHARACTER LU -->
- <Key
- latin:keyLabel="ฦ" />
- </case>
- <default>
- <!-- U+0E1C: "ผ" THAI CHARACTER PHO PHUNG -->
- <Key
- latin:keyLabel="ผ" />
- <!-- U+0E1B: "ป" THAI CHARACTER PO PLA -->
- <Key
- latin:keyLabel="ป" />
- <!-- U+0E41: "แ" THAI CHARACTER SARA AE -->
- <Key
- latin:keyLabel="แ" />
- <!-- U+0E2D: "อ" THAI CHARACTER O ANG -->
- <Key
- latin:keyLabel="อ" />
- <!-- U+0E34: " ิ" THAI CHARACTER SARA I -->
- <Key
- latin:keyLabel="ิ" />
- <!-- U+0E37: " ื" THAI CHARACTER SARA UEE -->
- <Key
- latin:keyLabel="ื" />
- <!-- U+0E17: "ท" THAI CHARACTER THO THAHAN -->
- <Key
- latin:keyLabel="ท" />
- <!-- U+0E21: "ม" THAI CHARACTER MO MA -->
- <Key
- latin:keyLabel="ม" />
- <!-- U+0E43: "ใ" THAI CHARACTER SARA AI MAIMUAN -->
- <Key
- latin:keyLabel="ใ" />
- <!-- U+0E1D: "ฝ" THAI CHARACTER FO FA -->
- <Key
- latin:keyLabel="ฝ" />
- </default>
- </switch>
-</merge>
diff --git a/java/res/xml-sw600dp/rows_thai.xml b/java/res/xml-sw600dp/rows_thai.xml
index c1fe55b..bc89640 100644
--- a/java/res/xml-sw600dp/rows_thai.xml
+++ b/java/res/xml-sw600dp/rows_thai.xml
@@ -27,8 +27,7 @@
latin:keyWidth="7.5%p"
>
<include
- latin:keyboardLayout="@xml/rowkeys_thai1"
- latin:keyXPos="3.75%p" />
+ latin:keyboardLayout="@xml/rowkeys_thai1" />
<Key
latin:keyStyle="deleteKeyStyle"
latin:keyWidth="fillRight" />
@@ -38,14 +37,16 @@
>
<include
latin:keyboardLayout="@xml/rowkeys_thai2"
- latin:keyXPos="0.719%p" />
+ latin:keyXPos="2.5%p" />
+ <include
+ latin:keyboardLayout="@xml/key_thai_kho_khuat" />
</Row>
<Row
latin:keyWidth="7.5%p"
>
<include
latin:keyboardLayout="@xml/rowkeys_thai3"
- latin:keyXPos="3.75%p" />
+ latin:keyXPos="5.0%p" />
<Key
latin:keyStyle="enterKeyStyle"
latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw768dp-land/kbd_thai.xml b/java/res/xml-sw768dp-land/kbd_thai.xml
deleted file mode 100644
index b2cdbc3..0000000
--- a/java/res/xml-sw768dp-land/kbd_thai.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<Keyboard
- xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
- latin:rowHeight="20%p"
- latin:verticalGap="2.65%p"
- latin:touchPositionCorrectionData="@null"
->
- <include
- latin:keyboardLayout="@xml/rows_thai" />
-</Keyboard>
diff --git a/java/res/xml-sw768dp-land/kbd_thai_symbols.xml b/java/res/xml-sw768dp-land/kbd_thai_symbols.xml
deleted file mode 100644
index 1531458..0000000
--- a/java/res/xml-sw768dp-land/kbd_thai_symbols.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<Keyboard
- xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
- latin:rowHeight="20%p"
- latin:verticalGap="2.65%p"
- latin:touchPositionCorrectionData="@null"
->
- <include
- latin:keyboardLayout="@xml/rows_thai_symbols" />
-</Keyboard>
diff --git a/java/res/xml-sw768dp-land/kbd_thai_symbols_shift.xml b/java/res/xml-sw768dp-land/kbd_thai_symbols_shift.xml
deleted file mode 100644
index fa30f24..0000000
--- a/java/res/xml-sw768dp-land/kbd_thai_symbols_shift.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<Keyboard
- xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
- latin:rowHeight="20%p"
- latin:verticalGap="2.65%p"
- latin:touchPositionCorrectionData="@null"
->
- <include
- latin:keyboardLayout="@xml/rows_thai_symbols_shift" />
-</Keyboard>
diff --git a/java/res/xml-sw768dp/kbd_thai.xml b/java/res/xml-sw768dp/kbd_thai.xml
deleted file mode 100644
index 593ccbd..0000000
--- a/java/res/xml-sw768dp/kbd_thai.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<Keyboard
- xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
- latin:rowHeight="20%p"
- latin:verticalGap="2.95%p"
- latin:touchPositionCorrectionData="@null"
->
- <include
- latin:keyboardLayout="@xml/rows_thai" />
-</Keyboard>
diff --git a/java/res/xml-sw768dp/kbd_thai_symbols.xml b/java/res/xml-sw768dp/kbd_thai_symbols.xml
index e2e5f5d..0cd9a61 100644
--- a/java/res/xml-sw768dp/kbd_thai_symbols.xml
+++ b/java/res/xml-sw768dp/kbd_thai_symbols.xml
@@ -21,7 +21,9 @@
<Keyboard
xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
latin:rowHeight="20%p"
- latin:verticalGap="2.95%p"
+ latin:verticalGap="@fraction/key_bottom_gap_5row"
+ latin:keyLetterSize="@fraction/key_letter_ratio_5row"
+ latin:keyShiftedLetterHintRatio="@fraction/key_uppercase_letter_ratio_5row"
latin:touchPositionCorrectionData="@null"
>
<include
diff --git a/java/res/xml-sw768dp/kbd_thai_symbols_shift.xml b/java/res/xml-sw768dp/kbd_thai_symbols_shift.xml
index a1358d4..a68fec4 100644
--- a/java/res/xml-sw768dp/kbd_thai_symbols_shift.xml
+++ b/java/res/xml-sw768dp/kbd_thai_symbols_shift.xml
@@ -21,7 +21,9 @@
<Keyboard
xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
latin:rowHeight="20%p"
- latin:verticalGap="2.95%p"
+ latin:verticalGap="@fraction/key_bottom_gap_5row"
+ latin:keyLetterSize="@fraction/key_letter_ratio_5row"
+ latin:keyShiftedLetterHintRatio="@fraction/key_uppercase_letter_ratio_5row"
latin:touchPositionCorrectionData="@null"
>
<include
diff --git a/java/res/xml-sw768dp/rowkeys_thai_digits.xml b/java/res/xml-sw768dp/rowkeys_thai_digits.xml
index 5122830..55196eb 100644
--- a/java/res/xml-sw768dp/rowkeys_thai_digits.xml
+++ b/java/res/xml-sw768dp/rowkeys_thai_digits.xml
@@ -23,32 +23,42 @@
>
<!-- U+0E51: "๑" THAI DIGIT ONE -->
<Key
- latin:keyLabel="๑" />
+ latin:keyLabel="๑"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0E52: "๒" THAI DIGIT TWO -->
<Key
- latin:keyLabel="๒" />
+ latin:keyLabel="๒"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0E53: "๓" THAI DIGIT THREE -->
<Key
- latin:keyLabel="๓" />
+ latin:keyLabel="๓"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0E54: "๔" THAI DIGIT FOUR -->
<Key
- latin:keyLabel="๔" />
+ latin:keyLabel="๔"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0E55: "๕" THAI DIGIT FIVE -->
<Key
- latin:keyLabel="๕" />
+ latin:keyLabel="๕"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0E56: "๖" THAI DIGIT SIX -->
<Key
- latin:keyLabel="๖" />
+ latin:keyLabel="๖"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0E57: "๗" THAI DIGIT SEVEN -->
<Key
- latin:keyLabel="๗" />
+ latin:keyLabel="๗"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0E58: "๘" THAI DIGIT EIGHT -->
<Key
- latin:keyLabel="๘" />
+ latin:keyLabel="๘"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0E59: "๙" THAI DIGIT NINE -->
<Key
- latin:keyLabel="๙" />
+ latin:keyLabel="๙"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0E50: "๐" THAI DIGIT ZERO -->
<Key
- latin:keyLabel="๐" />
+ latin:keyLabel="๐"
+ latin:keyLabelFlags="fontNormal" />
</merge>
diff --git a/java/res/xml-sw768dp/rows_thai.xml b/java/res/xml-sw768dp/rows_thai.xml
index 7721bc5..5f9b383 100644
--- a/java/res/xml-sw768dp/rows_thai.xml
+++ b/java/res/xml-sw768dp/rows_thai.xml
@@ -28,7 +28,7 @@
>
<include
latin:keyboardLayout="@xml/rowkeys_thai1"
- latin:keyXPos="11.508%p" />
+ latin:keyXPos="3.799%p" />
<Key
latin:keyStyle="deleteKeyStyle"
latin:keyWidth="fillRight"/>
@@ -42,9 +42,11 @@
latin:keyWidth="7.969%p" />
<include
latin:keyboardLayout="@xml/rowkeys_thai2" />
+ <include
+ latin:keyboardLayout="@xml/key_thai_kho_khuat" />
</Row>
<Row
- latin:keyWidth="7.125%p"
+ latin:keyWidth="7.079%p"
>
<Key
latin:keyStyle="toSymbolKeyStyle"
diff --git a/java/res/xml/kbd_pcqwerty.xml b/java/res/xml/kbd_pcqwerty.xml
index cebca4f..777c71a 100644
--- a/java/res/xml/kbd_pcqwerty.xml
+++ b/java/res/xml/kbd_pcqwerty.xml
@@ -21,7 +21,9 @@
<Keyboard
xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
latin:rowHeight="20%p"
- latin:verticalGap="3.20%p"
+ latin:verticalGap="@fraction/key_bottom_gap_5row"
+ latin:keyLetterSize="@fraction/key_letter_ratio_5row"
+ latin:keyShiftedLetterHintRatio="@fraction/key_uppercase_letter_ratio_5row"
latin:touchPositionCorrectionData="@null"
>
<include
diff --git a/java/res/xml/kbd_pcqwerty_symbols.xml b/java/res/xml/kbd_pcqwerty_symbols.xml
index fd64e5b..a2297f7 100644
--- a/java/res/xml/kbd_pcqwerty_symbols.xml
+++ b/java/res/xml/kbd_pcqwerty_symbols.xml
@@ -21,7 +21,9 @@
<Keyboard
xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
latin:rowHeight="20%p"
- latin:verticalGap="3.20%p"
+ latin:verticalGap="@fraction/key_bottom_gap_5row"
+ latin:keyLetterSize="@fraction/key_letter_ratio_5row"
+ latin:keyShiftedLetterHintRatio="@fraction/key_uppercase_letter_ratio_5row"
latin:touchPositionCorrectionData="@null"
>
<include
diff --git a/java/res/xml/kbd_thai.xml b/java/res/xml/kbd_thai.xml
index 058ca16..b4a4a0b 100644
--- a/java/res/xml/kbd_thai.xml
+++ b/java/res/xml/kbd_thai.xml
@@ -20,6 +20,11 @@
<Keyboard
xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+ latin:rowHeight="20%p"
+ latin:verticalGap="@fraction/key_bottom_gap_5row"
+ latin:keyLetterSize="@fraction/key_letter_ratio_5row"
+ latin:keyShiftedLetterHintRatio="@fraction/key_uppercase_letter_ratio_5row"
+ latin:touchPositionCorrectionData="@null"
>
<include
latin:keyboardLayout="@xml/rows_thai" />
diff --git a/java/res/xml/key_thai_kho_khuat.xml b/java/res/xml/key_thai_kho_khuat.xml
new file mode 100644
index 0000000..0ffd0f9
--- /dev/null
+++ b/java/res/xml/key_thai_kho_khuat.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+ xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+ <switch>
+ <case
+ latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
+ >
+ <!-- U+0E05: "ฅ" THAI CHARACTER KHO KHON -->
+ <Key
+ latin:keyLabel="ฅ"
+ latin:keyLabelFlags="fontNormal" />
+ </case>
+ <default>
+ <!-- U+0E03: "ฃ" THAI CHARACTER KHO KHUAT -->
+ <Key
+ latin:keyLabel="ฃ"
+ latin:keyLabelFlags="fontNormal" />
+ </default>
+ </switch>
+</merge>
diff --git a/java/res/xml/method.xml b/java/res/xml/method.xml
index acdf764..613e9f6 100644
--- a/java/res/xml/method.xml
+++ b/java/res/xml/method.xml
@@ -34,6 +34,7 @@
el: Greek/greek
en_US: English United States/qwerty
en_GB: English Great Britain/qwerty
+ eo: Esperanto/spanish
es: Spanish/spanish
et: Estonian/nordic
fa: Persian/arabic
@@ -154,6 +155,12 @@
/>
<subtype android:icon="@drawable/ic_subtype_keyboard"
android:label="@string/subtype_generic"
+ android:imeSubtypeLocale="eo"
+ android:imeSubtypeMode="keyboard"
+ android:imeSubtypeExtraValue="KeyboardLayoutSet=spanish"
+ />
+ <subtype android:icon="@drawable/ic_subtype_keyboard"
+ android:label="@string/subtype_generic"
android:imeSubtypeLocale="es"
android:imeSubtypeMode="keyboard"
android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
diff --git a/java/res/xml/rowkeys_arabic1.xml b/java/res/xml/rowkeys_arabic1.xml
index b1bf790..a4bef83 100644
--- a/java/res/xml/rowkeys_arabic1.xml
+++ b/java/res/xml/rowkeys_arabic1.xml
@@ -26,13 +26,15 @@
<Key
latin:keyLabel="ض"
latin:keyHintLabel="1"
- latin:additionalMoreKeys="1,١" />
+ latin:additionalMoreKeys="1,١"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0635: "ص" ARABIC LETTER SAD
U+0662: "٢" ARABIC-INDIC DIGIT TWO -->
<Key
latin:keyLabel="ص"
latin:keyHintLabel="2"
- latin:additionalMoreKeys="2,٢" />
+ latin:additionalMoreKeys="2,٢"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0642: "ق" ARABIC LETTER QAF
U+06A8: "ڨ" ARABIC LETTER QAF WITH THREE DOTS ABOVE
U+0663: "٣" ARABIC-INDIC DIGIT THREE -->
@@ -41,7 +43,8 @@
latin:keyLabel="ق"
latin:keyHintLabel="3"
latin:additionalMoreKeys="3,٣"
- latin:moreKeys="ڨ" />
+ latin:moreKeys="ڨ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0641: "ف" ARABIC LETTER FEH
U+06A4: "ڤ" ARABIC LETTER VEH
U+06A2: "ڢ" ARABIC LETTER FEH WITH DOT MOVED BELOW
@@ -53,19 +56,22 @@
latin:keyLabel="ف"
latin:keyHintLabel="4"
latin:additionalMoreKeys="4,٤"
- latin:moreKeys="ڤ,ڢ,ڥ" />
+ latin:moreKeys="ڤ,ڢ,ڥ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+063A: "غ" ARABIC LETTER GHAIN
U+0665: "٥" ARABIC-INDIC DIGIT FIVE -->
<Key
latin:keyLabel="غ"
latin:keyHintLabel="5"
- latin:additionalMoreKeys="5,٥" />
+ latin:additionalMoreKeys="5,٥"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0639: "ع" ARABIC LETTER AIN
U+0666: "٦" ARABIC-INDIC DIGIT SIX -->
<Key
latin:keyLabel="ع"
latin:keyHintLabel="6"
- latin:additionalMoreKeys="6,٦" />
+ latin:additionalMoreKeys="6,٦"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0647: "ه" ARABIC LETTER HEH
U+FEEB: "ﻫ" ARABIC LETTER HEH INITIAL FORM
U+0647 U+200D: ARABIC LETTER HEH + ZERO WIDTH JOINER
@@ -74,19 +80,22 @@
latin:keyLabel="ه"
latin:keyHintLabel="7"
latin:additionalMoreKeys="7,٧"
- latin:moreKeys="ﻫ|ه‍" />
+ latin:moreKeys="ﻫ|ه‍"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+062E: "خ" ARABIC LETTER KHAH
U+0668: "٨" ARABIC-INDIC DIGIT EIGHT -->
<Key
latin:keyLabel="خ"
latin:keyHintLabel="8"
- latin:additionalMoreKeys="8,٨" />
+ latin:additionalMoreKeys="8,٨"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+062D: "ح" ARABIC LETTER HAH
U+0669: "٩" ARABIC-INDIC DIGIT NINE -->
<Key
latin:keyLabel="ح"
latin:keyHintLabel="9"
- latin:additionalMoreKeys="9,٩" />
+ latin:additionalMoreKeys="9,٩"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+062C: "ج" ARABIC LETTER JEEM
U+0686: "چ" ARABIC LETTER TCHEH
U+0660: "٠" ARABIC-INDIC DIGIT ZERO -->
@@ -94,5 +103,6 @@
latin:keyLabel="ج"
latin:keyHintLabel="0"
latin:additionalMoreKeys="0,٠"
- latin:moreKeys="چ" />
+ latin:moreKeys="چ"
+ latin:keyLabelFlags="fontNormal" />
</merge>
diff --git a/java/res/xml/rowkeys_arabic2.xml b/java/res/xml/rowkeys_arabic2.xml
index f86aae0..d733f64 100644
--- a/java/res/xml/rowkeys_arabic2.xml
+++ b/java/res/xml/rowkeys_arabic2.xml
@@ -26,21 +26,25 @@
<!-- TODO: DroidSansArabic lacks the glyph of U+069C ARABIC LETTER SEEN WITH THREE DOTS BELOW AND THREE DOTS ABOVE -->
<Key
latin:keyLabel="ش"
- latin:moreKeys="ڜ" />
+ latin:moreKeys="ڜ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0633: "س" ARABIC LETTER SEEN -->
<Key
- latin:keyLabel="س" />
+ latin:keyLabel="س"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+064A: "ي" ARABIC LETTER YEH
U+0626: "ئ" ARABIC LETTER YEH WITH HAMZA ABOVE
U+0649: "ى" ARABIC LETTER ALEF MAKSURA -->
<Key
latin:keyLabel="ي"
- latin:moreKeys="ئ,ى" />
+ latin:moreKeys="ئ,ى"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0628: "ب" ARABIC LETTER BEH
U+067E: "پ" ARABIC LETTER PEH -->
<Key
latin:keyLabel="ب"
- latin:moreKeys="پ" />
+ latin:moreKeys="پ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0644: "ل" ARABIC LETTER LAM
U+FEFB: "ﻻ" ARABIC LIGATURE LAM WITH ALEF ISOLATED FORM
U+0627: "ا" ARABIC LETTER ALEF
@@ -52,7 +56,8 @@
U+0622: "آ" ARABIC LETTER ALEF WITH MADDA ABOVE -->
<Key
latin:keyLabel="ل"
- latin:moreKeys="ﻻ|لا,ﻷ|لأ,ﻹ|لإ,ﻵ|لآ" />
+ latin:moreKeys="ﻻ|لا,ﻷ|لأ,ﻹ|لإ,ﻵ|لآ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0627: "ا" ARABIC LETTER ALEF
U+0621: "ء" ARABIC LETTER HAMZA
U+0671: "ٱ" ARABIC LETTER ALEF WASLA
@@ -61,23 +66,27 @@
U+0622: "آ" ARABIC LETTER ALEF WITH MADDA ABOVE -->
<Key
latin:keyLabel="ا"
- latin:moreKeys="ء,ٱ,أ,إ,آ" />
+ latin:moreKeys="ء,ٱ,أ,إ,آ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+062A: "ت" ARABIC LETTER TEH
U+062B: "ﺙ" ARABIC LETTER THEH -->
<Key
latin:keyLabel="ت"
- latin:moreKeys="ث" />
+ latin:moreKeys="ث"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0646: "ن" ARABIC LETTER NOON -->
<Key
- latin:keyLabel="ن" />
+ latin:keyLabel="ن"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0645: "م" ARABIC LETTER MEEM -->
<Key
- latin:keyLabel="م" />
+ latin:keyLabel="م"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0643: "ك" ARABIC LETTER KAF
U+06AF: "گ" ARABIC LETTER GAF
U+06A9: "ک" ARABIC LETTER KEHEH -->
<Key
latin:keyLabel="ك"
latin:moreKeys="گ,ک"
- latin:keyWidth="fillRight" />
+ latin:keyLabelFlags="fontNormal" />
</merge>
diff --git a/java/res/xml/rowkeys_arabic3.xml b/java/res/xml/rowkeys_arabic3.xml
index 9e9eac0..e4e6948 100644
--- a/java/res/xml/rowkeys_arabic3.xml
+++ b/java/res/xml/rowkeys_arabic3.xml
@@ -23,30 +23,38 @@
>
<!-- U+0638: "ظ" ARABIC LETTER ZAH -->
<Key
- latin:keyLabel="ظ" />
+ latin:keyLabel="ظ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0637: "ط" ARABIC LETTER TAH -->
<Key
- latin:keyLabel="ط" />
+ latin:keyLabel="ط"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0630: "ذ" ARABIC LETTER THAL -->
<Key
- latin:keyLabel="ذ" />
+ latin:keyLabel="ذ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+062F: "د" ARABIC LETTER DAL -->
<Key
- latin:keyLabel="د" />
+ latin:keyLabel="د"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0632: "ز" ARABIC LETTER ZAIN
U+0698: "ژ" ARABIC LETTER JEH -->
<Key
latin:keyLabel="ز"
- latin:moreKeys="ژ" />
+ latin:moreKeys="ژ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0631: "ر" ARABIC LETTER REH -->
<Key
- latin:keyLabel="ر" />
+ latin:keyLabel="ر"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0629: "ة" ARABIC LETTER TEH MARBUTA -->
<Key
- latin:keyLabel="ة" />
+ latin:keyLabel="ة"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0648: "و" ARABIC LETTER WAW
U+0624: "ﺅ" ARABIC LETTER WAW WITH HAMZA ABOVE -->
<Key
latin:keyLabel="و"
- latin:moreKeys="ؤ" />
+ latin:moreKeys="ؤ"
+ latin:keyLabelFlags="fontNormal" />
</merge>
diff --git a/java/res/xml/rowkeys_farsi1.xml b/java/res/xml/rowkeys_farsi1.xml
index 840b048..0ccf1ab 100644
--- a/java/res/xml/rowkeys_farsi1.xml
+++ b/java/res/xml/rowkeys_farsi1.xml
@@ -28,31 +28,36 @@
latin:keyLabel="ص"
latin:moreKeys="ض,%"
latin:keyHintLabel="۱"
- latin:additionalMoreKeys="۱,1" />
+ latin:additionalMoreKeys="۱,1"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0642: "ق" ARABIC LETTER QAF
U+06F2: "۲" EXTENDED ARABIC-INDIC DIGIT TWO -->
<Key
latin:keyLabel="ق"
latin:keyHintLabel="۲"
- latin:additionalMoreKeys="۲,2" />
+ latin:additionalMoreKeys="۲,2"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0641: "ف" ARABIC LETTER FEH
U+06F3: "۳" EXTENDED ARABIC-INDIC DIGIT THREE -->
<Key
latin:keyLabel="ف"
latin:keyHintLabel="۳"
- latin:additionalMoreKeys="۳,3" />
+ latin:additionalMoreKeys="۳,3"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+063A: "غ" ARABIC LETTER GHAIN
U+06F4: "۴" EXTENDED ARABIC-INDIC DIGIT FOUR -->
<Key
latin:keyLabel="غ"
latin:keyHintLabel="۴"
- latin:additionalMoreKeys="۴,4" />
+ latin:additionalMoreKeys="۴,4"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0639: "ع" ARABIC LETTER AIN
U+06F5: "۵" EXTENDED ARABIC-INDIC DIGIT FIVE -->
<Key
latin:keyLabel="ع"
latin:keyHintLabel="۵"
- latin:additionalMoreKeys="۵,5" />
+ latin:additionalMoreKeys="۵,5"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0647: "ه" ARABIC LETTER HEH
U+FEEB: "ﻫ" ARABIC LETTER HEH INITIAL FORM
U+0647/U+200D: ARABIC LETTER HEH + ZERO WIDTH JOINER
@@ -63,29 +68,34 @@
latin:keyLabel="ه"
latin:moreKeys="ﻫ|ه‍,هٔ,ة,%"
latin:keyHintLabel="۶"
- latin:additionalMoreKeys="۶,6" />
+ latin:additionalMoreKeys="۶,6"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+062E: "خ" ARABIC LETTER KHAH
U+06F7: "۷" EXTENDED ARABIC-INDIC DIGIT SEVEN -->
<Key
latin:keyLabel="خ"
latin:keyHintLabel="۷"
- latin:additionalMoreKeys="۷,7" />
+ latin:additionalMoreKeys="۷,7"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+062D: "ح" ARABIC LETTER HAH
U+06F8: "۸" EXTENDED ARABIC-INDIC DIGIT EIGHT -->
<Key
latin:keyLabel="ح"
latin:keyHintLabel="۸"
- latin:additionalMoreKeys="۸,8" />
+ latin:additionalMoreKeys="۸,8"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+062C: "ج" ARABIC LETTER JEEM
U+06F9: "۹" EXTENDED ARABIC-INDIC DIGIT NINE -->
<Key
latin:keyLabel="ج"
latin:keyHintLabel="۹"
- latin:additionalMoreKeys="۹,9" />
+ latin:additionalMoreKeys="۹,9"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0686: "چ" ARABIC LETTER TCHEH
U+06F0: "۰" EXTENDED ARABIC-INDIC DIGIT ZERO -->
<Key
latin:keyLabel="چ"
latin:keyHintLabel="۰"
- latin:additionalMoreKeys="۰,0" />
+ latin:additionalMoreKeys="۰,0"
+ latin:keyLabelFlags="fontNormal" />
</merge>
diff --git a/java/res/xml/rowkeys_farsi2.xml b/java/res/xml/rowkeys_farsi2.xml
index 2154893..4b6abe2 100644
--- a/java/res/xml/rowkeys_farsi2.xml
+++ b/java/res/xml/rowkeys_farsi2.xml
@@ -23,12 +23,14 @@
>
<!-- U+0634: "ش" ARABIC LETTER SHEEN -->
<Key
- latin:keyLabel="ش" />
+ latin:keyLabel="ش"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0633: "س" ARABIC LETTER SEEN
U+0636: "ض" ARABIC LETTER DAD -->
<Key
latin:keyLabel="س"
- latin:moreKeys="ض" />
+ latin:moreKeys="ض"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+06CC: "ی" ARABIC LETTER FARSI YEH
U+0626: "ئ" ARABIC LETTER YEH WITH HAMZA ABOVE
U+064A: "ي" ARABIC LETTER YEH
@@ -36,13 +38,16 @@
U+0649: "ى" ARABIC LETTER ALEF MAKSURA -->
<Key
latin:keyLabel="ی"
- latin:moreKeys="ئ,ي,ﯨ|ى" />
+ latin:moreKeys="ئ,ي,ﯨ|ى"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0628: "ب" ARABIC LETTER BEH -->
<Key
- latin:keyLabel="ب" />
+ latin:keyLabel="ب"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0644: "ل" ARABIC LETTER LAM -->
<Key
- latin:keyLabel="ل" />
+ latin:keyLabel="ل"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0627: "ا" ARABIC LETTER ALEF
U+0621: "ء" ARABIC LETTER HAMZA
U+0622: "آ" ARABIC LETTER ALEF WITH MADDA ABOVE
@@ -51,22 +56,27 @@
U+0625: "إ" ARABIC LETTER ALEF WITH HAMZA BELOW -->
<Key
latin:keyLabel="ا"
- latin:moreKeys="ء,آ,أ,ٱ,إ" />
+ latin:moreKeys="ء,آ,أ,ٱ,إ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+062A: "ت" ARABIC LETTER TEH
U+062B: "ﺙ" ARABIC LETTER THEH
U+0629: "ة": ARABIC LETTER TEH MARBUTA -->
<Key
latin:keyLabel="ت"
- latin:moreKeys="ث,ة" />
+ latin:moreKeys="ث,ة"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0646: "ن" ARABIC LETTER NOON -->
<Key
- latin:keyLabel="ن" />
+ latin:keyLabel="ن"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0645: "م" ARABIC LETTER MEEM -->
<Key
- latin:keyLabel="م" />
+ latin:keyLabel="م"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+06A9: "ک" ARABIC LETTER KEHEH
U+0643: "ك" ARABIC LETTER KAF -->
<Key
latin:keyLabel="ک"
- latin:moreKeys="ك" />
+ latin:moreKeys="ك"
+ latin:keyLabelFlags="fontNormal" />
</merge>
diff --git a/java/res/xml/rowkeys_farsi3.xml b/java/res/xml/rowkeys_farsi3.xml
index 29c3513..7d2e81f 100644
--- a/java/res/xml/rowkeys_farsi3.xml
+++ b/java/res/xml/rowkeys_farsi3.xml
@@ -25,30 +25,38 @@
U+0638: "ظ" ARABIC LETTER ZAH -->
<Key
latin:keyLabel="ط"
- latin:moreKeys="ظ" />
+ latin:moreKeys="ظ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0632: "ز" ARABIC LETTER ZAIN
U+0698: "ژ" ARABIC LETTER JEH -->
<Key
latin:keyLabel="ز"
- latin:moreKeys="ژ" />
+ latin:moreKeys="ژ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0631: "ر" ARABIC LETTER REH -->
<Key
- latin:keyLabel="ر" />
+ latin:keyLabel="ر"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0630: "ذ" ARABIC LETTER THAL -->
<Key
- latin:keyLabel="ذ" />
+ latin:keyLabel="ذ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+062F: "د" ARABIC LETTER DAL -->
<Key
- latin:keyLabel="د" />
+ latin:keyLabel="د"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+067E: "پ" ARABIC LETTER PEH -->
<Key
- latin:keyLabel="پ" />
+ latin:keyLabel="پ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0648: "و" ARABIC LETTER WAW
U+0624: "ؤ" ARABIC LETTER WAW WITH HAMZA ABOVE -->
<Key
latin:keyLabel="و"
- latin:moreKeys="ؤ" />
+ latin:moreKeys="ؤ"
+ latin:keyLabelFlags="fontNormal" />
<!-- U+06AF: "گ" ARABIC LETTER GAF -->
<Key
- latin:keyLabel="گ" />
+ latin:keyLabel="گ"
+ latin:keyLabelFlags="fontNormal" />
</merge>
diff --git a/java/res/xml/rowkeys_qwerty1.xml b/java/res/xml/rowkeys_qwerty1.xml
index 84d6134..e7c9b59 100644
--- a/java/res/xml/rowkeys_qwerty1.xml
+++ b/java/res/xml/rowkeys_qwerty1.xml
@@ -22,11 +22,12 @@
xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
>
<Key
- latin:keyLabel="q"
+ latin:keyLabel="!text/keylabel_for_q"
latin:keyHintLabel="1"
- latin:additionalMoreKeys="1" />
+ latin:additionalMoreKeys="1"
+ latin:moreKeys="!text/more_keys_for_q" />
<Key
- latin:keyLabel="w"
+ latin:keyLabel="!text/keylabel_for_w"
latin:keyHintLabel="2"
latin:additionalMoreKeys="2"
latin:moreKeys="!text/more_keys_for_w" />
@@ -46,7 +47,7 @@
latin:additionalMoreKeys="5"
latin:moreKeys="!text/more_keys_for_t" />
<Key
- latin:keyLabel="y"
+ latin:keyLabel="!text/keylabel_for_y"
latin:keyHintLabel="6"
latin:additionalMoreKeys="6"
latin:moreKeys="!text/more_keys_for_y" />
diff --git a/java/res/xml/rowkeys_qwerty3.xml b/java/res/xml/rowkeys_qwerty3.xml
index a74aeb8..b70fd72 100644
--- a/java/res/xml/rowkeys_qwerty3.xml
+++ b/java/res/xml/rowkeys_qwerty3.xml
@@ -25,7 +25,8 @@
latin:keyLabel="z"
latin:moreKeys="!text/more_keys_for_z" />
<Key
- latin:keyLabel="x" />
+ latin:keyLabel="!text/keylabel_for_x"
+ latin:moreKeys="!text/more_keys_for_x" />
<Key
latin:keyLabel="c"
latin:moreKeys="!text/more_keys_for_c" />
diff --git a/java/res/xml/rowkeys_spanish2.xml b/java/res/xml/rowkeys_spanish2.xml
index 4c7e579..335dff3 100644
--- a/java/res/xml/rowkeys_spanish2.xml
+++ b/java/res/xml/rowkeys_spanish2.xml
@@ -25,5 +25,5 @@
latin:keyboardLayout="@xml/rowkeys_qwerty2" />
<!-- U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE -->
<Key
- latin:keyLabel="ñ" />
+ latin:keyLabel="!text/keylabel_for_spanish_row2_10" />
</merge>
diff --git a/java/res/xml/rowkeys_thai1.xml b/java/res/xml/rowkeys_thai1.xml
index 4b49da1..950d2a4 100644
--- a/java/res/xml/rowkeys_thai1.xml
+++ b/java/res/xml/rowkeys_thai1.xml
@@ -25,100 +25,110 @@
<case
latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
>
- <!-- U+0E0E: "ฎ" THAI CHARACTER DO CHADA -->
<Key
- latin:keyLabel="ฎ" />
- <!-- U+0E11: "ฑ" THAI CHARACTER THO NANGMONTHO -->
- <Key
- latin:keyLabel="ฑ" />
- <!-- U+0E18: "ธ" THAI CHARACTER THO THONG -->
- <Key
- latin:keyLabel="ธ" />
- <!-- U+0E13: "ณ" THAI CHARACTER NO NEN -->
- <Key
- latin:keyLabel="ณ" />
- <!-- U+0E0D: "ญ" THAI CHARACTER YO YING -->
- <Key
- latin:keyLabel="ญ" />
- <!-- U+0E10: "ฐ" THAI CHARACTER THO THAN -->
- <Key
- latin:keyLabel="ฐ" />
- <!-- U+0E03: "ฃ" THAI CHARACTER KHO KHUAT -->
- <Key
- latin:keyLabel="ฃ" />
- <!-- U+0E05: "ฅ" THAI CHARACTER KHO KHON -->
- <Key
- latin:keyLabel="ฅ" />
- <!-- U+0E51: "๑" THAI DIGIT ONE
- U+0E52: "๒" THAI DIGIT TWO
- U+0E53: "๓" THAI DIGIT THREE
- U+0E54: "๔" THAI DIGIT FOUR
- U+0E55: "๕" THAI DIGIT FIVE -->
+ latin:keyLabel="+" />
+ <!-- U+0E51: "๑" THAI DIGIT ONE -->
<Key
latin:keyLabel="๑"
- latin:moreKeys="!fixedColumnOrder!4,๒,๓,๔,๕" />
- <!-- U+0E56: "๖" THAI DIGIT SIX
- U+0E57: "๗" THAI DIGIT SEVEN
- U+0E58: "๘" THAI DIGIT EIGHT
- U+0E59: "๙" THAI DIGIT NINE
- U+0E50: "๐" THAI DIGIT ZERO -->
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E52: "๒" THAI DIGIT TWO -->
+ <Key
+ latin:keyLabel="๒"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E53: "๓" THAI DIGIT THREE -->
+ <Key
+ latin:keyLabel="๓"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E54: "๔" THAI DIGIT FOUR -->
+ <Key
+ latin:keyLabel="๔"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0020: " " SPACE
+ U+0E39: " ู" THAI CHARACTER SARA UU -->
+ <!-- Note: The space character is needed as a preceding letter to draw some Thai
+ composing characters correctly. -->
+ <Key
+ latin:keyLabel=" ู"
+ latin:code="0x0E39"
+ latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+ <!-- U+0E3F: "฿" THAI CURRENCY SYMBOL BAHT -->
+ <Key
+ latin:keyLabel="฿"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E55: "๕" THAI DIGIT FIVE -->
+ <Key
+ latin:keyLabel="๕"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E56: "๖" THAI DIGIT SIX -->
<Key
latin:keyLabel="๖"
- latin:moreKeys="!fixedColumnOrder!4,๗,๘,๙,๐" />
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E57: "๗" THAI DIGIT SEVEN -->
+ <Key
+ latin:keyLabel="๗"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E58: "๘" THAI DIGIT EIGHT -->
+ <Key
+ latin:keyLabel="๘"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E59: "๙" THAI DIGIT NINE -->
+ <Key
+ latin:keyLabel="๙"
+ latin:keyLabelFlags="fontNormal" />
</case>
<default>
+ <!-- U+0E45: "ๅ" THAI CHARACTER LAKKHANGYAO -->
+ <Key
+ latin:keyLabel="ๅ"
+ latin:keyLabelFlags="fontNormal" />
+ <Key
+ latin:keyLabel="/" />
+ <Key
+ latin:keyLabel="_" />
<!-- U+0E20: "ภ" THAI CHARACTER PHO SAMPHAO -->
<Key
latin:keyLabel="ภ"
- latin:keyHintLabel="1"
- latin:additionalMoreKeys="1,๑" />
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0E16: "ถ" THAI CHARACTER THO THUNG -->
<Key
latin:keyLabel="ถ"
- latin:keyHintLabel="2"
- latin:additionalMoreKeys="2,๒" />
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0020: " " SPACE
+ U+0E38: " ุ" THAI CHARACTER SARA U -->
+ <!-- Note: The space character is needed as a preceding letter to draw some Thai
+ composing characters correctly. -->
+ <Key
+ latin:keyLabel=" ุ"
+ latin:code="0x0E38"
+ latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+ <!-- U+0020: " " SPACE
+ U+0E36: " ึ" THAI CHARACTER SARA UE -->
+ <!-- Note: The space character is needed as a preceding letter to draw some Thai
+ composing characters correctly. -->
+ <Key
+ latin:keyLabel=" ึ"
+ latin:code="0x0E36"
+ latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
<!-- U+0E04: "ค" THAI CHARACTER KHO KHWAI -->
<Key
latin:keyLabel="ค"
- latin:keyHintLabel="3"
- latin:additionalMoreKeys="3,๓" />
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0E15: "ต" THAI CHARACTER TO TAO -->
<Key
latin:keyLabel="ต"
- latin:keyHintLabel="4"
- latin:additionalMoreKeys="4,๔" />
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0E08: "จ" THAI CHARACTER CHO CHAN -->
<Key
latin:keyLabel="จ"
- latin:keyHintLabel="5"
- latin:additionalMoreKeys="5,๕" />
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0E02: "ข" THAI CHARACTER KHO KHAI -->
<Key
latin:keyLabel="ข"
- latin:keyHintLabel="6"
- latin:additionalMoreKeys="6,๖" />
+ latin:keyLabelFlags="fontNormal" />
<!-- U+0E0A: "ช" THAI CHARACTER CHO CHANG -->
<Key
latin:keyLabel="ช"
- latin:keyHintLabel="7"
- latin:additionalMoreKeys="7,๗" />
- <!-- U+0E23: "ร" THAI CHARACTER RO RUA
- U+0E25: "ล" THAI CHARACTER LO LING -->
- <Key
- latin:keyLabel="ร"
- latin:moreKeys="ล"
- latin:keyHintLabel="8"
- latin:additionalMoreKeys="8,๘" />
- <!-- U+0E19: "น" THAI CHARACTER NO NU -->
- <Key
- latin:keyLabel="น"
- latin:keyHintLabel="9"
- latin:additionalMoreKeys="9,๙" />
- <!-- U+0E22: "ย" THAI CHARACTER YO YAK -->
- <Key
- latin:keyLabel="ย"
- latin:keyHintLabel="0"
- latin:additionalMoreKeys="0,๐" />
+ latin:keyLabelFlags="fontNormal" />
</default>
</switch>
</merge>
diff --git a/java/res/xml/rowkeys_thai2.xml b/java/res/xml/rowkeys_thai2.xml
index 80e3563..f602994 100644
--- a/java/res/xml/rowkeys_thai2.xml
+++ b/java/res/xml/rowkeys_thai2.xml
@@ -25,83 +25,116 @@
<case
latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
>
- <!-- U+0E24: "ฤ" THAI CHARACTER RU -->
+ <!-- U+0E50: "๐" THAI DIGIT ZERO -->
<Key
- latin:keyLabel="ฤ" />
- <!-- U+0E06: "ฆ" THAI CHARACTER KHO RAKHANG -->
+ latin:keyLabel="๐"
+ latin:keyLabelFlags="fontNormal" />
<Key
- latin:keyLabel="ฆ" />
- <!-- U+0E0F: "ฏ" THAI CHARACTER TO PATAK -->
+ latin:keyLabel=""" />
+ <!-- U+0E0E: "ฎ" THAI CHARACTER DO CHADA -->
<Key
- latin:keyLabel="ฏ" />
- <!-- U+0E0C: "ฌ" THAI CHARACTER CHO CHOE -->
+ latin:keyLabel="ฎ"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E11: "ฑ" THAI CHARACTER THO NANGMONTHO -->
<Key
- latin:keyLabel="ฌ" />
- <!-- U+0E29: "ษ" THAI CHARACTER SO RUSI -->
+ latin:keyLabel="ฑ"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E18: "ธ" THAI CHARACTER THO THONG -->
<Key
- latin:keyLabel="ษ" />
- <!-- U+0E28: "ศ" THAI CHARACTER SO SALA -->
+ latin:keyLabel="ธ"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0020: " " SPACE
+ U+0E4D: " ํ" THAI CHARACTER THANTHAKHAT -->
+ <!-- Note: The space character is needed as a preceding letter to draw some Thai
+ composing characters correctly. -->
<Key
- latin:keyLabel="ศ" />
- <!-- U+0E0B: "ซ" THAI CHARACTER SO SO -->
+ latin:keyLabel=" ํ"
+ latin:code="0x0E4D"
+ latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+ <!-- U+0020: " " SPACE
+ U+0E4A: " ๊" THAI CHARACTER MAI TRI -->
+ <!-- Note: The space character is needed as a preceding letter to draw some Thai
+ composing characters correctly. -->
<Key
- latin:keyLabel="ซ" />
- <!-- U+0E3F: "฿" THAI CURRENCY SYMBOL BAHT
- U+0E45: "ๅ" THAI CHARACTER LAKKHANGYAO -->
+ latin:keyLabel=" ๊"
+ latin:code="0x0E4A"
+ latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+ <!-- U+0E13: "ณ" THAI CHARACTER NO NEN -->
<Key
- latin:keyLabel="฿"
- latin:moreKeys="ๅ" />
- <!-- U+0E46: "ๆ" THAI CHARACTER MAIYAMOK
- U+0E2F: "ฯ" THAI CHARACTER PAIYANNOI -->
+ latin:keyLabel="ณ"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E2F: "ฯ" THAI CHARACTER PAIYANNOI -->
<Key
- latin:keyLabel="ๆ"
- latin:moreKeys="ฯ" />
+ latin:keyLabel="ฯ"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E0D: "ญ" THAI CHARACTER YO YING -->
+ <Key
+ latin:keyLabel="ญ"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E10: "ฐ" THAI CHARACTER THO THAN -->
+ <Key
+ latin:keyLabel="ฐ"
+ latin:keyLabelFlags="fontNormal" />
+ <Key
+ latin:keyLabel="," />
</case>
<default>
- <!-- U+0E1F: "ฟ" THAI CHARACTER FO FAN
- U+0E1E: "พ" THAI CHARACTER PHO PHAN -->
+ <!-- U+0E46: "ๆ" THAI CHARACTER MAIYAMOK -->
<Key
- latin:keyLabel="ฟ"
- latin:moreKeys="พ" />
- <!-- U+0E2B: "ห" THAI CHARACTER HO HIP -->
+ latin:keyLabel="ๆ"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E44: "ไ" THAI CHARACTER SARA AI MAIMALAI -->
<Key
- latin:keyLabel="ห" />
- <!-- U+0E01: "ก" THAI CHARACTER KO KAI -->
+ latin:keyLabel="ไ"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E33: "ำ" THAI CHARACTER SARA AM -->
<Key
- latin:keyLabel="ก" />
- <!-- U+0E14: "ด" THAI CHARACTER DO DEK -->
+ latin:keyLabel="ำ"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E1E: "พ" THAI CHARACTER PHO PHAN -->
<Key
- latin:keyLabel="ด" />
- <!-- U+0E2A: "ส" THAI CHARACTER SO SUA -->
- <Key
- latin:keyLabel="ส" />
- <!-- U+0E27: "ว" THAI CHARACTER WO WAEN -->
- <Key
- latin:keyLabel="ว" />
- <!-- U+0E07: "ง" THAI CHARACTER NGO NGU -->
- <Key
- latin:keyLabel="ง" />
- <!-- U+0E30: "ะ" THAI CHARACTER SARA A
- U+0E32: "า" THAI CHARACTER SARA AA
- U+0E33: " ำ" THAI CHARACTER SARA AM
- U+0E40: "เ" THAI CHARACTER SARA E
- U+0E41: "แ" THAI CHARACTER SARA AE
- U+0E43: "ใ" THAI CHARACTER SARA AI MAIMUAN
- U+0E44: "ไ" THAI CHARACTER SARA AI MAIMALAI
- U+0E42: "โ" THAI CHARACTER SARA O -->
+ latin:keyLabel="พ"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E30: "ะ" THAI CHARACTER SARA A -->
<Key
latin:keyLabel="ะ"
- latin:moreKeys="า,ำ,เ,แ,ใ,ไ,โ" />
- <!-- U+0E31: " ั" THAI CHARACTER MAI HAN-AKAT
- U+0E34: " ิ" THAI CHARACTER SARA I
- U+0E35: " ี" THAI CHARACTER SARA II
- U+0E36: " ึ" THAI CHARACTER SARA UE
- U+0E37: " ื" THAI CHARACTER SARA UEE
- U+0E38: " ุ" THAI CHARACTER SARA U
- U+0E39: " ู" THAI CHARACTER SARA UU -->
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0020: " " SPACE
+ U+0E31: " ั" THAI CHARACTER MAI HAN-AKAT -->
+ <!-- Note: The space character is needed as a preceding letter to draw some Thai
+ composing characters correctly. -->
<Key
- latin:keyLabel="ั"
- latin:moreKeys="ิ,ี,ึ,ื,ุ,ู" />
+ latin:keyLabel=" ั"
+ latin:code="0x0E31"
+ latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+ <!-- U+0020: " " SPACE
+ U+0E35: " ี" HAI CHARACTER SARA II -->
+ <!-- Note: The space character is needed as a preceding letter to draw some Thai
+ composing characters correctly. -->
+ <Key
+ latin:keyLabel=" ี"
+ latin:code="0x0E35"
+ latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+ <!-- U+0E23: "ร" THAI CHARACTER RO RUA -->
+ <Key
+ latin:keyLabel="ร"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E19: "น" THAI CHARACTER NO NU -->
+ <Key
+ latin:keyLabel="น"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E22: "ย" THAI CHARACTER YO YAK -->
+ <Key
+ latin:keyLabel="ย"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E1A: "บ" THAI CHARACTER BO BAIMAI -->
+ <Key
+ latin:keyLabel="บ"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E25: "ล" THAI CHARACTER LO LING -->
+ <Key
+ latin:keyLabel="ล"
+ latin:keyLabelFlags="fontNormal" />
</default>
</switch>
</merge>
diff --git a/java/res/xml/rowkeys_thai3.xml b/java/res/xml/rowkeys_thai3.xml
index b833807..7b6e637 100644
--- a/java/res/xml/rowkeys_thai3.xml
+++ b/java/res/xml/rowkeys_thai3.xml
@@ -25,59 +25,110 @@
<case
latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
>
- <!-- U+0E09: "ฉ" THAI CHARACTER CHO CHING -->
+ <!-- U+0E24: "ฤ" THAI CHARACTER RU -->
<Key
- latin:keyLabel="ฉ" />
- <!-- U+0E2E: "ฮ" THAI CHARACTER HO NOKHUK -->
+ latin:keyLabel="ฤ"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E06: "ฆ" THAI CHARACTER KHO RAKHANG -->
<Key
- latin:keyLabel="ฮ" />
- <!-- U+0E12: "ฒ" THAI CHARACTER THO PHUTHAO -->
+ latin:keyLabel="ฆ"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E0F: "ฏ" THAI CHARACTER TO PATAK -->
<Key
- latin:keyLabel="ฒ" />
- <!-- U+0E2C: "ฬ" THAI CHARACTER LO CHULA -->
+ latin:keyLabel="ฏ"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E42: "โ" THAI CHARACTER SARA O -->
<Key
- latin:keyLabel="ฬ" />
- <!-- U+0E26: "ฦ" THAI CHARACTER LU -->
+ latin:keyLabel="โ"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E0C: "ฌ" THAI CHARACTER CHO CHOE -->
<Key
- latin:keyLabel="ฦ" />
- <!-- U+0E4C: " ์" THAI CHARACTER THANTHAKHAT
- U+0E4D: " ํ" THAI CHARACTER NIKHAHIT
- U+0E3A: " ฺ" THAI CHARACTER PHINTHU -->
+ latin:keyLabel="ฌ"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0020: " " SPACE
+ U+0E47: " ็" THAI CHARACTER MAITAIKHU -->
+ <!-- Note: The space character is needed as a preceding letter to draw some Thai
+ composing characters correctly. -->
<Key
- latin:keyLabel="์"
- latin:moreKeys="ํ,ฺ" />
- <!-- U+0E47: " ็" THAI CHARACTER MAITAIKHU -->
+ latin:keyLabel=" ็"
+ latin:code="0x0E47"
+ latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+ <!-- U+0020: " " SPACE
+ U+0E4B: " ๋" THAI CHARACTER MAI CHATTAWA -->
+ <!-- Note: The space character is needed as a preceding letter to draw some Thai
+ composing characters correctly. -->
<Key
- latin:keyLabel="็" />
+ latin:keyLabel=" ๋"
+ latin:code="0x0E4B"
+ latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+ <!-- U+0E29: "ษ" THAI CHARACTER SO RUSI -->
+ <Key
+ latin:keyLabel="ษ"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E28: "ศ" THAI CHARACTER SO SALA -->
+ <Key
+ latin:keyLabel="ศ"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E0B: "ซ" THAI CHARACTER SO SO -->
+ <Key
+ latin:keyLabel="ซ"
+ latin:keyLabelFlags="fontNormal" />
+ <Key
+ latin:keyLabel="." />
</case>
<default>
- <!-- U+0E1C: "ผ" THAI CHARACTER PHO PHUNG -->
+ <!-- U+0E1F: "ฟ" THAI CHARACTER FO FAN -->
<Key
- latin:keyLabel="ผ" />
- <!-- U+0E1B: "ป" THAI CHARACTER PO PLA
- U+0E1A: "บ" THAI CHARACTER BO BAIMAI -->
+ latin:keyLabel="ฟ"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E2B: "ห" THAI CHARACTER HO HIP -->
<Key
- latin:keyLabel="ป"
- latin:moreKeys="บ" />
- <!-- U+0E2D: "อ" THAI CHARACTER O ANG -->
+ latin:keyLabel="ห"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E01: "ก" THAI CHARACTER KO KAI -->
<Key
- latin:keyLabel="อ" />
- <!-- U+0E17: "ท" THAI CHARACTER THO THAHAN -->
+ latin:keyLabel="ก"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E14: "ด" THAI CHARACTER DO DEK -->
<Key
- latin:keyLabel="ท" />
- <!-- U+0E21: "ม" THAI CHARACTER MO MA -->
+ latin:keyLabel="ด"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E40: "เ" THAI CHARACTER SARA E -->
<Key
- latin:keyLabel="ม" />
- <!-- U+0E1D: "ฝ" THAI CHARACTER FO FA -->
+ latin:keyLabel="เ"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0020: " " SPACE
+ U+0E49: " ้" THAI CHARACTER MAI THO -->
+ <!-- Note: The space character is needed as a preceding letter to draw some Thai
+ composing characters correctly. -->
<Key
- latin:keyLabel="ฝ" />
- <!-- U+0E48: " ่" THAI CHARACTER MAI EK
- U+0E49: " ้" THAI CHARACTER MAI THO
- U+0E4A: " ๊" THAI CHARACTER MAI TRI
- U+0E4B: " ๋" THAI CHARACTER MAI CHATTAWA -->
+ latin:keyLabel=" ้"
+ latin:code="0x0E49"
+ latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+ <!-- U+0020: " " SPACE
+ U+0E48: " ่" THAI CHARACTER MAI EK -->
+ <!-- Note: The space character is needed as a preceding letter to draw some Thai
+ composing characters correctly. -->
<Key
- latin:keyLabel="่"
- latin:moreKeys="้,๊,๋" />
+ latin:keyLabel=" ่"
+ latin:code="0x0E48"
+ latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+ <!-- U+0E32: "า" THAI CHARACTER SARA AA -->
+ <Key
+ latin:keyLabel="า"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E2A: "ส" THAI CHARACTER SO SUA -->
+ <Key
+ latin:keyLabel="ส"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E27: "ว" THAI CHARACTER WO WAEN -->
+ <Key
+ latin:keyLabel="ว"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E07: "ง" THAI CHARACTER NGO NGU -->
+ <Key
+ latin:keyLabel="ง"
+ latin:keyLabelFlags="fontNormal" />
</default>
</switch>
</merge>
diff --git a/java/res/xml/rowkeys_thai4.xml b/java/res/xml/rowkeys_thai4.xml
new file mode 100644
index 0000000..8a78424
--- /dev/null
+++ b/java/res/xml/rowkeys_thai4.xml
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+ xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+ <switch>
+ <case
+ latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
+ >
+ <Key
+ latin:keyLabel="(" />
+ <Key
+ latin:keyLabel=")" />
+ <!-- U+0E09: "ฉ" THAI CHARACTER CHO CHING -->
+ <Key
+ latin:keyLabel="ฉ"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E2E: "ฮ" THAI CHARACTER HO NOKHUK -->
+ <Key
+ latin:keyLabel="ฮ"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0020: " " SPACE
+ U+0E3A: " ฺ" THAI CHARACTER PHINTHU -->
+ <!-- Note: The space character is needed as a preceding letter to draw some Thai
+ composing characters correctly. -->
+ <Key
+ latin:keyLabel=" ฺ"
+ latin:code="0x0E3A"
+ latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+ <!-- U+0020: " " SPACE
+ U+0E4C: " ์" THAI CHARACTER THANTHAKHAT -->
+ <!-- Note: The space character is needed as a preceding letter to draw some Thai
+ composing characters correctly. -->
+ <Key
+ latin:keyLabel=" ์"
+ latin:code="0x0E4C"
+ latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+ <Key
+ latin:keyLabel="\?" />
+ <!-- U+0E12: "ฒ" THAI CHARACTER THO PHUTHAO -->
+ <Key
+ latin:keyLabel="ฒ"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E2C: "ฬ" THAI CHARACTER LO CHULA -->
+ <Key
+ latin:keyLabel="ฬ"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E26: "ฦ" THAI CHARACTER LU -->
+ <Key
+ latin:keyLabel="ฦ"
+ latin:keyLabelFlags="fontNormal" />
+ </case>
+ <default>
+ <!-- U+0E1C: "ผ" THAI CHARACTER PHO PHUNG -->
+ <Key
+ latin:keyLabel="ผ"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E1B: "ป" THAI CHARACTER PO PLA -->
+ <Key
+ latin:keyLabel="ป"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E41: "แ" THAI CHARACTER SARA AE -->
+ <Key
+ latin:keyLabel="แ"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E2D: "อ" THAI CHARACTER O ANG -->
+ <Key
+ latin:keyLabel="อ"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0020: " " SPACE
+ U+0E34: " ิ" THAI CHARACTER SARA I -->
+ <!-- Note: The space character is needed as a preceding letter to draw some Thai
+ composing characters correctly. -->
+ <Key
+ latin:keyLabel=" ิ"
+ latin:code="0x0E34"
+ latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+ <!-- U+0020: " " SPACE
+ U+0E37: " ื" THAI CHARACTER SARA UEE -->
+ <!-- Note: The space character is needed as a preceding letter to draw some Thai
+ composing characters correctly. -->
+ <Key
+ latin:keyLabel=" ื"
+ latin:code="0x0E37"
+ latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+ <!-- U+0E17: "ท" THAI CHARACTER THO THAHAN -->
+ <Key
+ latin:keyLabel="ท"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E21: "ม" THAI CHARACTER MO MA -->
+ <Key
+ latin:keyLabel="ม"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E43: "ใ" THAI CHARACTER SARA AI MAIMUAN -->
+ <Key
+ latin:keyLabel="ใ"
+ latin:keyLabelFlags="fontNormal" />
+ <!-- U+0E1D: "ฝ" THAI CHARACTER FO FA -->
+ <Key
+ latin:keyLabel="ฝ"
+ latin:keyLabelFlags="fontNormal" />
+ </default>
+ </switch>
+</merge>
diff --git a/java/res/xml/rows_thai.xml b/java/res/xml/rows_thai.xml
index 6b80df6..108b7e1 100644
--- a/java/res/xml/rows_thai.xml
+++ b/java/res/xml/rows_thai.xml
@@ -24,31 +24,34 @@
<include
latin:keyboardLayout="@xml/key_styles_common" />
<Row
- latin:keyWidth="10%p"
+ latin:keyWidth="8.3333%p"
>
<include
latin:keyboardLayout="@xml/rowkeys_thai1" />
</Row>
<Row
- latin:keyWidth="10%p"
+ latin:keyWidth="8.3333%p"
>
<include
- latin:keyboardLayout="@xml/rowkeys_thai2"
- latin:keyXPos="5%p" />
+ latin:keyboardLayout="@xml/rowkeys_thai2" />
</Row>
<Row
- latin:keyWidth="10%p"
+ latin:keyWidth="8.3333%p"
>
- <Key
- latin:keyStyle="shiftKeyStyle"
- latin:keyWidth="15%p"
- latin:visualInsetsRight="1%p" />
<include
latin:keyboardLayout="@xml/rowkeys_thai3" />
+ <include
+ latin:keyboardLayout="@xml/key_thai_kho_khuat" />
+ </Row>
+ <Row
+ latin:keyWidth="8.3333%p"
+ >
<Key
- latin:keyStyle="deleteKeyStyle"
- latin:keyWidth="fillRight"
- latin:visualInsetsLeft="1%p" />
+ latin:keyStyle="shiftKeyStyle" />
+ <include
+ latin:keyboardLayout="@xml/rowkeys_thai4" />
+ <Key
+ latin:keyStyle="deleteKeyStyle" />
</Row>
<include
latin:keyboardLayout="@xml/row_qwerty4" />
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
index 56f9c2a..5af5d04 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
@@ -35,6 +35,7 @@
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardView;
+import com.android.inputmethod.latin.CollectionUtils;
/**
* Exposes a virtual view sub-tree for {@link KeyboardView} and generates
@@ -55,7 +56,7 @@
private final AccessibilityUtils mAccessibilityUtils;
/** A map of integer IDs to {@link Key}s. */
- private final SparseArray<Key> mVirtualViewIdToKey = new SparseArray<Key>();
+ private final SparseArray<Key> mVirtualViewIdToKey = CollectionUtils.newSparseArray();
/** Temporary rect used to calculate in-screen bounds. */
private final Rect mTempBoundsInScreen = new Rect();
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
index 58d3022..1eee1df 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
@@ -37,7 +37,7 @@
import com.android.inputmethod.latin.InputTypeUtils;
import com.android.inputmethod.latin.R;
-public class AccessibilityUtils {
+public final class AccessibilityUtils {
private static final String TAG = AccessibilityUtils.class.getSimpleName();
private static final String CLASS = AccessibilityUtils.class.getClass().getName();
private static final String PACKAGE = AccessibilityUtils.class.getClass().getPackage()
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
index ed3468a..77940c0 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
@@ -24,7 +24,6 @@
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewConfiguration;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
@@ -44,7 +43,7 @@
/**
* Inset in pixels to look for keys when the user's finger exits the
- * keyboard area. See {@link ViewConfiguration#getScaledEdgeSlop()}.
+ * keyboard area.
*/
private int mEdgeSlop;
@@ -62,7 +61,8 @@
private void initInternal(InputMethodService inputMethod) {
mInputMethod = inputMethod;
- mEdgeSlop = ViewConfiguration.get(inputMethod).getScaledEdgeSlop();
+ mEdgeSlop = inputMethod.getResources().getDimensionPixelSize(
+ R.dimen.accessibility_edge_slop);
}
/**
@@ -127,8 +127,14 @@
public boolean dispatchHoverEvent(MotionEvent event, PointerTracker tracker) {
final int x = (int) event.getX();
final int y = (int) event.getY();
- final Key key = tracker.getKeyOn(x, y);
final Key previousKey = mLastHoverKey;
+ final Key key;
+
+ if (pointInView(x, y)) {
+ key = tracker.getKeyOn(x, y);
+ } else {
+ key = null;
+ }
mLastHoverKey = key;
@@ -136,7 +142,7 @@
case MotionEvent.ACTION_HOVER_EXIT:
// Make sure we're not getting an EXIT event because the user slid
// off the keyboard area, then force a key press.
- if (pointInView(x, y) && (key != null)) {
+ if (key != null) {
getAccessibilityNodeProvider().simulateKeyPress(key);
}
//$FALL-THROUGH$
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
index 9b74070..5c45448 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -25,6 +25,7 @@
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardId;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.R;
import java.util.HashMap;
@@ -38,7 +39,7 @@
private static KeyCodeDescriptionMapper sInstance = new KeyCodeDescriptionMapper();
// Map of key labels to spoken description resource IDs
- private final HashMap<CharSequence, Integer> mKeyLabelMap;
+ private final HashMap<CharSequence, Integer> mKeyLabelMap = CollectionUtils.newHashMap();
// Sparse array of spoken description resource IDs indexed by key codes
private final SparseIntArray mKeyCodeMap;
@@ -52,7 +53,6 @@
}
private KeyCodeDescriptionMapper() {
- mKeyLabelMap = new HashMap<CharSequence, Integer>();
mKeyCodeMap = new SparseIntArray();
}
diff --git a/java/src/com/android/inputmethod/compat/CompatUtils.java b/java/src/com/android/inputmethod/compat/CompatUtils.java
index ce427e9..ffed6ec 100644
--- a/java/src/com/android/inputmethod/compat/CompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/CompatUtils.java
@@ -24,7 +24,7 @@
import java.lang.reflect.Field;
import java.lang.reflect.Method;
-public class CompatUtils {
+public final class CompatUtils {
private static final String TAG = CompatUtils.class.getSimpleName();
private static final String EXTRA_INPUT_METHOD_ID = "input_method_id";
// TODO: Can these be constants instead of literal String constants?
diff --git a/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java b/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java
index 08c246f..210058b 100644
--- a/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java
@@ -20,7 +20,7 @@
import java.lang.reflect.Field;
-public class EditorInfoCompatUtils {
+public final class EditorInfoCompatUtils {
// EditorInfo.IME_FLAG_FORCE_ASCII has been introduced since API#16 (JellyBean).
private static final Field FIELD_IME_FLAG_FORCE_ASCII = CompatUtils.getField(
EditorInfo.class, "IME_FLAG_FORCE_ASCII");
diff --git a/java/src/com/android/inputmethod/compat/InputMethodServiceCompatUtils.java b/java/src/com/android/inputmethod/compat/InputMethodServiceCompatUtils.java
index 0befa7a..8eea31e 100644
--- a/java/src/com/android/inputmethod/compat/InputMethodServiceCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/InputMethodServiceCompatUtils.java
@@ -20,7 +20,7 @@
import java.lang.reflect.Method;
-public class InputMethodServiceCompatUtils {
+public final class InputMethodServiceCompatUtils {
private static final Method METHOD_enableHardwareAcceleration =
CompatUtils.getMethod(InputMethodService.class, "enableHardwareAcceleration");
diff --git a/java/src/com/android/inputmethod/compat/SettingsSecureCompatUtils.java b/java/src/com/android/inputmethod/compat/SettingsSecureCompatUtils.java
index 1b79992..db5abd0 100644
--- a/java/src/com/android/inputmethod/compat/SettingsSecureCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/SettingsSecureCompatUtils.java
@@ -18,7 +18,7 @@
import java.lang.reflect.Field;
-public class SettingsSecureCompatUtils {
+public final class SettingsSecureCompatUtils {
private static final Field FIELD_ACCESSIBILITY_SPEAK_PASSWORD = CompatUtils.getField(
android.provider.Settings.Secure.class, "ACCESSIBILITY_SPEAK_PASSWORD");
diff --git a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
index 1183b5f..159f436 100644
--- a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
+++ b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
@@ -16,10 +16,6 @@
package com.android.inputmethod.compat;
-import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.SuggestedWords;
-import com.android.inputmethod.latin.SuggestionSpanPickedNotificationReceiver;
-
import android.content.Context;
import android.text.Spannable;
import android.text.SpannableString;
@@ -27,12 +23,17 @@
import android.text.TextUtils;
import android.util.Log;
+import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.SuggestionSpanPickedNotificationReceiver;
+
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Locale;
-public class SuggestionSpanUtils {
+public final class SuggestionSpanUtils {
private static final String TAG = SuggestionSpanUtils.class.getSimpleName();
// TODO: Use reflection to get field values
public static final String ACTION_SUGGESTION_PICKED =
@@ -119,7 +120,7 @@
} else {
spannable = new SpannableString(pickedWord);
}
- final ArrayList<String> suggestionsList = new ArrayList<String>();
+ final ArrayList<String> suggestionsList = CollectionUtils.newArrayList();
for (int i = 0; i < suggestedWords.size(); ++i) {
if (suggestionsList.size() >= OBJ_SUGGESTIONS_MAX_SIZE) {
break;
diff --git a/java/src/com/android/inputmethod/compat/SuggestionsInfoCompatUtils.java b/java/src/com/android/inputmethod/compat/SuggestionsInfoCompatUtils.java
index e5f9db2..8314212 100644
--- a/java/src/com/android/inputmethod/compat/SuggestionsInfoCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/SuggestionsInfoCompatUtils.java
@@ -20,7 +20,7 @@
import java.lang.reflect.Field;
-public class SuggestionsInfoCompatUtils {
+public final class SuggestionsInfoCompatUtils {
private static final Field FIELD_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS = CompatUtils.getField(
SuggestionsInfo.class, "RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS");
private static final Integer OBJ_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS = (Integer) CompatUtils
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index 178c9ff..03c2164 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -31,11 +31,16 @@
import android.util.Log;
import android.util.Xml;
+import com.android.inputmethod.keyboard.internal.KeyDrawParams;
import com.android.inputmethod.keyboard.internal.KeySpecParser;
-import com.android.inputmethod.keyboard.internal.KeySpecParser.MoreKeySpec;
-import com.android.inputmethod.keyboard.internal.KeyStyles.KeyStyle;
+import com.android.inputmethod.keyboard.internal.KeyStyle;
+import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;
import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
+import com.android.inputmethod.keyboard.internal.KeyboardParams;
+import com.android.inputmethod.keyboard.internal.KeyboardRow;
+import com.android.inputmethod.keyboard.internal.MoreKeySpec;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.ResourceUtils;
import com.android.inputmethod.latin.StringUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -54,7 +59,6 @@
* The key code (unicode or custom code) that this key generates.
*/
public final int mCode;
- public final int mAltCode;
/** Label to display */
public final String mLabel;
@@ -89,22 +93,11 @@
/** Icon to display instead of a label. Icon takes precedence over a label */
private final int mIconId;
- /** Icon for disabled state */
- private final int mDisabledIconId;
- /** Preview version of the icon, for the preview popup */
- private final int mPreviewIconId;
/** Width of the key, not including the gap */
public final int mWidth;
/** Height of the key, not including the gap */
public final int mHeight;
- /** The horizontal gap around this key */
- public final int mHorizontalGap;
- /** The vertical gap below this key */
- public final int mVerticalGap;
- /** The visual insets */
- public final int mVisualInsetsLeft;
- public final int mVisualInsetsRight;
/** X coordinate of the key in the keyboard layout */
public final int mX;
/** Y coordinate of the key in the keyboard layout */
@@ -112,8 +105,6 @@
/** Hit bounding box of the key */
public final Rect mHitBox = new Rect();
- /** Text to output when pressed. This can be multiple characters, like ".com" */
- public final CharSequence mOutputText;
/** More keys */
public final MoreKeySpec[] mMoreKeys;
/** More keys column number and flags */
@@ -143,6 +134,34 @@
private static final int ACTION_FLAGS_ALT_CODE_WHILE_TYPING = 0x04;
private static final int ACTION_FLAGS_ENABLE_LONG_PRESS = 0x08;
+ public final KeyVisualAttributes mKeyVisualAttributes;
+
+ private final OptionalAttributes mOptionalAttributes;
+
+ private static class OptionalAttributes {
+ /** Text to output when pressed. This can be multiple characters, like ".com" */
+ public final String mOutputText;
+ public final int mAltCode;
+ /** Icon for disabled state */
+ public final int mDisabledIconId;
+ /** Preview version of the icon, for the preview popup */
+ public final int mPreviewIconId;
+ /** The visual insets */
+ public final int mVisualInsetsLeft;
+ public final int mVisualInsetsRight;
+
+ public OptionalAttributes(final String outputText, final int altCode,
+ final int disabledIconId, final int previewIconId,
+ final int visualInsetsLeft, final int visualInsetsRight) {
+ mOutputText = outputText;
+ mAltCode = altCode;
+ mDisabledIconId = disabledIconId;
+ mPreviewIconId = previewIconId;
+ mVisualInsetsLeft = visualInsetsLeft;
+ mVisualInsetsRight = visualInsetsRight;
+ }
+ }
+
private final int mHashCode;
/** The current pressed state of this key */
@@ -153,8 +172,8 @@
/**
* This constructor is being used only for keys in more keys keyboard.
*/
- public Key(Keyboard.Params params, MoreKeySpec moreKeySpec, int x, int y, int width, int height,
- int labelFlags) {
+ public Key(final KeyboardParams params, final MoreKeySpec moreKeySpec, final int x, final int y,
+ final int width, final int height, final int labelFlags) {
this(params, moreKeySpec.mLabel, null, moreKeySpec.mIconId, moreKeySpec.mCode,
moreKeySpec.mOutputText, x, y, width, height, labelFlags);
}
@@ -162,13 +181,11 @@
/**
* This constructor is being used only for key in popup suggestions pane.
*/
- public Key(Keyboard.Params params, String label, String hintLabel, int iconId,
- int code, String outputText, int x, int y, int width, int height, int labelFlags) {
+ public Key(final KeyboardParams params, final String label, final String hintLabel,
+ final int iconId, final int code, final String outputText, final int x, final int y,
+ final int width, final int height, final int labelFlags) {
mHeight = height - params.mVerticalGap;
- mHorizontalGap = params.mHorizontalGap;
- mVerticalGap = params.mVerticalGap;
- mVisualInsetsLeft = mVisualInsetsRight = 0;
- mWidth = width - mHorizontalGap;
+ mWidth = width - params.mHorizontalGap;
mHintLabel = hintLabel;
mLabelFlags = labelFlags;
mBackgroundType = BACKGROUND_TYPE_NORMAL;
@@ -176,17 +193,20 @@
mMoreKeys = null;
mMoreKeysColumnAndFlags = 0;
mLabel = label;
- mOutputText = outputText;
+ if (outputText == null) {
+ mOptionalAttributes = null;
+ } else {
+ mOptionalAttributes = new OptionalAttributes(outputText, CODE_UNSPECIFIED,
+ ICON_UNDEFINED, ICON_UNDEFINED, 0, 0);
+ }
mCode = code;
mEnabled = (code != CODE_UNSPECIFIED);
- mAltCode = CODE_UNSPECIFIED;
mIconId = iconId;
- mDisabledIconId = ICON_UNDEFINED;
- mPreviewIconId = ICON_UNDEFINED;
// Horizontal gap is divided equally to both sides of the key.
- mX = x + mHorizontalGap / 2;
+ mX = x + params.mHorizontalGap / 2;
mY = y;
mHitBox.set(x, y, x + width + 1, y + height);
+ mKeyVisualAttributes = null;
mHashCode = computeHashCode(this);
}
@@ -201,12 +221,11 @@
* @param parser the XML parser containing the attributes for this key
* @throws XmlPullParserException
*/
- public Key(Resources res, Keyboard.Params params, Keyboard.Builder.Row row,
- XmlPullParser parser) throws XmlPullParserException {
+ public Key(final Resources res, final KeyboardParams params, final KeyboardRow row,
+ final XmlPullParser parser) throws XmlPullParserException {
final float horizontalGap = isSpacer() ? 0 : params.mHorizontalGap;
final int keyHeight = row.mRowHeight;
- mVerticalGap = params.mVerticalGap;
- mHeight = keyHeight - mVerticalGap;
+ mHeight = keyHeight - params.mVerticalGap;
final TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Keyboard_Key);
@@ -220,7 +239,6 @@
mX = Math.round(keyXPos + horizontalGap / 2);
mY = keyYPos;
mWidth = Math.round(keyWidth - horizontalGap);
- mHorizontalGap = Math.round(horizontalGap);
mHitBox.set(Math.round(keyXPos), keyYPos, Math.round(keyXPos + keyWidth) + 1,
keyYPos + keyHeight);
// Update row to have current x coordinate.
@@ -229,15 +247,15 @@
mBackgroundType = style.getInt(keyAttr,
R.styleable.Keyboard_Key_backgroundType, row.getDefaultBackgroundType());
- mVisualInsetsLeft = Math.round(Keyboard.Builder.getDimensionOrFraction(keyAttr,
+ final int visualInsetsLeft = Math.round(ResourceUtils.getDimensionOrFraction(keyAttr,
R.styleable.Keyboard_Key_visualInsetsLeft, params.mBaseWidth, 0));
- mVisualInsetsRight = Math.round(Keyboard.Builder.getDimensionOrFraction(keyAttr,
+ final int visualInsetsRight = Math.round(ResourceUtils.getDimensionOrFraction(keyAttr,
R.styleable.Keyboard_Key_visualInsetsRight, params.mBaseWidth, 0));
mIconId = KeySpecParser.getIconId(style.getString(keyAttr,
R.styleable.Keyboard_Key_keyIcon));
- mDisabledIconId = KeySpecParser.getIconId(style.getString(keyAttr,
+ final int disabledIconId = KeySpecParser.getIconId(style.getString(keyAttr,
R.styleable.Keyboard_Key_keyIconDisabled));
- mPreviewIconId = KeySpecParser.getIconId(style.getString(keyAttr,
+ final int previewIconId = KeySpecParser.getIconId(style.getString(keyAttr,
R.styleable.Keyboard_Key_keyIconPreview));
mLabelFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags)
@@ -331,21 +349,28 @@
} else {
mCode = KeySpecParser.toUpperCaseOfCodeForLocale(code, needsToUpperCase, locale);
}
- mOutputText = outputText;
- mAltCode = KeySpecParser.toUpperCaseOfCodeForLocale(
+ final int altCode = KeySpecParser.toUpperCaseOfCodeForLocale(
KeySpecParser.parseCode(style.getString(keyAttr,
R.styleable.Keyboard_Key_altCode), params.mCodesSet, CODE_UNSPECIFIED),
needsToUpperCase, locale);
- mHashCode = computeHashCode(this);
-
+ if (outputText == null && altCode == CODE_UNSPECIFIED
+ && disabledIconId == ICON_UNDEFINED && previewIconId == ICON_UNDEFINED
+ && visualInsetsLeft == 0 && visualInsetsRight == 0) {
+ mOptionalAttributes = null;
+ } else {
+ mOptionalAttributes = new OptionalAttributes(outputText, altCode,
+ disabledIconId, previewIconId,
+ visualInsetsLeft, visualInsetsRight);
+ }
+ mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr);
keyAttr.recycle();
-
+ mHashCode = computeHashCode(this);
if (hasShiftedLetterHint() && TextUtils.isEmpty(mHintLabel)) {
Log.w(TAG, "hasShiftedLetterHint specified without keyHintLabel: " + this);
}
}
- private static boolean needsToUpperCase(int labelFlags, int keyboardElementId) {
+ private static boolean needsToUpperCase(final int labelFlags, final int keyboardElementId) {
if ((labelFlags & LABEL_FLAGS_PRESERVE_CASE) != 0) return false;
switch (keyboardElementId) {
case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
@@ -358,7 +383,7 @@
}
}
- private static int computeHashCode(Key key) {
+ private static int computeHashCode(final Key key) {
return Arrays.hashCode(new Object[] {
key.mX,
key.mY,
@@ -370,22 +395,22 @@
key.mIconId,
key.mBackgroundType,
Arrays.hashCode(key.mMoreKeys),
- key.mOutputText,
+ key.getOutputText(),
key.mActionFlags,
key.mLabelFlags,
// Key can be distinguishable without the following members.
- // key.mAltCode,
- // key.mDisabledIconId,
- // key.mPreviewIconId,
+ // key.mOptionalAttributes.mAltCode,
+ // key.mOptionalAttributes.mDisabledIconId,
+ // key.mOptionalAttributes.mPreviewIconId,
// key.mHorizontalGap,
// key.mVerticalGap,
- // key.mVisualInsetLeft,
- // key.mVisualInsetRight,
+ // key.mOptionalAttributes.mVisualInsetLeft,
+ // key.mOptionalAttributes.mVisualInsetRight,
// key.mMaxMoreKeysColumn,
});
}
- private boolean equals(Key o) {
+ private boolean equals(final Key o) {
if (this == o) return true;
return o.mX == mX
&& o.mY == mY
@@ -397,7 +422,7 @@
&& o.mIconId == mIconId
&& o.mBackgroundType == mBackgroundType
&& Arrays.equals(o.mMoreKeys, mMoreKeys)
- && TextUtils.equals(o.mOutputText, mOutputText)
+ && TextUtils.equals(o.getOutputText(), getOutputText())
&& o.mActionFlags == mActionFlags
&& o.mLabelFlags == mLabelFlags;
}
@@ -408,7 +433,7 @@
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(final Object o) {
return o instanceof Key && equals((Key)o);
}
@@ -425,7 +450,7 @@
KeyboardIconsSet.getIconName(mIconId), backgroundName(mBackgroundType));
}
- private static String backgroundName(int backgroundType) {
+ private static String backgroundName(final int backgroundType) {
switch (backgroundType) {
case BACKGROUND_TYPE_NORMAL: return "normal";
case BACKGROUND_TYPE_FUNCTIONAL: return "functional";
@@ -436,19 +461,19 @@
}
}
- public void markAsLeftEdge(Keyboard.Params params) {
+ public void markAsLeftEdge(final KeyboardParams params) {
mHitBox.left = params.mHorizontalEdgesPadding;
}
- public void markAsRightEdge(Keyboard.Params params) {
+ public void markAsRightEdge(final KeyboardParams params) {
mHitBox.right = params.mOccupiedWidth - params.mHorizontalEdgesPadding;
}
- public void markAsTopEdge(Keyboard.Params params) {
+ public void markAsTopEdge(final KeyboardParams params) {
mHitBox.top = params.mTopPadding;
}
- public void markAsBottomEdge(Keyboard.Params params) {
+ public void markAsBottomEdge(final KeyboardParams params) {
mHitBox.bottom = params.mOccupiedHeight + params.mBottomPadding;
}
@@ -456,129 +481,169 @@
return this instanceof Spacer;
}
- public boolean isShift() {
+ public final boolean isShift() {
return mCode == CODE_SHIFT;
}
- public boolean isModifier() {
+ public final boolean isModifier() {
return mCode == CODE_SHIFT || mCode == CODE_SWITCH_ALPHA_SYMBOL;
}
- public boolean isRepeatable() {
+ public final boolean isRepeatable() {
return (mActionFlags & ACTION_FLAGS_IS_REPEATABLE) != 0;
}
- public boolean noKeyPreview() {
+ public final boolean noKeyPreview() {
return (mActionFlags & ACTION_FLAGS_NO_KEY_PREVIEW) != 0;
}
- public boolean altCodeWhileTyping() {
+ public final boolean altCodeWhileTyping() {
return (mActionFlags & ACTION_FLAGS_ALT_CODE_WHILE_TYPING) != 0;
}
- public boolean isLongPressEnabled() {
+ public final boolean isLongPressEnabled() {
// We need not start long press timer on the key which has activated shifted letter.
return (mActionFlags & ACTION_FLAGS_ENABLE_LONG_PRESS) != 0
&& (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) == 0;
}
- public Typeface selectTypeface(Typeface defaultTypeface) {
+ public final Typeface selectTypeface(final KeyDrawParams params) {
// TODO: Handle "bold" here too?
if ((mLabelFlags & LABEL_FLAGS_FONT_NORMAL) != 0) {
return Typeface.DEFAULT;
} else if ((mLabelFlags & LABEL_FLAGS_FONT_MONO_SPACE) != 0) {
return Typeface.MONOSPACE;
} else {
- return defaultTypeface;
+ return params.mTypeface;
}
}
- public int selectTextSize(int letterSize, int largeLetterSize, int labelSize,
- int largeLabelSize, int hintLabelSize) {
+ public final int selectTextSize(final KeyDrawParams params) {
switch (mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_TEXT_RATIO_MASK) {
case LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO:
- return letterSize;
+ return params.mLetterSize;
case LABEL_FLAGS_FOLLOW_KEY_LARGE_LETTER_RATIO:
- return largeLetterSize;
+ return params.mLargeLetterSize;
case LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO:
- return labelSize;
+ return params.mLabelSize;
case LABEL_FLAGS_FOLLOW_KEY_LARGE_LABEL_RATIO:
- return largeLabelSize;
+ return params.mLargeLabelSize;
case LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO:
- return hintLabelSize;
+ return params.mHintLabelSize;
default: // No follow key ratio flag specified.
- return StringUtils.codePointCount(mLabel) == 1 ? letterSize : labelSize;
+ return StringUtils.codePointCount(mLabel) == 1 ? params.mLetterSize : params.mLabelSize;
}
}
- public boolean isAlignLeft() {
+ public final int selectTextColor(final KeyDrawParams params) {
+ return isShiftedLetterActivated() ? params.mTextInactivatedColor : params.mTextColor;
+ }
+
+ public final int selectHintTextSize(final KeyDrawParams params) {
+ if (hasHintLabel()) {
+ return params.mHintLabelSize;
+ } else if (hasShiftedLetterHint()) {
+ return params.mShiftedLetterHintSize;
+ } else {
+ return params.mHintLetterSize;
+ }
+ }
+
+ public final int selectHintTextColor(final KeyDrawParams params) {
+ if (hasHintLabel()) {
+ return params.mHintLabelColor;
+ } else if (hasShiftedLetterHint()) {
+ return isShiftedLetterActivated() ? params.mShiftedLetterHintActivatedColor
+ : params.mShiftedLetterHintInactivatedColor;
+ } else {
+ return params.mHintLetterColor;
+ }
+ }
+
+ public final int selectMoreKeyTextSize(final KeyDrawParams params) {
+ return hasLabelsInMoreKeys() ? params.mLabelSize : params.mLetterSize;
+ }
+
+ public final boolean isAlignLeft() {
return (mLabelFlags & LABEL_FLAGS_ALIGN_LEFT) != 0;
}
- public boolean isAlignRight() {
+ public final boolean isAlignRight() {
return (mLabelFlags & LABEL_FLAGS_ALIGN_RIGHT) != 0;
}
- public boolean isAlignLeftOfCenter() {
+ public final boolean isAlignLeftOfCenter() {
return (mLabelFlags & LABEL_FLAGS_ALIGN_LEFT_OF_CENTER) != 0;
}
- public boolean hasPopupHint() {
+ public final boolean hasPopupHint() {
return (mLabelFlags & LABEL_FLAGS_HAS_POPUP_HINT) != 0;
}
- public boolean hasShiftedLetterHint() {
+ public final boolean hasShiftedLetterHint() {
return (mLabelFlags & LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT) != 0;
}
- public boolean hasHintLabel() {
+ public final boolean hasHintLabel() {
return (mLabelFlags & LABEL_FLAGS_HAS_HINT_LABEL) != 0;
}
- public boolean hasLabelWithIconLeft() {
+ public final boolean hasLabelWithIconLeft() {
return (mLabelFlags & LABEL_FLAGS_WITH_ICON_LEFT) != 0;
}
- public boolean hasLabelWithIconRight() {
+ public final boolean hasLabelWithIconRight() {
return (mLabelFlags & LABEL_FLAGS_WITH_ICON_RIGHT) != 0;
}
- public boolean needsXScale() {
+ public final boolean needsXScale() {
return (mLabelFlags & LABEL_FLAGS_AUTO_X_SCALE) != 0;
}
- public boolean isShiftedLetterActivated() {
+ public final boolean isShiftedLetterActivated() {
return (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) != 0;
}
- public int getMoreKeysColumn() {
+ public final int getMoreKeysColumn() {
return mMoreKeysColumnAndFlags & MORE_KEYS_COLUMN_MASK;
}
- public boolean isFixedColumnOrderMoreKeys() {
+ public final boolean isFixedColumnOrderMoreKeys() {
return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER) != 0;
}
- public boolean hasLabelsInMoreKeys() {
+ public final boolean hasLabelsInMoreKeys() {
return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_HAS_LABELS) != 0;
}
- public int getMoreKeyLabelFlags() {
+ public final int getMoreKeyLabelFlags() {
return hasLabelsInMoreKeys()
? LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO
: LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO;
}
- public boolean needsDividersInMoreKeys() {
+ public final boolean needsDividersInMoreKeys() {
return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_NEEDS_DIVIDERS) != 0;
}
- public boolean hasEmbeddedMoreKey() {
+ public final boolean hasEmbeddedMoreKey() {
return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_EMBEDDED_MORE_KEY) != 0;
}
- public Drawable getIcon(KeyboardIconsSet iconSet, int alpha) {
- final int iconId = mEnabled ? mIconId : mDisabledIconId;
+ public final String getOutputText() {
+ final OptionalAttributes attrs = mOptionalAttributes;
+ return (attrs != null) ? attrs.mOutputText : null;
+ }
+
+ public final int getAltCode() {
+ final OptionalAttributes attrs = mOptionalAttributes;
+ return (attrs != null) ? attrs.mAltCode : CODE_UNSPECIFIED;
+ }
+
+ public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) {
+ final OptionalAttributes attrs = mOptionalAttributes;
+ final int disabledIconId = (attrs != null) ? attrs.mDisabledIconId : ICON_UNDEFINED;
+ final int iconId = mEnabled ? mIconId : disabledIconId;
final Drawable icon = iconSet.getIconDrawable(iconId);
if (icon != null) {
icon.setAlpha(alpha);
@@ -586,10 +651,22 @@
return icon;
}
- public Drawable getPreviewIcon(KeyboardIconsSet iconSet) {
- return mPreviewIconId != ICON_UNDEFINED
- ? iconSet.getIconDrawable(mPreviewIconId)
- : iconSet.getIconDrawable(mIconId);
+ public Drawable getPreviewIcon(final KeyboardIconsSet iconSet) {
+ final OptionalAttributes attrs = mOptionalAttributes;
+ final int previewIconId = (attrs != null) ? attrs.mPreviewIconId : ICON_UNDEFINED;
+ return previewIconId != ICON_UNDEFINED
+ ? iconSet.getIconDrawable(previewIconId) : iconSet.getIconDrawable(mIconId);
+ }
+
+ public final int getDrawX() {
+ final OptionalAttributes attrs = mOptionalAttributes;
+ return (attrs == null) ? mX : mX + attrs.mVisualInsetsLeft;
+ }
+
+ public final int getDrawWidth() {
+ final OptionalAttributes attrs = mOptionalAttributes;
+ return (attrs == null) ? mWidth
+ : mWidth - attrs.mVisualInsetsLeft - attrs.mVisualInsetsRight;
}
/**
@@ -610,11 +687,11 @@
mPressed = false;
}
- public boolean isEnabled() {
+ public final boolean isEnabled() {
return mEnabled;
}
- public void setEnabled(boolean enabled) {
+ public void setEnabled(final boolean enabled) {
mEnabled = enabled;
}
@@ -624,9 +701,9 @@
* @param y the y-coordinate of the point
* @return whether or not the point falls on the key. If the key is attached to an edge, it
* will assume that all points between the key and the edge are considered to be on the key.
- * @see #markAsLeftEdge(Keyboard.Params) etc.
+ * @see #markAsLeftEdge(KeyboardParams) etc.
*/
- public boolean isOnKey(int x, int y) {
+ public boolean isOnKey(final int x, final int y) {
return mHitBox.contains(x, y);
}
@@ -636,7 +713,7 @@
* @param y the y-coordinate of the point
* @return the square of the distance of the point from the nearest edge of the key
*/
- public int squaredDistanceToEdge(int x, int y) {
+ public int squaredDistanceToEdge(final int x, final int y) {
final int left = mX;
final int right = left + mWidth;
final int top = mY;
@@ -702,7 +779,7 @@
* @return the drawable state of the key.
* @see android.graphics.drawable.StateListDrawable#setState(int[])
*/
- public int[] getCurrentDrawableState() {
+ public final int[] getCurrentDrawableState() {
switch (mBackgroundType) {
case BACKGROUND_TYPE_FUNCTIONAL:
return mPressed ? KEY_STATE_FUNCTIONAL_PRESSED : KEY_STATE_FUNCTIONAL_NORMAL;
@@ -718,15 +795,16 @@
}
public static class Spacer extends Key {
- public Spacer(Resources res, Keyboard.Params params, Keyboard.Builder.Row row,
- XmlPullParser parser) throws XmlPullParserException {
+ public Spacer(final Resources res, final KeyboardParams params, final KeyboardRow row,
+ final XmlPullParser parser) throws XmlPullParserException {
super(res, params, row, parser);
}
/**
* This constructor is being used only for divider in more keys keyboard.
*/
- protected Spacer(Keyboard.Params params, int x, int y, int width, int height) {
+ protected Spacer(final KeyboardParams params, final int x, final int y, final int width,
+ final int height) {
super(params, null, null, ICON_UNDEFINED, CODE_UNSPECIFIED,
null, x, y, width, height, 0);
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
index c0e6aa8..868c8ca 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
@@ -16,10 +16,10 @@
package com.android.inputmethod.keyboard;
+import com.android.inputmethod.latin.Constants;
+
public class KeyDetector {
- public static final int NOT_A_CODE = -1;
-
private final int mKeyHysteresisDistanceSquared;
private Keyboard mKeyboard;
@@ -103,7 +103,7 @@
final StringBuilder sb = new StringBuilder();
boolean addDelimiter = false;
for (final int code : codes) {
- if (code == NOT_A_CODE) break;
+ if (code == Constants.NOT_A_CODE) break;
if (addDelimiter) sb.append(", ");
sb.append(Keyboard.printableCode(code));
addDelimiter = true;
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index 9198500..261d1eb 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -16,38 +16,15 @@
package com.android.inputmethod.keyboard;
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
import android.util.Log;
import android.util.SparseArray;
-import android.util.SparseIntArray;
-import android.util.TypedValue;
-import android.util.Xml;
-import android.view.InflateException;
-import com.android.inputmethod.keyboard.internal.KeyStyles;
-import com.android.inputmethod.keyboard.internal.KeyboardCodesSet;
+import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;
import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
-import com.android.inputmethod.keyboard.internal.KeyboardTextsSet;
-import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
-import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.SubtypeLocale;
-import com.android.inputmethod.latin.Utils;
-import com.android.inputmethod.latin.XmlParseUtils;
+import com.android.inputmethod.keyboard.internal.KeyboardParams;
+import com.android.inputmethod.latin.CollectionUtils;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Locale;
/**
* Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard
@@ -119,6 +96,9 @@
/** Default gap between rows */
public final int mVerticalGap;
+ /** Per keyboard key visual parameters */
+ public final KeyVisualAttributes mKeyVisualAttributes;
+
public final int mMostCommonKeyHeight;
public final int mMostCommonKeyWidth;
@@ -134,12 +114,12 @@
public final Key[] mAltCodeKeysWhileTyping;
public final KeyboardIconsSet mIconsSet;
- private final SparseArray<Key> mKeyCache = new SparseArray<Key>();
+ private final SparseArray<Key> mKeyCache = CollectionUtils.newSparseArray();
private final ProximityInfo mProximityInfo;
private final boolean mProximityCharsCorrectionEnabled;
- public Keyboard(Params params) {
+ public Keyboard(final KeyboardParams params) {
mId = params.mId;
mThemeId = params.mThemeId;
mOccupiedHeight = params.mOccupiedHeight;
@@ -148,7 +128,7 @@
mMostCommonKeyWidth = params.mMostCommonKeyWidth;
mMoreKeysTemplate = params.mMoreKeysTemplate;
mMaxMoreKeysKeyboardColumn = params.mMaxMoreKeysKeyboardColumn;
-
+ mKeyVisualAttributes = params.mKeyVisualAttributes;
mTopPadding = params.mTopPadding;
mVerticalGap = params.mVerticalGap;
@@ -164,7 +144,7 @@
mProximityCharsCorrectionEnabled = params.mProximityCharsCorrectionEnabled;
}
- public boolean hasProximityCharsCorrection(int code) {
+ public boolean hasProximityCharsCorrection(final int code) {
if (!mProximityCharsCorrectionEnabled) {
return false;
}
@@ -180,7 +160,7 @@
return mProximityInfo;
}
- public Key getKey(int code) {
+ public Key getKey(final int code) {
if (code == CODE_UNSPECIFIED) {
return null;
}
@@ -201,7 +181,7 @@
}
}
- public boolean hasKey(Key aKey) {
+ public boolean hasKey(final Key aKey) {
if (mKeyCache.indexOfValue(aKey) >= 0) {
return true;
}
@@ -215,7 +195,7 @@
return false;
}
- public static boolean isLetterCode(int code) {
+ public static boolean isLetterCode(final int code) {
return code >= CODE_SPACE;
}
@@ -224,170 +204,6 @@
return mId.toString();
}
- public static class Params {
- public KeyboardId mId;
- public int mThemeId;
-
- /** Total height and width of the keyboard, including the paddings and keys */
- public int mOccupiedHeight;
- public int mOccupiedWidth;
-
- /** Base height and width of the keyboard used to calculate rows' or keys' heights and
- * widths
- */
- public int mBaseHeight;
- public int mBaseWidth;
-
- public int mTopPadding;
- public int mBottomPadding;
- public int mHorizontalEdgesPadding;
- public int mHorizontalCenterPadding;
-
- public int mDefaultRowHeight;
- public int mDefaultKeyWidth;
- public int mHorizontalGap;
- public int mVerticalGap;
-
- public int mMoreKeysTemplate;
- public int mMaxMoreKeysKeyboardColumn;
-
- public int GRID_WIDTH;
- public int GRID_HEIGHT;
-
- public final HashSet<Key> mKeys = new HashSet<Key>();
- public final ArrayList<Key> mShiftKeys = new ArrayList<Key>();
- public final ArrayList<Key> mAltCodeKeysWhileTyping = new ArrayList<Key>();
- public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet();
- public final KeyboardCodesSet mCodesSet = new KeyboardCodesSet();
- public final KeyboardTextsSet mTextsSet = new KeyboardTextsSet();
- public final KeyStyles mKeyStyles = new KeyStyles(mTextsSet);
-
- public KeyboardLayoutSet.KeysCache mKeysCache;
-
- public int mMostCommonKeyHeight = 0;
- public int mMostCommonKeyWidth = 0;
-
- public boolean mProximityCharsCorrectionEnabled;
-
- public final TouchPositionCorrection mTouchPositionCorrection =
- new TouchPositionCorrection();
-
- public static class TouchPositionCorrection {
- private static final int TOUCH_POSITION_CORRECTION_RECORD_SIZE = 3;
-
- public boolean mEnabled;
- public float[] mXs;
- public float[] mYs;
- public float[] mRadii;
-
- public void load(String[] data) {
- final int dataLength = data.length;
- if (dataLength % TOUCH_POSITION_CORRECTION_RECORD_SIZE != 0) {
- if (LatinImeLogger.sDBG)
- throw new RuntimeException(
- "the size of touch position correction data is invalid");
- return;
- }
-
- final int length = dataLength / TOUCH_POSITION_CORRECTION_RECORD_SIZE;
- mXs = new float[length];
- mYs = new float[length];
- mRadii = new float[length];
- try {
- for (int i = 0; i < dataLength; ++i) {
- final int type = i % TOUCH_POSITION_CORRECTION_RECORD_SIZE;
- final int index = i / TOUCH_POSITION_CORRECTION_RECORD_SIZE;
- final float value = Float.parseFloat(data[i]);
- if (type == 0) {
- mXs[index] = value;
- } else if (type == 1) {
- mYs[index] = value;
- } else {
- mRadii[index] = value;
- }
- }
- } catch (NumberFormatException e) {
- if (LatinImeLogger.sDBG) {
- throw new RuntimeException(
- "the number format for touch position correction data is invalid");
- }
- mXs = null;
- mYs = null;
- mRadii = null;
- }
- }
-
- // TODO: Remove this method.
- public void setEnabled(boolean enabled) {
- mEnabled = enabled;
- }
-
- public boolean isValid() {
- return mEnabled && mXs != null && mYs != null && mRadii != null
- && mXs.length > 0 && mYs.length > 0 && mRadii.length > 0;
- }
- }
-
- protected void clearKeys() {
- mKeys.clear();
- mShiftKeys.clear();
- clearHistogram();
- }
-
- public void onAddKey(Key newKey) {
- final Key key = (mKeysCache != null) ? mKeysCache.get(newKey) : newKey;
- final boolean zeroWidthSpacer = key.isSpacer() && key.mWidth == 0;
- if (!zeroWidthSpacer) {
- mKeys.add(key);
- updateHistogram(key);
- }
- if (key.mCode == Keyboard.CODE_SHIFT) {
- mShiftKeys.add(key);
- }
- if (key.altCodeWhileTyping()) {
- mAltCodeKeysWhileTyping.add(key);
- }
- }
-
- private int mMaxHeightCount = 0;
- private int mMaxWidthCount = 0;
- private final SparseIntArray mHeightHistogram = new SparseIntArray();
- private final SparseIntArray mWidthHistogram = new SparseIntArray();
-
- private void clearHistogram() {
- mMostCommonKeyHeight = 0;
- mMaxHeightCount = 0;
- mHeightHistogram.clear();
-
- mMaxWidthCount = 0;
- mMostCommonKeyWidth = 0;
- mWidthHistogram.clear();
- }
-
- private static int updateHistogramCounter(SparseIntArray histogram, int key) {
- final int index = histogram.indexOfKey(key);
- final int count = (index >= 0 ? histogram.get(key) : 0) + 1;
- histogram.put(key, count);
- return count;
- }
-
- private void updateHistogram(Key key) {
- final int height = key.mHeight + key.mVerticalGap;
- final int heightCount = updateHistogramCounter(mHeightHistogram, height);
- if (heightCount > mMaxHeightCount) {
- mMaxHeightCount = heightCount;
- mMostCommonKeyHeight = height;
- }
-
- final int width = key.mWidth + key.mHorizontalGap;
- final int widthCount = updateHistogramCounter(mWidthHistogram, width);
- if (widthCount > mMaxWidthCount) {
- mMaxWidthCount = widthCount;
- mMostCommonKeyWidth = width;
- }
- }
- }
-
/**
* Returns the array of the keys that are closest to the given point.
* @param x the x-coordinate of the point
@@ -395,14 +211,14 @@
* @return the array of the nearest keys to the given point. If the given
* point is out of range, then an array of size zero is returned.
*/
- public Key[] getNearestKeys(int x, int y) {
+ public Key[] getNearestKeys(final int x, final int y) {
// Avoid dead pixels at edges of the keyboard
final int adjustedX = Math.max(0, Math.min(x, mOccupiedWidth - 1));
final int adjustedY = Math.max(0, Math.min(y, mOccupiedHeight - 1));
return mProximityInfo.getNearestKeys(adjustedX, adjustedY);
}
- public static String printableCode(int code) {
+ public static String printableCode(final int code) {
switch (code) {
case CODE_SHIFT: return "shift";
case CODE_SWITCH_ALPHA_SYMBOL: return "symbol";
@@ -424,934 +240,4 @@
return String.format("'\\u%04x'", code);
}
}
-
- /**
- * Keyboard Building helper.
- *
- * This class parses Keyboard XML file and eventually build a Keyboard.
- * The Keyboard XML file looks like:
- * <pre>
- * <!-- xml/keyboard.xml -->
- * <Keyboard keyboard_attributes*>
- * <!-- Keyboard Content -->
- * <Row row_attributes*>
- * <!-- Row Content -->
- * <Key key_attributes* />
- * <Spacer horizontalGap="32.0dp" />
- * <include keyboardLayout="@xml/other_keys">
- * ...
- * </Row>
- * <include keyboardLayout="@xml/other_rows">
- * ...
- * </Keyboard>
- * </pre>
- * The XML file which is included in other file must have <merge> as root element,
- * such as:
- * <pre>
- * <!-- xml/other_keys.xml -->
- * <merge>
- * <Key key_attributes* />
- * ...
- * </merge>
- * </pre>
- * and
- * <pre>
- * <!-- xml/other_rows.xml -->
- * <merge>
- * <Row row_attributes*>
- * <Key key_attributes* />
- * </Row>
- * ...
- * </merge>
- * </pre>
- * You can also use switch-case-default tags to select Rows and Keys.
- * <pre>
- * <switch>
- * <case case_attribute*>
- * <!-- Any valid tags at switch position -->
- * </case>
- * ...
- * <default>
- * <!-- Any valid tags at switch position -->
- * </default>
- * </switch>
- * </pre>
- * You can declare Key style and specify styles within Key tags.
- * <pre>
- * <switch>
- * <case mode="email">
- * <key-style styleName="f1-key" parentStyle="modifier-key"
- * keyLabel=".com"
- * />
- * </case>
- * <case mode="url">
- * <key-style styleName="f1-key" parentStyle="modifier-key"
- * keyLabel="http://"
- * />
- * </case>
- * </switch>
- * ...
- * <Key keyStyle="shift-key" ... />
- * </pre>
- */
-
- public static class Builder<KP extends Params> {
- private static final String BUILDER_TAG = "Keyboard.Builder";
- private static final boolean DEBUG = false;
-
- // Keyboard XML Tags
- private static final String TAG_KEYBOARD = "Keyboard";
- private static final String TAG_ROW = "Row";
- private static final String TAG_KEY = "Key";
- private static final String TAG_SPACER = "Spacer";
- private static final String TAG_INCLUDE = "include";
- private static final String TAG_MERGE = "merge";
- private static final String TAG_SWITCH = "switch";
- private static final String TAG_CASE = "case";
- private static final String TAG_DEFAULT = "default";
- public static final String TAG_KEY_STYLE = "key-style";
-
- private static final int DEFAULT_KEYBOARD_COLUMNS = 10;
- private static final int DEFAULT_KEYBOARD_ROWS = 4;
-
- protected final KP mParams;
- protected final Context mContext;
- protected final Resources mResources;
- private final DisplayMetrics mDisplayMetrics;
-
- private int mCurrentY = 0;
- private Row mCurrentRow = null;
- private boolean mLeftEdge;
- private boolean mTopEdge;
- private Key mRightEdgeKey = null;
-
- /**
- * Container for keys in the keyboard. All keys in a row are at the same Y-coordinate.
- * Some of the key size defaults can be overridden per row from what the {@link Keyboard}
- * defines.
- */
- public static class Row {
- // keyWidth enum constants
- private static final int KEYWIDTH_NOT_ENUM = 0;
- private static final int KEYWIDTH_FILL_RIGHT = -1;
-
- private final Params mParams;
- /** Default width of a key in this row. */
- private float mDefaultKeyWidth;
- /** Default height of a key in this row. */
- public final int mRowHeight;
- /** Default keyLabelFlags in this row. */
- private int mDefaultKeyLabelFlags;
- /** Default backgroundType for this row */
- private int mDefaultBackgroundType;
-
- private final int mCurrentY;
- // Will be updated by {@link Key}'s constructor.
- private float mCurrentX;
-
- public Row(Resources res, Params params, XmlPullParser parser, int y) {
- mParams = params;
- TypedArray keyboardAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
- R.styleable.Keyboard);
- mRowHeight = (int)Builder.getDimensionOrFraction(keyboardAttr,
- R.styleable.Keyboard_rowHeight,
- params.mBaseHeight, params.mDefaultRowHeight);
- keyboardAttr.recycle();
- TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
- R.styleable.Keyboard_Key);
- mDefaultKeyWidth = Builder.getDimensionOrFraction(keyAttr,
- R.styleable.Keyboard_Key_keyWidth,
- params.mBaseWidth, params.mDefaultKeyWidth);
- mDefaultBackgroundType = keyAttr.getInt(R.styleable.Keyboard_Key_backgroundType,
- Key.BACKGROUND_TYPE_NORMAL);
- keyAttr.recycle();
-
- // TODO: Initialize this with <Row> attribute as backgroundType is done.
- mDefaultKeyLabelFlags = 0;
- mCurrentY = y;
- mCurrentX = 0.0f;
- }
-
- public float getDefaultKeyWidth() {
- return mDefaultKeyWidth;
- }
-
- public void setDefaultKeyWidth(float defaultKeyWidth) {
- mDefaultKeyWidth = defaultKeyWidth;
- }
-
- public int getDefaultKeyLabelFlags() {
- return mDefaultKeyLabelFlags;
- }
-
- public void setDefaultKeyLabelFlags(int keyLabelFlags) {
- mDefaultKeyLabelFlags = keyLabelFlags;
- }
-
- public int getDefaultBackgroundType() {
- return mDefaultBackgroundType;
- }
-
- public void setDefaultBackgroundType(int backgroundType) {
- mDefaultBackgroundType = backgroundType;
- }
-
- public void setXPos(float keyXPos) {
- mCurrentX = keyXPos;
- }
-
- public void advanceXPos(float width) {
- mCurrentX += width;
- }
-
- public int getKeyY() {
- return mCurrentY;
- }
-
- public float getKeyX(TypedArray keyAttr) {
- final int keyboardRightEdge = mParams.mOccupiedWidth
- - mParams.mHorizontalEdgesPadding;
- if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) {
- final float keyXPos = Builder.getDimensionOrFraction(keyAttr,
- R.styleable.Keyboard_Key_keyXPos, mParams.mBaseWidth, 0);
- if (keyXPos < 0) {
- // If keyXPos is negative, the actual x-coordinate will be
- // keyboardWidth + keyXPos.
- // keyXPos shouldn't be less than mCurrentX because drawable area for this
- // key starts at mCurrentX. Or, this key will overlaps the adjacent key on
- // its left hand side.
- return Math.max(keyXPos + keyboardRightEdge, mCurrentX);
- } else {
- return keyXPos + mParams.mHorizontalEdgesPadding;
- }
- }
- return mCurrentX;
- }
-
- public float getKeyWidth(TypedArray keyAttr) {
- return getKeyWidth(keyAttr, mCurrentX);
- }
-
- public float getKeyWidth(TypedArray keyAttr, float keyXPos) {
- final int widthType = Builder.getEnumValue(keyAttr,
- R.styleable.Keyboard_Key_keyWidth, KEYWIDTH_NOT_ENUM);
- switch (widthType) {
- case KEYWIDTH_FILL_RIGHT:
- final int keyboardRightEdge =
- mParams.mOccupiedWidth - mParams.mHorizontalEdgesPadding;
- // If keyWidth is fillRight, the actual key width will be determined to fill
- // out the area up to the right edge of the keyboard.
- return keyboardRightEdge - keyXPos;
- default: // KEYWIDTH_NOT_ENUM
- return Builder.getDimensionOrFraction(keyAttr,
- R.styleable.Keyboard_Key_keyWidth,
- mParams.mBaseWidth, mDefaultKeyWidth);
- }
- }
- }
-
- public Builder(Context context, KP params) {
- mContext = context;
- final Resources res = context.getResources();
- mResources = res;
- mDisplayMetrics = res.getDisplayMetrics();
-
- mParams = params;
-
- params.GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width);
- params.GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height);
- }
-
- public void setAutoGenerate(KeyboardLayoutSet.KeysCache keysCache) {
- mParams.mKeysCache = keysCache;
- }
-
- public Builder<KP> load(int xmlId, KeyboardId id) {
- mParams.mId = id;
- final XmlResourceParser parser = mResources.getXml(xmlId);
- try {
- parseKeyboard(parser);
- } catch (XmlPullParserException e) {
- Log.w(BUILDER_TAG, "keyboard XML parse error: " + e);
- throw new IllegalArgumentException(e);
- } catch (IOException e) {
- Log.w(BUILDER_TAG, "keyboard XML parse error: " + e);
- throw new RuntimeException(e);
- } finally {
- parser.close();
- }
- return this;
- }
-
- // TODO: Remove this method.
- public void setTouchPositionCorrectionEnabled(boolean enabled) {
- mParams.mTouchPositionCorrection.setEnabled(enabled);
- }
-
- public void setProximityCharsCorrectionEnabled(boolean enabled) {
- mParams.mProximityCharsCorrectionEnabled = enabled;
- }
-
- public Keyboard build() {
- return new Keyboard(mParams);
- }
-
- private int mIndent;
- private static final String SPACES = " ";
-
- private static String spaces(int count) {
- return (count < SPACES.length()) ? SPACES.substring(0, count) : SPACES;
- }
-
- private void startTag(String format, Object ... args) {
- Log.d(BUILDER_TAG, String.format(spaces(++mIndent * 2) + format, args));
- }
-
- private void endTag(String format, Object ... args) {
- Log.d(BUILDER_TAG, String.format(spaces(mIndent-- * 2) + format, args));
- }
-
- private void startEndTag(String format, Object ... args) {
- Log.d(BUILDER_TAG, String.format(spaces(++mIndent * 2) + format, args));
- mIndent--;
- }
-
- private void parseKeyboard(XmlPullParser parser)
- throws XmlPullParserException, IOException {
- if (DEBUG) startTag("<%s> %s", TAG_KEYBOARD, mParams.mId);
- int event;
- while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
- if (event == XmlPullParser.START_TAG) {
- final String tag = parser.getName();
- if (TAG_KEYBOARD.equals(tag)) {
- parseKeyboardAttributes(parser);
- startKeyboard();
- parseKeyboardContent(parser, false);
- break;
- } else {
- throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEYBOARD);
- }
- }
- }
- }
-
- private void parseKeyboardAttributes(XmlPullParser parser) {
- final int displayWidth = mDisplayMetrics.widthPixels;
- final TypedArray keyboardAttr = mContext.obtainStyledAttributes(
- Xml.asAttributeSet(parser), R.styleable.Keyboard, R.attr.keyboardStyle,
- R.style.Keyboard);
- final TypedArray keyAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
- R.styleable.Keyboard_Key);
- try {
- final int displayHeight = mDisplayMetrics.heightPixels;
- final String keyboardHeightString = Utils.getDeviceOverrideValue(
- mResources, R.array.keyboard_heights, null);
- final float keyboardHeight;
- if (keyboardHeightString != null) {
- keyboardHeight = Float.parseFloat(keyboardHeightString)
- * mDisplayMetrics.density;
- } else {
- keyboardHeight = keyboardAttr.getDimension(
- R.styleable.Keyboard_keyboardHeight, displayHeight / 2);
- }
- final float maxKeyboardHeight = getDimensionOrFraction(keyboardAttr,
- R.styleable.Keyboard_maxKeyboardHeight, displayHeight, displayHeight / 2);
- float minKeyboardHeight = getDimensionOrFraction(keyboardAttr,
- R.styleable.Keyboard_minKeyboardHeight, displayHeight, displayHeight / 2);
- if (minKeyboardHeight < 0) {
- // Specified fraction was negative, so it should be calculated against display
- // width.
- minKeyboardHeight = -getDimensionOrFraction(keyboardAttr,
- R.styleable.Keyboard_minKeyboardHeight, displayWidth, displayWidth / 2);
- }
- final Params params = mParams;
- // Keyboard height will not exceed maxKeyboardHeight and will not be less than
- // minKeyboardHeight.
- params.mOccupiedHeight = (int)Math.max(
- Math.min(keyboardHeight, maxKeyboardHeight), minKeyboardHeight);
- params.mOccupiedWidth = params.mId.mWidth;
- params.mTopPadding = (int)getDimensionOrFraction(keyboardAttr,
- R.styleable.Keyboard_keyboardTopPadding, params.mOccupiedHeight, 0);
- params.mBottomPadding = (int)getDimensionOrFraction(keyboardAttr,
- R.styleable.Keyboard_keyboardBottomPadding, params.mOccupiedHeight, 0);
- params.mHorizontalEdgesPadding = (int)getDimensionOrFraction(keyboardAttr,
- R.styleable.Keyboard_keyboardHorizontalEdgesPadding,
- mParams.mOccupiedWidth, 0);
-
- params.mBaseWidth = params.mOccupiedWidth - params.mHorizontalEdgesPadding * 2
- - params.mHorizontalCenterPadding;
- params.mDefaultKeyWidth = (int)getDimensionOrFraction(keyAttr,
- R.styleable.Keyboard_Key_keyWidth, params.mBaseWidth,
- params.mBaseWidth / DEFAULT_KEYBOARD_COLUMNS);
- params.mHorizontalGap = (int)getDimensionOrFraction(keyboardAttr,
- R.styleable.Keyboard_horizontalGap, params.mBaseWidth, 0);
- params.mVerticalGap = (int)getDimensionOrFraction(keyboardAttr,
- R.styleable.Keyboard_verticalGap, params.mOccupiedHeight, 0);
- params.mBaseHeight = params.mOccupiedHeight - params.mTopPadding
- - params.mBottomPadding + params.mVerticalGap;
- params.mDefaultRowHeight = (int)getDimensionOrFraction(keyboardAttr,
- R.styleable.Keyboard_rowHeight, params.mBaseHeight,
- params.mBaseHeight / DEFAULT_KEYBOARD_ROWS);
-
- params.mMoreKeysTemplate = keyboardAttr.getResourceId(
- R.styleable.Keyboard_moreKeysTemplate, 0);
- params.mMaxMoreKeysKeyboardColumn = keyAttr.getInt(
- R.styleable.Keyboard_Key_maxMoreKeysColumn, 5);
-
- params.mThemeId = keyboardAttr.getInt(R.styleable.Keyboard_themeId, 0);
- params.mIconsSet.loadIcons(keyboardAttr);
- final String language = params.mId.mLocale.getLanguage();
- params.mCodesSet.setLanguage(language);
- params.mTextsSet.setLanguage(language);
- final RunInLocale<Void> job = new RunInLocale<Void>() {
- @Override
- protected Void job(Resources res) {
- params.mTextsSet.loadStringResources(mContext);
- return null;
- }
- };
- // Null means the current system locale.
- final Locale locale = SubtypeLocale.isNoLanguage(params.mId.mSubtype)
- ? null : params.mId.mLocale;
- job.runInLocale(mResources, locale);
-
- final int resourceId = keyboardAttr.getResourceId(
- R.styleable.Keyboard_touchPositionCorrectionData, 0);
- params.mTouchPositionCorrection.setEnabled(resourceId != 0);
- if (resourceId != 0) {
- final String[] data = mResources.getStringArray(resourceId);
- params.mTouchPositionCorrection.load(data);
- }
- } finally {
- keyAttr.recycle();
- keyboardAttr.recycle();
- }
- }
-
- private void parseKeyboardContent(XmlPullParser parser, boolean skip)
- throws XmlPullParserException, IOException {
- int event;
- while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
- if (event == XmlPullParser.START_TAG) {
- final String tag = parser.getName();
- if (TAG_ROW.equals(tag)) {
- Row row = parseRowAttributes(parser);
- if (DEBUG) startTag("<%s>%s", TAG_ROW, skip ? " skipped" : "");
- if (!skip) {
- startRow(row);
- }
- parseRowContent(parser, row, skip);
- } else if (TAG_INCLUDE.equals(tag)) {
- parseIncludeKeyboardContent(parser, skip);
- } else if (TAG_SWITCH.equals(tag)) {
- parseSwitchKeyboardContent(parser, skip);
- } else if (TAG_KEY_STYLE.equals(tag)) {
- parseKeyStyle(parser, skip);
- } else {
- throw new XmlParseUtils.IllegalStartTag(parser, TAG_ROW);
- }
- } else if (event == XmlPullParser.END_TAG) {
- final String tag = parser.getName();
- if (DEBUG) endTag("</%s>", tag);
- if (TAG_KEYBOARD.equals(tag)) {
- endKeyboard();
- break;
- } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)
- || TAG_MERGE.equals(tag)) {
- break;
- } else {
- throw new XmlParseUtils.IllegalEndTag(parser, TAG_ROW);
- }
- }
- }
- }
-
- private Row parseRowAttributes(XmlPullParser parser) throws XmlPullParserException {
- final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
- R.styleable.Keyboard);
- try {
- if (a.hasValue(R.styleable.Keyboard_horizontalGap))
- throw new XmlParseUtils.IllegalAttribute(parser, "horizontalGap");
- if (a.hasValue(R.styleable.Keyboard_verticalGap))
- throw new XmlParseUtils.IllegalAttribute(parser, "verticalGap");
- return new Row(mResources, mParams, parser, mCurrentY);
- } finally {
- a.recycle();
- }
- }
-
- private void parseRowContent(XmlPullParser parser, Row row, boolean skip)
- throws XmlPullParserException, IOException {
- int event;
- while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
- if (event == XmlPullParser.START_TAG) {
- final String tag = parser.getName();
- if (TAG_KEY.equals(tag)) {
- parseKey(parser, row, skip);
- } else if (TAG_SPACER.equals(tag)) {
- parseSpacer(parser, row, skip);
- } else if (TAG_INCLUDE.equals(tag)) {
- parseIncludeRowContent(parser, row, skip);
- } else if (TAG_SWITCH.equals(tag)) {
- parseSwitchRowContent(parser, row, skip);
- } else if (TAG_KEY_STYLE.equals(tag)) {
- parseKeyStyle(parser, skip);
- } else {
- throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEY);
- }
- } else if (event == XmlPullParser.END_TAG) {
- final String tag = parser.getName();
- if (DEBUG) endTag("</%s>", tag);
- if (TAG_ROW.equals(tag)) {
- if (!skip) {
- endRow(row);
- }
- break;
- } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)
- || TAG_MERGE.equals(tag)) {
- break;
- } else {
- throw new XmlParseUtils.IllegalEndTag(parser, TAG_KEY);
- }
- }
- }
- }
-
- private void parseKey(XmlPullParser parser, Row row, boolean skip)
- throws XmlPullParserException, IOException {
- if (skip) {
- XmlParseUtils.checkEndTag(TAG_KEY, parser);
- if (DEBUG) startEndTag("<%s /> skipped", TAG_KEY);
- } else {
- final Key key = new Key(mResources, mParams, row, parser);
- if (DEBUG) {
- startEndTag("<%s%s %s moreKeys=%s />", TAG_KEY,
- (key.isEnabled() ? "" : " disabled"), key,
- Arrays.toString(key.mMoreKeys));
- }
- XmlParseUtils.checkEndTag(TAG_KEY, parser);
- endKey(key);
- }
- }
-
- private void parseSpacer(XmlPullParser parser, Row row, boolean skip)
- throws XmlPullParserException, IOException {
- if (skip) {
- XmlParseUtils.checkEndTag(TAG_SPACER, parser);
- if (DEBUG) startEndTag("<%s /> skipped", TAG_SPACER);
- } else {
- final Key.Spacer spacer = new Key.Spacer(mResources, mParams, row, parser);
- if (DEBUG) startEndTag("<%s />", TAG_SPACER);
- XmlParseUtils.checkEndTag(TAG_SPACER, parser);
- endKey(spacer);
- }
- }
-
- private void parseIncludeKeyboardContent(XmlPullParser parser, boolean skip)
- throws XmlPullParserException, IOException {
- parseIncludeInternal(parser, null, skip);
- }
-
- private void parseIncludeRowContent(XmlPullParser parser, Row row, boolean skip)
- throws XmlPullParserException, IOException {
- parseIncludeInternal(parser, row, skip);
- }
-
- private void parseIncludeInternal(XmlPullParser parser, Row row, boolean skip)
- throws XmlPullParserException, IOException {
- if (skip) {
- XmlParseUtils.checkEndTag(TAG_INCLUDE, parser);
- if (DEBUG) startEndTag("</%s> skipped", TAG_INCLUDE);
- } else {
- final AttributeSet attr = Xml.asAttributeSet(parser);
- final TypedArray keyboardAttr = mResources.obtainAttributes(attr,
- R.styleable.Keyboard_Include);
- final TypedArray keyAttr = mResources.obtainAttributes(attr,
- R.styleable.Keyboard_Key);
- int keyboardLayout = 0;
- float savedDefaultKeyWidth = 0;
- int savedDefaultKeyLabelFlags = 0;
- int savedDefaultBackgroundType = Key.BACKGROUND_TYPE_NORMAL;
- try {
- XmlParseUtils.checkAttributeExists(keyboardAttr,
- R.styleable.Keyboard_Include_keyboardLayout, "keyboardLayout",
- TAG_INCLUDE, parser);
- keyboardLayout = keyboardAttr.getResourceId(
- R.styleable.Keyboard_Include_keyboardLayout, 0);
- if (row != null) {
- if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) {
- // Override current x coordinate.
- row.setXPos(row.getKeyX(keyAttr));
- }
- // TODO: Remove this if-clause and do the same as backgroundType below.
- savedDefaultKeyWidth = row.getDefaultKeyWidth();
- if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyWidth)) {
- // Override default key width.
- row.setDefaultKeyWidth(row.getKeyWidth(keyAttr));
- }
- savedDefaultKeyLabelFlags = row.getDefaultKeyLabelFlags();
- // Bitwise-or default keyLabelFlag if exists.
- row.setDefaultKeyLabelFlags(keyAttr.getInt(
- R.styleable.Keyboard_Key_keyLabelFlags, 0)
- | savedDefaultKeyLabelFlags);
- savedDefaultBackgroundType = row.getDefaultBackgroundType();
- // Override default backgroundType if exists.
- row.setDefaultBackgroundType(keyAttr.getInt(
- R.styleable.Keyboard_Key_backgroundType,
- savedDefaultBackgroundType));
- }
- } finally {
- keyboardAttr.recycle();
- keyAttr.recycle();
- }
-
- XmlParseUtils.checkEndTag(TAG_INCLUDE, parser);
- if (DEBUG) {
- startEndTag("<%s keyboardLayout=%s />",TAG_INCLUDE,
- mResources.getResourceEntryName(keyboardLayout));
- }
- final XmlResourceParser parserForInclude = mResources.getXml(keyboardLayout);
- try {
- parseMerge(parserForInclude, row, skip);
- } finally {
- if (row != null) {
- // Restore default keyWidth, keyLabelFlags, and backgroundType.
- row.setDefaultKeyWidth(savedDefaultKeyWidth);
- row.setDefaultKeyLabelFlags(savedDefaultKeyLabelFlags);
- row.setDefaultBackgroundType(savedDefaultBackgroundType);
- }
- parserForInclude.close();
- }
- }
- }
-
- private void parseMerge(XmlPullParser parser, Row row, boolean skip)
- throws XmlPullParserException, IOException {
- if (DEBUG) startTag("<%s>", TAG_MERGE);
- int event;
- while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
- if (event == XmlPullParser.START_TAG) {
- final String tag = parser.getName();
- if (TAG_MERGE.equals(tag)) {
- if (row == null) {
- parseKeyboardContent(parser, skip);
- } else {
- parseRowContent(parser, row, skip);
- }
- break;
- } else {
- throw new XmlParseUtils.ParseException(
- "Included keyboard layout must have <merge> root element", parser);
- }
- }
- }
- }
-
- private void parseSwitchKeyboardContent(XmlPullParser parser, boolean skip)
- throws XmlPullParserException, IOException {
- parseSwitchInternal(parser, null, skip);
- }
-
- private void parseSwitchRowContent(XmlPullParser parser, Row row, boolean skip)
- throws XmlPullParserException, IOException {
- parseSwitchInternal(parser, row, skip);
- }
-
- private void parseSwitchInternal(XmlPullParser parser, Row row, boolean skip)
- throws XmlPullParserException, IOException {
- if (DEBUG) startTag("<%s> %s", TAG_SWITCH, mParams.mId);
- boolean selected = false;
- int event;
- while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
- if (event == XmlPullParser.START_TAG) {
- final String tag = parser.getName();
- if (TAG_CASE.equals(tag)) {
- selected |= parseCase(parser, row, selected ? true : skip);
- } else if (TAG_DEFAULT.equals(tag)) {
- selected |= parseDefault(parser, row, selected ? true : skip);
- } else {
- throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEY);
- }
- } else if (event == XmlPullParser.END_TAG) {
- final String tag = parser.getName();
- if (TAG_SWITCH.equals(tag)) {
- if (DEBUG) endTag("</%s>", TAG_SWITCH);
- break;
- } else {
- throw new XmlParseUtils.IllegalEndTag(parser, TAG_KEY);
- }
- }
- }
- }
-
- private boolean parseCase(XmlPullParser parser, Row row, boolean skip)
- throws XmlPullParserException, IOException {
- final boolean selected = parseCaseCondition(parser);
- if (row == null) {
- // Processing Rows.
- parseKeyboardContent(parser, selected ? skip : true);
- } else {
- // Processing Keys.
- parseRowContent(parser, row, selected ? skip : true);
- }
- return selected;
- }
-
- private boolean parseCaseCondition(XmlPullParser parser) {
- final KeyboardId id = mParams.mId;
- if (id == null)
- return true;
-
- final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
- R.styleable.Keyboard_Case);
- try {
- final boolean keyboardLayoutSetElementMatched = matchTypedValue(a,
- R.styleable.Keyboard_Case_keyboardLayoutSetElement, id.mElementId,
- KeyboardId.elementIdToName(id.mElementId));
- final boolean modeMatched = matchTypedValue(a,
- R.styleable.Keyboard_Case_mode, id.mMode, KeyboardId.modeName(id.mMode));
- final boolean navigateNextMatched = matchBoolean(a,
- R.styleable.Keyboard_Case_navigateNext, id.navigateNext());
- final boolean navigatePreviousMatched = matchBoolean(a,
- R.styleable.Keyboard_Case_navigatePrevious, id.navigatePrevious());
- final boolean passwordInputMatched = matchBoolean(a,
- R.styleable.Keyboard_Case_passwordInput, id.passwordInput());
- final boolean clobberSettingsKeyMatched = matchBoolean(a,
- R.styleable.Keyboard_Case_clobberSettingsKey, id.mClobberSettingsKey);
- final boolean shortcutKeyEnabledMatched = matchBoolean(a,
- R.styleable.Keyboard_Case_shortcutKeyEnabled, id.mShortcutKeyEnabled);
- final boolean hasShortcutKeyMatched = matchBoolean(a,
- R.styleable.Keyboard_Case_hasShortcutKey, id.mHasShortcutKey);
- final boolean languageSwitchKeyEnabledMatched = matchBoolean(a,
- R.styleable.Keyboard_Case_languageSwitchKeyEnabled,
- id.mLanguageSwitchKeyEnabled);
- final boolean isMultiLineMatched = matchBoolean(a,
- R.styleable.Keyboard_Case_isMultiLine, id.isMultiLine());
- final boolean imeActionMatched = matchInteger(a,
- R.styleable.Keyboard_Case_imeAction, id.imeAction());
- final boolean localeCodeMatched = matchString(a,
- R.styleable.Keyboard_Case_localeCode, id.mLocale.toString());
- final boolean languageCodeMatched = matchString(a,
- R.styleable.Keyboard_Case_languageCode, id.mLocale.getLanguage());
- final boolean countryCodeMatched = matchString(a,
- R.styleable.Keyboard_Case_countryCode, id.mLocale.getCountry());
- final boolean selected = keyboardLayoutSetElementMatched && modeMatched
- && navigateNextMatched && navigatePreviousMatched && passwordInputMatched
- && clobberSettingsKeyMatched && shortcutKeyEnabledMatched
- && hasShortcutKeyMatched && languageSwitchKeyEnabledMatched
- && isMultiLineMatched && imeActionMatched && localeCodeMatched
- && languageCodeMatched && countryCodeMatched;
-
- if (DEBUG) {
- startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE,
- textAttr(a.getString(
- R.styleable.Keyboard_Case_keyboardLayoutSetElement),
- "keyboardLayoutSetElement"),
- textAttr(a.getString(R.styleable.Keyboard_Case_mode), "mode"),
- textAttr(a.getString(R.styleable.Keyboard_Case_imeAction),
- "imeAction"),
- booleanAttr(a, R.styleable.Keyboard_Case_navigateNext,
- "navigateNext"),
- booleanAttr(a, R.styleable.Keyboard_Case_navigatePrevious,
- "navigatePrevious"),
- booleanAttr(a, R.styleable.Keyboard_Case_clobberSettingsKey,
- "clobberSettingsKey"),
- booleanAttr(a, R.styleable.Keyboard_Case_passwordInput,
- "passwordInput"),
- booleanAttr(a, R.styleable.Keyboard_Case_shortcutKeyEnabled,
- "shortcutKeyEnabled"),
- booleanAttr(a, R.styleable.Keyboard_Case_hasShortcutKey,
- "hasShortcutKey"),
- booleanAttr(a, R.styleable.Keyboard_Case_languageSwitchKeyEnabled,
- "languageSwitchKeyEnabled"),
- booleanAttr(a, R.styleable.Keyboard_Case_isMultiLine,
- "isMultiLine"),
- textAttr(a.getString(R.styleable.Keyboard_Case_localeCode),
- "localeCode"),
- textAttr(a.getString(R.styleable.Keyboard_Case_languageCode),
- "languageCode"),
- textAttr(a.getString(R.styleable.Keyboard_Case_countryCode),
- "countryCode"),
- selected ? "" : " skipped");
- }
-
- return selected;
- } finally {
- a.recycle();
- }
- }
-
- private static boolean matchInteger(TypedArray a, int index, int value) {
- // If <case> does not have "index" attribute, that means this <case> is wild-card for
- // the attribute.
- return !a.hasValue(index) || a.getInt(index, 0) == value;
- }
-
- private static boolean matchBoolean(TypedArray a, int index, boolean value) {
- // If <case> does not have "index" attribute, that means this <case> is wild-card for
- // the attribute.
- 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)
- || stringArrayContains(a.getString(index).split("\\|"), value);
- }
-
- private static boolean matchTypedValue(TypedArray a, int index, int intValue,
- String strValue) {
- // If <case> does not have "index" attribute, that means this <case> is wild-card for
- // the attribute.
- final TypedValue v = a.peekValue(index);
- if (v == null)
- return true;
-
- if (isIntegerValue(v)) {
- return intValue == a.getInt(index, 0);
- } else if (isStringValue(v)) {
- return stringArrayContains(a.getString(index).split("\\|"), strValue);
- }
- return false;
- }
-
- private static boolean stringArrayContains(String[] array, String value) {
- for (final String elem : array) {
- if (elem.equals(value))
- return true;
- }
- return false;
- }
-
- private boolean parseDefault(XmlPullParser parser, Row row, boolean skip)
- throws XmlPullParserException, IOException {
- if (DEBUG) startTag("<%s>", TAG_DEFAULT);
- if (row == null) {
- parseKeyboardContent(parser, skip);
- } else {
- parseRowContent(parser, row, skip);
- }
- return true;
- }
-
- private void parseKeyStyle(XmlPullParser parser, boolean skip)
- throws XmlPullParserException, IOException {
- TypedArray keyStyleAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
- R.styleable.Keyboard_KeyStyle);
- TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser),
- R.styleable.Keyboard_Key);
- try {
- if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName))
- throw new XmlParseUtils.ParseException("<" + TAG_KEY_STYLE
- + "/> needs styleName attribute", parser);
- if (DEBUG) {
- startEndTag("<%s styleName=%s />%s", TAG_KEY_STYLE,
- keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName),
- skip ? " skipped" : "");
- }
- if (!skip)
- mParams.mKeyStyles.parseKeyStyleAttributes(keyStyleAttr, keyAttrs, parser);
- } finally {
- keyStyleAttr.recycle();
- keyAttrs.recycle();
- }
- XmlParseUtils.checkEndTag(TAG_KEY_STYLE, parser);
- }
-
- private void startKeyboard() {
- mCurrentY += mParams.mTopPadding;
- mTopEdge = true;
- }
-
- private void startRow(Row row) {
- addEdgeSpace(mParams.mHorizontalEdgesPadding, row);
- mCurrentRow = row;
- mLeftEdge = true;
- mRightEdgeKey = null;
- }
-
- private void endRow(Row row) {
- if (mCurrentRow == null)
- throw new InflateException("orphan end row tag");
- if (mRightEdgeKey != null) {
- mRightEdgeKey.markAsRightEdge(mParams);
- mRightEdgeKey = null;
- }
- addEdgeSpace(mParams.mHorizontalEdgesPadding, row);
- mCurrentY += row.mRowHeight;
- mCurrentRow = null;
- mTopEdge = false;
- }
-
- private void endKey(Key key) {
- mParams.onAddKey(key);
- if (mLeftEdge) {
- key.markAsLeftEdge(mParams);
- mLeftEdge = false;
- }
- if (mTopEdge) {
- key.markAsTopEdge(mParams);
- }
- mRightEdgeKey = key;
- }
-
- private void endKeyboard() {
- // nothing to do here.
- }
-
- private void addEdgeSpace(float width, Row row) {
- row.advanceXPos(width);
- mLeftEdge = false;
- mRightEdgeKey = null;
- }
-
- public static float getDimensionOrFraction(TypedArray a, int index, int base,
- float defValue) {
- final TypedValue value = a.peekValue(index);
- if (value == null)
- return defValue;
- if (isFractionValue(value)) {
- return a.getFraction(index, base, base, defValue);
- } else if (isDimensionValue(value)) {
- return a.getDimension(index, defValue);
- }
- return defValue;
- }
-
- public static int getEnumValue(TypedArray a, int index, int defValue) {
- final TypedValue value = a.peekValue(index);
- if (value == null)
- return defValue;
- if (isIntegerValue(value)) {
- return a.getInt(index, defValue);
- }
- return defValue;
- }
-
- private static boolean isFractionValue(TypedValue v) {
- return v.type == TypedValue.TYPE_FRACTION;
- }
-
- private static boolean isDimensionValue(TypedValue v) {
- return v.type == TypedValue.TYPE_DIMENSION;
- }
-
- private static boolean isIntegerValue(TypedValue v) {
- return v.type >= TypedValue.TYPE_FIRST_INT && v.type <= TypedValue.TYPE_LAST_INT;
- }
-
- private static boolean isStringValue(TypedValue v) {
- return v.type == TypedValue.TYPE_STRING;
- }
-
- private static String textAttr(String value, String name) {
- return value != null ? String.format(" %s=%s", name, value) : "";
- }
-
- private static String booleanAttr(TypedArray a, int index, String name) {
- return a.hasValue(index)
- ? String.format(" %s=%s", name, a.getBoolean(index, false)) : "";
- }
- }
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
index b1621a5..5c8f78f 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
@@ -16,6 +16,7 @@
package com.android.inputmethod.keyboard;
+import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.InputPointers;
public interface KeyboardActionListener {
@@ -44,21 +45,16 @@
*
* @param primaryCode this is the code of the key that was pressed
* @param x x-coordinate pixel of touched event. If {@link #onCodeInput} is not called by
- * {@link PointerTracker} or so, the value should be {@link #NOT_A_TOUCH_COORDINATE}.
- * If it's called on insertion from the suggestion strip, it should be
- * {@link #SUGGESTION_STRIP_COORDINATE}.
+ * {@link PointerTracker} or so, the value should be
+ * {@link Constants#NOT_A_COORDINATE}. If it's called on insertion from the
+ * suggestion strip, it should be {@link Constants#SUGGESTION_STRIP_COORDINATE}.
* @param y y-coordinate pixel of touched event. If {@link #onCodeInput} is not called by
- * {@link PointerTracker} or so, the value should be {@link #NOT_A_TOUCH_COORDINATE}.
- * If it's called on insertion from the suggestion strip, it should be
- * {@link #SUGGESTION_STRIP_COORDINATE}.
+ * {@link PointerTracker} or so, the value should be
+ * {@link Constants#NOT_A_COORDINATE}.If it's called on insertion from the
+ * suggestion strip, it should be {@link Constants#SUGGESTION_STRIP_COORDINATE}.
*/
public void onCodeInput(int primaryCode, int x, int y);
- // See {@link Adapter#isInvalidCoordinate(int)}.
- public static final int NOT_A_TOUCH_COORDINATE = -1;
- public static final int SUGGESTION_STRIP_COORDINATE = -2;
- public static final int SPELL_CHECKER_COORDINATE = -3;
-
/**
* Sends a sequence of characters to the listener.
*
@@ -119,9 +115,9 @@
// TODO: Remove this method when the vertical correction is removed.
public static boolean isInvalidCoordinate(int coordinate) {
- // Detect {@link KeyboardActionListener#NOT_A_TOUCH_COORDINATE},
- // {@link KeyboardActionListener#SUGGESTION_STRIP_COORDINATE}, and
- // {@link KeyboardActionListener#SPELL_CHECKER_COORDINATE}.
+ // Detect {@link Constants#NOT_A_COORDINATE},
+ // {@link Constants#SUGGESTION_STRIP_COORDINATE}, and
+ // {@link Constants#SPELL_CHECKER_COORDINATE}.
return coordinate < 0;
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
index 64b3f09..aaccf63 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
@@ -35,7 +35,10 @@
import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.compat.EditorInfoCompatUtils;
-import com.android.inputmethod.keyboard.KeyboardLayoutSet.Params.ElementParams;
+import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
+import com.android.inputmethod.keyboard.internal.KeyboardParams;
+import com.android.inputmethod.keyboard.internal.KeysCache;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.InputAttributes;
import com.android.inputmethod.latin.InputTypeUtils;
import com.android.inputmethod.latin.LatinImeLogger;
@@ -71,41 +74,25 @@
private final Params mParams;
private static final HashMap<KeyboardId, SoftReference<Keyboard>> sKeyboardCache =
- new HashMap<KeyboardId, SoftReference<Keyboard>>();
+ CollectionUtils.newHashMap();
private static final KeysCache sKeysCache = new KeysCache();
public static class KeyboardLayoutSetException extends RuntimeException {
public final KeyboardId mKeyboardId;
- public KeyboardLayoutSetException(Throwable cause, KeyboardId keyboardId) {
+ public KeyboardLayoutSetException(final Throwable cause, final KeyboardId keyboardId) {
super(cause);
mKeyboardId = keyboardId;
}
}
- public static class KeysCache {
- private final HashMap<Key, Key> mMap;
-
- public KeysCache() {
- mMap = new HashMap<Key, Key>();
- }
-
- public void clear() {
- mMap.clear();
- }
-
- public Key get(Key key) {
- final Key existingKey = mMap.get(key);
- if (existingKey != null) {
- // Reuse the existing element that equals to "key" without adding "key" to the map.
- return existingKey;
- }
- mMap.put(key, key);
- return key;
- }
+ private static class ElementParams {
+ int mKeyboardXmlId;
+ boolean mProximityCharsCorrectionEnabled;
+ public ElementParams() {}
}
- static class Params {
+ private static class Params {
String mKeyboardLayoutSetName;
int mMode;
EditorInfo mEditorInfo;
@@ -120,12 +107,8 @@
int mWidth;
// Sparse array of KeyboardLayoutSet element parameters indexed by element's id.
final SparseArray<ElementParams> mKeyboardLayoutSetElementIdToParamsMap =
- new SparseArray<ElementParams>();
-
- static class ElementParams {
- int mKeyboardXmlId;
- boolean mProximityCharsCorrectionEnabled;
- }
+ CollectionUtils.newSparseArray();
+ public Params() {}
}
public static void clearKeyboardCache() {
@@ -133,12 +116,12 @@
sKeysCache.clear();
}
- private KeyboardLayoutSet(Context context, Params params) {
+ KeyboardLayoutSet(final Context context, final Params params) {
mContext = context;
mParams = params;
}
- public Keyboard getKeyboard(int baseKeyboardLayoutSetElementId) {
+ public Keyboard getKeyboard(final int baseKeyboardLayoutSetElementId) {
final int keyboardLayoutSetElementId;
switch (mParams.mMode) {
case KeyboardId.MODE_PHONE:
@@ -173,12 +156,12 @@
}
}
- private Keyboard getKeyboard(ElementParams elementParams, final KeyboardId id) {
+ private Keyboard getKeyboard(final ElementParams elementParams, final KeyboardId id) {
final SoftReference<Keyboard> ref = sKeyboardCache.get(id);
Keyboard keyboard = (ref == null) ? null : ref.get();
if (keyboard == null) {
- final Keyboard.Builder<Keyboard.Params> builder =
- new Keyboard.Builder<Keyboard.Params>(mContext, new Keyboard.Params());
+ final KeyboardBuilder<KeyboardParams> builder =
+ new KeyboardBuilder<KeyboardParams>(mContext, new KeyboardParams());
if (id.isAlphabetKeyboard()) {
builder.setAutoGenerate(sKeysCache);
}
@@ -205,7 +188,7 @@
// KeyboardLayoutSet element id that is a key in keyboard_set.xml. Also that file specifies
// which XML layout should be used for each keyboard. The KeyboardId is an internal key for
// Keyboard object.
- private KeyboardId getKeyboardId(int keyboardLayoutSetElementId) {
+ private KeyboardId getKeyboardId(final int keyboardLayoutSetElementId) {
final Params params = mParams;
final boolean isSymbols = (keyboardLayoutSetElementId == KeyboardId.ELEMENT_SYMBOLS
|| keyboardLayoutSetElementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED);
@@ -228,7 +211,7 @@
private static final EditorInfo EMPTY_EDITOR_INFO = new EditorInfo();
- public Builder(Context context, EditorInfo editorInfo) {
+ public Builder(final Context context, final EditorInfo editorInfo) {
mContext = context;
mPackageName = context.getPackageName();
mResources = context.getResources();
@@ -241,7 +224,8 @@
mPackageName, NO_SETTINGS_KEY, mEditorInfo);
}
- public Builder setScreenGeometry(int deviceFormFactor, int orientation, int widthPixels) {
+ public Builder setScreenGeometry(final int deviceFormFactor, final int orientation,
+ final int widthPixels) {
final Params params = mParams;
params.mDeviceFormFactor = deviceFormFactor;
params.mOrientation = orientation;
@@ -249,7 +233,7 @@
return this;
}
- public Builder setSubtype(InputMethodSubtype subtype) {
+ public Builder setSubtype(final InputMethodSubtype subtype) {
final boolean asciiCapable = subtype.containsExtraValueKey(ASCII_CAPABLE);
@SuppressWarnings("deprecation")
final boolean deprecatedForceAscii = InputAttributes.inPrivateImeOptions(
@@ -266,8 +250,8 @@
return this;
}
- public Builder setOptions(boolean voiceKeyEnabled, boolean voiceKeyOnMain,
- boolean languageSwitchKeyEnabled) {
+ public Builder setOptions(final boolean voiceKeyEnabled, final boolean voiceKeyOnMain,
+ final boolean languageSwitchKeyEnabled) {
@SuppressWarnings("deprecation")
final boolean deprecatedNoMicrophone = InputAttributes.inPrivateImeOptions(
null, NO_MICROPHONE_COMPAT, mEditorInfo);
@@ -280,7 +264,7 @@
return this;
}
- public void setTouchPositionCorrectionEnabled(boolean enabled) {
+ public void setTouchPositionCorrectionEnabled(final boolean enabled) {
mParams.mTouchPositionCorrectionEnabled = enabled;
}
@@ -301,7 +285,7 @@
return new KeyboardLayoutSet(mContext, mParams);
}
- private void parseKeyboardLayoutSet(Resources res, int resId)
+ private void parseKeyboardLayoutSet(final Resources res, final int resId)
throws XmlPullParserException, IOException {
final XmlResourceParser parser = res.getXml(resId);
try {
@@ -321,7 +305,7 @@
}
}
- private void parseKeyboardLayoutSetContent(XmlPullParser parser)
+ private void parseKeyboardLayoutSetContent(final XmlPullParser parser)
throws XmlPullParserException, IOException {
int event;
while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
@@ -343,7 +327,7 @@
}
}
- private void parseKeyboardLayoutSetElement(XmlPullParser parser)
+ private void parseKeyboardLayoutSetElement(final XmlPullParser parser)
throws XmlPullParserException, IOException {
final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.KeyboardLayoutSet_Element);
@@ -370,7 +354,7 @@
}
}
- private static int getKeyboardMode(EditorInfo editorInfo) {
+ private static int getKeyboardMode(final EditorInfo editorInfo) {
if (editorInfo == null)
return KeyboardId.MODE_TEXT;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 10f651a..fd789f0 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -21,7 +21,6 @@
import android.content.res.Resources;
import android.util.Log;
import android.view.ContextThemeWrapper;
-import android.view.InflateException;
import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.EditorInfo;
@@ -38,7 +37,7 @@
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SettingsValues;
import com.android.inputmethod.latin.SubtypeSwitcher;
-import com.android.inputmethod.latin.Utils;
+import com.android.inputmethod.latin.WordComposer;
public class KeyboardSwitcher implements KeyboardState.SwitchActions {
private static final String TAG = KeyboardSwitcher.class.getSimpleName();
@@ -46,24 +45,24 @@
public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20110916";
static class KeyboardTheme {
- public final String mName;
public final int mThemeId;
public final int mStyleId;
- public KeyboardTheme(String name, int themeId, int styleId) {
- mName = name;
+ // Note: The themeId should be aligned with "themeId" attribute of Keyboard style
+ // in values/style.xml.
+ public KeyboardTheme(int themeId, int styleId) {
mThemeId = themeId;
mStyleId = styleId;
}
}
private static final KeyboardTheme[] KEYBOARD_THEMES = {
- new KeyboardTheme("Basic", 0, R.style.KeyboardTheme),
- new KeyboardTheme("HighContrast", 1, R.style.KeyboardTheme_HighContrast),
- new KeyboardTheme("Stone", 6, R.style.KeyboardTheme_Stone),
- new KeyboardTheme("Stone.Bold", 7, R.style.KeyboardTheme_Stone_Bold),
- new KeyboardTheme("GingerBread", 8, R.style.KeyboardTheme_Gingerbread),
- new KeyboardTheme("IceCreamSandwich", 5, R.style.KeyboardTheme_IceCreamSandwich),
+ new KeyboardTheme(0, R.style.KeyboardTheme),
+ new KeyboardTheme(1, R.style.KeyboardTheme_HighContrast),
+ new KeyboardTheme(6, R.style.KeyboardTheme_Stone),
+ new KeyboardTheme(7, R.style.KeyboardTheme_Stone_Bold),
+ new KeyboardTheme(8, R.style.KeyboardTheme_Gingerbread),
+ new KeyboardTheme(5, R.style.KeyboardTheme_IceCreamSandwich),
};
private SubtypeSwitcher mSubtypeSwitcher;
@@ -354,22 +353,9 @@
mKeyboardView.closing();
}
- Utils.GCUtils.getInstance().reset();
- boolean tryGC = true;
- for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
- try {
- setContextThemeWrapper(mLatinIME, mKeyboardTheme);
- mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
- R.layout.input_view, null);
- tryGC = false;
- } catch (OutOfMemoryError e) {
- Log.w(TAG, "load keyboard failed: " + e);
- tryGC = Utils.GCUtils.getInstance().tryGCOrWait(mKeyboardTheme.mName, e);
- } catch (InflateException e) {
- Log.w(TAG, "load keyboard failed: " + e);
- tryGC = Utils.GCUtils.getInstance().tryGCOrWait(mKeyboardTheme.mName, e);
- }
- }
+ setContextThemeWrapper(mLatinIME, mKeyboardTheme);
+ mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
+ R.layout.input_view, null);
mKeyboardView = (MainKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view);
if (isHardwareAcceleratedDrawingEnabled) {
@@ -402,4 +388,16 @@
}
}
}
+
+ public int getManualCapsMode() {
+ switch (getKeyboard().mId.mElementId) {
+ case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
+ case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
+ return WordComposer.CAPS_MODE_MANUAL_SHIFT_LOCKED;
+ case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
+ return WordComposer.CAPS_MODE_MANUAL_SHIFTED;
+ default:
+ return WordComposer.CAPS_MODE_OFF;
+ }
+ }
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index fcf97b9..5b02f9f 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -30,6 +30,7 @@
import android.graphics.drawable.Drawable;
import android.os.Message;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.SparseArray;
import android.util.TypedValue;
import android.view.LayoutInflater;
@@ -37,7 +38,11 @@
import android.view.ViewGroup;
import android.widget.TextView;
+import com.android.inputmethod.keyboard.internal.KeyDrawParams;
+import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams;
+import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;
import com.android.inputmethod.keyboard.internal.PreviewPlacerView;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
@@ -51,39 +56,72 @@
/**
* A view that renders a virtual {@link Keyboard}.
*
- * @attr ref R.styleable#KeyboardView_backgroundDimAlpha
* @attr ref R.styleable#KeyboardView_keyBackground
- * @attr ref R.styleable#KeyboardView_keyLetterRatio
- * @attr ref R.styleable#KeyboardView_keyLargeLetterRatio
- * @attr ref R.styleable#KeyboardView_keyLabelRatio
- * @attr ref R.styleable#KeyboardView_keyHintLetterRatio
- * @attr ref R.styleable#KeyboardView_keyShiftedLetterHintRatio
- * @attr ref R.styleable#KeyboardView_keyHintLabelRatio
+ * @attr ref R.styleable#KeyboardView_moreKeysLayout
+ * @attr ref R.styleable#KeyboardView_keyPreviewLayout
+ * @attr ref R.styleable#KeyboardView_keyPreviewBackground
+ * @attr ref R.styleable#KeyboardView_keyPreviewLeftBackground
+ * @attr ref R.styleable#KeyboardView_keyPreviewRightBackground
+ * @attr ref R.styleable#KeyboardView_keyPreviewOffset
+ * @attr ref R.styleable#KeyboardView_keyPreviewHeight
+ * @attr ref R.styleable#KeyboardView_keyPreviewLingerTimeout
* @attr ref R.styleable#KeyboardView_keyLabelHorizontalPadding
* @attr ref R.styleable#KeyboardView_keyHintLetterPadding
* @attr ref R.styleable#KeyboardView_keyPopupHintLetterPadding
* @attr ref R.styleable#KeyboardView_keyShiftedLetterHintPadding
- * @attr ref R.styleable#KeyboardView_keyTextStyle
- * @attr ref R.styleable#KeyboardView_keyPreviewLayout
- * @attr ref R.styleable#KeyboardView_keyPreviewTextRatio
- * @attr ref R.styleable#KeyboardView_keyPreviewOffset
- * @attr ref R.styleable#KeyboardView_keyPreviewHeight
- * @attr ref R.styleable#KeyboardView_keyTextColor
- * @attr ref R.styleable#KeyboardView_keyTextColorDisabled
- * @attr ref R.styleable#KeyboardView_keyHintLetterColor
- * @attr ref R.styleable#KeyboardView_keyHintLabelColor
- * @attr ref R.styleable#KeyboardView_keyShiftedLetterHintInactivatedColor
- * @attr ref R.styleable#KeyboardView_keyShiftedLetterHintActivatedColor
- * @attr ref R.styleable#KeyboardView_shadowColor
- * @attr ref R.styleable#KeyboardView_shadowRadius
+ * @attr ref R.styleable#KeyboardView_keyTextShadowRadius
+ * @attr ref R.styleable#KeyboardView_backgroundDimAlpha
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextSize
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextColor
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextOffset
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextShadingColor
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextShadingBorder
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextShadowColor
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextShadowBorder
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextConnectorColor
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextConnectorWidth
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextLingerTimeout
+ * @attr ref R.styleable#KeyboardView_gesturePreviewTrailFadeoutStartDelay
+ * @attr ref R.styleable#KeyboardView_gesturePreviewTrailFadeoutDuration
+ * @attr ref R.styleable#KeyboardView_gesturePreviewTrailUpdateInterval
+ * @attr ref R.styleable#KeyboardView_gesturePreviewTrailColor
+ * @attr ref R.styleable#KeyboardView_gesturePreviewTrailWidth
+ * @attr ref R.styleable#KeyboardView_verticalCorrection
+ * @attr ref R.styleable#Keyboard_Key_keyTypeface
+ * @attr ref R.styleable#Keyboard_Key_keyLetterSize
+ * @attr ref R.styleable#Keyboard_Key_keyLabelSize
+ * @attr ref R.styleable#Keyboard_Key_keyLargeLetterRatio
+ * @attr ref R.styleable#Keyboard_Key_keyLargeLabelRatio
+ * @attr ref R.styleable#Keyboard_Key_keyHintLetterRatio
+ * @attr ref R.styleable#Keyboard_Key_keyShiftedLetterHintRatio
+ * @attr ref R.styleable#Keyboard_Key_keyHintLabelRatio
+ * @attr ref R.styleable#Keyboard_Key_keyPreviewTextRatio
+ * @attr ref R.styleable#Keyboard_Key_keyTextColor
+ * @attr ref R.styleable#Keyboard_Key_keyTextColorDisabled
+ * @attr ref R.styleable#Keyboard_Key_keyTextShadowColor
+ * @attr ref R.styleable#Keyboard_Key_keyHintLetterColor
+ * @attr ref R.styleable#Keyboard_Key_keyHintLabelColor
+ * @attr ref R.styleable#Keyboard_Key_keyShiftedLetterHintInactivatedColor
+ * @attr ref R.styleable#Keyboard_Key_keyShiftedLetterHintActivatedColor
+ * @attr ref R.styleable#Keyboard_Key_keyPreviewTextColor
*/
public class KeyboardView extends View implements PointerTracker.DrawingProxy {
+ private static final String TAG = KeyboardView.class.getSimpleName();
+
// Miscellaneous constants
private static final int[] LONG_PRESSABLE_STATE_SET = { android.R.attr.state_long_pressable };
// XML attributes
+ private final KeyVisualAttributes mKeyVisualAttributes;
+ private final int mKeyLabelHorizontalPadding;
+ private final float mKeyHintLetterPadding;
+ private final float mKeyPopupHintLetterPadding;
+ private final float mKeyShiftedLetterHintPadding;
+ private final float mKeyTextShadowRadius;
protected final float mVerticalCorrection;
protected final int mMoreKeysLayout;
+ protected final Drawable mKeyBackground;
+ protected final Rect mKeyBackgroundPadding = new Rect();
private final int mBackgroundDimAlpha;
// HORIZONTAL ELLIPSIS "...", character for popup hint.
@@ -99,11 +137,19 @@
// Main keyboard
private Keyboard mKeyboard;
- protected final KeyDrawParams mKeyDrawParams;
+ protected final KeyDrawParams mKeyDrawParams = new KeyDrawParams();
// Key preview
+ private static final int PREVIEW_ALPHA = 240;
private final int mKeyPreviewLayoutId;
- protected final KeyPreviewDrawParams mKeyPreviewDrawParams;
+ private final Drawable mPreviewBackground;
+ private final Drawable mPreviewLeftBackground;
+ private final Drawable mPreviewRightBackground;
+ private final int mPreviewOffset;
+ private final int mPreviewHeight;
+ private final int mPreviewLingerTimeout;
+ private final SparseArray<TextView> mKeyPreviewTexts = CollectionUtils.newSparseArray();
+ protected final KeyPreviewDrawParams mKeyPreviewDrawParams = new KeyPreviewDrawParams();
private boolean mShowKeyPreviewPopup = true;
private int mDelayAfterPreview;
private final PreviewPlacerView mPreviewPlacerView;
@@ -114,7 +160,7 @@
/** True if all keys should be drawn */
private boolean mInvalidateAllKeys;
/** The keys that should be drawn */
- private final HashSet<Key> mInvalidatedKeys = new HashSet<Key>();
+ private final HashSet<Key> mInvalidatedKeys = CollectionUtils.newHashSet();
/** The working rectangle variable */
private final Rect mWorkingRect = new Rect();
/** The keyboard bitmap buffer for faster updates */
@@ -126,9 +172,9 @@
private final Paint mPaint = new Paint();
private final Paint.FontMetrics mFontMetrics = new Paint.FontMetrics();
// This sparse array caches key label text height in pixel indexed by key label text size.
- private static final SparseArray<Float> sTextHeightCache = new SparseArray<Float>();
+ private static final SparseArray<Float> sTextHeightCache = CollectionUtils.newSparseArray();
// This sparse array caches key label text width in pixel indexed by key label text size.
- private static final SparseArray<Float> sTextWidthCache = new SparseArray<Float>();
+ private static final SparseArray<Float> sTextWidthCache = CollectionUtils.newSparseArray();
private static final char[] KEY_LABEL_REFERENCE_CHAR = { 'M' };
private static final char[] KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR = { '8' };
@@ -137,31 +183,34 @@
public static class DrawingHandler extends StaticInnerHandlerWrapper<KeyboardView> {
private static final int MSG_DISMISS_KEY_PREVIEW = 0;
- public DrawingHandler(KeyboardView outerInstance) {
+ public DrawingHandler(final KeyboardView outerInstance) {
super(outerInstance);
}
@Override
- public void handleMessage(Message msg) {
+ public void handleMessage(final Message msg) {
final KeyboardView keyboardView = getOuterInstance();
if (keyboardView == null) return;
final PointerTracker tracker = (PointerTracker) msg.obj;
switch (msg.what) {
case MSG_DISMISS_KEY_PREVIEW:
- tracker.getKeyPreviewText().setVisibility(View.INVISIBLE);
+ final TextView previewText = keyboardView.mKeyPreviewTexts.get(tracker.mPointerId);
+ if (previewText != null) {
+ previewText.setVisibility(INVISIBLE);
+ }
break;
}
}
- public void dismissKeyPreview(long delay, PointerTracker tracker) {
+ public void dismissKeyPreview(final long delay, final PointerTracker tracker) {
sendMessageDelayed(obtainMessage(MSG_DISMISS_KEY_PREVIEW, tracker), delay);
}
- public void cancelDismissKeyPreview(PointerTracker tracker) {
+ public void cancelDismissKeyPreview(final PointerTracker tracker) {
removeMessages(MSG_DISMISS_KEY_PREVIEW, tracker);
}
- public void cancelAllDismissKeyPreviews() {
+ private void cancelAllDismissKeyPreviews() {
removeMessages(MSG_DISMISS_KEY_PREVIEW);
}
@@ -170,220 +219,74 @@
}
}
- protected static class KeyDrawParams {
- // XML attributes
- public final int mKeyTextColor;
- public final int mKeyTextInactivatedColor;
- public final Typeface mKeyTextStyle;
- public final float mKeyLabelHorizontalPadding;
- public final float mKeyHintLetterPadding;
- public final float mKeyPopupHintLetterPadding;
- public final float mKeyShiftedLetterHintPadding;
- public final int mShadowColor;
- public final float mShadowRadius;
- public final Drawable mKeyBackground;
- public final int mKeyHintLetterColor;
- public final int mKeyHintLabelColor;
- public final int mKeyShiftedLetterHintInactivatedColor;
- public final int mKeyShiftedLetterHintActivatedColor;
-
- /* package */ final float mKeyLetterRatio;
- private final float mKeyLargeLetterRatio;
- private final float mKeyLabelRatio;
- private final float mKeyLargeLabelRatio;
- private final float mKeyHintLetterRatio;
- private final float mKeyShiftedLetterHintRatio;
- private final float mKeyHintLabelRatio;
- private static final float UNDEFINED_RATIO = -1.0f;
-
- public final Rect mPadding = new Rect();
- public int mKeyLetterSize;
- public int mKeyLargeLetterSize;
- public int mKeyLabelSize;
- public int mKeyLargeLabelSize;
- public int mKeyHintLetterSize;
- public int mKeyShiftedLetterHintSize;
- public int mKeyHintLabelSize;
- public int mAnimAlpha;
-
- public KeyDrawParams(TypedArray a) {
- mKeyBackground = a.getDrawable(R.styleable.KeyboardView_keyBackground);
- if (a.hasValue(R.styleable.KeyboardView_keyLetterSize)) {
- mKeyLetterRatio = UNDEFINED_RATIO;
- mKeyLetterSize = a.getDimensionPixelSize(R.styleable.KeyboardView_keyLetterSize, 0);
- } else {
- mKeyLetterRatio = getRatio(a, R.styleable.KeyboardView_keyLetterRatio);
- }
- if (a.hasValue(R.styleable.KeyboardView_keyLabelSize)) {
- mKeyLabelRatio = UNDEFINED_RATIO;
- mKeyLabelSize = a.getDimensionPixelSize(R.styleable.KeyboardView_keyLabelSize, 0);
- } else {
- mKeyLabelRatio = getRatio(a, R.styleable.KeyboardView_keyLabelRatio);
- }
- mKeyLargeLabelRatio = getRatio(a, R.styleable.KeyboardView_keyLargeLabelRatio);
- mKeyLargeLetterRatio = getRatio(a, R.styleable.KeyboardView_keyLargeLetterRatio);
- mKeyHintLetterRatio = getRatio(a, R.styleable.KeyboardView_keyHintLetterRatio);
- mKeyShiftedLetterHintRatio = getRatio(a,
- R.styleable.KeyboardView_keyShiftedLetterHintRatio);
- mKeyHintLabelRatio = getRatio(a, R.styleable.KeyboardView_keyHintLabelRatio);
- mKeyLabelHorizontalPadding = a.getDimension(
- R.styleable.KeyboardView_keyLabelHorizontalPadding, 0);
- mKeyHintLetterPadding = a.getDimension(
- R.styleable.KeyboardView_keyHintLetterPadding, 0);
- mKeyPopupHintLetterPadding = a.getDimension(
- R.styleable.KeyboardView_keyPopupHintLetterPadding, 0);
- mKeyShiftedLetterHintPadding = a.getDimension(
- R.styleable.KeyboardView_keyShiftedLetterHintPadding, 0);
- mKeyTextColor = a.getColor(R.styleable.KeyboardView_keyTextColor, 0xFF000000);
- mKeyTextInactivatedColor = a.getColor(
- R.styleable.KeyboardView_keyTextInactivatedColor, 0xFF000000);
- mKeyHintLetterColor = a.getColor(R.styleable.KeyboardView_keyHintLetterColor, 0);
- mKeyHintLabelColor = a.getColor(R.styleable.KeyboardView_keyHintLabelColor, 0);
- mKeyShiftedLetterHintInactivatedColor = a.getColor(
- R.styleable.KeyboardView_keyShiftedLetterHintInactivatedColor, 0);
- mKeyShiftedLetterHintActivatedColor = a.getColor(
- R.styleable.KeyboardView_keyShiftedLetterHintActivatedColor, 0);
- mKeyTextStyle = Typeface.defaultFromStyle(
- a.getInt(R.styleable.KeyboardView_keyTextStyle, Typeface.NORMAL));
- mShadowColor = a.getColor(R.styleable.KeyboardView_shadowColor, 0);
- mShadowRadius = a.getFloat(R.styleable.KeyboardView_shadowRadius, 0f);
-
- mKeyBackground.getPadding(mPadding);
- }
-
- public void updateKeyHeight(int keyHeight) {
- if (mKeyLetterRatio >= 0.0f) {
- mKeyLetterSize = (int)(keyHeight * mKeyLetterRatio);
- }
- if (mKeyLabelRatio >= 0.0f) {
- mKeyLabelSize = (int)(keyHeight * mKeyLabelRatio);
- }
- mKeyLargeLabelSize = (int)(keyHeight * mKeyLargeLabelRatio);
- mKeyLargeLetterSize = (int)(keyHeight * mKeyLargeLetterRatio);
- mKeyHintLetterSize = (int)(keyHeight * mKeyHintLetterRatio);
- mKeyShiftedLetterHintSize = (int)(keyHeight * mKeyShiftedLetterHintRatio);
- mKeyHintLabelSize = (int)(keyHeight * mKeyHintLabelRatio);
- }
-
- public void blendAlpha(Paint paint) {
- final int color = paint.getColor();
- paint.setARGB((paint.getAlpha() * mAnimAlpha) / Constants.Color.ALPHA_OPAQUE,
- Color.red(color), Color.green(color), Color.blue(color));
- }
- }
-
- /* package */ static class KeyPreviewDrawParams {
- // XML attributes.
- public final Drawable mPreviewBackground;
- public final Drawable mPreviewLeftBackground;
- public final Drawable mPreviewRightBackground;
- public final int mPreviewTextColor;
- public final int mPreviewOffset;
- public final int mPreviewHeight;
- public final Typeface mKeyTextStyle;
- public final int mLingerTimeout;
-
- private final float mPreviewTextRatio;
- private final float mKeyLetterRatio;
-
- // The graphical geometry of the key preview.
- // <-width->
- // +-------+ ^
- // | | |
- // |preview| height (visible)
- // | | |
- // + + ^ v
- // \ / |offset
- // +-\ /-+ v
- // | +-+ |
- // |parent |
- // | key|
- // +-------+
- // The background of a {@link TextView} being used for a key preview may have invisible
- // paddings. To align the more keys keyboard panel's visible part with the visible part of
- // the background, we need to record the width and height of key preview that don't include
- // invisible paddings.
- public int mPreviewVisibleWidth;
- public int mPreviewVisibleHeight;
- // The key preview may have an arbitrary offset and its background that may have a bottom
- // padding. To align the more keys keyboard and the key preview we also need to record the
- // offset between the top edge of parent key and the bottom of the visible part of key
- // preview background.
- public int mPreviewVisibleOffset;
-
- public int mPreviewTextSize;
- public int mKeyLetterSize;
- public final int[] mCoordinates = new int[2];
-
- private static final int PREVIEW_ALPHA = 240;
-
- public KeyPreviewDrawParams(TypedArray a, KeyDrawParams keyDrawParams) {
- mPreviewBackground = a.getDrawable(R.styleable.KeyboardView_keyPreviewBackground);
- mPreviewLeftBackground = a.getDrawable(
- R.styleable.KeyboardView_keyPreviewLeftBackground);
- mPreviewRightBackground = a.getDrawable(
- R.styleable.KeyboardView_keyPreviewRightBackground);
- setAlpha(mPreviewBackground, PREVIEW_ALPHA);
- setAlpha(mPreviewLeftBackground, PREVIEW_ALPHA);
- setAlpha(mPreviewRightBackground, PREVIEW_ALPHA);
- mPreviewOffset = a.getDimensionPixelOffset(
- R.styleable.KeyboardView_keyPreviewOffset, 0);
- mPreviewHeight = a.getDimensionPixelSize(
- R.styleable.KeyboardView_keyPreviewHeight, 80);
- mPreviewTextRatio = getRatio(a, R.styleable.KeyboardView_keyPreviewTextRatio);
- mPreviewTextColor = a.getColor(R.styleable.KeyboardView_keyPreviewTextColor, 0);
- mLingerTimeout = a.getInt(R.styleable.KeyboardView_keyPreviewLingerTimeout, 0);
-
- mKeyLetterRatio = keyDrawParams.mKeyLetterRatio;
- mKeyTextStyle = keyDrawParams.mKeyTextStyle;
- }
-
- public void updateKeyHeight(int keyHeight) {
- if (mPreviewTextRatio >= 0.0f) {
- mPreviewTextSize = (int)(keyHeight * mPreviewTextRatio);
- }
- if (mKeyLetterRatio >= 0.0f) {
- mKeyLetterSize = (int)(keyHeight * mKeyLetterRatio);
- }
- }
-
- private static void setAlpha(Drawable drawable, int alpha) {
- if (drawable == null) return;
- drawable.setAlpha(alpha);
- }
- }
-
- public KeyboardView(Context context, AttributeSet attrs) {
+ public KeyboardView(final Context context, final AttributeSet attrs) {
this(context, attrs, R.attr.keyboardViewStyle);
}
- public KeyboardView(Context context, AttributeSet attrs, int defStyle) {
+ public KeyboardView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
- final TypedArray a = context.obtainStyledAttributes(
- attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
-
- mKeyDrawParams = new KeyDrawParams(a);
- mKeyPreviewDrawParams = new KeyPreviewDrawParams(a, mKeyDrawParams);
- mKeyPreviewLayoutId = a.getResourceId(R.styleable.KeyboardView_keyPreviewLayout, 0);
+ final TypedArray keyboardViewAttr = context.obtainStyledAttributes(attrs,
+ R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
+ mKeyBackground = keyboardViewAttr.getDrawable(R.styleable.KeyboardView_keyBackground);
+ mKeyBackground.getPadding(mKeyBackgroundPadding);
+ mPreviewBackground = keyboardViewAttr.getDrawable(
+ R.styleable.KeyboardView_keyPreviewBackground);
+ mPreviewLeftBackground = keyboardViewAttr.getDrawable(
+ R.styleable.KeyboardView_keyPreviewLeftBackground);
+ mPreviewRightBackground = keyboardViewAttr.getDrawable(
+ R.styleable.KeyboardView_keyPreviewRightBackground);
+ setAlpha(mPreviewBackground, PREVIEW_ALPHA);
+ setAlpha(mPreviewLeftBackground, PREVIEW_ALPHA);
+ setAlpha(mPreviewRightBackground, PREVIEW_ALPHA);
+ mPreviewOffset = keyboardViewAttr.getDimensionPixelOffset(
+ R.styleable.KeyboardView_keyPreviewOffset, 0);
+ mPreviewHeight = keyboardViewAttr.getDimensionPixelSize(
+ R.styleable.KeyboardView_keyPreviewHeight, 80);
+ mPreviewLingerTimeout = keyboardViewAttr.getInt(
+ R.styleable.KeyboardView_keyPreviewLingerTimeout, 0);
+ mDelayAfterPreview = mPreviewLingerTimeout;
+ mKeyLabelHorizontalPadding = keyboardViewAttr.getDimensionPixelOffset(
+ R.styleable.KeyboardView_keyLabelHorizontalPadding, 0);
+ mKeyHintLetterPadding = keyboardViewAttr.getDimension(
+ R.styleable.KeyboardView_keyHintLetterPadding, 0);
+ mKeyPopupHintLetterPadding = keyboardViewAttr.getDimension(
+ R.styleable.KeyboardView_keyPopupHintLetterPadding, 0);
+ mKeyShiftedLetterHintPadding = keyboardViewAttr.getDimension(
+ R.styleable.KeyboardView_keyShiftedLetterHintPadding, 0);
+ mKeyTextShadowRadius = keyboardViewAttr.getFloat(
+ R.styleable.KeyboardView_keyTextShadowRadius, 0.0f);
+ mKeyPreviewLayoutId = keyboardViewAttr.getResourceId(
+ R.styleable.KeyboardView_keyPreviewLayout, 0);
if (mKeyPreviewLayoutId == 0) {
mShowKeyPreviewPopup = false;
}
- mVerticalCorrection = a.getDimensionPixelOffset(
+ mVerticalCorrection = keyboardViewAttr.getDimension(
R.styleable.KeyboardView_verticalCorrection, 0);
- mMoreKeysLayout = a.getResourceId(R.styleable.KeyboardView_moreKeysLayout, 0);
- mBackgroundDimAlpha = a.getInt(R.styleable.KeyboardView_backgroundDimAlpha, 0);
- mPreviewPlacerView = new PreviewPlacerView(context, a);
- a.recycle();
+ mMoreKeysLayout = keyboardViewAttr.getResourceId(
+ R.styleable.KeyboardView_moreKeysLayout, 0);
+ mBackgroundDimAlpha = keyboardViewAttr.getInt(
+ R.styleable.KeyboardView_backgroundDimAlpha, 0);
+ keyboardViewAttr.recycle();
- mDelayAfterPreview = mKeyPreviewDrawParams.mLingerTimeout;
+ final TypedArray keyAttr = context.obtainStyledAttributes(attrs,
+ R.styleable.Keyboard_Key, defStyle, R.style.KeyboardView);
+ mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr);
+ keyAttr.recycle();
+ mPreviewPlacerView = new PreviewPlacerView(context, attrs);
mPaint.setAntiAlias(true);
}
- // Read fraction value in TypedArray as float.
- /* package */ static float getRatio(TypedArray a, int index) {
- return a.getFraction(index, 1000, 1000, 1) / 1000.0f;
+ private static void setAlpha(final Drawable drawable, final int alpha) {
+ if (drawable == null) return;
+ drawable.setAlpha(alpha);
+ }
+
+ private static void blendAlpha(final Paint paint, final int alpha) {
+ final int color = paint.getColor();
+ paint.setARGB((paint.getAlpha() * alpha) / Constants.Color.ALPHA_OPAQUE,
+ Color.red(color), Color.green(color), Color.blue(color));
}
/**
@@ -393,14 +296,14 @@
* @see #getKeyboard()
* @param keyboard the keyboard to display in this view
*/
- public void setKeyboard(Keyboard keyboard) {
+ public void setKeyboard(final Keyboard keyboard) {
mKeyboard = keyboard;
LatinImeLogger.onSetKeyboard(keyboard);
requestLayout();
invalidateAllKeys();
final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
- mKeyDrawParams.updateKeyHeight(keyHeight);
- mKeyPreviewDrawParams.updateKeyHeight(keyHeight);
+ mKeyDrawParams.updateParams(keyHeight, mKeyVisualAttributes);
+ mKeyDrawParams.updateParams(keyHeight, keyboard.mKeyVisualAttributes);
}
/**
@@ -419,7 +322,7 @@
* @param delay the delay after which the preview is dismissed
* @see #isKeyPreviewPopupEnabled()
*/
- public void setKeyPreviewPopupEnabled(boolean previewEnabled, int delay) {
+ public void setKeyPreviewPopupEnabled(final boolean previewEnabled, final int delay) {
mShowKeyPreviewPopup = previewEnabled;
mDelayAfterPreview = delay;
}
@@ -433,14 +336,14 @@
return mShowKeyPreviewPopup;
}
- public void setGesturePreviewMode(boolean drawsGesturePreviewTrail,
- boolean drawsGestureFloatingPreviewText) {
+ public void setGesturePreviewMode(final boolean drawsGesturePreviewTrail,
+ final boolean drawsGestureFloatingPreviewText) {
mPreviewPlacerView.setGesturePreviewMode(
drawsGesturePreviewTrail, drawsGestureFloatingPreviewText);
}
@Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
if (mKeyboard != null) {
// The main keyboard expands to the display width.
final int height = mKeyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom();
@@ -451,7 +354,7 @@
}
@Override
- public void onDraw(Canvas canvas) {
+ public void onDraw(final Canvas canvas) {
super.onDraw(canvas);
if (canvas.isHardwareAccelerated()) {
onDrawKeyboard(canvas);
@@ -462,12 +365,7 @@
if (bufferNeedsUpdates || mOffscreenBuffer == null) {
if (maybeAllocateOffscreenBuffer()) {
mInvalidateAllKeys = true;
- // TODO: Stop using the offscreen canvas even when in software rendering
- if (mOffscreenCanvas != null) {
- mOffscreenCanvas.setBitmap(mOffscreenBuffer);
- } else {
- mOffscreenCanvas = new Canvas(mOffscreenBuffer);
- }
+ maybeCreateOffscreenCanvas();
}
onDrawKeyboard(mOffscreenCanvas);
}
@@ -496,13 +394,21 @@
}
}
+ private void maybeCreateOffscreenCanvas() {
+ // TODO: Stop using the offscreen canvas even when in software rendering
+ if (mOffscreenCanvas != null) {
+ mOffscreenCanvas.setBitmap(mOffscreenBuffer);
+ } else {
+ mOffscreenCanvas = new Canvas(mOffscreenBuffer);
+ }
+ }
+
private void onDrawKeyboard(final Canvas canvas) {
if (mKeyboard == null) return;
final int width = getWidth();
final int height = getHeight();
final Paint paint = mPaint;
- final KeyDrawParams params = mKeyDrawParams;
// Calculate clip region and set.
final boolean drawAllKeys = mInvalidateAllKeys || mInvalidatedKeys.isEmpty();
@@ -535,13 +441,13 @@
if (drawAllKeys || isHardwareAccelerated) {
// Draw all keys.
for (final Key key : mKeyboard.mKeys) {
- onDrawKey(key, canvas, paint, params);
+ onDrawKey(key, canvas, paint);
}
} else {
// Draw invalidated keys.
for (final Key key : mInvalidatedKeys) {
if (mKeyboard.hasKey(key)) {
- onDrawKey(key, canvas, paint, params);
+ onDrawKey(key, canvas, paint);
}
}
}
@@ -565,7 +471,7 @@
mInvalidateAllKeys = false;
}
- public void dimEntireKeyboard(boolean dimmed) {
+ public void dimEntireKeyboard(final boolean dimmed) {
final boolean needsRedrawing = mNeedsToDimEntireKeyboard != dimmed;
mNeedsToDimEntireKeyboard = dimmed;
if (needsRedrawing) {
@@ -573,14 +479,18 @@
}
}
- private void onDrawKey(Key key, Canvas canvas, Paint paint, KeyDrawParams params) {
- final int keyDrawX = key.mX + key.mVisualInsetsLeft + getPaddingLeft();
+ private void onDrawKey(final Key key, final Canvas canvas, final Paint paint) {
+ final int keyDrawX = key.getDrawX() + getPaddingLeft();
final int keyDrawY = key.mY + getPaddingTop();
canvas.translate(keyDrawX, keyDrawY);
+ final int keyHeight = mKeyboard.mMostCommonKeyHeight - mKeyboard.mVerticalGap;
+ final KeyVisualAttributes attr = key.mKeyVisualAttributes;
+ final KeyDrawParams params = mKeyDrawParams.mayCloneAndUpdateParams(keyHeight, attr);
params.mAnimAlpha = Constants.Color.ALPHA_OPAQUE;
+
if (!key.isSpacer()) {
- onDrawKeyBackground(key, canvas, params);
+ onDrawKeyBackground(key, canvas);
}
onDrawKeyTopVisuals(key, canvas, paint, params);
@@ -588,14 +498,14 @@
}
// Draw key background.
- protected void onDrawKeyBackground(Key key, Canvas canvas, KeyDrawParams params) {
- final int bgWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight
- + params.mPadding.left + params.mPadding.right;
- final int bgHeight = key.mHeight + params.mPadding.top + params.mPadding.bottom;
- final int bgX = -params.mPadding.left;
- final int bgY = -params.mPadding.top;
+ protected void onDrawKeyBackground(Key key, Canvas canvas) {
+ final Rect padding = mKeyBackgroundPadding;
+ final int bgWidth = key.getDrawWidth() + padding.left + padding.right;
+ final int bgHeight = key.mHeight + padding.top + padding.bottom;
+ final int bgX = -padding.left;
+ final int bgY = -padding.top;
final int[] drawableState = key.getCurrentDrawableState();
- final Drawable background = params.mKeyBackground;
+ final Drawable background = mKeyBackground;
background.setState(drawableState);
final Rect bounds = background.getBounds();
if (bgWidth != bounds.right || bgHeight != bounds.bottom) {
@@ -611,7 +521,7 @@
// Draw key top visuals.
protected void onDrawKeyTopVisuals(Key key, Canvas canvas, Paint paint, KeyDrawParams params) {
- final int keyWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight;
+ final int keyWidth = key.getDrawWidth();
final int keyHeight = key.mHeight;
final float centerX = keyWidth * 0.5f;
final float centerY = keyHeight * 0.5f;
@@ -625,12 +535,8 @@
float positionX = centerX;
if (key.mLabel != null) {
final String label = key.mLabel;
- // For characters, use large font. For labels like "Done", use smaller font.
- paint.setTypeface(key.selectTypeface(params.mKeyTextStyle));
- final int labelSize = key.selectTextSize(params.mKeyLetterSize,
- params.mKeyLargeLetterSize, params.mKeyLabelSize, params.mKeyLargeLabelSize,
- params.mKeyHintLabelSize);
- paint.setTextSize(labelSize);
+ paint.setTypeface(key.selectTypeface(params));
+ paint.setTextSize(key.selectTextSize(params));
final float labelCharHeight = getCharHeight(KEY_LABEL_REFERENCE_CHAR, paint);
final float labelCharWidth = getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint);
@@ -640,10 +546,10 @@
// Horizontal label text alignment
float labelWidth = 0;
if (key.isAlignLeft()) {
- positionX = (int)params.mKeyLabelHorizontalPadding;
+ positionX = mKeyLabelHorizontalPadding;
paint.setTextAlign(Align.LEFT);
} else if (key.isAlignRight()) {
- positionX = keyWidth - (int)params.mKeyLabelHorizontalPadding;
+ positionX = keyWidth - mKeyLabelHorizontalPadding;
paint.setTextAlign(Align.RIGHT);
} else if (key.isAlignLeftOfCenter()) {
// TODO: Parameterise this?
@@ -668,16 +574,15 @@
Math.min(1.0f, (keyWidth * MAX_LABEL_RATIO) / getLabelWidth(label, paint)));
}
- paint.setColor(key.isShiftedLetterActivated()
- ? params.mKeyTextInactivatedColor : params.mKeyTextColor);
+ paint.setColor(key.selectTextColor(params));
if (key.isEnabled()) {
// Set a drop shadow for the text
- paint.setShadowLayer(params.mShadowRadius, 0, 0, params.mShadowColor);
+ paint.setShadowLayer(mKeyTextShadowRadius, 0, 0, params.mTextShadowColor);
} else {
// Make label invisible
paint.setColor(Color.TRANSPARENT);
}
- params.blendAlpha(paint);
+ blendAlpha(paint, params.mAnimAlpha);
canvas.drawText(label, 0, label.length(), positionX, baseline, paint);
// Turn off drop shadow and reset x-scale.
paint.setShadowLayer(0, 0, 0, 0);
@@ -705,25 +610,10 @@
// Draw hint label.
if (key.mHintLabel != null) {
- final String hint = key.mHintLabel;
- final int hintColor;
- final int hintSize;
- if (key.hasHintLabel()) {
- hintColor = params.mKeyHintLabelColor;
- hintSize = params.mKeyHintLabelSize;
- paint.setTypeface(Typeface.DEFAULT);
- } else if (key.hasShiftedLetterHint()) {
- hintColor = key.isShiftedLetterActivated()
- ? params.mKeyShiftedLetterHintActivatedColor
- : params.mKeyShiftedLetterHintInactivatedColor;
- hintSize = params.mKeyShiftedLetterHintSize;
- } else { // key.hasHintLetter()
- hintColor = params.mKeyHintLetterColor;
- hintSize = params.mKeyHintLetterSize;
- }
- paint.setColor(hintColor);
- params.blendAlpha(paint);
- paint.setTextSize(hintSize);
+ final String hintLabel = key.mHintLabel;
+ paint.setTextSize(key.selectHintTextSize(params));
+ paint.setColor(key.selectHintTextColor(params));
+ blendAlpha(paint, params.mAnimAlpha);
final float hintX, hintY;
if (key.hasHintLabel()) {
// The hint label is placed just right of the key label. Used mainly on
@@ -734,19 +624,19 @@
paint.setTextAlign(Align.LEFT);
} else if (key.hasShiftedLetterHint()) {
// The hint label is placed at top-right corner of the key. Used mainly on tablet.
- hintX = keyWidth - params.mKeyShiftedLetterHintPadding
+ hintX = keyWidth - mKeyShiftedLetterHintPadding
- getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) / 2;
paint.getFontMetrics(mFontMetrics);
hintY = -mFontMetrics.top;
paint.setTextAlign(Align.CENTER);
} else { // key.hasHintLetter()
// The hint letter is placed at top-right corner of the key. Used mainly on phone.
- hintX = keyWidth - params.mKeyHintLetterPadding
+ hintX = keyWidth - mKeyHintLetterPadding
- getCharWidth(KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR, paint) / 2;
hintY = -paint.ascent();
paint.setTextAlign(Align.CENTER);
}
- canvas.drawText(hint, 0, hint.length(), hintX, hintY, paint);
+ canvas.drawText(hintLabel, 0, hintLabel.length(), hintX, hintY, paint);
if (LatinImeLogger.sVISUALDEBUG) {
final Paint line = new Paint();
@@ -757,15 +647,15 @@
// Draw key icon.
if (key.mLabel == null && icon != null) {
- final int iconWidth = icon.getIntrinsicWidth();
+ final int iconWidth = Math.min(icon.getIntrinsicWidth(), keyWidth);
final int iconHeight = icon.getIntrinsicHeight();
final int iconX, alignX;
final int iconY = (keyHeight - iconHeight) / 2;
if (key.isAlignLeft()) {
- iconX = (int)params.mKeyLabelHorizontalPadding;
+ iconX = mKeyLabelHorizontalPadding;
alignX = iconX;
} else if (key.isAlignRight()) {
- iconX = keyWidth - (int)params.mKeyLabelHorizontalPadding - iconWidth;
+ iconX = keyWidth - mKeyLabelHorizontalPadding - iconWidth;
alignX = iconX + iconWidth;
} else { // Align center
iconX = (keyWidth - iconWidth) / 2;
@@ -787,16 +677,16 @@
// Draw popup hint "..." at the bottom right corner of the key.
protected void drawKeyPopupHint(Key key, Canvas canvas, Paint paint, KeyDrawParams params) {
- final int keyWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight;
+ final int keyWidth = key.getDrawWidth();
final int keyHeight = key.mHeight;
- paint.setTypeface(params.mKeyTextStyle);
- paint.setTextSize(params.mKeyHintLetterSize);
- paint.setColor(params.mKeyHintLabelColor);
+ paint.setTypeface(params.mTypeface);
+ paint.setTextSize(params.mHintLetterSize);
+ paint.setColor(params.mHintLabelColor);
paint.setTextAlign(Align.CENTER);
- final float hintX = keyWidth - params.mKeyHintLetterPadding
+ final float hintX = keyWidth - mKeyHintLetterPadding
- getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) / 2;
- final float hintY = keyHeight - params.mKeyPopupHintLetterPadding;
+ final float hintY = keyHeight - mKeyPopupHintLetterPadding;
canvas.drawText(POPUP_HINT_CHAR, hintX, hintY, paint);
if (LatinImeLogger.sVISUALDEBUG) {
@@ -889,8 +779,8 @@
public Paint newDefaultLabelPaint() {
final Paint paint = new Paint();
paint.setAntiAlias(true);
- paint.setTypeface(mKeyDrawParams.mKeyTextStyle);
- paint.setTextSize(mKeyDrawParams.mKeyLabelSize);
+ paint.setTypeface(mKeyDrawParams.mTypeface);
+ paint.setTextSize(mKeyDrawParams.mLabelSize);
return paint;
}
@@ -901,23 +791,38 @@
}
}
- // Called by {@link PointerTracker} constructor to create a TextView.
- @Override
- public TextView inflateKeyPreviewText() {
+ private TextView getKeyPreviewText(final int pointerId) {
+ TextView previewText = mKeyPreviewTexts.get(pointerId);
+ if (previewText != null) {
+ return previewText;
+ }
final Context context = getContext();
if (mKeyPreviewLayoutId != 0) {
- return (TextView)LayoutInflater.from(context).inflate(mKeyPreviewLayoutId, null);
+ previewText = (TextView)LayoutInflater.from(context).inflate(mKeyPreviewLayoutId, null);
} else {
- return new TextView(context);
+ previewText = new TextView(context);
}
+ mKeyPreviewTexts.put(pointerId, previewText);
+ return previewText;
+ }
+
+ private void dismissAllKeyPreviews() {
+ final int pointerCount = mKeyPreviewTexts.size();
+ for (int id = 0; id < pointerCount; id++) {
+ final TextView previewText = mKeyPreviewTexts.get(id);
+ if (previewText != null) {
+ previewText.setVisibility(INVISIBLE);
+ }
+ }
+ PointerTracker.setReleasedKeyGraphicsToAllKeys();
}
@Override
- public void dismissKeyPreview(PointerTracker tracker) {
+ public void dismissKeyPreview(final PointerTracker tracker) {
mDrawingHandler.dismissKeyPreview(mDelayAfterPreview, tracker);
}
- private void addKeyPreview(TextView keyPreview) {
+ private void addKeyPreview(final TextView keyPreview) {
locatePreviewPlacerView();
mPreviewPlacerView.addView(
keyPreview, ViewLayoutUtils.newLayoutParam(mPreviewPlacerView, 0, 0));
@@ -930,9 +835,18 @@
final int[] viewOrigin = new int[2];
getLocationInWindow(viewOrigin);
mPreviewPlacerView.setOrigin(viewOrigin[0], viewOrigin[1]);
- final ViewGroup windowContentView =
- (ViewGroup)getRootView().findViewById(android.R.id.content);
- windowContentView.addView(mPreviewPlacerView);
+ final View rootView = getRootView();
+ if (rootView == null) {
+ Log.w(TAG, "Cannot find root view");
+ return;
+ }
+ final ViewGroup windowContentView = (ViewGroup)rootView.findViewById(android.R.id.content);
+ // Note: It'd be very weird if we get null by android.R.id.content.
+ if (windowContentView == null) {
+ Log.w(TAG, "Cannot find android.R.id.content view to add PreviewPlacerView");
+ } else {
+ windowContentView.addView(mPreviewPlacerView);
+ }
}
public void showGestureFloatingPreviewText(String gestureFloatingPreviewText) {
@@ -946,17 +860,21 @@
}
@Override
- public void showGestureTrail(PointerTracker tracker) {
+ public void showGesturePreviewTrail(final PointerTracker tracker) {
locatePreviewPlacerView();
mPreviewPlacerView.invalidatePointer(tracker);
}
@SuppressWarnings("deprecation") // setBackgroundDrawable is replaced by setBackground in API16
@Override
- public void showKeyPreview(PointerTracker tracker) {
- if (!mShowKeyPreviewPopup) return;
+ public void showKeyPreview(final PointerTracker tracker) {
+ final KeyPreviewDrawParams previewParams = mKeyPreviewDrawParams;
+ if (!mShowKeyPreviewPopup) {
+ previewParams.mPreviewVisibleOffset = -mKeyboard.mVerticalGap;
+ return;
+ }
- final TextView previewText = tracker.getKeyPreviewText();
+ final TextView previewText = getKeyPreviewText(tracker.mPointerId);
// If the key preview has no parent view yet, add it to the ViewGroup which can place
// key preview absolutely in SoftInputWindow.
if (previewText.getParent() == null) {
@@ -971,18 +889,18 @@
if (key == null)
return;
- final KeyPreviewDrawParams params = mKeyPreviewDrawParams;
+ final KeyDrawParams drawParams = mKeyDrawParams;
final String label = key.isShiftedLetterActivated() ? key.mHintLabel : key.mLabel;
- // What we show as preview should match what we show on a key top in onBufferDraw().
+ // What we show as preview should match what we show on a key top in onDraw().
if (label != null) {
// TODO Should take care of temporaryShiftLabel here.
previewText.setCompoundDrawables(null, null, null, null);
if (StringUtils.codePointCount(label) > 1) {
- previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, params.mKeyLetterSize);
+ previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, drawParams.mLetterSize);
previewText.setTypeface(Typeface.DEFAULT_BOLD);
} else {
- previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, params.mPreviewTextSize);
- previewText.setTypeface(params.mKeyTextStyle);
+ previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, drawParams.mPreviewTextSize);
+ previewText.setTypeface(key.selectTypeface(drawParams));
}
previewText.setText(label);
} else {
@@ -990,48 +908,48 @@
key.getPreviewIcon(mKeyboard.mIconsSet));
previewText.setText(null);
}
- previewText.setBackgroundDrawable(params.mPreviewBackground);
+ previewText.setBackgroundDrawable(mPreviewBackground);
previewText.measure(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
- final int keyDrawWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight;
+ final int keyDrawWidth = key.getDrawWidth();
final int previewWidth = previewText.getMeasuredWidth();
- final int previewHeight = params.mPreviewHeight;
+ final int previewHeight = mPreviewHeight;
// The width and height of visible part of the key preview background. The content marker
// of the background 9-patch have to cover the visible part of the background.
- params.mPreviewVisibleWidth = previewWidth - previewText.getPaddingLeft()
+ previewParams.mPreviewVisibleWidth = previewWidth - previewText.getPaddingLeft()
- previewText.getPaddingRight();
- params.mPreviewVisibleHeight = previewHeight - previewText.getPaddingTop()
+ previewParams.mPreviewVisibleHeight = previewHeight - previewText.getPaddingTop()
- previewText.getPaddingBottom();
// The distance between the top edge of the parent key and the bottom of the visible part
// of the key preview background.
- params.mPreviewVisibleOffset = params.mPreviewOffset - previewText.getPaddingBottom();
- getLocationInWindow(params.mCoordinates);
+ previewParams.mPreviewVisibleOffset = mPreviewOffset - previewText.getPaddingBottom();
+ getLocationInWindow(previewParams.mCoordinates);
// The key preview is horizontally aligned with the center of the visible part of the
// parent key. If it doesn't fit in this {@link KeyboardView}, it is moved inward to fit and
// the left/right background is used if such background is specified.
- int previewX = key.mX + key.mVisualInsetsLeft - (previewWidth - keyDrawWidth) / 2
- + params.mCoordinates[0];
+ int previewX = key.getDrawX() - (previewWidth - keyDrawWidth) / 2
+ + previewParams.mCoordinates[0];
if (previewX < 0) {
previewX = 0;
- if (params.mPreviewLeftBackground != null) {
- previewText.setBackgroundDrawable(params.mPreviewLeftBackground);
+ if (mPreviewLeftBackground != null) {
+ previewText.setBackgroundDrawable(mPreviewLeftBackground);
}
} else if (previewX > getWidth() - previewWidth) {
previewX = getWidth() - previewWidth;
- if (params.mPreviewRightBackground != null) {
- previewText.setBackgroundDrawable(params.mPreviewRightBackground);
+ if (mPreviewRightBackground != null) {
+ previewText.setBackgroundDrawable(mPreviewRightBackground);
}
}
// The key preview is placed vertically above the top edge of the parent key with an
// arbitrary offset.
- final int previewY = key.mY - previewHeight + params.mPreviewOffset
- + params.mCoordinates[1];
+ final int previewY = key.mY - previewHeight + mPreviewOffset
+ + previewParams.mCoordinates[1];
// Set the preview background state
previewText.getBackground().setState(
key.mMoreKeys != null ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
- previewText.setTextColor(params.mPreviewTextColor);
+ previewText.setTextColor(drawParams.mPreviewTextColor);
ViewLayoutUtils.placeViewAt(
previewText, previewX, previewY, previewWidth, previewHeight);
previewText.setVisibility(VISIBLE);
@@ -1067,7 +985,7 @@
}
public void closing() {
- PointerTracker.dismissAllKeyPreviews();
+ dismissAllKeyPreviews();
cancelAllMessages();
mInvalidateAllKeys = true;
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 0a929f3..f6b66a7 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -43,15 +43,16 @@
import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy;
import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
+import com.android.inputmethod.keyboard.internal.KeyDrawParams;
import com.android.inputmethod.keyboard.internal.SuddenJumpingTouchEventHandler;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.LatinIME;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.ResourceUtils;
import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
import com.android.inputmethod.latin.StringUtils;
import com.android.inputmethod.latin.SubtypeLocale;
-import com.android.inputmethod.latin.Utils;
import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils;
import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.research.ResearchLogger;
@@ -62,9 +63,25 @@
/**
* A view that is responsible for detecting key presses and touch movements.
*
- * @attr ref R.styleable#KeyboardView_keyHysteresisDistance
- * @attr ref R.styleable#KeyboardView_verticalCorrection
- * @attr ref R.styleable#KeyboardView_popupLayout
+ * @attr ref R.styleable#MainKeyboardView_autoCorrectionSpacebarLedEnabled
+ * @attr ref R.styleable#MainKeyboardView_autoCorrectionSpacebarLedIcon
+ * @attr ref R.styleable#MainKeyboardView_spacebarTextRatio
+ * @attr ref R.styleable#MainKeyboardView_spacebarTextColor
+ * @attr ref R.styleable#MainKeyboardView_spacebarTextShadowColor
+ * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFinalAlpha
+ * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFadeoutAnimator
+ * @attr ref R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator
+ * @attr ref R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator
+ * @attr ref R.styleable#MainKeyboardView_keyHysteresisDistance
+ * @attr ref R.styleable#MainKeyboardView_touchNoiseThresholdTime
+ * @attr ref R.styleable#MainKeyboardView_touchNoiseThresholdDistance
+ * @attr ref R.styleable#MainKeyboardView_slidingKeyInputEnable
+ * @attr ref R.styleable#MainKeyboardView_keyRepeatStartTimeout
+ * @attr ref R.styleable#MainKeyboardView_keyRepeatInterval
+ * @attr ref R.styleable#MainKeyboardView_longPressKeyTimeout
+ * @attr ref R.styleable#MainKeyboardView_longPressShiftKeyTimeout
+ * @attr ref R.styleable#MainKeyboardView_ignoreAltCodeKeyTimeout
+ * @attr ref R.styleable#MainKeyboardView_showMoreKeysKeyboardAtTouchPoint
*/
public class MainKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler,
SuddenJumpingTouchEventHandler.ProcessMotionEvent {
@@ -110,7 +127,6 @@
new WeakHashMap<Key, MoreKeysPanel>();
private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint;
- private final PointerTrackerParams mPointerTrackerParams;
private final SuddenJumpingTouchEventHandler mTouchScreenRegulator;
protected KeyDetector mKeyDetector;
@@ -127,15 +143,30 @@
private static final int MSG_LONGPRESS_KEY = 2;
private static final int MSG_DOUBLE_TAP = 3;
- private final KeyTimerParams mParams;
+ private final int mKeyRepeatStartTimeout;
+ private final int mKeyRepeatInterval;
+ private final int mLongPressKeyTimeout;
+ private final int mLongPressShiftKeyTimeout;
+ private final int mIgnoreAltCodeKeyTimeout;
- public KeyTimerHandler(MainKeyboardView outerInstance, KeyTimerParams params) {
+ public KeyTimerHandler(final MainKeyboardView outerInstance,
+ final TypedArray mainKeyboardViewAttr) {
super(outerInstance);
- mParams = params;
+
+ mKeyRepeatStartTimeout = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0);
+ mKeyRepeatInterval = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_keyRepeatInterval, 0);
+ mLongPressKeyTimeout = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_longPressKeyTimeout, 0);
+ mLongPressShiftKeyTimeout = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_longPressShiftKeyTimeout, 0);
+ mIgnoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0);
}
@Override
- public void handleMessage(Message msg) {
+ public void handleMessage(final Message msg) {
final MainKeyboardView keyboardView = getOuterInstance();
final PointerTracker tracker = (PointerTracker) msg.obj;
switch (msg.what) {
@@ -146,7 +177,7 @@
final Key currentKey = tracker.getKey();
if (currentKey != null && currentKey.mCode == msg.arg1) {
tracker.onRegisterKey(currentKey);
- startKeyRepeatTimer(tracker, mParams.mKeyRepeatInterval);
+ startKeyRepeatTimer(tracker, mKeyRepeatInterval);
}
break;
case MSG_LONGPRESS_KEY:
@@ -159,15 +190,15 @@
}
}
- private void startKeyRepeatTimer(PointerTracker tracker, long delay) {
+ private void startKeyRepeatTimer(final PointerTracker tracker, final long delay) {
final Key key = tracker.getKey();
if (key == null) return;
sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, key.mCode, 0, tracker), delay);
}
@Override
- public void startKeyRepeatTimer(PointerTracker tracker) {
- startKeyRepeatTimer(tracker, mParams.mKeyRepeatStartTimeout);
+ public void startKeyRepeatTimer(final PointerTracker tracker) {
+ startKeyRepeatTimer(tracker, mKeyRepeatStartTimeout);
}
public void cancelKeyRepeatTimer() {
@@ -180,12 +211,12 @@
}
@Override
- public void startLongPressTimer(int code) {
+ public void startLongPressTimer(final int code) {
cancelLongPressTimer();
final int delay;
switch (code) {
case Keyboard.CODE_SHIFT:
- delay = mParams.mLongPressShiftKeyTimeout;
+ delay = mLongPressShiftKeyTimeout;
break;
default:
delay = 0;
@@ -197,7 +228,7 @@
}
@Override
- public void startLongPressTimer(PointerTracker tracker) {
+ public void startLongPressTimer(final PointerTracker tracker) {
cancelLongPressTimer();
if (tracker == null) {
return;
@@ -206,15 +237,15 @@
final int delay;
switch (key.mCode) {
case Keyboard.CODE_SHIFT:
- delay = mParams.mLongPressShiftKeyTimeout;
+ delay = mLongPressShiftKeyTimeout;
break;
default:
if (KeyboardSwitcher.getInstance().isInMomentarySwitchState()) {
// We use longer timeout for sliding finger input started from the symbols
// mode key.
- delay = mParams.mLongPressKeyTimeout * 3;
+ delay = mLongPressKeyTimeout * 3;
} else {
- delay = mParams.mLongPressKeyTimeout;
+ delay = mLongPressKeyTimeout;
}
break;
}
@@ -251,7 +282,7 @@
}
@Override
- public void startTypingStateTimer(Key typedKey) {
+ public void startTypingStateTimer(final Key typedKey) {
if (typedKey.isModifier() || typedKey.altCodeWhileTyping()) {
return;
}
@@ -268,7 +299,7 @@
}
sendMessageDelayed(
- obtainMessage(MSG_TYPING_STATE_EXPIRED), mParams.mIgnoreAltCodeKeyTimeout);
+ obtainMessage(MSG_TYPING_STATE_EXPIRED), mIgnoreAltCodeKeyTimeout);
if (isTyping) {
return;
}
@@ -307,55 +338,11 @@
}
}
- public static class PointerTrackerParams {
- public final boolean mSlidingKeyInputEnabled;
- public final int mTouchNoiseThresholdTime;
- public final float mTouchNoiseThresholdDistance;
-
- public static final PointerTrackerParams DEFAULT = new PointerTrackerParams();
-
- private PointerTrackerParams() {
- mSlidingKeyInputEnabled = false;
- mTouchNoiseThresholdTime =0;
- mTouchNoiseThresholdDistance = 0;
- }
-
- public PointerTrackerParams(TypedArray mainKeyboardViewAttr) {
- mSlidingKeyInputEnabled = mainKeyboardViewAttr.getBoolean(
- R.styleable.MainKeyboardView_slidingKeyInputEnable, false);
- mTouchNoiseThresholdTime = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_touchNoiseThresholdTime, 0);
- mTouchNoiseThresholdDistance = mainKeyboardViewAttr.getDimension(
- R.styleable.MainKeyboardView_touchNoiseThresholdDistance, 0);
- }
- }
-
- static class KeyTimerParams {
- public final int mKeyRepeatStartTimeout;
- public final int mKeyRepeatInterval;
- public final int mLongPressKeyTimeout;
- public final int mLongPressShiftKeyTimeout;
- public final int mIgnoreAltCodeKeyTimeout;
-
- public KeyTimerParams(TypedArray mainKeyboardViewAttr) {
- mKeyRepeatStartTimeout = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0);
- mKeyRepeatInterval = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_keyRepeatInterval, 0);
- mLongPressKeyTimeout = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_longPressKeyTimeout, 0);
- mLongPressShiftKeyTimeout = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_longPressShiftKeyTimeout, 0);
- mIgnoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0);
- }
- }
-
- public MainKeyboardView(Context context, AttributeSet attrs) {
+ public MainKeyboardView(final Context context, final AttributeSet attrs) {
this(context, attrs, R.attr.mainKeyboardViewStyle);
}
- public MainKeyboardView(Context context, AttributeSet attrs, int defStyle) {
+ public MainKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
mTouchScreenRegulator = new SuddenJumpingTouchEventHandler(getContext(), this);
@@ -364,7 +351,7 @@
.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT);
final Resources res = getResources();
final boolean needsPhantomSuddenMoveEventHack = Boolean.parseBoolean(
- Utils.getDeviceOverrideValue(res,
+ ResourceUtils.getDeviceOverrideValue(res,
R.array.phantom_sudden_move_event_device_list, "false"));
PointerTracker.init(mHasDistinctMultitouch, needsPhantomSuddenMoveEventHack);
@@ -374,8 +361,8 @@
R.styleable.MainKeyboardView_autoCorrectionSpacebarLedEnabled, false);
mAutoCorrectionSpacebarLedIcon = a.getDrawable(
R.styleable.MainKeyboardView_autoCorrectionSpacebarLedIcon);
- mSpacebarTextRatio = a.getFraction(R.styleable.MainKeyboardView_spacebarTextRatio,
- 1000, 1000, 1) / 1000.0f;
+ mSpacebarTextRatio = a.getFraction(
+ R.styleable.MainKeyboardView_spacebarTextRatio, 1, 1, 1.0f);
mSpacebarTextColor = a.getColor(R.styleable.MainKeyboardView_spacebarTextColor, 0);
mSpacebarTextShadowColor = a.getColor(
R.styleable.MainKeyboardView_spacebarTextShadowColor, 0);
@@ -389,19 +376,15 @@
final int altCodeKeyWhileTypingFadeinAnimatorResId = a.getResourceId(
R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0);
- final KeyTimerParams keyTimerParams = new KeyTimerParams(a);
- mPointerTrackerParams = new PointerTrackerParams(a);
-
final float keyHysteresisDistance = a.getDimension(
R.styleable.MainKeyboardView_keyHysteresisDistance, 0);
mKeyDetector = new KeyDetector(keyHysteresisDistance);
- mKeyTimerHandler = new KeyTimerHandler(this, keyTimerParams);
+ mKeyTimerHandler = new KeyTimerHandler(this, a);
mConfigShowMoreKeysKeyboardAtTouchedPoint = a.getBoolean(
R.styleable.MainKeyboardView_showMoreKeysKeyboardAtTouchedPoint, false);
+ PointerTracker.setParameters(a);
a.recycle();
- PointerTracker.setParameters(mPointerTrackerParams);
-
mLanguageOnSpacebarFadeoutAnimator = loadObjectAnimator(
languageOnSpacebarFadeoutAnimatorResId, this);
mAltCodeKeyWhileTypingFadeoutAnimator = loadObjectAnimator(
@@ -410,7 +393,7 @@
altCodeKeyWhileTypingFadeinAnimatorResId, this);
}
- private ObjectAnimator loadObjectAnimator(int resId, Object target) {
+ private ObjectAnimator loadObjectAnimator(final int resId, final Object target) {
if (resId == 0) return null;
final ObjectAnimator animator = (ObjectAnimator)AnimatorInflater.loadAnimator(
getContext(), resId);
@@ -425,7 +408,7 @@
return mLanguageOnSpacebarAnimAlpha;
}
- public void setLanguageOnSpacebarAnimAlpha(int alpha) {
+ public void setLanguageOnSpacebarAnimAlpha(final int alpha) {
mLanguageOnSpacebarAnimAlpha = alpha;
invalidateKey(mSpaceKey);
}
@@ -434,12 +417,12 @@
return mAltCodeKeyWhileTypingAnimAlpha;
}
- public void setAltCodeKeyWhileTypingAnimAlpha(int alpha) {
+ public void setAltCodeKeyWhileTypingAnimAlpha(final int alpha) {
mAltCodeKeyWhileTypingAnimAlpha = alpha;
updateAltCodeKeyWhileTyping();
}
- public void setKeyboardActionListener(KeyboardActionListener listener) {
+ public void setKeyboardActionListener(final KeyboardActionListener listener) {
mKeyboardActionListener = listener;
PointerTracker.setKeyboardActionListener(listener);
}
@@ -476,7 +459,7 @@
* @param keyboard the keyboard to display in this view
*/
@Override
- public void setKeyboard(Keyboard keyboard) {
+ public void setKeyboard(final Keyboard keyboard) {
// Remove any pending messages, except dismissing preview and key repeat.
mKeyTimerHandler.cancelLongPressTimer();
super.setKeyboard(keyboard);
@@ -501,11 +484,11 @@
}
// Note that this method is called from a non-UI thread.
- public void setMainDictionaryAvailability(boolean mainDictionaryAvailable) {
+ public void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) {
PointerTracker.setMainDictionaryAvailability(mainDictionaryAvailable);
}
- public void setGestureHandlingEnabledByUser(boolean gestureHandlingEnabledByUser) {
+ public void setGestureHandlingEnabledByUser(final boolean gestureHandlingEnabledByUser) {
PointerTracker.setGestureHandlingEnabledByUser(gestureHandlingEnabledByUser);
}
@@ -517,7 +500,7 @@
return mHasDistinctMultitouch;
}
- public void setDistinctMultitouch(boolean hasDistinctMultitouch) {
+ public void setDistinctMultitouch(final boolean hasDistinctMultitouch) {
mHasDistinctMultitouch = hasDistinctMultitouch;
}
@@ -528,7 +511,17 @@
// to properly show the splash screen, which requires that the window token of the
// KeyboardView be non-null.
if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.getInstance().mainKeyboardView_onAttachedToWindow();
+ ResearchLogger.getInstance().mainKeyboardView_onAttachedToWindow(this);
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ // Notify the research logger that the keyboard view has been detached. This is needed
+ // to invalidate the reference of {@link MainKeyboardView} to null.
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.getInstance().mainKeyboardView_onDetachedFromWindow();
}
}
@@ -538,7 +531,8 @@
super.cancelAllMessages();
}
- private boolean openMoreKeysKeyboardIfRequired(Key parentKey, PointerTracker tracker) {
+ private boolean openMoreKeysKeyboardIfRequired(final Key parentKey,
+ final PointerTracker tracker) {
// Check if we have a popup layout specified first.
if (mMoreKeysLayout == 0) {
return false;
@@ -553,7 +547,7 @@
}
// This default implementation returns a more keys panel.
- protected MoreKeysPanel onCreateMoreKeysPanel(Key parentKey) {
+ protected MoreKeysPanel onCreateMoreKeysPanel(final Key parentKey) {
if (parentKey.mMoreKeys == null)
return null;
@@ -579,7 +573,7 @@
* @return true if the long press is handled, false otherwise. Subclasses should call the
* method on the base class if the subclass doesn't wish to handle the call.
*/
- protected boolean onLongPress(Key parentKey, PointerTracker tracker) {
+ protected boolean onLongPress(final Key parentKey, final PointerTracker tracker) {
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.mainKeyboardView_onLongPress();
}
@@ -603,21 +597,20 @@
return openMoreKeysPanel(parentKey, tracker);
}
- private boolean invokeCustomRequest(int code) {
+ private boolean invokeCustomRequest(final int code) {
return mKeyboardActionListener.onCustomRequest(code);
}
- private void invokeCodeInput(int primaryCode) {
- mKeyboardActionListener.onCodeInput(primaryCode,
- KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
- KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
+ private void invokeCodeInput(final int primaryCode) {
+ mKeyboardActionListener.onCodeInput(
+ primaryCode, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
}
- private void invokeReleaseKey(int primaryCode) {
+ private void invokeReleaseKey(final int primaryCode) {
mKeyboardActionListener.onReleaseKey(primaryCode, false);
}
- private boolean openMoreKeysPanel(Key parentKey, PointerTracker tracker) {
+ private boolean openMoreKeysPanel(final Key parentKey, final PointerTracker tracker) {
MoreKeysPanel moreKeysPanel = mMoreKeysPanelCache.get(parentKey);
if (moreKeysPanel == null) {
moreKeysPanel = onCreateMoreKeysPanel(parentKey);
@@ -643,9 +636,9 @@
// The more keys keyboard is usually vertically aligned with the top edge of the parent key
// (plus vertical gap). If the key preview is enabled, the more keys keyboard is vertically
// aligned with the bottom edge of the visible part of the key preview.
- final int pointY = parentKey.mY + (keyPreviewEnabled
- ? mKeyPreviewDrawParams.mPreviewVisibleOffset
- : -parentKey.mVerticalGap);
+ // {@code mPreviewVisibleOffset} has been set appropriately in
+ // {@link KeyboardView#showKeyPreview(PointerTracker)}.
+ final int pointY = parentKey.mY + mKeyPreviewDrawParams.mPreviewVisibleOffset;
moreKeysPanel.showMoreKeysPanel(
this, this, pointX, pointY, mMoreKeysWindow, mKeyboardActionListener);
final int translatedX = moreKeysPanel.translateX(tracker.getLastX());
@@ -668,7 +661,7 @@
}
@Override
- public boolean onTouchEvent(MotionEvent me) {
+ public boolean onTouchEvent(final MotionEvent me) {
if (getKeyboard() == null) {
return false;
}
@@ -679,7 +672,7 @@
}
@Override
- public boolean processMotionEvent(MotionEvent me) {
+ public boolean processMotionEvent(final MotionEvent me) {
final boolean nonDistinctMultitouch = !mHasDistinctMultitouch;
final int action = me.getActionMasked();
final int pointerCount = me.getPointerCount();
@@ -846,7 +839,7 @@
* otherwise
*/
@Override
- public boolean dispatchHoverEvent(MotionEvent event) {
+ public boolean dispatchHoverEvent(final MotionEvent event) {
if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
final PointerTracker tracker = PointerTracker.getPointerTracker(0, this);
return AccessibleKeyboardViewProxy.getInstance().dispatchHoverEvent(event, tracker);
@@ -856,7 +849,7 @@
return false;
}
- public void updateShortcutKey(boolean available) {
+ public void updateShortcutKey(final boolean available) {
final Keyboard keyboard = getKeyboard();
if (keyboard == null) return;
final Key shortcutKey = keyboard.getKey(Keyboard.CODE_SHORTCUT);
@@ -873,8 +866,8 @@
}
}
- public void startDisplayLanguageOnSpacebar(boolean subtypeChanged,
- boolean needsToDisplayLanguage, boolean hasMultipleEnabledIMEsOrSubtypes) {
+ public void startDisplayLanguageOnSpacebar(final boolean subtypeChanged,
+ final boolean needsToDisplayLanguage, final boolean hasMultipleEnabledIMEsOrSubtypes) {
mNeedsToDisplayLanguage = needsToDisplayLanguage;
mHasMultipleEnabledIMEsOrSubtypes = hasMultipleEnabledIMEsOrSubtypes;
final ObjectAnimator animator = mLanguageOnSpacebarFadeoutAnimator;
@@ -896,14 +889,15 @@
invalidateKey(mSpaceKey);
}
- public void updateAutoCorrectionState(boolean isAutoCorrection) {
+ public void updateAutoCorrectionState(final boolean isAutoCorrection) {
if (!mAutoCorrectionSpacebarLedEnabled) return;
mAutoCorrectionSpacebarLedOn = isAutoCorrection;
invalidateKey(mSpaceKey);
}
@Override
- protected void onDrawKeyTopVisuals(Key key, Canvas canvas, Paint paint, KeyDrawParams params) {
+ protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint,
+ final KeyDrawParams params) {
if (key.altCodeWhileTyping() && key.isEnabled()) {
params.mAnimAlpha = mAltCodeKeyWhileTypingAnimAlpha;
}
@@ -921,7 +915,7 @@
}
}
- private boolean fitsTextIntoWidth(final int width, String text, Paint paint) {
+ private boolean fitsTextIntoWidth(final int width, final String text, final Paint paint) {
paint.setTextScaleX(1.0f);
final float textWidth = getLabelWidth(text, paint);
if (textWidth < width) return true;
@@ -934,7 +928,7 @@
}
// Layout language name on spacebar.
- private String layoutLanguageOnSpacebar(Paint paint, InputMethodSubtype subtype,
+ private String layoutLanguageOnSpacebar(final Paint paint, final InputMethodSubtype subtype,
final int width) {
// Choose appropriate language name to fit into the width.
String text = getFullDisplayName(subtype, getResources());
@@ -955,7 +949,7 @@
return "";
}
- private void drawSpacebar(Key key, Canvas canvas, Paint paint) {
+ private void drawSpacebar(final Key key, final Canvas canvas, final Paint paint) {
final int width = key.mWidth;
final int height = key.mHeight;
@@ -1010,7 +1004,7 @@
// zz azerty T AZERTY AZERTY
// Get InputMethodSubtype's full display name in its locale.
- static String getFullDisplayName(InputMethodSubtype subtype, Resources res) {
+ static String getFullDisplayName(final InputMethodSubtype subtype, final Resources res) {
if (SubtypeLocale.isNoLanguage(subtype)) {
return SubtypeLocale.getKeyboardLayoutSetDisplayName(subtype);
}
@@ -1019,7 +1013,7 @@
}
// Get InputMethodSubtype's short display name in its locale.
- static String getShortDisplayName(InputMethodSubtype subtype) {
+ static String getShortDisplayName(final InputMethodSubtype subtype) {
if (SubtypeLocale.isNoLanguage(subtype)) {
return "";
}
@@ -1028,7 +1022,7 @@
}
// Get InputMethodSubtype's middle display name in its locale.
- static String getMiddleDisplayName(InputMethodSubtype subtype) {
+ static String getMiddleDisplayName(final InputMethodSubtype subtype) {
if (SubtypeLocale.isNoLanguage(subtype)) {
return SubtypeLocale.getKeyboardLayoutSetDisplayName(subtype);
}
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
index a3741a2..c9af888 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
@@ -20,15 +20,17 @@
import android.graphics.drawable.Drawable;
import android.view.View;
-import com.android.inputmethod.keyboard.internal.KeySpecParser.MoreKeySpec;
+import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
+import com.android.inputmethod.keyboard.internal.KeyboardParams;
+import com.android.inputmethod.keyboard.internal.MoreKeySpec;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.StringUtils;
public class MoreKeysKeyboard extends Keyboard {
private final int mDefaultKeyCoordX;
- MoreKeysKeyboard(Builder.MoreKeysKeyboardParams params) {
+ MoreKeysKeyboard(final MoreKeysKeyboardParams params) {
super(params);
mDefaultKeyCoordX = params.getDefaultKeyCoordX() + params.mDefaultKeyWidth / 2;
}
@@ -37,220 +39,222 @@
return mDefaultKeyCoordX;
}
- public static class Builder extends Keyboard.Builder<Builder.MoreKeysKeyboardParams> {
+ /* package for test */
+ static class MoreKeysKeyboardParams extends KeyboardParams {
+ public boolean mIsFixedOrder;
+ /* package */int mTopRowAdjustment;
+ public int mNumRows;
+ public int mNumColumns;
+ public int mTopKeys;
+ public int mLeftKeys;
+ public int mRightKeys; // includes default key.
+ public int mDividerWidth;
+ public int mColumnWidth;
+
+ public MoreKeysKeyboardParams() {
+ super();
+ }
+
+ /**
+ * Set keyboard parameters of more keys keyboard.
+ *
+ * @param numKeys number of keys in this more keys keyboard.
+ * @param maxColumns number of maximum columns of this more keys keyboard.
+ * @param keyWidth more keys keyboard key width in pixel, including horizontal gap.
+ * @param rowHeight more keys keyboard row height in pixel, including vertical gap.
+ * @param coordXInParent coordinate x of the key preview in parent keyboard.
+ * @param parentKeyboardWidth parent keyboard width in pixel.
+ * @param isFixedColumnOrder if true, more keys should be laid out in fixed order.
+ * @param dividerWidth width of divider, zero for no dividers.
+ */
+ public void setParameters(final int numKeys, final int maxColumns, final int keyWidth,
+ final int rowHeight, final int coordXInParent, final int parentKeyboardWidth,
+ final boolean isFixedColumnOrder, final int dividerWidth) {
+ mIsFixedOrder = isFixedColumnOrder;
+ if (parentKeyboardWidth / keyWidth < maxColumns) {
+ throw new IllegalArgumentException(
+ "Keyboard is too small to hold more keys keyboard: "
+ + parentKeyboardWidth + " " + keyWidth + " " + maxColumns);
+ }
+ mDefaultKeyWidth = keyWidth;
+ mDefaultRowHeight = rowHeight;
+
+ final int numRows = (numKeys + maxColumns - 1) / maxColumns;
+ mNumRows = numRows;
+ final int numColumns = mIsFixedOrder ? Math.min(numKeys, maxColumns)
+ : getOptimizedColumns(numKeys, maxColumns);
+ mNumColumns = numColumns;
+ final int topKeys = numKeys % numColumns;
+ mTopKeys = topKeys == 0 ? numColumns : topKeys;
+
+ final int numLeftKeys = (numColumns - 1) / 2;
+ final int numRightKeys = numColumns - numLeftKeys; // including default key.
+ // Maximum number of keys we can layout both side of the parent key
+ final int maxLeftKeys = coordXInParent / keyWidth;
+ final int maxRightKeys = (parentKeyboardWidth - coordXInParent) / keyWidth;
+ int leftKeys, rightKeys;
+ if (numLeftKeys > maxLeftKeys) {
+ leftKeys = maxLeftKeys;
+ rightKeys = numColumns - leftKeys;
+ } else if (numRightKeys > maxRightKeys + 1) {
+ rightKeys = maxRightKeys + 1; // include default key
+ leftKeys = numColumns - rightKeys;
+ } else {
+ leftKeys = numLeftKeys;
+ rightKeys = numRightKeys;
+ }
+ // If the left keys fill the left side of the parent key, entire more keys keyboard
+ // should be shifted to the right unless the parent key is on the left edge.
+ if (maxLeftKeys == leftKeys && leftKeys > 0) {
+ leftKeys--;
+ rightKeys++;
+ }
+ // If the right keys fill the right side of the parent key, entire more keys
+ // should be shifted to the left unless the parent key is on the right edge.
+ if (maxRightKeys == rightKeys - 1 && rightKeys > 1) {
+ leftKeys++;
+ rightKeys--;
+ }
+ mLeftKeys = leftKeys;
+ mRightKeys = rightKeys;
+
+ // Adjustment of the top row.
+ mTopRowAdjustment = mIsFixedOrder ? getFixedOrderTopRowAdjustment()
+ : getAutoOrderTopRowAdjustment();
+ mDividerWidth = dividerWidth;
+ mColumnWidth = mDefaultKeyWidth + mDividerWidth;
+ mBaseWidth = mOccupiedWidth = mNumColumns * mColumnWidth - mDividerWidth;
+ // Need to subtract the bottom row's gutter only.
+ mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight - mVerticalGap
+ + mTopPadding + mBottomPadding;
+ }
+
+ private int getFixedOrderTopRowAdjustment() {
+ if (mNumRows == 1 || mTopKeys % 2 == 1 || mTopKeys == mNumColumns
+ || mLeftKeys == 0 || mRightKeys == 1) {
+ return 0;
+ }
+ return -1;
+ }
+
+ private int getAutoOrderTopRowAdjustment() {
+ if (mNumRows == 1 || mTopKeys == 1 || mNumColumns % 2 == mTopKeys % 2
+ || mLeftKeys == 0 || mRightKeys == 1) {
+ return 0;
+ }
+ return -1;
+ }
+
+ // Return key position according to column count (0 is default).
+ /* package */int getColumnPos(final int n) {
+ return mIsFixedOrder ? getFixedOrderColumnPos(n) : getAutomaticColumnPos(n);
+ }
+
+ private int getFixedOrderColumnPos(final int n) {
+ final int col = n % mNumColumns;
+ final int row = n / mNumColumns;
+ if (!isTopRow(row)) {
+ return col - mLeftKeys;
+ }
+ final int rightSideKeys = mTopKeys / 2;
+ final int leftSideKeys = mTopKeys - (rightSideKeys + 1);
+ final int pos = col - leftSideKeys;
+ final int numLeftKeys = mLeftKeys + mTopRowAdjustment;
+ final int numRightKeys = mRightKeys - 1;
+ if (numRightKeys >= rightSideKeys && numLeftKeys >= leftSideKeys) {
+ return pos;
+ } else if (numRightKeys < rightSideKeys) {
+ return pos - (rightSideKeys - numRightKeys);
+ } else { // numLeftKeys < leftSideKeys
+ return pos + (leftSideKeys - numLeftKeys);
+ }
+ }
+
+ private int getAutomaticColumnPos(final int n) {
+ final int col = n % mNumColumns;
+ final int row = n / mNumColumns;
+ int leftKeys = mLeftKeys;
+ if (isTopRow(row)) {
+ leftKeys += mTopRowAdjustment;
+ }
+ if (col == 0) {
+ // default position.
+ return 0;
+ }
+
+ int pos = 0;
+ int right = 1; // include default position key.
+ int left = 0;
+ int i = 0;
+ while (true) {
+ // Assign right key if available.
+ if (right < mRightKeys) {
+ pos = right;
+ right++;
+ i++;
+ }
+ if (i >= col)
+ break;
+ // Assign left key if available.
+ if (left < leftKeys) {
+ left++;
+ pos = -left;
+ i++;
+ }
+ if (i >= col)
+ break;
+ }
+ return pos;
+ }
+
+ private static int getTopRowEmptySlots(final int numKeys, final int numColumns) {
+ final int remainings = numKeys % numColumns;
+ return remainings == 0 ? 0 : numColumns - remainings;
+ }
+
+ private int getOptimizedColumns(final int numKeys, final int maxColumns) {
+ int numColumns = Math.min(numKeys, maxColumns);
+ while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) {
+ numColumns--;
+ }
+ return numColumns;
+ }
+
+ public int getDefaultKeyCoordX() {
+ return mLeftKeys * mColumnWidth;
+ }
+
+ public int getX(final int n, final int row) {
+ final int x = getColumnPos(n) * mColumnWidth + getDefaultKeyCoordX();
+ if (isTopRow(row)) {
+ return x + mTopRowAdjustment * (mColumnWidth / 2);
+ }
+ return x;
+ }
+
+ public int getY(final int row) {
+ return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding;
+ }
+
+ public void markAsEdgeKey(final Key key, final int row) {
+ if (row == 0)
+ key.markAsTopEdge(this);
+ if (isTopRow(row))
+ key.markAsBottomEdge(this);
+ }
+
+ private boolean isTopRow(final int rowCount) {
+ return mNumRows > 1 && rowCount == mNumRows - 1;
+ }
+ }
+
+ public static class Builder extends KeyboardBuilder<MoreKeysKeyboardParams> {
private final Key mParentKey;
private final Drawable mDivider;
private static final float LABEL_PADDING_RATIO = 0.2f;
private static final float DIVIDER_RATIO = 0.2f;
- public static class MoreKeysKeyboardParams extends Keyboard.Params {
- public boolean mIsFixedOrder;
- /* package */int mTopRowAdjustment;
- public int mNumRows;
- public int mNumColumns;
- public int mTopKeys;
- public int mLeftKeys;
- public int mRightKeys; // includes default key.
- public int mDividerWidth;
- public int mColumnWidth;
-
- public MoreKeysKeyboardParams() {
- super();
- }
-
- /**
- * Set keyboard parameters of more keys keyboard.
- *
- * @param numKeys number of keys in this more keys keyboard.
- * @param maxColumns number of maximum columns of this more keys keyboard.
- * @param keyWidth more keys keyboard key width in pixel, including horizontal gap.
- * @param rowHeight more keys keyboard row height in pixel, including vertical gap.
- * @param coordXInParent coordinate x of the key preview in parent keyboard.
- * @param parentKeyboardWidth parent keyboard width in pixel.
- * @param isFixedColumnOrder if true, more keys should be laid out in fixed order.
- * @param dividerWidth width of divider, zero for no dividers.
- */
- public void setParameters(int numKeys, int maxColumns, int keyWidth, int rowHeight,
- int coordXInParent, int parentKeyboardWidth, boolean isFixedColumnOrder,
- int dividerWidth) {
- mIsFixedOrder = isFixedColumnOrder;
- if (parentKeyboardWidth / keyWidth < maxColumns) {
- throw new IllegalArgumentException(
- "Keyboard is too small to hold more keys keyboard: "
- + parentKeyboardWidth + " " + keyWidth + " " + maxColumns);
- }
- mDefaultKeyWidth = keyWidth;
- mDefaultRowHeight = rowHeight;
-
- final int numRows = (numKeys + maxColumns - 1) / maxColumns;
- mNumRows = numRows;
- final int numColumns = mIsFixedOrder ? Math.min(numKeys, maxColumns)
- : getOptimizedColumns(numKeys, maxColumns);
- mNumColumns = numColumns;
- final int topKeys = numKeys % numColumns;
- mTopKeys = topKeys == 0 ? numColumns : topKeys;
-
- final int numLeftKeys = (numColumns - 1) / 2;
- final int numRightKeys = numColumns - numLeftKeys; // including default key.
- // Maximum number of keys we can layout both side of the parent key
- final int maxLeftKeys = coordXInParent / keyWidth;
- final int maxRightKeys = (parentKeyboardWidth - coordXInParent) / keyWidth;
- int leftKeys, rightKeys;
- if (numLeftKeys > maxLeftKeys) {
- leftKeys = maxLeftKeys;
- rightKeys = numColumns - leftKeys;
- } else if (numRightKeys > maxRightKeys + 1) {
- rightKeys = maxRightKeys + 1; // include default key
- leftKeys = numColumns - rightKeys;
- } else {
- leftKeys = numLeftKeys;
- rightKeys = numRightKeys;
- }
- // If the left keys fill the left side of the parent key, entire more keys keyboard
- // should be shifted to the right unless the parent key is on the left edge.
- if (maxLeftKeys == leftKeys && leftKeys > 0) {
- leftKeys--;
- rightKeys++;
- }
- // If the right keys fill the right side of the parent key, entire more keys
- // should be shifted to the left unless the parent key is on the right edge.
- if (maxRightKeys == rightKeys - 1 && rightKeys > 1) {
- leftKeys++;
- rightKeys--;
- }
- mLeftKeys = leftKeys;
- mRightKeys = rightKeys;
-
- // Adjustment of the top row.
- mTopRowAdjustment = mIsFixedOrder ? getFixedOrderTopRowAdjustment()
- : getAutoOrderTopRowAdjustment();
- mDividerWidth = dividerWidth;
- mColumnWidth = mDefaultKeyWidth + mDividerWidth;
- mBaseWidth = mOccupiedWidth = mNumColumns * mColumnWidth - mDividerWidth;
- // Need to subtract the bottom row's gutter only.
- mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight - mVerticalGap
- + mTopPadding + mBottomPadding;
- }
-
- private int getFixedOrderTopRowAdjustment() {
- if (mNumRows == 1 || mTopKeys % 2 == 1 || mTopKeys == mNumColumns
- || mLeftKeys == 0 || mRightKeys == 1) {
- return 0;
- }
- return -1;
- }
-
- private int getAutoOrderTopRowAdjustment() {
- if (mNumRows == 1 || mTopKeys == 1 || mNumColumns % 2 == mTopKeys % 2
- || mLeftKeys == 0 || mRightKeys == 1) {
- return 0;
- }
- return -1;
- }
-
- // Return key position according to column count (0 is default).
- /* package */int getColumnPos(int n) {
- return mIsFixedOrder ? getFixedOrderColumnPos(n) : getAutomaticColumnPos(n);
- }
-
- private int getFixedOrderColumnPos(int n) {
- final int col = n % mNumColumns;
- final int row = n / mNumColumns;
- if (!isTopRow(row)) {
- return col - mLeftKeys;
- }
- final int rightSideKeys = mTopKeys / 2;
- final int leftSideKeys = mTopKeys - (rightSideKeys + 1);
- final int pos = col - leftSideKeys;
- final int numLeftKeys = mLeftKeys + mTopRowAdjustment;
- final int numRightKeys = mRightKeys - 1;
- if (numRightKeys >= rightSideKeys && numLeftKeys >= leftSideKeys) {
- return pos;
- } else if (numRightKeys < rightSideKeys) {
- return pos - (rightSideKeys - numRightKeys);
- } else { // numLeftKeys < leftSideKeys
- return pos + (leftSideKeys - numLeftKeys);
- }
- }
-
- private int getAutomaticColumnPos(int n) {
- final int col = n % mNumColumns;
- final int row = n / mNumColumns;
- int leftKeys = mLeftKeys;
- if (isTopRow(row)) {
- leftKeys += mTopRowAdjustment;
- }
- if (col == 0) {
- // default position.
- return 0;
- }
-
- int pos = 0;
- int right = 1; // include default position key.
- int left = 0;
- int i = 0;
- while (true) {
- // Assign right key if available.
- if (right < mRightKeys) {
- pos = right;
- right++;
- i++;
- }
- if (i >= col)
- break;
- // Assign left key if available.
- if (left < leftKeys) {
- left++;
- pos = -left;
- i++;
- }
- if (i >= col)
- break;
- }
- return pos;
- }
-
- private static int getTopRowEmptySlots(int numKeys, int numColumns) {
- final int remainings = numKeys % numColumns;
- return remainings == 0 ? 0 : numColumns - remainings;
- }
-
- private int getOptimizedColumns(int numKeys, int maxColumns) {
- int numColumns = Math.min(numKeys, maxColumns);
- while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) {
- numColumns--;
- }
- return numColumns;
- }
-
- public int getDefaultKeyCoordX() {
- return mLeftKeys * mColumnWidth;
- }
-
- public int getX(int n, int row) {
- final int x = getColumnPos(n) * mColumnWidth + getDefaultKeyCoordX();
- if (isTopRow(row)) {
- return x + mTopRowAdjustment * (mColumnWidth / 2);
- }
- return x;
- }
-
- public int getY(int row) {
- return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding;
- }
-
- public void markAsEdgeKey(Key key, int row) {
- if (row == 0)
- key.markAsTopEdge(this);
- if (isTopRow(row))
- key.markAsBottomEdge(this);
- }
-
- private boolean isTopRow(int rowCount) {
- return mNumRows > 1 && rowCount == mNumRows - 1;
- }
- }
/**
* The builder of MoreKeysKeyboard.
@@ -258,7 +262,8 @@
* @param parentKey the {@link Key} that invokes more keys keyboard.
* @param parentKeyboardView the {@link KeyboardView} that contains the parentKey.
*/
- public Builder(View containerView, Key parentKey, KeyboardView parentKeyboardView) {
+ public Builder(final View containerView, final Key parentKey,
+ final KeyboardView parentKeyboardView) {
super(containerView.getContext(), new MoreKeysKeyboardParams());
final Keyboard parentKeyboard = parentKeyboardView.getKeyboard();
load(parentKeyboard.mMoreKeysTemplate, parentKeyboard.mId);
@@ -300,14 +305,14 @@
dividerWidth);
}
- private static int getMaxKeyWidth(KeyboardView view, Key parentKey, int minKeyWidth) {
+ private static int getMaxKeyWidth(final KeyboardView view, final Key parentKey,
+ final int minKeyWidth) {
final int padding = (int)(view.getResources()
.getDimension(R.dimen.more_keys_keyboard_key_horizontal_padding)
+ (parentKey.hasLabelsInMoreKeys() ? minKeyWidth * LABEL_PADDING_RATIO : 0));
final Paint paint = view.newDefaultLabelPaint();
- paint.setTextSize(parentKey.hasLabelsInMoreKeys()
- ? view.mKeyDrawParams.mKeyLabelSize
- : view.mKeyDrawParams.mKeyLetterSize);
+ paint.setTypeface(parentKey.selectTypeface(view.mKeyDrawParams));
+ paint.setTextSize(parentKey.selectMoreKeyTextSize(view.mKeyDrawParams));
int maxWidth = minKeyWidth;
for (final MoreKeySpec spec : parentKey.mMoreKeys) {
final String label = spec.mLabel;
@@ -322,24 +327,6 @@
return maxWidth;
}
- private static class MoreKeyDivider extends Key.Spacer {
- private final Drawable mIcon;
-
- public MoreKeyDivider(MoreKeysKeyboardParams params, Drawable icon, int x, int y) {
- super(params, x, y, params.mDividerWidth, params.mDefaultRowHeight);
- mIcon = icon;
- }
-
- @Override
- public Drawable getIcon(KeyboardIconsSet iconSet, int alpha) {
- // KeyboardIconsSet and alpha are unused. Use the icon that has been passed to the
- // constructor.
- // TODO: Drawable itself should have an alpha value.
- mIcon.setAlpha(128);
- return mIcon;
- }
- }
-
@Override
public MoreKeysKeyboard build() {
final MoreKeysKeyboardParams params = mParams;
@@ -368,4 +355,23 @@
return new MoreKeysKeyboard(params);
}
}
+
+ private static class MoreKeyDivider extends Key.Spacer {
+ private final Drawable mIcon;
+
+ public MoreKeyDivider(final MoreKeysKeyboardParams params, final Drawable icon,
+ final int x, final int y) {
+ super(params, x, y, params.mDividerWidth, params.mDefaultRowHeight);
+ mIcon = icon;
+ }
+
+ @Override
+ public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) {
+ // KeyboardIconsSet and alpha are unused. Use the icon that has been passed to the
+ // constructor.
+ // TODO: Drawable itself should have an alpha value.
+ mIcon.setAlpha(128);
+ return mIcon;
+ }
+ }
}
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
index 870eff2..e513a14 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
@@ -25,6 +25,7 @@
import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy;
import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
+import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.InputPointers;
import com.android.inputmethod.latin.R;
@@ -50,7 +51,8 @@
public void onCodeInput(int primaryCode, int x, int y) {
// Because a more keys keyboard doesn't need proximity characters correction, we don't
// send touch event coordinates.
- mListener.onCodeInput(primaryCode, NOT_A_TOUCH_COORDINATE, NOT_A_TOUCH_COORDINATE);
+ mListener.onCodeInput(
+ primaryCode, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
}
@Override
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 7d565a6..5a79d50 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -16,19 +16,19 @@
package com.android.inputmethod.keyboard;
-import android.graphics.Canvas;
-import android.graphics.Paint;
+import android.content.res.TypedArray;
import android.os.SystemClock;
import android.util.Log;
import android.view.MotionEvent;
-import android.view.View;
-import android.widget.TextView;
import com.android.inputmethod.accessibility.AccessibilityUtils;
import com.android.inputmethod.keyboard.internal.GestureStroke;
+import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewTrail;
import com.android.inputmethod.keyboard.internal.PointerTrackerQueue;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.InputPointers;
import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.research.ResearchLogger;
@@ -78,10 +78,9 @@
public interface DrawingProxy extends MoreKeysPanel.Controller {
public void invalidateKey(Key key);
- public TextView inflateKeyPreviewText();
public void showKeyPreview(PointerTracker tracker);
public void dismissKeyPreview(PointerTracker tracker);
- public void showGestureTrail(PointerTracker tracker);
+ public void showGesturePreviewTrail(PointerTracker tracker);
}
public interface TimerProxy {
@@ -120,14 +119,39 @@
}
}
+ static class PointerTrackerParams {
+ public final boolean mSlidingKeyInputEnabled;
+ public final int mTouchNoiseThresholdTime;
+ public final float mTouchNoiseThresholdDistance;
+ public final int mTouchNoiseThresholdDistanceSquared;
+
+ public static final PointerTrackerParams DEFAULT = new PointerTrackerParams();
+
+ private PointerTrackerParams() {
+ mSlidingKeyInputEnabled = false;
+ mTouchNoiseThresholdTime = 0;
+ mTouchNoiseThresholdDistance = 0.0f;
+ mTouchNoiseThresholdDistanceSquared = 0;
+ }
+
+ public PointerTrackerParams(TypedArray mainKeyboardViewAttr) {
+ mSlidingKeyInputEnabled = mainKeyboardViewAttr.getBoolean(
+ R.styleable.MainKeyboardView_slidingKeyInputEnable, false);
+ mTouchNoiseThresholdTime = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_touchNoiseThresholdTime, 0);
+ final float touchNouseThresholdDistance = mainKeyboardViewAttr.getDimension(
+ R.styleable.MainKeyboardView_touchNoiseThresholdDistance, 0);
+ mTouchNoiseThresholdDistance = touchNouseThresholdDistance;
+ mTouchNoiseThresholdDistanceSquared =
+ (int)(touchNouseThresholdDistance * touchNouseThresholdDistance);
+ }
+ }
+
// Parameters for pointer handling.
- private static MainKeyboardView.PointerTrackerParams sParams;
- private static int sTouchNoiseThresholdDistanceSquared;
+ private static PointerTrackerParams sParams;
private static boolean sNeedsPhantomSuddenMoveEventHack;
- private static final ArrayList<PointerTracker> sTrackers = new ArrayList<PointerTracker>();
- private static final InputPointers sAggregratedPointers = new InputPointers(
- GestureStroke.DEFAULT_CAPACITY);
+ private static final ArrayList<PointerTracker> sTrackers = CollectionUtils.newArrayList();
private static PointerTrackerQueue sPointerTrackerQueue;
public final int mPointerId;
@@ -139,15 +163,14 @@
private Keyboard mKeyboard;
private int mKeyQuarterWidthSquared;
- private final TextView mKeyPreviewText;
- private boolean mIsAlphabetKeyboard;
- private boolean mIsPossibleGesture = false;
- private boolean mInGesture = false;
-
- // TODO: Remove these variables
- private int mLastRecognitionPointSize = 0;
- private long mLastRecognitionTime = 0;
+ private boolean mIsDetectingGesture = false; // per PointerTracker.
+ private static boolean sInGesture = false;
+ private static long sGestureFirstDownTime;
+ private static final InputPointers sAggregratedPointers = new InputPointers(
+ GestureStroke.DEFAULT_CAPACITY);
+ private static int sLastRecognitionPointSize = 0;
+ private static long sLastRecognitionTime = 0;
// The position and time at which first down event occurred.
private long mDownTime;
@@ -185,7 +208,7 @@
private static final KeyboardActionListener EMPTY_LISTENER =
new KeyboardActionListener.Adapter();
- private final GestureStroke mGestureStroke;
+ private final GestureStrokeWithPreviewTrail mGestureStrokeWithPreviewTrail;
public static void init(boolean hasDistinctMultitouch,
boolean needsPhantomSuddenMoveEventHack) {
@@ -195,14 +218,11 @@
sPointerTrackerQueue = null;
}
sNeedsPhantomSuddenMoveEventHack = needsPhantomSuddenMoveEventHack;
-
- setParameters(MainKeyboardView.PointerTrackerParams.DEFAULT);
+ sParams = PointerTrackerParams.DEFAULT;
}
- public static void setParameters(MainKeyboardView.PointerTrackerParams params) {
- sParams = params;
- sTouchNoiseThresholdDistanceSquared = (int)(
- params.mTouchNoiseThresholdDistance * params.mTouchNoiseThresholdDistance);
+ public static void setParameters(final TypedArray mainKeyboardViewAttr) {
+ sParams = new PointerTrackerParams(mainKeyboardViewAttr);
}
private static void updateGestureHandlingMode() {
@@ -213,17 +233,17 @@
}
// Note that this method is called from a non-UI thread.
- public static void setMainDictionaryAvailability(boolean mainDictionaryAvailable) {
+ public static void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) {
sMainDictionaryAvailable = mainDictionaryAvailable;
updateGestureHandlingMode();
}
- public static void setGestureHandlingEnabledByUser(boolean gestureHandlingEnabledByUser) {
+ public static void setGestureHandlingEnabledByUser(final boolean gestureHandlingEnabledByUser) {
sGestureHandlingEnabledByUser = gestureHandlingEnabledByUser;
updateGestureHandlingMode();
}
- public static PointerTracker getPointerTracker(final int id, KeyEventHandler handler) {
+ public static PointerTracker getPointerTracker(final int id, final KeyEventHandler handler) {
final ArrayList<PointerTracker> trackers = sTrackers;
// Create pointer trackers until we can get 'id+1'-th tracker, if needed.
@@ -239,7 +259,7 @@
return sPointerTrackerQueue != null ? sPointerTrackerQueue.isAnyInSlidingKeyInput() : false;
}
- public static void setKeyboardActionListener(KeyboardActionListener listener) {
+ public static void setKeyboardActionListener(final KeyboardActionListener listener) {
final int trackersSize = sTrackers.size();
for (int i = 0; i < trackersSize; ++i) {
final PointerTracker tracker = sTrackers.get(i);
@@ -247,7 +267,7 @@
}
}
- public static void setKeyDetector(KeyDetector keyDetector) {
+ public static void setKeyDetector(final KeyDetector keyDetector) {
final int trackersSize = sTrackers.size();
for (int i = 0; i < trackersSize; ++i) {
final PointerTracker tracker = sTrackers.get(i);
@@ -260,67 +280,29 @@
updateGestureHandlingMode();
}
- public static void dismissAllKeyPreviews() {
+ public static void setReleasedKeyGraphicsToAllKeys() {
final int trackersSize = sTrackers.size();
for (int i = 0; i < trackersSize; ++i) {
final PointerTracker tracker = sTrackers.get(i);
- tracker.getKeyPreviewText().setVisibility(View.INVISIBLE);
tracker.setReleasedKeyGraphics(tracker.mCurrentKey);
}
}
- // TODO: To handle multi-touch gestures we may want to move this method to
- // {@link PointerTrackerQueue}.
- private static InputPointers getIncrementalBatchPoints() {
- final int trackersSize = sTrackers.size();
- for (int i = 0; i < trackersSize; ++i) {
- final PointerTracker tracker = sTrackers.get(i);
- tracker.mGestureStroke.appendIncrementalBatchPoints(sAggregratedPointers);
- }
- return sAggregratedPointers;
- }
-
- // TODO: To handle multi-touch gestures we may want to move this method to
- // {@link PointerTrackerQueue}.
- private static InputPointers getAllBatchPoints() {
- final int trackersSize = sTrackers.size();
- for (int i = 0; i < trackersSize; ++i) {
- final PointerTracker tracker = sTrackers.get(i);
- tracker.mGestureStroke.appendAllBatchPoints(sAggregratedPointers);
- }
- return sAggregratedPointers;
- }
-
- // TODO: To handle multi-touch gestures we may want to move this method to
- // {@link PointerTrackerQueue}.
- public static void clearBatchInputPointsOfAllPointerTrackers() {
- final int trackersSize = sTrackers.size();
- for (int i = 0; i < trackersSize; ++i) {
- final PointerTracker tracker = sTrackers.get(i);
- tracker.mGestureStroke.reset();
- }
- sAggregratedPointers.reset();
- }
-
- private PointerTracker(int id, KeyEventHandler handler) {
- if (handler == null)
+ private PointerTracker(final int id, final KeyEventHandler handler) {
+ if (handler == null) {
throw new NullPointerException();
+ }
mPointerId = id;
- mGestureStroke = new GestureStroke(id);
+ mGestureStrokeWithPreviewTrail = new GestureStrokeWithPreviewTrail(id);
setKeyDetectorInner(handler.getKeyDetector());
mListener = handler.getKeyboardActionListener();
mDrawingProxy = handler.getDrawingProxy();
mTimerProxy = handler.getTimerProxy();
- mKeyPreviewText = mDrawingProxy.inflateKeyPreviewText();
- }
-
- public TextView getKeyPreviewText() {
- return mKeyPreviewText;
}
// Returns true if keyboard has been changed by this callback.
- private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key) {
- if (mInGesture) {
+ private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key) {
+ if (sInGesture) {
return false;
}
final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
@@ -344,13 +326,14 @@
// 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 x, int y) {
+ private void callListenerOnCodeInput(final Key key, final int primaryCode, final int x,
+ final int y) {
final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState();
- final int code = altersCode ? key.mAltCode : primaryCode;
+ final int code = altersCode ? key.getAltCode() : primaryCode;
if (DEBUG_LISTENER) {
- Log.d(TAG, "onCodeInput: " + Keyboard.printableCode(code) + " text=" + key.mOutputText
- + " x=" + x + " y=" + y
+ Log.d(TAG, "onCodeInput: " + Keyboard.printableCode(code)
+ + " text=" + key.getOutputText() + " x=" + x + " y=" + y
+ " ignoreModifier=" + ignoreModifierKey + " altersCode=" + altersCode
+ " enabled=" + key.isEnabled());
}
@@ -364,7 +347,7 @@
// Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state.
if (key.isEnabled() || altersCode) {
if (code == Keyboard.CODE_OUTPUT_TEXT) {
- mListener.onTextInput(key.mOutputText);
+ mListener.onTextInput(key.getOutputText());
} else if (code != Keyboard.CODE_UNSPECIFIED) {
mListener.onCodeInput(code, x, y);
}
@@ -373,8 +356,9 @@
// 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, boolean withSliding) {
- if (mInGesture) {
+ private void callListenerOnRelease(final Key key, final int primaryCode,
+ final boolean withSliding) {
+ if (sInGesture) {
return;
}
final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
@@ -396,19 +380,19 @@
}
private void callListenerOnCancelInput() {
- if (DEBUG_LISTENER)
+ if (DEBUG_LISTENER) {
Log.d(TAG, "onCancelInput");
+ }
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.pointerTracker_callListenerOnCancelInput();
}
mListener.onCancelInput();
}
- private void setKeyDetectorInner(KeyDetector keyDetector) {
+ private void setKeyDetectorInner(final KeyDetector keyDetector) {
mKeyDetector = keyDetector;
mKeyboard = keyDetector.getKeyboard();
- mIsAlphabetKeyboard = mKeyboard.mId.isAlphabetKeyboard();
- mGestureStroke.setGestureSampleLength(mKeyboard.mMostCommonKeyWidth);
+ mGestureStrokeWithPreviewTrail.setGestureSampleLength(mKeyboard.mMostCommonKeyWidth);
final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY);
if (newKey != mCurrentKey) {
if (mDrawingProxy != null) {
@@ -434,11 +418,11 @@
return mCurrentKey != null && mCurrentKey.isModifier();
}
- public Key getKeyOn(int x, int y) {
+ public Key getKeyOn(final int x, final int y) {
return mKeyDetector.detectHitKey(x, y);
}
- private void setReleasedKeyGraphics(Key key) {
+ private void setReleasedKeyGraphics(final Key key) {
mDrawingProxy.dismissKeyPreview(this);
if (key == null) {
return;
@@ -456,20 +440,20 @@
}
if (key.altCodeWhileTyping()) {
- final int altCode = key.mAltCode;
+ final int altCode = key.getAltCode();
final Key altKey = mKeyboard.getKey(altCode);
if (altKey != null) {
updateReleaseKeyGraphics(altKey);
}
for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) {
- if (k != key && k.mAltCode == altCode) {
+ if (k != key && k.getAltCode() == altCode) {
updateReleaseKeyGraphics(k);
}
}
}
}
- private void setPressedKeyGraphics(Key key) {
+ private void setPressedKeyGraphics(final Key key) {
if (key == null) {
return;
}
@@ -481,7 +465,7 @@
return;
}
- if (!key.noKeyPreview() && !mInGesture) {
+ if (!key.noKeyPreview() && !sInGesture) {
mDrawingProxy.showKeyPreview(this);
}
updatePressKeyGraphics(key);
@@ -495,33 +479,31 @@
}
if (key.altCodeWhileTyping() && mTimerProxy.isTypingState()) {
- final int altCode = key.mAltCode;
+ final int altCode = key.getAltCode();
final Key altKey = mKeyboard.getKey(altCode);
if (altKey != null) {
updatePressKeyGraphics(altKey);
}
for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) {
- if (k != key && k.mAltCode == altCode) {
+ if (k != key && k.getAltCode() == altCode) {
updatePressKeyGraphics(k);
}
}
}
}
- private void updateReleaseKeyGraphics(Key key) {
+ private void updateReleaseKeyGraphics(final Key key) {
key.onReleased();
mDrawingProxy.invalidateKey(key);
}
- private void updatePressKeyGraphics(Key key) {
+ private void updatePressKeyGraphics(final Key key) {
key.onPressed();
mDrawingProxy.invalidateKey(key);
}
- public void drawGestureTrail(Canvas canvas, Paint paint) {
- if (mInGesture) {
- mGestureStroke.drawGestureTrail(canvas, paint);
- }
+ public GestureStrokeWithPreviewTrail getGestureStrokeWithPreviewTrail() {
+ return mGestureStrokeWithPreviewTrail;
}
public int getLastX() {
@@ -536,76 +518,91 @@
return mDownTime;
}
- private Key onDownKey(int x, int y, long eventTime) {
+ private Key onDownKey(final int x, final int y, final long eventTime) {
mDownTime = eventTime;
return onMoveToNewKey(onMoveKeyInternal(x, y), x, y);
}
- private Key onMoveKeyInternal(int x, int y) {
+ private Key onMoveKeyInternal(final int x, final int y) {
mLastX = x;
mLastY = y;
return mKeyDetector.detectHitKey(x, y);
}
- private Key onMoveKey(int x, int y) {
+ private Key onMoveKey(final int x, final int y) {
return onMoveKeyInternal(x, y);
}
- private Key onMoveToNewKey(Key newKey, int x, int y) {
+ private Key onMoveToNewKey(final Key newKey, final int x, final int y) {
mCurrentKey = newKey;
mKeyX = x;
mKeyY = y;
return newKey;
}
+ private static int getActivePointerTrackerCount() {
+ return (sPointerTrackerQueue == null) ? 1 : sPointerTrackerQueue.size();
+ }
+
private void startBatchInput() {
if (DEBUG_LISTENER) {
Log.d(TAG, "onStartBatchInput");
}
- mInGesture = true;
+ sInGesture = true;
mListener.onStartBatchInput();
+ mDrawingProxy.showGesturePreviewTrail(this);
}
- private void updateBatchInput(InputPointers batchPoints) {
- if (DEBUG_LISTENER) {
- Log.d(TAG, "onUpdateBatchInput: batchPoints=" + batchPoints.getPointerSize());
+ private void updateBatchInput(final long eventTime) {
+ synchronized (sAggregratedPointers) {
+ mGestureStrokeWithPreviewTrail.appendIncrementalBatchPoints(sAggregratedPointers);
+ final int size = sAggregratedPointers.getPointerSize();
+ if (size > sLastRecognitionPointSize
+ && eventTime > sLastRecognitionTime + MIN_GESTURE_RECOGNITION_TIME) {
+ sLastRecognitionPointSize = size;
+ sLastRecognitionTime = eventTime;
+ if (DEBUG_LISTENER) {
+ Log.d(TAG, "onUpdateBatchInput: batchPoints=" + size);
+ }
+ mListener.onUpdateBatchInput(sAggregratedPointers);
+ }
}
- mListener.onUpdateBatchInput(batchPoints);
+ mDrawingProxy.showGesturePreviewTrail(this);
}
- private void endBatchInput(InputPointers batchPoints) {
- if (DEBUG_LISTENER) {
- Log.d(TAG, "onEndBatchInput: batchPoints=" + batchPoints.getPointerSize());
+ private void endBatchInput() {
+ synchronized (sAggregratedPointers) {
+ mGestureStrokeWithPreviewTrail.appendAllBatchPoints(sAggregratedPointers);
+ if (getActivePointerTrackerCount() == 1) {
+ if (DEBUG_LISTENER) {
+ Log.d(TAG, "onEndBatchInput: batchPoints="
+ + sAggregratedPointers.getPointerSize());
+ }
+ sInGesture = false;
+ mListener.onEndBatchInput(sAggregratedPointers);
+ clearBatchInputPointsOfAllPointerTrackers();
+ }
}
- mListener.onEndBatchInput(batchPoints);
- clearBatchInputRecognitionStateOfThisPointerTracker();
+ mDrawingProxy.showGesturePreviewTrail(this);
+ }
+
+ private static void abortBatchInput() {
clearBatchInputPointsOfAllPointerTrackers();
}
- private void abortBatchInput() {
- clearBatchInputRecognitionStateOfThisPointerTracker();
- clearBatchInputPointsOfAllPointerTrackers();
- }
-
- private void clearBatchInputRecognitionStateOfThisPointerTracker() {
- mIsPossibleGesture = false;
- mInGesture = false;
- mLastRecognitionPointSize = 0;
- mLastRecognitionTime = 0;
- }
-
- private boolean updateBatchInputRecognitionState(long eventTime, int size) {
- if (size > mLastRecognitionPointSize
- && eventTime > mLastRecognitionTime + MIN_GESTURE_RECOGNITION_TIME) {
- mLastRecognitionPointSize = size;
- mLastRecognitionTime = eventTime;
- return true;
+ private static void clearBatchInputPointsOfAllPointerTrackers() {
+ final int trackersSize = sTrackers.size();
+ for (int i = 0; i < trackersSize; ++i) {
+ final PointerTracker tracker = sTrackers.get(i);
+ tracker.mGestureStrokeWithPreviewTrail.reset();
}
- return false;
+ sAggregratedPointers.reset();
+ sLastRecognitionPointSize = 0;
+ sLastRecognitionTime = 0;
}
- public void processMotionEvent(int action, int x, int y, long eventTime,
- KeyEventHandler handler) {
+ public void processMotionEvent(final int action, final int x, final int y, final long eventTime,
+ final KeyEventHandler handler) {
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
@@ -624,9 +621,11 @@
}
}
- public void onDownEvent(int x, int y, long eventTime, KeyEventHandler handler) {
- if (DEBUG_EVENT)
+ public void onDownEvent(final int x, final int y, final long eventTime,
+ final KeyEventHandler handler) {
+ if (DEBUG_EVENT) {
printTouchEvent("onDownEvent:", x, y, eventTime);
+ }
mDrawingProxy = handler.getDrawingProxy();
mTimerProxy = handler.getTimerProxy();
@@ -638,7 +637,7 @@
final int dx = x - mLastX;
final int dy = y - mLastY;
final int distanceSquared = (dx * dx + dy * dy);
- if (distanceSquared < sTouchNoiseThresholdDistanceSquared) {
+ if (distanceSquared < sParams.mTouchNoiseThresholdDistanceSquared) {
if (DEBUG_MODE)
Log.w(TAG, "onDownEvent: ignore potential noise: time=" + deltaT
+ " distance=" + distanceSquared);
@@ -650,8 +649,8 @@
}
}
- final PointerTrackerQueue queue = sPointerTrackerQueue;
final Key key = getKeyOn(x, y);
+ final PointerTrackerQueue queue = sPointerTrackerQueue;
if (queue != null) {
if (key != null && key.isModifier()) {
// Before processing a down event of modifier key, all pointers already being
@@ -661,20 +660,30 @@
queue.add(this);
}
onDownEventInternal(x, y, eventTime);
- if (queue != null && queue.size() == 1) {
- mIsPossibleGesture = false;
+ if (!sShouldHandleGesture) {
+ return;
+ }
+ final int activePointerTrackerCount = getActivePointerTrackerCount();
+ if (activePointerTrackerCount == 1) {
+ mIsDetectingGesture = false;
// A gesture should start only from the letter key.
- if (sShouldHandleGesture && mIsAlphabetKeyboard && !mIsShowingMoreKeysPanel
- && key != null && Keyboard.isLetterCode(key.mCode)) {
- mIsPossibleGesture = true;
- // TODO: pointer times should be relative to first down even in entire batch input
- // instead of resetting to 0 for each new down event.
- mGestureStroke.addPoint(x, y, 0, false);
+ final boolean isAlphabetKeyboard = (mKeyboard != null)
+ && mKeyboard.mId.isAlphabetKeyboard();
+ if (isAlphabetKeyboard && !mIsShowingMoreKeysPanel && key != null
+ && Keyboard.isLetterCode(key.mCode)) {
+ mIsDetectingGesture = true;
+ sGestureFirstDownTime = eventTime;
+ mGestureStrokeWithPreviewTrail.addPoint(x, y, 0, false /* isHistorical */);
}
+ } else if (sInGesture && activePointerTrackerCount > 1) {
+ mIsDetectingGesture = true;
+ final int elapsedTimeFromFirstDown = (int)(eventTime - sGestureFirstDownTime);
+ mGestureStrokeWithPreviewTrail.addPoint(x, y, elapsedTimeFromFirstDown,
+ false /* isHistorical */);
}
}
- private void onDownEventInternal(int x, int y, long eventTime) {
+ private void onDownEventInternal(final int x, final int y, final long eventTime) {
Key key = onDownKey(x, y, eventTime);
// Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding
// from modifier key, or 3) this pointer's KeyDetector always allows sliding input.
@@ -699,40 +708,38 @@
}
}
- private void startSlidingKeyInput(Key key) {
+ private void startSlidingKeyInput(final Key key) {
if (!mIsInSlidingKeyInput) {
mIgnoreModifierKey = key.isModifier();
}
mIsInSlidingKeyInput = true;
}
- private void onGestureMoveEvent(PointerTracker tracker, int x, int y, long eventTime,
- boolean isHistorical, Key key) {
- final int gestureTime = (int)(eventTime - tracker.getDownTime());
- if (sShouldHandleGesture && mIsPossibleGesture) {
- final GestureStroke stroke = mGestureStroke;
+ private void onGestureMoveEvent(final int x, final int y, final long eventTime,
+ final boolean isHistorical, final Key key) {
+ final int gestureTime = (int)(eventTime - sGestureFirstDownTime);
+ if (mIsDetectingGesture) {
+ final GestureStroke stroke = mGestureStrokeWithPreviewTrail;
stroke.addPoint(x, y, gestureTime, isHistorical);
- if (!mInGesture && stroke.isStartOfAGesture()) {
+ if (!sInGesture && stroke.isStartOfAGesture()) {
startBatchInput();
}
- }
- if (key != null && mInGesture) {
- final InputPointers batchPoints = getIncrementalBatchPoints();
- mDrawingProxy.showGestureTrail(this);
- if (updateBatchInputRecognitionState(eventTime, batchPoints.getPointerSize())) {
- updateBatchInput(batchPoints);
+ if (sInGesture && key != null) {
+ updateBatchInput(eventTime);
}
}
}
- public void onMoveEvent(int x, int y, long eventTime, MotionEvent me) {
- if (DEBUG_MOVE_EVENT)
+ public void onMoveEvent(final int x, final int y, final long eventTime, final MotionEvent me) {
+ if (DEBUG_MOVE_EVENT) {
printTouchEvent("onMoveEvent:", x, y, eventTime);
- if (mKeyAlreadyProcessed)
+ }
+ if (mKeyAlreadyProcessed) {
return;
+ }
- if (me != null) {
+ if (sShouldHandleGesture && me != null) {
// Add historical points to gesture path.
final int pointerIndex = me.findPointerIndex(mPointerId);
final int historicalSize = me.getHistorySize();
@@ -740,24 +747,31 @@
final int historicalX = (int)me.getHistoricalX(pointerIndex, h);
final int historicalY = (int)me.getHistoricalY(pointerIndex, h);
final long historicalTime = me.getHistoricalEventTime(h);
- onGestureMoveEvent(this, historicalX, historicalY, historicalTime,
+ onGestureMoveEvent(historicalX, historicalY, historicalTime,
true /* isHistorical */, null);
}
}
+ onMoveEventInternal(x, y, eventTime);
+ }
+
+ private void onMoveEventInternal(final int x, final int y, final long eventTime) {
final int lastX = mLastX;
final int lastY = mLastY;
final Key oldKey = mCurrentKey;
Key key = onMoveKey(x, y);
- // Register move event on gesture tracker.
- onGestureMoveEvent(this, x, y, eventTime, false /* isHistorical */, key);
- if (mInGesture) {
- mIgnoreModifierKey = true;
- mTimerProxy.cancelLongPressTimer();
- mIsInSlidingKeyInput = true;
- mCurrentKey = null;
- setReleasedKeyGraphics(oldKey);
+ if (sShouldHandleGesture) {
+ // Register move event on gesture tracker.
+ onGestureMoveEvent(x, y, eventTime, false /* isHistorical */, key);
+ if (sInGesture) {
+ mIgnoreModifierKey = true;
+ mTimerProxy.cancelLongPressTimer();
+ mIsInSlidingKeyInput = true;
+ mCurrentKey = null;
+ setReleasedKeyGraphics(oldKey);
+ return;
+ }
}
if (key != null) {
@@ -802,7 +816,7 @@
// TODO: Should find a way to balance gesture detection and this hack.
if (sNeedsPhantomSuddenMoveEventHack
&& lastMoveSquared >= mKeyQuarterWidthSquared
- && !mIsPossibleGesture) {
+ && !mIsDetectingGesture) {
if (DEBUG_MODE) {
Log.w(TAG, String.format("onMoveEvent:"
+ " phantom sudden move event is translated to "
@@ -820,11 +834,11 @@
// touch panels when there are close multiple touches.
// Caveat: When in chording input mode with a modifier key, we don't use
// this hack.
- if (me != null && me.getPointerCount() > 1
+ if (getActivePointerTrackerCount() > 1 && sPointerTrackerQueue != null
&& !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) {
onUpEventInternal();
}
- if (!mIsPossibleGesture) {
+ if (!mIsDetectingGesture) {
mKeyAlreadyProcessed = true;
}
setReleasedKeyGraphics(oldKey);
@@ -842,7 +856,7 @@
if (mIsAllowedSlidingKeyInput) {
onMoveToNewKey(key, x, y);
} else {
- if (!mIsPossibleGesture) {
+ if (!mIsDetectingGesture) {
mKeyAlreadyProcessed = true;
}
}
@@ -850,13 +864,14 @@
}
}
- public void onUpEvent(int x, int y, long eventTime) {
- if (DEBUG_EVENT)
+ public void onUpEvent(final int x, final int y, final long eventTime) {
+ if (DEBUG_EVENT) {
printTouchEvent("onUpEvent :", x, y, eventTime);
+ }
final PointerTrackerQueue queue = sPointerTrackerQueue;
if (queue != null) {
- if (!mInGesture) {
+ if (!sInGesture) {
if (mCurrentKey != null && mCurrentKey.isModifier()) {
// Before processing an up event of modifier key, all pointers already being
// tracked should be released.
@@ -865,18 +880,21 @@
queue.releaseAllPointersOlderThan(this, eventTime);
}
}
- queue.remove(this);
}
onUpEventInternal();
+ if (queue != null) {
+ queue.remove(this);
+ }
}
// Let this pointer tracker know that one of newer-than-this pointer trackers got an up event.
// This pointer tracker needs to keep the key top graphics "pressed", but needs to get a
// "virtual" up event.
@Override
- public void onPhantomUpEvent(long eventTime) {
- if (DEBUG_EVENT)
+ public void onPhantomUpEvent(final long eventTime) {
+ if (DEBUG_EVENT) {
printTouchEvent("onPhntEvent:", getLastX(), getLastY(), eventTime);
+ }
onUpEventInternal();
mKeyAlreadyProcessed = true;
}
@@ -884,37 +902,35 @@
private void onUpEventInternal() {
mTimerProxy.cancelKeyTimers();
mIsInSlidingKeyInput = false;
- mIsPossibleGesture = false;
+ mIsDetectingGesture = false;
+ final Key currentKey = mCurrentKey;
+ mCurrentKey = null;
// Release the last pressed key.
- setReleasedKeyGraphics(mCurrentKey);
+ setReleasedKeyGraphics(currentKey);
if (mIsShowingMoreKeysPanel) {
mDrawingProxy.dismissMoreKeysPanel();
mIsShowingMoreKeysPanel = false;
}
- if (mInGesture) {
- // Register up event on gesture tracker.
- // TODO: Figure out how to deal with multiple fingers that are in gesture, sliding,
- // and/or tapping mode?
- endBatchInput(getAllBatchPoints());
- if (mCurrentKey != null) {
- callListenerOnRelease(mCurrentKey, mCurrentKey.mCode, true);
- mCurrentKey = null;
+ if (sInGesture) {
+ if (currentKey != null) {
+ callListenerOnRelease(currentKey, currentKey.mCode, true);
}
- mDrawingProxy.showGestureTrail(this);
+ endBatchInput();
return;
}
- // This event will be recognized as a regular code input. Clear unused batch points so they
- // are not mistakenly included in the next batch event.
+ // This event will be recognized as a regular code input. Clear unused possible batch points
+ // so they are not mistakenly displayed as preview.
clearBatchInputPointsOfAllPointerTrackers();
- if (mKeyAlreadyProcessed)
+ if (mKeyAlreadyProcessed) {
return;
- if (mCurrentKey != null && !mCurrentKey.isRepeatable()) {
- detectAndSendKey(mCurrentKey, mKeyX, mKeyY);
+ }
+ if (currentKey != null && !currentKey.isRepeatable()) {
+ detectAndSendKey(currentKey, mKeyX, mKeyY);
}
}
- public void onShowMoreKeysPanel(int x, int y, KeyEventHandler handler) {
+ public void onShowMoreKeysPanel(final int x, final int y, final KeyEventHandler handler) {
abortBatchInput();
onLongPressed();
mIsShowingMoreKeysPanel = true;
@@ -930,9 +946,10 @@
}
}
- public void onCancelEvent(int x, int y, long eventTime) {
- if (DEBUG_EVENT)
+ public void onCancelEvent(final int x, final int y, final long eventTime) {
+ if (DEBUG_EVENT) {
printTouchEvent("onCancelEvt:", x, y, eventTime);
+ }
final PointerTrackerQueue queue = sPointerTrackerQueue;
if (queue != null) {
@@ -952,24 +969,25 @@
}
}
- private void startRepeatKey(Key key) {
- if (key != null && key.isRepeatable() && !mInGesture) {
+ private void startRepeatKey(final Key key) {
+ if (key != null && key.isRepeatable() && !sInGesture) {
onRegisterKey(key);
mTimerProxy.startKeyRepeatTimer(this);
}
}
- public void onRegisterKey(Key key) {
+ public void onRegisterKey(final Key key) {
if (key != null) {
detectAndSendKey(key, key.mX, key.mY);
mTimerProxy.startTypingStateTimer(key);
}
}
- private boolean isMajorEnoughMoveToBeOnNewKey(int x, int y, Key newKey) {
- if (mKeyDetector == null)
+ private boolean isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final Key newKey) {
+ if (mKeyDetector == null) {
throw new NullPointerException("keyboard and/or key detector not set");
- Key curKey = mCurrentKey;
+ }
+ final Key curKey = mCurrentKey;
if (newKey == curKey) {
return false;
} else if (curKey != null) {
@@ -980,24 +998,25 @@
}
}
- private void startLongPressTimer(Key key) {
- if (key != null && key.isLongPressEnabled() && !mInGesture) {
+ private void startLongPressTimer(final Key key) {
+ if (key != null && key.isLongPressEnabled() && !sInGesture) {
mTimerProxy.startLongPressTimer(this);
}
}
- private void detectAndSendKey(Key key, int x, int y) {
+ private void detectAndSendKey(final Key key, final int x, final int y) {
if (key == null) {
callListenerOnCancelInput();
return;
}
- int code = key.mCode;
+ final int code = key.mCode;
callListenerOnCodeInput(key, code, x, y);
callListenerOnRelease(key, code, false);
}
- private void printTouchEvent(String title, int x, int y, long eventTime) {
+ private void printTouchEvent(final String title, final int x, final int y,
+ final long eventTime) {
final Key key = mKeyDetector.detectHitKey(x, y);
final String code = KeyDetector.printableCode(key);
Log.d(TAG, String.format("%s%s[%d] %4d %4d %5d %s", title,
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index ac0a56b..e1b082c 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -19,7 +19,8 @@
import android.graphics.Rect;
import android.text.TextUtils;
-import com.android.inputmethod.keyboard.Keyboard.Params.TouchPositionCorrection;
+import com.android.inputmethod.keyboard.internal.TouchPositionCorrection;
+import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.JniUtils;
import java.util.Arrays;
@@ -47,9 +48,10 @@
private final Key[][] mGridNeighbors;
private final String mLocaleStr;
- ProximityInfo(String localeStr, int gridWidth, int gridHeight, int minWidth, int height,
- int mostCommonKeyWidth, int mostCommonKeyHeight, final Key[] keys,
- TouchPositionCorrection touchPositionCorrection) {
+ ProximityInfo(final String localeStr, final int gridWidth, final int gridHeight,
+ final int minWidth, final int height, final int mostCommonKeyWidth,
+ final int mostCommonKeyHeight, final Key[] keys,
+ final TouchPositionCorrection touchPositionCorrection) {
if (TextUtils.isEmpty(localeStr)) {
mLocaleStr = "";
} else {
@@ -80,7 +82,7 @@
}
public static ProximityInfo createSpellCheckerProximityInfo(final int[] proximity,
- int rowSize, int gridWidth, int gridHeight) {
+ final int rowSize, final int gridWidth, final int gridHeight) {
final ProximityInfo spellCheckerProximityInfo = createDummyProximityInfo();
spellCheckerProximityInfo.mNativeProximityInfo =
spellCheckerProximityInfo.setProximityInfoNative("",
@@ -111,7 +113,7 @@
final Key[] keys = mKeys;
final TouchPositionCorrection touchPositionCorrection = mTouchPositionCorrection;
final int[] proximityCharsArray = new int[mGridSize * MAX_PROXIMITY_CHARS_SIZE];
- Arrays.fill(proximityCharsArray, KeyDetector.NOT_A_CODE);
+ Arrays.fill(proximityCharsArray, Constants.NOT_A_CODE);
for (int i = 0; i < mGridSize; ++i) {
final int proximityCharsLength = gridNeighborKeys[i].length;
for (int j = 0; j < proximityCharsLength; ++j) {
@@ -234,7 +236,7 @@
dest[index++] = code;
}
if (index < destLength) {
- dest[index] = KeyDetector.NOT_A_CODE;
+ dest[index] = Constants.NOT_A_CODE;
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/ViewLayoutUtils.java b/java/src/com/android/inputmethod/keyboard/ViewLayoutUtils.java
index ee50470..dc12fa4 100644
--- a/java/src/com/android/inputmethod/keyboard/ViewLayoutUtils.java
+++ b/java/src/com/android/inputmethod/keyboard/ViewLayoutUtils.java
@@ -22,7 +22,7 @@
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
-public class ViewLayoutUtils {
+public final class ViewLayoutUtils {
private ViewLayoutUtils() {
// This utility class is not publicly instantiable.
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
new file mode 100644
index 0000000..e814d80
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.SystemClock;
+
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.ResizableIntArray;
+
+class GesturePreviewTrail {
+ private static final int DEFAULT_CAPACITY = GestureStrokeWithPreviewTrail.PREVIEW_CAPACITY;
+
+ private final GesturePreviewTrailParams mPreviewParams;
+ private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
+ private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
+ private final ResizableIntArray mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY);
+ private int mCurrentStrokeId = -1;
+ private long mCurrentDownTime;
+ private int mTrailStartIndex;
+
+ // Use this value as imaginary zero because x-coordinates may be zero.
+ private static final int DOWN_EVENT_MARKER = -128;
+
+ static class GesturePreviewTrailParams {
+ public final int mFadeoutStartDelay;
+ public final int mFadeoutDuration;
+ public final int mUpdateInterval;
+
+ public GesturePreviewTrailParams(final TypedArray keyboardViewAttr) {
+ mFadeoutStartDelay = keyboardViewAttr.getInt(
+ R.styleable.KeyboardView_gesturePreviewTrailFadeoutStartDelay, 0);
+ mFadeoutDuration = keyboardViewAttr.getInt(
+ R.styleable.KeyboardView_gesturePreviewTrailFadeoutDuration, 0);
+ mUpdateInterval = keyboardViewAttr.getInt(
+ R.styleable.KeyboardView_gesturePreviewTrailUpdateInterval, 0);
+ }
+ }
+
+ public GesturePreviewTrail(final GesturePreviewTrailParams params) {
+ mPreviewParams = params;
+ }
+
+ private static int markAsDownEvent(final int xCoord) {
+ return DOWN_EVENT_MARKER - xCoord;
+ }
+
+ private static boolean isDownEventXCoord(final int xCoordOrMark) {
+ return xCoordOrMark <= DOWN_EVENT_MARKER;
+ }
+
+ private static int getXCoordValue(final int xCoordOrMark) {
+ return isDownEventXCoord(xCoordOrMark)
+ ? DOWN_EVENT_MARKER - xCoordOrMark : xCoordOrMark;
+ }
+
+ public void addStroke(final GestureStrokeWithPreviewTrail stroke, final long downTime) {
+ final int strokeId = stroke.getGestureStrokeId();
+ final boolean isNewStroke = strokeId != mCurrentStrokeId;
+ final int trailSize = mEventTimes.getLength();
+ stroke.appendPreviewStroke(mEventTimes, mXCoordinates, mYCoordinates);
+ final int newTrailSize = mEventTimes.getLength();
+ if (stroke.getGestureStrokePreviewSize() == 0) {
+ return;
+ }
+ if (isNewStroke) {
+ final int elapsedTime = (int)(downTime - mCurrentDownTime);
+ final int[] eventTimes = mEventTimes.getPrimitiveArray();
+ for (int i = mTrailStartIndex; i < trailSize; i++) {
+ eventTimes[i] -= elapsedTime;
+ }
+
+ if (newTrailSize > trailSize) {
+ final int[] xCoords = mXCoordinates.getPrimitiveArray();
+ xCoords[trailSize] = markAsDownEvent(xCoords[trailSize]);
+ }
+ mCurrentDownTime = downTime;
+ mCurrentStrokeId = strokeId;
+ }
+ }
+
+ private int getAlpha(final int elapsedTime) {
+ if (elapsedTime < mPreviewParams.mFadeoutStartDelay) {
+ return Constants.Color.ALPHA_OPAQUE;
+ }
+ final int decreasingAlpha = Constants.Color.ALPHA_OPAQUE
+ * (elapsedTime - mPreviewParams.mFadeoutStartDelay)
+ / mPreviewParams.mFadeoutDuration;
+ return Constants.Color.ALPHA_OPAQUE - decreasingAlpha;
+ }
+
+ /**
+ * Draw gesture preview trail
+ * @param canvas The canvas to draw the gesture preview trail
+ * @param paint The paint object to be used to draw the gesture preview trail
+ * @return true if some gesture preview trails remain to be drawn
+ */
+ public boolean drawGestureTrail(final Canvas canvas, final Paint paint) {
+ final int trailSize = mEventTimes.getLength();
+ if (trailSize == 0) {
+ return false;
+ }
+
+ final int[] eventTimes = mEventTimes.getPrimitiveArray();
+ final int[] xCoords = mXCoordinates.getPrimitiveArray();
+ final int[] yCoords = mYCoordinates.getPrimitiveArray();
+ final int sinceDown = (int)(SystemClock.uptimeMillis() - mCurrentDownTime);
+ final int lingeringDuration = mPreviewParams.mFadeoutStartDelay
+ + mPreviewParams.mFadeoutDuration;
+ int startIndex;
+ for (startIndex = mTrailStartIndex; startIndex < trailSize; startIndex++) {
+ final int elapsedTime = sinceDown - eventTimes[startIndex];
+ // Skip too old trail points.
+ if (elapsedTime < lingeringDuration) {
+ break;
+ }
+ }
+ mTrailStartIndex = startIndex;
+
+ if (startIndex < trailSize) {
+ int lastX = getXCoordValue(xCoords[startIndex]);
+ int lastY = yCoords[startIndex];
+ for (int i = startIndex + 1; i < trailSize - 1; i++) {
+ final int x = xCoords[i];
+ final int y = yCoords[i];
+ final int elapsedTime = sinceDown - eventTimes[i];
+ // Draw trail line only when the current point isn't a down point.
+ if (!isDownEventXCoord(x)) {
+ paint.setAlpha(getAlpha(elapsedTime));
+ canvas.drawLine(lastX, lastY, x, y, paint);
+ }
+ lastX = getXCoordValue(x);
+ lastY = y;
+ }
+ }
+
+ final int newSize = trailSize - startIndex;
+ if (newSize < startIndex) {
+ mTrailStartIndex = 0;
+ if (newSize > 0) {
+ System.arraycopy(eventTimes, startIndex, eventTimes, 0, newSize);
+ System.arraycopy(xCoords, startIndex, xCoords, 0, newSize);
+ System.arraycopy(yCoords, startIndex, yCoords, 0, newSize);
+ }
+ mEventTimes.setLength(newSize);
+ mXCoordinates.setLength(newSize);
+ mYCoordinates.setLength(newSize);
+ }
+ return newSize > 0;
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
index 79e977a..8251344 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
@@ -14,10 +14,6 @@
package com.android.inputmethod.keyboard.internal;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-
-import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.InputPointers;
import com.android.inputmethod.latin.ResizableIntArray;
@@ -48,13 +44,8 @@
private static final float DOUBLE_PI = (float)(2.0f * Math.PI);
- // Fade based on number of gesture samples, see MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT
- private static final int DRAWING_GESTURE_FADE_START = 10;
- private static final int DRAWING_GESTURE_FADE_RATE = 6;
-
- public GestureStroke(int pointerId) {
+ public GestureStroke(final int pointerId) {
mPointerId = pointerId;
- reset();
}
public void setGestureSampleLength(final int keyWidth) {
@@ -139,8 +130,12 @@
}
private void appendBatchPoints(final InputPointers out, final int size) {
+ final int length = size - mLastIncrementalBatchSize;
+ if (length <= 0) {
+ return;
+ }
out.append(mPointerId, mEventTimes, mXCoordinates, mYCoordinates,
- mLastIncrementalBatchSize, size - mLastIncrementalBatchSize);
+ mLastIncrementalBatchSize, length);
mLastIncrementalBatchSize = size;
}
@@ -158,7 +153,7 @@
if (dx == 0 && dy == 0) return 0;
// Would it be faster to call atan2f() directly via JNI? Not sure about what the JIT
// does with Math.atan2().
- return (float)Math.atan2((double)dy, (double)dx);
+ return (float)Math.atan2(dy, dx);
}
private static float getAngleDiff(final float a1, final float a2) {
@@ -168,20 +163,4 @@
}
return diff;
}
-
- public void drawGestureTrail(final Canvas canvas, final Paint paint) {
- // TODO: These paint parameter interpolation should be tunable, possibly introduce an object
- // that implements an interface such as Paint getPaint(int step, int strokePoints)
- final int size = mXCoordinates.getLength();
- final int[] xCoords = mXCoordinates.getPrimitiveArray();
- final int[] yCoords = mYCoordinates.getPrimitiveArray();
- int alpha = Constants.Color.ALPHA_OPAQUE;
- for (int i = size - 1; i > 0 && alpha > 0; i--) {
- paint.setAlpha(alpha);
- if (size - i > DRAWING_GESTURE_FADE_START) {
- alpha -= DRAWING_GESTURE_FADE_RATE;
- }
- canvas.drawLine(xCoords[i - 1], yCoords[i - 1], xCoords[i], yCoords[i], paint);
- }
- }
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewTrail.java
new file mode 100644
index 0000000..6c1a9bc
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewTrail.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import com.android.inputmethod.latin.ResizableIntArray;
+
+public class GestureStrokeWithPreviewTrail extends GestureStroke {
+ public static final int PREVIEW_CAPACITY = 256;
+
+ private final ResizableIntArray mPreviewEventTimes = new ResizableIntArray(PREVIEW_CAPACITY);
+ private final ResizableIntArray mPreviewXCoordinates = new ResizableIntArray(PREVIEW_CAPACITY);
+ private final ResizableIntArray mPreviewYCoordinates = new ResizableIntArray(PREVIEW_CAPACITY);
+
+ private int mStrokeId;
+ private int mLastPreviewSize;
+
+ public GestureStrokeWithPreviewTrail(final int pointerId) {
+ super(pointerId);
+ }
+
+ @Override
+ public void reset() {
+ super.reset();
+ mStrokeId++;
+ mLastPreviewSize = 0;
+ mPreviewEventTimes.setLength(0);
+ mPreviewXCoordinates.setLength(0);
+ mPreviewYCoordinates.setLength(0);
+ }
+
+ public int getGestureStrokeId() {
+ return mStrokeId;
+ }
+
+ public int getGestureStrokePreviewSize() {
+ return mPreviewEventTimes.getLength();
+ }
+
+ @Override
+ public void addPoint(final int x, final int y, final int time, final boolean isHistorical) {
+ super.addPoint(x, y, time, isHistorical);
+ mPreviewEventTimes.add(time);
+ mPreviewXCoordinates.add(x);
+ mPreviewYCoordinates.add(y);
+ }
+
+ public void appendPreviewStroke(final ResizableIntArray eventTimes,
+ final ResizableIntArray xCoords, final ResizableIntArray yCoords) {
+ final int length = mPreviewEventTimes.getLength() - mLastPreviewSize;
+ if (length <= 0) {
+ return;
+ }
+ eventTimes.append(mPreviewEventTimes, mLastPreviewSize, length);
+ xCoords.append(mPreviewXCoordinates, mLastPreviewSize, length);
+ yCoords.append(mPreviewYCoordinates, mLastPreviewSize, length);
+ mLastPreviewSize = mPreviewEventTimes.getLength();
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java
new file mode 100644
index 0000000..203bab6
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.graphics.Typeface;
+
+import com.android.inputmethod.latin.ResourceUtils;
+
+public class KeyDrawParams {
+ public Typeface mTypeface;
+
+ public int mLetterSize;
+ public int mLabelSize;
+ public int mLargeLetterSize;
+ public int mLargeLabelSize;
+ public int mHintLetterSize;
+ public int mShiftedLetterHintSize;
+ public int mHintLabelSize;
+ public int mPreviewTextSize;
+
+ public int mTextColor;
+ public int mTextInactivatedColor;
+ public int mTextShadowColor;
+ public int mHintLetterColor;
+ public int mHintLabelColor;
+ public int mShiftedLetterHintInactivatedColor;
+ public int mShiftedLetterHintActivatedColor;
+ public int mPreviewTextColor;
+
+ public int mAnimAlpha;
+
+ public KeyDrawParams() {}
+
+ private KeyDrawParams(final KeyDrawParams copyFrom) {
+ mTypeface = copyFrom.mTypeface;
+
+ mLetterSize = copyFrom.mLetterSize;
+ mLabelSize = copyFrom.mLabelSize;
+ mLargeLetterSize = copyFrom.mLargeLetterSize;
+ mLargeLabelSize = copyFrom.mLargeLabelSize;
+ mHintLetterSize = copyFrom.mHintLetterSize;
+ mShiftedLetterHintSize = copyFrom.mShiftedLetterHintSize;
+ mHintLabelSize = copyFrom.mHintLabelSize;
+ mPreviewTextSize = copyFrom.mPreviewTextSize;
+
+ mTextColor = copyFrom.mTextColor;
+ mTextInactivatedColor = copyFrom.mTextInactivatedColor;
+ mTextShadowColor = copyFrom.mTextShadowColor;
+ mHintLetterColor = copyFrom.mHintLetterColor;
+ mHintLabelColor = copyFrom.mHintLabelColor;
+ mShiftedLetterHintInactivatedColor = copyFrom.mShiftedLetterHintInactivatedColor;
+ mShiftedLetterHintActivatedColor = copyFrom.mShiftedLetterHintActivatedColor;
+ mPreviewTextColor = copyFrom.mPreviewTextColor;
+
+ mAnimAlpha = copyFrom.mAnimAlpha;
+ }
+
+ public void updateParams(final int keyHeight, final KeyVisualAttributes attr) {
+ if (attr == null) {
+ return;
+ }
+
+ if (attr.mTypeface != null) {
+ mTypeface = attr.mTypeface;
+ }
+
+ mLetterSize = selectTextSizeFromDimensionOrRatio(keyHeight,
+ attr.mLetterSize, attr.mLetterRatio, mLetterSize);
+ mLabelSize = selectTextSizeFromDimensionOrRatio(keyHeight,
+ attr.mLabelSize, attr.mLabelRatio, mLabelSize);
+ mLargeLabelSize = selectTextSize(keyHeight, attr.mLargeLabelRatio, mLargeLabelSize);
+ mLargeLetterSize = selectTextSize(keyHeight, attr.mLargeLetterRatio, mLargeLetterSize);
+ mHintLetterSize = selectTextSize(keyHeight, attr.mHintLetterRatio, mHintLetterSize);
+ mShiftedLetterHintSize = selectTextSize(keyHeight,
+ attr.mShiftedLetterHintRatio, mShiftedLetterHintSize);
+ mHintLabelSize = selectTextSize(keyHeight, attr.mHintLabelRatio, mHintLabelSize);
+ mPreviewTextSize = selectTextSize(keyHeight, attr.mPreviewTextRatio, mPreviewTextSize);
+
+ mTextColor = selectColor(attr.mTextColor, mTextColor);
+ mTextInactivatedColor = selectColor(attr.mTextInactivatedColor, mTextInactivatedColor);
+ mTextShadowColor = selectColor(attr.mTextShadowColor, mTextShadowColor);
+ mHintLetterColor = selectColor(attr.mHintLetterColor, mHintLetterColor);
+ mHintLabelColor = selectColor(attr.mHintLabelColor, mHintLabelColor);
+ mShiftedLetterHintInactivatedColor = selectColor(
+ attr.mShiftedLetterHintInactivatedColor, mShiftedLetterHintInactivatedColor);
+ mShiftedLetterHintActivatedColor = selectColor(
+ attr.mShiftedLetterHintActivatedColor, mShiftedLetterHintActivatedColor);
+ mPreviewTextColor = selectColor(attr.mPreviewTextColor, mPreviewTextColor);
+ }
+
+ public KeyDrawParams mayCloneAndUpdateParams(final int keyHeight,
+ final KeyVisualAttributes attr) {
+ if (attr == null) {
+ return this;
+ }
+ final KeyDrawParams newParams = new KeyDrawParams(this);
+ newParams.updateParams(keyHeight, attr);
+ return newParams;
+ }
+
+ private static final int selectTextSizeFromDimensionOrRatio(final int keyHeight,
+ final int dimens, final float ratio, final int defaultDimens) {
+ if (ResourceUtils.isValidDimensionPixelSize(dimens)) {
+ return dimens;
+ }
+ if (ResourceUtils.isValidFraction(ratio)) {
+ return (int)(keyHeight * ratio);
+ }
+ return defaultDimens;
+ }
+
+ private static final int selectTextSize(final int keyHeight, final float ratio,
+ final int defaultSize) {
+ if (ResourceUtils.isValidFraction(ratio)) {
+ return (int)(keyHeight * ratio);
+ }
+ return defaultSize;
+ }
+
+ private static final int selectColor(final int attrColor, final int defaultColor) {
+ if (attrColor != 0) {
+ return attrColor;
+ }
+ return defaultColor;
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java
new file mode 100644
index 0000000..996a722
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+public class KeyPreviewDrawParams {
+ // The graphical geometry of the key preview.
+ // <-width->
+ // +-------+ ^
+ // | | |
+ // |preview| height (visible)
+ // | | |
+ // + + ^ v
+ // \ / |offset
+ // +-\ /-+ v
+ // | +-+ |
+ // |parent |
+ // | key|
+ // +-------+
+ // The background of a {@link TextView} being used for a key preview may have invisible
+ // paddings. To align the more keys keyboard panel's visible part with the visible part of
+ // the background, we need to record the width and height of key preview that don't include
+ // invisible paddings.
+ public int mPreviewVisibleWidth;
+ public int mPreviewVisibleHeight;
+ // The key preview may have an arbitrary offset and its background that may have a bottom
+ // padding. To align the more keys keyboard and the key preview we also need to record the
+ // offset between the top edge of parent key and the bottom of the visible part of key
+ // preview background.
+ public int mPreviewVisibleOffset;
+
+ public final int[] mCoordinates = new int[2];
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
index 94a7b82..2a57caa 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
@@ -21,6 +21,7 @@
import android.text.TextUtils;
import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.StringUtils;
@@ -55,59 +56,20 @@
private static final char ESCAPE_CHAR = '\\';
private static final char LABEL_END = '|';
private static final String PREFIX_TEXT = "!text/";
- private static final String PREFIX_ICON = "!icon/";
+ static final String PREFIX_ICON = "!icon/";
private static final String PREFIX_CODE = "!code/";
private static final String PREFIX_HEX = "0x";
private static final String ADDITIONAL_MORE_KEY_MARKER = "%";
- public static class MoreKeySpec {
- public final int mCode;
- public final String mLabel;
- public final String mOutputText;
- public final int mIconId;
-
- public MoreKeySpec(final String moreKeySpec, boolean needsToUpperCase, Locale locale,
- final KeyboardCodesSet codesSet) {
- mLabel = toUpperCaseOfStringForLocale(getLabel(moreKeySpec),
- needsToUpperCase, locale);
- final int code = toUpperCaseOfCodeForLocale(getCode(moreKeySpec, codesSet),
- needsToUpperCase, locale);
- if (code == Keyboard.CODE_UNSPECIFIED) {
- // Some letter, for example German Eszett (U+00DF: "ß"), has multiple characters
- // upper case representation ("SS").
- mCode = Keyboard.CODE_OUTPUT_TEXT;
- mOutputText = mLabel;
- } else {
- mCode = code;
- mOutputText = toUpperCaseOfStringForLocale(getOutputText(moreKeySpec),
- needsToUpperCase, locale);
- }
- mIconId = getIconId(moreKeySpec);
- }
-
- @Override
- public String toString() {
- final String label = (mIconId == KeyboardIconsSet.ICON_UNDEFINED ? mLabel
- : PREFIX_ICON + KeyboardIconsSet.getIconName(mIconId));
- final String output = (mCode == Keyboard.CODE_OUTPUT_TEXT ? mOutputText
- : Keyboard.printableCode(mCode));
- if (StringUtils.codePointCount(label) == 1 && label.codePointAt(0) == mCode) {
- return output;
- } else {
- return label + "|" + output;
- }
- }
- }
-
private KeySpecParser() {
// Intentional empty constructor for utility class.
}
- private static boolean hasIcon(String moreKeySpec) {
+ private static boolean hasIcon(final String moreKeySpec) {
return moreKeySpec.startsWith(PREFIX_ICON);
}
- private static boolean hasCode(String moreKeySpec) {
+ private static boolean hasCode(final String moreKeySpec) {
final int end = indexOfLabelEnd(moreKeySpec, 0);
if (end > 0 && end + 1 < moreKeySpec.length() && moreKeySpec.startsWith(
PREFIX_CODE, end + 1)) {
@@ -116,7 +78,7 @@
return false;
}
- private static String parseEscape(String text) {
+ private static String parseEscape(final String text) {
if (text.indexOf(ESCAPE_CHAR) < 0) {
return text;
}
@@ -135,7 +97,7 @@
return sb.toString();
}
- private static int indexOfLabelEnd(String moreKeySpec, int start) {
+ private static int indexOfLabelEnd(final String moreKeySpec, final int start) {
if (moreKeySpec.indexOf(ESCAPE_CHAR, start) < 0) {
final int end = moreKeySpec.indexOf(LABEL_END, start);
if (end == 0) {
@@ -156,7 +118,7 @@
return -1;
}
- public static String getLabel(String moreKeySpec) {
+ public static String getLabel(final String moreKeySpec) {
if (hasIcon(moreKeySpec)) {
return null;
}
@@ -169,7 +131,7 @@
return label;
}
- private static String getOutputTextInternal(String moreKeySpec) {
+ private static String getOutputTextInternal(final String moreKeySpec) {
final int end = indexOfLabelEnd(moreKeySpec, 0);
if (end <= 0) {
return null;
@@ -180,7 +142,7 @@
return parseEscape(moreKeySpec.substring(end + /* LABEL_END */1));
}
- static String getOutputText(String moreKeySpec) {
+ static String getOutputText(final String moreKeySpec) {
if (hasCode(moreKeySpec)) {
return null;
}
@@ -204,7 +166,7 @@
return (StringUtils.codePointCount(label) == 1) ? null : label;
}
- static int getCode(String moreKeySpec, KeyboardCodesSet codesSet) {
+ static int getCode(final String moreKeySpec, final KeyboardCodesSet codesSet) {
if (hasCode(moreKeySpec)) {
final int end = indexOfLabelEnd(moreKeySpec, 0);
if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) {
@@ -229,7 +191,8 @@
return Keyboard.CODE_OUTPUT_TEXT;
}
- public static int parseCode(String text, KeyboardCodesSet codesSet, int defCode) {
+ public static int parseCode(final String text, final KeyboardCodesSet codesSet,
+ final int defCode) {
if (text == null) return defCode;
if (text.startsWith(PREFIX_CODE)) {
return codesSet.getCode(text.substring(PREFIX_CODE.length()));
@@ -240,7 +203,7 @@
}
}
- public static int getIconId(String moreKeySpec) {
+ public static int getIconId(final String moreKeySpec) {
if (moreKeySpec != null && hasIcon(moreKeySpec)) {
final int end = moreKeySpec.indexOf(LABEL_END, PREFIX_ICON.length());
final String name = (end < 0) ? moreKeySpec.substring(PREFIX_ICON.length())
@@ -250,7 +213,7 @@
return KeyboardIconsSet.ICON_UNDEFINED;
}
- private static <T> ArrayList<T> arrayAsList(T[] array, int start, int end) {
+ private static <T> ArrayList<T> arrayAsList(final T[] array, final int start, final int end) {
if (array == null) {
throw new NullPointerException();
}
@@ -258,7 +221,7 @@
throw new IllegalArgumentException();
}
- final ArrayList<T> list = new ArrayList<T>(end - start);
+ final ArrayList<T> list = CollectionUtils.newArrayList(end - start);
for (int i = start; i < end; i++) {
list.add(array[i]);
}
@@ -267,7 +230,7 @@
private static final String[] EMPTY_STRING_ARRAY = new String[0];
- private static String[] filterOutEmptyString(String[] array) {
+ private static String[] filterOutEmptyString(final String[] array) {
if (array == null) {
return EMPTY_STRING_ARRAY;
}
@@ -288,8 +251,8 @@
return out.toArray(new String[out.size()]);
}
- public static String[] insertAdditionalMoreKeys(String[] moreKeySpecs,
- String[] additionalMoreKeySpecs) {
+ public static String[] insertAdditionalMoreKeys(final String[] moreKeySpecs,
+ final String[] additionalMoreKeySpecs) {
final String[] moreKeys = filterOutEmptyString(moreKeySpecs);
final String[] additionalMoreKeys = filterOutEmptyString(additionalMoreKeySpecs);
final int moreKeysCount = moreKeys.length;
@@ -356,12 +319,13 @@
@SuppressWarnings("serial")
public static class KeySpecParserError extends RuntimeException {
- public KeySpecParserError(String message) {
+ public KeySpecParserError(final String message) {
super(message);
}
}
- public static String resolveTextReference(String rawText, KeyboardTextsSet textsSet) {
+ public static String resolveTextReference(final String rawText,
+ final KeyboardTextsSet textsSet) {
int level = 0;
String text = rawText;
StringBuilder sb;
@@ -407,7 +371,7 @@
return text;
}
- private static int searchTextNameEnd(String text, int start) {
+ private static int searchTextNameEnd(final String text, final int start) {
final int size = text.length();
for (int pos = start; pos < size; pos++) {
final char c = text.charAt(pos);
@@ -420,7 +384,7 @@
return size;
}
- public static String[] parseCsvString(String rawText, KeyboardTextsSet textsSet) {
+ public static String[] parseCsvString(final String rawText, final KeyboardTextsSet textsSet) {
final String text = resolveTextReference(rawText, textsSet);
final int size = text.length();
if (size == 0) {
@@ -438,7 +402,7 @@
// Skip empty entry.
if (pos - start > 0) {
if (list == null) {
- list = new ArrayList<String>();
+ list = CollectionUtils.newArrayList();
}
list.add(text.substring(start, pos));
}
@@ -459,7 +423,8 @@
return list.toArray(new String[list.size()]);
}
- public static int getIntValue(String[] moreKeys, String key, int defaultValue) {
+ public static int getIntValue(final String[] moreKeys, final String key,
+ final int defaultValue) {
if (moreKeys == null) {
return defaultValue;
}
@@ -485,7 +450,7 @@
return value;
}
- public static boolean getBooleanValue(String[] moreKeys, String key) {
+ public static boolean getBooleanValue(final String[] moreKeys, final String key) {
if (moreKeys == null) {
return false;
}
@@ -501,8 +466,8 @@
return value;
}
- public static int toUpperCaseOfCodeForLocale(int code, boolean needsToUpperCase,
- Locale locale) {
+ public static int toUpperCaseOfCodeForLocale(final int code, final boolean needsToUpperCase,
+ final Locale locale) {
if (!Keyboard.isLetterCode(code) || !needsToUpperCase) return code;
final String text = new String(new int[] { code } , 0, 1);
final String casedText = KeySpecParser.toUpperCaseOfStringForLocale(
@@ -511,8 +476,8 @@
? casedText.codePointAt(0) : CODE_UNSPECIFIED;
}
- public static String toUpperCaseOfStringForLocale(String text, boolean needsToUpperCase,
- Locale locale) {
+ public static String toUpperCaseOfStringForLocale(final String text,
+ final boolean needsToUpperCase, final Locale locale) {
if (text == null || !needsToUpperCase) return text;
return text.toUpperCase(locale);
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java
new file mode 100644
index 0000000..e8cacf9
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.content.res.TypedArray;
+
+public abstract class KeyStyle {
+ private final KeyboardTextsSet mTextsSet;
+
+ public abstract String[] getStringArray(TypedArray a, int index);
+ public abstract String getString(TypedArray a, int index);
+ public abstract int getInt(TypedArray a, int index, int defaultValue);
+ public abstract int getFlag(TypedArray a, int index);
+
+ protected KeyStyle(final KeyboardTextsSet textsSet) {
+ mTextsSet = textsSet;
+ }
+
+ protected String parseString(final TypedArray a, final int index) {
+ if (a.hasValue(index)) {
+ return KeySpecParser.resolveTextReference(a.getString(index), mTextsSet);
+ }
+ return null;
+ }
+
+ protected String[] parseStringArray(final TypedArray a, final int index) {
+ if (a.hasValue(index)) {
+ return KeySpecParser.parseCsvString(a.getString(index), mTextsSet);
+ }
+ return null;
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
similarity index 71%
rename from java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
rename to java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
index 291b3b9..71fd305 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
@@ -20,7 +20,7 @@
import android.util.Log;
import android.util.SparseArray;
-import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.XmlParseUtils;
@@ -29,75 +29,62 @@
import java.util.HashMap;
-public class KeyStyles {
- private static final String TAG = KeyStyles.class.getSimpleName();
+public class KeyStylesSet {
+ private static final String TAG = KeyStylesSet.class.getSimpleName();
private static final boolean DEBUG = false;
- final HashMap<String, KeyStyle> mStyles = new HashMap<String, KeyStyle>();
+ private final HashMap<String, KeyStyle> mStyles = CollectionUtils.newHashMap();
- final KeyboardTextsSet mTextsSet;
+ private final KeyboardTextsSet mTextsSet;
private final KeyStyle mEmptyKeyStyle;
private static final String EMPTY_STYLE_NAME = "<empty>";
- public KeyStyles(KeyboardTextsSet textsSet) {
+ public KeyStylesSet(final KeyboardTextsSet textsSet) {
mTextsSet = textsSet;
- mEmptyKeyStyle = new EmptyKeyStyle();
+ mEmptyKeyStyle = new EmptyKeyStyle(textsSet);
mStyles.put(EMPTY_STYLE_NAME, mEmptyKeyStyle);
}
- public abstract class KeyStyle {
- public abstract String[] getStringArray(TypedArray a, int index);
- public abstract String getString(TypedArray a, int index);
- public abstract int getInt(TypedArray a, int index, int defaultValue);
- public abstract int getFlag(TypedArray a, int index);
-
- protected String parseString(TypedArray a, int index) {
- if (a.hasValue(index)) {
- return KeySpecParser.resolveTextReference(a.getString(index), mTextsSet);
- }
- return null;
+ private static class EmptyKeyStyle extends KeyStyle {
+ EmptyKeyStyle(final KeyboardTextsSet textsSet) {
+ super(textsSet);
}
- protected String[] parseStringArray(TypedArray a, int index) {
- if (a.hasValue(index)) {
- return KeySpecParser.parseCsvString(a.getString(index), mTextsSet);
- }
- return null;
- }
- }
-
- class EmptyKeyStyle extends KeyStyle {
@Override
- public String[] getStringArray(TypedArray a, int index) {
+ public String[] getStringArray(final TypedArray a, final int index) {
return parseStringArray(a, index);
}
@Override
- public String getString(TypedArray a, int index) {
+ public String getString(final TypedArray a, final int index) {
return parseString(a, index);
}
@Override
- public int getInt(TypedArray a, int index, int defaultValue) {
+ public int getInt(final TypedArray a, final int index, final int defaultValue) {
return a.getInt(index, defaultValue);
}
@Override
- public int getFlag(TypedArray a, int index) {
+ public int getFlag(final TypedArray a, final int index) {
return a.getInt(index, 0);
}
}
- private class DeclaredKeyStyle extends KeyStyle {
+ private static class DeclaredKeyStyle extends KeyStyle {
+ private final HashMap<String, KeyStyle> mStyles;
private final String mParentStyleName;
- private final SparseArray<Object> mStyleAttributes = new SparseArray<Object>();
+ private final SparseArray<Object> mStyleAttributes = CollectionUtils.newSparseArray();
- public DeclaredKeyStyle(String parentStyleName) {
+ public DeclaredKeyStyle(final String parentStyleName, final KeyboardTextsSet textsSet,
+ final HashMap<String, KeyStyle> styles) {
+ super(textsSet);
mParentStyleName = parentStyleName;
+ mStyles = styles;
}
@Override
- public String[] getStringArray(TypedArray a, int index) {
+ public String[] getStringArray(final TypedArray a, final int index) {
if (a.hasValue(index)) {
return parseStringArray(a, index);
}
@@ -110,7 +97,7 @@
}
@Override
- public String getString(TypedArray a, int index) {
+ public String getString(final TypedArray a, final int index) {
if (a.hasValue(index)) {
return parseString(a, index);
}
@@ -123,7 +110,7 @@
}
@Override
- public int getInt(TypedArray a, int index, int defaultValue) {
+ public int getInt(final TypedArray a, final int index, final int defaultValue) {
if (a.hasValue(index)) {
return a.getInt(index, defaultValue);
}
@@ -136,7 +123,7 @@
}
@Override
- public int getFlag(TypedArray a, int index) {
+ public int getFlag(final TypedArray a, final int index) {
int flags = a.getInt(index, 0);
final Object value = mStyleAttributes.get(index);
if (value != null) {
@@ -146,7 +133,7 @@
return flags | parentStyle.getFlag(a, index);
}
- void readKeyAttributes(TypedArray keyAttr) {
+ public void readKeyAttributes(final TypedArray keyAttr) {
// TODO: Currently not all Key attributes can be declared as style.
readString(keyAttr, R.styleable.Keyboard_Key_code);
readString(keyAttr, R.styleable.Keyboard_Key_altCode);
@@ -164,38 +151,38 @@
readFlag(keyAttr, R.styleable.Keyboard_Key_keyActionFlags);
}
- private void readString(TypedArray a, int index) {
+ private void readString(final TypedArray a, final int index) {
if (a.hasValue(index)) {
mStyleAttributes.put(index, parseString(a, index));
}
}
- private void readInt(TypedArray a, int index) {
+ private void readInt(final TypedArray a, final int index) {
if (a.hasValue(index)) {
mStyleAttributes.put(index, a.getInt(index, 0));
}
}
- private void readFlag(TypedArray a, int index) {
+ private void readFlag(final TypedArray a, final int index) {
if (a.hasValue(index)) {
final Integer value = (Integer)mStyleAttributes.get(index);
mStyleAttributes.put(index, a.getInt(index, 0) | (value != null ? value : 0));
}
}
- private void readStringArray(TypedArray a, int index) {
+ private void readStringArray(final TypedArray a, final int index) {
if (a.hasValue(index)) {
mStyleAttributes.put(index, parseStringArray(a, index));
}
}
}
- public void parseKeyStyleAttributes(TypedArray keyStyleAttr, TypedArray keyAttrs,
- XmlPullParser parser) throws XmlPullParserException {
+ public void parseKeyStyleAttributes(final TypedArray keyStyleAttr, final TypedArray keyAttrs,
+ final XmlPullParser parser) throws XmlPullParserException {
final String styleName = keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName);
if (DEBUG) {
Log.d(TAG, String.format("<%s styleName=%s />",
- Keyboard.Builder.TAG_KEY_STYLE, styleName));
+ KeyboardBuilder.TAG_KEY_STYLE, styleName));
if (mStyles.containsKey(styleName)) {
Log.d(TAG, "key-style " + styleName + " is overridden at "
+ parser.getPositionDescription());
@@ -210,12 +197,12 @@
"Unknown parentStyle " + parentStyleName, parser);
}
}
- final DeclaredKeyStyle style = new DeclaredKeyStyle(parentStyleName);
+ final DeclaredKeyStyle style = new DeclaredKeyStyle(parentStyleName, mTextsSet, mStyles);
style.readKeyAttributes(keyAttrs);
mStyles.put(styleName, style);
}
- public KeyStyle getKeyStyle(TypedArray keyAttr, XmlPullParser parser)
+ public KeyStyle getKeyStyle(final TypedArray keyAttr, final XmlPullParser parser)
throws XmlParseUtils.ParseException {
if (!keyAttr.hasValue(R.styleable.Keyboard_Key_keyStyle)) {
return mEmptyKeyStyle;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java b/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java
new file mode 100644
index 0000000..04cc152
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.content.res.TypedArray;
+import android.graphics.Typeface;
+import android.util.SparseIntArray;
+
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.ResourceUtils;
+
+public class KeyVisualAttributes {
+ public final Typeface mTypeface;
+
+ public final float mLetterRatio;
+ public final int mLetterSize;
+ public final float mLabelRatio;
+ public final int mLabelSize;
+ public final float mLargeLetterRatio;
+ public final float mLargeLabelRatio;
+ public final float mHintLetterRatio;
+ public final float mShiftedLetterHintRatio;
+ public final float mHintLabelRatio;
+ public final float mPreviewTextRatio;
+
+ public final int mTextColor;
+ public final int mTextInactivatedColor;
+ public final int mTextShadowColor;
+ public final int mHintLetterColor;
+ public final int mHintLabelColor;
+ public final int mShiftedLetterHintInactivatedColor;
+ public final int mShiftedLetterHintActivatedColor;
+ public final int mPreviewTextColor;
+
+ private static final int[] VISUAL_ATTRIBUTE_IDS = {
+ R.styleable.Keyboard_Key_keyTypeface,
+ R.styleable.Keyboard_Key_keyLetterSize,
+ R.styleable.Keyboard_Key_keyLabelSize,
+ R.styleable.Keyboard_Key_keyLargeLetterRatio,
+ R.styleable.Keyboard_Key_keyLargeLabelRatio,
+ R.styleable.Keyboard_Key_keyHintLetterRatio,
+ R.styleable.Keyboard_Key_keyShiftedLetterHintRatio,
+ R.styleable.Keyboard_Key_keyHintLabelRatio,
+ R.styleable.Keyboard_Key_keyPreviewTextRatio,
+ R.styleable.Keyboard_Key_keyTextColor,
+ R.styleable.Keyboard_Key_keyTextInactivatedColor,
+ R.styleable.Keyboard_Key_keyTextShadowColor,
+ R.styleable.Keyboard_Key_keyHintLetterColor,
+ R.styleable.Keyboard_Key_keyHintLabelColor,
+ R.styleable.Keyboard_Key_keyShiftedLetterHintInactivatedColor,
+ R.styleable.Keyboard_Key_keyShiftedLetterHintActivatedColor,
+ R.styleable.Keyboard_Key_keyPreviewTextColor,
+ };
+ private static final SparseIntArray sVisualAttributeIds = new SparseIntArray();
+ private static final int ATTR_DEFINED = 1;
+ private static final int ATTR_NOT_FOUND = 0;
+ static {
+ for (final int attrId : VISUAL_ATTRIBUTE_IDS) {
+ sVisualAttributeIds.put(attrId, ATTR_DEFINED);
+ }
+ }
+
+ public static KeyVisualAttributes newInstance(final TypedArray keyAttr) {
+ final int indexCount = keyAttr.getIndexCount();
+ for (int i = 0; i < indexCount; i++) {
+ final int attrId = keyAttr.getIndex(i);
+ if (sVisualAttributeIds.get(attrId, ATTR_NOT_FOUND) == ATTR_NOT_FOUND) {
+ continue;
+ }
+ return new KeyVisualAttributes(keyAttr);
+ }
+ return null;
+ }
+
+ private KeyVisualAttributes(final TypedArray keyAttr) {
+ if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyTypeface)) {
+ mTypeface = Typeface.defaultFromStyle(
+ keyAttr.getInt(R.styleable.Keyboard_Key_keyTypeface, Typeface.NORMAL));
+ } else {
+ mTypeface = null;
+ }
+
+ mLetterRatio = ResourceUtils.getFraction(keyAttr,
+ R.styleable.Keyboard_Key_keyLetterSize);
+ mLetterSize = ResourceUtils.getDimensionPixelSize(keyAttr,
+ R.styleable.Keyboard_Key_keyLetterSize);
+ mLabelRatio = ResourceUtils.getFraction(keyAttr,
+ R.styleable.Keyboard_Key_keyLabelSize);
+ mLabelSize = ResourceUtils.getDimensionPixelSize(keyAttr,
+ R.styleable.Keyboard_Key_keyLabelSize);
+ mLargeLetterRatio = ResourceUtils.getFraction(keyAttr,
+ R.styleable.Keyboard_Key_keyLargeLetterRatio);
+ mLargeLabelRatio = ResourceUtils.getFraction(keyAttr,
+ R.styleable.Keyboard_Key_keyLargeLabelRatio);
+ mHintLetterRatio = ResourceUtils.getFraction(keyAttr,
+ R.styleable.Keyboard_Key_keyHintLetterRatio);
+ mShiftedLetterHintRatio = ResourceUtils.getFraction(keyAttr,
+ R.styleable.Keyboard_Key_keyShiftedLetterHintRatio);
+ mHintLabelRatio = ResourceUtils.getFraction(keyAttr,
+ R.styleable.Keyboard_Key_keyHintLabelRatio);
+ mPreviewTextRatio = ResourceUtils.getFraction(keyAttr,
+ R.styleable.Keyboard_Key_keyPreviewTextRatio);
+
+ mTextColor = keyAttr.getColor(R.styleable.Keyboard_Key_keyTextColor, 0);
+ mTextInactivatedColor = keyAttr.getColor(
+ R.styleable.Keyboard_Key_keyTextInactivatedColor, 0);
+ mTextShadowColor = keyAttr.getColor(R.styleable.Keyboard_Key_keyTextShadowColor, 0);
+ mHintLetterColor = keyAttr.getColor(R.styleable.Keyboard_Key_keyHintLetterColor, 0);
+ mHintLabelColor = keyAttr.getColor(R.styleable.Keyboard_Key_keyHintLabelColor, 0);
+ mShiftedLetterHintInactivatedColor = keyAttr.getColor(
+ R.styleable.Keyboard_Key_keyShiftedLetterHintInactivatedColor, 0);
+ mShiftedLetterHintActivatedColor = keyAttr.getColor(
+ R.styleable.Keyboard_Key_keyShiftedLetterHintActivatedColor, 0);
+ mPreviewTextColor = keyAttr.getColor(R.styleable.Keyboard_Key_keyPreviewTextColor, 0);
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
new file mode 100644
index 0000000..31c7cb5
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
@@ -0,0 +1,814 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+import android.util.Xml;
+import android.view.InflateException;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardId;
+import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.ResourceUtils;
+import com.android.inputmethod.latin.StringUtils;
+import com.android.inputmethod.latin.SubtypeLocale;
+import com.android.inputmethod.latin.XmlParseUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Locale;
+
+/**
+ * Keyboard Building helper.
+ *
+ * This class parses Keyboard XML file and eventually build a Keyboard.
+ * The Keyboard XML file looks like:
+ * <pre>
+ * <!-- xml/keyboard.xml -->
+ * <Keyboard keyboard_attributes*>
+ * <!-- Keyboard Content -->
+ * <Row row_attributes*>
+ * <!-- Row Content -->
+ * <Key key_attributes* />
+ * <Spacer horizontalGap="32.0dp" />
+ * <include keyboardLayout="@xml/other_keys">
+ * ...
+ * </Row>
+ * <include keyboardLayout="@xml/other_rows">
+ * ...
+ * </Keyboard>
+ * </pre>
+ * The XML file which is included in other file must have <merge> as root element,
+ * such as:
+ * <pre>
+ * <!-- xml/other_keys.xml -->
+ * <merge>
+ * <Key key_attributes* />
+ * ...
+ * </merge>
+ * </pre>
+ * and
+ * <pre>
+ * <!-- xml/other_rows.xml -->
+ * <merge>
+ * <Row row_attributes*>
+ * <Key key_attributes* />
+ * </Row>
+ * ...
+ * </merge>
+ * </pre>
+ * You can also use switch-case-default tags to select Rows and Keys.
+ * <pre>
+ * <switch>
+ * <case case_attribute*>
+ * <!-- Any valid tags at switch position -->
+ * </case>
+ * ...
+ * <default>
+ * <!-- Any valid tags at switch position -->
+ * </default>
+ * </switch>
+ * </pre>
+ * You can declare Key style and specify styles within Key tags.
+ * <pre>
+ * <switch>
+ * <case mode="email">
+ * <key-style styleName="f1-key" parentStyle="modifier-key"
+ * keyLabel=".com"
+ * />
+ * </case>
+ * <case mode="url">
+ * <key-style styleName="f1-key" parentStyle="modifier-key"
+ * keyLabel="http://"
+ * />
+ * </case>
+ * </switch>
+ * ...
+ * <Key keyStyle="shift-key" ... />
+ * </pre>
+ */
+
+public class KeyboardBuilder<KP extends KeyboardParams> {
+ private static final String BUILDER_TAG = "Keyboard.Builder";
+ private static final boolean DEBUG = false;
+
+ // Keyboard XML Tags
+ private static final String TAG_KEYBOARD = "Keyboard";
+ private static final String TAG_ROW = "Row";
+ private static final String TAG_KEY = "Key";
+ private static final String TAG_SPACER = "Spacer";
+ private static final String TAG_INCLUDE = "include";
+ private static final String TAG_MERGE = "merge";
+ private static final String TAG_SWITCH = "switch";
+ private static final String TAG_CASE = "case";
+ private static final String TAG_DEFAULT = "default";
+ public static final String TAG_KEY_STYLE = "key-style";
+
+ private static final int DEFAULT_KEYBOARD_COLUMNS = 10;
+ private static final int DEFAULT_KEYBOARD_ROWS = 4;
+
+ protected final KP mParams;
+ protected final Context mContext;
+ protected final Resources mResources;
+ private final DisplayMetrics mDisplayMetrics;
+
+ private int mCurrentY = 0;
+ private KeyboardRow mCurrentRow = null;
+ private boolean mLeftEdge;
+ private boolean mTopEdge;
+ private Key mRightEdgeKey = null;
+
+ public KeyboardBuilder(final Context context, final KP params) {
+ mContext = context;
+ final Resources res = context.getResources();
+ mResources = res;
+ mDisplayMetrics = res.getDisplayMetrics();
+
+ mParams = params;
+
+ params.GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width);
+ params.GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height);
+ }
+
+ public void setAutoGenerate(final KeysCache keysCache) {
+ mParams.mKeysCache = keysCache;
+ }
+
+ public KeyboardBuilder<KP> load(final int xmlId, final KeyboardId id) {
+ mParams.mId = id;
+ final XmlResourceParser parser = mResources.getXml(xmlId);
+ try {
+ parseKeyboard(parser);
+ } catch (XmlPullParserException e) {
+ Log.w(BUILDER_TAG, "keyboard XML parse error: " + e);
+ throw new IllegalArgumentException(e);
+ } catch (IOException e) {
+ Log.w(BUILDER_TAG, "keyboard XML parse error: " + e);
+ throw new RuntimeException(e);
+ } finally {
+ parser.close();
+ }
+ return this;
+ }
+
+ // TODO: Remove this method.
+ public void setTouchPositionCorrectionEnabled(final boolean enabled) {
+ mParams.mTouchPositionCorrection.setEnabled(enabled);
+ }
+
+ public void setProximityCharsCorrectionEnabled(final boolean enabled) {
+ mParams.mProximityCharsCorrectionEnabled = enabled;
+ }
+
+ public Keyboard build() {
+ return new Keyboard(mParams);
+ }
+
+ private int mIndent;
+ private static final String SPACES = " ";
+
+ private static String spaces(final int count) {
+ return (count < SPACES.length()) ? SPACES.substring(0, count) : SPACES;
+ }
+
+ private void startTag(final String format, final Object ... args) {
+ Log.d(BUILDER_TAG, String.format(spaces(++mIndent * 2) + format, args));
+ }
+
+ private void endTag(final String format, final Object ... args) {
+ Log.d(BUILDER_TAG, String.format(spaces(mIndent-- * 2) + format, args));
+ }
+
+ private void startEndTag(final String format, final Object ... args) {
+ Log.d(BUILDER_TAG, String.format(spaces(++mIndent * 2) + format, args));
+ mIndent--;
+ }
+
+ private void parseKeyboard(final XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ if (DEBUG) startTag("<%s> %s", TAG_KEYBOARD, mParams.mId);
+ int event;
+ while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (event == XmlPullParser.START_TAG) {
+ final String tag = parser.getName();
+ if (TAG_KEYBOARD.equals(tag)) {
+ parseKeyboardAttributes(parser);
+ startKeyboard();
+ parseKeyboardContent(parser, false);
+ break;
+ } else {
+ throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEYBOARD);
+ }
+ }
+ }
+ }
+
+ private void parseKeyboardAttributes(final XmlPullParser parser) {
+ final int displayWidth = mDisplayMetrics.widthPixels;
+ final TypedArray keyboardAttr = mContext.obtainStyledAttributes(
+ Xml.asAttributeSet(parser), R.styleable.Keyboard, R.attr.keyboardStyle,
+ R.style.Keyboard);
+ final TypedArray keyAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
+ R.styleable.Keyboard_Key);
+ try {
+ final int displayHeight = mDisplayMetrics.heightPixels;
+ final String keyboardHeightString = ResourceUtils.getDeviceOverrideValue(
+ mResources, R.array.keyboard_heights, null);
+ final float keyboardHeight;
+ if (keyboardHeightString != null) {
+ keyboardHeight = Float.parseFloat(keyboardHeightString)
+ * mDisplayMetrics.density;
+ } else {
+ keyboardHeight = keyboardAttr.getDimension(
+ R.styleable.Keyboard_keyboardHeight, displayHeight / 2);
+ }
+ final float maxKeyboardHeight = ResourceUtils.getDimensionOrFraction(keyboardAttr,
+ R.styleable.Keyboard_maxKeyboardHeight, displayHeight, displayHeight / 2);
+ float minKeyboardHeight = ResourceUtils.getDimensionOrFraction(keyboardAttr,
+ R.styleable.Keyboard_minKeyboardHeight, displayHeight, displayHeight / 2);
+ if (minKeyboardHeight < 0) {
+ // Specified fraction was negative, so it should be calculated against display
+ // width.
+ minKeyboardHeight = -ResourceUtils.getDimensionOrFraction(keyboardAttr,
+ R.styleable.Keyboard_minKeyboardHeight, displayWidth, displayWidth / 2);
+ }
+ final KeyboardParams params = mParams;
+ // Keyboard height will not exceed maxKeyboardHeight and will not be less than
+ // minKeyboardHeight.
+ params.mOccupiedHeight = (int)Math.max(
+ Math.min(keyboardHeight, maxKeyboardHeight), minKeyboardHeight);
+ params.mOccupiedWidth = params.mId.mWidth;
+ params.mTopPadding = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
+ R.styleable.Keyboard_keyboardTopPadding, params.mOccupiedHeight, 0);
+ params.mBottomPadding = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
+ R.styleable.Keyboard_keyboardBottomPadding, params.mOccupiedHeight, 0);
+ params.mHorizontalEdgesPadding = (int)ResourceUtils.getDimensionOrFraction(
+ keyboardAttr,
+ R.styleable.Keyboard_keyboardHorizontalEdgesPadding,
+ mParams.mOccupiedWidth, 0);
+
+ params.mBaseWidth = params.mOccupiedWidth - params.mHorizontalEdgesPadding * 2
+ - params.mHorizontalCenterPadding;
+ params.mDefaultKeyWidth = (int)ResourceUtils.getDimensionOrFraction(keyAttr,
+ R.styleable.Keyboard_Key_keyWidth, params.mBaseWidth,
+ params.mBaseWidth / DEFAULT_KEYBOARD_COLUMNS);
+ params.mHorizontalGap = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
+ R.styleable.Keyboard_horizontalGap, params.mBaseWidth, 0);
+ params.mVerticalGap = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
+ R.styleable.Keyboard_verticalGap, params.mOccupiedHeight, 0);
+ params.mBaseHeight = params.mOccupiedHeight - params.mTopPadding
+ - params.mBottomPadding + params.mVerticalGap;
+ params.mDefaultRowHeight = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
+ R.styleable.Keyboard_rowHeight, params.mBaseHeight,
+ params.mBaseHeight / DEFAULT_KEYBOARD_ROWS);
+
+ params.mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr);
+
+ params.mMoreKeysTemplate = keyboardAttr.getResourceId(
+ R.styleable.Keyboard_moreKeysTemplate, 0);
+ params.mMaxMoreKeysKeyboardColumn = keyAttr.getInt(
+ R.styleable.Keyboard_Key_maxMoreKeysColumn, 5);
+
+ params.mThemeId = keyboardAttr.getInt(R.styleable.Keyboard_themeId, 0);
+ params.mIconsSet.loadIcons(keyboardAttr);
+ final String language = params.mId.mLocale.getLanguage();
+ params.mCodesSet.setLanguage(language);
+ params.mTextsSet.setLanguage(language);
+ final RunInLocale<Void> job = new RunInLocale<Void>() {
+ @Override
+ protected Void job(Resources res) {
+ params.mTextsSet.loadStringResources(mContext);
+ return null;
+ }
+ };
+ // Null means the current system locale.
+ final Locale locale = SubtypeLocale.isNoLanguage(params.mId.mSubtype)
+ ? null : params.mId.mLocale;
+ job.runInLocale(mResources, locale);
+
+ final int resourceId = keyboardAttr.getResourceId(
+ R.styleable.Keyboard_touchPositionCorrectionData, 0);
+ params.mTouchPositionCorrection.setEnabled(resourceId != 0);
+ if (resourceId != 0) {
+ final String[] data = mResources.getStringArray(resourceId);
+ params.mTouchPositionCorrection.load(data);
+ }
+ } finally {
+ keyAttr.recycle();
+ keyboardAttr.recycle();
+ }
+ }
+
+ private void parseKeyboardContent(final XmlPullParser parser, final boolean skip)
+ throws XmlPullParserException, IOException {
+ int event;
+ while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (event == XmlPullParser.START_TAG) {
+ final String tag = parser.getName();
+ if (TAG_ROW.equals(tag)) {
+ final KeyboardRow row = parseRowAttributes(parser);
+ if (DEBUG) startTag("<%s>%s", TAG_ROW, skip ? " skipped" : "");
+ if (!skip) {
+ startRow(row);
+ }
+ parseRowContent(parser, row, skip);
+ } else if (TAG_INCLUDE.equals(tag)) {
+ parseIncludeKeyboardContent(parser, skip);
+ } else if (TAG_SWITCH.equals(tag)) {
+ parseSwitchKeyboardContent(parser, skip);
+ } else if (TAG_KEY_STYLE.equals(tag)) {
+ parseKeyStyle(parser, skip);
+ } else {
+ throw new XmlParseUtils.IllegalStartTag(parser, TAG_ROW);
+ }
+ } else if (event == XmlPullParser.END_TAG) {
+ final String tag = parser.getName();
+ if (DEBUG) endTag("</%s>", tag);
+ if (TAG_KEYBOARD.equals(tag)) {
+ endKeyboard();
+ break;
+ } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)
+ || TAG_MERGE.equals(tag)) {
+ break;
+ } else {
+ throw new XmlParseUtils.IllegalEndTag(parser, TAG_ROW);
+ }
+ }
+ }
+ }
+
+ private KeyboardRow parseRowAttributes(final XmlPullParser parser)
+ throws XmlPullParserException {
+ final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
+ R.styleable.Keyboard);
+ try {
+ if (a.hasValue(R.styleable.Keyboard_horizontalGap)) {
+ throw new XmlParseUtils.IllegalAttribute(parser, "horizontalGap");
+ }
+ if (a.hasValue(R.styleable.Keyboard_verticalGap)) {
+ throw new XmlParseUtils.IllegalAttribute(parser, "verticalGap");
+ }
+ return new KeyboardRow(mResources, mParams, parser, mCurrentY);
+ } finally {
+ a.recycle();
+ }
+ }
+
+ private void parseRowContent(final XmlPullParser parser, final KeyboardRow row,
+ final boolean skip) throws XmlPullParserException, IOException {
+ int event;
+ while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (event == XmlPullParser.START_TAG) {
+ final String tag = parser.getName();
+ if (TAG_KEY.equals(tag)) {
+ parseKey(parser, row, skip);
+ } else if (TAG_SPACER.equals(tag)) {
+ parseSpacer(parser, row, skip);
+ } else if (TAG_INCLUDE.equals(tag)) {
+ parseIncludeRowContent(parser, row, skip);
+ } else if (TAG_SWITCH.equals(tag)) {
+ parseSwitchRowContent(parser, row, skip);
+ } else if (TAG_KEY_STYLE.equals(tag)) {
+ parseKeyStyle(parser, skip);
+ } else {
+ throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEY);
+ }
+ } else if (event == XmlPullParser.END_TAG) {
+ final String tag = parser.getName();
+ if (DEBUG) endTag("</%s>", tag);
+ if (TAG_ROW.equals(tag)) {
+ if (!skip) {
+ endRow(row);
+ }
+ break;
+ } else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)
+ || TAG_MERGE.equals(tag)) {
+ break;
+ } else {
+ throw new XmlParseUtils.IllegalEndTag(parser, TAG_KEY);
+ }
+ }
+ }
+ }
+
+ private void parseKey(final XmlPullParser parser, final KeyboardRow row, final boolean skip)
+ throws XmlPullParserException, IOException {
+ if (skip) {
+ XmlParseUtils.checkEndTag(TAG_KEY, parser);
+ if (DEBUG) {
+ startEndTag("<%s /> skipped", TAG_KEY);
+ }
+ } else {
+ final Key key = new Key(mResources, mParams, row, parser);
+ if (DEBUG) {
+ startEndTag("<%s%s %s moreKeys=%s />", TAG_KEY,
+ (key.isEnabled() ? "" : " disabled"), key,
+ Arrays.toString(key.mMoreKeys));
+ }
+ XmlParseUtils.checkEndTag(TAG_KEY, parser);
+ endKey(key);
+ }
+ }
+
+ private void parseSpacer(final XmlPullParser parser, final KeyboardRow row, final boolean skip)
+ throws XmlPullParserException, IOException {
+ if (skip) {
+ XmlParseUtils.checkEndTag(TAG_SPACER, parser);
+ if (DEBUG) startEndTag("<%s /> skipped", TAG_SPACER);
+ } else {
+ final Key.Spacer spacer = new Key.Spacer(mResources, mParams, row, parser);
+ if (DEBUG) startEndTag("<%s />", TAG_SPACER);
+ XmlParseUtils.checkEndTag(TAG_SPACER, parser);
+ endKey(spacer);
+ }
+ }
+
+ private void parseIncludeKeyboardContent(final XmlPullParser parser, final boolean skip)
+ throws XmlPullParserException, IOException {
+ parseIncludeInternal(parser, null, skip);
+ }
+
+ private void parseIncludeRowContent(final XmlPullParser parser, final KeyboardRow row,
+ final boolean skip) throws XmlPullParserException, IOException {
+ parseIncludeInternal(parser, row, skip);
+ }
+
+ private void parseIncludeInternal(final XmlPullParser parser, final KeyboardRow row,
+ final boolean skip) throws XmlPullParserException, IOException {
+ if (skip) {
+ XmlParseUtils.checkEndTag(TAG_INCLUDE, parser);
+ if (DEBUG) startEndTag("</%s> skipped", TAG_INCLUDE);
+ } else {
+ final AttributeSet attr = Xml.asAttributeSet(parser);
+ final TypedArray keyboardAttr = mResources.obtainAttributes(attr,
+ R.styleable.Keyboard_Include);
+ final TypedArray keyAttr = mResources.obtainAttributes(attr,
+ R.styleable.Keyboard_Key);
+ int keyboardLayout = 0;
+ float savedDefaultKeyWidth = 0;
+ int savedDefaultKeyLabelFlags = 0;
+ int savedDefaultBackgroundType = Key.BACKGROUND_TYPE_NORMAL;
+ try {
+ XmlParseUtils.checkAttributeExists(keyboardAttr,
+ R.styleable.Keyboard_Include_keyboardLayout, "keyboardLayout",
+ TAG_INCLUDE, parser);
+ keyboardLayout = keyboardAttr.getResourceId(
+ R.styleable.Keyboard_Include_keyboardLayout, 0);
+ if (row != null) {
+ if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) {
+ // Override current x coordinate.
+ row.setXPos(row.getKeyX(keyAttr));
+ }
+ // TODO: Remove this if-clause and do the same as backgroundType below.
+ savedDefaultKeyWidth = row.getDefaultKeyWidth();
+ if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyWidth)) {
+ // Override default key width.
+ row.setDefaultKeyWidth(row.getKeyWidth(keyAttr));
+ }
+ savedDefaultKeyLabelFlags = row.getDefaultKeyLabelFlags();
+ // Bitwise-or default keyLabelFlag if exists.
+ row.setDefaultKeyLabelFlags(keyAttr.getInt(
+ R.styleable.Keyboard_Key_keyLabelFlags, 0)
+ | savedDefaultKeyLabelFlags);
+ savedDefaultBackgroundType = row.getDefaultBackgroundType();
+ // Override default backgroundType if exists.
+ row.setDefaultBackgroundType(keyAttr.getInt(
+ R.styleable.Keyboard_Key_backgroundType,
+ savedDefaultBackgroundType));
+ }
+ } finally {
+ keyboardAttr.recycle();
+ keyAttr.recycle();
+ }
+
+ XmlParseUtils.checkEndTag(TAG_INCLUDE, parser);
+ if (DEBUG) {
+ startEndTag("<%s keyboardLayout=%s />",TAG_INCLUDE,
+ mResources.getResourceEntryName(keyboardLayout));
+ }
+ final XmlResourceParser parserForInclude = mResources.getXml(keyboardLayout);
+ try {
+ parseMerge(parserForInclude, row, skip);
+ } finally {
+ if (row != null) {
+ // Restore default keyWidth, keyLabelFlags, and backgroundType.
+ row.setDefaultKeyWidth(savedDefaultKeyWidth);
+ row.setDefaultKeyLabelFlags(savedDefaultKeyLabelFlags);
+ row.setDefaultBackgroundType(savedDefaultBackgroundType);
+ }
+ parserForInclude.close();
+ }
+ }
+ }
+
+ private void parseMerge(final XmlPullParser parser, final KeyboardRow row, final boolean skip)
+ throws XmlPullParserException, IOException {
+ if (DEBUG) startTag("<%s>", TAG_MERGE);
+ int event;
+ while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (event == XmlPullParser.START_TAG) {
+ final String tag = parser.getName();
+ if (TAG_MERGE.equals(tag)) {
+ if (row == null) {
+ parseKeyboardContent(parser, skip);
+ } else {
+ parseRowContent(parser, row, skip);
+ }
+ break;
+ } else {
+ throw new XmlParseUtils.ParseException(
+ "Included keyboard layout must have <merge> root element", parser);
+ }
+ }
+ }
+ }
+
+ private void parseSwitchKeyboardContent(final XmlPullParser parser, final boolean skip)
+ throws XmlPullParserException, IOException {
+ parseSwitchInternal(parser, null, skip);
+ }
+
+ private void parseSwitchRowContent(final XmlPullParser parser, final KeyboardRow row,
+ final boolean skip) throws XmlPullParserException, IOException {
+ parseSwitchInternal(parser, row, skip);
+ }
+
+ private void parseSwitchInternal(final XmlPullParser parser, final KeyboardRow row,
+ final boolean skip) throws XmlPullParserException, IOException {
+ if (DEBUG) startTag("<%s> %s", TAG_SWITCH, mParams.mId);
+ boolean selected = false;
+ int event;
+ while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (event == XmlPullParser.START_TAG) {
+ final String tag = parser.getName();
+ if (TAG_CASE.equals(tag)) {
+ selected |= parseCase(parser, row, selected ? true : skip);
+ } else if (TAG_DEFAULT.equals(tag)) {
+ selected |= parseDefault(parser, row, selected ? true : skip);
+ } else {
+ throw new XmlParseUtils.IllegalStartTag(parser, TAG_KEY);
+ }
+ } else if (event == XmlPullParser.END_TAG) {
+ final String tag = parser.getName();
+ if (TAG_SWITCH.equals(tag)) {
+ if (DEBUG) endTag("</%s>", TAG_SWITCH);
+ break;
+ } else {
+ throw new XmlParseUtils.IllegalEndTag(parser, TAG_KEY);
+ }
+ }
+ }
+ }
+
+ private boolean parseCase(final XmlPullParser parser, final KeyboardRow row, final boolean skip)
+ throws XmlPullParserException, IOException {
+ final boolean selected = parseCaseCondition(parser);
+ if (row == null) {
+ // Processing Rows.
+ parseKeyboardContent(parser, selected ? skip : true);
+ } else {
+ // Processing Keys.
+ parseRowContent(parser, row, selected ? skip : true);
+ }
+ return selected;
+ }
+
+ private boolean parseCaseCondition(final XmlPullParser parser) {
+ final KeyboardId id = mParams.mId;
+ if (id == null) {
+ return true;
+ }
+ final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
+ R.styleable.Keyboard_Case);
+ try {
+ final boolean keyboardLayoutSetElementMatched = matchTypedValue(a,
+ R.styleable.Keyboard_Case_keyboardLayoutSetElement, id.mElementId,
+ KeyboardId.elementIdToName(id.mElementId));
+ final boolean modeMatched = matchTypedValue(a,
+ R.styleable.Keyboard_Case_mode, id.mMode, KeyboardId.modeName(id.mMode));
+ final boolean navigateNextMatched = matchBoolean(a,
+ R.styleable.Keyboard_Case_navigateNext, id.navigateNext());
+ final boolean navigatePreviousMatched = matchBoolean(a,
+ R.styleable.Keyboard_Case_navigatePrevious, id.navigatePrevious());
+ final boolean passwordInputMatched = matchBoolean(a,
+ R.styleable.Keyboard_Case_passwordInput, id.passwordInput());
+ final boolean clobberSettingsKeyMatched = matchBoolean(a,
+ R.styleable.Keyboard_Case_clobberSettingsKey, id.mClobberSettingsKey);
+ final boolean shortcutKeyEnabledMatched = matchBoolean(a,
+ R.styleable.Keyboard_Case_shortcutKeyEnabled, id.mShortcutKeyEnabled);
+ final boolean hasShortcutKeyMatched = matchBoolean(a,
+ R.styleable.Keyboard_Case_hasShortcutKey, id.mHasShortcutKey);
+ final boolean languageSwitchKeyEnabledMatched = matchBoolean(a,
+ R.styleable.Keyboard_Case_languageSwitchKeyEnabled,
+ id.mLanguageSwitchKeyEnabled);
+ final boolean isMultiLineMatched = matchBoolean(a,
+ R.styleable.Keyboard_Case_isMultiLine, id.isMultiLine());
+ final boolean imeActionMatched = matchInteger(a,
+ R.styleable.Keyboard_Case_imeAction, id.imeAction());
+ final boolean localeCodeMatched = matchString(a,
+ R.styleable.Keyboard_Case_localeCode, id.mLocale.toString());
+ final boolean languageCodeMatched = matchString(a,
+ R.styleable.Keyboard_Case_languageCode, id.mLocale.getLanguage());
+ final boolean countryCodeMatched = matchString(a,
+ R.styleable.Keyboard_Case_countryCode, id.mLocale.getCountry());
+ final boolean selected = keyboardLayoutSetElementMatched && modeMatched
+ && navigateNextMatched && navigatePreviousMatched && passwordInputMatched
+ && clobberSettingsKeyMatched && shortcutKeyEnabledMatched
+ && hasShortcutKeyMatched && languageSwitchKeyEnabledMatched
+ && isMultiLineMatched && imeActionMatched && localeCodeMatched
+ && languageCodeMatched && countryCodeMatched;
+
+ if (DEBUG) {
+ startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE,
+ textAttr(a.getString(
+ R.styleable.Keyboard_Case_keyboardLayoutSetElement),
+ "keyboardLayoutSetElement"),
+ textAttr(a.getString(R.styleable.Keyboard_Case_mode), "mode"),
+ textAttr(a.getString(R.styleable.Keyboard_Case_imeAction),
+ "imeAction"),
+ booleanAttr(a, R.styleable.Keyboard_Case_navigateNext,
+ "navigateNext"),
+ booleanAttr(a, R.styleable.Keyboard_Case_navigatePrevious,
+ "navigatePrevious"),
+ booleanAttr(a, R.styleable.Keyboard_Case_clobberSettingsKey,
+ "clobberSettingsKey"),
+ booleanAttr(a, R.styleable.Keyboard_Case_passwordInput,
+ "passwordInput"),
+ booleanAttr(a, R.styleable.Keyboard_Case_shortcutKeyEnabled,
+ "shortcutKeyEnabled"),
+ booleanAttr(a, R.styleable.Keyboard_Case_hasShortcutKey,
+ "hasShortcutKey"),
+ booleanAttr(a, R.styleable.Keyboard_Case_languageSwitchKeyEnabled,
+ "languageSwitchKeyEnabled"),
+ booleanAttr(a, R.styleable.Keyboard_Case_isMultiLine,
+ "isMultiLine"),
+ textAttr(a.getString(R.styleable.Keyboard_Case_localeCode),
+ "localeCode"),
+ textAttr(a.getString(R.styleable.Keyboard_Case_languageCode),
+ "languageCode"),
+ textAttr(a.getString(R.styleable.Keyboard_Case_countryCode),
+ "countryCode"),
+ selected ? "" : " skipped");
+ }
+
+ return selected;
+ } finally {
+ a.recycle();
+ }
+ }
+
+ private static boolean matchInteger(final TypedArray a, final int index, final int value) {
+ // If <case> does not have "index" attribute, that means this <case> is wild-card for
+ // the attribute.
+ return !a.hasValue(index) || a.getInt(index, 0) == value;
+ }
+
+ private static boolean matchBoolean(final TypedArray a, final int index, final boolean value) {
+ // If <case> does not have "index" attribute, that means this <case> is wild-card for
+ // the attribute.
+ return !a.hasValue(index) || a.getBoolean(index, false) == value;
+ }
+
+ private static boolean matchString(final TypedArray a, final int index, final String value) {
+ // If <case> does not have "index" attribute, that means this <case> is wild-card for
+ // the attribute.
+ return !a.hasValue(index)
+ || StringUtils.containsInArray(value, a.getString(index).split("\\|"));
+ }
+
+ private static boolean matchTypedValue(final TypedArray a, final int index, final int intValue,
+ final String strValue) {
+ // If <case> does not have "index" attribute, that means this <case> is wild-card for
+ // the attribute.
+ final TypedValue v = a.peekValue(index);
+ if (v == null) {
+ return true;
+ }
+ if (ResourceUtils.isIntegerValue(v)) {
+ return intValue == a.getInt(index, 0);
+ } else if (ResourceUtils.isStringValue(v)) {
+ return StringUtils.containsInArray(strValue, a.getString(index).split("\\|"));
+ }
+ return false;
+ }
+
+ private boolean parseDefault(final XmlPullParser parser, final KeyboardRow row,
+ final boolean skip) throws XmlPullParserException, IOException {
+ if (DEBUG) startTag("<%s>", TAG_DEFAULT);
+ if (row == null) {
+ parseKeyboardContent(parser, skip);
+ } else {
+ parseRowContent(parser, row, skip);
+ }
+ return true;
+ }
+
+ private void parseKeyStyle(final XmlPullParser parser, final boolean skip)
+ throws XmlPullParserException, IOException {
+ TypedArray keyStyleAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
+ R.styleable.Keyboard_KeyStyle);
+ TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser),
+ R.styleable.Keyboard_Key);
+ try {
+ if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName)) {
+ throw new XmlParseUtils.ParseException("<" + TAG_KEY_STYLE
+ + "/> needs styleName attribute", parser);
+ }
+ if (DEBUG) {
+ startEndTag("<%s styleName=%s />%s", TAG_KEY_STYLE,
+ keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName),
+ skip ? " skipped" : "");
+ }
+ if (!skip) {
+ mParams.mKeyStyles.parseKeyStyleAttributes(keyStyleAttr, keyAttrs, parser);
+ }
+ } finally {
+ keyStyleAttr.recycle();
+ keyAttrs.recycle();
+ }
+ XmlParseUtils.checkEndTag(TAG_KEY_STYLE, parser);
+ }
+
+ private void startKeyboard() {
+ mCurrentY += mParams.mTopPadding;
+ mTopEdge = true;
+ }
+
+ private void startRow(final KeyboardRow row) {
+ addEdgeSpace(mParams.mHorizontalEdgesPadding, row);
+ mCurrentRow = row;
+ mLeftEdge = true;
+ mRightEdgeKey = null;
+ }
+
+ private void endRow(final KeyboardRow row) {
+ if (mCurrentRow == null) {
+ throw new InflateException("orphan end row tag");
+ }
+ if (mRightEdgeKey != null) {
+ mRightEdgeKey.markAsRightEdge(mParams);
+ mRightEdgeKey = null;
+ }
+ addEdgeSpace(mParams.mHorizontalEdgesPadding, row);
+ mCurrentY += row.mRowHeight;
+ mCurrentRow = null;
+ mTopEdge = false;
+ }
+
+ private void endKey(final Key key) {
+ mParams.onAddKey(key);
+ if (mLeftEdge) {
+ key.markAsLeftEdge(mParams);
+ mLeftEdge = false;
+ }
+ if (mTopEdge) {
+ key.markAsTopEdge(mParams);
+ }
+ mRightEdgeKey = key;
+ }
+
+ private void endKeyboard() {
+ // nothing to do here.
+ }
+
+ private void addEdgeSpace(final float width, final KeyboardRow row) {
+ row.advanceXPos(width);
+ mLeftEdge = false;
+ mRightEdgeKey = null;
+ }
+
+ private static String textAttr(final String value, final String name) {
+ return value != null ? String.format(" %s=%s", name, value) : "";
+ }
+
+ private static String booleanAttr(final TypedArray a, final int index, final String name) {
+ return a.hasValue(index)
+ ? String.format(" %s=%s", name, a.getBoolean(index, false)) : "";
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
index f7981a3..f7923d0 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
@@ -17,13 +17,13 @@
package com.android.inputmethod.keyboard.internal;
import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.CollectionUtils;
import java.util.HashMap;
public class KeyboardCodesSet {
- private static final HashMap<String, int[]> sLanguageToCodesMap =
- new HashMap<String, int[]>();
- private static final HashMap<String, Integer> sNameToIdMap = new HashMap<String, Integer>();
+ private static final HashMap<String, int[]> sLanguageToCodesMap = CollectionUtils.newHashMap();
+ private static final HashMap<String, Integer> sNameToIdMap = CollectionUtils.newHashMap();
private int[] mCodes = DEFAULT;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
index 5155851..4a98a36 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
@@ -22,6 +22,7 @@
import android.util.Log;
import android.util.SparseIntArray;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.R;
import java.util.HashMap;
@@ -35,7 +36,7 @@
private static final SparseIntArray ATTR_ID_TO_ICON_ID = new SparseIntArray();
// Icon name to icon id map.
- private static final HashMap<String, Integer> sNameToIdsMap = new HashMap<String, Integer>();
+ private static final HashMap<String, Integer> sNameToIdsMap = CollectionUtils.newHashMap();
private static final Object[] NAMES_AND_ATTR_IDS = {
"undefined", ATTR_UNDEFINED,
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
new file mode 100644
index 0000000..ab5d31d
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.util.SparseIntArray;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardId;
+import com.android.inputmethod.latin.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+public class KeyboardParams {
+ public KeyboardId mId;
+ public int mThemeId;
+
+ /** Total height and width of the keyboard, including the paddings and keys */
+ public int mOccupiedHeight;
+ public int mOccupiedWidth;
+
+ /** Base height and width of the keyboard used to calculate rows' or keys' heights and
+ * widths
+ */
+ public int mBaseHeight;
+ public int mBaseWidth;
+
+ public int mTopPadding;
+ public int mBottomPadding;
+ public int mHorizontalEdgesPadding;
+ public int mHorizontalCenterPadding;
+
+ public KeyVisualAttributes mKeyVisualAttributes;
+
+ public int mDefaultRowHeight;
+ public int mDefaultKeyWidth;
+ public int mHorizontalGap;
+ public int mVerticalGap;
+
+ public int mMoreKeysTemplate;
+ public int mMaxMoreKeysKeyboardColumn;
+
+ public int GRID_WIDTH;
+ public int GRID_HEIGHT;
+
+ public final HashSet<Key> mKeys = CollectionUtils.newHashSet();
+ public final ArrayList<Key> mShiftKeys = CollectionUtils.newArrayList();
+ public final ArrayList<Key> mAltCodeKeysWhileTyping = CollectionUtils.newArrayList();
+ public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet();
+ public final KeyboardCodesSet mCodesSet = new KeyboardCodesSet();
+ public final KeyboardTextsSet mTextsSet = new KeyboardTextsSet();
+ public final KeyStylesSet mKeyStyles = new KeyStylesSet(mTextsSet);
+
+ public KeysCache mKeysCache;
+
+ public int mMostCommonKeyHeight = 0;
+ public int mMostCommonKeyWidth = 0;
+
+ public boolean mProximityCharsCorrectionEnabled;
+
+ public final TouchPositionCorrection mTouchPositionCorrection =
+ new TouchPositionCorrection();
+
+ protected void clearKeys() {
+ mKeys.clear();
+ mShiftKeys.clear();
+ clearHistogram();
+ }
+
+ public void onAddKey(final Key newKey) {
+ final Key key = (mKeysCache != null) ? mKeysCache.get(newKey) : newKey;
+ final boolean zeroWidthSpacer = key.isSpacer() && key.mWidth == 0;
+ if (!zeroWidthSpacer) {
+ mKeys.add(key);
+ updateHistogram(key);
+ }
+ if (key.mCode == Keyboard.CODE_SHIFT) {
+ mShiftKeys.add(key);
+ }
+ if (key.altCodeWhileTyping()) {
+ mAltCodeKeysWhileTyping.add(key);
+ }
+ }
+
+ private int mMaxHeightCount = 0;
+ private int mMaxWidthCount = 0;
+ private final SparseIntArray mHeightHistogram = new SparseIntArray();
+ private final SparseIntArray mWidthHistogram = new SparseIntArray();
+
+ private void clearHistogram() {
+ mMostCommonKeyHeight = 0;
+ mMaxHeightCount = 0;
+ mHeightHistogram.clear();
+
+ mMaxWidthCount = 0;
+ mMostCommonKeyWidth = 0;
+ mWidthHistogram.clear();
+ }
+
+ private static int updateHistogramCounter(final SparseIntArray histogram, final int key) {
+ final int index = histogram.indexOfKey(key);
+ final int count = (index >= 0 ? histogram.get(key) : 0) + 1;
+ histogram.put(key, count);
+ return count;
+ }
+
+ private void updateHistogram(final Key key) {
+ final int height = key.mHeight + mVerticalGap;
+ final int heightCount = updateHistogramCounter(mHeightHistogram, height);
+ if (heightCount > mMaxHeightCount) {
+ mMaxHeightCount = heightCount;
+ mMostCommonKeyHeight = height;
+ }
+
+ final int width = key.mWidth + mHorizontalGap;
+ final int widthCount = updateHistogramCounter(mWidthHistogram, width);
+ if (widthCount > mMaxWidthCount) {
+ mMaxWidthCount = widthCount;
+ mMostCommonKeyWidth = width;
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java
new file mode 100644
index 0000000..eb17b0e
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.util.Xml;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.ResourceUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * Container for keys in the keyboard. All keys in a row are at the same Y-coordinate.
+ * Some of the key size defaults can be overridden per row from what the {@link Keyboard}
+ * defines.
+ */
+public class KeyboardRow {
+ // keyWidth enum constants
+ private static final int KEYWIDTH_NOT_ENUM = 0;
+ private static final int KEYWIDTH_FILL_RIGHT = -1;
+
+ private final KeyboardParams mParams;
+ /** Default width of a key in this row. */
+ private float mDefaultKeyWidth;
+ /** Default height of a key in this row. */
+ public final int mRowHeight;
+ /** Default keyLabelFlags in this row. */
+ private int mDefaultKeyLabelFlags;
+ /** Default backgroundType for this row */
+ private int mDefaultBackgroundType;
+
+ private final int mCurrentY;
+ // Will be updated by {@link Key}'s constructor.
+ private float mCurrentX;
+
+ public KeyboardRow(final Resources res, final KeyboardParams params, final XmlPullParser parser,
+ final int y) {
+ mParams = params;
+ TypedArray keyboardAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
+ R.styleable.Keyboard);
+ mRowHeight = (int)ResourceUtils.getDimensionOrFraction(keyboardAttr,
+ R.styleable.Keyboard_rowHeight,
+ params.mBaseHeight, params.mDefaultRowHeight);
+ keyboardAttr.recycle();
+ TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
+ R.styleable.Keyboard_Key);
+ mDefaultKeyWidth = ResourceUtils.getDimensionOrFraction(keyAttr,
+ R.styleable.Keyboard_Key_keyWidth,
+ params.mBaseWidth, params.mDefaultKeyWidth);
+ mDefaultBackgroundType = keyAttr.getInt(R.styleable.Keyboard_Key_backgroundType,
+ Key.BACKGROUND_TYPE_NORMAL);
+ keyAttr.recycle();
+
+ // TODO: Initialize this with <Row> attribute as backgroundType is done.
+ mDefaultKeyLabelFlags = 0;
+ mCurrentY = y;
+ mCurrentX = 0.0f;
+ }
+
+ public float getDefaultKeyWidth() {
+ return mDefaultKeyWidth;
+ }
+
+ public void setDefaultKeyWidth(final float defaultKeyWidth) {
+ mDefaultKeyWidth = defaultKeyWidth;
+ }
+
+ public int getDefaultKeyLabelFlags() {
+ return mDefaultKeyLabelFlags;
+ }
+
+ public void setDefaultKeyLabelFlags(final int keyLabelFlags) {
+ mDefaultKeyLabelFlags = keyLabelFlags;
+ }
+
+ public int getDefaultBackgroundType() {
+ return mDefaultBackgroundType;
+ }
+
+ public void setDefaultBackgroundType(final int backgroundType) {
+ mDefaultBackgroundType = backgroundType;
+ }
+
+ public void setXPos(final float keyXPos) {
+ mCurrentX = keyXPos;
+ }
+
+ public void advanceXPos(final float width) {
+ mCurrentX += width;
+ }
+
+ public int getKeyY() {
+ return mCurrentY;
+ }
+
+ public float getKeyX(final TypedArray keyAttr) {
+ final int keyboardRightEdge = mParams.mOccupiedWidth
+ - mParams.mHorizontalEdgesPadding;
+ if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) {
+ final float keyXPos = ResourceUtils.getDimensionOrFraction(keyAttr,
+ R.styleable.Keyboard_Key_keyXPos, mParams.mBaseWidth, 0);
+ if (keyXPos < 0) {
+ // If keyXPos is negative, the actual x-coordinate will be
+ // keyboardWidth + keyXPos.
+ // keyXPos shouldn't be less than mCurrentX because drawable area for this
+ // key starts at mCurrentX. Or, this key will overlaps the adjacent key on
+ // its left hand side.
+ return Math.max(keyXPos + keyboardRightEdge, mCurrentX);
+ } else {
+ return keyXPos + mParams.mHorizontalEdgesPadding;
+ }
+ }
+ return mCurrentX;
+ }
+
+ public float getKeyWidth(final TypedArray keyAttr) {
+ return getKeyWidth(keyAttr, mCurrentX);
+ }
+
+ public float getKeyWidth(final TypedArray keyAttr, final float keyXPos) {
+ final int widthType = ResourceUtils.getEnumValue(keyAttr,
+ R.styleable.Keyboard_Key_keyWidth, KEYWIDTH_NOT_ENUM);
+ switch (widthType) {
+ case KEYWIDTH_FILL_RIGHT:
+ final int keyboardRightEdge =
+ mParams.mOccupiedWidth - mParams.mHorizontalEdgesPadding;
+ // If keyWidth is fillRight, the actual key width will be determined to fill
+ // out the area up to the right edge of the keyboard.
+ return keyboardRightEdge - keyXPos;
+ default: // KEYWIDTH_NOT_ENUM
+ return ResourceUtils.getDimensionOrFraction(keyAttr,
+ R.styleable.Keyboard_Key_keyWidth,
+ mParams.mBaseWidth, mDefaultKeyWidth);
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
index bec0f1f..3b7c6ad 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.res.Resources;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.R;
import java.util.HashMap;
@@ -45,14 +46,12 @@
*/
public final class KeyboardTextsSet {
// Language to texts map.
- private static final HashMap<String, String[]> sLocaleToTextsMap =
- new HashMap<String, String[]>();
- private static final HashMap<String, Integer> sNameToIdsMap =
- new HashMap<String, Integer>();
+ private static final HashMap<String, String[]> sLocaleToTextsMap = CollectionUtils.newHashMap();
+ private static final HashMap<String, Integer> sNameToIdsMap = CollectionUtils.newHashMap();
private String[] mTexts;
// Resource name to text map.
- private HashMap<String, String> mResourceNameToTextsMap = new HashMap<String, String>();
+ private HashMap<String, String> mResourceNameToTextsMap = CollectionUtils.newHashMap();
public void setLanguage(final String language) {
mTexts = sLocaleToTextsMap.get(language);
@@ -211,22 +210,29 @@
/* 103 */ "keylabel_for_apostrophe",
/* 104 */ "keyhintlabel_for_apostrophe",
/* 105 */ "more_keys_for_apostrophe",
- /* 106 */ "more_keys_for_am_pm",
- /* 107 */ "settings_as_more_key",
- /* 108 */ "shortcut_as_more_key",
- /* 109 */ "action_next_as_more_key",
- /* 110 */ "action_previous_as_more_key",
- /* 111 */ "label_to_more_symbol_key",
- /* 112 */ "label_to_more_symbol_for_tablet_key",
- /* 113 */ "label_tab_key",
- /* 114 */ "label_to_phone_numeric_key",
- /* 115 */ "label_to_phone_symbols_key",
- /* 116 */ "label_time_am",
- /* 117 */ "label_time_pm",
- /* 118 */ "label_to_symbol_key_pcqwerty",
- /* 119 */ "keylabel_for_popular_domain",
- /* 120 */ "more_keys_for_popular_domain",
- /* 121 */ "more_keys_for_smiley",
+ /* 106 */ "more_keys_for_q",
+ /* 107 */ "more_keys_for_x",
+ /* 108 */ "keylabel_for_q",
+ /* 109 */ "keylabel_for_w",
+ /* 110 */ "keylabel_for_y",
+ /* 111 */ "keylabel_for_x",
+ /* 112 */ "keylabel_for_spanish_row2_10",
+ /* 113 */ "more_keys_for_am_pm",
+ /* 114 */ "settings_as_more_key",
+ /* 115 */ "shortcut_as_more_key",
+ /* 116 */ "action_next_as_more_key",
+ /* 117 */ "action_previous_as_more_key",
+ /* 118 */ "label_to_more_symbol_key",
+ /* 119 */ "label_to_more_symbol_for_tablet_key",
+ /* 120 */ "label_tab_key",
+ /* 121 */ "label_to_phone_numeric_key",
+ /* 122 */ "label_to_phone_symbols_key",
+ /* 123 */ "label_time_am",
+ /* 124 */ "label_time_pm",
+ /* 125 */ "label_to_symbol_key_pcqwerty",
+ /* 126 */ "keylabel_for_popular_domain",
+ /* 127 */ "more_keys_for_popular_domain",
+ /* 128 */ "more_keys_for_smiley",
};
private static final String EMPTY = "";
@@ -349,33 +355,41 @@
/* 103 */ "\'",
/* 104 */ "\"",
/* 105 */ "\"",
- /* 106 */ "!fixedColumnOrder!2,!hasLabels!,!text/label_time_am,!text/label_time_pm",
- /* 107 */ "!icon/settings_key|!code/key_settings",
- /* 108 */ "!icon/shortcut_key|!code/key_shortcut",
- /* 109 */ "!hasLabels!,!text/label_next_key|!code/key_action_next",
- /* 110 */ "!hasLabels!,!text/label_previous_key|!code/key_action_previous",
+ /* 106 */ EMPTY,
+ /* 107 */ EMPTY,
+ /* 108 */ "q",
+ /* 109 */ "w",
+ /* 110 */ "y",
+ /* 111 */ "x",
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ /* 112 */ "\u00F1",
+ /* 113 */ "!fixedColumnOrder!2,!hasLabels!,!text/label_time_am,!text/label_time_pm",
+ /* 114 */ "!icon/settings_key|!code/key_settings",
+ /* 115 */ "!icon/shortcut_key|!code/key_shortcut",
+ /* 116 */ "!hasLabels!,!text/label_next_key|!code/key_action_next",
+ /* 117 */ "!hasLabels!,!text/label_previous_key|!code/key_action_previous",
// Label for "switch to more symbol" modifier key. Must be short to fit on key!
- /* 111 */ "= \\ <",
+ /* 118 */ "= \\ <",
// Label for "switch to more symbol" modifier key on tablets. Must be short to fit on key!
- /* 112 */ "~ \\ {",
+ /* 119 */ "~ \\ {",
// Label for "Tab" key. Must be short to fit on key!
- /* 113 */ "Tab",
+ /* 120 */ "Tab",
// Label for "switch to phone numeric" key. Must be short to fit on key!
- /* 114 */ "123",
+ /* 121 */ "123",
// Label for "switch to phone symbols" key. Must be short to fit on key!
// U+FF0A: "*" FULLWIDTH ASTERISK
// U+FF03: "#" FULLWIDTH NUMBER SIGN
- /* 115 */ "\uFF0A\uFF03",
+ /* 122 */ "\uFF0A\uFF03",
// Key label for "ante meridiem"
- /* 116 */ "AM",
+ /* 123 */ "AM",
// Key label for "post meridiem"
- /* 117 */ "PM",
+ /* 124 */ "PM",
// Label for "switch to symbols" key on PC QWERTY layout
- /* 118 */ "Sym",
- /* 119 */ ".com",
+ /* 125 */ "Sym",
+ /* 126 */ ".com",
// popular web domains for the locale - most popular, displayed on the keyboard
- /* 120 */ "!hasLabels!,.net,.org,.gov,.edu",
- /* 121 */ "!fixedColumnOrder!5,!hasLabels!,=-O|=-O ,:-P|:-P ,;-)|;-) ,:-(|:-( ,:-)|:-) ,:-!|:-! ,:-$|:-$ ,B-)|B-) ,:O|:O ,:-*|:-* ,:-D|:-D ,:\'(|:\'( ,:-\\\\|:-\\\\ ,O:-)|O:-) ,:-[|:-[ ",
+ /* 127 */ "!hasLabels!,.net,.org,.gov,.edu",
+ /* 128 */ "!fixedColumnOrder!5,!hasLabels!,=-O|=-O ,:-P|:-P ,;-)|;-) ,:-(|:-( ,:-)|:-) ,:-!|:-! ,:-$|:-$ ,B-)|B-) ,:O|:O ,:-*|:-* ,:-D|:-D ,:\'(|:\'( ,:-\\\\|:-\\\\ ,O:-)|O:-) ,:-[|:-[ ",
};
/* Language af: Afrikaans */
@@ -858,6 +872,144 @@
/* 7 */ "\u00E7",
};
+ /* Language eo: Esperanto */
+ private static final String[] LANGUAGE_eo = {
+ // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+ // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+ // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+ // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ // U+00E6: "æ" LATIN SMALL LETTER AE
+ // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+ // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+ // U+0103: "ă" LATIN SMALL LETTER A WITH BREVE
+ // U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
+ // U+00AA: "ª" FEMININE ORDINAL INDICATOR
+ /* 0 */ "\u00E1,\u00E0,\u00E2,\u00E4,\u00E6,\u00E3,\u00E5,\u0101,\u0103,\u0105,\u00AA",
+ // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ // U+011B: "ě" LATIN SMALL LETTER E WITH CARON
+ // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ // U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+ // U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+ // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
+ /* 1 */ "\u00E9,\u011B,\u00E8,\u00EA,\u00EB,\u0119,\u0117,\u0113",
+ // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+ // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+ // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+ // U+0129: "ĩ" LATIN SMALL LETTER I WITH TILDE
+ // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+ // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+ // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
+ // U+0131: "ı" LATIN SMALL LETTER DOTLESS I
+ // U+0133: "ij" LATIN SMALL LIGATURE IJ
+ /* 2 */ "\u00ED,\u00EE,\u00EF,\u0129,\u00EC,\u012F,\u012B,\u0131,\u0133",
+ // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+ // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+ // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+ // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+ // U+0153: "œ" LATIN SMALL LIGATURE OE
+ // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+ // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
+ // U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE
+ // U+00BA: "º" MASCULINE ORDINAL INDICATOR
+ /* 3 */ "\u00F3,\u00F6,\u00F4,\u00F2,\u00F5,\u0153,\u00F8,\u014D,\u0151,\u00BA",
+ // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ // U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE
+ // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ // U+0169: "ũ" LATIN SMALL LETTER U WITH TILDE
+ // U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE
+ // U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK
+ // U+00B5: "µ" MICRO SIGN
+ /* 4 */ "\u00FA,\u016F,\u00FB,\u00FC,\u00F9,\u016B,\u0169,\u0171,\u0173,\u00B5",
+ // U+00DF: "ß" LATIN SMALL LETTER SHARP S
+ // U+0161: "š" LATIN SMALL LETTER S WITH CARON
+ // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
+ // U+0219: "ș" LATIN SMALL LETTER S WITH COMMA BELOW
+ // U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA
+ /* 5 */ "\u00DF,\u0161,\u015B,\u0219,\u015F",
+ // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+ // U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA
+ // U+0148: "ň" LATIN SMALL LETTER N WITH CARON
+ // U+0149: "ʼn" LATIN SMALL LETTER N PRECEDED BY APOSTROPHE
+ // U+014B: "ŋ" LATIN SMALL LETTER ENG
+ /* 6 */ "\u00F1,\u0144,\u0146,\u0148,\u0149,\u014B",
+ // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
+ // U+010D: "č" LATIN SMALL LETTER C WITH CARON
+ // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+ // U+010B: "ċ" LATIN SMALL LETTER C WITH DOT ABOVE
+ /* 7 */ "\u0107,\u010D,\u00E7,\u010B",
+ // U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
+ // U+0177: "ŷ" LATIN SMALL LETTER Y WITH CIRCUMFLEX
+ // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
+ // U+00FE: "þ" LATIN SMALL LETTER THORN
+ /* 8 */ "y,\u00FD,\u0177,\u00FF,\u00FE",
+ // U+00F0: "ð" LATIN SMALL LETTER ETH
+ // U+010F: "ď" LATIN SMALL LETTER D WITH CARON
+ // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE
+ /* 9 */ "\u00F0,\u010F,\u0111",
+ // U+0159: "ř" LATIN SMALL LETTER R WITH CARON
+ // U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE
+ // U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA
+ /* 10 */ "\u0159,\u0155,\u0157",
+ // U+0165: "ť" LATIN SMALL LETTER T WITH CARON
+ // U+021B: "ț" LATIN SMALL LETTER T WITH COMMA BELOW
+ // U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
+ // U+0167: "ŧ" LATIN SMALL LETTER T WITH STROKE
+ /* 11 */ "\u0165,\u021B,\u0163,\u0167",
+ // U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
+ // U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
+ // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
+ /* 12 */ "\u017A,\u017C,\u017E",
+ // U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA
+ // U+0138: "ĸ" LATIN SMALL LETTER KRA
+ /* 13 */ "\u0137,\u0138",
+ // U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE
+ // U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA
+ // U+013E: "ľ" LATIN SMALL LETTER L WITH CARON
+ // U+0140: "ŀ" LATIN SMALL LETTER L WITH MIDDLE DOT
+ // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
+ /* 14 */ "\u013A,\u013C,\u013E,\u0140,\u0142",
+ // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
+ // U+0121: "ġ" LATIN SMALL LETTER G WITH DOT ABOVE
+ // U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA
+ /* 15 */ "\u011F,\u0121,\u0123",
+ // U+0175: "ŵ" LATIN SMALL LETTER W WITH CIRCUMFLEX
+ /* 16 */ "w,\u0175",
+ // U+0125: "ĥ" LATIN SMALL LETTER H WITH CIRCUMFLEX
+ // U+0127: "ħ" LATIN SMALL LETTER H WITH STROKE
+ /* 17 */ "\u0125,\u0127",
+ /* 18 */ null,
+ // U+0175: "ŵ" LATIN SMALL LETTER W WITH CIRCUMFLEX
+ /* 19 */ "w,\u0175",
+ /* 20~ */
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null,
+ /* ~105 */
+ /* 106 */ "q",
+ /* 107 */ "x",
+ // U+015D: "ŝ" LATIN SMALL LETTER S WITH CIRCUMFLEX
+ /* 108 */ "\u015D",
+ // U+011D: "ĝ" LATIN SMALL LETTER G WITH CIRCUMFLEX
+ /* 109 */ "\u011D",
+ // U+016D: "ŭ" LATIN SMALL LETTER U WITH BREVE
+ /* 110 */ "\u016D",
+ // U+0109: "ĉ" LATIN SMALL LETTER C WITH CIRCUMFLEX
+ /* 111 */ "\u0109",
+ // U+0135: "ĵ" LATIN SMALL LETTER J WITH CIRCUMFLEX
+ /* 112 */ "\u0135",
+ };
+
/* Language es: Spanish */
private static final String[] LANGUAGE_es = {
// U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
@@ -2696,6 +2848,7 @@
"da", LANGUAGE_da, /* Danish */
"de", LANGUAGE_de, /* German */
"en", LANGUAGE_en, /* English */
+ "eo", LANGUAGE_eo, /* Esperanto */
"es", LANGUAGE_es, /* Spanish */
"et", LANGUAGE_et, /* Estonian */
"fa", LANGUAGE_fa, /* Persian */
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java b/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java
new file mode 100644
index 0000000..f54617c
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.latin.CollectionUtils;
+
+import java.util.HashMap;
+
+public class KeysCache {
+ private final HashMap<Key, Key> mMap = CollectionUtils.newHashMap();
+
+ public void clear() {
+ mMap.clear();
+ }
+
+ public Key get(final Key key) {
+ final Key existingKey = mMap.get(key);
+ if (existingKey != null) {
+ // Reuse the existing element that equals to "key" without adding "key" to the map.
+ return existingKey;
+ }
+ mMap.put(key, key);
+ return key;
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
new file mode 100644
index 0000000..5da2654
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.StringUtils;
+
+import java.util.Locale;
+
+public class MoreKeySpec {
+ public final int mCode;
+ public final String mLabel;
+ public final String mOutputText;
+ public final int mIconId;
+
+ public MoreKeySpec(final String moreKeySpec, boolean needsToUpperCase, final Locale locale,
+ final KeyboardCodesSet codesSet) {
+ mLabel = KeySpecParser.toUpperCaseOfStringForLocale(
+ KeySpecParser.getLabel(moreKeySpec), needsToUpperCase, locale);
+ final int code = KeySpecParser.toUpperCaseOfCodeForLocale(
+ KeySpecParser.getCode(moreKeySpec, codesSet), needsToUpperCase, locale);
+ if (code == Keyboard.CODE_UNSPECIFIED) {
+ // Some letter, for example German Eszett (U+00DF: "ß"), has multiple characters
+ // upper case representation ("SS").
+ mCode = Keyboard.CODE_OUTPUT_TEXT;
+ mOutputText = mLabel;
+ } else {
+ mCode = code;
+ mOutputText = KeySpecParser.toUpperCaseOfStringForLocale(
+ KeySpecParser.getOutputText(moreKeySpec), needsToUpperCase, locale);
+ }
+ mIconId = KeySpecParser.getIconId(moreKeySpec);
+ }
+
+ @Override
+ public String toString() {
+ final String label = (mIconId == KeyboardIconsSet.ICON_UNDEFINED ? mLabel
+ : KeySpecParser.PREFIX_ICON + KeyboardIconsSet.getIconName(mIconId));
+ final String output = (mCode == Keyboard.CODE_OUTPUT_TEXT ? mOutputText
+ : Keyboard.printableCode(mCode));
+ if (StringUtils.codePointCount(label) == 1 && label.codePointAt(0) == mCode) {
+ return output;
+ } else {
+ return label + "|" + output;
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
index 1c7ceaf..e0858c0 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
@@ -18,6 +18,8 @@
import android.util.Log;
+import com.android.inputmethod.latin.CollectionUtils;
+
import java.util.ArrayList;
public class PointerTrackerQueue {
@@ -32,7 +34,7 @@
private static final int INITIAL_CAPACITY = 10;
private final ArrayList<Element> mExpandableArrayOfActivePointers =
- new ArrayList<Element>(INITIAL_CAPACITY);
+ CollectionUtils.newArrayList(INITIAL_CAPACITY);
private int mArraySize = 0;
public synchronized int size() {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
index d0fecf0..269b202 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
@@ -23,10 +23,13 @@
import android.graphics.Paint.Align;
import android.os.Message;
import android.text.TextUtils;
+import android.util.AttributeSet;
import android.util.SparseArray;
import android.widget.RelativeLayout;
import com.android.inputmethod.keyboard.PointerTracker;
+import com.android.inputmethod.keyboard.internal.GesturePreviewTrail.GesturePreviewTrailParams;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
@@ -46,29 +49,42 @@
private int mXOrigin;
private int mYOrigin;
- private final SparseArray<PointerTracker> mPointers = new SparseArray<PointerTracker>();
+ private final SparseArray<GesturePreviewTrail> mGesturePreviewTrails =
+ CollectionUtils.newSparseArray();
+ private final GesturePreviewTrailParams mGesturePreviewTrailParams;
private String mGestureFloatingPreviewText;
+ private int mLastPointerX;
+ private int mLastPointerY;
+
private boolean mDrawsGesturePreviewTrail;
private boolean mDrawsGestureFloatingPreviewText;
- private final DrawingHandler mDrawingHandler = new DrawingHandler(this);
+ private final DrawingHandler mDrawingHandler;
private static class DrawingHandler extends StaticInnerHandlerWrapper<PreviewPlacerView> {
private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 0;
+ private static final int MSG_UPDATE_GESTURE_PREVIEW_TRAIL = 1;
- public DrawingHandler(PreviewPlacerView outerInstance) {
+ private final GesturePreviewTrailParams mGesturePreviewTrailParams;
+
+ public DrawingHandler(final PreviewPlacerView outerInstance,
+ final GesturePreviewTrailParams gesturePreviewTrailParams) {
super(outerInstance);
+ mGesturePreviewTrailParams = gesturePreviewTrailParams;
}
@Override
- public void handleMessage(Message msg) {
+ public void handleMessage(final Message msg) {
final PreviewPlacerView placerView = getOuterInstance();
if (placerView == null) return;
switch (msg.what) {
case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT:
placerView.setGestureFloatingPreviewText(null);
break;
+ case MSG_UPDATE_GESTURE_PREVIEW_TRAIL:
+ placerView.invalidate();
+ break;
}
}
@@ -84,15 +100,32 @@
placerView.mGestureFloatingPreviewTextLingerTimeout);
}
+ private void cancelUpdateGestureTrailPreview() {
+ removeMessages(MSG_UPDATE_GESTURE_PREVIEW_TRAIL);
+ }
+
+ public void postUpdateGestureTrailPreview() {
+ cancelUpdateGestureTrailPreview();
+ sendMessageDelayed(obtainMessage(MSG_UPDATE_GESTURE_PREVIEW_TRAIL),
+ mGesturePreviewTrailParams.mUpdateInterval);
+ }
+
public void cancelAllMessages() {
cancelDismissGestureFloatingPreviewText();
+ cancelUpdateGestureTrailPreview();
}
}
- public PreviewPlacerView(Context context, TypedArray keyboardViewAttr) {
+ public PreviewPlacerView(final Context context, final AttributeSet attrs) {
+ this(context, attrs, R.attr.keyboardViewStyle);
+ }
+
+ public PreviewPlacerView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context);
setWillNotDraw(false);
+ final TypedArray keyboardViewAttr = context.obtainStyledAttributes(
+ attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
final int gestureFloatingPreviewTextSize = keyboardViewAttr.getDimensionPixelSize(
R.styleable.KeyboardView_gestureFloatingPreviewTextSize, 0);
mGestureFloatingPreviewTextColor = keyboardViewAttr.getColor(
@@ -117,6 +150,10 @@
R.styleable.KeyboardView_gesturePreviewTrailColor, 0);
final int gesturePreviewTrailWidth = keyboardViewAttr.getDimensionPixelSize(
R.styleable.KeyboardView_gesturePreviewTrailWidth, 0);
+ mGesturePreviewTrailParams = new GesturePreviewTrailParams(keyboardViewAttr);
+ keyboardViewAttr.recycle();
+
+ mDrawingHandler = new DrawingHandler(this, mGesturePreviewTrailParams);
mGesturePaint = new Paint();
mGesturePaint.setAntiAlias(true);
@@ -132,48 +169,60 @@
mTextPaint.setTextSize(gestureFloatingPreviewTextSize);
}
- public void setOrigin(int x, int y) {
+ public void setOrigin(final int x, final int y) {
mXOrigin = x;
mYOrigin = y;
}
- public void setGesturePreviewMode(boolean drawsGesturePreviewTrail,
- boolean drawsGestureFloatingPreviewText) {
+ public void setGesturePreviewMode(final boolean drawsGesturePreviewTrail,
+ final boolean drawsGestureFloatingPreviewText) {
mDrawsGesturePreviewTrail = drawsGesturePreviewTrail;
mDrawsGestureFloatingPreviewText = drawsGestureFloatingPreviewText;
}
- public void invalidatePointer(PointerTracker tracker) {
- synchronized (mPointers) {
- mPointers.put(tracker.mPointerId, tracker);
- // TODO: Should narrow the invalidate region.
- invalidate();
+ public void invalidatePointer(final PointerTracker tracker) {
+ GesturePreviewTrail trail;
+ synchronized (mGesturePreviewTrails) {
+ trail = mGesturePreviewTrails.get(tracker.mPointerId);
+ if (trail == null) {
+ trail = new GesturePreviewTrail(mGesturePreviewTrailParams);
+ mGesturePreviewTrails.put(tracker.mPointerId, trail);
+ }
}
+ trail.addStroke(tracker.getGestureStrokeWithPreviewTrail(), tracker.getDownTime());
+
+ mLastPointerX = tracker.getLastX();
+ mLastPointerY = tracker.getLastY();
+ // TODO: Should narrow the invalidate region.
+ invalidate();
}
@Override
- public void onDraw(Canvas canvas) {
+ public void onDraw(final Canvas canvas) {
super.onDraw(canvas);
- synchronized (mPointers) {
- canvas.translate(mXOrigin, mYOrigin);
- final int trackerCount = mPointers.size();
- boolean hasDrawnFloatingPreviewText = false;
- for (int index = 0; index < trackerCount; index++) {
- final PointerTracker tracker = mPointers.valueAt(index);
- if (mDrawsGesturePreviewTrail) {
- tracker.drawGestureTrail(canvas, mGesturePaint);
- }
- // TODO: Figure out more cleaner way to draw gesture preview text.
- if (mDrawsGestureFloatingPreviewText && !hasDrawnFloatingPreviewText) {
- drawGestureFloatingPreviewText(canvas, tracker, mGestureFloatingPreviewText);
- hasDrawnFloatingPreviewText = true;
+ canvas.translate(mXOrigin, mYOrigin);
+ if (mDrawsGesturePreviewTrail) {
+ boolean needsUpdatingGesturePreviewTrail = false;
+ synchronized (mGesturePreviewTrails) {
+ // Trails count == fingers count that have ever been active.
+ final int trailsCount = mGesturePreviewTrails.size();
+ for (int index = 0; index < trailsCount; index++) {
+ final GesturePreviewTrail trail = mGesturePreviewTrails.valueAt(index);
+ needsUpdatingGesturePreviewTrail |=
+ trail.drawGestureTrail(canvas, mGesturePaint);
}
}
- canvas.translate(-mXOrigin, -mYOrigin);
+ if (needsUpdatingGesturePreviewTrail) {
+ mDrawingHandler.postUpdateGestureTrailPreview();
+ }
}
+ if (mDrawsGestureFloatingPreviewText) {
+ drawGestureFloatingPreviewText(canvas, mGestureFloatingPreviewText);
+ }
+ canvas.translate(-mXOrigin, -mYOrigin);
}
- public void setGestureFloatingPreviewText(String gestureFloatingPreviewText) {
+ public void setGestureFloatingPreviewText(final String gestureFloatingPreviewText) {
mGestureFloatingPreviewText = gestureFloatingPreviewText;
invalidate();
}
@@ -186,15 +235,17 @@
mDrawingHandler.cancelAllMessages();
}
- private void drawGestureFloatingPreviewText(Canvas canvas, PointerTracker tracker,
- String gestureFloatingPreviewText) {
+ private void drawGestureFloatingPreviewText(final Canvas canvas,
+ final String gestureFloatingPreviewText) {
if (TextUtils.isEmpty(gestureFloatingPreviewText)) {
return;
}
final Paint paint = mTextPaint;
- final int lastX = tracker.getLastX();
- final int lastY = tracker.getLastY();
+ // TODO: Figure out how we should deal with the floating preview text with multiple moving
+ // fingers.
+ final int lastX = mLastPointerX;
+ final int lastY = mLastPointerY;
final int textSize = (int)paint.getTextSize();
final int canvasWidth = canvas.getWidth();
diff --git a/java/src/com/android/inputmethod/keyboard/internal/SuddenJumpingTouchEventHandler.java b/java/src/com/android/inputmethod/keyboard/internal/SuddenJumpingTouchEventHandler.java
index 9e2cbec..a591a7a 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/SuddenJumpingTouchEventHandler.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/SuddenJumpingTouchEventHandler.java
@@ -24,7 +24,7 @@
import com.android.inputmethod.keyboard.MainKeyboardView;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.Utils;
+import com.android.inputmethod.latin.ResourceUtils;
import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.research.ResearchLogger;
@@ -53,7 +53,7 @@
public SuddenJumpingTouchEventHandler(Context context, ProcessMotionEvent view) {
mView = view;
- mNeedsSuddenJumpingHack = Boolean.parseBoolean(Utils.getDeviceOverrideValue(
+ mNeedsSuddenJumpingHack = Boolean.parseBoolean(ResourceUtils.getDeviceOverrideValue(
context.getResources(), R.array.sudden_jumping_touch_event_device_list, "false"));
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java b/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java
new file mode 100644
index 0000000..69dc01c
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import com.android.inputmethod.latin.LatinImeLogger;
+
+public class TouchPositionCorrection {
+ private static final int TOUCH_POSITION_CORRECTION_RECORD_SIZE = 3;
+
+ public boolean mEnabled;
+ public float[] mXs;
+ public float[] mYs;
+ public float[] mRadii;
+
+ public void load(final String[] data) {
+ final int dataLength = data.length;
+ if (dataLength % TOUCH_POSITION_CORRECTION_RECORD_SIZE != 0) {
+ if (LatinImeLogger.sDBG) {
+ throw new RuntimeException(
+ "the size of touch position correction data is invalid");
+ }
+ return;
+ }
+
+ final int length = dataLength / TOUCH_POSITION_CORRECTION_RECORD_SIZE;
+ mXs = new float[length];
+ mYs = new float[length];
+ mRadii = new float[length];
+ try {
+ for (int i = 0; i < dataLength; ++i) {
+ final int type = i % TOUCH_POSITION_CORRECTION_RECORD_SIZE;
+ final int index = i / TOUCH_POSITION_CORRECTION_RECORD_SIZE;
+ final float value = Float.parseFloat(data[i]);
+ if (type == 0) {
+ mXs[index] = value;
+ } else if (type == 1) {
+ mYs[index] = value;
+ } else {
+ mRadii[index] = value;
+ }
+ }
+ } catch (NumberFormatException e) {
+ if (LatinImeLogger.sDBG) {
+ throw new RuntimeException(
+ "the number format for touch position correction data is invalid");
+ }
+ mXs = null;
+ mYs = null;
+ mRadii = null;
+ }
+ }
+
+ // TODO: Remove this method.
+ public void setEnabled(final boolean enabled) {
+ mEnabled = enabled;
+ }
+
+ public boolean isValid() {
+ return mEnabled && mXs != null && mYs != null && mRadii != null
+ && mXs.length > 0 && mYs.length > 0 && mRadii.length > 0;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
index f8f1395..4b47a26 100644
--- a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
+++ b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
@@ -91,7 +91,7 @@
}
final String[] prefSubtypeArray = prefSubtypes.split(PREF_SUBTYPE_SEPARATOR);
final ArrayList<InputMethodSubtype> subtypesList =
- new ArrayList<InputMethodSubtype>(prefSubtypeArray.length);
+ CollectionUtils.newArrayList(prefSubtypeArray.length);
for (final String prefSubtype : prefSubtypeArray) {
final InputMethodSubtype subtype = createAdditionalSubtype(prefSubtype);
if (subtype.getNameResId() == SubtypeLocale.UNKNOWN_KEYBOARD_LAYOUT) {
diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
index 779a388..d01592a 100644
--- a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
+++ b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
@@ -89,7 +89,7 @@
super(context, android.R.layout.simple_spinner_item);
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- final TreeSet<SubtypeLocaleItem> items = new TreeSet<SubtypeLocaleItem>();
+ final TreeSet<SubtypeLocaleItem> items = CollectionUtils.newTreeSet();
final InputMethodInfo imi = ImfUtils.getInputMethodInfoOfThisIme(context);
final int count = imi.getSubtypeCount();
for (int i = 0; i < count; i++) {
@@ -533,7 +533,7 @@
private InputMethodSubtype[] getSubtypes() {
final PreferenceGroup group = getPreferenceScreen();
- final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
+ final ArrayList<InputMethodSubtype> subtypes = CollectionUtils.newArrayList();
final int count = group.getPreferenceCount();
for (int i = 0; i < count; i++) {
final Preference pref = group.getPreference(i);
diff --git a/java/src/com/android/inputmethod/latin/AutoCorrection.java b/java/src/com/android/inputmethod/latin/AutoCorrection.java
index 0481668..01ba300 100644
--- a/java/src/com/android/inputmethod/latin/AutoCorrection.java
+++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java
@@ -39,7 +39,6 @@
}
final CharSequence lowerCasedWord = word.toString().toLowerCase();
for (final String key : dictionaries.keySet()) {
- if (key.equals(Dictionary.TYPE_WHITELIST)) continue;
final Dictionary dictionary = dictionaries.get(key);
// It's unclear how realistically 'dictionary' can be null, but the monkey is somehow
// managing to get null in here. Presumably the language is changing to a language with
@@ -64,7 +63,6 @@
}
int maxFreq = -1;
for (final String key : dictionaries.keySet()) {
- if (key.equals(Dictionary.TYPE_WHITELIST)) continue;
final Dictionary dictionary = dictionaries.get(key);
if (null == dictionary) continue;
final int tempFreq = dictionary.getFrequency(word);
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index f0f5cd3..8909526 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.text.TextUtils;
+import android.util.SparseArray;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
@@ -51,6 +52,7 @@
private static final int TYPED_LETTER_MULTIPLIER = 2;
private long mNativeDict;
+ private final Locale mLocale;
private final int[] mInputCodePoints = new int[MAX_WORD_LENGTH];
// TODO: The below should be int[] mOutputCodePoints
private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_RESULTS];
@@ -59,7 +61,25 @@
private final int[] mOutputTypes = new int[MAX_RESULTS];
private final boolean mUseFullEditDistance;
- private final DicTraverseSession mDicTraverseSession;
+
+ private final SparseArray<DicTraverseSession> mDicTraverseSessions =
+ CollectionUtils.newSparseArray();
+
+ // TODO: There should be a way to remove used DicTraverseSession objects from
+ // {@code mDicTraverseSessions}.
+ private DicTraverseSession getTraverseSession(int traverseSessionId) {
+ synchronized(mDicTraverseSessions) {
+ DicTraverseSession traverseSession = mDicTraverseSessions.get(traverseSessionId);
+ if (traverseSession == null) {
+ traverseSession = mDicTraverseSessions.get(traverseSessionId);
+ if (traverseSession == null) {
+ traverseSession = new DicTraverseSession(mLocale, mNativeDict);
+ mDicTraverseSessions.put(traverseSessionId, traverseSession);
+ }
+ }
+ return traverseSession;
+ }
+ }
/**
* Constructor for the binary dictionary. This is supposed to be called from the
@@ -76,10 +96,9 @@
final String filename, final long offset, final long length,
final boolean useFullEditDistance, final Locale locale, final String dictType) {
super(dictType);
+ mLocale = locale;
mUseFullEditDistance = useFullEditDistance;
loadDictionary(filename, offset, length);
- mDicTraverseSession = new DicTraverseSession(locale);
- mDicTraverseSession.initSession(mNativeDict);
}
static {
@@ -109,8 +128,15 @@
@Override
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
final CharSequence prevWord, final ProximityInfo proximityInfo) {
+ return getSuggestionsWithSessionId(composer, prevWord, proximityInfo, 0);
+ }
+
+ @Override
+ public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
+ final CharSequence prevWord, final ProximityInfo proximityInfo, int sessionId) {
if (!isValidDictionary()) return null;
- Arrays.fill(mInputCodePoints, WordComposer.NOT_A_CODE);
+
+ Arrays.fill(mInputCodePoints, Constants.NOT_A_CODE);
// TODO: toLowerCase in the native code
final int[] prevWordCodePointArray = (null == prevWord)
? null : StringUtils.toCodePointArray(prevWord.toString());
@@ -128,18 +154,18 @@
final int codesSize = isGesture ? ips.getPointerSize() : composerSize;
// proximityInfo and/or prevWordForBigrams may not be null.
final int tmpCount = getSuggestionsNative(mNativeDict,
- proximityInfo.getNativeProximityInfo(), mDicTraverseSession.getSession(),
+ proximityInfo.getNativeProximityInfo(), getTraverseSession(sessionId).getSession(),
ips.getXCoordinates(), ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(),
mInputCodePoints, codesSize, 0 /* commitPoint */, isGesture, prevWordCodePointArray,
mUseFullEditDistance, mOutputChars, mOutputScores, mSpaceIndices, mOutputTypes);
final int count = Math.min(tmpCount, MAX_PREDICTIONS);
- final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>();
+ final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
for (int j = 0; j < count; ++j) {
if (composerSize > 0 && mOutputScores[j] < 1) break;
final int start = j * MAX_WORD_LENGTH;
int len = 0;
- while (len < MAX_WORD_LENGTH && mOutputChars[start + len] != 0) {
+ while (len < MAX_WORD_LENGTH && mOutputChars[start + len] != 0) {
++len;
}
if (len > 0) {
@@ -186,12 +212,20 @@
}
@Override
- public synchronized void close() {
- mDicTraverseSession.close();
+ public void close() {
+ synchronized (mDicTraverseSessions) {
+ final int sessionsSize = mDicTraverseSessions.size();
+ for (int index = 0; index < sessionsSize; ++index) {
+ final DicTraverseSession traverseSession = mDicTraverseSessions.valueAt(index);
+ if (traverseSession != null) {
+ traverseSession.close();
+ }
+ }
+ }
closeInternal();
}
- private void closeInternal() {
+ private synchronized void closeInternal() {
if (mNativeDict != 0) {
closeNative(mNativeDict);
mNativeDict = 0;
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index 236c198..799aea8 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -99,7 +99,7 @@
}
try {
- final List<WordListInfo> list = new ArrayList<WordListInfo>();
+ final List<WordListInfo> list = CollectionUtils.newArrayList();
do {
final String wordListId = c.getString(0);
final String wordListLocale = c.getString(1);
@@ -267,7 +267,7 @@
final ContentResolver resolver = context.getContentResolver();
final List<WordListInfo> idList = getWordListWordListInfos(locale, context,
hasDefaultWordList);
- final List<AssetFileAddress> fileAddressList = new ArrayList<AssetFileAddress>();
+ final List<AssetFileAddress> fileAddressList = CollectionUtils.newArrayList();
for (WordListInfo id : idList) {
final AssetFileAddress afd = cacheWordList(id.mId, id.mLocale, resolver, context);
if (null != afd) {
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index 063243e..e1cb195 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -16,6 +16,8 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
+
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -23,6 +25,10 @@
import android.util.Log;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
@@ -51,6 +57,9 @@
private static final String MAIN_DICTIONARY_CATEGORY = "main";
public static final String ID_CATEGORY_SEPARATOR = ":";
+ // The key considered to read the version attribute in a dictionary file.
+ private static String VERSION_KEY = "version";
+
// Prevents this from being instantiated
private BinaryDictionaryGetter() {}
@@ -254,8 +263,7 @@
final Context context) {
final File[] directoryList = getCachedDirectoryList(context);
if (null == directoryList) return EMPTY_FILE_ARRAY;
- final HashMap<String, FileAndMatchLevel> cacheFiles =
- new HashMap<String, FileAndMatchLevel>();
+ final HashMap<String, FileAndMatchLevel> cacheFiles = CollectionUtils.newHashMap();
for (File directory : directoryList) {
if (!directory.isDirectory()) continue;
final String dirLocale = getWordListIdFromFileName(directory.getName());
@@ -336,6 +344,54 @@
return MAIN_DICTIONARY_CATEGORY.equals(idArray[0]);
}
+ // ## HACK ## we prevent usage of a dictionary before version 18 for English only. The reason
+ // for this is, since those do not include whitelist entries, the new code with an old version
+ // of the dictionary would lose whitelist functionality.
+ private static boolean hackCanUseDictionaryFile(final Locale locale, final File f) {
+ // Only for English - other languages didn't have a whitelist, hence this
+ // ad-hock ## HACK ##
+ if (!Locale.ENGLISH.getLanguage().equals(locale.getLanguage())) return true;
+
+ FileInputStream inStream = null;
+ try {
+ // Read the version of the file
+ inStream = new FileInputStream(f);
+ final ByteBuffer buffer = inStream.getChannel().map(
+ FileChannel.MapMode.READ_ONLY, 0, f.length());
+ final int magic = buffer.getInt();
+ if (magic != BinaryDictInputOutput.VERSION_2_MAGIC_NUMBER) {
+ return false;
+ }
+ final int formatVersion = buffer.getInt();
+ final int headerSize = buffer.getInt();
+ final HashMap<String, String> options = CollectionUtils.newHashMap();
+ BinaryDictInputOutput.populateOptions(buffer, headerSize, options);
+
+ final String version = options.get(VERSION_KEY);
+ if (null == version) {
+ // No version in the options : the format is unexpected
+ return false;
+ }
+ // Version 18 is the first one to include the whitelist
+ // Obviously this is a big ## HACK ##
+ return Integer.parseInt(version) >= 18;
+ } catch (java.io.FileNotFoundException e) {
+ return false;
+ } catch (java.io.IOException e) {
+ return false;
+ } catch (NumberFormatException e) {
+ return false;
+ } finally {
+ if (inStream != null) {
+ try {
+ inStream.close();
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
+ }
+ }
+
/**
* Returns a list of file addresses for a given locale, trying relevant methods in order.
*
@@ -362,18 +418,19 @@
final DictPackSettings dictPackSettings = new DictPackSettings(context);
boolean foundMainDict = false;
- final ArrayList<AssetFileAddress> fileList = new ArrayList<AssetFileAddress>();
+ final ArrayList<AssetFileAddress> fileList = CollectionUtils.newArrayList();
// cachedWordLists may not be null, see doc for getCachedDictionaryList
for (final File f : cachedWordLists) {
final String wordListId = getWordListIdFromFileName(f.getName());
- if (isMainWordListId(wordListId)) {
+ final boolean canUse = f.canRead() && hackCanUseDictionaryFile(locale, f);
+ if (canUse && isMainWordListId(wordListId)) {
foundMainDict = true;
}
if (!dictPackSettings.isWordListActive(wordListId)) continue;
- if (f.canRead()) {
+ if (canUse) {
fileList.add(AssetFileAddress.makeFromFileName(f.getPath()));
} else {
- Log.e(TAG, "Found a cached dictionary file but cannot read it");
+ Log.e(TAG, "Found a cached dictionary file but cannot read or use it");
}
}
diff --git a/java/src/com/android/inputmethod/latin/CollectionUtils.java b/java/src/com/android/inputmethod/latin/CollectionUtils.java
new file mode 100644
index 0000000..c75f2df
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/CollectionUtils.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public final class CollectionUtils {
+ private CollectionUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static <K,V> HashMap<K,V> newHashMap() {
+ return new HashMap<K,V>();
+ }
+
+ public static <K,V> TreeMap<K,V> newTreeMap() {
+ return new TreeMap<K,V>();
+ }
+
+ public static <K, V> Map<K,V> newSynchronizedTreeMap() {
+ final TreeMap<K,V> treeMap = newTreeMap();
+ return Collections.synchronizedMap(treeMap);
+ }
+
+ public static <K,V> ConcurrentHashMap<K,V> newConcurrentHashMap() {
+ return new ConcurrentHashMap<K,V>();
+ }
+
+ public static <E> HashSet<E> newHashSet() {
+ return new HashSet<E>();
+ }
+
+ public static <E> TreeSet<E> newTreeSet() {
+ return new TreeSet<E>();
+ }
+
+ public static <E> ArrayList<E> newArrayList() {
+ return new ArrayList<E>();
+ }
+
+ public static <E> ArrayList<E> newArrayList(final int initialCapacity) {
+ return new ArrayList<E>(initialCapacity);
+ }
+
+ public static <E> ArrayList<E> newArrayList(final Collection<E> collection) {
+ return new ArrayList<E>(collection);
+ }
+
+ public static <E> LinkedList<E> newLinkedList() {
+ return new LinkedList<E>();
+ }
+
+ public static <E> CopyOnWriteArrayList<E> newCopyOnWriteArrayList() {
+ return new CopyOnWriteArrayList<E>();
+ }
+
+ public static <E> CopyOnWriteArrayList<E> newCopyOnWriteArrayList(
+ final Collection<E> collection) {
+ return new CopyOnWriteArrayList<E>(collection);
+ }
+
+ public static <E> CopyOnWriteArrayList<E> newCopyOnWriteArrayList(final E[] array) {
+ return new CopyOnWriteArrayList<E>(array);
+ }
+
+ public static <E> SparseArray<E> newSparseArray() {
+ return new SparseArray<E>();
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index 1242967..d71c0f9 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -128,6 +128,13 @@
}
}
+ public static final int NOT_A_CODE = -1;
+
+ // See {@link KeyboardActionListener.Adapter#isInvalidCoordinate(int)}.
+ public static final int NOT_A_COORDINATE = -1;
+ public static final int SUGGESTION_STRIP_COORDINATE = -2;
+ public static final int SPELL_CHECKER_COORDINATE = -3;
+
private Constants() {
// This utility class is not publicly instantiable.
}
diff --git a/java/src/com/android/inputmethod/latin/DicTraverseSession.java b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
index c768153..359da72 100644
--- a/java/src/com/android/inputmethod/latin/DicTraverseSession.java
+++ b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
@@ -22,6 +22,7 @@
static {
JniUtils.loadNativeLibrary();
}
+
private native long setDicTraverseSessionNative(String locale);
private native void initDicTraverseSessionNative(long nativeDicTraverseSession,
long dictionary, int[] previousWord, int previousWordLength);
@@ -29,9 +30,10 @@
private long mNativeDicTraverseSession;
- public DicTraverseSession(Locale locale) {
+ public DicTraverseSession(Locale locale, long dictionary) {
mNativeDicTraverseSession = createNativeDicTraverseSession(
locale != null ? locale.toString() : "");
+ initSession(dictionary);
}
public long getSession() {
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index 60fe17b..88d0c09 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -42,7 +42,6 @@
public static final String TYPE_USER = "user";
// User history dictionary internal to LatinIME.
public static final String TYPE_USER_HISTORY = "history";
- public static final String TYPE_WHITELIST ="whitelist";
protected final String mDictType;
public Dictionary(final String dictType) {
@@ -62,6 +61,13 @@
abstract public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
final CharSequence prevWord, final ProximityInfo proximityInfo);
+ // The default implementation of this method ignores sessionId.
+ // Subclasses that want to use sessionId need to override this method.
+ public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
+ final CharSequence prevWord, final ProximityInfo proximityInfo, int sessionId) {
+ return getSuggestions(composer, prevWord, proximityInfo);
+ }
+
/**
* Checks if the given word occurs in the dictionary
* @param word the word to search for. The search should be case-insensitive.
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
index ee80f25..4acab6b 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -35,22 +35,22 @@
public DictionaryCollection(final String dictType) {
super(dictType);
- mDictionaries = new CopyOnWriteArrayList<Dictionary>();
+ mDictionaries = CollectionUtils.newCopyOnWriteArrayList();
}
public DictionaryCollection(final String dictType, Dictionary... dictionaries) {
super(dictType);
if (null == dictionaries) {
- mDictionaries = new CopyOnWriteArrayList<Dictionary>();
+ mDictionaries = CollectionUtils.newCopyOnWriteArrayList();
} else {
- mDictionaries = new CopyOnWriteArrayList<Dictionary>(dictionaries);
+ mDictionaries = CollectionUtils.newCopyOnWriteArrayList(dictionaries);
mDictionaries.removeAll(Collections.singleton(null));
}
}
public DictionaryCollection(final String dictType, Collection<Dictionary> dictionaries) {
super(dictType);
- mDictionaries = new CopyOnWriteArrayList<Dictionary>(dictionaries);
+ mDictionaries = CollectionUtils.newCopyOnWriteArrayList(dictionaries);
mDictionaries.removeAll(Collections.singleton(null));
}
@@ -63,7 +63,7 @@
// dictionary and add the rest to it if not null, hence the get(0)
ArrayList<SuggestedWordInfo> suggestions = dictionaries.get(0).getSuggestions(composer,
prevWord, proximityInfo);
- if (null == suggestions) suggestions = new ArrayList<SuggestedWordInfo>();
+ if (null == suggestions) suggestions = CollectionUtils.newArrayList();
final int length = dictionaries.size();
for (int i = 1; i < length; ++ i) {
final ArrayList<SuggestedWordInfo> sugg = dictionaries.get(i).getSuggestions(composer,
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index 06a5f4b..cdd01d0 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -53,7 +53,7 @@
createBinaryDictionary(context, locale));
}
- final LinkedList<Dictionary> dictList = new LinkedList<Dictionary>();
+ final LinkedList<Dictionary> dictList = CollectionUtils.newLinkedList();
final ArrayList<AssetFileAddress> assetFileList =
BinaryDictionaryGetter.getDictionaryFiles(locale, context);
if (null != assetFileList) {
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 016530a..8a509be 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -62,7 +62,7 @@
* that filename.
*/
private static final HashMap<String, DictionaryController> sSharedDictionaryControllers =
- new HashMap<String, DictionaryController>();
+ CollectionUtils.newHashMap();
/** The application context. */
protected final Context mContext;
@@ -159,9 +159,9 @@
* the native side.
*/
public void clearFusionDictionary() {
+ final HashMap<String, String> attributes = CollectionUtils.newHashMap();
mFusionDictionary = new FusionDictionary(new Node(),
- new FusionDictionary.DictionaryOptions(new HashMap<String, String>(), false,
- false));
+ new FusionDictionary.DictionaryOptions(attributes, false, false));
}
/**
@@ -172,12 +172,12 @@
// considering performance regression.
protected void addWord(final String word, final String shortcutTarget, final int frequency) {
if (shortcutTarget == null) {
- mFusionDictionary.add(word, frequency, null);
+ mFusionDictionary.add(word, frequency, null, false /* isNotAWord */);
} else {
// TODO: Do this in the subclass, with this class taking an arraylist.
- final ArrayList<WeightedString> shortcutTargets = new ArrayList<WeightedString>();
+ final ArrayList<WeightedString> shortcutTargets = CollectionUtils.newArrayList();
shortcutTargets.add(new WeightedString(shortcutTarget, frequency));
- mFusionDictionary.add(word, frequency, shortcutTargets);
+ mFusionDictionary.add(word, frequency, shortcutTargets, false /* isNotAWord */);
}
}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index d101aaf..8a38d1e 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -19,7 +19,6 @@
import android.content.Context;
import android.text.TextUtils;
-import com.android.inputmethod.keyboard.KeyDetector;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
@@ -231,7 +230,7 @@
childNode.mTerminal = true;
if (isShortcutOnly) {
if (null == childNode.mShortcutTargets) {
- childNode.mShortcutTargets = new ArrayList<char[]>();
+ childNode.mShortcutTargets = CollectionUtils.newArrayList();
}
childNode.mShortcutTargets.add(shortcutTarget.toCharArray());
} else {
@@ -251,7 +250,7 @@
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
final CharSequence prevWord, final ProximityInfo proximityInfo) {
if (reloadDictionaryIfRequired()) return null;
- if (composer.size() <= 1) {
+ if (composer.size() > 1) {
if (composer.size() >= BinaryDictionary.MAX_WORD_LENGTH) {
return null;
}
@@ -260,7 +259,7 @@
return suggestions;
} else {
if (TextUtils.isEmpty(prevWord)) return null;
- final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>();
+ final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
runBigramReverseLookUp(prevWord, suggestions);
return suggestions;
}
@@ -279,7 +278,7 @@
protected ArrayList<SuggestedWordInfo> getWordsInner(final WordComposer codes,
final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) {
- final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>();
+ final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
mInputLength = codes.size();
if (mCodes.length < mInputLength) mCodes = new int[mInputLength][];
final InputPointers ips = codes.getInputPointers();
@@ -292,9 +291,9 @@
mCodes[i] = new int[ProximityInfo.MAX_PROXIMITY_CHARS_SIZE];
}
final int x = xCoordinates != null && i < xCoordinates.length ?
- xCoordinates[i] : WordComposer.NOT_A_COORDINATE;
+ xCoordinates[i] : Constants.NOT_A_COORDINATE;
final int y = xCoordinates != null && i < yCoordinates.length ?
- yCoordinates[i] : WordComposer.NOT_A_COORDINATE;
+ yCoordinates[i] : Constants.NOT_A_COORDINATE;
proximityInfo.fillArrayWithNearestKeyCodes(x, y, codes.getCodeAt(i), mCodes[i]);
}
mMaxDepth = mInputLength * 3;
@@ -487,7 +486,7 @@
for (int j = 0; j < alternativesSize; j++) {
final int addedAttenuation = (j > 0 ? 1 : 2);
final int currentChar = currentChars[j];
- if (currentChar == KeyDetector.NOT_A_CODE) {
+ if (currentChar == Constants.NOT_A_CODE) {
break;
}
if (currentChar == lowerC || currentChar == c) {
@@ -551,7 +550,7 @@
Node secondWord = searchWord(mRoots, word2, 0, null);
LinkedList<NextWord> bigrams = firstWord.mNGrams;
if (bigrams == null || bigrams.size() == 0) {
- firstWord.mNGrams = new LinkedList<NextWord>();
+ firstWord.mNGrams = CollectionUtils.newLinkedList();
bigrams = firstWord.mNGrams;
} else {
for (NextWord nw : bigrams) {
diff --git a/java/src/com/android/inputmethod/latin/ImfUtils.java b/java/src/com/android/inputmethod/latin/ImfUtils.java
index 1461c02..2674e45 100644
--- a/java/src/com/android/inputmethod/latin/ImfUtils.java
+++ b/java/src/com/android/inputmethod/latin/ImfUtils.java
@@ -29,7 +29,7 @@
/**
* Utility class for Input Method Framework
*/
-public class ImfUtils {
+public final class ImfUtils {
private ImfUtils() {
// This utility class is not publicly instantiable.
}
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
index e561f59..7bcda9b 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -29,10 +29,12 @@
final public boolean mInputTypeNoAutoCorrect;
final public boolean mIsSettingsSuggestionStripOn;
final public boolean mApplicationSpecifiedCompletionOn;
+ final private int mInputType;
public InputAttributes(final EditorInfo editorInfo, final boolean isFullscreenMode) {
final int inputType = null != editorInfo ? editorInfo.inputType : 0;
final int inputClass = inputType & InputType.TYPE_MASK_CLASS;
+ mInputType = inputType;
if (inputClass != InputType.TYPE_CLASS_TEXT) {
// If we are not looking at a TYPE_CLASS_TEXT field, the following strange
// cases may arise, so we do a couple sanity checks for them. If it's a
@@ -93,6 +95,10 @@
}
}
+ public boolean isSameInputType(final EditorInfo editorInfo) {
+ return editorInfo.inputType == mInputType;
+ }
+
@SuppressWarnings("unused")
private void dumpFlags(final int inputType) {
Log.i(TAG, "Input class:");
diff --git a/java/src/com/android/inputmethod/latin/InputPointers.java b/java/src/com/android/inputmethod/latin/InputPointers.java
index cbc916a..ff2feb5 100644
--- a/java/src/com/android/inputmethod/latin/InputPointers.java
+++ b/java/src/com/android/inputmethod/latin/InputPointers.java
@@ -93,7 +93,7 @@
}
mXCoordinates.append(xCoordinates, startPos, length);
mYCoordinates.append(yCoordinates, startPos, length);
- mPointerIds.fill(pointerId, startPos, length);
+ mPointerIds.fill(pointerId, mPointerIds.getLength(), length);
mTimes.append(times, startPos, length);
}
@@ -124,4 +124,10 @@
public int[] getTimes() {
return mTimes.getPrimitiveArray();
}
+
+ @Override
+ public String toString() {
+ return "size=" + getPointerSize() + " id=" + mPointerIds + " time=" + mTimes
+ + " x=" + mXCoordinates + " y=" + mYCoordinates;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/InputTypeUtils.java b/java/src/com/android/inputmethod/latin/InputTypeUtils.java
index 40c3b76..500866a 100644
--- a/java/src/com/android/inputmethod/latin/InputTypeUtils.java
+++ b/java/src/com/android/inputmethod/latin/InputTypeUtils.java
@@ -18,7 +18,7 @@
import android.text.InputType;
-public class InputTypeUtils implements InputType {
+public final class InputTypeUtils implements InputType {
private static final int WEB_TEXT_PASSWORD_INPUT_TYPE =
TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_WEB_PASSWORD;
private static final int WEB_TEXT_EMAIL_ADDRESS_INPUT_TYPE =
diff --git a/java/src/com/android/inputmethod/latin/JniUtils.java b/java/src/com/android/inputmethod/latin/JniUtils.java
index 86a3826..f930599 100644
--- a/java/src/com/android/inputmethod/latin/JniUtils.java
+++ b/java/src/com/android/inputmethod/latin/JniUtils.java
@@ -20,7 +20,7 @@
import com.android.inputmethod.latin.define.JniLibName;
-public class JniUtils {
+public final class JniUtils {
private static final String TAG = JniUtils.class.getSimpleName();
private JniUtils() {
diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java
index bb39ce4..dd73a97 100644
--- a/java/src/com/android/inputmethod/latin/LastComposedWord.java
+++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java
@@ -38,12 +38,12 @@
// an auto-correction.
public static final int COMMIT_TYPE_CANCEL_AUTO_CORRECT = 3;
- public static final int NOT_A_SEPARATOR = -1;
+ public static final String NOT_A_SEPARATOR = "";
public final int[] mPrimaryKeyCodes;
public final String mTypedWord;
public final String mCommittedWord;
- public final int mSeparatorCode;
+ public final String mSeparatorString;
public final CharSequence mPrevWord;
public final InputPointers mInputPointers = new InputPointers(BinaryDictionary.MAX_WORD_LENGTH);
@@ -56,14 +56,14 @@
// immutable. Do not fiddle with their contents after you passed them to this constructor.
public LastComposedWord(final int[] primaryKeyCodes, final InputPointers inputPointers,
final String typedWord, final String committedWord,
- final int separatorCode, final CharSequence prevWord) {
+ final String separatorString, final CharSequence prevWord) {
mPrimaryKeyCodes = primaryKeyCodes;
if (inputPointers != null) {
mInputPointers.copy(inputPointers);
}
mTypedWord = typedWord;
mCommittedWord = committedWord;
- mSeparatorCode = separatorCode;
+ mSeparatorString = separatorString;
mActive = true;
mPrevWord = prevWord;
}
@@ -80,7 +80,7 @@
return TextUtils.equals(mTypedWord, mCommittedWord);
}
- public static int getSeparatorLength(final int separatorCode) {
- return NOT_A_SEPARATOR == separatorCode ? 0 : 1;
+ public static int getSeparatorLength(final String separatorString) {
+ return StringUtils.codePointCount(separatorString);
}
}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 446d44e..39c3a80 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -361,7 +361,7 @@
mPrefs = prefs;
LatinImeLogger.init(this, prefs);
if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.getInstance().init(this, prefs, mKeyboardSwitcher);
+ ResearchLogger.getInstance().init(this, prefs);
}
InputMethodManagerCompatWrapper.init(this);
SubtypeSwitcher.init(this);
@@ -381,18 +381,7 @@
ImfUtils.setAdditionalInputMethodSubtypes(this, mCurrentSettings.getAdditionalSubtypes());
- Utils.GCUtils.getInstance().reset();
- boolean tryGC = true;
- // Shouldn't this be removed? I think that from Honeycomb on, the GC is now actually working
- // as expected and this code is useless.
- for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
- try {
- initSuggest();
- tryGC = false;
- } catch (OutOfMemoryError e) {
- tryGC = Utils.GCUtils.getInstance().tryGCOrWait("InitSuggest", e);
- }
- }
+ initSuggest();
mDisplayOrientation = res.getConfiguration().orientation;
@@ -416,7 +405,8 @@
}
// Has to be package-visible for unit tests
- /* package */ void loadSettings() {
+ /* package for test */
+ void loadSettings() {
// Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged()
// is not guaranteed. It may even be called at the same time on a different thread.
if (null == mPrefs) mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
@@ -540,7 +530,10 @@
@Override
public void onConfigurationChanged(Configuration conf) {
- mSubtypeSwitcher.onConfigurationChanged(conf);
+ // System locale has been changed. Needs to reload keyboard.
+ if (mSubtypeSwitcher.onConfigurationChanged(conf, this)) {
+ loadKeyboard();
+ }
// If orientation changed while predicting, commit the change
if (mDisplayOrientation != conf.orientation) {
mDisplayOrientation = conf.orientation;
@@ -607,6 +600,7 @@
// Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged()
// is not guaranteed. It may even be called at the same time on a different thread.
mSubtypeSwitcher.updateSubtype(subtype);
+ loadKeyboard();
}
private void onStartInputInternal(EditorInfo editorInfo, boolean restarting) {
@@ -670,8 +664,20 @@
accessUtils.onStartInputViewInternal(mainKeyboardView, editorInfo, restarting);
}
- if (!restarting) {
- mSubtypeSwitcher.updateParametersOnStartInputView();
+ final boolean selectionChanged = mLastSelectionStart != editorInfo.initialSelStart
+ || mLastSelectionEnd != editorInfo.initialSelEnd;
+ final boolean inputTypeChanged = !mCurrentSettings.isSameInputType(editorInfo);
+ final boolean isDifferentTextField = !restarting || inputTypeChanged;
+ if (isDifferentTextField) {
+ final boolean currentSubtypeEnabled = mSubtypeSwitcher
+ .updateParametersOnStartInputViewAndReturnIfCurrentSubtypeEnabled();
+ if (!currentSubtypeEnabled) {
+ // Current subtype is disabled. Needs to update subtype and keyboard.
+ final InputMethodSubtype newSubtype = ImfUtils.getCurrentInputMethodSubtype(
+ this, mSubtypeSwitcher.getNoLanguageSubtype());
+ mSubtypeSwitcher.updateSubtype(newSubtype);
+ loadKeyboard();
+ }
}
// The EditorInfo might have a flag that affects fullscreen mode.
@@ -679,9 +685,7 @@
updateFullscreenMode();
mApplicationSpecifiedCompletions = null;
- final boolean selectionChanged = mLastSelectionStart != editorInfo.initialSelStart
- || mLastSelectionEnd != editorInfo.initialSelEnd;
- if (!restarting || selectionChanged) {
+ if (isDifferentTextField || selectionChanged) {
// If the selection changed, we reset the input state. Essentially, we come here with
// restarting == true when the app called setText() or similar. We should reset the
// state if the app set the text to something else, but keep it if it set a suggestion
@@ -696,7 +700,7 @@
}
}
- if (!restarting) {
+ if (isDifferentTextField) {
mainKeyboardView.closing();
loadSettings();
@@ -905,13 +909,13 @@
}
}
}
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_onDisplayCompletions(applicationSpecifiedCompletions);
- }
if (!mCurrentSettings.isApplicationSpecifiedCompletionsOn()) return;
mApplicationSpecifiedCompletions = applicationSpecifiedCompletions;
if (applicationSpecifiedCompletions == null) {
clearSuggestionStrip();
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_onDisplayCompletions(null);
+ }
return;
}
@@ -933,6 +937,9 @@
// this case? This says to keep whatever the user typed.
mWordComposer.setAutoCorrection(mWordComposer.getTypedWord());
setSuggestionStripShown(true);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_onDisplayCompletions(applicationSpecifiedCompletions);
+ }
}
private void setSuggestionStripShownInternal(boolean shown, boolean needsInputViewShown) {
@@ -1048,18 +1055,15 @@
mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
}
- private void commitTyped(final int separatorCode) {
+ private void commitTyped(final String separatorString) {
if (!mWordComposer.isComposingWord()) return;
final CharSequence typedWord = mWordComposer.getTypedWord();
if (typedWord.length() > 0) {
mConnection.commitText(typedWord, 1);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_commitText(typedWord);
- }
final CharSequence prevWord = addToUserHistoryDictionary(typedWord);
mLastComposedWord = mWordComposer.commitWord(
LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, typedWord.toString(),
- separatorCode, prevWord);
+ separatorString, prevWord);
}
}
@@ -1091,18 +1095,27 @@
return mConnection.getCursorCapsMode(inputType);
}
+ // Factor in auto-caps and manual caps and compute the current caps mode.
+ private int getActualCapsMode() {
+ final int manual = mKeyboardSwitcher.getManualCapsMode();
+ if (manual != WordComposer.CAPS_MODE_OFF) return manual;
+ final int auto = getCurrentAutoCapsState();
+ if (0 != (auto & TextUtils.CAP_MODE_CHARACTERS)) {
+ return WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED;
+ }
+ if (0 != auto) return WordComposer.CAPS_MODE_AUTO_SHIFTED;
+ return WordComposer.CAPS_MODE_OFF;
+ }
+
private void swapSwapperAndSpace() {
CharSequence lastTwo = mConnection.getTextBeforeCursor(2, 0);
// It is guaranteed lastTwo.charAt(1) is a swapper - else this method is not called.
if (lastTwo != null && lastTwo.length() == 2
&& lastTwo.charAt(0) == Keyboard.CODE_SPACE) {
mConnection.deleteSurroundingText(2, 0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(2);
- }
mConnection.commitText(lastTwo.charAt(1) + " ", 1);
if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_swapSwapperAndSpaceWhileInBatchEdit();
+ ResearchLogger.latinIME_swapSwapperAndSpace();
}
mKeyboardSwitcher.updateShiftState();
}
@@ -1119,9 +1132,6 @@
mHandler.cancelDoubleSpacesTimer();
mConnection.deleteSurroundingText(2, 0);
mConnection.commitText(". ", 1);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_doubleSpaceAutoPeriod();
- }
mKeyboardSwitcher.updateShiftState();
return true;
}
@@ -1185,9 +1195,6 @@
private void performEditorAction(int actionId) {
mConnection.performEditorAction(actionId);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_performEditorAction(actionId);
- }
}
private void handleLanguageSwitchKey() {
@@ -1224,6 +1231,9 @@
// For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}.
if (code >= '0' && code <= '9') {
super.sendKeyChar((char)code);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_sendKeyCodePoint(code);
+ }
return;
}
@@ -1240,9 +1250,6 @@
final String text = new String(new int[] { code }, 0, 1);
mConnection.commitText(text, text.length());
}
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_sendKeyCodePoint(code);
- }
}
// Implementation of {@link KeyboardActionListener}.
@@ -1254,11 +1261,6 @@
}
mLastKeyTime = when;
mConnection.beginBatchEdit();
-
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_onCodeInput(primaryCode, x, y);
- }
-
final KeyboardSwitcher switcher = mKeyboardSwitcher;
// The space state depends only on the last character pressed and its own previous
// state. Here, we revert the space state to neutral if the key is actually modifying
@@ -1291,7 +1293,7 @@
onSettingsKeyPressed();
break;
case Keyboard.CODE_SHORTCUT:
- mSubtypeSwitcher.switchToShortcutIME();
+ mSubtypeSwitcher.switchToShortcutIME(this);
break;
case Keyboard.CODE_ACTION_ENTER:
performEditorAction(getActionId(switcher.getKeyboard()));
@@ -1307,7 +1309,7 @@
break;
case Keyboard.CODE_RESEARCH:
if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.getInstance().presentResearchDialog(this);
+ ResearchLogger.getInstance().onResearchKeySelected(this);
}
break;
default:
@@ -1324,8 +1326,8 @@
keyX = x;
keyY = y;
} else {
- keyX = NOT_A_TOUCH_COORDINATE;
- keyY = NOT_A_TOUCH_COORDINATE;
+ keyX = Constants.NOT_A_COORDINATE;
+ keyY = Constants.NOT_A_COORDINATE;
}
handleCharacter(primaryCode, keyX, keyY, spaceState);
}
@@ -1338,45 +1340,49 @@
if (!didAutoCorrect && primaryCode != Keyboard.CODE_SHIFT
&& primaryCode != Keyboard.CODE_SWITCH_ALPHA_SYMBOL)
mLastComposedWord.deactivate();
- mEnteredText = null;
+ if (Keyboard.CODE_DELETE != primaryCode) {
+ mEnteredText = null;
+ }
mConnection.endBatchEdit();
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_onCodeInput(primaryCode, x, y);
+ }
}
// Called from PointerTracker through the KeyboardActionListener interface
@Override
public void onTextInput(CharSequence rawText) {
mConnection.beginBatchEdit();
- commitTyped(LastComposedWord.NOT_A_SEPARATOR);
+ if (mWordComposer.isComposingWord()) {
+ commitCurrentAutoCorrection(rawText.toString());
+ } else {
+ resetComposingState(true /* alsoResetLastComposedWord */);
+ }
mHandler.postUpdateSuggestionStrip();
final CharSequence text = specificTldProcessingOnTextInput(rawText);
if (SPACE_STATE_PHANTOM == mSpaceState) {
sendKeyCodePoint(Keyboard.CODE_SPACE);
}
mConnection.commitText(text, 1);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_commitText(text);
- }
mConnection.endBatchEdit();
mKeyboardSwitcher.updateShiftState();
mKeyboardSwitcher.onCodeInput(Keyboard.CODE_OUTPUT_TEXT);
mSpaceState = SPACE_STATE_NONE;
mEnteredText = text;
- resetComposingState(true /* alsoResetLastComposedWord */);
}
@Override
public void onStartBatchInput() {
mConnection.beginBatchEdit();
if (mWordComposer.isComposingWord()) {
- commitTyped(LastComposedWord.NOT_A_SEPARATOR);
+ commitCurrentAutoCorrection(LastComposedWord.NOT_A_SEPARATOR);
mExpectingUpdateSelection = true;
// TODO: Can we remove this?
mSpaceState = SPACE_STATE_PHANTOM;
}
mConnection.endBatchEdit();
// TODO: Should handle TextUtils.CAP_MODE_CHARACTER.
- mWordComposer.setAutoCapitalized(
- getCurrentAutoCapsState() != Constants.TextUtils.CAP_MODE_OFF);
+ mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode());
}
@Override
@@ -1448,21 +1454,6 @@
// In many cases, we may have to put the keyboard in auto-shift state again.
mHandler.postUpdateShiftState();
- if (mEnteredText != null && mConnection.sameAsTextBeforeCursor(mEnteredText)) {
- // Cancel multi-character input: remove the text we just entered.
- // This is triggered on backspace after a key that inputs multiple characters,
- // like the smiley key or the .com key.
- final int length = mEnteredText.length();
- mConnection.deleteSurroundingText(length, 0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(length);
- }
- // If we have mEnteredText, then we know that mHasUncommittedTypedChars == false.
- // In addition we know that spaceState is false, and that we should not be
- // reverting any autocorrect at this point. So we can safely return.
- return;
- }
-
if (mWordComposer.isComposingWord()) {
final int length = mWordComposer.size();
if (length > 0) {
@@ -1476,9 +1467,6 @@
mHandler.postUpdateSuggestionStrip();
} else {
mConnection.deleteSurroundingText(1, 0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(1);
- }
}
} else {
if (mLastComposedWord.canRevertCommit()) {
@@ -1486,6 +1474,18 @@
revertCommit();
return;
}
+ if (mEnteredText != null && mConnection.sameAsTextBeforeCursor(mEnteredText)) {
+ // Cancel multi-character input: remove the text we just entered.
+ // This is triggered on backspace after a key that inputs multiple characters,
+ // like the smiley key or the .com key.
+ final int length = mEnteredText.length();
+ mConnection.deleteSurroundingText(length, 0);
+ mEnteredText = null;
+ // If we have mEnteredText, then we know that mHasUncommittedTypedChars == false.
+ // In addition we know that spaceState is false, and that we should not be
+ // reverting any autocorrect at this point. So we can safely return.
+ return;
+ }
if (SPACE_STATE_DOUBLE == spaceState) {
mHandler.cancelDoubleSpacesTimer();
if (mConnection.revertDoubleSpace()) {
@@ -1507,9 +1507,6 @@
final int lengthToDelete = mLastSelectionEnd - mLastSelectionStart;
mConnection.setSelection(mLastSelectionEnd, mLastSelectionEnd);
mConnection.deleteSurroundingText(lengthToDelete, 0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(lengthToDelete);
- }
} else {
// There is no selection, just delete one character.
if (NOT_A_CURSOR_POSITION == mLastSelectionEnd) {
@@ -1528,14 +1525,8 @@
} else {
mConnection.deleteSurroundingText(1, 0);
}
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(1);
- }
if (mDeleteCount > DELETE_ACCELERATE_AT) {
mConnection.deleteSurroundingText(1, 0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(1);
- }
}
}
if (mCurrentSettings.isSuggestionsRequested(mDisplayOrientation)) {
@@ -1611,13 +1602,12 @@
mWordComposer.add(primaryCode, keyX, keyY);
// If it's the first letter, make note of auto-caps state
if (mWordComposer.size() == 1) {
- mWordComposer.setAutoCapitalized(
- getCurrentAutoCapsState() != Constants.TextUtils.CAP_MODE_OFF);
+ mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode());
}
mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
} else {
final boolean swapWeakSpace = maybeStripSpace(primaryCode,
- spaceState, KeyboardActionListener.SUGGESTION_STRIP_COORDINATE == x);
+ spaceState, Constants.SUGGESTION_STRIP_COORDINATE == x);
sendKeyCodePoint(primaryCode);
@@ -1639,15 +1629,16 @@
// Handle separator
if (mWordComposer.isComposingWord()) {
if (mCurrentSettings.mCorrectionEnabled) {
- commitCurrentAutoCorrection(primaryCode);
+ // TODO: maybe cache Strings in an <String> sparse array or something
+ commitCurrentAutoCorrection(new String(new int[]{primaryCode}, 0, 1));
didAutoCorrect = true;
} else {
- commitTyped(primaryCode);
+ commitTyped(new String(new int[]{primaryCode}, 0, 1));
}
}
final boolean swapWeakSpace = maybeStripSpace(primaryCode, spaceState,
- KeyboardActionListener.SUGGESTION_STRIP_COORDINATE == x);
+ Constants.SUGGESTION_STRIP_COORDINATE == x);
if (SPACE_STATE_PHANTOM == spaceState &&
mCurrentSettings.isPhantomSpacePromotingSymbol(primaryCode)) {
@@ -1694,6 +1685,7 @@
Utils.Stats.onSeparator((char)primaryCode, x, y);
+ mHandler.postUpdateShiftState();
return didAutoCorrect;
}
@@ -1714,7 +1706,8 @@
// TODO: make this private
// Outside LatinIME, only used by the test suite.
- /* package for tests */ boolean isShowingPunctuationList() {
+ /* package for tests */
+ boolean isShowingPunctuationList() {
if (mSuggestionStripView == null) return false;
return mCurrentSettings.mSuggestPuncList == mSuggestionStripView.getSuggestions();
}
@@ -1845,7 +1838,7 @@
setSuggestionStripShown(isSuggestionsStripVisible());
}
- private void commitCurrentAutoCorrection(final int separatorCodePoint) {
+ private void commitCurrentAutoCorrection(final String separatorString) {
// Complete any pending suggestions query first
if (mHandler.hasPendingUpdateSuggestions()) {
updateSuggestionStrip();
@@ -1859,14 +1852,10 @@
throw new RuntimeException("We have an auto-correction but the typed word "
+ "is empty? Impossible! I must commit suicide.");
}
- Utils.Stats.onAutoCorrection(typedWord, autoCorrection.toString(), separatorCodePoint);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_commitCurrentAutoCorrection(typedWord,
- autoCorrection.toString());
- }
+ Utils.Stats.onAutoCorrection(typedWord, autoCorrection.toString(), separatorString);
mExpectingUpdateSelection = true;
commitChosenWord(autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD,
- separatorCodePoint);
+ separatorString);
if (!typedWord.equals(autoCorrection)) {
// This will make the correction flash for a short while as a visual clue
// to the user that auto-correction happened.
@@ -1880,8 +1869,7 @@
// Called from {@link SuggestionStripView} through the {@link SuggestionStripView#Listener}
// interface
@Override
- public void pickSuggestionManually(final int index, final CharSequence suggestion,
- final int x, final int y) {
+ public void pickSuggestionManually(final int index, final CharSequence suggestion) {
final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions();
// If this is a punctuation picked from the suggestion strip, pass it to onCodeInput
if (suggestion.length() == 1 && isShowingPunctuationList()) {
@@ -1889,13 +1877,12 @@
// So, LatinImeLogger logs "" as a user's input.
LatinImeLogger.logOnManualSuggestion("", suggestion.toString(), index, suggestedWords);
// Rely on onCodeInput to do the complicated swapping/stripping logic consistently.
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_punctuationSuggestion(index, suggestion, x, y);
- }
final int primaryCode = suggestion.charAt(0);
onCodeInput(primaryCode,
- KeyboardActionListener.SUGGESTION_STRIP_COORDINATE,
- KeyboardActionListener.SUGGESTION_STRIP_COORDINATE);
+ Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_punctuationSuggestion(index, suggestion);
+ }
return;
}
@@ -1922,10 +1909,6 @@
final CompletionInfo completionInfo = mApplicationSpecifiedCompletions[index];
mConnection.commitCompletion(completionInfo);
mConnection.endBatchEdit();
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_pickApplicationSpecifiedCompletion(index,
- completionInfo.getText(), x, y);
- }
return;
}
@@ -1934,12 +1917,12 @@
final String replacedWord = mWordComposer.getTypedWord().toString();
LatinImeLogger.logOnManualSuggestion(replacedWord,
suggestion.toString(), index, suggestedWords);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion, x, y);
- }
mExpectingUpdateSelection = true;
commitChosenWord(suggestion, LastComposedWord.COMMIT_TYPE_MANUAL_PICK,
LastComposedWord.NOT_A_SEPARATOR);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion);
+ }
mConnection.endBatchEdit();
// Don't allow cancellation of manual pick
mLastComposedWord.deactivate();
@@ -1955,8 +1938,8 @@
// If the suggestion is not in the dictionary, the hint should be shown.
&& !AutoCorrection.isValidWord(mSuggest.getUnigramDictionaries(), suggestion, true);
- Utils.Stats.onSeparator((char)Keyboard.CODE_SPACE, WordComposer.NOT_A_COORDINATE,
- WordComposer.NOT_A_COORDINATE);
+ Utils.Stats.onSeparator((char)Keyboard.CODE_SPACE,
+ Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
if (showingAddToDictionaryHint && mIsUserDictionaryAvailable) {
mSuggestionStripView.showAddToDictionaryHint(
suggestion, mCurrentSettings.mHintToSaveText);
@@ -1970,13 +1953,10 @@
* Commits the chosen word to the text field and saves it for later retrieval.
*/
private void commitChosenWord(final CharSequence chosenWord, final int commitType,
- final int separatorCode) {
+ final String separatorString) {
final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions();
mConnection.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan(
this, chosenWord, suggestedWords, mIsMainDictionaryAvailable), 1);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_commitText(chosenWord);
- }
// Add the word to the user history dictionary
final CharSequence prevWord = addToUserHistoryDictionary(chosenWord);
// TODO: figure out here if this is an auto-correct or if the best word is actually
@@ -1984,7 +1964,7 @@
// LastComposedWord#didCommitTypedWord by string equality of the remembered
// strings.
mLastComposedWord = mWordComposer.commitWord(commitType, chosenWord.toString(),
- separatorCode, prevWord);
+ separatorString, prevWord);
}
private void setPunctuationSuggestions() {
@@ -1999,6 +1979,7 @@
private CharSequence addToUserHistoryDictionary(final CharSequence suggestion) {
if (TextUtils.isEmpty(suggestion)) return null;
+ if (mSuggest == null) return null;
// If correction is not enabled, we don't add words to the user history dictionary.
// That's to avoid unintended additions in some sensitive fields, or fields that
@@ -2010,7 +1991,7 @@
final CharSequence prevWord
= mConnection.getNthPreviousWord(mCurrentSettings.mWordSeparators, 2);
final String secondWord;
- if (mWordComposer.isAutoCapitalized() && !mWordComposer.isMostlyCaps()) {
+ if (mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps()) {
secondWord = suggestion.toString().toLowerCase(
mSubtypeSwitcher.getCurrentSubtypeLocale());
} else {
@@ -2043,9 +2024,6 @@
mWordComposer.setComposingWord(word, mKeyboardSwitcher.getKeyboard());
final int length = word.length();
mConnection.deleteSurroundingText(length, 0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(length);
- }
mConnection.setComposingText(word, 1);
mHandler.postUpdateSuggestionStrip();
}
@@ -2056,7 +2034,7 @@
final CharSequence committedWord = mLastComposedWord.mCommittedWord;
final int cancelLength = committedWord.length();
final int separatorLength = LastComposedWord.getSeparatorLength(
- mLastComposedWord.mSeparatorCode);
+ mLastComposedWord.mSeparatorString);
// TODO: should we check our saved separator against the actual contents of the text view?
final int deleteLength = cancelLength + separatorLength;
if (DEBUG) {
@@ -2073,18 +2051,13 @@
}
}
mConnection.deleteSurroundingText(deleteLength, 0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(deleteLength);
- }
if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) {
mUserHistoryDictionary.cancelAddingUserHistory(
previousWord.toString(), committedWord.toString());
}
- mConnection.commitText(originallyTypedWord, 1);
- // Re-insert the separator
- sendKeyCodePoint(mLastComposedWord.mSeparatorCode);
- Utils.Stats.onSeparator(mLastComposedWord.mSeparatorCode, WordComposer.NOT_A_COORDINATE,
- WordComposer.NOT_A_COORDINATE);
+ mConnection.commitText(originallyTypedWord + mLastComposedWord.mSeparatorString, 1);
+ Utils.Stats.onSeparator(mLastComposedWord.mSeparatorString,
+ Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_revertCommit(originallyTypedWord);
}
@@ -2100,9 +2073,10 @@
return mCurrentSettings.isWordSeparator(code);
}
- // Notify that language or mode have been changed and toggleLanguage will update KeyboardID
- // according to new language or mode. Called from SubtypeSwitcher.
- public void onRefreshKeyboard() {
+ // TODO: Make this private
+ // Outside LatinIME, only used by the {@link InputTestsBase} test suite.
+ /* package for test */
+ void loadKeyboard() {
// When the device locale is changed in SetupWizard etc., this method may get called via
// onConfigurationChanged before SoftInputWindow is shown.
initSuggest();
diff --git a/java/src/com/android/inputmethod/latin/LocaleUtils.java b/java/src/com/android/inputmethod/latin/LocaleUtils.java
index b938dd3..feb1b2d 100644
--- a/java/src/com/android/inputmethod/latin/LocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/LocaleUtils.java
@@ -31,7 +31,10 @@
* update/bugfix to this file, consider also updating/fixing the version in the
* dictionary pack.
*/
-public class LocaleUtils {
+public final class LocaleUtils {
+ private static final HashMap<String, Long> EMPTY_LT_HASH_MAP = CollectionUtils.newHashMap();
+ private static final String LOCALE_AND_TIME_STR_SEPARATER = ",";
+
private LocaleUtils() {
// Intentional empty constructor for utility class.
}
@@ -193,7 +196,7 @@
}
}
- private static final HashMap<String, Locale> sLocaleCache = new HashMap<String, Locale>();
+ private static final HashMap<String, Locale> sLocaleCache = CollectionUtils.newHashMap();
/**
* Creates a locale from a string specification.
@@ -219,4 +222,38 @@
return retval;
}
}
+
+ public static HashMap<String, Long> localeAndTimeStrToHashMap(String str) {
+ if (TextUtils.isEmpty(str)) {
+ return EMPTY_LT_HASH_MAP;
+ }
+ final String[] ss = str.split(LOCALE_AND_TIME_STR_SEPARATER);
+ final int N = ss.length;
+ if (N < 2 || N % 2 != 0) {
+ return EMPTY_LT_HASH_MAP;
+ }
+ final HashMap<String, Long> retval = CollectionUtils.newHashMap();
+ for (int i = 0; i < N / 2; ++i) {
+ final String localeStr = ss[i * 2];
+ final long time = Long.valueOf(ss[i * 2 + 1]);
+ retval.put(localeStr, time);
+ }
+ return retval;
+ }
+
+ public static String localeAndTimeHashMapToStr(HashMap<String, Long> map) {
+ if (map == null || map.isEmpty()) {
+ return "";
+ }
+ final StringBuilder builder = new StringBuilder();
+ for (String localeStr : map.keySet()) {
+ if (builder.length() > 0) {
+ builder.append(LOCALE_AND_TIME_STR_SEPARATER);
+ }
+ final Long time = map.get(localeStr);
+ builder.append(localeStr).append(LOCALE_AND_TIME_STR_SEPARATER);
+ builder.append(String.valueOf(time));
+ }
+ return builder.toString();
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/ResizableIntArray.java b/java/src/com/android/inputmethod/latin/ResizableIntArray.java
index 387d45a..c660f92 100644
--- a/java/src/com/android/inputmethod/latin/ResizableIntArray.java
+++ b/java/src/com/android/inputmethod/latin/ResizableIntArray.java
@@ -131,4 +131,16 @@
mLength = endPos;
}
}
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < mLength; i++) {
+ if (i != 0) {
+ sb.append(",");
+ }
+ sb.append(mArray[i]);
+ }
+ return "[" + sb + "]";
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/ResourceUtils.java b/java/src/com/android/inputmethod/latin/ResourceUtils.java
new file mode 100644
index 0000000..5021ad3
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/ResourceUtils.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.os.Build;
+import android.util.TypedValue;
+
+import java.util.HashMap;
+
+public final class ResourceUtils {
+ public static final float UNDEFINED_RATIO = -1.0f;
+ public static final int UNDEFINED_DIMENSION = -1;
+
+ private ResourceUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ private static final String HARDWARE_PREFIX = Build.HARDWARE + ",";
+ private static final HashMap<String, String> sDeviceOverrideValueMap =
+ CollectionUtils.newHashMap();
+
+ public static String getDeviceOverrideValue(Resources res, int overrideResId, String defValue) {
+ final int orientation = res.getConfiguration().orientation;
+ final String key = overrideResId + "-" + orientation;
+ if (!sDeviceOverrideValueMap.containsKey(key)) {
+ String overrideValue = defValue;
+ for (final String element : res.getStringArray(overrideResId)) {
+ if (element.startsWith(HARDWARE_PREFIX)) {
+ overrideValue = element.substring(HARDWARE_PREFIX.length());
+ break;
+ }
+ }
+ sDeviceOverrideValueMap.put(key, overrideValue);
+ }
+ return sDeviceOverrideValueMap.get(key);
+ }
+
+ public static boolean isValidFraction(final float fraction) {
+ return fraction >= 0.0f;
+ }
+
+ // {@link Resources#getDimensionPixelSize(int)} returns at least one pixel size.
+ public static boolean isValidDimensionPixelSize(final int dimension) {
+ return dimension > 0;
+ }
+
+ // {@link Resources#getDimensionPixelOffset(int)} may return zero pixel offset.
+ public static boolean isValidDimensionPixelOffset(final int dimension) {
+ return dimension >= 0;
+ }
+
+ public static float getFraction(final TypedArray a, final int index, final float defValue) {
+ final TypedValue value = a.peekValue(index);
+ if (value == null || !isFractionValue(value)) {
+ return defValue;
+ }
+ return a.getFraction(index, 1, 1, defValue);
+ }
+
+ public static float getFraction(final TypedArray a, final int index) {
+ return getFraction(a, index, UNDEFINED_RATIO);
+ }
+
+ public static int getDimensionPixelSize(final TypedArray a, final int index) {
+ final TypedValue value = a.peekValue(index);
+ if (value == null || !isDimensionValue(value)) {
+ return ResourceUtils.UNDEFINED_DIMENSION;
+ }
+ return a.getDimensionPixelSize(index, ResourceUtils.UNDEFINED_DIMENSION);
+ }
+
+ public static float getDimensionOrFraction(TypedArray a, int index, int base,
+ float defValue) {
+ final TypedValue value = a.peekValue(index);
+ if (value == null) {
+ return defValue;
+ }
+ if (isFractionValue(value)) {
+ return a.getFraction(index, base, base, defValue);
+ } else if (isDimensionValue(value)) {
+ return a.getDimension(index, defValue);
+ }
+ return defValue;
+ }
+
+ public static int getEnumValue(TypedArray a, int index, int defValue) {
+ final TypedValue value = a.peekValue(index);
+ if (value == null) {
+ return defValue;
+ }
+ if (isIntegerValue(value)) {
+ return a.getInt(index, defValue);
+ }
+ return defValue;
+ }
+
+ public static boolean isFractionValue(TypedValue v) {
+ return v.type == TypedValue.TYPE_FRACTION;
+ }
+
+ public static boolean isDimensionValue(TypedValue v) {
+ return v.type == TypedValue.TYPE_DIMENSION;
+ }
+
+ public static boolean isIntegerValue(TypedValue v) {
+ return v.type >= TypedValue.TYPE_FIRST_INT && v.type <= TypedValue.TYPE_LAST_INT;
+ }
+
+ public static boolean isStringValue(TypedValue v) {
+ return v.type == TypedValue.TYPE_STRING;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 8b4c173..41e59e9 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -55,7 +55,9 @@
public void beginBatchEdit() {
if (++mNestLevel == 1) {
mIC = mParent.getCurrentInputConnection();
- if (null != mIC) mIC.beginBatchEdit();
+ if (null != mIC) {
+ mIC.beginBatchEdit();
+ }
} else {
if (DBG) {
throw new RuntimeException("Nest level too deep");
@@ -66,7 +68,9 @@
}
public void endBatchEdit() {
if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead
- if (--mNestLevel == 0 && null != mIC) mIC.endBatchEdit();
+ if (--mNestLevel == 0 && null != mIC) {
+ mIC.endBatchEdit();
+ }
}
private void checkBatchEdit() {
@@ -79,12 +83,22 @@
public void finishComposingText() {
checkBatchEdit();
- if (null != mIC) mIC.finishComposingText();
+ if (null != mIC) {
+ mIC.finishComposingText();
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.richInputConnection_finishComposingText();
+ }
+ }
}
public void commitText(final CharSequence text, final int i) {
checkBatchEdit();
- if (null != mIC) mIC.commitText(text, i);
+ if (null != mIC) {
+ mIC.commitText(text, i);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.richInputConnection_commitText(text, i);
+ }
+ }
}
public int getCursorCapsMode(final int inputType) {
@@ -107,37 +121,72 @@
public void deleteSurroundingText(final int i, final int j) {
checkBatchEdit();
- if (null != mIC) mIC.deleteSurroundingText(i, j);
+ if (null != mIC) {
+ mIC.deleteSurroundingText(i, j);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.richInputConnection_deleteSurroundingText(i, j);
+ }
+ }
}
public void performEditorAction(final int actionId) {
mIC = mParent.getCurrentInputConnection();
- if (null != mIC) mIC.performEditorAction(actionId);
+ if (null != mIC) {
+ mIC.performEditorAction(actionId);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.richInputConnection_performEditorAction(actionId);
+ }
+ }
}
public void sendKeyEvent(final KeyEvent keyEvent) {
checkBatchEdit();
- if (null != mIC) mIC.sendKeyEvent(keyEvent);
+ if (null != mIC) {
+ mIC.sendKeyEvent(keyEvent);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.richInputConnection_sendKeyEvent(keyEvent);
+ }
+ }
}
public void setComposingText(final CharSequence text, final int i) {
checkBatchEdit();
- if (null != mIC) mIC.setComposingText(text, i);
+ if (null != mIC) {
+ mIC.setComposingText(text, i);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.richInputConnection_setComposingText(text, i);
+ }
+ }
}
public void setSelection(final int from, final int to) {
checkBatchEdit();
- if (null != mIC) mIC.setSelection(from, to);
+ if (null != mIC) {
+ mIC.setSelection(from, to);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.richInputConnection_setSelection(from, to);
+ }
+ }
}
public void commitCorrection(final CorrectionInfo correctionInfo) {
checkBatchEdit();
- if (null != mIC) mIC.commitCorrection(correctionInfo);
+ if (null != mIC) {
+ mIC.commitCorrection(correctionInfo);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.richInputConnection_commitCorrection(correctionInfo);
+ }
+ }
}
public void commitCompletion(final CompletionInfo completionInfo) {
checkBatchEdit();
- if (null != mIC) mIC.commitCompletion(completionInfo);
+ if (null != mIC) {
+ mIC.commitCompletion(completionInfo);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.richInputConnection_commitCompletion(completionInfo);
+ }
+ }
}
public CharSequence getNthPreviousWord(final String sentenceSeperators, final int n) {
@@ -315,9 +364,6 @@
if (lastOne != null && lastOne.length() == 1
&& lastOne.charAt(0) == Keyboard.CODE_SPACE) {
deleteSurroundingText(1, 0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(1);
- }
}
}
@@ -382,13 +428,7 @@
return false;
}
deleteSurroundingText(2, 0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(2);
- }
commitText(" ", 1);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_revertDoubleSpaceWhileInBatchEdit();
- }
return true;
}
@@ -409,13 +449,7 @@
return false;
}
deleteSurroundingText(2, 0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(2);
- }
commitText(" " + textBeforeCursor.subSequence(0, 1), 1);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_revertSwapPunctuation();
- }
return true;
}
}
diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java
index 0843bdb..5e355a3 100644
--- a/java/src/com/android/inputmethod/latin/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -185,7 +185,7 @@
// Helper functions to create member values.
private static SuggestedWords createSuggestPuncList(final String[] puncs) {
- final ArrayList<SuggestedWordInfo> puncList = new ArrayList<SuggestedWordInfo>();
+ final ArrayList<SuggestedWordInfo> puncList = CollectionUtils.newArrayList();
if (puncs != null) {
for (final String puncSpec : puncs) {
puncList.add(new SuggestedWordInfo(KeySpecParser.getLabel(puncSpec),
@@ -375,8 +375,8 @@
return volume;
}
- return Float.parseFloat(
- Utils.getDeviceOverrideValue(res, R.array.keypress_volumes, "-1.0f"));
+ return Float.parseFloat(ResourceUtils.getDeviceOverrideValue(
+ res, R.array.keypress_volumes, "-1.0f"));
}
// Likewise
@@ -388,8 +388,8 @@
return ms;
}
- return Integer.parseInt(
- Utils.getDeviceOverrideValue(res, R.array.keypress_vibration_durations, "-1"));
+ return Integer.parseInt(ResourceUtils.getDeviceOverrideValue(
+ res, R.array.keypress_vibration_durations, "-1"));
}
// Likewise
@@ -401,7 +401,7 @@
public static long getLastUserHistoryWriteTime(
final SharedPreferences prefs, final String locale) {
final String str = prefs.getString(Settings.PREF_LAST_USER_DICTIONARY_WRITE_TIME, "");
- final HashMap<String, Long> map = Utils.localeAndTimeStrToHashMap(str);
+ final HashMap<String, Long> map = LocaleUtils.localeAndTimeStrToHashMap(str);
if (map.containsKey(locale)) {
return map.get(locale);
}
@@ -411,12 +411,16 @@
public static void setLastUserHistoryWriteTime(
final SharedPreferences prefs, final String locale) {
final String oldStr = prefs.getString(Settings.PREF_LAST_USER_DICTIONARY_WRITE_TIME, "");
- final HashMap<String, Long> map = Utils.localeAndTimeStrToHashMap(oldStr);
+ final HashMap<String, Long> map = LocaleUtils.localeAndTimeStrToHashMap(oldStr);
map.put(locale, System.currentTimeMillis());
- final String newStr = Utils.localeAndTimeHashMapToStr(map);
+ final String newStr = LocaleUtils.localeAndTimeHashMapToStr(map);
prefs.edit().putString(Settings.PREF_LAST_USER_DICTIONARY_WRITE_TIME, newStr).apply();
}
+ public boolean isSameInputType(final EditorInfo editorInfo) {
+ return mInputAttributes.isSameInputType(editorInfo);
+ }
+
// For debug.
public String getInputAttributesDebugString() {
return mInputAttributes.toString();
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
index 6e7d985..9c47a38 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -21,7 +21,7 @@
import java.util.ArrayList;
import java.util.Locale;
-public class StringUtils {
+public final class StringUtils {
private StringUtils() {
// This utility class is not publicly instantiable.
}
@@ -53,7 +53,7 @@
if (TextUtils.isEmpty(csv)) return "";
final String[] elements = csv.split(",");
if (!containsInArray(key, elements)) return csv;
- final ArrayList<String> result = new ArrayList<String>(elements.length - 1);
+ final ArrayList<String> result = CollectionUtils.newArrayList(elements.length - 1);
for (final String element : elements) {
if (!key.equals(element)) result.add(element);
}
diff --git a/java/src/com/android/inputmethod/latin/SubtypeLocale.java b/java/src/com/android/inputmethod/latin/SubtypeLocale.java
index 21c9c0d..de5f515 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeLocale.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeLocale.java
@@ -45,13 +45,13 @@
private static String[] sPredefinedKeyboardLayoutSet;
// Keyboard layout to its display name map.
private static final HashMap<String, String> sKeyboardLayoutToDisplayNameMap =
- new HashMap<String, String>();
+ CollectionUtils.newHashMap();
// Keyboard layout to subtype name resource id map.
private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap =
- new HashMap<String, Integer>();
+ CollectionUtils.newHashMap();
// Exceptional locale to subtype name resource id map.
private static final HashMap<String, Integer> sExceptionalLocaleToWithLayoutNameIdsMap =
- new HashMap<String, Integer>();
+ CollectionUtils.newHashMap();
private static final String SUBTYPE_NAME_RESOURCE_GENERIC_PREFIX =
"string/subtype_generic_";
private static final String SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX =
@@ -60,11 +60,11 @@
"string/subtype_no_language_";
// Exceptional locales to display name map.
private static final HashMap<String, String> sExceptionalDisplayNamesMap =
- new HashMap<String, String>();
+ CollectionUtils.newHashMap();
// Keyboard layout set name for the subtypes that don't have a keyboardLayoutSet extra value.
// This is for compatibility to keep the same subtype ids as pre-JellyBean.
private static final HashMap<String,String> sLocaleAndExtraValueToKeyboardLayoutSetMap =
- new HashMap<String,String>();
+ CollectionUtils.newHashMap();
private SubtypeLocale() {
// Intentional empty constructor for utility class.
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index a7a5fcb..c693edc 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -22,6 +22,7 @@
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.inputmethodservice.InputMethodService;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
@@ -42,7 +43,6 @@
private static final String TAG = SubtypeSwitcher.class.getSimpleName();
private static final SubtypeSwitcher sInstance = new SubtypeSwitcher();
- private /* final */ LatinIME mService;
private /* final */ InputMethodManager mImm;
private /* final */ Resources mResources;
private /* final */ ConnectivityManager mConnectivityManager;
@@ -68,11 +68,11 @@
return mEnabledSubtypeCount >= 2 || !mIsSystemLanguageSameAsInputLanguage;
}
- public void updateEnabledSubtypeCount(int count) {
+ public void updateEnabledSubtypeCount(final int count) {
mEnabledSubtypeCount = count;
}
- public void updateIsSystemLanguageSameAsInputLanguage(boolean isSame) {
+ public void updateIsSystemLanguageSameAsInputLanguage(final boolean isSame) {
mIsSystemLanguageSameAsInputLanguage = isSame;
}
}
@@ -81,18 +81,17 @@
return sInstance;
}
- public static void init(LatinIME service) {
- SubtypeLocale.init(service);
- sInstance.initialize(service);
- sInstance.updateAllParameters();
+ public static void init(final Context context) {
+ SubtypeLocale.init(context);
+ sInstance.initialize(context);
+ sInstance.updateAllParameters(context);
}
private SubtypeSwitcher() {
// Intentional empty constructor for singleton.
}
- private void initialize(LatinIME service) {
- mService = service;
+ private void initialize(final Context service) {
mResources = service.getResources();
mImm = ImfUtils.getInputMethodManager(service);
mConnectivityManager = (ConnectivityManager) service.getSystemService(
@@ -111,39 +110,46 @@
// Update all parameters stored in SubtypeSwitcher.
// Only configuration changed event is allowed to call this because this is heavy.
- private void updateAllParameters() {
+ private void updateAllParameters(final Context context) {
mCurrentSystemLocale = mResources.getConfiguration().locale;
- updateSubtype(ImfUtils.getCurrentInputMethodSubtype(mService, mNoLanguageSubtype));
- updateParametersOnStartInputView();
+ updateSubtype(ImfUtils.getCurrentInputMethodSubtype(context, mNoLanguageSubtype));
+ updateParametersOnStartInputViewAndReturnIfCurrentSubtypeEnabled();
}
- // Update parameters which are changed outside LatinIME. This parameters affect UI so they
- // should be updated every time onStartInputview.
- public void updateParametersOnStartInputView() {
- updateEnabledSubtypes();
+ /**
+ * Update parameters which are changed outside LatinIME. This parameters affect UI so they
+ * should be updated every time onStartInputView.
+ *
+ * @return true if the current subtype is enabled.
+ */
+ public boolean updateParametersOnStartInputViewAndReturnIfCurrentSubtypeEnabled() {
+ final boolean currentSubtypeEnabled =
+ updateEnabledSubtypesAndReturnIfEnabled(mCurrentSubtype);
updateShortcutIME();
+ return currentSubtypeEnabled;
}
- // Reload enabledSubtypes from the framework.
- private void updateEnabledSubtypes() {
- final InputMethodSubtype currentSubtype = mCurrentSubtype;
- boolean foundCurrentSubtypeBecameDisabled = true;
+ /**
+ * Update enabled subtypes from the framework.
+ *
+ * @param subtype the subtype to be checked
+ * @return true if the {@code subtype} is enabled.
+ */
+ private boolean updateEnabledSubtypesAndReturnIfEnabled(final InputMethodSubtype subtype) {
final List<InputMethodSubtype> enabledSubtypesOfThisIme =
mImm.getEnabledInputMethodSubtypeList(null, true);
- for (InputMethodSubtype ims : enabledSubtypesOfThisIme) {
- if (ims.equals(currentSubtype)) {
- foundCurrentSubtypeBecameDisabled = false;
- }
- }
mNeedsToDisplayLanguage.updateEnabledSubtypeCount(enabledSubtypesOfThisIme.size());
- if (foundCurrentSubtypeBecameDisabled) {
- if (DBG) {
- Log.w(TAG, "Last subtype: "
- + currentSubtype.getLocale() + "/" + currentSubtype.getExtraValue());
- Log.w(TAG, "Last subtype was disabled. Update to the current one.");
+
+ for (final InputMethodSubtype ims : enabledSubtypesOfThisIme) {
+ if (ims.equals(subtype)) {
+ return true;
}
- updateSubtype(ImfUtils.getCurrentInputMethodSubtype(mService, mNoLanguageSubtype));
}
+ if (DBG) {
+ Log.w(TAG, "Subtype: " + subtype.getLocale() + "/" + subtype.getExtraValue()
+ + " was disabled");
+ }
+ return false;
}
private void updateShortcutIME() {
@@ -159,8 +165,8 @@
mImm.getShortcutInputMethodsAndSubtypes();
mShortcutInputMethodInfo = null;
mShortcutSubtype = null;
- for (InputMethodInfo imi : shortcuts.keySet()) {
- List<InputMethodSubtype> subtypes = shortcuts.get(imi);
+ for (final InputMethodInfo imi : shortcuts.keySet()) {
+ final List<InputMethodSubtype> subtypes = shortcuts.get(imi);
// TODO: Returns the first found IMI for now. Should handle all shortcuts as
// appropriate.
mShortcutInputMethodInfo = imi;
@@ -194,24 +200,24 @@
mCurrentSubtype = newSubtype;
updateShortcutIME();
- mService.onRefreshKeyboard();
}
////////////////////////////
// Shortcut IME functions //
////////////////////////////
- public void switchToShortcutIME() {
+ public void switchToShortcutIME(final InputMethodService context) {
if (mShortcutInputMethodInfo == null) {
return;
}
final String imiId = mShortcutInputMethodInfo.getId();
- switchToTargetIME(imiId, mShortcutSubtype);
+ switchToTargetIME(imiId, mShortcutSubtype, context);
}
- private void switchToTargetIME(final String imiId, final InputMethodSubtype subtype) {
- final IBinder token = mService.getWindow().getWindow().getAttributes().token;
+ private void switchToTargetIME(final String imiId, final InputMethodSubtype subtype,
+ final InputMethodService context) {
+ final IBinder token = context.getWindow().getWindow().getAttributes().token;
if (token == null) {
return;
}
@@ -253,7 +259,7 @@
return true;
}
- public void onNetworkStateChanged(Intent intent) {
+ public void onNetworkStateChanged(final Intent intent) {
final boolean noConnection = intent.getBooleanExtra(
ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
mIsNetworkConnected = !noConnection;
@@ -265,7 +271,7 @@
// Subtype Switching functions //
//////////////////////////////////
- public boolean needsToDisplayLanguage(Locale keyboardLocale) {
+ public boolean needsToDisplayLanguage(final Locale keyboardLocale) {
if (keyboardLocale.toString().equals(SubtypeLocale.NO_LANGUAGE)) {
return true;
}
@@ -279,12 +285,14 @@
return SubtypeLocale.getSubtypeLocale(mCurrentSubtype);
}
- public void onConfigurationChanged(Configuration conf) {
+ public boolean onConfigurationChanged(final Configuration conf, final Context context) {
final Locale systemLocale = conf.locale;
+ final boolean systemLocaleChanged = !systemLocale.equals(mCurrentSystemLocale);
// If system configuration was changed, update all parameters.
- if (!systemLocale.equals(mCurrentSystemLocale)) {
- updateAllParameters();
+ if (systemLocaleChanged) {
+ updateAllParameters(context);
}
+ return systemLocaleChanged;
}
public InputMethodSubtype getCurrentSubtype() {
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 8a2341d..51ed096 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -50,9 +50,8 @@
private Dictionary mMainDictionary;
private ContactsBinaryDictionary mContactsDict;
- private WhitelistDictionary mWhiteListDictionary;
private final ConcurrentHashMap<String, Dictionary> mDictionaries =
- new ConcurrentHashMap<String, Dictionary>();
+ CollectionUtils.newConcurrentHashMap();
public static final int MAX_SUGGESTIONS = 18;
@@ -74,21 +73,11 @@
mLocale = locale;
mMainDictionary = mainDict;
addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, mainDict);
- initWhitelistAndAutocorrectAndPool(context, locale);
- }
-
- private void initWhitelistAndAutocorrectAndPool(final Context context, final Locale locale) {
- mWhiteListDictionary = new WhitelistDictionary(context, locale);
- addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_WHITELIST, mWhiteListDictionary);
}
private void initAsynchronously(final Context context, final Locale locale,
final SuggestInitializationListener listener) {
resetMainDict(context, locale, listener);
-
- // TODO: read the whitelist and init the pool asynchronously too.
- // initPool should be done asynchronously now that the pool is thread-safe.
- initWhitelistAndAutocorrectAndPool(context, locale);
}
private static void addOrReplaceDictionary(
@@ -169,9 +158,17 @@
public SuggestedWords getSuggestedWords(
final WordComposer wordComposer, CharSequence prevWordForBigram,
final ProximityInfo proximityInfo, final boolean isCorrectionEnabled) {
+ return getSuggestedWordsWithSessionId(
+ wordComposer, prevWordForBigram, proximityInfo, isCorrectionEnabled, 0);
+ }
+
+ public SuggestedWords getSuggestedWordsWithSessionId(
+ final WordComposer wordComposer, CharSequence prevWordForBigram,
+ final ProximityInfo proximityInfo, final boolean isCorrectionEnabled, int sessionId) {
LatinImeLogger.onStartSuggestion(prevWordForBigram);
if (wordComposer.isBatchMode()) {
- return getSuggestedWordsForBatchInput(wordComposer, prevWordForBigram, proximityInfo);
+ return getSuggestedWordsForBatchInput(
+ wordComposer, prevWordForBigram, proximityInfo, sessionId);
} else {
return getSuggestedWordsForTypingInput(wordComposer, prevWordForBigram, proximityInfo,
isCorrectionEnabled);
@@ -208,15 +205,6 @@
wordComposerForLookup, prevWordForBigram, proximityInfo));
}
- final CharSequence whitelistedWordFromWhitelistDictionary =
- mWhiteListDictionary.getWhitelistedWord(consideredWord);
- if (whitelistedWordFromWhitelistDictionary != null) {
- // MAX_SCORE ensures this will be considered strong enough to be auto-corrected
- suggestionsSet.add(new SuggestedWordInfo(whitelistedWordFromWhitelistDictionary,
- SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_WHITELIST,
- Dictionary.TYPE_WHITELIST));
- }
-
final CharSequence whitelistedWord;
if (suggestionsSet.isEmpty()) {
whitelistedWord = null;
@@ -226,11 +214,6 @@
whitelistedWord = suggestionsSet.first().mWord;
}
- // TODO: Change this scheme - a boolean is not enough. A whitelisted word may be "valid"
- // but still autocorrected from - in the case the whitelist only capitalizes the word.
- // The whitelist should be case-insensitive, so it's not possible to be consistent with
- // a boolean flag. Right now this is handled with a slight hack in
- // WhitelistDictionary#shouldForciblyAutoCorrectFrom.
final boolean allowsToBeAutoCorrected = (null != whitelistedWord
&& !whitelistedWord.equals(consideredWord))
|| AutoCorrection.isNotAWord(mDictionaries, consideredWord,
@@ -259,7 +242,7 @@
}
final ArrayList<SuggestedWordInfo> suggestionsContainer =
- new ArrayList<SuggestedWordInfo>(suggestionsSet);
+ CollectionUtils.newArrayList(suggestionsSet);
final int suggestionsCount = suggestionsContainer.size();
final boolean isFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
final boolean isAllUpperCase = wordComposer.isAllUpperCase();
@@ -306,29 +289,28 @@
// Retrieves suggestions for the batch input.
private SuggestedWords getSuggestedWordsForBatchInput(
final WordComposer wordComposer, CharSequence prevWordForBigram,
- final ProximityInfo proximityInfo) {
+ final ProximityInfo proximityInfo, int sessionId) {
final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator,
MAX_SUGGESTIONS);
// At second character typed, search the unigrams (scores being affected by bigrams)
for (final String key : mDictionaries.keySet()) {
- // Skip UserUnigramDictionary and WhitelistDictionary to lookup
- if (key.equals(Dictionary.TYPE_USER_HISTORY)
- || key.equals(Dictionary.TYPE_WHITELIST)) {
+ // Skip User history dictionary for lookup
+ // TODO: The user history dictionary should just override getSuggestionsWithSessionId
+ // to make sure it doesn't return anything and we should remove this test
+ if (key.equals(Dictionary.TYPE_USER_HISTORY)) {
continue;
}
final Dictionary dictionary = mDictionaries.get(key);
- suggestionsSet.addAll(dictionary.getSuggestions(
- wordComposer, prevWordForBigram, proximityInfo));
+ suggestionsSet.addAll(dictionary.getSuggestionsWithSessionId(
+ wordComposer, prevWordForBigram, proximityInfo, sessionId));
}
final ArrayList<SuggestedWordInfo> suggestionsContainer =
- new ArrayList<SuggestedWordInfo>(suggestionsSet);
+ CollectionUtils.newArrayList(suggestionsSet);
final int suggestionsCount = suggestionsContainer.size();
- final boolean isFirstCharCapitalized = wordComposer.isAutoCapitalized();
- // TODO: Handle the manual temporary shifted mode.
- // TODO: Should handle TextUtils.CAP_MODE_CHARACTER.
- final boolean isAllUpperCase = false;
+ final boolean isFirstCharCapitalized = wordComposer.wasShiftedNoLock();
+ final boolean isAllUpperCase = wordComposer.isAllUpperCase();
if (isFirstCharCapitalized || isAllUpperCase) {
for (int i = 0; i < suggestionsCount; ++i) {
final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
@@ -356,7 +338,7 @@
typedWordInfo.setDebugString("+");
final int suggestionsSize = suggestions.size();
final ArrayList<SuggestedWordInfo> suggestionsList =
- new ArrayList<SuggestedWordInfo>(suggestionsSize);
+ CollectionUtils.newArrayList(suggestionsSize);
suggestionsList.add(typedWordInfo);
// Note: i here is the index in mScores[], but the index in mSuggestions is one more
// than i because we added the typed word to mSuggestions without touching mScores.
@@ -409,7 +391,7 @@
}
public void close() {
- final HashSet<Dictionary> dictionaries = new HashSet<Dictionary>();
+ final HashSet<Dictionary> dictionaries = CollectionUtils.newHashSet();
dictionaries.addAll(mDictionaries.values());
for (final Dictionary dictionary : dictionaries) {
dictionary.close();
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index 88fc006..68ecfa0 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -24,8 +24,10 @@
import java.util.HashSet;
public class SuggestedWords {
+ private static final ArrayList<SuggestedWordInfo> EMPTY_WORD_INFO_LIST =
+ CollectionUtils.newArrayList(0);
public static final SuggestedWords EMPTY = new SuggestedWords(
- new ArrayList<SuggestedWordInfo>(0), false, false, false, false, false);
+ EMPTY_WORD_INFO_LIST, false, false, false, false, false);
public final boolean mTypedWordValid;
// Note: this INCLUDES cases where the word will auto-correct to itself. A good definition
@@ -83,7 +85,7 @@
public static ArrayList<SuggestedWordInfo> getFromApplicationSpecifiedCompletions(
final CompletionInfo[] infos) {
- final ArrayList<SuggestedWordInfo> result = new ArrayList<SuggestedWordInfo>();
+ final ArrayList<SuggestedWordInfo> result = CollectionUtils.newArrayList();
for (CompletionInfo info : infos) {
if (null != info && info.getText() != null) {
result.add(new SuggestedWordInfo(info.getText(), SuggestedWordInfo.MAX_SCORE,
@@ -97,8 +99,8 @@
// and replace it with what the user currently typed.
public static ArrayList<SuggestedWordInfo> getTypedWordAndPreviousSuggestions(
final CharSequence typedWord, final SuggestedWords previousSuggestions) {
- final ArrayList<SuggestedWordInfo> suggestionsList = new ArrayList<SuggestedWordInfo>();
- final HashSet<String> alreadySeen = new HashSet<String>();
+ final ArrayList<SuggestedWordInfo> suggestionsList = CollectionUtils.newArrayList();
+ final HashSet<String> alreadySeen = CollectionUtils.newHashSet();
suggestionsList.add(new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE,
SuggestedWordInfo.KIND_TYPED, Dictionary.TYPE_USER_TYPED));
alreadySeen.add(typedWord.toString());
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
new file mode 100644
index 0000000..942c828
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.util.Log;
+
+import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
+import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.FusionDictionaryBufferInterface;
+import com.android.inputmethod.latin.makedict.FusionDictionary;
+import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
+import com.android.inputmethod.latin.makedict.PendingAttribute;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Reads and writes Binary files for a UserHistoryDictionary.
+ *
+ * All the methods in this class are static.
+ */
+public class UserHistoryDictIOUtils {
+ private static final String TAG = UserHistoryDictIOUtils.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ public interface OnAddWordListener {
+ public void setUnigram(final String word, final String shortcutTarget, final int frequency);
+ public void setBigram(final String word1, final String word2, final int frequency);
+ }
+
+ public interface BigramDictionaryInterface {
+ public int getFrequency(final String word1, final String word2);
+ }
+
+ public static final class ByteArrayWrapper implements FusionDictionaryBufferInterface {
+ private byte[] mBuffer;
+ private int mPosition;
+
+ ByteArrayWrapper(final byte[] buffer) {
+ mBuffer = buffer;
+ mPosition = 0;
+ }
+
+ @Override
+ public int readUnsignedByte() {
+ return ((int)mBuffer[mPosition++]) & 0xFF;
+ }
+
+ @Override
+ public int readUnsignedShort() {
+ final int retval = readUnsignedByte();
+ return (retval << 8) + readUnsignedByte();
+ }
+
+ @Override
+ public int readUnsignedInt24() {
+ final int retval = readUnsignedShort();
+ return (retval << 8) + readUnsignedByte();
+ }
+
+ @Override
+ public int readInt() {
+ final int retval = readUnsignedShort();
+ return (retval << 16) + readUnsignedShort();
+ }
+
+ @Override
+ public int position() {
+ return mPosition;
+ }
+
+ @Override
+ public void position(int position) {
+ mPosition = position;
+ }
+ }
+
+ /**
+ * Writes dictionary to file.
+ */
+ public static void writeDictionaryBinary(final OutputStream destination,
+ final BigramDictionaryInterface dict, final UserHistoryDictionaryBigramList bigrams,
+ final int version) {
+
+ final FusionDictionary fusionDict = constructFusionDictionary(dict, bigrams);
+
+ try {
+ BinaryDictInputOutput.writeDictionaryBinary(destination, fusionDict, version);
+ } catch (IOException e) {
+ Log.e(TAG, "IO exception while writing file: " + e);
+ } catch (UnsupportedFormatException e) {
+ Log.e(TAG, "Unsupported fomat: " + e);
+ }
+ }
+
+ /**
+ * Constructs a new FusionDictionary from BigramDictionaryInterface.
+ */
+ /* packages for test */ static FusionDictionary constructFusionDictionary(
+ final BigramDictionaryInterface dict, final UserHistoryDictionaryBigramList bigrams) {
+
+ final FusionDictionary fusionDict = new FusionDictionary(new Node(),
+ new FusionDictionary.DictionaryOptions(
+ new HashMap<String,String>(), false, false));
+
+ for (final String word1 : bigrams.keySet()) {
+ final HashMap<String, Byte> word1Bigrams = bigrams.getBigrams(word1);
+ for (final String word2 : word1Bigrams.keySet()) {
+ final int freq = dict.getFrequency(word1, word2);
+
+ if (DEBUG) {
+ if (word1 == null) {
+ Log.d(TAG, "add unigram: " + word2 + "," + Integer.toString(freq));
+ } else {
+ Log.d(TAG, "add bigram: " + word1
+ + "," + word2 + "," + Integer.toString(freq));
+ }
+ }
+
+ if (word1 == null) { // unigram
+ fusionDict.add(word2, freq, null, false /* isNotAWord */);
+ } else { // bigram
+ fusionDict.setBigram(word1, word2, freq);
+ }
+ bigrams.updateBigram(word1, word2, (byte)freq);
+ }
+ }
+
+ return fusionDict;
+ }
+
+ /**
+ * Reads dictionary from file.
+ */
+ public static void readDictionaryBinary(final FusionDictionaryBufferInterface buffer,
+ final OnAddWordListener dict) {
+ final Map<Integer, String> unigrams = CollectionUtils.newTreeMap();
+ final Map<Integer, Integer> frequencies = CollectionUtils.newTreeMap();
+ final Map<Integer, ArrayList<PendingAttribute>> bigrams = CollectionUtils.newTreeMap();
+
+ try {
+ BinaryDictInputOutput.readUnigramsAndBigramsBinary(buffer, unigrams, frequencies,
+ bigrams);
+ addWordsFromWordMap(unigrams, frequencies, bigrams, dict);
+ } catch (IOException e) {
+ Log.e(TAG, "IO exception while reading file: " + e);
+ } catch (UnsupportedFormatException e) {
+ Log.e(TAG, "Unsupported format: " + e);
+ }
+ }
+
+ /**
+ * Adds all unigrams and bigrams in maps to OnAddWordListener.
+ */
+ /* package for test */ static void addWordsFromWordMap(final Map<Integer, String> unigrams,
+ final Map<Integer, Integer> frequencies,
+ final Map<Integer, ArrayList<PendingAttribute>> bigrams, final OnAddWordListener to) {
+
+ for (Map.Entry<Integer, String> entry : unigrams.entrySet()) {
+ final String word1 = entry.getValue();
+ final int unigramFrequency = frequencies.get(entry.getKey());
+ to.setUnigram(word1, null, unigramFrequency);
+
+ final ArrayList<PendingAttribute> attrList = bigrams.get(entry.getKey());
+
+ if (attrList != null) {
+ for (final PendingAttribute attr : attrList) {
+ to.setBigram(word1, unigrams.get(attr.mAddress),
+ BinaryDictInputOutput.reconstructBigramFrequency(unigramFrequency,
+ attr.mFrequency));
+ }
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
index 3bb670c..6c9d1c2 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
@@ -52,14 +52,14 @@
private static final int FREQUENCY_FOR_TYPED = 2;
/** Maximum number of pairs. Pruning will start when databases goes above this number. */
- private static int sMaxHistoryBigrams = 10000;
+ public static final int sMaxHistoryBigrams = 10000;
/**
* When it hits maximum bigram pair, it will delete until you are left with
* only (sMaxHistoryBigrams - sDeleteHistoryBigrams) pairs.
* Do not keep this number small to avoid deleting too often.
*/
- private static int sDeleteHistoryBigrams = 1000;
+ public static final int sDeleteHistoryBigrams = 1000;
/**
* Database version should increase if the database structure changes
@@ -93,10 +93,10 @@
private final static HashMap<String, String> sDictProjectionMap;
private final static ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>
- sLangDictCache = new ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>();
+ sLangDictCache = CollectionUtils.newConcurrentHashMap();
static {
- sDictProjectionMap = new HashMap<String, String>();
+ sDictProjectionMap = CollectionUtils.newHashMap();
sDictProjectionMap.put(MAIN_COLUMN_ID, MAIN_COLUMN_ID);
sDictProjectionMap.put(MAIN_COLUMN_WORD1, MAIN_COLUMN_WORD1);
sDictProjectionMap.put(MAIN_COLUMN_WORD2, MAIN_COLUMN_WORD2);
@@ -109,12 +109,8 @@
private static DatabaseHelper sOpenHelper = null;
- public void setDatabaseMax(int maxHistoryBigram) {
- sMaxHistoryBigrams = maxHistoryBigram;
- }
-
- public void setDatabaseDelete(int deleteHistoryBigram) {
- sDeleteHistoryBigrams = deleteHistoryBigram;
+ public String getLocale() {
+ return mLocale;
}
public synchronized static UserHistoryDictionary getInstance(
@@ -502,9 +498,11 @@
needsToSave(fc, isValid, addLevel0Bigram)) {
freq = fc;
} else {
+ // Delete this entry
freq = -1;
}
} else {
+ // Delete this entry
freq = -1;
}
}
@@ -541,6 +539,7 @@
getContentValues(word1, word2, mLocale));
pairId = pairIdLong.intValue();
}
+ // Eliminate freq == 0 because that word is profanity.
if (freq > 0) {
if (PROFILE_SAVE_RESTORE) {
++profInsert;
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java
index 610652a..bb0f542 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java
@@ -29,9 +29,8 @@
public class UserHistoryDictionaryBigramList {
public static final byte FORGETTING_CURVE_INITIAL_VALUE = 0;
private static final String TAG = UserHistoryDictionaryBigramList.class.getSimpleName();
- private static final HashMap<String, Byte> EMPTY_BIGRAM_MAP = new HashMap<String, Byte>();
- private final HashMap<String, HashMap<String, Byte>> mBigramMap =
- new HashMap<String, HashMap<String, Byte>>();
+ private static final HashMap<String, Byte> EMPTY_BIGRAM_MAP = CollectionUtils.newHashMap();
+ private final HashMap<String, HashMap<String, Byte>> mBigramMap = CollectionUtils.newHashMap();
private int mSize = 0;
public void evictAll() {
@@ -57,7 +56,7 @@
if (mBigramMap.containsKey(word1)) {
map = mBigramMap.get(word1);
} else {
- map = new HashMap<String, Byte>();
+ map = CollectionUtils.newHashMap();
mBigramMap.put(word1, map);
}
if (!map.containsKey(word2)) {
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java b/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java
index 5a2fdf4..3d3bd98 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java
@@ -19,7 +19,7 @@
import android.text.format.DateUtils;
import android.util.Log;
-public class UserHistoryForgettingCurveUtils {
+public final class UserHistoryForgettingCurveUtils {
private static final String TAG = UserHistoryForgettingCurveUtils.class.getSimpleName();
private static final boolean DEBUG = false;
private static final int FC_FREQ_MAX = 127;
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index c6b5c33..1c98b92 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -16,20 +16,16 @@
package com.android.inputmethod.latin;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.content.res.Resources;
import android.inputmethodservice.InputMethodService;
import android.net.Uri;
import android.os.AsyncTask;
-import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
import android.text.TextUtils;
-import android.text.format.DateUtils;
import android.util.Log;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
@@ -45,9 +41,8 @@
import java.nio.channels.FileChannel;
import java.text.SimpleDateFormat;
import java.util.Date;
-import java.util.HashMap;
-public class Utils {
+public final class Utils {
private Utils() {
// This utility class is not publicly instantiable.
}
@@ -65,44 +60,6 @@
}
}
- public static class 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.
- public static final int GC_TRY_LOOP_MAX = 5;
- private static final long GC_INTERVAL = DateUtils.SECOND_IN_MILLIS;
- private static GCUtils sInstance = new GCUtils();
- private int mGCTryCount = 0;
-
- public static GCUtils getInstance() {
- return sInstance;
- }
-
- public void reset() {
- mGCTryCount = 0;
- }
-
- public boolean tryGCOrWait(String metaData, Throwable t) {
- if (mGCTryCount == 0) {
- System.gc();
- }
- if (++mGCTryCount > GC_TRY_COUNT) {
- LatinImeLogger.logOnException(metaData, t);
- return false;
- } else {
- try {
- Thread.sleep(GC_INTERVAL);
- return true;
- } catch (InterruptedException e) {
- Log.e(GC_TAG, "Sleep was interrupted.");
- LatinImeLogger.logOnException(metaData, t);
- return false;
- }
- }
- }
- }
-
/* package */ static class RingCharBuffer {
private static RingCharBuffer sRingCharBuffer = new RingCharBuffer();
private static final char PLACEHOLDER_DELIMITER_CHAR = '\uFFFC';
@@ -222,7 +179,7 @@
return getStackTrace(Integer.MAX_VALUE - 1);
}
- public static class UsabilityStudyLogUtils {
+ public static final class UsabilityStudyLogUtils {
// TODO: remove code duplication with ResearchLog class
private static final String USABILITY_TAG = UsabilityStudyLogUtils.class.getSimpleName();
private static final String FILENAME = "log.txt";
@@ -431,34 +388,38 @@
}
}
- public static float getDipScale(Context context) {
- final float scale = context.getResources().getDisplayMetrics().density;
- return scale;
- }
-
- /** Convert pixel to DIP */
- public static int dipToPixel(float scale, int dip) {
- return (int) (dip * scale + 0.5);
- }
-
- public static class Stats {
+ public static final class Stats {
public static void onNonSeparator(final char code, final int x,
final int y) {
RingCharBuffer.getInstance().push(code, x, y);
LatinImeLogger.logOnInputChar();
}
- public static void onSeparator(final int code, final int x,
- final int y) {
- // TODO: accept code points
- RingCharBuffer.getInstance().push((char)code, x, y);
+ public static void onSeparator(final int code, final int x, final int y) {
+ // Helper method to log a single code point separator
+ // TODO: cache this mapping of a code point to a string in a sparse array in StringUtils
+ onSeparator(new String(new int[]{code}, 0, 1), x, y);
+ }
+
+ public static void onSeparator(final String separator, final int x, final int y) {
+ final int length = separator.length();
+ for (int i = 0; i < length; i = Character.offsetByCodePoints(separator, i, 1)) {
+ int codePoint = Character.codePointAt(separator, i);
+ // TODO: accept code points
+ RingCharBuffer.getInstance().push((char)codePoint, x, y);
+ }
LatinImeLogger.logOnInputSeparator();
}
public static void onAutoCorrection(final String typedWord, final String correctedWord,
- final int separatorCode) {
+ final String separatorString) {
if (TextUtils.isEmpty(typedWord)) return;
- LatinImeLogger.logOnAutoCorrection(typedWord, correctedWord, separatorCode);
+ // TODO: this fails when the separator is more than 1 code point long, but
+ // the backend can't handle it yet. The only case when this happens is with
+ // smileys and other multi-character keys.
+ final int codePoint = TextUtils.isEmpty(separatorString) ? Constants.NOT_A_CODE
+ : separatorString.codePointAt(0);
+ LatinImeLogger.logOnAutoCorrection(typedWord, correctedWord, codePoint);
}
public static void onAutoCorrectionCancellation() {
@@ -474,60 +435,4 @@
if (TextUtils.isEmpty(info)) return null;
return info;
}
-
- private static final String HARDWARE_PREFIX = Build.HARDWARE + ",";
- private static final HashMap<String, String> sDeviceOverrideValueMap =
- new HashMap<String, String>();
-
- public static String getDeviceOverrideValue(Resources res, int overrideResId, String defValue) {
- final int orientation = res.getConfiguration().orientation;
- final String key = overrideResId + "-" + orientation;
- if (!sDeviceOverrideValueMap.containsKey(key)) {
- String overrideValue = defValue;
- for (final String element : res.getStringArray(overrideResId)) {
- if (element.startsWith(HARDWARE_PREFIX)) {
- overrideValue = element.substring(HARDWARE_PREFIX.length());
- break;
- }
- }
- sDeviceOverrideValueMap.put(key, overrideValue);
- }
- return sDeviceOverrideValueMap.get(key);
- }
-
- private static final HashMap<String, Long> EMPTY_LT_HASH_MAP = new HashMap<String, Long>();
- private static final String LOCALE_AND_TIME_STR_SEPARATER = ",";
- public static HashMap<String, Long> localeAndTimeStrToHashMap(String str) {
- if (TextUtils.isEmpty(str)) {
- return EMPTY_LT_HASH_MAP;
- }
- final String[] ss = str.split(LOCALE_AND_TIME_STR_SEPARATER);
- final int N = ss.length;
- if (N < 2 || N % 2 != 0) {
- return EMPTY_LT_HASH_MAP;
- }
- final HashMap<String, Long> retval = new HashMap<String, Long>();
- for (int i = 0; i < N / 2; ++i) {
- final String localeStr = ss[i * 2];
- final long time = Long.valueOf(ss[i * 2 + 1]);
- retval.put(localeStr, time);
- }
- return retval;
- }
-
- public static String localeAndTimeHashMapToStr(HashMap<String, Long> map) {
- if (map == null || map.isEmpty()) {
- return "";
- }
- final StringBuilder builder = new StringBuilder();
- for (String localeStr : map.keySet()) {
- if (builder.length() > 0) {
- builder.append(LOCALE_AND_TIME_STR_SEPARATER);
- }
- final Long time = map.get(localeStr);
- builder.append(localeStr).append(LOCALE_AND_TIME_STR_SEPARATER);
- builder.append(String.valueOf(time));
- }
- return builder.toString();
- }
}
diff --git a/java/src/com/android/inputmethod/latin/VibratorUtils.java b/java/src/com/android/inputmethod/latin/VibratorUtils.java
index 33ffdd9..b6696ce 100644
--- a/java/src/com/android/inputmethod/latin/VibratorUtils.java
+++ b/java/src/com/android/inputmethod/latin/VibratorUtils.java
@@ -19,7 +19,7 @@
import android.content.Context;
import android.os.Vibrator;
-public class VibratorUtils {
+public final class VibratorUtils {
private static final VibratorUtils sInstance = new VibratorUtils();
private Vibrator mVibrator;
diff --git a/java/src/com/android/inputmethod/latin/WhitelistDictionary.java b/java/src/com/android/inputmethod/latin/WhitelistDictionary.java
deleted file mode 100644
index 14476dc..0000000
--- a/java/src/com/android/inputmethod/latin/WhitelistDictionary.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.latin;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.inputmethod.keyboard.ProximityInfo;
-import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Locale;
-
-public class WhitelistDictionary extends ExpandableDictionary {
-
- private static final boolean DBG = LatinImeLogger.sDBG;
- private static final String TAG = WhitelistDictionary.class.getSimpleName();
-
- private final HashMap<String, Pair<Integer, String>> mWhitelistWords =
- new HashMap<String, Pair<Integer, String>>();
-
- // TODO: Conform to the async load contact of ExpandableDictionary
- public WhitelistDictionary(final Context context, final Locale locale) {
- super(context, Dictionary.TYPE_WHITELIST);
- // TODO: Move whitelist dictionary into main dictionary.
- final RunInLocale<Void> job = new RunInLocale<Void>() {
- @Override
- protected Void job(Resources res) {
- initWordlist(res.getStringArray(R.array.wordlist_whitelist));
- return null;
- }
- };
- job.runInLocale(context.getResources(), locale);
- }
-
- private void initWordlist(String[] wordlist) {
- mWhitelistWords.clear();
- final int N = wordlist.length;
- if (N % 3 != 0) {
- if (DBG) {
- Log.d(TAG, "The number of the whitelist is invalid.");
- }
- return;
- }
- try {
- for (int i = 0; i < N; i += 3) {
- final int score = Integer.valueOf(wordlist[i]);
- final String before = wordlist[i + 1];
- final String after = wordlist[i + 2];
- if (before != null && after != null) {
- mWhitelistWords.put(
- before.toLowerCase(), new Pair<Integer, String>(score, after));
- addWord(after, null /* shortcut */, score);
- }
- }
- } catch (NumberFormatException e) {
- if (DBG) {
- Log.d(TAG, "The score of the word is invalid.");
- }
- }
- }
-
- public String getWhitelistedWord(String before) {
- if (before == null) return null;
- final String lowerCaseBefore = before.toLowerCase();
- if(mWhitelistWords.containsKey(lowerCaseBefore)) {
- if (DBG) {
- Log.d(TAG, "--- found whitelistedWord: " + lowerCaseBefore);
- }
- return mWhitelistWords.get(lowerCaseBefore).second;
- }
- return null;
- }
-
- @Override
- public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final CharSequence prevWord, final ProximityInfo proximityInfo) {
- // Whitelist does not supply any suggestions or predictions.
- return null;
- }
-
- // See LatinIME#updateSuggestions. This breaks in the (queer) case that the whitelist
- // lists that word a should autocorrect to word b, and word c would autocorrect to
- // an upper-cased version of a. In this case, the way this return value is used would
- // remove the first candidate when the user typed the upper-cased version of A.
- // Example : abc -> def and xyz -> Abc
- // A user typing Abc would experience it being autocorrected to something else (not
- // necessarily def).
- // There is no such combination in the whitelist at the time and there probably won't
- // ever be - it doesn't make sense. But still.
- public boolean shouldForciblyAutoCorrectFrom(CharSequence word) {
- if (TextUtils.isEmpty(word)) return false;
- final String correction = getWhitelistedWord(word.toString());
- if (TextUtils.isEmpty(correction)) return false;
- return !correction.equals(word);
- }
-
- // Leave implementation of getWords and isValidWord to the superclass.
- // The words have been added to the ExpandableDictionary with addWord() inside initWordlist.
-}
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 5606a58..4b7adf2 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -17,7 +17,6 @@
package com.android.inputmethod.latin;
import com.android.inputmethod.keyboard.Key;
-import com.android.inputmethod.keyboard.KeyDetector;
import com.android.inputmethod.keyboard.Keyboard;
import java.util.Arrays;
@@ -26,12 +25,16 @@
* 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;
- public static final int NOT_A_COORDINATE = -1;
-
private static final int N = BinaryDictionary.MAX_WORD_LENGTH;
+ public static final int CAPS_MODE_OFF = 0;
+ // 1 is shift bit, 2 is caps bit, 4 is auto bit but this is just a convention as these bits
+ // aren't used anywhere in the code
+ public static final int CAPS_MODE_MANUAL_SHIFTED = 0x1;
+ public static final int CAPS_MODE_MANUAL_SHIFT_LOCKED = 0x3;
+ public static final int CAPS_MODE_AUTO_SHIFTED = 0x5;
+ public static final int CAPS_MODE_AUTO_SHIFT_LOCKED = 0x7;
+
private int[] mPrimaryKeyCodes;
private final InputPointers mInputPointers = new InputPointers(N);
private final StringBuilder mTypedWord;
@@ -42,7 +45,7 @@
// Cache these values for performance
private int mCapsCount;
private int mDigitsCount;
- private boolean mAutoCapitalized;
+ private int mCapitalizedMode;
private int mTrailingSingleQuotesCount;
private int mCodePointSize;
@@ -68,7 +71,7 @@
mCapsCount = source.mCapsCount;
mDigitsCount = source.mDigitsCount;
mIsFirstCharCapitalized = source.mIsFirstCharCapitalized;
- mAutoCapitalized = source.mAutoCapitalized;
+ mCapitalizedMode = source.mCapitalizedMode;
mTrailingSingleQuotesCount = source.mTrailingSingleQuotesCount;
mIsResumed = source.mIsResumed;
mIsBatchMode = source.mIsBatchMode;
@@ -166,7 +169,7 @@
final int codePoint = Character.codePointAt(word, i);
// We don't want to override the batch input points that are held in mInputPointers
// (See {@link #add(int,int,int)}).
- add(codePoint, NOT_A_COORDINATE, NOT_A_COORDINATE);
+ add(codePoint, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
}
}
@@ -181,7 +184,7 @@
add(codePoint, x, y);
return;
}
- add(codePoint, NOT_A_COORDINATE, NOT_A_COORDINATE);
+ add(codePoint, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
}
/**
@@ -262,7 +265,14 @@
* @return true if all user typed chars are upper case, false otherwise
*/
public boolean isAllUpperCase() {
- return (mCapsCount > 0) && (mCapsCount == size());
+ return mCapitalizedMode == CAPS_MODE_AUTO_SHIFT_LOCKED
+ || mCapitalizedMode == CAPS_MODE_MANUAL_SHIFT_LOCKED
+ || (mCapsCount > 0) && (mCapsCount == size());
+ }
+
+ public boolean wasShiftedNoLock() {
+ return mCapitalizedMode == CAPS_MODE_AUTO_SHIFTED
+ || mCapitalizedMode == CAPS_MODE_MANUAL_SHIFTED;
}
/**
@@ -280,20 +290,27 @@
}
/**
- * Saves the reason why the word is capitalized - whether it was automatic or
- * due to the user hitting shift in the middle of a sentence.
- * @param auto whether it was an automatic capitalization due to start of sentence
+ * Saves the caps mode at the start of composing.
+ *
+ * WordComposer needs to know about this for several reasons. The first is, we need to know
+ * after the fact what the reason was, to register the correct form into the user history
+ * dictionary: if the word was automatically capitalized, we should insert it in all-lower
+ * case but if it's a manual pressing of shift, then it should be inserted as is.
+ * Also, batch input needs to know about the current caps mode to display correctly
+ * capitalized suggestions.
+ * @param mode the mode at the time of start
*/
- public void setAutoCapitalized(boolean auto) {
- mAutoCapitalized = auto;
+ public void setCapitalizedModeAtStartComposingTime(final int mode) {
+ mCapitalizedMode = mode;
}
/**
* Returns whether the word was automatically capitalized.
* @return whether the word was automatically capitalized
*/
- public boolean isAutoCapitalized() {
- return mAutoCapitalized;
+ public boolean wasAutoCapitalized() {
+ return mCapitalizedMode == CAPS_MODE_AUTO_SHIFT_LOCKED
+ || mCapitalizedMode == CAPS_MODE_AUTO_SHIFTED;
}
/**
@@ -319,14 +336,14 @@
// `type' should be one of the LastComposedWord.COMMIT_TYPE_* constants above.
public LastComposedWord commitWord(final int type, final String committedWord,
- final int separatorCode, final CharSequence prevWord) {
+ final String separatorString, final CharSequence prevWord) {
// Note: currently, we come here whenever we commit a word. If it's a MANUAL_PICK
// or a DECIDED_WORD we may cancel the commit later; otherwise, we should deactivate
// the last composed word to ensure this does not happen.
final int[] primaryKeyCodes = mPrimaryKeyCodes;
mPrimaryKeyCodes = new int[N];
final LastComposedWord lastComposedWord = new LastComposedWord(primaryKeyCodes,
- mInputPointers, mTypedWord.toString(), committedWord, separatorCode,
+ mInputPointers, mTypedWord.toString(), committedWord, separatorString,
prevWord);
mInputPointers.reset();
if (type != LastComposedWord.COMMIT_TYPE_DECIDED_WORD
diff --git a/java/src/com/android/inputmethod/latin/XmlParseUtils.java b/java/src/com/android/inputmethod/latin/XmlParseUtils.java
index 481cdfa..b5cbaf1 100644
--- a/java/src/com/android/inputmethod/latin/XmlParseUtils.java
+++ b/java/src/com/android/inputmethod/latin/XmlParseUtils.java
@@ -23,7 +23,7 @@
import java.io.IOException;
-public class XmlParseUtils {
+public final class XmlParseUtils {
private XmlParseUtils() {
// This utility class is not publicly instantiable.
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
index 2c3eee7..abc39d9 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
@@ -22,15 +22,19 @@
import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
-import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
+import java.util.Stack;
import java.util.TreeMap;
/**
@@ -52,6 +56,8 @@
* s | has a terminal ? 1 bit, 1 = yes, 0 = no : FLAG_IS_TERMINAL
* | has shortcut targets ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_SHORTCUT_TARGETS
* | has bigrams ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_BIGRAMS
+ * | is not a word ? 1 bit, 1 = yes, 0 = no : FLAG_IS_NOT_A_WORD
+ * | is blacklisted ? 1 bit, 1 = yes, 0 = no : FLAG_IS_BLACKLISTED
*
* c | IF FLAG_HAS_MULTIPLE_CHARS
* h | char, char, char, char n * (1 or 3 bytes) : use CharGroupInfo for i/o helpers
@@ -124,7 +130,7 @@
*/
private static final int VERSION_1_MAGIC_NUMBER = 0x78B1;
- private static final int VERSION_2_MAGIC_NUMBER = 0x9BC13AFE;
+ public static final int VERSION_2_MAGIC_NUMBER = 0x9BC13AFE;
private static final int MINIMUM_SUPPORTED_VERSION = 1;
private static final int MAXIMUM_SUPPORTED_VERSION = 2;
private static final int NOT_A_VERSION_NUMBER = -1;
@@ -150,6 +156,8 @@
private static final int FLAG_IS_TERMINAL = 0x10;
private static final int FLAG_HAS_SHORTCUT_TARGETS = 0x08;
private static final int FLAG_HAS_BIGRAMS = 0x04;
+ private static final int FLAG_IS_NOT_A_WORD = 0x02;
+ private static final int FLAG_IS_BLACKLISTED = 0x01;
private static final int FLAG_ATTRIBUTE_HAS_NEXT = 0x80;
private static final int FLAG_ATTRIBUTE_OFFSET_NEGATIVE = 0x40;
@@ -185,6 +193,54 @@
// suspicion that a bug might be causing an infinite loop.
private static final int MAX_PASSES = 24;
+ public interface FusionDictionaryBufferInterface {
+ public int readUnsignedByte();
+ public int readUnsignedShort();
+ public int readUnsignedInt24();
+ public int readInt();
+ public int position();
+ public void position(int newPosition);
+ }
+
+ public static final class ByteBufferWrapper implements FusionDictionaryBufferInterface {
+ private ByteBuffer mBuffer;
+
+ public ByteBufferWrapper(final ByteBuffer buffer) {
+ mBuffer = buffer;
+ }
+
+ @Override
+ public int readUnsignedByte() {
+ return ((int)mBuffer.get()) & 0xFF;
+ }
+
+ @Override
+ public int readUnsignedShort() {
+ return ((int)mBuffer.getShort()) & 0xFFFF;
+ }
+
+ @Override
+ public int readUnsignedInt24() {
+ final int retval = readUnsignedByte();
+ return (retval << 16) + readUnsignedShort();
+ }
+
+ @Override
+ public int readInt() {
+ return mBuffer.getInt();
+ }
+
+ @Override
+ public int position() {
+ return mBuffer.position();
+ }
+
+ @Override
+ public void position(int newPos) {
+ mBuffer.position(newPos);
+ }
+ }
+
/**
* A class grouping utility function for our specific character encoding.
*/
@@ -307,33 +363,32 @@
}
/**
- * Reads a string from a RandomAccessFile. This is the converse of the above method.
+ * Reads a string from a buffer. This is the converse of the above method.
*/
- private static String readString(final RandomAccessFile source) throws IOException {
+ private static String readString(final FusionDictionaryBufferInterface buffer) {
final StringBuilder s = new StringBuilder();
- int character = readChar(source);
+ int character = readChar(buffer);
while (character != INVALID_CHARACTER) {
s.appendCodePoint(character);
- character = readChar(source);
+ character = readChar(buffer);
}
return s.toString();
}
/**
- * Reads a character from the file.
+ * Reads a character from the buffer.
*
* This follows the character format documented earlier in this source file.
*
- * @param source the file, positioned over an encoded character.
+ * @param buffer the buffer, positioned over an encoded character.
* @return the character code.
*/
- private static int readChar(RandomAccessFile source) throws IOException {
- int character = source.readUnsignedByte();
+ private static int readChar(final FusionDictionaryBufferInterface buffer) {
+ int character = buffer.readUnsignedByte();
if (!fitsOnOneByte(character)) {
- if (GROUP_CHARACTERS_TERMINATOR == character)
- return INVALID_CHARACTER;
+ if (GROUP_CHARACTERS_TERMINATOR == character) return INVALID_CHARACTER;
character <<= 16;
- character += source.readUnsignedShort();
+ character += buffer.readUnsignedShort();
}
return character;
}
@@ -728,6 +783,12 @@
}
flags |= FLAG_HAS_BIGRAMS;
}
+ if (group.mIsNotAWord) {
+ flags |= FLAG_IS_NOT_A_WORD;
+ }
+ if (group.mIsBlacklistEntry) {
+ flags |= FLAG_IS_BLACKLISTED;
+ }
return flags;
}
@@ -783,10 +844,10 @@
// their lower bound and exclude their higher bound so we need to have the first step
// start at exactly 1 unit higher than floor(unigramFreq + half a step).
// Note : to reconstruct the score, the dictionary reader will need to divide
- // MAX_TERMINAL_FREQUENCY - unigramFreq by 16.5 likewise, and add
- // (discretizedFrequency + 0.5) times this value to get the median value of the step,
- // which is the best approximation. This is how we get the most precise result with
- // only four bits.
+ // MAX_TERMINAL_FREQUENCY - unigramFreq by 16.5 likewise to get the value of the step,
+ // and add (discretizedFrequency + 0.5 + 0.5) times this value to get the best
+ // approximation. (0.5 to get the first step start, and 0.5 to get the middle of the
+ // step pointed by the discretized frequency.
final float stepSize =
(MAX_TERMINAL_FREQUENCY - unigramFrequency) / (1.5f + MAX_BIGRAM_FREQUENCY);
final float firstStepStart = 1 + unigramFrequency + (stepSize / 2.0f);
@@ -1091,46 +1152,46 @@
// readDictionaryBinary is the public entry point for them.
static final int[] characterBuffer = new int[MAX_WORD_LENGTH];
- private static CharGroupInfo readCharGroup(RandomAccessFile source,
- final int originalGroupAddress) throws IOException {
+ private static CharGroupInfo readCharGroup(final FusionDictionaryBufferInterface buffer,
+ final int originalGroupAddress) {
int addressPointer = originalGroupAddress;
- final int flags = source.readUnsignedByte();
+ final int flags = buffer.readUnsignedByte();
++addressPointer;
final int characters[];
if (0 != (flags & FLAG_HAS_MULTIPLE_CHARS)) {
int index = 0;
- int character = CharEncoding.readChar(source);
+ int character = CharEncoding.readChar(buffer);
addressPointer += CharEncoding.getCharSize(character);
while (-1 != character) {
characterBuffer[index++] = character;
- character = CharEncoding.readChar(source);
+ character = CharEncoding.readChar(buffer);
addressPointer += CharEncoding.getCharSize(character);
}
characters = Arrays.copyOfRange(characterBuffer, 0, index);
} else {
- final int character = CharEncoding.readChar(source);
+ final int character = CharEncoding.readChar(buffer);
addressPointer += CharEncoding.getCharSize(character);
characters = new int[] { character };
}
final int frequency;
if (0 != (FLAG_IS_TERMINAL & flags)) {
++addressPointer;
- frequency = source.readUnsignedByte();
+ frequency = buffer.readUnsignedByte();
} else {
frequency = CharGroup.NOT_A_TERMINAL;
}
int childrenAddress = addressPointer;
switch (flags & MASK_GROUP_ADDRESS_TYPE) {
case FLAG_GROUP_ADDRESS_TYPE_ONEBYTE:
- childrenAddress += source.readUnsignedByte();
+ childrenAddress += buffer.readUnsignedByte();
addressPointer += 1;
break;
case FLAG_GROUP_ADDRESS_TYPE_TWOBYTES:
- childrenAddress += source.readUnsignedShort();
+ childrenAddress += buffer.readUnsignedShort();
addressPointer += 2;
break;
case FLAG_GROUP_ADDRESS_TYPE_THREEBYTES:
- childrenAddress += (source.readUnsignedByte() << 16) + source.readUnsignedShort();
+ childrenAddress += buffer.readUnsignedInt24();
addressPointer += 3;
break;
case FLAG_GROUP_ADDRESS_TYPE_NOADDRESS:
@@ -1140,38 +1201,38 @@
}
ArrayList<WeightedString> shortcutTargets = null;
if (0 != (flags & FLAG_HAS_SHORTCUT_TARGETS)) {
- final long pointerBefore = source.getFilePointer();
+ final int pointerBefore = buffer.position();
shortcutTargets = new ArrayList<WeightedString>();
- source.readUnsignedShort(); // Skip the size
+ buffer.readUnsignedShort(); // Skip the size
while (true) {
- final int targetFlags = source.readUnsignedByte();
- final String word = CharEncoding.readString(source);
+ final int targetFlags = buffer.readUnsignedByte();
+ final String word = CharEncoding.readString(buffer);
shortcutTargets.add(new WeightedString(word,
targetFlags & FLAG_ATTRIBUTE_FREQUENCY));
if (0 == (targetFlags & FLAG_ATTRIBUTE_HAS_NEXT)) break;
}
- addressPointer += (source.getFilePointer() - pointerBefore);
+ addressPointer += buffer.position() - pointerBefore;
}
ArrayList<PendingAttribute> bigrams = null;
if (0 != (flags & FLAG_HAS_BIGRAMS)) {
bigrams = new ArrayList<PendingAttribute>();
while (true) {
- final int bigramFlags = source.readUnsignedByte();
+ final int bigramFlags = buffer.readUnsignedByte();
++addressPointer;
final int sign = 0 == (bigramFlags & FLAG_ATTRIBUTE_OFFSET_NEGATIVE) ? 1 : -1;
int bigramAddress = addressPointer;
switch (bigramFlags & MASK_ATTRIBUTE_ADDRESS_TYPE) {
case FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE:
- bigramAddress += sign * source.readUnsignedByte();
+ bigramAddress += sign * buffer.readUnsignedByte();
addressPointer += 1;
break;
case FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES:
- bigramAddress += sign * source.readUnsignedShort();
+ bigramAddress += sign * buffer.readUnsignedShort();
addressPointer += 2;
break;
case FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES:
- final int offset = ((source.readUnsignedByte() << 16)
- + source.readUnsignedShort());
+ final int offset = (buffer.readUnsignedByte() << 16)
+ + buffer.readUnsignedShort();
bigramAddress += sign * offset;
addressPointer += 3;
break;
@@ -1188,15 +1249,15 @@
}
/**
- * Reads and returns the char group count out of a file and forwards the pointer.
+ * Reads and returns the char group count out of a buffer and forwards the pointer.
*/
- private static int readCharGroupCount(RandomAccessFile source) throws IOException {
- final int msb = source.readUnsignedByte();
+ private static int readCharGroupCount(final FusionDictionaryBufferInterface buffer) {
+ final int msb = buffer.readUnsignedByte();
if (MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT >= msb) {
return msb;
} else {
return ((MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT & msb) << 8)
- + source.readUnsignedByte();
+ + buffer.readUnsignedByte();
}
}
@@ -1204,31 +1265,29 @@
// of this method. Since it performs direct, unbuffered random access to the file and
// may be called hundreds of thousands of times, the resulting performance is not
// reasonable without some kind of cache. Thus:
- // TODO: perform buffered I/O here and in other places in the code.
private static TreeMap<Integer, String> wordCache = new TreeMap<Integer, String>();
/**
* Finds, as a string, the word at the address passed as an argument.
*
- * @param source the file to read from.
+ * @param buffer the buffer to read from.
* @param headerSize the size of the header.
* @param address the address to seek.
* @return the word, as a string.
- * @throws IOException if the file can't be read.
*/
- private static String getWordAtAddress(final RandomAccessFile source, final long headerSize,
- int address) throws IOException {
+ private static String getWordAtAddress(final FusionDictionaryBufferInterface buffer,
+ final int headerSize, final int address) {
final String cachedString = wordCache.get(address);
if (null != cachedString) return cachedString;
- final long originalPointer = source.getFilePointer();
- source.seek(headerSize);
- final int count = readCharGroupCount(source);
+ final int originalPointer = buffer.position();
+ buffer.position(headerSize);
+ final int count = readCharGroupCount(buffer);
int groupOffset = getGroupCountSize(count);
final StringBuilder builder = new StringBuilder();
String result = null;
CharGroupInfo last = null;
for (int i = count - 1; i >= 0; --i) {
- CharGroupInfo info = readCharGroup(source, groupOffset);
+ CharGroupInfo info = readCharGroup(buffer, groupOffset);
groupOffset = info.mEndAddress;
if (info.mOriginalAddress == address) {
builder.append(new String(info.mCharacters, 0, info.mCharacters.length));
@@ -1239,9 +1298,9 @@
if (info.mChildrenAddress > address) {
if (null == last) continue;
builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
- source.seek(last.mChildrenAddress + headerSize);
+ buffer.position(last.mChildrenAddress + headerSize);
groupOffset = last.mChildrenAddress + 1;
- i = source.readUnsignedByte();
+ i = buffer.readUnsignedByte();
last = null;
continue;
}
@@ -1249,64 +1308,69 @@
}
if (0 == i && hasChildrenAddress(last.mChildrenAddress)) {
builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
- source.seek(last.mChildrenAddress + headerSize);
+ buffer.position(last.mChildrenAddress + headerSize);
groupOffset = last.mChildrenAddress + 1;
- i = source.readUnsignedByte();
+ i = buffer.readUnsignedByte();
last = null;
continue;
}
}
- source.seek(originalPointer);
+ buffer.position(originalPointer);
wordCache.put(address, result);
return result;
}
/**
- * Reads a single node from a binary file.
+ * Reads a single node from a buffer.
*
- * This methods reads the file at the current position of its file pointer. A node is
- * fully expected to start at the current position.
+ * This methods reads the file at the current position. A node is fully expected to start at
+ * the current position.
* This will recursively read other nodes into the structure, populating the reverse
* maps on the fly and using them to keep track of already read nodes.
*
- * @param source the data file, correctly positioned at the start of a node.
+ * @param buffer the buffer, correctly positioned at the start of a node.
* @param headerSize the size, in bytes, of the file header.
* @param reverseNodeMap a mapping from addresses to already read nodes.
* @param reverseGroupMap a mapping from addresses to already read character groups.
* @return the read node with all his children already read.
*/
- private static Node readNode(RandomAccessFile source, long headerSize,
- Map<Integer, Node> reverseNodeMap, Map<Integer, CharGroup> reverseGroupMap)
+ private static Node readNode(final FusionDictionaryBufferInterface buffer, final int headerSize,
+ final Map<Integer, Node> reverseNodeMap, final Map<Integer, CharGroup> reverseGroupMap)
throws IOException {
- final int nodeOrigin = (int)(source.getFilePointer() - headerSize);
- final int count = readCharGroupCount(source);
+ final int nodeOrigin = buffer.position() - headerSize;
+ final int count = readCharGroupCount(buffer);
final ArrayList<CharGroup> nodeContents = new ArrayList<CharGroup>();
int groupOffset = nodeOrigin + getGroupCountSize(count);
for (int i = count; i > 0; --i) {
- CharGroupInfo info = readCharGroup(source, groupOffset);
+ CharGroupInfo info = readCharGroup(buffer, groupOffset);
ArrayList<WeightedString> shortcutTargets = info.mShortcutTargets;
ArrayList<WeightedString> bigrams = null;
if (null != info.mBigrams) {
bigrams = new ArrayList<WeightedString>();
for (PendingAttribute bigram : info.mBigrams) {
- final String word = getWordAtAddress(source, headerSize, bigram.mAddress);
+ final String word = getWordAtAddress(
+ buffer, headerSize, bigram.mAddress);
bigrams.add(new WeightedString(word, bigram.mFrequency));
}
}
if (hasChildrenAddress(info.mChildrenAddress)) {
Node children = reverseNodeMap.get(info.mChildrenAddress);
if (null == children) {
- final long currentPosition = source.getFilePointer();
- source.seek(info.mChildrenAddress + headerSize);
- children = readNode(source, headerSize, reverseNodeMap, reverseGroupMap);
- source.seek(currentPosition);
+ final int currentPosition = buffer.position();
+ buffer.position(info.mChildrenAddress + headerSize);
+ children = readNode(
+ buffer, headerSize, reverseNodeMap, reverseGroupMap);
+ buffer.position(currentPosition);
}
nodeContents.add(
new CharGroup(info.mCharacters, shortcutTargets, bigrams, info.mFrequency,
- children));
+ 0 != (info.mFlags & FLAG_IS_NOT_A_WORD),
+ 0 != (info.mFlags & FLAG_IS_BLACKLISTED), children));
} else {
nodeContents.add(
- new CharGroup(info.mCharacters, shortcutTargets, bigrams, info.mFrequency));
+ new CharGroup(info.mCharacters, shortcutTargets, bigrams, info.mFrequency,
+ 0 != (info.mFlags & FLAG_IS_NOT_A_WORD),
+ 0 != (info.mFlags & FLAG_IS_BLACKLISTED)));
}
groupOffset = info.mEndAddress;
}
@@ -1316,59 +1380,205 @@
return node;
}
+ // TODO: move these methods (readUnigramsAndBigramsBinary(|Inner)) and an inner class (Position)
+ // out of this class.
+ private static class Position {
+ public static final int NOT_READ_GROUPCOUNT = -1;
+
+ public int mAddress;
+ public int mNumOfCharGroup;
+ public int mPosition;
+ public int mLength;
+
+ public Position(int address, int length) {
+ mAddress = address;
+ mLength = length;
+ mNumOfCharGroup = NOT_READ_GROUPCOUNT;
+ }
+ }
+
+ /**
+ * Tours all node without recursive call.
+ */
+ private static void readUnigramsAndBigramsBinaryInner(
+ final FusionDictionaryBufferInterface buffer, final int headerSize,
+ final Map<Integer, String> words, final Map<Integer, Integer> frequencies,
+ final Map<Integer, ArrayList<PendingAttribute>> bigrams) {
+ int[] pushedChars = new int[MAX_WORD_LENGTH + 1];
+
+ Stack<Position> stack = new Stack<Position>();
+ int index = 0;
+
+ Position initPos = new Position(headerSize, 0);
+ stack.push(initPos);
+
+ while (!stack.empty()) {
+ Position p = stack.peek();
+
+ if (DBG) {
+ MakedictLog.d("read: address=" + p.mAddress + ", numOfCharGroup=" +
+ p.mNumOfCharGroup + ", position=" + p.mPosition + ", length=" + p.mLength);
+ }
+
+ if (buffer.position() != p.mAddress) buffer.position(p.mAddress);
+ if (index != p.mLength) index = p.mLength;
+
+ if (p.mNumOfCharGroup == Position.NOT_READ_GROUPCOUNT) {
+ p.mNumOfCharGroup = readCharGroupCount(buffer);
+ p.mAddress += getGroupCountSize(p.mNumOfCharGroup);
+ p.mPosition = 0;
+ }
+
+ CharGroupInfo info = readCharGroup(buffer, p.mAddress - headerSize);
+ for (int i = 0; i < info.mCharacters.length; ++i) {
+ pushedChars[index++] = info.mCharacters[i];
+ }
+ p.mPosition++;
+
+ if (info.mFrequency != FusionDictionary.CharGroup.NOT_A_TERMINAL) { // found word
+ words.put(info.mOriginalAddress, new String(pushedChars, 0, index));
+ frequencies.put(info.mOriginalAddress, info.mFrequency);
+ if (info.mBigrams != null) bigrams.put(info.mOriginalAddress, info.mBigrams);
+ }
+
+ if (p.mPosition == p.mNumOfCharGroup) {
+ stack.pop();
+ } else {
+ // the node has more groups.
+ p.mAddress = buffer.position();
+ }
+
+ if (hasChildrenAddress(info.mChildrenAddress)) {
+ Position childrenPos = new Position(info.mChildrenAddress + headerSize, index);
+ stack.push(childrenPos);
+ }
+ }
+ }
+
+ /**
+ * Reads unigrams and bigrams from the binary file.
+ * Doesn't make the memory representation of the dictionary.
+ *
+ * @param buffer the buffer to read.
+ * @param words the map to store the address as a key and the word as a value.
+ * @param frequencies the map to store the address as a key and the frequency as a value.
+ * @param bigrams the map to store the address as a key and the list of address as a value.
+ * @throws IOException
+ * @throws UnsupportedFormatException
+ */
+ public static void readUnigramsAndBigramsBinary(final FusionDictionaryBufferInterface buffer,
+ final Map<Integer, String> words, final Map<Integer, Integer> frequencies,
+ final Map<Integer, ArrayList<PendingAttribute>> bigrams) throws IOException,
+ UnsupportedFormatException {
+ // Read header
+ final int version = checkFormatVersion(buffer);
+ final int optionsFlags = buffer.readUnsignedShort();
+ final HashMap<String, String> options = new HashMap<String, String>();
+ final int headerSize = readHeader(buffer, options, version);
+
+ readUnigramsAndBigramsBinaryInner(buffer, headerSize, words, frequencies, bigrams);
+ }
+
/**
* Helper function to get the binary format version from the header.
+ * @throws IOException
*/
- private static int getFormatVersion(final RandomAccessFile source) throws IOException {
- final int magic_v1 = source.readUnsignedShort();
- if (VERSION_1_MAGIC_NUMBER == magic_v1) return source.readUnsignedByte();
- final int magic_v2 = (magic_v1 << 16) + source.readUnsignedShort();
- if (VERSION_2_MAGIC_NUMBER == magic_v2) return source.readUnsignedShort();
+ private static int getFormatVersion(final FusionDictionaryBufferInterface buffer)
+ throws IOException {
+ final int magic_v1 = buffer.readUnsignedShort();
+ if (VERSION_1_MAGIC_NUMBER == magic_v1) return buffer.readUnsignedByte();
+ final int magic_v2 = (magic_v1 << 16) + buffer.readUnsignedShort();
+ if (VERSION_2_MAGIC_NUMBER == magic_v2) return buffer.readUnsignedShort();
return NOT_A_VERSION_NUMBER;
}
/**
- * Reads a random access file and returns the memory representation of the dictionary.
- *
- * This high-level method takes a binary file and reads its contents, populating a
- * FusionDictionary structure. The optional dict argument is an existing dictionary to
- * which words from the file should be added. If it is null, a new dictionary is created.
- *
- * @param source the file to read.
- * @param dict an optional dictionary to add words to, or null.
- * @return the created (or merged) dictionary.
+ * Helper function to get and validate the binary format version.
+ * @throws UnsupportedFormatException
+ * @throws IOException
*/
- public static FusionDictionary readDictionaryBinary(final RandomAccessFile source,
- final FusionDictionary dict) throws IOException, UnsupportedFormatException {
- // Check file version
- final int version = getFormatVersion(source);
- if (version < MINIMUM_SUPPORTED_VERSION || version > MAXIMUM_SUPPORTED_VERSION ) {
+ private static int checkFormatVersion(final FusionDictionaryBufferInterface buffer)
+ throws IOException, UnsupportedFormatException {
+ final int version = getFormatVersion(buffer);
+ if (version < MINIMUM_SUPPORTED_VERSION || version > MAXIMUM_SUPPORTED_VERSION) {
throw new UnsupportedFormatException("This file has version " + version
+ ", but this implementation does not support versions above "
+ MAXIMUM_SUPPORTED_VERSION);
}
+ return version;
+ }
- // Read options
- final int optionsFlags = source.readUnsignedShort();
-
- final long headerSize;
- final HashMap<String, String> options = new HashMap<String, String>();
+ /**
+ * Reads a header from a buffer.
+ * @throws IOException
+ * @throws UnsupportedFormatException
+ */
+ private static int readHeader(final FusionDictionaryBufferInterface buffer,
+ final HashMap<String, String> options, final int version)
+ throws IOException, UnsupportedFormatException {
+ final int headerSize;
if (version < FIRST_VERSION_WITH_HEADER_SIZE) {
- headerSize = source.getFilePointer();
+ headerSize = buffer.position();
} else {
- headerSize = (source.readUnsignedByte() << 24) + (source.readUnsignedByte() << 16)
- + (source.readUnsignedByte() << 8) + source.readUnsignedByte();
- while (source.getFilePointer() < headerSize) {
- final String key = CharEncoding.readString(source);
- final String value = CharEncoding.readString(source);
- options.put(key, value);
- }
- source.seek(headerSize);
+ headerSize = buffer.readInt();
+ populateOptions(buffer, headerSize, options);
+ buffer.position(headerSize);
}
+ if (headerSize < 0) {
+ throw new UnsupportedFormatException("header size can't be negative.");
+ }
+ return headerSize;
+ }
+
+ /**
+ * Reads options from a buffer and populate a map with their contents.
+ *
+ * The buffer is read at the current position, so the caller must take care the pointer
+ * is in the right place before calling this.
+ */
+ public static void populateOptions(final FusionDictionaryBufferInterface buffer,
+ final int headerSize, final HashMap<String, String> options) {
+ while (buffer.position() < headerSize) {
+ final String key = CharEncoding.readString(buffer);
+ final String value = CharEncoding.readString(buffer);
+ options.put(key, value);
+ }
+ }
+ // TODO: remove this method.
+ public static void populateOptions(final ByteBuffer buffer, final int headerSize,
+ final HashMap<String, String> options) {
+ populateOptions(new ByteBufferWrapper(buffer), headerSize, options);
+ }
+
+ /**
+ * Reads a buffer and returns the memory representation of the dictionary.
+ *
+ * This high-level method takes a buffer and reads its contents, populating a
+ * FusionDictionary structure. The optional dict argument is an existing dictionary to
+ * which words from the buffer should be added. If it is null, a new dictionary is created.
+ *
+ * @param buffer the buffer to read.
+ * @param dict an optional dictionary to add words to, or null.
+ * @return the created (or merged) dictionary.
+ */
+ public static FusionDictionary readDictionaryBinary(
+ final FusionDictionaryBufferInterface buffer, final FusionDictionary dict)
+ throws IOException, UnsupportedFormatException {
+ // clear cache
+ wordCache.clear();
+
+ // Read header
+ final int version = checkFormatVersion(buffer);
+ final int optionsFlags = buffer.readUnsignedShort();
+
+ final HashMap<String, String> options = new HashMap<String, String>();
+ final int headerSize = readHeader(buffer, options, version);
+
Map<Integer, Node> reverseNodeMapping = new TreeMap<Integer, Node>();
Map<Integer, CharGroup> reverseGroupMapping = new TreeMap<Integer, CharGroup>();
- final Node root = readNode(source, headerSize, reverseNodeMapping, reverseGroupMapping);
+ final Node root = readNode(
+ buffer, headerSize, reverseNodeMapping, reverseGroupMapping);
FusionDictionary newDict = new FusionDictionary(root,
new FusionDictionary.DictionaryOptions(options,
@@ -1376,7 +1586,11 @@
0 != (optionsFlags & FRENCH_LIGATURE_PROCESSING_FLAG)));
if (null != dict) {
for (final Word w : dict) {
- newDict.add(w.mWord, w.mFrequency, w.mShortcutTargets);
+ if (w.mIsBlacklistEntry) {
+ newDict.addBlacklistEntry(w.mWord, w.mShortcutTargets, w.mIsNotAWord);
+ } else {
+ newDict.add(w.mWord, w.mFrequency, w.mShortcutTargets, w.mIsNotAWord);
+ }
}
for (final Word w : dict) {
// By construction a binary dictionary may not have bigrams pointing to
@@ -1391,6 +1605,12 @@
return newDict;
}
+ // TODO: remove this method.
+ public static FusionDictionary readDictionaryBinary(final ByteBuffer buffer,
+ final FusionDictionary dict) throws IOException, UnsupportedFormatException {
+ return readDictionaryBinary(new ByteBufferWrapper(buffer), dict);
+ }
+
/**
* Basic test to find out whether the file is a binary dictionary or not.
*
@@ -1400,14 +1620,44 @@
* @return true if it's a binary dictionary, false otherwise
*/
public static boolean isBinaryDictionary(final String filename) {
+ FileInputStream inStream = null;
try {
- RandomAccessFile f = new RandomAccessFile(filename, "r");
- final int version = getFormatVersion(f);
+ final File file = new File(filename);
+ inStream = new FileInputStream(file);
+ final ByteBuffer buffer = inStream.getChannel().map(
+ FileChannel.MapMode.READ_ONLY, 0, file.length());
+ final int version = getFormatVersion(new ByteBufferWrapper(buffer));
return (version >= MINIMUM_SUPPORTED_VERSION && version <= MAXIMUM_SUPPORTED_VERSION);
} catch (FileNotFoundException e) {
return false;
} catch (IOException e) {
return false;
+ } finally {
+ if (inStream != null) {
+ try {
+ inStream.close();
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
}
}
+
+ /**
+ * Calculate bigram frequency from compressed value
+ *
+ * @see #makeBigramFlags
+ *
+ * @param unigramFrequency
+ * @param bigramFrequency compressed frequency
+ * @return approximate bigram frequency
+ */
+ public static int reconstructBigramFrequency(final int unigramFrequency,
+ final int bigramFrequency) {
+ final float stepSize = (MAX_TERMINAL_FREQUENCY - unigramFrequency)
+ / (1.5f + MAX_BIGRAM_FREQUENCY);
+ final float resultFreqFloat = (float)unigramFrequency
+ + stepSize * (bigramFrequency + 1.0f);
+ return (int)resultFreqFloat;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
index 5864db2..f1abea9 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
@@ -101,26 +101,34 @@
ArrayList<WeightedString> mBigrams;
int mFrequency; // NOT_A_TERMINAL == mFrequency indicates this is not a terminal.
Node mChildren;
+ boolean mIsNotAWord; // Only a shortcut
+ boolean mIsBlacklistEntry;
// The two following members to help with binary generation
int mCachedSize;
int mCachedAddress;
public CharGroup(final int[] chars, final ArrayList<WeightedString> shortcutTargets,
- final ArrayList<WeightedString> bigrams, final int frequency) {
+ final ArrayList<WeightedString> bigrams, final int frequency,
+ final boolean isNotAWord, final boolean isBlacklistEntry) {
mChars = chars;
mFrequency = frequency;
mShortcutTargets = shortcutTargets;
mBigrams = bigrams;
mChildren = null;
+ mIsNotAWord = isNotAWord;
+ mIsBlacklistEntry = isBlacklistEntry;
}
public CharGroup(final int[] chars, final ArrayList<WeightedString> shortcutTargets,
- final ArrayList<WeightedString> bigrams, final int frequency, final Node children) {
+ final ArrayList<WeightedString> bigrams, final int frequency,
+ final boolean isNotAWord, final boolean isBlacklistEntry, final Node children) {
mChars = chars;
mFrequency = frequency;
mShortcutTargets = shortcutTargets;
mBigrams = bigrams;
mChildren = children;
+ mIsNotAWord = isNotAWord;
+ mIsBlacklistEntry = isBlacklistEntry;
}
public void addChild(CharGroup n) {
@@ -197,8 +205,9 @@
* the existing ones if any. Note: unigram, bigram, and shortcut frequencies are only
* updated if they are higher than the existing ones.
*/
- public void update(int frequency, ArrayList<WeightedString> shortcutTargets,
- ArrayList<WeightedString> bigrams) {
+ public void update(final int frequency, final ArrayList<WeightedString> shortcutTargets,
+ final ArrayList<WeightedString> bigrams,
+ final boolean isNotAWord, final boolean isBlacklistEntry) {
if (frequency > mFrequency) {
mFrequency = frequency;
}
@@ -234,6 +243,8 @@
}
}
}
+ mIsNotAWord = isNotAWord;
+ mIsBlacklistEntry = isBlacklistEntry;
}
}
@@ -296,10 +307,24 @@
* @param word the word to add.
* @param frequency the frequency of the word, in the range [0..255].
* @param shortcutTargets a list of shortcut targets for this word, or null.
+ * @param isNotAWord true if this should not be considered a word (e.g. shortcut only)
*/
public void add(final String word, final int frequency,
- final ArrayList<WeightedString> shortcutTargets) {
- add(getCodePoints(word), frequency, shortcutTargets);
+ final ArrayList<WeightedString> shortcutTargets, final boolean isNotAWord) {
+ add(getCodePoints(word), frequency, shortcutTargets, isNotAWord,
+ false /* isBlacklistEntry */);
+ }
+
+ /**
+ * Helper method to add a blacklist entry as a string.
+ *
+ * @param word the word to add as a blacklist entry.
+ * @param shortcutTargets a list of shortcut targets for this word, or null.
+ * @param isNotAWord true if this is not a word for spellcheking purposes (shortcut only or so)
+ */
+ public void addBlacklistEntry(final String word,
+ final ArrayList<WeightedString> shortcutTargets, final boolean isNotAWord) {
+ add(getCodePoints(word), 0, shortcutTargets, isNotAWord, true /* isBlacklistEntry */);
}
/**
@@ -332,7 +357,8 @@
if (charGroup != null) {
final CharGroup charGroup2 = findWordInTree(mRoot, word2);
if (charGroup2 == null) {
- add(getCodePoints(word2), 0, null);
+ add(getCodePoints(word2), 0, null, false /* isNotAWord */,
+ false /* isBlacklistEntry */);
}
charGroup.addBigram(word2, frequency);
} else {
@@ -349,9 +375,12 @@
* @param word the word, as an int array.
* @param frequency the frequency of the word, in the range [0..255].
* @param shortcutTargets an optional list of shortcut targets for this word (null if none).
+ * @param isNotAWord true if this is not a word for spellcheking purposes (shortcut only or so)
+ * @param isBlacklistEntry true if this is a blacklisted word, false otherwise
*/
private void add(final int[] word, final int frequency,
- final ArrayList<WeightedString> shortcutTargets) {
+ final ArrayList<WeightedString> shortcutTargets,
+ final boolean isNotAWord, final boolean isBlacklistEntry) {
assert(frequency >= 0 && frequency <= 255);
Node currentNode = mRoot;
int charIndex = 0;
@@ -376,7 +405,7 @@
final int insertionIndex = findInsertionIndex(currentNode, word[charIndex]);
final CharGroup newGroup = new CharGroup(
Arrays.copyOfRange(word, charIndex, word.length),
- shortcutTargets, null /* bigrams */, frequency);
+ shortcutTargets, null /* bigrams */, frequency, isNotAWord, isBlacklistEntry);
currentNode.mData.add(insertionIndex, newGroup);
if (DBG) checkStack(currentNode);
} else {
@@ -386,13 +415,15 @@
// The new word is a prefix of an existing word, but the node on which it
// should end already exists as is. Since the old CharNode was not a terminal,
// make it one by filling in its frequency and other attributes
- currentGroup.update(frequency, shortcutTargets, null);
+ currentGroup.update(frequency, shortcutTargets, null, isNotAWord,
+ isBlacklistEntry);
} else {
// The new word matches the full old word and extends past it.
// We only have to create a new node and add it to the end of this.
final CharGroup newNode = new CharGroup(
Arrays.copyOfRange(word, charIndex + differentCharIndex, word.length),
- shortcutTargets, null /* bigrams */, frequency);
+ shortcutTargets, null /* bigrams */, frequency, isNotAWord,
+ isBlacklistEntry);
currentGroup.mChildren = new Node();
currentGroup.mChildren.mData.add(newNode);
}
@@ -400,7 +431,9 @@
if (0 == differentCharIndex) {
// Exact same word. Update the frequency if higher. This will also add the
// new shortcuts to the existing shortcut list if it already exists.
- currentGroup.update(frequency, shortcutTargets, null);
+ currentGroup.update(frequency, shortcutTargets, null,
+ currentGroup.mIsNotAWord && isNotAWord,
+ currentGroup.mIsBlacklistEntry || isBlacklistEntry);
} else {
// Partial prefix match only. We have to replace the current node with a node
// containing the current prefix and create two new ones for the tails.
@@ -408,21 +441,26 @@
final CharGroup newOldWord = new CharGroup(
Arrays.copyOfRange(currentGroup.mChars, differentCharIndex,
currentGroup.mChars.length), currentGroup.mShortcutTargets,
- currentGroup.mBigrams, currentGroup.mFrequency, currentGroup.mChildren);
+ currentGroup.mBigrams, currentGroup.mFrequency,
+ currentGroup.mIsNotAWord, currentGroup.mIsBlacklistEntry,
+ currentGroup.mChildren);
newChildren.mData.add(newOldWord);
final CharGroup newParent;
if (charIndex + differentCharIndex >= word.length) {
newParent = new CharGroup(
Arrays.copyOfRange(currentGroup.mChars, 0, differentCharIndex),
- shortcutTargets, null /* bigrams */, frequency, newChildren);
+ shortcutTargets, null /* bigrams */, frequency,
+ isNotAWord, isBlacklistEntry, newChildren);
} else {
newParent = new CharGroup(
Arrays.copyOfRange(currentGroup.mChars, 0, differentCharIndex),
- null /* shortcutTargets */, null /* bigrams */, -1, newChildren);
+ null /* shortcutTargets */, null /* bigrams */, -1,
+ false /* isNotAWord */, false /* isBlacklistEntry */, newChildren);
final CharGroup newWord = new CharGroup(Arrays.copyOfRange(word,
charIndex + differentCharIndex, word.length),
- shortcutTargets, null /* bigrams */, frequency);
+ shortcutTargets, null /* bigrams */, frequency,
+ isNotAWord, isBlacklistEntry);
final int addIndex = word[charIndex + differentCharIndex]
> currentGroup.mChars[differentCharIndex] ? 1 : 0;
newChildren.mData.add(addIndex, newWord);
@@ -483,7 +521,8 @@
private static int findInsertionIndex(final Node node, int character) {
final ArrayList<CharGroup> data = node.mData;
final CharGroup reference = new CharGroup(new int[] { character },
- null /* shortcutTargets */, null /* bigrams */, 0);
+ null /* shortcutTargets */, null /* bigrams */, 0, false /* isNotAWord */,
+ false /* isBlacklistEntry */);
int result = Collections.binarySearch(data, reference, CHARGROUP_COMPARATOR);
return result >= 0 ? result : -result - 1;
}
@@ -516,13 +555,23 @@
int indexOfGroup = findIndexOfChar(node, s.codePointAt(index));
if (CHARACTER_NOT_FOUND == indexOfGroup) return null;
currentGroup = node.mData.get(indexOfGroup);
+
+ if (s.length() - index < currentGroup.mChars.length) return null;
+ int newIndex = index;
+ while (newIndex < s.length() && newIndex - index < currentGroup.mChars.length) {
+ if (currentGroup.mChars[newIndex - index] != s.codePointAt(newIndex)) return null;
+ newIndex++;
+ }
+ index = newIndex;
+
if (DBG) checker.append(new String(currentGroup.mChars, 0, currentGroup.mChars.length));
- index += currentGroup.mChars.length;
if (index < s.length()) {
node = currentGroup.mChildren;
}
} while (null != node && index < s.length());
+ if (index < s.length()) return null;
+ if (!currentGroup.isTerminal()) return null;
if (DBG && !s.equals(checker.toString())) return null;
return currentGroup;
}
@@ -738,7 +787,8 @@
}
if (currentGroup.mFrequency >= 0)
return new Word(mCurrentString.toString(), currentGroup.mFrequency,
- currentGroup.mShortcutTargets, currentGroup.mBigrams);
+ currentGroup.mShortcutTargets, currentGroup.mBigrams,
+ currentGroup.mIsNotAWord, currentGroup.mIsBlacklistEntry);
} else {
mPositions.removeLast();
currentPos = mPositions.getLast();
diff --git a/java/src/com/android/inputmethod/latin/makedict/Word.java b/java/src/com/android/inputmethod/latin/makedict/Word.java
index 65fc72c..4683ef1 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Word.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Word.java
@@ -31,16 +31,21 @@
public final int mFrequency;
public final ArrayList<WeightedString> mShortcutTargets;
public final ArrayList<WeightedString> mBigrams;
+ public final boolean mIsNotAWord;
+ public final boolean mIsBlacklistEntry;
private int mHashCode = 0;
public Word(final String word, final int frequency,
final ArrayList<WeightedString> shortcutTargets,
- final ArrayList<WeightedString> bigrams) {
+ final ArrayList<WeightedString> bigrams,
+ final boolean isNotAWord, final boolean isBlacklistEntry) {
mWord = word;
mFrequency = frequency;
mShortcutTargets = shortcutTargets;
mBigrams = bigrams;
+ mIsNotAWord = isNotAWord;
+ mIsBlacklistEntry = isBlacklistEntry;
}
private static int computeHashCode(Word word) {
@@ -48,7 +53,9 @@
word.mWord,
word.mFrequency,
word.mShortcutTargets.hashCode(),
- word.mBigrams.hashCode()
+ word.mBigrams.hashCode(),
+ word.mIsNotAWord,
+ word.mIsBlacklistEntry
});
}
@@ -78,7 +85,9 @@
Word w = (Word)o;
return mFrequency == w.mFrequency && mWord.equals(w.mWord)
&& mShortcutTargets.equals(w.mShortcutTargets)
- && mBigrams.equals(w.mBigrams);
+ && mBigrams.equals(w.mBigrams)
+ && mIsNotAWord == w.mIsNotAWord
+ && mIsBlacklistEntry == w.mIsBlacklistEntry;
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 3bdfe1f..eef7a51 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -25,6 +25,7 @@
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.BinaryDictionary;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.ContactsBinaryDictionary;
import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.DictionaryCollection;
@@ -35,7 +36,6 @@
import com.android.inputmethod.latin.SynchronouslyLoadedContactsBinaryDictionary;
import com.android.inputmethod.latin.SynchronouslyLoadedUserBinaryDictionary;
import com.android.inputmethod.latin.UserBinaryDictionary;
-import com.android.inputmethod.latin.WhitelistDictionary;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -63,12 +63,9 @@
public static final int CAPITALIZE_ALL = 2; // All caps
private final static String[] EMPTY_STRING_ARRAY = new String[0];
- private Map<String, DictionaryPool> mDictionaryPools =
- Collections.synchronizedMap(new TreeMap<String, DictionaryPool>());
+ private Map<String, DictionaryPool> mDictionaryPools = CollectionUtils.newSynchronizedTreeMap();
private Map<String, UserBinaryDictionary> mUserDictionaries =
- Collections.synchronizedMap(new TreeMap<String, UserBinaryDictionary>());
- private Map<String, Dictionary> mWhitelistDictionaries =
- Collections.synchronizedMap(new TreeMap<String, Dictionary>());
+ CollectionUtils.newSynchronizedTreeMap();
private ContactsBinaryDictionary mContactsDictionary;
// The threshold for a candidate to be offered as a suggestion.
@@ -80,7 +77,7 @@
private final Object mUseContactsLock = new Object();
private final HashSet<WeakReference<DictionaryCollection>> mDictionaryCollectionsList =
- new HashSet<WeakReference<DictionaryCollection>>();
+ CollectionUtils.newHashSet();
public static final int SCRIPT_LATIN = 0;
public static final int SCRIPT_CYRILLIC = 1;
@@ -96,7 +93,7 @@
// proximity to pass to the dictionary descent algorithm.
// IMPORTANT: this only contains languages - do not write countries in there.
// Only the language is searched from the map.
- mLanguageToScript = new TreeMap<String, Integer>();
+ mLanguageToScript = CollectionUtils.newTreeMap();
mLanguageToScript.put("en", SCRIPT_LATIN);
mLanguageToScript.put("fr", SCRIPT_LATIN);
mLanguageToScript.put("de", SCRIPT_LATIN);
@@ -234,7 +231,7 @@
mSuggestionThreshold = suggestionThreshold;
mRecommendedThreshold = recommendedThreshold;
mMaxLength = maxLength;
- mSuggestions = new ArrayList<CharSequence>(maxLength + 1);
+ mSuggestions = CollectionUtils.newArrayList(maxLength + 1);
mScores = new int[mMaxLength];
}
@@ -362,12 +359,9 @@
private void closeAllDictionaries() {
final Map<String, DictionaryPool> oldPools = mDictionaryPools;
- mDictionaryPools = Collections.synchronizedMap(new TreeMap<String, DictionaryPool>());
+ mDictionaryPools = CollectionUtils.newSynchronizedTreeMap();
final Map<String, UserBinaryDictionary> oldUserDictionaries = mUserDictionaries;
- mUserDictionaries =
- Collections.synchronizedMap(new TreeMap<String, UserBinaryDictionary>());
- final Map<String, Dictionary> oldWhitelistDictionaries = mWhitelistDictionaries;
- mWhitelistDictionaries = Collections.synchronizedMap(new TreeMap<String, Dictionary>());
+ mUserDictionaries = CollectionUtils.newSynchronizedTreeMap();
new Thread("spellchecker_close_dicts") {
@Override
public void run() {
@@ -377,9 +371,6 @@
for (Dictionary dict : oldUserDictionaries.values()) {
dict.close();
}
- for (Dictionary dict : oldWhitelistDictionaries.values()) {
- dict.close();
- }
synchronized (mUseContactsLock) {
if (null != mContactsDictionary) {
// The synchronously loaded contacts dictionary should have been in one
@@ -423,12 +414,6 @@
mUserDictionaries.put(localeStr, userDictionary);
}
dictionaryCollection.addDictionary(userDictionary);
- Dictionary whitelistDictionary = mWhitelistDictionaries.get(localeStr);
- if (null == whitelistDictionary) {
- whitelistDictionary = new WhitelistDictionary(this, locale);
- mWhitelistDictionaries.put(localeStr, whitelistDictionary);
- }
- dictionaryCollection.addDictionary(whitelistDictionary);
synchronized (mUseContactsLock) {
if (mUseContactsDictionary) {
if (null == mContactsDictionary) {
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
index 501a0e2..5a1bd37 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
@@ -22,6 +22,8 @@
import android.view.textservice.SuggestionsInfo;
import android.view.textservice.TextInfo;
+import com.android.inputmethod.latin.CollectionUtils;
+
import java.util.ArrayList;
public class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheckerSession {
@@ -40,10 +42,10 @@
return null;
}
final int N = ssi.getSuggestionsCount();
- final ArrayList<Integer> additionalOffsets = new ArrayList<Integer>();
- final ArrayList<Integer> additionalLengths = new ArrayList<Integer>();
+ final ArrayList<Integer> additionalOffsets = CollectionUtils.newArrayList();
+ final ArrayList<Integer> additionalLengths = CollectionUtils.newArrayList();
final ArrayList<SuggestionsInfo> additionalSuggestionsInfos =
- new ArrayList<SuggestionsInfo>();
+ CollectionUtils.newArrayList();
String currentWord = null;
for (int i = 0; i < N; ++i) {
final SuggestionsInfo si = ssi.getSuggestionsInfoAt(i);
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index 06f5db7..f4784ff 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -24,6 +24,7 @@
import android.view.textservice.TextInfo;
import com.android.inputmethod.compat.SuggestionsInfoCompatUtils;
+import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.LocaleUtils;
import com.android.inputmethod.latin.WordComposer;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
@@ -194,7 +195,7 @@
DictAndProximity dictInfo = null;
try {
dictInfo = mDictionaryPool.pollWithDefaultTimeout();
- if (null == dictInfo) {
+ if (!DictionaryPool.isAValidDictionary(dictInfo)) {
return AndroidSpellCheckerService.getNotInDictEmptySuggestions();
}
return dictInfo.mDictionary.isValidWord(inText)
@@ -225,8 +226,8 @@
final int xy = SpellCheckerProximityInfo.getXYForCodePointAndScript(
codePoint, mScript);
if (SpellCheckerProximityInfo.NOT_A_COORDINATE_PAIR == xy) {
- composer.add(codePoint, WordComposer.NOT_A_COORDINATE,
- WordComposer.NOT_A_COORDINATE);
+ composer.add(codePoint,
+ Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
} else {
composer.add(codePoint, xy & 0xFFFF, xy >> 16);
}
@@ -237,7 +238,7 @@
DictAndProximity dictInfo = null;
try {
dictInfo = mDictionaryPool.pollWithDefaultTimeout();
- if (null == dictInfo) {
+ if (!DictionaryPool.isAValidDictionary(dictInfo)) {
return AndroidSpellCheckerService.getNotInDictEmptySuggestions();
}
final ArrayList<SuggestedWordInfo> suggestions =
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
index 83f82fa..53aa6c7 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
@@ -18,6 +18,13 @@
import android.util.Log;
+import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.WordComposer;
+
+import java.util.ArrayList;
import java.util.Locale;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -39,6 +46,26 @@
private final Locale mLocale;
private int mSize;
private volatile boolean mClosed;
+ final static ArrayList<SuggestedWordInfo> noSuggestions = CollectionUtils.newArrayList();
+ private final static DictAndProximity dummyDict = new DictAndProximity(
+ new Dictionary(Dictionary.TYPE_MAIN) {
+ @Override
+ public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
+ final CharSequence prevWord, final ProximityInfo proximityInfo) {
+ return noSuggestions;
+ }
+ @Override
+ public boolean isValidWord(CharSequence word) {
+ // This is never called. However if for some strange reason it ever gets
+ // called, returning true is less destructive (it will not underline the
+ // word in red).
+ return true;
+ }
+ }, null);
+
+ static public boolean isAValidDictionary(final DictAndProximity dictInfo) {
+ return null != dictInfo && dummyDict != dictInfo;
+ }
public DictionaryPool(final int maxSize, final AndroidSpellCheckerService service,
final Locale locale) {
@@ -98,7 +125,7 @@
public boolean offer(final DictAndProximity dict) {
if (mClosed) {
dict.mDictionary.close();
- return false;
+ return super.offer(dummyDict);
} else {
return super.offer(dict);
}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
index bd92d88..fe5225e 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
@@ -16,14 +16,15 @@
package com.android.inputmethod.latin.spellcheck;
-import com.android.inputmethod.keyboard.KeyDetector;
import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.Constants;
import java.util.TreeMap;
public class SpellCheckerProximityInfo {
/* public for test */
- final public static int NUL = KeyDetector.NOT_A_CODE;
+ final public static int NUL = Constants.NOT_A_CODE;
// This must be the same as MAX_PROXIMITY_CHARS_SIZE else it will not work inside
// native code - this value is passed at creation of the binary object and reused
@@ -59,7 +60,7 @@
// character.
// Since we need to build such an array, we want to be able to search in our big proximity
// data quickly by character, and a map is probably the best way to do this.
- final private static TreeMap<Integer, Integer> INDICES = new TreeMap<Integer, Integer>();
+ final private static TreeMap<Integer, Integer> INDICES = CollectionUtils.newTreeMap();
// The proximity here is the union of
// - the proximity for a QWERTY keyboard.
@@ -122,7 +123,7 @@
}
private static class Cyrillic {
- final private static TreeMap<Integer, Integer> INDICES = new TreeMap<Integer, Integer>();
+ final private static TreeMap<Integer, Integer> INDICES = CollectionUtils.newTreeMap();
// TODO: The following table is solely based on the keyboard layout. Consult with Russian
// speakers on commonly misspelled words/letters.
final static int[] PROXIMITY = {
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index 58b01aa..1f883aa 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -23,7 +23,9 @@
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
+import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
+import com.android.inputmethod.keyboard.internal.KeyboardParams;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.Utils;
@@ -31,145 +33,149 @@
public class MoreSuggestions extends Keyboard {
public static final int SUGGESTION_CODE_BASE = 1024;
- MoreSuggestions(Builder.MoreSuggestionsParam params) {
+ MoreSuggestions(final MoreSuggestionsParam params) {
super(params);
}
- public static class Builder extends Keyboard.Builder<Builder.MoreSuggestionsParam> {
+ private static class MoreSuggestionsParam extends KeyboardParams {
+ private final int[] mWidths = new int[SuggestionStripView.MAX_SUGGESTIONS];
+ private final int[] mRowNumbers = new int[SuggestionStripView.MAX_SUGGESTIONS];
+ private final int[] mColumnOrders = new int[SuggestionStripView.MAX_SUGGESTIONS];
+ private final int[] mNumColumnsInRow = new int[SuggestionStripView.MAX_SUGGESTIONS];
+ private static final int MAX_COLUMNS_IN_ROW = 3;
+ private int mNumRows;
+ public Drawable mDivider;
+ public int mDividerWidth;
+
+ public MoreSuggestionsParam() {
+ super();
+ }
+
+ public int layout(final SuggestedWords suggestions, final int fromPos, final int maxWidth,
+ final int minWidth, final int maxRow, final MoreSuggestionsView view) {
+ clearKeys();
+ final Resources res = view.getContext().getResources();
+ mDivider = res.getDrawable(R.drawable.more_suggestions_divider);
+ mDividerWidth = mDivider.getIntrinsicWidth();
+ final int padding = (int) res.getDimension(
+ R.dimen.more_suggestions_key_horizontal_padding);
+ final Paint paint = view.newDefaultLabelPaint();
+
+ int row = 0;
+ int pos = fromPos, rowStartPos = fromPos;
+ final int size = Math.min(suggestions.size(), SuggestionStripView.MAX_SUGGESTIONS);
+ while (pos < size) {
+ final String word = suggestions.getWord(pos).toString();
+ // TODO: Should take care of text x-scaling.
+ mWidths[pos] = (int)view.getLabelWidth(word, paint) + padding;
+ final int numColumn = pos - rowStartPos + 1;
+ final int columnWidth =
+ (maxWidth - mDividerWidth * (numColumn - 1)) / numColumn;
+ if (numColumn > MAX_COLUMNS_IN_ROW
+ || !fitInWidth(rowStartPos, pos + 1, columnWidth)) {
+ if ((row + 1) >= maxRow) {
+ break;
+ }
+ mNumColumnsInRow[row] = pos - rowStartPos;
+ rowStartPos = pos;
+ row++;
+ }
+ mColumnOrders[pos] = pos - rowStartPos;
+ mRowNumbers[pos] = row;
+ pos++;
+ }
+ mNumColumnsInRow[row] = pos - rowStartPos;
+ mNumRows = row + 1;
+ mBaseWidth = mOccupiedWidth = Math.max(
+ minWidth, calcurateMaxRowWidth(fromPos, pos));
+ mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap;
+ return pos - fromPos;
+ }
+
+ private boolean fitInWidth(final int startPos, final int endPos, final int width) {
+ for (int pos = startPos; pos < endPos; pos++) {
+ if (mWidths[pos] > width)
+ return false;
+ }
+ return true;
+ }
+
+ private int calcurateMaxRowWidth(final int startPos, final int endPos) {
+ int maxRowWidth = 0;
+ int pos = startPos;
+ for (int row = 0; row < mNumRows; row++) {
+ final int numColumnInRow = mNumColumnsInRow[row];
+ int maxKeyWidth = 0;
+ while (pos < endPos && mRowNumbers[pos] == row) {
+ maxKeyWidth = Math.max(maxKeyWidth, mWidths[pos]);
+ pos++;
+ }
+ maxRowWidth = Math.max(maxRowWidth,
+ maxKeyWidth * numColumnInRow + mDividerWidth * (numColumnInRow - 1));
+ }
+ return maxRowWidth;
+ }
+
+ private static final int[][] COLUMN_ORDER_TO_NUMBER = {
+ { 0, },
+ { 1, 0, },
+ { 2, 0, 1},
+ };
+
+ public int getNumColumnInRow(final int pos) {
+ return mNumColumnsInRow[mRowNumbers[pos]];
+ }
+
+ public int getColumnNumber(final int pos) {
+ final int columnOrder = mColumnOrders[pos];
+ final int numColumn = getNumColumnInRow(pos);
+ return COLUMN_ORDER_TO_NUMBER[numColumn - 1][columnOrder];
+ }
+
+ public int getX(final int pos) {
+ final int columnNumber = getColumnNumber(pos);
+ return columnNumber * (getWidth(pos) + mDividerWidth);
+ }
+
+ public int getY(final int pos) {
+ final int row = mRowNumbers[pos];
+ return (mNumRows -1 - row) * mDefaultRowHeight + mTopPadding;
+ }
+
+ public int getWidth(final int pos) {
+ final int numColumnInRow = getNumColumnInRow(pos);
+ return (mOccupiedWidth - mDividerWidth * (numColumnInRow - 1)) / numColumnInRow;
+ }
+
+ public void markAsEdgeKey(final Key key, final int pos) {
+ final int row = mRowNumbers[pos];
+ if (row == 0)
+ key.markAsBottomEdge(this);
+ if (row == mNumRows - 1)
+ key.markAsTopEdge(this);
+
+ final int numColumnInRow = mNumColumnsInRow[row];
+ final int column = getColumnNumber(pos);
+ if (column == 0)
+ key.markAsLeftEdge(this);
+ if (column == numColumnInRow - 1)
+ key.markAsRightEdge(this);
+ }
+ }
+
+ public static class Builder extends KeyboardBuilder<MoreSuggestionsParam> {
private final MoreSuggestionsView mPaneView;
private SuggestedWords mSuggestions;
private int mFromPos;
private int mToPos;
- public static class MoreSuggestionsParam extends Keyboard.Params {
- private final int[] mWidths = new int[SuggestionStripView.MAX_SUGGESTIONS];
- private final int[] mRowNumbers = new int[SuggestionStripView.MAX_SUGGESTIONS];
- private final int[] mColumnOrders = new int[SuggestionStripView.MAX_SUGGESTIONS];
- private final int[] mNumColumnsInRow = new int[SuggestionStripView.MAX_SUGGESTIONS];
- private static final int MAX_COLUMNS_IN_ROW = 3;
- private int mNumRows;
- public Drawable mDivider;
- public int mDividerWidth;
-
- public int layout(SuggestedWords suggestions, int fromPos, int maxWidth, int minWidth,
- int maxRow, MoreSuggestionsView view) {
- clearKeys();
- final Resources res = view.getContext().getResources();
- mDivider = res.getDrawable(R.drawable.more_suggestions_divider);
- mDividerWidth = mDivider.getIntrinsicWidth();
- final int padding = (int) res.getDimension(
- R.dimen.more_suggestions_key_horizontal_padding);
- final Paint paint = view.newDefaultLabelPaint();
-
- int row = 0;
- int pos = fromPos, rowStartPos = fromPos;
- final int size = Math.min(suggestions.size(), SuggestionStripView.MAX_SUGGESTIONS);
- while (pos < size) {
- final String word = suggestions.getWord(pos).toString();
- // TODO: Should take care of text x-scaling.
- mWidths[pos] = (int)view.getLabelWidth(word, paint) + padding;
- final int numColumn = pos - rowStartPos + 1;
- final int columnWidth =
- (maxWidth - mDividerWidth * (numColumn - 1)) / numColumn;
- if (numColumn > MAX_COLUMNS_IN_ROW
- || !fitInWidth(rowStartPos, pos + 1, columnWidth)) {
- if ((row + 1) >= maxRow) {
- break;
- }
- mNumColumnsInRow[row] = pos - rowStartPos;
- rowStartPos = pos;
- row++;
- }
- mColumnOrders[pos] = pos - rowStartPos;
- mRowNumbers[pos] = row;
- pos++;
- }
- mNumColumnsInRow[row] = pos - rowStartPos;
- mNumRows = row + 1;
- mBaseWidth = mOccupiedWidth = Math.max(
- minWidth, calcurateMaxRowWidth(fromPos, pos));
- mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap;
- return pos - fromPos;
- }
-
- private boolean fitInWidth(int startPos, int endPos, int width) {
- for (int pos = startPos; pos < endPos; pos++) {
- if (mWidths[pos] > width)
- return false;
- }
- return true;
- }
-
- private int calcurateMaxRowWidth(int startPos, int endPos) {
- int maxRowWidth = 0;
- int pos = startPos;
- for (int row = 0; row < mNumRows; row++) {
- final int numColumnInRow = mNumColumnsInRow[row];
- int maxKeyWidth = 0;
- while (pos < endPos && mRowNumbers[pos] == row) {
- maxKeyWidth = Math.max(maxKeyWidth, mWidths[pos]);
- pos++;
- }
- maxRowWidth = Math.max(maxRowWidth,
- maxKeyWidth * numColumnInRow + mDividerWidth * (numColumnInRow - 1));
- }
- return maxRowWidth;
- }
-
- private static final int[][] COLUMN_ORDER_TO_NUMBER = {
- { 0, },
- { 1, 0, },
- { 2, 0, 1},
- };
-
- public int getNumColumnInRow(int pos) {
- return mNumColumnsInRow[mRowNumbers[pos]];
- }
-
- public int getColumnNumber(int pos) {
- final int columnOrder = mColumnOrders[pos];
- final int numColumn = getNumColumnInRow(pos);
- return COLUMN_ORDER_TO_NUMBER[numColumn - 1][columnOrder];
- }
-
- public int getX(int pos) {
- final int columnNumber = getColumnNumber(pos);
- return columnNumber * (getWidth(pos) + mDividerWidth);
- }
-
- public int getY(int pos) {
- final int row = mRowNumbers[pos];
- return (mNumRows -1 - row) * mDefaultRowHeight + mTopPadding;
- }
-
- public int getWidth(int pos) {
- final int numColumnInRow = getNumColumnInRow(pos);
- return (mOccupiedWidth - mDividerWidth * (numColumnInRow - 1)) / numColumnInRow;
- }
-
- public void markAsEdgeKey(Key key, int pos) {
- final int row = mRowNumbers[pos];
- if (row == 0)
- key.markAsBottomEdge(this);
- if (row == mNumRows - 1)
- key.markAsTopEdge(this);
-
- final int numColumnInRow = mNumColumnsInRow[row];
- final int column = getColumnNumber(pos);
- if (column == 0)
- key.markAsLeftEdge(this);
- if (column == numColumnInRow - 1)
- key.markAsRightEdge(this);
- }
- }
-
- public Builder(MoreSuggestionsView paneView) {
+ public Builder(final MoreSuggestionsView paneView) {
super(paneView.getContext(), new MoreSuggestionsParam());
mPaneView = paneView;
}
- public Builder layout(SuggestedWords suggestions, int fromPos, int maxWidth,
- int minWidth, int maxRow) {
+ public Builder layout(final SuggestedWords suggestions, final int fromPos,
+ final int maxWidth, final int minWidth, final int maxRow) {
final Keyboard keyboard = KeyboardSwitcher.getInstance().getKeyboard();
final int xmlId = R.xml.kbd_suggestions_pane_template;
load(xmlId, keyboard.mId);
@@ -183,25 +189,6 @@
return this;
}
- private static class Divider extends Key.Spacer {
- private final Drawable mIcon;
-
- public Divider(Keyboard.Params params, Drawable icon, int x, int y, int width,
- int height) {
- super(params, x, y, width, height);
- mIcon = icon;
- }
-
- @Override
- public Drawable getIcon(KeyboardIconsSet iconSet, int alpha) {
- // KeyboardIconsSet and alpha are unused. Use the icon that has been passed to the
- // constructor.
- // TODO: Drawable itself should have an alpha value.
- mIcon.setAlpha(128);
- return mIcon;
- }
- }
-
@Override
public MoreSuggestions build() {
final MoreSuggestionsParam params = mParams;
@@ -228,4 +215,23 @@
return new MoreSuggestions(params);
}
}
+
+ private static class Divider extends Key.Spacer {
+ private final Drawable mIcon;
+
+ public Divider(final KeyboardParams params, final Drawable icon, final int x,
+ final int y, final int width, final int height) {
+ super(params, x, y, width, height);
+ mIcon = icon;
+ }
+
+ @Override
+ public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) {
+ // KeyboardIconsSet and alpha are unused. Use the icon that has been passed to the
+ // constructor.
+ // TODO: Drawable itself should have an alpha value.
+ mIcon.setAlpha(128);
+ return mIcon;
+ }
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index b57ffd2..9e8ab81 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -58,8 +58,10 @@
import com.android.inputmethod.keyboard.PointerTracker;
import com.android.inputmethod.keyboard.ViewLayoutUtils;
import com.android.inputmethod.latin.AutoCorrection;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.ResourceUtils;
import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.Utils;
@@ -72,7 +74,7 @@
OnLongClickListener {
public interface Listener {
public boolean addWordToUserDictionary(String word);
- public void pickSuggestionManually(int index, CharSequence word, int x, int y);
+ public void pickSuggestionManually(int index, CharSequence word);
}
// The maximum number of suggestions available. See {@link Suggest#mPrefMaxSuggestions}.
@@ -88,9 +90,9 @@
private final MoreSuggestions.Builder mMoreSuggestionsBuilder;
private final PopupWindow mMoreSuggestionsWindow;
- private final ArrayList<TextView> mWords = new ArrayList<TextView>();
- private final ArrayList<TextView> mInfos = new ArrayList<TextView>();
- private final ArrayList<View> mDividers = new ArrayList<View>();
+ private final ArrayList<TextView> mWords = CollectionUtils.newArrayList();
+ private final ArrayList<TextView> mInfos = CollectionUtils.newArrayList();
+ private final ArrayList<View> mDividers = CollectionUtils.newArrayList();
private final PopupWindow mPreviewPopup;
private final TextView mPreviewText;
@@ -131,7 +133,7 @@
private static class SuggestionStripViewParams {
private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3;
- private static final int DEFAULT_CENTER_SUGGESTION_PERCENTILE = 40;
+ private static final float DEFAULT_CENTER_SUGGESTION_PERCENTILE = 0.40f;
private static final int DEFAULT_MAX_MORE_SUGGESTIONS_ROW = 2;
private static final int PUNCTUATIONS_IN_STRIP = 5;
@@ -167,7 +169,7 @@
private final int mSuggestionStripOption;
- private final ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>();
+ private final ArrayList<CharSequence> mTexts = CollectionUtils.newArrayList();
public boolean mMoreSuggestionsAvailable;
@@ -195,16 +197,16 @@
R.styleable.SuggestionStripView, defStyle, R.style.SuggestionStripViewStyle);
mSuggestionStripOption = a.getInt(
R.styleable.SuggestionStripView_suggestionStripOption, 0);
- final float alphaValidTypedWord = getPercent(a,
- R.styleable.SuggestionStripView_alphaValidTypedWord, 100);
- final float alphaTypedWord = getPercent(a,
- R.styleable.SuggestionStripView_alphaTypedWord, 100);
- final float alphaAutoCorrect = getPercent(a,
- R.styleable.SuggestionStripView_alphaAutoCorrect, 100);
- final float alphaSuggested = getPercent(a,
- R.styleable.SuggestionStripView_alphaSuggested, 100);
- mAlphaObsoleted = getPercent(a,
- R.styleable.SuggestionStripView_alphaSuggested, 100);
+ final float alphaValidTypedWord = ResourceUtils.getFraction(a,
+ R.styleable.SuggestionStripView_alphaValidTypedWord, 1.0f);
+ final float alphaTypedWord = ResourceUtils.getFraction(a,
+ R.styleable.SuggestionStripView_alphaTypedWord, 1.0f);
+ final float alphaAutoCorrect = ResourceUtils.getFraction(a,
+ R.styleable.SuggestionStripView_alphaAutoCorrect, 1.0f);
+ final float alphaSuggested = ResourceUtils.getFraction(a,
+ R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
+ mAlphaObsoleted = ResourceUtils.getFraction(a,
+ R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
mColorValidTypedWord = applyAlpha(a.getColor(
R.styleable.SuggestionStripView_colorValidTypedWord, 0), alphaValidTypedWord);
mColorTypedWord = applyAlpha(a.getColor(
@@ -216,14 +218,14 @@
mSuggestionsCountInStrip = a.getInt(
R.styleable.SuggestionStripView_suggestionsCountInStrip,
DEFAULT_SUGGESTIONS_COUNT_IN_STRIP);
- mCenterSuggestionWeight = getPercent(a,
+ mCenterSuggestionWeight = ResourceUtils.getFraction(a,
R.styleable.SuggestionStripView_centerSuggestionPercentile,
DEFAULT_CENTER_SUGGESTION_PERCENTILE);
mMaxMoreSuggestionsRow = a.getInt(
R.styleable.SuggestionStripView_maxMoreSuggestionsRow,
DEFAULT_MAX_MORE_SUGGESTIONS_ROW);
- mMinMoreSuggestionsWidth = getRatio(a,
- R.styleable.SuggestionStripView_minMoreSuggestionsWidth);
+ mMinMoreSuggestionsWidth = ResourceUtils.getFraction(a,
+ R.styleable.SuggestionStripView_minMoreSuggestionsWidth, 1.0f);
a.recycle();
mMoreSuggestionsHint = getMoreSuggestionsHint(res,
@@ -277,16 +279,6 @@
return new BitmapDrawable(res, buffer);
}
- // Read integer value in TypedArray as percent.
- private static float getPercent(TypedArray a, int index, int defValue) {
- return a.getInt(index, defValue) / 100.0f;
- }
-
- // Read fraction value in TypedArray as float.
- private static float getRatio(TypedArray a, int index) {
- return a.getFraction(index, 1000, 1000, 1) / 1000.0f;
- }
-
private CharSequence getStyledSuggestionWord(SuggestedWords suggestedWords, int pos) {
final CharSequence word = suggestedWords.getWord(pos);
final boolean isAutoCorrect = pos == 1 && suggestedWords.willAutoCorrect();
@@ -726,9 +718,7 @@
public boolean onCustomRequest(int requestCode) {
final int index = requestCode;
final CharSequence word = mSuggestedWords.getWord(index);
- // TODO: change caller path so coordinates are passed through here
- mListener.pickSuggestionManually(index, word, NOT_A_TOUCH_COORDINATE,
- NOT_A_TOUCH_COORDINATE);
+ mListener.pickSuggestionManually(index, word);
dismissMoreSuggestions();
return true;
}
@@ -874,7 +864,7 @@
return;
final CharSequence word = mSuggestedWords.getWord(index);
- mListener.pickSuggestionManually(index, word, mLastX, mLastY);
+ mListener.pickSuggestionManually(index, word);
}
@Override
diff --git a/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java b/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java
new file mode 100644
index 0000000..5124a35
--- /dev/null
+++ b/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.research;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * Arrange for the uploading service to be run on regular intervals.
+ */
+public final class BootBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
+ ResearchLogger.scheduleUploadingService(context);
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/research/FeedbackActivity.java b/java/src/com/android/inputmethod/research/FeedbackActivity.java
index c9f3b47..11eae88 100644
--- a/java/src/com/android/inputmethod/research/FeedbackActivity.java
+++ b/java/src/com/android/inputmethod/research/FeedbackActivity.java
@@ -18,10 +18,7 @@
import android.app.Activity;
import android.os.Bundle;
-import android.text.Editable;
-import android.view.View;
import android.widget.CheckBox;
-import android.widget.EditText;
import com.android.inputmethod.latin.R;
@@ -31,6 +28,11 @@
super.onCreate(savedInstanceState);
setContentView(R.layout.research_feedback_activity);
final FeedbackLayout layout = (FeedbackLayout) findViewById(R.id.research_feedback_layout);
+ final CheckBox checkbox = (CheckBox) findViewById(R.id.research_feedback_include_history);
+ final CharSequence cs = checkbox.getText();
+ final String actualString = String.format(cs.toString(),
+ ResearchLogger.FEEDBACK_WORD_BUFFER_SIZE);
+ checkbox.setText(actualString);
layout.setActivity(this);
}
diff --git a/java/src/com/android/inputmethod/research/LogBuffer.java b/java/src/com/android/inputmethod/research/LogBuffer.java
new file mode 100644
index 0000000..ae7b157
--- /dev/null
+++ b/java/src/com/android/inputmethod/research/LogBuffer.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.research;
+
+import com.android.inputmethod.latin.CollectionUtils;
+
+import java.util.LinkedList;
+
+/**
+ * A buffer that holds a fixed number of LogUnits.
+ *
+ * LogUnits are added in and shifted out in temporal order. Only a subset of the LogUnits are
+ * actual words; the other LogUnits do not count toward the word limit. Once the buffer reaches
+ * capacity, adding another LogUnit that is a word evicts the oldest LogUnits out one at a time to
+ * stay under the capacity limit.
+ */
+public class LogBuffer {
+ protected final LinkedList<LogUnit> mLogUnits;
+ /* package for test */ int mWordCapacity;
+ // The number of members of mLogUnits that are actual words.
+ protected int mNumActualWords;
+
+ /**
+ * Create a new LogBuffer that can hold a fixed number of LogUnits that are words (and
+ * unlimited number of non-word LogUnits), and that outputs its result to a researchLog.
+ *
+ * @param wordCapacity maximum number of words
+ */
+ LogBuffer(final int wordCapacity) {
+ if (wordCapacity <= 0) {
+ throw new IllegalArgumentException("wordCapacity must be 1 or greater.");
+ }
+ mLogUnits = CollectionUtils.newLinkedList();
+ mWordCapacity = wordCapacity;
+ mNumActualWords = 0;
+ }
+
+ /**
+ * Adds a new LogUnit to the front of the LIFO queue, evicting existing LogUnit's
+ * (oldest first) if word capacity is reached.
+ */
+ public void shiftIn(LogUnit newLogUnit) {
+ if (newLogUnit.getWord() == null) {
+ // This LogUnit isn't a word, so it doesn't count toward the word-limit.
+ mLogUnits.add(newLogUnit);
+ return;
+ }
+ if (mNumActualWords == mWordCapacity) {
+ shiftOutThroughFirstWord();
+ }
+ mLogUnits.add(newLogUnit);
+ mNumActualWords++; // Must be a word, or we wouldn't be here.
+ }
+
+ private void shiftOutThroughFirstWord() {
+ while (!mLogUnits.isEmpty()) {
+ final LogUnit logUnit = mLogUnits.removeFirst();
+ onShiftOut(logUnit);
+ if (logUnit.hasWord()) {
+ // Successfully shifted out a word-containing LogUnit and made space for the new
+ // LogUnit.
+ mNumActualWords--;
+ break;
+ }
+ }
+ }
+
+ /**
+ * Removes all LogUnits from the buffer without calling onShiftOut().
+ */
+ public void clear() {
+ mLogUnits.clear();
+ mNumActualWords = 0;
+ }
+
+ /**
+ * Called when a LogUnit is removed from the LogBuffer as a result of a shiftIn. LogUnits are
+ * removed in the order entered. This method is not called when shiftOut is called directly.
+ *
+ * Base class does nothing; subclasses may override.
+ */
+ protected void onShiftOut(LogUnit logUnit) {
+ }
+
+ /**
+ * Called to deliberately remove the oldest LogUnit. Usually called when draining the
+ * LogBuffer.
+ */
+ public LogUnit shiftOut() {
+ if (mLogUnits.isEmpty()) {
+ return null;
+ }
+ final LogUnit logUnit = mLogUnits.removeFirst();
+ if (logUnit.hasWord()) {
+ mNumActualWords--;
+ }
+ return logUnit;
+ }
+}
diff --git a/java/src/com/android/inputmethod/research/LogUnit.java b/java/src/com/android/inputmethod/research/LogUnit.java
new file mode 100644
index 0000000..d8b3a29
--- /dev/null
+++ b/java/src/com/android/inputmethod/research/LogUnit.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.research;
+
+import com.android.inputmethod.latin.CollectionUtils;
+
+import java.util.ArrayList;
+
+/**
+ * A group of log statements related to each other.
+ *
+ * A LogUnit is collection of LogStatements, each of which is generated by at a particular point
+ * in the code. (There is no LogStatement class; the data is stored across the instance variables
+ * here.) A single LogUnit's statements can correspond to all the calls made while in the same
+ * composing region, or all the calls between committing the last composing region, and the first
+ * character of the next composing region.
+ *
+ * Individual statements in a log may be marked as potentially private. If so, then they are only
+ * published to a ResearchLog if the ResearchLogger determines that publishing the entire LogUnit
+ * will not violate the user's privacy. Checks for this may include whether other LogUnits have
+ * been published recently, or whether the LogUnit contains numbers, etc.
+ */
+/* package */ class LogUnit {
+ private final ArrayList<String[]> mKeysList = CollectionUtils.newArrayList();
+ private final ArrayList<Object[]> mValuesList = CollectionUtils.newArrayList();
+ private final ArrayList<Boolean> mIsPotentiallyPrivate = CollectionUtils.newArrayList();
+ private String mWord;
+ private boolean mContainsDigit;
+
+ public void addLogStatement(final String[] keys, final Object[] values,
+ final Boolean isPotentiallyPrivate) {
+ mKeysList.add(keys);
+ mValuesList.add(values);
+ mIsPotentiallyPrivate.add(isPotentiallyPrivate);
+ }
+
+ public void publishTo(final ResearchLog researchLog, final boolean isIncludingPrivateData) {
+ final int size = mKeysList.size();
+ for (int i = 0; i < size; i++) {
+ if (!mIsPotentiallyPrivate.get(i) || isIncludingPrivateData) {
+ researchLog.outputEvent(mKeysList.get(i), mValuesList.get(i));
+ }
+ }
+ }
+
+ public void setWord(String word) {
+ mWord = word;
+ }
+
+ public String getWord() {
+ return mWord;
+ }
+
+ public boolean hasWord() {
+ return mWord != null;
+ }
+
+ public void setContainsDigit() {
+ mContainsDigit = true;
+ }
+
+ public boolean hasDigit() {
+ return mContainsDigit;
+ }
+
+ public boolean isEmpty() {
+ return mKeysList.isEmpty();
+ }
+}
diff --git a/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java
new file mode 100644
index 0000000..745768d
--- /dev/null
+++ b/java/src/com/android/inputmethod/research/MainLogBuffer.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.research;
+
+import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.Suggest;
+
+import java.util.Random;
+
+public class MainLogBuffer extends LogBuffer {
+ // The size of the n-grams logged. E.g. N_GRAM_SIZE = 2 means to sample bigrams.
+ private static final int N_GRAM_SIZE = 2;
+ // The number of words between n-grams to omit from the log.
+ private static final int DEFAULT_NUMBER_OF_WORDS_BETWEEN_SAMPLES = 18;
+
+ private final ResearchLog mResearchLog;
+ private Suggest mSuggest;
+
+ // The minimum periodicity with which n-grams can be sampled. E.g. mWinWordPeriod is 10 if
+ // every 10th bigram is sampled, i.e., words 1-8 are not, but the bigram at words 9 and 10, etc.
+ // for 11-18, and the bigram at words 19 and 20. If an n-gram is not safe (e.g. it contains a
+ // number in the middle or an out-of-vocabulary word), then sampling is delayed until a safe
+ // n-gram does appear.
+ /* package for test */ int mMinWordPeriod;
+
+ // Counter for words left to suppress before an n-gram can be sampled. Reset to mMinWordPeriod
+ // after a sample is taken.
+ /* package for test */ int mWordsUntilSafeToSample;
+
+ public MainLogBuffer(final ResearchLog researchLog) {
+ super(N_GRAM_SIZE);
+ mResearchLog = researchLog;
+ mMinWordPeriod = DEFAULT_NUMBER_OF_WORDS_BETWEEN_SAMPLES + N_GRAM_SIZE;
+ final Random random = new Random();
+ mWordsUntilSafeToSample = random.nextInt(mMinWordPeriod);
+ }
+
+ public void setSuggest(Suggest suggest) {
+ mSuggest = suggest;
+ }
+
+ @Override
+ public void shiftIn(final LogUnit newLogUnit) {
+ super.shiftIn(newLogUnit);
+ if (newLogUnit.hasWord()) {
+ if (mWordsUntilSafeToSample > 0) {
+ mWordsUntilSafeToSample--;
+ }
+ }
+ }
+
+ public void resetWordCounter() {
+ mWordsUntilSafeToSample = mMinWordPeriod;
+ }
+
+ /**
+ * Determines whether the content of the MainLogBuffer can be safely uploaded in its complete
+ * form and still protect the user's privacy.
+ *
+ * The size of the MainLogBuffer is just enough to hold one n-gram, its corrections, and any
+ * non-character data that is typed between words. The decision about privacy is made based on
+ * the buffer's entire content. If it is decided that the privacy risks are too great to upload
+ * the contents of this buffer, a censored version of the LogItems may still be uploaded. E.g.,
+ * the screen orientation and other characteristics about the device can be uploaded without
+ * revealing much about the user.
+ */
+ public boolean isSafeToLog() {
+ // Check that we are not sampling too frequently. Having sampled recently might disclose
+ // too much of the user's intended meaning.
+ if (mWordsUntilSafeToSample > 0) {
+ return false;
+ }
+ if (mSuggest == null || !mSuggest.hasMainDictionary()) {
+ // Main dictionary is unavailable. Since we cannot check it, we cannot tell if a word
+ // is out-of-vocabulary or not. Therefore, we must judge the entire buffer contents to
+ // potentially pose a privacy risk.
+ return false;
+ }
+ // Reload the dictionary in case it has changed (e.g., because the user has changed
+ // languages).
+ final Dictionary dictionary = mSuggest.getMainDictionary();
+ if (dictionary == null) {
+ return false;
+ }
+ // Check each word in the buffer. If any word poses a privacy threat, we cannot upload the
+ // complete buffer contents in detail.
+ final int length = mLogUnits.size();
+ for (int i = 0; i < length; i++) {
+ final LogUnit logUnit = mLogUnits.get(i);
+ final String word = logUnit.getWord();
+ if (word == null) {
+ // Digits outside words are a privacy threat.
+ if (logUnit.hasDigit()) {
+ return false;
+ }
+ } else {
+ // Words not in the dictionary are a privacy threat.
+ if (!(dictionary.isValidWord(word))) {
+ return false;
+ }
+ }
+ }
+ // All checks have passed; this buffer's content can be safely uploaded.
+ return true;
+ }
+
+ @Override
+ protected void onShiftOut(LogUnit logUnit) {
+ if (mResearchLog != null) {
+ mResearchLog.publish(logUnit, false /* isIncludingPrivateData */);
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java
index 18bf3c0..cd9ff85 100644
--- a/java/src/com/android/inputmethod/research/ResearchLog.java
+++ b/java/src/com/android/inputmethod/research/ResearchLog.java
@@ -26,7 +26,6 @@
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.define.ProductionFlag;
-import com.android.inputmethod.research.ResearchLogger.LogUnit;
import java.io.BufferedWriter;
import java.io.File;
@@ -37,6 +36,7 @@
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
+import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@@ -51,21 +51,22 @@
*/
public class ResearchLog {
private static final String TAG = ResearchLog.class.getSimpleName();
- private static final JsonWriter NULL_JSON_WRITER = new JsonWriter(
- new OutputStreamWriter(new NullOutputStream()));
+ private static final boolean DEBUG = false;
+ private static final long FLUSH_DELAY_IN_MS = 1000 * 5;
+ private static final int ABORT_TIMEOUT_IN_MS = 1000 * 4;
- final ScheduledExecutorService mExecutor;
+ /* package */ final ScheduledExecutorService mExecutor;
/* package */ final File mFile;
private JsonWriter mJsonWriter = NULL_JSON_WRITER;
+ // true if at least one byte of data has been written out to the log file. This must be
+ // remembered because JsonWriter requires that calls matching calls to beginObject and
+ // endObject, as well as beginArray and endArray, and the file is opened lazily, only when
+ // it is certain that data will be written. Alternatively, the matching call exceptions
+ // could be caught, but this might suppress other errors.
+ private boolean mHasWrittenData = false;
- private int mLoggingState;
- private static final int LOGGING_STATE_UNSTARTED = 0;
- private static final int LOGGING_STATE_READY = 1; // don't create file until necessary
- private static final int LOGGING_STATE_RUNNING = 2;
- private static final int LOGGING_STATE_STOPPING = 3;
- private static final int LOGGING_STATE_STOPPED = 4;
- private static final long FLUSH_DELAY_IN_MS = 1000 * 5;
-
+ private static final JsonWriter NULL_JSON_WRITER = new JsonWriter(
+ new OutputStreamWriter(new NullOutputStream()));
private static class NullOutputStream extends OutputStream {
/** {@inheritDoc} */
@Override
@@ -84,128 +85,81 @@
}
}
- public ResearchLog(File outputFile) {
- mExecutor = Executors.newSingleThreadScheduledExecutor();
+ public ResearchLog(final File outputFile) {
if (outputFile == null) {
throw new IllegalArgumentException();
}
+ mExecutor = Executors.newSingleThreadScheduledExecutor();
mFile = outputFile;
- mLoggingState = LOGGING_STATE_UNSTARTED;
}
- public synchronized void start() throws IOException {
- switch (mLoggingState) {
- case LOGGING_STATE_UNSTARTED:
- mLoggingState = LOGGING_STATE_READY;
- break;
- case LOGGING_STATE_READY:
- case LOGGING_STATE_RUNNING:
- case LOGGING_STATE_STOPPING:
- case LOGGING_STATE_STOPPED:
- break;
- }
- }
-
- public synchronized void stop() {
- switch (mLoggingState) {
- case LOGGING_STATE_UNSTARTED:
- mLoggingState = LOGGING_STATE_STOPPED;
- break;
- case LOGGING_STATE_READY:
- case LOGGING_STATE_RUNNING:
- mExecutor.submit(new Callable<Object>() {
- @Override
- public Object call() throws Exception {
- try {
- mJsonWriter.endArray();
- mJsonWriter.flush();
- mJsonWriter.close();
- } finally {
- boolean success = mFile.setWritable(false, false);
- mLoggingState = LOGGING_STATE_STOPPED;
- }
- return null;
+ public synchronized void close() {
+ mExecutor.submit(new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ try {
+ if (mHasWrittenData) {
+ mJsonWriter.endArray();
+ mJsonWriter.flush();
+ mJsonWriter.close();
+ mHasWrittenData = false;
}
- });
- removeAnyScheduledFlush();
- mExecutor.shutdown();
- mLoggingState = LOGGING_STATE_STOPPING;
- break;
- case LOGGING_STATE_STOPPING:
- case LOGGING_STATE_STOPPED:
- }
- }
-
- public boolean isAlive() {
- switch (mLoggingState) {
- case LOGGING_STATE_UNSTARTED:
- case LOGGING_STATE_READY:
- case LOGGING_STATE_RUNNING:
- return true;
- }
- return false;
- }
-
- public void waitUntilStopped(final int timeoutInMs) throws InterruptedException {
+ } catch (Exception e) {
+ Log.d(TAG, "error when closing ResearchLog:");
+ e.printStackTrace();
+ } finally {
+ if (mFile.exists()) {
+ mFile.setWritable(false, false);
+ }
+ }
+ return null;
+ }
+ });
removeAnyScheduledFlush();
mExecutor.shutdown();
- mExecutor.awaitTermination(timeoutInMs, TimeUnit.MILLISECONDS);
}
+ private boolean mIsAbortSuccessful;
+
public synchronized void abort() {
- switch (mLoggingState) {
- case LOGGING_STATE_UNSTARTED:
- mLoggingState = LOGGING_STATE_STOPPED;
- isAbortSuccessful = true;
- break;
- case LOGGING_STATE_READY:
- case LOGGING_STATE_RUNNING:
- mExecutor.submit(new Callable<Object>() {
- @Override
- public Object call() throws Exception {
- try {
- mJsonWriter.endArray();
- mJsonWriter.close();
- } finally {
- isAbortSuccessful = mFile.delete();
- }
- return null;
+ mExecutor.submit(new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ try {
+ if (mHasWrittenData) {
+ mJsonWriter.endArray();
+ mJsonWriter.close();
+ mHasWrittenData = false;
}
- });
- removeAnyScheduledFlush();
- mExecutor.shutdown();
- mLoggingState = LOGGING_STATE_STOPPING;
- break;
- case LOGGING_STATE_STOPPING:
- case LOGGING_STATE_STOPPED:
- }
+ } finally {
+ mIsAbortSuccessful = mFile.delete();
+ }
+ return null;
+ }
+ });
+ removeAnyScheduledFlush();
+ mExecutor.shutdown();
}
- private boolean isAbortSuccessful;
- public boolean isAbortSuccessful() {
- return isAbortSuccessful;
+ public boolean blockingAbort() throws InterruptedException {
+ abort();
+ mExecutor.awaitTermination(ABORT_TIMEOUT_IN_MS, TimeUnit.MILLISECONDS);
+ return mIsAbortSuccessful;
+ }
+
+ public void awaitTermination(int delay, TimeUnit timeUnit) throws InterruptedException {
+ mExecutor.awaitTermination(delay, timeUnit);
}
/* package */ synchronized void flush() {
- switch (mLoggingState) {
- case LOGGING_STATE_UNSTARTED:
- break;
- case LOGGING_STATE_READY:
- case LOGGING_STATE_RUNNING:
- removeAnyScheduledFlush();
- mExecutor.submit(mFlushCallable);
- break;
- case LOGGING_STATE_STOPPING:
- case LOGGING_STATE_STOPPED:
- }
+ removeAnyScheduledFlush();
+ mExecutor.submit(mFlushCallable);
}
- private Callable<Object> mFlushCallable = new Callable<Object>() {
+ private final Callable<Object> mFlushCallable = new Callable<Object>() {
@Override
public Object call() throws Exception {
- if (mLoggingState == LOGGING_STATE_RUNNING) {
- mJsonWriter.flush();
- }
+ mJsonWriter.flush();
return null;
}
};
@@ -224,56 +178,40 @@
mFlushFuture = mExecutor.schedule(mFlushCallable, FLUSH_DELAY_IN_MS, TimeUnit.MILLISECONDS);
}
- public synchronized void publishPublicEvents(final LogUnit logUnit) {
- switch (mLoggingState) {
- case LOGGING_STATE_UNSTARTED:
- break;
- case LOGGING_STATE_READY:
- case LOGGING_STATE_RUNNING:
- mExecutor.submit(new Callable<Object>() {
- @Override
- public Object call() throws Exception {
- logUnit.publishPublicEventsTo(ResearchLog.this);
- scheduleFlush();
- return null;
- }
- });
- break;
- case LOGGING_STATE_STOPPING:
- case LOGGING_STATE_STOPPED:
- }
- }
-
- public synchronized void publishAllEvents(final LogUnit logUnit) {
- switch (mLoggingState) {
- case LOGGING_STATE_UNSTARTED:
- break;
- case LOGGING_STATE_READY:
- case LOGGING_STATE_RUNNING:
- mExecutor.submit(new Callable<Object>() {
- @Override
- public Object call() throws Exception {
- logUnit.publishAllEventsTo(ResearchLog.this);
- scheduleFlush();
- return null;
- }
- });
- break;
- case LOGGING_STATE_STOPPING:
- case LOGGING_STATE_STOPPED:
+ public synchronized void publish(final LogUnit logUnit, final boolean isIncludingPrivateData) {
+ try {
+ mExecutor.submit(new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ logUnit.publishTo(ResearchLog.this, isIncludingPrivateData);
+ scheduleFlush();
+ return null;
+ }
+ });
+ } catch (RejectedExecutionException e) {
+ // TODO: Add code to record loss of data, and report.
}
}
private static final String CURRENT_TIME_KEY = "_ct";
private static final String UPTIME_KEY = "_ut";
private static final String EVENT_TYPE_KEY = "_ty";
+
void outputEvent(final String[] keys, final Object[] values) {
- // not thread safe.
+ // Not thread safe.
+ if (keys.length == 0) {
+ return;
+ }
+ if (DEBUG) {
+ if (keys.length != values.length + 1) {
+ Log.d(TAG, "Key and Value list sizes do not match. " + keys[0]);
+ }
+ }
try {
if (mJsonWriter == NULL_JSON_WRITER) {
mJsonWriter = new JsonWriter(new BufferedWriter(new FileWriter(mFile)));
- mJsonWriter.setLenient(true);
mJsonWriter.beginArray();
+ mHasWrittenData = true;
}
mJsonWriter.beginObject();
mJsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis());
@@ -283,8 +221,8 @@
for (int i = 0; i < length; i++) {
mJsonWriter.name(keys[i + 1]);
Object value = values[i];
- if (value instanceof String) {
- mJsonWriter.value((String) value);
+ if (value instanceof CharSequence) {
+ mJsonWriter.value(value.toString());
} else if (value instanceof Number) {
mJsonWriter.value((Number) value);
} else if (value instanceof Boolean) {
@@ -319,7 +257,7 @@
for (Key keyboardKey : keyboardKeys) {
mJsonWriter.beginObject();
mJsonWriter.name("code").value(keyboardKey.mCode);
- mJsonWriter.name("altCode").value(keyboardKey.mAltCode);
+ mJsonWriter.name("altCode").value(keyboardKey.getAltCode());
mJsonWriter.name("x").value(keyboardKey.mX);
mJsonWriter.name("y").value(keyboardKey.mY);
mJsonWriter.name("w").value(keyboardKey.mWidth);
@@ -331,14 +269,11 @@
SuggestedWords words = (SuggestedWords) value;
mJsonWriter.beginObject();
mJsonWriter.name("typedWordValid").value(words.mTypedWordValid);
- mJsonWriter.name("willAutoCorrect")
- .value(words.mWillAutoCorrect);
+ mJsonWriter.name("willAutoCorrect").value(words.mWillAutoCorrect);
mJsonWriter.name("isPunctuationSuggestions")
- .value(words.mIsPunctuationSuggestions);
- mJsonWriter.name("isObsoleteSuggestions")
- .value(words.mIsObsoleteSuggestions);
- mJsonWriter.name("isPrediction")
- .value(words.mIsPrediction);
+ .value(words.mIsPunctuationSuggestions);
+ mJsonWriter.name("isObsoleteSuggestions").value(words.mIsObsoleteSuggestions);
+ mJsonWriter.name("isPrediction").value(words.mIsPrediction);
mJsonWriter.name("words");
mJsonWriter.beginArray();
final int size = words.size();
@@ -363,8 +298,8 @@
try {
mJsonWriter.close();
} catch (IllegalStateException e1) {
- // assume that this is just the json not being terminated properly.
- // ignore
+ // Assume that this is just the json not being terminated properly.
+ // Ignore
} catch (IOException e1) {
e1.printStackTrace();
} finally {
diff --git a/java/src/com/android/inputmethod/research/ResearchLogUploader.java b/java/src/com/android/inputmethod/research/ResearchLogUploader.java
deleted file mode 100644
index 3b12130..0000000
--- a/java/src/com/android/inputmethod/research/ResearchLogUploader.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.research;
-
-import android.Manifest;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.BatteryManager;
-import android.util.Log;
-
-import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.R.string;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileFilter;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-
-public final class ResearchLogUploader {
- private static final String TAG = ResearchLogUploader.class.getSimpleName();
- private static final int UPLOAD_INTERVAL_IN_MS = 1000 * 60 * 15; // every 15 min
- private static final int BUF_SIZE = 1024 * 8;
-
- private final boolean mCanUpload;
- private final Context mContext;
- private final File mFilesDir;
- private final URL mUrl;
- private final ScheduledExecutorService mExecutor;
-
- private Runnable doUploadRunnable = new UploadRunnable(null, false);
-
- public ResearchLogUploader(final Context context, final File filesDir) {
- mContext = context;
- mFilesDir = filesDir;
- final PackageManager packageManager = context.getPackageManager();
- final boolean hasPermission = packageManager.checkPermission(Manifest.permission.INTERNET,
- context.getPackageName()) == PackageManager.PERMISSION_GRANTED;
- if (!hasPermission) {
- mCanUpload = false;
- mUrl = null;
- mExecutor = null;
- return;
- }
- URL tempUrl = null;
- boolean canUpload = false;
- ScheduledExecutorService executor = null;
- try {
- final String urlString = context.getString(R.string.research_logger_upload_url);
- if (urlString == null || urlString.equals("")) {
- return;
- }
- tempUrl = new URL(urlString);
- canUpload = true;
- executor = Executors.newSingleThreadScheduledExecutor();
- } catch (MalformedURLException e) {
- tempUrl = null;
- e.printStackTrace();
- return;
- } finally {
- mCanUpload = canUpload;
- mUrl = tempUrl;
- mExecutor = executor;
- }
- }
-
- public void start() {
- if (mCanUpload) {
- Log.d(TAG, "scheduling regular uploading");
- mExecutor.scheduleWithFixedDelay(doUploadRunnable, UPLOAD_INTERVAL_IN_MS,
- UPLOAD_INTERVAL_IN_MS, TimeUnit.MILLISECONDS);
- } else {
- Log.d(TAG, "no permission to upload");
- }
- }
-
- public void uploadNow(final Callback callback) {
- // Perform an immediate upload. Note that this should happen even if there is
- // another upload happening right now, as it may have missed the latest changes.
- // TODO: Reschedule regular upload tests starting from now.
- if (mCanUpload) {
- mExecutor.submit(new UploadRunnable(callback, true));
- }
- }
-
- public interface Callback {
- public void onUploadCompleted(final boolean success);
- }
-
- private boolean isExternallyPowered() {
- final Intent intent = mContext.registerReceiver(null, new IntentFilter(
- Intent.ACTION_BATTERY_CHANGED));
- final int pluggedState = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
- return pluggedState == BatteryManager.BATTERY_PLUGGED_AC
- || pluggedState == BatteryManager.BATTERY_PLUGGED_USB;
- }
-
- private boolean hasWifiConnection() {
- final ConnectivityManager manager =
- (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
- final NetworkInfo wifiInfo = manager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
- return wifiInfo.isConnected();
- }
-
- class UploadRunnable implements Runnable {
- private final Callback mCallback;
- private final boolean mForceUpload;
-
- public UploadRunnable(final Callback callback, final boolean forceUpload) {
- mCallback = callback;
- mForceUpload = forceUpload;
- }
-
- @Override
- public void run() {
- doUpload();
- }
-
- private void doUpload() {
- if (!mForceUpload && (!isExternallyPowered() || !hasWifiConnection())) {
- return;
- }
- if (mFilesDir == null) {
- return;
- }
- final File[] files = mFilesDir.listFiles(new FileFilter() {
- @Override
- public boolean accept(File pathname) {
- return pathname.getName().startsWith(ResearchLogger.FILENAME_PREFIX)
- && !pathname.canWrite();
- }
- });
- boolean success = true;
- if (files.length == 0) {
- success = false;
- }
- for (final File file : files) {
- if (!uploadFile(file)) {
- success = false;
- }
- }
- if (mCallback != null) {
- mCallback.onUploadCompleted(success);
- }
- }
-
- private boolean uploadFile(File file) {
- Log.d(TAG, "attempting upload of " + file.getAbsolutePath());
- boolean success = false;
- final int contentLength = (int) file.length();
- HttpURLConnection connection = null;
- InputStream fileIs = null;
- try {
- fileIs = new FileInputStream(file);
- connection = (HttpURLConnection) mUrl.openConnection();
- connection.setRequestMethod("PUT");
- connection.setDoOutput(true);
- connection.setFixedLengthStreamingMode(contentLength);
- final OutputStream os = connection.getOutputStream();
- final byte[] buf = new byte[BUF_SIZE];
- int numBytesRead;
- while ((numBytesRead = fileIs.read(buf)) != -1) {
- os.write(buf, 0, numBytesRead);
- }
- if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
- Log.d(TAG, "upload failed: " + connection.getResponseCode());
- InputStream netIs = connection.getInputStream();
- BufferedReader reader = new BufferedReader(new InputStreamReader(netIs));
- String line;
- while ((line = reader.readLine()) != null) {
- Log.d(TAG, "| " + reader.readLine());
- }
- reader.close();
- return success;
- }
- file.delete();
- success = true;
- Log.d(TAG, "upload successful");
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- if (fileIs != null) {
- try {
- fileIs.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- if (connection != null) {
- connection.disconnect();
- }
- }
- return success;
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index cf6f31a..5c24871 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -18,11 +18,14 @@
import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET;
+import android.app.AlarmManager;
import android.app.AlertDialog;
import android.app.Dialog;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
+import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.PackageInfo;
@@ -32,28 +35,30 @@
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.inputmethodservice.InputMethodService;
+import android.net.Uri;
import android.os.Build;
import android.os.IBinder;
+import android.os.SystemClock;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Log;
+import android.view.KeyEvent;
import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnClickListener;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.CorrectionInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
-import android.widget.Button;
import android.widget.Toast;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardId;
-import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.keyboard.KeyboardView;
import com.android.inputmethod.keyboard.MainKeyboardView;
+import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.LatinIME;
import com.android.inputmethod.latin.R;
@@ -64,11 +69,8 @@
import com.android.inputmethod.latin.define.ProductionFlag;
import java.io.File;
-import java.io.IOException;
import java.text.SimpleDateFormat;
-import java.util.ArrayList;
import java.util.Date;
-import java.util.List;
import java.util.Locale;
import java.util.UUID;
@@ -94,24 +96,26 @@
new SimpleDateFormat("yyyyMMddHHmmssS", Locale.US);
private static final boolean IS_SHOWING_INDICATOR = true;
private static final boolean IS_SHOWING_INDICATOR_CLEARLY = false;
+ public static final int FEEDBACK_WORD_BUFFER_SIZE = 5;
// constants related to specific log points
private static final String WHITESPACE_SEPARATORS = " \t\n\r";
private static final int MAX_INPUTVIEW_LENGTH_TO_CAPTURE = 8192; // must be >=1
private static final String PREF_RESEARCH_LOGGER_UUID_STRING = "pref_research_logger_uuid";
- private static final int ABORT_TIMEOUT_IN_MS = 10 * 1000; // timeout to notify user
private static final ResearchLogger sInstance = new ResearchLogger();
// to write to a different filename, e.g., for testing, set mFile before calling start()
/* package */ File mFilesDir;
/* package */ String mUUIDString;
/* package */ ResearchLog mMainResearchLog;
- // The mIntentionalResearchLog records all events for the session, private or not (excepting
+ // mFeedbackLog records all events for the session, private or not (excepting
// passwords). It is written to permanent storage only if the user explicitly commands
// the system to do so.
- /* package */ ResearchLog mIntentionalResearchLog;
- // LogUnits are queued here and released only when the user requests the intentional log.
- private List<LogUnit> mIntentionalResearchLogQueue = new ArrayList<LogUnit>();
+ // LogUnits are queued in the LogBuffers and published to the ResearchLogs when words are
+ // complete.
+ /* package */ ResearchLog mFeedbackLog;
+ /* package */ MainLogBuffer mMainLogBuffer;
+ /* package */ LogBuffer mFeedbackLogBuffer;
private boolean mIsPasswordView = false;
private boolean mIsLoggingSuspended = false;
@@ -133,20 +137,24 @@
// used to check whether words are not unique
private Suggest mSuggest;
private Dictionary mDictionary;
- private KeyboardSwitcher mKeyboardSwitcher;
+ private MainKeyboardView mMainKeyboardView;
private InputMethodService mInputMethodService;
+ private final Statistics mStatistics;
- private ResearchLogUploader mResearchLogUploader;
+ private Intent mUploadIntent;
+ private PendingIntent mUploadPendingIntent;
+
+ private LogUnit mCurrentLogUnit = new LogUnit();
private ResearchLogger() {
+ mStatistics = Statistics.getInstance();
}
public static ResearchLogger getInstance() {
return sInstance;
}
- public void init(final InputMethodService ims, final SharedPreferences prefs,
- KeyboardSwitcher keyboardSwitcher) {
+ public void init(final InputMethodService ims, final SharedPreferences prefs) {
assert ims != null;
if (ims == null) {
Log.w(TAG, "IMS is null; logging is off");
@@ -176,11 +184,33 @@
e.apply();
}
}
- mResearchLogUploader = new ResearchLogUploader(ims, mFilesDir);
- mResearchLogUploader.start();
- mKeyboardSwitcher = keyboardSwitcher;
mInputMethodService = ims;
mPrefs = prefs;
+ mUploadIntent = new Intent(mInputMethodService, UploaderService.class);
+ mUploadPendingIntent = PendingIntent.getService(mInputMethodService, 0, mUploadIntent, 0);
+
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ scheduleUploadingService(mInputMethodService);
+ }
+ }
+
+ /**
+ * Arrange for the UploaderService to be run on a regular basis.
+ *
+ * Any existing scheduled invocation of UploaderService is removed and rescheduled. This may
+ * cause problems if this method is called often and frequent updates are required, but since
+ * the user will likely be sleeping at some point, if the interval is less that the expected
+ * sleep duration and this method is not called during that time, the service should be invoked
+ * at some point.
+ */
+ public static void scheduleUploadingService(Context context) {
+ final Intent intent = new Intent(context, UploaderService.class);
+ final PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0);
+ final AlarmManager manager =
+ (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ manager.cancel(pendingIntent);
+ manager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ UploaderService.RUN_INTERVAL, UploaderService.RUN_INTERVAL, pendingIntent);
}
private void cleanupLoggingDir(final File dir, final long time) {
@@ -192,10 +222,15 @@
}
}
- public void mainKeyboardView_onAttachedToWindow() {
+ public void mainKeyboardView_onAttachedToWindow(final MainKeyboardView mainKeyboardView) {
+ mMainKeyboardView = mainKeyboardView;
maybeShowSplashScreen();
}
+ public void mainKeyboardView_onDetachedFromWindow() {
+ mMainKeyboardView = null;
+ }
+
private boolean hasSeenSplash() {
return mPrefs.getBoolean(PREF_RESEARCH_HAS_SEEN_SPLASH, false);
}
@@ -209,98 +244,61 @@
if (mSplashDialog != null && mSplashDialog.isShowing()) {
return;
}
- final IBinder windowToken = mKeyboardSwitcher.getMainKeyboardView().getWindowToken();
+ final IBinder windowToken = mMainKeyboardView != null
+ ? mMainKeyboardView.getWindowToken() : null;
if (windowToken == null) {
return;
}
- mSplashDialog = new Dialog(mInputMethodService, android.R.style.Theme_Holo_Dialog);
- mSplashDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
- mSplashDialog.setContentView(R.layout.research_splash);
- mSplashDialog.setCancelable(true);
+ final AlertDialog.Builder builder = new AlertDialog.Builder(mInputMethodService)
+ .setTitle(R.string.research_splash_title)
+ .setMessage(R.string.research_splash_content)
+ .setPositiveButton(android.R.string.yes,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ onUserLoggingConsent();
+ mSplashDialog.dismiss();
+ }
+ })
+ .setNegativeButton(android.R.string.no,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final String packageName = mInputMethodService.getPackageName();
+ final Uri packageUri = Uri.parse("package:" + packageName);
+ final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE,
+ packageUri);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mInputMethodService.startActivity(intent);
+ }
+ })
+ .setCancelable(true)
+ .setOnCancelListener(
+ new OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ mInputMethodService.requestHideSelf(0);
+ }
+ });
+ mSplashDialog = builder.create();
final Window w = mSplashDialog.getWindow();
final WindowManager.LayoutParams lp = w.getAttributes();
lp.token = windowToken;
lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
w.setAttributes(lp);
w.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
- mSplashDialog.setOnCancelListener(new OnCancelListener() {
- @Override
- public void onCancel(DialogInterface dialog) {
- mInputMethodService.requestHideSelf(0);
- }
- });
- final Button doNotLogButton = (Button) mSplashDialog.findViewById(
- R.id.research_do_not_log_button);
- doNotLogButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- onUserLoggingElection(false);
- mSplashDialog.dismiss();
- }
- });
- final Button doLogButton = (Button) mSplashDialog.findViewById(R.id.research_do_log_button);
- doLogButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- onUserLoggingElection(true);
- mSplashDialog.dismiss();
- }
- });
mSplashDialog.show();
}
- public void onUserLoggingElection(final boolean enableLogging) {
- setLoggingAllowed(enableLogging);
+ public void onUserLoggingConsent() {
+ setLoggingAllowed(true);
if (mPrefs == null) {
return;
}
final Editor e = mPrefs.edit();
e.putBoolean(PREF_RESEARCH_HAS_SEEN_SPLASH, true);
e.apply();
- }
-
- private File createLogFile(File filesDir) {
- final StringBuilder sb = new StringBuilder();
- sb.append(FILENAME_PREFIX).append('-');
- sb.append(mUUIDString).append('-');
- sb.append(TIMESTAMP_DATEFORMAT.format(new Date()));
- sb.append(FILENAME_SUFFIX);
- return new File(filesDir, sb.toString());
- }
-
- private void start() {
- maybeShowSplashScreen();
- updateSuspendedState();
- requestIndicatorRedraw();
- if (!isAllowedToLog()) {
- // Log.w(TAG, "not in usability mode; not logging");
- return;
- }
- if (mFilesDir == null || !mFilesDir.exists()) {
- Log.w(TAG, "IME storage directory does not exist. Cannot start logging.");
- return;
- }
- try {
- if (mMainResearchLog == null || !mMainResearchLog.isAlive()) {
- mMainResearchLog = new ResearchLog(createLogFile(mFilesDir));
- }
- mMainResearchLog.start();
- if (mIntentionalResearchLog == null || !mIntentionalResearchLog.isAlive()) {
- mIntentionalResearchLog = new ResearchLog(createLogFile(mFilesDir));
- }
- mIntentionalResearchLog.start();
- } catch (IOException e) {
- Log.w(TAG, "Could not start ResearchLogger.");
- }
- }
-
- /* package */ void stop() {
- if (mMainResearchLog != null) {
- mMainResearchLog.stop();
- }
- if (mIntentionalResearchLog != null) {
- mIntentionalResearchLog.stop();
- }
+ restart();
}
private void setLoggingAllowed(boolean enableLogging) {
@@ -313,40 +311,104 @@
sIsLogging = enableLogging;
}
- public boolean abort() {
- boolean didAbortMainLog = false;
- if (mMainResearchLog != null) {
- mMainResearchLog.abort();
- try {
- mMainResearchLog.waitUntilStopped(ABORT_TIMEOUT_IN_MS);
- } catch (InterruptedException e) {
- // interrupted early. carry on.
- }
- if (mMainResearchLog.isAbortSuccessful()) {
- didAbortMainLog = true;
- }
- mMainResearchLog = null;
- }
- boolean didAbortIntentionalLog = false;
- if (mIntentionalResearchLog != null) {
- mIntentionalResearchLog.abort();
- try {
- mIntentionalResearchLog.waitUntilStopped(ABORT_TIMEOUT_IN_MS);
- } catch (InterruptedException e) {
- // interrupted early. carry on.
- }
- if (mIntentionalResearchLog.isAbortSuccessful()) {
- didAbortIntentionalLog = true;
- }
- mIntentionalResearchLog = null;
- }
- return didAbortMainLog && didAbortIntentionalLog;
+ private File createLogFile(File filesDir) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(FILENAME_PREFIX).append('-');
+ sb.append(mUUIDString).append('-');
+ sb.append(TIMESTAMP_DATEFORMAT.format(new Date()));
+ sb.append(FILENAME_SUFFIX);
+ return new File(filesDir, sb.toString());
}
- /* package */ void flush() {
- if (mMainResearchLog != null) {
- mMainResearchLog.flush();
+ private void checkForEmptyEditor() {
+ if (mInputMethodService == null) {
+ return;
}
+ final InputConnection ic = mInputMethodService.getCurrentInputConnection();
+ if (ic == null) {
+ return;
+ }
+ final CharSequence textBefore = ic.getTextBeforeCursor(1, 0);
+ if (!TextUtils.isEmpty(textBefore)) {
+ mStatistics.setIsEmptyUponStarting(false);
+ return;
+ }
+ final CharSequence textAfter = ic.getTextAfterCursor(1, 0);
+ if (!TextUtils.isEmpty(textAfter)) {
+ mStatistics.setIsEmptyUponStarting(false);
+ return;
+ }
+ if (textBefore != null && textAfter != null) {
+ mStatistics.setIsEmptyUponStarting(true);
+ }
+ }
+
+ private void start() {
+ maybeShowSplashScreen();
+ updateSuspendedState();
+ requestIndicatorRedraw();
+ mStatistics.reset();
+ checkForEmptyEditor();
+ if (!isAllowedToLog()) {
+ // Log.w(TAG, "not in usability mode; not logging");
+ return;
+ }
+ if (mFilesDir == null || !mFilesDir.exists()) {
+ Log.w(TAG, "IME storage directory does not exist. Cannot start logging.");
+ return;
+ }
+ if (mMainLogBuffer == null) {
+ mMainResearchLog = new ResearchLog(createLogFile(mFilesDir));
+ mMainLogBuffer = new MainLogBuffer(mMainResearchLog);
+ mMainLogBuffer.setSuggest(mSuggest);
+ }
+ if (mFeedbackLogBuffer == null) {
+ mFeedbackLog = new ResearchLog(createLogFile(mFilesDir));
+ // LogBuffer is one more than FEEDBACK_WORD_BUFFER_SIZE, because it must also hold
+ // the feedback LogUnit itself.
+ mFeedbackLogBuffer = new LogBuffer(FEEDBACK_WORD_BUFFER_SIZE + 1);
+ }
+ }
+
+ /* package */ void stop() {
+ logStatistics();
+ commitCurrentLogUnit();
+
+ if (mMainLogBuffer != null) {
+ publishLogBuffer(mMainLogBuffer, mMainResearchLog, false /* isIncludingPrivateData */);
+ mMainResearchLog.close();
+ mMainLogBuffer = null;
+ }
+ if (mFeedbackLogBuffer != null) {
+ mFeedbackLog.close();
+ mFeedbackLogBuffer = null;
+ }
+ }
+
+ public boolean abort() {
+ boolean didAbortMainLog = false;
+ if (mMainLogBuffer != null) {
+ mMainLogBuffer.clear();
+ try {
+ didAbortMainLog = mMainResearchLog.blockingAbort();
+ } catch (InterruptedException e) {
+ // Don't know whether this succeeded or not. We assume not; this is reported
+ // to the caller.
+ }
+ mMainLogBuffer = null;
+ }
+ boolean didAbortFeedbackLog = false;
+ if (mFeedbackLogBuffer != null) {
+ mFeedbackLogBuffer.clear();
+ try {
+ didAbortFeedbackLog = mFeedbackLog.blockingAbort();
+ } catch (InterruptedException e) {
+ // Don't know whether this succeeded or not. We assume not; this is reported
+ // to the caller.
+ }
+ mFeedbackLogBuffer = null;
+ }
+ return didAbortMainLog && didAbortFeedbackLog;
}
private void restart() {
@@ -387,14 +449,22 @@
abort();
}
requestIndicatorRedraw();
+ mPrefs = prefs;
+ prefsChanged(prefs);
}
- public void presentResearchDialog(final LatinIME latinIME) {
+ public void onResearchKeySelected(final LatinIME latinIME) {
if (mInFeedbackDialog) {
Toast.makeText(latinIME, R.string.research_please_exit_feedback_form,
Toast.LENGTH_LONG).show();
return;
}
+ presentFeedbackDialog(latinIME);
+ }
+
+ // TODO: currently unreachable. Remove after being sure no menu is needed.
+ /*
+ public void presentResearchDialog(final LatinIME latinIME) {
final CharSequence title = latinIME.getString(R.string.english_ime_research_log);
final boolean showEnable = mIsLoggingSuspended || !sIsLogging;
final CharSequence[] items = new CharSequence[] {
@@ -411,28 +481,7 @@
presentFeedbackDialog(latinIME);
break;
case 1:
- if (showEnable) {
- if (!sIsLogging) {
- setLoggingAllowed(true);
- }
- resumeLogging();
- Toast.makeText(latinIME,
- R.string.research_notify_session_logging_enabled,
- Toast.LENGTH_LONG).show();
- } else {
- Toast toast = Toast.makeText(latinIME,
- R.string.research_notify_session_log_deleting,
- Toast.LENGTH_LONG);
- toast.show();
- boolean isLogDeleted = abort();
- final long currentTime = System.currentTimeMillis();
- final long resumeTime = currentTime + 1000 * 60 *
- SUSPEND_DURATION_IN_MINUTES;
- suspendLoggingUntil(resumeTime);
- toast.cancel();
- Toast.makeText(latinIME, R.string.research_notify_logging_suspended,
- Toast.LENGTH_LONG).show();
- }
+ enableOrDisable(showEnable, latinIME);
break;
}
}
@@ -443,6 +492,7 @@
.setTitle(title);
latinIME.showOptionDialog(builder.create());
}
+ */
private boolean mInFeedbackDialog = false;
public void presentFeedbackDialog(LatinIME latinIME) {
@@ -450,79 +500,73 @@
latinIME.launchKeyboardedDialogActivity(FeedbackActivity.class);
}
- private ResearchLog mFeedbackLog;
- private List<LogUnit> mFeedbackQueue;
- private ResearchLog mSavedMainResearchLog;
- private ResearchLog mSavedIntentionalResearchLog;
- private List<LogUnit> mSavedIntentionalResearchLogQueue;
-
- private void saveLogsForFeedback() {
- mFeedbackLog = mIntentionalResearchLog;
- if (mIntentionalResearchLogQueue != null) {
- mFeedbackQueue = new ArrayList<LogUnit>(mIntentionalResearchLogQueue);
+ // TODO: currently unreachable. Remove after being sure enable/disable is
+ // not needed.
+ /*
+ public void enableOrDisable(final boolean showEnable, final LatinIME latinIME) {
+ if (showEnable) {
+ if (!sIsLogging) {
+ setLoggingAllowed(true);
+ }
+ resumeLogging();
+ Toast.makeText(latinIME,
+ R.string.research_notify_session_logging_enabled,
+ Toast.LENGTH_LONG).show();
} else {
- mFeedbackQueue = null;
+ Toast toast = Toast.makeText(latinIME,
+ R.string.research_notify_session_log_deleting,
+ Toast.LENGTH_LONG);
+ toast.show();
+ boolean isLogDeleted = abort();
+ final long currentTime = System.currentTimeMillis();
+ final long resumeTime = currentTime + 1000 * 60 *
+ SUSPEND_DURATION_IN_MINUTES;
+ suspendLoggingUntil(resumeTime);
+ toast.cancel();
+ Toast.makeText(latinIME, R.string.research_notify_logging_suspended,
+ Toast.LENGTH_LONG).show();
}
- mSavedMainResearchLog = mMainResearchLog;
- mSavedIntentionalResearchLog = mIntentionalResearchLog;
- mSavedIntentionalResearchLogQueue = mIntentionalResearchLogQueue;
+ }
+ */
- mMainResearchLog = null;
- mIntentionalResearchLog = null;
- mIntentionalResearchLogQueue = new ArrayList<LogUnit>();
+ private static final String[] EVENTKEYS_FEEDBACK = {
+ "UserTimestamp", "contents"
+ };
+ public void sendFeedback(final String feedbackContents, final boolean includeHistory) {
+ if (mFeedbackLogBuffer == null) {
+ return;
+ }
+ if (includeHistory) {
+ commitCurrentLogUnit();
+ } else {
+ mFeedbackLogBuffer.clear();
+ }
+ final LogUnit feedbackLogUnit = new LogUnit();
+ final Object[] values = {
+ feedbackContents
+ };
+ feedbackLogUnit.addLogStatement(EVENTKEYS_FEEDBACK, values,
+ false /* isPotentiallyPrivate */);
+ mFeedbackLogBuffer.shiftIn(feedbackLogUnit);
+ publishLogBuffer(mFeedbackLogBuffer, mFeedbackLog, true /* isIncludingPrivateData */);
+ mFeedbackLog.close();
+ uploadNow();
+ mFeedbackLog = new ResearchLog(createLogFile(mFilesDir));
}
- private static final int LOG_DRAIN_TIMEOUT_IN_MS = 1000 * 5;
- public void sendFeedback(final String feedbackContents, final boolean includeHistory) {
- if (includeHistory && mFeedbackLog != null) {
- try {
- LogUnit headerLogUnit = new LogUnit();
- headerLogUnit.addLogAtom(EVENTKEYS_INTENTIONAL_LOG, EVENTKEYS_NULLVALUES, false);
- mFeedbackLog.publishAllEvents(headerLogUnit);
- for (LogUnit logUnit : mFeedbackQueue) {
- mFeedbackLog.publishAllEvents(logUnit);
- }
- userFeedback(mFeedbackLog, feedbackContents);
- mFeedbackLog.stop();
- try {
- mFeedbackLog.waitUntilStopped(LOG_DRAIN_TIMEOUT_IN_MS);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- mIntentionalResearchLog = new ResearchLog(createLogFile(mFilesDir));
- mIntentionalResearchLog.start();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- mIntentionalResearchLogQueue.clear();
- }
- mResearchLogUploader.uploadNow(null);
- } else {
- // create a separate ResearchLog just for feedback
- final ResearchLog feedbackLog = new ResearchLog(createLogFile(mFilesDir));
- try {
- feedbackLog.start();
- userFeedback(feedbackLog, feedbackContents);
- feedbackLog.stop();
- feedbackLog.waitUntilStopped(LOG_DRAIN_TIMEOUT_IN_MS);
- mResearchLogUploader.uploadNow(null);
- } catch (IOException e) {
- e.printStackTrace();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
+ public void uploadNow() {
+ mInputMethodService.startService(mUploadIntent);
}
public void onLeavingSendFeedbackDialog() {
mInFeedbackDialog = false;
- mMainResearchLog = mSavedMainResearchLog;
- mIntentionalResearchLog = mSavedIntentionalResearchLog;
- mIntentionalResearchLogQueue = mSavedIntentionalResearchLogQueue;
}
public void initSuggest(Suggest suggest) {
mSuggest = suggest;
+ if (mMainLogBuffer != null) {
+ mMainLogBuffer.setSuggest(mSuggest);
+ }
}
private void setIsPasswordView(boolean isPasswordView) {
@@ -530,21 +574,17 @@
}
private boolean isAllowedToLog() {
- return !mIsPasswordView && !mIsLoggingSuspended && sIsLogging;
+ return !mIsPasswordView && !mIsLoggingSuspended && sIsLogging && !mInFeedbackDialog;
}
public void requestIndicatorRedraw() {
if (!IS_SHOWING_INDICATOR) {
return;
}
- if (mKeyboardSwitcher == null) {
+ if (mMainKeyboardView == null) {
return;
}
- final KeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
- if (mainKeyboardView == null) {
- return;
- }
- mainKeyboardView.invalidateAllKeys();
+ mMainKeyboardView.invalidateAllKeys();
}
@@ -577,13 +617,8 @@
}
}
- private static final String CURRENT_TIME_KEY = "_ct";
- private static final String UPTIME_KEY = "_ut";
- private static final String EVENT_TYPE_KEY = "_ty";
private static final Object[] EVENTKEYS_NULLVALUES = {};
- private LogUnit mCurrentLogUnit = new LogUnit();
-
/**
* Buffer a research log event, flagging it as privacy-sensitive.
*
@@ -599,10 +634,14 @@
final Object[] values) {
assert values.length + 1 == keys.length;
if (isAllowedToLog()) {
- mCurrentLogUnit.addLogAtom(keys, values, true);
+ mCurrentLogUnit.addLogStatement(keys, values, true /* isPotentiallyPrivate */);
}
}
+ private void setCurrentLogUnitContainsDigitFlag() {
+ mCurrentLogUnit.setContainsDigit();
+ }
+
/**
* Buffer a research log event, flaggint it as not privacy-sensitive.
*
@@ -618,139 +657,54 @@
private synchronized void enqueueEvent(final String[] keys, final Object[] values) {
assert values.length + 1 == keys.length;
if (isAllowedToLog()) {
- mCurrentLogUnit.addLogAtom(keys, values, false);
+ mCurrentLogUnit.addLogStatement(keys, values, false /* isPotentiallyPrivate */);
}
}
- // Used to track how often words are logged. Too-frequent logging can leak
- // semantics, disclosing private data.
- /* package for test */ static class LoggingFrequencyState {
- private static final int DEFAULT_WORD_LOG_FREQUENCY = 10;
- private int mWordsRemainingToSkip;
- private final int mFrequency;
-
- /**
- * Tracks how often words may be uploaded.
- *
- * @param frequency 1=Every word, 2=Every other word, etc.
- */
- public LoggingFrequencyState(int frequency) {
- mFrequency = frequency;
- mWordsRemainingToSkip = mFrequency;
- }
-
- public void onWordLogged() {
- mWordsRemainingToSkip = mFrequency;
- }
-
- public void onWordNotLogged() {
- if (mWordsRemainingToSkip > 1) {
- mWordsRemainingToSkip--;
- }
- }
-
- public boolean isSafeToLog() {
- return mWordsRemainingToSkip <= 1;
- }
- }
-
- /* package for test */ LoggingFrequencyState mLoggingFrequencyState =
- new LoggingFrequencyState(LoggingFrequencyState.DEFAULT_WORD_LOG_FREQUENCY);
-
- /* package for test */ boolean isPrivacyThreat(String word) {
- // Current checks:
- // - Word not in dictionary
- // - Word contains numbers
- // - Privacy-safe word not logged recently
- if (TextUtils.isEmpty(word)) {
- return false;
- }
- if (!mLoggingFrequencyState.isSafeToLog()) {
- return true;
- }
- final int length = word.length();
- boolean hasLetter = false;
- for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
- final int codePoint = Character.codePointAt(word, i);
- if (Character.isDigit(codePoint)) {
- return true;
- }
- if (Character.isLetter(codePoint)) {
- hasLetter = true;
- break; // Word may contain digits, but will only be allowed if in the dictionary.
- }
- }
- if (hasLetter) {
- if (mDictionary == null && mSuggest != null && mSuggest.hasMainDictionary()) {
- mDictionary = mSuggest.getMainDictionary();
- }
- if (mDictionary == null) {
- // Can't access dictionary. Assume privacy threat.
- return true;
- }
- return !(mDictionary.isValidWord(word));
- }
- // No letters, no numbers. Punctuation, space, or something else.
- return false;
- }
-
- private void onWordComplete(String word) {
- if (isPrivacyThreat(word)) {
- publishLogUnit(mCurrentLogUnit, true);
- mLoggingFrequencyState.onWordNotLogged();
- } else {
- publishLogUnit(mCurrentLogUnit, false);
- mLoggingFrequencyState.onWordLogged();
- }
- mCurrentLogUnit = new LogUnit();
- }
-
- private void publishLogUnit(LogUnit logUnit, boolean isPrivacySensitive) {
- if (!isAllowedToLog()) {
- return;
- }
- if (mMainResearchLog == null) {
- return;
- }
- if (isPrivacySensitive) {
- mMainResearchLog.publishPublicEvents(logUnit);
- } else {
- mMainResearchLog.publishAllEvents(logUnit);
- }
- mIntentionalResearchLogQueue.add(logUnit);
- }
-
- /* package */ void publishCurrentLogUnit(ResearchLog researchLog, boolean isPrivacySensitive) {
- publishLogUnit(mCurrentLogUnit, isPrivacySensitive);
- }
-
- static class LogUnit {
- private final List<String[]> mKeysList = new ArrayList<String[]>();
- private final List<Object[]> mValuesList = new ArrayList<Object[]>();
- private final List<Boolean> mIsPotentiallyPrivate = new ArrayList<Boolean>();
-
- private void addLogAtom(final String[] keys, final Object[] values,
- final Boolean isPotentiallyPrivate) {
- mKeysList.add(keys);
- mValuesList.add(values);
- mIsPotentiallyPrivate.add(isPotentiallyPrivate);
- }
-
- public void publishPublicEventsTo(ResearchLog researchLog) {
- final int size = mKeysList.size();
- for (int i = 0; i < size; i++) {
- if (!mIsPotentiallyPrivate.get(i)) {
- researchLog.outputEvent(mKeysList.get(i), mValuesList.get(i));
+ /* package for test */ void commitCurrentLogUnit() {
+ if (!mCurrentLogUnit.isEmpty()) {
+ if (mMainLogBuffer != null) {
+ mMainLogBuffer.shiftIn(mCurrentLogUnit);
+ if (mMainLogBuffer.isSafeToLog() && mMainResearchLog != null) {
+ publishLogBuffer(mMainLogBuffer, mMainResearchLog,
+ true /* isIncludingPrivateData */);
+ mMainLogBuffer.resetWordCounter();
}
}
+ if (mFeedbackLogBuffer != null) {
+ mFeedbackLogBuffer.shiftIn(mCurrentLogUnit);
+ }
+ mCurrentLogUnit = new LogUnit();
+ Log.d(TAG, "commitCurrentLogUnit");
}
+ }
- public void publishAllEventsTo(ResearchLog researchLog) {
- final int size = mKeysList.size();
- for (int i = 0; i < size; i++) {
- researchLog.outputEvent(mKeysList.get(i), mValuesList.get(i));
+ /* package for test */ void publishLogBuffer(final LogBuffer logBuffer,
+ final ResearchLog researchLog, final boolean isIncludingPrivateData) {
+ LogUnit logUnit;
+ while ((logUnit = logBuffer.shiftOut()) != null) {
+ researchLog.publish(logUnit, isIncludingPrivateData);
+ }
+ }
+
+ private boolean hasOnlyLetters(final String word) {
+ final int length = word.length();
+ for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
+ final int codePoint = word.codePointAt(i);
+ if (!Character.isLetter(codePoint)) {
+ return false;
}
}
+ return true;
+ }
+
+ private void onWordComplete(final String word) {
+ Log.d(TAG, "onWordComplete: " + word);
+ if (word != null && word.length() > 0 && hasOnlyLetters(word)) {
+ mCurrentLogUnit.setWord(word);
+ mStatistics.recordWordEntered();
+ }
+ commitCurrentLogUnit();
}
private static int scrubDigitFromCodePoint(int codePoint) {
@@ -803,12 +757,6 @@
return WORD_REPLACEMENT_STRING;
}
- // Special methods related to startup, shutdown, logging itself
-
- private static final String[] EVENTKEYS_INTENTIONAL_LOG = {
- "IntentionalLog"
- };
-
private static final String[] EVENTKEYS_LATINIME_ONSTARTINPUTVIEWINTERNAL = {
"LatinIMEOnStartInputViewInternal", "uuid", "packageName", "inputType", "imeOptions",
"fieldId", "display", "model", "prefs", "versionCode", "versionName", "outputFormatVersion"
@@ -816,9 +764,6 @@
public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo,
final SharedPreferences prefs) {
final ResearchLogger researchLogger = getInstance();
- if (researchLogger.mInFeedbackDialog) {
- researchLogger.saveLogsForFeedback();
- }
researchLogger.start();
if (editorInfo != null) {
final Context context = researchLogger.mInputMethodService;
@@ -846,32 +791,19 @@
stop();
}
- private static final String[] EVENTKEYS_LATINIME_COMMITTEXT = {
- "LatinIMECommitText", "typedWord"
- };
-
- public static void latinIME_commitText(final CharSequence typedWord) {
- final String scrubbedWord = scrubDigitsFromString(typedWord.toString());
- final Object[] values = {
- scrubbedWord
- };
- final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_COMMITTEXT, values);
- researchLogger.onWordComplete(scrubbedWord);
- }
-
private static final String[] EVENTKEYS_USER_FEEDBACK = {
"UserFeedback", "FeedbackContents"
};
- private void userFeedback(ResearchLog researchLog, String feedbackContents) {
- // this method is special; it directs the feedbackContents to a particular researchLog
- final LogUnit logUnit = new LogUnit();
+ private static final String[] EVENTKEYS_PREFS_CHANGED = {
+ "PrefsChanged", "prefs"
+ };
+ public static void prefsChanged(final SharedPreferences prefs) {
+ final ResearchLogger researchLogger = getInstance();
final Object[] values = {
- feedbackContents
+ prefs
};
- logUnit.addLogAtom(EVENTKEYS_USER_FEEDBACK, values, false);
- researchLog.publishAllEvents(logUnit);
+ researchLogger.enqueueEvent(EVENTKEYS_PREFS_CHANGED, values);
}
// Regular logging methods
@@ -908,51 +840,16 @@
"LatinIMEOnCodeInput", "code", "x", "y"
};
public static void latinIME_onCodeInput(final int code, final int x, final int y) {
+ final long time = SystemClock.uptimeMillis();
+ final ResearchLogger researchLogger = getInstance();
final Object[] values = {
Keyboard.printableCode(scrubDigitFromCodePoint(code)), x, y
};
- getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONCODEINPUT, values);
- }
-
- private static final String[] EVENTKEYS_CORRECTION = {
- "LogCorrection", "subgroup", "before", "after", "position"
- };
- public static void logCorrection(final String subgroup, final String before, final String after,
- final int position) {
- final Object[] values = {
- subgroup, scrubDigitsFromString(before), scrubDigitsFromString(after), position
- };
- getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_CORRECTION, values);
- }
-
- private static final String[] EVENTKEYS_LATINIME_COMMITCURRENTAUTOCORRECTION = {
- "LatinIMECommitCurrentAutoCorrection", "typedWord", "autoCorrection"
- };
- public static void latinIME_commitCurrentAutoCorrection(final String typedWord,
- final String autoCorrection) {
- final Object[] values = {
- scrubDigitsFromString(typedWord), scrubDigitsFromString(autoCorrection)
- };
- final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueuePotentiallyPrivateEvent(
- EVENTKEYS_LATINIME_COMMITCURRENTAUTOCORRECTION, values);
- }
-
- private static final String[] EVENTKEYS_LATINIME_DELETESURROUNDINGTEXT = {
- "LatinIMEDeleteSurroundingText", "length"
- };
- public static void latinIME_deleteSurroundingText(final int length) {
- final Object[] values = {
- length
- };
- getInstance().enqueueEvent(EVENTKEYS_LATINIME_DELETESURROUNDINGTEXT, values);
- }
-
- private static final String[] EVENTKEYS_LATINIME_DOUBLESPACEAUTOPERIOD = {
- "LatinIMEDoubleSpaceAutoPeriod"
- };
- public static void latinIME_doubleSpaceAutoPeriod() {
- getInstance().enqueueEvent(EVENTKEYS_LATINIME_DOUBLESPACEAUTOPERIOD, EVENTKEYS_NULLVALUES);
+ researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONCODEINPUT, values);
+ if (Character.isDigit(code)) {
+ researchLogger.setCurrentLogUnitContainsDigitFlag();
+ }
+ researchLogger.mStatistics.recordChar(code, time);
}
private static final String[] EVENTKEYS_LATINIME_ONDISPLAYCOMPLETIONS = {
@@ -979,6 +876,10 @@
public static void latinIME_onWindowHidden(final int savedSelectionStart,
final int savedSelectionEnd, final InputConnection ic) {
if (ic != null) {
+ // Capture the TextView contents. This will trigger onUpdateSelection(), so we
+ // set sLatinIMEExpectingUpdateSelection so that when onUpdateSelection() is called,
+ // it can tell that it was generated by the logging code, and not by the user, and
+ // therefore keep user-visible state as is.
ic.beginBatchEdit();
ic.performContextMenuAction(android.R.id.selectAll);
CharSequence charSequence = ic.getSelectedText(0);
@@ -1013,9 +914,7 @@
}
final ResearchLogger researchLogger = getInstance();
researchLogger.enqueueEvent(EVENTKEYS_LATINIME_ONWINDOWHIDDEN, values);
- // Play it safe. Remove privacy-sensitive events.
- researchLogger.publishLogUnit(researchLogger.mCurrentLogUnit, true);
- researchLogger.mCurrentLogUnit = new LogUnit();
+ researchLogger.commitCurrentLogUnit();
getInstance().stop();
}
}
@@ -1048,37 +947,15 @@
researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONUPDATESELECTION, values);
}
- private static final String[] EVENTKEYS_LATINIME_PERFORMEDITORACTION = {
- "LatinIMEPerformEditorAction", "imeActionNext"
- };
- public static void latinIME_performEditorAction(final int imeActionNext) {
- final Object[] values = {
- imeActionNext
- };
- getInstance().enqueueEvent(EVENTKEYS_LATINIME_PERFORMEDITORACTION, values);
- }
-
- private static final String[] EVENTKEYS_LATINIME_PICKAPPLICATIONSPECIFIEDCOMPLETION = {
- "LatinIMEPickApplicationSpecifiedCompletion", "index", "text", "x", "y"
- };
- public static void latinIME_pickApplicationSpecifiedCompletion(final int index,
- final CharSequence cs, int x, int y) {
- final Object[] values = {
- index, cs, x, y
- };
- final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueuePotentiallyPrivateEvent(
- EVENTKEYS_LATINIME_PICKAPPLICATIONSPECIFIEDCOMPLETION, values);
- }
-
private static final String[] EVENTKEYS_LATINIME_PICKSUGGESTIONMANUALLY = {
"LatinIMEPickSuggestionManually", "replacedWord", "index", "suggestion", "x", "y"
};
public static void latinIME_pickSuggestionManually(final String replacedWord,
- final int index, CharSequence suggestion, int x, int y) {
+ final int index, CharSequence suggestion) {
final Object[] values = {
- scrubDigitsFromString(replacedWord), index, suggestion == null ? null :
- scrubDigitsFromString(suggestion.toString()), x, y
+ scrubDigitsFromString(replacedWord), index,
+ (suggestion == null ? null : scrubDigitsFromString(suggestion.toString())),
+ Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE
};
final ResearchLogger researchLogger = getInstance();
researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_PICKSUGGESTIONMANUALLY,
@@ -1089,28 +966,14 @@
"LatinIMEPunctuationSuggestion", "index", "suggestion", "x", "y"
};
public static void latinIME_punctuationSuggestion(final int index,
- final CharSequence suggestion, int x, int y) {
+ final CharSequence suggestion) {
final Object[] values = {
- index, suggestion, x, y
+ index, suggestion,
+ Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE
};
getInstance().enqueueEvent(EVENTKEYS_LATINIME_PUNCTUATIONSUGGESTION, values);
}
- private static final String[] EVENTKEYS_LATINIME_REVERTDOUBLESPACEWHILEINBATCHEDIT = {
- "LatinIMERevertDoubleSpaceWhileInBatchEdit"
- };
- public static void latinIME_revertDoubleSpaceWhileInBatchEdit() {
- getInstance().enqueueEvent(EVENTKEYS_LATINIME_REVERTDOUBLESPACEWHILEINBATCHEDIT,
- EVENTKEYS_NULLVALUES);
- }
-
- private static final String[] EVENTKEYS_LATINIME_REVERTSWAPPUNCTUATION = {
- "LatinIMERevertSwapPunctuation"
- };
- public static void latinIME_revertSwapPunctuation() {
- getInstance().enqueueEvent(EVENTKEYS_LATINIME_REVERTSWAPPUNCTUATION, EVENTKEYS_NULLVALUES);
- }
-
private static final String[] EVENTKEYS_LATINIME_SENDKEYCODEPOINT = {
"LatinIMESendKeyCodePoint", "code"
};
@@ -1118,15 +981,18 @@
final Object[] values = {
Keyboard.printableCode(scrubDigitFromCodePoint(code))
};
- getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_SENDKEYCODEPOINT, values);
+ final ResearchLogger researchLogger = getInstance();
+ researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_SENDKEYCODEPOINT, values);
+ if (Character.isDigit(code)) {
+ researchLogger.setCurrentLogUnitContainsDigitFlag();
+ }
}
- private static final String[] EVENTKEYS_LATINIME_SWAPSWAPPERANDSPACEWHILEINBATCHEDIT = {
- "LatinIMESwapSwapperAndSpaceWhileInBatchEdit"
+ private static final String[] EVENTKEYS_LATINIME_SWAPSWAPPERANDSPACE = {
+ "LatinIMESwapSwapperAndSpace"
};
- public static void latinIME_swapSwapperAndSpaceWhileInBatchEdit() {
- getInstance().enqueueEvent(EVENTKEYS_LATINIME_SWAPSWAPPERANDSPACEWHILEINBATCHEDIT,
- EVENTKEYS_NULLVALUES);
+ public static void latinIME_swapSwapperAndSpace() {
+ getInstance().enqueueEvent(EVENTKEYS_LATINIME_SWAPSWAPPERANDSPACE, EVENTKEYS_NULLVALUES);
}
private static final String[] EVENTKEYS_MAINKEYBOARDVIEW_ONLONGPRESS = {
@@ -1197,7 +1063,7 @@
final int y, final boolean ignoreModifierKey, final boolean altersCode,
final int code) {
if (key != null) {
- CharSequence outputText = key.mOutputText;
+ String outputText = key.getOutputText();
final Object[] values = {
Keyboard.printableCode(scrubDigitFromCodePoint(code)), outputText == null ? null
: scrubDigitsFromString(outputText.toString()),
@@ -1245,6 +1111,128 @@
getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_POINTERTRACKER_ONMOVEEVENT, values);
}
+ private static final String[] EVENTKEYS_RICHINPUTCONNECTION_COMMITCOMPLETION = {
+ "RichInputConnectionCommitCompletion", "completionInfo"
+ };
+ public static void richInputConnection_commitCompletion(final CompletionInfo completionInfo) {
+ final Object[] values = {
+ completionInfo
+ };
+ final ResearchLogger researchLogger = getInstance();
+ researchLogger.enqueuePotentiallyPrivateEvent(
+ EVENTKEYS_RICHINPUTCONNECTION_COMMITCOMPLETION, values);
+ }
+
+ // Disabled for privacy-protection reasons. Because this event comes after
+ // richInputConnection_commitText, which is the event used to separate LogUnits, the
+ // data in this event can be associated with the next LogUnit, revealing information
+ // about the current word even if it was supposed to be suppressed. The occurrance of
+ // autocorrection can be determined by examining the difference between the text strings in
+ // the last call to richInputConnection_setComposingText before
+ // richInputConnection_commitText, so it's not a data loss.
+ // TODO: Figure out how to log this event without loss of privacy.
+ /*
+ private static final String[] EVENTKEYS_RICHINPUTCONNECTION_COMMITCORRECTION = {
+ "RichInputConnectionCommitCorrection", "typedWord", "autoCorrection"
+ };
+ */
+ public static void richInputConnection_commitCorrection(CorrectionInfo correctionInfo) {
+ /*
+ final String typedWord = correctionInfo.getOldText().toString();
+ final String autoCorrection = correctionInfo.getNewText().toString();
+ final Object[] values = {
+ scrubDigitsFromString(typedWord), scrubDigitsFromString(autoCorrection)
+ };
+ final ResearchLogger researchLogger = getInstance();
+ researchLogger.enqueuePotentiallyPrivateEvent(
+ EVENTKEYS_RICHINPUTCONNECTION_COMMITCORRECTION, values);
+ */
+ }
+
+ private static final String[] EVENTKEYS_RICHINPUTCONNECTION_COMMITTEXT = {
+ "RichInputConnectionCommitText", "typedWord", "newCursorPosition"
+ };
+ public static void richInputConnection_commitText(final CharSequence typedWord,
+ final int newCursorPosition) {
+ final String scrubbedWord = scrubDigitsFromString(typedWord.toString());
+ final Object[] values = {
+ scrubbedWord, newCursorPosition
+ };
+ final ResearchLogger researchLogger = getInstance();
+ researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_RICHINPUTCONNECTION_COMMITTEXT,
+ values);
+ researchLogger.onWordComplete(scrubbedWord);
+ }
+
+ private static final String[] EVENTKEYS_RICHINPUTCONNECTION_DELETESURROUNDINGTEXT = {
+ "RichInputConnectionDeleteSurroundingText", "beforeLength", "afterLength"
+ };
+ public static void richInputConnection_deleteSurroundingText(final int beforeLength,
+ final int afterLength) {
+ final Object[] values = {
+ beforeLength, afterLength
+ };
+ getInstance().enqueuePotentiallyPrivateEvent(
+ EVENTKEYS_RICHINPUTCONNECTION_DELETESURROUNDINGTEXT, values);
+ }
+
+ private static final String[] EVENTKEYS_RICHINPUTCONNECTION_FINISHCOMPOSINGTEXT = {
+ "RichInputConnectionFinishComposingText"
+ };
+ public static void richInputConnection_finishComposingText() {
+ getInstance().enqueueEvent(EVENTKEYS_RICHINPUTCONNECTION_FINISHCOMPOSINGTEXT,
+ EVENTKEYS_NULLVALUES);
+ }
+
+ private static final String[] EVENTKEYS_RICHINPUTCONNECTION_PERFORMEDITORACTION = {
+ "RichInputConnectionPerformEditorAction", "imeActionNext"
+ };
+ public static void richInputConnection_performEditorAction(final int imeActionNext) {
+ final Object[] values = {
+ imeActionNext
+ };
+ getInstance().enqueueEvent(EVENTKEYS_RICHINPUTCONNECTION_PERFORMEDITORACTION, values);
+ }
+
+ private static final String[] EVENTKEYS_RICHINPUTCONNECTION_SENDKEYEVENT = {
+ "RichInputConnectionSendKeyEvent", "eventTime", "action", "code"
+ };
+ public static void richInputConnection_sendKeyEvent(final KeyEvent keyEvent) {
+ final Object[] values = {
+ keyEvent.getEventTime(),
+ keyEvent.getAction(),
+ keyEvent.getKeyCode()
+ };
+ getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_RICHINPUTCONNECTION_SENDKEYEVENT,
+ values);
+ }
+
+ private static final String[] EVENTKEYS_RICHINPUTCONNECTION_SETCOMPOSINGTEXT = {
+ "RichInputConnectionSetComposingText", "text", "newCursorPosition"
+ };
+ public static void richInputConnection_setComposingText(final CharSequence text,
+ final int newCursorPosition) {
+ if (text == null) {
+ throw new RuntimeException("setComposingText is null");
+ }
+ final Object[] values = {
+ text, newCursorPosition
+ };
+ getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_RICHINPUTCONNECTION_SETCOMPOSINGTEXT,
+ values);
+ }
+
+ private static final String[] EVENTKEYS_RICHINPUTCONNECTION_SETSELECTION = {
+ "RichInputConnectionSetSelection", "from", "to"
+ };
+ public static void richInputConnection_setSelection(final int from, final int to) {
+ final Object[] values = {
+ from, to
+ };
+ getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_RICHINPUTCONNECTION_SETSELECTION,
+ values);
+ }
+
private static final String[] EVENTKEYS_SUDDENJUMPINGTOUCHEVENTHANDLER_ONTOUCHEVENT = {
"SuddenJumpingTouchEventHandlerOnTouchEvent", "motionEvent"
};
@@ -1277,4 +1265,24 @@
public void userTimestamp() {
getInstance().enqueueEvent(EVENTKEYS_USER_TIMESTAMP, EVENTKEYS_NULLVALUES);
}
+
+ private static final String[] EVENTKEYS_STATISTICS = {
+ "Statistics", "charCount", "letterCount", "numberCount", "spaceCount", "deleteOpsCount",
+ "wordCount", "isEmptyUponStarting", "isEmptinessStateKnown", "averageTimeBetweenKeys",
+ "averageTimeBeforeDelete", "averageTimeDuringRepeatedDelete", "averageTimeAfterDelete"
+ };
+ private static void logStatistics() {
+ final ResearchLogger researchLogger = getInstance();
+ final Statistics statistics = researchLogger.mStatistics;
+ final Object[] values = {
+ statistics.mCharCount, statistics.mLetterCount, statistics.mNumberCount,
+ statistics.mSpaceCount, statistics.mDeleteKeyCount,
+ statistics.mWordCount, statistics.mIsEmptyUponStarting,
+ statistics.mIsEmptinessStateKnown, statistics.mKeyCounter.getAverageTime(),
+ statistics.mBeforeDeleteKeyCounter.getAverageTime(),
+ statistics.mDuringRepeatedDeleteKeysCounter.getAverageTime(),
+ statistics.mAfterDeleteKeyCounter.getAverageTime()
+ };
+ researchLogger.enqueueEvent(EVENTKEYS_STATISTICS, values);
+ }
}
diff --git a/java/src/com/android/inputmethod/research/Statistics.java b/java/src/com/android/inputmethod/research/Statistics.java
new file mode 100644
index 0000000..eab465a
--- /dev/null
+++ b/java/src/com/android/inputmethod/research/Statistics.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.research;
+
+import com.android.inputmethod.keyboard.Keyboard;
+
+public class Statistics {
+ // Number of characters entered during a typing session
+ int mCharCount;
+ // Number of letter characters entered during a typing session
+ int mLetterCount;
+ // Number of number characters entered
+ int mNumberCount;
+ // Number of space characters entered
+ int mSpaceCount;
+ // Number of delete operations entered (taps on the backspace key)
+ int mDeleteKeyCount;
+ // Number of words entered during a session.
+ int mWordCount;
+ // Whether the text field was empty upon editing
+ boolean mIsEmptyUponStarting;
+ boolean mIsEmptinessStateKnown;
+
+ // Timers to count average time to enter a key, first press a delete key,
+ // between delete keys, and then to return typing after a delete key.
+ final AverageTimeCounter mKeyCounter = new AverageTimeCounter();
+ final AverageTimeCounter mBeforeDeleteKeyCounter = new AverageTimeCounter();
+ final AverageTimeCounter mDuringRepeatedDeleteKeysCounter = new AverageTimeCounter();
+ final AverageTimeCounter mAfterDeleteKeyCounter = new AverageTimeCounter();
+
+ static class AverageTimeCounter {
+ int mCount;
+ int mTotalTime;
+
+ public void reset() {
+ mCount = 0;
+ mTotalTime = 0;
+ }
+
+ public void add(long deltaTime) {
+ mCount++;
+ mTotalTime += deltaTime;
+ }
+
+ public int getAverageTime() {
+ if (mCount == 0) {
+ return 0;
+ }
+ return mTotalTime / mCount;
+ }
+ }
+
+ // To account for the interruptions when the user's attention is directed elsewhere, times
+ // longer than MIN_TYPING_INTERMISSION are not counted when estimating this statistic.
+ public static final int MIN_TYPING_INTERMISSION = 2 * 1000; // in milliseconds
+ public static final int MIN_DELETION_INTERMISSION = 10 * 1000; // in milliseconds
+
+ // The last time that a tap was performed
+ private long mLastTapTime;
+ // The type of the last keypress (delete key or not)
+ boolean mIsLastKeyDeleteKey;
+
+ private static final Statistics sInstance = new Statistics();
+
+ public static Statistics getInstance() {
+ return sInstance;
+ }
+
+ private Statistics() {
+ reset();
+ }
+
+ public void reset() {
+ mCharCount = 0;
+ mLetterCount = 0;
+ mNumberCount = 0;
+ mSpaceCount = 0;
+ mDeleteKeyCount = 0;
+ mWordCount = 0;
+ mIsEmptyUponStarting = true;
+ mIsEmptinessStateKnown = false;
+ mKeyCounter.reset();
+ mBeforeDeleteKeyCounter.reset();
+ mDuringRepeatedDeleteKeysCounter.reset();
+ mAfterDeleteKeyCounter.reset();
+
+ mLastTapTime = 0;
+ mIsLastKeyDeleteKey = false;
+ }
+
+ public void recordChar(int codePoint, long time) {
+ final long delta = time - mLastTapTime;
+ if (codePoint == Keyboard.CODE_DELETE) {
+ mDeleteKeyCount++;
+ if (delta < MIN_DELETION_INTERMISSION) {
+ if (mIsLastKeyDeleteKey) {
+ mDuringRepeatedDeleteKeysCounter.add(delta);
+ } else {
+ mBeforeDeleteKeyCounter.add(delta);
+ }
+ }
+ mIsLastKeyDeleteKey = true;
+ } else {
+ mCharCount++;
+ if (Character.isDigit(codePoint)) {
+ mNumberCount++;
+ }
+ if (Character.isLetter(codePoint)) {
+ mLetterCount++;
+ }
+ if (Character.isSpaceChar(codePoint)) {
+ mSpaceCount++;
+ }
+ if (mIsLastKeyDeleteKey && delta < MIN_DELETION_INTERMISSION) {
+ mAfterDeleteKeyCounter.add(delta);
+ } else if (!mIsLastKeyDeleteKey && delta < MIN_TYPING_INTERMISSION) {
+ mKeyCounter.add(delta);
+ }
+ mIsLastKeyDeleteKey = false;
+ }
+ mLastTapTime = time;
+ }
+
+ public void recordWordEntered() {
+ mWordCount++;
+ }
+
+ public void setIsEmptyUponStarting(final boolean isEmpty) {
+ mIsEmptyUponStarting = isEmpty;
+ mIsEmptinessStateKnown = true;
+ }
+}
diff --git a/java/src/com/android/inputmethod/research/UploaderService.java b/java/src/com/android/inputmethod/research/UploaderService.java
new file mode 100644
index 0000000..7a57490
--- /dev/null
+++ b/java/src/com/android/inputmethod/research/UploaderService.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.research;
+
+import android.Manifest;
+import android.app.AlarmManager;
+import android.app.IntentService;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.BatteryManager;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.inputmethod.latin.R;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+public final class UploaderService extends IntentService {
+ private static final String TAG = UploaderService.class.getSimpleName();
+ public static final long RUN_INTERVAL = AlarmManager.INTERVAL_HOUR;
+ private static final String EXTRA_UPLOAD_UNCONDITIONALLY = UploaderService.class.getName()
+ + ".extra.UPLOAD_UNCONDITIONALLY";
+ private static final int BUF_SIZE = 1024 * 8;
+ protected static final int TIMEOUT_IN_MS = 1000 * 4;
+
+ private boolean mCanUpload;
+ private File mFilesDir;
+ private URL mUrl;
+
+ public UploaderService() {
+ super("Research Uploader Service");
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ mCanUpload = false;
+ mFilesDir = null;
+ mUrl = null;
+
+ final PackageManager packageManager = getPackageManager();
+ final boolean hasPermission = packageManager.checkPermission(Manifest.permission.INTERNET,
+ getPackageName()) == PackageManager.PERMISSION_GRANTED;
+ if (!hasPermission) {
+ return;
+ }
+
+ try {
+ final String urlString = getString(R.string.research_logger_upload_url);
+ if (urlString == null || urlString.equals("")) {
+ return;
+ }
+ mFilesDir = getFilesDir();
+ mUrl = new URL(urlString);
+ mCanUpload = true;
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ if (!mCanUpload) {
+ return;
+ }
+ boolean isUploadingUnconditionally = false;
+ Bundle bundle = intent.getExtras();
+ if (bundle != null && bundle.containsKey(EXTRA_UPLOAD_UNCONDITIONALLY)) {
+ isUploadingUnconditionally = bundle.getBoolean(EXTRA_UPLOAD_UNCONDITIONALLY);
+ }
+ doUpload(isUploadingUnconditionally);
+ }
+
+ private boolean isExternallyPowered() {
+ final Intent intent = registerReceiver(null, new IntentFilter(
+ Intent.ACTION_BATTERY_CHANGED));
+ final int pluggedState = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
+ return pluggedState == BatteryManager.BATTERY_PLUGGED_AC
+ || pluggedState == BatteryManager.BATTERY_PLUGGED_USB;
+ }
+
+ private boolean hasWifiConnection() {
+ final ConnectivityManager manager =
+ (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
+ final NetworkInfo wifiInfo = manager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+ return wifiInfo.isConnected();
+ }
+
+ private void doUpload(final boolean isUploadingUnconditionally) {
+ if (!isUploadingUnconditionally && (!isExternallyPowered() || !hasWifiConnection())) {
+ return;
+ }
+ if (mFilesDir == null) {
+ return;
+ }
+ final File[] files = mFilesDir.listFiles(new FileFilter() {
+ @Override
+ public boolean accept(File pathname) {
+ return pathname.getName().startsWith(ResearchLogger.FILENAME_PREFIX)
+ && !pathname.canWrite();
+ }
+ });
+ boolean success = true;
+ if (files.length == 0) {
+ success = false;
+ }
+ for (final File file : files) {
+ if (!uploadFile(file)) {
+ success = false;
+ }
+ }
+ }
+
+ private boolean uploadFile(File file) {
+ Log.d(TAG, "attempting upload of " + file.getAbsolutePath());
+ boolean success = false;
+ final int contentLength = (int) file.length();
+ HttpURLConnection connection = null;
+ InputStream fileInputStream = null;
+ try {
+ fileInputStream = new FileInputStream(file);
+ connection = (HttpURLConnection) mUrl.openConnection();
+ connection.setRequestMethod("PUT");
+ connection.setDoOutput(true);
+ connection.setFixedLengthStreamingMode(contentLength);
+ final OutputStream os = connection.getOutputStream();
+ final byte[] buf = new byte[BUF_SIZE];
+ int numBytesRead;
+ while ((numBytesRead = fileInputStream.read(buf)) != -1) {
+ os.write(buf, 0, numBytesRead);
+ }
+ if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
+ Log.d(TAG, "upload failed: " + connection.getResponseCode());
+ InputStream netInputStream = connection.getInputStream();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(netInputStream));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ Log.d(TAG, "| " + reader.readLine());
+ }
+ reader.close();
+ return success;
+ }
+ file.delete();
+ success = true;
+ Log.d(TAG, "upload successful");
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ if (fileInputStream != null) {
+ try {
+ fileInputStream.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ if (connection != null) {
+ connection.disconnect();
+ }
+ }
+ return success;
+ }
+}
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index 2add7c9..a20958a 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -172,13 +172,13 @@
int spaceIndices[spaceIndicesLength];
const jsize outputTypesLength = env->GetArrayLength(outputTypesArray);
int outputTypes[outputTypesLength];
- memset(outputChars, 0, outputCharsLength * sizeof(outputChars[0]));
- memset(scores, 0, scoresLength * sizeof(scores[0]));
- memset(spaceIndices, 0, spaceIndicesLength * sizeof(spaceIndices[0]));
- memset(outputTypes, 0, outputTypesLength * sizeof(outputTypes[0]));
+ memset(outputChars, 0, sizeof(outputChars));
+ memset(scores, 0, sizeof(scores));
+ memset(spaceIndices, 0, sizeof(spaceIndices));
+ memset(outputTypes, 0, sizeof(outputTypes));
int count;
- if (isGesture || arraySize > 1) {
+ if (isGesture || arraySize > 0) {
count = dictionary->getSuggestions(pInfo, traverseSession, xCoordinates, yCoordinates,
times, pointerIds, inputCodePoints, arraySize, prevWordCodePoints,
prevWordCodePointsLength, commitPoint, isGesture, useFullEditDistance, outputChars,
diff --git a/native/jni/src/bigram_dictionary.h b/native/jni/src/bigram_dictionary.h
index d676cca..5f11ae8 100644
--- a/native/jni/src/bigram_dictionary.h
+++ b/native/jni/src/bigram_dictionary.h
@@ -29,8 +29,6 @@
BigramDictionary(const unsigned char *dict, int maxWordLength, int maxPredictions);
int getBigrams(const int32_t *word, int length, int *inputCodes, int codesSize,
unsigned short *outWords, int *frequencies, int *outputTypes) const;
- int getBigramListPositionForWord(const int32_t *prevWord, const int prevWordLength,
- const bool forceLowerCaseSearch) const;
void fillBigramAddressToFrequencyMapAndFilter(const int32_t *prevWord, const int prevWordLength,
std::map<int, int> *map, uint8_t *filter) const;
bool isValidBigram(const int32_t *word1, int length1, const int32_t *word2, int length2) const;
@@ -45,6 +43,8 @@
bool getFirstBitOfByte(int *pos) { return (DICT[*pos] & 0x80) > 0; }
bool getSecondBitOfByte(int *pos) { return (DICT[*pos] & 0x40) > 0; }
bool checkFirstCharacter(unsigned short *word, int *inputCodes) const;
+ int getBigramListPositionForWord(const int32_t *prevWord, const int prevWordLength,
+ const bool forceLowerCaseSearch) const;
const unsigned char *DICT;
const int MAX_WORD_LENGTH;
diff --git a/native/jni/src/binary_format.h b/native/jni/src/binary_format.h
index 4cabc84..25d504b 100644
--- a/native/jni/src/binary_format.h
+++ b/native/jni/src/binary_format.h
@@ -43,6 +43,10 @@
static const int FLAG_HAS_SHORTCUT_TARGETS = 0x08;
// Flag for bigram presence
static const int FLAG_HAS_BIGRAMS = 0x04;
+ // Flag for non-words (typically, shortcut only entries)
+ static const int FLAG_IS_NOT_A_WORD = 0x02;
+ // Flag for blacklist
+ static const int FLAG_IS_BLACKLISTED = 0x01;
// Attribute (bigram/shortcut) related flags:
// Flag for presence of more attributes
@@ -61,13 +65,6 @@
static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES = 0x20;
static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES = 0x30;
- private:
- DISALLOW_IMPLICIT_CONSTRUCTORS(BinaryFormat);
- const static int32_t MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
- const static int32_t CHARACTER_ARRAY_TERMINATOR = 0x1F;
- const static int MULTIPLE_BYTE_CHARACTER_ADDITIONAL_SIZE = 2;
-
- public:
const static int UNKNOWN_FORMAT = -1;
// Originally, format version 1 had a 16-bit magic number, then the version number `01'
// then options that must be 0. Hence the first 32-bits of the format are always as follow
@@ -94,7 +91,6 @@
static int skipFrequency(const uint8_t flags, const int pos);
static int skipShortcuts(const uint8_t *const dict, const uint8_t flags, const int pos);
static int skipBigrams(const uint8_t *const dict, const uint8_t flags, const int pos);
- static int skipAllAttributes(const uint8_t *const dict, const uint8_t flags, const int pos);
static int skipChildrenPosAndAttributes(const uint8_t *const dict, const uint8_t flags,
const int pos);
static int readChildrenPosition(const uint8_t *const dict, const uint8_t flags, const int pos);
@@ -118,6 +114,13 @@
REQUIRES_FRENCH_LIGATURES_PROCESSING = 0x4
};
const static unsigned int NO_FLAGS = 0;
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(BinaryFormat);
+ const static int32_t MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
+ const static int32_t CHARACTER_ARRAY_TERMINATOR = 0x1F;
+ const static int MULTIPLE_BYTE_CHARACTER_ADDITIONAL_SIZE = 2;
+ static int skipAllAttributes(const uint8_t *const dict, const uint8_t flags, const int pos);
};
inline int BinaryFormat::detectFormat(const uint8_t *const dict) {
diff --git a/native/jni/src/char_utils.cpp b/native/jni/src/char_utils.cpp
index 223291f..9d886da31 100644
--- a/native/jni/src/char_utils.cpp
+++ b/native/jni/src/char_utils.cpp
@@ -889,7 +889,7 @@
- static_cast<int>((static_cast<const struct LatinCapitalSmallPair *>(b))->capital);
}
-unsigned short latin_tolower(unsigned short c) {
+unsigned short latin_tolower(const unsigned short c) {
struct LatinCapitalSmallPair *p =
static_cast<struct LatinCapitalSmallPair *>(bsearch(&c, SORTED_CHAR_MAP,
sizeof(SORTED_CHAR_MAP) / sizeof(SORTED_CHAR_MAP[0]),
diff --git a/native/jni/src/char_utils.h b/native/jni/src/char_utils.h
index edd96bb..b30677f 100644
--- a/native/jni/src/char_utils.h
+++ b/native/jni/src/char_utils.h
@@ -17,21 +17,23 @@
#ifndef LATINIME_CHAR_UTILS_H
#define LATINIME_CHAR_UTILS_H
+#include <cctype>
+
namespace latinime {
-inline static int isAsciiUpper(unsigned short c) {
- return c >= 'A' && c <= 'Z';
+inline static bool isAsciiUpper(unsigned short c) {
+ return isupper(static_cast<int>(c)) != 0;
}
inline static unsigned short toAsciiLower(unsigned short c) {
return c - 'A' + 'a';
}
-inline static int isAscii(unsigned short c) {
- return c <= 127;
+inline static bool isAscii(unsigned short c) {
+ return isascii(static_cast<int>(c)) != 0;
}
-unsigned short latin_tolower(unsigned short c);
+unsigned short latin_tolower(const unsigned short c);
/**
* Table mapping most combined Latin, Greek, and Cyrillic characters
diff --git a/native/jni/src/correction.cpp b/native/jni/src/correction.cpp
index e55da01..9ad65b0 100644
--- a/native/jni/src/correction.cpp
+++ b/native/jni/src/correction.cpp
@@ -61,19 +61,19 @@
}
inline static void calcEditDistanceOneStep(int *editDistanceTable, const unsigned short *input,
- const int inputLength, const unsigned short *output, const int outputLength) {
+ const int inputSize, const unsigned short *output, const int outputLength) {
// TODO: Make sure that editDistance[0 ~ MAX_WORD_LENGTH_INTERNAL] is not touched.
- // Let dp[i][j] be editDistanceTable[i * (inputLength + 1) + j].
- // Assuming that dp[0][0] ... dp[outputLength - 1][inputLength] are already calculated,
- // and calculate dp[ouputLength][0] ... dp[outputLength][inputLength].
- int *const current = editDistanceTable + outputLength * (inputLength + 1);
- const int *const prev = editDistanceTable + (outputLength - 1) * (inputLength + 1);
+ // Let dp[i][j] be editDistanceTable[i * (inputSize + 1) + j].
+ // Assuming that dp[0][0] ... dp[outputLength - 1][inputSize] are already calculated,
+ // and calculate dp[ouputLength][0] ... dp[outputLength][inputSize].
+ int *const current = editDistanceTable + outputLength * (inputSize + 1);
+ const int *const prev = editDistanceTable + (outputLength - 1) * (inputSize + 1);
const int *const prevprev =
- outputLength >= 2 ? editDistanceTable + (outputLength - 2) * (inputLength + 1) : 0;
+ outputLength >= 2 ? editDistanceTable + (outputLength - 2) * (inputSize + 1) : 0;
current[0] = outputLength;
const uint32_t co = toBaseLowerCase(output[outputLength - 1]);
const uint32_t prevCO = outputLength >= 2 ? toBaseLowerCase(output[outputLength - 2]) : 0;
- for (int i = 1; i <= inputLength; ++i) {
+ for (int i = 1; i <= inputSize; ++i) {
const uint32_t ci = toBaseLowerCase(input[i - 1]);
const uint16_t cost = (ci == co) ? 0 : 1;
current[i] = min(current[i - 1] + 1, min(prev[i] + 1, prev[i - 1] + cost));
@@ -84,11 +84,11 @@
}
inline static int getCurrentEditDistance(int *editDistanceTable, const int editDistanceTableWidth,
- const int outputLength, const int inputLength) {
+ const int outputLength, const int inputSize) {
if (DEBUG_EDIT_DISTANCE) {
- AKLOGI("getCurrentEditDistance %d, %d", inputLength, outputLength);
+ AKLOGI("getCurrentEditDistance %d, %d", inputSize, outputLength);
}
- return editDistanceTable[(editDistanceTableWidth + 1) * (outputLength) + inputLength];
+ return editDistanceTable[(editDistanceTableWidth + 1) * (outputLength) + inputSize];
}
//////////////////////
@@ -109,12 +109,12 @@
mTotalTraverseCount = 0;
}
-void Correction::initCorrection(const ProximityInfo *pi, const int inputLength,
+void Correction::initCorrection(const ProximityInfo *pi, const int inputSize,
const int maxDepth) {
mProximityInfo = pi;
- mInputLength = inputLength;
+ mInputSize = inputSize;
mMaxDepth = maxDepth;
- mMaxEditDistance = mInputLength < 5 ? 2 : mInputLength / 2;
+ mMaxEditDistance = mInputSize < 5 ? 2 : mInputSize / 2;
// TODO: This is not supposed to be required. Check what's going wrong with
// editDistance[0 ~ MAX_WORD_LENGTH_INTERNAL]
initEditDistance(mEditDistanceTable);
@@ -168,26 +168,22 @@
}
int Correction::getFinalProbability(const int probability, unsigned short **word, int *wordLength) {
- return getFinalProbabilityInternal(probability, word, wordLength, mInputLength);
+ return getFinalProbabilityInternal(probability, word, wordLength, mInputSize);
}
int Correction::getFinalProbabilityForSubQueue(const int probability, unsigned short **word,
- int *wordLength, const int inputLength) {
- return getFinalProbabilityInternal(probability, word, wordLength, inputLength);
+ int *wordLength, const int inputSize) {
+ return getFinalProbabilityInternal(probability, word, wordLength, inputSize);
}
int Correction::getFinalProbabilityInternal(const int probability, unsigned short **word,
- int *wordLength, const int inputLength) {
+ int *wordLength, const int inputSize) {
const int outputIndex = mTerminalOutputIndex;
const int inputIndex = mTerminalInputIndex;
*wordLength = outputIndex + 1;
- if (outputIndex < MIN_SUGGEST_DEPTH) {
- return NOT_A_PROBABILITY;
- }
-
*word = mWord;
int finalProbability= Correction::RankingAlgorithm::calculateFinalProbability(
- inputIndex, outputIndex, probability, mEditDistanceTable, this, inputLength);
+ inputIndex, outputIndex, probability, mEditDistanceTable, this, inputSize);
return finalProbability;
}
@@ -230,7 +226,7 @@
}
// TODO: remove
-int Correction::getInputIndex() {
+int Correction::getInputIndex() const {
return mInputIndex;
}
@@ -274,13 +270,13 @@
// TODO: use edit distance here
return mOutputIndex - 1 >= mMaxDepth || mProximityCount > mMaxEditDistance
// Allow one char longer word for missing character
- || (!mDoAutoCompletion && (mOutputIndex > mInputLength));
+ || (!mDoAutoCompletion && (mOutputIndex > mInputSize));
}
void Correction::addCharToCurrentWord(const int32_t c) {
mWord[mOutputIndex] = c;
const unsigned short *primaryInputWord = mProximityInfoState.getPrimaryInputWord();
- calcEditDistanceOneStep(mEditDistanceTable, primaryInputWord, mInputLength,
+ calcEditDistanceOneStep(mEditDistanceTable, primaryInputWord, mInputSize,
mWord, mOutputIndex + 1);
}
@@ -329,7 +325,7 @@
// Skip checking this node
if (mNeedsToTraverseAllNodes || isSingleQuote(c)) {
bool incremented = false;
- if (mLastCharExceeded && mInputIndex == mInputLength - 1) {
+ if (mLastCharExceeded && mInputIndex == mInputSize - 1) {
// TODO: Do not check the proximity if EditDistance exceeds the threshold
const ProximityType matchId = mProximityInfoState.getMatchedProximityId(
mInputIndex, c, true, &proximityIndex);
@@ -358,7 +354,7 @@
if (mExcessiveCount == 0 && mExcessivePos < mOutputIndex) {
mExcessivePos = mOutputIndex;
}
- if (mExcessivePos < mInputLength - 1) {
+ if (mExcessivePos < mInputSize - 1) {
mExceeding = mExcessivePos == mInputIndex && canTryCorrection;
}
}
@@ -377,7 +373,7 @@
if (mTransposedCount == 0 && mTransposedPos < mOutputIndex) {
mTransposedPos = mOutputIndex;
}
- if (mTransposedPos < mInputLength - 1) {
+ if (mTransposedPos < mInputSize - 1) {
mTransposing = mInputIndex == mTransposedPos && canTryCorrection;
}
}
@@ -396,7 +392,7 @@
} else {
--mTransposedCount;
if (DEBUG_CORRECTION
- && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+ && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
&& (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
|| MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
DUMP_WORD(mWord, mOutputIndex);
@@ -427,7 +423,7 @@
&& isEquivalentChar(mProximityInfoState.getMatchedProximityId(
mInputIndex, mWord[mOutputIndex - 1], false))) {
if (DEBUG_CORRECTION
- && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+ && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
&& (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
|| MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
AKLOGI("CONVERSION p->e %c", mWord[mOutputIndex - 1]);
@@ -457,7 +453,7 @@
// As the current char turned out to be an unrelated char,
// we will try other correction-types. Please note that mCorrectionStates[mOutputIndex]
// here refers to the previous state.
- if (mInputIndex < mInputLength - 1 && mOutputIndex > 0 && mTransposedCount > 0
+ if (mInputIndex < mInputSize - 1 && mOutputIndex > 0 && mTransposedCount > 0
&& !mCorrectionStates[mOutputIndex].mTransposing
&& mCorrectionStates[mOutputIndex - 1].mTransposing
&& isEquivalentChar(mProximityInfoState.getMatchedProximityId(
@@ -494,7 +490,7 @@
++mSkippedCount;
--mProximityCount;
return processSkipChar(c, isTerminal, false);
- } else if (mInputIndex - 1 < mInputLength
+ } else if (mInputIndex - 1 < mInputSize
&& mSkippedCount > 0
&& mCorrectionStates[mOutputIndex].mSkipping
&& mCorrectionStates[mOutputIndex].mAdditionalProximityMatching
@@ -506,7 +502,7 @@
mProximityMatching = true;
++mProximityCount;
mDistances[mOutputIndex] = ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO;
- } else if ((mExceeding || mTransposing) && mInputIndex - 1 < mInputLength
+ } else if ((mExceeding || mTransposing) && mInputIndex - 1 < mInputSize
&& isEquivalentChar(
mProximityInfoState.getMatchedProximityId(mInputIndex + 1, c, false))) {
// 1.2. Excessive or transpose correction
@@ -517,7 +513,7 @@
incrementInputIndex();
}
if (DEBUG_CORRECTION
- && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+ && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
&& (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
|| MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
DUMP_WORD(mWord, mOutputIndex);
@@ -533,7 +529,7 @@
// 3. Skip correction
++mSkippedCount;
if (DEBUG_CORRECTION
- && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+ && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
&& (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
|| MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
AKLOGI("SKIP: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
@@ -546,7 +542,7 @@
++mProximityCount;
mDistances[mOutputIndex] = ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO;
if (DEBUG_CORRECTION
- && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+ && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
&& (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
|| MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
AKLOGI("ADDITIONALPROX: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
@@ -554,7 +550,7 @@
}
} else {
if (DEBUG_CORRECTION
- && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+ && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
&& (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
|| MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
DUMP_WORD(mWord, mOutputIndex);
@@ -564,7 +560,7 @@
return processUnrelatedCorrectionType();
}
} else if (secondTransposing) {
- // If inputIndex is greater than mInputLength, that means there is no
+ // If inputIndex is greater than mInputSize, that means there is no
// proximity chars. So, we don't need to check proximity.
mMatching = true;
} else if (isEquivalentChar(matchedProximityCharId)) {
@@ -577,7 +573,7 @@
mDistances[mOutputIndex] =
mProximityInfoState.getNormalizedSquaredDistance(mInputIndex, proximityIndex);
if (DEBUG_CORRECTION
- && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+ && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
&& (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
|| MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
AKLOGI("PROX: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
@@ -589,8 +585,8 @@
// 4. Last char excessive correction
mLastCharExceeded = mExcessiveCount == 0 && mSkippedCount == 0 && mTransposedCount == 0
- && mProximityCount == 0 && (mInputIndex == mInputLength - 2);
- const bool isSameAsUserTypedLength = (mInputLength == mInputIndex + 1) || mLastCharExceeded;
+ && mProximityCount == 0 && (mInputIndex == mInputSize - 2);
+ const bool isSameAsUserTypedLength = (mInputSize == mInputIndex + 1) || mLastCharExceeded;
if (mLastCharExceeded) {
++mExcessiveCount;
}
@@ -601,7 +597,7 @@
}
const bool needsToTryOnTerminalForTheLastPossibleExcessiveChar =
- mExceeding && mInputIndex == mInputLength - 2;
+ mExceeding && mInputIndex == mInputSize - 2;
// Finally, we are ready to go to the next character, the next "virtual node".
// We should advance the input index.
@@ -617,7 +613,7 @@
mTerminalInputIndex = mInputIndex - 1;
mTerminalOutputIndex = mOutputIndex - 1;
if (DEBUG_CORRECTION
- && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+ && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
&& (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0 || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
DUMP_WORD(mWord, mOutputIndex);
AKLOGI("ONTERMINAL(1): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
@@ -631,9 +627,6 @@
}
}
-Correction::~Correction() {
-}
-
inline static int getQuoteCount(const unsigned short *word, const int length) {
int quoteCount = 0;
for (int i = 0; i < length; ++i) {
@@ -655,7 +648,7 @@
/* static */
int Correction::RankingAlgorithm::calculateFinalProbability(const int inputIndex,
const int outputIndex, const int freq, int *editDistanceTable, const Correction *correction,
- const int inputLength) {
+ const int inputSize) {
const int excessivePos = correction->getExcessivePos();
const int typedLetterMultiplier = correction->TYPED_LETTER_MULTIPLIER;
const int fullWordMultiplier = correction->FULL_WORD_MULTIPLIER;
@@ -667,55 +660,55 @@
const bool lastCharExceeded = correction->mLastCharExceeded;
const bool useFullEditDistance = correction->mUseFullEditDistance;
const int outputLength = outputIndex + 1;
- if (skippedCount >= inputLength || inputLength == 0) {
+ if (skippedCount >= inputSize || inputSize == 0) {
return -1;
}
// TODO: find more robust way
- bool sameLength = lastCharExceeded ? (inputLength == inputIndex + 2)
- : (inputLength == inputIndex + 1);
+ bool sameLength = lastCharExceeded ? (inputSize == inputIndex + 2)
+ : (inputSize == inputIndex + 1);
// TODO: use mExcessiveCount
- const int matchCount = inputLength - correction->mProximityCount - excessiveCount;
+ const int matchCount = inputSize - correction->mProximityCount - excessiveCount;
const unsigned short *word = correction->mWord;
const bool skipped = skippedCount > 0;
const int quoteDiffCount = max(0, getQuoteCount(word, outputLength)
- - getQuoteCount(proximityInfoState->getPrimaryInputWord(), inputLength));
+ - getQuoteCount(proximityInfoState->getPrimaryInputWord(), inputSize));
// TODO: Calculate edit distance for transposed and excessive
int ed = 0;
if (DEBUG_DICT_FULL) {
- dumpEditDistance10ForDebug(editDistanceTable, correction->mInputLength, outputLength);
+ dumpEditDistance10ForDebug(editDistanceTable, correction->mInputSize, outputLength);
}
int adjustedProximityMatchedCount = proximityMatchedCount;
int finalFreq = freq;
if (DEBUG_CORRECTION_FREQ
- && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == inputLength)) {
+ && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == inputSize)) {
AKLOGI("FinalFreq0: %d", finalFreq);
}
// TODO: Optimize this.
if (transposedCount > 0 || proximityMatchedCount > 0 || skipped || excessiveCount > 0) {
- ed = getCurrentEditDistance(editDistanceTable, correction->mInputLength, outputLength,
- inputLength) - transposedCount;
+ ed = getCurrentEditDistance(editDistanceTable, correction->mInputSize, outputLength,
+ inputSize) - transposedCount;
const int matchWeight = powerIntCapped(typedLetterMultiplier,
- max(inputLength, outputLength) - ed);
+ max(inputSize, outputLength) - ed);
multiplyIntCapped(matchWeight, &finalFreq);
// TODO: Demote further if there are two or more excessive chars with longer user input?
- if (inputLength > outputLength) {
+ if (inputSize > outputLength) {
multiplyRate(INPUT_EXCEEDS_OUTPUT_DEMOTION_RATE, &finalFreq);
}
ed = max(0, ed - quoteDiffCount);
- adjustedProximityMatchedCount = min(max(0, ed - (outputLength - inputLength)),
+ adjustedProximityMatchedCount = min(max(0, ed - (outputLength - inputSize)),
proximityMatchedCount);
if (transposedCount <= 0) {
- if (ed == 1 && (inputLength == outputLength - 1 || inputLength == outputLength + 1)) {
+ if (ed == 1 && (inputSize == outputLength - 1 || inputSize == outputLength + 1)) {
// Promote a word with just one skipped or excessive char
if (sameLength) {
multiplyRate(WORDS_WITH_JUST_ONE_CORRECTION_PROMOTION_RATE
@@ -744,8 +737,8 @@
// Demotion for a word with missing character
if (skipped) {
const int demotionRate = WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE
- * (10 * inputLength - WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X)
- / (10 * inputLength
+ * (10 * inputSize - WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X)
+ / (10 * inputSize
- WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X + 10);
if (DEBUG_DICT_FULL) {
AKLOGI("Demotion rate for missing character is %d.", demotionRate);
@@ -847,7 +840,7 @@
? adjustedProximityMatchedCount
: (proximityMatchedCount + transposedCount);
multiplyRate(
- 100 - CORRECTION_COUNT_RATE_DEMOTION_RATE_BASE * errorCount / inputLength, &finalFreq);
+ 100 - CORRECTION_COUNT_RATE_DEMOTION_RATE_BASE * errorCount / inputSize, &finalFreq);
// Promotion for an exactly matched word
if (ed == 0) {
@@ -882,7 +875,7 @@
e ... exceeding
p ... proximity matching
*/
- if (matchCount == inputLength && matchCount >= 2 && !skipped
+ if (matchCount == inputSize && matchCount >= 2 && !skipped
&& word[matchCount] == word[matchCount - 1]) {
multiplyRate(WORDS_WITH_MATCH_SKIP_PROMOTION_RATE, &finalFreq);
}
@@ -892,8 +885,8 @@
multiplyIntCapped(fullWordMultiplier, &finalFreq);
}
- if (useFullEditDistance && outputLength > inputLength + 1) {
- const int diff = outputLength - inputLength - 1;
+ if (useFullEditDistance && outputLength > inputSize + 1) {
+ const int diff = outputLength - inputSize - 1;
const int divider = diff < 31 ? 1 << diff : S_INT_MAX;
finalFreq = divider > finalFreq ? 1 : finalFreq / divider;
}
@@ -903,8 +896,8 @@
}
if (DEBUG_CORRECTION_FREQ
- && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == inputLength)) {
- DUMP_WORD(correction->getPrimaryInputWord(), inputLength);
+ && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == inputSize)) {
+ DUMP_WORD(correction->getPrimaryInputWord(), inputSize);
DUMP_WORD(correction->mWord, outputLength);
AKLOGI("FinalFreq: [P%d, S%d, T%d, E%d, A%d] %d, %d, %d, %d, %d, %d", proximityMatchedCount,
skippedCount, transposedCount, excessiveCount, additionalProximityCount,
diff --git a/native/jni/src/correction.h b/native/jni/src/correction.h
index 57e7b71..f016d54 100644
--- a/native/jni/src/correction.h
+++ b/native/jni/src/correction.h
@@ -18,6 +18,7 @@
#define LATINIME_CORRECTION_H
#include <cassert>
+#include <cstring> // for memset()
#include <stdint.h>
#include "correction_state.h"
@@ -38,10 +39,108 @@
NOT_ON_TERMINAL
} CorrectionType;
+ Correction()
+ : mProximityInfo(0), mUseFullEditDistance(false), mDoAutoCompletion(false),
+ mMaxEditDistance(0), mMaxDepth(0), mInputSize(0), mSpaceProximityPos(0),
+ mMissingSpacePos(0), mTerminalInputIndex(0), mTerminalOutputIndex(0), mMaxErrors(0),
+ mTotalTraverseCount(0), mNeedsToTraverseAllNodes(false), mOutputIndex(0),
+ mInputIndex(0), mEquivalentCharCount(0), mProximityCount(0), mExcessiveCount(0),
+ mTransposedCount(0), mSkippedCount(0), mTransposedPos(0), mExcessivePos(0),
+ mSkipPos(0), mLastCharExceeded(false), mMatching(false), mProximityMatching(false),
+ mAdditionalProximityMatching(false), mExceeding(false), mTransposing(false),
+ mSkipping(false), mProximityInfoState() {
+ memset(mWord, 0, sizeof(mWord));
+ memset(mDistances, 0, sizeof(mDistances));
+ memset(mEditDistanceTable, 0, sizeof(mEditDistanceTable));
+ // NOTE: mCorrectionStates is an array of instances.
+ // No need to initialize it explicitly here.
+ }
+
+ virtual ~Correction() {}
+ void resetCorrection();
+ void initCorrection(
+ const ProximityInfo *pi, const int inputSize, const int maxWordLength);
+ void initCorrectionState(const int rootPos, const int childCount, const bool traverseAll);
+
+ // TODO: remove
+ void setCorrectionParams(const int skipPos, const int excessivePos, const int transposedPos,
+ const int spaceProximityPos, const int missingSpacePos, const bool useFullEditDistance,
+ const bool doAutoCompletion, const int maxErrors);
+ void checkState();
+ bool sameAsTyped();
+ bool initProcessState(const int index);
+
+ int getInputIndex() const;
+
+ bool needsToPrune() const;
+
+ int pushAndGetTotalTraverseCount() {
+ return ++mTotalTraverseCount;
+ }
+
+ int getFreqForSplitMultipleWords(
+ const int *freqArray, const int *wordLengthArray, const int wordCount,
+ const bool isSpaceProximity, const unsigned short *word);
+ int getFinalProbability(const int probability, unsigned short **word, int *wordLength);
+ int getFinalProbabilityForSubQueue(const int probability, unsigned short **word,
+ int *wordLength, const int inputSize);
+
+ CorrectionType processCharAndCalcState(const int32_t c, const bool isTerminal);
+
+ /////////////////////////
+ // Tree helper methods
+ int goDownTree(const int parentIndex, const int childCount, const int firstChildPos);
+
+ inline int getTreeSiblingPos(const int index) const {
+ return mCorrectionStates[index].mSiblingPos;
+ }
+
+ inline void setTreeSiblingPos(const int index, const int pos) {
+ mCorrectionStates[index].mSiblingPos = pos;
+ }
+
+ inline int getTreeParentIndex(const int index) const {
+ return mCorrectionStates[index].mParentIndex;
+ }
+
+ class RankingAlgorithm {
+ public:
+ static int calculateFinalProbability(const int inputIndex, const int depth,
+ const int probability, int *editDistanceTable, const Correction *correction,
+ const int inputSize);
+ static int calcFreqForSplitMultipleWords(const int *freqArray, const int *wordLengthArray,
+ const int wordCount, const Correction *correction, const bool isSpaceProximity,
+ const unsigned short *word);
+ static float calcNormalizedScore(const unsigned short *before, const int beforeLength,
+ const unsigned short *after, const int afterLength, const int score);
+ static int editDistance(const unsigned short *before,
+ const int beforeLength, const unsigned short *after, const int afterLength);
+ private:
+ static const int CODE_SPACE = ' ';
+ static const int MAX_INITIAL_SCORE = 255;
+ };
+
+ // proximity info state
+ void initInputParams(const ProximityInfo *proximityInfo, const int32_t *inputCodes,
+ const int inputSize, const int *xCoordinates, const int *yCoordinates) {
+ mProximityInfoState.initInputParams(0, MAX_POINT_TO_KEY_LENGTH,
+ proximityInfo, inputCodes, inputSize, xCoordinates, yCoordinates, 0, 0, false);
+ }
+
+ const unsigned short *getPrimaryInputWord() const {
+ return mProximityInfoState.getPrimaryInputWord();
+ }
+
+ unsigned short getPrimaryCharAt(const int index) const {
+ return mProximityInfoState.getPrimaryCharAt(index);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Correction);
+
/////////////////////////
// static inline utils //
/////////////////////////
-
static const int TWO_31ST_DIV_255 = S_INT_MAX / 255;
static inline int capped255MultForFullMatchAccentsOrCapitalizationDifference(const int num) {
return (num < TWO_31ST_DIV_255 ? 255 * num : S_INT_MAX);
@@ -94,107 +193,25 @@
}
}
- Correction() {};
- void resetCorrection();
- void initCorrection(
- const ProximityInfo *pi, const int inputLength, const int maxWordLength);
- void initCorrectionState(const int rootPos, const int childCount, const bool traverseAll);
-
- // TODO: remove
- void setCorrectionParams(const int skipPos, const int excessivePos, const int transposedPos,
- const int spaceProximityPos, const int missingSpacePos, const bool useFullEditDistance,
- const bool doAutoCompletion, const int maxErrors);
- void checkState();
- bool sameAsTyped();
- bool initProcessState(const int index);
-
- int getInputIndex();
-
- virtual ~Correction();
- int getSpaceProximityPos() const {
+ inline int getSpaceProximityPos() const {
return mSpaceProximityPos;
}
- int getMissingSpacePos() const {
+ inline int getMissingSpacePos() const {
return mMissingSpacePos;
}
- int getSkipPos() const {
+ inline int getSkipPos() const {
return mSkipPos;
}
- int getExcessivePos() const {
+ inline int getExcessivePos() const {
return mExcessivePos;
}
- int getTransposedPos() const {
+ inline int getTransposedPos() const {
return mTransposedPos;
}
- bool needsToPrune() const;
-
- int pushAndGetTotalTraverseCount() {
- return ++mTotalTraverseCount;
- }
-
- int getFreqForSplitMultipleWords(
- const int *freqArray, const int *wordLengthArray, const int wordCount,
- const bool isSpaceProximity, const unsigned short *word);
- int getFinalProbability(const int probability, unsigned short **word, int *wordLength);
- int getFinalProbabilityForSubQueue(const int probability, unsigned short **word,
- int *wordLength, const int inputLength);
-
- CorrectionType processCharAndCalcState(const int32_t c, const bool isTerminal);
-
- /////////////////////////
- // Tree helper methods
- int goDownTree(const int parentIndex, const int childCount, const int firstChildPos);
-
- inline int getTreeSiblingPos(const int index) const {
- return mCorrectionStates[index].mSiblingPos;
- }
-
- inline void setTreeSiblingPos(const int index, const int pos) {
- mCorrectionStates[index].mSiblingPos = pos;
- }
-
- inline int getTreeParentIndex(const int index) const {
- return mCorrectionStates[index].mParentIndex;
- }
-
- class RankingAlgorithm {
- public:
- static int calculateFinalProbability(const int inputIndex, const int depth,
- const int probability, int *editDistanceTable, const Correction *correction,
- const int inputLength);
- static int calcFreqForSplitMultipleWords(const int *freqArray, const int *wordLengthArray,
- const int wordCount, const Correction *correction, const bool isSpaceProximity,
- const unsigned short *word);
- static float calcNormalizedScore(const unsigned short *before, const int beforeLength,
- const unsigned short *after, const int afterLength, const int score);
- static int editDistance(const unsigned short *before,
- const int beforeLength, const unsigned short *after, const int afterLength);
- private:
- static const int CODE_SPACE = ' ';
- static const int MAX_INITIAL_SCORE = 255;
- };
-
- // proximity info state
- void initInputParams(const ProximityInfo *proximityInfo, const int32_t *inputCodes,
- const int inputLength, const int *xCoordinates, const int *yCoordinates) {
- mProximityInfoState.initInputParams(
- proximityInfo, inputCodes, inputLength, xCoordinates, yCoordinates);
- }
-
- const unsigned short *getPrimaryInputWord() const {
- return mProximityInfoState.getPrimaryInputWord();
- }
-
- unsigned short getPrimaryCharAt(const int index) const {
- return mProximityInfoState.getPrimaryCharAt(index);
- }
-
- private:
- DISALLOW_COPY_AND_ASSIGN(Correction);
inline void incrementInputIndex();
inline void incrementOutputIndex();
inline void startToTraverseAllNodes();
@@ -204,7 +221,7 @@
inline CorrectionType processUnrelatedCorrectionType();
inline void addCharToCurrentWord(const int32_t c);
inline int getFinalProbabilityInternal(const int probability, unsigned short **word,
- int *wordLength, const int inputLength);
+ int *wordLength, const int inputSize);
static const int TYPED_LETTER_MULTIPLIER = 2;
static const int FULL_WORD_MULTIPLIER = 2;
@@ -214,7 +231,7 @@
bool mDoAutoCompletion;
int mMaxEditDistance;
int mMaxDepth;
- int mInputLength;
+ int mInputSize;
int mSpaceProximityPos;
int mMissingSpacePos;
int mTerminalInputIndex;
diff --git a/native/jni/src/debug.h b/native/jni/src/debug.h
index 4e21640..8f6b69d 100644
--- a/native/jni/src/debug.h
+++ b/native/jni/src/debug.h
@@ -22,7 +22,7 @@
static inline unsigned char *convertToUnibyteString(unsigned short *input, unsigned char *output,
const unsigned int length) {
unsigned int i = 0;
- for (; i <= length && input[i] != 0; ++i)
+ for (; i < length && input[i] != 0; ++i)
output[i] = input[i] & 0xFF;
output[i] = 0;
return output;
@@ -31,7 +31,7 @@
static inline unsigned char *convertToUnibyteStringAndReplaceLastChar(unsigned short *input,
unsigned char *output, const unsigned int length, unsigned char c) {
unsigned int i = 0;
- for (; i <= length && input[i] != 0; ++i)
+ for (; i < length && input[i] != 0; ++i)
output[i] = input[i] & 0xFF;
if (i > 0) output[i-1] = c;
output[i] = 0;
diff --git a/native/jni/src/defines.h b/native/jni/src/defines.h
index 484fc6b..28661ab 100644
--- a/native/jni/src/defines.h
+++ b/native/jni/src/defines.h
@@ -83,12 +83,38 @@
AKLOGI("i[ %s ]", charBuf);
}
+#ifndef __ANDROID__
+#define ASSERT(success) do { if(!success) { showStackTrace(); assert(success);};} while (0)
+#define SHOW_STACK_TRACE do { showStackTrace(); } while (0)
+
+#include <execinfo.h>
+#include <stdlib.h>
+static inline void showStackTrace() {
+ void *callstack[128];
+ int i, frames = backtrace(callstack, 128);
+ char **strs = backtrace_symbols(callstack, frames);
+ for (i = 0; i < frames; ++i) {
+ if (i == 0) {
+ AKLOGI("=== Trace ===");
+ continue;
+ }
+ AKLOGI("%s", strs[i]);
+ }
+ free(strs);
+}
+#else
+#define ASSERT(success)
+#define SHOW_STACK_TRACE
+#endif
+
#else
#define AKLOGE(fmt, ...)
#define AKLOGI(fmt, ...)
#define DUMP_RESULT(words, frequencies, maxWordCount, maxWordLength)
#define DUMP_WORD(word, length)
#define DUMP_WORD_INT(word, length)
+#define ASSERT(success)
+#define SHOW_STACK_TRACE
#endif
#ifdef FLAG_DO_PROFILE
@@ -294,12 +320,13 @@
#define MAX_SPACES_INTERNAL 16
+// Max Distance between point to key
+#define MAX_POINT_TO_KEY_LENGTH 10000000
+
// TODO: Reduce this constant if possible; check the maximum number of digraphs in the same
// word in the dictionary for languages with digraphs, like German and French
#define DEFAULT_MAX_DIGRAPH_SEARCH_DEPTH 5
-// Minimum suggest depth for one word for all cases except for missing space suggestions.
-#define MIN_SUGGEST_DEPTH 1
#define MIN_USER_TYPED_LENGTH_FOR_MULTIPLE_WORD_SUGGESTION 3
#define MIN_USER_TYPED_LENGTH_FOR_EXCESSIVE_CHARACTER_SUGGESTION 3
diff --git a/native/jni/src/dic_traverse_wrapper.cpp b/native/jni/src/dic_traverse_wrapper.cpp
index 1f7dcbf..88ca9fa 100644
--- a/native/jni/src/dic_traverse_wrapper.cpp
+++ b/native/jni/src/dic_traverse_wrapper.cpp
@@ -19,8 +19,8 @@
#include "dic_traverse_wrapper.h"
namespace latinime {
-void *(*DicTraverseWrapper::sDicTraverseSessionFactoryMethod)(JNIEnv *env, jstring locale) = 0;
+void *(*DicTraverseWrapper::sDicTraverseSessionFactoryMethod)(JNIEnv *, jstring) = 0;
void (*DicTraverseWrapper::sDicTraverseSessionReleaseMethod)(void *) = 0;
void (*DicTraverseWrapper::sDicTraverseSessionInitMethod)(
- void *, Dictionary *, const int *, const int) = 0;
+ void *, const Dictionary *const, const int *, const int) = 0;
} // namespace latinime
diff --git a/native/jni/src/dic_traverse_wrapper.h b/native/jni/src/dic_traverse_wrapper.h
index 8396d00..2923824 100644
--- a/native/jni/src/dic_traverse_wrapper.h
+++ b/native/jni/src/dic_traverse_wrapper.h
@@ -34,7 +34,7 @@
return 0;
}
static void initDicTraverseSession(void *traverseSession,
- Dictionary *dictionary, const int *prevWord, const int prevWordLength) {
+ const Dictionary *const dictionary, const int *prevWord, const int prevWordLength) {
if (sDicTraverseSessionInitMethod) {
sDicTraverseSessionInitMethod(traverseSession, dictionary, prevWord, prevWordLength);
}
@@ -45,11 +45,11 @@
}
}
static void setTraverseSessionFactoryMethod(
- void *(*factoryMethod)(JNIEnv *env, jstring locale)) {
+ void *(*factoryMethod)(JNIEnv *, jstring)) {
sDicTraverseSessionFactoryMethod = factoryMethod;
}
static void setTraverseSessionInitMethod(
- void (*initMethod)(void *, Dictionary *, const int *, const int)) {
+ void (*initMethod)(void *, const Dictionary *const, const int *, const int)) {
sDicTraverseSessionInitMethod = initMethod;
}
static void setTraverseSessionReleaseMethod(void (*releaseMethod)(void *)) {
@@ -58,7 +58,8 @@
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(DicTraverseWrapper);
static void *(*sDicTraverseSessionFactoryMethod)(JNIEnv *, jstring);
- static void (*sDicTraverseSessionInitMethod)(void *, Dictionary *, const int *, const int);
+ static void (*sDicTraverseSessionInitMethod)(
+ void *, const Dictionary *const, const int *, const int);
static void (*sDicTraverseSessionReleaseMethod)(void *);
};
int register_DicTraverseSession(JNIEnv *env);
diff --git a/native/jni/src/dictionary.cpp b/native/jni/src/dictionary.cpp
index 158c3fb..2fbe83e 100644
--- a/native/jni/src/dictionary.cpp
+++ b/native/jni/src/dictionary.cpp
@@ -30,11 +30,15 @@
// TODO: Change the type of all keyCodes to uint32_t
Dictionary::Dictionary(void *dict, int dictSize, int mmapFd, int dictBufAdjust,
- int typedLetterMultiplier, int fullWordMultiplier,
- int maxWordLength, int maxWords, int maxPredictions)
- : mDict(static_cast<unsigned char *>(dict)),
- mOffsetDict((static_cast<unsigned char *>(dict)) + BinaryFormat::getHeaderSize(mDict)),
- mDictSize(dictSize), mMmapFd(mmapFd), mDictBufAdjust(dictBufAdjust) {
+ int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength, int maxWords,
+ int maxPredictions)
+ : mDict(static_cast<unsigned char *>(dict)),
+ mOffsetDict((static_cast<unsigned char *>(dict)) + BinaryFormat::getHeaderSize(mDict)),
+ mDictSize(dictSize), mMmapFd(mmapFd), mDictBufAdjust(dictBufAdjust),
+ mUnigramDictionary(new UnigramDictionary(mOffsetDict, typedLetterMultiplier,
+ fullWordMultiplier, maxWordLength, maxWords, BinaryFormat::getFlags(mDict))),
+ mBigramDictionary(new BigramDictionary(mOffsetDict, maxWordLength, maxPredictions)),
+ mGestureDecoder(new GestureDecoderWrapper(maxWordLength, maxWords)) {
if (DEBUG_DICT) {
if (MAX_WORD_LENGTH_INTERNAL < maxWordLength) {
AKLOGI("Max word length (%d) is greater than %d",
@@ -42,11 +46,6 @@
AKLOGI("IN NATIVE SUGGEST Version: %d", (mDict[0] & 0xFF));
}
}
- const unsigned int options = BinaryFormat::getFlags(mDict);
- mUnigramDictionary = new UnigramDictionary(mOffsetDict, typedLetterMultiplier,
- fullWordMultiplier, maxWordLength, maxWords, options);
- mBigramDictionary = new BigramDictionary(mOffsetDict, maxWordLength, maxPredictions);
- mGestureDecoder = new GestureDecoderWrapper(maxWordLength, maxWords);
}
Dictionary::~Dictionary() {
@@ -60,7 +59,7 @@
int *codes, int codesSize, int *prevWordChars,
int prevWordLength, int commitPoint, bool isGesture,
bool useFullEditDistance, unsigned short *outWords,
- int *frequencies, int *spaceIndices, int *outputTypes) {
+ int *frequencies, int *spaceIndices, int *outputTypes) const {
int result = 0;
if (isGesture) {
DicTraverseWrapper::initDicTraverseSession(
diff --git a/native/jni/src/dictionary.h b/native/jni/src/dictionary.h
index fd9e770..e9a03ce 100644
--- a/native/jni/src/dictionary.h
+++ b/native/jni/src/dictionary.h
@@ -48,7 +48,7 @@
int *ycoordinates, int *times, int *pointerIds, int *codes, int codesSize,
int *prevWordChars, int prevWordLength, int commitPoint, bool isGesture,
bool useFullEditDistance, unsigned short *outWords,
- int *frequencies, int *spaceIndices, int *outputTypes);
+ int *frequencies, int *spaceIndices, int *outputTypes) const;
int getBigrams(const int32_t *word, int length, int *codes, int codesSize,
unsigned short *outWords, int *frequencies, int *outputTypes) const;
diff --git a/native/jni/src/geometry_utils.h b/native/jni/src/geometry_utils.h
index 146eb80..f30e9fc 100644
--- a/native/jni/src/geometry_utils.h
+++ b/native/jni/src/geometry_utils.h
@@ -19,7 +19,6 @@
#include <cmath>
-#define MAX_DISTANCE 10000000
#define MAX_PATHS 2
#define DEBUG_DECODER false
diff --git a/native/jni/src/gesture/gesture_decoder_wrapper.h b/native/jni/src/gesture/gesture_decoder_wrapper.h
index f8bfe7c..92e1ded 100644
--- a/native/jni/src/gesture/gesture_decoder_wrapper.h
+++ b/native/jni/src/gesture/gesture_decoder_wrapper.h
@@ -29,8 +29,8 @@
class GestureDecoderWrapper : public IncrementalDecoderInterface {
public:
- GestureDecoderWrapper(const int maxWordLength, const int maxWords) {
- mIncrementalDecoderInterface = getGestureDecoderInstance(maxWordLength, maxWords);
+ GestureDecoderWrapper(const int maxWordLength, const int maxWords)
+ : mIncrementalDecoderInterface(getGestureDecoderInstance(maxWordLength, maxWords)) {
}
virtual ~GestureDecoderWrapper() {
@@ -39,7 +39,8 @@
int getSuggestions(ProximityInfo *pInfo, void *traverseSession, int *inputXs, int *inputYs,
int *times, int *pointerIds, int *codes, int inputSize, int commitPoint,
- unsigned short *outWords, int *frequencies, int *outputIndices, int *outputTypes) {
+ unsigned short *outWords, int *frequencies, int *outputIndices,
+ int *outputTypes) const {
if (!mIncrementalDecoderInterface) {
return 0;
}
diff --git a/native/jni/src/gesture/incremental_decoder_interface.h b/native/jni/src/gesture/incremental_decoder_interface.h
index 04f0095..d1395aa 100644
--- a/native/jni/src/gesture/incremental_decoder_interface.h
+++ b/native/jni/src/gesture/incremental_decoder_interface.h
@@ -31,7 +31,7 @@
virtual int getSuggestions(ProximityInfo *pInfo, void *traverseSession,
int *inputXs, int *inputYs, int *times, int *pointerIds, int *codes,
int inputSize, int commitPoint, unsigned short *outWords, int *frequencies,
- int *outputIndices, int *outputTypes) = 0;
+ int *outputIndices, int *outputTypes) const = 0;
IncrementalDecoderInterface() { };
virtual ~IncrementalDecoderInterface() { };
private:
diff --git a/native/jni/src/gesture/incremental_decoder_wrapper.h b/native/jni/src/gesture/incremental_decoder_wrapper.h
index 5cb2ee3..da7afdb 100644
--- a/native/jni/src/gesture/incremental_decoder_wrapper.h
+++ b/native/jni/src/gesture/incremental_decoder_wrapper.h
@@ -29,8 +29,8 @@
class IncrementalDecoderWrapper : public IncrementalDecoderInterface {
public:
- IncrementalDecoderWrapper(const int maxWordLength, const int maxWords) {
- mIncrementalDecoderInterface = getIncrementalDecoderInstance(maxWordLength, maxWords);
+ IncrementalDecoderWrapper(const int maxWordLength, const int maxWords)
+ : mIncrementalDecoderInterface(getIncrementalDecoderInstance(maxWordLength, maxWords)) {
}
virtual ~IncrementalDecoderWrapper() {
@@ -39,7 +39,8 @@
int getSuggestions(ProximityInfo *pInfo, void *traverseSession, int *inputXs, int *inputYs,
int *times, int *pointerIds, int *codes, int inputSize, int commitPoint,
- unsigned short *outWords, int *frequencies, int *outputIndices, int *outputTypes) {
+ unsigned short *outWords, int *frequencies, int *outputIndices,
+ int *outputTypes) const {
if (!mIncrementalDecoderInterface) {
return 0;
}
diff --git a/native/jni/src/hash_map_compat.h b/native/jni/src/hash_map_compat.h
new file mode 100644
index 0000000..116359a
--- /dev/null
+++ b/native/jni/src/hash_map_compat.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2012, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_HASH_MAP_COMPAT_H
+#define LATINIME_HASH_MAP_COMPAT_H
+
+// TODO: Use std::unordered_map that has been standardized in C++11
+
+#ifdef __APPLE__
+#include <ext/hash_map>
+#else // __APPLE__
+#include <hash_map>
+#endif // __APPLE__
+
+#ifdef __SGI_STL_PORT
+#define hash_map_compat stlport::hash_map
+#else // __SGI_STL_PORT
+#define hash_map_compat __gnu_cxx::hash_map
+#endif // __SGI_STL_PORT
+
+#endif // LATINIME_HASH_MAP_COMPAT_H
diff --git a/native/jni/src/proximity_info.cpp b/native/jni/src/proximity_info.cpp
index 1b9bac0..765632e 100644
--- a/native/jni/src/proximity_info.cpp
+++ b/native/jni/src/proximity_info.cpp
@@ -29,6 +29,9 @@
namespace latinime {
+/* static */ const int ProximityInfo::NOT_A_CODE = -1;
+/* static */ const float ProximityInfo::NOT_A_DISTANCE_FLOAT = -1.0f;
+
static inline void safeGetOrFillZeroIntArrayRegion(JNIEnv *env, jintArray jArray, jsize len,
jint *buffer) {
if (jArray && buffer) {
@@ -54,16 +57,17 @@
const jintArray keyWidths, const jintArray keyHeights, const jintArray keyCharCodes,
const jfloatArray sweetSpotCenterXs, const jfloatArray sweetSpotCenterYs,
const jfloatArray sweetSpotRadii)
- : MAX_PROXIMITY_CHARS_SIZE(maxProximityCharsSize), KEYBOARD_WIDTH(keyboardWidth),
- KEYBOARD_HEIGHT(keyboardHeight), GRID_WIDTH(gridWidth), GRID_HEIGHT(gridHeight),
- MOST_COMMON_KEY_WIDTH(mostCommonKeyWidth),
+ : MAX_PROXIMITY_CHARS_SIZE(maxProximityCharsSize), GRID_WIDTH(gridWidth),
+ GRID_HEIGHT(gridHeight), MOST_COMMON_KEY_WIDTH(mostCommonKeyWidth),
MOST_COMMON_KEY_WIDTH_SQUARE(mostCommonKeyWidth * mostCommonKeyWidth),
CELL_WIDTH((keyboardWidth + gridWidth - 1) / gridWidth),
CELL_HEIGHT((keyboardHeight + gridHeight - 1) / gridHeight),
KEY_COUNT(min(keyCount, MAX_KEY_COUNT_IN_A_KEYBOARD)),
HAS_TOUCH_POSITION_CORRECTION_DATA(keyCount > 0 && keyXCoordinates && keyYCoordinates
&& keyWidths && keyHeights && keyCharCodes && sweetSpotCenterXs
- && sweetSpotCenterYs && sweetSpotRadii) {
+ && sweetSpotCenterYs && sweetSpotRadii),
+ mProximityCharsArray(new int32_t[GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE
+ /* proximityGridLength */]) {
const int proximityGridLength = GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE;
if (DEBUG_PROXIMITY_INFO) {
AKLOGI("Create proximity info array %d", proximityGridLength);
@@ -75,7 +79,6 @@
}
memset(mLocaleStr, 0, sizeof(mLocaleStr));
env->GetStringUTFRegion(localeJStr, 0, env->GetStringLength(localeJStr), mLocaleStr);
- mProximityCharsArray = new int32_t[proximityGridLength];
safeGetOrFillZeroIntArrayRegion(env, proximityChars, proximityGridLength, mProximityCharsArray);
safeGetOrFillZeroIntArrayRegion(env, keyXCoordinates, KEY_COUNT, mKeyXCoordinates);
safeGetOrFillZeroIntArrayRegion(env, keyYCoordinates, KEY_COUNT, mKeyYCoordinates);
@@ -299,6 +302,6 @@
if (keyId0 >= 0 && keyId1 >= 0) {
return mKeyKeyDistancesG[keyId0][keyId1];
}
- return 0;
+ return MAX_POINT_TO_KEY_LENGTH;
}
} // namespace latinime
diff --git a/native/jni/src/proximity_info.h b/native/jni/src/proximity_info.h
index 8a407e7..822909b 100644
--- a/native/jni/src/proximity_info.h
+++ b/native/jni/src/proximity_info.h
@@ -41,21 +41,12 @@
float getNormalizedSquaredDistanceFromCenterFloat(
const int keyId, const int x, const int y) const;
bool sameAsTyped(const unsigned short *word, int length) const;
- int squaredDistanceToEdge(const int keyId, const int x, const int y) const;
- bool isOnKey(const int keyId, const int x, const int y) const {
- if (keyId < 0) return true; // NOT_A_ID is -1, but return whenever < 0 just in case
- const int left = mKeyXCoordinates[keyId];
- const int top = mKeyYCoordinates[keyId];
- const int right = left + mKeyWidths[keyId] + 1;
- const int bottom = top + mKeyHeights[keyId];
- return left < right && top < bottom && x >= left && x < right && y >= top && y < bottom;
- }
int getKeyIndex(const int c) const;
int getKeyCode(const int keyIndex) const;
bool hasSweetSpotData(const int keyIndex) const {
// When there are no calibration data for a key,
// the radius of the key is assigned to zero.
- return mSweetSpotRadii[keyIndex] > 0.0;
+ return mSweetSpotRadii[keyIndex] > 0.0f;
}
float getSweetSpotRadiiAt(int keyIndex) const {
return mSweetSpotRadii[keyIndex];
@@ -111,18 +102,14 @@
float getKeyCenterYOfIdG(int keyId) const;
int getKeyKeyDistanceG(int key0, int key1) const;
- // Returns the keyboard key-center information.
- void getCenters(int *centersX, int *centersY, int *codeToKeyIndex, int *keyToCodeIndex,
- int *keyCount, int *keyWidth) const;
-
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(ProximityInfo);
// The max number of the keys in one keyboard layout
static const int MAX_KEY_COUNT_IN_A_KEYBOARD = 64;
// The upper limit of the char code in mCodeToKeyIndex
static const int MAX_CHAR_CODE = 127;
- static const float NOT_A_DISTANCE_FLOAT = -1.0f;
- static const int NOT_A_CODE = -1;
+ static const int NOT_A_CODE;
+ static const float NOT_A_DISTANCE_FLOAT;
int getStartIndexFromCoordinates(const int x, const int y) const;
void initializeCodeToKeyIndex();
@@ -131,10 +118,17 @@
float calculateSquaredDistanceFromSweetSpotCenter(
const int keyIndex, const int inputIndex) const;
bool hasInputCoordinates() const;
+ int squaredDistanceToEdge(const int keyId, const int x, const int y) const;
+ bool isOnKey(const int keyId, const int x, const int y) const {
+ if (keyId < 0) return true; // NOT_A_ID is -1, but return whenever < 0 just in case
+ const int left = mKeyXCoordinates[keyId];
+ const int top = mKeyYCoordinates[keyId];
+ const int right = left + mKeyWidths[keyId] + 1;
+ const int bottom = top + mKeyHeights[keyId];
+ return left < right && top < bottom && x >= left && x < right && y >= top && y < bottom;
+ }
const int MAX_PROXIMITY_CHARS_SIZE;
- const int KEYBOARD_WIDTH;
- const int KEYBOARD_HEIGHT;
const int GRID_WIDTH;
const int GRID_HEIGHT;
const int MOST_COMMON_KEY_WIDTH;
diff --git a/native/jni/src/proximity_info_state.cpp b/native/jni/src/proximity_info_state.cpp
index 86c8a69..e13d4e6 100644
--- a/native/jni/src/proximity_info_state.cpp
+++ b/native/jni/src/proximity_info_state.cpp
@@ -20,13 +20,15 @@
#define LOG_TAG "LatinIME: proximity_info_state.cpp"
#include "defines.h"
+#include "geometry_utils.h"
#include "proximity_info.h"
#include "proximity_info_state.h"
namespace latinime {
-void ProximityInfoState::initInputParams(
- const ProximityInfo *proximityInfo, const int32_t *inputCodes, const int inputLength,
- const int *xCoordinates, const int *yCoordinates) {
+void ProximityInfoState::initInputParams(const int pointerId, const float maxPointToKeyLength,
+ const ProximityInfo *proximityInfo, const int32_t *const inputCodes, const int inputSize,
+ const int *const xCoordinates, const int *const yCoordinates, const int *const times,
+ const int *const pointerIds, const bool isGeometric) {
mProximityInfo = proximityInfo;
mHasTouchPositionCorrectionData = proximityInfo->hasTouchPositionCorrectionData();
mMostCommonKeyWidthSquare = proximityInfo->getMostCommonKeyWidthSquare();
@@ -36,76 +38,310 @@
mCellWidth = proximityInfo->getCellWidth();
mGridHeight = proximityInfo->getGridWidth();
mGridWidth = proximityInfo->getGridHeight();
- const int normalizedSquaredDistancesLength =
- MAX_PROXIMITY_CHARS_SIZE_INTERNAL * MAX_WORD_LENGTH_INTERNAL;
- for (int i = 0; i < normalizedSquaredDistancesLength; ++i) {
- mNormalizedSquaredDistances[i] = NOT_A_DISTANCE;
- }
- memset(mInputCodes, 0,
- MAX_WORD_LENGTH_INTERNAL * MAX_PROXIMITY_CHARS_SIZE_INTERNAL * sizeof(mInputCodes[0]));
+ memset(mInputCodes, 0, sizeof(mInputCodes));
- for (int i = 0; i < inputLength; ++i) {
- const int32_t primaryKey = inputCodes[i];
- const int x = xCoordinates[i];
- const int y = yCoordinates[i];
- int *proximities = &mInputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL];
- mProximityInfo->calculateNearbyKeyCodes(x, y, primaryKey, proximities);
- }
-
- if (DEBUG_PROXIMITY_CHARS) {
- for (int i = 0; i < inputLength; ++i) {
- AKLOGI("---");
- for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL; ++j) {
- int icc = mInputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j];
- int icfjc = inputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j];
- icc += 0;
- icfjc += 0;
- AKLOGI("--- (%d)%c,%c", i, icc, icfjc); AKLOGI("--- A<%d>,B<%d>", icc, icfjc);
- }
+ if (!isGeometric && pointerId == 0) {
+ // Initialize
+ // - mInputCodes
+ // - mNormalizedSquaredDistances
+ // TODO: Merge
+ for (int i = 0; i < inputSize; ++i) {
+ const int32_t primaryKey = inputCodes[i];
+ const int x = xCoordinates[i];
+ const int y = yCoordinates[i];
+ int *proximities = &mInputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL];
+ mProximityInfo->calculateNearbyKeyCodes(x, y, primaryKey, proximities);
}
- }
- mInputXCoordinates = xCoordinates;
- mInputYCoordinates = yCoordinates;
- mTouchPositionCorrectionEnabled =
- mHasTouchPositionCorrectionData && xCoordinates && yCoordinates;
- mInputLength = inputLength;
- for (int i = 0; i < inputLength; ++i) {
- mPrimaryInputWord[i] = getPrimaryCharAt(i);
- }
- mPrimaryInputWord[inputLength] = 0;
- if (DEBUG_PROXIMITY_CHARS) {
- AKLOGI("--- initInputParams");
- }
- for (int i = 0; i < mInputLength; ++i) {
- const int *proximityChars = getProximityCharsAt(i);
- const int primaryKey = proximityChars[0];
- const int x = xCoordinates[i];
- const int y = yCoordinates[i];
+
if (DEBUG_PROXIMITY_CHARS) {
- int a = x + y + primaryKey;
- a += 0;
- AKLOGI("--- Primary = %c, x = %d, y = %d", primaryKey, x, y);
- }
- for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL && proximityChars[j] > 0; ++j) {
- const int currentChar = proximityChars[j];
- const float squaredDistance =
- hasInputCoordinates() ? calculateNormalizedSquaredDistance(
- mProximityInfo->getKeyIndex(currentChar), i) :
- NOT_A_DISTANCE_FLOAT;
- if (squaredDistance >= 0.0f) {
- mNormalizedSquaredDistances[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j] =
- (int) (squaredDistance * NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR);
- } else {
- mNormalizedSquaredDistances[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j] =
- (j == 0) ? EQUIVALENT_CHAR_WITHOUT_DISTANCE_INFO :
- PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO;
- }
- if (DEBUG_PROXIMITY_CHARS) {
- AKLOGI("--- Proximity (%d) = %c", j, currentChar);
+ for (int i = 0; i < inputSize; ++i) {
+ AKLOGI("---");
+ for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL; ++j) {
+ int icc = mInputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j];
+ int icfjc = inputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j];
+ icc += 0;
+ icfjc += 0;
+ AKLOGI("--- (%d)%c,%c", i, icc, icfjc); AKLOGI("--- A<%d>,B<%d>", icc, icfjc);
+ }
}
}
}
+
+ ///////////////////////
+ // Setup touch points
+ mMaxPointToKeyLength = maxPointToKeyLength;
+ mInputXs.clear();
+ mInputYs.clear();
+ mTimes.clear();
+ mLengthCache.clear();
+ mDistanceCache.clear();
+ mInputSize = 0;
+
+ if (xCoordinates && yCoordinates) {
+ const bool proximityOnly = !isGeometric && (xCoordinates[0] < 0 || yCoordinates[0] < 0);
+ int lastInputIndex = 0;
+ for (int i = 0; i < inputSize; ++i) {
+ const int pid = pointerIds ? pointerIds[i] : 0;
+ if (pointerId == pid) {
+ lastInputIndex = i;
+ }
+ }
+ // Working space to save near keys distances for current, prev and prevprev input point.
+ NearKeysDistanceMap nearKeysDistances[3];
+ // These pointers are swapped for each inputs points.
+ NearKeysDistanceMap *currentNearKeysDistances = &nearKeysDistances[0];
+ NearKeysDistanceMap *prevNearKeysDistances = &nearKeysDistances[1];
+ NearKeysDistanceMap *prevPrevNearKeysDistances = &nearKeysDistances[2];
+
+ for (int i = 0; i < inputSize; ++i) {
+ // Assuming pointerId == 0 if pointerIds is null.
+ const int pid = pointerIds ? pointerIds[i] : 0;
+ if (pointerId == pid) {
+ const int c = isGeometric ? NOT_A_COORDINATE : getPrimaryCharAt(i);
+ const int x = proximityOnly ? NOT_A_COORDINATE : xCoordinates[i];
+ const int y = proximityOnly ? NOT_A_COORDINATE : yCoordinates[i];
+ const int time = times ? times[i] : -1;
+ if (pushTouchPoint(c, x, y, time, isGeometric, i == lastInputIndex,
+ currentNearKeysDistances, prevNearKeysDistances,
+ prevPrevNearKeysDistances)) {
+ // Previous point information was popped.
+ NearKeysDistanceMap *tmp = prevNearKeysDistances;
+ prevNearKeysDistances = currentNearKeysDistances;
+ currentNearKeysDistances = tmp;
+ } else {
+ NearKeysDistanceMap *tmp = prevPrevNearKeysDistances;
+ prevPrevNearKeysDistances = prevNearKeysDistances;
+ prevNearKeysDistances = currentNearKeysDistances;
+ currentNearKeysDistances = tmp;
+ }
+ }
+ }
+ mInputSize = mInputXs.size();
+ }
+
+ if (mInputSize > 0) {
+ const int keyCount = mProximityInfo->getKeyCount();
+ mDistanceCache.resize(mInputSize * keyCount);
+ for (int i = 0; i < mInputSize; ++i) {
+ for (int k = 0; k < keyCount; ++k) {
+ const int index = i * keyCount + k;
+ const int x = mInputXs[i];
+ const int y = mInputYs[i];
+ mDistanceCache[index] =
+ mProximityInfo->getNormalizedSquaredDistanceFromCenterFloat(k, x, y);
+ }
+ }
+ }
+
+ // end
+ ///////////////////////
+
+ memset(mNormalizedSquaredDistances, NOT_A_DISTANCE, sizeof(mNormalizedSquaredDistances));
+ memset(mPrimaryInputWord, 0, sizeof(mPrimaryInputWord));
+ mTouchPositionCorrectionEnabled = mInputSize > 0 && mHasTouchPositionCorrectionData
+ && xCoordinates && yCoordinates && !isGeometric;
+ if (!isGeometric && pointerId == 0) {
+ for (int i = 0; i < inputSize; ++i) {
+ mPrimaryInputWord[i] = getPrimaryCharAt(i);
+ }
+
+ for (int i = 0; i < mInputSize && mTouchPositionCorrectionEnabled; ++i) {
+ const int *proximityChars = getProximityCharsAt(i);
+ const int primaryKey = proximityChars[0];
+ const int x = xCoordinates[i];
+ const int y = yCoordinates[i];
+ if (DEBUG_PROXIMITY_CHARS) {
+ int a = x + y + primaryKey;
+ a += 0;
+ AKLOGI("--- Primary = %c, x = %d, y = %d", primaryKey, x, y);
+ }
+ for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL && proximityChars[j] > 0; ++j) {
+ const int currentChar = proximityChars[j];
+ const float squaredDistance =
+ hasInputCoordinates() ? calculateNormalizedSquaredDistance(
+ mProximityInfo->getKeyIndex(currentChar), i) :
+ NOT_A_DISTANCE_FLOAT;
+ if (squaredDistance >= 0.0f) {
+ mNormalizedSquaredDistances[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j] =
+ (int) (squaredDistance * NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR);
+ } else {
+ mNormalizedSquaredDistances[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j] =
+ (j == 0) ? EQUIVALENT_CHAR_WITHOUT_DISTANCE_INFO :
+ PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO;
+ }
+ if (DEBUG_PROXIMITY_CHARS) {
+ AKLOGI("--- Proximity (%d) = %c", j, currentChar);
+ }
+ }
+ }
+ }
+}
+
+// Calculating point to key distance for all near keys and returning the distance between
+// the given point and the nearest key position.
+float ProximityInfoState::updateNearKeysDistances(const int x, const int y,
+ NearKeysDistanceMap *const currentNearKeysDistances) {
+ static const float NEAR_KEY_THRESHOLD = 10.0f;
+
+ currentNearKeysDistances->clear();
+ const int keyCount = mProximityInfo->getKeyCount();
+ float nearestKeyDistance = mMaxPointToKeyLength;
+ for (int k = 0; k < keyCount; ++k) {
+ const float dist = mProximityInfo->getNormalizedSquaredDistanceFromCenterFloat(k, x, y);
+ if (dist < NEAR_KEY_THRESHOLD) {
+ currentNearKeysDistances->insert(std::pair<int, float>(k, dist));
+ }
+ if (nearestKeyDistance > dist) {
+ nearestKeyDistance = dist;
+ }
+ }
+ return nearestKeyDistance;
+}
+
+// Check if previous point is at local minimum position to near keys.
+bool ProximityInfoState::isPrevLocalMin(const NearKeysDistanceMap *const currentNearKeysDistances,
+ const NearKeysDistanceMap *const prevNearKeysDistances,
+ const NearKeysDistanceMap *const prevPrevNearKeysDistances) const {
+ static const float MARGIN = 0.5f;
+
+ for (NearKeysDistanceMap::const_iterator it = prevNearKeysDistances->begin();
+ it != prevNearKeysDistances->end(); ++it) {
+ NearKeysDistanceMap::const_iterator itPP = prevPrevNearKeysDistances->find(it->first);
+ NearKeysDistanceMap::const_iterator itC = currentNearKeysDistances->find(it->first);
+ if ((itPP == prevPrevNearKeysDistances->end() || itPP->second > it->second + MARGIN)
+ && (itC == currentNearKeysDistances->end() || itC->second > it->second + MARGIN)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Calculating a point score that indicates usefulness of the point.
+float ProximityInfoState::getPointScore(
+ const int x, const int y, const int time, const bool lastPoint, const float nearest,
+ const NearKeysDistanceMap *const currentNearKeysDistances,
+ const NearKeysDistanceMap *const prevNearKeysDistances,
+ const NearKeysDistanceMap *const prevPrevNearKeysDistances) const {
+ static const float BASE_SAMPLE_RATE_SCALE = 0.1f;
+ static const float SAVE_DISTANCE_SCALE = 12.0f;
+ static const float SAVE_DISTANCE_SCORE = 2.0f;
+ static const float SKIP_DISTANCE_SCALE = 1.5f;
+ static const float SKIP_DISTANCE_SCORE = -1.0f;
+ static const float CHECK_LOCALMIN_DISTANCE_THRESHOLD_SCALE = 2.5f;
+ static const float CHECK_LOCALMIN_DISTANCE_SCORE = -1.0f;
+ static const float STRAIGHT_ANGLE_THRESHOLD = M_PI_F / 32.0f;
+ static const float STRAIGHT_SKIP_DISTANCE_THRESHOLD_SCALE = 4.0f;
+ static const float STRAIGHT_SKIP_NEAREST_DISTANCE_THRESHOLD = 0.5f;
+ static const float STRAIGHT_SKIP_SCORE = -1.0f;
+
+ const std::size_t size = mInputXs.size();
+ if (size <= 1) {
+ return 0;
+ }
+ const float baseSampleRate = mProximityInfo->getMostCommonKeyWidth() * BASE_SAMPLE_RATE_SCALE;
+ const float distNext = getDistanceFloat(x, y, mInputXs.back(), mInputYs.back());
+ const float distPrev = getDistanceFloat(mInputXs.back(), mInputYs.back(),
+ mInputXs[size - 2], mInputYs[size - 2]);
+ float score = 0.0f;
+
+ // Sum of distances
+ if (distPrev + distNext > baseSampleRate * SAVE_DISTANCE_SCALE) {
+ score += SAVE_DISTANCE_SCORE;
+ }
+ // Distance
+ if (distPrev < baseSampleRate * SKIP_DISTANCE_SCALE) {
+ score += SKIP_DISTANCE_SCORE;
+ }
+ // Location
+ if (!isPrevLocalMin(currentNearKeysDistances, currentNearKeysDistances,
+ prevPrevNearKeysDistances)) {
+ if (distPrev < baseSampleRate * CHECK_LOCALMIN_DISTANCE_THRESHOLD_SCALE) {
+ score += CHECK_LOCALMIN_DISTANCE_SCORE;
+ }
+ }
+ // Angle
+ const float angle1 = getAngle(x, y, mInputXs.back(), mInputYs.back());
+ const float angle2 = getAngle(mInputXs.back(), mInputYs.back(),
+ mInputXs[size - 2], mInputYs[size - 2]);
+ if (getAngleDiff(angle1, angle2) < STRAIGHT_ANGLE_THRESHOLD) {
+ if (nearest > STRAIGHT_SKIP_NEAREST_DISTANCE_THRESHOLD
+ && distPrev < baseSampleRate * STRAIGHT_SKIP_DISTANCE_THRESHOLD_SCALE) {
+ score += STRAIGHT_SKIP_SCORE;
+ }
+ }
+ return score;
+}
+
+// Sampling touch point and pushing information to vectors.
+// Returning if previous point is popped or not.
+bool ProximityInfoState::pushTouchPoint(const int nodeChar, int x, int y, const int time,
+ const bool sample, const bool isLastPoint,
+ NearKeysDistanceMap *const currentNearKeysDistances,
+ const NearKeysDistanceMap *const prevNearKeysDistances,
+ const NearKeysDistanceMap *const prevPrevNearKeysDistances) {
+ static const float LAST_POINT_SKIP_DISTANCE_SCALE = 0.25f;
+
+ uint32_t size = mInputXs.size();
+ bool popped = false;
+ if (nodeChar < 0 && sample) {
+ const float nearest = updateNearKeysDistances(x, y, currentNearKeysDistances);
+ const float score = getPointScore(x, y, time, isLastPoint, nearest,
+ currentNearKeysDistances, prevNearKeysDistances, prevPrevNearKeysDistances);
+ if (score < 0) {
+ // Pop previous point because it would be useless.
+ mInputXs.pop_back();
+ mInputYs.pop_back();
+ mTimes.pop_back();
+ mLengthCache.pop_back();
+ size = mInputXs.size();
+ popped = true;
+ } else {
+ popped = false;
+ }
+ // Check if the last point should be skipped.
+ if (isLastPoint) {
+ if (size > 0 && getDistanceFloat(x, y, mInputXs.back(), mInputYs.back())
+ < mProximityInfo->getMostCommonKeyWidth() * LAST_POINT_SKIP_DISTANCE_SCALE) {
+ return popped;
+ } else if (size > 1) {
+ int minChar = 0;
+ float minDist = mMaxPointToKeyLength;
+ for (NearKeysDistanceMap::const_iterator it = currentNearKeysDistances->begin();
+ it != currentNearKeysDistances->end(); ++it) {
+ if(minDist > it->second){
+ minChar = it->first;
+ minDist = it->second;
+ }
+ }
+ NearKeysDistanceMap::const_iterator itPP =
+ prevNearKeysDistances->find(minChar);
+ if (itPP != prevNearKeysDistances->end() && minDist > itPP->second) {
+ return popped;
+ }
+ }
+ }
+ }
+
+ if (nodeChar >= 0 && (x < 0 || y < 0)) {
+ const int keyId = mProximityInfo->getKeyIndex(nodeChar);
+ if (keyId >= 0) {
+ x = mProximityInfo->getKeyCenterXOfIdG(keyId);
+ y = mProximityInfo->getKeyCenterYOfIdG(keyId);
+ }
+ }
+
+ // Pushing point information.
+ if (size > 0) {
+ mLengthCache.push_back(
+ mLengthCache.back() + getDistanceInt(x, y, mInputXs.back(), mInputYs.back()));
+ } else {
+ mLengthCache.push_back(0);
+ }
+ mInputXs.push_back(x);
+ mInputYs.push_back(y);
+ mTimes.push_back(time);
+ return popped;
}
float ProximityInfoState::calculateNormalizedSquaredDistance(
@@ -116,7 +352,7 @@
if (!mProximityInfo->hasSweetSpotData(keyIndex)) {
return NOT_A_DISTANCE_FLOAT;
}
- if (NOT_A_COORDINATE == mInputXCoordinates[inputIndex]) {
+ if (NOT_A_COORDINATE == mInputXs[inputIndex]) {
return NOT_A_DISTANCE_FLOAT;
}
const float squaredDistance = calculateSquaredDistanceFromSweetSpotCenter(
@@ -125,12 +361,37 @@
return squaredDistance / squaredRadius;
}
+int ProximityInfoState::getDuration(const int index) const {
+ if (mInputSize > 0 && index > 0 && index < static_cast<int>(mInputSize) - 1) {
+ return mTimes[index + 1] - mTimes[index - 1];
+ }
+ return 0;
+}
+
+float ProximityInfoState::getPointToKeyLength(int inputIndex, int charCode, float scale) {
+ const int keyId = mProximityInfo->getKeyIndex(charCode);
+ if (keyId >= 0) {
+ const int index = inputIndex * mProximityInfo->getKeyCount() + keyId;
+ return min(mDistanceCache[index] * scale, mMaxPointToKeyLength);
+ }
+ return 0;
+}
+
+int ProximityInfoState::getKeyKeyDistance(int key0, int key1) {
+ return mProximityInfo->getKeyKeyDistanceG(key0, key1);
+}
+
+int ProximityInfoState::getSpaceY() {
+ const int keyId = mProximityInfo->getKeyIndex(' ');
+ return mProximityInfo->getKeyCenterYOfIdG(keyId);
+}
+
float ProximityInfoState::calculateSquaredDistanceFromSweetSpotCenter(
const int keyIndex, const int inputIndex) const {
const float sweetSpotCenterX = mProximityInfo->getSweetSpotCenterXAt(keyIndex);
const float sweetSpotCenterY = mProximityInfo->getSweetSpotCenterYAt(keyIndex);
- const float inputX = static_cast<float>(mInputXCoordinates[inputIndex]);
- const float inputY = static_cast<float>(mInputYCoordinates[inputIndex]);
+ const float inputX = static_cast<float>(mInputXs[inputIndex]);
+ const float inputY = static_cast<float>(mInputYs[inputIndex]);
return square(inputX - sweetSpotCenterX) + square(inputY - sweetSpotCenterY);
}
} // namespace latinime
diff --git a/native/jni/src/proximity_info_state.h b/native/jni/src/proximity_info_state.h
index 474c407..746b9c9 100644
--- a/native/jni/src/proximity_info_state.h
+++ b/native/jni/src/proximity_info_state.h
@@ -17,11 +17,14 @@
#ifndef LATINIME_PROXIMITY_INFO_STATE_H
#define LATINIME_PROXIMITY_INFO_STATE_H
+#include <cstring> // for memset()
#include <stdint.h>
#include <string>
+#include <vector>
#include "char_utils.h"
#include "defines.h"
+#include "hash_map_compat.h"
namespace latinime {
@@ -40,18 +43,27 @@
/////////////////////////////////////////
// Defined in proximity_info_state.cpp //
/////////////////////////////////////////
- void initInputParams(
- const ProximityInfo *proximityInfo, const int32_t *inputCodes, const int inputLength,
- const int *xCoordinates, const int *yCoordinates);
+ void initInputParams(const int pointerId, const float maxPointToKeyLength,
+ const ProximityInfo *proximityInfo, const int32_t *const inputCodes,
+ const int inputSize, const int *xCoordinates, const int *yCoordinates,
+ const int *const times, const int *const pointerIds, const bool isGeometric);
/////////////////////////////////////////
// Defined here //
/////////////////////////////////////////
- ProximityInfoState() {};
- inline const int *getProximityCharsAt(const int index) const {
- return mInputCodes + (index * MAX_PROXIMITY_CHARS_SIZE_INTERNAL);
+ ProximityInfoState()
+ : mProximityInfo(0), mMaxPointToKeyLength(0),
+ mHasTouchPositionCorrectionData(false), mMostCommonKeyWidthSquare(0), mLocaleStr(),
+ mKeyCount(0), mCellHeight(0), mCellWidth(0), mGridHeight(0), mGridWidth(0),
+ mInputXs(), mInputYs(), mTimes(), mDistanceCache(), mLengthCache(),
+ mTouchPositionCorrectionEnabled(false), mInputSize(0) {
+ memset(mInputCodes, 0, sizeof(mInputCodes));
+ memset(mNormalizedSquaredDistances, 0, sizeof(mNormalizedSquaredDistances));
+ memset(mPrimaryInputWord, 0, sizeof(mPrimaryInputWord));
}
+ virtual ~ProximityInfoState() {}
+
inline unsigned short getPrimaryCharAt(const int index) const {
return getProximityCharsAt(index)[0];
}
@@ -68,14 +80,14 @@
}
inline bool existsAdjacentProximityChars(const int index) const {
- if (index < 0 || index >= mInputLength) return false;
+ if (index < 0 || index >= mInputSize) return false;
const int currentChar = getPrimaryCharAt(index);
const int leftIndex = index - 1;
if (leftIndex >= 0 && existsCharInProximityAt(leftIndex, currentChar)) {
return true;
}
const int rightIndex = index + 1;
- if (rightIndex < mInputLength && existsCharInProximityAt(rightIndex, currentChar)) {
+ if (rightIndex < mInputSize && existsCharInProximityAt(rightIndex, currentChar)) {
return true;
}
return false;
@@ -161,7 +173,7 @@
}
inline bool sameAsTyped(const unsigned short *word, int length) const {
- if (length != mInputLength) {
+ if (length != mInputSize) {
return false;
}
const int *inputCodes = mInputCodes;
@@ -175,8 +187,37 @@
return true;
}
+ int getDuration(const int index) const;
+
+ bool isUsed() const {
+ return mInputSize > 0;
+ }
+
+ uint32_t size() const {
+ return mInputSize;
+ }
+
+ int getInputX(int index) const {
+ return mInputXs[index];
+ }
+
+ int getInputY(int index) const {
+ return mInputYs[index];
+ }
+
+ int getLengthCache(int index) const {
+ return mLengthCache[index];
+ }
+
+ float getPointToKeyLength(int inputIndex, int charCode, float scale);
+
+ int getKeyKeyDistance(int key0, int key1);
+
+ int getSpaceY();
+
private:
DISALLOW_COPY_AND_ASSIGN(ProximityInfoState);
+ typedef hash_map_compat<int, float> NearKeysDistanceMap;
/////////////////////////////////////////
// Defined in proximity_info_state.cpp //
/////////////////////////////////////////
@@ -185,17 +226,38 @@
float calculateSquaredDistanceFromSweetSpotCenter(
const int keyIndex, const int inputIndex) const;
+ bool pushTouchPoint(const int nodeChar, int x, int y, const int time,
+ const bool sample, const bool isLastPoint,
+ NearKeysDistanceMap *const currentNearKeysDistances,
+ const NearKeysDistanceMap *const prevNearKeysDistances,
+ const NearKeysDistanceMap *const prevPrevNearKeysDistances);
/////////////////////////////////////////
// Defined here //
/////////////////////////////////////////
inline float square(const float x) const { return x * x; }
bool hasInputCoordinates() const {
- return mInputXCoordinates && mInputYCoordinates;
+ return mInputXs.size() > 0 && mInputYs.size() > 0;
}
+ inline const int *getProximityCharsAt(const int index) const {
+ return mInputCodes + (index * MAX_PROXIMITY_CHARS_SIZE_INTERNAL);
+ }
+
+ float updateNearKeysDistances(const int x, const int y,
+ NearKeysDistanceMap *const currentNearKeysDistances);
+ bool isPrevLocalMin(const NearKeysDistanceMap *const currentNearKeysDistances,
+ const NearKeysDistanceMap *const prevNearKeysDistances,
+ const NearKeysDistanceMap *const prevPrevNearKeysDistances) const;
+ float getPointScore(
+ const int x, const int y, const int time, const bool last, const float nearest,
+ const NearKeysDistanceMap *const currentNearKeysDistances,
+ const NearKeysDistanceMap *const prevNearKeysDistances,
+ const NearKeysDistanceMap *const prevPrevNearKeysDistances) const;
+
// const
const ProximityInfo *mProximityInfo;
+ float mMaxPointToKeyLength;
bool mHasTouchPositionCorrectionData;
int mMostCommonKeyWidthSquare;
std::string mLocaleStr;
@@ -205,12 +267,15 @@
int mGridHeight;
int mGridWidth;
- const int *mInputXCoordinates;
- const int *mInputYCoordinates;
+ std::vector<int> mInputXs;
+ std::vector<int> mInputYs;
+ std::vector<int> mTimes;
+ std::vector<float> mDistanceCache;
+ std::vector<int> mLengthCache;
bool mTouchPositionCorrectionEnabled;
int32_t mInputCodes[MAX_PROXIMITY_CHARS_SIZE_INTERNAL * MAX_WORD_LENGTH_INTERNAL];
int mNormalizedSquaredDistances[MAX_PROXIMITY_CHARS_SIZE_INTERNAL * MAX_WORD_LENGTH_INTERNAL];
- int mInputLength;
+ int mInputSize;
unsigned short mPrimaryInputWord[MAX_WORD_LENGTH_INTERNAL];
};
} // namespace latinime
diff --git a/native/jni/src/terminal_attributes.h b/native/jni/src/terminal_attributes.h
index 1ae9c7c..9ff2772 100644
--- a/native/jni/src/terminal_attributes.h
+++ b/native/jni/src/terminal_attributes.h
@@ -30,13 +30,13 @@
public:
class ShortcutIterator {
const uint8_t *const mDict;
- bool mHasNextShortcutTarget;
int mPos;
+ bool mHasNextShortcutTarget;
public:
- ShortcutIterator(const uint8_t *dict, const int pos, const uint8_t flags) : mDict(dict),
- mPos(pos) {
- mHasNextShortcutTarget = (0 != (flags & BinaryFormat::FLAG_HAS_SHORTCUT_TARGETS));
+ ShortcutIterator(const uint8_t *dict, const int pos, const uint8_t flags)
+ : mDict(dict), mPos(pos),
+ mHasNextShortcutTarget(0 != (flags & BinaryFormat::FLAG_HAS_SHORTCUT_TARGETS)) {
}
inline bool hasNextShortcutTarget() const {
@@ -62,13 +62,6 @@
}
};
- private:
- DISALLOW_IMPLICIT_CONSTRUCTORS(TerminalAttributes);
- const uint8_t *const mDict;
- const uint8_t mFlags;
- const int mStartPos;
-
- public:
TerminalAttributes(const uint8_t *const dict, const uint8_t flags, const int pos) :
mDict(dict), mFlags(flags), mStartPos(pos) {
}
@@ -78,6 +71,16 @@
// skipped quickly, so we ignore it.
return ShortcutIterator(mDict, mStartPos + BinaryFormat::SHORTCUT_LIST_SIZE_SIZE, mFlags);
}
+
+ bool isBlacklistedOrNotAWord() const {
+ return mFlags & (BinaryFormat::FLAG_IS_BLACKLISTED | BinaryFormat::FLAG_IS_NOT_A_WORD);
+ }
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(TerminalAttributes);
+ const uint8_t *const mDict;
+ const uint8_t mFlags;
+ const int mStartPos;
};
} // namespace latinime
#endif // LATINIME_TERMINAL_ATTRIBUTES_H
diff --git a/native/jni/src/unigram_dictionary.cpp b/native/jni/src/unigram_dictionary.cpp
index cc6d39a..d4c51df 100644
--- a/native/jni/src/unigram_dictionary.cpp
+++ b/native/jni/src/unigram_dictionary.cpp
@@ -237,7 +237,7 @@
void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo,
const int *xcoordinates, const int *ycoordinates, const int *codes,
- const int inputLength, const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
+ const int inputSize, const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
const bool useFullEditDistance, Correction *correction,
WordsPriorityQueuePool *queuePool) const {
@@ -247,7 +247,7 @@
PROF_START(1);
getOneWordSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, bigramMap, bigramFilter,
- useFullEditDistance, inputLength, correction, queuePool);
+ useFullEditDistance, inputSize, correction, queuePool);
PROF_END(1);
PROF_START(2);
@@ -263,7 +263,7 @@
WordsPriorityQueue *masterQueue = queuePool->getMasterQueue();
if (masterQueue->size() > 0) {
float nsForMaster = masterQueue->getHighestNormalizedScore(
- correction->getPrimaryInputWord(), inputLength, 0, 0, 0);
+ correction->getPrimaryInputWord(), inputSize, 0, 0, 0);
hasAutoCorrectionCandidate = (nsForMaster > START_TWO_WORDS_CORRECTION_THRESHOLD);
}
PROF_END(4);
@@ -271,9 +271,9 @@
PROF_START(5);
// Multiple word suggestions
if (SUGGEST_MULTIPLE_WORDS
- && inputLength >= MIN_USER_TYPED_LENGTH_FOR_MULTIPLE_WORD_SUGGESTION) {
+ && inputSize >= MIN_USER_TYPED_LENGTH_FOR_MULTIPLE_WORD_SUGGESTION) {
getSplitMultipleWordsSuggestions(proximityInfo, xcoordinates, ycoordinates, codes,
- useFullEditDistance, inputLength, correction, queuePool,
+ useFullEditDistance, inputSize, correction, queuePool,
hasAutoCorrectionCandidate);
}
PROF_END(5);
@@ -304,15 +304,15 @@
}
void UnigramDictionary::initSuggestions(ProximityInfo *proximityInfo, const int *xCoordinates,
- const int *yCoordinates, const int *codes, const int inputLength,
+ const int *yCoordinates, const int *codes, const int inputSize,
Correction *correction) const {
if (DEBUG_DICT) {
AKLOGI("initSuggest");
- DUMP_WORD_INT(codes, inputLength);
+ DUMP_WORD_INT(codes, inputSize);
}
- correction->initInputParams(proximityInfo, codes, inputLength, xCoordinates, yCoordinates);
- const int maxDepth = min(inputLength * MAX_DEPTH_MULTIPLIER, MAX_WORD_LENGTH);
- correction->initCorrection(proximityInfo, inputLength, maxDepth);
+ correction->initInputParams(proximityInfo, codes, inputSize, xCoordinates, yCoordinates);
+ const int maxDepth = min(inputSize * MAX_DEPTH_MULTIPLIER, MAX_WORD_LENGTH);
+ correction->initCorrection(proximityInfo, inputSize, maxDepth);
}
static const char QUOTE = '\'';
@@ -321,15 +321,15 @@
void UnigramDictionary::getOneWordSuggestions(ProximityInfo *proximityInfo,
const int *xcoordinates, const int *ycoordinates, const int *codes,
const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
- const bool useFullEditDistance, const int inputLength,
+ const bool useFullEditDistance, const int inputSize,
Correction *correction, WordsPriorityQueuePool *queuePool) const {
- initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, inputLength, correction);
- getSuggestionCandidates(useFullEditDistance, inputLength, bigramMap, bigramFilter, correction,
+ initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, inputSize, correction);
+ getSuggestionCandidates(useFullEditDistance, inputSize, bigramMap, bigramFilter, correction,
queuePool, true /* doAutoCompletion */, DEFAULT_MAX_ERRORS, FIRST_WORD_INDEX);
}
void UnigramDictionary::getSuggestionCandidates(const bool useFullEditDistance,
- const int inputLength, const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
+ const int inputSize, const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
Correction *correction, WordsPriorityQueuePool *queuePool,
const bool doAutoCompletion, const int maxErrors, const int currentWordIndex) const {
uint8_t totalTraverseCount = correction->pushAndGetTotalTraverseCount();
@@ -351,7 +351,7 @@
int childCount = BinaryFormat::getGroupCountAndForwardPointer(DICT_ROOT, &rootPosition);
int outputIndex = 0;
- correction->initCorrectionState(rootPosition, childCount, (inputLength <= 0));
+ correction->initCorrectionState(rootPosition, childCount, (inputSize <= 0));
// Depth first search
while (outputIndex >= 0) {
@@ -390,43 +390,44 @@
WordsPriorityQueue *masterQueue = queuePool->getMasterQueue();
const int finalProbability =
correction->getFinalProbability(probability, &wordPointer, &wordLength);
- if (finalProbability != NOT_A_PROBABILITY) {
- if (0 != finalProbability) {
- // If the probability is 0, we don't want to add this word. However we still
- // want to add its shortcuts (including a possible whitelist entry) if any.
- addWord(wordPointer, wordLength, finalProbability, masterQueue,
- Dictionary::KIND_CORRECTION);
- }
- const int shortcutProbability = finalProbability > 0 ? finalProbability - 1 : 0;
- // Please note that the shortcut candidates will be added to the master queue only.
- TerminalAttributes::ShortcutIterator iterator =
- terminalAttributes.getShortcutIterator();
- while (iterator.hasNextShortcutTarget()) {
- // TODO: addWord only supports weak ordering, meaning we have no means
- // to control the order of the shortcuts relative to one another or to the word.
- // We need to either modulate the probability of each shortcut according
- // to its own shortcut probability or to make the queue
- // so that the insert order is protected inside the queue for words
- // with the same score. For the moment we use -1 to make sure the shortcut will
- // never be in front of the word.
- uint16_t shortcutTarget[MAX_WORD_LENGTH_INTERNAL];
- int shortcutFrequency;
- const int shortcutTargetStringLength = iterator.getNextShortcutTarget(
- MAX_WORD_LENGTH_INTERNAL, shortcutTarget, &shortcutFrequency);
- int shortcutScore;
- int kind;
- if (shortcutFrequency == BinaryFormat::WHITELIST_SHORTCUT_FREQUENCY
- && correction->sameAsTyped()) {
- shortcutScore = S_INT_MAX;
- kind = Dictionary::KIND_WHITELIST;
- } else {
- shortcutScore = shortcutProbability;
- kind = Dictionary::KIND_CORRECTION;
- }
- addWord(shortcutTarget, shortcutTargetStringLength, shortcutScore,
- masterQueue, kind);
+ if (0 != finalProbability && !terminalAttributes.isBlacklistedOrNotAWord()) {
+ // If the probability is 0, we don't want to add this word. However we still
+ // want to add its shortcuts (including a possible whitelist entry) if any.
+ // Furthermore, if this is not a word (shortcut only for example) or a blacklisted
+ // entry then we never want to suggest this.
+ addWord(wordPointer, wordLength, finalProbability, masterQueue,
+ Dictionary::KIND_CORRECTION);
+ }
+
+ const int shortcutProbability = finalProbability > 0 ? finalProbability - 1 : 0;
+ // Please note that the shortcut candidates will be added to the master queue only.
+ TerminalAttributes::ShortcutIterator iterator =
+ terminalAttributes.getShortcutIterator();
+ while (iterator.hasNextShortcutTarget()) {
+ // TODO: addWord only supports weak ordering, meaning we have no means
+ // to control the order of the shortcuts relative to one another or to the word.
+ // We need to either modulate the probability of each shortcut according
+ // to its own shortcut probability or to make the queue
+ // so that the insert order is protected inside the queue for words
+ // with the same score. For the moment we use -1 to make sure the shortcut will
+ // never be in front of the word.
+ uint16_t shortcutTarget[MAX_WORD_LENGTH_INTERNAL];
+ int shortcutFrequency;
+ const int shortcutTargetStringLength = iterator.getNextShortcutTarget(
+ MAX_WORD_LENGTH_INTERNAL, shortcutTarget, &shortcutFrequency);
+ int shortcutScore;
+ int kind;
+ if (shortcutFrequency == BinaryFormat::WHITELIST_SHORTCUT_FREQUENCY
+ && correction->sameAsTyped()) {
+ shortcutScore = S_INT_MAX;
+ kind = Dictionary::KIND_WHITELIST;
+ } else {
+ shortcutScore = shortcutProbability;
+ kind = Dictionary::KIND_CORRECTION;
}
+ addWord(shortcutTarget, shortcutTargetStringLength, shortcutScore,
+ masterQueue, kind);
}
}
@@ -447,7 +448,7 @@
int UnigramDictionary::getSubStringSuggestion(
ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates,
const int *codes, const bool useFullEditDistance, Correction *correction,
- WordsPriorityQueuePool *queuePool, const int inputLength,
+ WordsPriorityQueuePool *queuePool, const int inputSize,
const bool hasAutoCorrectionCandidate, const int currentWordIndex,
const int inputWordStartPos, const int inputWordLength,
const int outputWordStartPos, const bool isSpaceProximity, int *freqArray,
@@ -498,7 +499,7 @@
int nextWordLength = 0;
// TODO: Optimize init suggestion
initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes,
- inputLength, correction);
+ inputSize, correction);
unsigned short word[MAX_WORD_LENGTH_INTERNAL];
int freq = getMostFrequentWordLike(
@@ -567,7 +568,7 @@
*outputWordLength = tempOutputWordLength;
}
- if ((inputWordStartPos + inputWordLength) < inputLength) {
+ if ((inputWordStartPos + inputWordLength) < inputSize) {
if (outputWordStartPos + nextWordLength >= MAX_WORD_LENGTH) {
return FLAG_MULTIPLE_SUGGEST_SKIP;
}
@@ -586,7 +587,7 @@
freqArray[i], wordLengthArray[i]);
}
AKLOGI("Split two words: freq = %d, length = %d, %d, isSpace ? %d", pairFreq,
- inputLength, tempOutputWordLength, isSpaceProximity);
+ inputSize, tempOutputWordLength, isSpaceProximity);
}
addWord(outputWord, tempOutputWordLength, pairFreq, queuePool->getMasterQueue(),
Dictionary::KIND_CORRECTION);
@@ -596,7 +597,7 @@
void UnigramDictionary::getMultiWordsSuggestionRec(ProximityInfo *proximityInfo,
const int *xcoordinates, const int *ycoordinates, const int *codes,
- const bool useFullEditDistance, const int inputLength,
+ const bool useFullEditDistance, const int inputSize,
Correction *correction, WordsPriorityQueuePool *queuePool,
const bool hasAutoCorrectionCandidate, const int startInputPos, const int startWordIndex,
const int outputWordLength, int *freqArray, int *wordLengthArray,
@@ -607,11 +608,11 @@
}
if (startWordIndex >= 1
&& (hasAutoCorrectionCandidate
- || inputLength < MIN_INPUT_LENGTH_FOR_THREE_OR_MORE_WORDS_CORRECTION)) {
+ || inputSize < MIN_INPUT_LENGTH_FOR_THREE_OR_MORE_WORDS_CORRECTION)) {
// Do not suggest 3+ words if already has auto correction candidate
return;
}
- for (int i = startInputPos + 1; i < inputLength; ++i) {
+ for (int i = startInputPos + 1; i < inputSize; ++i) {
if (DEBUG_CORRECTION_FREQ) {
AKLOGI("Multi words(%d), start in %d sep %d start out %d",
startWordIndex, startInputPos, i, outputWordLength);
@@ -622,7 +623,7 @@
int inputWordStartPos = startInputPos;
int inputWordLength = i - startInputPos;
const int suggestionFlag = getSubStringSuggestion(proximityInfo, xcoordinates, ycoordinates,
- codes, useFullEditDistance, correction, queuePool, inputLength,
+ codes, useFullEditDistance, correction, queuePool, inputSize,
hasAutoCorrectionCandidate, startWordIndex, inputWordStartPos, inputWordLength,
outputWordLength, true /* not used */, freqArray, wordLengthArray, outputWord,
&tempOutputWordLength);
@@ -639,14 +640,14 @@
// Next word
// Missing space
inputWordStartPos = i;
- inputWordLength = inputLength - i;
+ inputWordLength = inputSize - i;
if(getSubStringSuggestion(proximityInfo, xcoordinates, ycoordinates, codes,
- useFullEditDistance, correction, queuePool, inputLength, hasAutoCorrectionCandidate,
+ useFullEditDistance, correction, queuePool, inputSize, hasAutoCorrectionCandidate,
startWordIndex + 1, inputWordStartPos, inputWordLength, tempOutputWordLength,
false /* missing space */, freqArray, wordLengthArray, outputWord, 0)
!= FLAG_MULTIPLE_SUGGEST_CONTINUE) {
getMultiWordsSuggestionRec(proximityInfo, xcoordinates, ycoordinates, codes,
- useFullEditDistance, inputLength, correction, queuePool,
+ useFullEditDistance, inputSize, correction, queuePool,
hasAutoCorrectionCandidate, inputWordStartPos, startWordIndex + 1,
tempOutputWordLength, freqArray, wordLengthArray, outputWord);
}
@@ -669,7 +670,7 @@
AKLOGI("Do mistyped space correction");
}
getSubStringSuggestion(proximityInfo, xcoordinates, ycoordinates, codes,
- useFullEditDistance, correction, queuePool, inputLength, hasAutoCorrectionCandidate,
+ useFullEditDistance, correction, queuePool, inputSize, hasAutoCorrectionCandidate,
startWordIndex + 1, inputWordStartPos, inputWordLength, tempOutputWordLength,
true /* mistyped space */, freqArray, wordLengthArray, outputWord, 0);
}
@@ -677,10 +678,10 @@
void UnigramDictionary::getSplitMultipleWordsSuggestions(ProximityInfo *proximityInfo,
const int *xcoordinates, const int *ycoordinates, const int *codes,
- const bool useFullEditDistance, const int inputLength,
+ const bool useFullEditDistance, const int inputSize,
Correction *correction, WordsPriorityQueuePool *queuePool,
const bool hasAutoCorrectionCandidate) const {
- if (inputLength >= MAX_WORD_LENGTH) return;
+ if (inputSize >= MAX_WORD_LENGTH) return;
if (DEBUG_DICT) {
AKLOGI("--- Suggest multiple words");
}
@@ -693,7 +694,7 @@
const int startInputPos = 0;
const int startWordIndex = 0;
getMultiWordsSuggestionRec(proximityInfo, xcoordinates, ycoordinates, codes,
- useFullEditDistance, inputLength, correction, queuePool, hasAutoCorrectionCandidate,
+ useFullEditDistance, inputSize, correction, queuePool, hasAutoCorrectionCandidate,
startInputPos, startWordIndex, outputWordLength, freqArray, wordLengthArray,
outputWord);
}
@@ -701,13 +702,13 @@
// Wrapper for getMostFrequentWordLikeInner, which matches it to the previous
// interface.
inline int UnigramDictionary::getMostFrequentWordLike(const int startInputIndex,
- const int inputLength, Correction *correction, unsigned short *word) const {
- uint16_t inWord[inputLength];
+ const int inputSize, Correction *correction, unsigned short *word) const {
+ uint16_t inWord[inputSize];
- for (int i = 0; i < inputLength; ++i) {
+ for (int i = 0; i < inputSize; ++i) {
inWord[i] = (uint16_t)correction->getPrimaryCharAt(startInputIndex + i);
}
- return getMostFrequentWordLikeInner(inWord, inputLength, word);
+ return getMostFrequentWordLikeInner(inWord, inputSize, word);
}
// This function will take the position of a character array within a CharGroup,
@@ -842,6 +843,12 @@
return NOT_A_PROBABILITY;
}
const uint8_t flags = BinaryFormat::getFlagsAndForwardPointer(root, &pos);
+ if (flags & (BinaryFormat::FLAG_IS_BLACKLISTED | BinaryFormat::FLAG_IS_NOT_A_WORD)) {
+ // If this is not a word, or if it's a blacklisted entry, it should behave as
+ // having no frequency outside of the suggestion process (where it should be used
+ // for shortcuts).
+ return NOT_A_PROBABILITY;
+ }
const bool hasMultipleChars = (0 != (BinaryFormat::FLAG_HAS_MULTIPLE_CHARS & flags));
if (hasMultipleChars) {
pos = BinaryFormat::skipOtherCharacters(root, pos);
diff --git a/native/jni/src/unigram_dictionary.h b/native/jni/src/unigram_dictionary.h
index 6083f01..2c66222 100644
--- a/native/jni/src/unigram_dictionary.h
+++ b/native/jni/src/unigram_dictionary.h
@@ -53,7 +53,7 @@
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(UnigramDictionary);
void getWordSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
- const int *ycoordinates, const int *codes, const int inputLength,
+ const int *ycoordinates, const int *codes, const int inputSize,
const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
const bool useFullEditDistance, Correction *correction,
WordsPriorityQueuePool *queuePool) const;
@@ -72,16 +72,16 @@
Correction *correction) const;
void getOneWordSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
const int *ycoordinates, const int *codes, const std::map<int, int> *bigramMap,
- const uint8_t *bigramFilter, const bool useFullEditDistance, const int inputLength,
+ const uint8_t *bigramFilter, const bool useFullEditDistance, const int inputSize,
Correction *correction, WordsPriorityQueuePool *queuePool) const;
void getSuggestionCandidates(
- const bool useFullEditDistance, const int inputLength,
+ const bool useFullEditDistance, const int inputSize,
const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
Correction *correction, WordsPriorityQueuePool *queuePool, const bool doAutoCompletion,
const int maxErrors, const int currentWordIndex) const;
void getSplitMultipleWordsSuggestions(ProximityInfo *proximityInfo,
const int *xcoordinates, const int *ycoordinates, const int *codes,
- const bool useFullEditDistance, const int inputLength,
+ const bool useFullEditDistance, const int inputSize,
Correction *correction, WordsPriorityQueuePool *queuePool,
const bool hasAutoCorrectionCandidate) const;
void onTerminal(const int freq, const TerminalAttributes& terminalAttributes,
@@ -92,21 +92,21 @@
const uint8_t *bigramFilter, Correction *correction, int *newCount,
int *newChildPosition, int *nextSiblingPosition, WordsPriorityQueuePool *queuePool,
const int currentWordIndex) const;
- int getMostFrequentWordLike(const int startInputIndex, const int inputLength,
+ int getMostFrequentWordLike(const int startInputIndex, const int inputSize,
Correction *correction, unsigned short *word) const;
int getMostFrequentWordLikeInner(const uint16_t *const inWord, const int length,
short unsigned int *outWord) const;
int getSubStringSuggestion(
ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates,
const int *codes, const bool useFullEditDistance, Correction *correction,
- WordsPriorityQueuePool *queuePool, const int inputLength,
+ WordsPriorityQueuePool *queuePool, const int inputSize,
const bool hasAutoCorrectionCandidate, const int currentWordIndex,
const int inputWordStartPos, const int inputWordLength,
const int outputWordStartPos, const bool isSpaceProximity, int *freqArray,
int *wordLengthArray, unsigned short *outputWord, int *outputWordLength) const;
void getMultiWordsSuggestionRec(ProximityInfo *proximityInfo,
const int *xcoordinates, const int *ycoordinates, const int *codes,
- const bool useFullEditDistance, const int inputLength,
+ const bool useFullEditDistance, const int inputSize,
Correction *correction, WordsPriorityQueuePool *queuePool,
const bool hasAutoCorrectionCandidate, const int startPos, const int startWordIndex,
const int outputWordLength, int *freqArray, int *wordLengthArray,
diff --git a/native/jni/src/words_priority_queue.h b/native/jni/src/words_priority_queue.h
index 1e4e00a..19efa5d 100644
--- a/native/jni/src/words_priority_queue.h
+++ b/native/jni/src/words_priority_queue.h
@@ -44,17 +44,16 @@
}
};
- WordsPriorityQueue(int maxWords, int maxWordLength) :
- MAX_WORDS((unsigned int) maxWords), MAX_WORD_LENGTH(
- (unsigned int) maxWordLength) {
- mSuggestedWords = new SuggestedWord[maxWordLength];
+ WordsPriorityQueue(int maxWords, int maxWordLength)
+ : mSuggestions(), MAX_WORDS(static_cast<unsigned int>(maxWords)),
+ MAX_WORD_LENGTH(static_cast<unsigned int>(maxWordLength)),
+ mSuggestedWords(new SuggestedWord[maxWordLength]), mHighestSuggestedWord(0) {
for (int i = 0; i < maxWordLength; ++i) {
mSuggestedWords[i].mUsed = false;
}
- mHighestSuggestedWord = 0;
}
- ~WordsPriorityQueue() {
+ virtual ~WordsPriorityQueue() {
delete[] mSuggestedWords;
}
diff --git a/native/jni/src/words_priority_queue_pool.h b/native/jni/src/words_priority_queue_pool.h
index 3888729..c5de979 100644
--- a/native/jni/src/words_priority_queue_pool.h
+++ b/native/jni/src/words_priority_queue_pool.h
@@ -24,9 +24,10 @@
class WordsPriorityQueuePool {
public:
- WordsPriorityQueuePool(int mainQueueMaxWords, int subQueueMaxWords, int maxWordLength) {
- // Note: using placement new() requires the caller to call the destructor explicitly.
- mMasterQueue = new(mMasterQueueBuf) WordsPriorityQueue(mainQueueMaxWords, maxWordLength);
+ WordsPriorityQueuePool(int mainQueueMaxWords, int subQueueMaxWords, int maxWordLength)
+ // Note: using placement new() requires the caller to call the destructor explicitly.
+ : mMasterQueue(new(mMasterQueueBuf) WordsPriorityQueue(
+ mainQueueMaxWords, maxWordLength)) {
for (int i = 0, subQueueBufOffset = 0;
i < MULTIPLE_WORDS_SUGGESTION_MAX_WORDS * SUB_QUEUE_MAX_COUNT;
++i, subQueueBufOffset += sizeof(WordsPriorityQueue)) {
@@ -85,11 +86,11 @@
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(WordsPriorityQueuePool);
+ char mMasterQueueBuf[sizeof(WordsPriorityQueue)];
+ char mSubQueueBuf[SUB_QUEUE_MAX_COUNT * MULTIPLE_WORDS_SUGGESTION_MAX_WORDS
+ * sizeof(WordsPriorityQueue)];
WordsPriorityQueue *mMasterQueue;
WordsPriorityQueue *mSubQueues[SUB_QUEUE_MAX_COUNT * MULTIPLE_WORDS_SUGGESTION_MAX_WORDS];
- char mMasterQueueBuf[sizeof(WordsPriorityQueue)];
- char mSubQueueBuf[MULTIPLE_WORDS_SUGGESTION_MAX_WORDS
- * SUB_QUEUE_MAX_COUNT * sizeof(WordsPriorityQueue)];
};
} // namespace latinime
#endif // LATINIME_WORDS_PRIORITY_QUEUE_POOL_H
diff --git a/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderFixedOrderTests.java b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderFixedOrderTests.java
index 5c6c834..2a244a7 100644
--- a/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderFixedOrderTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderFixedOrderTests.java
@@ -18,7 +18,7 @@
import android.test.AndroidTestCase;
-import com.android.inputmethod.keyboard.MoreKeysKeyboard.Builder.MoreKeysKeyboardParams;
+import com.android.inputmethod.keyboard.MoreKeysKeyboard.MoreKeysKeyboardParams;
public class MoreKeysKeyboardBuilderFixedOrderTests extends AndroidTestCase {
private static final int WIDTH = 10;
diff --git a/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderTests.java b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderTests.java
index 31f0e0f..e6c76db 100644
--- a/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderTests.java
@@ -18,7 +18,7 @@
import android.test.AndroidTestCase;
-import com.android.inputmethod.keyboard.MoreKeysKeyboard.Builder.MoreKeysKeyboardParams;
+import com.android.inputmethod.keyboard.MoreKeysKeyboard.MoreKeysKeyboardParams;
public class MoreKeysKeyboardBuilderTests extends AndroidTestCase {
private static final int WIDTH = 10;
diff --git a/tests/src/com/android/inputmethod/keyboard/SpacebarTextTests.java b/tests/src/com/android/inputmethod/keyboard/SpacebarTextTests.java
index 87501ee..bc50439 100644
--- a/tests/src/com/android/inputmethod/keyboard/SpacebarTextTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/SpacebarTextTests.java
@@ -22,6 +22,7 @@
import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.latin.AdditionalSubtype;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.ImfUtils;
import com.android.inputmethod.latin.StringUtils;
import com.android.inputmethod.latin.SubtypeLocale;
@@ -31,7 +32,7 @@
public class SpacebarTextTests extends AndroidTestCase {
// Locale to subtypes list.
- private final ArrayList<InputMethodSubtype> mSubtypesList = new ArrayList<InputMethodSubtype>();
+ private final ArrayList<InputMethodSubtype> mSubtypesList = CollectionUtils.newArrayList();
private Resources mRes;
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java
index 3dc4543..1346c00 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java
@@ -19,6 +19,8 @@
import android.app.Instrumentation;
import android.test.InstrumentationTestCase;
+import com.android.inputmethod.latin.CollectionUtils;
+
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
@@ -42,7 +44,7 @@
}
private static String[] getAllResourceIdNames(final Class<?> resourceIdClass) {
- final ArrayList<String> names = new ArrayList<String>();
+ final ArrayList<String> names = CollectionUtils.newArrayList();
for (final Field field : resourceIdClass.getFields()) {
if (field.getType() == Integer.TYPE) {
names.add(field.getName());
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java
index 0b174a7..1ab5775 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java
@@ -23,7 +23,6 @@
import android.test.AndroidTestCase;
import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.internal.KeySpecParser.MoreKeySpec;
import java.util.Arrays;
import java.util.Locale;
diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictIOTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictIOTests.java
new file mode 100644
index 0000000..9b7f4a7
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/BinaryDictIOTests.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
+import com.android.inputmethod.latin.makedict.FusionDictionary;
+import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup;
+import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
+import com.android.inputmethod.latin.makedict.PendingAttribute;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Random;
+import java.util.Set;
+
+/**
+ * Unit tests for BinaryDictInputOutput
+ */
+public class BinaryDictIOTests extends AndroidTestCase {
+ private static final String TAG = BinaryDictIOTests.class.getSimpleName();
+ private static final int MAX_UNIGRAMS = 1000;
+ private static final int UNIGRAM_FREQ = 10;
+ private static final int BIGRAM_FREQ = 50;
+ private static final int TOLERANCE_OF_BIGRAM_FREQ = 5;
+
+ private static final String[] CHARACTERS =
+ {
+ "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
+ "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"
+ };
+
+ // Utilities for test
+ /**
+ * Generates a random word.
+ */
+ private String generateWord(final int value) {
+ final int lengthOfChars = CHARACTERS.length;
+ StringBuilder builder = new StringBuilder("a");
+ long lvalue = Math.abs((long)value);
+ while (lvalue > 0) {
+ builder.append(CHARACTERS[(int)(lvalue % lengthOfChars)]);
+ lvalue /= lengthOfChars;
+ }
+ return builder.toString();
+ }
+
+ private List<String> generateWords(final int number, final Random random) {
+ final Set<String> wordSet = CollectionUtils.newHashSet();
+ while (wordSet.size() < number) {
+ wordSet.add(generateWord(random.nextInt()));
+ }
+ return new ArrayList<String>(wordSet);
+ }
+
+ /**
+ * Adds unigrams to the dictionary.
+ */
+ private void addUnigrams(final int number,
+ final FusionDictionary dict,
+ final List<String> words) {
+ for (int i = 0; i < number; ++i) {
+ final String word = words.get(i);
+ dict.add(word, UNIGRAM_FREQ, null, false /* isNotAWord */);
+ }
+ }
+
+ private void addBigrams(final FusionDictionary dict,
+ final List<String> words,
+ final SparseArray<List<Integer>> bigrams) {
+ for (int i = 0; i < bigrams.size(); ++i) {
+ final int w1 = bigrams.keyAt(i);
+ for (int w2 : bigrams.valueAt(i)) {
+ dict.setBigram(words.get(w1), words.get(w2), BIGRAM_FREQ);
+ }
+ }
+ }
+
+ private long timeWritingDictToFile(final File file, final FusionDictionary dict) {
+
+ long now = -1, diff = -1;
+
+ try {
+ final FileOutputStream out = new FileOutputStream(file);
+
+ now = System.currentTimeMillis();
+ BinaryDictInputOutput.writeDictionaryBinary(out, dict, 2);
+ diff = System.currentTimeMillis() - now;
+
+ out.flush();
+ out.close();
+ } catch (IOException e) {
+ Log.e(TAG, "IO exception while writing file: " + e);
+ } catch (UnsupportedFormatException e) {
+ Log.e(TAG, "UnsupportedFormatException: " + e);
+ }
+
+ return diff;
+ }
+
+ private void checkDictionary(final FusionDictionary dict,
+ final List<String> words,
+ final SparseArray<List<Integer>> bigrams) {
+ assertNotNull(dict);
+
+ // check unigram
+ for (final String word : words) {
+ final CharGroup cg = FusionDictionary.findWordInTree(dict.mRoot, word);
+ assertNotNull(cg);
+ }
+
+ // check bigram
+ for (int i = 0; i < bigrams.size(); ++i) {
+ final int w1 = bigrams.keyAt(i);
+ for (final int w2 : bigrams.valueAt(i)) {
+ final CharGroup cg = FusionDictionary.findWordInTree(dict.mRoot, words.get(w1));
+ assertNotNull(words.get(w1) + "," + words.get(w2), cg.getBigram(words.get(w2)));
+ }
+ }
+ }
+
+ // Tests for readDictionaryBinary and writeDictionaryBinary
+
+ private long timeReadingAndCheckDict(final File file, final List<String> words,
+ final SparseArray<List<Integer>> bigrams) {
+
+ long now, diff = -1;
+
+ FileInputStream inStream = null;
+ try {
+ inStream = new FileInputStream(file);
+ final ByteBuffer buffer = inStream.getChannel().map(
+ FileChannel.MapMode.READ_ONLY, 0, file.length());
+
+ now = System.currentTimeMillis();
+
+ final FusionDictionary dict =
+ BinaryDictInputOutput.readDictionaryBinary(buffer, null);
+
+ diff = System.currentTimeMillis() - now;
+
+ checkDictionary(dict, words, bigrams);
+ return diff;
+
+ } catch (IOException e) {
+ Log.e(TAG, "raise IOException while reading file " + e);
+ } catch (UnsupportedFormatException e) {
+ Log.e(TAG, "Unsupported format: " + e);
+ } finally {
+ if (inStream != null) {
+ try {
+ inStream.close();
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
+ }
+
+ return diff;
+ }
+
+ private String runReadAndWrite(final List<String> words,
+ final SparseArray<List<Integer>> bigrams,
+ final String message) {
+ final FusionDictionary dict = new FusionDictionary(new Node(),
+ new FusionDictionary.DictionaryOptions(
+ new HashMap<String,String>(), false, false));
+
+ File file = null;
+ try {
+ file = File.createTempFile("runReadAndWrite", ".dict");
+ } catch (IOException e) {
+ Log.e(TAG, "IOException: " + e);
+ }
+
+ assertNotNull(file);
+
+ addUnigrams(words.size(), dict, words);
+ addBigrams(dict, words, bigrams);
+ // check original dictionary
+ checkDictionary(dict, words, bigrams);
+
+ final long write = timeWritingDictToFile(file, dict);
+ final long read = timeReadingAndCheckDict(file, words, bigrams);
+
+ return "PROF: read=" + read + "ms, write=" + write + "ms :" + message;
+ }
+
+ public void testReadAndWrite() {
+ final List<String> results = new ArrayList<String>();
+
+ final Random random = new Random(123456);
+ final List<String> words = generateWords(MAX_UNIGRAMS, random);
+ final SparseArray<List<Integer>> emptyArray = CollectionUtils.newSparseArray();
+
+ final SparseArray<List<Integer>> chain = CollectionUtils.newSparseArray();
+ for (int i = 0; i < words.size(); ++i) chain.put(i, new ArrayList<Integer>());
+ for (int i = 1; i < words.size(); ++i) chain.get(i-1).add(i);
+
+ final SparseArray<List<Integer>> star = CollectionUtils.newSparseArray();
+ final List<Integer> list0 = CollectionUtils.newArrayList();
+ star.put(0, list0);
+ for (int i = 1; i < words.size(); ++i) star.get(0).add(i);
+
+ results.add(runReadAndWrite(words, emptyArray, "only unigram"));
+ results.add(runReadAndWrite(words, chain, "chain"));
+ results.add(runReadAndWrite(words, star, "star"));
+
+ for (final String result : results) {
+ Log.d(TAG, result);
+ }
+ }
+
+ // Tests for readUnigramsAndBigramsBinary
+
+ private void checkWordMap(final List<String> expectedWords,
+ final SparseArray<List<Integer>> expectedBigrams,
+ final Map<Integer, String> resultWords,
+ final Map<Integer, Integer> resultFrequencies,
+ final Map<Integer, ArrayList<PendingAttribute>> resultBigrams) {
+ // check unigrams
+ final Set<String> actualWordsSet = new HashSet<String>(resultWords.values());
+ final Set<String> expectedWordsSet = new HashSet<String>(expectedWords);
+ assertEquals(actualWordsSet, expectedWordsSet);
+
+ for (int freq : resultFrequencies.values()) {
+ assertEquals(freq, UNIGRAM_FREQ);
+ }
+
+ // check bigrams
+ final Map<String, List<String>> expBigrams = new HashMap<String, List<String>>();
+ for (int i = 0; i < expectedBigrams.size(); ++i) {
+ final String word1 = expectedWords.get(expectedBigrams.keyAt(i));
+ for (int w2 : expectedBigrams.valueAt(i)) {
+ if (expBigrams.get(word1) == null) {
+ expBigrams.put(word1, new ArrayList<String>());
+ }
+ expBigrams.get(word1).add(expectedWords.get(w2));
+ }
+ }
+
+ final Map<String, List<String>> actBigrams = new HashMap<String, List<String>>();
+ for (Entry<Integer, ArrayList<PendingAttribute>> entry : resultBigrams.entrySet()) {
+ final String word1 = resultWords.get(entry.getKey());
+ final int unigramFreq = resultFrequencies.get(entry.getKey());
+ for (PendingAttribute attr : entry.getValue()) {
+ final String word2 = resultWords.get(attr.mAddress);
+ if (actBigrams.get(word1) == null) {
+ actBigrams.put(word1, new ArrayList<String>());
+ }
+ actBigrams.get(word1).add(word2);
+
+ final int bigramFreq = BinaryDictInputOutput.reconstructBigramFrequency(
+ unigramFreq, attr.mFrequency);
+ assertTrue(Math.abs(bigramFreq - BIGRAM_FREQ) < TOLERANCE_OF_BIGRAM_FREQ);
+ }
+ }
+
+ assertEquals(actBigrams, expBigrams);
+ }
+
+ private long timeAndCheckReadUnigramsAndBigramsBinary(final File file, final List<String> words,
+ final SparseArray<List<Integer>> bigrams) {
+ FileInputStream inStream = null;
+
+ final Map<Integer, String> resultWords = CollectionUtils.newTreeMap();
+ final Map<Integer, ArrayList<PendingAttribute>> resultBigrams =
+ CollectionUtils.newTreeMap();
+ final Map<Integer, Integer> resultFreqs = CollectionUtils.newTreeMap();
+
+ long now = -1, diff = -1;
+ try {
+ inStream = new FileInputStream(file);
+ final ByteBuffer buffer = inStream.getChannel().map(
+ FileChannel.MapMode.READ_ONLY, 0, file.length());
+
+ now = System.currentTimeMillis();
+ BinaryDictInputOutput.readUnigramsAndBigramsBinary(
+ new BinaryDictInputOutput.ByteBufferWrapper(buffer), resultWords, resultFreqs,
+ resultBigrams);
+ diff = System.currentTimeMillis() - now;
+ checkWordMap(words, bigrams, resultWords, resultFreqs, resultBigrams);
+ } catch (IOException e) {
+ Log.e(TAG, "IOException " + e);
+ } catch (UnsupportedFormatException e) {
+ Log.e(TAG, "UnsupportedFormatException: " + e);
+ } finally {
+ if (inStream != null) {
+ try {
+ inStream.close();
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
+ }
+
+ return diff;
+ }
+
+ private void runReadUnigramsAndBigramsBinary(final List<String> words,
+ final SparseArray<List<Integer>> bigrams) {
+
+ // making the dictionary from lists of words.
+ final FusionDictionary dict = new FusionDictionary(new Node(),
+ new FusionDictionary.DictionaryOptions(
+ new HashMap<String, String>(), false, false));
+
+ File file = null;
+ try {
+ file = File.createTempFile("runReadUnigrams", ".dict");
+ } catch (IOException e) {
+ Log.e(TAG, "IOException: " + e);
+ }
+
+ assertNotNull(file);
+
+ addUnigrams(words.size(), dict, words);
+ addBigrams(dict, words, bigrams);
+ timeWritingDictToFile(file, dict);
+
+ long wordMap = timeAndCheckReadUnigramsAndBigramsBinary(file, words, bigrams);
+ long fullReading = timeReadingAndCheckDict(file, words, bigrams);
+
+ Log.d(TAG, "read=" + fullReading + ", bytearray=" + wordMap);
+ }
+
+ public void testReadUnigramsAndBigramsBinary() {
+ final List<String> results = new ArrayList<String>();
+
+ final Random random = new Random(123456);
+ final List<String> words = generateWords(MAX_UNIGRAMS, random);
+ final SparseArray<List<Integer>> emptyArray = CollectionUtils.newSparseArray();
+
+ runReadUnigramsAndBigramsBinary(words, emptyArray);
+
+ final SparseArray<List<Integer>> star = CollectionUtils.newSparseArray();
+ for (int i = 1; i < words.size(); ++i) {
+ star.put(i-1, new ArrayList<Integer>());
+ star.get(i-1).add(i);
+ }
+ runReadUnigramsAndBigramsBinary(words, star);
+ }
+}
diff --git a/tests/src/com/android/inputmethod/latin/FusionDictionaryTests.java b/tests/src/com/android/inputmethod/latin/FusionDictionaryTests.java
new file mode 100644
index 0000000..123959b
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/FusionDictionaryTests.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.test.AndroidTestCase;
+
+import com.android.inputmethod.latin.makedict.FusionDictionary;
+import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
+
+import java.util.HashMap;
+
+/**
+ * Unit test for FusionDictionary
+ */
+public class FusionDictionaryTests extends AndroidTestCase {
+ public void testFindWordInTree() {
+ FusionDictionary dict = new FusionDictionary(new Node(),
+ new FusionDictionary.DictionaryOptions(new HashMap<String,String>(), false, false));
+
+ dict.add("abc", 10, null, false /* isNotAWord */);
+ assertNull(FusionDictionary.findWordInTree(dict.mRoot, "aaa"));
+ assertNotNull(FusionDictionary.findWordInTree(dict.mRoot, "abc"));
+
+ dict.add("aa", 10, null, false /* isNotAWord */);
+ assertNull(FusionDictionary.findWordInTree(dict.mRoot, "aaa"));
+ assertNotNull(FusionDictionary.findWordInTree(dict.mRoot, "aa"));
+
+ dict.add("babcd", 10, null, false /* isNotAWord */);
+ dict.add("bacde", 10, null, false /* isNotAWord */);
+ assertNull(FusionDictionary.findWordInTree(dict.mRoot, "ba"));
+ assertNotNull(FusionDictionary.findWordInTree(dict.mRoot, "babcd"));
+ assertNotNull(FusionDictionary.findWordInTree(dict.mRoot, "bacde"));
+ }
+}
diff --git a/tests/src/com/android/inputmethod/latin/InputPointersTests.java b/tests/src/com/android/inputmethod/latin/InputPointersTests.java
index 6f04f3e..cc55076 100644
--- a/tests/src/com/android/inputmethod/latin/InputPointersTests.java
+++ b/tests/src/com/android/inputmethod/latin/InputPointersTests.java
@@ -18,6 +18,8 @@
import android.test.AndroidTestCase;
+import java.util.Arrays;
+
public class InputPointersTests extends AndroidTestCase {
private static final int DEFAULT_CAPACITY = 48;
@@ -162,6 +164,61 @@
src.getTimes(), 0, dst.getTimes(), dstLen, srcLen);
}
+ public void testAppendResizableIntArray() {
+ final int srcLen = 100;
+ final int srcPointerId = 1;
+ final int[] srcPointerIds = new int[srcLen];
+ Arrays.fill(srcPointerIds, srcPointerId);
+ final ResizableIntArray srcTimes = new ResizableIntArray(DEFAULT_CAPACITY);
+ final ResizableIntArray srcXCoords = new ResizableIntArray(DEFAULT_CAPACITY);
+ final ResizableIntArray srcYCoords= new ResizableIntArray(DEFAULT_CAPACITY);
+ for (int i = 0; i < srcLen; i++) {
+ srcTimes.add(i * 2);
+ srcXCoords.add(i * 3);
+ srcYCoords.add(i * 4);
+ }
+ final int dstLen = 50;
+ final InputPointers dst = new InputPointers(DEFAULT_CAPACITY);
+ for (int i = 0; i < dstLen; i++) {
+ final int value = -i - 1;
+ dst.addPointer(value * 4, value * 3, value * 2, value);
+ }
+ final InputPointers dstCopy = new InputPointers(DEFAULT_CAPACITY);
+ dstCopy.copy(dst);
+
+ dst.append(srcPointerId, srcTimes, srcXCoords, srcYCoords, 0, 0);
+ assertEquals("size after append zero", dstLen, dst.getPointerSize());
+ assertArrayEquals("xCoordinates after append zero",
+ dstCopy.getXCoordinates(), 0, dst.getXCoordinates(), 0, dstLen);
+ assertArrayEquals("yCoordinates after append zero",
+ dstCopy.getYCoordinates(), 0, dst.getYCoordinates(), 0, dstLen);
+ assertArrayEquals("pointerIds after append zero",
+ dstCopy.getPointerIds(), 0, dst.getPointerIds(), 0, dstLen);
+ assertArrayEquals("times after append zero",
+ dstCopy.getTimes(), 0, dst.getTimes(), 0, dstLen);
+
+ dst.append(srcPointerId, srcTimes, srcXCoords, srcYCoords, 0, srcLen);
+ assertEquals("size after append", dstLen + srcLen, dst.getPointerSize());
+ assertTrue("primitive length after append",
+ dst.getPointerIds().length >= dstLen + srcLen);
+ assertArrayEquals("original xCoordinates values after append",
+ dstCopy.getXCoordinates(), 0, dst.getXCoordinates(), 0, dstLen);
+ assertArrayEquals("original yCoordinates values after append",
+ dstCopy.getYCoordinates(), 0, dst.getYCoordinates(), 0, dstLen);
+ assertArrayEquals("original pointerIds values after append",
+ dstCopy.getPointerIds(), 0, dst.getPointerIds(), 0, dstLen);
+ assertArrayEquals("original times values after append",
+ dstCopy.getTimes(), 0, dst.getTimes(), 0, dstLen);
+ assertArrayEquals("appended xCoordinates values after append",
+ srcXCoords.getPrimitiveArray(), 0, dst.getXCoordinates(), dstLen, srcLen);
+ assertArrayEquals("appended yCoordinates values after append",
+ srcYCoords.getPrimitiveArray(), 0, dst.getYCoordinates(), dstLen, srcLen);
+ assertArrayEquals("appended pointerIds values after append",
+ srcPointerIds, 0, dst.getPointerIds(), dstLen, srcLen);
+ assertArrayEquals("appended times values after append",
+ srcTimes.getPrimitiveArray(), 0, dst.getTimes(), dstLen, srcLen);
+ }
+
private static void assertArrayEquals(String message, int[] expecteds, int expectedPos,
int[] actuals, int actualPos, int length) {
if (expecteds == null && actuals == null) {
diff --git a/tests/src/com/android/inputmethod/latin/InputTestsBase.java b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
index c672d51..ffd95f5 100644
--- a/tests/src/com/android/inputmethod/latin/InputTestsBase.java
+++ b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
@@ -39,7 +39,6 @@
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.KeyboardActionListener;
import java.util.HashMap;
@@ -136,7 +135,6 @@
mLatinIME.onCreateInputView();
mLatinIME.onStartInputView(ei, false);
mInputConnection = ic;
- mKeyboard = mLatinIME.mKeyboardSwitcher.getKeyboard();
changeLanguage("en_US");
}
@@ -222,9 +220,7 @@
return;
}
}
- mLatinIME.onCodeInput(codePoint,
- KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
- KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
+ mLatinIME.onCodeInput(codePoint, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
//mLatinIME.onReleaseKey(codePoint, false);
}
@@ -256,13 +252,13 @@
fail("InputMethodSubtype for locale " + locale + " is not enabled");
}
SubtypeSwitcher.getInstance().updateSubtype(subtype);
+ mLatinIME.loadKeyboard();
+ mKeyboard = mLatinIME.mKeyboardSwitcher.getKeyboard();
waitForDictionaryToBeLoaded();
}
protected void pickSuggestionManually(final int index, final CharSequence suggestion) {
- mLatinIME.pickSuggestionManually(index, suggestion,
- KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
- KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
+ mLatinIME.pickSuggestionManually(index, suggestion);
}
// Helper to avoid writing the try{}catch block each time
diff --git a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
index c70c2fd..52a3745 100644
--- a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
+++ b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
@@ -28,7 +28,7 @@
public class SubtypeLocaleTests extends AndroidTestCase {
// Locale to subtypes list.
- private final ArrayList<InputMethodSubtype> mSubtypesList = new ArrayList<InputMethodSubtype>();
+ private final ArrayList<InputMethodSubtype> mSubtypesList = CollectionUtils.newArrayList();
private Resources mRes;
diff --git a/tests/src/com/android/inputmethod/latin/UserHistoryDictIOUtilsTests.java b/tests/src/com/android/inputmethod/latin/UserHistoryDictIOUtilsTests.java
new file mode 100644
index 0000000..8f0551b
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/UserHistoryDictIOUtilsTests.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import com.android.inputmethod.latin.UserHistoryDictIOUtils.BigramDictionaryInterface;
+import com.android.inputmethod.latin.UserHistoryDictIOUtils.OnAddWordListener;
+import com.android.inputmethod.latin.makedict.FusionDictionary;
+import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup;
+
+import android.content.Context;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+
+/**
+ * Unit tests for UserHistoryDictIOUtils
+ */
+public class UserHistoryDictIOUtilsTests extends AndroidTestCase
+ implements BigramDictionaryInterface {
+
+ private static final String TAG = UserHistoryDictIOUtilsTests.class.getSimpleName();
+ private static final int UNIGRAM_FREQUENCY = 50;
+ private static final int BIGRAM_FREQUENCY = 100;
+ private static final ArrayList<String> NOT_HAVE_BIGRAM = new ArrayList<String>();
+
+ /**
+ * Return same frequency for all words and bigrams
+ */
+ @Override
+ public int getFrequency(String word1, String word2) {
+ if (word1 == null) return UNIGRAM_FREQUENCY;
+ return BIGRAM_FREQUENCY;
+ }
+
+ // Utilities for Testing
+
+ private void addWord(final String word,
+ final HashMap<String, ArrayList<String> > addedWords) {
+ if (!addedWords.containsKey(word)) {
+ addedWords.put(word, new ArrayList<String>());
+ }
+ }
+
+ private void addBigram(final String word1, final String word2,
+ final HashMap<String, ArrayList<String> > addedWords) {
+ addWord(word1, addedWords);
+ addWord(word2, addedWords);
+ addedWords.get(word1).add(word2);
+ }
+
+ private void addBigramToBigramList(final String word1, final String word2,
+ final HashMap<String, ArrayList<String> > addedWords,
+ final UserHistoryDictionaryBigramList bigramList) {
+ bigramList.addBigram(null, word1);
+ bigramList.addBigram(word1, word2);
+
+ addBigram(word1, word2, addedWords);
+ }
+
+ private void checkWordInFusionDict(final FusionDictionary dict, final String word,
+ final ArrayList<String> expectedBigrams) {
+ final CharGroup group = FusionDictionary.findWordInTree(dict.mRoot, word);
+ assertNotNull(group);
+ assertTrue(group.isTerminal());
+
+ for (final String bigram : expectedBigrams) {
+ assertNotNull(group.getBigram(bigram));
+ }
+ }
+
+ private void checkWordsInFusionDict(final FusionDictionary dict,
+ final HashMap<String, ArrayList<String> > bigrams) {
+ for (final String word : bigrams.keySet()) {
+ if (bigrams.containsKey(word)) {
+ checkWordInFusionDict(dict, word, bigrams.get(word));
+ } else {
+ checkWordInFusionDict(dict, word, NOT_HAVE_BIGRAM);
+ }
+ }
+ }
+
+ private void checkWordInBigramList(
+ final UserHistoryDictionaryBigramList bigramList, final String word,
+ final ArrayList<String> expectedBigrams) {
+ // check unigram
+ final HashMap<String,Byte> unigramMap = bigramList.getBigrams(null);
+ assertTrue(unigramMap.containsKey(word));
+
+ // check bigrams
+ final ArrayList<String> actualBigrams = new ArrayList<String>(
+ bigramList.getBigrams(word).keySet());
+
+ Collections.sort(expectedBigrams);
+ Collections.sort(actualBigrams);
+ assertEquals(expectedBigrams, actualBigrams);
+ }
+
+ private void checkWordsInBigramList(final UserHistoryDictionaryBigramList bigramList,
+ final HashMap<String, ArrayList<String> > addedWords) {
+ for (final String word : addedWords.keySet()) {
+ if (addedWords.containsKey(word)) {
+ checkWordInBigramList(bigramList, word, addedWords.get(word));
+ } else {
+ checkWordInBigramList(bigramList, word, NOT_HAVE_BIGRAM);
+ }
+ }
+ }
+
+ private void writeDictToFile(final File file,
+ final UserHistoryDictionaryBigramList bigramList) {
+ try {
+ final FileOutputStream out = new FileOutputStream(file);
+ UserHistoryDictIOUtils.writeDictionaryBinary(out, this, bigramList, 2);
+ out.flush();
+ out.close();
+ } catch (IOException e) {
+ Log.e(TAG, "IO exception while writing file: " + e);
+ }
+ }
+
+ private void readDictFromFile(final File file, final OnAddWordListener listener) {
+ FileInputStream inStream = null;
+
+ try {
+ inStream = new FileInputStream(file);
+ final byte[] buffer = new byte[(int)file.length()];
+ inStream.read(buffer);
+
+ UserHistoryDictIOUtils.readDictionaryBinary(
+ new UserHistoryDictIOUtils.ByteArrayWrapper(buffer), listener);
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "file not found: " + e);
+ } catch (IOException e) {
+ Log.e(TAG, "IOException: " + e);
+ } finally {
+ if (inStream != null) {
+ try {
+ inStream.close();
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
+ }
+ }
+
+ public void testGenerateFusionDictionary() {
+ final UserHistoryDictionaryBigramList originalList = new UserHistoryDictionaryBigramList();
+
+ final HashMap<String, ArrayList<String> > addedWords =
+ new HashMap<String, ArrayList<String>>();
+ addBigramToBigramList("this", "is", addedWords, originalList);
+ addBigramToBigramList("this", "was", addedWords, originalList);
+ addBigramToBigramList("hello", "world", addedWords, originalList);
+
+ final FusionDictionary fusionDict =
+ UserHistoryDictIOUtils.constructFusionDictionary(this, originalList);
+
+ checkWordsInFusionDict(fusionDict, addedWords);
+ }
+
+ public void testReadAndWrite() {
+ final Context context = getContext();
+
+ File file = null;
+ try {
+ file = File.createTempFile("testReadAndWrite", ".dict");
+ } catch (IOException e) {
+ Log.d(TAG, "IOException while creating a temporary file: " + e);
+ }
+ assertNotNull(file);
+
+ // make original dictionary
+ final UserHistoryDictionaryBigramList originalList = new UserHistoryDictionaryBigramList();
+ final HashMap<String, ArrayList<String>> addedWords = CollectionUtils.newHashMap();
+ addBigramToBigramList("this" , "is" , addedWords, originalList);
+ addBigramToBigramList("this" , "was" , addedWords, originalList);
+ addBigramToBigramList("is" , "not" , addedWords, originalList);
+ addBigramToBigramList("hello", "world", addedWords, originalList);
+
+ // write to file
+ writeDictToFile(file, originalList);
+
+ // make result dict.
+ final UserHistoryDictionaryBigramList resultList = new UserHistoryDictionaryBigramList();
+ final OnAddWordListener listener = new OnAddWordListener() {
+ @Override
+ public void setUnigram(final String word,
+ final String shortcutTarget, final int frequency) {
+ Log.d(TAG, "in: setUnigram: " + word + "," + frequency);
+ resultList.addBigram(null, word, (byte)frequency);
+ }
+ @Override
+ public void setBigram(final String word1, final String word2, final int frequency) {
+ Log.d(TAG, "in: setBigram: " + word1 + "," + word2 + "," + frequency);
+ resultList.addBigram(word1, word2, (byte)frequency);
+ }
+ };
+
+ // load from file
+ readDictFromFile(file, listener);
+ checkWordsInBigramList(resultList, addedWords);
+
+ // add new bigram
+ addBigramToBigramList("hello", "java", addedWords, resultList);
+
+ // rewrite
+ writeDictToFile(file, resultList);
+ final UserHistoryDictionaryBigramList resultList2 = new UserHistoryDictionaryBigramList();
+ final OnAddWordListener listener2 = new OnAddWordListener() {
+ @Override
+ public void setUnigram(final String word,
+ final String shortcutTarget, final int frequency) {
+ Log.d(TAG, "in: setUnigram: " + word + "," + frequency);
+ resultList2.addBigram(null, word, (byte)frequency);
+ }
+ @Override
+ public void setBigram(final String word1, final String word2, final int frequency) {
+ Log.d(TAG, "in: setBigram: " + word1 + "," + word2 + "," + frequency);
+ resultList2.addBigram(word1, word2, (byte)frequency);
+ }
+ };
+
+ // load from file
+ readDictFromFile(file, listener2);
+ checkWordsInBigramList(resultList2, addedWords);
+ }
+}
diff --git a/tools/dicttool/Android.mk b/tools/dicttool/Android.mk
index df8cb10..b0b47af 100644
--- a/tools/dicttool/Android.mk
+++ b/tools/dicttool/Android.mk
@@ -26,7 +26,6 @@
LOCAL_JAR_MANIFEST := etc/manifest.txt
LOCAL_MODULE := dicttool_aosp
LOCAL_JAVA_LIBRARIES := junit
-LOCAL_MODULE_TAGS := eng
include $(BUILD_HOST_JAVA_LIBRARY)
include $(LOCAL_PATH)/etc/Android.mk
diff --git a/tools/dicttool/etc/Android.mk b/tools/dicttool/etc/Android.mk
index 8952827..0c611b7 100644
--- a/tools/dicttool/etc/Android.mk
+++ b/tools/dicttool/etc/Android.mk
@@ -15,6 +15,5 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := eng
LOCAL_PREBUILT_EXECUTABLES := dicttool_aosp makedict_aosp
include $(BUILD_HOST_PREBUILT)
diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/DictionaryMaker.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/DictionaryMaker.java
index 25e1740..fbfc1da 100644
--- a/tools/dicttool/src/android/inputmethod/latin/dicttool/DictionaryMaker.java
+++ b/tools/dicttool/src/android/inputmethod/latin/dicttool/DictionaryMaker.java
@@ -27,7 +27,8 @@
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
-import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
import java.util.Arrays;
import java.util.LinkedList;
@@ -238,8 +239,23 @@
*/
private static FusionDictionary readBinaryFile(final String binaryFilename)
throws FileNotFoundException, IOException, UnsupportedFormatException {
- final RandomAccessFile inputFile = new RandomAccessFile(binaryFilename, "r");
- return BinaryDictInputOutput.readDictionaryBinary(inputFile, null);
+ FileInputStream inStream = null;
+
+ try {
+ final File file = new File(binaryFilename);
+ inStream = new FileInputStream(file);
+ final ByteBuffer buffer = inStream.getChannel().map(
+ FileChannel.MapMode.READ_ONLY, 0, file.length());
+ return BinaryDictInputOutput.readDictionaryBinary(buffer, null);
+ } finally {
+ if (inStream != null) {
+ try {
+ inStream.close();
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
+ }
}
/**
diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/XmlDictInputOutput.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/XmlDictInputOutput.java
index 9ce8c49..c31cd72 100644
--- a/tools/dicttool/src/android/inputmethod/latin/dicttool/XmlDictInputOutput.java
+++ b/tools/dicttool/src/android/inputmethod/latin/dicttool/XmlDictInputOutput.java
@@ -50,6 +50,7 @@
private static final String SHORTCUT_TAG = "shortcut";
private static final String FREQUENCY_ATTR = "f";
private static final String WORD_ATTR = "word";
+ private static final String NOT_A_WORD_ATTR = "not_a_word";
private static final int SHORTCUT_ONLY_DEFAULT_FREQ = 1;
@@ -92,7 +93,7 @@
final FusionDictionary dict = mDictionary;
for (final String shortcutOnly : mShortcutsMap.keySet()) {
if (dict.hasWord(shortcutOnly)) continue;
- dict.add(shortcutOnly, 0, mShortcutsMap.get(shortcutOnly));
+ dict.add(shortcutOnly, 0, mShortcutsMap.get(shortcutOnly), true /* isNotAWord */);
}
mDictionary = null;
mShortcutsMap.clear();
@@ -144,7 +145,7 @@
@Override
public void endElement(String uri, String localName, String qName) {
if (WORD == mState) {
- mDictionary.add(mWord, mFreq, mShortcutsMap.get(mWord));
+ mDictionary.add(mWord, mFreq, mShortcutsMap.get(mWord), false /* isNotAWord */);
mState = START;
}
}
@@ -345,7 +346,8 @@
destination.write("<!-- Warning: there is no code to read this format yet. -->\n");
for (Word word : set) {
destination.write(" <" + WORD_TAG + " " + WORD_ATTR + "=\"" + word.mWord + "\" "
- + FREQUENCY_ATTR + "=\"" + word.mFrequency + "\">");
+ + FREQUENCY_ATTR + "=\"" + word.mFrequency
+ + (word.mIsNotAWord ? "\" " + NOT_A_WORD_ATTR + "=\"true" : "") + "\">");
if (null != word.mShortcutTargets) {
destination.write("\n");
for (WeightedString target : word.mShortcutTargets) {
diff --git a/tools/dicttool/tests/com/android/inputmethod/latin/makedict/BinaryDictInputOutputTest.java b/tools/dicttool/tests/com/android/inputmethod/latin/makedict/BinaryDictInputOutputTest.java
index 24042f1..88589b8 100644
--- a/tools/dicttool/tests/com/android/inputmethod/latin/makedict/BinaryDictInputOutputTest.java
+++ b/tools/dicttool/tests/com/android/inputmethod/latin/makedict/BinaryDictInputOutputTest.java
@@ -43,11 +43,11 @@
final FusionDictionary dict = new FusionDictionary(new Node(),
new DictionaryOptions(new HashMap<String, String>(),
false /* germanUmlautProcessing */, false /* frenchLigatureProcessing */));
- dict.add("foo", 1, null);
- dict.add("fta", 1, null);
- dict.add("ftb", 1, null);
- dict.add("bar", 1, null);
- dict.add("fool", 1, null);
+ dict.add("foo", 1, null, false /* isNotAWord */);
+ dict.add("fta", 1, null, false /* isNotAWord */);
+ dict.add("ftb", 1, null, false /* isNotAWord */);
+ dict.add("bar", 1, null, false /* isNotAWord */);
+ dict.add("fool", 1, null, false /* isNotAWord */);
final ArrayList<Node> result = BinaryDictInputOutput.flattenTree(dict.mRoot);
assertEquals(4, result.size());
while (!result.isEmpty()) {
diff --git a/tools/maketext/Android.mk b/tools/maketext/Android.mk
index 98731b7..77914ca 100644
--- a/tools/maketext/Android.mk
+++ b/tools/maketext/Android.mk
@@ -19,7 +19,6 @@
LOCAL_SRC_FILES += $(call all-java-files-under,src)
LOCAL_JAR_MANIFEST := etc/manifest.txt
LOCAL_JAVA_RESOURCE_DIRS := res
-LOCAL_MODULE_TAGS := eng
LOCAL_MODULE := maketext
include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/maketext/etc/Android.mk b/tools/maketext/etc/Android.mk
index 4fa194b..475676b 100644
--- a/tools/maketext/etc/Android.mk
+++ b/tools/maketext/etc/Android.mk
@@ -15,7 +15,6 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := eng
-
LOCAL_PREBUILT_EXECUTABLES := maketext
+
include $(BUILD_HOST_PREBUILT)
diff --git a/tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl b/tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl
index f6c84ea..774094c 100644
--- a/tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl
+++ b/tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.res.Resources;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.R;
import java.util.HashMap;
@@ -45,14 +46,12 @@
*/
public final class KeyboardTextsSet {
// Language to texts map.
- private static final HashMap<String, String[]> sLocaleToTextsMap =
- new HashMap<String, String[]>();
- private static final HashMap<String, Integer> sNameToIdsMap =
- new HashMap<String, Integer>();
+ private static final HashMap<String, String[]> sLocaleToTextsMap = CollectionUtils.newHashMap();
+ private static final HashMap<String, Integer> sNameToIdsMap = CollectionUtils.newHashMap();
private String[] mTexts;
// Resource name to text map.
- private HashMap<String, String> mResourceNameToTextsMap = new HashMap<String, String>();
+ private HashMap<String, String> mResourceNameToTextsMap = CollectionUtils.newHashMap();
public void setLanguage(final String language) {
mTexts = sLocaleToTextsMap.get(language);
diff --git a/tools/maketext/res/values-eo/donottranslate-more-keys.xml b/tools/maketext/res/values-eo/donottranslate-more-keys.xml
new file mode 100644
index 0000000..e929869
--- /dev/null
+++ b/tools/maketext/res/values-eo/donottranslate-more-keys.xml
@@ -0,0 +1,146 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE
+ U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+ U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+ U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+ U+00E6: "æ" LATIN SMALL LETTER AE
+ U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE
+ U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
+ U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
+ U+0103: "ă" LATIN SMALL LETTER A WITH BREVE
+ U+0105: "ą" LATIN SMALL LETTER A WITH OGONEK
+ U+00AA: "ª" FEMININE ORDINAL INDICATOR -->
+ <string name="more_keys_for_a">á,à,â,ä,æ,ã,å,ā,ă,ą,ª</string>
+ <!-- U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+ U+011B: "ě" LATIN SMALL LETTER E WITH CARON
+ U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+ U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
+ U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
+ U+0119: "ę" LATIN SMALL LETTER E WITH OGONEK
+ U+0117: "ė" LATIN SMALL LETTER E WITH DOT ABOVE
+ U+0113: "ē" LATIN SMALL LETTER E WITH MACRON -->
+ <string name="more_keys_for_e">é,ě,è,ê,ë,ę,ė,ē</string>
+ <!-- U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+ U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+ U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+ U+0129: "ĩ" LATIN SMALL LETTER I WITH TILDE
+ U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+ U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+ U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
+ U+0131: "ı" LATIN SMALL LETTER DOTLESS I
+ U+0133: "ij" LATIN SMALL LIGATURE IJ -->
+ <string name="more_keys_for_i">í,î,ï,ĩ,ì,į,ī,ı,ij</string>
+ <!-- U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+ U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+ U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+ U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+ U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+ U+0153: "œ" LATIN SMALL LIGATURE OE
+ U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+ U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
+ U+0151: "ő" LATIN SMALL LETTER O WITH DOUBLE ACUTE
+ U+00BA: "º" MASCULINE ORDINAL INDICATOR -->
+ <string name="more_keys_for_o">ó,ö,ô,ò,õ,œ,ø,ō,ő,º</string>
+ <!-- U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+ U+016F: "ů" LATIN SMALL LETTER U WITH RING ABOVE
+ U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+ U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+ U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+ U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+ U+0169: "ũ" LATIN SMALL LETTER U WITH TILDE
+ U+0171: "ű" LATIN SMALL LETTER U WITH DOUBLE ACUTE
+ U+0173: "ų" LATIN SMALL LETTER U WITH OGONEK
+ U+00B5: "µ" MICRO SIGN -->
+ <string name="more_keys_for_u">ú,ů,û,ü,ù,ū,ũ,ű,ų,µ</string>
+ <!-- U+00DF: "ß" LATIN SMALL LETTER SHARP S
+ U+0161: "š" LATIN SMALL LETTER S WITH CARON
+ U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
+ U+0219: "ș" LATIN SMALL LETTER S WITH COMMA BELOW
+ U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA -->
+ <string name="more_keys_for_s">ß,š,ś,ș,ş</string>
+ <!-- U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
+ U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
+ U+0146: "ņ" LATIN SMALL LETTER N WITH CEDILLA
+ U+0148: "ň" LATIN SMALL LETTER N WITH CARON
+ U+0149: "ʼn" LATIN SMALL LETTER N PRECEDED BY APOSTROPHE
+ U+014B: "ŋ" LATIN SMALL LETTER ENG -->
+ <string name="more_keys_for_n">ñ,ń,ņ,ň,ʼn,ŋ</string>
+ <!-- U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
+ U+010D: "č" LATIN SMALL LETTER C WITH CARON
+ U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+ U+010B: "ċ" LATIN SMALL LETTER C WITH DOT ABOVE -->
+ <string name="more_keys_for_c">ć,č,ç,ċ</string>
+ <!-- U+00FD: "ý" LATIN SMALL LETTER Y WITH ACUTE
+ U+0177: "ŷ" LATIN SMALL LETTER Y WITH CIRCUMFLEX
+ U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
+ U+00FE: "þ" LATIN SMALL LETTER THORN -->
+ <string name="more_keys_for_y">y,ý,ŷ,ÿ,þ</string>
+ <!-- U+00F0: "ð" LATIN SMALL LETTER ETH
+ U+010F: "ď" LATIN SMALL LETTER D WITH CARON
+ U+0111: "đ" LATIN SMALL LETTER D WITH STROKE -->
+ <string name="more_keys_for_d">ð,ď,đ</string>
+ <!-- U+0159: "ř" LATIN SMALL LETTER R WITH CARON
+ U+0155: "ŕ" LATIN SMALL LETTER R WITH ACUTE
+ U+0157: "ŗ" LATIN SMALL LETTER R WITH CEDILLA -->
+ <string name="more_keys_for_r">ř,ŕ,ŗ</string>
+ <!-- U+0165: "ť" LATIN SMALL LETTER T WITH CARON
+ U+021B: "ț" LATIN SMALL LETTER T WITH COMMA BELOW
+ U+0163: "ţ" LATIN SMALL LETTER T WITH CEDILLA
+ U+0167: "ŧ" LATIN SMALL LETTER T WITH STROKE -->
+ <string name="more_keys_for_t">ť,ț,ţ,ŧ</string>
+ <!-- U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
+ U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
+ U+017E: "ž" LATIN SMALL LETTER Z WITH CARON -->
+ <string name="more_keys_for_z">ź,ż,ž</string>
+ <!-- U+0137: "ķ" LATIN SMALL LETTER K WITH CEDILLA
+ U+0138: "ĸ" LATIN SMALL LETTER KRA -->
+ <string name="more_keys_for_k">ķ,ĸ</string>
+ <!-- U+013A: "ĺ" LATIN SMALL LETTER L WITH ACUTE
+ U+013C: "ļ" LATIN SMALL LETTER L WITH CEDILLA
+ U+013E: "ľ" LATIN SMALL LETTER L WITH CARON
+ U+0140: "ŀ" LATIN SMALL LETTER L WITH MIDDLE DOT
+ U+0142: "ł" LATIN SMALL LETTER L WITH STROKE -->
+ <string name="more_keys_for_l">ĺ,ļ,ľ,ŀ,ł</string>
+ <!-- U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
+ U+0121: "ġ" LATIN SMALL LETTER G WITH DOT ABOVE
+ U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA -->
+ <string name="more_keys_for_g">ğ,ġ,ģ</string>
+ <!-- U+0175: "ŵ" LATIN SMALL LETTER W WITH CIRCUMFLEX -->
+ <string name="more_keys_for_v">w,ŵ</string>
+ <!-- U+0125: "ĥ" LATIN SMALL LETTER H WITH CIRCUMFLEX
+ U+0127: "ħ" LATIN SMALL LETTER H WITH STROKE -->
+ <string name="more_keys_for_h">ĥ,ħ</string>
+ <!-- U+0175: "ŵ" LATIN SMALL LETTER W WITH CIRCUMFLEX -->
+ <string name="more_keys_for_w">w,ŵ</string>
+ <string name="more_keys_for_q">q</string>
+ <string name="more_keys_for_x">x</string>
+ <!-- U+015D: "ŝ" LATIN SMALL LETTER S WITH CIRCUMFLEX -->
+ <string name="keylabel_for_q">ŝ</string>
+ <!-- U+011D: "ĝ" LATIN SMALL LETTER G WITH CIRCUMFLEX -->
+ <string name="keylabel_for_w">ĝ</string>
+ <!-- U+016D: "ŭ" LATIN SMALL LETTER U WITH BREVE -->
+ <string name="keylabel_for_y">ŭ</string>
+ <!-- U+0109: "ĉ" LATIN SMALL LETTER C WITH CIRCUMFLEX -->
+ <string name="keylabel_for_x">ĉ</string>
+ <!-- U+0135: "ĵ" LATIN SMALL LETTER J WITH CIRCUMFLEX -->
+ <string name="keylabel_for_spanish_row2_10">ĵ</string>
+</resources>
diff --git a/tools/maketext/res/values/donottranslate-more-keys.xml b/tools/maketext/res/values/donottranslate-more-keys.xml
index 543e936..4d7100b 100644
--- a/tools/maketext/res/values/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values/donottranslate-more-keys.xml
@@ -177,6 +177,14 @@
<string name="keylabel_for_apostrophe">\'</string>
<string name="keyhintlabel_for_apostrophe">\"</string>
<string name="more_keys_for_apostrophe">\"</string>
+ <string name="more_keys_for_q"></string>
+ <string name="more_keys_for_x"></string>
+ <string name="keylabel_for_q">q</string>
+ <string name="keylabel_for_w">w</string>
+ <string name="keylabel_for_y">y</string>
+ <string name="keylabel_for_x">x</string>
+ <!-- U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE -->
+ <string name="keylabel_for_spanish_row2_10">ñ</string>
<string name="more_keys_for_am_pm">!fixedColumnOrder!2,!hasLabels!,\@string/label_time_am,\@string/label_time_pm</string>
<string name="settings_as_more_key">!icon/settings_key|!code/key_settings</string>
<string name="shortcut_as_more_key">!icon/shortcut_key|!code/key_shortcut</string>
diff --git a/tools/maketext/src/com/android/inputmethod/latin/maketext/JarUtils.java b/tools/maketext/src/com/android/inputmethod/latin/maketext/JarUtils.java
index 07a6c30..6d6bc0e 100644
--- a/tools/maketext/src/com/android/inputmethod/latin/maketext/JarUtils.java
+++ b/tools/maketext/src/com/android/inputmethod/latin/maketext/JarUtils.java
@@ -26,7 +26,7 @@
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
-public class JarUtils {
+public final class JarUtils {
private JarUtils() {
// This utility class is not publicly instantiable.
}