Merge remote-tracking branch 'goog/jb-mr1-dev' into mergescriptpackage

main
Satoshi Kataoka 2012-08-29 14:01:41 +09:00
commit 9761fa5786
145 changed files with 5257 additions and 3465 deletions

View File

@ -0,0 +1,297 @@
<?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.
*/
-->
<shortcuts>
<entry shortcut="ill">
<target replacement="I'll" priority="whitelist" />
</entry>
<entry shortcut="acomodate">
<target replacement="accommodate" priority="whitelist" />
</entry>
<entry shortcut="aint">
<target replacement="ain't" priority="whitelist" />
</entry>
<entry shortcut="alot">
<target replacement="a lot" priority="whitelist" />
</entry>
<entry shortcut="andteh">
<target replacement="and the" priority="whitelist" />
</entry>
<entry shortcut="arent">
<target replacement="aren't" priority="whitelist" />
</entry>
<entry shortcut="bern">
<target replacement="been" priority="whitelist" />
</entry>
<entry shortcut="bot">
<target replacement="not" priority="whitelist" />
</entry>
<entry shortcut="bur">
<target replacement="but" priority="whitelist" />
</entry>
<entry shortcut="cam">
<target replacement="can" priority="whitelist" />
</entry>
<entry shortcut="cant">
<target replacement="can't" priority="whitelist" />
</entry>
<entry shortcut="dame">
<target replacement="same" priority="whitelist" />
</entry>
<entry shortcut="didint">
<target replacement="didn't" priority="whitelist" />
</entry>
<entry shortcut="dormer">
<target replacement="former" priority="whitelist" />
</entry>
<entry shortcut="dud">
<target replacement="did" priority="whitelist" />
</entry>
<entry shortcut="fay">
<target replacement="day" priority="whitelist" />
</entry>
<entry shortcut="fife">
<target replacement="five" priority="whitelist" />
</entry>
<entry shortcut="foo">
<target replacement="for" priority="whitelist" />
</entry>
<entry shortcut="fora">
<target replacement="for a" priority="whitelist" />
</entry>
<entry shortcut="galled">
<target replacement="called" priority="whitelist" />
</entry>
<entry shortcut="goo">
<target replacement="too" priority="whitelist" />
</entry>
<entry shortcut="hed">
<target replacement="he'd" priority="whitelist" />
</entry>
<entry shortcut="hel">
<target replacement="he'll" priority="whitelist" />
</entry>
<entry shortcut="heres">
<target replacement="here's" priority="whitelist" />
</entry>
<entry shortcut="hew">
<target replacement="new" priority="whitelist" />
</entry>
<entry shortcut="hoe">
<target replacement="how" priority="whitelist" />
</entry>
<entry shortcut="hoes">
<target replacement="how's" priority="whitelist" />
</entry>
<entry shortcut="howd">
<target replacement="how'd" priority="whitelist" />
</entry>
<entry shortcut="howll">
<target replacement="how'll" priority="whitelist" />
</entry>
<entry shortcut="hows">
<target replacement="how's" priority="whitelist" />
</entry>
<entry shortcut="howve">
<target replacement="how've" priority="whitelist" />
</entry>
<entry shortcut="hum">
<target replacement="him" priority="whitelist" />
</entry>
<entry shortcut="i">
<target replacement="I" priority="whitelist" />
</entry>
<entry shortcut="ifs">
<target replacement="its" priority="whitelist" />
</entry>
<entry shortcut="il">
<target replacement="I'll" priority="whitelist" />
</entry>
<entry shortcut="im">
<target replacement="I'm" priority="whitelist" />
</entry>
<entry shortcut="inteh">
<target replacement="in the" priority="whitelist" />
</entry>
<entry shortcut="itd">
<target replacement="it'd" priority="whitelist" />
</entry>
<entry shortcut="itsa">
<target replacement="it's a" priority="whitelist" />
</entry>
<entry shortcut="lets">
<target replacement="let's" priority="whitelist" />
</entry>
<entry shortcut="maam">
<target replacement="ma'am" priority="whitelist" />
</entry>
<entry shortcut="manu">
<target replacement="many" priority="whitelist" />
</entry>
<entry shortcut="mare">
<target replacement="made" priority="whitelist" />
</entry>
<entry shortcut="mew">
<target replacement="new" priority="whitelist" />
</entry>
<entry shortcut="mire">
<target replacement="more" priority="whitelist" />
</entry>
<entry shortcut="moat">
<target replacement="most" priority="whitelist" />
</entry>
<entry shortcut="mot">
<target replacement="not" priority="whitelist" />
</entry>
<entry shortcut="mote">
<target replacement="note" priority="whitelist" />
</entry>
<entry shortcut="motes">
<target replacement="notes" priority="whitelist" />
</entry>
<entry shortcut="mow">
<target replacement="now" priority="whitelist" />
</entry>
<entry shortcut="namer">
<target replacement="named" priority="whitelist" />
</entry>
<entry shortcut="nave">
<target replacement="have" priority="whitelist" />
</entry>
<entry shortcut="nee">
<target replacement="new" priority="whitelist" />
</entry>
<entry shortcut="nigh">
<target replacement="high" priority="whitelist" />
</entry>
<entry shortcut="nit">
<target replacement="not" priority="whitelist" />
</entry>
<entry shortcut="oft">
<target replacement="off" priority="whitelist" />
</entry>
<entry shortcut="os">
<target replacement="is" priority="whitelist" />
</entry>
<entry shortcut="pater">
<target replacement="later" priority="whitelist" />
</entry>
<entry shortcut="rook">
<target replacement="took" priority="whitelist" />
</entry>
<entry shortcut="shel">
<target replacement="she'll" priority="whitelist" />
</entry>
<entry shortcut="shouldent">
<target replacement="shouldn't" priority="whitelist" />
</entry>
<entry shortcut="sill">
<target replacement="will" priority="whitelist" />
</entry>
<entry shortcut="sown">
<target replacement="down" priority="whitelist" />
</entry>
<entry shortcut="thatd">
<target replacement="that'd" priority="whitelist" />
</entry>
<entry shortcut="tine">
<target replacement="time" priority="whitelist" />
</entry>
<entry shortcut="thong">
<target replacement="thing" priority="whitelist" />
</entry>
<entry shortcut="tome">
<target replacement="time" priority="whitelist" />
</entry>
<entry shortcut="uf">
<target replacement="if" priority="whitelist" />
</entry>
<entry shortcut="un">
<target replacement="in" priority="whitelist" />
</entry>
<entry shortcut="UnitedStates">
<target replacement="United States" priority="whitelist" />
</entry>
<entry shortcut="unitedstates">
<target replacement="United States" priority="whitelist" />
</entry>
<entry shortcut="visavis">
<target replacement="vis-a-vis" priority="whitelist" />
</entry>
<entry shortcut="wierd">
<target replacement="weird" priority="whitelist" />
</entry>
<entry shortcut="wel">
<target replacement="we'll" priority="whitelist" />
</entry>
<entry shortcut="wer">
<target replacement="we're" priority="whitelist" />
</entry>
<entry shortcut="whatd">
<target replacement="what'd" priority="whitelist" />
</entry>
<entry shortcut="whatm">
<target replacement="what'm" priority="whitelist" />
</entry>
<entry shortcut="whatre">
<target replacement="what're" priority="whitelist" />
</entry>
<entry shortcut="whats">
<target replacement="what's" priority="whitelist" />
</entry>
<entry shortcut="whens">
<target replacement="when's" priority="whitelist" />
</entry>
<entry shortcut="whered">
<target replacement="where'd" priority="whitelist" />
</entry>
<entry shortcut="wherell">
<target replacement="where'll" priority="whitelist" />
</entry>
<entry shortcut="wheres">
<target replacement="where's" priority="whitelist" />
</entry>
<entry shortcut="wholl">
<target replacement="who'll" priority="whitelist" />
</entry>
<entry shortcut="whove">
<target replacement="who've" priority="whitelist" />
</entry>
<entry shortcut="whyd">
<target replacement="why'd" priority="whitelist" />
</entry>
<entry shortcut="whyll">
<target replacement="why'll" priority="whitelist" />
</entry>
<entry shortcut="whys">
<target replacement="why's" priority="whitelist" />
</entry>
<entry shortcut="whyve">
<target replacement="why've" priority="whitelist" />
</entry>
<entry shortcut="wont">
<target replacement="won't" priority="whitelist" />
</entry>
<entry shortcut="yall">
<target replacement="y'all" priority="whitelist" />
</entry>
<entry shortcut="youd">
<target replacement="you'd" priority="whitelist" />
</entry>
</shortcuts>

View File

