diff options
Diffstat (limited to 'java')
162 files changed, 7882 insertions, 5668 deletions
diff --git a/java/res/layout/research_splash.xml b/java/res/layout/research_splash.xml deleted file mode 100644 index 56fd7024f..000000000 --- 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 262017916..000000000 --- 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 56642c478..57e9ff451 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 000000000..8d5b00766 --- /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 000000000..fcb666d57 --- /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 62597258c..a1546f11d 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 000000000..7f293e4dc --- /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 a478df89a..9664bf9ed 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 5596ba41c..e608f7d7b 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 b95c858dc..511217068 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 ce33b73cb..ec9d75988 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 4975d6540..c8f64351c 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 54a6687a3..8e2d43e4e 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 c59bad302..aa16c7770 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 2569f2317..1d7e57bce 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 3b433e4ab..d1120694b 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 index d4ecbfaa4..2cad15eb0 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 07b3f31c7..bd60844a3 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. --> - <!-- 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_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_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] --> + <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_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 ae67c4369..634b32a78 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 b75980f2f..000000000 --- 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/rowkeys_arabic1.xml b/java/res/xml-sw600dp/rowkeys_arabic1.xml index 44fdc676d..6a0e25786 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 3eba2fbf3..00e69ace7 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 911550f4a..b0bcd78d6 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 53208f286..7b312404a 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 234f98430..3b759b66c 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 998ba72d6..3597618ce 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 6aec7c2c5..000000000 --- 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 edb759a89..000000000 --- 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 7507dde86..000000000 --- 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 64549bdce..000000000 --- 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 c1fe55b39..bc89640ac 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 b2cdbc373..000000000 --- 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 1531458ea..000000000 --- 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 fa30f24c0..000000000 --- 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 593ccbd48..000000000 --- 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 e2e5f5d56..0cd9a61ea 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 a1358d4a2..a68fec458 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 512283096..55196ebc3 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 7721bc5a9..5f9b383f8 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 cebca4ff7..777c71af3 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 fd64e5bf4..a2297f702 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 058ca16a3..b4a4a0b92 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-sw600dp/kbd_thai.xml b/java/res/xml/key_thai_kho_khuat.xml index b75980f2f..0ffd0f924 100644 --- a/java/res/xml-sw600dp/kbd_thai.xml +++ b/java/res/xml/key_thai_kho_khuat.xml @@ -18,12 +18,23 @@ */ --> -<Keyboard +<merge 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> + <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 acdf7645f..613e9f6c4 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 b1bf790e4..a4bef83c6 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 f86aae014..d733f6411 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 9e9eac0d9..e4e694812 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 840b048f7..0ccf1ab54 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 21548936e..4b6abe2ab 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 29c35134c..7d2e81f7d 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 84d613460..e7c9b590b 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 a74aeb842..b70fd729f 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 4c7e57997..335dff33c 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 4b49da171..950d2a456 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 --> + latin:keyLabel="+" /> + <!-- U+0E51: "๑" THAI DIGIT ONE --> <Key - latin:keyLabel="ฑ" /> - <!-- U+0E18: "ธ" THAI CHARACTER THO THONG --> - <Key - latin:keyLabel="ธ" /> - <!-- U+0E13: "ณ" THAI CHARACTER NO NEN --> + latin:keyLabel="๑" + latin:keyLabelFlags="fontNormal" /> + <!-- U+0E52: "๒" THAI DIGIT TWO --> <Key - latin:keyLabel="ณ" /> - <!-- U+0E0D: "ญ" THAI CHARACTER YO YING --> + latin:keyLabel="๒" + latin:keyLabelFlags="fontNormal" /> + <!-- U+0E53: "๓" THAI DIGIT THREE --> <Key - latin:keyLabel="ญ" /> - <!-- U+0E10: "ฐ" THAI CHARACTER THO THAN --> + latin:keyLabel="๓" + latin:keyLabelFlags="fontNormal" /> + <!-- U+0E54: "๔" THAI DIGIT FOUR --> <Key - latin:keyLabel="ฐ" /> - <!-- U+0E03: "ฃ" THAI CHARACTER KHO KHUAT --> + 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="ฃ" /> - <!-- U+0E05: "ฅ" THAI CHARACTER KHO KHON --> + latin:keyLabel=" ู" + latin:code="0x0E39" + latin:keyLabelFlags="fontNormal|followKeyLetterRatio" /> + <!-- U+0E3F: "฿" THAI CURRENCY SYMBOL BAHT --> <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="฿" + latin:keyLabelFlags="fontNormal" /> + <!-- U+0E55: "๕" THAI DIGIT FIVE --> <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: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 80e3563f8..f602994b9 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 --> - <Key - latin:keyLabel="ฤ" /> - <!-- U+0E06: "ฆ" THAI CHARACTER KHO RAKHANG --> - <Key - latin:keyLabel="ฆ" /> - <!-- U+0E0F: "ฏ" THAI CHARACTER TO PATAK --> - <Key - latin:keyLabel="ฏ" /> - <!-- U+0E0C: "ฌ" THAI CHARACTER CHO CHOE --> - <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="ซ" /> - <!-- U+0E3F: "฿" THAI CURRENCY SYMBOL BAHT - U+0E45: "ๅ" THAI CHARACTER LAKKHANGYAO --> - <Key - latin:keyLabel="฿" - latin:moreKeys="ๅ" /> - <!-- U+0E46: "ๆ" THAI CHARACTER MAIYAMOK - U+0E2F: "ฯ" THAI CHARACTER PAIYANNOI --> - <Key - latin:keyLabel="ๆ" - latin:moreKeys="ฯ" /> + <!-- U+0E50: "๐" THAI DIGIT ZERO --> + <Key + latin:keyLabel="๐" + latin:keyLabelFlags="fontNormal" /> + <Key + latin:keyLabel=""" /> + <!-- U+0E0E: "ฎ" THAI CHARACTER DO CHADA --> + <Key + latin:keyLabel="ฎ" + latin:keyLabelFlags="fontNormal" /> + <!-- U+0E11: "ฑ" THAI CHARACTER THO NANGMONTHO --> + <Key + latin:keyLabel="ฑ" + latin:keyLabelFlags="fontNormal" /> + <!-- U+0E18: "ธ" THAI CHARACTER THO THONG --> + <Key + 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=" ํ" + 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=" ๊" + latin:code="0x0E4A" + latin:keyLabelFlags="fontNormal|followKeyLetterRatio" /> + <!-- U+0E13: "ณ" THAI CHARACTER NO NEN --> + <Key + latin:keyLabel="ณ" + latin:keyLabelFlags="fontNormal" /> + <!-- U+0E2F: "ฯ" THAI CHARACTER PAIYANNOI --> + <Key + 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 --> - <Key - latin:keyLabel="ฟ" - latin:moreKeys="พ" /> - <!-- U+0E2B: "ห" THAI CHARACTER HO HIP --> + <!-- U+0E46: "ๆ" THAI CHARACTER MAIYAMOK --> <Key - latin:keyLabel="ห" /> - <!-- U+0E01: "ก" THAI CHARACTER KO KAI --> - <Key - latin:keyLabel="ก" /> - <!-- U+0E14: "ด" THAI CHARACTER DO DEK --> - <Key - latin:keyLabel="ด" /> - <!-- U+0E2A: "ส" THAI CHARACTER SO SUA --> + latin:keyLabel="ๆ" + latin:keyLabelFlags="fontNormal" /> + <!-- U+0E44: "ไ" THAI CHARACTER SARA AI MAIMALAI --> <Key - latin:keyLabel="ส" /> - <!-- U+0E27: "ว" THAI CHARACTER WO WAEN --> + latin:keyLabel="ไ" + latin:keyLabelFlags="fontNormal" /> + <!-- U+0E33: "ำ" THAI CHARACTER SARA AM --> <Key - latin:keyLabel="ว" /> - <!-- U+0E07: "ง" THAI CHARACTER NGO NGU --> + latin:keyLabel="ำ" + latin:keyLabelFlags="fontNormal" /> + <!-- U+0E1E: "พ" THAI CHARACTER PHO PHAN --> <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 --> - <Key - latin:keyLabel="ั" - latin:moreKeys="ิ,ี,ึ,ื,ุ,ู" /> + 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: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 b8338073c..7b6e6372e 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: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="็" /> + 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: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:moreKeys="้,๊,๋" /> + 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 000000000..8a784242c --- /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 6b80df640..108b7e1fc 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="shiftKeyStyle" /> + <include + latin:keyboardLayout="@xml/rowkeys_thai4" /> <Key - latin:keyStyle="deleteKeyStyle" - latin:keyWidth="fillRight" - latin:visualInsetsLeft="1%p" /> + 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 56f9c2ab2..5af5d044f 100644 --- a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java +++ b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java @@ -35,6 +35,7 @@ import android.view.inputmethod.EditorInfo; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardView; +import com.android.inputmethod.latin.CollectionUtils; /** * Exposes a virtual view sub-tree for {@link KeyboardView} and generates @@ -55,7 +56,7 @@ public class AccessibilityEntityProvider extends AccessibilityNodeProviderCompat 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 58d3022c9..1eee1df87 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.compat.SettingsSecureCompatUtils; 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 ed3468afb..77940c086 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.AccessibilityEventCompat; 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 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat { /** * 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 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat { 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 class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat { 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 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat { 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 9b74070af..5c45448a5 100644 --- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java +++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java @@ -25,6 +25,7 @@ import android.view.inputmethod.EditorInfo; 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 @@ public class KeyCodeDescriptionMapper { 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 @@ public class KeyCodeDescriptionMapper { } 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 ce427e9c9..ffed6ecb1 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.Constructor; 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 08c246f8b..210058bec 100644 --- a/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java +++ b/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java @@ -20,7 +20,7 @@ import android.view.inputmethod.EditorInfo; 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 0befa7a66..8eea31ed2 100644 --- a/java/src/com/android/inputmethod/compat/InputMethodServiceCompatUtils.java +++ b/java/src/com/android/inputmethod/compat/InputMethodServiceCompatUtils.java @@ -20,7 +20,7 @@ import android.inputmethodservice.InputMethodService; 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 1b79992f0..db5abd0fe 100644 --- a/java/src/com/android/inputmethod/compat/SettingsSecureCompatUtils.java +++ b/java/src/com/android/inputmethod/compat/SettingsSecureCompatUtils.java @@ -18,7 +18,7 @@ package com.android.inputmethod.compat; 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 1183b5fb9..159f43650 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.Spanned; 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 @@ public class SuggestionSpanUtils { } 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 e5f9db27c..8314212c9 100644 --- a/java/src/com/android/inputmethod/compat/SuggestionsInfoCompatUtils.java +++ b/java/src/com/android/inputmethod/compat/SuggestionsInfoCompatUtils.java @@ -20,7 +20,7 @@ import android.view.textservice.SuggestionsInfo; 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 178c9ff05..03c216474 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.text.TextUtils; 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 @@ public class Key { * 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 @@ public class Key { /** 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 @@ public class Key { /** 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 @@ public class Key { 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 @@ public class Key { /** * 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 @@ public class Key { /** * 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 @@ public class Key { 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 @@ public class Key { * @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 @@ public class Key { 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 @@ public class Key { 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 @@ public class Key { } 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 @@ public class Key { } } - 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 @@ public class Key { 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 @@ public class Key { && 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 @@ public class Key { } @Override - public boolean equals(Object o) { + public boolean equals(final Object o) { return o instanceof Key && equals((Key)o); } @@ -425,7 +450,7 @@ public class Key { 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 class Key { } } - 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 @@ public class Key { 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 @@ public class Key { 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 @@ public class Key { 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 @@ public class Key { * @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 @@ public class Key { * @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 @@ public class Key { * @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 class Key { } 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 c0e6aa8d7..868c8cab5 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; +public class KeyDetector { private final int mKeyHysteresisDistanceSquared; private Keyboard mKeyboard; @@ -103,7 +103,7 @@ public class KeyDetector { 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 919850095..261d1eba7 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 @@ public class Keyboard { /** 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 class Keyboard { 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 @@ public class Keyboard { mMostCommonKeyWidth = params.mMostCommonKeyWidth; mMoreKeysTemplate = params.mMoreKeysTemplate; mMaxMoreKeysKeyboardColumn = params.mMaxMoreKeysKeyboardColumn; - + mKeyVisualAttributes = params.mKeyVisualAttributes; mTopPadding = params.mTopPadding; mVerticalGap = params.mVerticalGap; @@ -164,7 +144,7 @@ public class Keyboard { mProximityCharsCorrectionEnabled = params.mProximityCharsCorrectionEnabled; } - public boolean hasProximityCharsCorrection(int code) { + public boolean hasProximityCharsCorrection(final int code) { if (!mProximityCharsCorrectionEnabled) { return false; } @@ -180,7 +160,7 @@ public class Keyboard { return mProximityInfo; } - public Key getKey(int code) { + public Key getKey(final int code) { if (code == CODE_UNSPECIFIED) { return null; } @@ -201,7 +181,7 @@ public class Keyboard { } } - public boolean hasKey(Key aKey) { + public boolean hasKey(final Key aKey) { if (mKeyCache.indexOfValue(aKey) >= 0) { return true; } @@ -215,7 +195,7 @@ public class Keyboard { return false; } - public static boolean isLetterCode(int code) { + public static boolean isLetterCode(final int code) { return code >= CODE_SPACE; } @@ -224,170 +204,6 @@ public class Keyboard { 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 @@ public class Keyboard { * @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 @@ public class Keyboard { 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 b1621a55b..5c8f78f5e 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 @@ public interface KeyboardActionListener { * * @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 @@ public interface KeyboardActionListener { // 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 64b3f0952..aaccf63ba 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.EditorInfo; 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 @@ public class KeyboardLayoutSet { 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 @@ public class KeyboardLayoutSet { 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 @@ public class KeyboardLayoutSet { 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 @@ public class KeyboardLayoutSet { } } - 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 @@ public class KeyboardLayoutSet { // 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 @@ public class KeyboardLayoutSet { 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 @@ public class KeyboardLayoutSet { 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 @@ public class KeyboardLayoutSet { 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 @@ public class KeyboardLayoutSet { 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 @@ public class KeyboardLayoutSet { return this; } - public void setTouchPositionCorrectionEnabled(boolean enabled) { + public void setTouchPositionCorrectionEnabled(final boolean enabled) { mParams.mTouchPositionCorrectionEnabled = enabled; } @@ -301,7 +285,7 @@ public class KeyboardLayoutSet { 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 @@ public class KeyboardLayoutSet { } } - 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 @@ public class KeyboardLayoutSet { } } - 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 @@ public class KeyboardLayoutSet { } } - 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 10f651ad1..fd789f029 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.SharedPreferences; 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.LatinImeLogger; 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 class KeyboardSwitcher implements KeyboardState.SwitchActions { 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 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions { 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 class KeyboardSwitcher implements KeyboardState.SwitchActions { } } } + + 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 fcf97b99c..5b02f9f5a 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.Typeface; 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.View; 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 @@ import java.util.HashSet; /** * 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 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { // 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 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { /** 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 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { 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 class KeyboardView extends View implements PointerTracker.DrawingProxy { 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 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { } } - 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(); - - mDelayAfterPreview = mKeyPreviewDrawParams.mLingerTimeout; - + mMoreKeysLayout = keyboardViewAttr.getResourceId( + R.styleable.KeyboardView_moreKeysLayout, 0); + mBackgroundDimAlpha = keyboardViewAttr.getInt( + R.styleable.KeyboardView_backgroundDimAlpha, 0); + keyboardViewAttr.recycle(); + + 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 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { * @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 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { * @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 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { 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 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { } @Override - public void onDraw(Canvas canvas) { + public void onDraw(final Canvas canvas) { super.onDraw(canvas); if (canvas.isHardwareAccelerated()) { onDrawKeyboard(canvas); @@ -462,12 +365,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { 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 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { } } + 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 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { 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 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { 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 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { } } - 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 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { } // 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 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { // 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 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { 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 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { // 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 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { 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 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { // 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 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { 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 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { // 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 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { // 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 class KeyboardView extends View implements PointerTracker.DrawingProxy { 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 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { } } - // 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 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { 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 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { } @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 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { 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 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { 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 class KeyboardView extends View implements PointerTracker.DrawingProxy { } 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 0a929f33f..f6b66a79e 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.AccessibilityUtils; 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 @@ import java.util.WeakHashMap; /** * 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 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key new WeakHashMap<Key, MoreKeysPanel>(); private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint; - private final PointerTrackerParams mPointerTrackerParams; private final SuddenJumpingTouchEventHandler mTouchScreenRegulator; protected KeyDetector mKeyDetector; @@ -127,15 +143,30 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key 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 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key 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 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key } } - 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 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key } @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 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key } @Override - public void startLongPressTimer(PointerTracker tracker) { + public void startLongPressTimer(final PointerTracker tracker) { cancelLongPressTimer(); if (tracker == null) { return; @@ -206,15 +237,15 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key 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 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key } @Override - public void startTypingStateTimer(Key typedKey) { + public void startTypingStateTimer(final Key typedKey) { if (typedKey.isModifier() || typedKey.altCodeWhileTyping()) { return; } @@ -268,7 +299,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key } sendMessageDelayed( - obtainMessage(MSG_TYPING_STATE_EXPIRED), mParams.mIgnoreAltCodeKeyTimeout); + obtainMessage(MSG_TYPING_STATE_EXPIRED), mIgnoreAltCodeKeyTimeout); if (isTyping) { return; } @@ -307,55 +338,11 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key } } - 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 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key .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 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key 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 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key 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 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key 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 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key return mLanguageOnSpacebarAnimAlpha; } - public void setLanguageOnSpacebarAnimAlpha(int alpha) { + public void setLanguageOnSpacebarAnimAlpha(final int alpha) { mLanguageOnSpacebarAnimAlpha = alpha; invalidateKey(mSpaceKey); } @@ -434,12 +417,12 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key 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 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key * @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 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key } // 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 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key return mHasDistinctMultitouch; } - public void setDistinctMultitouch(boolean hasDistinctMultitouch) { + public void setDistinctMultitouch(final boolean hasDistinctMultitouch) { mHasDistinctMultitouch = hasDistinctMultitouch; } @@ -528,7 +511,17 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key // 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 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key 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 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key } // 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 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key * @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 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key 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 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key // 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 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key } @Override - public boolean onTouchEvent(MotionEvent me) { + public boolean onTouchEvent(final MotionEvent me) { if (getKeyboard() == null) { return false; } @@ -679,7 +672,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key } @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 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key * 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 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key 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 class MainKeyboardView extends KeyboardView implements PointerTracker.Key } } - 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 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key 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 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key } } - 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 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key } // 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 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key 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 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key // 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 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key } // 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 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key } // 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 a3741a2d8..c9af888f9 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.Paint; 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,228 +39,231 @@ public class MoreKeysKeyboard extends Keyboard { return mDefaultKeyCoordX; } - public static class Builder extends Keyboard.Builder<Builder.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; + /* 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(); + } - 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(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); } - - /** - * 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; + 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; } - - private int getFixedOrderTopRowAdjustment() { - if (mNumRows == 1 || mTopKeys % 2 == 1 || mTopKeys == mNumColumns - || mLeftKeys == 0 || mRightKeys == 1) { - return 0; - } - return -1; + // 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++; } - - private int getAutoOrderTopRowAdjustment() { - if (mNumRows == 1 || mTopKeys == 1 || mNumColumns % 2 == mTopKeys % 2 - || mLeftKeys == 0 || mRightKeys == 1) { - return 0; - } - return -1; + // 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; + } - // Return key position according to column count (0 is default). - /* package */int getColumnPos(int n) { - return mIsFixedOrder ? getFixedOrderColumnPos(n) : getAutomaticColumnPos(n); + private int getFixedOrderTopRowAdjustment() { + if (mNumRows == 1 || mTopKeys % 2 == 1 || mTopKeys == mNumColumns + || mLeftKeys == 0 || mRightKeys == 1) { + return 0; } + return -1; + } - 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 getAutoOrderTopRowAdjustment() { + if (mNumRows == 1 || mTopKeys == 1 || mNumColumns % 2 == mTopKeys % 2 + || mLeftKeys == 0 || mRightKeys == 1) { + return 0; } + return -1; + } - 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; - } + // Return key position according to column count (0 is default). + /* package */int getColumnPos(final int n) { + return mIsFixedOrder ? getFixedOrderColumnPos(n) : getAutomaticColumnPos(n); + } - 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; - } + 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 static int getTopRowEmptySlots(int numKeys, int numColumns) { - final int remainings = numKeys % numColumns; - return remainings == 0 ? 0 : numColumns - remainings; + 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; } - private int getOptimizedColumns(int numKeys, int maxColumns) { - int numColumns = Math.min(numKeys, maxColumns); - while (getTopRowEmptySlots(numKeys, numColumns) >= mNumRows) { - numColumns--; + 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++; } - return numColumns; + if (i >= col) + break; + // Assign left key if available. + if (left < leftKeys) { + left++; + pos = -left; + i++; + } + if (i >= col) + break; } + return pos; + } - public int getDefaultKeyCoordX() { - return mLeftKeys * mColumnWidth; - } + private static int getTopRowEmptySlots(final int numKeys, final int numColumns) { + final int remainings = numKeys % numColumns; + return remainings == 0 ? 0 : numColumns - remainings; + } - public int getX(int n, int row) { - final int x = getColumnPos(n) * mColumnWidth + getDefaultKeyCoordX(); - if (isTopRow(row)) { - return x + mTopRowAdjustment * (mColumnWidth / 2); - } - return x; + 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 getY(int row) { - return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding; - } + public int getDefaultKeyCoordX() { + return mLeftKeys * mColumnWidth; + } - public void markAsEdgeKey(Key key, int row) { - if (row == 0) - key.markAsTopEdge(this); - if (isTopRow(row)) - key.markAsBottomEdge(this); + 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; + } - private boolean isTopRow(int rowCount) { - return mNumRows > 1 && rowCount == mNumRows - 1; - } + 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; + + /** * The builder of MoreKeysKeyboard. * @param containerView the container of {@link MoreKeysKeyboardView}. * @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 @@ public class MoreKeysKeyboard extends Keyboard { 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 @@ public class MoreKeysKeyboard extends Keyboard { 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 @@ public class MoreKeysKeyboard extends Keyboard { 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 870eff29f..e513a1477 100644 --- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java @@ -25,6 +25,7 @@ import android.widget.PopupWindow; 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 class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel 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 7d565a64f..5a79d508f 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 class PointerTracker implements PointerTrackerQueue.Element { 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 @@ public class PointerTracker implements PointerTrackerQueue.Element { } } + 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 @@ public class PointerTracker implements PointerTrackerQueue.Element { 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 @@ public class PointerTracker implements PointerTrackerQueue.Element { 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 @@ public class PointerTracker implements PointerTrackerQueue.Element { 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 @@ public class PointerTracker implements PointerTrackerQueue.Element { } // 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 @@ public class PointerTracker implements PointerTrackerQueue.Element { 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 class PointerTracker implements PointerTrackerQueue.Element { } } - 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 @@ public class PointerTracker implements PointerTrackerQueue.Element { 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 @@ public class PointerTracker implements PointerTrackerQueue.Element { // 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 @@ public class PointerTracker implements PointerTrackerQueue.Element { // 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 @@ public class PointerTracker implements PointerTrackerQueue.Element { // 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 @@ public class PointerTracker implements PointerTrackerQueue.Element { } 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 @@ public class PointerTracker implements PointerTrackerQueue.Element { 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 @@ public class PointerTracker implements PointerTrackerQueue.Element { } 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 @@ public class PointerTracker implements PointerTrackerQueue.Element { return; } - if (!key.noKeyPreview() && !mInGesture) { + if (!key.noKeyPreview() && !sInGesture) { mDrawingProxy.showKeyPreview(this); } updatePressKeyGraphics(key); @@ -495,33 +479,31 @@ public class PointerTracker implements PointerTrackerQueue.Element { } 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 @@ public class PointerTracker implements PointerTrackerQueue.Element { 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(); - } - - private void updateBatchInput(InputPointers batchPoints) { - if (DEBUG_LISTENER) { - Log.d(TAG, "onUpdateBatchInput: batchPoints=" + batchPoints.getPointerSize()); + mDrawingProxy.showGesturePreviewTrail(this); + } + + 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(); - clearBatchInputPointsOfAllPointerTrackers(); + mDrawingProxy.showGesturePreviewTrail(this); } - private void abortBatchInput() { - clearBatchInputRecognitionStateOfThisPointerTracker(); + private static void abortBatchInput() { 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 class PointerTracker implements PointerTrackerQueue.Element { } } - 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 @@ public class PointerTracker implements PointerTrackerQueue.Element { 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 @@ public class PointerTracker implements PointerTrackerQueue.Element { } } - 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 @@ public class PointerTracker implements PointerTrackerQueue.Element { 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 @@ public class PointerTracker implements PointerTrackerQueue.Element { } } - 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 @@ public class PointerTracker implements PointerTrackerQueue.Element { 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 @@ public class PointerTracker implements PointerTrackerQueue.Element { // 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 @@ public class PointerTracker implements PointerTrackerQueue.Element { // 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 @@ public class PointerTracker implements PointerTrackerQueue.Element { if (mIsAllowedSlidingKeyInput) { onMoveToNewKey(key, x, y); } else { - if (!mIsPossibleGesture) { + if (!mIsDetectingGesture) { mKeyAlreadyProcessed = true; } } @@ -850,13 +864,14 @@ public class PointerTracker implements PointerTrackerQueue.Element { } } - 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 @@ public class PointerTracker implements PointerTrackerQueue.Element { 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 @@ public class PointerTracker implements PointerTrackerQueue.Element { 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 class PointerTracker implements PointerTrackerQueue.Element { } } - 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 @@ public class PointerTracker implements PointerTrackerQueue.Element { } } - 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 @@ public class PointerTracker implements PointerTrackerQueue.Element { } } - 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 ac0a56ba3..e1b082c16 100644 --- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java +++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java @@ -19,7 +19,8 @@ package com.android.inputmethod.keyboard; 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 @@ public class ProximityInfo { 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 class ProximityInfo { } 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 @@ public class ProximityInfo { 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 @@ public class ProximityInfo { 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 ee5047083..dc12fa468 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.view.ViewGroup.MarginLayoutParams; 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 000000000..e814d8009 --- /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 79e977a40..825134468 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 @@ public class GestureStroke { 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 @@ public class GestureStroke { } 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 @@ public class GestureStroke { 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 @@ public class GestureStroke { } 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 000000000..6c1a9bc01 --- /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 000000000..203bab6ff --- /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 000000000..996a722c0 --- /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 94a7b826f..2a57caa5f 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 static com.android.inputmethod.keyboard.Keyboard.CODE_UNSPECIFIED; 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 @@ public class KeySpecParser { 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 @@ public class KeySpecParser { 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 @@ public class KeySpecParser { 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 @@ public class KeySpecParser { return -1; } - public static String getLabel(String moreKeySpec) { + public static String getLabel(final String moreKeySpec) { if (hasIcon(moreKeySpec)) { return null; } @@ -169,7 +131,7 @@ public class KeySpecParser { 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 @@ public class KeySpecParser { 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 @@ public class KeySpecParser { 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 @@ public class KeySpecParser { 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 class KeySpecParser { } } - 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 @@ public class KeySpecParser { 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 @@ public class KeySpecParser { 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 @@ public class KeySpecParser { 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 @@ public class KeySpecParser { 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 @@ public class KeySpecParser { @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 @@ public class KeySpecParser { 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 @@ public class KeySpecParser { 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 @@ public class KeySpecParser { // 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 @@ public class KeySpecParser { 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 @@ public class KeySpecParser { 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 @@ public class KeySpecParser { 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 @@ public class KeySpecParser { ? 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 000000000..e8cacf9e7 --- /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 index 291b3b943..71fd30563 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.content.res.TypedArray; 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 org.xmlpull.v1.XmlPullParserException; 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; - } - - protected String[] parseStringArray(TypedArray a, int index) { - if (a.hasValue(index)) { - return KeySpecParser.parseCsvString(a.getString(index), mTextsSet); - } - return null; + private static class EmptyKeyStyle extends KeyStyle { + EmptyKeyStyle(final KeyboardTextsSet textsSet) { + super(textsSet); } - } - 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 @@ public class KeyStyles { } @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 @@ public class KeyStyles { } @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 @@ public class KeyStyles { } @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 @@ public class KeyStyles { 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 @@ public class KeyStyles { 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 @@ public class KeyStyles { "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 000000000..04cc152fe --- /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 000000000..31c7cb565 --- /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 f7981a320..f7923d0b9 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 5155851fe..4a98a3698 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.graphics.drawable.Drawable; 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 @@ public class KeyboardIconsSet { 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 000000000..ab5d31d42 --- /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 000000000..eb17b0ea4 --- /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 bec0f1fef..3b7c6ad7a 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 @@ package com.android.inputmethod.keyboard.internal; 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 @@ import java.util.HashMap; */ 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 @@ public final class KeyboardTextsSet { /* 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 @@ public final class KeyboardTextsSet { /* 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 @@ public final class KeyboardTextsSet { /* 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 @@ public final class KeyboardTextsSet { "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 000000000..f54617c98 --- /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 000000000..5da26543f --- /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 1c7ceaf92..e0858c019 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 @@ package com.android.inputmethod.keyboard.internal; import android.util.Log; +import com.android.inputmethod.latin.CollectionUtils; + import java.util.ArrayList; public class PointerTrackerQueue { @@ -32,7 +34,7 @@ public class PointerTrackerQueue { 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 d0fecf060..269b202b5 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; 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 @@ public class PreviewPlacerView extends RelativeLayout { 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; + + private final GesturePreviewTrailParams mGesturePreviewTrailParams; - public DrawingHandler(PreviewPlacerView outerInstance) { + 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 @@ public class PreviewPlacerView extends RelativeLayout { 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 @@ public class PreviewPlacerView extends RelativeLayout { 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 @@ public class PreviewPlacerView extends RelativeLayout { 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 @@ public class PreviewPlacerView extends RelativeLayout { 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 9e2cbec52..a591a7ac3 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.Keyboard; 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 class SuddenJumpingTouchEventHandler { 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 000000000..69dc01cd6 --- /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 f8f1395b3..4b47a261f 100644 --- a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java +++ b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java @@ -91,7 +91,7 @@ public class AdditionalSubtype { } 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 779a38823..d01592a4d 100644 --- a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java +++ b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java @@ -89,7 +89,7 @@ public class AdditionalSubtypeSettings extends PreferenceFragment { 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 @@ public class AdditionalSubtypeSettings extends PreferenceFragment { 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 048166807..01ba30077 100644 --- a/java/src/com/android/inputmethod/latin/AutoCorrection.java +++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java @@ -39,7 +39,6 @@ public class AutoCorrection { } 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 @@ public class AutoCorrection { } 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 f0f5cd320..8909526d8 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -18,6 +18,7 @@ package com.android.inputmethod.latin; 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 @@ public class BinaryDictionary extends Dictionary { 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 @@ public class BinaryDictionary extends Dictionary { 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 @@ public class BinaryDictionary extends Dictionary { 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 @@ public class BinaryDictionary extends Dictionary { @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 @@ public class BinaryDictionary extends Dictionary { 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 @@ public class BinaryDictionary extends Dictionary { } @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 236c198ad..799aea8ef 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -99,7 +99,7 @@ public class BinaryDictionaryFileDumper { } 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 @@ public class BinaryDictionaryFileDumper { 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 063243e1b..e1cb195bc 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.content.res.AssetFileDescriptor; 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 @@ class BinaryDictionaryGetter { 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 @@ class BinaryDictionaryGetter { 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 @@ class BinaryDictionaryGetter { 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 @@ class BinaryDictionaryGetter { 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 000000000..c75f2df5c --- /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 1242967ad..d71c0f995 100644 --- a/java/src/com/android/inputmethod/latin/Constants.java +++ b/java/src/com/android/inputmethod/latin/Constants.java @@ -128,6 +128,13 @@ public final class Constants { } } + 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 c76815363..359da72cc 100644 --- a/java/src/com/android/inputmethod/latin/DicTraverseSession.java +++ b/java/src/com/android/inputmethod/latin/DicTraverseSession.java @@ -22,6 +22,7 @@ public class DicTraverseSession { 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 @@ public class DicTraverseSession { 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 60fe17b19..88d0c09dd 100644 --- a/java/src/com/android/inputmethod/latin/Dictionary.java +++ b/java/src/com/android/inputmethod/latin/Dictionary.java @@ -42,7 +42,6 @@ public abstract class Dictionary { 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 @@ public abstract class Dictionary { 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 ee80f2532..4acab6b05 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java +++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java @@ -35,22 +35,22 @@ public class DictionaryCollection extends Dictionary { 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 @@ public class DictionaryCollection extends Dictionary { // 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 06a5f4b72..cdd01d0c7 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java +++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java @@ -53,7 +53,7 @@ public class DictionaryFactory { 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 016530abb..8a509be48 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java @@ -62,7 +62,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { * 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 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { * 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 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { // 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 d101aaf15..8a38d1e1b 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java @@ -19,7 +19,6 @@ package com.android.inputmethod.latin; import android.content.Context; import android.text.TextUtils; -import com.android.inputmethod.keyboard.KeyDetector; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; @@ -231,7 +230,7 @@ public class ExpandableDictionary extends Dictionary { 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 class ExpandableDictionary extends Dictionary { 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 @@ public class ExpandableDictionary extends Dictionary { 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 @@ public class ExpandableDictionary extends Dictionary { 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 @@ public class ExpandableDictionary extends Dictionary { 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 @@ public class ExpandableDictionary extends Dictionary { 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 @@ public class ExpandableDictionary extends Dictionary { 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 1461c0240..2674e4575 100644 --- a/java/src/com/android/inputmethod/latin/ImfUtils.java +++ b/java/src/com/android/inputmethod/latin/ImfUtils.java @@ -29,7 +29,7 @@ import java.util.List; /** * 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 e561f5956..7bcda9bc4 100644 --- a/java/src/com/android/inputmethod/latin/InputAttributes.java +++ b/java/src/com/android/inputmethod/latin/InputAttributes.java @@ -29,10 +29,12 @@ public class InputAttributes { 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 class InputAttributes { } } + 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 cbc916a7e..ff2feb51d 100644 --- a/java/src/com/android/inputmethod/latin/InputPointers.java +++ b/java/src/com/android/inputmethod/latin/InputPointers.java @@ -93,7 +93,7 @@ public class InputPointers { } 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 class InputPointers { 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 40c3b765e..500866a13 100644 --- a/java/src/com/android/inputmethod/latin/InputTypeUtils.java +++ b/java/src/com/android/inputmethod/latin/InputTypeUtils.java @@ -18,7 +18,7 @@ package com.android.inputmethod.latin; 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 86a3826d8..f9305991a 100644 --- a/java/src/com/android/inputmethod/latin/JniUtils.java +++ b/java/src/com/android/inputmethod/latin/JniUtils.java @@ -20,7 +20,7 @@ import android.util.Log; 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 bb39ce4f7..dd73a978c 100644 --- a/java/src/com/android/inputmethod/latin/LastComposedWord.java +++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java @@ -38,12 +38,12 @@ public class LastComposedWord { // 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 @@ public class LastComposedWord { // 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 @@ public class LastComposedWord { 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 446d44e7a..39c3a808f 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -361,7 +361,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } // 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } - if (!restarting) { + if (isDifferentTextField) { mainKeyboardView.closing(); loadSettings(); @@ -905,13 +909,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } } - 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mHandler.cancelDoubleSpacesTimer(); mConnection.deleteSurroundingText(2, 0); mConnection.commitText(". ", 1); - if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.latinIME_doubleSpaceAutoPeriod(); - } mKeyboardSwitcher.updateShiftState(); return true; } @@ -1185,9 +1195,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private void performEditorAction(int actionId) { mConnection.performEditorAction(actionId); - if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.latinIME_performEditorAction(actionId); - } } private void handleLanguageSwitchKey() { @@ -1224,6 +1231,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen break; case Keyboard.CODE_RESEARCH: if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.getInstance().presentResearchDialog(this); + ResearchLogger.getInstance().onResearchKeySelected(this); } break; default: @@ -1324,8 +1326,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mHandler.postUpdateSuggestionStrip(); } else { mConnection.deleteSurroundingText(1, 0); - if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.latinIME_deleteSurroundingText(1); - } } } else { if (mLastComposedWord.canRevertCommit()) { @@ -1486,6 +1474,18 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen Utils.Stats.onSeparator((char)primaryCode, x, y); + mHandler.postUpdateShiftState(); return didAutoCorrect; } @@ -1714,7 +1706,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen * 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen 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 b938dd336..feb1b2d0e 100644 --- a/java/src/com/android/inputmethod/latin/LocaleUtils.java +++ b/java/src/com/android/inputmethod/latin/LocaleUtils.java @@ -31,7 +31,10 @@ import java.util.Locale; * 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 @@ public class LocaleUtils { } } - 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 @@ public class LocaleUtils { 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 387d45a53..c660f92c4 100644 --- a/java/src/com/android/inputmethod/latin/ResizableIntArray.java +++ b/java/src/com/android/inputmethod/latin/ResizableIntArray.java @@ -131,4 +131,16 @@ public class ResizableIntArray { 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 000000000..5021ad384 --- /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 8b4c17322..41e59e92d 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -55,7 +55,9 @@ public class RichInputConnection { 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 class RichInputConnection { } 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 class RichInputConnection { 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 class RichInputConnection { 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 @@ public class RichInputConnection { 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 @@ public class RichInputConnection { 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 @@ public class RichInputConnection { 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 0843bdbbc..5e355a3b8 100644 --- a/java/src/com/android/inputmethod/latin/SettingsValues.java +++ b/java/src/com/android/inputmethod/latin/SettingsValues.java @@ -185,7 +185,7 @@ public class SettingsValues { // 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 @@ public class SettingsValues { 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 @@ public class SettingsValues { 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 class SettingsValues { 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 class SettingsValues { 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 6e7d985d6..9c47a38c2 100644 --- a/java/src/com/android/inputmethod/latin/StringUtils.java +++ b/java/src/com/android/inputmethod/latin/StringUtils.java @@ -21,7 +21,7 @@ import android.text.TextUtils; 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 @@ public class StringUtils { 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 21c9c0d1e..de5f515b0 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeLocale.java +++ b/java/src/com/android/inputmethod/latin/SubtypeLocale.java @@ -45,13 +45,13 @@ public class SubtypeLocale { 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 @@ public class SubtypeLocale { "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 a7a5fcb5f..c693edcca 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.Context; 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 @@ public class SubtypeSwitcher { 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 @@ public class SubtypeSwitcher { 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 @@ public class SubtypeSwitcher { 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 @@ public class SubtypeSwitcher { // 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 @@ public class SubtypeSwitcher { 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 @@ public class SubtypeSwitcher { 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 @@ public class SubtypeSwitcher { 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 @@ public class SubtypeSwitcher { // 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 @@ public class SubtypeSwitcher { 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 8a2341d5e..51ed09604 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -50,9 +50,8 @@ public class Suggest { 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 @@ public class Suggest { 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 class Suggest { 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 @@ public class Suggest { 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 @@ public class Suggest { 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 @@ public class Suggest { } 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 @@ public class Suggest { // 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 @@ public class Suggest { 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 class Suggest { } 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 88fc006df..68ecfa0d7 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.Arrays; 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 class SuggestedWords { 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 @@ public class SuggestedWords { // 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 000000000..942c82837 --- /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 3bb670c9a..6c9d1c250 100644 --- a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java +++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java @@ -52,14 +52,14 @@ public class UserHistoryDictionary extends ExpandableDictionary { 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 @@ public class UserHistoryDictionary extends ExpandableDictionary { 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 @@ public class UserHistoryDictionary extends ExpandableDictionary { 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 @@ public class UserHistoryDictionary extends ExpandableDictionary { needsToSave(fc, isValid, addLevel0Bigram)) { freq = fc; } else { + // Delete this entry freq = -1; } } else { + // Delete this entry freq = -1; } } @@ -541,6 +539,7 @@ public class UserHistoryDictionary extends ExpandableDictionary { 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 610652ac1..bb0f5429a 100644 --- a/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java +++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java @@ -29,9 +29,8 @@ import java.util.Set; 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 @@ public class UserHistoryDictionaryBigramList { 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 5a2fdf48e..3d3bd980c 100644 --- a/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java +++ b/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java @@ -19,7 +19,7 @@ package com.android.inputmethod.latin; 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 c6b5c338b..1c98b92cd 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.io.PrintWriter; 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 class Utils { } } - 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 @@ public class Utils { 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 class Utils { } } - 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 @@ public class Utils { 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 33ffdd9c9..b6696cec0 100644 --- a/java/src/com/android/inputmethod/latin/VibratorUtils.java +++ b/java/src/com/android/inputmethod/latin/VibratorUtils.java @@ -19,7 +19,7 @@ package com.android.inputmethod.latin; 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 14476dcf0..000000000 --- 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 5606a58e4..4b7adf26b 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 @@ import java.util.Arrays; * 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 @@ public class WordComposer { // 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 @@ public class WordComposer { 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 @@ public class WordComposer { 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 @@ public class WordComposer { 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 @@ public class WordComposer { * @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 @@ public class WordComposer { } /** - * 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 @@ public class WordComposer { // `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 481cdfa47..b5cbaf19e 100644 --- a/java/src/com/android/inputmethod/latin/XmlParseUtils.java +++ b/java/src/com/android/inputmethod/latin/XmlParseUtils.java @@ -23,7 +23,7 @@ import org.xmlpull.v1.XmlPullParserException; 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 2c3eee74c..abc39d923 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.Node; 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 @@ public class BinaryDictInputOutput { * 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 @@ public class BinaryDictInputOutput { */ 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 @@ public class BinaryDictInputOutput { 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 @@ public class BinaryDictInputOutput { // 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 @@ public class BinaryDictInputOutput { } /** - * 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 @@ public class BinaryDictInputOutput { } 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 @@ public class BinaryDictInputOutput { // 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 @@ public class BinaryDictInputOutput { // 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 @@ public class BinaryDictInputOutput { } 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 @@ public class BinaryDictInputOutput { } /** - * 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 @@ public class BinaryDictInputOutput { // 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 @@ public class BinaryDictInputOutput { 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 @@ public class BinaryDictInputOutput { } 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 @@ public class BinaryDictInputOutput { 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 @@ public class BinaryDictInputOutput { 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 @@ public class BinaryDictInputOutput { 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 @@ public class BinaryDictInputOutput { * @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 5864db28e..f1abea9ec 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 @@ public class FusionDictionary implements Iterable<Word> { 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 @@ public class FusionDictionary implements Iterable<Word> { * 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 @@ public class FusionDictionary implements Iterable<Word> { } } } + mIsNotAWord = isNotAWord; + mIsBlacklistEntry = isBlacklistEntry; } } @@ -296,10 +307,24 @@ public class FusionDictionary implements Iterable<Word> { * @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 @@ public class FusionDictionary implements Iterable<Word> { 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 @@ public class FusionDictionary implements Iterable<Word> { * @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 @@ public class FusionDictionary implements Iterable<Word> { 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 @@ public class FusionDictionary implements Iterable<Word> { // 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 @@ public class FusionDictionary implements Iterable<Word> { 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 @@ public class FusionDictionary implements Iterable<Word> { 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 @@ public class FusionDictionary implements Iterable<Word> { 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 @@ public class FusionDictionary implements Iterable<Word> { 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 @@ public class FusionDictionary implements Iterable<Word> { } 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 65fc72c40..4683ef154 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 class Word implements Comparable<Word> { 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 @@ public class Word implements Comparable<Word> { word.mWord, word.mFrequency, word.mShortcutTargets.hashCode(), - word.mBigrams.hashCode() + word.mBigrams.hashCode(), + word.mIsNotAWord, + word.mIsBlacklistEntry }); } @@ -78,7 +85,9 @@ public class Word implements Comparable<Word> { 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 3bdfe1f27..eef7a51f2 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 android.view.textservice.SuggestionsInfo; 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.StringUtils; 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 class AndroidSpellCheckerService extends SpellCheckerService 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 @@ public class AndroidSpellCheckerService extends SpellCheckerService 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 @@ public class AndroidSpellCheckerService extends SpellCheckerService // 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 @@ public class AndroidSpellCheckerService extends SpellCheckerService 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 @@ public class AndroidSpellCheckerService extends SpellCheckerService 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 @@ public class AndroidSpellCheckerService extends SpellCheckerService 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 @@ public class AndroidSpellCheckerService extends SpellCheckerService 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 501a0e221..5a1bd37f5 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.SentenceSuggestionsInfo; 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 @@ public class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheckerSess 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 06f5db749..f4784ff1a 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.SuggestionsInfo; 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 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { 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 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { 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 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { 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 83f82faeb..53aa6c719 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 @@ package com.android.inputmethod.latin.spellcheck; 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 @@ public class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> { 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 class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> { 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 bd92d883b..fe5225ebd 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 @@ public class SpellCheckerProximityInfo { // 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 @@ public class SpellCheckerProximityInfo { } 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 58b01aa55..1f883aa60 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 android.graphics.drawable.Drawable; 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 @@ import com.android.inputmethod.latin.Utils; 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 final MoreSuggestionsView mPaneView; - private SuggestedWords mSuggestions; - private int mFromPos; - private int mToPos; + 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 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++; + 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; } - mColumnOrders[pos] = pos - rowStartPos; - mRowNumbers[pos] = row; - pos++; + mNumColumnsInRow[row] = pos - rowStartPos; + rowStartPos = pos; + row++; } - mNumColumnsInRow[row] = pos - rowStartPos; - mNumRows = row + 1; - mBaseWidth = mOccupiedWidth = Math.max( - minWidth, calcurateMaxRowWidth(fromPos, pos)); - mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap; - return pos - fromPos; + 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 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(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)); + 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++; } - return maxRowWidth; + 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]]; - } + private static final int[][] COLUMN_ORDER_TO_NUMBER = { + { 0, }, + { 1, 0, }, + { 2, 0, 1}, + }; - 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 getNumColumnInRow(final int pos) { + return mNumColumnsInRow[mRowNumbers[pos]]; + } - public int getX(int pos) { - final int columnNumber = getColumnNumber(pos); - return columnNumber * (getWidth(pos) + mDividerWidth); - } + 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 getY(int pos) { - final int row = mRowNumbers[pos]; - return (mNumRows -1 - row) * mDefaultRowHeight + mTopPadding; - } + public int getX(final int pos) { + final int columnNumber = getColumnNumber(pos); + return columnNumber * (getWidth(pos) + mDividerWidth); + } - public int getWidth(int pos) { - final int numColumnInRow = getNumColumnInRow(pos); - return (mOccupiedWidth - mDividerWidth * (numColumnInRow - 1)) / numColumnInRow; - } + public int getY(final int pos) { + final int row = mRowNumbers[pos]; + return (mNumRows -1 - row) * mDefaultRowHeight + mTopPadding; + } - 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); + public int getWidth(final int pos) { + final int numColumnInRow = getNumColumnInRow(pos); + return (mOccupiedWidth - mDividerWidth * (numColumnInRow - 1)) / numColumnInRow; + } - final int numColumnInRow = mNumColumnsInRow[row]; - final int column = getColumnNumber(pos); - if (column == 0) - key.markAsLeftEdge(this); - if (column == numColumnInRow - 1) - key.markAsRightEdge(this); - } + 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 Builder(MoreSuggestionsView paneView) { + public static class Builder extends KeyboardBuilder<MoreSuggestionsParam> { + private final MoreSuggestionsView mPaneView; + private SuggestedWords mSuggestions; + private int mFromPos; + private int mToPos; + + 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 @@ public class MoreSuggestions extends Keyboard { 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 @@ public class MoreSuggestions extends Keyboard { 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 b57ffd2de..9e8ab81b0 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.MoreKeysPanel; 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 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen 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 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen 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 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen 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 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen 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 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen 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 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen 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 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen 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 class SuggestionStripView extends RelativeLayout implements OnClickListen 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 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen 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 000000000..5124a35a6 --- /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 c9f3b476a..11eae8813 100644 --- a/java/src/com/android/inputmethod/research/FeedbackActivity.java +++ b/java/src/com/android/inputmethod/research/FeedbackActivity.java @@ -18,10 +18,7 @@ package com.android.inputmethod.research; 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 @@ public class FeedbackActivity extends Activity { 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 000000000..ae7b1579a --- /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 000000000..d8b3a29ff --- /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 000000000..745768d35 --- /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 18bf3c07f..cd9ff85f8 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.keyboard.Key; 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.io.OutputStreamWriter; 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 @@ import java.util.concurrent.TimeUnit; */ 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 class ResearchLog { } } - 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: - } + } 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(); } - public boolean isAlive() { - switch (mLoggingState) { - case LOGGING_STATE_UNSTARTED: - case LOGGING_STATE_READY: - case LOGGING_STATE_RUNNING: - return true; - } - return false; - } + private boolean mIsAbortSuccessful; - public void waitUntilStopped(final int timeoutInMs) throws InterruptedException { + public synchronized void abort() { + mExecutor.submit(new Callable<Object>() { + @Override + public Object call() throws Exception { + try { + if (mHasWrittenData) { + mJsonWriter.endArray(); + mJsonWriter.close(); + mHasWrittenData = false; + } + } finally { + mIsAbortSuccessful = mFile.delete(); + } + return null; + } + }); removeAnyScheduledFlush(); mExecutor.shutdown(); - mExecutor.awaitTermination(timeoutInMs, TimeUnit.MILLISECONDS); } - 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; - } - }); - removeAnyScheduledFlush(); - mExecutor.shutdown(); - mLoggingState = LOGGING_STATE_STOPPING; - break; - case LOGGING_STATE_STOPPING: - case LOGGING_STATE_STOPPED: - } + public boolean blockingAbort() throws InterruptedException { + abort(); + mExecutor.awaitTermination(ABORT_TIMEOUT_IN_MS, TimeUnit.MILLISECONDS); + return mIsAbortSuccessful; } - private boolean isAbortSuccessful; - public boolean isAbortSuccessful() { - return isAbortSuccessful; + 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 @@ public class ResearchLog { 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 @@ public class ResearchLog { 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 @@ public class ResearchLog { 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 @@ public class ResearchLog { 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 @@ public class ResearchLog { 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 3b1213009..000000000 --- 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 cf6f31a0a..5c2487195 100644 --- a/java/src/com/android/inputmethod/research/ResearchLogger.java +++ b/java/src/com/android/inputmethod/research/ResearchLogger.java @@ -18,11 +18,14 @@ package com.android.inputmethod.research; 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.Color; 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.SuggestedWords; 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 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang 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 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang // 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 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang 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 class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } } - 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,54 +244,71 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang 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(); + restart(); + } + + private void setLoggingAllowed(boolean enableLogging) { + if (mPrefs == null) { + return; + } + Editor e = mPrefs.edit(); + e.putBoolean(PREF_USABILITY_STUDY_MODE, enableLogging); + e.apply(); + sIsLogging = enableLogging; } private File createLogFile(File filesDir) { @@ -268,10 +320,35 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang return new File(filesDir, sb.toString()); } + 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; @@ -280,73 +357,58 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang 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."); + 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() { - if (mMainResearchLog != null) { - mMainResearchLog.stop(); - } - if (mIntentionalResearchLog != null) { - mIntentionalResearchLog.stop(); - } - } + logStatistics(); + commitCurrentLogUnit(); - private void setLoggingAllowed(boolean enableLogging) { - if (mPrefs == null) { - return; + if (mMainLogBuffer != null) { + publishLogBuffer(mMainLogBuffer, mMainResearchLog, false /* isIncludingPrivateData */); + mMainResearchLog.close(); + mMainLogBuffer = null; + } + if (mFeedbackLogBuffer != null) { + mFeedbackLog.close(); + mFeedbackLogBuffer = null; } - Editor e = mPrefs.edit(); - e.putBoolean(PREF_USABILITY_STUDY_MODE, enableLogging); - e.apply(); - sIsLogging = enableLogging; } public boolean abort() { boolean didAbortMainLog = false; - if (mMainResearchLog != null) { - mMainResearchLog.abort(); + if (mMainLogBuffer != null) { + mMainLogBuffer.clear(); try { - mMainResearchLog.waitUntilStopped(ABORT_TIMEOUT_IN_MS); + didAbortMainLog = mMainResearchLog.blockingAbort(); } catch (InterruptedException e) { - // interrupted early. carry on. + // Don't know whether this succeeded or not. We assume not; this is reported + // to the caller. } - if (mMainResearchLog.isAbortSuccessful()) { - didAbortMainLog = true; - } - mMainResearchLog = null; + mMainLogBuffer = null; } - boolean didAbortIntentionalLog = false; - if (mIntentionalResearchLog != null) { - mIntentionalResearchLog.abort(); + boolean didAbortFeedbackLog = false; + if (mFeedbackLogBuffer != null) { + mFeedbackLogBuffer.clear(); try { - mIntentionalResearchLog.waitUntilStopped(ABORT_TIMEOUT_IN_MS); + didAbortFeedbackLog = mFeedbackLog.blockingAbort(); } catch (InterruptedException e) { - // interrupted early. carry on. - } - if (mIntentionalResearchLog.isAbortSuccessful()) { - didAbortIntentionalLog = true; + // Don't know whether this succeeded or not. We assume not; this is reported + // to the caller. } - mIntentionalResearchLog = null; - } - return didAbortMainLog && didAbortIntentionalLog; - } - - /* package */ void flush() { - if (mMainResearchLog != null) { - mMainResearchLog.flush(); + mFeedbackLogBuffer = null; } + return didAbortMainLog && didAbortFeedbackLog; } private void restart() { @@ -387,14 +449,22 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang 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 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang 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 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang .setTitle(title); latinIME.showOptionDialog(builder.create()); } + */ private boolean mInFeedbackDialog = false; public void presentFeedbackDialog(LatinIME latinIME) { @@ -450,79 +500,73 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang 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 int LOG_DRAIN_TIMEOUT_IN_MS = 1000 * 5; + private static final String[] EVENTKEYS_FEEDBACK = { + "UserTimestamp", "contents" + }; 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); + if (mFeedbackLogBuffer == null) { + return; + } + if (includeHistory) { + commitCurrentLogUnit(); } 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(); - } + 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)); + } + + 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 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } private boolean isAllowedToLog() { - return !mIsPasswordView && !mIsLoggingSuspended && sIsLogging; + return !mIsPasswordView && !mIsLoggingSuspended && sIsLogging && !mInFeedbackDialog; } public void requestIndicatorRedraw() { if (!IS_SHOWING_INDICATOR) { return; } - if (mKeyboardSwitcher == null) { - return; - } - final KeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); - if (mainKeyboardView == null) { + if (mMainKeyboardView == null) { return; } - mainKeyboardView.invalidateAllKeys(); + mMainKeyboardView.invalidateAllKeys(); } @@ -577,13 +617,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } } - 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 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang 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 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang 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--; + /* 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 boolean isSafeToLog() { - return mWordsRemainingToSkip <= 1; + /* 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); } } - /* 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; - } + private boolean hasOnlyLetters(final String word) { 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; + final int codePoint = word.codePointAt(i); + if (!Character.isLetter(codePoint)) { + return false; } - 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(); + return true; } - 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)); - } - } - } - - 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)); - } + 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 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang 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 class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang 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 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang 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 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang "LatinIMEOnCodeInput", "code", "x", "y" }; public static void latinIME_onCodeInput(final int code, final int x, final int y) { - final Object[] values = { - Keyboard.printableCode(scrubDigitFromCodePoint(code)), x, y - }; - getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONCODEINPUT, values); - } - - private static final String[] EVENTKEYS_CORRECTION = { - "LogCorrection", "subgroup", "before", "after", "position" - }; - public static void logCorrection(final String subgroup, final String before, final String after, - final int position) { - final Object[] values = { - subgroup, scrubDigitsFromString(before), scrubDigitsFromString(after), position - }; - getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_CORRECTION, values); - } - - private static final String[] EVENTKEYS_LATINIME_COMMITCURRENTAUTOCORRECTION = { - "LatinIMECommitCurrentAutoCorrection", "typedWord", "autoCorrection" - }; - public static void latinIME_commitCurrentAutoCorrection(final String typedWord, - final String autoCorrection) { - final Object[] values = { - scrubDigitsFromString(typedWord), scrubDigitsFromString(autoCorrection) - }; + final long time = SystemClock.uptimeMillis(); 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 + Keyboard.printableCode(scrubDigitFromCodePoint(code)), x, y }; - 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 class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang 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 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } 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 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang 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 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang "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 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang 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 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang 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 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang 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 class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang 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 000000000..eab465aa2 --- /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 000000000..7a5749096 --- /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; + } +} |