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>(...); <init>(...);
} }
-keepclasseswithmembernames class * {
native <methods>;
}
-keep class com.android.inputmethod.research.ResearchLogger { -keep class com.android.inputmethod.research.ResearchLogger {
void flush(); void flush();
void publishCurrentLogUnit(...); 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. --> checkable+checked+pressed. -->
<attr name="keyBackground" format="reference" /> <attr name="keyBackground" format="reference" />
<!-- Size of the text for one letter keys. If not defined, keyLetterRatio takes effect. --> <!-- Size of the text for one letter keys. If specified as fraction, the text size is
<attr name="keyLetterSize" format="dimension" /> measured in the proportion of key height. -->
<!-- Size of the text for keys with multiple letters. If not defined, keyLabelRatio takes <attr name="keyLetterSize" format="dimension|fraction" />
effect. --> <!-- Size of the text for keys with multiple letters. If specified as fraction, the text
<attr name="keyLabelSize" format="dimension" /> size is measured in the proportion of key height. -->
<!-- Size of the text for one letter keys, in the proportion of key height. --> <attr name="keyLabelSize" format="dimension|fraction" />
<attr name="keyLetterRatio" format="float" />
<!-- Large size of the text for one letter keys, in the proportion of key height. --> <!-- Large size of the text for one letter keys, in the proportion of key height. -->
<attr name="keyLargeLetterRatio" format="float" /> <attr name="keyLargeLetterRatio" format="fraction" />
<!-- Size of the text for keys with multiple letters, in the proportion of key height. -->
<attr name="keyLabelRatio" format="float" />
<!-- Large size of the text for keys with multiple letters, in the proportion of key height. --> <!-- 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 <!-- Size of the text for hint letter (= one character hint label), in the proportion of
key height. --> 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. --> <!-- 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. --> <!-- 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. --> <!-- Horizontal padding of left/right aligned key label to the edge of the key. -->
<attr name="keyLabelHorizontalPadding" format="dimension" /> <attr name="keyLabelHorizontalPadding" format="dimension" />
<!-- Right padding of hint letter to the edge of the key.--> <!-- Right padding of hint letter to the edge of the key.-->
@ -96,8 +94,8 @@
<attr name="keyPreviewOffset" format="dimension" /> <attr name="keyPreviewOffset" format="dimension" />
<!-- Height of the key press feedback popup. --> <!-- Height of the key press feedback popup. -->
<attr name="keyPreviewHeight" format="dimension" /> <attr name="keyPreviewHeight" format="dimension" />
<!-- Size of the text for key press feedback popup, int the proportion of key height --> <!-- Size of the text for key press feedback popup, in the proportion of key height. -->
<attr name="keyPreviewTextRatio" format="float" /> <attr name="keyPreviewTextRatio" format="fraction" />
<!-- Delay after key releasing and key press feedback dismissing in millisecond --> <!-- Delay after key releasing and key press feedback dismissing in millisecond -->
<attr name="keyPreviewLingerTimeout" format="integer" /> <attr name="keyPreviewLingerTimeout" format="integer" />
@ -131,6 +129,12 @@
<attr name="gestureFloatingPreviewTextConnectorWidth" format="dimension" /> <attr name="gestureFloatingPreviewTextConnectorWidth" format="dimension" />
<!-- Delay after gesture input and gesture floating preview text dismissing in millisecond --> <!-- Delay after gesture input and gesture floating preview text dismissing in millisecond -->
<attr name="gestureFloatingPreviewTextLingerTimeout" format="integer" /> <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="gesturePreviewTrailColor" format="color" />
<attr name="gesturePreviewTrailWidth" format="dimension" /> <attr name="gesturePreviewTrailWidth" format="dimension" />
</declare-styleable> </declare-styleable>
@ -181,13 +185,13 @@
<attr name="colorTypedWord" format="color" /> <attr name="colorTypedWord" format="color" />
<attr name="colorAutoCorrect" format="color" /> <attr name="colorAutoCorrect" format="color" />
<attr name="colorSuggested" format="color" /> <attr name="colorSuggested" format="color" />
<attr name="alphaValidTypedWord" format="integer" /> <attr name="alphaValidTypedWord" format="fraction" />
<attr name="alphaTypedWord" format="integer" /> <attr name="alphaTypedWord" format="fraction" />
<attr name="alphaAutoCorrect" format="integer" /> <attr name="alphaAutoCorrect" format="fraction" />
<attr name="alphaSuggested" format="integer" /> <attr name="alphaSuggested" format="fraction" />
<attr name="alphaObsoleted" format="integer" /> <attr name="alphaObsoleted" format="fraction" />
<attr name="suggestionsCountInStrip" format="integer" /> <attr name="suggestionsCountInStrip" format="integer" />
<attr name="centerSuggestionPercentile" format="integer" /> <attr name="centerSuggestionPercentile" format="fraction" />
<attr name="maxMoreSuggestionsRow" format="integer" /> <attr name="maxMoreSuggestionsRow" format="integer" />
<attr name="minMoreSuggestionsWidth" format="float" /> <attr name="minMoreSuggestionsWidth" format="float" />
</declare-styleable> </declare-styleable>

View File

@ -50,6 +50,9 @@
--> -->
<integer name="config_key_preview_linger_timeout">70</integer> <integer name="config_key_preview_linger_timeout">70</integer>
<integer name="config_gesture_floating_preview_text_linger_timeout">200</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 Configuration for MainKeyboardView
--> -->

View File

@ -92,7 +92,7 @@
<dimen name="suggestion_text_size">18dp</dimen> <dimen name="suggestion_text_size">18dp</dimen>
<dimen name="more_suggestions_hint_text_size">27dp</dimen> <dimen name="more_suggestions_hint_text_size">27dp</dimen>
<integer name="suggestions_count_in_strip">3</integer> <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 --> <!-- Gesture preview parameters -->
<dimen name="gesture_preview_trail_width">2.5dp</dimen> <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_shadow_border">17.5dp</dimen>
<dimen name="gesture_floating_preview_text_shading_border">7.5dp</dimen> <dimen name="gesture_floating_preview_text_shading_border">7.5dp</dimen>
<dimen name="gesture_floating_preview_text_connector_width">1.0dp</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> </resources>

View File

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

View File

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

View File

@ -261,7 +261,8 @@
<string name="research_feedback_dialog_title" translatable="false">Send feedback</string> <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] --> <!-- 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 --> <!-- 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] --> <!-- 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 --> <!-- TODO: remove translatable=false attribute once text is stable -->
<string name="research_feedback_hint" translatable="false">Enter your feedback here.</string> <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 --> <!-- TODO: remove translatable=false attribute once text is stable -->
<string name="research_send_usage_info" translatable="false">Send usage info</string> <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 --> <!-- Preference for input language selection -->
<string name="select_language">Input languages</string> <string name="select_language">Input languages</string>

View File

@ -35,9 +35,9 @@
<style name="KeyboardView"> <style name="KeyboardView">
<item name="android:background">@drawable/keyboard_background</item> <item name="android:background">@drawable/keyboard_background</item>
<item name="keyBackground">@drawable/btn_keyboard_key</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="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="keyLargeLabelRatio">@fraction/key_large_label_ratio</item>
<item name="keyHintLetterRatio">@fraction/key_hint_letter_ratio</item> <item name="keyHintLetterRatio">@fraction/key_hint_letter_ratio</item>
<item name="keyHintLabelRatio">@fraction/key_hint_label_ratio</item> <item name="keyHintLabelRatio">@fraction/key_hint_label_ratio</item>
@ -78,6 +78,9 @@
<item name="gestureFloatingPreviewTextConnectorColor">@android:color/white</item> <item name="gestureFloatingPreviewTextConnectorColor">@android:color/white</item>
<item name="gestureFloatingPreviewTextConnectorWidth">@dimen/gesture_floating_preview_text_connector_width</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="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="gesturePreviewTrailColor">@android:color/holo_blue_light</item>
<item name="gesturePreviewTrailWidth">@dimen/gesture_preview_trail_width</item> <item name="gesturePreviewTrailWidth">@dimen/gesture_preview_trail_width</item>
<!-- Common attributes of MainKeyboardView --> <!-- Common attributes of MainKeyboardView -->
@ -135,9 +138,9 @@
<item name="colorTypedWord">@android:color/white</item> <item name="colorTypedWord">@android:color/white</item>
<item name="colorAutoCorrect">#FFFCAE00</item> <item name="colorAutoCorrect">#FFFCAE00</item>
<item name="colorSuggested">#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="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="maxMoreSuggestionsRow">@integer/max_more_suggestions_row</item>
<item name="minMoreSuggestionsWidth">@fraction/min_more_suggestions_width</item> <item name="minMoreSuggestionsWidth">@fraction/min_more_suggestions_width</item>
</style> </style>
@ -370,12 +373,12 @@
<item name="colorTypedWord">@android:color/holo_blue_light</item> <item name="colorTypedWord">@android:color/holo_blue_light</item>
<item name="colorAutoCorrect">@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="colorSuggested">@android:color/holo_blue_light</item>
<item name="alphaValidTypedWord">85</item> <item name="alphaValidTypedWord">85%</item>
<item name="alphaTypedWord">85</item> <item name="alphaTypedWord">85%</item>
<item name="alphaSuggested">70</item> <item name="alphaSuggested">70%</item>
<item name="alphaObsoleted">70</item> <item name="alphaObsoleted">70%</item>
<item name="suggestionsCountInStrip">@integer/suggestions_count_in_strip</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="maxMoreSuggestionsRow">@integer/max_more_suggestions_row</item>
<item name="minMoreSuggestionsWidth">@fraction/min_more_suggestions_width</item> <item name="minMoreSuggestionsWidth">@fraction/min_more_suggestions_width</item>
</style> </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.Key;
import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardView; import com.android.inputmethod.keyboard.KeyboardView;
import com.android.inputmethod.latin.CollectionUtils;
/** /**
* Exposes a virtual view sub-tree for {@link KeyboardView} and generates * Exposes a virtual view sub-tree for {@link KeyboardView} and generates
@ -55,7 +56,7 @@ public class AccessibilityEntityProvider extends AccessibilityNodeProviderCompat
private final AccessibilityUtils mAccessibilityUtils; private final AccessibilityUtils mAccessibilityUtils;
/** A map of integer IDs to {@link Key}s. */ /** 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. */ /** Temporary rect used to calculate in-screen bounds. */
private final Rect mTempBoundsInScreen = new Rect(); private final Rect mTempBoundsInScreen = new Rect();

View File