@ -44,6 +44,10 @@
<init>(...);
}
-keepclasseswithmembernames class * {
native <methods>;
}
-keep class com.android.inputmethod.research.ResearchLogger {
void flush();
void publishCurrentLogUnit(...);

View File

@ -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>

View File

@ -41,26 +41,24 @@
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" />
<!-- 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="float" />
<!-- Size of the text for keys with multiple letters, in the proportion of key height. -->
<attr name="keyLabelRatio" format="float" />
<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="float" />
<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="float" />
<attr name="keyHintLetterRatio" format="fraction" />
<!-- Size of the text for hint label, in the proportion of key height. -->
<attr name="keyHintLabelRatio" format="float" />
<attr name="keyHintLabelRatio" format="fraction" />
<!-- Size of the text for shifted letter hint, in the proportion of key height. -->
<attr name="keyShiftedLetterHintRatio" format="float" />
<attr name="keyShiftedLetterHintRatio" format="dimension|fraction" />
<!-- 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.-->
@ -96,8 +94,8 @@
<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" />
<!-- Size of the text for key press feedback popup, in the proportion of key height. -->
<attr name="keyPreviewTextRatio" format="fraction" />
<!-- Delay after key releasing and key press feedback dismissing in millisecond -->
<attr name="keyPreviewLingerTimeout" format="integer" />
@ -131,6 +129,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 +185,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>

View File

@ -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
-->

View File

@ -92,7 +92,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 +101,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>

View File

@ -22,5 +22,6 @@
<!-- Build.HARDWARE,duration_in_milliseconds -->
<item>herring,5</item>
<item>tuna,5</item>
<item>mako,20</item>
</string-array>
</resources>

View File

@ -24,5 +24,6 @@
<item>tuna,0.5</item>
<item>stingray,0.4</item>
<item>grouper,0.3</item>
<item>mako,0.3</item>
</string-array>
</resources>

View File

@ -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>
@ -288,6 +289,10 @@
<!-- TODO: remove translatable=false attribute once text is stable -->
<string name="research_send_usage_info" translatable="false">Send usage info</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_log_uploader_name" translatable="false">Research Uploader Service</string>
<!-- Preference for input language selection -->
<string name="select_language">Input languages</string>

View File

@ -35,9 +35,9 @@
<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>
@ -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>
@ -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>

View File

@ -1,29 +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">
</string-array>
</resources>

View File

@ -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();

View File

@ -19,10 +19,15 @@ package com.android.inputmethod.accessibility;
import android.content.Context;
import android.inputmethodservice.InputMethodService;
import android.media.AudioManager;
import android.os.Build;
import android.os.SystemClock;
import android.provider.Settings;
import android.support.v4.view.accessibility.AccessibilityEventCompat;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.inputmethod.EditorInfo;
@ -138,9 +143,10 @@ public class AccessibilityUtils {
* Sends the specified text to the {@link AccessibilityManager} to be
* spoken.
*
* @param text the text to speak
* @param view The source view.
* @param text The text to speak.
*/
public void speak(CharSequence text) {
public void announceForAccessibility(View view, CharSequence text) {
if (!mAccessibilityManager.isEnabled()) {
Log.e(TAG, "Attempted to speak when accessibility was disabled!");
return;
@ -149,8 +155,7 @@ public class AccessibilityUtils {
// The following is a hack to avoid using the heavy-weight TextToSpeech
// class. Instead, we're just forcing a fake AccessibilityEvent into
// the screen reader to make it speak.
final AccessibilityEvent event = AccessibilityEvent
.obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED);
final AccessibilityEvent event = AccessibilityEvent.obtain();
event.setPackageName(PACKAGE);
event.setClassName(CLASS);
@ -158,20 +163,34 @@ public class AccessibilityUtils {
event.setEnabled(true);
event.getText().add(text);
mAccessibilityManager.sendAccessibilityEvent(event);
// Platforms starting at SDK 16 should use announce events.
if (Build.VERSION.SDK_INT >= 16) {
event.setEventType(AccessibilityEventCompat.TYPE_ANNOUNCEMENT);
} else {
event.setEventType(AccessibilityEvent.TYPE_VIEW_FOCUSED);
}
final ViewParent viewParent = view.getParent();
if ((viewParent == null) || !(viewParent instanceof ViewGroup)) {
Log.e(TAG, "Failed to obtain ViewParent in announceForAccessibility");
return;
}
viewParent.requestSendAccessibilityEvent(view, event);
}
/**
* Handles speaking the "connect a headset to hear passwords" notification
* when connecting to a password field.
*
* @param view The source view.
* @param editorInfo The input connection's editor info attribute.
* @param restarting Whether the connection is being restarted.
*/
public void onStartInputViewInternal(EditorInfo editorInfo, boolean restarting) {
public void onStartInputViewInternal(View view, EditorInfo editorInfo, boolean restarting) {
if (shouldObscureInput(editorInfo)) {
final CharSequence text = mContext.getText(R.string.spoken_use_headphones);
speak(text);
announceForAccessibility(view, text);
}
}

View File

@ -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);
}
/**
@ -114,8 +114,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;
@ -123,7 +129,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$
@ -250,7 +256,7 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
text = context.getText(R.string.spoken_description_shiftmode_off);
}
AccessibilityUtils.getInstance().speak(text);
AccessibilityUtils.getInstance().announceForAccessibility(mView, text);
}
/**
@ -290,6 +296,6 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
}
final String text = context.getString(resId);
AccessibilityUtils.getInstance().speak(text);
AccessibilityUtils.getInstance().announceForAccessibility(mView, text);
}
}

View File

@ -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();
}

View File

@ -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,6 +23,11 @@ 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;
@ -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;

View File

@ -16,10 +16,10 @@
package com.android.inputmethod.keyboard;
import com.android.inputmethod.latin.Constants;
public class KeyDetector {
public static final int NOT_A_CODE = -1;
private final int mKeyHysteresisDistanceSquared;
private Keyboard mKeyboard;
@ -59,6 +59,9 @@ public class KeyDetector {
}
public Keyboard getKeyboard() {
if (mKeyboard == null) {
throw new IllegalStateException("keyboard isn't set");
}
return mKeyboard;
}
@ -100,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;

View File

@ -33,6 +33,7 @@ import com.android.inputmethod.keyboard.internal.KeyStyles;
import com.android.inputmethod.keyboard.internal.KeyboardCodesSet;
import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
import com.android.inputmethod.keyboard.internal.KeyboardTextsSet;
import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
import com.android.inputmethod.latin.R;
@ -134,7 +135,7 @@ 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;
@ -219,6 +220,11 @@ public class Keyboard {
return code >= CODE_SPACE;
}
@Override
public String toString() {
return mId.toString();
}
public static class Params {
public KeyboardId mId;
public int mThemeId;
@ -249,9 +255,9 @@ public class Keyboard {
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 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();
@ -278,9 +284,10 @@ public class Keyboard {
public void load(String[] data) {
final int dataLength = data.length;
if (dataLength % TOUCH_POSITION_CORRECTION_RECORD_SIZE != 0) {
if (LatinImeLogger.sDBG)
if (LatinImeLogger.sDBG) {
throw new RuntimeException(
"the size of touch position correction data is invalid");
}
return;
}
@ -865,10 +872,12 @@ public class Keyboard {
final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Keyboard);
try {
if (a.hasValue(R.styleable.Keyboard_horizontalGap))
if (a.hasValue(R.styleable.Keyboard_horizontalGap)) {
throw new XmlParseUtils.IllegalAttribute(parser, "horizontalGap");
if (a.hasValue(R.styleable.Keyboard_verticalGap))
}
if (a.hasValue(R.styleable.Keyboard_verticalGap)) {
throw new XmlParseUtils.IllegalAttribute(parser, "verticalGap");
}
return new Row(mResources, mParams, parser, mCurrentY);
} finally {
a.recycle();
@ -916,7 +925,9 @@ public class Keyboard {
throws XmlPullParserException, IOException {
if (skip) {
XmlParseUtils.checkEndTag(TAG_KEY, parser);
if (DEBUG) startEndTag("<%s /> skipped", TAG_KEY);
if (DEBUG) {
startEndTag("<%s /> skipped", TAG_KEY);
}
} else {
final Key key = new Key(mResources, mParams, row, parser);
if (DEBUG) {
@ -1094,9 +1105,9 @@ public class Keyboard {
private boolean parseCaseCondition(XmlPullParser parser) {
final KeyboardId id = mParams.mId;
if (id == null)
if (id == null) {
return true;
}
final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Keyboard_Case);
try {
@ -1200,9 +1211,9 @@ public class Keyboard {
// 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)
if (v == null) {
return true;
}
if (isIntegerValue(v)) {
return intValue == a.getInt(index, 0);
} else if (isStringValue(v)) {
@ -1213,9 +1224,10 @@ public class Keyboard {
private static boolean stringArrayContains(String[] array, String value) {
for (final String elem : array) {
if (elem.equals(value))
if (elem.equals(value)) {
return true;
}
}
return false;
}
@ -1237,16 +1249,18 @@ public class Keyboard {
TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Keyboard_Key);
try {
if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName))
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)
if (!skip) {
mParams.mKeyStyles.parseKeyStyleAttributes(keyStyleAttr, keyAttrs, parser);
}
} finally {
keyStyleAttr.recycle();
keyAttrs.recycle();
@ -1267,8 +1281,9 @@ public class Keyboard {
}
private void endRow(Row row) {
if (mCurrentRow == null)
if (mCurrentRow == null) {
throw new InflateException("orphan end row tag");
}
if (mRightEdgeKey != null) {
mRightEdgeKey.markAsRightEdge(mParams);
mRightEdgeKey = null;
@ -1304,8 +1319,9 @@ public class Keyboard {
public static float getDimensionOrFraction(TypedArray a, int index, int base,
float defValue) {
final TypedValue value = a.peekValue(index);
if (value == null)
if (value == null) {
return defValue;
}
if (isFractionValue(value)) {
return a.getFraction(index, base, base, defValue);
} else if (isDimensionValue(value)) {
@ -1316,8 +1332,9 @@ public class Keyboard {
public static int getEnumValue(TypedArray a, int index, int defValue) {
final TypedValue value = a.peekValue(index);
if (value == null)
if (value == null) {
return defValue;
}
if (isIntegerValue(value)) {
return a.getInt(index, defValue);
}

View File

@ -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;
}
}

View File

@ -36,6 +36,7 @@ import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.compat.EditorInfoCompatUtils;
import com.android.inputmethod.keyboard.KeyboardLayoutSet.Params.ElementParams;
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,7 +72,7 @@ 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 {
@ -84,11 +85,7 @@ public class KeyboardLayoutSet {
}
public static class KeysCache {
private final HashMap<Key, Key> mMap;
public KeysCache() {
mMap = new HashMap<Key, Key>();
}
private final HashMap<Key, Key> mMap = CollectionUtils.newHashMap();
public void clear() {
mMap.clear();
@ -120,7 +117,7 @@ public class KeyboardLayoutSet {
int mWidth;
// Sparse array of KeyboardLayoutSet element parameters indexed by element's id.
final SparseArray<ElementParams> mKeyboardLayoutSetElementIdToParamsMap =
new SparseArray<ElementParams>();
CollectionUtils.newSparseArray();
static class ElementParams {
int mKeyboardXmlId;

View File

@ -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);
}
}
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;
}
}
}

View File

@ -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;
@ -38,6 +39,7 @@ import android.view.ViewGroup;
import android.widget.TextView;
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;
@ -78,8 +80,12 @@ import java.util.HashSet;
* @attr ref R.styleable#KeyboardView_shadowRadius
*/
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 };
private static final float UNDEFINED_RATIO = -1.0f;
private static final int UNDEFINED_DIMENSION = -1;
// XML attributes
protected final float mVerticalCorrection;
@ -103,23 +109,19 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
// Key preview
private final int mKeyPreviewLayoutId;
private final SparseArray<TextView> mKeyPreviewTexts = CollectionUtils.newSparseArray();
protected final KeyPreviewDrawParams mKeyPreviewDrawParams;
private boolean mShowKeyPreviewPopup = true;
private int mDelayAfterPreview;
private final PreviewPlacerView mPreviewPlacerView;
/** True if {@link KeyboardView} should handle gesture events. */
protected boolean mShouldHandleGesture;
// Drawing
/** True if the entire keyboard needs to be dimmed. */
private boolean mNeedsToDimEntireKeyboard;
/** Whether the keyboard bitmap buffer needs to be redrawn before it's blitted. **/
private boolean mBufferNeedsUpdate;
/** 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 */
@ -131,9 +133,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' };
@ -153,7 +155,10 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
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;
}
}
@ -166,7 +171,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
removeMessages(MSG_DISMISS_KEY_PREVIEW, tracker);
}
public void cancelAllDismissKeyPreviews() {
private void cancelAllDismissKeyPreviews() {
removeMessages(MSG_DISMISS_KEY_PREVIEW);
}
@ -199,7 +204,6 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
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;
@ -211,26 +215,22 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
public int mKeyHintLabelSize;
public int mAnimAlpha;
public KeyDrawParams(TypedArray a) {
public KeyDrawParams(final 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 (!isValidFraction(mKeyLetterRatio = getFraction(a,
R.styleable.KeyboardView_keyLetterSize))) {
mKeyLetterSize = getDimensionPixelSize(a, R.styleable.KeyboardView_keyLetterSize);
}
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);
if (!isValidFraction(mKeyLabelRatio = getFraction(a,
R.styleable.KeyboardView_keyLabelSize))) {
mKeyLabelSize = getDimensionPixelSize(a, R.styleable.KeyboardView_keyLabelSize);
}
mKeyLargeLabelRatio = getRatio(a, R.styleable.KeyboardView_keyLargeLabelRatio);
mKeyLargeLetterRatio = getRatio(a, R.styleable.KeyboardView_keyLargeLetterRatio);
mKeyHintLetterRatio = getRatio(a, R.styleable.KeyboardView_keyHintLetterRatio);
mKeyShiftedLetterHintRatio = getRatio(a,
mKeyLargeLabelRatio = getFraction(a, R.styleable.KeyboardView_keyLargeLabelRatio);
mKeyLargeLetterRatio = getFraction(a, R.styleable.KeyboardView_keyLargeLetterRatio);
mKeyHintLetterRatio = getFraction(a, R.styleable.KeyboardView_keyHintLetterRatio);
mKeyShiftedLetterHintRatio = getFraction(a,
R.styleable.KeyboardView_keyShiftedLetterHintRatio);
mKeyHintLabelRatio = getRatio(a, R.styleable.KeyboardView_keyHintLabelRatio);
mKeyHintLabelRatio = getFraction(a, R.styleable.KeyboardView_keyHintLabelRatio);
mKeyLabelHorizontalPadding = a.getDimension(
R.styleable.KeyboardView_keyLabelHorizontalPadding, 0);
mKeyHintLetterPadding = a.getDimension(
@ -257,10 +257,10 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
}
public void updateKeyHeight(int keyHeight) {
if (mKeyLetterRatio >= 0.0f) {
if (isValidFraction(mKeyLetterRatio)) {
mKeyLetterSize = (int)(keyHeight * mKeyLetterRatio);
}
if (mKeyLabelRatio >= 0.0f) {
if (isValidFraction(mKeyLabelRatio)) {
mKeyLabelSize = (int)(keyHeight * mKeyLabelRatio);
}
mKeyLargeLabelSize = (int)(keyHeight * mKeyLargeLabelRatio);
@ -335,7 +335,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
R.styleable.KeyboardView_keyPreviewOffset, 0);
mPreviewHeight = a.getDimensionPixelSize(
R.styleable.KeyboardView_keyPreviewHeight, 80);
mPreviewTextRatio = getRatio(a, R.styleable.KeyboardView_keyPreviewTextRatio);
mPreviewTextRatio = getFraction(a, R.styleable.KeyboardView_keyPreviewTextRatio);
mPreviewTextColor = a.getColor(R.styleable.KeyboardView_keyPreviewTextColor, 0);
mLingerTimeout = a.getInt(R.styleable.KeyboardView_keyPreviewLingerTimeout, 0);
@ -367,9 +367,9 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
mKeyDrawParams = new KeyDrawParams(a);
mKeyPreviewDrawParams = new KeyPreviewDrawParams(a, mKeyDrawParams);
mDelayAfterPreview = mKeyPreviewDrawParams.mLingerTimeout;
mKeyPreviewLayoutId = a.getResourceId(R.styleable.KeyboardView_keyPreviewLayout, 0);
if (mKeyPreviewLayoutId == 0) {
mShowKeyPreviewPopup = false;
@ -378,17 +378,30 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
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;
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;
static boolean isValidFraction(final float fraction) {
return fraction >= 0.0f;
}
static float getFraction(final TypedArray a, final int index) {
final TypedValue value = a.peekValue(index);
if (value == null || value.type != TypedValue.TYPE_FRACTION) {
return UNDEFINED_RATIO;
}
return a.getFraction(index, 1, 1, UNDEFINED_RATIO);
}
public static int getDimensionPixelSize(final TypedArray a, final int index) {
final TypedValue value = a.peekValue(index);
if (value == null || value.type != TypedValue.TYPE_DIMENSION) {
return UNDEFINED_DIMENSION;
}
return a.getDimensionPixelSize(index, UNDEFINED_DIMENSION);
}
/**
@ -438,9 +451,8 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
return mShowKeyPreviewPopup;
}
public void setGestureHandlingMode(boolean shouldHandleGesture,
boolean drawsGesturePreviewTrail, boolean drawsGestureFloatingPreviewText) {
mShouldHandleGesture = shouldHandleGesture;
public void setGesturePreviewMode(boolean drawsGesturePreviewTrail,
boolean drawsGestureFloatingPreviewText) {
mPreviewPlacerView.setGesturePreviewMode(
drawsGesturePreviewTrail, drawsGestureFloatingPreviewText);
}
@ -463,16 +475,12 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
onDrawKeyboard(canvas);
return;
}
if (mBufferNeedsUpdate || mOffscreenBuffer == null) {
mBufferNeedsUpdate = false;
final boolean bufferNeedsUpdates = mInvalidateAllKeys || !mInvalidatedKeys.isEmpty();
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);
}
@ -501,6 +509,15 @@ 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;
@ -528,14 +545,13 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
}
if (!isHardwareAccelerated) {
canvas.clipRegion(mClipRegion, Region.Op.REPLACE);
}
// Draw keyboard background.
canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
final Drawable background = getBackground();
if (background != null) {
background.draw(canvas);
}
}
// TODO: Confirm if it's really required to draw all keys when hardware acceleration is on.
if (drawAllKeys || isHardwareAccelerated) {
@ -907,15 +923,30 @@ 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
@ -936,10 +967,19 @@ 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);
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) {
locatePreviewPlacerView();
@ -952,7 +992,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
}
@Override
public void showGestureTrail(PointerTracker tracker) {
public void showGesturePreviewTrail(PointerTracker tracker) {
locatePreviewPlacerView();
mPreviewPlacerView.invalidatePointer(tracker);
}
@ -962,7 +1002,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
public void showKeyPreview(PointerTracker tracker) {
if (!mShowKeyPreviewPopup) 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) {
@ -1052,7 +1092,6 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
public void invalidateAllKeys() {
mInvalidatedKeys.clear();
mInvalidateAllKeys = true;
mBufferNeedsUpdate = true;
invalidate();
}
@ -1070,13 +1109,11 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
mInvalidatedKeys.add(key);
final int x = key.mX + getPaddingLeft();
final int y = key.mY + getPaddingTop();
mWorkingRect.set(x, y, x + key.mWidth, y + key.mHeight);
mBufferNeedsUpdate = true;
invalidate(mWorkingRect);
invalidate(x, y, x + key.mWidth, y + key.mHeight);
}
public void closing() {
PointerTracker.dismissAllKeyPreviews();
dismissAllKeyPreviews();
cancelAllMessages();
mInvalidateAllKeys = true;

View File

@ -110,7 +110,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,11 +126,26 @@ 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
@ -146,7 +160,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:
@ -167,7 +181,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
@Override
public void startKeyRepeatTimer(PointerTracker tracker) {
startKeyRepeatTimer(tracker, mParams.mKeyRepeatStartTimeout);
startKeyRepeatTimer(tracker, mKeyRepeatStartTimeout);
}
public void cancelKeyRepeatTimer() {
@ -185,7 +199,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
final int delay;
switch (code) {
case Keyboard.CODE_SHIFT:
delay = mParams.mLongPressShiftKeyTimeout;
delay = mLongPressShiftKeyTimeout;
break;
default:
delay = 0;
@ -206,15 +220,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;
}
@ -268,7 +282,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,50 +321,6 @@ 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) {
this(context, attrs, R.attr.mainKeyboardViewStyle);
}
@ -374,8 +344,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 +359,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(
@ -482,7 +448,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
super.setKeyboard(keyboard);
mKeyDetector.setKeyboard(
keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection);
PointerTracker.setKeyDetector(mKeyDetector, mShouldHandleGesture);
PointerTracker.setKeyDetector(mKeyDetector);
mTouchScreenRegulator.setKeyboard(keyboard);
mMoreKeysPanelCache.clear();
@ -500,12 +466,13 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
AccessibleKeyboardViewProxy.getInstance().setKeyboard(keyboard);
}
@Override
public void setGestureHandlingMode(final boolean shouldHandleGesture,
boolean drawsGesturePreviewTrail, boolean drawsGestureFloatingPreviewText) {
super.setGestureHandlingMode(shouldHandleGesture, drawsGesturePreviewTrail,
drawsGestureFloatingPreviewText);
PointerTracker.setKeyDetector(mKeyDetector, shouldHandleGesture);
// Note that this method is called from a non-UI thread.
public void setMainDictionaryAvailability(boolean mainDictionaryAvailable) {
PointerTracker.setMainDictionaryAvailability(mainDictionaryAvailable);
}
public void setGestureHandlingEnabledByUser(boolean gestureHandlingEnabledByUser) {
PointerTracker.setGestureHandlingEnabledByUser(gestureHandlingEnabledByUser);
}
/**
@ -527,7 +494,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();
}
}
@ -607,9 +584,8 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
}
private void invokeCodeInput(int primaryCode) {
mKeyboardActionListener.onCodeInput(primaryCode,
KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
mKeyboardActionListener.onCodeInput(
primaryCode, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
}
private void invokeReleaseKey(int primaryCode) {
@ -834,20 +810,6 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
return false;
}
@Override
public void draw(Canvas c) {
Utils.GCUtils.getInstance().reset();
boolean tryGC = true;
for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
try {
super.draw(c);
tryGC = false;
} catch (OutOfMemoryError e) {
tryGC = Utils.GCUtils.getInstance().tryGCOrWait(TAG, e);
}
}
}
/**
* Receives hover events from the input framework.
*

View File

@ -39,11 +39,7 @@ public class MoreKeysDetector extends KeyDetector {
Key nearestKey = null;
int nearestDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare;
final Keyboard keyboard = getKeyboard();
if (keyboard == null) {
throw new NullPointerException("Keyboard isn't set");
}
for (final Key key : keyboard.mKeys) {
for (final Key key : getKeyboard().mKeys) {
final int dist = key.squaredDistanceToEdge(touchX, touchY);
if (dist < nearestDist) {
nearestKey = key;

View File

@ -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

View File

@ -16,25 +16,25 @@
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;
import java.util.ArrayList;
public class PointerTracker implements PointerTrackerQueue.ElementActions {
public class PointerTracker implements PointerTrackerQueue.Element {
private static final String TAG = PointerTracker.class.getSimpleName();
private static final boolean DEBUG_EVENT = false;
private static final boolean DEBUG_MOVE_EVENT = false;
@ -43,6 +43,9 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
/** True if {@link PointerTracker}s should handle gesture events. */
private static boolean sShouldHandleGesture = false;
private static boolean sMainDictionaryAvailable = false;
private static boolean sGestureHandlingEnabledByInputField = false;
private static boolean sGestureHandlingEnabledByUser = false;
private static final int MIN_GESTURE_RECOGNITION_TIME = 100; // msec
@ -75,10 +78,9 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
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 {
@ -117,19 +119,40 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
}
}
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;
// HACK: Change gesture detection criteria depending on this variable.
// TODO: Find more comprehensive ways to detect a gesture start.
// True when the previous user input was a gesture input, not a typing input.
private static boolean sWasInGesture;
public final int mPointerId;
@ -140,15 +163,14 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
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;
@ -186,7 +208,7 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
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) {
@ -196,28 +218,32 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
sPointerTrackerQueue = null;
}
sNeedsPhantomSuddenMoveEventHack = needsPhantomSuddenMoveEventHack;
setParameters(MainKeyboardView.PointerTrackerParams.DEFAULT);
updateGestureHandlingMode(null, false /* shouldHandleGesture */);
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(Keyboard keyboard, boolean shouldHandleGesture) {
if (!shouldHandleGesture
|| AccessibilityUtils.getInstance().isTouchExplorationEnabled()
|| (keyboard != null && keyboard.mId.passwordInput())) {
sShouldHandleGesture = false;
} else {
sShouldHandleGesture = true;
}
private static void updateGestureHandlingMode() {
sShouldHandleGesture = sMainDictionaryAvailable
&& sGestureHandlingEnabledByInputField
&& sGestureHandlingEnabledByUser
&& !AccessibilityUtils.getInstance().isTouchExplorationEnabled();
}
public static PointerTracker getPointerTracker(final int id, KeyEventHandler handler) {
// Note that this method is called from a non-UI thread.
public static void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) {
sMainDictionaryAvailable = mainDictionaryAvailable;
updateGestureHandlingMode();
}
public static void setGestureHandlingEnabledByUser(final boolean gestureHandlingEnabledByUser) {
sGestureHandlingEnabledByUser = gestureHandlingEnabledByUser;
updateGestureHandlingMode();
}
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.
@ -233,7 +259,7 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
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);
@ -241,7 +267,7 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
}
}
public static void setKeyDetector(KeyDetector keyDetector, boolean shouldHandleGesture) {
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);
@ -250,70 +276,33 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
tracker.mKeyboardLayoutHasBeenChanged = true;
}
final Keyboard keyboard = keyDetector.getKeyboard();
updateGestureHandlingMode(keyboard, shouldHandleGesture);
sGestureHandlingEnabledByInputField = !keyboard.mId.passwordInput();
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();
@ -337,7 +326,8 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
// 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;
@ -366,8 +356,9 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
// 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();
@ -389,20 +380,19 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
}
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, mKeyboard.mMostCommonKeyHeight);
mGestureStrokeWithPreviewTrail.setGestureSampleLength(mKeyboard.mMostCommonKeyWidth);
final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY);
if (newKey != mCurrentKey) {
if (mDrawingProxy != null) {
@ -428,11 +418,11 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
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;
@ -463,7 +453,7 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
}
}
private void setPressedKeyGraphics(Key key) {
private void setPressedKeyGraphics(final Key key) {
if (key == null) {
return;
}
@ -475,7 +465,7 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
return;
}
if (!key.noKeyPreview() && !mInGesture) {
if (!key.noKeyPreview() && !sInGesture) {
mDrawingProxy.showKeyPreview(this);
}
updatePressKeyGraphics(key);
@ -502,20 +492,18 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
}
}
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, mLastX, mLastY);
}
public GestureStrokeWithPreviewTrail getGestureStrokeWithPreviewTrail() {
return mGestureStrokeWithPreviewTrail;
}
public int getLastX() {
@ -530,77 +518,91 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
return mDownTime;
}
private Key onDownKey(int x, int y, long eventTime) {
private Key onDownKey(final int x, final int y, final long eventTime) {
mDownTime = eventTime;
return onMoveToNewKey(onMoveKeyInternal(x, y), x, y);
}
private Key onMoveKeyInternal(int x, int y) {
private Key onMoveKeyInternal(final int x, final int y) {
mLastX = x;
mLastY = y;
return mKeyDetector.detectHitKey(x, y);
}
private Key onMoveKey(int x, int y) {
private Key onMoveKey(final int x, final int y) {
return onMoveKeyInternal(x, y);
}
private Key onMoveToNewKey(Key newKey, int x, int y) {
private Key onMoveToNewKey(final Key newKey, final int x, final int y) {
mCurrentKey = newKey;
mKeyX = x;
mKeyY = y;
return newKey;
}
private static int getActivePointerTrackerCount() {
return (sPointerTrackerQueue == null) ? 1 : sPointerTrackerQueue.size();
}
private void startBatchInput() {
if (DEBUG_LISTENER) {
Log.d(TAG, "onStartBatchInput");
}
mInGesture = true;
sInGesture = true;
mListener.onStartBatchInput();
mDrawingProxy.showGesturePreviewTrail(this);
}
private void updateBatchInput(InputPointers batchPoints) {
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=" + batchPoints.getPointerSize());
Log.d(TAG, "onUpdateBatchInput: batchPoints=" + size);
}
mListener.onUpdateBatchInput(batchPoints);
mListener.onUpdateBatchInput(sAggregratedPointers);
}
}
mDrawingProxy.showGesturePreviewTrail(this);
}
private void endBatchInput(InputPointers batchPoints) {
private void endBatchInput() {
synchronized (sAggregratedPointers) {
mGestureStrokeWithPreviewTrail.appendAllBatchPoints(sAggregratedPointers);
if (getActivePointerTrackerCount() == 1) {
if (DEBUG_LISTENER) {
Log.d(TAG, "onEndBatchInput: batchPoints=" + batchPoints.getPointerSize());
Log.d(TAG, "onEndBatchInput: batchPoints="
+ sAggregratedPointers.getPointerSize());
}
mListener.onEndBatchInput(batchPoints);
clearBatchInputRecognitionStateOfThisPointerTracker();
sInGesture = false;
mListener.onEndBatchInput(sAggregratedPointers);
clearBatchInputPointsOfAllPointerTrackers();
sWasInGesture = true;
}
}
mDrawingProxy.showGesturePreviewTrail(this);
}
private void abortBatchInput() {
clearBatchInputRecognitionStateOfThisPointerTracker();
private static void abortBatchInput() {
clearBatchInputPointsOfAllPointerTrackers();
}
private void clearBatchInputRecognitionStateOfThisPointerTracker() {
mIsPossibleGesture = false;
mInGesture = false;
mLastRecognitionPointSize = 0;
mLastRecognitionTime = 0;
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();
}
sAggregratedPointers.reset();
sLastRecognitionPointSize = 0;
sLastRecognitionTime = 0;
}
private boolean updateBatchInputRecognitionState(long eventTime, int size) {
if (size > mLastRecognitionPointSize
&& eventTime > mLastRecognitionTime + MIN_GESTURE_RECOGNITION_TIME) {
mLastRecognitionPointSize = size;
mLastRecognitionTime = eventTime;
return true;
}
return false;
}
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:
@ -619,9 +621,11 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
}
}
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();
@ -633,7 +637,7 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
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);
@ -645,8 +649,8 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
}
}
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
@ -656,20 +660,30 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
queue.add(this);
}
onDownEventInternal(x, y, eventTime);
if (queue != null && queue.size() == 1) {
mIsPossibleGesture = 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);
if (!sShouldHandleGesture) {
return;
}
final int activePointerTrackerCount = getActivePointerTrackerCount();
if (activePointerTrackerCount == 1) {
mIsDetectingGesture = false;
// A gesture should start only from the letter key.
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.
@ -694,40 +708,38 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
}
}
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(gestureTime, sWasInGesture)) {
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();
@ -735,24 +747,31 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
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);
if (sShouldHandleGesture) {
// Register move event on gesture tracker.
onGestureMoveEvent(this, x, y, eventTime, false /* isHistorical */, key);
if (mInGesture) {
onGestureMoveEvent(x, y, eventTime, false /* isHistorical */, key);
if (sInGesture) {
mIgnoreModifierKey = true;
mTimerProxy.cancelLongPressTimer();
mIsInSlidingKeyInput = true;
mCurrentKey = null;
setReleasedKeyGraphics(oldKey);
return;
}
}
if (key != null) {
@ -797,7 +816,7 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
// 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 "
@ -815,11 +834,11 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
// 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);
@ -837,7 +856,7 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
if (mIsAllowedSlidingKeyInput) {
onMoveToNewKey(key, x, y);
} else {
if (!mIsPossibleGesture) {
if (!mIsDetectingGesture) {
mKeyAlreadyProcessed = true;
}
}
@ -845,13 +864,14 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
}
}
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.
@ -860,18 +880,21 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
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;
}
@ -879,37 +902,35 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
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;
@ -925,9 +946,10 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
}
}
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) {
@ -947,24 +969,25 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
}
}
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) {
@ -975,25 +998,25 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
}
}
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);
sWasInGesture = 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,

View File

@ -18,9 +18,9 @@ package com.android.inputmethod.keyboard;
import android.graphics.Rect;
import android.text.TextUtils;
import android.util.FloatMath;
import com.android.inputmethod.keyboard.Keyboard.Params.TouchPositionCorrection;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.JniUtils;
import java.util.Arrays;
@ -112,7 +112,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) {
@ -155,7 +155,9 @@ public class ProximityInfo {
final float radius = touchPositionCorrection.mRadii[row];
sweetSpotCenterXs[i] = hitBox.exactCenterX() + x * hitBoxWidth;
sweetSpotCenterYs[i] = hitBox.exactCenterY() + y * hitBoxHeight;
sweetSpotRadii[i] = radius * FloatMath.sqrt(
// Note that, in recent versions of Android, FloatMath is actually slower than
// java.lang.Math due to the way the JIT optimizes java.lang.Math.
sweetSpotRadii[i] = radius * (float)Math.sqrt(
hitBoxWidth * hitBoxWidth + hitBoxHeight * hitBoxHeight);
}
}
@ -233,7 +235,7 @@ public class ProximityInfo {
dest[index++] = code;
}
if (index < destLength) {
dest[index] = KeyDetector.NOT_A_CODE;
dest[index] = Constants.NOT_A_CODE;
}
}

View File

@ -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;
}
}

View File

@ -14,11 +14,6 @@
package com.android.inputmethod.keyboard.internal;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.FloatMath;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.InputPointers;
import com.android.inputmethod.latin.ResizableIntArray;
@ -38,44 +33,30 @@ public class GestureStroke {
private int mLastPointY;
private int mMinGestureLength;
private int mMinGestureLengthWhileInGesture;
private int mMinGestureSampleLength;
// TODO: Move some of these to resource.
private static final float MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH = 1.0f;
private static final float MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH_WHILE_IN_GESTURE = 0.5f;
private static final int MIN_GESTURE_DURATION = 150; // msec
private static final int MIN_GESTURE_DURATION_WHILE_IN_GESTURE = 75; // msec
private static final float MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT = 1.0f / 6.0f;
private static final float MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH = 0.75f;
private static final int MIN_GESTURE_DURATION = 100; // msec
private static final float MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH = 1.0f / 6.0f;
private static final float GESTURE_RECOG_SPEED_THRESHOLD = 0.4f; // dip/msec
private static final float GESTURE_RECOG_CURVATURE_THRESHOLD = (float)(Math.PI / 4.0f);
private static final float DOUBLE_PI = (float)(2 * Math.PI);
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, final int keyHeight) {
public void setGestureSampleLength(final int keyWidth) {
// TODO: Find an appropriate base metric for these length. Maybe diagonal length of the key?
mMinGestureLength = (int)(keyWidth * MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH);
mMinGestureLengthWhileInGesture = (int)(
keyWidth * MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH_WHILE_IN_GESTURE);
mMinGestureSampleLength = (int)(keyHeight * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT);
mMinGestureSampleLength = (int)(keyWidth * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH);
}
public boolean isStartOfAGesture(final int downDuration, final boolean wasInGesture) {
// The tolerance of the time duration and the stroke length to detect the start of a
// gesture stroke should be eased when the previous input was a gesture input.
if (wasInGesture) {
return downDuration > MIN_GESTURE_DURATION_WHILE_IN_GESTURE
&& mLength > mMinGestureLengthWhileInGesture;
}
public boolean isStartOfAGesture() {
final int size = mEventTimes.getLength();
final int downDuration = (size > 0) ? mEventTimes.get(size - 1) : 0;
return downDuration > MIN_GESTURE_DURATION && mLength > mMinGestureLength;
}
@ -149,23 +130,29 @@ 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;
}
private static float getDistance(final int p1x, final int p1y,
final int p2x, final int p2y) {
final float dx = p1x - p2x;
final float dy = p1y - p2y;
// TODO: Optimize out this {@link FloatMath#sqrt(float)} call.
return FloatMath.sqrt(dx * dx + dy * dy);
private static float getDistance(final int x1, final int y1, final int x2, final int y2) {
final float dx = x1 - x2;
final float dy = y1 - y2;
// Note that, in recent versions of Android, FloatMath is actually slower than
// java.lang.Math due to the way the JIT optimizes java.lang.Math.
return (float)Math.sqrt(dx * dx + dy * dy);
}
private static float getAngle(final int p1x, final int p1y, final int p2x, final int p2y) {
final int dx = p1x - p2x;
final int dy = p1y - p2y;
private static float getAngle(final int x1, final int y1, final int x2, final int y2) {
final int dx = x1 - x2;
final int dy = y1 - y2;
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(dy, dx);
}
@ -176,23 +163,4 @@ public class GestureStroke {
}
return diff;
}
public void drawGestureTrail(Canvas canvas, Paint paint, int lastX, int lastY) {
// 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();
int[] xCoords = mXCoordinates.getPrimitiveArray();
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);
if (i == size - 1) {
canvas.drawLine(lastX, lastY, xCoords[i], yCoords[i], paint);
}
}
}
}

View File

@ -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();
}
}

View File

@ -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;
@ -258,7 +259,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]);
}
@ -438,7 +439,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));
}

View File