@ -19,10 +19,15 @@ package com.android.inputmethod.accessibility;
import android.content.Context; import android.content.Context;
import android.inputmethodservice.InputMethodService; import android.inputmethodservice.InputMethodService;
import android.media.AudioManager; import android.media.AudioManager;
import android.os.Build;
import android.os.SystemClock; import android.os.SystemClock;
import android.provider.Settings; import android.provider.Settings;
import android.support.v4.view.accessibility.AccessibilityEventCompat;
import android.util.Log; import android.util.Log;
import android.view.MotionEvent; 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.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
@ -138,9 +143,10 @@ public class AccessibilityUtils {
* Sends the specified text to the {@link AccessibilityManager} to be * Sends the specified text to the {@link AccessibilityManager} to be
* spoken. * 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()) { if (!mAccessibilityManager.isEnabled()) {
Log.e(TAG, "Attempted to speak when accessibility was disabled!"); Log.e(TAG, "Attempted to speak when accessibility was disabled!");
return; return;
@ -149,8 +155,7 @@ public class AccessibilityUtils {
// The following is a hack to avoid using the heavy-weight TextToSpeech // The following is a hack to avoid using the heavy-weight TextToSpeech
// class. Instead, we're just forcing a fake AccessibilityEvent into // class. Instead, we're just forcing a fake AccessibilityEvent into
// the screen reader to make it speak. // the screen reader to make it speak.
final AccessibilityEvent event = AccessibilityEvent final AccessibilityEvent event = AccessibilityEvent.obtain();
.obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED);
event.setPackageName(PACKAGE); event.setPackageName(PACKAGE);
event.setClassName(CLASS); event.setClassName(CLASS);
@ -158,20 +163,34 @@ public class AccessibilityUtils {
event.setEnabled(true); event.setEnabled(true);
event.getText().add(text); 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 * Handles speaking the "connect a headset to hear passwords" notification
* when connecting to a password field. * when connecting to a password field.
* *
* @param view The source view.
* @param editorInfo The input connection's editor info attribute. * @param editorInfo The input connection's editor info attribute.
* @param restarting Whether the connection is being restarted. * @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)) { if (shouldObscureInput(editorInfo)) {
final CharSequence text = mContext.getText(R.string.spoken_use_headphones); 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.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewConfiguration;
import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard; 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 * 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; private int mEdgeSlop;
@ -62,7 +61,8 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
private void initInternal(InputMethodService inputMethod) { private void initInternal(InputMethodService inputMethod) {
mInputMethod = 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) { public boolean dispatchHoverEvent(MotionEvent event, PointerTracker tracker) {
final int x = (int) event.getX(); final int x = (int) event.getX();
final int y = (int) event.getY(); final int y = (int) event.getY();
final Key key = tracker.getKeyOn(x, y);
final Key previousKey = mLastHoverKey; final Key previousKey = mLastHoverKey;
final Key key;
if (pointInView(x, y)) {
key = tracker.getKeyOn(x, y);
} else {
key = null;
}
mLastHoverKey = key; mLastHoverKey = key;
@ -123,7 +129,7 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
case MotionEvent.ACTION_HOVER_EXIT: case MotionEvent.ACTION_HOVER_EXIT:
// Make sure we're not getting an EXIT event because the user slid // Make sure we're not getting an EXIT event because the user slid
// off the keyboard area, then force a key press. // off the keyboard area, then force a key press.
if (pointInView(x, y) && (key != null)) { if (key != null) {
getAccessibilityNodeProvider().simulateKeyPress(key); getAccessibilityNodeProvider().simulateKeyPress(key);
} }
//$FALL-THROUGH$ //$FALL-THROUGH$
@ -250,7 +256,7 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
text = context.getText(R.string.spoken_description_shiftmode_off); 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); 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.Key;
import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardId; import com.android.inputmethod.keyboard.KeyboardId;
import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.R;
import java.util.HashMap; import java.util.HashMap;
@ -38,7 +39,7 @@ public class KeyCodeDescriptionMapper {
private static KeyCodeDescriptionMapper sInstance = new KeyCodeDescriptionMapper(); private static KeyCodeDescriptionMapper sInstance = new KeyCodeDescriptionMapper();
// Map of key labels to spoken description resource IDs // 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 // Sparse array of spoken description resource IDs indexed by key codes
private final SparseIntArray mKeyCodeMap; private final SparseIntArray mKeyCodeMap;
@ -52,7 +53,6 @@ public class KeyCodeDescriptionMapper {
} }
private KeyCodeDescriptionMapper() { private KeyCodeDescriptionMapper() {
mKeyLabelMap = new HashMap<CharSequence, Integer>();
mKeyCodeMap = new SparseIntArray(); mKeyCodeMap = new SparseIntArray();
} }

View File

@ -16,10 +16,6 @@
package com.android.inputmethod.compat; 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.content.Context;
import android.text.Spannable; import android.text.Spannable;
import android.text.SpannableString; import android.text.SpannableString;
@ -27,6 +23,11 @@ import android.text.Spanned;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; 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.Constructor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
@ -119,7 +120,7 @@ public class SuggestionSpanUtils {
} else { } else {
spannable = new SpannableString(pickedWord); 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) { for (int i = 0; i < suggestedWords.size(); ++i) {
if (suggestionsList.size() >= OBJ_SUGGESTIONS_MAX_SIZE) { if (suggestionsList.size() >= OBJ_SUGGESTIONS_MAX_SIZE) {
break; break;

View File

@ -16,10 +16,10 @@
package com.android.inputmethod.keyboard; package com.android.inputmethod.keyboard;
import com.android.inputmethod.latin.Constants;
public class KeyDetector { public class KeyDetector {
public static final int NOT_A_CODE = -1;
private final int mKeyHysteresisDistanceSquared; private final int mKeyHysteresisDistanceSquared;
private Keyboard mKeyboard; private Keyboard mKeyboard;
@ -59,6 +59,9 @@ public class KeyDetector {
} }
public Keyboard getKeyboard() { public Keyboard getKeyboard() {
if (mKeyboard == null) {
throw new IllegalStateException("keyboard isn't set");
}
return mKeyboard; return mKeyboard;
} }
@ -100,7 +103,7 @@ public class KeyDetector {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
boolean addDelimiter = false; boolean addDelimiter = false;
for (final int code : codes) { for (final int code : codes) {
if (code == NOT_A_CODE) break; if (code == Constants.NOT_A_CODE) break;
if (addDelimiter) sb.append(", "); if (addDelimiter) sb.append(", ");
sb.append(Keyboard.printableCode(code)); sb.append(Keyboard.printableCode(code));
addDelimiter = true; 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.KeyboardCodesSet;
import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
import com.android.inputmethod.keyboard.internal.KeyboardTextsSet; import com.android.inputmethod.keyboard.internal.KeyboardTextsSet;
import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.LocaleUtils.RunInLocale; import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.R;
@ -134,7 +135,7 @@ public class Keyboard {
public final Key[] mAltCodeKeysWhileTyping; public final Key[] mAltCodeKeysWhileTyping;
public final KeyboardIconsSet mIconsSet; 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 ProximityInfo mProximityInfo;
private final boolean mProximityCharsCorrectionEnabled; private final boolean mProximityCharsCorrectionEnabled;
@ -219,6 +220,11 @@ public class Keyboard {
return code >= CODE_SPACE; return code >= CODE_SPACE;
} }
@Override
public String toString() {
return mId.toString();
}
public static class Params { public static class Params {
public KeyboardId mId; public KeyboardId mId;
public int mThemeId; public int mThemeId;
@ -249,9 +255,9 @@ public class Keyboard {
public int GRID_WIDTH; public int GRID_WIDTH;
public int GRID_HEIGHT; public int GRID_HEIGHT;
public final HashSet<Key> mKeys = new HashSet<Key>(); public final HashSet<Key> mKeys = CollectionUtils.newHashSet();
public final ArrayList<Key> mShiftKeys = new ArrayList<Key>(); public final ArrayList<Key> mShiftKeys = CollectionUtils.newArrayList();
public final ArrayList<Key> mAltCodeKeysWhileTyping = new ArrayList<Key>(); public final ArrayList<Key> mAltCodeKeysWhileTyping = CollectionUtils.newArrayList();
public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet(); public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet();
public final KeyboardCodesSet mCodesSet = new KeyboardCodesSet(); public final KeyboardCodesSet mCodesSet = new KeyboardCodesSet();
public final KeyboardTextsSet mTextsSet = new KeyboardTextsSet(); public final KeyboardTextsSet mTextsSet = new KeyboardTextsSet();
@ -278,9 +284,10 @@ public class Keyboard {
public void load(String[] data) { public void load(String[] data) {
final int dataLength = data.length; final int dataLength = data.length;
if (dataLength % TOUCH_POSITION_CORRECTION_RECORD_SIZE != 0) { if (dataLength % TOUCH_POSITION_CORRECTION_RECORD_SIZE != 0) {
if (LatinImeLogger.sDBG) if (LatinImeLogger.sDBG) {
throw new RuntimeException( throw new RuntimeException(
"the size of touch position correction data is invalid"); "the size of touch position correction data is invalid");
}
return; return;
} }
@ -319,7 +326,7 @@ public class Keyboard {
public boolean isValid() { public boolean isValid() {
return mEnabled && mXs != null && mYs != null && mRadii != null return mEnabled && mXs != null && mYs != null && mRadii != null
&& mXs.length > 0 && mYs.length > 0 && mRadii.length > 0; && mXs.length > 0 && mYs.length > 0 && mRadii.length > 0;
} }
} }
@ -865,10 +872,12 @@ public class Keyboard {
final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Keyboard); R.styleable.Keyboard);
try { try {
if (a.hasValue(R.styleable.Keyboard_horizontalGap)) if (a.hasValue(R.styleable.Keyboard_horizontalGap)) {
throw new XmlParseUtils.IllegalAttribute(parser, "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"); throw new XmlParseUtils.IllegalAttribute(parser, "verticalGap");
}
return new Row(mResources, mParams, parser, mCurrentY); return new Row(mResources, mParams, parser, mCurrentY);
} finally { } finally {
a.recycle(); a.recycle();
@ -916,7 +925,9 @@ public class Keyboard {
throws XmlPullParserException, IOException { throws XmlPullParserException, IOException {
if (skip) { if (skip) {
XmlParseUtils.checkEndTag(TAG_KEY, parser); XmlParseUtils.checkEndTag(TAG_KEY, parser);
if (DEBUG) startEndTag("<%s /> skipped", TAG_KEY); if (DEBUG) {
startEndTag("<%s /> skipped", TAG_KEY);
}
} else { } else {
final Key key = new Key(mResources, mParams, row, parser); final Key key = new Key(mResources, mParams, row, parser);
if (DEBUG) { if (DEBUG) {
@ -1094,9 +1105,9 @@ public class Keyboard {
private boolean parseCaseCondition(XmlPullParser parser) { private boolean parseCaseCondition(XmlPullParser parser) {
final KeyboardId id = mParams.mId; final KeyboardId id = mParams.mId;
if (id == null) if (id == null) {
return true; return true;
}
final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser), final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Keyboard_Case); R.styleable.Keyboard_Case);
try { try {
@ -1200,9 +1211,9 @@ public class Keyboard {
// If <case> does not have "index" attribute, that means this <case> is wild-card for // If <case> does not have "index" attribute, that means this <case> is wild-card for
// the attribute. // the attribute.
final TypedValue v = a.peekValue(index); final TypedValue v = a.peekValue(index);
if (v == null) if (v == null) {
return true; return true;
}
if (isIntegerValue(v)) { if (isIntegerValue(v)) {
return intValue == a.getInt(index, 0); return intValue == a.getInt(index, 0);
} else if (isStringValue(v)) { } else if (isStringValue(v)) {
@ -1213,8 +1224,9 @@ public class Keyboard {
private static boolean stringArrayContains(String[] array, String value) { private static boolean stringArrayContains(String[] array, String value) {
for (final String elem : array) { for (final String elem : array) {
if (elem.equals(value)) if (elem.equals(value)) {
return true; return true;
}
} }
return false; return false;
} }
@ -1237,16 +1249,18 @@ public class Keyboard {
TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser), TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Keyboard_Key); R.styleable.Keyboard_Key);
try { try {
if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName)) if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName)) {
throw new XmlParseUtils.ParseException("<" + TAG_KEY_STYLE throw new XmlParseUtils.ParseException("<" + TAG_KEY_STYLE
+ "/> needs styleName attribute", parser); + "/> needs styleName attribute", parser);
}
if (DEBUG) { if (DEBUG) {
startEndTag("<%s styleName=%s />%s", TAG_KEY_STYLE, startEndTag("<%s styleName=%s />%s", TAG_KEY_STYLE,
keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName), keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName),
skip ? " skipped" : ""); skip ? " skipped" : "");
} }
if (!skip) if (!skip) {
mParams.mKeyStyles.parseKeyStyleAttributes(keyStyleAttr, keyAttrs, parser); mParams.mKeyStyles.parseKeyStyleAttributes(keyStyleAttr, keyAttrs, parser);
}
} finally { } finally {
keyStyleAttr.recycle(); keyStyleAttr.recycle();
keyAttrs.recycle(); keyAttrs.recycle();
@ -1267,8 +1281,9 @@ public class Keyboard {
} }
private void endRow(Row row) { private void endRow(Row row) {
if (mCurrentRow == null) if (mCurrentRow == null) {
throw new InflateException("orphan end row tag"); throw new InflateException("orphan end row tag");
}
if (mRightEdgeKey != null) { if (mRightEdgeKey != null) {
mRightEdgeKey.markAsRightEdge(mParams); mRightEdgeKey.markAsRightEdge(mParams);
mRightEdgeKey = null; mRightEdgeKey = null;
@ -1304,8 +1319,9 @@ public class Keyboard {
public static float getDimensionOrFraction(TypedArray a, int index, int base, public static float getDimensionOrFraction(TypedArray a, int index, int base,
float defValue) { float defValue) {
final TypedValue value = a.peekValue(index); final TypedValue value = a.peekValue(index);
if (value == null) if (value == null) {
return defValue; return defValue;
}
if (isFractionValue(value)) { if (isFractionValue(value)) {
return a.getFraction(index, base, base, defValue); return a.getFraction(index, base, base, defValue);
} else if (isDimensionValue(value)) { } else if (isDimensionValue(value)) {
@ -1316,8 +1332,9 @@ public class Keyboard {
public static int getEnumValue(TypedArray a, int index, int defValue) { public static int getEnumValue(TypedArray a, int index, int defValue) {
final TypedValue value = a.peekValue(index); final TypedValue value = a.peekValue(index);
if (value == null) if (value == null) {
return defValue; return defValue;
}
if (isIntegerValue(value)) { if (isIntegerValue(value)) {
return a.getInt(index, defValue); return a.getInt(index, defValue);
} }

View File

@ -16,6 +16,7 @@
package com.android.inputmethod.keyboard; package com.android.inputmethod.keyboard;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.InputPointers; import com.android.inputmethod.latin.InputPointers;
public interface KeyboardActionListener { public interface KeyboardActionListener {
@ -44,21 +45,16 @@ public interface KeyboardActionListener {
* *
* @param primaryCode this is the code of the key that was pressed * @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 * @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}. * {@link PointerTracker} or so, the value should be
* If it's called on insertion from the suggestion strip, it should be * {@link Constants#NOT_A_COORDINATE}. If it's called on insertion from the
* {@link #SUGGESTION_STRIP_COORDINATE}. * 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 * @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}. * {@link PointerTracker} or so, the value should be
* If it's called on insertion from the suggestion strip, it should be * {@link Constants#NOT_A_COORDINATE}.If it's called on insertion from the
* {@link #SUGGESTION_STRIP_COORDINATE}. * suggestion strip, it should be {@link Constants#SUGGESTION_STRIP_COORDINATE}.
*/ */
public void onCodeInput(int primaryCode, int x, int y); 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. * 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. // TODO: Remove this method when the vertical correction is removed.
public static boolean isInvalidCoordinate(int coordinate) { public static boolean isInvalidCoordinate(int coordinate) {
// Detect {@link KeyboardActionListener#NOT_A_TOUCH_COORDINATE}, // Detect {@link Constants#NOT_A_COORDINATE},
// {@link KeyboardActionListener#SUGGESTION_STRIP_COORDINATE}, and // {@link Constants#SUGGESTION_STRIP_COORDINATE}, and
// {@link KeyboardActionListener#SPELL_CHECKER_COORDINATE}. // {@link Constants#SPELL_CHECKER_COORDINATE}.
return coordinate < 0; return coordinate < 0;
} }
} }

View File

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

View File

@ -21,7 +21,6 @@ import android.content.SharedPreferences;
import android.content.res.Resources; import android.content.res.Resources;
import android.util.Log; import android.util.Log;
import android.view.ContextThemeWrapper; import android.view.ContextThemeWrapper;
import android.view.InflateException;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.inputmethod.EditorInfo; 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.R;
import com.android.inputmethod.latin.SettingsValues; import com.android.inputmethod.latin.SettingsValues;
import com.android.inputmethod.latin.SubtypeSwitcher; import com.android.inputmethod.latin.SubtypeSwitcher;
import com.android.inputmethod.latin.Utils; import com.android.inputmethod.latin.WordComposer;
public class KeyboardSwitcher implements KeyboardState.SwitchActions { public class KeyboardSwitcher implements KeyboardState.SwitchActions {
private static final String TAG = KeyboardSwitcher.class.getSimpleName(); 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"; public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20110916";
static class KeyboardTheme { static class KeyboardTheme {
public final String mName;
public final int mThemeId; public final int mThemeId;
public final int mStyleId; public final int mStyleId;
public KeyboardTheme(String name, int themeId, int styleId) { // Note: The themeId should be aligned with "themeId" attribute of Keyboard style
mName = name; // in values/style.xml.
public KeyboardTheme(int themeId, int styleId) {
mThemeId = themeId; mThemeId = themeId;
mStyleId = styleId; mStyleId = styleId;
} }
} }
private static final KeyboardTheme[] KEYBOARD_THEMES = { private static final KeyboardTheme[] KEYBOARD_THEMES = {
new KeyboardTheme("Basic", 0, R.style.KeyboardTheme), new KeyboardTheme(0, R.style.KeyboardTheme),
new KeyboardTheme("HighContrast", 1, R.style.KeyboardTheme_HighContrast), new KeyboardTheme(1, R.style.KeyboardTheme_HighContrast),
new KeyboardTheme("Stone", 6, R.style.KeyboardTheme_Stone), new KeyboardTheme(6, R.style.KeyboardTheme_Stone),
new KeyboardTheme("Stone.Bold", 7, R.style.KeyboardTheme_Stone_Bold), new KeyboardTheme(7, R.style.KeyboardTheme_Stone_Bold),
new KeyboardTheme("GingerBread", 8, R.style.KeyboardTheme_Gingerbread), new KeyboardTheme(8, R.style.KeyboardTheme_Gingerbread),
new KeyboardTheme("IceCreamSandwich", 5, R.style.KeyboardTheme_IceCreamSandwich), new KeyboardTheme(5, R.style.KeyboardTheme_IceCreamSandwich),
}; };
private SubtypeSwitcher mSubtypeSwitcher; private SubtypeSwitcher mSubtypeSwitcher;
@ -354,22 +353,9 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions {
mKeyboardView.closing(); mKeyboardView.closing();
} }
Utils.GCUtils.getInstance().reset(); setContextThemeWrapper(mLatinIME, mKeyboardTheme);
boolean tryGC = true; mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) { R.layout.input_view, null);
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); mKeyboardView = (MainKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view);
if (isHardwareAcceleratedDrawingEnabled) { 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.graphics.drawable.Drawable;
import android.os.Message; import android.os.Message;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray; import android.util.SparseArray;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -38,6 +39,7 @@ import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import com.android.inputmethod.keyboard.internal.PreviewPlacerView; import com.android.inputmethod.keyboard.internal.PreviewPlacerView;
import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.R;
@ -78,8 +80,12 @@ import java.util.HashSet;
* @attr ref R.styleable#KeyboardView_shadowRadius * @attr ref R.styleable#KeyboardView_shadowRadius
*/ */
public class KeyboardView extends View implements PointerTracker.DrawingProxy { public class KeyboardView extends View implements PointerTracker.DrawingProxy {
private static final String TAG = KeyboardView.class.getSimpleName();
// Miscellaneous constants // Miscellaneous constants
private static final int[] LONG_PRESSABLE_STATE_SET = { android.R.attr.state_long_pressable }; 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 // XML attributes
protected final float mVerticalCorrection; protected final float mVerticalCorrection;
@ -103,23 +109,19 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
// Key preview // Key preview
private final int mKeyPreviewLayoutId; private final int mKeyPreviewLayoutId;
private final SparseArray<TextView> mKeyPreviewTexts = CollectionUtils.newSparseArray();
protected final KeyPreviewDrawParams mKeyPreviewDrawParams; protected final KeyPreviewDrawParams mKeyPreviewDrawParams;
private boolean mShowKeyPreviewPopup = true; private boolean mShowKeyPreviewPopup = true;
private int mDelayAfterPreview; private int mDelayAfterPreview;
private final PreviewPlacerView mPreviewPlacerView; private final PreviewPlacerView mPreviewPlacerView;
/** True if {@link KeyboardView} should handle gesture events. */
protected boolean mShouldHandleGesture;
// Drawing // Drawing
/** True if the entire keyboard needs to be dimmed. */ /** True if the entire keyboard needs to be dimmed. */
private boolean mNeedsToDimEntireKeyboard; 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 */ /** True if all keys should be drawn */
private boolean mInvalidateAllKeys; private boolean mInvalidateAllKeys;
/** The keys that should be drawn */ /** 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 */ /** The working rectangle variable */
private final Rect mWorkingRect = new Rect(); private final Rect mWorkingRect = new Rect();
/** The keyboard bitmap buffer for faster updates */ /** 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 mPaint = new Paint();
private final Paint.FontMetrics mFontMetrics = new Paint.FontMetrics(); private final Paint.FontMetrics mFontMetrics = new Paint.FontMetrics();
// This sparse array caches key label text height in pixel indexed by key label text size. // 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. // 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_LABEL_REFERENCE_CHAR = { 'M' };
private static final char[] KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR = { '8' }; 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; final PointerTracker tracker = (PointerTracker) msg.obj;
switch (msg.what) { switch (msg.what) {
case MSG_DISMISS_KEY_PREVIEW: 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; break;
} }
} }
@ -166,7 +171,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
removeMessages(MSG_DISMISS_KEY_PREVIEW, tracker); removeMessages(MSG_DISMISS_KEY_PREVIEW, tracker);
} }
public void cancelAllDismissKeyPreviews() { private void cancelAllDismissKeyPreviews() {
removeMessages(MSG_DISMISS_KEY_PREVIEW); removeMessages(MSG_DISMISS_KEY_PREVIEW);
} }
@ -199,7 +204,6 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
private final float mKeyHintLetterRatio; private final float mKeyHintLetterRatio;
private final float mKeyShiftedLetterHintRatio; private final float mKeyShiftedLetterHintRatio;
private final float mKeyHintLabelRatio; private final float mKeyHintLabelRatio;
private static final float UNDEFINED_RATIO = -1.0f;
public final Rect mPadding = new Rect(); public final Rect mPadding = new Rect();
public int mKeyLetterSize; public int mKeyLetterSize;
@ -211,26 +215,22 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
public int mKeyHintLabelSize; public int mKeyHintLabelSize;
public int mAnimAlpha; public int mAnimAlpha;
public KeyDrawParams(TypedArray a) { public KeyDrawParams(final TypedArray a) {
mKeyBackground = a.getDrawable(R.styleable.KeyboardView_keyBackground); mKeyBackground = a.getDrawable(R.styleable.KeyboardView_keyBackground);
if (a.hasValue(R.styleable.KeyboardView_keyLetterSize)) { if (!isValidFraction(mKeyLetterRatio = getFraction(a,
mKeyLetterRatio = UNDEFINED_RATIO; R.styleable.KeyboardView_keyLetterSize))) {
mKeyLetterSize = a.getDimensionPixelSize(R.styleable.KeyboardView_keyLetterSize, 0); mKeyLetterSize = getDimensionPixelSize(a, R.styleable.KeyboardView_keyLetterSize);
} else {
mKeyLetterRatio = getRatio(a, R.styleable.KeyboardView_keyLetterRatio);
} }
if (a.hasValue(R.styleable.KeyboardView_keyLabelSize)) { if (!isValidFraction(mKeyLabelRatio = getFraction(a,
mKeyLabelRatio = UNDEFINED_RATIO; R.styleable.KeyboardView_keyLabelSize))) {
mKeyLabelSize = a.getDimensionPixelSize(R.styleable.KeyboardView_keyLabelSize, 0); mKeyLabelSize = getDimensionPixelSize(a, R.styleable.KeyboardView_keyLabelSize);
} else {
mKeyLabelRatio = getRatio(a, R.styleable.KeyboardView_keyLabelRatio);
} }
mKeyLargeLabelRatio = getRatio(a, R.styleable.KeyboardView_keyLargeLabelRatio); mKeyLargeLabelRatio = getFraction(a, R.styleable.KeyboardView_keyLargeLabelRatio);
mKeyLargeLetterRatio = getRatio(a, R.styleable.KeyboardView_keyLargeLetterRatio); mKeyLargeLetterRatio = getFraction(a, R.styleable.KeyboardView_keyLargeLetterRatio);
mKeyHintLetterRatio = getRatio(a, R.styleable.KeyboardView_keyHintLetterRatio); mKeyHintLetterRatio = getFraction(a, R.styleable.KeyboardView_keyHintLetterRatio);
mKeyShiftedLetterHintRatio = getRatio(a, mKeyShiftedLetterHintRatio = getFraction(a,
R.styleable.KeyboardView_keyShiftedLetterHintRatio); R.styleable.KeyboardView_keyShiftedLetterHintRatio);
mKeyHintLabelRatio = getRatio(a, R.styleable.KeyboardView_keyHintLabelRatio); mKeyHintLabelRatio = getFraction(a, R.styleable.KeyboardView_keyHintLabelRatio);
mKeyLabelHorizontalPadding = a.getDimension( mKeyLabelHorizontalPadding = a.getDimension(
R.styleable.KeyboardView_keyLabelHorizontalPadding, 0); R.styleable.KeyboardView_keyLabelHorizontalPadding, 0);
mKeyHintLetterPadding = a.getDimension( mKeyHintLetterPadding = a.getDimension(
@ -257,10 +257,10 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
} }
public void updateKeyHeight(int keyHeight) { public void updateKeyHeight(int keyHeight) {
if (mKeyLetterRatio >= 0.0f) { if (isValidFraction(mKeyLetterRatio)) {
mKeyLetterSize = (int)(keyHeight * mKeyLetterRatio); mKeyLetterSize = (int)(keyHeight * mKeyLetterRatio);
} }
if (mKeyLabelRatio >= 0.0f) { if (isValidFraction(mKeyLabelRatio)) {
mKeyLabelSize = (int)(keyHeight * mKeyLabelRatio); mKeyLabelSize = (int)(keyHeight * mKeyLabelRatio);
} }
mKeyLargeLabelSize = (int)(keyHeight * mKeyLargeLabelRatio); mKeyLargeLabelSize = (int)(keyHeight * mKeyLargeLabelRatio);
@ -335,7 +335,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
R.styleable.KeyboardView_keyPreviewOffset, 0); R.styleable.KeyboardView_keyPreviewOffset, 0);
mPreviewHeight = a.getDimensionPixelSize( mPreviewHeight = a.getDimensionPixelSize(
R.styleable.KeyboardView_keyPreviewHeight, 80); 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); mPreviewTextColor = a.getColor(R.styleable.KeyboardView_keyPreviewTextColor, 0);
mLingerTimeout = a.getInt(R.styleable.KeyboardView_keyPreviewLingerTimeout, 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( final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView); attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
mKeyDrawParams = new KeyDrawParams(a); mKeyDrawParams = new KeyDrawParams(a);
mKeyPreviewDrawParams = new KeyPreviewDrawParams(a, mKeyDrawParams); mKeyPreviewDrawParams = new KeyPreviewDrawParams(a, mKeyDrawParams);
mDelayAfterPreview = mKeyPreviewDrawParams.mLingerTimeout;
mKeyPreviewLayoutId = a.getResourceId(R.styleable.KeyboardView_keyPreviewLayout, 0); mKeyPreviewLayoutId = a.getResourceId(R.styleable.KeyboardView_keyPreviewLayout, 0);
if (mKeyPreviewLayoutId == 0) { if (mKeyPreviewLayoutId == 0) {
mShowKeyPreviewPopup = false; mShowKeyPreviewPopup = false;
@ -378,17 +378,30 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
R.styleable.KeyboardView_verticalCorrection, 0); R.styleable.KeyboardView_verticalCorrection, 0);
mMoreKeysLayout = a.getResourceId(R.styleable.KeyboardView_moreKeysLayout, 0); mMoreKeysLayout = a.getResourceId(R.styleable.KeyboardView_moreKeysLayout, 0);
mBackgroundDimAlpha = a.getInt(R.styleable.KeyboardView_backgroundDimAlpha, 0); mBackgroundDimAlpha = a.getInt(R.styleable.KeyboardView_backgroundDimAlpha, 0);
mPreviewPlacerView = new PreviewPlacerView(context, a);
a.recycle(); a.recycle();
mDelayAfterPreview = mKeyPreviewDrawParams.mLingerTimeout; mPreviewPlacerView = new PreviewPlacerView(context, attrs);
mPaint.setAntiAlias(true); mPaint.setAntiAlias(true);
} }
// Read fraction value in TypedArray as float. static boolean isValidFraction(final float fraction) {
/* package */ static float getRatio(TypedArray a, int index) { return fraction >= 0.0f;
return a.getFraction(index, 1000, 1000, 1) / 1000.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; return mShowKeyPreviewPopup;
} }
public void setGestureHandlingMode(boolean shouldHandleGesture, public void setGesturePreviewMode(boolean drawsGesturePreviewTrail,
boolean drawsGesturePreviewTrail, boolean drawsGestureFloatingPreviewText) { boolean drawsGestureFloatingPreviewText) {
mShouldHandleGesture = shouldHandleGesture;
mPreviewPlacerView.setGesturePreviewMode( mPreviewPlacerView.setGesturePreviewMode(
drawsGesturePreviewTrail, drawsGestureFloatingPreviewText); drawsGesturePreviewTrail, drawsGestureFloatingPreviewText);
} }
@ -463,16 +475,12 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
onDrawKeyboard(canvas); onDrawKeyboard(canvas);
return; return;
} }
if (mBufferNeedsUpdate || mOffscreenBuffer == null) {
mBufferNeedsUpdate = false; final boolean bufferNeedsUpdates = mInvalidateAllKeys || !mInvalidatedKeys.isEmpty();
if (bufferNeedsUpdates || mOffscreenBuffer == null) {
if (maybeAllocateOffscreenBuffer()) { if (maybeAllocateOffscreenBuffer()) {
mInvalidateAllKeys = true; mInvalidateAllKeys = true;
// TODO: Stop using the offscreen canvas even when in software rendering maybeCreateOffscreenCanvas();
if (mOffscreenCanvas != null) {
mOffscreenCanvas.setBitmap(mOffscreenBuffer);
} else {
mOffscreenCanvas = new Canvas(mOffscreenBuffer);
}
} }
onDrawKeyboard(mOffscreenCanvas); 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) { private void onDrawKeyboard(final Canvas canvas) {
if (mKeyboard == null) return; if (mKeyboard == null) return;
@ -528,13 +545,12 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
} }
if (!isHardwareAccelerated) { if (!isHardwareAccelerated) {
canvas.clipRegion(mClipRegion, Region.Op.REPLACE); canvas.clipRegion(mClipRegion, Region.Op.REPLACE);
} // Draw keyboard background.
canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
// Draw keyboard background. final Drawable background = getBackground();
canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR); if (background != null) {
final Drawable background = getBackground(); background.draw(canvas);
if (background != null) { }
background.draw(canvas);
} }
// TODO: Confirm if it's really required to draw all keys when hardware acceleration is on. // TODO: Confirm if it's really required to draw all keys when hardware acceleration is on.
@ -907,15 +923,30 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
} }
} }
// Called by {@link PointerTracker} constructor to create a TextView. private TextView getKeyPreviewText(final int pointerId) {
@Override TextView previewText = mKeyPreviewTexts.get(pointerId);
public TextView inflateKeyPreviewText() { if (previewText != null) {
return previewText;
}
final Context context = getContext(); final Context context = getContext();
if (mKeyPreviewLayoutId != 0) { if (mKeyPreviewLayoutId != 0) {
return (TextView)LayoutInflater.from(context).inflate(mKeyPreviewLayoutId, null); previewText = (TextView)LayoutInflater.from(context).inflate(mKeyPreviewLayoutId, null);
} else { } 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 @Override
@ -936,9 +967,18 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
final int[] viewOrigin = new int[2]; final int[] viewOrigin = new int[2];
getLocationInWindow(viewOrigin); getLocationInWindow(viewOrigin);
mPreviewPlacerView.setOrigin(viewOrigin[0], viewOrigin[1]); mPreviewPlacerView.setOrigin(viewOrigin[0], viewOrigin[1]);
final ViewGroup windowContentView = final View rootView = getRootView();
(ViewGroup)getRootView().findViewById(android.R.id.content); if (rootView == null) {
windowContentView.addView(mPreviewPlacerView); 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) { public void showGestureFloatingPreviewText(String gestureFloatingPreviewText) {
@ -952,7 +992,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
} }
@Override @Override
public void showGestureTrail(PointerTracker tracker) { public void showGesturePreviewTrail(PointerTracker tracker) {
locatePreviewPlacerView(); locatePreviewPlacerView();
mPreviewPlacerView.invalidatePointer(tracker); mPreviewPlacerView.invalidatePointer(tracker);
} }
@ -962,7 +1002,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
public void showKeyPreview(PointerTracker tracker) { public void showKeyPreview(PointerTracker tracker) {
if (!mShowKeyPreviewPopup) return; 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 // If the key preview has no parent view yet, add it to the ViewGroup which can place
// key preview absolutely in SoftInputWindow. // key preview absolutely in SoftInputWindow.
if (previewText.getParent() == null) { if (previewText.getParent() == null) {
@ -1052,7 +1092,6 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
public void invalidateAllKeys() { public void invalidateAllKeys() {
mInvalidatedKeys.clear(); mInvalidatedKeys.clear();
mInvalidateAllKeys = true; mInvalidateAllKeys = true;
mBufferNeedsUpdate = true;
invalidate(); invalidate();
} }
@ -1070,13 +1109,11 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
mInvalidatedKeys.add(key); mInvalidatedKeys.add(key);
final int x = key.mX + getPaddingLeft(); final int x = key.mX + getPaddingLeft();
final int y = key.mY + getPaddingTop(); final int y = key.mY + getPaddingTop();
mWorkingRect.set(x, y, x + key.mWidth, y + key.mHeight); invalidate(x, y, x + key.mWidth, y + key.mHeight);
mBufferNeedsUpdate = true;
invalidate(mWorkingRect);
} }
public void closing() { public void closing() {
PointerTracker.dismissAllKeyPreviews(); dismissAllKeyPreviews();
cancelAllMessages(); cancelAllMessages();
mInvalidateAllKeys = true; mInvalidateAllKeys = true;

View File

@ -110,7 +110,6 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
new WeakHashMap<Key, MoreKeysPanel>(); new WeakHashMap<Key, MoreKeysPanel>();
private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint; private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint;
private final PointerTrackerParams mPointerTrackerParams;
private final SuddenJumpingTouchEventHandler mTouchScreenRegulator; private final SuddenJumpingTouchEventHandler mTouchScreenRegulator;
protected KeyDetector mKeyDetector; 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_LONGPRESS_KEY = 2;
private static final int MSG_DOUBLE_TAP = 3; 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); 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 @Override
@ -146,7 +160,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
final Key currentKey = tracker.getKey(); final Key currentKey = tracker.getKey();
if (currentKey != null && currentKey.mCode == msg.arg1) { if (currentKey != null && currentKey.mCode == msg.arg1) {
tracker.onRegisterKey(currentKey); tracker.onRegisterKey(currentKey);
startKeyRepeatTimer(tracker, mParams.mKeyRepeatInterval); startKeyRepeatTimer(tracker, mKeyRepeatInterval);
} }
break; break;
case MSG_LONGPRESS_KEY: case MSG_LONGPRESS_KEY:
@ -167,7 +181,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
@Override @Override
public void startKeyRepeatTimer(PointerTracker tracker) { public void startKeyRepeatTimer(PointerTracker tracker) {
startKeyRepeatTimer(tracker, mParams.mKeyRepeatStartTimeout); startKeyRepeatTimer(tracker, mKeyRepeatStartTimeout);
} }
public void cancelKeyRepeatTimer() { public void cancelKeyRepeatTimer() {
@ -185,7 +199,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
final int delay; final int delay;
switch (code) { switch (code) {
case Keyboard.CODE_SHIFT: case Keyboard.CODE_SHIFT:
delay = mParams.mLongPressShiftKeyTimeout; delay = mLongPressShiftKeyTimeout;
break; break;
default: default:
delay = 0; delay = 0;
@ -206,15 +220,15 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
final int delay; final int delay;
switch (key.mCode) { switch (key.mCode) {
case Keyboard.CODE_SHIFT: case Keyboard.CODE_SHIFT:
delay = mParams.mLongPressShiftKeyTimeout; delay = mLongPressShiftKeyTimeout;
break; break;
default: default:
if (KeyboardSwitcher.getInstance().isInMomentarySwitchState()) { if (KeyboardSwitcher.getInstance().isInMomentarySwitchState()) {
// We use longer timeout for sliding finger input started from the symbols // We use longer timeout for sliding finger input started from the symbols
// mode key. // mode key.
delay = mParams.mLongPressKeyTimeout * 3; delay = mLongPressKeyTimeout * 3;
} else { } else {
delay = mParams.mLongPressKeyTimeout; delay = mLongPressKeyTimeout;
} }
break; break;
} }
@ -268,7 +282,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
} }
sendMessageDelayed( sendMessageDelayed(
obtainMessage(MSG_TYPING_STATE_EXPIRED), mParams.mIgnoreAltCodeKeyTimeout); obtainMessage(MSG_TYPING_STATE_EXPIRED), mIgnoreAltCodeKeyTimeout);
if (isTyping) { if (isTyping) {
return; 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) { public MainKeyboardView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.mainKeyboardViewStyle); this(context, attrs, R.attr.mainKeyboardViewStyle);
} }
@ -374,8 +344,8 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
R.styleable.MainKeyboardView_autoCorrectionSpacebarLedEnabled, false); R.styleable.MainKeyboardView_autoCorrectionSpacebarLedEnabled, false);
mAutoCorrectionSpacebarLedIcon = a.getDrawable( mAutoCorrectionSpacebarLedIcon = a.getDrawable(
R.styleable.MainKeyboardView_autoCorrectionSpacebarLedIcon); R.styleable.MainKeyboardView_autoCorrectionSpacebarLedIcon);
mSpacebarTextRatio = a.getFraction(R.styleable.MainKeyboardView_spacebarTextRatio, mSpacebarTextRatio = a.getFraction(
1000, 1000, 1) / 1000.0f; R.styleable.MainKeyboardView_spacebarTextRatio, 1, 1, 1.0f);
mSpacebarTextColor = a.getColor(R.styleable.MainKeyboardView_spacebarTextColor, 0); mSpacebarTextColor = a.getColor(R.styleable.MainKeyboardView_spacebarTextColor, 0);
mSpacebarTextShadowColor = a.getColor( mSpacebarTextShadowColor = a.getColor(
R.styleable.MainKeyboardView_spacebarTextShadowColor, 0); R.styleable.MainKeyboardView_spacebarTextShadowColor, 0);
@ -389,19 +359,15 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
final int altCodeKeyWhileTypingFadeinAnimatorResId = a.getResourceId( final int altCodeKeyWhileTypingFadeinAnimatorResId = a.getResourceId(
R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0); R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0);
final KeyTimerParams keyTimerParams = new KeyTimerParams(a);
mPointerTrackerParams = new PointerTrackerParams(a);
final float keyHysteresisDistance = a.getDimension( final float keyHysteresisDistance = a.getDimension(
R.styleable.MainKeyboardView_keyHysteresisDistance, 0); R.styleable.MainKeyboardView_keyHysteresisDistance, 0);
mKeyDetector = new KeyDetector(keyHysteresisDistance); mKeyDetector = new KeyDetector(keyHysteresisDistance);
mKeyTimerHandler = new KeyTimerHandler(this, keyTimerParams); mKeyTimerHandler = new KeyTimerHandler(this, a);
mConfigShowMoreKeysKeyboardAtTouchedPoint = a.getBoolean( mConfigShowMoreKeysKeyboardAtTouchedPoint = a.getBoolean(
R.styleable.MainKeyboardView_showMoreKeysKeyboardAtTouchedPoint, false); R.styleable.MainKeyboardView_showMoreKeysKeyboardAtTouchedPoint, false);
PointerTracker.setParameters(a);
a.recycle(); a.recycle();
PointerTracker.setParameters(mPointerTrackerParams);
mLanguageOnSpacebarFadeoutAnimator = loadObjectAnimator( mLanguageOnSpacebarFadeoutAnimator = loadObjectAnimator(
languageOnSpacebarFadeoutAnimatorResId, this); languageOnSpacebarFadeoutAnimatorResId, this);
mAltCodeKeyWhileTypingFadeoutAnimator = loadObjectAnimator( mAltCodeKeyWhileTypingFadeoutAnimator = loadObjectAnimator(
@ -482,7 +448,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
super.setKeyboard(keyboard); super.setKeyboard(keyboard);
mKeyDetector.setKeyboard( mKeyDetector.setKeyboard(
keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection); keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection);
PointerTracker.setKeyDetector(mKeyDetector, mShouldHandleGesture); PointerTracker.setKeyDetector(mKeyDetector);
mTouchScreenRegulator.setKeyboard(keyboard); mTouchScreenRegulator.setKeyboard(keyboard);
mMoreKeysPanelCache.clear(); mMoreKeysPanelCache.clear();
@ -500,12 +466,13 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
AccessibleKeyboardViewProxy.getInstance().setKeyboard(keyboard); AccessibleKeyboardViewProxy.getInstance().setKeyboard(keyboard);
} }
@Override // Note that this method is called from a non-UI thread.
public void setGestureHandlingMode(final boolean shouldHandleGesture, public void setMainDictionaryAvailability(boolean mainDictionaryAvailable) {
boolean drawsGesturePreviewTrail, boolean drawsGestureFloatingPreviewText) { PointerTracker.setMainDictionaryAvailability(mainDictionaryAvailable);
super.setGestureHandlingMode(shouldHandleGesture, drawsGesturePreviewTrail, }
drawsGestureFloatingPreviewText);
PointerTracker.setKeyDetector(mKeyDetector, shouldHandleGesture); 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 // to properly show the splash screen, which requires that the window token of the
// KeyboardView be non-null. // KeyboardView be non-null.
if (ProductionFlag.IS_EXPERIMENTAL) { 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) { private void invokeCodeInput(int primaryCode) {
mKeyboardActionListener.onCodeInput(primaryCode, mKeyboardActionListener.onCodeInput(
KeyboardActionListener.NOT_A_TOUCH_COORDINATE, primaryCode, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
} }
private void invokeReleaseKey(int primaryCode) { private void invokeReleaseKey(int primaryCode) {
@ -834,20 +810,6 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
return false; 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. * Receives hover events from the input framework.
* *

View File

@ -39,11 +39,7 @@ public class MoreKeysDetector extends KeyDetector {
Key nearestKey = null; Key nearestKey = null;
int nearestDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare; int nearestDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare;
final Keyboard keyboard = getKeyboard(); for (final Key key : getKeyboard().mKeys) {
if (keyboard == null) {
throw new NullPointerException("Keyboard isn't set");
}
for (final Key key : keyboard.mKeys) {
final int dist = key.squaredDistanceToEdge(touchX, touchY); final int dist = key.squaredDistanceToEdge(touchX, touchY);
if (dist < nearestDist) { if (dist < nearestDist) {
nearestKey = key; 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.DrawingProxy;
import com.android.inputmethod.keyboard.PointerTracker.TimerProxy; import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.InputPointers; import com.android.inputmethod.latin.InputPointers;
import com.android.inputmethod.latin.R; 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) { public void onCodeInput(int primaryCode, int x, int y) {
// Because a more keys keyboard doesn't need proximity characters correction, we don't // Because a more keys keyboard doesn't need proximity characters correction, we don't
// send touch event coordinates. // 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 @Override

View File

@ -16,25 +16,25 @@
package com.android.inputmethod.keyboard; package com.android.inputmethod.keyboard;
import android.graphics.Canvas; import android.content.res.TypedArray;
import android.graphics.Paint;
import android.os.SystemClock; import android.os.SystemClock;
import android.util.Log; import android.util.Log;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;
import com.android.inputmethod.accessibility.AccessibilityUtils; import com.android.inputmethod.accessibility.AccessibilityUtils;
import com.android.inputmethod.keyboard.internal.GestureStroke; 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.keyboard.internal.PointerTrackerQueue;
import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.InputPointers; import com.android.inputmethod.latin.InputPointers;
import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.research.ResearchLogger; import com.android.inputmethod.research.ResearchLogger;
import java.util.ArrayList; 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 String TAG = PointerTracker.class.getSimpleName();
private static final boolean DEBUG_EVENT = false; private static final boolean DEBUG_EVENT = false;
private static final boolean DEBUG_MOVE_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. */ /** True if {@link PointerTracker}s should handle gesture events. */
private static boolean sShouldHandleGesture = false; 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 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 interface DrawingProxy extends MoreKeysPanel.Controller {
public void invalidateKey(Key key); public void invalidateKey(Key key);
public TextView inflateKeyPreviewText();
public void showKeyPreview(PointerTracker tracker); public void showKeyPreview(PointerTracker tracker);
public void dismissKeyPreview(PointerTracker tracker); public void dismissKeyPreview(PointerTracker tracker);
public void showGestureTrail(PointerTracker tracker); public void showGesturePreviewTrail(PointerTracker tracker);
} }
public interface TimerProxy { 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. // Parameters for pointer handling.
private static MainKeyboardView.PointerTrackerParams sParams; private static PointerTrackerParams sParams;
private static int sTouchNoiseThresholdDistanceSquared;
private static boolean sNeedsPhantomSuddenMoveEventHack; private static boolean sNeedsPhantomSuddenMoveEventHack;
private static final ArrayList<PointerTracker> sTrackers = new ArrayList<PointerTracker>(); private static final ArrayList<PointerTracker> sTrackers = CollectionUtils.newArrayList();
private static final InputPointers sAggregratedPointers = new InputPointers(
GestureStroke.DEFAULT_CAPACITY);
private static PointerTrackerQueue sPointerTrackerQueue; 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; public final int mPointerId;
@ -140,15 +163,14 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
private Keyboard mKeyboard; private Keyboard mKeyboard;
private int mKeyQuarterWidthSquared; private int mKeyQuarterWidthSquared;
private final TextView mKeyPreviewText;
private boolean mIsAlphabetKeyboard; private boolean mIsDetectingGesture = false; // per PointerTracker.
private boolean mIsPossibleGesture = false; private static boolean sInGesture = false;
private boolean mInGesture = false; private static long sGestureFirstDownTime;
private static final InputPointers sAggregratedPointers = new InputPointers(
// TODO: Remove these variables GestureStroke.DEFAULT_CAPACITY);
private int mLastRecognitionPointSize = 0; private static int sLastRecognitionPointSize = 0;
private long mLastRecognitionTime = 0; private static long sLastRecognitionTime = 0;
// The position and time at which first down event occurred. // The position and time at which first down event occurred.
private long mDownTime; private long mDownTime;
@ -186,7 +208,7 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
private static final KeyboardActionListener EMPTY_LISTENER = private static final KeyboardActionListener EMPTY_LISTENER =
new KeyboardActionListener.Adapter(); new KeyboardActionListener.Adapter();
private final GestureStroke mGestureStroke; private final GestureStrokeWithPreviewTrail mGestureStrokeWithPreviewTrail;
public static void init(boolean hasDistinctMultitouch, public static void init(boolean hasDistinctMultitouch,
boolean needsPhantomSuddenMoveEventHack) { boolean needsPhantomSuddenMoveEventHack) {
@ -196,28 +218,32 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
sPointerTrackerQueue = null; sPointerTrackerQueue = null;
} }
sNeedsPhantomSuddenMoveEventHack = needsPhantomSuddenMoveEventHack; sNeedsPhantomSuddenMoveEventHack = needsPhantomSuddenMoveEventHack;
sParams = PointerTrackerParams.DEFAULT;
setParameters(MainKeyboardView.PointerTrackerParams.DEFAULT);
updateGestureHandlingMode(null, false /* shouldHandleGesture */);
} }
public static void setParameters(MainKeyboardView.PointerTrackerParams params) { public static void setParameters(final TypedArray mainKeyboardViewAttr) {
sParams = params; sParams = new PointerTrackerParams(mainKeyboardViewAttr);
sTouchNoiseThresholdDistanceSquared = (int)(
params.mTouchNoiseThresholdDistance * params.mTouchNoiseThresholdDistance);
} }
private static void updateGestureHandlingMode(Keyboard keyboard, boolean shouldHandleGesture) { private static void updateGestureHandlingMode() {
if (!shouldHandleGesture sShouldHandleGesture = sMainDictionaryAvailable
|| AccessibilityUtils.getInstance().isTouchExplorationEnabled() && sGestureHandlingEnabledByInputField
|| (keyboard != null && keyboard.mId.passwordInput())) { && sGestureHandlingEnabledByUser
sShouldHandleGesture = false; && !AccessibilityUtils.getInstance().isTouchExplorationEnabled();
} else {
sShouldHandleGesture = true;
}
} }
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; final ArrayList<PointerTracker> trackers = sTrackers;
// Create pointer trackers until we can get 'id+1'-th tracker, if needed. // 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; return sPointerTrackerQueue != null ? sPointerTrackerQueue.isAnyInSlidingKeyInput() : false;
} }
public static void setKeyboardActionListener(KeyboardActionListener listener) { public static void setKeyboardActionListener(final KeyboardActionListener listener) {
final int trackersSize = sTrackers.size(); final int trackersSize = sTrackers.size();
for (int i = 0; i < trackersSize; ++i) { for (int i = 0; i < trackersSize; ++i) {
final PointerTracker tracker = sTrackers.get(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(); final int trackersSize = sTrackers.size();
for (int i = 0; i < trackersSize; ++i) { for (int i = 0; i < trackersSize; ++i) {
final PointerTracker tracker = sTrackers.get(i); final PointerTracker tracker = sTrackers.get(i);
@ -250,70 +276,33 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
tracker.mKeyboardLayoutHasBeenChanged = true; tracker.mKeyboardLayoutHasBeenChanged = true;
} }
final Keyboard keyboard = keyDetector.getKeyboard(); 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(); final int trackersSize = sTrackers.size();
for (int i = 0; i < trackersSize; ++i) { for (int i = 0; i < trackersSize; ++i) {
final PointerTracker tracker = sTrackers.get(i); final PointerTracker tracker = sTrackers.get(i);
tracker.getKeyPreviewText().setVisibility(View.INVISIBLE);
tracker.setReleasedKeyGraphics(tracker.mCurrentKey); tracker.setReleasedKeyGraphics(tracker.mCurrentKey);
} }
} }
// TODO: To handle multi-touch gestures we may want to move this method to private PointerTracker(final int id, final KeyEventHandler handler) {
// {@link PointerTrackerQueue}. if (handler == null) {
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)
throw new NullPointerException(); throw new NullPointerException();
}
mPointerId = id; mPointerId = id;
mGestureStroke = new GestureStroke(id); mGestureStrokeWithPreviewTrail = new GestureStrokeWithPreviewTrail(id);
setKeyDetectorInner(handler.getKeyDetector()); setKeyDetectorInner(handler.getKeyDetector());
mListener = handler.getKeyboardActionListener(); mListener = handler.getKeyboardActionListener();
mDrawingProxy = handler.getDrawingProxy(); mDrawingProxy = handler.getDrawingProxy();
mTimerProxy = handler.getTimerProxy(); mTimerProxy = handler.getTimerProxy();
mKeyPreviewText = mDrawingProxy.inflateKeyPreviewText();
}
public TextView getKeyPreviewText() {
return mKeyPreviewText;
} }
// Returns true if keyboard has been changed by this callback. // Returns true if keyboard has been changed by this callback.
private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key) { private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key) {
if (mInGesture) { if (sInGesture) {
return false; return false;
} }
final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier(); 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 // Note that we need primaryCode argument because the keyboard may in shifted state and the
// primaryCode is different from {@link Key#mCode}. // 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 ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState(); final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState();
final int code = altersCode ? key.mAltCode : primaryCode; 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 // Note that we need primaryCode argument because the keyboard may in shifted state and the
// primaryCode is different from {@link Key#mCode}. // primaryCode is different from {@link Key#mCode}.
private void callListenerOnRelease(Key key, int primaryCode, boolean withSliding) { private void callListenerOnRelease(final Key key, final int primaryCode,
if (mInGesture) { final boolean withSliding) {
if (sInGesture) {
return; return;
} }
final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier(); final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
@ -389,20 +380,19 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
} }
private void callListenerOnCancelInput() { private void callListenerOnCancelInput() {
if (DEBUG_LISTENER) if (DEBUG_LISTENER) {
Log.d(TAG, "onCancelInput"); Log.d(TAG, "onCancelInput");
}
if (ProductionFlag.IS_EXPERIMENTAL) { if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.pointerTracker_callListenerOnCancelInput(); ResearchLogger.pointerTracker_callListenerOnCancelInput();
} }
mListener.onCancelInput(); mListener.onCancelInput();
} }
private void setKeyDetectorInner(KeyDetector keyDetector) { private void setKeyDetectorInner(final KeyDetector keyDetector) {
mKeyDetector = keyDetector; mKeyDetector = keyDetector;
mKeyboard = keyDetector.getKeyboard(); mKeyboard = keyDetector.getKeyboard();
mIsAlphabetKeyboard = mKeyboard.mId.isAlphabetKeyboard(); mGestureStrokeWithPreviewTrail.setGestureSampleLength(mKeyboard.mMostCommonKeyWidth);
mGestureStroke.setGestureSampleLength(
mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight);
final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY); final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY);
if (newKey != mCurrentKey) { if (newKey != mCurrentKey) {
if (mDrawingProxy != null) { if (mDrawingProxy != null) {
@ -428,11 +418,11 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
return mCurrentKey != null && mCurrentKey.isModifier(); 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); return mKeyDetector.detectHitKey(x, y);
} }
private void setReleasedKeyGraphics(Key key) { private void setReleasedKeyGraphics(final Key key) {
mDrawingProxy.dismissKeyPreview(this); mDrawingProxy.dismissKeyPreview(this);
if (key == null) { if (key == null) {
return; 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) { if (key == null) {
return; return;
} }
@ -475,7 +465,7 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
return; return;
} }
if (!key.noKeyPreview() && !mInGesture) { if (!key.noKeyPreview() && !sInGesture) {
mDrawingProxy.showKeyPreview(this); mDrawingProxy.showKeyPreview(this);
} }
updatePressKeyGraphics(key); 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(); key.onReleased();
mDrawingProxy.invalidateKey(key); mDrawingProxy.invalidateKey(key);
} }
private void updatePressKeyGraphics(Key key) { private void updatePressKeyGraphics(final Key key) {
key.onPressed(); key.onPressed();
mDrawingProxy.invalidateKey(key); mDrawingProxy.invalidateKey(key);
} }
public void drawGestureTrail(Canvas canvas, Paint paint) { public GestureStrokeWithPreviewTrail getGestureStrokeWithPreviewTrail() {
if (mInGesture) { return mGestureStrokeWithPreviewTrail;
mGestureStroke.drawGestureTrail(canvas, paint, mLastX, mLastY);
}
} }
public int getLastX() { public int getLastX() {
@ -530,77 +518,91 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
return mDownTime; 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; mDownTime = eventTime;
return onMoveToNewKey(onMoveKeyInternal(x, y), x, y); 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; mLastX = x;
mLastY = y; mLastY = y;
return mKeyDetector.detectHitKey(x, 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); 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; mCurrentKey = newKey;
mKeyX = x; mKeyX = x;
mKeyY = y; mKeyY = y;
return newKey; return newKey;
} }
private static int getActivePointerTrackerCount() {
return (sPointerTrackerQueue == null) ? 1 : sPointerTrackerQueue.size();
}
private void startBatchInput() { private void startBatchInput() {
if (DEBUG_LISTENER) { if (DEBUG_LISTENER) {
Log.d(TAG, "onStartBatchInput"); Log.d(TAG, "onStartBatchInput");
} }
mInGesture = true; sInGesture = true;
mListener.onStartBatchInput(); mListener.onStartBatchInput();
mDrawingProxy.showGesturePreviewTrail(this);
} }
private void updateBatchInput(InputPointers batchPoints) { private void updateBatchInput(final long eventTime) {
if (DEBUG_LISTENER) { synchronized (sAggregratedPointers) {
Log.d(TAG, "onUpdateBatchInput: batchPoints=" + batchPoints.getPointerSize()); mGestureStrokeWithPreviewTrail.appendIncrementalBatchPoints(sAggregratedPointers);
final int size = sAggregratedPointers.getPointerSize();
if (size > sLastRecognitionPointSize
&& eventTime > sLastRecognitionTime + MIN_GESTURE_RECOGNITION_TIME) {
sLastRecognitionPointSize = size;
sLastRecognitionTime = eventTime;
if (DEBUG_LISTENER) {
Log.d(TAG, "onUpdateBatchInput: batchPoints=" + size);
}
mListener.onUpdateBatchInput(sAggregratedPointers);
}
} }
mListener.onUpdateBatchInput(batchPoints); mDrawingProxy.showGesturePreviewTrail(this);
} }
private void endBatchInput(InputPointers batchPoints) { private void endBatchInput() {
if (DEBUG_LISTENER) { synchronized (sAggregratedPointers) {
Log.d(TAG, "onEndBatchInput: batchPoints=" + batchPoints.getPointerSize()); mGestureStrokeWithPreviewTrail.appendAllBatchPoints(sAggregratedPointers);
if (getActivePointerTrackerCount() == 1) {
if (DEBUG_LISTENER) {
Log.d(TAG, "onEndBatchInput: batchPoints="
+ sAggregratedPointers.getPointerSize());
}
sInGesture = false;
mListener.onEndBatchInput(sAggregratedPointers);
clearBatchInputPointsOfAllPointerTrackers();
}
} }
mListener.onEndBatchInput(batchPoints); mDrawingProxy.showGesturePreviewTrail(this);
clearBatchInputRecognitionStateOfThisPointerTracker();
clearBatchInputPointsOfAllPointerTrackers();
sWasInGesture = true;
} }
private void abortBatchInput() { private static void abortBatchInput() {
clearBatchInputRecognitionStateOfThisPointerTracker();
clearBatchInputPointsOfAllPointerTrackers(); clearBatchInputPointsOfAllPointerTrackers();
} }
private void clearBatchInputRecognitionStateOfThisPointerTracker() { private static void clearBatchInputPointsOfAllPointerTrackers() {
mIsPossibleGesture = false; final int trackersSize = sTrackers.size();
mInGesture = false; for (int i = 0; i < trackersSize; ++i) {
mLastRecognitionPointSize = 0; final PointerTracker tracker = sTrackers.get(i);
mLastRecognitionTime = 0; tracker.mGestureStrokeWithPreviewTrail.reset();
}
private boolean updateBatchInputRecognitionState(long eventTime, int size) {
if (size > mLastRecognitionPointSize
&& eventTime > mLastRecognitionTime + MIN_GESTURE_RECOGNITION_TIME) {
mLastRecognitionPointSize = size;
mLastRecognitionTime = eventTime;
return true;
} }
return false; sAggregratedPointers.reset();
sLastRecognitionPointSize = 0;
sLastRecognitionTime = 0;
} }
public void processMotionEvent(int action, int x, int y, long eventTime, public void processMotionEvent(final int action, final int x, final int y, final long eventTime,
KeyEventHandler handler) { final KeyEventHandler handler) {
switch (action) { switch (action) {
case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_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) { public void onDownEvent(final int x, final int y, final long eventTime,
if (DEBUG_EVENT) final KeyEventHandler handler) {
if (DEBUG_EVENT) {
printTouchEvent("onDownEvent:", x, y, eventTime); printTouchEvent("onDownEvent:", x, y, eventTime);
}
mDrawingProxy = handler.getDrawingProxy(); mDrawingProxy = handler.getDrawingProxy();
mTimerProxy = handler.getTimerProxy(); mTimerProxy = handler.getTimerProxy();
@ -633,7 +637,7 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
final int dx = x - mLastX; final int dx = x - mLastX;
final int dy = y - mLastY; final int dy = y - mLastY;
final int distanceSquared = (dx * dx + dy * dy); final int distanceSquared = (dx * dx + dy * dy);
if (distanceSquared < sTouchNoiseThresholdDistanceSquared) { if (distanceSquared < sParams.mTouchNoiseThresholdDistanceSquared) {
if (DEBUG_MODE) if (DEBUG_MODE)
Log.w(TAG, "onDownEvent: ignore potential noise: time=" + deltaT Log.w(TAG, "onDownEvent: ignore potential noise: time=" + deltaT
+ " distance=" + distanceSquared); + " distance=" + distanceSquared);
@ -645,8 +649,8 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
} }
} }
final PointerTrackerQueue queue = sPointerTrackerQueue;
final Key key = getKeyOn(x, y); final Key key = getKeyOn(x, y);
final PointerTrackerQueue queue = sPointerTrackerQueue;
if (queue != null) { if (queue != null) {
if (key != null && key.isModifier()) { if (key != null && key.isModifier()) {
// Before processing a down event of modifier key, all pointers already being // 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); queue.add(this);
} }
onDownEventInternal(x, y, eventTime); onDownEventInternal(x, y, eventTime);
if (queue != null && queue.size() == 1) { if (!sShouldHandleGesture) {
mIsPossibleGesture = false; return;
}
final int activePointerTrackerCount = getActivePointerTrackerCount();
if (activePointerTrackerCount == 1) {
mIsDetectingGesture = false;
// A gesture should start only from the letter key. // A gesture should start only from the letter key.
if (sShouldHandleGesture && mIsAlphabetKeyboard && !mIsShowingMoreKeysPanel final boolean isAlphabetKeyboard = (mKeyboard != null)
&& key != null && Keyboard.isLetterCode(key.mCode)) { && mKeyboard.mId.isAlphabetKeyboard();
mIsPossibleGesture = true; if (isAlphabetKeyboard && !mIsShowingMoreKeysPanel && key != null
// TODO: pointer times should be relative to first down even in entire batch input && Keyboard.isLetterCode(key.mCode)) {
// instead of resetting to 0 for each new down event. mIsDetectingGesture = true;
mGestureStroke.addPoint(x, y, 0, false); 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); Key key = onDownKey(x, y, eventTime);
// Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding // 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. // 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) { if (!mIsInSlidingKeyInput) {
mIgnoreModifierKey = key.isModifier(); mIgnoreModifierKey = key.isModifier();
} }
mIsInSlidingKeyInput = true; mIsInSlidingKeyInput = true;
} }
private void onGestureMoveEvent(PointerTracker tracker, int x, int y, long eventTime, private void onGestureMoveEvent(final int x, final int y, final long eventTime,
boolean isHistorical, Key key) { final boolean isHistorical, final Key key) {
final int gestureTime = (int)(eventTime - tracker.getDownTime()); final int gestureTime = (int)(eventTime - sGestureFirstDownTime);
if (sShouldHandleGesture && mIsPossibleGesture) { if (mIsDetectingGesture) {
final GestureStroke stroke = mGestureStroke; final GestureStroke stroke = mGestureStrokeWithPreviewTrail;
stroke.addPoint(x, y, gestureTime, isHistorical); stroke.addPoint(x, y, gestureTime, isHistorical);
if (!mInGesture && stroke.isStartOfAGesture(gestureTime, sWasInGesture)) { if (!sInGesture && stroke.isStartOfAGesture()) {
startBatchInput(); startBatchInput();
} }
}
if (key != null && mInGesture) { if (sInGesture && key != null) {
final InputPointers batchPoints = getIncrementalBatchPoints(); updateBatchInput(eventTime);
mDrawingProxy.showGestureTrail(this);
if (updateBatchInputRecognitionState(eventTime, batchPoints.getPointerSize())) {
updateBatchInput(batchPoints);
} }
} }
} }
public void onMoveEvent(int x, int y, long eventTime, MotionEvent me) { public void onMoveEvent(final int x, final int y, final long eventTime, final MotionEvent me) {
if (DEBUG_MOVE_EVENT) if (DEBUG_MOVE_EVENT) {
printTouchEvent("onMoveEvent:", x, y, eventTime); printTouchEvent("onMoveEvent:", x, y, eventTime);
if (mKeyAlreadyProcessed) }
if (mKeyAlreadyProcessed) {
return; return;
}
if (me != null) { if (sShouldHandleGesture && me != null) {
// Add historical points to gesture path. // Add historical points to gesture path.
final int pointerIndex = me.findPointerIndex(mPointerId); final int pointerIndex = me.findPointerIndex(mPointerId);
final int historicalSize = me.getHistorySize(); 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 historicalX = (int)me.getHistoricalX(pointerIndex, h);
final int historicalY = (int)me.getHistoricalY(pointerIndex, h); final int historicalY = (int)me.getHistoricalY(pointerIndex, h);
final long historicalTime = me.getHistoricalEventTime(h); final long historicalTime = me.getHistoricalEventTime(h);
onGestureMoveEvent(this, historicalX, historicalY, historicalTime, onGestureMoveEvent(historicalX, historicalY, historicalTime,
true /* isHistorical */, null); 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 lastX = mLastX;
final int lastY = mLastY; final int lastY = mLastY;
final Key oldKey = mCurrentKey; final Key oldKey = mCurrentKey;
Key key = onMoveKey(x, y); Key key = onMoveKey(x, y);
// Register move event on gesture tracker. if (sShouldHandleGesture) {
onGestureMoveEvent(this, x, y, eventTime, false /* isHistorical */, key); // Register move event on gesture tracker.
if (mInGesture) { onGestureMoveEvent(x, y, eventTime, false /* isHistorical */, key);
mIgnoreModifierKey = true; if (sInGesture) {
mTimerProxy.cancelLongPressTimer(); mIgnoreModifierKey = true;
mIsInSlidingKeyInput = true; mTimerProxy.cancelLongPressTimer();
mCurrentKey = null; mIsInSlidingKeyInput = true;
setReleasedKeyGraphics(oldKey); mCurrentKey = null;
setReleasedKeyGraphics(oldKey);
return;
}
} }
if (key != null) { 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. // TODO: Should find a way to balance gesture detection and this hack.
if (sNeedsPhantomSuddenMoveEventHack if (sNeedsPhantomSuddenMoveEventHack
&& lastMoveSquared >= mKeyQuarterWidthSquared && lastMoveSquared >= mKeyQuarterWidthSquared
&& !mIsPossibleGesture) { && !mIsDetectingGesture) {
if (DEBUG_MODE) { if (DEBUG_MODE) {
Log.w(TAG, String.format("onMoveEvent:" Log.w(TAG, String.format("onMoveEvent:"
+ " phantom sudden move event is translated to " + " 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. // touch panels when there are close multiple touches.
// Caveat: When in chording input mode with a modifier key, we don't use // Caveat: When in chording input mode with a modifier key, we don't use
// this hack. // this hack.
if (me != null && me.getPointerCount() > 1 if (getActivePointerTrackerCount() > 1 && sPointerTrackerQueue != null
&& !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) { && !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) {
onUpEventInternal(); onUpEventInternal();
} }
if (!mIsPossibleGesture) { if (!mIsDetectingGesture) {
mKeyAlreadyProcessed = true; mKeyAlreadyProcessed = true;
} }
setReleasedKeyGraphics(oldKey); setReleasedKeyGraphics(oldKey);
@ -837,7 +856,7 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
if (mIsAllowedSlidingKeyInput) { if (mIsAllowedSlidingKeyInput) {
onMoveToNewKey(key, x, y); onMoveToNewKey(key, x, y);
} else { } else {
if (!mIsPossibleGesture) { if (!mIsDetectingGesture) {
mKeyAlreadyProcessed = true; mKeyAlreadyProcessed = true;
} }
} }
@ -845,13 +864,14 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
} }
} }
public void onUpEvent(int x, int y, long eventTime) { public void onUpEvent(final int x, final int y, final long eventTime) {
if (DEBUG_EVENT) if (DEBUG_EVENT) {
printTouchEvent("onUpEvent :", x, y, eventTime); printTouchEvent("onUpEvent :", x, y, eventTime);
}
final PointerTrackerQueue queue = sPointerTrackerQueue; final PointerTrackerQueue queue = sPointerTrackerQueue;
if (queue != null) { if (queue != null) {
if (!mInGesture) { if (!sInGesture) {
if (mCurrentKey != null && mCurrentKey.isModifier()) { if (mCurrentKey != null && mCurrentKey.isModifier()) {
// Before processing an up event of modifier key, all pointers already being // Before processing an up event of modifier key, all pointers already being
// tracked should be released. // tracked should be released.
@ -860,18 +880,21 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
queue.releaseAllPointersOlderThan(this, eventTime); queue.releaseAllPointersOlderThan(this, eventTime);
} }
} }
queue.remove(this);
} }
onUpEventInternal(); onUpEventInternal();
if (queue != null) {
queue.remove(this);
}
} }
// Let this pointer tracker know that one of newer-than-this pointer trackers got an up event. // 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 // This pointer tracker needs to keep the key top graphics "pressed", but needs to get a
// "virtual" up event. // "virtual" up event.
@Override @Override
public void onPhantomUpEvent(long eventTime) { public void onPhantomUpEvent(final long eventTime) {
if (DEBUG_EVENT) if (DEBUG_EVENT) {
printTouchEvent("onPhntEvent:", getLastX(), getLastY(), eventTime); printTouchEvent("onPhntEvent:", getLastX(), getLastY(), eventTime);
}
onUpEventInternal(); onUpEventInternal();
mKeyAlreadyProcessed = true; mKeyAlreadyProcessed = true;
} }
@ -879,37 +902,35 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
private void onUpEventInternal() { private void onUpEventInternal() {
mTimerProxy.cancelKeyTimers(); mTimerProxy.cancelKeyTimers();
mIsInSlidingKeyInput = false; mIsInSlidingKeyInput = false;
mIsPossibleGesture = false; mIsDetectingGesture = false;
final Key currentKey = mCurrentKey;
mCurrentKey = null;
// Release the last pressed key. // Release the last pressed key.
setReleasedKeyGraphics(mCurrentKey); setReleasedKeyGraphics(currentKey);
if (mIsShowingMoreKeysPanel) { if (mIsShowingMoreKeysPanel) {
mDrawingProxy.dismissMoreKeysPanel(); mDrawingProxy.dismissMoreKeysPanel();
mIsShowingMoreKeysPanel = false; mIsShowingMoreKeysPanel = false;
} }
if (mInGesture) { if (sInGesture) {
// Register up event on gesture tracker. if (currentKey != null) {
// TODO: Figure out how to deal with multiple fingers that are in gesture, sliding, callListenerOnRelease(currentKey, currentKey.mCode, true);
// and/or tapping mode?
endBatchInput(getAllBatchPoints());
if (mCurrentKey != null) {
callListenerOnRelease(mCurrentKey, mCurrentKey.mCode, true);
mCurrentKey = null;
} }
mDrawingProxy.showGestureTrail(this); endBatchInput();
return; return;
} }
// This event will be recognized as a regular code input. Clear unused batch points so they // This event will be recognized as a regular code input. Clear unused possible batch points
// are not mistakenly included in the next batch event. // so they are not mistakenly displayed as preview.
clearBatchInputPointsOfAllPointerTrackers(); clearBatchInputPointsOfAllPointerTrackers();
if (mKeyAlreadyProcessed) if (mKeyAlreadyProcessed) {
return; 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(); abortBatchInput();
onLongPressed(); onLongPressed();
mIsShowingMoreKeysPanel = true; mIsShowingMoreKeysPanel = true;
@ -925,9 +946,10 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
} }
} }
public void onCancelEvent(int x, int y, long eventTime) { public void onCancelEvent(final int x, final int y, final long eventTime) {
if (DEBUG_EVENT) if (DEBUG_EVENT) {
printTouchEvent("onCancelEvt:", x, y, eventTime); printTouchEvent("onCancelEvt:", x, y, eventTime);
}
final PointerTrackerQueue queue = sPointerTrackerQueue; final PointerTrackerQueue queue = sPointerTrackerQueue;
if (queue != null) { if (queue != null) {
@ -947,24 +969,25 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
} }
} }
private void startRepeatKey(Key key) { private void startRepeatKey(final Key key) {
if (key != null && key.isRepeatable() && !mInGesture) { if (key != null && key.isRepeatable() && !sInGesture) {
onRegisterKey(key); onRegisterKey(key);
mTimerProxy.startKeyRepeatTimer(this); mTimerProxy.startKeyRepeatTimer(this);
} }
} }
public void onRegisterKey(Key key) { public void onRegisterKey(final Key key) {
if (key != null) { if (key != null) {
detectAndSendKey(key, key.mX, key.mY); detectAndSendKey(key, key.mX, key.mY);
mTimerProxy.startTypingStateTimer(key); mTimerProxy.startTypingStateTimer(key);
} }
} }
private boolean isMajorEnoughMoveToBeOnNewKey(int x, int y, Key newKey) { private boolean isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final Key newKey) {
if (mKeyDetector == null) if (mKeyDetector == null) {
throw new NullPointerException("keyboard and/or key detector not set"); throw new NullPointerException("keyboard and/or key detector not set");
Key curKey = mCurrentKey; }
final Key curKey = mCurrentKey;
if (newKey == curKey) { if (newKey == curKey) {
return false; return false;
} else if (curKey != null) { } else if (curKey != null) {
@ -975,25 +998,25 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
} }
} }
private void startLongPressTimer(Key key) { private void startLongPressTimer(final Key key) {
if (key != null && key.isLongPressEnabled() && !mInGesture) { if (key != null && key.isLongPressEnabled() && !sInGesture) {
mTimerProxy.startLongPressTimer(this); 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) { if (key == null) {
callListenerOnCancelInput(); callListenerOnCancelInput();
return; return;
} }
int code = key.mCode; final int code = key.mCode;
callListenerOnCodeInput(key, code, x, y); callListenerOnCodeInput(key, code, x, y);
callListenerOnRelease(key, code, false); 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 Key key = mKeyDetector.detectHitKey(x, y);
final String code = KeyDetector.printableCode(key); final String code = KeyDetector.printableCode(key);
Log.d(TAG, String.format("%s%s[%d] %4d %4d %5d %s", title, 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.graphics.Rect;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.FloatMath;
import com.android.inputmethod.keyboard.Keyboard.Params.TouchPositionCorrection; import com.android.inputmethod.keyboard.Keyboard.Params.TouchPositionCorrection;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.JniUtils; import com.android.inputmethod.latin.JniUtils;
import java.util.Arrays; import java.util.Arrays;
@ -112,7 +112,7 @@ public class ProximityInfo {
final Key[] keys = mKeys; final Key[] keys = mKeys;
final TouchPositionCorrection touchPositionCorrection = mTouchPositionCorrection; final TouchPositionCorrection touchPositionCorrection = mTouchPositionCorrection;
final int[] proximityCharsArray = new int[mGridSize * MAX_PROXIMITY_CHARS_SIZE]; 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) { for (int i = 0; i < mGridSize; ++i) {
final int proximityCharsLength = gridNeighborKeys[i].length; final int proximityCharsLength = gridNeighborKeys[i].length;
for (int j = 0; j < proximityCharsLength; ++j) { for (int j = 0; j < proximityCharsLength; ++j) {
@ -155,7 +155,9 @@ public class ProximityInfo {
final float radius = touchPositionCorrection.mRadii[row]; final float radius = touchPositionCorrection.mRadii[row];
sweetSpotCenterXs[i] = hitBox.exactCenterX() + x * hitBoxWidth; sweetSpotCenterXs[i] = hitBox.exactCenterX() + x * hitBoxWidth;
sweetSpotCenterYs[i] = hitBox.exactCenterY() + y * hitBoxHeight; 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); hitBoxWidth * hitBoxWidth + hitBoxHeight * hitBoxHeight);
} }
} }
@ -233,7 +235,7 @@ public class ProximityInfo {
dest[index++] = code; dest[index++] = code;
} }
if (index < destLength) { 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; 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.InputPointers;
import com.android.inputmethod.latin.ResizableIntArray; import com.android.inputmethod.latin.ResizableIntArray;
@ -38,44 +33,30 @@ public class GestureStroke {
private int mLastPointY; private int mLastPointY;
private int mMinGestureLength; private int mMinGestureLength;
private int mMinGestureLengthWhileInGesture;
private int mMinGestureSampleLength; private int mMinGestureSampleLength;
// TODO: Move some of these to resource. // 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 = 0.75f;
private static final float MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH_WHILE_IN_GESTURE = 0.5f; private static final int MIN_GESTURE_DURATION = 100; // msec
private static final int MIN_GESTURE_DURATION = 150; // msec private static final float MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH = 1.0f / 6.0f;
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 GESTURE_RECOG_SPEED_THRESHOLD = 0.4f; // dip/msec 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 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 public GestureStroke(final int pointerId) {
private static final int DRAWING_GESTURE_FADE_START = 10;
private static final int DRAWING_GESTURE_FADE_RATE = 6;
public GestureStroke(int pointerId) {
mPointerId = 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? // 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); mMinGestureLength = (int)(keyWidth * MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH);
mMinGestureLengthWhileInGesture = (int)( mMinGestureSampleLength = (int)(keyWidth * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH);
keyWidth * MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH_WHILE_IN_GESTURE);
mMinGestureSampleLength = (int)(keyHeight * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT);
} }
public boolean isStartOfAGesture(final int downDuration, final boolean wasInGesture) { public boolean isStartOfAGesture() {
// The tolerance of the time duration and the stroke length to detect the start of a final int size = mEventTimes.getLength();
// gesture stroke should be eased when the previous input was a gesture input. final int downDuration = (size > 0) ? mEventTimes.get(size - 1) : 0;
if (wasInGesture) {
return downDuration > MIN_GESTURE_DURATION_WHILE_IN_GESTURE
&& mLength > mMinGestureLengthWhileInGesture;
}
return downDuration > MIN_GESTURE_DURATION && mLength > mMinGestureLength; return downDuration > MIN_GESTURE_DURATION && mLength > mMinGestureLength;
} }
@ -149,23 +130,29 @@ public class GestureStroke {
} }
private void appendBatchPoints(final InputPointers out, final int size) { private void appendBatchPoints(final InputPointers out, final int size) {
final int length = size - mLastIncrementalBatchSize;
if (length <= 0) {
return;
}
out.append(mPointerId, mEventTimes, mXCoordinates, mYCoordinates, out.append(mPointerId, mEventTimes, mXCoordinates, mYCoordinates,
mLastIncrementalBatchSize, size - mLastIncrementalBatchSize); mLastIncrementalBatchSize, length);
mLastIncrementalBatchSize = size; mLastIncrementalBatchSize = size;
} }
private static float getDistance(final int p1x, final int p1y, private static float getDistance(final int x1, final int y1, final int x2, final int y2) {
final int p2x, final int p2y) { final float dx = x1 - x2;
final float dx = p1x - p2x; final float dy = y1 - y2;
final float dy = p1y - p2y; // Note that, in recent versions of Android, FloatMath is actually slower than
// TODO: Optimize out this {@link FloatMath#sqrt(float)} call. // java.lang.Math due to the way the JIT optimizes java.lang.Math.
return FloatMath.sqrt(dx * dx + dy * dy); return (float)Math.sqrt(dx * dx + dy * dy);
} }
private static float getAngle(final int p1x, final int p1y, final int p2x, final int p2y) { private static float getAngle(final int x1, final int y1, final int x2, final int y2) {
final int dx = p1x - p2x; final int dx = x1 - x2;
final int dy = p1y - p2y; final int dy = y1 - y2;
if (dx == 0 && dy == 0) return 0; 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); return (float)Math.atan2(dy, dx);
} }
@ -176,23 +163,4 @@ public class GestureStroke {
} }
return diff; 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 android.text.TextUtils;
import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.StringUtils; import com.android.inputmethod.latin.StringUtils;
@ -258,7 +259,7 @@ public class KeySpecParser {
throw new IllegalArgumentException(); 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++) { for (int i = start; i < end; i++) {
list.add(array[i]); list.add(array[i]);
} }
@ -438,7 +439,7 @@ public class KeySpecParser {
// Skip empty entry. // Skip empty entry.
if (pos - start > 0) { if (pos - start > 0) {
if (list == null) { if (list == null) {
list = new ArrayList<String>(); list = CollectionUtils.newArrayList();
} }
list.add(text.substring(start, pos)); list.add(text.substring(start, pos));
} }

View File

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

View File

@ -17,13 +17,13 @@
package com.android.inputmethod.keyboard.internal; package com.android.inputmethod.keyboard.internal;
import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.latin.CollectionUtils;
import java.util.HashMap; import java.util.HashMap;
public class KeyboardCodesSet { public class KeyboardCodesSet {
private static final HashMap<String, int[]> sLanguageToCodesMap = private static final HashMap<String, int[]> sLanguageToCodesMap = CollectionUtils.newHashMap();
new HashMap<String, int[]>(); private static final HashMap<String, Integer> sNameToIdMap = CollectionUtils.newHashMap();
private static final HashMap<String, Integer> sNameToIdMap = new HashMap<String, Integer>();
private int[] mCodes = DEFAULT; private int[] mCodes = DEFAULT;

View File

@ -22,6 +22,7 @@ import android.graphics.drawable.Drawable;
import android.util.Log; import android.util.Log;
import android.util.SparseIntArray; import android.util.SparseIntArray;
import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.R;
import java.util.HashMap; import java.util.HashMap;
@ -35,7 +36,7 @@ public class KeyboardIconsSet {
private static final SparseIntArray ATTR_ID_TO_ICON_ID = new SparseIntArray(); private static final SparseIntArray ATTR_ID_TO_ICON_ID = new SparseIntArray();
// Icon name to icon id map. // 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 = { private static final Object[] NAMES_AND_ATTR_IDS = {
"undefined", ATTR_UNDEFINED, "undefined", ATTR_UNDEFINED,

View File

@ -19,6 +19,7 @@ package com.android.inputmethod.keyboard.internal;
import android.content.Context; import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.R;
import java.util.HashMap; import java.util.HashMap;
@ -45,14 +46,12 @@ import java.util.HashMap;
*/ */
public final class KeyboardTextsSet { public final class KeyboardTextsSet {
// Language to texts map. // Language to texts map.
private static final HashMap<String, String[]> sLocaleToTextsMap = private static final HashMap<String, String[]> sLocaleToTextsMap = CollectionUtils.newHashMap();
new HashMap<String, String[]>(); private static final HashMap<String, Integer> sNameToIdsMap = CollectionUtils.newHashMap();
private static final HashMap<String, Integer> sNameToIdsMap =
new HashMap<String, Integer>();
private String[] mTexts; private String[] mTexts;
// Resource name to text map. // 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) { public void setLanguage(final String language) {
mTexts = sLocaleToTextsMap.get(language); mTexts = sLocaleToTextsMap.get(language);

View File

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

View File

@ -23,10 +23,13 @@ import android.graphics.Paint;
import android.graphics.Paint.Align; import android.graphics.Paint.Align;
import android.os.Message; import android.os.Message;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.SparseArray; import android.util.SparseArray;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import com.android.inputmethod.keyboard.PointerTracker; 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.R;
import com.android.inputmethod.latin.StaticInnerHandlerWrapper; import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
@ -46,29 +49,42 @@ public class PreviewPlacerView extends RelativeLayout {
private int mXOrigin; private int mXOrigin;
private int mYOrigin; 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 String mGestureFloatingPreviewText;
private int mLastPointerX;
private int mLastPointerY;
private boolean mDrawsGesturePreviewTrail; private boolean mDrawsGesturePreviewTrail;
private boolean mDrawsGestureFloatingPreviewText; private boolean mDrawsGestureFloatingPreviewText;
private final DrawingHandler mDrawingHandler = new DrawingHandler(this); private final DrawingHandler mDrawingHandler;
private static class DrawingHandler extends StaticInnerHandlerWrapper<PreviewPlacerView> { private static class DrawingHandler extends StaticInnerHandlerWrapper<PreviewPlacerView> {
private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 0; 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); super(outerInstance);
mGesturePreviewTrailParams = gesturePreviewTrailParams;
} }
@Override @Override
public void handleMessage(Message msg) { public void handleMessage(final Message msg) {
final PreviewPlacerView placerView = getOuterInstance(); final PreviewPlacerView placerView = getOuterInstance();
if (placerView == null) return; if (placerView == null) return;
switch (msg.what) { switch (msg.what) {
case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT: case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT:
placerView.setGestureFloatingPreviewText(null); placerView.setGestureFloatingPreviewText(null);
break; break;
case MSG_UPDATE_GESTURE_PREVIEW_TRAIL:
placerView.invalidate();
break;
} }
} }
@ -84,15 +100,32 @@ public class PreviewPlacerView extends RelativeLayout {
placerView.mGestureFloatingPreviewTextLingerTimeout); 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() { public void cancelAllMessages() {
cancelDismissGestureFloatingPreviewText(); 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); super(context);
setWillNotDraw(false); setWillNotDraw(false);
final TypedArray keyboardViewAttr = context.obtainStyledAttributes(
attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
final int gestureFloatingPreviewTextSize = keyboardViewAttr.getDimensionPixelSize( final int gestureFloatingPreviewTextSize = keyboardViewAttr.getDimensionPixelSize(
R.styleable.KeyboardView_gestureFloatingPreviewTextSize, 0); R.styleable.KeyboardView_gestureFloatingPreviewTextSize, 0);
mGestureFloatingPreviewTextColor = keyboardViewAttr.getColor( mGestureFloatingPreviewTextColor = keyboardViewAttr.getColor(
@ -117,6 +150,10 @@ public class PreviewPlacerView extends RelativeLayout {
R.styleable.KeyboardView_gesturePreviewTrailColor, 0); R.styleable.KeyboardView_gesturePreviewTrailColor, 0);
final int gesturePreviewTrailWidth = keyboardViewAttr.getDimensionPixelSize( final int gesturePreviewTrailWidth = keyboardViewAttr.getDimensionPixelSize(
R.styleable.KeyboardView_gesturePreviewTrailWidth, 0); R.styleable.KeyboardView_gesturePreviewTrailWidth, 0);
mGesturePreviewTrailParams = new GesturePreviewTrailParams(keyboardViewAttr);
keyboardViewAttr.recycle();
mDrawingHandler = new DrawingHandler(this, mGesturePreviewTrailParams);
mGesturePaint = new Paint(); mGesturePaint = new Paint();
mGesturePaint.setAntiAlias(true); mGesturePaint.setAntiAlias(true);
@ -132,48 +169,60 @@ public class PreviewPlacerView extends RelativeLayout {
mTextPaint.setTextSize(gestureFloatingPreviewTextSize); mTextPaint.setTextSize(gestureFloatingPreviewTextSize);
} }
public void setOrigin(int x, int y) { public void setOrigin(final int x, final int y) {
mXOrigin = x; mXOrigin = x;
mYOrigin = y; mYOrigin = y;
} }
public void setGesturePreviewMode(boolean drawsGesturePreviewTrail, public void setGesturePreviewMode(final boolean drawsGesturePreviewTrail,
boolean drawsGestureFloatingPreviewText) { final boolean drawsGestureFloatingPreviewText) {
mDrawsGesturePreviewTrail = drawsGesturePreviewTrail; mDrawsGesturePreviewTrail = drawsGesturePreviewTrail;
mDrawsGestureFloatingPreviewText = drawsGestureFloatingPreviewText; mDrawsGestureFloatingPreviewText = drawsGestureFloatingPreviewText;
} }
public void invalidatePointer(PointerTracker tracker) { public void invalidatePointer(final PointerTracker tracker) {
synchronized (mPointers) { GesturePreviewTrail trail;
mPointers.put(tracker.mPointerId, tracker); synchronized (mGesturePreviewTrails) {
// TODO: Should narrow the invalidate region. trail = mGesturePreviewTrails.get(tracker.mPointerId);
invalidate(); 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 @Override
public void onDraw(Canvas canvas) { public void onDraw(final Canvas canvas) {
super.onDraw(canvas); super.onDraw(canvas);
synchronized (mPointers) { canvas.translate(mXOrigin, mYOrigin);
canvas.translate(mXOrigin, mYOrigin); if (mDrawsGesturePreviewTrail) {
final int trackerCount = mPointers.size(); boolean needsUpdatingGesturePreviewTrail = false;
boolean hasDrawnFloatingPreviewText = false; synchronized (mGesturePreviewTrails) {
for (int index = 0; index < trackerCount; index++) { // Trails count == fingers count that have ever been active.
final PointerTracker tracker = mPointers.valueAt(index); final int trailsCount = mGesturePreviewTrails.size();
if (mDrawsGesturePreviewTrail) { for (int index = 0; index < trailsCount; index++) {
tracker.drawGestureTrail(canvas, mGesturePaint); final GesturePreviewTrail trail = mGesturePreviewTrails.valueAt(index);
} needsUpdatingGesturePreviewTrail |=
// TODO: Figure out more cleaner way to draw gesture preview text. trail.drawGestureTrail(canvas, mGesturePaint);
if (mDrawsGestureFloatingPreviewText && !hasDrawnFloatingPreviewText) {
drawGestureFloatingPreviewText(canvas, tracker, mGestureFloatingPreviewText);
hasDrawnFloatingPreviewText = true;
} }
} }
canvas.translate(-mXOrigin, -mYOrigin); if (needsUpdatingGesturePreviewTrail) {
mDrawingHandler.postUpdateGestureTrailPreview();
}
} }
if (mDrawsGestureFloatingPreviewText) {
drawGestureFloatingPreviewText(canvas, mGestureFloatingPreviewText);
}
canvas.translate(-mXOrigin, -mYOrigin);
} }
public void setGestureFloatingPreviewText(String gestureFloatingPreviewText) { public void setGestureFloatingPreviewText(final String gestureFloatingPreviewText) {
mGestureFloatingPreviewText = gestureFloatingPreviewText; mGestureFloatingPreviewText = gestureFloatingPreviewText;
invalidate(); invalidate();
} }
@ -186,15 +235,17 @@ public class PreviewPlacerView extends RelativeLayout {
mDrawingHandler.cancelAllMessages(); mDrawingHandler.cancelAllMessages();
} }
private void drawGestureFloatingPreviewText(Canvas canvas, PointerTracker tracker, private void drawGestureFloatingPreviewText(final Canvas canvas,
String gestureFloatingPreviewText) { final String gestureFloatingPreviewText) {
if (TextUtils.isEmpty(gestureFloatingPreviewText)) { if (TextUtils.isEmpty(gestureFloatingPreviewText)) {
return; return;
} }
final Paint paint = mTextPaint; final Paint paint = mTextPaint;
final int lastX = tracker.getLastX(); // TODO: Figure out how we should deal with the floating preview text with multiple moving
final int lastY = tracker.getLastY(); // fingers.
final int lastX = mLastPointerX;
final int lastY = mLastPointerY;
final int textSize = (int)paint.getTextSize(); final int textSize = (int)paint.getTextSize();
final int canvasWidth = canvas.getWidth(); final int canvasWidth = canvas.getWidth();

View File

@ -91,7 +91,7 @@ public class AdditionalSubtype {
} }
final String[] prefSubtypeArray = prefSubtypes.split(PREF_SUBTYPE_SEPARATOR); final String[] prefSubtypeArray = prefSubtypes.split(PREF_SUBTYPE_SEPARATOR);
final ArrayList<InputMethodSubtype> subtypesList = final ArrayList<InputMethodSubtype> subtypesList =
new ArrayList<InputMethodSubtype>(prefSubtypeArray.length); CollectionUtils.newArrayList(prefSubtypeArray.length);
for (final String prefSubtype : prefSubtypeArray) { for (final String prefSubtype : prefSubtypeArray) {
final InputMethodSubtype subtype = createAdditionalSubtype(prefSubtype); final InputMethodSubtype subtype = createAdditionalSubtype(prefSubtype);
if (subtype.getNameResId() == SubtypeLocale.UNKNOWN_KEYBOARD_LAYOUT) { 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); super(context, android.R.layout.simple_spinner_item);
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_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 InputMethodInfo imi = ImfUtils.getInputMethodInfoOfThisIme(context);
final int count = imi.getSubtypeCount(); final int count = imi.getSubtypeCount();
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
@ -533,7 +533,7 @@ public class AdditionalSubtypeSettings extends PreferenceFragment {
private InputMethodSubtype[] getSubtypes() { private InputMethodSubtype[] getSubtypes() {
final PreferenceGroup group = getPreferenceScreen(); final PreferenceGroup group = getPreferenceScreen();
final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); final ArrayList<InputMethodSubtype> subtypes = CollectionUtils.newArrayList();
final int count = group.getPreferenceCount(); final int count = group.getPreferenceCount();
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
final Preference pref = group.getPreference(i); final Preference pref = group.getPreference(i);

View File

@ -39,7 +39,6 @@ public class AutoCorrection {
} }
final CharSequence lowerCasedWord = word.toString().toLowerCase(); final CharSequence lowerCasedWord = word.toString().toLowerCase();
for (final String key : dictionaries.keySet()) { for (final String key : dictionaries.keySet()) {
if (key.equals(Dictionary.TYPE_WHITELIST)) continue;
final Dictionary dictionary = dictionaries.get(key); final Dictionary dictionary = dictionaries.get(key);
// It's unclear how realistically 'dictionary' can be null, but the monkey is somehow // 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 // 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; int maxFreq = -1;
for (final String key : dictionaries.keySet()) { for (final String key : dictionaries.keySet()) {
if (key.equals(Dictionary.TYPE_WHITELIST)) continue;
final Dictionary dictionary = dictionaries.get(key); final Dictionary dictionary = dictionaries.get(key);
if (null == dictionary) continue; if (null == dictionary) continue;
final int tempFreq = dictionary.getFrequency(word); final int tempFreq = dictionary.getFrequency(word);
@ -75,17 +73,10 @@ public class AutoCorrection {
return maxFreq; return maxFreq;
} }
// Returns true if this is a whitelist entry, or it isn't in any dictionary. // Returns true if this isn't in any dictionary.
public static boolean isWhitelistedOrNotAWord( public static boolean isNotAWord(
final ConcurrentHashMap<String, Dictionary> dictionaries, final ConcurrentHashMap<String, Dictionary> dictionaries,
final CharSequence word, final boolean ignoreCase) { 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); return !isValidWord(dictionaries, word, ignoreCase);
} }

View File

@ -18,6 +18,7 @@ package com.android.inputmethod.latin;
import android.content.Context; import android.content.Context;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.SparseArray;
import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; 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 static final int TYPED_LETTER_MULTIPLIER = 2;
private long mNativeDict; 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 char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_RESULTS];
private final int[] mSpaceIndices = new int[MAX_SPACES]; private final int[] mSpaceIndices = new int[MAX_SPACES];
private final int[] mOutputScores = new int[MAX_RESULTS]; private final int[] mOutputScores = new int[MAX_RESULTS];
@ -59,6 +62,25 @@ public class BinaryDictionary extends Dictionary {
private final boolean mUseFullEditDistance; 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 * Constructor for the binary dictionary. This is supposed to be called from the
* dictionary factory. * dictionary factory.
@ -74,6 +96,7 @@ public class BinaryDictionary extends Dictionary {
final String filename, final long offset, final long length, final String filename, final long offset, final long length,
final boolean useFullEditDistance, final Locale locale, final String dictType) { final boolean useFullEditDistance, final Locale locale, final String dictType) {
super(dictType); super(dictType);
mLocale = locale;
mUseFullEditDistance = useFullEditDistance; mUseFullEditDistance = useFullEditDistance;
loadDictionary(filename, offset, length); loadDictionary(filename, offset, length);
} }
@ -86,18 +109,17 @@ public class BinaryDictionary extends Dictionary {
int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength, int maxWords, int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength, int maxWords,
int maxPredictions); int maxPredictions);
private native void closeNative(long dict); 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 boolean isValidBigramNative(long dict, int[] word1, int[] word2);
private native int getSuggestionsNative(long dict, long proximityInfo, int[] xCoordinates, private native int getSuggestionsNative(long dict, long proximityInfo, long traverseSession,
int[] yCoordinates, int[] times, int[] pointerIds, int[] inputCodes, int codesSize, int[] xCoordinates, int[] yCoordinates, int[] times, int[] pointerIds,
int commitPoint, boolean isGesture, int[] inputCodePoints, int codesSize, int commitPoint, boolean isGesture,
int[] prevWordCodePointArray, boolean useFullEditDistance, char[] outputChars, int[] prevWordCodePointArray, boolean useFullEditDistance, char[] outputChars,
int[] outputScores, int[] outputIndices, int[] outputTypes); int[] outputScores, int[] outputIndices, int[] outputTypes);
private static native float calcNormalizedScoreNative( private static native float calcNormalizedScoreNative(char[] before, char[] after, int score);
char[] before, int beforeLength, char[] after, int afterLength, int score); private static native int editDistanceNative(char[] before, char[] after);
private static native int editDistanceNative(
char[] before, int beforeLength, char[] after, int afterLength);
// TODO: Move native dict into session
private final void loadDictionary(String path, long startOffset, long length) { private final void loadDictionary(String path, long startOffset, long length) {
mNativeDict = openNative(path, startOffset, length, TYPED_LETTER_MULTIPLIER, mNativeDict = openNative(path, startOffset, length, TYPED_LETTER_MULTIPLIER,
FULL_WORD_SCORE_MULTIPLIER, MAX_WORD_LENGTH, MAX_WORDS, MAX_PREDICTIONS); FULL_WORD_SCORE_MULTIPLIER, MAX_WORD_LENGTH, MAX_WORDS, MAX_PREDICTIONS);
@ -106,10 +128,15 @@ public class BinaryDictionary extends Dictionary {
@Override @Override
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
final CharSequence prevWord, final ProximityInfo proximityInfo) { 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; if (!isValidDictionary()) return null;
Arrays.fill(mInputCodes, WordComposer.NOT_A_CODE);
Arrays.fill(mOutputChars, (char) 0); Arrays.fill(mInputCodePoints, Constants.NOT_A_CODE);
Arrays.fill(mOutputScores, 0);
// TODO: toLowerCase in the native code // TODO: toLowerCase in the native code
final int[] prevWordCodePointArray = (null == prevWord) final int[] prevWordCodePointArray = (null == prevWord)
? null : StringUtils.toCodePointArray(prevWord.toString()); ? null : StringUtils.toCodePointArray(prevWord.toString());
@ -119,7 +146,7 @@ public class BinaryDictionary extends Dictionary {
if (composerSize <= 1 || !isGesture) { if (composerSize <= 1 || !isGesture) {
if (composerSize > MAX_WORD_LENGTH - 1) return null; if (composerSize > MAX_WORD_LENGTH - 1) return null;
for (int i = 0; i < composerSize; i++) { for (int i = 0; i < composerSize; i++) {
mInputCodes[i] = composer.getCodeAt(i); mInputCodePoints[i] = composer.getCodeAt(i);
} }
} }
@ -127,24 +154,25 @@ public class BinaryDictionary extends Dictionary {
final int codesSize = isGesture ? ips.getPointerSize() : composerSize; final int codesSize = isGesture ? ips.getPointerSize() : composerSize;
// proximityInfo and/or prevWordForBigrams may not be null. // proximityInfo and/or prevWordForBigrams may not be null.
final int tmpCount = getSuggestionsNative(mNativeDict, final int tmpCount = getSuggestionsNative(mNativeDict,
proximityInfo.getNativeProximityInfo(), ips.getXCoordinates(), proximityInfo.getNativeProximityInfo(), getTraverseSession(sessionId).getSession(),
ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(), ips.getXCoordinates(), ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(),
mInputCodes, codesSize, 0 /* commitPoint */, isGesture, prevWordCodePointArray, mInputCodePoints, codesSize, 0 /* commitPoint */, isGesture, prevWordCodePointArray,
mUseFullEditDistance, mOutputChars, mOutputScores, mSpaceIndices, mOutputTypes); mUseFullEditDistance, mOutputChars, mOutputScores, mSpaceIndices, mOutputTypes);
final int count = Math.min(tmpCount, MAX_PREDICTIONS); 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) { for (int j = 0; j < count; ++j) {
if (composerSize > 0 && mOutputScores[j] < 1) break; if (composerSize > 0 && mOutputScores[j] < 1) break;
final int start = j * MAX_WORD_LENGTH; final int start = j * MAX_WORD_LENGTH;
int len = 0; int len = 0;
while (len < MAX_WORD_LENGTH && mOutputChars[start + len] != 0) { while (len < MAX_WORD_LENGTH && mOutputChars[start + len] != 0) {
++len; ++len;
} }
if (len > 0) { if (len > 0) {
final int score = SuggestedWordInfo.KIND_WHITELIST == mOutputTypes[j]
? SuggestedWordInfo.MAX_SCORE : mOutputScores[j];
suggestions.add(new SuggestedWordInfo( suggestions.add(new SuggestedWordInfo(
new String(mOutputChars, start, len), new String(mOutputChars, start, len), score, mOutputTypes[j], mDictType));
mOutputScores[j], SuggestedWordInfo.KIND_CORRECTION, mDictType));
} }
} }
return suggestions; return suggestions;
@ -155,13 +183,11 @@ public class BinaryDictionary extends Dictionary {
} }
public static float calcNormalizedScore(String before, String after, int score) { public static float calcNormalizedScore(String before, String after, int score) {
return calcNormalizedScoreNative(before.toCharArray(), before.length(), return calcNormalizedScoreNative(before.toCharArray(), after.toCharArray(), score);
after.toCharArray(), after.length(), score);
} }
public static int editDistance(String before, String after) { public static int editDistance(String before, String after) {
return editDistanceNative( return editDistanceNative(before.toCharArray(), after.toCharArray());
before.toCharArray(), before.length(), after.toCharArray(), after.length());
} }
@Override @Override
@ -172,8 +198,8 @@ public class BinaryDictionary extends Dictionary {
@Override @Override
public int getFrequency(CharSequence word) { public int getFrequency(CharSequence word) {
if (word == null) return -1; if (word == null) return -1;
int[] chars = StringUtils.toCodePointArray(word.toString()); int[] codePoints = StringUtils.toCodePointArray(word.toString());
return getFrequencyNative(mNativeDict, chars, chars.length); return getFrequencyNative(mNativeDict, codePoints);
} }
// TODO: Add a batch process version (isValidBigramMultiple?) to avoid excessive numbers of jni // TODO: Add a batch process version (isValidBigramMultiple?) to avoid excessive numbers of jni
@ -186,11 +212,20 @@ public class BinaryDictionary extends Dictionary {
} }
@Override @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(); closeInternal();
} }
private void closeInternal() { private synchronized void closeInternal() {
if (mNativeDict != 0) { if (mNativeDict != 0) {
closeNative(mNativeDict); closeNative(mNativeDict);
mNativeDict = 0; mNativeDict = 0;

View File

@ -99,7 +99,7 @@ public class BinaryDictionaryFileDumper {
} }
try { try {
final List<WordListInfo> list = new ArrayList<WordListInfo>(); final List<WordListInfo> list = CollectionUtils.newArrayList();
do { do {
final String wordListId = c.getString(0); final String wordListId = c.getString(0);
final String wordListLocale = c.getString(1); final String wordListLocale = c.getString(1);
@ -267,7 +267,7 @@ public class BinaryDictionaryFileDumper {
final ContentResolver resolver = context.getContentResolver(); final ContentResolver resolver = context.getContentResolver();
final List<WordListInfo> idList = getWordListWordListInfos(locale, context, final List<WordListInfo> idList = getWordListWordListInfos(locale, context,
hasDefaultWordList); hasDefaultWordList);
final List<AssetFileAddress> fileAddressList = new ArrayList<AssetFileAddress>(); final List<AssetFileAddress> fileAddressList = CollectionUtils.newArrayList();
for (WordListInfo id : idList) { for (WordListInfo id : idList) {
final AssetFileAddress afd = cacheWordList(id.mId, id.mLocale, resolver, context); final AssetFileAddress afd = cacheWordList(id.mId, id.mLocale, resolver, context);
if (null != afd) { if (null != afd) {

View File

@ -16,6 +16,8 @@
package com.android.inputmethod.latin; package com.android.inputmethod.latin;
import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.NameNotFoundException;
@ -23,6 +25,10 @@ import android.content.res.AssetFileDescriptor;
import android.util.Log; import android.util.Log;
import java.io.File; 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.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
@ -51,6 +57,9 @@ class BinaryDictionaryGetter {
private static final String MAIN_DICTIONARY_CATEGORY = "main"; private static final String MAIN_DICTIONARY_CATEGORY = "main";
public static final String ID_CATEGORY_SEPARATOR = ":"; 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 // Prevents this from being instantiated
private BinaryDictionaryGetter() {} private BinaryDictionaryGetter() {}
@ -254,8 +263,7 @@ class BinaryDictionaryGetter {
final Context context) { final Context context) {
final File[] directoryList = getCachedDirectoryList(context); final File[] directoryList = getCachedDirectoryList(context);
if (null == directoryList) return EMPTY_FILE_ARRAY; if (null == directoryList) return EMPTY_FILE_ARRAY;
final HashMap<String, FileAndMatchLevel> cacheFiles = final HashMap<String, FileAndMatchLevel> cacheFiles = CollectionUtils.newHashMap();
new HashMap<String, FileAndMatchLevel>();
for (File directory : directoryList) { for (File directory : directoryList) {
if (!directory.isDirectory()) continue; if (!directory.isDirectory()) continue;
final String dirLocale = getWordListIdFromFileName(directory.getName()); final String dirLocale = getWordListIdFromFileName(directory.getName());
@ -336,6 +344,54 @@ class BinaryDictionaryGetter {
return MAIN_DICTIONARY_CATEGORY.equals(idArray[0]); 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. * 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); final DictPackSettings dictPackSettings = new DictPackSettings(context);
boolean foundMainDict = false; 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 // cachedWordLists may not be null, see doc for getCachedDictionaryList
for (final File f : cachedWordLists) { for (final File f : cachedWordLists) {
final String wordListId = getWordListIdFromFileName(f.getName()); final String wordListId = getWordListIdFromFileName(f.getName());
if (isMainWordListId(wordListId)) { final boolean canUse = f.canRead() && hackCanUseDictionaryFile(locale, f);
if (canUse && isMainWordListId(wordListId)) {
foundMainDict = true; foundMainDict = true;
} }
if (!dictPackSettings.isWordListActive(wordListId)) continue; if (!dictPackSettings.isWordListActive(wordListId)) continue;
if (f.canRead()) { if (canUse) {
fileList.add(AssetFileAddress.makeFromFileName(f.getPath())); fileList.add(AssetFileAddress.makeFromFileName(f.getPath()));
} else { } 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() { private Constants() {
// This utility class is not publicly instantiable. // 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"; public static final String TYPE_USER = "user";
// User history dictionary internal to LatinIME. // User history dictionary internal to LatinIME.
public static final String TYPE_USER_HISTORY = "history"; public static final String TYPE_USER_HISTORY = "history";
public static final String TYPE_WHITELIST ="whitelist";
protected final String mDictType; protected final String mDictType;
public Dictionary(final String dictType) { public Dictionary(final String dictType) {
@ -62,6 +61,13 @@ public abstract class Dictionary {
abstract public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, abstract public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
final CharSequence prevWord, final ProximityInfo proximityInfo); 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 * Checks if the given word occurs in the dictionary
* @param word the word to search for. The search should be case-insensitive. * @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) { public DictionaryCollection(final String dictType) {
super(dictType); super(dictType);
mDictionaries = new CopyOnWriteArrayList<Dictionary>(); mDictionaries = CollectionUtils.newCopyOnWriteArrayList();
} }
public DictionaryCollection(final String dictType, Dictionary... dictionaries) { public DictionaryCollection(final String dictType, Dictionary... dictionaries) {
super(dictType); super(dictType);
if (null == dictionaries) { if (null == dictionaries) {
mDictionaries = new CopyOnWriteArrayList<Dictionary>(); mDictionaries = CollectionUtils.newCopyOnWriteArrayList();
} else { } else {
mDictionaries = new CopyOnWriteArrayList<Dictionary>(dictionaries); mDictionaries = CollectionUtils.newCopyOnWriteArrayList(dictionaries);
mDictionaries.removeAll(Collections.singleton(null)); mDictionaries.removeAll(Collections.singleton(null));
} }
} }
public DictionaryCollection(final String dictType, Collection<Dictionary> dictionaries) { public DictionaryCollection(final String dictType, Collection<Dictionary> dictionaries) {
super(dictType); super(dictType);
mDictionaries = new CopyOnWriteArrayList<Dictionary>(dictionaries); mDictionaries = CollectionUtils.newCopyOnWriteArrayList(dictionaries);
mDictionaries.removeAll(Collections.singleton(null)); 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) // dictionary and add the rest to it if not null, hence the get(0)
ArrayList<SuggestedWordInfo> suggestions = dictionaries.get(0).getSuggestions(composer, ArrayList<SuggestedWordInfo> suggestions = dictionaries.get(0).getSuggestions(composer,
prevWord, proximityInfo); prevWord, proximityInfo);
if (null == suggestions) suggestions = new ArrayList<SuggestedWordInfo>(); if (null == suggestions) suggestions = CollectionUtils.newArrayList();
final int length = dictionaries.size(); final int length = dictionaries.size();
for (int i = 1; i < length; ++ i) { for (int i = 1; i < length; ++ i) {
final ArrayList<SuggestedWordInfo> sugg = dictionaries.get(i).getSuggestions(composer, final ArrayList<SuggestedWordInfo> sugg = dictionaries.get(i).getSuggestions(composer,

View File

@ -53,7 +53,7 @@ public class DictionaryFactory {
createBinaryDictionary(context, locale)); createBinaryDictionary(context, locale));
} }
final LinkedList<Dictionary> dictList = new LinkedList<Dictionary>(); final LinkedList<Dictionary> dictList = CollectionUtils.newLinkedList();
final ArrayList<AssetFileAddress> assetFileList = final ArrayList<AssetFileAddress> assetFileList =
BinaryDictionaryGetter.getDictionaryFiles(locale, context); BinaryDictionaryGetter.getDictionaryFiles(locale, context);
if (null != assetFileList) { if (null != assetFileList) {

View File

@ -62,7 +62,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
* that filename. * that filename.
*/ */
private static final HashMap<String, DictionaryController> sSharedDictionaryControllers = private static final HashMap<String, DictionaryController> sSharedDictionaryControllers =
new HashMap<String, DictionaryController>(); CollectionUtils.newHashMap();
/** The application context. */ /** The application context. */
protected final Context mContext; protected final Context mContext;
@ -159,9 +159,9 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
* the native side. * the native side.
*/ */
public void clearFusionDictionary() { public void clearFusionDictionary() {
final HashMap<String, String> attributes = CollectionUtils.newHashMap();
mFusionDictionary = new FusionDictionary(new Node(), mFusionDictionary = new FusionDictionary(new Node(),
new FusionDictionary.DictionaryOptions(new HashMap<String, String>(), false, new FusionDictionary.DictionaryOptions(attributes, false, false));
false));
} }
/** /**
@ -175,7 +175,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
mFusionDictionary.add(word, frequency, null); mFusionDictionary.add(word, frequency, null);
} else { } else {
// TODO: Do this in the subclass, with this class taking an arraylist. // 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)); shortcutTargets.add(new WeightedString(shortcutTarget, frequency));
mFusionDictionary.add(word, frequency, shortcutTargets); mFusionDictionary.add(word, frequency, shortcutTargets);
} }

View File

@ -19,7 +19,6 @@ package com.android.inputmethod.latin;
import android.content.Context; import android.content.Context;
import android.text.TextUtils; import android.text.TextUtils;
import com.android.inputmethod.keyboard.KeyDetector;
import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
@ -231,7 +230,7 @@ public class ExpandableDictionary extends Dictionary {
childNode.mTerminal = true; childNode.mTerminal = true;
if (isShortcutOnly) { if (isShortcutOnly) {
if (null == childNode.mShortcutTargets) { if (null == childNode.mShortcutTargets) {
childNode.mShortcutTargets = new ArrayList<char[]>(); childNode.mShortcutTargets = CollectionUtils.newArrayList();
} }
childNode.mShortcutTargets.add(shortcutTarget.toCharArray()); childNode.mShortcutTargets.add(shortcutTarget.toCharArray());
} else { } else {
@ -251,7 +250,7 @@ public class ExpandableDictionary extends Dictionary {
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
final CharSequence prevWord, final ProximityInfo proximityInfo) { final CharSequence prevWord, final ProximityInfo proximityInfo) {
if (reloadDictionaryIfRequired()) return null; if (reloadDictionaryIfRequired()) return null;
if (composer.size() <= 1) { if (composer.size() > 1) {
if (composer.size() >= BinaryDictionary.MAX_WORD_LENGTH) { if (composer.size() >= BinaryDictionary.MAX_WORD_LENGTH) {
return null; return null;
} }
@ -260,7 +259,7 @@ public class ExpandableDictionary extends Dictionary {
return suggestions; return suggestions;
} else { } else {
if (TextUtils.isEmpty(prevWord)) return null; if (TextUtils.isEmpty(prevWord)) return null;
final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>(); final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
runBigramReverseLookUp(prevWord, suggestions); runBigramReverseLookUp(prevWord, suggestions);
return suggestions; return suggestions;
} }
@ -279,7 +278,7 @@ public class ExpandableDictionary extends Dictionary {
protected ArrayList<SuggestedWordInfo> getWordsInner(final WordComposer codes, protected ArrayList<SuggestedWordInfo> getWordsInner(final WordComposer codes,
final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) { final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) {
final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>(); final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
mInputLength = codes.size(); mInputLength = codes.size();
if (mCodes.length < mInputLength) mCodes = new int[mInputLength][]; if (mCodes.length < mInputLength) mCodes = new int[mInputLength][];
final InputPointers ips = codes.getInputPointers(); final InputPointers ips = codes.getInputPointers();
@ -292,9 +291,9 @@ public class ExpandableDictionary extends Dictionary {
mCodes[i] = new int[ProximityInfo.MAX_PROXIMITY_CHARS_SIZE]; mCodes[i] = new int[ProximityInfo.MAX_PROXIMITY_CHARS_SIZE];
} }
final int x = xCoordinates != null && i < xCoordinates.length ? 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 ? 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]); proximityInfo.fillArrayWithNearestKeyCodes(x, y, codes.getCodeAt(i), mCodes[i]);
} }
mMaxDepth = mInputLength * 3; mMaxDepth = mInputLength * 3;
@ -487,7 +486,7 @@ public class ExpandableDictionary extends Dictionary {
for (int j = 0; j < alternativesSize; j++) { for (int j = 0; j < alternativesSize; j++) {
final int addedAttenuation = (j > 0 ? 1 : 2); final int addedAttenuation = (j > 0 ? 1 : 2);
final int currentChar = currentChars[j]; final int currentChar = currentChars[j];
if (currentChar == KeyDetector.NOT_A_CODE) { if (currentChar == Constants.NOT_A_CODE) {
break; break;
} }
if (currentChar == lowerC || currentChar == c) { if (currentChar == lowerC || currentChar == c) {
@ -551,7 +550,7 @@ public class ExpandableDictionary extends Dictionary {
Node secondWord = searchWord(mRoots, word2, 0, null); Node secondWord = searchWord(mRoots, word2, 0, null);
LinkedList<NextWord> bigrams = firstWord.mNGrams; LinkedList<NextWord> bigrams = firstWord.mNGrams;
if (bigrams == null || bigrams.size() == 0) { if (bigrams == null || bigrams.size() == 0) {
firstWord.mNGrams = new LinkedList<NextWord>(); firstWord.mNGrams = CollectionUtils.newLinkedList();
bigrams = firstWord.mNGrams; bigrams = firstWord.mNGrams;
} else { } else {
for (NextWord nw : bigrams) { for (NextWord nw : bigrams) {

View File

@ -29,10 +29,12 @@ public class InputAttributes {
final public boolean mInputTypeNoAutoCorrect; final public boolean mInputTypeNoAutoCorrect;
final public boolean mIsSettingsSuggestionStripOn; final public boolean mIsSettingsSuggestionStripOn;
final public boolean mApplicationSpecifiedCompletionOn; final public boolean mApplicationSpecifiedCompletionOn;
final private int mInputType;
public InputAttributes(final EditorInfo editorInfo, final boolean isFullscreenMode) { public InputAttributes(final EditorInfo editorInfo, final boolean isFullscreenMode) {
final int inputType = null != editorInfo ? editorInfo.inputType : 0; final int inputType = null != editorInfo ? editorInfo.inputType : 0;
final int inputClass = inputType & InputType.TYPE_MASK_CLASS; final int inputClass = inputType & InputType.TYPE_MASK_CLASS;
mInputType = inputType;
if (inputClass != InputType.TYPE_CLASS_TEXT) { if (inputClass != InputType.TYPE_CLASS_TEXT) {
// If we are not looking at a TYPE_CLASS_TEXT field, the following strange // 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 // 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") @SuppressWarnings("unused")
private void dumpFlags(final int inputType) { private void dumpFlags(final int inputType) {
Log.i(TAG, "Input class:"); Log.i(TAG, "Input class:");

View File

@ -93,7 +93,7 @@ public class InputPointers {
} }
mXCoordinates.append(xCoordinates, startPos, length); mXCoordinates.append(xCoordinates, startPos, length);
mYCoordinates.append(yCoordinates, startPos, length); mYCoordinates.append(yCoordinates, startPos, length);
mPointerIds.fill(pointerId, startPos, length); mPointerIds.fill(pointerId, mPointerIds.getLength(), length);
mTimes.append(times, startPos, length); mTimes.append(times, startPos, length);
} }
@ -124,4 +124,10 @@ public class InputPointers {
public int[] getTimes() { public int[] getTimes() {
return mTimes.getPrimitiveArray(); 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; mPrefs = prefs;
LatinImeLogger.init(this, prefs); LatinImeLogger.init(this, prefs);
if (ProductionFlag.IS_EXPERIMENTAL) { if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.getInstance().init(this, prefs, mKeyboardSwitcher); ResearchLogger.getInstance().init(this, prefs);
} }
InputMethodManagerCompatWrapper.init(this); InputMethodManagerCompatWrapper.init(this);
SubtypeSwitcher.init(this); SubtypeSwitcher.init(this);
@ -381,18 +381,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
ImfUtils.setAdditionalInputMethodSubtypes(this, mCurrentSettings.getAdditionalSubtypes()); ImfUtils.setAdditionalInputMethodSubtypes(this, mCurrentSettings.getAdditionalSubtypes());
Utils.GCUtils.getInstance().reset(); initSuggest();
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; mDisplayOrientation = res.getConfiguration().orientation;
@ -416,7 +405,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} }
// Has to be package-visible for unit tests // 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() // 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. // is not guaranteed. It may even be called at the same time on a different thread.
if (null == mPrefs) mPrefs = PreferenceManager.getDefaultSharedPreferences(this); if (null == mPrefs) mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
@ -433,10 +423,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary()); resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary());
} }
// Note that this method is called from a non-UI thread.
@Override @Override
public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable) { public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable) {
mIsMainDictionaryAvailable = isMainDictionaryAvailable; mIsMainDictionaryAvailable = isMainDictionaryAvailable;
updateKeyboardViewGestureHandlingModeByMainDictionaryAvailability(); final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
if (mainKeyboardView != null) {
mainKeyboardView.setMainDictionaryAvailability(isMainDictionaryAvailable);
}
} }
private void initSuggest() { private void initSuggest() {
@ -517,7 +511,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
/* package private */ void resetSuggestMainDict() { /* package private */ void resetSuggestMainDict() {
final Locale subtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale(); final Locale subtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
mSuggest.resetMainDict(this, subtypeLocale); mSuggest.resetMainDict(this, subtypeLocale, this /* SuggestInitializationListener */);
mIsMainDictionaryAvailable = DictionaryFactory.isDictionaryAvailable(this, subtypeLocale); mIsMainDictionaryAvailable = DictionaryFactory.isDictionaryAvailable(this, subtypeLocale);
} }
@ -536,7 +530,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override @Override
public void onConfigurationChanged(Configuration conf) { 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 orientation changed while predicting, commit the change
if (mDisplayOrientation != conf.orientation) { if (mDisplayOrientation != conf.orientation) {
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() // 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. // is not guaranteed. It may even be called at the same time on a different thread.
mSubtypeSwitcher.updateSubtype(subtype); mSubtypeSwitcher.updateSubtype(subtype);
loadKeyboard();
} }
private void onStartInputInternal(EditorInfo editorInfo, boolean restarting) { 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. // Forward this event to the accessibility utilities, if enabled.
final AccessibilityUtils accessUtils = AccessibilityUtils.getInstance(); final AccessibilityUtils accessUtils = AccessibilityUtils.getInstance();
if (accessUtils.isTouchExplorationEnabled()) { if (accessUtils.isTouchExplorationEnabled()) {
accessUtils.onStartInputViewInternal(editorInfo, restarting); accessUtils.onStartInputViewInternal(mainKeyboardView, editorInfo, restarting);
} }
if (!restarting) { final boolean selectionChanged = mLastSelectionStart != editorInfo.initialSelStart
mSubtypeSwitcher.updateParametersOnStartInputView(); || 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. // The EditorInfo might have a flag that affects fullscreen mode.
@ -675,9 +685,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
updateFullscreenMode(); updateFullscreenMode();
mApplicationSpecifiedCompletions = null; mApplicationSpecifiedCompletions = null;
final boolean selectionChanged = mLastSelectionStart != editorInfo.initialSelStart if (isDifferentTextField || selectionChanged) {
|| mLastSelectionEnd != editorInfo.initialSelEnd;
if (!restarting || selectionChanged) {
// If the selection changed, we reset the input state. Essentially, we come here with // 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 // 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 // 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(); mainKeyboardView.closing();
loadSettings(); loadSettings();
@ -701,7 +709,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} }
switcher.loadKeyboard(editorInfo, mCurrentSettings); switcher.loadKeyboard(editorInfo, mCurrentSettings);
updateKeyboardViewGestureHandlingModeByMainDictionaryAvailability();
} }
setSuggestionStripShownInternal( setSuggestionStripShownInternal(
isSuggestionsStripVisible(), /* needsInputViewShown */ false); isSuggestionsStripVisible(), /* needsInputViewShown */ false);
@ -719,8 +726,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mHandler.cancelUpdateSuggestionStrip(); mHandler.cancelUpdateSuggestionStrip();
mHandler.cancelDoubleSpacesTimer(); mHandler.cancelDoubleSpacesTimer();
mainKeyboardView.setMainDictionaryAvailability(mIsMainDictionaryAvailable);
mainKeyboardView.setKeyPreviewPopupEnabled(mCurrentSettings.mKeyPreviewPopupOn, mainKeyboardView.setKeyPreviewPopupEnabled(mCurrentSettings.mKeyPreviewPopupOn,
mCurrentSettings.mKeyPreviewPopupDismissDelay); mCurrentSettings.mKeyPreviewPopupDismissDelay);
mainKeyboardView.setGestureHandlingEnabledByUser(mCurrentSettings.mGestureInputEnabled);
mainKeyboardView.setGesturePreviewMode(mCurrentSettings.mGesturePreviewTrailEnabled,
mCurrentSettings.mGestureFloatingPreviewTextEnabled);
if (TRACE) Debug.startMethodTracing("/data/trace/latinime"); 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; if (!mCurrentSettings.isApplicationSpecifiedCompletionsOn()) return;
mApplicationSpecifiedCompletions = applicationSpecifiedCompletions; mApplicationSpecifiedCompletions = applicationSpecifiedCompletions;
if (applicationSpecifiedCompletions == null) { if (applicationSpecifiedCompletions == null) {
clearSuggestionStrip(); clearSuggestionStrip();
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_onDisplayCompletions(null);
}
return; return;
} }
@ -926,6 +937,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// this case? This says to keep whatever the user typed. // this case? This says to keep whatever the user typed.
mWordComposer.setAutoCorrection(mWordComposer.getTypedWord()); mWordComposer.setAutoCorrection(mWordComposer.getTypedWord());
setSuggestionStripShown(true); setSuggestionStripShown(true);
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_onDisplayCompletions(applicationSpecifiedCompletions);
}
} }
private void setSuggestionStripShownInternal(boolean shown, boolean needsInputViewShown) { private void setSuggestionStripShownInternal(boolean shown, boolean needsInputViewShown) {
@ -1046,9 +1060,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final CharSequence typedWord = mWordComposer.getTypedWord(); final CharSequence typedWord = mWordComposer.getTypedWord();
if (typedWord.length() > 0) { if (typedWord.length() > 0) {
mConnection.commitText(typedWord, 1); mConnection.commitText(typedWord, 1);
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_commitText(typedWord);
}
final CharSequence prevWord = addToUserHistoryDictionary(typedWord); final CharSequence prevWord = addToUserHistoryDictionary(typedWord);
mLastComposedWord = mWordComposer.commitWord( mLastComposedWord = mWordComposer.commitWord(
LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, typedWord.toString(), LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, typedWord.toString(),
@ -1084,18 +1095,27 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return mConnection.getCursorCapsMode(inputType); 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() { private void swapSwapperAndSpace() {
CharSequence lastTwo = mConnection.getTextBeforeCursor(2, 0); CharSequence lastTwo = mConnection.getTextBeforeCursor(2, 0);
// It is guaranteed lastTwo.charAt(1) is a swapper - else this method is not called. // It is guaranteed lastTwo.charAt(1) is a swapper - else this method is not called.
if (lastTwo != null && lastTwo.length() == 2 if (lastTwo != null && lastTwo.length() == 2
&& lastTwo.charAt(0) == Keyboard.CODE_SPACE) { && lastTwo.charAt(0) == Keyboard.CODE_SPACE) {
mConnection.deleteSurroundingText(2, 0); mConnection.deleteSurroundingText(2, 0);
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_deleteSurroundingText(2);
}
mConnection.commitText(lastTwo.charAt(1) + " ", 1); mConnection.commitText(lastTwo.charAt(1) + " ", 1);
if (ProductionFlag.IS_EXPERIMENTAL) { if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_swapSwapperAndSpaceWhileInBatchEdit(); ResearchLogger.latinIME_swapSwapperAndSpace();
} }
mKeyboardSwitcher.updateShiftState(); mKeyboardSwitcher.updateShiftState();
} }
@ -1112,9 +1132,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mHandler.cancelDoubleSpacesTimer(); mHandler.cancelDoubleSpacesTimer();
mConnection.deleteSurroundingText(2, 0); mConnection.deleteSurroundingText(2, 0);
mConnection.commitText(". ", 1); mConnection.commitText(". ", 1);
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_doubleSpaceAutoPeriod();
}
mKeyboardSwitcher.updateShiftState(); mKeyboardSwitcher.updateShiftState();
return true; return true;
} }
@ -1178,9 +1195,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private void performEditorAction(int actionId) { private void performEditorAction(int actionId) {
mConnection.performEditorAction(actionId); mConnection.performEditorAction(actionId);
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_performEditorAction(actionId);
}
} }
private void handleLanguageSwitchKey() { private void handleLanguageSwitchKey() {
@ -1217,6 +1231,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}. // For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}.
if (code >= '0' && code <= '9') { if (code >= '0' && code <= '9') {
super.sendKeyChar((char)code); super.sendKeyChar((char)code);
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_sendKeyCodePoint(code);
}
return; return;
} }
@ -1233,9 +1250,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final String text = new String(new int[] { code }, 0, 1); final String text = new String(new int[] { code }, 0, 1);
mConnection.commitText(text, text.length()); mConnection.commitText(text, text.length());
} }
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_sendKeyCodePoint(code);
}
} }
// Implementation of {@link KeyboardActionListener}. // Implementation of {@link KeyboardActionListener}.
@ -1247,11 +1261,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} }
mLastKeyTime = when; mLastKeyTime = when;
mConnection.beginBatchEdit(); mConnection.beginBatchEdit();
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_onCodeInput(primaryCode, x, y);
}
final KeyboardSwitcher switcher = mKeyboardSwitcher; final KeyboardSwitcher switcher = mKeyboardSwitcher;
// The space state depends only on the last character pressed and its own previous // 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 // 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(); onSettingsKeyPressed();
break; break;
case Keyboard.CODE_SHORTCUT: case Keyboard.CODE_SHORTCUT:
mSubtypeSwitcher.switchToShortcutIME(); mSubtypeSwitcher.switchToShortcutIME(this);
break; break;
case Keyboard.CODE_ACTION_ENTER: case Keyboard.CODE_ACTION_ENTER:
performEditorAction(getActionId(switcher.getKeyboard())); performEditorAction(getActionId(switcher.getKeyboard()));
@ -1317,8 +1326,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
keyX = x; keyX = x;
keyY = y; keyY = y;
} else { } else {
keyX = NOT_A_TOUCH_COORDINATE; keyX = Constants.NOT_A_COORDINATE;
keyY = NOT_A_TOUCH_COORDINATE; keyY = Constants.NOT_A_COORDINATE;
} }
handleCharacter(primaryCode, keyX, keyY, spaceState); handleCharacter(primaryCode, keyX, keyY, spaceState);
} }
@ -1333,22 +1342,24 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mLastComposedWord.deactivate(); mLastComposedWord.deactivate();
mEnteredText = null; mEnteredText = null;
mConnection.endBatchEdit(); mConnection.endBatchEdit();
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_onCodeInput(primaryCode, x, y);
}
} }
// Called from PointerTracker through the KeyboardActionListener interface // Called from PointerTracker through the KeyboardActionListener interface
@Override @Override
public void onTextInput(CharSequence rawText) { public void onTextInput(CharSequence rawText) {
mConnection.beginBatchEdit(); mConnection.beginBatchEdit();
commitTyped(LastComposedWord.NOT_A_SEPARATOR); if (mWordComposer.isComposingWord()) {
commitCurrentAutoCorrection(LastComposedWord.NOT_A_SEPARATOR);
}
mHandler.postUpdateSuggestionStrip(); mHandler.postUpdateSuggestionStrip();
final CharSequence text = specificTldProcessingOnTextInput(rawText); final CharSequence text = specificTldProcessingOnTextInput(rawText);
if (SPACE_STATE_PHANTOM == mSpaceState) { if (SPACE_STATE_PHANTOM == mSpaceState) {
sendKeyCodePoint(Keyboard.CODE_SPACE); sendKeyCodePoint(Keyboard.CODE_SPACE);
} }
mConnection.commitText(text, 1); mConnection.commitText(text, 1);
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_commitText(text);
}
mConnection.endBatchEdit(); mConnection.endBatchEdit();
mKeyboardSwitcher.updateShiftState(); mKeyboardSwitcher.updateShiftState();
mKeyboardSwitcher.onCodeInput(Keyboard.CODE_OUTPUT_TEXT); mKeyboardSwitcher.onCodeInput(Keyboard.CODE_OUTPUT_TEXT);
@ -1361,15 +1372,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public void onStartBatchInput() { public void onStartBatchInput() {
mConnection.beginBatchEdit(); mConnection.beginBatchEdit();
if (mWordComposer.isComposingWord()) { if (mWordComposer.isComposingWord()) {
commitTyped(LastComposedWord.NOT_A_SEPARATOR); commitCurrentAutoCorrection(LastComposedWord.NOT_A_SEPARATOR);
mExpectingUpdateSelection = true; mExpectingUpdateSelection = true;
// TODO: Can we remove this? // TODO: Can we remove this?
mSpaceState = SPACE_STATE_PHANTOM; mSpaceState = SPACE_STATE_PHANTOM;
} }
mConnection.endBatchEdit(); mConnection.endBatchEdit();
// TODO: Should handle TextUtils.CAP_MODE_CHARACTER. // TODO: Should handle TextUtils.CAP_MODE_CHARACTER.
mWordComposer.setAutoCapitalized( mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode());
getCurrentAutoCapsState() != Constants.TextUtils.CAP_MODE_OFF);
} }
@Override @Override
@ -1447,9 +1457,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// like the smiley key or the .com key. // like the smiley key or the .com key.
final int length = mEnteredText.length(); final int length = mEnteredText.length();
mConnection.deleteSurroundingText(length, 0); mConnection.deleteSurroundingText(length, 0);
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_deleteSurroundingText(length);
}
// If we have mEnteredText, then we know that mHasUncommittedTypedChars == false. // If we have mEnteredText, then we know that mHasUncommittedTypedChars == false.
// In addition we know that spaceState is false, and that we should not be // 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. // reverting any autocorrect at this point. So we can safely return.
@ -1469,9 +1476,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mHandler.postUpdateSuggestionStrip(); mHandler.postUpdateSuggestionStrip();
} else { } else {
mConnection.deleteSurroundingText(1, 0); mConnection.deleteSurroundingText(1, 0);
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_deleteSurroundingText(1);
}
} }
} else { } else {
if (mLastComposedWord.canRevertCommit()) { if (mLastComposedWord.canRevertCommit()) {
@ -1500,9 +1504,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final int lengthToDelete = mLastSelectionEnd - mLastSelectionStart; final int lengthToDelete = mLastSelectionEnd - mLastSelectionStart;
mConnection.setSelection(mLastSelectionEnd, mLastSelectionEnd); mConnection.setSelection(mLastSelectionEnd, mLastSelectionEnd);
mConnection.deleteSurroundingText(lengthToDelete, 0); mConnection.deleteSurroundingText(lengthToDelete, 0);
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_deleteSurroundingText(lengthToDelete);
}
} else { } else {
// There is no selection, just delete one character. // There is no selection, just delete one character.
if (NOT_A_CURSOR_POSITION == mLastSelectionEnd) { if (NOT_A_CURSOR_POSITION == mLastSelectionEnd) {
@ -1521,14 +1522,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} else { } else {
mConnection.deleteSurroundingText(1, 0); mConnection.deleteSurroundingText(1, 0);
} }
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_deleteSurroundingText(1);
}
if (mDeleteCount > DELETE_ACCELERATE_AT) { if (mDeleteCount > DELETE_ACCELERATE_AT) {
mConnection.deleteSurroundingText(1, 0); mConnection.deleteSurroundingText(1, 0);
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_deleteSurroundingText(1);
}
} }
} }
if (mCurrentSettings.isSuggestionsRequested(mDisplayOrientation)) { if (mCurrentSettings.isSuggestionsRequested(mDisplayOrientation)) {
@ -1604,13 +1599,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mWordComposer.add(primaryCode, keyX, keyY); mWordComposer.add(primaryCode, keyX, keyY);
// If it's the first letter, make note of auto-caps state // If it's the first letter, make note of auto-caps state
if (mWordComposer.size() == 1) { if (mWordComposer.size() == 1) {
mWordComposer.setAutoCapitalized( mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode());
getCurrentAutoCapsState() != Constants.TextUtils.CAP_MODE_OFF);
} }
mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1); mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
} else { } else {
final boolean swapWeakSpace = maybeStripSpace(primaryCode, final boolean swapWeakSpace = maybeStripSpace(primaryCode,
spaceState, KeyboardActionListener.SUGGESTION_STRIP_COORDINATE == x); spaceState, Constants.SUGGESTION_STRIP_COORDINATE == x);
sendKeyCodePoint(primaryCode); sendKeyCodePoint(primaryCode);
@ -1640,7 +1634,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} }
final boolean swapWeakSpace = maybeStripSpace(primaryCode, spaceState, final boolean swapWeakSpace = maybeStripSpace(primaryCode, spaceState,
KeyboardActionListener.SUGGESTION_STRIP_COORDINATE == x); Constants.SUGGESTION_STRIP_COORDINATE == x);
if (SPACE_STATE_PHANTOM == spaceState && if (SPACE_STATE_PHANTOM == spaceState &&
mCurrentSettings.isPhantomSpacePromotingSymbol(primaryCode)) { mCurrentSettings.isPhantomSpacePromotingSymbol(primaryCode)) {
@ -1687,6 +1681,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
Utils.Stats.onSeparator((char)primaryCode, x, y); Utils.Stats.onSeparator((char)primaryCode, x, y);
mHandler.postUpdateShiftState();
return didAutoCorrect; return didAutoCorrect;
} }
@ -1707,7 +1702,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// TODO: make this private // TODO: make this private
// Outside LatinIME, only used by the test suite. // Outside LatinIME, only used by the test suite.
/* package for tests */ boolean isShowingPunctuationList() { /* package for tests */
boolean isShowingPunctuationList() {
if (mSuggestionStripView == null) return false; if (mSuggestionStripView == null) return false;
return mCurrentSettings.mSuggestPuncList == mSuggestionStripView.getSuggestions(); return mCurrentSettings.mSuggestPuncList == mSuggestionStripView.getSuggestions();
} }
@ -1853,10 +1849,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
+ "is empty? Impossible! I must commit suicide."); + "is empty? Impossible! I must commit suicide.");
} }
Utils.Stats.onAutoCorrection(typedWord, autoCorrection.toString(), separatorCodePoint); Utils.Stats.onAutoCorrection(typedWord, autoCorrection.toString(), separatorCodePoint);
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_commitCurrentAutoCorrection(typedWord,
autoCorrection.toString());
}
mExpectingUpdateSelection = true; mExpectingUpdateSelection = true;
commitChosenWord(autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD, commitChosenWord(autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD,
separatorCodePoint); separatorCodePoint);
@ -1873,8 +1865,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Called from {@link SuggestionStripView} through the {@link SuggestionStripView#Listener} // Called from {@link SuggestionStripView} through the {@link SuggestionStripView#Listener}
// interface // interface
@Override @Override
public void pickSuggestionManually(final int index, final CharSequence suggestion, public void pickSuggestionManually(final int index, final CharSequence suggestion) {
final int x, final int y) {
final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions(); final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions();
// If this is a punctuation picked from the suggestion strip, pass it to onCodeInput // If this is a punctuation picked from the suggestion strip, pass it to onCodeInput
if (suggestion.length() == 1 && isShowingPunctuationList()) { if (suggestion.length() == 1 && isShowingPunctuationList()) {
@ -1882,13 +1873,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// So, LatinImeLogger logs "" as a user's input. // So, LatinImeLogger logs "" as a user's input.
LatinImeLogger.logOnManualSuggestion("", suggestion.toString(), index, suggestedWords); LatinImeLogger.logOnManualSuggestion("", suggestion.toString(), index, suggestedWords);
// Rely on onCodeInput to do the complicated swapping/stripping logic consistently. // 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); final int primaryCode = suggestion.charAt(0);
onCodeInput(primaryCode, onCodeInput(primaryCode,
KeyboardActionListener.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE);
KeyboardActionListener.SUGGESTION_STRIP_COORDINATE); if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_punctuationSuggestion(index, suggestion);
}
return; return;
} }
@ -1915,10 +1905,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final CompletionInfo completionInfo = mApplicationSpecifiedCompletions[index]; final CompletionInfo completionInfo = mApplicationSpecifiedCompletions[index];
mConnection.commitCompletion(completionInfo); mConnection.commitCompletion(completionInfo);
mConnection.endBatchEdit(); mConnection.endBatchEdit();
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_pickApplicationSpecifiedCompletion(index,
completionInfo.getText(), x, y);
}
return; return;
} }
@ -1927,12 +1913,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final String replacedWord = mWordComposer.getTypedWord().toString(); final String replacedWord = mWordComposer.getTypedWord().toString();
LatinImeLogger.logOnManualSuggestion(replacedWord, LatinImeLogger.logOnManualSuggestion(replacedWord,
suggestion.toString(), index, suggestedWords); suggestion.toString(), index, suggestedWords);
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion, x, y);
}
mExpectingUpdateSelection = true; mExpectingUpdateSelection = true;
commitChosenWord(suggestion, LastComposedWord.COMMIT_TYPE_MANUAL_PICK, commitChosenWord(suggestion, LastComposedWord.COMMIT_TYPE_MANUAL_PICK,
LastComposedWord.NOT_A_SEPARATOR); LastComposedWord.NOT_A_SEPARATOR);
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion);
}
mConnection.endBatchEdit(); mConnection.endBatchEdit();
// Don't allow cancellation of manual pick // Don't allow cancellation of manual pick
mLastComposedWord.deactivate(); 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. // If the suggestion is not in the dictionary, the hint should be shown.
&& !AutoCorrection.isValidWord(mSuggest.getUnigramDictionaries(), suggestion, true); && !AutoCorrection.isValidWord(mSuggest.getUnigramDictionaries(), suggestion, true);
Utils.Stats.onSeparator((char)Keyboard.CODE_SPACE, WordComposer.NOT_A_COORDINATE, Utils.Stats.onSeparator((char)Keyboard.CODE_SPACE,
WordComposer.NOT_A_COORDINATE); Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
if (showingAddToDictionaryHint && mIsUserDictionaryAvailable) { if (showingAddToDictionaryHint && mIsUserDictionaryAvailable) {
mSuggestionStripView.showAddToDictionaryHint( mSuggestionStripView.showAddToDictionaryHint(
suggestion, mCurrentSettings.mHintToSaveText); suggestion, mCurrentSettings.mHintToSaveText);
@ -1967,9 +1953,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions(); final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions();
mConnection.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan( mConnection.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan(
this, chosenWord, suggestedWords, mIsMainDictionaryAvailable), 1); this, chosenWord, suggestedWords, mIsMainDictionaryAvailable), 1);
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_commitText(chosenWord);
}
// Add the word to the user history dictionary // Add the word to the user history dictionary
final CharSequence prevWord = addToUserHistoryDictionary(chosenWord); final CharSequence prevWord = addToUserHistoryDictionary(chosenWord);
// TODO: figure out here if this is an auto-correct or if the best word is actually // 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) { private CharSequence addToUserHistoryDictionary(final CharSequence suggestion) {
if (TextUtils.isEmpty(suggestion)) return null; 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. // 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 // 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 final CharSequence prevWord
= mConnection.getNthPreviousWord(mCurrentSettings.mWordSeparators, 2); = mConnection.getNthPreviousWord(mCurrentSettings.mWordSeparators, 2);
final String secondWord; final String secondWord;
if (mWordComposer.isAutoCapitalized() && !mWordComposer.isMostlyCaps()) { if (mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps()) {
secondWord = suggestion.toString().toLowerCase( secondWord = suggestion.toString().toLowerCase(
mSubtypeSwitcher.getCurrentSubtypeLocale()); mSubtypeSwitcher.getCurrentSubtypeLocale());
} else { } else {
@ -2036,9 +2020,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mWordComposer.setComposingWord(word, mKeyboardSwitcher.getKeyboard()); mWordComposer.setComposingWord(word, mKeyboardSwitcher.getKeyboard());
final int length = word.length(); final int length = word.length();
mConnection.deleteSurroundingText(length, 0); mConnection.deleteSurroundingText(length, 0);
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_deleteSurroundingText(length);
}
mConnection.setComposingText(word, 1); mConnection.setComposingText(word, 1);
mHandler.postUpdateSuggestionStrip(); mHandler.postUpdateSuggestionStrip();
} }
@ -2066,9 +2047,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} }
} }
mConnection.deleteSurroundingText(deleteLength, 0); mConnection.deleteSurroundingText(deleteLength, 0);
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_deleteSurroundingText(deleteLength);
}
if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) { if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) {
mUserHistoryDictionary.cancelAddingUserHistory( mUserHistoryDictionary.cancelAddingUserHistory(
previousWord.toString(), committedWord.toString()); previousWord.toString(), committedWord.toString());
@ -2076,8 +2054,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mConnection.commitText(originallyTypedWord, 1); mConnection.commitText(originallyTypedWord, 1);
// Re-insert the separator // Re-insert the separator
sendKeyCodePoint(mLastComposedWord.mSeparatorCode); sendKeyCodePoint(mLastComposedWord.mSeparatorCode);
Utils.Stats.onSeparator(mLastComposedWord.mSeparatorCode, WordComposer.NOT_A_COORDINATE, Utils.Stats.onSeparator(mLastComposedWord.mSeparatorCode,
WordComposer.NOT_A_COORDINATE); Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
if (ProductionFlag.IS_EXPERIMENTAL) { if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_revertCommit(originallyTypedWord); ResearchLogger.latinIME_revertCommit(originallyTypedWord);
} }
@ -2093,9 +2071,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return mCurrentSettings.isWordSeparator(code); return mCurrentSettings.isWordSeparator(code);
} }
// Notify that language or mode have been changed and toggleLanguage will update KeyboardID // TODO: Make this private
// according to new language or mode. Called from SubtypeSwitcher. // Outside LatinIME, only used by the {@link InputTestsBase} test suite.
public void onRefreshKeyboard() { /* package for test */
void loadKeyboard() {
// When the device locale is changed in SetupWizard etc., this method may get called via // When the device locale is changed in SetupWizard etc., this method may get called via
// onConfigurationChanged before SoftInputWindow is shown. // onConfigurationChanged before SoftInputWindow is shown.
initSuggest(); initSuggest();
@ -2103,7 +2082,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (mKeyboardSwitcher.getMainKeyboardView() != null) { if (mKeyboardSwitcher.getMainKeyboardView() != null) {
// Reload keyboard because the current language has been changed. // Reload keyboard because the current language has been changed.
mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mCurrentSettings); mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mCurrentSettings);
updateKeyboardViewGestureHandlingModeByMainDictionaryAvailability();
} }
// Since we just changed languages, we should re-evaluate suggestions with whatever word // 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 // 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(); 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 // TODO: Remove this method from {@link LatinIME} and move {@link FeedbackManager} to
// {@link KeyboardSwitcher}. Called from KeyboardSwitcher // {@link KeyboardSwitcher}. Called from KeyboardSwitcher
public void hapticAndAudioFeedback(final int primaryCode) { 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. * 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; 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() { public void beginBatchEdit() {
if (++mNestLevel == 1) { if (++mNestLevel == 1) {
mIC = mParent.getCurrentInputConnection(); mIC = mParent.getCurrentInputConnection();
if (null != mIC) mIC.beginBatchEdit(); if (null != mIC) {
mIC.beginBatchEdit();
}
} else { } else {
if (DBG) { if (DBG) {
throw new RuntimeException("Nest level too deep"); throw new RuntimeException("Nest level too deep");
@ -66,7 +68,9 @@ public class RichInputConnection {
} }
public void endBatchEdit() { public void endBatchEdit() {
if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead 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() { private void checkBatchEdit() {
@ -79,12 +83,22 @@ public class RichInputConnection {
public void finishComposingText() { public void finishComposingText() {
checkBatchEdit(); 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) { public void commitText(final CharSequence text, final int i) {
checkBatchEdit(); 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) { public int getCursorCapsMode(final int inputType) {
@ -107,37 +121,72 @@ public class RichInputConnection {
public void deleteSurroundingText(final int i, final int j) { public void deleteSurroundingText(final int i, final int j) {
checkBatchEdit(); 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) { public void performEditorAction(final int actionId) {
mIC = mParent.getCurrentInputConnection(); 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) { public void sendKeyEvent(final KeyEvent keyEvent) {
checkBatchEdit(); 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) { public void setComposingText(final CharSequence text, final int i) {
checkBatchEdit(); 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) { public void setSelection(final int from, final int to) {
checkBatchEdit(); 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) { public void commitCorrection(final CorrectionInfo correctionInfo) {
checkBatchEdit(); 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) { public void commitCompletion(final CompletionInfo completionInfo) {
checkBatchEdit(); 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) { public CharSequence getNthPreviousWord(final String sentenceSeperators, final int n) {
@ -315,9 +364,6 @@ public class RichInputConnection {
if (lastOne != null && lastOne.length() == 1 if (lastOne != null && lastOne.length() == 1
&& lastOne.charAt(0) == Keyboard.CODE_SPACE) { && lastOne.charAt(0) == Keyboard.CODE_SPACE) {
deleteSurroundingText(1, 0); deleteSurroundingText(1, 0);
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_deleteSurroundingText(1);
}
} }
} }
@ -382,13 +428,7 @@ public class RichInputConnection {
return false; return false;
} }
deleteSurroundingText(2, 0); deleteSurroundingText(2, 0);
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_deleteSurroundingText(2);
}
commitText(" ", 1); commitText(" ", 1);
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_revertDoubleSpaceWhileInBatchEdit();
}
return true; return true;
} }
@ -409,13 +449,7 @@ public class RichInputConnection {
return false; return false;
} }
deleteSurroundingText(2, 0); deleteSurroundingText(2, 0);
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_deleteSurroundingText(2);
}
commitText(" " + textBeforeCursor.subSequence(0, 1), 1); commitText(" " + textBeforeCursor.subSequence(0, 1), 1);
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_revertSwapPunctuation();
}
return true; return true;
} }
} }

View File

@ -185,7 +185,7 @@ public class SettingsValues {
// Helper functions to create member values. // Helper functions to create member values.
private static SuggestedWords createSuggestPuncList(final String[] puncs) { private static SuggestedWords createSuggestPuncList(final String[] puncs) {
final ArrayList<SuggestedWordInfo> puncList = new ArrayList<SuggestedWordInfo>(); final ArrayList<SuggestedWordInfo> puncList = CollectionUtils.newArrayList();
if (puncs != null) { if (puncs != null) {
for (final String puncSpec : puncs) { for (final String puncSpec : puncs) {
puncList.add(new SuggestedWordInfo(KeySpecParser.getLabel(puncSpec), 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(); prefs.edit().putString(Settings.PREF_LAST_USER_DICTIONARY_WRITE_TIME, newStr).apply();
} }
public boolean isSameInputType(final EditorInfo editorInfo) {
return mInputAttributes.isSameInputType(editorInfo);
}
// For debug. // For debug.
public String getInputAttributesDebugString() { public String getInputAttributesDebugString() {
return mInputAttributes.toString(); return mInputAttributes.toString();

View File

@ -53,7 +53,7 @@ public class StringUtils {
if (TextUtils.isEmpty(csv)) return ""; if (TextUtils.isEmpty(csv)) return "";
final String[] elements = csv.split(","); final String[] elements = csv.split(",");
if (!containsInArray(key, elements)) return csv; 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) { for (final String element : elements) {
if (!key.equals(element)) result.add(element); if (!key.equals(element)) result.add(element);
} }

View File

@ -45,13 +45,13 @@ public class SubtypeLocale {
private static String[] sPredefinedKeyboardLayoutSet; private static String[] sPredefinedKeyboardLayoutSet;
// Keyboard layout to its display name map. // Keyboard layout to its display name map.
private static final HashMap<String, String> sKeyboardLayoutToDisplayNameMap = private static final HashMap<String, String> sKeyboardLayoutToDisplayNameMap =
new HashMap<String, String>(); CollectionUtils.newHashMap();
// Keyboard layout to subtype name resource id map. // Keyboard layout to subtype name resource id map.
private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap = private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap =
new HashMap<String, Integer>(); CollectionUtils.newHashMap();
// Exceptional locale to subtype name resource id map. // Exceptional locale to subtype name resource id map.
private static final HashMap<String, Integer> sExceptionalLocaleToWithLayoutNameIdsMap = private static final HashMap<String, Integer> sExceptionalLocaleToWithLayoutNameIdsMap =
new HashMap<String, Integer>(); CollectionUtils.newHashMap();
private static final String SUBTYPE_NAME_RESOURCE_GENERIC_PREFIX = private static final String SUBTYPE_NAME_RESOURCE_GENERIC_PREFIX =
"string/subtype_generic_"; "string/subtype_generic_";
private static final String SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX = private static final String SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX =
@ -60,11 +60,11 @@ public class SubtypeLocale {
"string/subtype_no_language_"; "string/subtype_no_language_";
// Exceptional locales to display name map. // Exceptional locales to display name map.
private static final HashMap<String, String> sExceptionalDisplayNamesMap = 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. // 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. // This is for compatibility to keep the same subtype ids as pre-JellyBean.
private static final HashMap<String,String> sLocaleAndExtraValueToKeyboardLayoutSetMap = private static final HashMap<String,String> sLocaleAndExtraValueToKeyboardLayoutSetMap =
new HashMap<String,String>(); CollectionUtils.newHashMap();
private SubtypeLocale() { private SubtypeLocale() {
// Intentional empty constructor for utility class. // Intentional empty constructor for utility class.

View File

@ -22,6 +22,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.inputmethodservice.InputMethodService;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.NetworkInfo; import android.net.NetworkInfo;
import android.os.AsyncTask; import android.os.AsyncTask;
@ -42,7 +43,6 @@ public class SubtypeSwitcher {
private static final String TAG = SubtypeSwitcher.class.getSimpleName(); private static final String TAG = SubtypeSwitcher.class.getSimpleName();
private static final SubtypeSwitcher sInstance = new SubtypeSwitcher(); private static final SubtypeSwitcher sInstance = new SubtypeSwitcher();
private /* final */ LatinIME mService;
private /* final */ InputMethodManager mImm; private /* final */ InputMethodManager mImm;
private /* final */ Resources mResources; private /* final */ Resources mResources;
private /* final */ ConnectivityManager mConnectivityManager; private /* final */ ConnectivityManager mConnectivityManager;
@ -68,11 +68,11 @@ public class SubtypeSwitcher {
return mEnabledSubtypeCount >= 2 || !mIsSystemLanguageSameAsInputLanguage; return mEnabledSubtypeCount >= 2 || !mIsSystemLanguageSameAsInputLanguage;
} }
public void updateEnabledSubtypeCount(int count) { public void updateEnabledSubtypeCount(final int count) {
mEnabledSubtypeCount = count; mEnabledSubtypeCount = count;
} }
public void updateIsSystemLanguageSameAsInputLanguage(boolean isSame) { public void updateIsSystemLanguageSameAsInputLanguage(final boolean isSame) {
mIsSystemLanguageSameAsInputLanguage = isSame; mIsSystemLanguageSameAsInputLanguage = isSame;
} }
} }
@ -81,18 +81,17 @@ public class SubtypeSwitcher {
return sInstance; return sInstance;
} }
public static void init(LatinIME service) { public static void init(final Context context) {
SubtypeLocale.init(service); SubtypeLocale.init(context);
sInstance.initialize(service); sInstance.initialize(context);
sInstance.updateAllParameters(); sInstance.updateAllParameters(context);
} }
private SubtypeSwitcher() { private SubtypeSwitcher() {
// Intentional empty constructor for singleton. // Intentional empty constructor for singleton.
} }
private void initialize(LatinIME service) { private void initialize(final Context service) {
mService = service;
mResources = service.getResources(); mResources = service.getResources();
mImm = ImfUtils.getInputMethodManager(service); mImm = ImfUtils.getInputMethodManager(service);
mConnectivityManager = (ConnectivityManager) service.getSystemService( mConnectivityManager = (ConnectivityManager) service.getSystemService(
@ -111,39 +110,46 @@ public class SubtypeSwitcher {
// Update all parameters stored in SubtypeSwitcher. // Update all parameters stored in SubtypeSwitcher.
// Only configuration changed event is allowed to call this because this is heavy. // 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; mCurrentSystemLocale = mResources.getConfiguration().locale;
updateSubtype(ImfUtils.getCurrentInputMethodSubtype(mService, mNoLanguageSubtype)); updateSubtype(ImfUtils.getCurrentInputMethodSubtype(context, mNoLanguageSubtype));
updateParametersOnStartInputView(); updateParametersOnStartInputViewAndReturnIfCurrentSubtypeEnabled();
} }
// Update parameters which are changed outside LatinIME. This parameters affect UI so they /**
// should be updated every time onStartInputview. * Update parameters which are changed outside LatinIME. This parameters affect UI so they
public void updateParametersOnStartInputView() { * should be updated every time onStartInputView.
updateEnabledSubtypes(); *
* @return true if the current subtype is enabled.
*/
public boolean updateParametersOnStartInputViewAndReturnIfCurrentSubtypeEnabled() {
final boolean currentSubtypeEnabled =
updateEnabledSubtypesAndReturnIfEnabled(mCurrentSubtype);
updateShortcutIME(); updateShortcutIME();
return currentSubtypeEnabled;
} }
// Reload enabledSubtypes from the framework. /**
private void updateEnabledSubtypes() { * Update enabled subtypes from the framework.
final InputMethodSubtype currentSubtype = mCurrentSubtype; *
boolean foundCurrentSubtypeBecameDisabled = true; * @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 = final List<InputMethodSubtype> enabledSubtypesOfThisIme =
mImm.getEnabledInputMethodSubtypeList(null, true); mImm.getEnabledInputMethodSubtypeList(null, true);
for (InputMethodSubtype ims : enabledSubtypesOfThisIme) {
if (ims.equals(currentSubtype)) {
foundCurrentSubtypeBecameDisabled = false;
}
}
mNeedsToDisplayLanguage.updateEnabledSubtypeCount(enabledSubtypesOfThisIme.size()); mNeedsToDisplayLanguage.updateEnabledSubtypeCount(enabledSubtypesOfThisIme.size());
if (foundCurrentSubtypeBecameDisabled) {
if (DBG) { for (final InputMethodSubtype ims : enabledSubtypesOfThisIme) {
Log.w(TAG, "Last subtype: " if (ims.equals(subtype)) {
+ currentSubtype.getLocale() + "/" + currentSubtype.getExtraValue()); return true;
Log.w(TAG, "Last subtype was disabled. Update to the current one.");
} }
updateSubtype(ImfUtils.getCurrentInputMethodSubtype(mService, mNoLanguageSubtype));
} }
if (DBG) {
Log.w(TAG, "Subtype: " + subtype.getLocale() + "/" + subtype.getExtraValue()
+ " was disabled");
}
return false;
} }
private void updateShortcutIME() { private void updateShortcutIME() {
@ -159,8 +165,8 @@ public class SubtypeSwitcher {
mImm.getShortcutInputMethodsAndSubtypes(); mImm.getShortcutInputMethodsAndSubtypes();
mShortcutInputMethodInfo = null; mShortcutInputMethodInfo = null;
mShortcutSubtype = null; mShortcutSubtype = null;
for (InputMethodInfo imi : shortcuts.keySet()) { for (final InputMethodInfo imi : shortcuts.keySet()) {
List<InputMethodSubtype> subtypes = shortcuts.get(imi); final List<InputMethodSubtype> subtypes = shortcuts.get(imi);
// TODO: Returns the first found IMI for now. Should handle all shortcuts as // TODO: Returns the first found IMI for now. Should handle all shortcuts as
// appropriate. // appropriate.
mShortcutInputMethodInfo = imi; mShortcutInputMethodInfo = imi;
@ -194,24 +200,24 @@ public class SubtypeSwitcher {
mCurrentSubtype = newSubtype; mCurrentSubtype = newSubtype;
updateShortcutIME(); updateShortcutIME();
mService.onRefreshKeyboard();
} }
//////////////////////////// ////////////////////////////
// Shortcut IME functions // // Shortcut IME functions //
//////////////////////////// ////////////////////////////
public void switchToShortcutIME() { public void switchToShortcutIME(final InputMethodService context) {
if (mShortcutInputMethodInfo == null) { if (mShortcutInputMethodInfo == null) {
return; return;
} }
final String imiId = mShortcutInputMethodInfo.getId(); final String imiId = mShortcutInputMethodInfo.getId();
switchToTargetIME(imiId, mShortcutSubtype); switchToTargetIME(imiId, mShortcutSubtype, context);
} }
private void switchToTargetIME(final String imiId, final InputMethodSubtype subtype) { private void switchToTargetIME(final String imiId, final InputMethodSubtype subtype,
final IBinder token = mService.getWindow().getWindow().getAttributes().token; final InputMethodService context) {
final IBinder token = context.getWindow().getWindow().getAttributes().token;
if (token == null) { if (token == null) {
return; return;
} }
@ -253,7 +259,7 @@ public class SubtypeSwitcher {
return true; return true;
} }
public void onNetworkStateChanged(Intent intent) { public void onNetworkStateChanged(final Intent intent) {
final boolean noConnection = intent.getBooleanExtra( final boolean noConnection = intent.getBooleanExtra(
ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
mIsNetworkConnected = !noConnection; mIsNetworkConnected = !noConnection;
@ -265,7 +271,7 @@ public class SubtypeSwitcher {
// Subtype Switching functions // // Subtype Switching functions //
////////////////////////////////// //////////////////////////////////
public boolean needsToDisplayLanguage(Locale keyboardLocale) { public boolean needsToDisplayLanguage(final Locale keyboardLocale) {
if (keyboardLocale.toString().equals(SubtypeLocale.NO_LANGUAGE)) { if (keyboardLocale.toString().equals(SubtypeLocale.NO_LANGUAGE)) {
return true; return true;
} }
@ -279,12 +285,14 @@ public class SubtypeSwitcher {
return SubtypeLocale.getSubtypeLocale(mCurrentSubtype); return SubtypeLocale.getSubtypeLocale(mCurrentSubtype);
} }
public void onConfigurationChanged(Configuration conf) { public boolean onConfigurationChanged(final Configuration conf, final Context context) {
final Locale systemLocale = conf.locale; final Locale systemLocale = conf.locale;
final boolean systemLocaleChanged = !systemLocale.equals(mCurrentSystemLocale);
// If system configuration was changed, update all parameters. // If system configuration was changed, update all parameters.
if (!systemLocale.equals(mCurrentSystemLocale)) { if (systemLocaleChanged) {
updateAllParameters(); updateAllParameters(context);
} }
return systemLocaleChanged;
} }
public InputMethodSubtype getCurrentSubtype() { public InputMethodSubtype getCurrentSubtype() {

View File

@ -50,9 +50,8 @@ public class Suggest {
private Dictionary mMainDictionary; private Dictionary mMainDictionary;
private ContactsBinaryDictionary mContactsDict; private ContactsBinaryDictionary mContactsDict;
private WhitelistDictionary mWhiteListDictionary;
private final ConcurrentHashMap<String, Dictionary> mDictionaries = private final ConcurrentHashMap<String, Dictionary> mDictionaries =
new ConcurrentHashMap<String, Dictionary>(); CollectionUtils.newConcurrentHashMap();
public static final int MAX_SUGGESTIONS = 18; public static final int MAX_SUGGESTIONS = 18;
@ -60,13 +59,11 @@ public class Suggest {
// Locale used for upper- and title-casing words // Locale used for upper- and title-casing words
private final Locale mLocale; private final Locale mLocale;
private final SuggestInitializationListener mListener;
public Suggest(final Context context, final Locale locale, public Suggest(final Context context, final Locale locale,
final SuggestInitializationListener listener) { final SuggestInitializationListener listener) {
initAsynchronously(context, locale); initAsynchronously(context, locale, listener);
mLocale = locale; mLocale = locale;
mListener = listener;
} }
/* package for test */ Suggest(final Context context, final File dictionary, /* package for test */ Suggest(final Context context, final File dictionary,
@ -74,23 +71,13 @@ public class Suggest {
final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(context, dictionary, final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(context, dictionary,
startOffset, length /* useFullEditDistance */, false, locale); startOffset, length /* useFullEditDistance */, false, locale);
mLocale = locale; mLocale = locale;
mListener = null;
mMainDictionary = mainDict; mMainDictionary = mainDict;
addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, mainDict); addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, mainDict);
initWhitelistAndAutocorrectAndPool(context, locale);
} }
private void initWhitelistAndAutocorrectAndPool(final Context context, final Locale locale) { private void initAsynchronously(final Context context, final Locale locale,
mWhiteListDictionary = new WhitelistDictionary(context, locale); final SuggestInitializationListener listener) {
addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_WHITELIST, mWhiteListDictionary); resetMainDict(context, locale, listener);
}
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 static void addOrReplaceDictionary( 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; mMainDictionary = null;
if (mListener != null) { if (listener != null) {
mListener.onUpdateMainDictionaryAvailability(hasMainDictionary()); listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
} }
new Thread("InitializeBinaryDictionary") { new Thread("InitializeBinaryDictionary") {
@Override @Override
@ -116,8 +104,8 @@ public class Suggest {
DictionaryFactory.createMainDictionaryFromManager(context, locale); DictionaryFactory.createMainDictionaryFromManager(context, locale);
addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, newMainDict); addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, newMainDict);
mMainDictionary = newMainDict; mMainDictionary = newMainDict;
if (mListener != null) { if (listener != null) {
mListener.onUpdateMainDictionaryAvailability(hasMainDictionary()); listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
} }
} }
}.start(); }.start();
@ -170,9 +158,17 @@ public class Suggest {
public SuggestedWords getSuggestedWords( public SuggestedWords getSuggestedWords(
final WordComposer wordComposer, CharSequence prevWordForBigram, final WordComposer wordComposer, CharSequence prevWordForBigram,
final ProximityInfo proximityInfo, final boolean isCorrectionEnabled) { 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); LatinImeLogger.onStartSuggestion(prevWordForBigram);
if (wordComposer.isBatchMode()) { if (wordComposer.isBatchMode()) {
return getSuggestedWordsForBatchInput(wordComposer, prevWordForBigram, proximityInfo); return getSuggestedWordsForBatchInput(
wordComposer, prevWordForBigram, proximityInfo, sessionId);
} else { } else {
return getSuggestedWordsForTypingInput(wordComposer, prevWordForBigram, proximityInfo, return getSuggestedWordsForTypingInput(wordComposer, prevWordForBigram, proximityInfo,
isCorrectionEnabled); isCorrectionEnabled);
@ -209,23 +205,20 @@ public class Suggest {
wordComposerForLookup, prevWordForBigram, proximityInfo)); wordComposerForLookup, prevWordForBigram, proximityInfo));
} }
// TODO: Change this scheme - a boolean is not enough. A whitelisted word may be "valid" final CharSequence whitelistedWord;
// but still autocorrected from - in the case the whitelist only capitalizes the word. if (suggestionsSet.isEmpty()) {
// The whitelist should be case-insensitive, so it's not possible to be consistent with whitelistedWord = null;
// a boolean flag. Right now this is handled with a slight hack in } else if (SuggestedWordInfo.KIND_WHITELIST != suggestionsSet.first().mKind) {
// WhitelistDictionary#shouldForciblyAutoCorrectFrom. whitelistedWord = null;
final boolean allowsToBeAutoCorrected = AutoCorrection.isWhitelistedOrNotAWord( } else {
mDictionaries, consideredWord, wordComposer.isFirstCharCapitalized()); whitelistedWord = suggestionsSet.first().mWord;
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 boolean allowsToBeAutoCorrected = (null != whitelistedWord
&& !whitelistedWord.equals(consideredWord))
|| AutoCorrection.isNotAWord(mDictionaries, consideredWord,
wordComposer.isFirstCharCapitalized());
final boolean hasAutoCorrection; final boolean hasAutoCorrection;
// TODO: using isCorrectionEnabled here is not very good. It's probably useless, because // 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 // 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 = final ArrayList<SuggestedWordInfo> suggestionsContainer =
new ArrayList<SuggestedWordInfo>(suggestionsSet); CollectionUtils.newArrayList(suggestionsSet);
final int suggestionsCount = suggestionsContainer.size(); final int suggestionsCount = suggestionsContainer.size();
final boolean isFirstCharCapitalized = wordComposer.isFirstCharCapitalized(); final boolean isFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
final boolean isAllUpperCase = wordComposer.isAllUpperCase(); final boolean isAllUpperCase = wordComposer.isAllUpperCase();
@ -296,29 +289,28 @@ public class Suggest {
// Retrieves suggestions for the batch input. // Retrieves suggestions for the batch input.
private SuggestedWords getSuggestedWordsForBatchInput( private SuggestedWords getSuggestedWordsForBatchInput(
final WordComposer wordComposer, CharSequence prevWordForBigram, final WordComposer wordComposer, CharSequence prevWordForBigram,
final ProximityInfo proximityInfo) { final ProximityInfo proximityInfo, int sessionId) {
final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator, final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator,
MAX_SUGGESTIONS); MAX_SUGGESTIONS);
// At second character typed, search the unigrams (scores being affected by bigrams) // At second character typed, search the unigrams (scores being affected by bigrams)
for (final String key : mDictionaries.keySet()) { for (final String key : mDictionaries.keySet()) {
// Skip UserUnigramDictionary and WhitelistDictionary to lookup // Skip User history dictionary for lookup
if (key.equals(Dictionary.TYPE_USER_HISTORY) // TODO: The user history dictionary should just override getSuggestionsWithSessionId
|| key.equals(Dictionary.TYPE_WHITELIST)) { // to make sure it doesn't return anything and we should remove this test
if (key.equals(Dictionary.TYPE_USER_HISTORY)) {
continue; continue;
} }
final Dictionary dictionary = mDictionaries.get(key); final Dictionary dictionary = mDictionaries.get(key);
suggestionsSet.addAll(dictionary.getSuggestions( suggestionsSet.addAll(dictionary.getSuggestionsWithSessionId(
wordComposer, prevWordForBigram, proximityInfo)); wordComposer, prevWordForBigram, proximityInfo, sessionId));
} }
final ArrayList<SuggestedWordInfo> suggestionsContainer = final ArrayList<SuggestedWordInfo> suggestionsContainer =
new ArrayList<SuggestedWordInfo>(suggestionsSet); CollectionUtils.newArrayList(suggestionsSet);
final int suggestionsCount = suggestionsContainer.size(); final int suggestionsCount = suggestionsContainer.size();
final boolean isFirstCharCapitalized = wordComposer.isAutoCapitalized(); final boolean isFirstCharCapitalized = wordComposer.wasShiftedNoLock();
// TODO: Handle the manual temporary shifted mode. final boolean isAllUpperCase = wordComposer.isAllUpperCase();
// TODO: Should handle TextUtils.CAP_MODE_CHARACTER.
final boolean isAllUpperCase = false;
if (isFirstCharCapitalized || isAllUpperCase) { if (isFirstCharCapitalized || isAllUpperCase) {
for (int i = 0; i < suggestionsCount; ++i) { for (int i = 0; i < suggestionsCount; ++i) {
final SuggestedWordInfo wordInfo = suggestionsContainer.get(i); final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
@ -346,7 +338,7 @@ public class Suggest {
typedWordInfo.setDebugString("+"); typedWordInfo.setDebugString("+");
final int suggestionsSize = suggestions.size(); final int suggestionsSize = suggestions.size();
final ArrayList<SuggestedWordInfo> suggestionsList = final ArrayList<SuggestedWordInfo> suggestionsList =
new ArrayList<SuggestedWordInfo>(suggestionsSize); CollectionUtils.newArrayList(suggestionsSize);
suggestionsList.add(typedWordInfo); suggestionsList.add(typedWordInfo);
// Note: i here is the index in mScores[], but the index in mSuggestions is one more // 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. // than i because we added the typed word to mSuggestions without touching mScores.
@ -399,7 +391,7 @@ public class Suggest {
} }
public void close() { public void close() {
final HashSet<Dictionary> dictionaries = new HashSet<Dictionary>(); final HashSet<Dictionary> dictionaries = CollectionUtils.newHashSet();
dictionaries.addAll(mDictionaries.values()); dictionaries.addAll(mDictionaries.values());
for (final Dictionary dictionary : dictionaries) { for (final Dictionary dictionary : dictionaries) {
dictionary.close(); dictionary.close();

View File

@ -24,8 +24,10 @@ import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
public class SuggestedWords { public class SuggestedWords {
private static final ArrayList<SuggestedWordInfo> EMPTY_WORD_INFO_LIST =
CollectionUtils.newArrayList(0);
public static final SuggestedWords EMPTY = new SuggestedWords( 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; public final boolean mTypedWordValid;
// Note: this INCLUDES cases where the word will auto-correct to itself. A good definition // 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( public static ArrayList<SuggestedWordInfo> getFromApplicationSpecifiedCompletions(
final CompletionInfo[] infos) { final CompletionInfo[] infos) {
final ArrayList<SuggestedWordInfo> result = new ArrayList<SuggestedWordInfo>(); final ArrayList<SuggestedWordInfo> result = CollectionUtils.newArrayList();
for (CompletionInfo info : infos) { for (CompletionInfo info : infos) {
if (null != info && info.getText() != null) { if (null != info && info.getText() != null) {
result.add(new SuggestedWordInfo(info.getText(), SuggestedWordInfo.MAX_SCORE, 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. // and replace it with what the user currently typed.
public static ArrayList<SuggestedWordInfo> getTypedWordAndPreviousSuggestions( public static ArrayList<SuggestedWordInfo> getTypedWordAndPreviousSuggestions(
final CharSequence typedWord, final SuggestedWords previousSuggestions) { final CharSequence typedWord, final SuggestedWords previousSuggestions) {
final ArrayList<SuggestedWordInfo> suggestionsList = new ArrayList<SuggestedWordInfo>(); final ArrayList<SuggestedWordInfo> suggestionsList = CollectionUtils.newArrayList();
final HashSet<String> alreadySeen = new HashSet<String>(); final HashSet<String> alreadySeen = CollectionUtils.newHashSet();
suggestionsList.add(new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE, suggestionsList.add(new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE,
SuggestedWordInfo.KIND_TYPED, Dictionary.TYPE_USER_TYPED)); SuggestedWordInfo.KIND_TYPED, Dictionary.TYPE_USER_TYPED));
alreadySeen.add(typedWord.toString()); alreadySeen.add(typedWord.toString());

View File

@ -52,14 +52,14 @@ public class UserHistoryDictionary extends ExpandableDictionary {
private static final int FREQUENCY_FOR_TYPED = 2; private static final int FREQUENCY_FOR_TYPED = 2;
/** Maximum number of pairs. Pruning will start when databases goes above this number. */ /** 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 * When it hits maximum bigram pair, it will delete until you are left with
* only (sMaxHistoryBigrams - sDeleteHistoryBigrams) pairs. * only (sMaxHistoryBigrams - sDeleteHistoryBigrams) pairs.
* Do not keep this number small to avoid deleting too often. * 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 * 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 HashMap<String, String> sDictProjectionMap;
private final static ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>> private final static ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>
sLangDictCache = new ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>(); sLangDictCache = CollectionUtils.newConcurrentHashMap();
static { static {
sDictProjectionMap = new HashMap<String, String>(); sDictProjectionMap = CollectionUtils.newHashMap();
sDictProjectionMap.put(MAIN_COLUMN_ID, MAIN_COLUMN_ID); sDictProjectionMap.put(MAIN_COLUMN_ID, MAIN_COLUMN_ID);
sDictProjectionMap.put(MAIN_COLUMN_WORD1, MAIN_COLUMN_WORD1); sDictProjectionMap.put(MAIN_COLUMN_WORD1, MAIN_COLUMN_WORD1);
sDictProjectionMap.put(MAIN_COLUMN_WORD2, MAIN_COLUMN_WORD2); sDictProjectionMap.put(MAIN_COLUMN_WORD2, MAIN_COLUMN_WORD2);
@ -109,12 +109,8 @@ public class UserHistoryDictionary extends ExpandableDictionary {
private static DatabaseHelper sOpenHelper = null; private static DatabaseHelper sOpenHelper = null;
public void setDatabaseMax(int maxHistoryBigram) { public String getLocale() {
sMaxHistoryBigrams = maxHistoryBigram; return mLocale;
}
public void setDatabaseDelete(int deleteHistoryBigram) {
sDeleteHistoryBigrams = deleteHistoryBigram;
} }
public synchronized static UserHistoryDictionary getInstance( public synchronized static UserHistoryDictionary getInstance(
@ -502,9 +498,11 @@ public class UserHistoryDictionary extends ExpandableDictionary {
needsToSave(fc, isValid, addLevel0Bigram)) { needsToSave(fc, isValid, addLevel0Bigram)) {
freq = fc; freq = fc;
} else { } else {
// Delete this entry
freq = -1; freq = -1;
} }
} else { } else {
// Delete this entry
freq = -1; freq = -1;
} }
} }
@ -541,6 +539,7 @@ public class UserHistoryDictionary extends ExpandableDictionary {
getContentValues(word1, word2, mLocale)); getContentValues(word1, word2, mLocale));
pairId = pairIdLong.intValue(); pairId = pairIdLong.intValue();
} }
// Eliminate freq == 0 because that word is profanity.
if (freq > 0) { if (freq > 0) {
if (PROFILE_SAVE_RESTORE) { if (PROFILE_SAVE_RESTORE) {
++profInsert; ++profInsert;

View File

@ -29,9 +29,8 @@ import java.util.Set;
public class UserHistoryDictionaryBigramList { public class UserHistoryDictionaryBigramList {
public static final byte FORGETTING_CURVE_INITIAL_VALUE = 0; public static final byte FORGETTING_CURVE_INITIAL_VALUE = 0;
private static final String TAG = UserHistoryDictionaryBigramList.class.getSimpleName(); private static final String TAG = UserHistoryDictionaryBigramList.class.getSimpleName();
private static final HashMap<String, Byte> EMPTY_BIGRAM_MAP = new HashMap<String, Byte>(); private static final HashMap<String, Byte> EMPTY_BIGRAM_MAP = CollectionUtils.newHashMap();
private final HashMap<String, HashMap<String, Byte>> mBigramMap = private final HashMap<String, HashMap<String, Byte>> mBigramMap = CollectionUtils.newHashMap();
new HashMap<String, HashMap<String, Byte>>();
private int mSize = 0; private int mSize = 0;
public void evictAll() { public void evictAll() {
@ -57,7 +56,7 @@ public class UserHistoryDictionaryBigramList {
if (mBigramMap.containsKey(word1)) { if (mBigramMap.containsKey(word1)) {
map = mBigramMap.get(word1); map = mBigramMap.get(word1);
} else { } else {
map = new HashMap<String, Byte>(); map = CollectionUtils.newHashMap();
mBigramMap.put(word1, map); mBigramMap.put(word1, map);
} }
if (!map.containsKey(word2)) { if (!map.containsKey(word2)) {

View File

@ -212,7 +212,7 @@ public class UserHistoryForgettingCurveUtils {
for (int j = 0; j < ELAPSED_TIME_MAX; ++j) { for (int j = 0; j < ELAPSED_TIME_MAX; ++j) {
final float elapsedHours = j * ELAPSED_TIME_INTERVAL_HOURS; final float elapsedHours = j * ELAPSED_TIME_INTERVAL_HOURS;
final float freq = initialFreq 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)); final int intFreq = Math.min(FC_FREQ_MAX, Math.max(0, (int)freq));
SCORE_TABLE[i][j] = intFreq; 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 { /* package */ static class RingCharBuffer {
private static RingCharBuffer sRingCharBuffer = new RingCharBuffer(); private static RingCharBuffer sRingCharBuffer = new RingCharBuffer();
private static final char PLACEHOLDER_DELIMITER_CHAR = '\uFFFC'; 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 String HARDWARE_PREFIX = Build.HARDWARE + ",";
private static final HashMap<String, String> sDeviceOverrideValueMap = private static final HashMap<String, String> sDeviceOverrideValueMap =
new HashMap<String, String>(); CollectionUtils.newHashMap();
public static String getDeviceOverrideValue(Resources res, int overrideResId, String defValue) { public static String getDeviceOverrideValue(Resources res, int overrideResId, String defValue) {
final int orientation = res.getConfiguration().orientation; final int orientation = res.getConfiguration().orientation;
@ -495,7 +457,7 @@ public class Utils {
return sDeviceOverrideValueMap.get(key); 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 = ","; private static final String LOCALE_AND_TIME_STR_SEPARATER = ",";
public static HashMap<String, Long> localeAndTimeStrToHashMap(String str) { public static HashMap<String, Long> localeAndTimeStrToHashMap(String str) {
if (TextUtils.isEmpty(str)) { if (TextUtils.isEmpty(str)) {
@ -506,7 +468,7 @@ public class Utils {
if (N < 2 || N % 2 != 0) { if (N < 2 || N % 2 != 0) {
return EMPTY_LT_HASH_MAP; 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) { for (int i = 0; i < N / 2; ++i) {
final String localeStr = ss[i * 2]; final String localeStr = ss[i * 2];
final long time = Long.valueOf(ss[i * 2 + 1]); 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; package com.android.inputmethod.latin;
import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.KeyDetector;
import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.Keyboard;
import java.util.Arrays; 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 * A place to store the currently composing word with information such as adjacent key codes as well
*/ */
public class WordComposer { 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; 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 int[] mPrimaryKeyCodes;
private final InputPointers mInputPointers = new InputPointers(N); private final InputPointers mInputPointers = new InputPointers(N);
private final StringBuilder mTypedWord; private final StringBuilder mTypedWord;
@ -42,7 +45,7 @@ public class WordComposer {
// Cache these values for performance // Cache these values for performance
private int mCapsCount; private int mCapsCount;
private int mDigitsCount; private int mDigitsCount;
private boolean mAutoCapitalized; private int mCapitalizedMode;
private int mTrailingSingleQuotesCount; private int mTrailingSingleQuotesCount;
private int mCodePointSize; private int mCodePointSize;
@ -68,7 +71,7 @@ public class WordComposer {
mCapsCount = source.mCapsCount; mCapsCount = source.mCapsCount;
mDigitsCount = source.mDigitsCount; mDigitsCount = source.mDigitsCount;
mIsFirstCharCapitalized = source.mIsFirstCharCapitalized; mIsFirstCharCapitalized = source.mIsFirstCharCapitalized;
mAutoCapitalized = source.mAutoCapitalized; mCapitalizedMode = source.mCapitalizedMode;
mTrailingSingleQuotesCount = source.mTrailingSingleQuotesCount; mTrailingSingleQuotesCount = source.mTrailingSingleQuotesCount;
mIsResumed = source.mIsResumed; mIsResumed = source.mIsResumed;
mIsBatchMode = source.mIsBatchMode; mIsBatchMode = source.mIsBatchMode;
@ -166,7 +169,7 @@ public class WordComposer {
final int codePoint = Character.codePointAt(word, i); final int codePoint = Character.codePointAt(word, i);
// We don't want to override the batch input points that are held in mInputPointers // We don't want to override the batch input points that are held in mInputPointers
// (See {@link #add(int,int,int)}). // (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); add(codePoint, x, y);
return; 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 * @return true if all user typed chars are upper case, false otherwise
*/ */
public boolean isAllUpperCase() { 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 * Saves the caps mode at the start of composing.
* 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 * 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) { public void setCapitalizedModeAtStartComposingTime(final int mode) {
mAutoCapitalized = auto; mCapitalizedMode = mode;
} }
/** /**
* Returns whether the word was automatically capitalized. * Returns whether the word was automatically capitalized.
* @return whether the word was automatically capitalized * @return whether the word was automatically capitalized
*/ */
public boolean isAutoCapitalized() { public boolean wasAutoCapitalized() {
return mAutoCapitalized; 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 com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.RandomAccessFile; import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; 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_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 MINIMUM_SUPPORTED_VERSION = 1;
private static final int MAXIMUM_SUPPORTED_VERSION = 2; private static final int MAXIMUM_SUPPORTED_VERSION = 2;
private static final int NOT_A_VERSION_NUMBER = -1; 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(); final StringBuilder s = new StringBuilder();
int character = readChar(source); int character = readChar(buffer);
while (character != INVALID_CHARACTER) { while (character != INVALID_CHARACTER) {
s.appendCodePoint(character); s.appendCodePoint(character);
character = readChar(source); character = readChar(buffer);
} }
return s.toString(); 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. * 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. * @return the character code.
*/ */
private static int readChar(RandomAccessFile source) throws IOException { private static int readChar(final ByteBuffer buffer) {
int character = source.readUnsignedByte(); int character = readUnsignedByte(buffer);
if (!fitsOnOneByte(character)) { if (!fitsOnOneByte(character)) {
if (GROUP_CHARACTERS_TERMINATOR == character) if (GROUP_CHARACTERS_TERMINATOR == character) return INVALID_CHARACTER;
return INVALID_CHARACTER;
character <<= 16; character <<= 16;
character += source.readUnsignedShort(); character += readUnsignedShort(buffer);
} }
return character; 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 // 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). // start at exactly 1 unit higher than floor(unigramFreq + half a step).
// Note : to reconstruct the score, the dictionary reader will need to divide // Note : to reconstruct the score, the dictionary reader will need to divide
// MAX_TERMINAL_FREQUENCY - unigramFreq by 16.5 likewise, and add // MAX_TERMINAL_FREQUENCY - unigramFreq by 16.5 likewise to get the value of the step,
// (discretizedFrequency + 0.5) times this value to get the median value of the step, // and add (discretizedFrequency + 0.5 + 0.5) times this value to get the best
// which is the best approximation. This is how we get the most precise result with // approximation. (0.5 to get the first step start, and 0.5 to get the middle of the
// only four bits. // step pointed by the discretized frequency.
final float stepSize = final float stepSize =
(MAX_TERMINAL_FREQUENCY - unigramFrequency) / (1.5f + MAX_BIGRAM_FREQUENCY); (MAX_TERMINAL_FREQUENCY - unigramFrequency) / (1.5f + MAX_BIGRAM_FREQUENCY);
final float firstStepStart = 1 + unigramFrequency + (stepSize / 2.0f); final float firstStepStart = 1 + unigramFrequency + (stepSize / 2.0f);
@ -1091,46 +1093,46 @@ public class BinaryDictInputOutput {
// readDictionaryBinary is the public entry point for them. // readDictionaryBinary is the public entry point for them.
static final int[] characterBuffer = new int[MAX_WORD_LENGTH]; static final int[] characterBuffer = new int[MAX_WORD_LENGTH];
private static CharGroupInfo readCharGroup(RandomAccessFile source, private static CharGroupInfo readCharGroup(final ByteBuffer buffer,
final int originalGroupAddress) throws IOException { final int originalGroupAddress) {
int addressPointer = originalGroupAddress; int addressPointer = originalGroupAddress;
final int flags = source.readUnsignedByte(); final int flags = readUnsignedByte(buffer);
++addressPointer; ++addressPointer;
final int characters[]; final int characters[];
if (0 != (flags & FLAG_HAS_MULTIPLE_CHARS)) { if (0 != (flags & FLAG_HAS_MULTIPLE_CHARS)) {
int index = 0; int index = 0;
int character = CharEncoding.readChar(source); int character = CharEncoding.readChar(buffer);
addressPointer += CharEncoding.getCharSize(character); addressPointer += CharEncoding.getCharSize(character);
while (-1 != character) { while (-1 != character) {
characterBuffer[index++] = character; characterBuffer[index++] = character;
character = CharEncoding.readChar(source); character = CharEncoding.readChar(buffer);
addressPointer += CharEncoding.getCharSize(character); addressPointer += CharEncoding.getCharSize(character);
} }
characters = Arrays.copyOfRange(characterBuffer, 0, index); characters = Arrays.copyOfRange(characterBuffer, 0, index);
} else { } else {
final int character = CharEncoding.readChar(source); final int character = CharEncoding.readChar(buffer);
addressPointer += CharEncoding.getCharSize(character); addressPointer += CharEncoding.getCharSize(character);
characters = new int[] { character }; characters = new int[] { character };
} }
final int frequency; final int frequency;
if (0 != (FLAG_IS_TERMINAL & flags)) { if (0 != (FLAG_IS_TERMINAL & flags)) {
++addressPointer; ++addressPointer;
frequency = source.readUnsignedByte(); frequency = readUnsignedByte(buffer);
} else { } else {
frequency = CharGroup.NOT_A_TERMINAL; frequency = CharGroup.NOT_A_TERMINAL;
} }
int childrenAddress = addressPointer; int childrenAddress = addressPointer;
switch (flags & MASK_GROUP_ADDRESS_TYPE) { switch (flags & MASK_GROUP_ADDRESS_TYPE) {
case FLAG_GROUP_ADDRESS_TYPE_ONEBYTE: case FLAG_GROUP_ADDRESS_TYPE_ONEBYTE:
childrenAddress += source.readUnsignedByte(); childrenAddress += readUnsignedByte(buffer);
addressPointer += 1; addressPointer += 1;
break; break;
case FLAG_GROUP_ADDRESS_TYPE_TWOBYTES: case FLAG_GROUP_ADDRESS_TYPE_TWOBYTES:
childrenAddress += source.readUnsignedShort(); childrenAddress += readUnsignedShort(buffer);
addressPointer += 2; addressPointer += 2;
break; break;
case FLAG_GROUP_ADDRESS_TYPE_THREEBYTES: case FLAG_GROUP_ADDRESS_TYPE_THREEBYTES:
childrenAddress += (source.readUnsignedByte() << 16) + source.readUnsignedShort(); childrenAddress += readUnsignedInt24(buffer);
addressPointer += 3; addressPointer += 3;
break; break;
case FLAG_GROUP_ADDRESS_TYPE_NOADDRESS: case FLAG_GROUP_ADDRESS_TYPE_NOADDRESS:
@ -1140,38 +1142,38 @@ public class BinaryDictInputOutput {
} }
ArrayList<WeightedString> shortcutTargets = null; ArrayList<WeightedString> shortcutTargets = null;
if (0 != (flags & FLAG_HAS_SHORTCUT_TARGETS)) { if (0 != (flags & FLAG_HAS_SHORTCUT_TARGETS)) {
final long pointerBefore = source.getFilePointer(); final int pointerBefore = buffer.position();
shortcutTargets = new ArrayList<WeightedString>(); shortcutTargets = new ArrayList<WeightedString>();
source.readUnsignedShort(); // Skip the size buffer.getShort(); // Skip the size
while (true) { while (true) {
final int targetFlags = source.readUnsignedByte(); final int targetFlags = readUnsignedByte(buffer);
final String word = CharEncoding.readString(source); final String word = CharEncoding.readString(buffer);
shortcutTargets.add(new WeightedString(word, shortcutTargets.add(new WeightedString(word,
targetFlags & FLAG_ATTRIBUTE_FREQUENCY)); targetFlags & FLAG_ATTRIBUTE_FREQUENCY));
if (0 == (targetFlags & FLAG_ATTRIBUTE_HAS_NEXT)) break; if (0 == (targetFlags & FLAG_ATTRIBUTE_HAS_NEXT)) break;
} }
addressPointer += (source.getFilePointer() - pointerBefore); addressPointer += buffer.position() - pointerBefore;
} }
ArrayList<PendingAttribute> bigrams = null; ArrayList<PendingAttribute> bigrams = null;
if (0 != (flags & FLAG_HAS_BIGRAMS)) { if (0 != (flags & FLAG_HAS_BIGRAMS)) {
bigrams = new ArrayList<PendingAttribute>(); bigrams = new ArrayList<PendingAttribute>();
while (true) { while (true) {
final int bigramFlags = source.readUnsignedByte(); final int bigramFlags = readUnsignedByte(buffer);
++addressPointer; ++addressPointer;
final int sign = 0 == (bigramFlags & FLAG_ATTRIBUTE_OFFSET_NEGATIVE) ? 1 : -1; final int sign = 0 == (bigramFlags & FLAG_ATTRIBUTE_OFFSET_NEGATIVE) ? 1 : -1;
int bigramAddress = addressPointer; int bigramAddress = addressPointer;
switch (bigramFlags & MASK_ATTRIBUTE_ADDRESS_TYPE) { switch (bigramFlags & MASK_ATTRIBUTE_ADDRESS_TYPE) {
case FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE: case FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE:
bigramAddress += sign * source.readUnsignedByte(); bigramAddress += sign * readUnsignedByte(buffer);
addressPointer += 1; addressPointer += 1;
break; break;
case FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES: case FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES:
bigramAddress += sign * source.readUnsignedShort(); bigramAddress += sign * readUnsignedShort(buffer);
addressPointer += 2; addressPointer += 2;
break; break;
case FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES: case FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES:
final int offset = ((source.readUnsignedByte() << 16) final int offset = (readUnsignedByte(buffer) << 16)
+ source.readUnsignedShort()); + readUnsignedShort(buffer);
bigramAddress += sign * offset; bigramAddress += sign * offset;
addressPointer += 3; addressPointer += 3;
break; 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 { private static int readCharGroupCount(final ByteBuffer buffer) {
final int msb = source.readUnsignedByte(); final int msb = readUnsignedByte(buffer);
if (MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT >= msb) { if (MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT >= msb) {
return msb; return msb;
} else { } else {
return ((MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT & msb) << 8) 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 // 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 // may be called hundreds of thousands of times, the resulting performance is not
// reasonable without some kind of cache. Thus: // 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>(); private static TreeMap<Integer, String> wordCache = new TreeMap<Integer, String>();
/** /**
* Finds, as a string, the word at the address passed as an argument. * 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 headerSize the size of the header.
* @param address the address to seek. * @param address the address to seek.
* @return the word, as a string. * @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, private static String getWordAtAddress(final ByteBuffer buffer, final int headerSize,
int address) throws IOException { final int address) {
final String cachedString = wordCache.get(address); final String cachedString = wordCache.get(address);
if (null != cachedString) return cachedString; if (null != cachedString) return cachedString;
final long originalPointer = source.getFilePointer(); final int originalPointer = buffer.position();
source.seek(headerSize); buffer.position(headerSize);
final int count = readCharGroupCount(source); final int count = readCharGroupCount(buffer);
int groupOffset = getGroupCountSize(count); int groupOffset = getGroupCountSize(count);
final StringBuilder builder = new StringBuilder(); final StringBuilder builder = new StringBuilder();
String result = null; String result = null;
CharGroupInfo last = null; CharGroupInfo last = null;
for (int i = count - 1; i >= 0; --i) { for (int i = count - 1; i >= 0; --i) {
CharGroupInfo info = readCharGroup(source, groupOffset); CharGroupInfo info = readCharGroup(buffer, groupOffset);
groupOffset = info.mEndAddress; groupOffset = info.mEndAddress;
if (info.mOriginalAddress == address) { if (info.mOriginalAddress == address) {
builder.append(new String(info.mCharacters, 0, info.mCharacters.length)); builder.append(new String(info.mCharacters, 0, info.mCharacters.length));
@ -1239,9 +1239,9 @@ public class BinaryDictInputOutput {
if (info.mChildrenAddress > address) { if (info.mChildrenAddress > address) {
if (null == last) continue; if (null == last) continue;
builder.append(new String(last.mCharacters, 0, last.mCharacters.length)); builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
source.seek(last.mChildrenAddress + headerSize); buffer.position(last.mChildrenAddress + headerSize);
groupOffset = last.mChildrenAddress + 1; groupOffset = last.mChildrenAddress + 1;
i = source.readUnsignedByte(); i = readUnsignedByte(buffer);
last = null; last = null;
continue; continue;
} }
@ -1249,14 +1249,14 @@ public class BinaryDictInputOutput {
} }
if (0 == i && hasChildrenAddress(last.mChildrenAddress)) { if (0 == i && hasChildrenAddress(last.mChildrenAddress)) {
builder.append(new String(last.mCharacters, 0, last.mCharacters.length)); builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
source.seek(last.mChildrenAddress + headerSize); buffer.position(last.mChildrenAddress + headerSize);
groupOffset = last.mChildrenAddress + 1; groupOffset = last.mChildrenAddress + 1;
i = source.readUnsignedByte(); i = readUnsignedByte(buffer);
last = null; last = null;
continue; continue;
} }
} }
source.seek(originalPointer); buffer.position(originalPointer);
wordCache.put(address, result); wordCache.put(address, result);
return result; return result;
} }
@ -1269,44 +1269,47 @@ public class BinaryDictInputOutput {
* This will recursively read other nodes into the structure, populating the reverse * 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. * 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 headerSize the size, in bytes, of the file header.
* @param reverseNodeMap a mapping from addresses to already read nodes. * @param reverseNodeMap a mapping from addresses to already read nodes.
* @param reverseGroupMap a mapping from addresses to already read character groups. * @param reverseGroupMap a mapping from addresses to already read character groups.
* @return the read node with all his children already read. * @return the read node with all his children already read.
*/ */
private static Node readNode(RandomAccessFile source, long headerSize, private static Node readNode(final ByteBuffer buffer, final int headerSize,
Map<Integer, Node> reverseNodeMap, Map<Integer, CharGroup> reverseGroupMap) final Map<Integer, Node> reverseNodeMap, final Map<Integer, CharGroup> reverseGroupMap)
throws IOException { throws IOException {
final int nodeOrigin = (int)(source.getFilePointer() - headerSize); final int nodeOrigin = buffer.position() - headerSize;
final int count = readCharGroupCount(source); final int count = readCharGroupCount(buffer);
final ArrayList<CharGroup> nodeContents = new ArrayList<CharGroup>(); final ArrayList<CharGroup> nodeContents = new ArrayList<CharGroup>();
int groupOffset = nodeOrigin + getGroupCountSize(count); int groupOffset = nodeOrigin + getGroupCountSize(count);
for (int i = count; i > 0; --i) { for (int i = count; i > 0; --i) {
CharGroupInfo info = readCharGroup(source, groupOffset); CharGroupInfo info =readCharGroup(buffer, groupOffset);
ArrayList<WeightedString> shortcutTargets = info.mShortcutTargets; ArrayList<WeightedString> shortcutTargets = info.mShortcutTargets;
ArrayList<WeightedString> bigrams = null; ArrayList<WeightedString> bigrams = null;
if (null != info.mBigrams) { if (null != info.mBigrams) {
bigrams = new ArrayList<WeightedString>(); bigrams = new ArrayList<WeightedString>();
for (PendingAttribute bigram : info.mBigrams) { 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)); bigrams.add(new WeightedString(word, bigram.mFrequency));
} }
} }
if (hasChildrenAddress(info.mChildrenAddress)) { if (hasChildrenAddress(info.mChildrenAddress)) {
Node children = reverseNodeMap.get(info.mChildrenAddress); Node children = reverseNodeMap.get(info.mChildrenAddress);
if (null == children) { if (null == children) {
final long currentPosition = source.getFilePointer(); final int currentPosition = buffer.position();
source.seek(info.mChildrenAddress + headerSize); buffer.position(info.mChildrenAddress + headerSize);
children = readNode(source, headerSize, reverseNodeMap, reverseGroupMap); children = readNode(
source.seek(currentPosition); buffer, headerSize, reverseNodeMap, reverseGroupMap);
buffer.position(currentPosition);
} }
nodeContents.add( nodeContents.add(
new CharGroup(info.mCharacters, shortcutTargets, bigrams, info.mFrequency, new CharGroup(info.mCharacters, shortcutTargets,
children)); bigrams, info.mFrequency, children));
} else { } else {
nodeContents.add( nodeContents.add(
new CharGroup(info.mCharacters, shortcutTargets, bigrams, info.mFrequency)); new CharGroup(info.mCharacters, shortcutTargets,
bigrams, info.mFrequency));
} }
groupOffset = info.mEndAddress; groupOffset = info.mEndAddress;
} }
@ -1318,57 +1321,76 @@ public class BinaryDictInputOutput {
/** /**
* Helper function to get the binary format version from the header. * Helper function to get the binary format version from the header.
* @throws IOException
*/ */
private static int getFormatVersion(final RandomAccessFile source) throws IOException { private static int getFormatVersion(final ByteBuffer buffer) throws IOException {
final int magic_v1 = source.readUnsignedShort(); final int magic_v1 = readUnsignedShort(buffer);
if (VERSION_1_MAGIC_NUMBER == magic_v1) return source.readUnsignedByte(); if (VERSION_1_MAGIC_NUMBER == magic_v1) return readUnsignedByte(buffer);
final int magic_v2 = (magic_v1 << 16) + source.readUnsignedShort(); final int magic_v2 = (magic_v1 << 16) + readUnsignedShort(buffer);
if (VERSION_2_MAGIC_NUMBER == magic_v2) return source.readUnsignedShort(); if (VERSION_2_MAGIC_NUMBER == magic_v2) return readUnsignedShort(buffer);
return NOT_A_VERSION_NUMBER; 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 * 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 * 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. * 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. * @param dict an optional dictionary to add words to, or null.
* @return the created (or merged) dictionary. * @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 { final FusionDictionary dict) throws IOException, UnsupportedFormatException {
// Check file version // Check file version
final int version = getFormatVersion(source); final int version = getFormatVersion(buffer);
if (version < MINIMUM_SUPPORTED_VERSION || version > MAXIMUM_SUPPORTED_VERSION ) { if (version < MINIMUM_SUPPORTED_VERSION || version > MAXIMUM_SUPPORTED_VERSION) {
throw new UnsupportedFormatException("This file has version " + version throw new UnsupportedFormatException("This file has version " + version
+ ", but this implementation does not support versions above " + ", but this implementation does not support versions above "
+ MAXIMUM_SUPPORTED_VERSION); + MAXIMUM_SUPPORTED_VERSION);
} }
// Read options // clear cache
final int optionsFlags = source.readUnsignedShort(); wordCache.clear();
final long headerSize; // Read options
final int optionsFlags = readUnsignedShort(buffer);
final int headerSize;
final HashMap<String, String> options = new HashMap<String, String>(); final HashMap<String, String> options = new HashMap<String, String>();
if (version < FIRST_VERSION_WITH_HEADER_SIZE) { if (version < FIRST_VERSION_WITH_HEADER_SIZE) {
headerSize = source.getFilePointer(); headerSize = buffer.position();
} else { } else {
headerSize = (source.readUnsignedByte() << 24) + (source.readUnsignedByte() << 16) headerSize = buffer.getInt();
+ (source.readUnsignedByte() << 8) + source.readUnsignedByte(); populateOptions(buffer, headerSize, options);
while (source.getFilePointer() < headerSize) { buffer.position(headerSize);
final String key = CharEncoding.readString(source); }
final String value = CharEncoding.readString(source);
options.put(key, value); if (headerSize < 0) {
} throw new UnsupportedFormatException("header size can't be negative.");
source.seek(headerSize);
} }
Map<Integer, Node> reverseNodeMapping = new TreeMap<Integer, Node>(); Map<Integer, Node> reverseNodeMapping = new TreeMap<Integer, Node>();
Map<Integer, CharGroup> reverseGroupMapping = new TreeMap<Integer, CharGroup>(); 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, FusionDictionary newDict = new FusionDictionary(root,
new FusionDictionary.DictionaryOptions(options, new FusionDictionary.DictionaryOptions(options,
@ -1391,6 +1413,28 @@ public class BinaryDictInputOutput {
return newDict; 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. * 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 * @return true if it's a binary dictionary, false otherwise
*/ */
public static boolean isBinaryDictionary(final String filename) { public static boolean isBinaryDictionary(final String filename) {
FileInputStream inStream = null;
try { try {
RandomAccessFile f = new RandomAccessFile(filename, "r"); final File file = new File(filename);
final int version = getFormatVersion(f); 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); return (version >= MINIMUM_SUPPORTED_VERSION && version <= MAXIMUM_SUPPORTED_VERSION);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
return false; return false;
} catch (IOException e) { } catch (IOException e) {
return false; 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)); int indexOfGroup = findIndexOfChar(node, s.codePointAt(index));
if (CHARACTER_NOT_FOUND == indexOfGroup) return null; if (CHARACTER_NOT_FOUND == indexOfGroup) return null;
currentGroup = node.mData.get(indexOfGroup); 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)); if (DBG) checker.append(new String(currentGroup.mChars, 0, currentGroup.mChars.length));
index += currentGroup.mChars.length;
if (index < s.length()) { if (index < s.length()) {
node = currentGroup.mChildren; node = currentGroup.mChildren;
} }
} while (null != node && index < s.length()); } 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; if (DBG && !s.equals(checker.toString())) return null;
return currentGroup; return currentGroup;
} }

View File

@ -25,6 +25,7 @@ import android.view.textservice.SuggestionsInfo;
import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.BinaryDictionary; import com.android.inputmethod.latin.BinaryDictionary;
import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.ContactsBinaryDictionary; import com.android.inputmethod.latin.ContactsBinaryDictionary;
import com.android.inputmethod.latin.Dictionary; import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.DictionaryCollection; 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.SynchronouslyLoadedContactsBinaryDictionary;
import com.android.inputmethod.latin.SynchronouslyLoadedUserBinaryDictionary; import com.android.inputmethod.latin.SynchronouslyLoadedUserBinaryDictionary;
import com.android.inputmethod.latin.UserBinaryDictionary; import com.android.inputmethod.latin.UserBinaryDictionary;
import com.android.inputmethod.latin.WhitelistDictionary;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.ArrayList;
@ -63,12 +63,9 @@ public class AndroidSpellCheckerService extends SpellCheckerService
public static final int CAPITALIZE_ALL = 2; // All caps public static final int CAPITALIZE_ALL = 2; // All caps
private final static String[] EMPTY_STRING_ARRAY = new String[0]; private final static String[] EMPTY_STRING_ARRAY = new String[0];
private Map<String, DictionaryPool> mDictionaryPools = private Map<String, DictionaryPool> mDictionaryPools = CollectionUtils.newSynchronizedTreeMap();
Collections.synchronizedMap(new TreeMap<String, DictionaryPool>());
private Map<String, UserBinaryDictionary> mUserDictionaries = private Map<String, UserBinaryDictionary> mUserDictionaries =
Collections.synchronizedMap(new TreeMap<String, UserBinaryDictionary>()); CollectionUtils.newSynchronizedTreeMap();
private Map<String, Dictionary> mWhitelistDictionaries =
Collections.synchronizedMap(new TreeMap<String, Dictionary>());
private ContactsBinaryDictionary mContactsDictionary; private ContactsBinaryDictionary mContactsDictionary;
// The threshold for a candidate to be offered as a suggestion. // 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 Object mUseContactsLock = new Object();
private final HashSet<WeakReference<DictionaryCollection>> mDictionaryCollectionsList = private final HashSet<WeakReference<DictionaryCollection>> mDictionaryCollectionsList =
new HashSet<WeakReference<DictionaryCollection>>(); CollectionUtils.newHashSet();
public static final int SCRIPT_LATIN = 0; public static final int SCRIPT_LATIN = 0;
public static final int SCRIPT_CYRILLIC = 1; public static final int SCRIPT_CYRILLIC = 1;
@ -96,7 +93,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService
// proximity to pass to the dictionary descent algorithm. // proximity to pass to the dictionary descent algorithm.
// IMPORTANT: this only contains languages - do not write countries in there. // IMPORTANT: this only contains languages - do not write countries in there.
// Only the language is searched from the map. // Only the language is searched from the map.
mLanguageToScript = new TreeMap<String, Integer>(); mLanguageToScript = CollectionUtils.newTreeMap();
mLanguageToScript.put("en", SCRIPT_LATIN); mLanguageToScript.put("en", SCRIPT_LATIN);
mLanguageToScript.put("fr", SCRIPT_LATIN); mLanguageToScript.put("fr", SCRIPT_LATIN);
mLanguageToScript.put("de", SCRIPT_LATIN); mLanguageToScript.put("de", SCRIPT_LATIN);
@ -234,7 +231,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService
mSuggestionThreshold = suggestionThreshold; mSuggestionThreshold = suggestionThreshold;
mRecommendedThreshold = recommendedThreshold; mRecommendedThreshold = recommendedThreshold;
mMaxLength = maxLength; mMaxLength = maxLength;
mSuggestions = new ArrayList<CharSequence>(maxLength + 1); mSuggestions = CollectionUtils.newArrayList(maxLength + 1);
mScores = new int[mMaxLength]; mScores = new int[mMaxLength];
} }
@ -362,12 +359,9 @@ public class AndroidSpellCheckerService extends SpellCheckerService
private void closeAllDictionaries() { private void closeAllDictionaries() {
final Map<String, DictionaryPool> oldPools = mDictionaryPools; final Map<String, DictionaryPool> oldPools = mDictionaryPools;
mDictionaryPools = Collections.synchronizedMap(new TreeMap<String, DictionaryPool>()); mDictionaryPools = CollectionUtils.newSynchronizedTreeMap();
final Map<String, UserBinaryDictionary> oldUserDictionaries = mUserDictionaries; final Map<String, UserBinaryDictionary> oldUserDictionaries = mUserDictionaries;
mUserDictionaries = mUserDictionaries = CollectionUtils.newSynchronizedTreeMap();
Collections.synchronizedMap(new TreeMap<String, UserBinaryDictionary>());
final Map<String, Dictionary> oldWhitelistDictionaries = mWhitelistDictionaries;
mWhitelistDictionaries = Collections.synchronizedMap(new TreeMap<String, Dictionary>());
new Thread("spellchecker_close_dicts") { new Thread("spellchecker_close_dicts") {
@Override @Override
public void run() { public void run() {
@ -377,9 +371,6 @@ public class AndroidSpellCheckerService extends SpellCheckerService
for (Dictionary dict : oldUserDictionaries.values()) { for (Dictionary dict : oldUserDictionaries.values()) {
dict.close(); dict.close();
} }
for (Dictionary dict : oldWhitelistDictionaries.values()) {
dict.close();
}
synchronized (mUseContactsLock) { synchronized (mUseContactsLock) {
if (null != mContactsDictionary) { if (null != mContactsDictionary) {
// The synchronously loaded contacts dictionary should have been in one // The synchronously loaded contacts dictionary should have been in one
@ -423,12 +414,6 @@ public class AndroidSpellCheckerService extends SpellCheckerService
mUserDictionaries.put(localeStr, userDictionary); mUserDictionaries.put(localeStr, userDictionary);
} }
dictionaryCollection.addDictionary(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) { synchronized (mUseContactsLock) {
if (mUseContactsDictionary) { if (mUseContactsDictionary) {
if (null == mContactsDictionary) { if (null == mContactsDictionary) {

View File

@ -22,6 +22,8 @@ import android.view.textservice.SentenceSuggestionsInfo;
import android.view.textservice.SuggestionsInfo; import android.view.textservice.SuggestionsInfo;
import android.view.textservice.TextInfo; import android.view.textservice.TextInfo;
import com.android.inputmethod.latin.CollectionUtils;
import java.util.ArrayList; import java.util.ArrayList;
public class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheckerSession { public class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheckerSession {
@ -40,10 +42,10 @@ public class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheckerSess
return null; return null;
} }
final int N = ssi.getSuggestionsCount(); final int N = ssi.getSuggestionsCount();
final ArrayList<Integer> additionalOffsets = new ArrayList<Integer>(); final ArrayList<Integer> additionalOffsets = CollectionUtils.newArrayList();
final ArrayList<Integer> additionalLengths = new ArrayList<Integer>(); final ArrayList<Integer> additionalLengths = CollectionUtils.newArrayList();
final ArrayList<SuggestionsInfo> additionalSuggestionsInfos = final ArrayList<SuggestionsInfo> additionalSuggestionsInfos =
new ArrayList<SuggestionsInfo>(); CollectionUtils.newArrayList();
String currentWord = null; String currentWord = null;
for (int i = 0; i < N; ++i) { for (int i = 0; i < N; ++i) {
final SuggestionsInfo si = ssi.getSuggestionsInfoAt(i); final SuggestionsInfo si = ssi.getSuggestionsInfoAt(i);

View File

@ -24,6 +24,7 @@ import android.view.textservice.SuggestionsInfo;
import android.view.textservice.TextInfo; import android.view.textservice.TextInfo;
import com.android.inputmethod.compat.SuggestionsInfoCompatUtils; import com.android.inputmethod.compat.SuggestionsInfoCompatUtils;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.LocaleUtils; import com.android.inputmethod.latin.LocaleUtils;
import com.android.inputmethod.latin.WordComposer; import com.android.inputmethod.latin.WordComposer;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
@ -193,8 +194,8 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
if (shouldFilterOut(inText, mScript)) { if (shouldFilterOut(inText, mScript)) {
DictAndProximity dictInfo = null; DictAndProximity dictInfo = null;
try { try {
dictInfo = mDictionaryPool.takeOrGetNull(); dictInfo = mDictionaryPool.pollWithDefaultTimeout();
if (null == dictInfo) { if (!DictionaryPool.isAValidDictionary(dictInfo)) {
return AndroidSpellCheckerService.getNotInDictEmptySuggestions(); return AndroidSpellCheckerService.getNotInDictEmptySuggestions();
} }
return dictInfo.mDictionary.isValidWord(inText) return dictInfo.mDictionary.isValidWord(inText)
@ -225,8 +226,8 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
final int xy = SpellCheckerProximityInfo.getXYForCodePointAndScript( final int xy = SpellCheckerProximityInfo.getXYForCodePointAndScript(
codePoint, mScript); codePoint, mScript);
if (SpellCheckerProximityInfo.NOT_A_COORDINATE_PAIR == xy) { if (SpellCheckerProximityInfo.NOT_A_COORDINATE_PAIR == xy) {
composer.add(codePoint, WordComposer.NOT_A_COORDINATE, composer.add(codePoint,
WordComposer.NOT_A_COORDINATE); Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
} else { } else {
composer.add(codePoint, xy & 0xFFFF, xy >> 16); composer.add(codePoint, xy & 0xFFFF, xy >> 16);
} }
@ -236,8 +237,8 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
boolean isInDict = true; boolean isInDict = true;
DictAndProximity dictInfo = null; DictAndProximity dictInfo = null;
try { try {
dictInfo = mDictionaryPool.takeOrGetNull(); dictInfo = mDictionaryPool.pollWithDefaultTimeout();
if (null == dictInfo) { if (!DictionaryPool.isAValidDictionary(dictInfo)) {
return AndroidSpellCheckerService.getNotInDictEmptySuggestions(); return AndroidSpellCheckerService.getNotInDictEmptySuggestions();
} }
final ArrayList<SuggestedWordInfo> suggestions = final ArrayList<SuggestedWordInfo> suggestions =

View File

@ -16,19 +16,56 @@
package com.android.inputmethod.latin.spellcheck; 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.Locale;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/** /**
* A blocking queue that creates dictionaries up to a certain limit as necessary. * 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") @SuppressWarnings("serial")
public class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> { 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 AndroidSpellCheckerService mService;
private final int mMaxSize; private final int mMaxSize;
private final Locale mLocale; private final Locale mLocale;
private int mSize; private int mSize;
private volatile boolean mClosed; 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, public DictionaryPool(final int maxSize, final AndroidSpellCheckerService service,
final Locale locale) { final Locale locale) {
@ -41,13 +78,23 @@ public class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> {
} }
@Override @Override
public DictAndProximity take() throws InterruptedException { public DictAndProximity poll(final long timeout, final TimeUnit unit)
throws InterruptedException {
final DictAndProximity dict = poll(); final DictAndProximity dict = poll();
if (null != dict) return dict; if (null != dict) return dict;
synchronized(this) { synchronized(this) {
if (mSize >= mMaxSize) { if (mSize >= mMaxSize) {
// Our pool is already full. Wait until some dictionary is ready. // Our pool is already full. Wait until some dictionary is ready, or TIMEOUT
return super.take(); // 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 { } else {
++mSize; ++mSize;
return mService.createDictAndProximity(mLocale); return mService.createDictAndProximity(mLocale);
@ -56,9 +103,9 @@ public class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> {
} }
// Convenience method // Convenience method
public DictAndProximity takeOrGetNull() { public DictAndProximity pollWithDefaultTimeout() {
try { try {
return take(); return poll(TIMEOUT, TimeUnit.SECONDS);
} catch (InterruptedException e) { } catch (InterruptedException e) {
return null; return null;
} }
@ -78,7 +125,7 @@ public class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> {
public boolean offer(final DictAndProximity dict) { public boolean offer(final DictAndProximity dict) {
if (mClosed) { if (mClosed) {
dict.mDictionary.close(); dict.mDictionary.close();
return false; return super.offer(dummyDict);
} else { } else {
return super.offer(dict); return super.offer(dict);
} }

View File

@ -16,14 +16,15 @@
package com.android.inputmethod.latin.spellcheck; package com.android.inputmethod.latin.spellcheck;
import com.android.inputmethod.keyboard.KeyDetector;
import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.Constants;
import java.util.TreeMap; import java.util.TreeMap;
public class SpellCheckerProximityInfo { public class SpellCheckerProximityInfo {
/* public for test */ /* 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 // 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 // native code - this value is passed at creation of the binary object and reused
@ -59,7 +60,7 @@ public class SpellCheckerProximityInfo {
// character. // character.
// Since we need to build such an array, we want to be able to search in our big proximity // 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. // 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 here is the union of
// - the proximity for a QWERTY keyboard. // - 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,
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 { static {
buildProximityIndices(PROXIMITY, INDICES); buildProximityIndices(PROXIMITY, INDICES);
@ -121,7 +123,7 @@ public class SpellCheckerProximityInfo {
} }
private static class Cyrillic { 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 // TODO: The following table is solely based on the keyboard layout. Consult with Russian
// speakers on commonly misspelled words/letters. // speakers on commonly misspelled words/letters.
final static int[] PROXIMITY = { 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.PointerTracker;
import com.android.inputmethod.keyboard.ViewLayoutUtils; import com.android.inputmethod.keyboard.ViewLayoutUtils;
import com.android.inputmethod.latin.AutoCorrection; import com.android.inputmethod.latin.AutoCorrection;
import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.StaticInnerHandlerWrapper; import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
@ -72,7 +73,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
OnLongClickListener { OnLongClickListener {
public interface Listener { public interface Listener {
public boolean addWordToUserDictionary(String word); 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}. // 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 MoreSuggestions.Builder mMoreSuggestionsBuilder;
private final PopupWindow mMoreSuggestionsWindow; private final PopupWindow mMoreSuggestionsWindow;
private final ArrayList<TextView> mWords = new ArrayList<TextView>(); private final ArrayList<TextView> mWords = CollectionUtils.newArrayList();
private final ArrayList<TextView> mInfos = new ArrayList<TextView>(); private final ArrayList<TextView> mInfos = CollectionUtils.newArrayList();
private final ArrayList<View> mDividers = new ArrayList<View>(); private final ArrayList<View> mDividers = CollectionUtils.newArrayList();
private final PopupWindow mPreviewPopup; private final PopupWindow mPreviewPopup;
private final TextView mPreviewText; private final TextView mPreviewText;
@ -131,7 +132,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
private static class SuggestionStripViewParams { private static class SuggestionStripViewParams {
private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3; 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 DEFAULT_MAX_MORE_SUGGESTIONS_ROW = 2;
private static final int PUNCTUATIONS_IN_STRIP = 5; 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 int mSuggestionStripOption;
private final ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>(); private final ArrayList<CharSequence> mTexts = CollectionUtils.newArrayList();
public boolean mMoreSuggestionsAvailable; public boolean mMoreSuggestionsAvailable;
@ -195,16 +196,16 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
R.styleable.SuggestionStripView, defStyle, R.style.SuggestionStripViewStyle); R.styleable.SuggestionStripView, defStyle, R.style.SuggestionStripViewStyle);
mSuggestionStripOption = a.getInt( mSuggestionStripOption = a.getInt(
R.styleable.SuggestionStripView_suggestionStripOption, 0); R.styleable.SuggestionStripView_suggestionStripOption, 0);
final float alphaValidTypedWord = getPercent(a, final float alphaValidTypedWord = getFraction(a,
R.styleable.SuggestionStripView_alphaValidTypedWord, 100); R.styleable.SuggestionStripView_alphaValidTypedWord, 1.0f);
final float alphaTypedWord = getPercent(a, final float alphaTypedWord = getFraction(a,
R.styleable.SuggestionStripView_alphaTypedWord, 100); R.styleable.SuggestionStripView_alphaTypedWord, 1.0f);
final float alphaAutoCorrect = getPercent(a, final float alphaAutoCorrect = getFraction(a,
R.styleable.SuggestionStripView_alphaAutoCorrect, 100); R.styleable.SuggestionStripView_alphaAutoCorrect, 1.0f);
final float alphaSuggested = getPercent(a, final float alphaSuggested = getFraction(a,
R.styleable.SuggestionStripView_alphaSuggested, 100); R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
mAlphaObsoleted = getPercent(a, mAlphaObsoleted = getFraction(a,
R.styleable.SuggestionStripView_alphaSuggested, 100); R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
mColorValidTypedWord = applyAlpha(a.getColor( mColorValidTypedWord = applyAlpha(a.getColor(
R.styleable.SuggestionStripView_colorValidTypedWord, 0), alphaValidTypedWord); R.styleable.SuggestionStripView_colorValidTypedWord, 0), alphaValidTypedWord);
mColorTypedWord = applyAlpha(a.getColor( mColorTypedWord = applyAlpha(a.getColor(
@ -216,14 +217,14 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
mSuggestionsCountInStrip = a.getInt( mSuggestionsCountInStrip = a.getInt(
R.styleable.SuggestionStripView_suggestionsCountInStrip, R.styleable.SuggestionStripView_suggestionsCountInStrip,
DEFAULT_SUGGESTIONS_COUNT_IN_STRIP); DEFAULT_SUGGESTIONS_COUNT_IN_STRIP);
mCenterSuggestionWeight = getPercent(a, mCenterSuggestionWeight = getFraction(a,
R.styleable.SuggestionStripView_centerSuggestionPercentile, R.styleable.SuggestionStripView_centerSuggestionPercentile,
DEFAULT_CENTER_SUGGESTION_PERCENTILE); DEFAULT_CENTER_SUGGESTION_PERCENTILE);
mMaxMoreSuggestionsRow = a.getInt( mMaxMoreSuggestionsRow = a.getInt(
R.styleable.SuggestionStripView_maxMoreSuggestionsRow, R.styleable.SuggestionStripView_maxMoreSuggestionsRow,
DEFAULT_MAX_MORE_SUGGESTIONS_ROW); DEFAULT_MAX_MORE_SUGGESTIONS_ROW);
mMinMoreSuggestionsWidth = getRatio(a, mMinMoreSuggestionsWidth = getFraction(a,
R.styleable.SuggestionStripView_minMoreSuggestionsWidth); R.styleable.SuggestionStripView_minMoreSuggestionsWidth, 1.0f);
a.recycle(); a.recycle();
mMoreSuggestionsHint = getMoreSuggestionsHint(res, mMoreSuggestionsHint = getMoreSuggestionsHint(res,
@ -277,14 +278,8 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
return new BitmapDrawable(res, buffer); return new BitmapDrawable(res, buffer);
} }
// Read integer value in TypedArray as percent. static float getFraction(final TypedArray a, final int index, final float defValue) {
private static float getPercent(TypedArray a, int index, int defValue) { return a.getFraction(index, 1, 1, defValue);
return a.getInt(index, defValue) / 100.0f;
}
// Read fraction value in TypedArray as float.
private static float getRatio(TypedArray a, int index) {
return a.getFraction(index, 1000, 1000, 1) / 1000.0f;
} }
private CharSequence getStyledSuggestionWord(SuggestedWords suggestedWords, int pos) { private CharSequence getStyledSuggestionWord(SuggestedWords suggestedWords, int pos) {
@ -726,9 +721,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
public boolean onCustomRequest(int requestCode) { public boolean onCustomRequest(int requestCode) {
final int index = requestCode; final int index = requestCode;
final CharSequence word = mSuggestedWords.getWord(index); final CharSequence word = mSuggestedWords.getWord(index);
// TODO: change caller path so coordinates are passed through here mListener.pickSuggestionManually(index, word);
mListener.pickSuggestionManually(index, word, NOT_A_TOUCH_COORDINATE,
NOT_A_TOUCH_COORDINATE);
dismissMoreSuggestions(); dismissMoreSuggestions();
return true; return true;
} }
@ -874,7 +867,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
return; return;
final CharSequence word = mSuggestedWords.getWord(index); final CharSequence word = mSuggestedWords.getWord(index);
mListener.pickSuggestionManually(index, word, mLastX, mLastY); mListener.pickSuggestionManually(index, word);
} }
@Override @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.app.Activity;
import android.os.Bundle; import android.os.Bundle;
import android.text.Editable;
import android.view.View;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.EditText;
import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.R;
@ -31,6 +28,11 @@ public class FeedbackActivity extends Activity {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.research_feedback_activity); setContentView(R.layout.research_feedback_activity);
final FeedbackLayout layout = (FeedbackLayout) findViewById(R.id.research_feedback_layout); 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); 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;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.research.ResearchLogger.LogUnit;
import java.io.BufferedWriter; import java.io.BufferedWriter;
import java.io.File; import java.io.File;
@ -37,6 +36,7 @@ import java.io.OutputStreamWriter;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -51,21 +51,22 @@ import java.util.concurrent.TimeUnit;
*/ */
public class ResearchLog { public class ResearchLog {
private static final String TAG = ResearchLog.class.getSimpleName(); private static final String TAG = ResearchLog.class.getSimpleName();
private static final JsonWriter NULL_JSON_WRITER = new JsonWriter( private static final boolean DEBUG = false;
new OutputStreamWriter(new NullOutputStream())); 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; /* package */ final File mFile;
private JsonWriter mJsonWriter = NULL_JSON_WRITER; 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 JsonWriter NULL_JSON_WRITER = new JsonWriter(
private static final int LOGGING_STATE_UNSTARTED = 0; new OutputStreamWriter(new NullOutputStream()));
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 class NullOutputStream extends OutputStream { private static class NullOutputStream extends OutputStream {
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
@ -84,128 +85,81 @@ public class ResearchLog {
} }
} }
public ResearchLog(File outputFile) { public ResearchLog(final File outputFile) {
mExecutor = Executors.newSingleThreadScheduledExecutor();
if (outputFile == null) { if (outputFile == null) {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
mExecutor = Executors.newSingleThreadScheduledExecutor();
mFile = outputFile; mFile = outputFile;
mLoggingState = LOGGING_STATE_UNSTARTED;
} }
public synchronized void start() throws IOException { public synchronized void close() {
switch (mLoggingState) { mExecutor.submit(new Callable<Object>() {
case LOGGING_STATE_UNSTARTED: @Override
mLoggingState = LOGGING_STATE_READY; public Object call() throws Exception {
break; try {
case LOGGING_STATE_READY: if (mHasWrittenData) {
case LOGGING_STATE_RUNNING: mJsonWriter.endArray();
case LOGGING_STATE_STOPPING: mJsonWriter.flush();
case LOGGING_STATE_STOPPED: mJsonWriter.close();
break; mHasWrittenData = false;
}
}
public synchronized void stop() {
switch (mLoggingState) {
case LOGGING_STATE_UNSTARTED:
mLoggingState = LOGGING_STATE_STOPPED;
break;
case LOGGING_STATE_READY:
case LOGGING_STATE_RUNNING:
mExecutor.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
try {
mJsonWriter.endArray();
mJsonWriter.flush();
mJsonWriter.close();
} finally {
boolean success = mFile.setWritable(false, false);
mLoggingState = LOGGING_STATE_STOPPED;
}
return null;
} }
}); } catch (Exception e) {
removeAnyScheduledFlush(); Log.d(TAG, "error when closing ResearchLog:");
mExecutor.shutdown(); e.printStackTrace();
mLoggingState = LOGGING_STATE_STOPPING; } finally {
break; if (mFile.exists()) {
case LOGGING_STATE_STOPPING: mFile.setWritable(false, false);
case LOGGING_STATE_STOPPED: }
} }
} return null;
}
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(); removeAnyScheduledFlush();
mExecutor.shutdown(); mExecutor.shutdown();
mExecutor.awaitTermination(timeoutInMs, TimeUnit.MILLISECONDS);
} }
private boolean mIsAbortSuccessful;
public synchronized void abort() { public synchronized void abort() {
switch (mLoggingState) { mExecutor.submit(new Callable<Object>() {
case LOGGING_STATE_UNSTARTED: @Override
mLoggingState = LOGGING_STATE_STOPPED; public Object call() throws Exception {
isAbortSuccessful = true; try {
break; if (mHasWrittenData) {
case LOGGING_STATE_READY: mJsonWriter.endArray();
case LOGGING_STATE_RUNNING: mJsonWriter.close();
mExecutor.submit(new Callable<Object>() { mHasWrittenData = false;
@Override
public Object call() throws Exception {
try {
mJsonWriter.endArray();
mJsonWriter.close();
} finally {
isAbortSuccessful = mFile.delete();
}
return null;
} }
}); } finally {
removeAnyScheduledFlush(); mIsAbortSuccessful = mFile.delete();
mExecutor.shutdown(); }
mLoggingState = LOGGING_STATE_STOPPING; return null;
break; }
case LOGGING_STATE_STOPPING: });
case LOGGING_STATE_STOPPED: removeAnyScheduledFlush();
} mExecutor.shutdown();
} }
private boolean isAbortSuccessful; public boolean blockingAbort() throws InterruptedException {
public boolean isAbortSuccessful() { abort();
return isAbortSuccessful; 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() { /* package */ synchronized void flush() {
switch (mLoggingState) { removeAnyScheduledFlush();
case LOGGING_STATE_UNSTARTED: mExecutor.submit(mFlushCallable);
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 @Override
public Object call() throws Exception { public Object call() throws Exception {
if (mLoggingState == LOGGING_STATE_RUNNING) { mJsonWriter.flush();
mJsonWriter.flush();
}
return null; return null;
} }
}; };
@ -224,56 +178,40 @@ public class ResearchLog {
mFlushFuture = mExecutor.schedule(mFlushCallable, FLUSH_DELAY_IN_MS, TimeUnit.MILLISECONDS); mFlushFuture = mExecutor.schedule(mFlushCallable, FLUSH_DELAY_IN_MS, TimeUnit.MILLISECONDS);
} }
public synchronized void publishPublicEvents(final LogUnit logUnit) { public synchronized void publish(final LogUnit logUnit, final boolean isIncludingPrivateData) {
switch (mLoggingState) { try {
case LOGGING_STATE_UNSTARTED: mExecutor.submit(new Callable<Object>() {
break; @Override
case LOGGING_STATE_READY: public Object call() throws Exception {
case LOGGING_STATE_RUNNING: logUnit.publishTo(ResearchLog.this, isIncludingPrivateData);
mExecutor.submit(new Callable<Object>() { scheduleFlush();
@Override return null;
public Object call() throws Exception { }
logUnit.publishPublicEventsTo(ResearchLog.this); });
scheduleFlush(); } catch (RejectedExecutionException e) {
return null; // TODO: Add code to record loss of data, and report.
}
});
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:
} }
} }
private static final String CURRENT_TIME_KEY = "_ct"; private static final String CURRENT_TIME_KEY = "_ct";
private static final String UPTIME_KEY = "_ut"; private static final String UPTIME_KEY = "_ut";
private static final String EVENT_TYPE_KEY = "_ty"; private static final String EVENT_TYPE_KEY = "_ty";
void outputEvent(final String[] keys, final Object[] values) { 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 { try {
if (mJsonWriter == NULL_JSON_WRITER) { if (mJsonWriter == NULL_JSON_WRITER) {
mJsonWriter = new JsonWriter(new BufferedWriter(new FileWriter(mFile))); mJsonWriter = new JsonWriter(new BufferedWriter(new FileWriter(mFile)));
mJsonWriter.setLenient(true);
mJsonWriter.beginArray(); mJsonWriter.beginArray();
mHasWrittenData = true;
} }
mJsonWriter.beginObject(); mJsonWriter.beginObject();
mJsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis()); mJsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis());
@ -283,8 +221,8 @@ public class ResearchLog {
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
mJsonWriter.name(keys[i + 1]); mJsonWriter.name(keys[i + 1]);
Object value = values[i]; Object value = values[i];
if (value instanceof String) { if (value instanceof CharSequence) {
mJsonWriter.value((String) value); mJsonWriter.value(value.toString());
} else if (value instanceof Number) { } else if (value instanceof Number) {
mJsonWriter.value((Number) value); mJsonWriter.value((Number) value);
} else if (value instanceof Boolean) { } else if (value instanceof Boolean) {
@ -331,14 +269,11 @@ public class ResearchLog {
SuggestedWords words = (SuggestedWords) value; SuggestedWords words = (SuggestedWords) value;
mJsonWriter.beginObject(); mJsonWriter.beginObject();
mJsonWriter.name("typedWordValid").value(words.mTypedWordValid); mJsonWriter.name("typedWordValid").value(words.mTypedWordValid);
mJsonWriter.name("willAutoCorrect") mJsonWriter.name("willAutoCorrect").value(words.mWillAutoCorrect);
.value(words.mWillAutoCorrect);
mJsonWriter.name("isPunctuationSuggestions") mJsonWriter.name("isPunctuationSuggestions")
.value(words.mIsPunctuationSuggestions); .value(words.mIsPunctuationSuggestions);
mJsonWriter.name("isObsoleteSuggestions") mJsonWriter.name("isObsoleteSuggestions").value(words.mIsObsoleteSuggestions);
.value(words.mIsObsoleteSuggestions); mJsonWriter.name("isPrediction").value(words.mIsPrediction);
mJsonWriter.name("isPrediction")
.value(words.mIsPrediction);
mJsonWriter.name("words"); mJsonWriter.name("words");
mJsonWriter.beginArray(); mJsonWriter.beginArray();
final int size = words.size(); final int size = words.size();
@ -363,8 +298,8 @@ public class ResearchLog {
try { try {
mJsonWriter.close(); mJsonWriter.close();
} catch (IllegalStateException e1) { } catch (IllegalStateException e1) {
// assume that this is just the json not being terminated properly. // Assume that this is just the json not being terminated properly.
// ignore // Ignore
} catch (IOException e1) { } catch (IOException e1) {
e1.printStackTrace(); e1.printStackTrace();
} finally { } 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 := \ LATIN_IME_JNI_SRC_FILES := \
com_android_inputmethod_keyboard_ProximityInfo.cpp \ com_android_inputmethod_keyboard_ProximityInfo.cpp \
com_android_inputmethod_latin_BinaryDictionary.cpp \ com_android_inputmethod_latin_BinaryDictionary.cpp \
com_android_inputmethod_latin_NativeUtils.cpp \ com_android_inputmethod_latin_DicTraverseSession.cpp \
jni_common.cpp jni_common.cpp
LATIN_IME_CORE_SRC_FILES := \ LATIN_IME_CORE_SRC_FILES := \
@ -46,6 +46,7 @@ LATIN_IME_CORE_SRC_FILES := \
char_utils.cpp \ char_utils.cpp \
correction.cpp \ correction.cpp \
dictionary.cpp \ dictionary.cpp \
dic_traverse_wrapper.cpp \
proximity_info.cpp \ proximity_info.cpp \
proximity_info_state.cpp \ proximity_info_state.cpp \
unigram_dictionary.cpp \ unigram_dictionary.cpp \

View File

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

View File

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

View File

@ -24,32 +24,5 @@ namespace latinime {
int registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods,
int numMethods); 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 } // namespace latinime
#endif // LATINIME_JNI_COMMON_H #endif // LATINIME_JNI_COMMON_H

View File

@ -17,7 +17,9 @@
#include "additional_proximity_chars.h" #include "additional_proximity_chars.h"
namespace latinime { 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] = { const int32_t AdditionalProximityChars::EN_US_ADDITIONAL_A[EN_US_ADDITIONAL_A_SIZE] = {
'e', 'i', 'o', 'u' 'e', 'i', 'o', 'u'

View File

@ -17,8 +17,8 @@
#ifndef LATINIME_ADDITIONAL_PROXIMITY_CHARS_H #ifndef LATINIME_ADDITIONAL_PROXIMITY_CHARS_H
#define LATINIME_ADDITIONAL_PROXIMITY_CHARS_H #define LATINIME_ADDITIONAL_PROXIMITY_CHARS_H
#include <cstring>
#include <stdint.h> #include <stdint.h>
#include <string>
#include "defines.h" #include "defines.h"
@ -27,7 +27,7 @@ namespace latinime {
class AdditionalProximityChars { class AdditionalProximityChars {
private: private:
DISALLOW_IMPLICIT_CONSTRUCTORS(AdditionalProximityChars); 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 int EN_US_ADDITIONAL_A_SIZE = 4;
static const int32_t EN_US_ADDITIONAL_A[]; static const int32_t EN_US_ADDITIONAL_A[];
static const int EN_US_ADDITIONAL_E_SIZE = 4; 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 int EN_US_ADDITIONAL_U_SIZE = 4;
static const int32_t EN_US_ADDITIONAL_U[]; static const int32_t EN_US_ADDITIONAL_U[];
static bool isEnLocale(const std::string *locale_str) { static bool isEnLocale(const char *localeStr) {
return locale_str && locale_str->size() >= LOCALE_EN_US.size() const size_t LOCALE_EN_US_SIZE = strlen(LOCALE_EN_US);
&& LOCALE_EN_US.compare(0, LOCALE_EN_US.size(), *locale_str); return localeStr && strlen(localeStr) >= LOCALE_EN_US_SIZE
&& strncmp(localeStr, LOCALE_EN_US, LOCALE_EN_US_SIZE) == 0;
} }
public: public:
static int getAdditionalCharsSize(const std::string *locale_str, const int32_t c) { static int getAdditionalCharsSize(const char *localeStr, const int32_t c) {
if (!isEnLocale(locale_str)) { if (!isEnLocale(localeStr)) {
return 0; return 0;
} }
switch(c) { switch(c) {
@ -65,8 +66,8 @@ class AdditionalProximityChars {
} }
} }
static const int32_t *getAdditionalChars(const std::string *locale_str, const int32_t c) { static const int32_t *getAdditionalChars(const char *localeStr, const int32_t c) {
if (!isEnLocale(locale_str)) { if (!isEnLocale(localeStr)) {
return 0; return 0;
} }
switch(c) { switch(c) {
@ -84,10 +85,6 @@ class AdditionalProximityChars {
return 0; return 0;
} }
} }
static bool hasAdditionalChars(const std::string *locale_str, const int32_t c) {
return getAdditionalCharsSize(locale_str, c) > 0;
}
}; };
} // namespace latinime } // namespace latinime
#endif // LATINIME_ADDITIONAL_PROXIMITY_CHARS_H #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); AKLOGI("Bigram: InsertAt -> %d MAX_PREDICTIONS: %d", insertAt, MAX_PREDICTIONS);
} }
if (insertAt < MAX_PREDICTIONS) { if (insertAt < MAX_PREDICTIONS) {
memmove((char*) bigramFreq + (insertAt + 1) * sizeof(bigramFreq[0]), memmove(bigramFreq + (insertAt + 1),
(char*) bigramFreq + insertAt * sizeof(bigramFreq[0]), bigramFreq + insertAt,
(MAX_PREDICTIONS - insertAt - 1) * sizeof(bigramFreq[0])); (MAX_PREDICTIONS - insertAt - 1) * sizeof(bigramFreq[0]));
bigramFreq[insertAt] = frequency; bigramFreq[insertAt] = frequency;
outputTypes[insertAt] = Dictionary::KIND_PREDICTION; outputTypes[insertAt] = Dictionary::KIND_PREDICTION;
memmove((char*) bigramChars + (insertAt + 1) * MAX_WORD_LENGTH * sizeof(short), memmove(bigramChars + (insertAt + 1) * MAX_WORD_LENGTH,
(char*) bigramChars + (insertAt ) * MAX_WORD_LENGTH * sizeof(short), bigramChars + insertAt * MAX_WORD_LENGTH,
(MAX_PREDICTIONS - insertAt - 1) * sizeof(short) * MAX_WORD_LENGTH); (MAX_PREDICTIONS - insertAt - 1) * sizeof(bigramChars[0]) * MAX_WORD_LENGTH);
unsigned short *dest = bigramChars + (insertAt ) * MAX_WORD_LENGTH; unsigned short *dest = bigramChars + insertAt * MAX_WORD_LENGTH;
while (length--) { while (length--) {
*dest++ = *word++; *dest++ = *word++;
} }

View File

@ -29,8 +29,6 @@ class BigramDictionary {
BigramDictionary(const unsigned char *dict, int maxWordLength, int maxPredictions); BigramDictionary(const unsigned char *dict, int maxWordLength, int maxPredictions);
int getBigrams(const int32_t *word, int length, int *inputCodes, int codesSize, int getBigrams(const int32_t *word, int length, int *inputCodes, int codesSize,
unsigned short *outWords, int *frequencies, int *outputTypes) const; 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, void fillBigramAddressToFrequencyMapAndFilter(const int32_t *prevWord, const int prevWordLength,
std::map<int, int> *map, uint8_t *filter) const; std::map<int, int> *map, uint8_t *filter) const;
bool isValidBigram(const int32_t *word1, int length1, const int32_t *word2, int length2) 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 getFirstBitOfByte(int *pos) { return (DICT[*pos] & 0x80) > 0; }
bool getSecondBitOfByte(int *pos) { return (DICT[*pos] & 0x40) > 0; } bool getSecondBitOfByte(int *pos) { return (DICT[*pos] & 0x40) > 0; }
bool checkFirstCharacter(unsigned short *word, int *inputCodes) const; 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 unsigned char *DICT;
const int MAX_WORD_LENGTH; 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. // Mask for attribute frequency, stored on 4 bits inside the flags byte.
static const int MASK_ATTRIBUTE_FREQUENCY = 0x0F; 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. // Mask and flags for attribute address type selection.
static const int MASK_ATTRIBUTE_ADDRESS_TYPE = 0x30; 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_TWOBYTES = 0x20;
static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES = 0x30; 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; const static int UNKNOWN_FORMAT = -1;
// Originally, format version 1 had a 16-bit magic number, then the version number `01' // 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 // 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 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 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 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, static int skipChildrenPosAndAttributes(const uint8_t *const dict, const uint8_t flags,
const int pos); const int pos);
static int readChildrenPosition(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 bool hasChildrenInFlags(const uint8_t flags);
static int getAttributeAddressAndForwardPointer(const uint8_t *const dict, const uint8_t flags, static int getAttributeAddressAndForwardPointer(const uint8_t *const dict, const uint8_t flags,
int *pos); int *pos);
static int getAttributeFrequencyFromFlags(const int flags);
static int getTerminalPosition(const uint8_t *const root, const int32_t *const inWord, static int getTerminalPosition(const uint8_t *const root, const int32_t *const inWord,
const int length, const bool forceLowerCaseSearch); const int length, const bool forceLowerCaseSearch);
static int getWordAtAddress(const uint8_t *const root, const int address, const int maxDepth, 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 REQUIRES_FRENCH_LIGATURES_PROCESSING = 0x4
}; };
const static unsigned int NO_FLAGS = 0; 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) { 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 // 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. // dictionary. If no match is found, it returns NOT_VALID_WORD.
inline int BinaryFormat::getTerminalPosition(const uint8_t *const root, 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