@ -21,6 +21,7 @@ import android.util.Log;
import android.util.SparseArray;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.XmlParseUtils;
@ -33,7 +34,7 @@ public class KeyStyles {
private static final String TAG = KeyStyles.class.getSimpleName();
private static final boolean DEBUG = false;
final HashMap<String, KeyStyle> mStyles = new HashMap<String, KeyStyle>();
final HashMap<String, KeyStyle> mStyles = CollectionUtils.newHashMap();
final KeyboardTextsSet mTextsSet;
private final KeyStyle mEmptyKeyStyle;
@ -90,7 +91,7 @@ public class KeyStyles {
private class DeclaredKeyStyle extends KeyStyle {
private final String mParentStyleName;
private final SparseArray<Object> mStyleAttributes = new SparseArray<Object>();
private final SparseArray<Object> mStyleAttributes = CollectionUtils.newSparseArray();
public DeclaredKeyStyle(String parentStyleName) {
mParentStyleName = parentStyleName;

View File

@ -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;

View File

@ -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,

View File

@ -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);

View File

@ -18,85 +18,148 @@ package com.android.inputmethod.keyboard.internal;
import android.util.Log;
import java.util.Iterator;
import java.util.LinkedList;
import com.android.inputmethod.latin.CollectionUtils;
import java.util.ArrayList;
public class PointerTrackerQueue {
private static final String TAG = PointerTrackerQueue.class.getSimpleName();
private static final boolean DEBUG = false;
public interface ElementActions {
public interface Element {
public boolean isModifier();
public boolean isInSlidingKeyInput();
public void onPhantomUpEvent(long eventTime);
}
// TODO: Use ring buffer instead of {@link LinkedList}.
private final LinkedList<ElementActions> mQueue = new LinkedList<ElementActions>();
private static final int INITIAL_CAPACITY = 10;
private final ArrayList<Element> mExpandableArrayOfActivePointers =
CollectionUtils.newArrayList(INITIAL_CAPACITY);
private int mArraySize = 0;
public int size() {
return mQueue.size();
public synchronized int size() {
return mArraySize;
}
public synchronized void add(ElementActions tracker) {
mQueue.add(tracker);
public synchronized void add(final Element pointer) {
final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
final int arraySize = mArraySize;
if (arraySize < expandableArray.size()) {
expandableArray.set(arraySize, pointer);
} else {
expandableArray.add(pointer);
}
mArraySize = arraySize + 1;
}
public synchronized void remove(ElementActions tracker) {
mQueue.remove(tracker);
public synchronized void remove(final Element pointer) {
final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
final int arraySize = mArraySize;
int newSize = 0;
for (int index = 0; index < arraySize; index++) {
final Element element = expandableArray.get(index);
if (element == pointer) {
if (newSize != index) {
Log.w(TAG, "Found duplicated element in remove: " + pointer);
}
continue; // Remove this element from the expandableArray.
}
if (newSize != index) {
// Shift this element toward the beginning of the expandableArray.
expandableArray.set(newSize, element);
}
newSize++;
}
mArraySize = newSize;
}
public synchronized void releaseAllPointersOlderThan(ElementActions tracker,
long eventTime) {
public synchronized void releaseAllPointersOlderThan(final Element pointer,
final long eventTime) {
if (DEBUG) {
Log.d(TAG, "releaseAllPoniterOlderThan: " + tracker + " " + this);
Log.d(TAG, "releaseAllPoniterOlderThan: " + pointer + " " + this);
}
if (!mQueue.contains(tracker)) {
return;
final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
final int arraySize = mArraySize;
int newSize, index;
for (newSize = index = 0; index < arraySize; index++) {
final Element element = expandableArray.get(index);
if (element == pointer) {
break; // Stop releasing elements.
}
final Iterator<ElementActions> it = mQueue.iterator();
while (it.hasNext()) {
final ElementActions t = it.next();
if (t == tracker) {
break;
if (!element.isModifier()) {
element.onPhantomUpEvent(eventTime);
continue; // Remove this element from the expandableArray.
}
if (!t.isModifier()) {
t.onPhantomUpEvent(eventTime);
it.remove();
if (newSize != index) {
// Shift this element toward the beginning of the expandableArray.
expandableArray.set(newSize, element);
}
newSize++;
}
// Shift rest of the expandableArray.
int count = 0;
for (; index < arraySize; index++) {
final Element element = expandableArray.get(index);
if (element == pointer) {
if (count > 0) {
Log.w(TAG, "Found duplicated element in releaseAllPointersOlderThan: "
+ pointer);
}
count++;
}
if (newSize != index) {
expandableArray.set(newSize, expandableArray.get(index));
newSize++;
}
}
mArraySize = newSize;
}
public void releaseAllPointers(long eventTime) {
public void releaseAllPointers(final long eventTime) {
releaseAllPointersExcept(null, eventTime);
}
public synchronized void releaseAllPointersExcept(ElementActions tracker, long eventTime) {
public synchronized void releaseAllPointersExcept(final Element pointer,
final long eventTime) {
if (DEBUG) {
if (tracker == null) {
if (pointer == null) {
Log.d(TAG, "releaseAllPoniters: " + this);
} else {
Log.d(TAG, "releaseAllPoniterExcept: " + tracker + " " + this);
Log.d(TAG, "releaseAllPoniterExcept: " + pointer + " " + this);
}
}
final Iterator<ElementActions> it = mQueue.iterator();
while (it.hasNext()) {
final ElementActions t = it.next();
if (t != tracker) {
t.onPhantomUpEvent(eventTime);
it.remove();
final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
final int arraySize = mArraySize;
int newSize = 0, count = 0;
for (int index = 0; index < arraySize; index++) {
final Element element = expandableArray.get(index);
if (element == pointer) {
if (count > 0) {
Log.w(TAG, "Found duplicated element in releaseAllPointersExcept: " + pointer);
}
count++;
} else {
element.onPhantomUpEvent(eventTime);
continue; // Remove this element from the expandableArray.
}
if (newSize != index) {
// Shift this element toward the beginning of the expandableArray.
expandableArray.set(newSize, element);
}
newSize++;
}
mArraySize = newSize;
}
public synchronized boolean hasModifierKeyOlderThan(ElementActions tracker) {
final Iterator<ElementActions> it = mQueue.iterator();
while (it.hasNext()) {
final ElementActions t = it.next();
if (t == tracker) {
break;
public synchronized boolean hasModifierKeyOlderThan(final Element pointer) {
final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
final int arraySize = mArraySize;
for (int index = 0; index < arraySize; index++) {
final Element element = expandableArray.get(index);
if (element == pointer) {
return false; // Stop searching modifier key.
}
if (t.isModifier()) {
if (element.isModifier()) {
return true;
}
}
@ -104,8 +167,11 @@ public class PointerTrackerQueue {
}
public synchronized boolean isAnyInSlidingKeyInput() {
for (final ElementActions tracker : mQueue) {
if (tracker.isInSlidingKeyInput()) {
final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
final int arraySize = mArraySize;
for (int index = 0; index < arraySize; index++) {
final Element element = expandableArray.get(index);
if (element.isInSlidingKeyInput()) {
return true;
}
}
@ -113,12 +179,15 @@ public class PointerTrackerQueue {
}
@Override
public String toString() {
public synchronized String toString() {
final StringBuilder sb = new StringBuilder();
for (final ElementActions tracker : mQueue) {
final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
final int arraySize = mArraySize;
for (int index = 0; index < arraySize; index++) {
final Element element = expandableArray.get(index);
if (sb.length() > 0)
sb.append(" ");
sb.append(tracker.toString());
sb.append(element.toString());
}
return "[" + sb.toString() + "]";
}

View File

@ -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;
public DrawingHandler(PreviewPlacerView outerInstance) {
private final GesturePreviewTrailParams mGesturePreviewTrailParams;
public DrawingHandler(final PreviewPlacerView outerInstance,
final GesturePreviewTrailParams gesturePreviewTrailParams) {
super(outerInstance);
mGesturePreviewTrailParams = gesturePreviewTrailParams;
}
@Override
public void handleMessage(Message msg) {
public void handleMessage(final Message msg) {
final PreviewPlacerView placerView = getOuterInstance();
if (placerView == null) return;
switch (msg.what) {
case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT:
placerView.setGestureFloatingPreviewText(null);
break;
case MSG_UPDATE_GESTURE_PREVIEW_TRAIL:
placerView.invalidate();
break;
}
}
@ -84,15 +100,32 @@ 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);
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);
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);
}
// TODO: Figure out more cleaner way to draw gesture preview text.
if (mDrawsGestureFloatingPreviewText && !hasDrawnFloatingPreviewText) {
drawGestureFloatingPreviewText(canvas, tracker, mGestureFloatingPreviewText);
hasDrawnFloatingPreviewText = true;
}
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();

View File

@ -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) {

View File

@ -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);

View File

@ -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);
@ -75,17 +73,10 @@ public class AutoCorrection {
return maxFreq;
}
// Returns true if this is a whitelist entry, or it isn't in any dictionary.
public static boolean isWhitelistedOrNotAWord(
// Returns true if this isn't in any dictionary.
public static boolean isNotAWord(
final ConcurrentHashMap<String, Dictionary> dictionaries,
final CharSequence word, final boolean ignoreCase) {
final WhitelistDictionary whitelistDictionary =
(WhitelistDictionary)dictionaries.get(Dictionary.TYPE_WHITELIST);
// If "word" is in the whitelist dictionary, it should not be auto corrected.
if (whitelistDictionary != null
&& whitelistDictionary.shouldForciblyAutoCorrectFrom(word)) {
return true;
}
return !isValidWord(dictionaries, word, ignoreCase);
}

View File

@ -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,7 +52,9 @@ public class BinaryDictionary extends Dictionary {
private static final int TYPED_LETTER_MULTIPLIER = 2;
private long mNativeDict;
private final int[] mInputCodes = new int[MAX_WORD_LENGTH];
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];
private final int[] mSpaceIndices = new int[MAX_SPACES];
private final int[] mOutputScores = new int[MAX_RESULTS];
@ -59,6 +62,25 @@ public class BinaryDictionary extends Dictionary {
private final boolean mUseFullEditDistance;
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
* dictionary factory.
@ -74,6 +96,7 @@ 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);
}
@ -86,18 +109,17 @@ public class BinaryDictionary extends Dictionary {
int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength, int maxWords,
int maxPredictions);
private native void closeNative(long dict);
private native int getFrequencyNative(long dict, int[] word, int wordLength);
private native int getFrequencyNative(long dict, int[] word);
private native boolean isValidBigramNative(long dict, int[] word1, int[] word2);
private native int getSuggestionsNative(long dict, long proximityInfo, int[] xCoordinates,
int[] yCoordinates, int[] times, int[] pointerIds, int[] inputCodes, int codesSize,
int commitPoint, boolean isGesture,
private native int getSuggestionsNative(long dict, long proximityInfo, long traverseSession,
int[] xCoordinates, int[] yCoordinates, int[] times, int[] pointerIds,
int[] inputCodePoints, int codesSize, int commitPoint, boolean isGesture,
int[] prevWordCodePointArray, boolean useFullEditDistance, char[] outputChars,
int[] outputScores, int[] outputIndices, int[] outputTypes);
private static native float calcNormalizedScoreNative(
char[] before, int beforeLength, char[] after, int afterLength, int score);
private static native int editDistanceNative(
char[] before, int beforeLength, char[] after, int afterLength);
private static native float calcNormalizedScoreNative(char[] before, char[] after, int score);
private static native int editDistanceNative(char[] before, char[] after);
// TODO: Move native dict into session
private final void loadDictionary(String path, long startOffset, long length) {
mNativeDict = openNative(path, startOffset, length, TYPED_LETTER_MULTIPLIER,
FULL_WORD_SCORE_MULTIPLIER, MAX_WORD_LENGTH, MAX_WORDS, MAX_PREDICTIONS);
@ -106,10 +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(mInputCodes, WordComposer.NOT_A_CODE);
Arrays.fill(mOutputChars, (char) 0);
Arrays.fill(mOutputScores, 0);
Arrays.fill(mInputCodePoints, Constants.NOT_A_CODE);
// TODO: toLowerCase in the native code
final int[] prevWordCodePointArray = (null == prevWord)
? null : StringUtils.toCodePointArray(prevWord.toString());
@ -119,7 +146,7 @@ public class BinaryDictionary extends Dictionary {
if (composerSize <= 1 || !isGesture) {
if (composerSize > MAX_WORD_LENGTH - 1) return null;
for (int i = 0; i < composerSize; i++) {
mInputCodes[i] = composer.getCodeAt(i);
mInputCodePoints[i] = composer.getCodeAt(i);
}
}
@ -127,13 +154,13 @@ 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(), ips.getXCoordinates(),
ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(),
mInputCodes, codesSize, 0 /* commitPoint */, isGesture, prevWordCodePointArray,
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;
@ -142,9 +169,10 @@ public class BinaryDictionary extends Dictionary {
++len;
}
if (len > 0) {
final int score = SuggestedWordInfo.KIND_WHITELIST == mOutputTypes[j]
? SuggestedWordInfo.MAX_SCORE : mOutputScores[j];
suggestions.add(new SuggestedWordInfo(
new String(mOutputChars, start, len),
mOutputScores[j], SuggestedWordInfo.KIND_CORRECTION, mDictType));
new String(mOutputChars, start, len), score, mOutputTypes[j], mDictType));
}
}
return suggestions;
@ -155,13 +183,11 @@ public class BinaryDictionary extends Dictionary {
}
public static float calcNormalizedScore(String before, String after, int score) {
return calcNormalizedScoreNative(before.toCharArray(), before.length(),
after.toCharArray(), after.length(), score);
return calcNormalizedScoreNative(before.toCharArray(), after.toCharArray(), score);
}
public static int editDistance(String before, String after) {
return editDistanceNative(
before.toCharArray(), before.length(), after.toCharArray(), after.length());
return editDistanceNative(before.toCharArray(), after.toCharArray());
}
@Override
@ -172,8 +198,8 @@ public class BinaryDictionary extends Dictionary {
@Override
public int getFrequency(CharSequence word) {
if (word == null) return -1;
int[] chars = StringUtils.toCodePointArray(word.toString());
return getFrequencyNative(mNativeDict, chars, chars.length);
int[] codePoints = StringUtils.toCodePointArray(word.toString());
return getFrequencyNative(mNativeDict, codePoints);
}
// TODO: Add a batch process version (isValidBigramMultiple?) to avoid excessive numbers of jni
@ -186,11 +212,20 @@ public class BinaryDictionary extends Dictionary {
}
@Override
public synchronized void 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;

View File

@ -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) {

View File

@ -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");
}
}

View File

@ -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 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>();
}
}

View File

@ -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.
}

View File

@ -0,0 +1,75 @@
/*
* 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 java.util.Locale;
public class DicTraverseSession {
static {
JniUtils.loadNativeLibrary();
}
private native long setDicTraverseSessionNative(String locale);
private native void initDicTraverseSessionNative(long nativeDicTraverseSession,
long dictionary, int[] previousWord, int previousWordLength);
private native void releaseDicTraverseSessionNative(long nativeDicTraverseSession);
private long mNativeDicTraverseSession;
public DicTraverseSession(Locale locale, long dictionary) {
mNativeDicTraverseSession = createNativeDicTraverseSession(
locale != null ? locale.toString() : "");
initSession(dictionary);
}
public long getSession() {
return mNativeDicTraverseSession;
}
public void initSession(long dictionary) {
initSession(dictionary, null, 0);
}
public void initSession(long dictionary, int[] previousWord, int previousWordLength) {
initDicTraverseSessionNative(
mNativeDicTraverseSession, dictionary, previousWord, previousWordLength);
}
private final long createNativeDicTraverseSession(String locale) {
return setDicTraverseSessionNative(locale);
}
private void closeInternal() {
if (mNativeDicTraverseSession != 0) {
releaseDicTraverseSessionNative(mNativeDicTraverseSession);
mNativeDicTraverseSession = 0;
}
}
public void close() {
closeInternal();
}
@Override
protected void finalize() throws Throwable {
try {
closeInternal();
} finally {
super.finalize();
}
}
}

View File

@ -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.

View File

@ -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,

View File

@ -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) {

View File

@ -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));
}
/**
@ -175,7 +175,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
mFusionDictionary.add(word, frequency, null);
} 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);
}

View File

@ -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) {

View File

@ -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:");

View File

@ -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;
}
}

View File

@ -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);
}
}
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);
@ -433,10 +423,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary());
}
// Note that this method is called from a non-UI thread.
@Override
public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable) {
mIsMainDictionaryAvailable = isMainDictionaryAvailable;
updateKeyboardViewGestureHandlingModeByMainDictionaryAvailability();
final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
if (mainKeyboardView != null) {
mainKeyboardView.setMainDictionaryAvailability(isMainDictionaryAvailable);
}
}
private void initSuggest() {
@ -517,7 +511,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
/* package private */ void resetSuggestMainDict() {
final Locale subtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
mSuggest.resetMainDict(this, subtypeLocale);
mSuggest.resetMainDict(this, subtypeLocale, this /* SuggestInitializationListener */);
mIsMainDictionaryAvailable = DictionaryFactory.isDictionaryAvailable(this, subtypeLocale);
}
@ -536,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;
@ -603,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) {
@ -663,11 +661,23 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Forward this event to the accessibility utilities, if enabled.
final AccessibilityUtils accessUtils = AccessibilityUtils.getInstance();
if (accessUtils.isTouchExplorationEnabled()) {
accessUtils.onStartInputViewInternal(editorInfo, restarting);
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.
@ -675,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
@ -692,7 +700,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
if (!restarting) {
if (isDifferentTextField) {
mainKeyboardView.closing();
loadSettings();
@ -701,7 +709,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
switcher.loadKeyboard(editorInfo, mCurrentSettings);
updateKeyboardViewGestureHandlingModeByMainDictionaryAvailability();
}
setSuggestionStripShownInternal(
isSuggestionsStripVisible(), /* needsInputViewShown */ false);
@ -719,8 +726,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mHandler.cancelUpdateSuggestionStrip();
mHandler.cancelDoubleSpacesTimer();
mainKeyboardView.setMainDictionaryAvailability(mIsMainDictionaryAvailable);
mainKeyboardView.setKeyPreviewPopupEnabled(mCurrentSettings.mKeyPreviewPopupOn,
mCurrentSettings.mKeyPreviewPopupDismissDelay);
mainKeyboardView.setGestureHandlingEnabledByUser(mCurrentSettings.mGestureInputEnabled);
mainKeyboardView.setGesturePreviewMode(mCurrentSettings.mGesturePreviewTrailEnabled,
mCurrentSettings.mGestureFloatingPreviewTextEnabled);
if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
}
@ -898,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;
}
@ -926,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) {
@ -1046,9 +1060,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
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(),
@ -1084,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();
}
@ -1112,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;
}
@ -1178,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() {
@ -1217,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;
}
@ -1233,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}.
@ -1247,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
@ -1284,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()));
@ -1317,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);
}
@ -1333,22 +1342,24 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mLastComposedWord.deactivate();
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(LastComposedWord.NOT_A_SEPARATOR);
}
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);
@ -1361,15 +1372,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
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
@ -1447,9 +1457,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// 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.
@ -1469,9 +1476,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()) {
@ -1500,9 +1504,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) {
@ -1521,14 +1522,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)) {
@ -1604,13 +1599,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);
@ -1640,7 +1634,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
final boolean swapWeakSpace = maybeStripSpace(primaryCode, spaceState,
KeyboardActionListener.SUGGESTION_STRIP_COORDINATE == x);
Constants.SUGGESTION_STRIP_COORDINATE == x);
if (SPACE_STATE_PHANTOM == spaceState &&
mCurrentSettings.isPhantomSpacePromotingSymbol(primaryCode)) {
@ -1687,6 +1681,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
Utils.Stats.onSeparator((char)primaryCode, x, y);
mHandler.postUpdateShiftState();
return didAutoCorrect;
}
@ -1707,7 +1702,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();
}
@ -1853,10 +1849,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
+ "is empty? Impossible! I must commit suicide.");
}
Utils.Stats.onAutoCorrection(typedWord, autoCorrection.toString(), separatorCodePoint);
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_commitCurrentAutoCorrection(typedWord,
autoCorrection.toString());
}
mExpectingUpdateSelection = true;
commitChosenWord(autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD,
separatorCodePoint);
@ -1873,8 +1865,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()) {
@ -1882,13 +1873,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;
}
@ -1915,10 +1905,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;
}
@ -1927,12 +1913,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();
@ -1948,8 +1934,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);
@ -1967,9 +1953,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
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
@ -1992,6 +1975,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
@ -2003,7 +1987,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 {
@ -2036,9 +2020,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();
}
@ -2066,9 +2047,6 @@ 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());
@ -2076,8 +2054,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mConnection.commitText(originallyTypedWord, 1);
// Re-insert the separator
sendKeyCodePoint(mLastComposedWord.mSeparatorCode);
Utils.Stats.onSeparator(mLastComposedWord.mSeparatorCode, WordComposer.NOT_A_COORDINATE,
WordComposer.NOT_A_COORDINATE);
Utils.Stats.onSeparator(mLastComposedWord.mSeparatorCode,
Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_revertCommit(originallyTypedWord);
}
@ -2093,9 +2071,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();
@ -2103,7 +2082,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (mKeyboardSwitcher.getMainKeyboardView() != null) {
// Reload keyboard because the current language has been changed.
mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mCurrentSettings);
updateKeyboardViewGestureHandlingModeByMainDictionaryAvailability();
}
// Since we just changed languages, we should re-evaluate suggestions with whatever word
// we are currently composing. If we are not composing anything, we may want to display
@ -2111,17 +2089,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mHandler.postUpdateSuggestionStrip();
}
private void updateKeyboardViewGestureHandlingModeByMainDictionaryAvailability() {
final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
if (mainKeyboardView != null) {
final boolean shouldHandleGesture = mCurrentSettings.mGestureInputEnabled
&& mIsMainDictionaryAvailable;
mainKeyboardView.setGestureHandlingMode(shouldHandleGesture,
mCurrentSettings.mGesturePreviewTrailEnabled,
mCurrentSettings.mGestureFloatingPreviewTextEnabled);
}
}
// TODO: Remove this method from {@link LatinIME} and move {@link FeedbackManager} to
// {@link KeyboardSwitcher}. Called from KeyboardSwitcher
public void hapticAndAudioFeedback(final int primaryCode) {

View File

@ -193,7 +193,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.

View File

@ -1,32 +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.latin;
public class NativeUtils {
static {
JniUtils.loadNativeLibrary();
}
private NativeUtils() {
// This utility class is not publicly instantiable.
}
/**
* This method just calls up libm's powf() directly.
*/
public static native float powf(float x, float y);
}

View File

@ -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 + "]";
}
}

View File

@ -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;
}
}

View File

@ -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),
@ -417,6 +417,10 @@ public class SettingsValues {
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();

View File

@ -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);
}

View File

@ -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.

View File

@ -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) {
for (final InputMethodSubtype ims : enabledSubtypesOfThisIme) {
if (ims.equals(subtype)) {
return true;
}
}
if (DBG) {
Log.w(TAG, "Last subtype: "
+ currentSubtype.getLocale() + "/" + currentSubtype.getExtraValue());
Log.w(TAG, "Last subtype was disabled. Update to the current one.");
}
updateSubtype(ImfUtils.getCurrentInputMethodSubtype(mService, mNoLanguageSubtype));
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() {

View File

@ -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;
@ -60,13 +59,11 @@ public class Suggest {
// Locale used for upper- and title-casing words
private final Locale mLocale;
private final SuggestInitializationListener mListener;
public Suggest(final Context context, final Locale locale,
final SuggestInitializationListener listener) {
initAsynchronously(context, locale);
initAsynchronously(context, locale, listener);
mLocale = locale;
mListener = listener;
}
/* package for test */ Suggest(final Context context, final File dictionary,
@ -74,23 +71,13 @@ public class Suggest {
final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(context, dictionary,
startOffset, length /* useFullEditDistance */, false, locale);
mLocale = locale;
mListener = null;
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) {
resetMainDict(context, locale);
// 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 void initAsynchronously(final Context context, final Locale locale,
final SuggestInitializationListener listener) {
resetMainDict(context, locale, listener);
}
private static void addOrReplaceDictionary(
@ -104,10 +91,11 @@ public class Suggest {
}
}
public void resetMainDict(final Context context, final Locale locale) {
public void resetMainDict(final Context context, final Locale locale,
final SuggestInitializationListener listener) {
mMainDictionary = null;
if (mListener != null) {
mListener.onUpdateMainDictionaryAvailability(hasMainDictionary());
if (listener != null) {
listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
}
new Thread("InitializeBinaryDictionary") {
@Override
@ -116,8 +104,8 @@ public class Suggest {
DictionaryFactory.createMainDictionaryFromManager(context, locale);
addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, newMainDict);
mMainDictionary = newMainDict;
if (mListener != null) {
mListener.onUpdateMainDictionaryAvailability(hasMainDictionary());
if (listener != null) {
listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
}
}
}.start();
@ -170,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);
@ -209,23 +205,20 @@ public class Suggest {
wordComposerForLookup, prevWordForBigram, proximityInfo));
}
// TODO: Change this scheme - a boolean is not enough. A whitelisted word may be "valid"
// but still autocorrected from - in the case the whitelist only capitalizes the word.
// The whitelist should be case-insensitive, so it's not possible to be consistent with
// a boolean flag. Right now this is handled with a slight hack in
// WhitelistDictionary#shouldForciblyAutoCorrectFrom.
final boolean allowsToBeAutoCorrected = AutoCorrection.isWhitelistedOrNotAWord(
mDictionaries, consideredWord, wordComposer.isFirstCharCapitalized());
final CharSequence whitelistedWord =
mWhiteListDictionary.getWhitelistedWord(consideredWord);
if (whitelistedWord != null) {
// MAX_SCORE ensures this will be considered strong enough to be auto-corrected
suggestionsSet.add(new SuggestedWordInfo(whitelistedWord,
SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_WHITELIST,
Dictionary.TYPE_WHITELIST));
final CharSequence whitelistedWord;
if (suggestionsSet.isEmpty()) {
whitelistedWord = null;
} else if (SuggestedWordInfo.KIND_WHITELIST != suggestionsSet.first().mKind) {
whitelistedWord = null;
} else {
whitelistedWord = suggestionsSet.first().mWord;
}
final boolean allowsToBeAutoCorrected = (null != whitelistedWord
&& !whitelistedWord.equals(consideredWord))
|| AutoCorrection.isNotAWord(mDictionaries, consideredWord,
wordComposer.isFirstCharCapitalized());
final boolean hasAutoCorrection;
// TODO: using isCorrectionEnabled here is not very good. It's probably useless, because
// any attempt to do auto-correction is already shielded with a test for this flag; at the
@ -249,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();
@ -296,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);
@ -346,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.
@ -399,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();

View File

@ -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());

View File

@ -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;

View File

@ -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)) {

View File

@ -212,7 +212,7 @@ public class UserHistoryForgettingCurveUtils {
for (int j = 0; j < ELAPSED_TIME_MAX; ++j) {
final float elapsedHours = j * ELAPSED_TIME_INTERVAL_HOURS;
final float freq = initialFreq
* NativeUtils.powf(initialFreq, elapsedHours / HALF_LIFE_HOURS);
* (float)Math.pow(initialFreq, elapsedHours / HALF_LIFE_HOURS);
final int intFreq = Math.min(FC_FREQ_MAX, Math.max(0, (int)freq));
SCORE_TABLE[i][j] = intFreq;
}

View File

@ -65,44 +65,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';
@ -477,7 +439,7 @@ public class Utils {
private static final String HARDWARE_PREFIX = Build.HARDWARE + ",";
private static final HashMap<String, String> sDeviceOverrideValueMap =
new HashMap<String, String>();
CollectionUtils.newHashMap();
public static String getDeviceOverrideValue(Resources res, int overrideResId, String defValue) {
final int orientation = res.getConfiguration().orientation;
@ -495,7 +457,7 @@ public class Utils {
return sDeviceOverrideValueMap.get(key);
}
private static final HashMap<String, Long> EMPTY_LT_HASH_MAP = new HashMap<String, Long>();
private static final HashMap<String, Long> EMPTY_LT_HASH_MAP = CollectionUtils.newHashMap();
private static final String LOCALE_AND_TIME_STR_SEPARATER = ",";
public static HashMap<String, Long> localeAndTimeStrToHashMap(String str) {
if (TextUtils.isEmpty(str)) {
@ -506,7 +468,7 @@ public class Utils {
if (N < 2 || N % 2 != 0) {
return EMPTY_LT_HASH_MAP;
}
final HashMap<String, Long> retval = new HashMap<String, Long>();
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]);

View File

@ -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.
}

View File

@ -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;
}
/**

View File

@ -22,10 +22,13 @@ 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;
@ -124,7 +127,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;
@ -307,33 +310,32 @@ public class BinaryDictInputOutput {
}
/**
* Reads a string from a RandomAccessFile. This is the converse of the above method.
* Reads a string from a ByteBuffer. This is the converse of the above method.
*/
private static String readString(final RandomAccessFile source) throws IOException {
private static String readString(final ByteBuffer 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 ByteBuffer.
*
* 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 ByteBuffer buffer) {
int character = readUnsignedByte(buffer);
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 += readUnsignedShort(buffer);
}
return character;
}
@ -783,10 +785,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 +1093,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 ByteBuffer buffer,
final int originalGroupAddress) {
int addressPointer = originalGroupAddress;
final int flags = source.readUnsignedByte();
final int flags = readUnsignedByte(buffer);
++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 = readUnsignedByte(buffer);
} 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 += readUnsignedByte(buffer);
addressPointer += 1;
break;
case FLAG_GROUP_ADDRESS_TYPE_TWOBYTES:
childrenAddress += source.readUnsignedShort();
childrenAddress += readUnsignedShort(buffer);
addressPointer += 2;
break;
case FLAG_GROUP_ADDRESS_TYPE_THREEBYTES:
childrenAddress += (source.readUnsignedByte() << 16) + source.readUnsignedShort();
childrenAddress += readUnsignedInt24(buffer);
addressPointer += 3;
break;
case FLAG_GROUP_ADDRESS_TYPE_NOADDRESS:
@ -1140,38 +1142,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.getShort(); // Skip the size
while (true) {
final int targetFlags = source.readUnsignedByte();
final String word = CharEncoding.readString(source);
final int targetFlags = readUnsignedByte(buffer);
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 = readUnsignedByte(buffer);
++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 * readUnsignedByte(buffer);
addressPointer += 1;
break;
case FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES:
bigramAddress += sign * source.readUnsignedShort();
bigramAddress += sign * readUnsignedShort(buffer);
addressPointer += 2;
break;
case FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES:
final int offset = ((source.readUnsignedByte() << 16)
+ source.readUnsignedShort());
final int offset = (readUnsignedByte(buffer) << 16)
+ readUnsignedShort(buffer);
bigramAddress += sign * offset;
addressPointer += 3;
break;
@ -1188,15 +1190,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 ByteBuffer buffer) {
final int msb = readUnsignedByte(buffer);
if (MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT >= msb) {
return msb;
} else {
return ((MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT & msb) << 8)
+ source.readUnsignedByte();
+ readUnsignedByte(buffer);
}
}
@ -1204,31 +1206,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 ByteBuffer 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 +1239,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 = readUnsignedByte(buffer);
last = null;
continue;
}
@ -1249,14 +1249,14 @@ 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 = readUnsignedByte(buffer);
last = null;
continue;
}
}
source.seek(originalPointer);
buffer.position(originalPointer);
wordCache.put(address, result);
return result;
}
@ -1269,44 +1269,47 @@ public class BinaryDictInputOutput {
* 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 ByteBuffer 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));
new CharGroup(info.mCharacters, shortcutTargets,
bigrams, info.mFrequency, children));
} else {
nodeContents.add(
new CharGroup(info.mCharacters, shortcutTargets, bigrams, info.mFrequency));
new CharGroup(info.mCharacters, shortcutTargets,
bigrams, info.mFrequency));
}
groupOffset = info.mEndAddress;
}
@ -1318,57 +1321,76 @@ public class BinaryDictInputOutput {
/**
* 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 ByteBuffer buffer) throws IOException {
final int magic_v1 = readUnsignedShort(buffer);
if (VERSION_1_MAGIC_NUMBER == magic_v1) return readUnsignedByte(buffer);
final int magic_v2 = (magic_v1 << 16) + readUnsignedShort(buffer);
if (VERSION_2_MAGIC_NUMBER == magic_v2) return readUnsignedShort(buffer);
return NOT_A_VERSION_NUMBER;
}
/**
* Reads a random access file and returns the memory representation of the dictionary.
* Reads options from a file and populate a map with their contents.
*
* The file is read at the current file pointer, so the caller must take care the pointer
* is in the right place before calling this.
*/
public static void populateOptions(final ByteBuffer 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);
}
}
/**
* Reads a byte buffer 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 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 RandomAccessFile source,
public static FusionDictionary readDictionaryBinary(final ByteBuffer buffer,
final FusionDictionary dict) throws IOException, UnsupportedFormatException {
// Check file version
final int version = getFormatVersion(source);
if (version < MINIMUM_SUPPORTED_VERSION || version > MAXIMUM_SUPPORTED_VERSION ) {
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);
}
// Read options
final int optionsFlags = source.readUnsignedShort();
// clear cache
wordCache.clear();
final long headerSize;
// Read options
final int optionsFlags = readUnsignedShort(buffer);
final int headerSize;
final HashMap<String, String> options = new HashMap<String, String>();
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);
headerSize = buffer.getInt();
populateOptions(buffer, headerSize, options);
buffer.position(headerSize);
}
source.seek(headerSize);
if (headerSize < 0) {
throw new UnsupportedFormatException("header size can't be negative.");
}
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,
@ -1391,6 +1413,28 @@ public class BinaryDictInputOutput {
return newDict;
}
/**
* Helper function to read one byte from ByteBuffer.
*/
private static int readUnsignedByte(final ByteBuffer buffer) {
return ((int)buffer.get()) & 0xFF;
}
/**
* Helper function to read two byte from ByteBuffer.
*/
private static int readUnsignedShort(final ByteBuffer buffer) {
return ((int)buffer.getShort()) & 0xFFFF;
}
/**
* Helper function to read three byte from ByteBuffer.
*/
private static int readUnsignedInt24(final ByteBuffer buffer) {
final int value = readUnsignedByte(buffer) << 16;
return value + readUnsignedShort(buffer);
}
/**
* Basic test to find out whether the file is a binary dictionary or not.
*
@ -1400,14 +1444,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(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;
}
}

View File

@ -516,13 +516,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;
}

View File

@ -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) {

View File

@ -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);

View File

@ -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;
@ -193,8 +194,8 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
if (shouldFilterOut(inText, mScript)) {
DictAndProximity dictInfo = null;
try {
dictInfo = mDictionaryPool.takeOrGetNull();
if (null == dictInfo) {
dictInfo = mDictionaryPool.pollWithDefaultTimeout();
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);
}
@ -236,8 +237,8 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
boolean isInDict = true;
DictAndProximity dictInfo = null;
try {
dictInfo = mDictionaryPool.takeOrGetNull();
if (null == dictInfo) {
dictInfo = mDictionaryPool.pollWithDefaultTimeout();
if (!DictionaryPool.isAValidDictionary(dictInfo)) {
return AndroidSpellCheckerService.getNotInDictEmptySuggestions();
}
final ArrayList<SuggestedWordInfo> suggestions =

View File

@ -16,19 +16,56 @@
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;
/**
* A blocking queue that creates dictionaries up to a certain limit as necessary.
* As a deadlock-detecting device, if waiting for more than TIMEOUT = 3 seconds, we
* will clear the queue and generate its contents again. This is transparent for
* the client code, but may help with sloppy clients.
*/
@SuppressWarnings("serial")
public class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> {
private final static String TAG = DictionaryPool.class.getSimpleName();
// How many seconds we wait for a dictionary to become available. Past this delay, we give up in
// fear some bug caused a deadlock, and reset the whole pool.
private final static int TIMEOUT = 3;
private final AndroidSpellCheckerService mService;
private final int mMaxSize;
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) {
@ -41,13 +78,23 @@ public class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> {
}
@Override
public DictAndProximity take() throws InterruptedException {
public DictAndProximity poll(final long timeout, final TimeUnit unit)
throws InterruptedException {
final DictAndProximity dict = poll();
if (null != dict) return dict;
synchronized(this) {
if (mSize >= mMaxSize) {
// Our pool is already full. Wait until some dictionary is ready.
return super.take();
// Our pool is already full. Wait until some dictionary is ready, or TIMEOUT
// expires to avoid a deadlock.
final DictAndProximity result = super.poll(timeout, unit);
if (null == result) {
Log.e(TAG, "Deadlock detected ! Resetting dictionary pool");
clear();
mSize = 1;
return mService.createDictAndProximity(mLocale);
} else {
return result;
}
} else {
++mSize;
return mService.createDictAndProximity(mLocale);
@ -56,9 +103,9 @@ public class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> {
}
// Convenience method
public DictAndProximity takeOrGetNull() {
public DictAndProximity pollWithDefaultTimeout() {
try {
return take();
return poll(TIMEOUT, TimeUnit.SECONDS);
} catch (InterruptedException e) {
return null;
}
@ -78,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);
}

View File

@ -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.
@ -111,6 +112,7 @@ public class SpellCheckerProximityInfo {
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
};
static {
buildProximityIndices(PROXIMITY, INDICES);
@ -121,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 = {

View File

@ -58,6 +58,7 @@ 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.StaticInnerHandlerWrapper;
@ -72,7 +73,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 +89,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 +132,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 +168,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 +196,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 = getFraction(a,
R.styleable.SuggestionStripView_alphaValidTypedWord, 1.0f);
final float alphaTypedWord = getFraction(a,
R.styleable.SuggestionStripView_alphaTypedWord, 1.0f);
final float alphaAutoCorrect = getFraction(a,
R.styleable.SuggestionStripView_alphaAutoCorrect, 1.0f);
final float alphaSuggested = getFraction(a,
R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
mAlphaObsoleted = getFraction(a,
R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
mColorValidTypedWord = applyAlpha(a.getColor(
R.styleable.SuggestionStripView_colorValidTypedWord, 0), alphaValidTypedWord);
mColorTypedWord = applyAlpha(a.getColor(
@ -216,14 +217,14 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
mSuggestionsCountInStrip = a.getInt(
R.styleable.SuggestionStripView_suggestionsCountInStrip,
DEFAULT_SUGGESTIONS_COUNT_IN_STRIP);
mCenterSuggestionWeight = getPercent(a,
mCenterSuggestionWeight = 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 = getFraction(a,
R.styleable.SuggestionStripView_minMoreSuggestionsWidth, 1.0f);
a.recycle();
mMoreSuggestionsHint = getMoreSuggestionsHint(res,
@ -277,14 +278,8 @@ 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;
static float getFraction(final TypedArray a, final int index, final float defValue) {
return a.getFraction(index, 1, 1, defValue);
}
private CharSequence getStyledSuggestionWord(SuggestedWords suggestedWords, int pos) {
@ -726,9 +721,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 +867,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

View File

@ -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);
}
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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 */);
}
}
}

View File

@ -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:
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;
}
} catch (Exception e) {
Log.d(TAG, "error when closing ResearchLog:");
e.printStackTrace();
} finally {
boolean success = mFile.setWritable(false, false);
mLoggingState = LOGGING_STATE_STOPPED;
if (mFile.exists()) {
mFile.setWritable(false, false);
}
}
return null;
}
});
removeAnyScheduledFlush();
mExecutor.shutdown();
mLoggingState = LOGGING_STATE_STOPPING;
break;
case LOGGING_STATE_STOPPING:
case LOGGING_STATE_STOPPED:
}
}
public boolean isAlive() {
switch (mLoggingState) {
case LOGGING_STATE_UNSTARTED:
case LOGGING_STATE_READY:
case LOGGING_STATE_RUNNING:
return true;
}
return false;
}
public void waitUntilStopped(final int timeoutInMs) throws InterruptedException {
removeAnyScheduledFlush();
mExecutor.shutdown();
mExecutor.awaitTermination(timeoutInMs, TimeUnit.MILLISECONDS);
}
private boolean mIsAbortSuccessful;
public synchronized void abort() {
switch (mLoggingState) {
case LOGGING_STATE_UNSTARTED:
mLoggingState = LOGGING_STATE_STOPPED;
isAbortSuccessful = true;
break;
case LOGGING_STATE_READY:
case LOGGING_STATE_RUNNING:
mExecutor.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
try {
if (mHasWrittenData) {
mJsonWriter.endArray();
mJsonWriter.close();
mHasWrittenData = false;
}
} finally {
isAbortSuccessful = mFile.delete();
mIsAbortSuccessful = mFile.delete();
}
return null;
}
});
removeAnyScheduledFlush();
mExecutor.shutdown();
mLoggingState = LOGGING_STATE_STOPPING;
break;
case LOGGING_STATE_STOPPING:
case LOGGING_STATE_STOPPED:
}
}
private boolean isAbortSuccessful;
public boolean isAbortSuccessful() {
return isAbortSuccessful;
public boolean blockingAbort() throws InterruptedException {
abort();
mExecutor.awaitTermination(ABORT_TIMEOUT_IN_MS, TimeUnit.MILLISECONDS);
return mIsAbortSuccessful;
}
public void awaitTermination(int delay, TimeUnit timeUnit) throws InterruptedException {
mExecutor.awaitTermination(delay, timeUnit);
}
/* package */ synchronized void flush() {
switch (mLoggingState) {
case LOGGING_STATE_UNSTARTED:
break;
case LOGGING_STATE_READY:
case LOGGING_STATE_RUNNING:
removeAnyScheduledFlush();
mExecutor.submit(mFlushCallable);
break;
case LOGGING_STATE_STOPPING:
case LOGGING_STATE_STOPPED:
}
}
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();
}
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:
public synchronized void publish(final LogUnit logUnit, final boolean isIncludingPrivateData) {
try {
mExecutor.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
logUnit.publishPublicEventsTo(ResearchLog.this);
logUnit.publishTo(ResearchLog.this, isIncludingPrivateData);
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:
} 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) {
@ -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);
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 {

View File

@ -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;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -36,7 +36,7 @@ LOCAL_CFLAGS += -Wno-unused-parameter -Wno-unused-function
LATIN_IME_JNI_SRC_FILES := \
com_android_inputmethod_keyboard_ProximityInfo.cpp \
com_android_inputmethod_latin_BinaryDictionary.cpp \
com_android_inputmethod_latin_NativeUtils.cpp \
com_android_inputmethod_latin_DicTraverseSession.cpp \
jni_common.cpp
LATIN_IME_CORE_SRC_FILES := \
@ -46,6 +46,7 @@ LATIN_IME_CORE_SRC_FILES := \
char_utils.cpp \
correction.cpp \
dictionary.cpp \
dic_traverse_wrapper.cpp \
proximity_info.cpp \
proximity_info_state.cpp \
unigram_dictionary.cpp \

View File

@ -16,8 +16,6 @@
#define LOG_TAG "LatinIME: jni: ProximityInfo"
#include <string>
#include "com_android_inputmethod_keyboard_ProximityInfo.h"
#include "jni.h"
#include "jni_common.h"
@ -26,53 +24,27 @@
namespace latinime {
static jlong latinime_Keyboard_setProximityInfo(JNIEnv *env, jobject object,
jstring localejStr, jint maxProximityCharsSize, jint displayWidth, jint displayHeight,
jint gridWidth, jint gridHeight, jint mostCommonkeyWidth, jintArray proximityCharsArray,
jint keyCount, jintArray keyXCoordinateArray, jintArray keyYCoordinateArray,
jintArray keyWidthArray, jintArray keyHeightArray, jintArray keyCharCodeArray,
jfloatArray sweetSpotCenterXArray, jfloatArray sweetSpotCenterYArray,
jfloatArray sweetSpotRadiusArray) {
const char *localeStrPtr = env->GetStringUTFChars(localejStr, 0);
const std::string localeStr(localeStrPtr);
jint *proximityChars = env->GetIntArrayElements(proximityCharsArray, 0);
jint *keyXCoordinates = safeGetIntArrayElements(env, keyXCoordinateArray);
jint *keyYCoordinates = safeGetIntArrayElements(env, keyYCoordinateArray);
jint *keyWidths = safeGetIntArrayElements(env, keyWidthArray);
jint *keyHeights = safeGetIntArrayElements(env, keyHeightArray);
jint *keyCharCodes = safeGetIntArrayElements(env, keyCharCodeArray);
jfloat *sweetSpotCenterXs = safeGetFloatArrayElements(env, sweetSpotCenterXArray);
jfloat *sweetSpotCenterYs = safeGetFloatArrayElements(env, sweetSpotCenterYArray);
jfloat *sweetSpotRadii = safeGetFloatArrayElements(env, sweetSpotRadiusArray);
ProximityInfo *proximityInfo = new ProximityInfo(
localeStr, maxProximityCharsSize, displayWidth, displayHeight, gridWidth, gridHeight,
mostCommonkeyWidth, (const int32_t*)proximityChars, keyCount,
(const int32_t*)keyXCoordinates, (const int32_t*)keyYCoordinates,
(const int32_t*)keyWidths, (const int32_t*)keyHeights, (const int32_t*)keyCharCodes,
(const float*)sweetSpotCenterXs, (const float*)sweetSpotCenterYs,
(const float*)sweetSpotRadii);
safeReleaseFloatArrayElements(env, sweetSpotRadiusArray, sweetSpotRadii);
safeReleaseFloatArrayElements(env, sweetSpotCenterYArray, sweetSpotCenterYs);
safeReleaseFloatArrayElements(env, sweetSpotCenterXArray, sweetSpotCenterXs);
safeReleaseIntArrayElements(env, keyCharCodeArray, keyCharCodes);
safeReleaseIntArrayElements(env, keyHeightArray, keyHeights);
safeReleaseIntArrayElements(env, keyWidthArray, keyWidths);
safeReleaseIntArrayElements(env, keyYCoordinateArray, keyYCoordinates);
safeReleaseIntArrayElements(env, keyXCoordinateArray, keyXCoordinates);
env->ReleaseIntArrayElements(proximityCharsArray, proximityChars, 0);
env->ReleaseStringUTFChars(localejStr, localeStrPtr);
return (jlong)proximityInfo;
jstring localeJStr, jint maxProximityCharsSize, jint displayWidth, jint displayHeight,
jint gridWidth, jint gridHeight, jint mostCommonkeyWidth, jintArray proximityChars,
jint keyCount, jintArray keyXCoordinates, jintArray keyYCoordinates,
jintArray keyWidths, jintArray keyHeights, jintArray keyCharCodes,
jfloatArray sweetSpotCenterXs, jfloatArray sweetSpotCenterYs, jfloatArray sweetSpotRadii) {
ProximityInfo *proximityInfo = new ProximityInfo(env, localeJStr, maxProximityCharsSize,
displayWidth, displayHeight, gridWidth, gridHeight, mostCommonkeyWidth, proximityChars,
keyCount, keyXCoordinates, keyYCoordinates, keyWidths, keyHeights, keyCharCodes,
sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii);
return reinterpret_cast<jlong>(proximityInfo);
}
static void latinime_Keyboard_release(JNIEnv *env, jobject object, jlong proximityInfo) {
ProximityInfo *pi = (ProximityInfo*)proximityInfo;
if (!pi) return;
ProximityInfo *pi = reinterpret_cast<ProximityInfo *>(proximityInfo);
delete pi;
}
static JNINativeMethod sKeyboardMethods[] = {
{"setProximityInfoNative", "(Ljava/lang/String;IIIIII[II[I[I[I[I[I[F[F[F)J",
(void*)latinime_Keyboard_setProximityInfo},
{"releaseProximityInfoNative", "(J)V", (void*)latinime_Keyboard_release}
reinterpret_cast<void *>(latinime_Keyboard_setProximityInfo)},
{"releaseProximityInfoNative", "(J)V", reinterpret_cast<void *>(latinime_Keyboard_release)}
};
int register_ProximityInfo(JNIEnv *env) {

View File

@ -14,15 +14,12 @@
* limitations under the License.
*/
#include <cstring> // for memset()
#define LOG_TAG "LatinIME: jni: BinaryDictionary"
#include "binary_format.h"
#include "com_android_inputmethod_latin_BinaryDictionary.h"
#include "correction.h"
#include "defines.h"
#include "dictionary.h"
#include "jni.h"
#include "jni_common.h"
#include "defines.h" // for macros below
#ifdef USE_MMAP_FOR_DICTIONARY
#include <cerrno>
@ -30,13 +27,21 @@
#include <sys/mman.h>
#else // USE_MMAP_FOR_DICTIONARY
#include <cstdlib>
#include <cstdio> // for fopen() etc.
#endif // USE_MMAP_FOR_DICTIONARY
#include "binary_format.h"
#include "com_android_inputmethod_latin_BinaryDictionary.h"
#include "correction.h"
#include "dictionary.h"
#include "jni.h"
#include "jni_common.h"
namespace latinime {
class ProximityInfo;
void releaseDictBuf(void *dictBuf, const size_t length, int fd);
static void releaseDictBuf(const void *dictBuf, const size_t length, const int fd);
static jlong latinime_BinaryDictionary_open(JNIEnv *env, jobject object,
jstring sourceDir, jlong dictOffset, jlong dictSize,
@ -44,11 +49,14 @@ static jlong latinime_BinaryDictionary_open(JNIEnv *env, jobject object,
jint maxPredictions) {
PROF_OPEN;
PROF_START(66);
const char *sourceDirChars = env->GetStringUTFChars(sourceDir, 0);
if (sourceDirChars == 0) {
const jsize sourceDirUtf8Length = env->GetStringUTFLength(sourceDir);
if (sourceDirUtf8Length <= 0) {
AKLOGE("DICT: Can't get sourceDir string");
return 0;
}
char sourceDirChars[sourceDirUtf8Length + 1];
env->GetStringUTFRegion(sourceDir, 0, env->GetStringLength(sourceDir), sourceDirChars);
sourceDirChars[sourceDirUtf8Length] = '\0';
int fd = 0;
void *dictBuf = 0;
int adjust = 0;
@ -68,7 +76,7 @@ static jlong latinime_BinaryDictionary_open(JNIEnv *env, jobject object,
AKLOGE("DICT: Can't mmap dictionary. errno=%d", errno);
return 0;
}
dictBuf = (void *)((char *)dictBuf + adjust);
dictBuf = static_cast<char *>(dictBuf) + adjust;
#else // USE_MMAP_FOR_DICTIONARY
/* malloc version */
FILE *file = 0;
@ -98,17 +106,16 @@ static jlong latinime_BinaryDictionary_open(JNIEnv *env, jobject object,
return 0;
}
#endif // USE_MMAP_FOR_DICTIONARY
env->ReleaseStringUTFChars(sourceDir, sourceDirChars);
if (!dictBuf) {
AKLOGE("DICT: dictBuf is null");
return 0;
}
Dictionary *dictionary = 0;
if (BinaryFormat::UNKNOWN_FORMAT == BinaryFormat::detectFormat((uint8_t*)dictBuf)) {
if (BinaryFormat::UNKNOWN_FORMAT
== BinaryFormat::detectFormat(static_cast<uint8_t *>(dictBuf))) {
AKLOGE("DICT: dictionary format is unknown, bad magic number");
#ifdef USE_MMAP_FOR_DICTIONARY
releaseDictBuf(((char*)dictBuf) - adjust, adjDictSize, fd);
releaseDictBuf(static_cast<const char *>(dictBuf) - adjust, adjDictSize, fd);
#else // USE_MMAP_FOR_DICTIONARY
releaseDictBuf(dictBuf, 0, 0);
#endif // USE_MMAP_FOR_DICTIONARY
@ -122,106 +129,131 @@ static jlong latinime_BinaryDictionary_open(JNIEnv *env, jobject object,
}
static int latinime_BinaryDictionary_getSuggestions(JNIEnv *env, jobject object, jlong dict,
jlong proximityInfo, jintArray xCoordinatesArray, jintArray yCoordinatesArray,
jintArray timesArray, jintArray pointerIdArray, jintArray inputArray, jint arraySize,
jint commitPoint, jboolean isGesture,
jintArray prevWordForBigrams, jboolean useFullEditDistance, jcharArray outputArray,
jintArray frequencyArray, jintArray spaceIndexArray, jintArray outputTypesArray) {
Dictionary *dictionary = (Dictionary*) dict;
jlong proximityInfo, jlong dicTraverseSession, jintArray xCoordinatesArray,
jintArray yCoordinatesArray, jintArray timesArray, jintArray pointerIdsArray,
jintArray inputCodePointsArray, jint arraySize, jint commitPoint, jboolean isGesture,
jintArray prevWordCodePointsForBigrams, jboolean useFullEditDistance,
jcharArray outputCharsArray, jintArray scoresArray, jintArray spaceIndicesArray,
jintArray outputTypesArray) {
Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
if (!dictionary) return 0;
ProximityInfo *pInfo = (ProximityInfo*)proximityInfo;
int *xCoordinates = env->GetIntArrayElements(xCoordinatesArray, 0);
int *yCoordinates = env->GetIntArrayElements(yCoordinatesArray, 0);
int *times = env->GetIntArrayElements(timesArray, 0);
int *pointerIds = env->GetIntArrayElements(pointerIdArray, 0);
int *frequencies = env->GetIntArrayElements(frequencyArray, 0);
int *inputCodes = env->GetIntArrayElements(inputArray, 0);
jchar *outputChars = env->GetCharArrayElements(outputArray, 0);
int *spaceIndices = env->GetIntArrayElements(spaceIndexArray, 0);
int *outputTypes = env->GetIntArrayElements(outputTypesArray, 0);
jint *prevWordChars = prevWordForBigrams
? env->GetIntArrayElements(prevWordForBigrams, 0) : 0;
jsize prevWordLength = prevWordChars ? env->GetArrayLength(prevWordForBigrams) : 0;
ProximityInfo *pInfo = reinterpret_cast<ProximityInfo *>(proximityInfo);
void *traverseSession = reinterpret_cast<void *>(dicTraverseSession);
// Input values
int xCoordinates[arraySize];
int yCoordinates[arraySize];
int times[arraySize];
int pointerIds[arraySize];
const jsize inputCodePointsLength = env->GetArrayLength(inputCodePointsArray);
int inputCodePoints[inputCodePointsLength];
const jsize prevWordCodePointsLength =
prevWordCodePointsForBigrams ? env->GetArrayLength(prevWordCodePointsForBigrams) : 0;
int prevWordCodePointsInternal[prevWordCodePointsLength];
int *prevWordCodePoints = 0;
env->GetIntArrayRegion(xCoordinatesArray, 0, arraySize, xCoordinates);
env->GetIntArrayRegion(yCoordinatesArray, 0, arraySize, yCoordinates);
env->GetIntArrayRegion(timesArray, 0, arraySize, times);
env->GetIntArrayRegion(pointerIdsArray, 0, arraySize, pointerIds);
env->GetIntArrayRegion(inputCodePointsArray, 0, inputCodePointsLength, inputCodePoints);
if (prevWordCodePointsForBigrams) {
env->GetIntArrayRegion(prevWordCodePointsForBigrams, 0, prevWordCodePointsLength,
prevWordCodePointsInternal);
prevWordCodePoints = prevWordCodePointsInternal;
}
// Output values
// TODO: Should be "outputCodePointsLength" and "int outputCodePoints[]"
const jsize outputCharsLength = env->GetArrayLength(outputCharsArray);
unsigned short outputChars[outputCharsLength];
const jsize scoresLength = env->GetArrayLength(scoresArray);
int scores[scoresLength];
const jsize spaceIndicesLength = env->GetArrayLength(spaceIndicesArray);
int spaceIndices[spaceIndicesLength];
const jsize outputTypesLength = env->GetArrayLength(outputTypesArray);
int outputTypes[outputTypesLength];
memset(outputChars, 0, sizeof(outputChars));
memset(scores, 0, sizeof(scores));
memset(spaceIndices, 0, sizeof(spaceIndices));
memset(outputTypes, 0, sizeof(outputTypes));
int count;
if (isGesture || arraySize > 1) {
count = dictionary->getSuggestions(pInfo, xCoordinates, yCoordinates, times, pointerIds,
inputCodes, arraySize, prevWordChars, prevWordLength, commitPoint, isGesture,
useFullEditDistance, (unsigned short*) outputChars, frequencies, spaceIndices,
outputTypes);
if (isGesture || arraySize > 0) {
count = dictionary->getSuggestions(pInfo, traverseSession, xCoordinates, yCoordinates,
times, pointerIds, inputCodePoints, arraySize, prevWordCodePoints,
prevWordCodePointsLength, commitPoint, isGesture, useFullEditDistance, outputChars,
scores, spaceIndices, outputTypes);
} else {
count = dictionary->getBigrams(prevWordChars, prevWordLength, inputCodes,
arraySize, (unsigned short*) outputChars, frequencies, outputTypes);
count = dictionary->getBigrams(prevWordCodePoints, prevWordCodePointsLength,
inputCodePoints, arraySize, outputChars, scores, outputTypes);
}
if (prevWordChars) {
env->ReleaseIntArrayElements(prevWordForBigrams, prevWordChars, JNI_ABORT);
}
env->ReleaseIntArrayElements(outputTypesArray, outputTypes, 0);
env->ReleaseIntArrayElements(spaceIndexArray, spaceIndices, 0);
env->ReleaseCharArrayElements(outputArray, outputChars, 0);
env->ReleaseIntArrayElements(inputArray, inputCodes, JNI_ABORT);
env->ReleaseIntArrayElements(frequencyArray, frequencies, 0);
env->ReleaseIntArrayElements(pointerIdArray, pointerIds, 0);
env->ReleaseIntArrayElements(timesArray, times, 0);
env->ReleaseIntArrayElements(yCoordinatesArray, yCoordinates, 0);
env->ReleaseIntArrayElements(xCoordinatesArray, xCoordinates, 0);
// Copy back the output values
// TODO: Should be SetIntArrayRegion()
env->SetCharArrayRegion(outputCharsArray, 0, outputCharsLength, outputChars);
env->SetIntArrayRegion(scoresArray, 0, scoresLength, scores);
env->SetIntArrayRegion(spaceIndicesArray, 0, spaceIndicesLength, spaceIndices);
env->SetIntArrayRegion(outputTypesArray, 0, outputTypesLength, outputTypes);
return count;
}
static jint latinime_BinaryDictionary_getFrequency(JNIEnv *env, jobject object, jlong dict,
jintArray wordArray, jint wordLength) {
Dictionary *dictionary = (Dictionary*)dict;
if (!dictionary) return (jboolean) false;
jint *word = env->GetIntArrayElements(wordArray, 0);
jint result = dictionary->getFrequency(word, wordLength);
env->ReleaseIntArrayElements(wordArray, word, JNI_ABORT);
return result;
jintArray wordArray) {
Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
if (!dictionary) return 0;
const jsize codePointLength = env->GetArrayLength(wordArray);
int codePoints[codePointLength];
env->GetIntArrayRegion(wordArray, 0, codePointLength, codePoints);
return dictionary->getFrequency(codePoints, codePointLength);
}
static jboolean latinime_BinaryDictionary_isValidBigram(JNIEnv *env, jobject object, jlong dict,
jintArray wordArray1, jintArray wordArray2) {
Dictionary *dictionary = (Dictionary*)dict;
Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
if (!dictionary) return (jboolean) false;
jint *word1 = env->GetIntArrayElements(wordArray1, 0);
jint *word2 = env->GetIntArrayElements(wordArray2, 0);
jsize length1 = word1 ? env->GetArrayLength(wordArray1) : 0;
jsize length2 = word2 ? env->GetArrayLength(wordArray2) : 0;
jboolean result = dictionary->isValidBigram(word1, length1, word2, length2);
env->ReleaseIntArrayElements(wordArray2, word2, JNI_ABORT);
env->ReleaseIntArrayElements(wordArray1, word1, JNI_ABORT);
return result;
const jsize codePointLength1 = env->GetArrayLength(wordArray1);
const jsize codePointLength2 = env->GetArrayLength(wordArray2);
int codePoints1[codePointLength1];
int codePoints2[codePointLength2];
env->GetIntArrayRegion(wordArray1, 0, codePointLength1, codePoints1);
env->GetIntArrayRegion(wordArray2, 0, codePointLength2, codePoints2);
return dictionary->isValidBigram(codePoints1, codePointLength1, codePoints2, codePointLength2);
}
static jfloat latinime_BinaryDictionary_calcNormalizedScore(JNIEnv *env, jobject object,
jcharArray before, jint beforeLength, jcharArray after, jint afterLength, jint score) {
jchar *beforeChars = env->GetCharArrayElements(before, 0);
jchar *afterChars = env->GetCharArrayElements(after, 0);
jfloat result = Correction::RankingAlgorithm::calcNormalizedScore((unsigned short*)beforeChars,
beforeLength, (unsigned short*)afterChars, afterLength, score);
env->ReleaseCharArrayElements(after, afterChars, JNI_ABORT);
env->ReleaseCharArrayElements(before, beforeChars, JNI_ABORT);
return result;
jcharArray before, jcharArray after, jint score) {
jsize beforeLength = env->GetArrayLength(before);
jsize afterLength = env->GetArrayLength(after);
jchar beforeChars[beforeLength];
jchar afterChars[afterLength];
env->GetCharArrayRegion(before, 0, beforeLength, beforeChars);
env->GetCharArrayRegion(after, 0, afterLength, afterChars);
return Correction::RankingAlgorithm::calcNormalizedScore(
static_cast<unsigned short *>(beforeChars), beforeLength,
static_cast<unsigned short *>(afterChars), afterLength, score);
}
static jint latinime_BinaryDictionary_editDistance(JNIEnv *env, jobject object,
jcharArray before, jint beforeLength, jcharArray after, jint afterLength) {
jchar *beforeChars = env->GetCharArrayElements(before, 0);
jchar *afterChars = env->GetCharArrayElements(after, 0);
jint result = Correction::RankingAlgorithm::editDistance(
(unsigned short*)beforeChars, beforeLength, (unsigned short*)afterChars, afterLength);
env->ReleaseCharArrayElements(after, afterChars, JNI_ABORT);
env->ReleaseCharArrayElements(before, beforeChars, JNI_ABORT);
return result;
jcharArray before, jcharArray after) {
jsize beforeLength = env->GetArrayLength(before);
jsize afterLength = env->GetArrayLength(after);
jchar beforeChars[beforeLength];
jchar afterChars[afterLength];
env->GetCharArrayRegion(before, 0, beforeLength, beforeChars);
env->GetCharArrayRegion(after, 0, afterLength, afterChars);
return Correction::RankingAlgorithm::editDistance(
static_cast<unsigned short *>(beforeChars), beforeLength,
static_cast<unsigned short *>(afterChars), afterLength);
}
static void latinime_BinaryDictionary_close(JNIEnv *env, jobject object, jlong dict) {
Dictionary *dictionary = (Dictionary*)dict;
Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
if (!dictionary) return;
void *dictBuf = dictionary->getDict();
const void *dictBuf = dictionary->getDict();
if (!dictBuf) return;
#ifdef USE_MMAP_FOR_DICTIONARY
releaseDictBuf((void *)((char *)dictBuf - dictionary->getDictBufAdjust()),
releaseDictBuf(static_cast<const char *>(dictBuf) - dictionary->getDictBufAdjust(),
dictionary->getDictSize() + dictionary->getDictBufAdjust(), dictionary->getMmapFd());
#else // USE_MMAP_FOR_DICTIONARY
releaseDictBuf(dictBuf, 0, 0);
@ -229,9 +261,9 @@ static void latinime_BinaryDictionary_close(JNIEnv *env, jobject object, jlong d
delete dictionary;
}
void releaseDictBuf(void *dictBuf, const size_t length, int fd) {
static void releaseDictBuf(const void *dictBuf, const size_t length, const int fd) {
#ifdef USE_MMAP_FOR_DICTIONARY
int ret = munmap(dictBuf, length);
int ret = munmap(const_cast<void *>(dictBuf), length);
if (ret != 0) {
AKLOGE("DICT: Failure in munmap. ret=%d errno=%d", ret, errno);
}
@ -240,20 +272,24 @@ void releaseDictBuf(void *dictBuf, const size_t length, int fd) {
AKLOGE("DICT: Failure in close. ret=%d errno=%d", ret, errno);
}
#else // USE_MMAP_FOR_DICTIONARY
free(dictBuf);
free(const_cast<void *>(dictBuf));
#endif // USE_MMAP_FOR_DICTIONARY
}
static JNINativeMethod sMethods[] = {
{"openNative", "(Ljava/lang/String;JJIIIII)J", (void*)latinime_BinaryDictionary_open},
{"closeNative", "(J)V", (void*)latinime_BinaryDictionary_close},
{"getSuggestionsNative", "(JJ[I[I[I[I[IIIZ[IZ[C[I[I[I)I",
(void*) latinime_BinaryDictionary_getSuggestions},
{"getFrequencyNative", "(J[II)I", (void*)latinime_BinaryDictionary_getFrequency},
{"isValidBigramNative", "(J[I[I)Z", (void*)latinime_BinaryDictionary_isValidBigram},
{"calcNormalizedScoreNative", "([CI[CII)F",
(void*)latinime_BinaryDictionary_calcNormalizedScore},
{"editDistanceNative", "([CI[CI)I", (void*)latinime_BinaryDictionary_editDistance}
{"openNative", "(Ljava/lang/String;JJIIIII)J",
reinterpret_cast<void *>(latinime_BinaryDictionary_open)},
{"closeNative", "(J)V", reinterpret_cast<void *>(latinime_BinaryDictionary_close)},
{"getSuggestionsNative", "(JJJ[I[I[I[I[IIIZ[IZ[C[I[I[I)I",
reinterpret_cast<void *>(latinime_BinaryDictionary_getSuggestions)},
{"getFrequencyNative", "(J[I)I",
reinterpret_cast<void *>(latinime_BinaryDictionary_getFrequency)},
{"isValidBigramNative", "(J[I[I)Z",
reinterpret_cast<void *>(latinime_BinaryDictionary_isValidBigram)},
{"calcNormalizedScoreNative", "([C[CI)F",
reinterpret_cast<void *>(latinime_BinaryDictionary_calcNormalizedScore)},
{"editDistanceNative", "([C[C)I",
reinterpret_cast<void *>(latinime_BinaryDictionary_editDistance)}
};
int register_BinaryDictionary(JNIEnv *env) {

View File

@ -0,0 +1,63 @@
/*
* 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.
*/
#define LOG_TAG "LatinIME: jni: Session"
#include "com_android_inputmethod_latin_DicTraverseSession.h"
#include "dic_traverse_wrapper.h"
#include "jni.h"
#include "jni_common.h"
namespace latinime {
class Dictionary;
static jlong latinime_setDicTraverseSession(JNIEnv *env, jobject object, jstring localeJStr) {
void *traverseSession = DicTraverseWrapper::getDicTraverseSession(env, localeJStr);
return reinterpret_cast<jlong>(traverseSession);
}
static void latinime_initDicTraverseSession(JNIEnv *env, jobject object, jlong traverseSession,
jlong dictionary, jintArray previousWord, jint previousWordLength) {
void *ts = reinterpret_cast<void *>(traverseSession);
Dictionary *dict = reinterpret_cast<Dictionary *>(dictionary);
if (!previousWord) {
DicTraverseWrapper::initDicTraverseSession(ts, dict, 0, 0);
return;
}
int prevWord[previousWordLength];
env->GetIntArrayRegion(previousWord, 0, previousWordLength, prevWord);
DicTraverseWrapper::initDicTraverseSession(ts, dict, prevWord, previousWordLength);
}
static void latinime_releaseDicTraverseSession(JNIEnv *env, jobject object, jlong traverseSession) {
void *ts = reinterpret_cast<void *>(traverseSession);
DicTraverseWrapper::releaseDicTraverseSession(ts);
}
static JNINativeMethod sMethods[] = {
{"setDicTraverseSessionNative", "(Ljava/lang/String;)J",
reinterpret_cast<void *>(latinime_setDicTraverseSession)},
{"initDicTraverseSessionNative", "(JJ[II)V",
reinterpret_cast<void *>(latinime_initDicTraverseSession)},
{"releaseDicTraverseSessionNative", "(J)V",
reinterpret_cast<void *>(latinime_releaseDicTraverseSession)}
};
int register_DicTraverseSession(JNIEnv *env) {
const char *const kClassPathName = "com/android/inputmethod/latin/DicTraverseSession";
return registerNativeMethods(env, kClassPathName, sMethods,
sizeof(sMethods) / sizeof(sMethods[0]));
}
} // namespace latinime

View File

@ -14,14 +14,13 @@
* limitations under the License.
*/
#ifndef _COM_ANDROID_INPUTMETHOD_LATIN_NATIVEUTILS_H
#define _COM_ANDROID_INPUTMETHOD_LATIN_NATIVEUTILS_H
#ifndef _COM_ANDROID_INPUTMETHOD_LATIN_DICTRAVERSESESSION_H
#define _COM_ANDROID_INPUTMETHOD_LATIN_DICTRAVERSESESSION_H
#include "defines.h"
#include "jni.h"
namespace latinime {
int register_NativeUtils(JNIEnv *env);
int register_DicTraverseSession(JNIEnv *env);
} // namespace latinime
#endif // _COM_ANDROID_INPUTMETHOD_LATIN_NATIVEUTILS_H
#endif // _COM_ANDROID_INPUTMETHOD_LATIN_DICTRAVERSESESSION_H

View File

@ -16,15 +16,15 @@
#define LOG_TAG "LatinIME: jni"
#include <cassert>
#include "com_android_inputmethod_keyboard_ProximityInfo.h"
#include "com_android_inputmethod_latin_BinaryDictionary.h"
#include "com_android_inputmethod_latin_NativeUtils.h"
#include "com_android_inputmethod_latin_DicTraverseSession.h"
#include "defines.h"
#include "jni.h"
#include "jni_common.h"
#include <cassert>
using namespace latinime;
/*
@ -34,7 +34,7 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = 0;
jint result = -1;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
AKLOGE("ERROR: GetEnv failed");
goto bail;
}
@ -45,13 +45,13 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
goto bail;
}
if (!register_ProximityInfo(env)) {
AKLOGE("ERROR: ProximityInfo native registration failed");
if (!register_DicTraverseSession(env)) {
AKLOGE("ERROR: DicTraverseSession native registration failed");
goto bail;
}
if (!register_NativeUtils(env)) {
AKLOGE("ERROR: NativeUtils native registration failed");
if (!register_ProximityInfo(env)) {
AKLOGE("ERROR: ProximityInfo native registration failed");
goto bail;
}

View File

@ -24,32 +24,5 @@ namespace latinime {
int registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods,
int numMethods);
inline jint *safeGetIntArrayElements(JNIEnv *env, jintArray jArray) {
if (jArray) {
return env->GetIntArrayElements(jArray, 0);
} else {
return 0;
}
}
inline jfloat *safeGetFloatArrayElements(JNIEnv *env, jfloatArray jArray) {
if (jArray) {
return env->GetFloatArrayElements(jArray, 0);
} else {
return 0;
}
}
inline void safeReleaseIntArrayElements(JNIEnv *env, jintArray jArray, jint *cArray) {
if (jArray) {
env->ReleaseIntArrayElements(jArray, cArray, 0);
}
}
inline void safeReleaseFloatArrayElements(JNIEnv *env, jfloatArray jArray, jfloat *cArray) {
if (jArray) {
env->ReleaseFloatArrayElements(jArray, cArray, 0);
}
}
} // namespace latinime
#endif // LATINIME_JNI_COMMON_H

View File

@ -17,7 +17,9 @@
#include "additional_proximity_chars.h"
namespace latinime {
const std::string AdditionalProximityChars::LOCALE_EN_US("en");
// TODO: Stop using hardcoded additional proximity characters.
// TODO: Have proximity character informations in each language's binary dictionary.
const char *AdditionalProximityChars::LOCALE_EN_US = "en";
const int32_t AdditionalProximityChars::EN_US_ADDITIONAL_A[EN_US_ADDITIONAL_A_SIZE] = {
'e', 'i', 'o', 'u'

View File

@ -17,8 +17,8 @@
#ifndef LATINIME_ADDITIONAL_PROXIMITY_CHARS_H
#define LATINIME_ADDITIONAL_PROXIMITY_CHARS_H
#include <cstring>
#include <stdint.h>
#include <string>
#include "defines.h"
@ -27,7 +27,7 @@ namespace latinime {
class AdditionalProximityChars {
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(AdditionalProximityChars);
static const std::string LOCALE_EN_US;
static const char *LOCALE_EN_US;
static const int EN_US_ADDITIONAL_A_SIZE = 4;
static const int32_t EN_US_ADDITIONAL_A[];
static const int EN_US_ADDITIONAL_E_SIZE = 4;
@ -39,14 +39,15 @@ class AdditionalProximityChars {
static const int EN_US_ADDITIONAL_U_SIZE = 4;
static const int32_t EN_US_ADDITIONAL_U[];
static bool isEnLocale(const std::string *locale_str) {
return locale_str && locale_str->size() >= LOCALE_EN_US.size()
&& LOCALE_EN_US.compare(0, LOCALE_EN_US.size(), *locale_str);
static bool isEnLocale(const char *localeStr) {
const size_t LOCALE_EN_US_SIZE = strlen(LOCALE_EN_US);
return localeStr && strlen(localeStr) >= LOCALE_EN_US_SIZE
&& strncmp(localeStr, LOCALE_EN_US, LOCALE_EN_US_SIZE) == 0;
}
public:
static int getAdditionalCharsSize(const std::string *locale_str, const int32_t c) {
if (!isEnLocale(locale_str)) {
static int getAdditionalCharsSize(const char *localeStr, const int32_t c) {
if (!isEnLocale(localeStr)) {
return 0;
}
switch(c) {
@ -65,8 +66,8 @@ class AdditionalProximityChars {
}
}
static const int32_t *getAdditionalChars(const std::string *locale_str, const int32_t c) {
if (!isEnLocale(locale_str)) {
static const int32_t *getAdditionalChars(const char *localeStr, const int32_t c) {
if (!isEnLocale(localeStr)) {
return 0;
}
switch(c) {
@ -84,10 +85,6 @@ class AdditionalProximityChars {
return 0;
}
}
static bool hasAdditionalChars(const std::string *locale_str, const int32_t c) {
return getAdditionalCharsSize(locale_str, c) > 0;
}
};
} // namespace latinime
#endif // LATINIME_ADDITIONAL_PROXIMITY_CHARS_H

View File

@ -60,15 +60,15 @@ bool BigramDictionary::addWordBigram(unsigned short *word, int length, int frequ
AKLOGI("Bigram: InsertAt -> %d MAX_PREDICTIONS: %d", insertAt, MAX_PREDICTIONS);
}
if (insertAt < MAX_PREDICTIONS) {
memmove((char*) bigramFreq + (insertAt + 1) * sizeof(bigramFreq[0]),
(char*) bigramFreq + insertAt * sizeof(bigramFreq[0]),
memmove(bigramFreq + (insertAt + 1),
bigramFreq + insertAt,
(MAX_PREDICTIONS - insertAt - 1) * sizeof(bigramFreq[0]));
bigramFreq[insertAt] = frequency;
outputTypes[insertAt] = Dictionary::KIND_PREDICTION;
memmove((char*) bigramChars + (insertAt + 1) * MAX_WORD_LENGTH * sizeof(short),
(char*) bigramChars + (insertAt ) * MAX_WORD_LENGTH * sizeof(short),
(MAX_PREDICTIONS - insertAt - 1) * sizeof(short) * MAX_WORD_LENGTH);
unsigned short *dest = bigramChars + (insertAt ) * MAX_WORD_LENGTH;
memmove(bigramChars + (insertAt + 1) * MAX_WORD_LENGTH,
bigramChars + insertAt * MAX_WORD_LENGTH,
(MAX_PREDICTIONS - insertAt - 1) * sizeof(bigramChars[0]) * MAX_WORD_LENGTH);
unsigned short *dest = bigramChars + insertAt * MAX_WORD_LENGTH;
while (length--) {
*dest++ = *word++;
}

View File

@ -29,8 +29,6 @@ class BigramDictionary {
BigramDictionary(const unsigned char *dict, int maxWordLength, int maxPredictions);
int getBigrams(const int32_t *word, int length, int *inputCodes, int codesSize,
unsigned short *outWords, int *frequencies, int *outputTypes) const;
int getBigramListPositionForWord(const int32_t *prevWord, const int prevWordLength,
const bool forceLowerCaseSearch) const;
void fillBigramAddressToFrequencyMapAndFilter(const int32_t *prevWord, const int prevWordLength,
std::map<int, int> *map, uint8_t *filter) const;
bool isValidBigram(const int32_t *word1, int length1, const int32_t *word2, int length2) const;
@ -45,6 +43,8 @@ class BigramDictionary {
bool getFirstBitOfByte(int *pos) { return (DICT[*pos] & 0x80) > 0; }
bool getSecondBitOfByte(int *pos) { return (DICT[*pos] & 0x40) > 0; }
bool checkFirstCharacter(unsigned short *word, int *inputCodes) const;
int getBigramListPositionForWord(const int32_t *prevWord, const int prevWordLength,
const bool forceLowerCaseSearch) const;
const unsigned char *DICT;
const int MAX_WORD_LENGTH;

View File

@ -52,6 +52,8 @@ class BinaryFormat {
// Mask for attribute frequency, stored on 4 bits inside the flags byte.
static const int MASK_ATTRIBUTE_FREQUENCY = 0x0F;
// The numeric value of the shortcut frequency that means 'whitelist'.
static const int WHITELIST_SHORTCUT_FREQUENCY = 15;
// Mask and flags for attribute address type selection.
static const int MASK_ATTRIBUTE_ADDRESS_TYPE = 0x30;
@ -59,13 +61,6 @@ class BinaryFormat {
static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES = 0x20;
static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES = 0x30;
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(BinaryFormat);
const static int32_t MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
const static int32_t CHARACTER_ARRAY_TERMINATOR = 0x1F;
const static int MULTIPLE_BYTE_CHARACTER_ADDITIONAL_SIZE = 2;
public:
const static int UNKNOWN_FORMAT = -1;
// Originally, format version 1 had a 16-bit magic number, then the version number `01'
// then options that must be 0. Hence the first 32-bits of the format are always as follow
@ -92,13 +87,13 @@ class BinaryFormat {
static int skipFrequency(const uint8_t flags, const int pos);
static int skipShortcuts(const uint8_t *const dict, const uint8_t flags, const int pos);
static int skipBigrams(const uint8_t *const dict, const uint8_t flags, const int pos);
static int skipAllAttributes(const uint8_t *const dict, const uint8_t flags, const int pos);
static int skipChildrenPosAndAttributes(const uint8_t *const dict, const uint8_t flags,
const int pos);
static int readChildrenPosition(const uint8_t *const dict, const uint8_t flags, const int pos);
static bool hasChildrenInFlags(const uint8_t flags);
static int getAttributeAddressAndForwardPointer(const uint8_t *const dict, const uint8_t flags,
int *pos);
static int getAttributeFrequencyFromFlags(const int flags);
static int getTerminalPosition(const uint8_t *const root, const int32_t *const inWord,
const int length, const bool forceLowerCaseSearch);
static int getWordAtAddress(const uint8_t *const root, const int address, const int maxDepth,
@ -115,6 +110,13 @@ class BinaryFormat {
REQUIRES_FRENCH_LIGATURES_PROCESSING = 0x4
};
const static unsigned int NO_FLAGS = 0;
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(BinaryFormat);
const static int32_t MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
const static int32_t CHARACTER_ARRAY_TERMINATOR = 0x1F;
const static int MULTIPLE_BYTE_CHARACTER_ADDITIONAL_SIZE = 2;
static int skipAllAttributes(const uint8_t *const dict, const uint8_t flags, const int pos);
};
inline int BinaryFormat::detectFormat(const uint8_t *const dict) {
@ -340,6 +342,10 @@ inline int BinaryFormat::getAttributeAddressAndForwardPointer(const uint8_t *con
}
}
inline int BinaryFormat::getAttributeFrequencyFromFlags(const int flags) {
return flags & MASK_ATTRIBUTE_FREQUENCY;
}
// This function gets the byte position of the last chargroup of the exact matching word in the
// dictionary. If no match is found, it returns NOT_VALID_WORD.
inline int BinaryFormat::getTerminalPosition(const uint8_t *const root,

Some files were not shown because too many files have changed in this diff Show More