Merge remote-tracking branch 'goog/jb-mr1-dev' into mergescriptpackage
diff --git a/dictionaries/en_whitelist.xml b/dictionaries/en_whitelist.xml
new file mode 100644
index 0000000..e11935f
--- /dev/null
+++ b/dictionaries/en_whitelist.xml
@@ -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>
diff --git a/java/proguard.flags b/java/proguard.flags
index 24b4c19..ac5b7df 100644
--- a/java/proguard.flags
+++ b/java/proguard.flags
@@ -44,6 +44,10 @@
<init>(...);
}
+-keepclasseswithmembernames class * {
+ native <methods>;
+}
+
-keep class com.android.inputmethod.research.ResearchLogger {
void flush();
void publishCurrentLogUnit(...);
diff --git a/java/res/values-en/whitelist.xml b/java/res/values-en/whitelist.xml
deleted file mode 100644
index 2620179..0000000
--- a/java/res/values-en/whitelist.xml
+++ /dev/null
@@ -1,411 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!--
- An entry of the whitelist word should be:
- 1. (int)frequency
- 2. (String)before
- 3. (String)after
- -->
- <string-array name="wordlist_whitelist" translatable="false">
-
- <item>255</item>
- <item>ill</item>
- <item>I\'ll</item>
-
- <!-- TODO: Trim down more entries by removing ones that get auto-corrected by the
- Android keyboard's own typing error correction algorithms. -->
-
- <item>255</item>
- <item>acomodate</item>
- <item>accommodate</item>
-
- <item>255</item>
- <item>aint</item>
- <item>ain\'t</item>
-
- <item>255</item>
- <item>alot</item>
- <item>a lot</item>
-
- <item>255</item>
- <item>andteh</item>
- <item>and the</item>
-
- <item>255</item>
- <item>arent</item>
- <item>aren\'t</item>
-
- <item>255</item>
- <item>bot</item>
- <item>not</item>
-
- <item>255</item>
- <item>bern</item>
- <item>been</item>
-
- <item>255</item>
- <item>bot</item>
- <item>not</item>
-
- <item>255</item>
- <item>bur</item>
- <item>but</item>
-
- <item>255</item>
- <item>cam</item>
- <item>can</item>
-
- <item>255</item>
- <item>cant</item>
- <item>can\'t</item>
-
- <item>255</item>
- <item>dame</item>
- <item>same</item>
-
- <item>255</item>
- <item>didint</item>
- <item>didn\'t</item>
-
- <item>255</item>
- <item>dormer</item>
- <item>former</item>
-
- <item>255</item>
- <item>dud</item>
- <item>did</item>
-
- <item>255</item>
- <item>fay</item>
- <item>day</item>
-
- <item>255</item>
- <item>fife</item>
- <item>five</item>
-
- <item>255</item>
- <item>foo</item>
- <item>for</item>
-
- <item>255</item>
- <item>fora</item>
- <item>for a</item>
-
- <item>255</item>
- <item>galled</item>
- <item>called</item>
-
- <item>255</item>
- <item>goo</item>
- <item>too</item>
-
- <item>255</item>
- <item>hed</item>
- <item>he\'d</item>
-
- <item>255</item>
- <item>hel</item>
- <item>he\'ll</item>
-
- <item>255</item>
- <item>heres</item>
- <item>here\'s</item>
-
- <item>255</item>
- <item>hew</item>
- <item>new</item>
-
- <item>255</item>
- <item>hoe</item>
- <item>how</item>
-
- <item>255</item>
- <item>hoes</item>
- <item>how\'s</item>
-
- <item>255</item>
- <item>howd</item>
- <item>how\'d</item>
-
- <item>255</item>
- <item>howll</item>
- <item>how\'ll</item>
-
- <item>255</item>
- <item>hows</item>
- <item>how\'s</item>
-
- <item>255</item>
- <item>howve</item>
- <item>how\'ve</item>
-
- <item>255</item>
- <item>hum</item>
- <item>him</item>
-
- <item>255</item>
- <item>i</item>
- <item>I</item>
-
- <item>255</item>
- <item>ifs</item>
- <item>its</item>
-
- <item>255</item>
- <item>il</item>
- <item>I\'ll</item>
-
- <item>255</item>
- <item>im</item>
- <item>I\'m</item>
-
- <item>255</item>
- <item>inteh</item>
- <item>in the</item>
-
- <item>255</item>
- <item>itd</item>
- <item>it\'d</item>
-
- <item>255</item>
- <item>itsa</item>
- <item>it\'s a</item>
-
- <item>255</item>
- <item>lets</item>
- <item>let\'s</item>
-
- <item>255</item>
- <item>maam</item>
- <item>ma\'am</item>
-
- <item>255</item>
- <item>manu</item>
- <item>many</item>
-
- <item>255</item>
- <item>mare</item>
- <item>made</item>
-
- <item>255</item>
- <item>mew</item>
- <item>new</item>
-
- <item>255</item>
- <item>mire</item>
- <item>more</item>
-
- <item>255</item>
- <item>moat</item>
- <item>most</item>
-
- <item>255</item>
- <item>mot</item>
- <item>not</item>
-
- <item>255</item>
- <item>mote</item>
- <item>note</item>
-
- <item>255</item>
- <item>motes</item>
- <item>notes</item>
-
- <item>255</item>
- <item>mow</item>
- <item>now</item>
-
- <item>255</item>
- <item>namer</item>
- <item>named</item>
-
- <item>255</item>
- <item>nave</item>
- <item>have</item>
-
- <item>255</item>
- <item>nee</item>
- <item>new</item>
-
- <item>255</item>
- <item>nigh</item>
- <item>high</item>
-
- <item>255</item>
- <item>nit</item>
- <item>not</item>
-
- <item>255</item>
- <item>oft</item>
- <item>off</item>
-
- <item>255</item>
- <item>os</item>
- <item>is</item>
-
- <item>255</item>
- <item>pater</item>
- <item>later</item>
-
- <item>255</item>
- <item>rook</item>
- <item>took</item>
-
- <item>255</item>
- <item>shel</item>
- <item>she\'ll</item>
-
- <item>255</item>
- <item>shouldent</item>
- <item>shouldn\'t</item>
-
- <item>255</item>
- <item>sill</item>
- <item>will</item>
-
- <item>255</item>
- <item>sown</item>
- <item>down</item>
-
- <item>255</item>
- <item>thatd</item>
- <item>that\'d</item>
-
- <item>255</item>
- <item>tine</item>
- <item>time</item>
-
- <item>255</item>
- <item>thong</item>
- <item>thing</item>
-
- <item>255</item>
- <item>tome</item>
- <item>time</item>
-
- <!-- through additional proximity, 'uf' becomes 'of'. 'o' is not next to 'u' so anyone
- typing 'uf' probably meant 'if', but 'of' is much more common and should be left
- higher than 'if', hence the need for this entry. -->
- <item>255</item>
- <item>uf</item>
- <item>if</item>
-
- <!-- 'un' becomes 'UN' because of perfect match ; even if we remove 'UN', then 'un'
- will become 'on' for the same reason as above. So list this here. -->
- <item>255</item>
- <item>un</item>
- <item>in</item>
-
- <!-- does it really make any sense to have the following here? -->
- <item>255</item>
- <item>UnitedStates</item>
- <item>United States</item>
-
- <item>255</item>
- <item>unitedstates</item>
- <item>United States</item>
-
- <item>255</item>
- <item>visavis</item>
- <item>vis-a-vis</item>
-
- <item>255</item>
- <item>wierd</item>
- <item>weird</item>
-
- <item>255</item>
- <item>wel</item>
- <item>we\'ll</item>
-
- <item>255</item>
- <item>wer</item>
- <item>we\'re</item>
-
- <item>255</item>
- <item>whatd</item>
- <item>what\'d</item>
-
- <item>255</item>
- <item>whatm</item>
- <item>what\'m</item>
-
- <item>255</item>
- <item>whatre</item>
- <item>what\'re</item>
-
- <item>255</item>
- <item>whats</item>
- <item>what\'s</item>
-
- <item>255</item>
- <item>whens</item>
- <item>when\'s</item>
-
- <item>255</item>
- <item>whered</item>
- <item>where\'d</item>
-
- <item>255</item>
- <item>wherell</item>
- <item>where\'ll</item>
-
- <item>255</item>
- <item>wheres</item>
- <item>where\'s</item>
-
- <item>255</item>
- <item>wholl</item>
- <item>who\'ll</item>
-
- <item>255</item>
- <item>whove</item>
- <item>who\'ve</item>
-
- <item>255</item>
- <item>whyd</item>
- <item>why\'d</item>
-
- <item>255</item>
- <item>whyll</item>
- <item>why\'ll</item>
-
- <item>255</item>
- <item>whys</item>
- <item>why\'s</item>
-
- <item>255</item>
- <item>whyve</item>
- <item>why\'ve</item>
-
- <item>255</item>
- <item>wont</item>
- <item>won\'t</item>
-
- <item>255</item>
- <item>yall</item>
- <item>y\'all</item>
-
- <item>255</item>
- <item>youd</item>
- <item>you\'d</item>
-
- </string-array>
-</resources>
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index 4975d65..76e76cc 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -41,26 +41,24 @@
checkable+checked+pressed. -->
<attr name="keyBackground" format="reference" />
- <!-- Size of the text for one letter keys. If not defined, keyLetterRatio takes effect. -->
- <attr name="keyLetterSize" format="dimension" />
- <!-- Size of the text for keys with multiple letters. If not defined, keyLabelRatio takes
- effect. -->
- <attr name="keyLabelSize" format="dimension" />
- <!-- Size of the text for one letter keys, in the proportion of key height. -->
- <attr name="keyLetterRatio" format="float" />
+ <!-- Size of the text for one letter keys. If specified as fraction, the text size is
+ measured in the proportion of key height. -->
+ <attr name="keyLetterSize" format="dimension|fraction" />
+ <!-- Size of the text for keys with multiple letters. If specified as fraction, the text
+ size is measured in the proportion of key height. -->
+ <attr name="keyLabelSize" format="dimension|fraction" />
<!-- Large size of the text for one letter keys, in the proportion of key height. -->
- <attr name="keyLargeLetterRatio" format="float" />
- <!-- Size of the text for keys with multiple letters, in the proportion of key height. -->
- <attr name="keyLabelRatio" format="float" />
+ <attr name="keyLargeLetterRatio" format="fraction" />
<!-- Large size of the text for keys with multiple letters, in the proportion of key height. -->
- <attr name="keyLargeLabelRatio" format="float" />
+ <attr name="keyLargeLabelRatio" format="fraction" />
<!-- Size of the text for hint letter (= one character hint label), in the proportion of
key height. -->
- <attr name="keyHintLetterRatio" format="float" />
+ <attr name="keyHintLetterRatio" format="fraction" />
<!-- Size of the text for hint label, in the proportion of key height. -->
- <attr name="keyHintLabelRatio" format="float" />
+ <attr name="keyHintLabelRatio" format="fraction" />
<!-- Size of the text for shifted letter hint, in the proportion of key height. -->
- <attr name="keyShiftedLetterHintRatio" format="float" />
+ <attr name="keyShiftedLetterHintRatio" format="dimension|fraction" />
+
<!-- Horizontal padding of left/right aligned key label to the edge of the key. -->
<attr name="keyLabelHorizontalPadding" format="dimension" />
<!-- Right padding of hint letter to the edge of the key.-->
@@ -96,8 +94,8 @@
<attr name="keyPreviewOffset" format="dimension" />
<!-- Height of the key press feedback popup. -->
<attr name="keyPreviewHeight" format="dimension" />
- <!-- Size of the text for key press feedback popup, int the proportion of key height -->
- <attr name="keyPreviewTextRatio" format="float" />
+ <!-- Size of the text for key press feedback popup, in the proportion of key height. -->
+ <attr name="keyPreviewTextRatio" format="fraction" />
<!-- Delay after key releasing and key press feedback dismissing in millisecond -->
<attr name="keyPreviewLingerTimeout" format="integer" />
@@ -131,6 +129,12 @@
<attr name="gestureFloatingPreviewTextConnectorWidth" format="dimension" />
<!-- Delay after gesture input and gesture floating preview text dismissing in millisecond -->
<attr name="gestureFloatingPreviewTextLingerTimeout" format="integer" />
+ <!-- Delay after gesture trail starts fading out in millisecond. -->
+ <attr name="gesturePreviewTrailFadeoutStartDelay" format="integer" />
+ <!-- Duration while gesture preview trail is fading out in millisecond. -->
+ <attr name="gesturePreviewTrailFadeoutDuration" format="integer" />
+ <!-- Interval of updating gesture preview trail in millisecond. -->
+ <attr name="gesturePreviewTrailUpdateInterval" format="integer" />
<attr name="gesturePreviewTrailColor" format="color" />
<attr name="gesturePreviewTrailWidth" format="dimension" />
</declare-styleable>
@@ -181,13 +185,13 @@
<attr name="colorTypedWord" format="color" />
<attr name="colorAutoCorrect" format="color" />
<attr name="colorSuggested" format="color" />
- <attr name="alphaValidTypedWord" format="integer" />
- <attr name="alphaTypedWord" format="integer" />
- <attr name="alphaAutoCorrect" format="integer" />
- <attr name="alphaSuggested" format="integer" />
- <attr name="alphaObsoleted" format="integer" />
+ <attr name="alphaValidTypedWord" format="fraction" />
+ <attr name="alphaTypedWord" format="fraction" />
+ <attr name="alphaAutoCorrect" format="fraction" />
+ <attr name="alphaSuggested" format="fraction" />
+ <attr name="alphaObsoleted" format="fraction" />
<attr name="suggestionsCountInStrip" format="integer" />
- <attr name="centerSuggestionPercentile" format="integer" />
+ <attr name="centerSuggestionPercentile" format="fraction" />
<attr name="maxMoreSuggestionsRow" format="integer" />
<attr name="minMoreSuggestionsWidth" format="float" />
</declare-styleable>
diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index 54a6687..8e2d43e 100644
--- a/java/res/values/config.xml
+++ b/java/res/values/config.xml
@@ -50,6 +50,9 @@
-->
<integer name="config_key_preview_linger_timeout">70</integer>
<integer name="config_gesture_floating_preview_text_linger_timeout">200</integer>
+ <integer name="config_gesture_preview_trail_fadeout_start_delay">100</integer>
+ <integer name="config_gesture_preview_trail_fadeout_duration">800</integer>
+ <integer name="config_gesture_preview_trail_update_interval">20</integer>
<!--
Configuration for MainKeyboardView
-->
diff --git a/java/res/values/dimens.xml b/java/res/values/dimens.xml
index c59bad3..4fd942b 100644
--- a/java/res/values/dimens.xml
+++ b/java/res/values/dimens.xml
@@ -92,7 +92,7 @@
<dimen name="suggestion_text_size">18dp</dimen>
<dimen name="more_suggestions_hint_text_size">27dp</dimen>
<integer name="suggestions_count_in_strip">3</integer>
- <integer name="center_suggestion_percentile">36</integer>
+ <fraction name="center_suggestion_percentile">36%</fraction>
<!-- Gesture preview parameters -->
<dimen name="gesture_preview_trail_width">2.5dp</dimen>
@@ -101,4 +101,7 @@
<dimen name="gesture_floating_preview_text_shadow_border">17.5dp</dimen>
<dimen name="gesture_floating_preview_text_shading_border">7.5dp</dimen>
<dimen name="gesture_floating_preview_text_connector_width">1.0dp</dimen>
+
+ <!-- Inset used in Accessibility mode to avoid accidental key presses when a finger slides off the screen. -->
+ <dimen name="accessibility_edge_slop">8dp</dimen>
</resources>
diff --git a/java/res/values/keypress-vibration-durations.xml b/java/res/values/keypress-vibration-durations.xml
index 2569f23..8f6b647 100644
--- a/java/res/values/keypress-vibration-durations.xml
+++ b/java/res/values/keypress-vibration-durations.xml
@@ -22,5 +22,6 @@
<!-- Build.HARDWARE,duration_in_milliseconds -->
<item>herring,5</item>
<item>tuna,5</item>
+ <item>mako,20</item>
</string-array>
</resources>
diff --git a/java/res/values/keypress-volumes.xml b/java/res/values/keypress-volumes.xml
index 3b433e4..dd86d6c 100644
--- a/java/res/values/keypress-volumes.xml
+++ b/java/res/values/keypress-volumes.xml
@@ -24,5 +24,6 @@
<item>tuna,0.5</item>
<item>stingray,0.4</item>
<item>grouper,0.3</item>
+ <item>mako,0.3</item>
</string-array>
</resources>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 07b3f31..35cbcf3 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -261,7 +261,8 @@
<string name="research_feedback_dialog_title" translatable="false">Send feedback</string>
<!-- Text for checkbox option to include user data in feedback for research purposes [CHAR LIMIT=50] -->
<!-- TODO: remove translatable=false attribute once text is stable -->
- <string name="research_feedback_include_history_label" translatable="false">Include last 5 words entered</string>
+ <!-- TODO: handle multilingual plurals -->
+ <string name="research_feedback_include_history_label" translatable="false">Include last <xliff:g id="word">%d</xliff:g> words entered</string>
<!-- Hint to user about the text entry field where they should enter research feedback [CHAR LIMIT=40] -->
<!-- TODO: remove translatable=false attribute once text is stable -->
<string name="research_feedback_hint" translatable="false">Enter your feedback here.</string>
@@ -288,6 +289,10 @@
<!-- TODO: remove translatable=false attribute once text is stable -->
<string name="research_send_usage_info" translatable="false">Send usage info</string>
+ <!-- Name for the research uploading service to be displayed to users. [CHAR LIMIT=50] -->
+ <!-- TODO: remove translatable=false attribute once text is stable -->
+ <string name="research_log_uploader_name" translatable="false">Research Uploader Service</string>
+
<!-- Preference for input language selection -->
<string name="select_language">Input languages</string>
diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml
index ae67c43..0220c83 100644
--- a/java/res/values/styles.xml
+++ b/java/res/values/styles.xml
@@ -35,9 +35,9 @@
<style name="KeyboardView">
<item name="android:background">@drawable/keyboard_background</item>
<item name="keyBackground">@drawable/btn_keyboard_key</item>
- <item name="keyLetterRatio">@fraction/key_letter_ratio</item>
+ <item name="keyLetterSize">@fraction/key_letter_ratio</item>
<item name="keyLargeLetterRatio">@fraction/key_large_letter_ratio</item>
- <item name="keyLabelRatio">@fraction/key_label_ratio</item>
+ <item name="keyLabelSize">@fraction/key_label_ratio</item>
<item name="keyLargeLabelRatio">@fraction/key_large_label_ratio</item>
<item name="keyHintLetterRatio">@fraction/key_hint_letter_ratio</item>
<item name="keyHintLabelRatio">@fraction/key_hint_label_ratio</item>
@@ -78,6 +78,9 @@
<item name="gestureFloatingPreviewTextConnectorColor">@android:color/white</item>
<item name="gestureFloatingPreviewTextConnectorWidth">@dimen/gesture_floating_preview_text_connector_width</item>
<item name="gestureFloatingPreviewTextLingerTimeout">@integer/config_gesture_floating_preview_text_linger_timeout</item>
+ <item name="gesturePreviewTrailFadeoutStartDelay">@integer/config_gesture_preview_trail_fadeout_start_delay</item>
+ <item name="gesturePreviewTrailFadeoutDuration">@integer/config_gesture_preview_trail_fadeout_duration</item>
+ <item name="gesturePreviewTrailUpdateInterval">@integer/config_gesture_preview_trail_update_interval</item>
<item name="gesturePreviewTrailColor">@android:color/holo_blue_light</item>
<item name="gesturePreviewTrailWidth">@dimen/gesture_preview_trail_width</item>
<!-- Common attributes of MainKeyboardView -->
@@ -135,9 +138,9 @@
<item name="colorTypedWord">@android:color/white</item>
<item name="colorAutoCorrect">#FFFCAE00</item>
<item name="colorSuggested">#FFFCAE00</item>
- <item name="alphaObsoleted">50</item>
+ <item name="alphaObsoleted">50%</item>
<item name="suggestionsCountInStrip">@integer/suggestions_count_in_strip</item>
- <item name="centerSuggestionPercentile">@integer/center_suggestion_percentile</item>
+ <item name="centerSuggestionPercentile">@fraction/center_suggestion_percentile</item>
<item name="maxMoreSuggestionsRow">@integer/max_more_suggestions_row</item>
<item name="minMoreSuggestionsWidth">@fraction/min_more_suggestions_width</item>
</style>
@@ -370,12 +373,12 @@
<item name="colorTypedWord">@android:color/holo_blue_light</item>
<item name="colorAutoCorrect">@android:color/holo_blue_light</item>
<item name="colorSuggested">@android:color/holo_blue_light</item>
- <item name="alphaValidTypedWord">85</item>
- <item name="alphaTypedWord">85</item>
- <item name="alphaSuggested">70</item>
- <item name="alphaObsoleted">70</item>
+ <item name="alphaValidTypedWord">85%</item>
+ <item name="alphaTypedWord">85%</item>
+ <item name="alphaSuggested">70%</item>
+ <item name="alphaObsoleted">70%</item>
<item name="suggestionsCountInStrip">@integer/suggestions_count_in_strip</item>
- <item name="centerSuggestionPercentile">@integer/center_suggestion_percentile</item>
+ <item name="centerSuggestionPercentile">@fraction/center_suggestion_percentile</item>
<item name="maxMoreSuggestionsRow">@integer/max_more_suggestions_row</item>
<item name="minMoreSuggestionsWidth">@fraction/min_more_suggestions_width</item>
</style>
diff --git a/java/res/values/whitelist.xml b/java/res/values/whitelist.xml
deleted file mode 100644
index d4ecbfa..0000000
--- a/java/res/values/whitelist.xml
+++ /dev/null
@@ -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>
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
index 70e38fd..039c77b 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
@@ -35,6 +35,7 @@
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardView;
+import com.android.inputmethod.latin.CollectionUtils;
/**
* Exposes a virtual view sub-tree for {@link KeyboardView} and generates
@@ -55,7 +56,7 @@
private final AccessibilityUtils mAccessibilityUtils;
/** A map of integer IDs to {@link Key}s. */
- private final SparseArray<Key> mVirtualViewIdToKey = new SparseArray<Key>();
+ private final SparseArray<Key> mVirtualViewIdToKey = CollectionUtils.newSparseArray();
/** Temporary rect used to calculate in-screen bounds. */
private final Rect mTempBoundsInScreen = new Rect();
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
index 616b1c6..58d3022 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
@@ -19,10 +19,15 @@
import android.content.Context;
import android.inputmethodservice.InputMethodService;
import android.media.AudioManager;
+import android.os.Build;
import android.os.SystemClock;
import android.provider.Settings;
+import android.support.v4.view.accessibility.AccessibilityEventCompat;
import android.util.Log;
import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.inputmethod.EditorInfo;
@@ -138,9 +143,10 @@
* Sends the specified text to the {@link AccessibilityManager} to be
* spoken.
*
- * @param text the text to speak
+ * @param view The source view.
+ * @param text The text to speak.
*/
- public void speak(CharSequence text) {
+ public void announceForAccessibility(View view, CharSequence text) {
if (!mAccessibilityManager.isEnabled()) {
Log.e(TAG, "Attempted to speak when accessibility was disabled!");
return;
@@ -149,8 +155,7 @@
// The following is a hack to avoid using the heavy-weight TextToSpeech
// class. Instead, we're just forcing a fake AccessibilityEvent into
// the screen reader to make it speak.
- final AccessibilityEvent event = AccessibilityEvent
- .obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+ final AccessibilityEvent event = AccessibilityEvent.obtain();
event.setPackageName(PACKAGE);
event.setClassName(CLASS);
@@ -158,20 +163,34 @@
event.setEnabled(true);
event.getText().add(text);
- mAccessibilityManager.sendAccessibilityEvent(event);
+ // Platforms starting at SDK 16 should use announce events.
+ if (Build.VERSION.SDK_INT >= 16) {
+ event.setEventType(AccessibilityEventCompat.TYPE_ANNOUNCEMENT);
+ } else {
+ event.setEventType(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+ }
+
+ final ViewParent viewParent = view.getParent();
+ if ((viewParent == null) || !(viewParent instanceof ViewGroup)) {
+ Log.e(TAG, "Failed to obtain ViewParent in announceForAccessibility");
+ return;
+ }
+
+ viewParent.requestSendAccessibilityEvent(view, event);
}
/**
* Handles speaking the "connect a headset to hear passwords" notification
* when connecting to a password field.
*
+ * @param view The source view.
* @param editorInfo The input connection's editor info attribute.
* @param restarting Whether the connection is being restarted.
*/
- public void onStartInputViewInternal(EditorInfo editorInfo, boolean restarting) {
+ public void onStartInputViewInternal(View view, EditorInfo editorInfo, boolean restarting) {
if (shouldObscureInput(editorInfo)) {
final CharSequence text = mContext.getText(R.string.spoken_use_headphones);
- speak(text);
+ announceForAccessibility(view, text);
}
}
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
index 4ecbf82..e42de0b 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
@@ -24,7 +24,6 @@
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewConfiguration;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
@@ -44,7 +43,7 @@
/**
* Inset in pixels to look for keys when the user's finger exits the
- * keyboard area. See {@link ViewConfiguration#getScaledEdgeSlop()}.
+ * keyboard area.
*/
private int mEdgeSlop;
@@ -62,7 +61,8 @@
private void initInternal(InputMethodService inputMethod) {
mInputMethod = inputMethod;
- mEdgeSlop = ViewConfiguration.get(inputMethod).getScaledEdgeSlop();
+ mEdgeSlop = inputMethod.getResources().getDimensionPixelSize(
+ R.dimen.accessibility_edge_slop);
}
/**
@@ -114,8 +114,14 @@
public boolean dispatchHoverEvent(MotionEvent event, PointerTracker tracker) {
final int x = (int) event.getX();
final int y = (int) event.getY();
- final Key key = tracker.getKeyOn(x, y);
final Key previousKey = mLastHoverKey;
+ final Key key;
+
+ if (pointInView(x, y)) {
+ key = tracker.getKeyOn(x, y);
+ } else {
+ key = null;
+ }
mLastHoverKey = key;
@@ -123,7 +129,7 @@
case MotionEvent.ACTION_HOVER_EXIT:
// Make sure we're not getting an EXIT event because the user slid
// off the keyboard area, then force a key press.
- if (pointInView(x, y) && (key != null)) {
+ if (key != null) {
getAccessibilityNodeProvider().simulateKeyPress(key);
}
//$FALL-THROUGH$
@@ -250,7 +256,7 @@
text = context.getText(R.string.spoken_description_shiftmode_off);
}
- AccessibilityUtils.getInstance().speak(text);
+ AccessibilityUtils.getInstance().announceForAccessibility(mView, text);
}
/**
@@ -290,6 +296,6 @@
}
final String text = context.getString(resId);
- AccessibilityUtils.getInstance().speak(text);
+ AccessibilityUtils.getInstance().announceForAccessibility(mView, text);
}
}
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
index 9b74070..5c45448 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -25,6 +25,7 @@
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardId;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.R;
import java.util.HashMap;
@@ -38,7 +39,7 @@
private static KeyCodeDescriptionMapper sInstance = new KeyCodeDescriptionMapper();
// Map of key labels to spoken description resource IDs
- private final HashMap<CharSequence, Integer> mKeyLabelMap;
+ private final HashMap<CharSequence, Integer> mKeyLabelMap = CollectionUtils.newHashMap();
// Sparse array of spoken description resource IDs indexed by key codes
private final SparseIntArray mKeyCodeMap;
@@ -52,7 +53,6 @@
}
private KeyCodeDescriptionMapper() {
- mKeyLabelMap = new HashMap<CharSequence, Integer>();
mKeyCodeMap = new SparseIntArray();
}
diff --git a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
index 1183b5f..6ba309f 100644
--- a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
+++ b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
@@ -16,10 +16,6 @@
package com.android.inputmethod.compat;
-import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.SuggestedWords;
-import com.android.inputmethod.latin.SuggestionSpanPickedNotificationReceiver;
-
import android.content.Context;
import android.text.Spannable;
import android.text.SpannableString;
@@ -27,6 +23,11 @@
import android.text.TextUtils;
import android.util.Log;
+import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.SuggestionSpanPickedNotificationReceiver;
+
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
@@ -119,7 +120,7 @@
} else {
spannable = new SpannableString(pickedWord);
}
- final ArrayList<String> suggestionsList = new ArrayList<String>();
+ final ArrayList<String> suggestionsList = CollectionUtils.newArrayList();
for (int i = 0; i < suggestedWords.size(); ++i) {
if (suggestionsList.size() >= OBJ_SUGGESTIONS_MAX_SIZE) {
break;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
index 97d88af..868c8ca 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
@@ -16,10 +16,10 @@
package com.android.inputmethod.keyboard;
+import com.android.inputmethod.latin.Constants;
+
public class KeyDetector {
- public static final int NOT_A_CODE = -1;
-
private final int mKeyHysteresisDistanceSquared;
private Keyboard mKeyboard;
@@ -59,6 +59,9 @@
}
public Keyboard getKeyboard() {
+ if (mKeyboard == null) {
+ throw new IllegalStateException("keyboard isn't set");
+ }
return mKeyboard;
}
@@ -100,7 +103,7 @@
final StringBuilder sb = new StringBuilder();
boolean addDelimiter = false;
for (final int code : codes) {
- if (code == NOT_A_CODE) break;
+ if (code == Constants.NOT_A_CODE) break;
if (addDelimiter) sb.append(", ");
sb.append(Keyboard.printableCode(code));
addDelimiter = true;
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index 3abe890..e37868b 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -33,6 +33,7 @@
import com.android.inputmethod.keyboard.internal.KeyboardCodesSet;
import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
import com.android.inputmethod.keyboard.internal.KeyboardTextsSet;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
import com.android.inputmethod.latin.R;
@@ -134,7 +135,7 @@
public final Key[] mAltCodeKeysWhileTyping;
public final KeyboardIconsSet mIconsSet;
- private final SparseArray<Key> mKeyCache = new SparseArray<Key>();
+ private final SparseArray<Key> mKeyCache = CollectionUtils.newSparseArray();
private final ProximityInfo mProximityInfo;
private final boolean mProximityCharsCorrectionEnabled;
@@ -219,6 +220,11 @@
return code >= CODE_SPACE;
}
+ @Override
+ public String toString() {
+ return mId.toString();
+ }
+
public static class Params {
public KeyboardId mId;
public int mThemeId;
@@ -249,9 +255,9 @@
public int GRID_WIDTH;
public int GRID_HEIGHT;
- public final HashSet<Key> mKeys = new HashSet<Key>();
- public final ArrayList<Key> mShiftKeys = new ArrayList<Key>();
- public final ArrayList<Key> mAltCodeKeysWhileTyping = new ArrayList<Key>();
+ public final HashSet<Key> mKeys = CollectionUtils.newHashSet();
+ public final ArrayList<Key> mShiftKeys = CollectionUtils.newArrayList();
+ public final ArrayList<Key> mAltCodeKeysWhileTyping = CollectionUtils.newArrayList();
public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet();
public final KeyboardCodesSet mCodesSet = new KeyboardCodesSet();
public final KeyboardTextsSet mTextsSet = new KeyboardTextsSet();
@@ -278,9 +284,10 @@
public void load(String[] data) {
final int dataLength = data.length;
if (dataLength % TOUCH_POSITION_CORRECTION_RECORD_SIZE != 0) {
- if (LatinImeLogger.sDBG)
+ if (LatinImeLogger.sDBG) {
throw new RuntimeException(
"the size of touch position correction data is invalid");
+ }
return;
}
@@ -319,7 +326,7 @@
public boolean isValid() {
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 @@
final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Keyboard);
try {
- if (a.hasValue(R.styleable.Keyboard_horizontalGap))
+ if (a.hasValue(R.styleable.Keyboard_horizontalGap)) {
throw new XmlParseUtils.IllegalAttribute(parser, "horizontalGap");
- if (a.hasValue(R.styleable.Keyboard_verticalGap))
+ }
+ if (a.hasValue(R.styleable.Keyboard_verticalGap)) {
throw new XmlParseUtils.IllegalAttribute(parser, "verticalGap");
+ }
return new Row(mResources, mParams, parser, mCurrentY);
} finally {
a.recycle();
@@ -916,7 +925,9 @@
throws XmlPullParserException, IOException {
if (skip) {
XmlParseUtils.checkEndTag(TAG_KEY, parser);
- if (DEBUG) startEndTag("<%s /> skipped", TAG_KEY);
+ if (DEBUG) {
+ startEndTag("<%s /> skipped", TAG_KEY);
+ }
} else {
final Key key = new Key(mResources, mParams, row, parser);
if (DEBUG) {
@@ -1094,9 +1105,9 @@
private boolean parseCaseCondition(XmlPullParser parser) {
final KeyboardId id = mParams.mId;
- if (id == null)
+ if (id == null) {
return true;
-
+ }
final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Keyboard_Case);
try {
@@ -1200,9 +1211,9 @@
// If <case> does not have "index" attribute, that means this <case> is wild-card for
// the attribute.
final TypedValue v = a.peekValue(index);
- if (v == null)
+ if (v == null) {
return true;
-
+ }
if (isIntegerValue(v)) {
return intValue == a.getInt(index, 0);
} else if (isStringValue(v)) {
@@ -1213,8 +1224,9 @@
private static boolean stringArrayContains(String[] array, String value) {
for (final String elem : array) {
- if (elem.equals(value))
+ if (elem.equals(value)) {
return true;
+ }
}
return false;
}
@@ -1237,16 +1249,18 @@
TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Keyboard_Key);
try {
- if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName))
+ if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName)) {
throw new XmlParseUtils.ParseException("<" + TAG_KEY_STYLE
+ "/> needs styleName attribute", parser);
+ }
if (DEBUG) {
startEndTag("<%s styleName=%s />%s", TAG_KEY_STYLE,
- keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName),
- skip ? " skipped" : "");
+ keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName),
+ skip ? " skipped" : "");
}
- if (!skip)
+ if (!skip) {
mParams.mKeyStyles.parseKeyStyleAttributes(keyStyleAttr, keyAttrs, parser);
+ }
} finally {
keyStyleAttr.recycle();
keyAttrs.recycle();
@@ -1267,8 +1281,9 @@
}
private void endRow(Row row) {
- if (mCurrentRow == null)
+ if (mCurrentRow == null) {
throw new InflateException("orphan end row tag");
+ }
if (mRightEdgeKey != null) {
mRightEdgeKey.markAsRightEdge(mParams);
mRightEdgeKey = null;
@@ -1304,8 +1319,9 @@
public static float getDimensionOrFraction(TypedArray a, int index, int base,
float defValue) {
final TypedValue value = a.peekValue(index);
- if (value == null)
+ if (value == null) {
return defValue;
+ }
if (isFractionValue(value)) {
return a.getFraction(index, base, base, defValue);
} else if (isDimensionValue(value)) {
@@ -1316,8 +1332,9 @@
public static int getEnumValue(TypedArray a, int index, int defValue) {
final TypedValue value = a.peekValue(index);
- if (value == null)
+ if (value == null) {
return defValue;
+ }
if (isIntegerValue(value)) {
return a.getInt(index, defValue);
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
index b1621a5..5c8f78f 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
@@ -16,6 +16,7 @@
package com.android.inputmethod.keyboard;
+import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.InputPointers;
public interface KeyboardActionListener {
@@ -44,21 +45,16 @@
*
* @param primaryCode this is the code of the key that was pressed
* @param x x-coordinate pixel of touched event. If {@link #onCodeInput} is not called by
- * {@link PointerTracker} or so, the value should be {@link #NOT_A_TOUCH_COORDINATE}.
- * If it's called on insertion from the suggestion strip, it should be
- * {@link #SUGGESTION_STRIP_COORDINATE}.
+ * {@link PointerTracker} or so, the value should be
+ * {@link Constants#NOT_A_COORDINATE}. If it's called on insertion from the
+ * suggestion strip, it should be {@link Constants#SUGGESTION_STRIP_COORDINATE}.
* @param y y-coordinate pixel of touched event. If {@link #onCodeInput} is not called by
- * {@link PointerTracker} or so, the value should be {@link #NOT_A_TOUCH_COORDINATE}.
- * If it's called on insertion from the suggestion strip, it should be
- * {@link #SUGGESTION_STRIP_COORDINATE}.
+ * {@link PointerTracker} or so, the value should be
+ * {@link Constants#NOT_A_COORDINATE}.If it's called on insertion from the
+ * suggestion strip, it should be {@link Constants#SUGGESTION_STRIP_COORDINATE}.
*/
public void onCodeInput(int primaryCode, int x, int y);
- // See {@link Adapter#isInvalidCoordinate(int)}.
- public static final int NOT_A_TOUCH_COORDINATE = -1;
- public static final int SUGGESTION_STRIP_COORDINATE = -2;
- public static final int SPELL_CHECKER_COORDINATE = -3;
-
/**
* Sends a sequence of characters to the listener.
*
@@ -119,9 +115,9 @@
// TODO: Remove this method when the vertical correction is removed.
public static boolean isInvalidCoordinate(int coordinate) {
- // Detect {@link KeyboardActionListener#NOT_A_TOUCH_COORDINATE},
- // {@link KeyboardActionListener#SUGGESTION_STRIP_COORDINATE}, and
- // {@link KeyboardActionListener#SPELL_CHECKER_COORDINATE}.
+ // Detect {@link Constants#NOT_A_COORDINATE},
+ // {@link Constants#SUGGESTION_STRIP_COORDINATE}, and
+ // {@link Constants#SPELL_CHECKER_COORDINATE}.
return coordinate < 0;
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
index 64b3f09..76ac3de 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
@@ -36,6 +36,7 @@
import com.android.inputmethod.compat.EditorInfoCompatUtils;
import com.android.inputmethod.keyboard.KeyboardLayoutSet.Params.ElementParams;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.InputAttributes;
import com.android.inputmethod.latin.InputTypeUtils;
import com.android.inputmethod.latin.LatinImeLogger;
@@ -71,7 +72,7 @@
private final Params mParams;
private static final HashMap<KeyboardId, SoftReference<Keyboard>> sKeyboardCache =
- new HashMap<KeyboardId, SoftReference<Keyboard>>();
+ CollectionUtils.newHashMap();
private static final KeysCache sKeysCache = new KeysCache();
public static class KeyboardLayoutSetException extends RuntimeException {
@@ -84,11 +85,7 @@
}
public static class KeysCache {
- private final HashMap<Key, Key> mMap;
-
- public KeysCache() {
- mMap = new HashMap<Key, Key>();
- }
+ private final HashMap<Key, Key> mMap = CollectionUtils.newHashMap();
public void clear() {
mMap.clear();
@@ -120,7 +117,7 @@
int mWidth;
// Sparse array of KeyboardLayoutSet element parameters indexed by element's id.
final SparseArray<ElementParams> mKeyboardLayoutSetElementIdToParamsMap =
- new SparseArray<ElementParams>();
+ CollectionUtils.newSparseArray();
static class ElementParams {
int mKeyboardXmlId;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 10f651a..fd789f0 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -21,7 +21,6 @@
import android.content.res.Resources;
import android.util.Log;
import android.view.ContextThemeWrapper;
-import android.view.InflateException;
import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.EditorInfo;
@@ -38,7 +37,7 @@
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SettingsValues;
import com.android.inputmethod.latin.SubtypeSwitcher;
-import com.android.inputmethod.latin.Utils;
+import com.android.inputmethod.latin.WordComposer;
public class KeyboardSwitcher implements KeyboardState.SwitchActions {
private static final String TAG = KeyboardSwitcher.class.getSimpleName();
@@ -46,24 +45,24 @@
public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20110916";
static class KeyboardTheme {
- public final String mName;
public final int mThemeId;
public final int mStyleId;
- public KeyboardTheme(String name, int themeId, int styleId) {
- mName = name;
+ // Note: The themeId should be aligned with "themeId" attribute of Keyboard style
+ // in values/style.xml.
+ public KeyboardTheme(int themeId, int styleId) {
mThemeId = themeId;
mStyleId = styleId;
}
}
private static final KeyboardTheme[] KEYBOARD_THEMES = {
- new KeyboardTheme("Basic", 0, R.style.KeyboardTheme),
- new KeyboardTheme("HighContrast", 1, R.style.KeyboardTheme_HighContrast),
- new KeyboardTheme("Stone", 6, R.style.KeyboardTheme_Stone),
- new KeyboardTheme("Stone.Bold", 7, R.style.KeyboardTheme_Stone_Bold),
- new KeyboardTheme("GingerBread", 8, R.style.KeyboardTheme_Gingerbread),
- new KeyboardTheme("IceCreamSandwich", 5, R.style.KeyboardTheme_IceCreamSandwich),
+ new KeyboardTheme(0, R.style.KeyboardTheme),
+ new KeyboardTheme(1, R.style.KeyboardTheme_HighContrast),
+ new KeyboardTheme(6, R.style.KeyboardTheme_Stone),
+ new KeyboardTheme(7, R.style.KeyboardTheme_Stone_Bold),
+ new KeyboardTheme(8, R.style.KeyboardTheme_Gingerbread),
+ new KeyboardTheme(5, R.style.KeyboardTheme_IceCreamSandwich),
};
private SubtypeSwitcher mSubtypeSwitcher;
@@ -354,22 +353,9 @@
mKeyboardView.closing();
}
- Utils.GCUtils.getInstance().reset();
- boolean tryGC = true;
- for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
- try {
- setContextThemeWrapper(mLatinIME, mKeyboardTheme);
- mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
- R.layout.input_view, null);
- tryGC = false;
- } catch (OutOfMemoryError e) {
- Log.w(TAG, "load keyboard failed: " + e);
- tryGC = Utils.GCUtils.getInstance().tryGCOrWait(mKeyboardTheme.mName, e);
- } catch (InflateException e) {
- Log.w(TAG, "load keyboard failed: " + e);
- tryGC = Utils.GCUtils.getInstance().tryGCOrWait(mKeyboardTheme.mName, e);
- }
- }
+ setContextThemeWrapper(mLatinIME, mKeyboardTheme);
+ mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
+ R.layout.input_view, null);
mKeyboardView = (MainKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view);
if (isHardwareAcceleratedDrawingEnabled) {
@@ -402,4 +388,16 @@
}
}
}
+
+ public int getManualCapsMode() {
+ switch (getKeyboard().mId.mElementId) {
+ case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
+ case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
+ return WordComposer.CAPS_MODE_MANUAL_SHIFT_LOCKED;
+ case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
+ return WordComposer.CAPS_MODE_MANUAL_SHIFTED;
+ default:
+ return WordComposer.CAPS_MODE_OFF;
+ }
+ }
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 0e6de70..127ac71 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -30,6 +30,7 @@
import android.graphics.drawable.Drawable;
import android.os.Message;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.SparseArray;
import android.util.TypedValue;
import android.view.LayoutInflater;
@@ -38,6 +39,7 @@
import android.widget.TextView;
import com.android.inputmethod.keyboard.internal.PreviewPlacerView;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
@@ -78,8 +80,12 @@
* @attr ref R.styleable#KeyboardView_shadowRadius
*/
public class KeyboardView extends View implements PointerTracker.DrawingProxy {
+ private static final String TAG = KeyboardView.class.getSimpleName();
+
// Miscellaneous constants
private static final int[] LONG_PRESSABLE_STATE_SET = { android.R.attr.state_long_pressable };
+ private static final float UNDEFINED_RATIO = -1.0f;
+ private static final int UNDEFINED_DIMENSION = -1;
// XML attributes
protected final float mVerticalCorrection;
@@ -103,23 +109,19 @@
// Key preview
private final int mKeyPreviewLayoutId;
+ private final SparseArray<TextView> mKeyPreviewTexts = CollectionUtils.newSparseArray();
protected final KeyPreviewDrawParams mKeyPreviewDrawParams;
private boolean mShowKeyPreviewPopup = true;
private int mDelayAfterPreview;
private final PreviewPlacerView mPreviewPlacerView;
- /** True if {@link KeyboardView} should handle gesture events. */
- protected boolean mShouldHandleGesture;
-
// Drawing
/** True if the entire keyboard needs to be dimmed. */
private boolean mNeedsToDimEntireKeyboard;
- /** Whether the keyboard bitmap buffer needs to be redrawn before it's blitted. **/
- private boolean mBufferNeedsUpdate;
/** True if all keys should be drawn */
private boolean mInvalidateAllKeys;
/** The keys that should be drawn */
- private final HashSet<Key> mInvalidatedKeys = new HashSet<Key>();
+ private final HashSet<Key> mInvalidatedKeys = CollectionUtils.newHashSet();
/** The working rectangle variable */
private final Rect mWorkingRect = new Rect();
/** The keyboard bitmap buffer for faster updates */
@@ -131,9 +133,9 @@
private final Paint mPaint = new Paint();
private final Paint.FontMetrics mFontMetrics = new Paint.FontMetrics();
// This sparse array caches key label text height in pixel indexed by key label text size.
- private static final SparseArray<Float> sTextHeightCache = new SparseArray<Float>();
+ private static final SparseArray<Float> sTextHeightCache = CollectionUtils.newSparseArray();
// This sparse array caches key label text width in pixel indexed by key label text size.
- private static final SparseArray<Float> sTextWidthCache = new SparseArray<Float>();
+ private static final SparseArray<Float> sTextWidthCache = CollectionUtils.newSparseArray();
private static final char[] KEY_LABEL_REFERENCE_CHAR = { 'M' };
private static final char[] KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR = { '8' };
@@ -153,7 +155,10 @@
final PointerTracker tracker = (PointerTracker) msg.obj;
switch (msg.what) {
case MSG_DISMISS_KEY_PREVIEW:
- tracker.getKeyPreviewText().setVisibility(View.INVISIBLE);
+ final TextView previewText = keyboardView.mKeyPreviewTexts.get(tracker.mPointerId);
+ if (previewText != null) {
+ previewText.setVisibility(INVISIBLE);
+ }
break;
}
}
@@ -166,7 +171,7 @@
removeMessages(MSG_DISMISS_KEY_PREVIEW, tracker);
}
- public void cancelAllDismissKeyPreviews() {
+ private void cancelAllDismissKeyPreviews() {
removeMessages(MSG_DISMISS_KEY_PREVIEW);
}
@@ -199,7 +204,6 @@
private final float mKeyHintLetterRatio;
private final float mKeyShiftedLetterHintRatio;
private final float mKeyHintLabelRatio;
- private static final float UNDEFINED_RATIO = -1.0f;
public final Rect mPadding = new Rect();
public int mKeyLetterSize;
@@ -211,26 +215,22 @@
public int mKeyHintLabelSize;
public int mAnimAlpha;
- public KeyDrawParams(TypedArray a) {
+ public KeyDrawParams(final TypedArray a) {
mKeyBackground = a.getDrawable(R.styleable.KeyboardView_keyBackground);
- if (a.hasValue(R.styleable.KeyboardView_keyLetterSize)) {
- mKeyLetterRatio = UNDEFINED_RATIO;
- mKeyLetterSize = a.getDimensionPixelSize(R.styleable.KeyboardView_keyLetterSize, 0);
- } else {
- mKeyLetterRatio = getRatio(a, R.styleable.KeyboardView_keyLetterRatio);
+ if (!isValidFraction(mKeyLetterRatio = getFraction(a,
+ R.styleable.KeyboardView_keyLetterSize))) {
+ mKeyLetterSize = getDimensionPixelSize(a, R.styleable.KeyboardView_keyLetterSize);
}
- if (a.hasValue(R.styleable.KeyboardView_keyLabelSize)) {
- mKeyLabelRatio = UNDEFINED_RATIO;
- mKeyLabelSize = a.getDimensionPixelSize(R.styleable.KeyboardView_keyLabelSize, 0);
- } else {
- mKeyLabelRatio = getRatio(a, R.styleable.KeyboardView_keyLabelRatio);
+ if (!isValidFraction(mKeyLabelRatio = getFraction(a,
+ R.styleable.KeyboardView_keyLabelSize))) {
+ mKeyLabelSize = getDimensionPixelSize(a, R.styleable.KeyboardView_keyLabelSize);
}
- mKeyLargeLabelRatio = getRatio(a, R.styleable.KeyboardView_keyLargeLabelRatio);
- mKeyLargeLetterRatio = getRatio(a, R.styleable.KeyboardView_keyLargeLetterRatio);
- mKeyHintLetterRatio = getRatio(a, R.styleable.KeyboardView_keyHintLetterRatio);
- mKeyShiftedLetterHintRatio = getRatio(a,
+ mKeyLargeLabelRatio = getFraction(a, R.styleable.KeyboardView_keyLargeLabelRatio);
+ mKeyLargeLetterRatio = getFraction(a, R.styleable.KeyboardView_keyLargeLetterRatio);
+ mKeyHintLetterRatio = getFraction(a, R.styleable.KeyboardView_keyHintLetterRatio);
+ mKeyShiftedLetterHintRatio = getFraction(a,
R.styleable.KeyboardView_keyShiftedLetterHintRatio);
- mKeyHintLabelRatio = getRatio(a, R.styleable.KeyboardView_keyHintLabelRatio);
+ mKeyHintLabelRatio = getFraction(a, R.styleable.KeyboardView_keyHintLabelRatio);
mKeyLabelHorizontalPadding = a.getDimension(
R.styleable.KeyboardView_keyLabelHorizontalPadding, 0);
mKeyHintLetterPadding = a.getDimension(
@@ -257,10 +257,10 @@
}
public void updateKeyHeight(int keyHeight) {
- if (mKeyLetterRatio >= 0.0f) {
+ if (isValidFraction(mKeyLetterRatio)) {
mKeyLetterSize = (int)(keyHeight * mKeyLetterRatio);
}
- if (mKeyLabelRatio >= 0.0f) {
+ if (isValidFraction(mKeyLabelRatio)) {
mKeyLabelSize = (int)(keyHeight * mKeyLabelRatio);
}
mKeyLargeLabelSize = (int)(keyHeight * mKeyLargeLabelRatio);
@@ -335,7 +335,7 @@
R.styleable.KeyboardView_keyPreviewOffset, 0);
mPreviewHeight = a.getDimensionPixelSize(
R.styleable.KeyboardView_keyPreviewHeight, 80);
- mPreviewTextRatio = getRatio(a, R.styleable.KeyboardView_keyPreviewTextRatio);
+ mPreviewTextRatio = getFraction(a, R.styleable.KeyboardView_keyPreviewTextRatio);
mPreviewTextColor = a.getColor(R.styleable.KeyboardView_keyPreviewTextColor, 0);
mLingerTimeout = a.getInt(R.styleable.KeyboardView_keyPreviewLingerTimeout, 0);
@@ -367,9 +367,9 @@
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
-
mKeyDrawParams = new KeyDrawParams(a);
mKeyPreviewDrawParams = new KeyPreviewDrawParams(a, mKeyDrawParams);
+ mDelayAfterPreview = mKeyPreviewDrawParams.mLingerTimeout;
mKeyPreviewLayoutId = a.getResourceId(R.styleable.KeyboardView_keyPreviewLayout, 0);
if (mKeyPreviewLayoutId == 0) {
mShowKeyPreviewPopup = false;
@@ -378,17 +378,30 @@
R.styleable.KeyboardView_verticalCorrection, 0);
mMoreKeysLayout = a.getResourceId(R.styleable.KeyboardView_moreKeysLayout, 0);
mBackgroundDimAlpha = a.getInt(R.styleable.KeyboardView_backgroundDimAlpha, 0);
- mPreviewPlacerView = new PreviewPlacerView(context, a);
a.recycle();
- mDelayAfterPreview = mKeyPreviewDrawParams.mLingerTimeout;
-
+ mPreviewPlacerView = new PreviewPlacerView(context, attrs);
mPaint.setAntiAlias(true);
}
- // Read fraction value in TypedArray as float.
- /* package */ static float getRatio(TypedArray a, int index) {
- return a.getFraction(index, 1000, 1000, 1) / 1000.0f;
+ static boolean isValidFraction(final float fraction) {
+ return fraction >= 0.0f;
+ }
+
+ static float getFraction(final TypedArray a, final int index) {
+ final TypedValue value = a.peekValue(index);
+ if (value == null || value.type != TypedValue.TYPE_FRACTION) {
+ return UNDEFINED_RATIO;
+ }
+ return a.getFraction(index, 1, 1, UNDEFINED_RATIO);
+ }
+
+ public static int getDimensionPixelSize(final TypedArray a, final int index) {
+ final TypedValue value = a.peekValue(index);
+ if (value == null || value.type != TypedValue.TYPE_DIMENSION) {
+ return UNDEFINED_DIMENSION;
+ }
+ return a.getDimensionPixelSize(index, UNDEFINED_DIMENSION);
}
/**
@@ -438,9 +451,8 @@
return mShowKeyPreviewPopup;
}
- public void setGestureHandlingMode(boolean shouldHandleGesture,
- boolean drawsGesturePreviewTrail, boolean drawsGestureFloatingPreviewText) {
- mShouldHandleGesture = shouldHandleGesture;
+ public void setGesturePreviewMode(boolean drawsGesturePreviewTrail,
+ boolean drawsGestureFloatingPreviewText) {
mPreviewPlacerView.setGesturePreviewMode(
drawsGesturePreviewTrail, drawsGestureFloatingPreviewText);
}
@@ -463,16 +475,12 @@
onDrawKeyboard(canvas);
return;
}
- if (mBufferNeedsUpdate || mOffscreenBuffer == null) {
- mBufferNeedsUpdate = false;
+
+ final boolean bufferNeedsUpdates = mInvalidateAllKeys || !mInvalidatedKeys.isEmpty();
+ if (bufferNeedsUpdates || mOffscreenBuffer == null) {
if (maybeAllocateOffscreenBuffer()) {
mInvalidateAllKeys = true;
- // TODO: Stop using the offscreen canvas even when in software rendering
- if (mOffscreenCanvas != null) {
- mOffscreenCanvas.setBitmap(mOffscreenBuffer);
- } else {
- mOffscreenCanvas = new Canvas(mOffscreenBuffer);
- }
+ maybeCreateOffscreenCanvas();
}
onDrawKeyboard(mOffscreenCanvas);
}
@@ -501,6 +509,15 @@
}
}
+ private void maybeCreateOffscreenCanvas() {
+ // TODO: Stop using the offscreen canvas even when in software rendering
+ if (mOffscreenCanvas != null) {
+ mOffscreenCanvas.setBitmap(mOffscreenBuffer);
+ } else {
+ mOffscreenCanvas = new Canvas(mOffscreenBuffer);
+ }
+ }
+
private void onDrawKeyboard(final Canvas canvas) {
if (mKeyboard == null) return;
@@ -528,13 +545,12 @@
}
if (!isHardwareAccelerated) {
canvas.clipRegion(mClipRegion, Region.Op.REPLACE);
- }
-
- // Draw keyboard background.
- canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
- final Drawable background = getBackground();
- if (background != null) {
- background.draw(canvas);
+ // Draw keyboard background.
+ canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
+ final Drawable background = getBackground();
+ if (background != null) {
+ background.draw(canvas);
+ }
}
// TODO: Confirm if it's really required to draw all keys when hardware acceleration is on.
@@ -907,15 +923,30 @@
}
}
- // Called by {@link PointerTracker} constructor to create a TextView.
- @Override
- public TextView inflateKeyPreviewText() {
+ private TextView getKeyPreviewText(final int pointerId) {
+ TextView previewText = mKeyPreviewTexts.get(pointerId);
+ if (previewText != null) {
+ return previewText;
+ }
final Context context = getContext();
if (mKeyPreviewLayoutId != 0) {
- return (TextView)LayoutInflater.from(context).inflate(mKeyPreviewLayoutId, null);
+ previewText = (TextView)LayoutInflater.from(context).inflate(mKeyPreviewLayoutId, null);
} else {
- return new TextView(context);
+ previewText = new TextView(context);
}
+ mKeyPreviewTexts.put(pointerId, previewText);
+ return previewText;
+ }
+
+ private void dismissAllKeyPreviews() {
+ final int pointerCount = mKeyPreviewTexts.size();
+ for (int id = 0; id < pointerCount; id++) {
+ final TextView previewText = mKeyPreviewTexts.get(id);
+ if (previewText != null) {
+ previewText.setVisibility(INVISIBLE);
+ }
+ }
+ PointerTracker.setReleasedKeyGraphicsToAllKeys();
}
@Override
@@ -936,9 +967,18 @@
final int[] viewOrigin = new int[2];
getLocationInWindow(viewOrigin);
mPreviewPlacerView.setOrigin(viewOrigin[0], viewOrigin[1]);
- final ViewGroup windowContentView =
- (ViewGroup)getRootView().findViewById(android.R.id.content);
- windowContentView.addView(mPreviewPlacerView);
+ final View rootView = getRootView();
+ if (rootView == null) {
+ Log.w(TAG, "Cannot find root view");
+ return;
+ }
+ final ViewGroup windowContentView = (ViewGroup)rootView.findViewById(android.R.id.content);
+ // Note: It'd be very weird if we get null by android.R.id.content.
+ if (windowContentView == null) {
+ Log.w(TAG, "Cannot find android.R.id.content view to add PreviewPlacerView");
+ } else {
+ windowContentView.addView(mPreviewPlacerView);
+ }
}
public void showGestureFloatingPreviewText(String gestureFloatingPreviewText) {
@@ -952,7 +992,7 @@
}
@Override
- public void showGestureTrail(PointerTracker tracker) {
+ public void showGesturePreviewTrail(PointerTracker tracker) {
locatePreviewPlacerView();
mPreviewPlacerView.invalidatePointer(tracker);
}
@@ -962,7 +1002,7 @@
public void showKeyPreview(PointerTracker tracker) {
if (!mShowKeyPreviewPopup) return;
- final TextView previewText = tracker.getKeyPreviewText();
+ final TextView previewText = getKeyPreviewText(tracker.mPointerId);
// If the key preview has no parent view yet, add it to the ViewGroup which can place
// key preview absolutely in SoftInputWindow.
if (previewText.getParent() == null) {
@@ -1052,7 +1092,6 @@
public void invalidateAllKeys() {
mInvalidatedKeys.clear();
mInvalidateAllKeys = true;
- mBufferNeedsUpdate = true;
invalidate();
}
@@ -1070,13 +1109,11 @@
mInvalidatedKeys.add(key);
final int x = key.mX + getPaddingLeft();
final int y = key.mY + getPaddingTop();
- mWorkingRect.set(x, y, x + key.mWidth, y + key.mHeight);
- mBufferNeedsUpdate = true;
- invalidate(mWorkingRect);
+ invalidate(x, y, x + key.mWidth, y + key.mHeight);
}
public void closing() {
- PointerTracker.dismissAllKeyPreviews();
+ dismissAllKeyPreviews();
cancelAllMessages();
mInvalidateAllKeys = true;
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index fe9cb94..df84271 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -110,7 +110,6 @@
new WeakHashMap<Key, MoreKeysPanel>();
private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint;
- private final PointerTrackerParams mPointerTrackerParams;
private final SuddenJumpingTouchEventHandler mTouchScreenRegulator;
protected KeyDetector mKeyDetector;
@@ -127,11 +126,26 @@
private static final int MSG_LONGPRESS_KEY = 2;
private static final int MSG_DOUBLE_TAP = 3;
- private final KeyTimerParams mParams;
+ private final int mKeyRepeatStartTimeout;
+ private final int mKeyRepeatInterval;
+ private final int mLongPressKeyTimeout;
+ private final int mLongPressShiftKeyTimeout;
+ private final int mIgnoreAltCodeKeyTimeout;
- public KeyTimerHandler(MainKeyboardView outerInstance, KeyTimerParams params) {
+ public KeyTimerHandler(final MainKeyboardView outerInstance,
+ final TypedArray mainKeyboardViewAttr) {
super(outerInstance);
- mParams = params;
+
+ mKeyRepeatStartTimeout = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0);
+ mKeyRepeatInterval = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_keyRepeatInterval, 0);
+ mLongPressKeyTimeout = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_longPressKeyTimeout, 0);
+ mLongPressShiftKeyTimeout = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_longPressShiftKeyTimeout, 0);
+ mIgnoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0);
}
@Override
@@ -146,7 +160,7 @@
final Key currentKey = tracker.getKey();
if (currentKey != null && currentKey.mCode == msg.arg1) {
tracker.onRegisterKey(currentKey);
- startKeyRepeatTimer(tracker, mParams.mKeyRepeatInterval);
+ startKeyRepeatTimer(tracker, mKeyRepeatInterval);
}
break;
case MSG_LONGPRESS_KEY:
@@ -167,7 +181,7 @@
@Override
public void startKeyRepeatTimer(PointerTracker tracker) {
- startKeyRepeatTimer(tracker, mParams.mKeyRepeatStartTimeout);
+ startKeyRepeatTimer(tracker, mKeyRepeatStartTimeout);
}
public void cancelKeyRepeatTimer() {
@@ -185,7 +199,7 @@
final int delay;
switch (code) {
case Keyboard.CODE_SHIFT:
- delay = mParams.mLongPressShiftKeyTimeout;
+ delay = mLongPressShiftKeyTimeout;
break;
default:
delay = 0;
@@ -206,15 +220,15 @@
final int delay;
switch (key.mCode) {
case Keyboard.CODE_SHIFT:
- delay = mParams.mLongPressShiftKeyTimeout;
+ delay = mLongPressShiftKeyTimeout;
break;
default:
if (KeyboardSwitcher.getInstance().isInMomentarySwitchState()) {
// We use longer timeout for sliding finger input started from the symbols
// mode key.
- delay = mParams.mLongPressKeyTimeout * 3;
+ delay = mLongPressKeyTimeout * 3;
} else {
- delay = mParams.mLongPressKeyTimeout;
+ delay = mLongPressKeyTimeout;
}
break;
}
@@ -268,7 +282,7 @@
}
sendMessageDelayed(
- obtainMessage(MSG_TYPING_STATE_EXPIRED), mParams.mIgnoreAltCodeKeyTimeout);
+ obtainMessage(MSG_TYPING_STATE_EXPIRED), mIgnoreAltCodeKeyTimeout);
if (isTyping) {
return;
}
@@ -307,50 +321,6 @@
}
}
- public static class PointerTrackerParams {
- public final boolean mSlidingKeyInputEnabled;
- public final int mTouchNoiseThresholdTime;
- public final float mTouchNoiseThresholdDistance;
-
- public static final PointerTrackerParams DEFAULT = new PointerTrackerParams();
-
- private PointerTrackerParams() {
- mSlidingKeyInputEnabled = false;
- mTouchNoiseThresholdTime =0;
- mTouchNoiseThresholdDistance = 0;
- }
-
- public PointerTrackerParams(TypedArray mainKeyboardViewAttr) {
- mSlidingKeyInputEnabled = mainKeyboardViewAttr.getBoolean(
- R.styleable.MainKeyboardView_slidingKeyInputEnable, false);
- mTouchNoiseThresholdTime = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_touchNoiseThresholdTime, 0);
- mTouchNoiseThresholdDistance = mainKeyboardViewAttr.getDimension(
- R.styleable.MainKeyboardView_touchNoiseThresholdDistance, 0);
- }
- }
-
- static class KeyTimerParams {
- public final int mKeyRepeatStartTimeout;
- public final int mKeyRepeatInterval;
- public final int mLongPressKeyTimeout;
- public final int mLongPressShiftKeyTimeout;
- public final int mIgnoreAltCodeKeyTimeout;
-
- public KeyTimerParams(TypedArray mainKeyboardViewAttr) {
- mKeyRepeatStartTimeout = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0);
- mKeyRepeatInterval = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_keyRepeatInterval, 0);
- mLongPressKeyTimeout = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_longPressKeyTimeout, 0);
- mLongPressShiftKeyTimeout = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_longPressShiftKeyTimeout, 0);
- mIgnoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt(
- R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0);
- }
- }
-
public MainKeyboardView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.mainKeyboardViewStyle);
}
@@ -374,8 +344,8 @@
R.styleable.MainKeyboardView_autoCorrectionSpacebarLedEnabled, false);
mAutoCorrectionSpacebarLedIcon = a.getDrawable(
R.styleable.MainKeyboardView_autoCorrectionSpacebarLedIcon);
- mSpacebarTextRatio = a.getFraction(R.styleable.MainKeyboardView_spacebarTextRatio,
- 1000, 1000, 1) / 1000.0f;
+ mSpacebarTextRatio = a.getFraction(
+ R.styleable.MainKeyboardView_spacebarTextRatio, 1, 1, 1.0f);
mSpacebarTextColor = a.getColor(R.styleable.MainKeyboardView_spacebarTextColor, 0);
mSpacebarTextShadowColor = a.getColor(
R.styleable.MainKeyboardView_spacebarTextShadowColor, 0);
@@ -389,19 +359,15 @@
final int altCodeKeyWhileTypingFadeinAnimatorResId = a.getResourceId(
R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0);
- final KeyTimerParams keyTimerParams = new KeyTimerParams(a);
- mPointerTrackerParams = new PointerTrackerParams(a);
-
final float keyHysteresisDistance = a.getDimension(
R.styleable.MainKeyboardView_keyHysteresisDistance, 0);
mKeyDetector = new KeyDetector(keyHysteresisDistance);
- mKeyTimerHandler = new KeyTimerHandler(this, keyTimerParams);
+ mKeyTimerHandler = new KeyTimerHandler(this, a);
mConfigShowMoreKeysKeyboardAtTouchedPoint = a.getBoolean(
R.styleable.MainKeyboardView_showMoreKeysKeyboardAtTouchedPoint, false);
+ PointerTracker.setParameters(a);
a.recycle();
- PointerTracker.setParameters(mPointerTrackerParams);
-
mLanguageOnSpacebarFadeoutAnimator = loadObjectAnimator(
languageOnSpacebarFadeoutAnimatorResId, this);
mAltCodeKeyWhileTypingFadeoutAnimator = loadObjectAnimator(
@@ -482,7 +448,7 @@
super.setKeyboard(keyboard);
mKeyDetector.setKeyboard(
keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection);
- PointerTracker.setKeyDetector(mKeyDetector, mShouldHandleGesture);
+ PointerTracker.setKeyDetector(mKeyDetector);
mTouchScreenRegulator.setKeyboard(keyboard);
mMoreKeysPanelCache.clear();
@@ -500,12 +466,13 @@
AccessibleKeyboardViewProxy.getInstance().setKeyboard(keyboard);
}
- @Override
- public void setGestureHandlingMode(final boolean shouldHandleGesture,
- boolean drawsGesturePreviewTrail, boolean drawsGestureFloatingPreviewText) {
- super.setGestureHandlingMode(shouldHandleGesture, drawsGesturePreviewTrail,
- drawsGestureFloatingPreviewText);
- PointerTracker.setKeyDetector(mKeyDetector, shouldHandleGesture);
+ // Note that this method is called from a non-UI thread.
+ public void setMainDictionaryAvailability(boolean mainDictionaryAvailable) {
+ PointerTracker.setMainDictionaryAvailability(mainDictionaryAvailable);
+ }
+
+ public void setGestureHandlingEnabledByUser(boolean gestureHandlingEnabledByUser) {
+ PointerTracker.setGestureHandlingEnabledByUser(gestureHandlingEnabledByUser);
}
/**
@@ -527,7 +494,17 @@
// to properly show the splash screen, which requires that the window token of the
// KeyboardView be non-null.
if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.getInstance().mainKeyboardView_onAttachedToWindow();
+ ResearchLogger.getInstance().mainKeyboardView_onAttachedToWindow(this);
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ // Notify the research logger that the keyboard view has been detached. This is needed
+ // to invalidate the reference of {@link MainKeyboardView} to null.
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.getInstance().mainKeyboardView_onDetachedFromWindow();
}
}
@@ -607,9 +584,8 @@
}
private void invokeCodeInput(int primaryCode) {
- mKeyboardActionListener.onCodeInput(primaryCode,
- KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
- KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
+ mKeyboardActionListener.onCodeInput(
+ primaryCode, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
}
private void invokeReleaseKey(int primaryCode) {
@@ -834,20 +810,6 @@
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.
*
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java b/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
index a183546..cd4e300 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
@@ -39,11 +39,7 @@
Key nearestKey = null;
int nearestDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare;
- final Keyboard keyboard = getKeyboard();
- if (keyboard == null) {
- throw new NullPointerException("Keyboard isn't set");
- }
- for (final Key key : keyboard.mKeys) {
+ for (final Key key : getKeyboard().mKeys) {
final int dist = key.squaredDistanceToEdge(touchX, touchY);
if (dist < nearestDist) {
nearestKey = key;
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
index 870eff2..e513a14 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
@@ -25,6 +25,7 @@
import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy;
import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
+import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.InputPointers;
import com.android.inputmethod.latin.R;
@@ -50,7 +51,8 @@
public void onCodeInput(int primaryCode, int x, int y) {
// Because a more keys keyboard doesn't need proximity characters correction, we don't
// send touch event coordinates.
- mListener.onCodeInput(primaryCode, NOT_A_TOUCH_COORDINATE, NOT_A_TOUCH_COORDINATE);
+ mListener.onCodeInput(
+ primaryCode, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
}
@Override
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 184011f..be101cf 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -16,25 +16,25 @@
package com.android.inputmethod.keyboard;
-import android.graphics.Canvas;
-import android.graphics.Paint;
+import android.content.res.TypedArray;
import android.os.SystemClock;
import android.util.Log;
import android.view.MotionEvent;
-import android.view.View;
-import android.widget.TextView;
import com.android.inputmethod.accessibility.AccessibilityUtils;
import com.android.inputmethod.keyboard.internal.GestureStroke;
+import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewTrail;
import com.android.inputmethod.keyboard.internal.PointerTrackerQueue;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.InputPointers;
import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.research.ResearchLogger;
import java.util.ArrayList;
-public class PointerTracker implements PointerTrackerQueue.ElementActions {
+public class PointerTracker implements PointerTrackerQueue.Element {
private static final String TAG = PointerTracker.class.getSimpleName();
private static final boolean DEBUG_EVENT = false;
private static final boolean DEBUG_MOVE_EVENT = false;
@@ -43,6 +43,9 @@
/** True if {@link PointerTracker}s should handle gesture events. */
private static boolean sShouldHandleGesture = false;
+ private static boolean sMainDictionaryAvailable = false;
+ private static boolean sGestureHandlingEnabledByInputField = false;
+ private static boolean sGestureHandlingEnabledByUser = false;
private static final int MIN_GESTURE_RECOGNITION_TIME = 100; // msec
@@ -75,10 +78,9 @@
public interface DrawingProxy extends MoreKeysPanel.Controller {
public void invalidateKey(Key key);
- public TextView inflateKeyPreviewText();
public void showKeyPreview(PointerTracker tracker);
public void dismissKeyPreview(PointerTracker tracker);
- public void showGestureTrail(PointerTracker tracker);
+ public void showGesturePreviewTrail(PointerTracker tracker);
}
public interface TimerProxy {
@@ -117,19 +119,40 @@
}
}
+ static class PointerTrackerParams {
+ public final boolean mSlidingKeyInputEnabled;
+ public final int mTouchNoiseThresholdTime;
+ public final float mTouchNoiseThresholdDistance;
+ public final int mTouchNoiseThresholdDistanceSquared;
+
+ public static final PointerTrackerParams DEFAULT = new PointerTrackerParams();
+
+ private PointerTrackerParams() {
+ mSlidingKeyInputEnabled = false;
+ mTouchNoiseThresholdTime = 0;
+ mTouchNoiseThresholdDistance = 0.0f;
+ mTouchNoiseThresholdDistanceSquared = 0;
+ }
+
+ public PointerTrackerParams(TypedArray mainKeyboardViewAttr) {
+ mSlidingKeyInputEnabled = mainKeyboardViewAttr.getBoolean(
+ R.styleable.MainKeyboardView_slidingKeyInputEnable, false);
+ mTouchNoiseThresholdTime = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_touchNoiseThresholdTime, 0);
+ final float touchNouseThresholdDistance = mainKeyboardViewAttr.getDimension(
+ R.styleable.MainKeyboardView_touchNoiseThresholdDistance, 0);
+ mTouchNoiseThresholdDistance = touchNouseThresholdDistance;
+ mTouchNoiseThresholdDistanceSquared =
+ (int)(touchNouseThresholdDistance * touchNouseThresholdDistance);
+ }
+ }
+
// Parameters for pointer handling.
- private static MainKeyboardView.PointerTrackerParams sParams;
- private static int sTouchNoiseThresholdDistanceSquared;
+ private static PointerTrackerParams sParams;
private static boolean sNeedsPhantomSuddenMoveEventHack;
- private static final ArrayList<PointerTracker> sTrackers = new ArrayList<PointerTracker>();
- private static final InputPointers sAggregratedPointers = new InputPointers(
- GestureStroke.DEFAULT_CAPACITY);
+ private static final ArrayList<PointerTracker> sTrackers = CollectionUtils.newArrayList();
private static PointerTrackerQueue sPointerTrackerQueue;
- // HACK: Change gesture detection criteria depending on this variable.
- // TODO: Find more comprehensive ways to detect a gesture start.
- // True when the previous user input was a gesture input, not a typing input.
- private static boolean sWasInGesture;
public final int mPointerId;
@@ -140,15 +163,14 @@
private Keyboard mKeyboard;
private int mKeyQuarterWidthSquared;
- private final TextView mKeyPreviewText;
- private boolean mIsAlphabetKeyboard;
- private boolean mIsPossibleGesture = false;
- private boolean mInGesture = false;
-
- // TODO: Remove these variables
- private int mLastRecognitionPointSize = 0;
- private long mLastRecognitionTime = 0;
+ private boolean mIsDetectingGesture = false; // per PointerTracker.
+ private static boolean sInGesture = false;
+ private static long sGestureFirstDownTime;
+ private static final InputPointers sAggregratedPointers = new InputPointers(
+ GestureStroke.DEFAULT_CAPACITY);
+ private static int sLastRecognitionPointSize = 0;
+ private static long sLastRecognitionTime = 0;
// The position and time at which first down event occurred.
private long mDownTime;
@@ -186,7 +208,7 @@
private static final KeyboardActionListener EMPTY_LISTENER =
new KeyboardActionListener.Adapter();
- private final GestureStroke mGestureStroke;
+ private final GestureStrokeWithPreviewTrail mGestureStrokeWithPreviewTrail;
public static void init(boolean hasDistinctMultitouch,
boolean needsPhantomSuddenMoveEventHack) {
@@ -196,28 +218,32 @@
sPointerTrackerQueue = null;
}
sNeedsPhantomSuddenMoveEventHack = needsPhantomSuddenMoveEventHack;
-
- setParameters(MainKeyboardView.PointerTrackerParams.DEFAULT);
- updateGestureHandlingMode(null, false /* shouldHandleGesture */);
+ sParams = PointerTrackerParams.DEFAULT;
}
- public static void setParameters(MainKeyboardView.PointerTrackerParams params) {
- sParams = params;
- sTouchNoiseThresholdDistanceSquared = (int)(
- params.mTouchNoiseThresholdDistance * params.mTouchNoiseThresholdDistance);
+ public static void setParameters(final TypedArray mainKeyboardViewAttr) {
+ sParams = new PointerTrackerParams(mainKeyboardViewAttr);
}
- private static void updateGestureHandlingMode(Keyboard keyboard, boolean shouldHandleGesture) {
- if (!shouldHandleGesture
- || AccessibilityUtils.getInstance().isTouchExplorationEnabled()
- || (keyboard != null && keyboard.mId.passwordInput())) {
- sShouldHandleGesture = false;
- } else {
- sShouldHandleGesture = true;
- }
+ private static void updateGestureHandlingMode() {
+ sShouldHandleGesture = sMainDictionaryAvailable
+ && sGestureHandlingEnabledByInputField
+ && sGestureHandlingEnabledByUser
+ && !AccessibilityUtils.getInstance().isTouchExplorationEnabled();
}
- public static PointerTracker getPointerTracker(final int id, KeyEventHandler handler) {
+ // Note that this method is called from a non-UI thread.
+ public static void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) {
+ sMainDictionaryAvailable = mainDictionaryAvailable;
+ updateGestureHandlingMode();
+ }
+
+ public static void setGestureHandlingEnabledByUser(final boolean gestureHandlingEnabledByUser) {
+ sGestureHandlingEnabledByUser = gestureHandlingEnabledByUser;
+ updateGestureHandlingMode();
+ }
+
+ public static PointerTracker getPointerTracker(final int id, final KeyEventHandler handler) {
final ArrayList<PointerTracker> trackers = sTrackers;
// Create pointer trackers until we can get 'id+1'-th tracker, if needed.
@@ -233,7 +259,7 @@
return sPointerTrackerQueue != null ? sPointerTrackerQueue.isAnyInSlidingKeyInput() : false;
}
- public static void setKeyboardActionListener(KeyboardActionListener listener) {
+ public static void setKeyboardActionListener(final KeyboardActionListener listener) {
final int trackersSize = sTrackers.size();
for (int i = 0; i < trackersSize; ++i) {
final PointerTracker tracker = sTrackers.get(i);
@@ -241,7 +267,7 @@
}
}
- public static void setKeyDetector(KeyDetector keyDetector, boolean shouldHandleGesture) {
+ public static void setKeyDetector(final KeyDetector keyDetector) {
final int trackersSize = sTrackers.size();
for (int i = 0; i < trackersSize; ++i) {
final PointerTracker tracker = sTrackers.get(i);
@@ -250,70 +276,33 @@
tracker.mKeyboardLayoutHasBeenChanged = true;
}
final Keyboard keyboard = keyDetector.getKeyboard();
- updateGestureHandlingMode(keyboard, shouldHandleGesture);
+ sGestureHandlingEnabledByInputField = !keyboard.mId.passwordInput();
+ updateGestureHandlingMode();
}
- public static void dismissAllKeyPreviews() {
+ public static void setReleasedKeyGraphicsToAllKeys() {
final int trackersSize = sTrackers.size();
for (int i = 0; i < trackersSize; ++i) {
final PointerTracker tracker = sTrackers.get(i);
- tracker.getKeyPreviewText().setVisibility(View.INVISIBLE);
tracker.setReleasedKeyGraphics(tracker.mCurrentKey);
}
}
- // TODO: To handle multi-touch gestures we may want to move this method to
- // {@link PointerTrackerQueue}.
- private static InputPointers getIncrementalBatchPoints() {
- final int trackersSize = sTrackers.size();
- for (int i = 0; i < trackersSize; ++i) {
- final PointerTracker tracker = sTrackers.get(i);
- tracker.mGestureStroke.appendIncrementalBatchPoints(sAggregratedPointers);
- }
- return sAggregratedPointers;
- }
-
- // TODO: To handle multi-touch gestures we may want to move this method to
- // {@link PointerTrackerQueue}.
- private static InputPointers getAllBatchPoints() {
- final int trackersSize = sTrackers.size();
- for (int i = 0; i < trackersSize; ++i) {
- final PointerTracker tracker = sTrackers.get(i);
- tracker.mGestureStroke.appendAllBatchPoints(sAggregratedPointers);
- }
- return sAggregratedPointers;
- }
-
- // TODO: To handle multi-touch gestures we may want to move this method to
- // {@link PointerTrackerQueue}.
- public static void clearBatchInputPointsOfAllPointerTrackers() {
- final int trackersSize = sTrackers.size();
- for (int i = 0; i < trackersSize; ++i) {
- final PointerTracker tracker = sTrackers.get(i);
- tracker.mGestureStroke.reset();
- }
- sAggregratedPointers.reset();
- }
-
- private PointerTracker(int id, KeyEventHandler handler) {
- if (handler == null)
+ private PointerTracker(final int id, final KeyEventHandler handler) {
+ if (handler == null) {
throw new NullPointerException();
+ }
mPointerId = id;
- mGestureStroke = new GestureStroke(id);
+ mGestureStrokeWithPreviewTrail = new GestureStrokeWithPreviewTrail(id);
setKeyDetectorInner(handler.getKeyDetector());
mListener = handler.getKeyboardActionListener();
mDrawingProxy = handler.getDrawingProxy();
mTimerProxy = handler.getTimerProxy();
- mKeyPreviewText = mDrawingProxy.inflateKeyPreviewText();
- }
-
- public TextView getKeyPreviewText() {
- return mKeyPreviewText;
}
// Returns true if keyboard has been changed by this callback.
- private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key) {
- if (mInGesture) {
+ private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key) {
+ if (sInGesture) {
return false;
}
final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
@@ -337,7 +326,8 @@
// Note that we need primaryCode argument because the keyboard may in shifted state and the
// primaryCode is different from {@link Key#mCode}.
- private void callListenerOnCodeInput(Key key, int primaryCode, int x, int y) {
+ private void callListenerOnCodeInput(final Key key, final int primaryCode, final int x,
+ final int y) {
final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState();
final int code = altersCode ? key.mAltCode : primaryCode;
@@ -366,8 +356,9 @@
// Note that we need primaryCode argument because the keyboard may in shifted state and the
// primaryCode is different from {@link Key#mCode}.
- private void callListenerOnRelease(Key key, int primaryCode, boolean withSliding) {
- if (mInGesture) {
+ private void callListenerOnRelease(final Key key, final int primaryCode,
+ final boolean withSliding) {
+ if (sInGesture) {
return;
}
final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
@@ -389,20 +380,19 @@
}
private void callListenerOnCancelInput() {
- if (DEBUG_LISTENER)
+ if (DEBUG_LISTENER) {
Log.d(TAG, "onCancelInput");
+ }
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.pointerTracker_callListenerOnCancelInput();
}
mListener.onCancelInput();
}
- private void setKeyDetectorInner(KeyDetector keyDetector) {
+ private void setKeyDetectorInner(final KeyDetector keyDetector) {
mKeyDetector = keyDetector;
mKeyboard = keyDetector.getKeyboard();
- mIsAlphabetKeyboard = mKeyboard.mId.isAlphabetKeyboard();
- mGestureStroke.setGestureSampleLength(
- mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight);
+ mGestureStrokeWithPreviewTrail.setGestureSampleLength(mKeyboard.mMostCommonKeyWidth);
final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY);
if (newKey != mCurrentKey) {
if (mDrawingProxy != null) {
@@ -428,11 +418,11 @@
return mCurrentKey != null && mCurrentKey.isModifier();
}
- public Key getKeyOn(int x, int y) {
+ public Key getKeyOn(final int x, final int y) {
return mKeyDetector.detectHitKey(x, y);
}
- private void setReleasedKeyGraphics(Key key) {
+ private void setReleasedKeyGraphics(final Key key) {
mDrawingProxy.dismissKeyPreview(this);
if (key == null) {
return;
@@ -463,7 +453,7 @@
}
}
- private void setPressedKeyGraphics(Key key) {
+ private void setPressedKeyGraphics(final Key key) {
if (key == null) {
return;
}
@@ -475,7 +465,7 @@
return;
}
- if (!key.noKeyPreview() && !mInGesture) {
+ if (!key.noKeyPreview() && !sInGesture) {
mDrawingProxy.showKeyPreview(this);
}
updatePressKeyGraphics(key);
@@ -502,20 +492,18 @@
}
}
- private void updateReleaseKeyGraphics(Key key) {
+ private void updateReleaseKeyGraphics(final Key key) {
key.onReleased();
mDrawingProxy.invalidateKey(key);
}
- private void updatePressKeyGraphics(Key key) {
+ private void updatePressKeyGraphics(final Key key) {
key.onPressed();
mDrawingProxy.invalidateKey(key);
}
- public void drawGestureTrail(Canvas canvas, Paint paint) {
- if (mInGesture) {
- mGestureStroke.drawGestureTrail(canvas, paint, mLastX, mLastY);
- }
+ public GestureStrokeWithPreviewTrail getGestureStrokeWithPreviewTrail() {
+ return mGestureStrokeWithPreviewTrail;
}
public int getLastX() {
@@ -530,77 +518,91 @@
return mDownTime;
}
- private Key onDownKey(int x, int y, long eventTime) {
+ private Key onDownKey(final int x, final int y, final long eventTime) {
mDownTime = eventTime;
return onMoveToNewKey(onMoveKeyInternal(x, y), x, y);
}
- private Key onMoveKeyInternal(int x, int y) {
+ private Key onMoveKeyInternal(final int x, final int y) {
mLastX = x;
mLastY = y;
return mKeyDetector.detectHitKey(x, y);
}
- private Key onMoveKey(int x, int y) {
+ private Key onMoveKey(final int x, final int y) {
return onMoveKeyInternal(x, y);
}
- private Key onMoveToNewKey(Key newKey, int x, int y) {
+ private Key onMoveToNewKey(final Key newKey, final int x, final int y) {
mCurrentKey = newKey;
mKeyX = x;
mKeyY = y;
return newKey;
}
+ private static int getActivePointerTrackerCount() {
+ return (sPointerTrackerQueue == null) ? 1 : sPointerTrackerQueue.size();
+ }
+
private void startBatchInput() {
if (DEBUG_LISTENER) {
Log.d(TAG, "onStartBatchInput");
}
- mInGesture = true;
+ sInGesture = true;
mListener.onStartBatchInput();
+ mDrawingProxy.showGesturePreviewTrail(this);
}
- private void updateBatchInput(InputPointers batchPoints) {
- if (DEBUG_LISTENER) {
- Log.d(TAG, "onUpdateBatchInput: batchPoints=" + batchPoints.getPointerSize());
+ private void updateBatchInput(final long eventTime) {
+ synchronized (sAggregratedPointers) {
+ mGestureStrokeWithPreviewTrail.appendIncrementalBatchPoints(sAggregratedPointers);
+ final int size = sAggregratedPointers.getPointerSize();
+ if (size > sLastRecognitionPointSize
+ && eventTime > sLastRecognitionTime + MIN_GESTURE_RECOGNITION_TIME) {
+ sLastRecognitionPointSize = size;
+ sLastRecognitionTime = eventTime;
+ if (DEBUG_LISTENER) {
+ Log.d(TAG, "onUpdateBatchInput: batchPoints=" + size);
+ }
+ mListener.onUpdateBatchInput(sAggregratedPointers);
+ }
}
- mListener.onUpdateBatchInput(batchPoints);
+ mDrawingProxy.showGesturePreviewTrail(this);
}
- private void endBatchInput(InputPointers batchPoints) {
- if (DEBUG_LISTENER) {
- Log.d(TAG, "onEndBatchInput: batchPoints=" + batchPoints.getPointerSize());
+ private void endBatchInput() {
+ synchronized (sAggregratedPointers) {
+ mGestureStrokeWithPreviewTrail.appendAllBatchPoints(sAggregratedPointers);
+ if (getActivePointerTrackerCount() == 1) {
+ if (DEBUG_LISTENER) {
+ Log.d(TAG, "onEndBatchInput: batchPoints="
+ + sAggregratedPointers.getPointerSize());
+ }
+ sInGesture = false;
+ mListener.onEndBatchInput(sAggregratedPointers);
+ clearBatchInputPointsOfAllPointerTrackers();
+ }
}
- mListener.onEndBatchInput(batchPoints);
- clearBatchInputRecognitionStateOfThisPointerTracker();
- clearBatchInputPointsOfAllPointerTrackers();
- sWasInGesture = true;
+ mDrawingProxy.showGesturePreviewTrail(this);
}
- private void abortBatchInput() {
- clearBatchInputRecognitionStateOfThisPointerTracker();
+ private static void abortBatchInput() {
clearBatchInputPointsOfAllPointerTrackers();
}
- private void clearBatchInputRecognitionStateOfThisPointerTracker() {
- mIsPossibleGesture = false;
- mInGesture = false;
- mLastRecognitionPointSize = 0;
- mLastRecognitionTime = 0;
- }
-
- private boolean updateBatchInputRecognitionState(long eventTime, int size) {
- if (size > mLastRecognitionPointSize
- && eventTime > mLastRecognitionTime + MIN_GESTURE_RECOGNITION_TIME) {
- mLastRecognitionPointSize = size;
- mLastRecognitionTime = eventTime;
- return true;
+ private static void clearBatchInputPointsOfAllPointerTrackers() {
+ final int trackersSize = sTrackers.size();
+ for (int i = 0; i < trackersSize; ++i) {
+ final PointerTracker tracker = sTrackers.get(i);
+ tracker.mGestureStrokeWithPreviewTrail.reset();
}
- return false;
+ sAggregratedPointers.reset();
+ sLastRecognitionPointSize = 0;
+ sLastRecognitionTime = 0;
}
- public void processMotionEvent(int action, int x, int y, long eventTime,
- KeyEventHandler handler) {
+ public void processMotionEvent(final int action, final int x, final int y, final long eventTime,
+ final KeyEventHandler handler) {
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
@@ -619,9 +621,11 @@
}
}
- public void onDownEvent(int x, int y, long eventTime, KeyEventHandler handler) {
- if (DEBUG_EVENT)
+ public void onDownEvent(final int x, final int y, final long eventTime,
+ final KeyEventHandler handler) {
+ if (DEBUG_EVENT) {
printTouchEvent("onDownEvent:", x, y, eventTime);
+ }
mDrawingProxy = handler.getDrawingProxy();
mTimerProxy = handler.getTimerProxy();
@@ -633,7 +637,7 @@
final int dx = x - mLastX;
final int dy = y - mLastY;
final int distanceSquared = (dx * dx + dy * dy);
- if (distanceSquared < sTouchNoiseThresholdDistanceSquared) {
+ if (distanceSquared < sParams.mTouchNoiseThresholdDistanceSquared) {
if (DEBUG_MODE)
Log.w(TAG, "onDownEvent: ignore potential noise: time=" + deltaT
+ " distance=" + distanceSquared);
@@ -645,8 +649,8 @@
}
}
- final PointerTrackerQueue queue = sPointerTrackerQueue;
final Key key = getKeyOn(x, y);
+ final PointerTrackerQueue queue = sPointerTrackerQueue;
if (queue != null) {
if (key != null && key.isModifier()) {
// Before processing a down event of modifier key, all pointers already being
@@ -656,20 +660,30 @@
queue.add(this);
}
onDownEventInternal(x, y, eventTime);
- if (queue != null && queue.size() == 1) {
- mIsPossibleGesture = false;
+ if (!sShouldHandleGesture) {
+ return;
+ }
+ final int activePointerTrackerCount = getActivePointerTrackerCount();
+ if (activePointerTrackerCount == 1) {
+ mIsDetectingGesture = false;
// A gesture should start only from the letter key.
- if (sShouldHandleGesture && mIsAlphabetKeyboard && !mIsShowingMoreKeysPanel
- && key != null && Keyboard.isLetterCode(key.mCode)) {
- mIsPossibleGesture = true;
- // TODO: pointer times should be relative to first down even in entire batch input
- // instead of resetting to 0 for each new down event.
- mGestureStroke.addPoint(x, y, 0, false);
+ final boolean isAlphabetKeyboard = (mKeyboard != null)
+ && mKeyboard.mId.isAlphabetKeyboard();
+ if (isAlphabetKeyboard && !mIsShowingMoreKeysPanel && key != null
+ && Keyboard.isLetterCode(key.mCode)) {
+ mIsDetectingGesture = true;
+ sGestureFirstDownTime = eventTime;
+ mGestureStrokeWithPreviewTrail.addPoint(x, y, 0, false /* isHistorical */);
}
+ } else if (sInGesture && activePointerTrackerCount > 1) {
+ mIsDetectingGesture = true;
+ final int elapsedTimeFromFirstDown = (int)(eventTime - sGestureFirstDownTime);
+ mGestureStrokeWithPreviewTrail.addPoint(x, y, elapsedTimeFromFirstDown,
+ false /* isHistorical */);
}
}
- private void onDownEventInternal(int x, int y, long eventTime) {
+ private void onDownEventInternal(final int x, final int y, final long eventTime) {
Key key = onDownKey(x, y, eventTime);
// Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding
// from modifier key, or 3) this pointer's KeyDetector always allows sliding input.
@@ -694,40 +708,38 @@
}
}
- private void startSlidingKeyInput(Key key) {
+ private void startSlidingKeyInput(final Key key) {
if (!mIsInSlidingKeyInput) {
mIgnoreModifierKey = key.isModifier();
}
mIsInSlidingKeyInput = true;
}
- private void onGestureMoveEvent(PointerTracker tracker, int x, int y, long eventTime,
- boolean isHistorical, Key key) {
- final int gestureTime = (int)(eventTime - tracker.getDownTime());
- if (sShouldHandleGesture && mIsPossibleGesture) {
- final GestureStroke stroke = mGestureStroke;
+ private void onGestureMoveEvent(final int x, final int y, final long eventTime,
+ final boolean isHistorical, final Key key) {
+ final int gestureTime = (int)(eventTime - sGestureFirstDownTime);
+ if (mIsDetectingGesture) {
+ final GestureStroke stroke = mGestureStrokeWithPreviewTrail;
stroke.addPoint(x, y, gestureTime, isHistorical);
- if (!mInGesture && stroke.isStartOfAGesture(gestureTime, sWasInGesture)) {
+ if (!sInGesture && stroke.isStartOfAGesture()) {
startBatchInput();
}
- }
- if (key != null && mInGesture) {
- final InputPointers batchPoints = getIncrementalBatchPoints();
- mDrawingProxy.showGestureTrail(this);
- if (updateBatchInputRecognitionState(eventTime, batchPoints.getPointerSize())) {
- updateBatchInput(batchPoints);
+ if (sInGesture && key != null) {
+ updateBatchInput(eventTime);
}
}
}
- public void onMoveEvent(int x, int y, long eventTime, MotionEvent me) {
- if (DEBUG_MOVE_EVENT)
+ public void onMoveEvent(final int x, final int y, final long eventTime, final MotionEvent me) {
+ if (DEBUG_MOVE_EVENT) {
printTouchEvent("onMoveEvent:", x, y, eventTime);
- if (mKeyAlreadyProcessed)
+ }
+ if (mKeyAlreadyProcessed) {
return;
+ }
- if (me != null) {
+ if (sShouldHandleGesture && me != null) {
// Add historical points to gesture path.
final int pointerIndex = me.findPointerIndex(mPointerId);
final int historicalSize = me.getHistorySize();
@@ -735,24 +747,31 @@
final int historicalX = (int)me.getHistoricalX(pointerIndex, h);
final int historicalY = (int)me.getHistoricalY(pointerIndex, h);
final long historicalTime = me.getHistoricalEventTime(h);
- onGestureMoveEvent(this, historicalX, historicalY, historicalTime,
+ onGestureMoveEvent(historicalX, historicalY, historicalTime,
true /* isHistorical */, null);
}
}
+ onMoveEventInternal(x, y, eventTime);
+ }
+
+ private void onMoveEventInternal(final int x, final int y, final long eventTime) {
final int lastX = mLastX;
final int lastY = mLastY;
final Key oldKey = mCurrentKey;
Key key = onMoveKey(x, y);
- // Register move event on gesture tracker.
- onGestureMoveEvent(this, x, y, eventTime, false /* isHistorical */, key);
- if (mInGesture) {
- mIgnoreModifierKey = true;
- mTimerProxy.cancelLongPressTimer();
- mIsInSlidingKeyInput = true;
- mCurrentKey = null;
- setReleasedKeyGraphics(oldKey);
+ if (sShouldHandleGesture) {
+ // Register move event on gesture tracker.
+ onGestureMoveEvent(x, y, eventTime, false /* isHistorical */, key);
+ if (sInGesture) {
+ mIgnoreModifierKey = true;
+ mTimerProxy.cancelLongPressTimer();
+ mIsInSlidingKeyInput = true;
+ mCurrentKey = null;
+ setReleasedKeyGraphics(oldKey);
+ return;
+ }
}
if (key != null) {
@@ -797,7 +816,7 @@
// TODO: Should find a way to balance gesture detection and this hack.
if (sNeedsPhantomSuddenMoveEventHack
&& lastMoveSquared >= mKeyQuarterWidthSquared
- && !mIsPossibleGesture) {
+ && !mIsDetectingGesture) {
if (DEBUG_MODE) {
Log.w(TAG, String.format("onMoveEvent:"
+ " phantom sudden move event is translated to "
@@ -815,11 +834,11 @@
// touch panels when there are close multiple touches.
// Caveat: When in chording input mode with a modifier key, we don't use
// this hack.
- if (me != null && me.getPointerCount() > 1
+ if (getActivePointerTrackerCount() > 1 && sPointerTrackerQueue != null
&& !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) {
onUpEventInternal();
}
- if (!mIsPossibleGesture) {
+ if (!mIsDetectingGesture) {
mKeyAlreadyProcessed = true;
}
setReleasedKeyGraphics(oldKey);
@@ -837,7 +856,7 @@
if (mIsAllowedSlidingKeyInput) {
onMoveToNewKey(key, x, y);
} else {
- if (!mIsPossibleGesture) {
+ if (!mIsDetectingGesture) {
mKeyAlreadyProcessed = true;
}
}
@@ -845,13 +864,14 @@
}
}
- public void onUpEvent(int x, int y, long eventTime) {
- if (DEBUG_EVENT)
+ public void onUpEvent(final int x, final int y, final long eventTime) {
+ if (DEBUG_EVENT) {
printTouchEvent("onUpEvent :", x, y, eventTime);
+ }
final PointerTrackerQueue queue = sPointerTrackerQueue;
if (queue != null) {
- if (!mInGesture) {
+ if (!sInGesture) {
if (mCurrentKey != null && mCurrentKey.isModifier()) {
// Before processing an up event of modifier key, all pointers already being
// tracked should be released.
@@ -860,18 +880,21 @@
queue.releaseAllPointersOlderThan(this, eventTime);
}
}
- queue.remove(this);
}
onUpEventInternal();
+ if (queue != null) {
+ queue.remove(this);
+ }
}
// Let this pointer tracker know that one of newer-than-this pointer trackers got an up event.
// This pointer tracker needs to keep the key top graphics "pressed", but needs to get a
// "virtual" up event.
@Override
- public void onPhantomUpEvent(long eventTime) {
- if (DEBUG_EVENT)
+ public void onPhantomUpEvent(final long eventTime) {
+ if (DEBUG_EVENT) {
printTouchEvent("onPhntEvent:", getLastX(), getLastY(), eventTime);
+ }
onUpEventInternal();
mKeyAlreadyProcessed = true;
}
@@ -879,37 +902,35 @@
private void onUpEventInternal() {
mTimerProxy.cancelKeyTimers();
mIsInSlidingKeyInput = false;
- mIsPossibleGesture = false;
+ mIsDetectingGesture = false;
+ final Key currentKey = mCurrentKey;
+ mCurrentKey = null;
// Release the last pressed key.
- setReleasedKeyGraphics(mCurrentKey);
+ setReleasedKeyGraphics(currentKey);
if (mIsShowingMoreKeysPanel) {
mDrawingProxy.dismissMoreKeysPanel();
mIsShowingMoreKeysPanel = false;
}
- if (mInGesture) {
- // Register up event on gesture tracker.
- // TODO: Figure out how to deal with multiple fingers that are in gesture, sliding,
- // and/or tapping mode?
- endBatchInput(getAllBatchPoints());
- if (mCurrentKey != null) {
- callListenerOnRelease(mCurrentKey, mCurrentKey.mCode, true);
- mCurrentKey = null;
+ if (sInGesture) {
+ if (currentKey != null) {
+ callListenerOnRelease(currentKey, currentKey.mCode, true);
}
- mDrawingProxy.showGestureTrail(this);
+ endBatchInput();
return;
}
- // This event will be recognized as a regular code input. Clear unused batch points so they
- // are not mistakenly included in the next batch event.
+ // This event will be recognized as a regular code input. Clear unused possible batch points
+ // so they are not mistakenly displayed as preview.
clearBatchInputPointsOfAllPointerTrackers();
- if (mKeyAlreadyProcessed)
+ if (mKeyAlreadyProcessed) {
return;
- if (mCurrentKey != null && !mCurrentKey.isRepeatable()) {
- detectAndSendKey(mCurrentKey, mKeyX, mKeyY);
+ }
+ if (currentKey != null && !currentKey.isRepeatable()) {
+ detectAndSendKey(currentKey, mKeyX, mKeyY);
}
}
- public void onShowMoreKeysPanel(int x, int y, KeyEventHandler handler) {
+ public void onShowMoreKeysPanel(final int x, final int y, final KeyEventHandler handler) {
abortBatchInput();
onLongPressed();
mIsShowingMoreKeysPanel = true;
@@ -925,9 +946,10 @@
}
}
- public void onCancelEvent(int x, int y, long eventTime) {
- if (DEBUG_EVENT)
+ public void onCancelEvent(final int x, final int y, final long eventTime) {
+ if (DEBUG_EVENT) {
printTouchEvent("onCancelEvt:", x, y, eventTime);
+ }
final PointerTrackerQueue queue = sPointerTrackerQueue;
if (queue != null) {
@@ -947,24 +969,25 @@
}
}
- private void startRepeatKey(Key key) {
- if (key != null && key.isRepeatable() && !mInGesture) {
+ private void startRepeatKey(final Key key) {
+ if (key != null && key.isRepeatable() && !sInGesture) {
onRegisterKey(key);
mTimerProxy.startKeyRepeatTimer(this);
}
}
- public void onRegisterKey(Key key) {
+ public void onRegisterKey(final Key key) {
if (key != null) {
detectAndSendKey(key, key.mX, key.mY);
mTimerProxy.startTypingStateTimer(key);
}
}
- private boolean isMajorEnoughMoveToBeOnNewKey(int x, int y, Key newKey) {
- if (mKeyDetector == null)
+ private boolean isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final Key newKey) {
+ if (mKeyDetector == null) {
throw new NullPointerException("keyboard and/or key detector not set");
- Key curKey = mCurrentKey;
+ }
+ final Key curKey = mCurrentKey;
if (newKey == curKey) {
return false;
} else if (curKey != null) {
@@ -975,25 +998,25 @@
}
}
- private void startLongPressTimer(Key key) {
- if (key != null && key.isLongPressEnabled() && !mInGesture) {
+ private void startLongPressTimer(final Key key) {
+ if (key != null && key.isLongPressEnabled() && !sInGesture) {
mTimerProxy.startLongPressTimer(this);
}
}
- private void detectAndSendKey(Key key, int x, int y) {
+ private void detectAndSendKey(final Key key, final int x, final int y) {
if (key == null) {
callListenerOnCancelInput();
return;
}
- int code = key.mCode;
+ final int code = key.mCode;
callListenerOnCodeInput(key, code, x, y);
callListenerOnRelease(key, code, false);
- sWasInGesture = false;
}
- private void printTouchEvent(String title, int x, int y, long eventTime) {
+ private void printTouchEvent(final String title, final int x, final int y,
+ final long eventTime) {
final Key key = mKeyDetector.detectHitKey(x, y);
final String code = KeyDetector.printableCode(key);
Log.d(TAG, String.format("%s%s[%d] %4d %4d %5d %s", title,
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index ae123e2..71bf31f 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -18,9 +18,9 @@
import android.graphics.Rect;
import android.text.TextUtils;
-import android.util.FloatMath;
import com.android.inputmethod.keyboard.Keyboard.Params.TouchPositionCorrection;
+import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.JniUtils;
import java.util.Arrays;
@@ -112,7 +112,7 @@
final Key[] keys = mKeys;
final TouchPositionCorrection touchPositionCorrection = mTouchPositionCorrection;
final int[] proximityCharsArray = new int[mGridSize * MAX_PROXIMITY_CHARS_SIZE];
- Arrays.fill(proximityCharsArray, KeyDetector.NOT_A_CODE);
+ Arrays.fill(proximityCharsArray, Constants.NOT_A_CODE);
for (int i = 0; i < mGridSize; ++i) {
final int proximityCharsLength = gridNeighborKeys[i].length;
for (int j = 0; j < proximityCharsLength; ++j) {
@@ -155,7 +155,9 @@
final float radius = touchPositionCorrection.mRadii[row];
sweetSpotCenterXs[i] = hitBox.exactCenterX() + x * hitBoxWidth;
sweetSpotCenterYs[i] = hitBox.exactCenterY() + y * hitBoxHeight;
- sweetSpotRadii[i] = radius * FloatMath.sqrt(
+ // Note that, in recent versions of Android, FloatMath is actually slower than
+ // java.lang.Math due to the way the JIT optimizes java.lang.Math.
+ sweetSpotRadii[i] = radius * (float)Math.sqrt(
hitBoxWidth * hitBoxWidth + hitBoxHeight * hitBoxHeight);
}
}
@@ -233,7 +235,7 @@
dest[index++] = code;
}
if (index < destLength) {
- dest[index] = KeyDetector.NOT_A_CODE;
+ dest[index] = Constants.NOT_A_CODE;
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
new file mode 100644
index 0000000..e814d80
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.SystemClock;
+
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.ResizableIntArray;
+
+class GesturePreviewTrail {
+ private static final int DEFAULT_CAPACITY = GestureStrokeWithPreviewTrail.PREVIEW_CAPACITY;
+
+ private final GesturePreviewTrailParams mPreviewParams;
+ private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
+ private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
+ private final ResizableIntArray mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY);
+ private int mCurrentStrokeId = -1;
+ private long mCurrentDownTime;
+ private int mTrailStartIndex;
+
+ // Use this value as imaginary zero because x-coordinates may be zero.
+ private static final int DOWN_EVENT_MARKER = -128;
+
+ static class GesturePreviewTrailParams {
+ public final int mFadeoutStartDelay;
+ public final int mFadeoutDuration;
+ public final int mUpdateInterval;
+
+ public GesturePreviewTrailParams(final TypedArray keyboardViewAttr) {
+ mFadeoutStartDelay = keyboardViewAttr.getInt(
+ R.styleable.KeyboardView_gesturePreviewTrailFadeoutStartDelay, 0);
+ mFadeoutDuration = keyboardViewAttr.getInt(
+ R.styleable.KeyboardView_gesturePreviewTrailFadeoutDuration, 0);
+ mUpdateInterval = keyboardViewAttr.getInt(
+ R.styleable.KeyboardView_gesturePreviewTrailUpdateInterval, 0);
+ }
+ }
+
+ public GesturePreviewTrail(final GesturePreviewTrailParams params) {
+ mPreviewParams = params;
+ }
+
+ private static int markAsDownEvent(final int xCoord) {
+ return DOWN_EVENT_MARKER - xCoord;
+ }
+
+ private static boolean isDownEventXCoord(final int xCoordOrMark) {
+ return xCoordOrMark <= DOWN_EVENT_MARKER;
+ }
+
+ private static int getXCoordValue(final int xCoordOrMark) {
+ return isDownEventXCoord(xCoordOrMark)
+ ? DOWN_EVENT_MARKER - xCoordOrMark : xCoordOrMark;
+ }
+
+ public void addStroke(final GestureStrokeWithPreviewTrail stroke, final long downTime) {
+ final int strokeId = stroke.getGestureStrokeId();
+ final boolean isNewStroke = strokeId != mCurrentStrokeId;
+ final int trailSize = mEventTimes.getLength();
+ stroke.appendPreviewStroke(mEventTimes, mXCoordinates, mYCoordinates);
+ final int newTrailSize = mEventTimes.getLength();
+ if (stroke.getGestureStrokePreviewSize() == 0) {
+ return;
+ }
+ if (isNewStroke) {
+ final int elapsedTime = (int)(downTime - mCurrentDownTime);
+ final int[] eventTimes = mEventTimes.getPrimitiveArray();
+ for (int i = mTrailStartIndex; i < trailSize; i++) {
+ eventTimes[i] -= elapsedTime;
+ }
+
+ if (newTrailSize > trailSize) {
+ final int[] xCoords = mXCoordinates.getPrimitiveArray();
+ xCoords[trailSize] = markAsDownEvent(xCoords[trailSize]);
+ }
+ mCurrentDownTime = downTime;
+ mCurrentStrokeId = strokeId;
+ }
+ }
+
+ private int getAlpha(final int elapsedTime) {
+ if (elapsedTime < mPreviewParams.mFadeoutStartDelay) {
+ return Constants.Color.ALPHA_OPAQUE;
+ }
+ final int decreasingAlpha = Constants.Color.ALPHA_OPAQUE
+ * (elapsedTime - mPreviewParams.mFadeoutStartDelay)
+ / mPreviewParams.mFadeoutDuration;
+ return Constants.Color.ALPHA_OPAQUE - decreasingAlpha;
+ }
+
+ /**
+ * Draw gesture preview trail
+ * @param canvas The canvas to draw the gesture preview trail
+ * @param paint The paint object to be used to draw the gesture preview trail
+ * @return true if some gesture preview trails remain to be drawn
+ */
+ public boolean drawGestureTrail(final Canvas canvas, final Paint paint) {
+ final int trailSize = mEventTimes.getLength();
+ if (trailSize == 0) {
+ return false;
+ }
+
+ final int[] eventTimes = mEventTimes.getPrimitiveArray();
+ final int[] xCoords = mXCoordinates.getPrimitiveArray();
+ final int[] yCoords = mYCoordinates.getPrimitiveArray();
+ final int sinceDown = (int)(SystemClock.uptimeMillis() - mCurrentDownTime);
+ final int lingeringDuration = mPreviewParams.mFadeoutStartDelay
+ + mPreviewParams.mFadeoutDuration;
+ int startIndex;
+ for (startIndex = mTrailStartIndex; startIndex < trailSize; startIndex++) {
+ final int elapsedTime = sinceDown - eventTimes[startIndex];
+ // Skip too old trail points.
+ if (elapsedTime < lingeringDuration) {
+ break;
+ }
+ }
+ mTrailStartIndex = startIndex;
+
+ if (startIndex < trailSize) {
+ int lastX = getXCoordValue(xCoords[startIndex]);
+ int lastY = yCoords[startIndex];
+ for (int i = startIndex + 1; i < trailSize - 1; i++) {
+ final int x = xCoords[i];
+ final int y = yCoords[i];
+ final int elapsedTime = sinceDown - eventTimes[i];
+ // Draw trail line only when the current point isn't a down point.
+ if (!isDownEventXCoord(x)) {
+ paint.setAlpha(getAlpha(elapsedTime));
+ canvas.drawLine(lastX, lastY, x, y, paint);
+ }
+ lastX = getXCoordValue(x);
+ lastY = y;
+ }
+ }
+
+ final int newSize = trailSize - startIndex;
+ if (newSize < startIndex) {
+ mTrailStartIndex = 0;
+ if (newSize > 0) {
+ System.arraycopy(eventTimes, startIndex, eventTimes, 0, newSize);
+ System.arraycopy(xCoords, startIndex, xCoords, 0, newSize);
+ System.arraycopy(yCoords, startIndex, yCoords, 0, newSize);
+ }
+ mEventTimes.setLength(newSize);
+ mXCoordinates.setLength(newSize);
+ mYCoordinates.setLength(newSize);
+ }
+ return newSize > 0;
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
index 28d6c1d..8251344 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
@@ -14,11 +14,6 @@
package com.android.inputmethod.keyboard.internal;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.util.FloatMath;
-
-import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.InputPointers;
import com.android.inputmethod.latin.ResizableIntArray;
@@ -38,44 +33,30 @@
private int mLastPointY;
private int mMinGestureLength;
- private int mMinGestureLengthWhileInGesture;
private int mMinGestureSampleLength;
// TODO: Move some of these to resource.
- private static final float MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH = 1.0f;
- private static final float MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH_WHILE_IN_GESTURE = 0.5f;
- private static final int MIN_GESTURE_DURATION = 150; // msec
- private static final int MIN_GESTURE_DURATION_WHILE_IN_GESTURE = 75; // msec
- private static final float MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT = 1.0f / 6.0f;
+ private static final float MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH = 0.75f;
+ private static final int MIN_GESTURE_DURATION = 100; // msec
+ private static final float MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH = 1.0f / 6.0f;
private static final float GESTURE_RECOG_SPEED_THRESHOLD = 0.4f; // dip/msec
private static final float GESTURE_RECOG_CURVATURE_THRESHOLD = (float)(Math.PI / 4.0f);
- private static final float DOUBLE_PI = (float)(2 * Math.PI);
+ private static final float DOUBLE_PI = (float)(2.0f * Math.PI);
- // Fade based on number of gesture samples, see MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT
- private static final int DRAWING_GESTURE_FADE_START = 10;
- private static final int DRAWING_GESTURE_FADE_RATE = 6;
-
- public GestureStroke(int pointerId) {
+ public GestureStroke(final int pointerId) {
mPointerId = pointerId;
- reset();
}
- public void setGestureSampleLength(final int keyWidth, final int keyHeight) {
+ public void setGestureSampleLength(final int keyWidth) {
// TODO: Find an appropriate base metric for these length. Maybe diagonal length of the key?
mMinGestureLength = (int)(keyWidth * MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH);
- mMinGestureLengthWhileInGesture = (int)(
- keyWidth * MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH_WHILE_IN_GESTURE);
- mMinGestureSampleLength = (int)(keyHeight * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT);
+ mMinGestureSampleLength = (int)(keyWidth * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH);
}
- public boolean isStartOfAGesture(final int downDuration, final boolean wasInGesture) {
- // The tolerance of the time duration and the stroke length to detect the start of a
- // gesture stroke should be eased when the previous input was a gesture input.
- if (wasInGesture) {
- return downDuration > MIN_GESTURE_DURATION_WHILE_IN_GESTURE
- && mLength > mMinGestureLengthWhileInGesture;
- }
+ public boolean isStartOfAGesture() {
+ final int size = mEventTimes.getLength();
+ final int downDuration = (size > 0) ? mEventTimes.get(size - 1) : 0;
return downDuration > MIN_GESTURE_DURATION && mLength > mMinGestureLength;
}
@@ -149,23 +130,29 @@
}
private void appendBatchPoints(final InputPointers out, final int size) {
+ final int length = size - mLastIncrementalBatchSize;
+ if (length <= 0) {
+ return;
+ }
out.append(mPointerId, mEventTimes, mXCoordinates, mYCoordinates,
- mLastIncrementalBatchSize, size - mLastIncrementalBatchSize);
+ mLastIncrementalBatchSize, length);
mLastIncrementalBatchSize = size;
}
- private static float getDistance(final int p1x, final int p1y,
- final int p2x, final int p2y) {
- final float dx = p1x - p2x;
- final float dy = p1y - p2y;
- // TODO: Optimize out this {@link FloatMath#sqrt(float)} call.
- return FloatMath.sqrt(dx * dx + dy * dy);
+ private static float getDistance(final int x1, final int y1, final int x2, final int y2) {
+ final float dx = x1 - x2;
+ final float dy = y1 - y2;
+ // Note that, in recent versions of Android, FloatMath is actually slower than
+ // java.lang.Math due to the way the JIT optimizes java.lang.Math.
+ return (float)Math.sqrt(dx * dx + dy * dy);
}
- private static float getAngle(final int p1x, final int p1y, final int p2x, final int p2y) {
- final int dx = p1x - p2x;
- final int dy = p1y - p2y;
+ private static float getAngle(final int x1, final int y1, final int x2, final int y2) {
+ final int dx = x1 - x2;
+ final int dy = y1 - y2;
if (dx == 0 && dy == 0) return 0;
+ // Would it be faster to call atan2f() directly via JNI? Not sure about what the JIT
+ // does with Math.atan2().
return (float)Math.atan2(dy, dx);
}
@@ -176,23 +163,4 @@
}
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);
- }
- }
- }
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewTrail.java
new file mode 100644
index 0000000..6c1a9bc
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewTrail.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import com.android.inputmethod.latin.ResizableIntArray;
+
+public class GestureStrokeWithPreviewTrail extends GestureStroke {
+ public static final int PREVIEW_CAPACITY = 256;
+
+ private final ResizableIntArray mPreviewEventTimes = new ResizableIntArray(PREVIEW_CAPACITY);
+ private final ResizableIntArray mPreviewXCoordinates = new ResizableIntArray(PREVIEW_CAPACITY);
+ private final ResizableIntArray mPreviewYCoordinates = new ResizableIntArray(PREVIEW_CAPACITY);
+
+ private int mStrokeId;
+ private int mLastPreviewSize;
+
+ public GestureStrokeWithPreviewTrail(final int pointerId) {
+ super(pointerId);
+ }
+
+ @Override
+ public void reset() {
+ super.reset();
+ mStrokeId++;
+ mLastPreviewSize = 0;
+ mPreviewEventTimes.setLength(0);
+ mPreviewXCoordinates.setLength(0);
+ mPreviewYCoordinates.setLength(0);
+ }
+
+ public int getGestureStrokeId() {
+ return mStrokeId;
+ }
+
+ public int getGestureStrokePreviewSize() {
+ return mPreviewEventTimes.getLength();
+ }
+
+ @Override
+ public void addPoint(final int x, final int y, final int time, final boolean isHistorical) {
+ super.addPoint(x, y, time, isHistorical);
+ mPreviewEventTimes.add(time);
+ mPreviewXCoordinates.add(x);
+ mPreviewYCoordinates.add(y);
+ }
+
+ public void appendPreviewStroke(final ResizableIntArray eventTimes,
+ final ResizableIntArray xCoords, final ResizableIntArray yCoords) {
+ final int length = mPreviewEventTimes.getLength() - mLastPreviewSize;
+ if (length <= 0) {
+ return;
+ }
+ eventTimes.append(mPreviewEventTimes, mLastPreviewSize, length);
+ xCoords.append(mPreviewXCoordinates, mLastPreviewSize, length);
+ yCoords.append(mPreviewYCoordinates, mLastPreviewSize, length);
+ mLastPreviewSize = mPreviewEventTimes.getLength();
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
index 94a7b82..13214bb 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
@@ -21,6 +21,7 @@
import android.text.TextUtils;
import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.StringUtils;
@@ -258,7 +259,7 @@
throw new IllegalArgumentException();
}
- final ArrayList<T> list = new ArrayList<T>(end - start);
+ final ArrayList<T> list = CollectionUtils.newArrayList(end - start);
for (int i = start; i < end; i++) {
list.add(array[i]);
}
@@ -438,7 +439,7 @@
// Skip empty entry.
if (pos - start > 0) {
if (list == null) {
- list = new ArrayList<String>();
+ list = CollectionUtils.newArrayList();
}
list.add(text.substring(start, pos));
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
index 291b3b9..e40cf45 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
@@ -21,6 +21,7 @@
import android.util.SparseArray;
import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.XmlParseUtils;
@@ -33,7 +34,7 @@
private static final String TAG = KeyStyles.class.getSimpleName();
private static final boolean DEBUG = false;
- final HashMap<String, KeyStyle> mStyles = new HashMap<String, KeyStyle>();
+ final HashMap<String, KeyStyle> mStyles = CollectionUtils.newHashMap();
final KeyboardTextsSet mTextsSet;
private final KeyStyle mEmptyKeyStyle;
@@ -90,7 +91,7 @@
private class DeclaredKeyStyle extends KeyStyle {
private final String mParentStyleName;
- private final SparseArray<Object> mStyleAttributes = new SparseArray<Object>();
+ private final SparseArray<Object> mStyleAttributes = CollectionUtils.newSparseArray();
public DeclaredKeyStyle(String parentStyleName) {
mParentStyleName = parentStyleName;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
index f7981a3..f7923d0 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
@@ -17,13 +17,13 @@
package com.android.inputmethod.keyboard.internal;
import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.CollectionUtils;
import java.util.HashMap;
public class KeyboardCodesSet {
- private static final HashMap<String, int[]> sLanguageToCodesMap =
- new HashMap<String, int[]>();
- private static final HashMap<String, Integer> sNameToIdMap = new HashMap<String, Integer>();
+ private static final HashMap<String, int[]> sLanguageToCodesMap = CollectionUtils.newHashMap();
+ private static final HashMap<String, Integer> sNameToIdMap = CollectionUtils.newHashMap();
private int[] mCodes = DEFAULT;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
index 5155851..4a98a36 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
@@ -22,6 +22,7 @@
import android.util.Log;
import android.util.SparseIntArray;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.R;
import java.util.HashMap;
@@ -35,7 +36,7 @@
private static final SparseIntArray ATTR_ID_TO_ICON_ID = new SparseIntArray();
// Icon name to icon id map.
- private static final HashMap<String, Integer> sNameToIdsMap = new HashMap<String, Integer>();
+ private static final HashMap<String, Integer> sNameToIdsMap = CollectionUtils.newHashMap();
private static final Object[] NAMES_AND_ATTR_IDS = {
"undefined", ATTR_UNDEFINED,
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
index bec0f1f..a608cde 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.res.Resources;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.R;
import java.util.HashMap;
@@ -45,14 +46,12 @@
*/
public final class KeyboardTextsSet {
// Language to texts map.
- private static final HashMap<String, String[]> sLocaleToTextsMap =
- new HashMap<String, String[]>();
- private static final HashMap<String, Integer> sNameToIdsMap =
- new HashMap<String, Integer>();
+ private static final HashMap<String, String[]> sLocaleToTextsMap = CollectionUtils.newHashMap();
+ private static final HashMap<String, Integer> sNameToIdsMap = CollectionUtils.newHashMap();
private String[] mTexts;
// Resource name to text map.
- private HashMap<String, String> mResourceNameToTextsMap = new HashMap<String, String>();
+ private HashMap<String, String> mResourceNameToTextsMap = CollectionUtils.newHashMap();
public void setLanguage(final String language) {
mTexts = sLocaleToTextsMap.get(language);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
index bd16480..e0858c0 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
@@ -18,85 +18,148 @@
import android.util.Log;
-import java.util.Iterator;
-import java.util.LinkedList;
+import com.android.inputmethod.latin.CollectionUtils;
+
+import java.util.ArrayList;
public class PointerTrackerQueue {
private static final String TAG = PointerTrackerQueue.class.getSimpleName();
private static final boolean DEBUG = false;
- public interface ElementActions {
+ public interface Element {
public boolean isModifier();
public boolean isInSlidingKeyInput();
public void onPhantomUpEvent(long eventTime);
}
- // TODO: Use ring buffer instead of {@link LinkedList}.
- private final LinkedList<ElementActions> mQueue = new LinkedList<ElementActions>();
+ private static final int INITIAL_CAPACITY = 10;
+ private final ArrayList<Element> mExpandableArrayOfActivePointers =
+ CollectionUtils.newArrayList(INITIAL_CAPACITY);
+ private int mArraySize = 0;
- public int size() {
- return mQueue.size();
+ public synchronized int size() {
+ return mArraySize;
}
- public synchronized void add(ElementActions tracker) {
- mQueue.add(tracker);
+ public synchronized void add(final Element pointer) {
+ final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
+ final int arraySize = mArraySize;
+ if (arraySize < expandableArray.size()) {
+ expandableArray.set(arraySize, pointer);
+ } else {
+ expandableArray.add(pointer);
+ }
+ mArraySize = arraySize + 1;
}
- public synchronized void remove(ElementActions tracker) {
- mQueue.remove(tracker);
+ public synchronized void remove(final Element pointer) {
+ final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
+ final int arraySize = mArraySize;
+ int newSize = 0;
+ for (int index = 0; index < arraySize; index++) {
+ final Element element = expandableArray.get(index);
+ if (element == pointer) {
+ if (newSize != index) {
+ Log.w(TAG, "Found duplicated element in remove: " + pointer);
+ }
+ continue; // Remove this element from the expandableArray.
+ }
+ if (newSize != index) {
+ // Shift this element toward the beginning of the expandableArray.
+ expandableArray.set(newSize, element);
+ }
+ newSize++;
+ }
+ mArraySize = newSize;
}
- public synchronized void releaseAllPointersOlderThan(ElementActions tracker,
- long eventTime) {
+ public synchronized void releaseAllPointersOlderThan(final Element pointer,
+ final long eventTime) {
if (DEBUG) {
- Log.d(TAG, "releaseAllPoniterOlderThan: " + tracker + " " + this);
+ Log.d(TAG, "releaseAllPoniterOlderThan: " + pointer + " " + this);
}
- if (!mQueue.contains(tracker)) {
- return;
- }
- final Iterator<ElementActions> it = mQueue.iterator();
- while (it.hasNext()) {
- final ElementActions t = it.next();
- if (t == tracker) {
- break;
+ final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
+ final int arraySize = mArraySize;
+ int newSize, index;
+ for (newSize = index = 0; index < arraySize; index++) {
+ final Element element = expandableArray.get(index);
+ if (element == pointer) {
+ break; // Stop releasing elements.
}
- if (!t.isModifier()) {
- t.onPhantomUpEvent(eventTime);
- it.remove();
+ if (!element.isModifier()) {
+ 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++;
+ }
+ // Shift rest of the expandableArray.
+ int count = 0;
+ for (; index < arraySize; index++) {
+ final Element element = expandableArray.get(index);
+ if (element == pointer) {
+ if (count > 0) {
+ Log.w(TAG, "Found duplicated element in releaseAllPointersOlderThan: "
+ + pointer);
+ }
+ count++;
+ }
+ if (newSize != index) {
+ expandableArray.set(newSize, expandableArray.get(index));
+ newSize++;
}
}
+ mArraySize = newSize;
}
- public void releaseAllPointers(long eventTime) {
+ public void releaseAllPointers(final long eventTime) {
releaseAllPointersExcept(null, eventTime);
}
- public synchronized void releaseAllPointersExcept(ElementActions tracker, long eventTime) {
+ public synchronized void releaseAllPointersExcept(final Element pointer,
+ final long eventTime) {
if (DEBUG) {
- if (tracker == null) {
+ if (pointer == null) {
Log.d(TAG, "releaseAllPoniters: " + this);
} else {
- Log.d(TAG, "releaseAllPoniterExcept: " + tracker + " " + this);
+ Log.d(TAG, "releaseAllPoniterExcept: " + pointer + " " + this);
}
}
- final Iterator<ElementActions> it = mQueue.iterator();
- while (it.hasNext()) {
- final ElementActions t = it.next();
- if (t != tracker) {
- t.onPhantomUpEvent(eventTime);
- it.remove();
+ final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
+ final int arraySize = mArraySize;
+ int newSize = 0, count = 0;
+ for (int index = 0; index < arraySize; index++) {
+ final Element element = expandableArray.get(index);
+ if (element == pointer) {
+ if (count > 0) {
+ Log.w(TAG, "Found duplicated element in releaseAllPointersExcept: " + pointer);
+ }
+ count++;
+ } else {
+ element.onPhantomUpEvent(eventTime);
+ continue; // Remove this element from the expandableArray.
}
+ if (newSize != index) {
+ // Shift this element toward the beginning of the expandableArray.
+ expandableArray.set(newSize, element);
+ }
+ newSize++;
}
+ mArraySize = newSize;
}
- public synchronized boolean hasModifierKeyOlderThan(ElementActions tracker) {
- final Iterator<ElementActions> it = mQueue.iterator();
- while (it.hasNext()) {
- final ElementActions t = it.next();
- if (t == tracker) {
- break;
+ public synchronized boolean hasModifierKeyOlderThan(final Element pointer) {
+ final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
+ final int arraySize = mArraySize;
+ for (int index = 0; index < arraySize; index++) {
+ final Element element = expandableArray.get(index);
+ if (element == pointer) {
+ return false; // Stop searching modifier key.
}
- if (t.isModifier()) {
+ if (element.isModifier()) {
return true;
}
}
@@ -104,8 +167,11 @@
}
public synchronized boolean isAnyInSlidingKeyInput() {
- for (final ElementActions tracker : mQueue) {
- if (tracker.isInSlidingKeyInput()) {
+ final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
+ final int arraySize = mArraySize;
+ for (int index = 0; index < arraySize; index++) {
+ final Element element = expandableArray.get(index);
+ if (element.isInSlidingKeyInput()) {
return true;
}
}
@@ -113,12 +179,15 @@
}
@Override
- public String toString() {
+ public synchronized String toString() {
final StringBuilder sb = new StringBuilder();
- for (final ElementActions tracker : mQueue) {
+ final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
+ final int arraySize = mArraySize;
+ for (int index = 0; index < arraySize; index++) {
+ final Element element = expandableArray.get(index);
if (sb.length() > 0)
sb.append(" ");
- sb.append(tracker.toString());
+ sb.append(element.toString());
}
return "[" + sb.toString() + "]";
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
index d0fecf0..269b202 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
@@ -23,10 +23,13 @@
import android.graphics.Paint.Align;
import android.os.Message;
import android.text.TextUtils;
+import android.util.AttributeSet;
import android.util.SparseArray;
import android.widget.RelativeLayout;
import com.android.inputmethod.keyboard.PointerTracker;
+import com.android.inputmethod.keyboard.internal.GesturePreviewTrail.GesturePreviewTrailParams;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
@@ -46,29 +49,42 @@
private int mXOrigin;
private int mYOrigin;
- private final SparseArray<PointerTracker> mPointers = new SparseArray<PointerTracker>();
+ private final SparseArray<GesturePreviewTrail> mGesturePreviewTrails =
+ CollectionUtils.newSparseArray();
+ private final GesturePreviewTrailParams mGesturePreviewTrailParams;
private String mGestureFloatingPreviewText;
+ private int mLastPointerX;
+ private int mLastPointerY;
+
private boolean mDrawsGesturePreviewTrail;
private boolean mDrawsGestureFloatingPreviewText;
- private final DrawingHandler mDrawingHandler = new DrawingHandler(this);
+ private final DrawingHandler mDrawingHandler;
private static class DrawingHandler extends StaticInnerHandlerWrapper<PreviewPlacerView> {
private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 0;
+ private static final int MSG_UPDATE_GESTURE_PREVIEW_TRAIL = 1;
- public DrawingHandler(PreviewPlacerView outerInstance) {
+ private final GesturePreviewTrailParams mGesturePreviewTrailParams;
+
+ public DrawingHandler(final PreviewPlacerView outerInstance,
+ final GesturePreviewTrailParams gesturePreviewTrailParams) {
super(outerInstance);
+ mGesturePreviewTrailParams = gesturePreviewTrailParams;
}
@Override
- public void handleMessage(Message msg) {
+ public void handleMessage(final Message msg) {
final PreviewPlacerView placerView = getOuterInstance();
if (placerView == null) return;
switch (msg.what) {
case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT:
placerView.setGestureFloatingPreviewText(null);
break;
+ case MSG_UPDATE_GESTURE_PREVIEW_TRAIL:
+ placerView.invalidate();
+ break;
}
}
@@ -84,15 +100,32 @@
placerView.mGestureFloatingPreviewTextLingerTimeout);
}
+ private void cancelUpdateGestureTrailPreview() {
+ removeMessages(MSG_UPDATE_GESTURE_PREVIEW_TRAIL);
+ }
+
+ public void postUpdateGestureTrailPreview() {
+ cancelUpdateGestureTrailPreview();
+ sendMessageDelayed(obtainMessage(MSG_UPDATE_GESTURE_PREVIEW_TRAIL),
+ mGesturePreviewTrailParams.mUpdateInterval);
+ }
+
public void cancelAllMessages() {
cancelDismissGestureFloatingPreviewText();
+ cancelUpdateGestureTrailPreview();
}
}
- public PreviewPlacerView(Context context, TypedArray keyboardViewAttr) {
+ public PreviewPlacerView(final Context context, final AttributeSet attrs) {
+ this(context, attrs, R.attr.keyboardViewStyle);
+ }
+
+ public PreviewPlacerView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context);
setWillNotDraw(false);
+ final TypedArray keyboardViewAttr = context.obtainStyledAttributes(
+ attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
final int gestureFloatingPreviewTextSize = keyboardViewAttr.getDimensionPixelSize(
R.styleable.KeyboardView_gestureFloatingPreviewTextSize, 0);
mGestureFloatingPreviewTextColor = keyboardViewAttr.getColor(
@@ -117,6 +150,10 @@
R.styleable.KeyboardView_gesturePreviewTrailColor, 0);
final int gesturePreviewTrailWidth = keyboardViewAttr.getDimensionPixelSize(
R.styleable.KeyboardView_gesturePreviewTrailWidth, 0);
+ mGesturePreviewTrailParams = new GesturePreviewTrailParams(keyboardViewAttr);
+ keyboardViewAttr.recycle();
+
+ mDrawingHandler = new DrawingHandler(this, mGesturePreviewTrailParams);
mGesturePaint = new Paint();
mGesturePaint.setAntiAlias(true);
@@ -132,48 +169,60 @@
mTextPaint.setTextSize(gestureFloatingPreviewTextSize);
}
- public void setOrigin(int x, int y) {
+ public void setOrigin(final int x, final int y) {
mXOrigin = x;
mYOrigin = y;
}
- public void setGesturePreviewMode(boolean drawsGesturePreviewTrail,
- boolean drawsGestureFloatingPreviewText) {
+ public void setGesturePreviewMode(final boolean drawsGesturePreviewTrail,
+ final boolean drawsGestureFloatingPreviewText) {
mDrawsGesturePreviewTrail = drawsGesturePreviewTrail;
mDrawsGestureFloatingPreviewText = drawsGestureFloatingPreviewText;
}
- public void invalidatePointer(PointerTracker tracker) {
- synchronized (mPointers) {
- mPointers.put(tracker.mPointerId, tracker);
- // TODO: Should narrow the invalidate region.
- invalidate();
+ public void invalidatePointer(final PointerTracker tracker) {
+ GesturePreviewTrail trail;
+ synchronized (mGesturePreviewTrails) {
+ trail = mGesturePreviewTrails.get(tracker.mPointerId);
+ if (trail == null) {
+ trail = new GesturePreviewTrail(mGesturePreviewTrailParams);
+ mGesturePreviewTrails.put(tracker.mPointerId, trail);
+ }
}
+ trail.addStroke(tracker.getGestureStrokeWithPreviewTrail(), tracker.getDownTime());
+
+ mLastPointerX = tracker.getLastX();
+ mLastPointerY = tracker.getLastY();
+ // TODO: Should narrow the invalidate region.
+ invalidate();
}
@Override
- public void onDraw(Canvas canvas) {
+ public void onDraw(final Canvas canvas) {
super.onDraw(canvas);
- synchronized (mPointers) {
- canvas.translate(mXOrigin, mYOrigin);
- final int trackerCount = mPointers.size();
- boolean hasDrawnFloatingPreviewText = false;
- for (int index = 0; index < trackerCount; index++) {
- final PointerTracker tracker = mPointers.valueAt(index);
- if (mDrawsGesturePreviewTrail) {
- tracker.drawGestureTrail(canvas, mGesturePaint);
- }
- // TODO: Figure out more cleaner way to draw gesture preview text.
- if (mDrawsGestureFloatingPreviewText && !hasDrawnFloatingPreviewText) {
- drawGestureFloatingPreviewText(canvas, tracker, mGestureFloatingPreviewText);
- hasDrawnFloatingPreviewText = true;
+ canvas.translate(mXOrigin, mYOrigin);
+ if (mDrawsGesturePreviewTrail) {
+ boolean needsUpdatingGesturePreviewTrail = false;
+ synchronized (mGesturePreviewTrails) {
+ // Trails count == fingers count that have ever been active.
+ final int trailsCount = mGesturePreviewTrails.size();
+ for (int index = 0; index < trailsCount; index++) {
+ final GesturePreviewTrail trail = mGesturePreviewTrails.valueAt(index);
+ needsUpdatingGesturePreviewTrail |=
+ trail.drawGestureTrail(canvas, mGesturePaint);
}
}
- canvas.translate(-mXOrigin, -mYOrigin);
+ if (needsUpdatingGesturePreviewTrail) {
+ mDrawingHandler.postUpdateGestureTrailPreview();
+ }
}
+ if (mDrawsGestureFloatingPreviewText) {
+ drawGestureFloatingPreviewText(canvas, mGestureFloatingPreviewText);
+ }
+ canvas.translate(-mXOrigin, -mYOrigin);
}
- public void setGestureFloatingPreviewText(String gestureFloatingPreviewText) {
+ public void setGestureFloatingPreviewText(final String gestureFloatingPreviewText) {
mGestureFloatingPreviewText = gestureFloatingPreviewText;
invalidate();
}
@@ -186,15 +235,17 @@
mDrawingHandler.cancelAllMessages();
}
- private void drawGestureFloatingPreviewText(Canvas canvas, PointerTracker tracker,
- String gestureFloatingPreviewText) {
+ private void drawGestureFloatingPreviewText(final Canvas canvas,
+ final String gestureFloatingPreviewText) {
if (TextUtils.isEmpty(gestureFloatingPreviewText)) {
return;
}
final Paint paint = mTextPaint;
- final int lastX = tracker.getLastX();
- final int lastY = tracker.getLastY();
+ // TODO: Figure out how we should deal with the floating preview text with multiple moving
+ // fingers.
+ final int lastX = mLastPointerX;
+ final int lastY = mLastPointerY;
final int textSize = (int)paint.getTextSize();
final int canvasWidth = canvas.getWidth();
diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
index f8f1395..4b47a26 100644
--- a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
+++ b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
@@ -91,7 +91,7 @@
}
final String[] prefSubtypeArray = prefSubtypes.split(PREF_SUBTYPE_SEPARATOR);
final ArrayList<InputMethodSubtype> subtypesList =
- new ArrayList<InputMethodSubtype>(prefSubtypeArray.length);
+ CollectionUtils.newArrayList(prefSubtypeArray.length);
for (final String prefSubtype : prefSubtypeArray) {
final InputMethodSubtype subtype = createAdditionalSubtype(prefSubtype);
if (subtype.getNameResId() == SubtypeLocale.UNKNOWN_KEYBOARD_LAYOUT) {
diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
index 779a388..d01592a 100644
--- a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
+++ b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
@@ -89,7 +89,7 @@
super(context, android.R.layout.simple_spinner_item);
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- final TreeSet<SubtypeLocaleItem> items = new TreeSet<SubtypeLocaleItem>();
+ final TreeSet<SubtypeLocaleItem> items = CollectionUtils.newTreeSet();
final InputMethodInfo imi = ImfUtils.getInputMethodInfoOfThisIme(context);
final int count = imi.getSubtypeCount();
for (int i = 0; i < count; i++) {
@@ -533,7 +533,7 @@
private InputMethodSubtype[] getSubtypes() {
final PreferenceGroup group = getPreferenceScreen();
- final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
+ final ArrayList<InputMethodSubtype> subtypes = CollectionUtils.newArrayList();
final int count = group.getPreferenceCount();
for (int i = 0; i < count; i++) {
final Preference pref = group.getPreference(i);
diff --git a/java/src/com/android/inputmethod/latin/AutoCorrection.java b/java/src/com/android/inputmethod/latin/AutoCorrection.java
index a663374..01ba300 100644
--- a/java/src/com/android/inputmethod/latin/AutoCorrection.java
+++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java
@@ -39,7 +39,6 @@
}
final CharSequence lowerCasedWord = word.toString().toLowerCase();
for (final String key : dictionaries.keySet()) {
- if (key.equals(Dictionary.TYPE_WHITELIST)) continue;
final Dictionary dictionary = dictionaries.get(key);
// It's unclear how realistically 'dictionary' can be null, but the monkey is somehow
// managing to get null in here. Presumably the language is changing to a language with
@@ -64,7 +63,6 @@
}
int maxFreq = -1;
for (final String key : dictionaries.keySet()) {
- if (key.equals(Dictionary.TYPE_WHITELIST)) continue;
final Dictionary dictionary = dictionaries.get(key);
if (null == dictionary) continue;
final int tempFreq = dictionary.getFrequency(word);
@@ -75,17 +73,10 @@
return maxFreq;
}
- // Returns true if this is a whitelist entry, or it isn't in any dictionary.
- public static boolean isWhitelistedOrNotAWord(
+ // Returns true if this isn't in any dictionary.
+ public static boolean isNotAWord(
final ConcurrentHashMap<String, Dictionary> dictionaries,
final CharSequence word, final boolean ignoreCase) {
- final WhitelistDictionary whitelistDictionary =
- (WhitelistDictionary)dictionaries.get(Dictionary.TYPE_WHITELIST);
- // If "word" is in the whitelist dictionary, it should not be auto corrected.
- if (whitelistDictionary != null
- && whitelistDictionary.shouldForciblyAutoCorrectFrom(word)) {
- return true;
- }
return !isValidWord(dictionaries, word, ignoreCase);
}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 534cffb..8909526 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.text.TextUtils;
+import android.util.SparseArray;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
@@ -51,7 +52,9 @@
private static final int TYPED_LETTER_MULTIPLIER = 2;
private long mNativeDict;
- private final int[] mInputCodes = new int[MAX_WORD_LENGTH];
+ private final Locale mLocale;
+ private final int[] mInputCodePoints = new int[MAX_WORD_LENGTH];
+ // TODO: The below should be int[] mOutputCodePoints
private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_RESULTS];
private final int[] mSpaceIndices = new int[MAX_SPACES];
private final int[] mOutputScores = new int[MAX_RESULTS];
@@ -59,6 +62,25 @@
private final boolean mUseFullEditDistance;
+ private final SparseArray<DicTraverseSession> mDicTraverseSessions =
+ CollectionUtils.newSparseArray();
+
+ // TODO: There should be a way to remove used DicTraverseSession objects from
+ // {@code mDicTraverseSessions}.
+ private DicTraverseSession getTraverseSession(int traverseSessionId) {
+ synchronized(mDicTraverseSessions) {
+ DicTraverseSession traverseSession = mDicTraverseSessions.get(traverseSessionId);
+ if (traverseSession == null) {
+ traverseSession = mDicTraverseSessions.get(traverseSessionId);
+ if (traverseSession == null) {
+ traverseSession = new DicTraverseSession(mLocale, mNativeDict);
+ mDicTraverseSessions.put(traverseSessionId, traverseSession);
+ }
+ }
+ return traverseSession;
+ }
+ }
+
/**
* Constructor for the binary dictionary. This is supposed to be called from the
* dictionary factory.
@@ -74,6 +96,7 @@
final String filename, final long offset, final long length,
final boolean useFullEditDistance, final Locale locale, final String dictType) {
super(dictType);
+ mLocale = locale;
mUseFullEditDistance = useFullEditDistance;
loadDictionary(filename, offset, length);
}
@@ -86,18 +109,17 @@
int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength, int maxWords,
int maxPredictions);
private native void closeNative(long dict);
- private native int getFrequencyNative(long dict, int[] word, int wordLength);
+ private native int getFrequencyNative(long dict, int[] word);
private native boolean isValidBigramNative(long dict, int[] word1, int[] word2);
- private native int getSuggestionsNative(long dict, long proximityInfo, int[] xCoordinates,
- int[] yCoordinates, int[] times, int[] pointerIds, int[] inputCodes, int codesSize,
- int commitPoint, boolean isGesture,
+ private native int getSuggestionsNative(long dict, long proximityInfo, long traverseSession,
+ int[] xCoordinates, int[] yCoordinates, int[] times, int[] pointerIds,
+ int[] inputCodePoints, int codesSize, int commitPoint, boolean isGesture,
int[] prevWordCodePointArray, boolean useFullEditDistance, char[] outputChars,
int[] outputScores, int[] outputIndices, int[] outputTypes);
- private static native float calcNormalizedScoreNative(
- char[] before, int beforeLength, char[] after, int afterLength, int score);
- private static native int editDistanceNative(
- char[] before, int beforeLength, char[] after, int afterLength);
+ private static native float calcNormalizedScoreNative(char[] before, char[] after, int score);
+ private static native int editDistanceNative(char[] before, char[] after);
+ // TODO: Move native dict into session
private final void loadDictionary(String path, long startOffset, long length) {
mNativeDict = openNative(path, startOffset, length, TYPED_LETTER_MULTIPLIER,
FULL_WORD_SCORE_MULTIPLIER, MAX_WORD_LENGTH, MAX_WORDS, MAX_PREDICTIONS);
@@ -106,10 +128,15 @@
@Override
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
final CharSequence prevWord, final ProximityInfo proximityInfo) {
+ return getSuggestionsWithSessionId(composer, prevWord, proximityInfo, 0);
+ }
+
+ @Override
+ public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
+ final CharSequence prevWord, final ProximityInfo proximityInfo, int sessionId) {
if (!isValidDictionary()) return null;
- Arrays.fill(mInputCodes, WordComposer.NOT_A_CODE);
- Arrays.fill(mOutputChars, (char) 0);
- Arrays.fill(mOutputScores, 0);
+
+ Arrays.fill(mInputCodePoints, Constants.NOT_A_CODE);
// TODO: toLowerCase in the native code
final int[] prevWordCodePointArray = (null == prevWord)
? null : StringUtils.toCodePointArray(prevWord.toString());
@@ -119,7 +146,7 @@
if (composerSize <= 1 || !isGesture) {
if (composerSize > MAX_WORD_LENGTH - 1) return null;
for (int i = 0; i < composerSize; i++) {
- mInputCodes[i] = composer.getCodeAt(i);
+ mInputCodePoints[i] = composer.getCodeAt(i);
}
}
@@ -127,24 +154,25 @@
final int codesSize = isGesture ? ips.getPointerSize() : composerSize;
// proximityInfo and/or prevWordForBigrams may not be null.
final int tmpCount = getSuggestionsNative(mNativeDict,
- proximityInfo.getNativeProximityInfo(), ips.getXCoordinates(),
- ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(),
- mInputCodes, codesSize, 0 /* commitPoint */, isGesture, prevWordCodePointArray,
+ proximityInfo.getNativeProximityInfo(), getTraverseSession(sessionId).getSession(),
+ ips.getXCoordinates(), ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(),
+ mInputCodePoints, codesSize, 0 /* commitPoint */, isGesture, prevWordCodePointArray,
mUseFullEditDistance, mOutputChars, mOutputScores, mSpaceIndices, mOutputTypes);
final int count = Math.min(tmpCount, MAX_PREDICTIONS);
- final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>();
+ final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
for (int j = 0; j < count; ++j) {
if (composerSize > 0 && mOutputScores[j] < 1) break;
final int start = j * MAX_WORD_LENGTH;
int len = 0;
- while (len < MAX_WORD_LENGTH && mOutputChars[start + len] != 0) {
+ while (len < MAX_WORD_LENGTH && mOutputChars[start + len] != 0) {
++len;
}
if (len > 0) {
+ final int score = SuggestedWordInfo.KIND_WHITELIST == mOutputTypes[j]
+ ? SuggestedWordInfo.MAX_SCORE : mOutputScores[j];
suggestions.add(new SuggestedWordInfo(
- new String(mOutputChars, start, len),
- mOutputScores[j], SuggestedWordInfo.KIND_CORRECTION, mDictType));
+ new String(mOutputChars, start, len), score, mOutputTypes[j], mDictType));
}
}
return suggestions;
@@ -155,13 +183,11 @@
}
public static float calcNormalizedScore(String before, String after, int score) {
- return calcNormalizedScoreNative(before.toCharArray(), before.length(),
- after.toCharArray(), after.length(), score);
+ return calcNormalizedScoreNative(before.toCharArray(), after.toCharArray(), score);
}
public static int editDistance(String before, String after) {
- return editDistanceNative(
- before.toCharArray(), before.length(), after.toCharArray(), after.length());
+ return editDistanceNative(before.toCharArray(), after.toCharArray());
}
@Override
@@ -172,8 +198,8 @@
@Override
public int getFrequency(CharSequence word) {
if (word == null) return -1;
- int[] chars = StringUtils.toCodePointArray(word.toString());
- return getFrequencyNative(mNativeDict, chars, chars.length);
+ int[] codePoints = StringUtils.toCodePointArray(word.toString());
+ return getFrequencyNative(mNativeDict, codePoints);
}
// TODO: Add a batch process version (isValidBigramMultiple?) to avoid excessive numbers of jni
@@ -186,11 +212,20 @@
}
@Override
- public synchronized void close() {
+ public void close() {
+ synchronized (mDicTraverseSessions) {
+ final int sessionsSize = mDicTraverseSessions.size();
+ for (int index = 0; index < sessionsSize; ++index) {
+ final DicTraverseSession traverseSession = mDicTraverseSessions.valueAt(index);
+ if (traverseSession != null) {
+ traverseSession.close();
+ }
+ }
+ }
closeInternal();
}
- private void closeInternal() {
+ private synchronized void closeInternal() {
if (mNativeDict != 0) {
closeNative(mNativeDict);
mNativeDict = 0;
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index 236c198..799aea8 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -99,7 +99,7 @@
}
try {
- final List<WordListInfo> list = new ArrayList<WordListInfo>();
+ final List<WordListInfo> list = CollectionUtils.newArrayList();
do {
final String wordListId = c.getString(0);
final String wordListLocale = c.getString(1);
@@ -267,7 +267,7 @@
final ContentResolver resolver = context.getContentResolver();
final List<WordListInfo> idList = getWordListWordListInfos(locale, context,
hasDefaultWordList);
- final List<AssetFileAddress> fileAddressList = new ArrayList<AssetFileAddress>();
+ final List<AssetFileAddress> fileAddressList = CollectionUtils.newArrayList();
for (WordListInfo id : idList) {
final AssetFileAddress afd = cacheWordList(id.mId, id.mLocale, resolver, context);
if (null != afd) {
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index 063243e..e1cb195 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -16,6 +16,8 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
+
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -23,6 +25,10 @@
import android.util.Log;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
@@ -51,6 +57,9 @@
private static final String MAIN_DICTIONARY_CATEGORY = "main";
public static final String ID_CATEGORY_SEPARATOR = ":";
+ // The key considered to read the version attribute in a dictionary file.
+ private static String VERSION_KEY = "version";
+
// Prevents this from being instantiated
private BinaryDictionaryGetter() {}
@@ -254,8 +263,7 @@
final Context context) {
final File[] directoryList = getCachedDirectoryList(context);
if (null == directoryList) return EMPTY_FILE_ARRAY;
- final HashMap<String, FileAndMatchLevel> cacheFiles =
- new HashMap<String, FileAndMatchLevel>();
+ final HashMap<String, FileAndMatchLevel> cacheFiles = CollectionUtils.newHashMap();
for (File directory : directoryList) {
if (!directory.isDirectory()) continue;
final String dirLocale = getWordListIdFromFileName(directory.getName());
@@ -336,6 +344,54 @@
return MAIN_DICTIONARY_CATEGORY.equals(idArray[0]);
}
+ // ## HACK ## we prevent usage of a dictionary before version 18 for English only. The reason
+ // for this is, since those do not include whitelist entries, the new code with an old version
+ // of the dictionary would lose whitelist functionality.
+ private static boolean hackCanUseDictionaryFile(final Locale locale, final File f) {
+ // Only for English - other languages didn't have a whitelist, hence this
+ // ad-hock ## HACK ##
+ if (!Locale.ENGLISH.getLanguage().equals(locale.getLanguage())) return true;
+
+ FileInputStream inStream = null;
+ try {
+ // Read the version of the file
+ inStream = new FileInputStream(f);
+ final ByteBuffer buffer = inStream.getChannel().map(
+ FileChannel.MapMode.READ_ONLY, 0, f.length());
+ final int magic = buffer.getInt();
+ if (magic != BinaryDictInputOutput.VERSION_2_MAGIC_NUMBER) {
+ return false;
+ }
+ final int formatVersion = buffer.getInt();
+ final int headerSize = buffer.getInt();
+ final HashMap<String, String> options = CollectionUtils.newHashMap();
+ BinaryDictInputOutput.populateOptions(buffer, headerSize, options);
+
+ final String version = options.get(VERSION_KEY);
+ if (null == version) {
+ // No version in the options : the format is unexpected
+ return false;
+ }
+ // Version 18 is the first one to include the whitelist
+ // Obviously this is a big ## HACK ##
+ return Integer.parseInt(version) >= 18;
+ } catch (java.io.FileNotFoundException e) {
+ return false;
+ } catch (java.io.IOException e) {
+ return false;
+ } catch (NumberFormatException e) {
+ return false;
+ } finally {
+ if (inStream != null) {
+ try {
+ inStream.close();
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
+ }
+ }
+
/**
* Returns a list of file addresses for a given locale, trying relevant methods in order.
*
@@ -362,18 +418,19 @@
final DictPackSettings dictPackSettings = new DictPackSettings(context);
boolean foundMainDict = false;
- final ArrayList<AssetFileAddress> fileList = new ArrayList<AssetFileAddress>();
+ final ArrayList<AssetFileAddress> fileList = CollectionUtils.newArrayList();
// cachedWordLists may not be null, see doc for getCachedDictionaryList
for (final File f : cachedWordLists) {
final String wordListId = getWordListIdFromFileName(f.getName());
- if (isMainWordListId(wordListId)) {
+ final boolean canUse = f.canRead() && hackCanUseDictionaryFile(locale, f);
+ if (canUse && isMainWordListId(wordListId)) {
foundMainDict = true;
}
if (!dictPackSettings.isWordListActive(wordListId)) continue;
- if (f.canRead()) {
+ if (canUse) {
fileList.add(AssetFileAddress.makeFromFileName(f.getPath()));
} else {
- Log.e(TAG, "Found a cached dictionary file but cannot read it");
+ Log.e(TAG, "Found a cached dictionary file but cannot read or use it");
}
}
diff --git a/java/src/com/android/inputmethod/latin/CollectionUtils.java b/java/src/com/android/inputmethod/latin/CollectionUtils.java
new file mode 100644
index 0000000..baa2ee1
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/CollectionUtils.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public class CollectionUtils {
+ private CollectionUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static <K,V> HashMap<K,V> newHashMap() {
+ return new HashMap<K,V>();
+ }
+
+ public static <K,V> TreeMap<K,V> newTreeMap() {
+ return new TreeMap<K,V>();
+ }
+
+ public static <K, V> Map<K,V> newSynchronizedTreeMap() {
+ final TreeMap<K,V> treeMap = newTreeMap();
+ return Collections.synchronizedMap(treeMap);
+ }
+
+ public static <K,V> ConcurrentHashMap<K,V> newConcurrentHashMap() {
+ return new ConcurrentHashMap<K,V>();
+ }
+
+ public static <E> HashSet<E> newHashSet() {
+ return new HashSet<E>();
+ }
+
+ public static <E> TreeSet<E> newTreeSet() {
+ return new TreeSet<E>();
+ }
+
+ public static <E> ArrayList<E> newArrayList() {
+ return new ArrayList<E>();
+ }
+
+ public static <E> ArrayList<E> newArrayList(final int initialCapacity) {
+ return new ArrayList<E>(initialCapacity);
+ }
+
+ public static <E> ArrayList<E> newArrayList(final Collection<E> collection) {
+ return new ArrayList<E>(collection);
+ }
+
+ public static <E> LinkedList<E> newLinkedList() {
+ return new LinkedList<E>();
+ }
+
+ public static <E> CopyOnWriteArrayList<E> newCopyOnWriteArrayList() {
+ return new CopyOnWriteArrayList<E>();
+ }
+
+ public static <E> CopyOnWriteArrayList<E> newCopyOnWriteArrayList(
+ final Collection<E> collection) {
+ return new CopyOnWriteArrayList<E>(collection);
+ }
+
+ public static <E> CopyOnWriteArrayList<E> newCopyOnWriteArrayList(final E[] array) {
+ return new CopyOnWriteArrayList<E>(array);
+ }
+
+ public static <E> SparseArray<E> newSparseArray() {
+ return new SparseArray<E>();
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index 1242967..d71c0f9 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -128,6 +128,13 @@
}
}
+ public static final int NOT_A_CODE = -1;
+
+ // See {@link KeyboardActionListener.Adapter#isInvalidCoordinate(int)}.
+ public static final int NOT_A_COORDINATE = -1;
+ public static final int SUGGESTION_STRIP_COORDINATE = -2;
+ public static final int SPELL_CHECKER_COORDINATE = -3;
+
private Constants() {
// This utility class is not publicly instantiable.
}
diff --git a/java/src/com/android/inputmethod/latin/DicTraverseSession.java b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
new file mode 100644
index 0000000..359da72
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
@@ -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();
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index 60fe17b..88d0c09 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -42,7 +42,6 @@
public static final String TYPE_USER = "user";
// User history dictionary internal to LatinIME.
public static final String TYPE_USER_HISTORY = "history";
- public static final String TYPE_WHITELIST ="whitelist";
protected final String mDictType;
public Dictionary(final String dictType) {
@@ -62,6 +61,13 @@
abstract public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
final CharSequence prevWord, final ProximityInfo proximityInfo);
+ // The default implementation of this method ignores sessionId.
+ // Subclasses that want to use sessionId need to override this method.
+ public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
+ final CharSequence prevWord, final ProximityInfo proximityInfo, int sessionId) {
+ return getSuggestions(composer, prevWord, proximityInfo);
+ }
+
/**
* Checks if the given word occurs in the dictionary
* @param word the word to search for. The search should be case-insensitive.
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
index ee80f25..4acab6b 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -35,22 +35,22 @@
public DictionaryCollection(final String dictType) {
super(dictType);
- mDictionaries = new CopyOnWriteArrayList<Dictionary>();
+ mDictionaries = CollectionUtils.newCopyOnWriteArrayList();
}
public DictionaryCollection(final String dictType, Dictionary... dictionaries) {
super(dictType);
if (null == dictionaries) {
- mDictionaries = new CopyOnWriteArrayList<Dictionary>();
+ mDictionaries = CollectionUtils.newCopyOnWriteArrayList();
} else {
- mDictionaries = new CopyOnWriteArrayList<Dictionary>(dictionaries);
+ mDictionaries = CollectionUtils.newCopyOnWriteArrayList(dictionaries);
mDictionaries.removeAll(Collections.singleton(null));
}
}
public DictionaryCollection(final String dictType, Collection<Dictionary> dictionaries) {
super(dictType);
- mDictionaries = new CopyOnWriteArrayList<Dictionary>(dictionaries);
+ mDictionaries = CollectionUtils.newCopyOnWriteArrayList(dictionaries);
mDictionaries.removeAll(Collections.singleton(null));
}
@@ -63,7 +63,7 @@
// dictionary and add the rest to it if not null, hence the get(0)
ArrayList<SuggestedWordInfo> suggestions = dictionaries.get(0).getSuggestions(composer,
prevWord, proximityInfo);
- if (null == suggestions) suggestions = new ArrayList<SuggestedWordInfo>();
+ if (null == suggestions) suggestions = CollectionUtils.newArrayList();
final int length = dictionaries.size();
for (int i = 1; i < length; ++ i) {
final ArrayList<SuggestedWordInfo> sugg = dictionaries.get(i).getSuggestions(composer,
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index 06a5f4b..cdd01d0 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -53,7 +53,7 @@
createBinaryDictionary(context, locale));
}
- final LinkedList<Dictionary> dictList = new LinkedList<Dictionary>();
+ final LinkedList<Dictionary> dictList = CollectionUtils.newLinkedList();
final ArrayList<AssetFileAddress> assetFileList =
BinaryDictionaryGetter.getDictionaryFiles(locale, context);
if (null != assetFileList) {
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 016530a..cdf5247 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -62,7 +62,7 @@
* that filename.
*/
private static final HashMap<String, DictionaryController> sSharedDictionaryControllers =
- new HashMap<String, DictionaryController>();
+ CollectionUtils.newHashMap();
/** The application context. */
protected final Context mContext;
@@ -159,9 +159,9 @@
* the native side.
*/
public void clearFusionDictionary() {
+ final HashMap<String, String> attributes = CollectionUtils.newHashMap();
mFusionDictionary = new FusionDictionary(new Node(),
- new FusionDictionary.DictionaryOptions(new HashMap<String, String>(), false,
- false));
+ new FusionDictionary.DictionaryOptions(attributes, false, false));
}
/**
@@ -175,7 +175,7 @@
mFusionDictionary.add(word, frequency, null);
} else {
// TODO: Do this in the subclass, with this class taking an arraylist.
- final ArrayList<WeightedString> shortcutTargets = new ArrayList<WeightedString>();
+ final ArrayList<WeightedString> shortcutTargets = CollectionUtils.newArrayList();
shortcutTargets.add(new WeightedString(shortcutTarget, frequency));
mFusionDictionary.add(word, frequency, shortcutTargets);
}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index d101aaf..8a38d1e 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -19,7 +19,6 @@
import android.content.Context;
import android.text.TextUtils;
-import com.android.inputmethod.keyboard.KeyDetector;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
@@ -231,7 +230,7 @@
childNode.mTerminal = true;
if (isShortcutOnly) {
if (null == childNode.mShortcutTargets) {
- childNode.mShortcutTargets = new ArrayList<char[]>();
+ childNode.mShortcutTargets = CollectionUtils.newArrayList();
}
childNode.mShortcutTargets.add(shortcutTarget.toCharArray());
} else {
@@ -251,7 +250,7 @@
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
final CharSequence prevWord, final ProximityInfo proximityInfo) {
if (reloadDictionaryIfRequired()) return null;
- if (composer.size() <= 1) {
+ if (composer.size() > 1) {
if (composer.size() >= BinaryDictionary.MAX_WORD_LENGTH) {
return null;
}
@@ -260,7 +259,7 @@
return suggestions;
} else {
if (TextUtils.isEmpty(prevWord)) return null;
- final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>();
+ final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
runBigramReverseLookUp(prevWord, suggestions);
return suggestions;
}
@@ -279,7 +278,7 @@
protected ArrayList<SuggestedWordInfo> getWordsInner(final WordComposer codes,
final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) {
- final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>();
+ final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
mInputLength = codes.size();
if (mCodes.length < mInputLength) mCodes = new int[mInputLength][];
final InputPointers ips = codes.getInputPointers();
@@ -292,9 +291,9 @@
mCodes[i] = new int[ProximityInfo.MAX_PROXIMITY_CHARS_SIZE];
}
final int x = xCoordinates != null && i < xCoordinates.length ?
- xCoordinates[i] : WordComposer.NOT_A_COORDINATE;
+ xCoordinates[i] : Constants.NOT_A_COORDINATE;
final int y = xCoordinates != null && i < yCoordinates.length ?
- yCoordinates[i] : WordComposer.NOT_A_COORDINATE;
+ yCoordinates[i] : Constants.NOT_A_COORDINATE;
proximityInfo.fillArrayWithNearestKeyCodes(x, y, codes.getCodeAt(i), mCodes[i]);
}
mMaxDepth = mInputLength * 3;
@@ -487,7 +486,7 @@
for (int j = 0; j < alternativesSize; j++) {
final int addedAttenuation = (j > 0 ? 1 : 2);
final int currentChar = currentChars[j];
- if (currentChar == KeyDetector.NOT_A_CODE) {
+ if (currentChar == Constants.NOT_A_CODE) {
break;
}
if (currentChar == lowerC || currentChar == c) {
@@ -551,7 +550,7 @@
Node secondWord = searchWord(mRoots, word2, 0, null);
LinkedList<NextWord> bigrams = firstWord.mNGrams;
if (bigrams == null || bigrams.size() == 0) {
- firstWord.mNGrams = new LinkedList<NextWord>();
+ firstWord.mNGrams = CollectionUtils.newLinkedList();
bigrams = firstWord.mNGrams;
} else {
for (NextWord nw : bigrams) {
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
index e561f59..7bcda9b 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -29,10 +29,12 @@
final public boolean mInputTypeNoAutoCorrect;
final public boolean mIsSettingsSuggestionStripOn;
final public boolean mApplicationSpecifiedCompletionOn;
+ final private int mInputType;
public InputAttributes(final EditorInfo editorInfo, final boolean isFullscreenMode) {
final int inputType = null != editorInfo ? editorInfo.inputType : 0;
final int inputClass = inputType & InputType.TYPE_MASK_CLASS;
+ mInputType = inputType;
if (inputClass != InputType.TYPE_CLASS_TEXT) {
// If we are not looking at a TYPE_CLASS_TEXT field, the following strange
// cases may arise, so we do a couple sanity checks for them. If it's a
@@ -93,6 +95,10 @@
}
}
+ public boolean isSameInputType(final EditorInfo editorInfo) {
+ return editorInfo.inputType == mInputType;
+ }
+
@SuppressWarnings("unused")
private void dumpFlags(final int inputType) {
Log.i(TAG, "Input class:");
diff --git a/java/src/com/android/inputmethod/latin/InputPointers.java b/java/src/com/android/inputmethod/latin/InputPointers.java
index cbc916a..ff2feb5 100644
--- a/java/src/com/android/inputmethod/latin/InputPointers.java
+++ b/java/src/com/android/inputmethod/latin/InputPointers.java
@@ -93,7 +93,7 @@
}
mXCoordinates.append(xCoordinates, startPos, length);
mYCoordinates.append(yCoordinates, startPos, length);
- mPointerIds.fill(pointerId, startPos, length);
+ mPointerIds.fill(pointerId, mPointerIds.getLength(), length);
mTimes.append(times, startPos, length);
}
@@ -124,4 +124,10 @@
public int[] getTimes() {
return mTimes.getPrimitiveArray();
}
+
+ @Override
+ public String toString() {
+ return "size=" + getPointerSize() + " id=" + mPointerIds + " time=" + mTimes
+ + " x=" + mXCoordinates + " y=" + mYCoordinates;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 4550860..83a3068 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -361,7 +361,7 @@
mPrefs = prefs;
LatinImeLogger.init(this, prefs);
if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.getInstance().init(this, prefs, mKeyboardSwitcher);
+ ResearchLogger.getInstance().init(this, prefs);
}
InputMethodManagerCompatWrapper.init(this);
SubtypeSwitcher.init(this);
@@ -381,18 +381,7 @@
ImfUtils.setAdditionalInputMethodSubtypes(this, mCurrentSettings.getAdditionalSubtypes());
- Utils.GCUtils.getInstance().reset();
- boolean tryGC = true;
- // Shouldn't this be removed? I think that from Honeycomb on, the GC is now actually working
- // as expected and this code is useless.
- for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
- try {
- initSuggest();
- tryGC = false;
- } catch (OutOfMemoryError e) {
- tryGC = Utils.GCUtils.getInstance().tryGCOrWait("InitSuggest", e);
- }
- }
+ initSuggest();
mDisplayOrientation = res.getConfiguration().orientation;
@@ -416,7 +405,8 @@
}
// Has to be package-visible for unit tests
- /* package */ void loadSettings() {
+ /* package for test */
+ void loadSettings() {
// Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged()
// is not guaranteed. It may even be called at the same time on a different thread.
if (null == mPrefs) mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
@@ -433,10 +423,14 @@
resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary());
}
+ // Note that this method is called from a non-UI thread.
@Override
public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable) {
mIsMainDictionaryAvailable = isMainDictionaryAvailable;
- updateKeyboardViewGestureHandlingModeByMainDictionaryAvailability();
+ final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
+ if (mainKeyboardView != null) {
+ mainKeyboardView.setMainDictionaryAvailability(isMainDictionaryAvailable);
+ }
}
private void initSuggest() {
@@ -517,7 +511,7 @@
/* package private */ void resetSuggestMainDict() {
final Locale subtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
- mSuggest.resetMainDict(this, subtypeLocale);
+ mSuggest.resetMainDict(this, subtypeLocale, this /* SuggestInitializationListener */);
mIsMainDictionaryAvailable = DictionaryFactory.isDictionaryAvailable(this, subtypeLocale);
}
@@ -536,7 +530,10 @@
@Override
public void onConfigurationChanged(Configuration conf) {
- mSubtypeSwitcher.onConfigurationChanged(conf);
+ // System locale has been changed. Needs to reload keyboard.
+ if (mSubtypeSwitcher.onConfigurationChanged(conf, this)) {
+ loadKeyboard();
+ }
// If orientation changed while predicting, commit the change
if (mDisplayOrientation != conf.orientation) {
mDisplayOrientation = conf.orientation;
@@ -603,6 +600,7 @@
// Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged()
// is not guaranteed. It may even be called at the same time on a different thread.
mSubtypeSwitcher.updateSubtype(subtype);
+ loadKeyboard();
}
private void onStartInputInternal(EditorInfo editorInfo, boolean restarting) {
@@ -663,11 +661,23 @@
// Forward this event to the accessibility utilities, if enabled.
final AccessibilityUtils accessUtils = AccessibilityUtils.getInstance();
if (accessUtils.isTouchExplorationEnabled()) {
- accessUtils.onStartInputViewInternal(editorInfo, restarting);
+ accessUtils.onStartInputViewInternal(mainKeyboardView, editorInfo, restarting);
}
- if (!restarting) {
- mSubtypeSwitcher.updateParametersOnStartInputView();
+ final boolean selectionChanged = mLastSelectionStart != editorInfo.initialSelStart
+ || mLastSelectionEnd != editorInfo.initialSelEnd;
+ final boolean inputTypeChanged = !mCurrentSettings.isSameInputType(editorInfo);
+ final boolean isDifferentTextField = !restarting || inputTypeChanged;
+ if (isDifferentTextField) {
+ final boolean currentSubtypeEnabled = mSubtypeSwitcher
+ .updateParametersOnStartInputViewAndReturnIfCurrentSubtypeEnabled();
+ if (!currentSubtypeEnabled) {
+ // Current subtype is disabled. Needs to update subtype and keyboard.
+ final InputMethodSubtype newSubtype = ImfUtils.getCurrentInputMethodSubtype(
+ this, mSubtypeSwitcher.getNoLanguageSubtype());
+ mSubtypeSwitcher.updateSubtype(newSubtype);
+ loadKeyboard();
+ }
}
// The EditorInfo might have a flag that affects fullscreen mode.
@@ -675,9 +685,7 @@
updateFullscreenMode();
mApplicationSpecifiedCompletions = null;
- final boolean selectionChanged = mLastSelectionStart != editorInfo.initialSelStart
- || mLastSelectionEnd != editorInfo.initialSelEnd;
- if (!restarting || selectionChanged) {
+ if (isDifferentTextField || selectionChanged) {
// If the selection changed, we reset the input state. Essentially, we come here with
// restarting == true when the app called setText() or similar. We should reset the
// state if the app set the text to something else, but keep it if it set a suggestion
@@ -692,7 +700,7 @@
}
}
- if (!restarting) {
+ if (isDifferentTextField) {
mainKeyboardView.closing();
loadSettings();
@@ -701,7 +709,6 @@
}
switcher.loadKeyboard(editorInfo, mCurrentSettings);
- updateKeyboardViewGestureHandlingModeByMainDictionaryAvailability();
}
setSuggestionStripShownInternal(
isSuggestionsStripVisible(), /* needsInputViewShown */ false);
@@ -719,8 +726,12 @@
mHandler.cancelUpdateSuggestionStrip();
mHandler.cancelDoubleSpacesTimer();
+ mainKeyboardView.setMainDictionaryAvailability(mIsMainDictionaryAvailable);
mainKeyboardView.setKeyPreviewPopupEnabled(mCurrentSettings.mKeyPreviewPopupOn,
mCurrentSettings.mKeyPreviewPopupDismissDelay);
+ mainKeyboardView.setGestureHandlingEnabledByUser(mCurrentSettings.mGestureInputEnabled);
+ mainKeyboardView.setGesturePreviewMode(mCurrentSettings.mGesturePreviewTrailEnabled,
+ mCurrentSettings.mGestureFloatingPreviewTextEnabled);
if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
}
@@ -898,13 +909,13 @@
}
}
}
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_onDisplayCompletions(applicationSpecifiedCompletions);
- }
if (!mCurrentSettings.isApplicationSpecifiedCompletionsOn()) return;
mApplicationSpecifiedCompletions = applicationSpecifiedCompletions;
if (applicationSpecifiedCompletions == null) {
clearSuggestionStrip();
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_onDisplayCompletions(null);
+ }
return;
}
@@ -926,6 +937,9 @@
// this case? This says to keep whatever the user typed.
mWordComposer.setAutoCorrection(mWordComposer.getTypedWord());
setSuggestionStripShown(true);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_onDisplayCompletions(applicationSpecifiedCompletions);
+ }
}
private void setSuggestionStripShownInternal(boolean shown, boolean needsInputViewShown) {
@@ -1046,9 +1060,6 @@
final CharSequence typedWord = mWordComposer.getTypedWord();
if (typedWord.length() > 0) {
mConnection.commitText(typedWord, 1);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_commitText(typedWord);
- }
final CharSequence prevWord = addToUserHistoryDictionary(typedWord);
mLastComposedWord = mWordComposer.commitWord(
LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, typedWord.toString(),
@@ -1084,18 +1095,27 @@
return mConnection.getCursorCapsMode(inputType);
}
+ // Factor in auto-caps and manual caps and compute the current caps mode.
+ private int getActualCapsMode() {
+ final int manual = mKeyboardSwitcher.getManualCapsMode();
+ if (manual != WordComposer.CAPS_MODE_OFF) return manual;
+ final int auto = getCurrentAutoCapsState();
+ if (0 != (auto & TextUtils.CAP_MODE_CHARACTERS)) {
+ return WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED;
+ }
+ if (0 != auto) return WordComposer.CAPS_MODE_AUTO_SHIFTED;
+ return WordComposer.CAPS_MODE_OFF;
+ }
+
private void swapSwapperAndSpace() {
CharSequence lastTwo = mConnection.getTextBeforeCursor(2, 0);
// It is guaranteed lastTwo.charAt(1) is a swapper - else this method is not called.
if (lastTwo != null && lastTwo.length() == 2
&& lastTwo.charAt(0) == Keyboard.CODE_SPACE) {
mConnection.deleteSurroundingText(2, 0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(2);
- }
mConnection.commitText(lastTwo.charAt(1) + " ", 1);
if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_swapSwapperAndSpaceWhileInBatchEdit();
+ ResearchLogger.latinIME_swapSwapperAndSpace();
}
mKeyboardSwitcher.updateShiftState();
}
@@ -1112,9 +1132,6 @@
mHandler.cancelDoubleSpacesTimer();
mConnection.deleteSurroundingText(2, 0);
mConnection.commitText(". ", 1);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_doubleSpaceAutoPeriod();
- }
mKeyboardSwitcher.updateShiftState();
return true;
}
@@ -1178,9 +1195,6 @@
private void performEditorAction(int actionId) {
mConnection.performEditorAction(actionId);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_performEditorAction(actionId);
- }
}
private void handleLanguageSwitchKey() {
@@ -1217,6 +1231,9 @@
// For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}.
if (code >= '0' && code <= '9') {
super.sendKeyChar((char)code);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_sendKeyCodePoint(code);
+ }
return;
}
@@ -1233,9 +1250,6 @@
final String text = new String(new int[] { code }, 0, 1);
mConnection.commitText(text, text.length());
}
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_sendKeyCodePoint(code);
- }
}
// Implementation of {@link KeyboardActionListener}.
@@ -1247,11 +1261,6 @@
}
mLastKeyTime = when;
mConnection.beginBatchEdit();
-
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_onCodeInput(primaryCode, x, y);
- }
-
final KeyboardSwitcher switcher = mKeyboardSwitcher;
// The space state depends only on the last character pressed and its own previous
// state. Here, we revert the space state to neutral if the key is actually modifying
@@ -1284,7 +1293,7 @@
onSettingsKeyPressed();
break;
case Keyboard.CODE_SHORTCUT:
- mSubtypeSwitcher.switchToShortcutIME();
+ mSubtypeSwitcher.switchToShortcutIME(this);
break;
case Keyboard.CODE_ACTION_ENTER:
performEditorAction(getActionId(switcher.getKeyboard()));
@@ -1317,8 +1326,8 @@
keyX = x;
keyY = y;
} else {
- keyX = NOT_A_TOUCH_COORDINATE;
- keyY = NOT_A_TOUCH_COORDINATE;
+ keyX = Constants.NOT_A_COORDINATE;
+ keyY = Constants.NOT_A_COORDINATE;
}
handleCharacter(primaryCode, keyX, keyY, spaceState);
}
@@ -1333,22 +1342,24 @@
mLastComposedWord.deactivate();
mEnteredText = null;
mConnection.endBatchEdit();
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_onCodeInput(primaryCode, x, y);
+ }
}
// Called from PointerTracker through the KeyboardActionListener interface
@Override
public void onTextInput(CharSequence rawText) {
mConnection.beginBatchEdit();
- commitTyped(LastComposedWord.NOT_A_SEPARATOR);
+ if (mWordComposer.isComposingWord()) {
+ commitCurrentAutoCorrection(LastComposedWord.NOT_A_SEPARATOR);
+ }
mHandler.postUpdateSuggestionStrip();
final CharSequence text = specificTldProcessingOnTextInput(rawText);
if (SPACE_STATE_PHANTOM == mSpaceState) {
sendKeyCodePoint(Keyboard.CODE_SPACE);
}
mConnection.commitText(text, 1);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_commitText(text);
- }
mConnection.endBatchEdit();
mKeyboardSwitcher.updateShiftState();
mKeyboardSwitcher.onCodeInput(Keyboard.CODE_OUTPUT_TEXT);
@@ -1361,15 +1372,14 @@
public void onStartBatchInput() {
mConnection.beginBatchEdit();
if (mWordComposer.isComposingWord()) {
- commitTyped(LastComposedWord.NOT_A_SEPARATOR);
+ commitCurrentAutoCorrection(LastComposedWord.NOT_A_SEPARATOR);
mExpectingUpdateSelection = true;
// TODO: Can we remove this?
mSpaceState = SPACE_STATE_PHANTOM;
}
mConnection.endBatchEdit();
// TODO: Should handle TextUtils.CAP_MODE_CHARACTER.
- mWordComposer.setAutoCapitalized(
- getCurrentAutoCapsState() != Constants.TextUtils.CAP_MODE_OFF);
+ mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode());
}
@Override
@@ -1447,9 +1457,6 @@
// like the smiley key or the .com key.
final int length = mEnteredText.length();
mConnection.deleteSurroundingText(length, 0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(length);
- }
// If we have mEnteredText, then we know that mHasUncommittedTypedChars == false.
// In addition we know that spaceState is false, and that we should not be
// reverting any autocorrect at this point. So we can safely return.
@@ -1469,9 +1476,6 @@
mHandler.postUpdateSuggestionStrip();
} else {
mConnection.deleteSurroundingText(1, 0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(1);
- }
}
} else {
if (mLastComposedWord.canRevertCommit()) {
@@ -1500,9 +1504,6 @@
final int lengthToDelete = mLastSelectionEnd - mLastSelectionStart;
mConnection.setSelection(mLastSelectionEnd, mLastSelectionEnd);
mConnection.deleteSurroundingText(lengthToDelete, 0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(lengthToDelete);
- }
} else {
// There is no selection, just delete one character.
if (NOT_A_CURSOR_POSITION == mLastSelectionEnd) {
@@ -1521,14 +1522,8 @@
} else {
mConnection.deleteSurroundingText(1, 0);
}
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(1);
- }
if (mDeleteCount > DELETE_ACCELERATE_AT) {
mConnection.deleteSurroundingText(1, 0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(1);
- }
}
}
if (mCurrentSettings.isSuggestionsRequested(mDisplayOrientation)) {
@@ -1604,13 +1599,12 @@
mWordComposer.add(primaryCode, keyX, keyY);
// If it's the first letter, make note of auto-caps state
if (mWordComposer.size() == 1) {
- mWordComposer.setAutoCapitalized(
- getCurrentAutoCapsState() != Constants.TextUtils.CAP_MODE_OFF);
+ mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode());
}
mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
} else {
final boolean swapWeakSpace = maybeStripSpace(primaryCode,
- spaceState, KeyboardActionListener.SUGGESTION_STRIP_COORDINATE == x);
+ spaceState, Constants.SUGGESTION_STRIP_COORDINATE == x);
sendKeyCodePoint(primaryCode);
@@ -1640,7 +1634,7 @@
}
final boolean swapWeakSpace = maybeStripSpace(primaryCode, spaceState,
- KeyboardActionListener.SUGGESTION_STRIP_COORDINATE == x);
+ Constants.SUGGESTION_STRIP_COORDINATE == x);
if (SPACE_STATE_PHANTOM == spaceState &&
mCurrentSettings.isPhantomSpacePromotingSymbol(primaryCode)) {
@@ -1687,6 +1681,7 @@
Utils.Stats.onSeparator((char)primaryCode, x, y);
+ mHandler.postUpdateShiftState();
return didAutoCorrect;
}
@@ -1707,7 +1702,8 @@
// TODO: make this private
// Outside LatinIME, only used by the test suite.
- /* package for tests */ boolean isShowingPunctuationList() {
+ /* package for tests */
+ boolean isShowingPunctuationList() {
if (mSuggestionStripView == null) return false;
return mCurrentSettings.mSuggestPuncList == mSuggestionStripView.getSuggestions();
}
@@ -1853,10 +1849,6 @@
+ "is empty? Impossible! I must commit suicide.");
}
Utils.Stats.onAutoCorrection(typedWord, autoCorrection.toString(), separatorCodePoint);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_commitCurrentAutoCorrection(typedWord,
- autoCorrection.toString());
- }
mExpectingUpdateSelection = true;
commitChosenWord(autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD,
separatorCodePoint);
@@ -1873,8 +1865,7 @@
// Called from {@link SuggestionStripView} through the {@link SuggestionStripView#Listener}
// interface
@Override
- public void pickSuggestionManually(final int index, final CharSequence suggestion,
- final int x, final int y) {
+ public void pickSuggestionManually(final int index, final CharSequence suggestion) {
final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions();
// If this is a punctuation picked from the suggestion strip, pass it to onCodeInput
if (suggestion.length() == 1 && isShowingPunctuationList()) {
@@ -1882,13 +1873,12 @@
// So, LatinImeLogger logs "" as a user's input.
LatinImeLogger.logOnManualSuggestion("", suggestion.toString(), index, suggestedWords);
// Rely on onCodeInput to do the complicated swapping/stripping logic consistently.
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_punctuationSuggestion(index, suggestion, x, y);
- }
final int primaryCode = suggestion.charAt(0);
onCodeInput(primaryCode,
- KeyboardActionListener.SUGGESTION_STRIP_COORDINATE,
- KeyboardActionListener.SUGGESTION_STRIP_COORDINATE);
+ Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_punctuationSuggestion(index, suggestion);
+ }
return;
}
@@ -1915,10 +1905,6 @@
final CompletionInfo completionInfo = mApplicationSpecifiedCompletions[index];
mConnection.commitCompletion(completionInfo);
mConnection.endBatchEdit();
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_pickApplicationSpecifiedCompletion(index,
- completionInfo.getText(), x, y);
- }
return;
}
@@ -1927,12 +1913,12 @@
final String replacedWord = mWordComposer.getTypedWord().toString();
LatinImeLogger.logOnManualSuggestion(replacedWord,
suggestion.toString(), index, suggestedWords);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion, x, y);
- }
mExpectingUpdateSelection = true;
commitChosenWord(suggestion, LastComposedWord.COMMIT_TYPE_MANUAL_PICK,
LastComposedWord.NOT_A_SEPARATOR);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion);
+ }
mConnection.endBatchEdit();
// Don't allow cancellation of manual pick
mLastComposedWord.deactivate();
@@ -1948,8 +1934,8 @@
// If the suggestion is not in the dictionary, the hint should be shown.
&& !AutoCorrection.isValidWord(mSuggest.getUnigramDictionaries(), suggestion, true);
- Utils.Stats.onSeparator((char)Keyboard.CODE_SPACE, WordComposer.NOT_A_COORDINATE,
- WordComposer.NOT_A_COORDINATE);
+ Utils.Stats.onSeparator((char)Keyboard.CODE_SPACE,
+ Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
if (showingAddToDictionaryHint && mIsUserDictionaryAvailable) {
mSuggestionStripView.showAddToDictionaryHint(
suggestion, mCurrentSettings.mHintToSaveText);
@@ -1967,9 +1953,6 @@
final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions();
mConnection.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan(
this, chosenWord, suggestedWords, mIsMainDictionaryAvailable), 1);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_commitText(chosenWord);
- }
// Add the word to the user history dictionary
final CharSequence prevWord = addToUserHistoryDictionary(chosenWord);
// TODO: figure out here if this is an auto-correct or if the best word is actually
@@ -1992,6 +1975,7 @@
private CharSequence addToUserHistoryDictionary(final CharSequence suggestion) {
if (TextUtils.isEmpty(suggestion)) return null;
+ if (mSuggest == null) return null;
// If correction is not enabled, we don't add words to the user history dictionary.
// That's to avoid unintended additions in some sensitive fields, or fields that
@@ -2003,7 +1987,7 @@
final CharSequence prevWord
= mConnection.getNthPreviousWord(mCurrentSettings.mWordSeparators, 2);
final String secondWord;
- if (mWordComposer.isAutoCapitalized() && !mWordComposer.isMostlyCaps()) {
+ if (mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps()) {
secondWord = suggestion.toString().toLowerCase(
mSubtypeSwitcher.getCurrentSubtypeLocale());
} else {
@@ -2036,9 +2020,6 @@
mWordComposer.setComposingWord(word, mKeyboardSwitcher.getKeyboard());
final int length = word.length();
mConnection.deleteSurroundingText(length, 0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(length);
- }
mConnection.setComposingText(word, 1);
mHandler.postUpdateSuggestionStrip();
}
@@ -2066,9 +2047,6 @@
}
}
mConnection.deleteSurroundingText(deleteLength, 0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(deleteLength);
- }
if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) {
mUserHistoryDictionary.cancelAddingUserHistory(
previousWord.toString(), committedWord.toString());
@@ -2076,8 +2054,8 @@
mConnection.commitText(originallyTypedWord, 1);
// Re-insert the separator
sendKeyCodePoint(mLastComposedWord.mSeparatorCode);
- Utils.Stats.onSeparator(mLastComposedWord.mSeparatorCode, WordComposer.NOT_A_COORDINATE,
- WordComposer.NOT_A_COORDINATE);
+ Utils.Stats.onSeparator(mLastComposedWord.mSeparatorCode,
+ Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIME_revertCommit(originallyTypedWord);
}
@@ -2093,9 +2071,10 @@
return mCurrentSettings.isWordSeparator(code);
}
- // Notify that language or mode have been changed and toggleLanguage will update KeyboardID
- // according to new language or mode. Called from SubtypeSwitcher.
- public void onRefreshKeyboard() {
+ // TODO: Make this private
+ // Outside LatinIME, only used by the {@link InputTestsBase} test suite.
+ /* package for test */
+ void loadKeyboard() {
// When the device locale is changed in SetupWizard etc., this method may get called via
// onConfigurationChanged before SoftInputWindow is shown.
initSuggest();
@@ -2103,7 +2082,6 @@
if (mKeyboardSwitcher.getMainKeyboardView() != null) {
// Reload keyboard because the current language has been changed.
mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mCurrentSettings);
- updateKeyboardViewGestureHandlingModeByMainDictionaryAvailability();
}
// Since we just changed languages, we should re-evaluate suggestions with whatever word
// we are currently composing. If we are not composing anything, we may want to display
@@ -2111,17 +2089,6 @@
mHandler.postUpdateSuggestionStrip();
}
- private void updateKeyboardViewGestureHandlingModeByMainDictionaryAvailability() {
- final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
- if (mainKeyboardView != null) {
- final boolean shouldHandleGesture = mCurrentSettings.mGestureInputEnabled
- && mIsMainDictionaryAvailable;
- mainKeyboardView.setGestureHandlingMode(shouldHandleGesture,
- mCurrentSettings.mGesturePreviewTrailEnabled,
- mCurrentSettings.mGestureFloatingPreviewTextEnabled);
- }
- }
-
// TODO: Remove this method from {@link LatinIME} and move {@link FeedbackManager} to
// {@link KeyboardSwitcher}. Called from KeyboardSwitcher
public void hapticAndAudioFeedback(final int primaryCode) {
diff --git a/java/src/com/android/inputmethod/latin/LocaleUtils.java b/java/src/com/android/inputmethod/latin/LocaleUtils.java
index b938dd3..3b08cab 100644
--- a/java/src/com/android/inputmethod/latin/LocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/LocaleUtils.java
@@ -193,7 +193,7 @@
}
}
- private static final HashMap<String, Locale> sLocaleCache = new HashMap<String, Locale>();
+ private static final HashMap<String, Locale> sLocaleCache = CollectionUtils.newHashMap();
/**
* Creates a locale from a string specification.
diff --git a/java/src/com/android/inputmethod/latin/NativeUtils.java b/java/src/com/android/inputmethod/latin/NativeUtils.java
deleted file mode 100644
index 9cc2bc0..0000000
--- a/java/src/com/android/inputmethod/latin/NativeUtils.java
+++ /dev/null
@@ -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);
-}
diff --git a/java/src/com/android/inputmethod/latin/ResizableIntArray.java b/java/src/com/android/inputmethod/latin/ResizableIntArray.java
index 387d45a..c660f92 100644
--- a/java/src/com/android/inputmethod/latin/ResizableIntArray.java
+++ b/java/src/com/android/inputmethod/latin/ResizableIntArray.java
@@ -131,4 +131,16 @@
mLength = endPos;
}
}
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < mLength; i++) {
+ if (i != 0) {
+ sb.append(",");
+ }
+ sb.append(mArray[i]);
+ }
+ return "[" + sb + "]";
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 8b4c173..41e59e9 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -55,7 +55,9 @@
public void beginBatchEdit() {
if (++mNestLevel == 1) {
mIC = mParent.getCurrentInputConnection();
- if (null != mIC) mIC.beginBatchEdit();
+ if (null != mIC) {
+ mIC.beginBatchEdit();
+ }
} else {
if (DBG) {
throw new RuntimeException("Nest level too deep");
@@ -66,7 +68,9 @@
}
public void endBatchEdit() {
if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead
- if (--mNestLevel == 0 && null != mIC) mIC.endBatchEdit();
+ if (--mNestLevel == 0 && null != mIC) {
+ mIC.endBatchEdit();
+ }
}
private void checkBatchEdit() {
@@ -79,12 +83,22 @@
public void finishComposingText() {
checkBatchEdit();
- if (null != mIC) mIC.finishComposingText();
+ if (null != mIC) {
+ mIC.finishComposingText();
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.richInputConnection_finishComposingText();
+ }
+ }
}
public void commitText(final CharSequence text, final int i) {
checkBatchEdit();
- if (null != mIC) mIC.commitText(text, i);
+ if (null != mIC) {
+ mIC.commitText(text, i);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.richInputConnection_commitText(text, i);
+ }
+ }
}
public int getCursorCapsMode(final int inputType) {
@@ -107,37 +121,72 @@
public void deleteSurroundingText(final int i, final int j) {
checkBatchEdit();
- if (null != mIC) mIC.deleteSurroundingText(i, j);
+ if (null != mIC) {
+ mIC.deleteSurroundingText(i, j);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.richInputConnection_deleteSurroundingText(i, j);
+ }
+ }
}
public void performEditorAction(final int actionId) {
mIC = mParent.getCurrentInputConnection();
- if (null != mIC) mIC.performEditorAction(actionId);
+ if (null != mIC) {
+ mIC.performEditorAction(actionId);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.richInputConnection_performEditorAction(actionId);
+ }
+ }
}
public void sendKeyEvent(final KeyEvent keyEvent) {
checkBatchEdit();
- if (null != mIC) mIC.sendKeyEvent(keyEvent);
+ if (null != mIC) {
+ mIC.sendKeyEvent(keyEvent);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.richInputConnection_sendKeyEvent(keyEvent);
+ }
+ }
}
public void setComposingText(final CharSequence text, final int i) {
checkBatchEdit();
- if (null != mIC) mIC.setComposingText(text, i);
+ if (null != mIC) {
+ mIC.setComposingText(text, i);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.richInputConnection_setComposingText(text, i);
+ }
+ }
}
public void setSelection(final int from, final int to) {
checkBatchEdit();
- if (null != mIC) mIC.setSelection(from, to);
+ if (null != mIC) {
+ mIC.setSelection(from, to);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.richInputConnection_setSelection(from, to);
+ }
+ }
}
public void commitCorrection(final CorrectionInfo correctionInfo) {
checkBatchEdit();
- if (null != mIC) mIC.commitCorrection(correctionInfo);
+ if (null != mIC) {
+ mIC.commitCorrection(correctionInfo);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.richInputConnection_commitCorrection(correctionInfo);
+ }
+ }
}
public void commitCompletion(final CompletionInfo completionInfo) {
checkBatchEdit();
- if (null != mIC) mIC.commitCompletion(completionInfo);
+ if (null != mIC) {
+ mIC.commitCompletion(completionInfo);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.richInputConnection_commitCompletion(completionInfo);
+ }
+ }
}
public CharSequence getNthPreviousWord(final String sentenceSeperators, final int n) {
@@ -315,9 +364,6 @@
if (lastOne != null && lastOne.length() == 1
&& lastOne.charAt(0) == Keyboard.CODE_SPACE) {
deleteSurroundingText(1, 0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(1);
- }
}
}
@@ -382,13 +428,7 @@
return false;
}
deleteSurroundingText(2, 0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(2);
- }
commitText(" ", 1);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_revertDoubleSpaceWhileInBatchEdit();
- }
return true;
}
@@ -409,13 +449,7 @@
return false;
}
deleteSurroundingText(2, 0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_deleteSurroundingText(2);
- }
commitText(" " + textBeforeCursor.subSequence(0, 1), 1);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_revertSwapPunctuation();
- }
return true;
}
}
diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java
index 0843bdb..dcd2532 100644
--- a/java/src/com/android/inputmethod/latin/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -185,7 +185,7 @@
// Helper functions to create member values.
private static SuggestedWords createSuggestPuncList(final String[] puncs) {
- final ArrayList<SuggestedWordInfo> puncList = new ArrayList<SuggestedWordInfo>();
+ final ArrayList<SuggestedWordInfo> puncList = CollectionUtils.newArrayList();
if (puncs != null) {
for (final String puncSpec : puncs) {
puncList.add(new SuggestedWordInfo(KeySpecParser.getLabel(puncSpec),
@@ -417,6 +417,10 @@
prefs.edit().putString(Settings.PREF_LAST_USER_DICTIONARY_WRITE_TIME, newStr).apply();
}
+ public boolean isSameInputType(final EditorInfo editorInfo) {
+ return mInputAttributes.isSameInputType(editorInfo);
+ }
+
// For debug.
public String getInputAttributesDebugString() {
return mInputAttributes.toString();
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
index 6e7d985..39c59b4 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -53,7 +53,7 @@
if (TextUtils.isEmpty(csv)) return "";
final String[] elements = csv.split(",");
if (!containsInArray(key, elements)) return csv;
- final ArrayList<String> result = new ArrayList<String>(elements.length - 1);
+ final ArrayList<String> result = CollectionUtils.newArrayList(elements.length - 1);
for (final String element : elements) {
if (!key.equals(element)) result.add(element);
}
diff --git a/java/src/com/android/inputmethod/latin/SubtypeLocale.java b/java/src/com/android/inputmethod/latin/SubtypeLocale.java
index 21c9c0d..de5f515 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeLocale.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeLocale.java
@@ -45,13 +45,13 @@
private static String[] sPredefinedKeyboardLayoutSet;
// Keyboard layout to its display name map.
private static final HashMap<String, String> sKeyboardLayoutToDisplayNameMap =
- new HashMap<String, String>();
+ CollectionUtils.newHashMap();
// Keyboard layout to subtype name resource id map.
private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap =
- new HashMap<String, Integer>();
+ CollectionUtils.newHashMap();
// Exceptional locale to subtype name resource id map.
private static final HashMap<String, Integer> sExceptionalLocaleToWithLayoutNameIdsMap =
- new HashMap<String, Integer>();
+ CollectionUtils.newHashMap();
private static final String SUBTYPE_NAME_RESOURCE_GENERIC_PREFIX =
"string/subtype_generic_";
private static final String SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX =
@@ -60,11 +60,11 @@
"string/subtype_no_language_";
// Exceptional locales to display name map.
private static final HashMap<String, String> sExceptionalDisplayNamesMap =
- new HashMap<String, String>();
+ CollectionUtils.newHashMap();
// Keyboard layout set name for the subtypes that don't have a keyboardLayoutSet extra value.
// This is for compatibility to keep the same subtype ids as pre-JellyBean.
private static final HashMap<String,String> sLocaleAndExtraValueToKeyboardLayoutSetMap =
- new HashMap<String,String>();
+ CollectionUtils.newHashMap();
private SubtypeLocale() {
// Intentional empty constructor for utility class.
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index a7a5fcb..c693edc 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -22,6 +22,7 @@
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.inputmethodservice.InputMethodService;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
@@ -42,7 +43,6 @@
private static final String TAG = SubtypeSwitcher.class.getSimpleName();
private static final SubtypeSwitcher sInstance = new SubtypeSwitcher();
- private /* final */ LatinIME mService;
private /* final */ InputMethodManager mImm;
private /* final */ Resources mResources;
private /* final */ ConnectivityManager mConnectivityManager;
@@ -68,11 +68,11 @@
return mEnabledSubtypeCount >= 2 || !mIsSystemLanguageSameAsInputLanguage;
}
- public void updateEnabledSubtypeCount(int count) {
+ public void updateEnabledSubtypeCount(final int count) {
mEnabledSubtypeCount = count;
}
- public void updateIsSystemLanguageSameAsInputLanguage(boolean isSame) {
+ public void updateIsSystemLanguageSameAsInputLanguage(final boolean isSame) {
mIsSystemLanguageSameAsInputLanguage = isSame;
}
}
@@ -81,18 +81,17 @@
return sInstance;
}
- public static void init(LatinIME service) {
- SubtypeLocale.init(service);
- sInstance.initialize(service);
- sInstance.updateAllParameters();
+ public static void init(final Context context) {
+ SubtypeLocale.init(context);
+ sInstance.initialize(context);
+ sInstance.updateAllParameters(context);
}
private SubtypeSwitcher() {
// Intentional empty constructor for singleton.
}
- private void initialize(LatinIME service) {
- mService = service;
+ private void initialize(final Context service) {
mResources = service.getResources();
mImm = ImfUtils.getInputMethodManager(service);
mConnectivityManager = (ConnectivityManager) service.getSystemService(
@@ -111,39 +110,46 @@
// Update all parameters stored in SubtypeSwitcher.
// Only configuration changed event is allowed to call this because this is heavy.
- private void updateAllParameters() {
+ private void updateAllParameters(final Context context) {
mCurrentSystemLocale = mResources.getConfiguration().locale;
- updateSubtype(ImfUtils.getCurrentInputMethodSubtype(mService, mNoLanguageSubtype));
- updateParametersOnStartInputView();
+ updateSubtype(ImfUtils.getCurrentInputMethodSubtype(context, mNoLanguageSubtype));
+ updateParametersOnStartInputViewAndReturnIfCurrentSubtypeEnabled();
}
- // Update parameters which are changed outside LatinIME. This parameters affect UI so they
- // should be updated every time onStartInputview.
- public void updateParametersOnStartInputView() {
- updateEnabledSubtypes();
+ /**
+ * Update parameters which are changed outside LatinIME. This parameters affect UI so they
+ * should be updated every time onStartInputView.
+ *
+ * @return true if the current subtype is enabled.
+ */
+ public boolean updateParametersOnStartInputViewAndReturnIfCurrentSubtypeEnabled() {
+ final boolean currentSubtypeEnabled =
+ updateEnabledSubtypesAndReturnIfEnabled(mCurrentSubtype);
updateShortcutIME();
+ return currentSubtypeEnabled;
}
- // Reload enabledSubtypes from the framework.
- private void updateEnabledSubtypes() {
- final InputMethodSubtype currentSubtype = mCurrentSubtype;
- boolean foundCurrentSubtypeBecameDisabled = true;
+ /**
+ * Update enabled subtypes from the framework.
+ *
+ * @param subtype the subtype to be checked
+ * @return true if the {@code subtype} is enabled.
+ */
+ private boolean updateEnabledSubtypesAndReturnIfEnabled(final InputMethodSubtype subtype) {
final List<InputMethodSubtype> enabledSubtypesOfThisIme =
mImm.getEnabledInputMethodSubtypeList(null, true);
- for (InputMethodSubtype ims : enabledSubtypesOfThisIme) {
- if (ims.equals(currentSubtype)) {
- foundCurrentSubtypeBecameDisabled = false;
- }
- }
mNeedsToDisplayLanguage.updateEnabledSubtypeCount(enabledSubtypesOfThisIme.size());
- if (foundCurrentSubtypeBecameDisabled) {
- if (DBG) {
- Log.w(TAG, "Last subtype: "
- + currentSubtype.getLocale() + "/" + currentSubtype.getExtraValue());
- Log.w(TAG, "Last subtype was disabled. Update to the current one.");
+
+ for (final InputMethodSubtype ims : enabledSubtypesOfThisIme) {
+ if (ims.equals(subtype)) {
+ return true;
}
- updateSubtype(ImfUtils.getCurrentInputMethodSubtype(mService, mNoLanguageSubtype));
}
+ if (DBG) {
+ Log.w(TAG, "Subtype: " + subtype.getLocale() + "/" + subtype.getExtraValue()
+ + " was disabled");
+ }
+ return false;
}
private void updateShortcutIME() {
@@ -159,8 +165,8 @@
mImm.getShortcutInputMethodsAndSubtypes();
mShortcutInputMethodInfo = null;
mShortcutSubtype = null;
- for (InputMethodInfo imi : shortcuts.keySet()) {
- List<InputMethodSubtype> subtypes = shortcuts.get(imi);
+ for (final InputMethodInfo imi : shortcuts.keySet()) {
+ final List<InputMethodSubtype> subtypes = shortcuts.get(imi);
// TODO: Returns the first found IMI for now. Should handle all shortcuts as
// appropriate.
mShortcutInputMethodInfo = imi;
@@ -194,24 +200,24 @@
mCurrentSubtype = newSubtype;
updateShortcutIME();
- mService.onRefreshKeyboard();
}
////////////////////////////
// Shortcut IME functions //
////////////////////////////
- public void switchToShortcutIME() {
+ public void switchToShortcutIME(final InputMethodService context) {
if (mShortcutInputMethodInfo == null) {
return;
}
final String imiId = mShortcutInputMethodInfo.getId();
- switchToTargetIME(imiId, mShortcutSubtype);
+ switchToTargetIME(imiId, mShortcutSubtype, context);
}
- private void switchToTargetIME(final String imiId, final InputMethodSubtype subtype) {
- final IBinder token = mService.getWindow().getWindow().getAttributes().token;
+ private void switchToTargetIME(final String imiId, final InputMethodSubtype subtype,
+ final InputMethodService context) {
+ final IBinder token = context.getWindow().getWindow().getAttributes().token;
if (token == null) {
return;
}
@@ -253,7 +259,7 @@
return true;
}
- public void onNetworkStateChanged(Intent intent) {
+ public void onNetworkStateChanged(final Intent intent) {
final boolean noConnection = intent.getBooleanExtra(
ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
mIsNetworkConnected = !noConnection;
@@ -265,7 +271,7 @@
// Subtype Switching functions //
//////////////////////////////////
- public boolean needsToDisplayLanguage(Locale keyboardLocale) {
+ public boolean needsToDisplayLanguage(final Locale keyboardLocale) {
if (keyboardLocale.toString().equals(SubtypeLocale.NO_LANGUAGE)) {
return true;
}
@@ -279,12 +285,14 @@
return SubtypeLocale.getSubtypeLocale(mCurrentSubtype);
}
- public void onConfigurationChanged(Configuration conf) {
+ public boolean onConfigurationChanged(final Configuration conf, final Context context) {
final Locale systemLocale = conf.locale;
+ final boolean systemLocaleChanged = !systemLocale.equals(mCurrentSystemLocale);
// If system configuration was changed, update all parameters.
- if (!systemLocale.equals(mCurrentSystemLocale)) {
- updateAllParameters();
+ if (systemLocaleChanged) {
+ updateAllParameters(context);
}
+ return systemLocaleChanged;
}
public InputMethodSubtype getCurrentSubtype() {
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 5e2a041..51ed096 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -50,9 +50,8 @@
private Dictionary mMainDictionary;
private ContactsBinaryDictionary mContactsDict;
- private WhitelistDictionary mWhiteListDictionary;
private final ConcurrentHashMap<String, Dictionary> mDictionaries =
- new ConcurrentHashMap<String, Dictionary>();
+ CollectionUtils.newConcurrentHashMap();
public static final int MAX_SUGGESTIONS = 18;
@@ -60,13 +59,11 @@
// Locale used for upper- and title-casing words
private final Locale mLocale;
- private final SuggestInitializationListener mListener;
public Suggest(final Context context, final Locale locale,
final SuggestInitializationListener listener) {
- initAsynchronously(context, locale);
+ initAsynchronously(context, locale, listener);
mLocale = locale;
- mListener = listener;
}
/* package for test */ Suggest(final Context context, final File dictionary,
@@ -74,23 +71,13 @@
final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(context, dictionary,
startOffset, length /* useFullEditDistance */, false, locale);
mLocale = locale;
- mListener = null;
mMainDictionary = mainDict;
addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, mainDict);
- initWhitelistAndAutocorrectAndPool(context, locale);
}
- private void initWhitelistAndAutocorrectAndPool(final Context context, final Locale locale) {
- mWhiteListDictionary = new WhitelistDictionary(context, locale);
- addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_WHITELIST, mWhiteListDictionary);
- }
-
- private void initAsynchronously(final Context context, final Locale locale) {
- resetMainDict(context, locale);
-
- // TODO: read the whitelist and init the pool asynchronously too.
- // initPool should be done asynchronously now that the pool is thread-safe.
- initWhitelistAndAutocorrectAndPool(context, locale);
+ private void initAsynchronously(final Context context, final Locale locale,
+ final SuggestInitializationListener listener) {
+ resetMainDict(context, locale, listener);
}
private static void addOrReplaceDictionary(
@@ -104,10 +91,11 @@
}
}
- public void resetMainDict(final Context context, final Locale locale) {
+ public void resetMainDict(final Context context, final Locale locale,
+ final SuggestInitializationListener listener) {
mMainDictionary = null;
- if (mListener != null) {
- mListener.onUpdateMainDictionaryAvailability(hasMainDictionary());
+ if (listener != null) {
+ listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
}
new Thread("InitializeBinaryDictionary") {
@Override
@@ -116,8 +104,8 @@
DictionaryFactory.createMainDictionaryFromManager(context, locale);
addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, newMainDict);
mMainDictionary = newMainDict;
- if (mListener != null) {
- mListener.onUpdateMainDictionaryAvailability(hasMainDictionary());
+ if (listener != null) {
+ listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
}
}
}.start();
@@ -170,9 +158,17 @@
public SuggestedWords getSuggestedWords(
final WordComposer wordComposer, CharSequence prevWordForBigram,
final ProximityInfo proximityInfo, final boolean isCorrectionEnabled) {
+ return getSuggestedWordsWithSessionId(
+ wordComposer, prevWordForBigram, proximityInfo, isCorrectionEnabled, 0);
+ }
+
+ public SuggestedWords getSuggestedWordsWithSessionId(
+ final WordComposer wordComposer, CharSequence prevWordForBigram,
+ final ProximityInfo proximityInfo, final boolean isCorrectionEnabled, int sessionId) {
LatinImeLogger.onStartSuggestion(prevWordForBigram);
if (wordComposer.isBatchMode()) {
- return getSuggestedWordsForBatchInput(wordComposer, prevWordForBigram, proximityInfo);
+ return getSuggestedWordsForBatchInput(
+ wordComposer, prevWordForBigram, proximityInfo, sessionId);
} else {
return getSuggestedWordsForTypingInput(wordComposer, prevWordForBigram, proximityInfo,
isCorrectionEnabled);
@@ -209,23 +205,20 @@
wordComposerForLookup, prevWordForBigram, proximityInfo));
}
- // TODO: Change this scheme - a boolean is not enough. A whitelisted word may be "valid"
- // but still autocorrected from - in the case the whitelist only capitalizes the word.
- // The whitelist should be case-insensitive, so it's not possible to be consistent with
- // a boolean flag. Right now this is handled with a slight hack in
- // WhitelistDictionary#shouldForciblyAutoCorrectFrom.
- final boolean allowsToBeAutoCorrected = AutoCorrection.isWhitelistedOrNotAWord(
- mDictionaries, consideredWord, wordComposer.isFirstCharCapitalized());
-
- final CharSequence whitelistedWord =
- mWhiteListDictionary.getWhitelistedWord(consideredWord);
- if (whitelistedWord != null) {
- // MAX_SCORE ensures this will be considered strong enough to be auto-corrected
- suggestionsSet.add(new SuggestedWordInfo(whitelistedWord,
- SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_WHITELIST,
- Dictionary.TYPE_WHITELIST));
+ final CharSequence whitelistedWord;
+ if (suggestionsSet.isEmpty()) {
+ whitelistedWord = null;
+ } else if (SuggestedWordInfo.KIND_WHITELIST != suggestionsSet.first().mKind) {
+ whitelistedWord = null;
+ } else {
+ whitelistedWord = suggestionsSet.first().mWord;
}
+ final boolean allowsToBeAutoCorrected = (null != whitelistedWord
+ && !whitelistedWord.equals(consideredWord))
+ || AutoCorrection.isNotAWord(mDictionaries, consideredWord,
+ wordComposer.isFirstCharCapitalized());
+
final boolean hasAutoCorrection;
// TODO: using isCorrectionEnabled here is not very good. It's probably useless, because
// any attempt to do auto-correction is already shielded with a test for this flag; at the
@@ -249,7 +242,7 @@
}
final ArrayList<SuggestedWordInfo> suggestionsContainer =
- new ArrayList<SuggestedWordInfo>(suggestionsSet);
+ CollectionUtils.newArrayList(suggestionsSet);
final int suggestionsCount = suggestionsContainer.size();
final boolean isFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
final boolean isAllUpperCase = wordComposer.isAllUpperCase();
@@ -296,29 +289,28 @@
// Retrieves suggestions for the batch input.
private SuggestedWords getSuggestedWordsForBatchInput(
final WordComposer wordComposer, CharSequence prevWordForBigram,
- final ProximityInfo proximityInfo) {
+ final ProximityInfo proximityInfo, int sessionId) {
final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator,
MAX_SUGGESTIONS);
// At second character typed, search the unigrams (scores being affected by bigrams)
for (final String key : mDictionaries.keySet()) {
- // Skip UserUnigramDictionary and WhitelistDictionary to lookup
- if (key.equals(Dictionary.TYPE_USER_HISTORY)
- || key.equals(Dictionary.TYPE_WHITELIST)) {
+ // Skip User history dictionary for lookup
+ // TODO: The user history dictionary should just override getSuggestionsWithSessionId
+ // to make sure it doesn't return anything and we should remove this test
+ if (key.equals(Dictionary.TYPE_USER_HISTORY)) {
continue;
}
final Dictionary dictionary = mDictionaries.get(key);
- suggestionsSet.addAll(dictionary.getSuggestions(
- wordComposer, prevWordForBigram, proximityInfo));
+ suggestionsSet.addAll(dictionary.getSuggestionsWithSessionId(
+ wordComposer, prevWordForBigram, proximityInfo, sessionId));
}
final ArrayList<SuggestedWordInfo> suggestionsContainer =
- new ArrayList<SuggestedWordInfo>(suggestionsSet);
+ CollectionUtils.newArrayList(suggestionsSet);
final int suggestionsCount = suggestionsContainer.size();
- final boolean isFirstCharCapitalized = wordComposer.isAutoCapitalized();
- // TODO: Handle the manual temporary shifted mode.
- // TODO: Should handle TextUtils.CAP_MODE_CHARACTER.
- final boolean isAllUpperCase = false;
+ final boolean isFirstCharCapitalized = wordComposer.wasShiftedNoLock();
+ final boolean isAllUpperCase = wordComposer.isAllUpperCase();
if (isFirstCharCapitalized || isAllUpperCase) {
for (int i = 0; i < suggestionsCount; ++i) {
final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
@@ -346,7 +338,7 @@
typedWordInfo.setDebugString("+");
final int suggestionsSize = suggestions.size();
final ArrayList<SuggestedWordInfo> suggestionsList =
- new ArrayList<SuggestedWordInfo>(suggestionsSize);
+ CollectionUtils.newArrayList(suggestionsSize);
suggestionsList.add(typedWordInfo);
// Note: i here is the index in mScores[], but the index in mSuggestions is one more
// than i because we added the typed word to mSuggestions without touching mScores.
@@ -399,7 +391,7 @@
}
public void close() {
- final HashSet<Dictionary> dictionaries = new HashSet<Dictionary>();
+ final HashSet<Dictionary> dictionaries = CollectionUtils.newHashSet();
dictionaries.addAll(mDictionaries.values());
for (final Dictionary dictionary : dictionaries) {
dictionary.close();
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index 88fc006..68ecfa0 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -24,8 +24,10 @@
import java.util.HashSet;
public class SuggestedWords {
+ private static final ArrayList<SuggestedWordInfo> EMPTY_WORD_INFO_LIST =
+ CollectionUtils.newArrayList(0);
public static final SuggestedWords EMPTY = new SuggestedWords(
- new ArrayList<SuggestedWordInfo>(0), false, false, false, false, false);
+ EMPTY_WORD_INFO_LIST, false, false, false, false, false);
public final boolean mTypedWordValid;
// Note: this INCLUDES cases where the word will auto-correct to itself. A good definition
@@ -83,7 +85,7 @@
public static ArrayList<SuggestedWordInfo> getFromApplicationSpecifiedCompletions(
final CompletionInfo[] infos) {
- final ArrayList<SuggestedWordInfo> result = new ArrayList<SuggestedWordInfo>();
+ final ArrayList<SuggestedWordInfo> result = CollectionUtils.newArrayList();
for (CompletionInfo info : infos) {
if (null != info && info.getText() != null) {
result.add(new SuggestedWordInfo(info.getText(), SuggestedWordInfo.MAX_SCORE,
@@ -97,8 +99,8 @@
// and replace it with what the user currently typed.
public static ArrayList<SuggestedWordInfo> getTypedWordAndPreviousSuggestions(
final CharSequence typedWord, final SuggestedWords previousSuggestions) {
- final ArrayList<SuggestedWordInfo> suggestionsList = new ArrayList<SuggestedWordInfo>();
- final HashSet<String> alreadySeen = new HashSet<String>();
+ final ArrayList<SuggestedWordInfo> suggestionsList = CollectionUtils.newArrayList();
+ final HashSet<String> alreadySeen = CollectionUtils.newHashSet();
suggestionsList.add(new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE,
SuggestedWordInfo.KIND_TYPED, Dictionary.TYPE_USER_TYPED));
alreadySeen.add(typedWord.toString());
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
index 3bb670c..6c9d1c2 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
@@ -52,14 +52,14 @@
private static final int FREQUENCY_FOR_TYPED = 2;
/** Maximum number of pairs. Pruning will start when databases goes above this number. */
- private static int sMaxHistoryBigrams = 10000;
+ public static final int sMaxHistoryBigrams = 10000;
/**
* When it hits maximum bigram pair, it will delete until you are left with
* only (sMaxHistoryBigrams - sDeleteHistoryBigrams) pairs.
* Do not keep this number small to avoid deleting too often.
*/
- private static int sDeleteHistoryBigrams = 1000;
+ public static final int sDeleteHistoryBigrams = 1000;
/**
* Database version should increase if the database structure changes
@@ -93,10 +93,10 @@
private final static HashMap<String, String> sDictProjectionMap;
private final static ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>
- sLangDictCache = new ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>();
+ sLangDictCache = CollectionUtils.newConcurrentHashMap();
static {
- sDictProjectionMap = new HashMap<String, String>();
+ sDictProjectionMap = CollectionUtils.newHashMap();
sDictProjectionMap.put(MAIN_COLUMN_ID, MAIN_COLUMN_ID);
sDictProjectionMap.put(MAIN_COLUMN_WORD1, MAIN_COLUMN_WORD1);
sDictProjectionMap.put(MAIN_COLUMN_WORD2, MAIN_COLUMN_WORD2);
@@ -109,12 +109,8 @@
private static DatabaseHelper sOpenHelper = null;
- public void setDatabaseMax(int maxHistoryBigram) {
- sMaxHistoryBigrams = maxHistoryBigram;
- }
-
- public void setDatabaseDelete(int deleteHistoryBigram) {
- sDeleteHistoryBigrams = deleteHistoryBigram;
+ public String getLocale() {
+ return mLocale;
}
public synchronized static UserHistoryDictionary getInstance(
@@ -502,9 +498,11 @@
needsToSave(fc, isValid, addLevel0Bigram)) {
freq = fc;
} else {
+ // Delete this entry
freq = -1;
}
} else {
+ // Delete this entry
freq = -1;
}
}
@@ -541,6 +539,7 @@
getContentValues(word1, word2, mLocale));
pairId = pairIdLong.intValue();
}
+ // Eliminate freq == 0 because that word is profanity.
if (freq > 0) {
if (PROFILE_SAVE_RESTORE) {
++profInsert;
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java
index 610652a..bb0f542 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java
@@ -29,9 +29,8 @@
public class UserHistoryDictionaryBigramList {
public static final byte FORGETTING_CURVE_INITIAL_VALUE = 0;
private static final String TAG = UserHistoryDictionaryBigramList.class.getSimpleName();
- private static final HashMap<String, Byte> EMPTY_BIGRAM_MAP = new HashMap<String, Byte>();
- private final HashMap<String, HashMap<String, Byte>> mBigramMap =
- new HashMap<String, HashMap<String, Byte>>();
+ private static final HashMap<String, Byte> EMPTY_BIGRAM_MAP = CollectionUtils.newHashMap();
+ private final HashMap<String, HashMap<String, Byte>> mBigramMap = CollectionUtils.newHashMap();
private int mSize = 0;
public void evictAll() {
@@ -57,7 +56,7 @@
if (mBigramMap.containsKey(word1)) {
map = mBigramMap.get(word1);
} else {
- map = new HashMap<String, Byte>();
+ map = CollectionUtils.newHashMap();
mBigramMap.put(word1, map);
}
if (!map.containsKey(word2)) {
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java b/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java
index 1de95d7..5a2fdf4 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java
@@ -212,7 +212,7 @@
for (int j = 0; j < ELAPSED_TIME_MAX; ++j) {
final float elapsedHours = j * ELAPSED_TIME_INTERVAL_HOURS;
final float freq = initialFreq
- * NativeUtils.powf(initialFreq, elapsedHours / HALF_LIFE_HOURS);
+ * (float)Math.pow(initialFreq, elapsedHours / HALF_LIFE_HOURS);
final int intFreq = Math.min(FC_FREQ_MAX, Math.max(0, (int)freq));
SCORE_TABLE[i][j] = intFreq;
}
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index c6b5c33..fc7a421 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -65,44 +65,6 @@
}
}
- public static class GCUtils {
- private static final String GC_TAG = GCUtils.class.getSimpleName();
- public static final int GC_TRY_COUNT = 2;
- // GC_TRY_LOOP_MAX is used for the hard limit of GC wait,
- // GC_TRY_LOOP_MAX should be greater than GC_TRY_COUNT.
- public static final int GC_TRY_LOOP_MAX = 5;
- private static final long GC_INTERVAL = DateUtils.SECOND_IN_MILLIS;
- private static GCUtils sInstance = new GCUtils();
- private int mGCTryCount = 0;
-
- public static GCUtils getInstance() {
- return sInstance;
- }
-
- public void reset() {
- mGCTryCount = 0;
- }
-
- public boolean tryGCOrWait(String metaData, Throwable t) {
- if (mGCTryCount == 0) {
- System.gc();
- }
- if (++mGCTryCount > GC_TRY_COUNT) {
- LatinImeLogger.logOnException(metaData, t);
- return false;
- } else {
- try {
- Thread.sleep(GC_INTERVAL);
- return true;
- } catch (InterruptedException e) {
- Log.e(GC_TAG, "Sleep was interrupted.");
- LatinImeLogger.logOnException(metaData, t);
- return false;
- }
- }
- }
- }
-
/* package */ static class RingCharBuffer {
private static RingCharBuffer sRingCharBuffer = new RingCharBuffer();
private static final char PLACEHOLDER_DELIMITER_CHAR = '\uFFFC';
@@ -477,7 +439,7 @@
private static final String HARDWARE_PREFIX = Build.HARDWARE + ",";
private static final HashMap<String, String> sDeviceOverrideValueMap =
- new HashMap<String, String>();
+ CollectionUtils.newHashMap();
public static String getDeviceOverrideValue(Resources res, int overrideResId, String defValue) {
final int orientation = res.getConfiguration().orientation;
@@ -495,7 +457,7 @@
return sDeviceOverrideValueMap.get(key);
}
- private static final HashMap<String, Long> EMPTY_LT_HASH_MAP = new HashMap<String, Long>();
+ private static final HashMap<String, Long> EMPTY_LT_HASH_MAP = CollectionUtils.newHashMap();
private static final String LOCALE_AND_TIME_STR_SEPARATER = ",";
public static HashMap<String, Long> localeAndTimeStrToHashMap(String str) {
if (TextUtils.isEmpty(str)) {
@@ -506,7 +468,7 @@
if (N < 2 || N % 2 != 0) {
return EMPTY_LT_HASH_MAP;
}
- final HashMap<String, Long> retval = new HashMap<String, Long>();
+ final HashMap<String, Long> retval = CollectionUtils.newHashMap();
for (int i = 0; i < N / 2; ++i) {
final String localeStr = ss[i * 2];
final long time = Long.valueOf(ss[i * 2 + 1]);
diff --git a/java/src/com/android/inputmethod/latin/WhitelistDictionary.java b/java/src/com/android/inputmethod/latin/WhitelistDictionary.java
deleted file mode 100644
index 14476dc..0000000
--- a/java/src/com/android/inputmethod/latin/WhitelistDictionary.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.latin;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.inputmethod.keyboard.ProximityInfo;
-import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Locale;
-
-public class WhitelistDictionary extends ExpandableDictionary {
-
- private static final boolean DBG = LatinImeLogger.sDBG;
- private static final String TAG = WhitelistDictionary.class.getSimpleName();
-
- private final HashMap<String, Pair<Integer, String>> mWhitelistWords =
- new HashMap<String, Pair<Integer, String>>();
-
- // TODO: Conform to the async load contact of ExpandableDictionary
- public WhitelistDictionary(final Context context, final Locale locale) {
- super(context, Dictionary.TYPE_WHITELIST);
- // TODO: Move whitelist dictionary into main dictionary.
- final RunInLocale<Void> job = new RunInLocale<Void>() {
- @Override
- protected Void job(Resources res) {
- initWordlist(res.getStringArray(R.array.wordlist_whitelist));
- return null;
- }
- };
- job.runInLocale(context.getResources(), locale);
- }
-
- private void initWordlist(String[] wordlist) {
- mWhitelistWords.clear();
- final int N = wordlist.length;
- if (N % 3 != 0) {
- if (DBG) {
- Log.d(TAG, "The number of the whitelist is invalid.");
- }
- return;
- }
- try {
- for (int i = 0; i < N; i += 3) {
- final int score = Integer.valueOf(wordlist[i]);
- final String before = wordlist[i + 1];
- final String after = wordlist[i + 2];
- if (before != null && after != null) {
- mWhitelistWords.put(
- before.toLowerCase(), new Pair<Integer, String>(score, after));
- addWord(after, null /* shortcut */, score);
- }
- }
- } catch (NumberFormatException e) {
- if (DBG) {
- Log.d(TAG, "The score of the word is invalid.");
- }
- }
- }
-
- public String getWhitelistedWord(String before) {
- if (before == null) return null;
- final String lowerCaseBefore = before.toLowerCase();
- if(mWhitelistWords.containsKey(lowerCaseBefore)) {
- if (DBG) {
- Log.d(TAG, "--- found whitelistedWord: " + lowerCaseBefore);
- }
- return mWhitelistWords.get(lowerCaseBefore).second;
- }
- return null;
- }
-
- @Override
- public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final CharSequence prevWord, final ProximityInfo proximityInfo) {
- // Whitelist does not supply any suggestions or predictions.
- return null;
- }
-
- // See LatinIME#updateSuggestions. This breaks in the (queer) case that the whitelist
- // lists that word a should autocorrect to word b, and word c would autocorrect to
- // an upper-cased version of a. In this case, the way this return value is used would
- // remove the first candidate when the user typed the upper-cased version of A.
- // Example : abc -> def and xyz -> Abc
- // A user typing Abc would experience it being autocorrected to something else (not
- // necessarily def).
- // There is no such combination in the whitelist at the time and there probably won't
- // ever be - it doesn't make sense. But still.
- public boolean shouldForciblyAutoCorrectFrom(CharSequence word) {
- if (TextUtils.isEmpty(word)) return false;
- final String correction = getWhitelistedWord(word.toString());
- if (TextUtils.isEmpty(correction)) return false;
- return !correction.equals(word);
- }
-
- // Leave implementation of getWords and isValidWord to the superclass.
- // The words have been added to the ExpandableDictionary with addWord() inside initWordlist.
-}
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 5606a58..ecec60f 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -17,7 +17,6 @@
package com.android.inputmethod.latin;
import com.android.inputmethod.keyboard.Key;
-import com.android.inputmethod.keyboard.KeyDetector;
import com.android.inputmethod.keyboard.Keyboard;
import java.util.Arrays;
@@ -26,12 +25,16 @@
* A place to store the currently composing word with information such as adjacent key codes as well
*/
public class WordComposer {
-
- public static final int NOT_A_CODE = KeyDetector.NOT_A_CODE;
- public static final int NOT_A_COORDINATE = -1;
-
private static final int N = BinaryDictionary.MAX_WORD_LENGTH;
+ public static final int CAPS_MODE_OFF = 0;
+ // 1 is shift bit, 2 is caps bit, 4 is auto bit but this is just a convention as these bits
+ // aren't used anywhere in the code
+ public static final int CAPS_MODE_MANUAL_SHIFTED = 0x1;
+ public static final int CAPS_MODE_MANUAL_SHIFT_LOCKED = 0x3;
+ public static final int CAPS_MODE_AUTO_SHIFTED = 0x5;
+ public static final int CAPS_MODE_AUTO_SHIFT_LOCKED = 0x7;
+
private int[] mPrimaryKeyCodes;
private final InputPointers mInputPointers = new InputPointers(N);
private final StringBuilder mTypedWord;
@@ -42,7 +45,7 @@
// Cache these values for performance
private int mCapsCount;
private int mDigitsCount;
- private boolean mAutoCapitalized;
+ private int mCapitalizedMode;
private int mTrailingSingleQuotesCount;
private int mCodePointSize;
@@ -68,7 +71,7 @@
mCapsCount = source.mCapsCount;
mDigitsCount = source.mDigitsCount;
mIsFirstCharCapitalized = source.mIsFirstCharCapitalized;
- mAutoCapitalized = source.mAutoCapitalized;
+ mCapitalizedMode = source.mCapitalizedMode;
mTrailingSingleQuotesCount = source.mTrailingSingleQuotesCount;
mIsResumed = source.mIsResumed;
mIsBatchMode = source.mIsBatchMode;
@@ -166,7 +169,7 @@
final int codePoint = Character.codePointAt(word, i);
// We don't want to override the batch input points that are held in mInputPointers
// (See {@link #add(int,int,int)}).
- add(codePoint, NOT_A_COORDINATE, NOT_A_COORDINATE);
+ add(codePoint, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
}
}
@@ -181,7 +184,7 @@
add(codePoint, x, y);
return;
}
- add(codePoint, NOT_A_COORDINATE, NOT_A_COORDINATE);
+ add(codePoint, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
}
/**
@@ -262,7 +265,14 @@
* @return true if all user typed chars are upper case, false otherwise
*/
public boolean isAllUpperCase() {
- return (mCapsCount > 0) && (mCapsCount == size());
+ return mCapitalizedMode == CAPS_MODE_AUTO_SHIFT_LOCKED
+ || mCapitalizedMode == CAPS_MODE_MANUAL_SHIFT_LOCKED
+ || (mCapsCount > 0) && (mCapsCount == size());
+ }
+
+ public boolean wasShiftedNoLock() {
+ return mCapitalizedMode == CAPS_MODE_AUTO_SHIFTED
+ || mCapitalizedMode == CAPS_MODE_MANUAL_SHIFTED;
}
/**
@@ -280,20 +290,27 @@
}
/**
- * Saves the reason why the word is capitalized - whether it was automatic or
- * due to the user hitting shift in the middle of a sentence.
- * @param auto whether it was an automatic capitalization due to start of sentence
+ * Saves the caps mode at the start of composing.
+ *
+ * WordComposer needs to know about this for several reasons. The first is, we need to know
+ * after the fact what the reason was, to register the correct form into the user history
+ * dictionary: if the word was automatically capitalized, we should insert it in all-lower
+ * case but if it's a manual pressing of shift, then it should be inserted as is.
+ * Also, batch input needs to know about the current caps mode to display correctly
+ * capitalized suggestions.
+ * @param mode the mode at the time of start
*/
- public void setAutoCapitalized(boolean auto) {
- mAutoCapitalized = auto;
+ public void setCapitalizedModeAtStartComposingTime(final int mode) {
+ mCapitalizedMode = mode;
}
/**
* Returns whether the word was automatically capitalized.
* @return whether the word was automatically capitalized
*/
- public boolean isAutoCapitalized() {
- return mAutoCapitalized;
+ public boolean wasAutoCapitalized() {
+ return mCapitalizedMode == CAPS_MODE_AUTO_SHIFT_LOCKED
+ || mCapitalizedMode == CAPS_MODE_AUTO_SHIFTED;
}
/**
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
index 2c3eee7..161b94c 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
@@ -22,10 +22,13 @@
import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
-import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -124,7 +127,7 @@
*/
private static final int VERSION_1_MAGIC_NUMBER = 0x78B1;
- private static final int VERSION_2_MAGIC_NUMBER = 0x9BC13AFE;
+ public static final int VERSION_2_MAGIC_NUMBER = 0x9BC13AFE;
private static final int MINIMUM_SUPPORTED_VERSION = 1;
private static final int MAXIMUM_SUPPORTED_VERSION = 2;
private static final int NOT_A_VERSION_NUMBER = -1;
@@ -307,33 +310,32 @@
}
/**
- * Reads a string from a RandomAccessFile. This is the converse of the above method.
+ * Reads a string from a ByteBuffer. This is the converse of the above method.
*/
- private static String readString(final RandomAccessFile source) throws IOException {
+ private static String readString(final ByteBuffer buffer) {
final StringBuilder s = new StringBuilder();
- int character = readChar(source);
+ int character = readChar(buffer);
while (character != INVALID_CHARACTER) {
s.appendCodePoint(character);
- character = readChar(source);
+ character = readChar(buffer);
}
return s.toString();
}
/**
- * Reads a character from the file.
+ * Reads a character from the ByteBuffer.
*
* This follows the character format documented earlier in this source file.
*
- * @param source the file, positioned over an encoded character.
+ * @param buffer the buffer, positioned over an encoded character.
* @return the character code.
*/
- private static int readChar(RandomAccessFile source) throws IOException {
- int character = source.readUnsignedByte();
+ private static int readChar(final ByteBuffer buffer) {
+ int character = readUnsignedByte(buffer);
if (!fitsOnOneByte(character)) {
- if (GROUP_CHARACTERS_TERMINATOR == character)
- return INVALID_CHARACTER;
+ if (GROUP_CHARACTERS_TERMINATOR == character) return INVALID_CHARACTER;
character <<= 16;
- character += source.readUnsignedShort();
+ character += readUnsignedShort(buffer);
}
return character;
}
@@ -783,10 +785,10 @@
// their lower bound and exclude their higher bound so we need to have the first step
// start at exactly 1 unit higher than floor(unigramFreq + half a step).
// Note : to reconstruct the score, the dictionary reader will need to divide
- // MAX_TERMINAL_FREQUENCY - unigramFreq by 16.5 likewise, and add
- // (discretizedFrequency + 0.5) times this value to get the median value of the step,
- // which is the best approximation. This is how we get the most precise result with
- // only four bits.
+ // MAX_TERMINAL_FREQUENCY - unigramFreq by 16.5 likewise to get the value of the step,
+ // and add (discretizedFrequency + 0.5 + 0.5) times this value to get the best
+ // approximation. (0.5 to get the first step start, and 0.5 to get the middle of the
+ // step pointed by the discretized frequency.
final float stepSize =
(MAX_TERMINAL_FREQUENCY - unigramFrequency) / (1.5f + MAX_BIGRAM_FREQUENCY);
final float firstStepStart = 1 + unigramFrequency + (stepSize / 2.0f);
@@ -1091,46 +1093,46 @@
// readDictionaryBinary is the public entry point for them.
static final int[] characterBuffer = new int[MAX_WORD_LENGTH];
- private static CharGroupInfo readCharGroup(RandomAccessFile source,
- final int originalGroupAddress) throws IOException {
+ private static CharGroupInfo readCharGroup(final ByteBuffer buffer,
+ final int originalGroupAddress) {
int addressPointer = originalGroupAddress;
- final int flags = source.readUnsignedByte();
+ final int flags = readUnsignedByte(buffer);
++addressPointer;
final int characters[];
if (0 != (flags & FLAG_HAS_MULTIPLE_CHARS)) {
int index = 0;
- int character = CharEncoding.readChar(source);
+ int character = CharEncoding.readChar(buffer);
addressPointer += CharEncoding.getCharSize(character);
while (-1 != character) {
characterBuffer[index++] = character;
- character = CharEncoding.readChar(source);
+ character = CharEncoding.readChar(buffer);
addressPointer += CharEncoding.getCharSize(character);
}
characters = Arrays.copyOfRange(characterBuffer, 0, index);
} else {
- final int character = CharEncoding.readChar(source);
+ final int character = CharEncoding.readChar(buffer);
addressPointer += CharEncoding.getCharSize(character);
characters = new int[] { character };
}
final int frequency;
if (0 != (FLAG_IS_TERMINAL & flags)) {
++addressPointer;
- frequency = source.readUnsignedByte();
+ frequency = readUnsignedByte(buffer);
} else {
frequency = CharGroup.NOT_A_TERMINAL;
}
int childrenAddress = addressPointer;
switch (flags & MASK_GROUP_ADDRESS_TYPE) {
case FLAG_GROUP_ADDRESS_TYPE_ONEBYTE:
- childrenAddress += source.readUnsignedByte();
+ childrenAddress += readUnsignedByte(buffer);
addressPointer += 1;
break;
case FLAG_GROUP_ADDRESS_TYPE_TWOBYTES:
- childrenAddress += source.readUnsignedShort();
+ childrenAddress += readUnsignedShort(buffer);
addressPointer += 2;
break;
case FLAG_GROUP_ADDRESS_TYPE_THREEBYTES:
- childrenAddress += (source.readUnsignedByte() << 16) + source.readUnsignedShort();
+ childrenAddress += readUnsignedInt24(buffer);
addressPointer += 3;
break;
case FLAG_GROUP_ADDRESS_TYPE_NOADDRESS:
@@ -1140,38 +1142,38 @@
}
ArrayList<WeightedString> shortcutTargets = null;
if (0 != (flags & FLAG_HAS_SHORTCUT_TARGETS)) {
- final long pointerBefore = source.getFilePointer();
+ final int pointerBefore = buffer.position();
shortcutTargets = new ArrayList<WeightedString>();
- source.readUnsignedShort(); // Skip the size
+ buffer.getShort(); // Skip the size
while (true) {
- final int targetFlags = source.readUnsignedByte();
- final String word = CharEncoding.readString(source);
+ final int targetFlags = readUnsignedByte(buffer);
+ final String word = CharEncoding.readString(buffer);
shortcutTargets.add(new WeightedString(word,
targetFlags & FLAG_ATTRIBUTE_FREQUENCY));
if (0 == (targetFlags & FLAG_ATTRIBUTE_HAS_NEXT)) break;
}
- addressPointer += (source.getFilePointer() - pointerBefore);
+ addressPointer += buffer.position() - pointerBefore;
}
ArrayList<PendingAttribute> bigrams = null;
if (0 != (flags & FLAG_HAS_BIGRAMS)) {
bigrams = new ArrayList<PendingAttribute>();
while (true) {
- final int bigramFlags = source.readUnsignedByte();
+ final int bigramFlags = readUnsignedByte(buffer);
++addressPointer;
final int sign = 0 == (bigramFlags & FLAG_ATTRIBUTE_OFFSET_NEGATIVE) ? 1 : -1;
int bigramAddress = addressPointer;
switch (bigramFlags & MASK_ATTRIBUTE_ADDRESS_TYPE) {
case FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE:
- bigramAddress += sign * source.readUnsignedByte();
+ bigramAddress += sign * readUnsignedByte(buffer);
addressPointer += 1;
break;
case FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES:
- bigramAddress += sign * source.readUnsignedShort();
+ bigramAddress += sign * readUnsignedShort(buffer);
addressPointer += 2;
break;
case FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES:
- final int offset = ((source.readUnsignedByte() << 16)
- + source.readUnsignedShort());
+ final int offset = (readUnsignedByte(buffer) << 16)
+ + readUnsignedShort(buffer);
bigramAddress += sign * offset;
addressPointer += 3;
break;
@@ -1188,15 +1190,15 @@
}
/**
- * Reads and returns the char group count out of a file and forwards the pointer.
+ * Reads and returns the char group count out of a buffer and forwards the pointer.
*/
- private static int readCharGroupCount(RandomAccessFile source) throws IOException {
- final int msb = source.readUnsignedByte();
+ private static int readCharGroupCount(final ByteBuffer buffer) {
+ final int msb = readUnsignedByte(buffer);
if (MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT >= msb) {
return msb;
} else {
return ((MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT & msb) << 8)
- + source.readUnsignedByte();
+ + readUnsignedByte(buffer);
}
}
@@ -1204,31 +1206,29 @@
// of this method. Since it performs direct, unbuffered random access to the file and
// may be called hundreds of thousands of times, the resulting performance is not
// reasonable without some kind of cache. Thus:
- // TODO: perform buffered I/O here and in other places in the code.
private static TreeMap<Integer, String> wordCache = new TreeMap<Integer, String>();
/**
* Finds, as a string, the word at the address passed as an argument.
*
- * @param source the file to read from.
+ * @param buffer the buffer to read from.
* @param headerSize the size of the header.
* @param address the address to seek.
* @return the word, as a string.
- * @throws IOException if the file can't be read.
*/
- private static String getWordAtAddress(final RandomAccessFile source, final long headerSize,
- int address) throws IOException {
+ private static String getWordAtAddress(final ByteBuffer buffer, final int headerSize,
+ final int address) {
final String cachedString = wordCache.get(address);
if (null != cachedString) return cachedString;
- final long originalPointer = source.getFilePointer();
- source.seek(headerSize);
- final int count = readCharGroupCount(source);
+ final int originalPointer = buffer.position();
+ buffer.position(headerSize);
+ final int count = readCharGroupCount(buffer);
int groupOffset = getGroupCountSize(count);
final StringBuilder builder = new StringBuilder();
String result = null;
CharGroupInfo last = null;
for (int i = count - 1; i >= 0; --i) {
- CharGroupInfo info = readCharGroup(source, groupOffset);
+ CharGroupInfo info = readCharGroup(buffer, groupOffset);
groupOffset = info.mEndAddress;
if (info.mOriginalAddress == address) {
builder.append(new String(info.mCharacters, 0, info.mCharacters.length));
@@ -1239,9 +1239,9 @@
if (info.mChildrenAddress > address) {
if (null == last) continue;
builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
- source.seek(last.mChildrenAddress + headerSize);
+ buffer.position(last.mChildrenAddress + headerSize);
groupOffset = last.mChildrenAddress + 1;
- i = source.readUnsignedByte();
+ i = readUnsignedByte(buffer);
last = null;
continue;
}
@@ -1249,14 +1249,14 @@
}
if (0 == i && hasChildrenAddress(last.mChildrenAddress)) {
builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
- source.seek(last.mChildrenAddress + headerSize);
+ buffer.position(last.mChildrenAddress + headerSize);
groupOffset = last.mChildrenAddress + 1;
- i = source.readUnsignedByte();
+ i = readUnsignedByte(buffer);
last = null;
continue;
}
}
- source.seek(originalPointer);
+ buffer.position(originalPointer);
wordCache.put(address, result);
return result;
}
@@ -1269,44 +1269,47 @@
* This will recursively read other nodes into the structure, populating the reverse
* maps on the fly and using them to keep track of already read nodes.
*
- * @param source the data file, correctly positioned at the start of a node.
+ * @param buffer the buffer, correctly positioned at the start of a node.
* @param headerSize the size, in bytes, of the file header.
* @param reverseNodeMap a mapping from addresses to already read nodes.
* @param reverseGroupMap a mapping from addresses to already read character groups.
* @return the read node with all his children already read.
*/
- private static Node readNode(RandomAccessFile source, long headerSize,
- Map<Integer, Node> reverseNodeMap, Map<Integer, CharGroup> reverseGroupMap)
+ private static Node readNode(final ByteBuffer buffer, final int headerSize,
+ final Map<Integer, Node> reverseNodeMap, final Map<Integer, CharGroup> reverseGroupMap)
throws IOException {
- final int nodeOrigin = (int)(source.getFilePointer() - headerSize);
- final int count = readCharGroupCount(source);
+ final int nodeOrigin = buffer.position() - headerSize;
+ final int count = readCharGroupCount(buffer);
final ArrayList<CharGroup> nodeContents = new ArrayList<CharGroup>();
int groupOffset = nodeOrigin + getGroupCountSize(count);
for (int i = count; i > 0; --i) {
- CharGroupInfo info = readCharGroup(source, groupOffset);
+ CharGroupInfo info =readCharGroup(buffer, groupOffset);
ArrayList<WeightedString> shortcutTargets = info.mShortcutTargets;
ArrayList<WeightedString> bigrams = null;
if (null != info.mBigrams) {
bigrams = new ArrayList<WeightedString>();
for (PendingAttribute bigram : info.mBigrams) {
- final String word = getWordAtAddress(source, headerSize, bigram.mAddress);
+ final String word = getWordAtAddress(
+ buffer, headerSize, bigram.mAddress);
bigrams.add(new WeightedString(word, bigram.mFrequency));
}
}
if (hasChildrenAddress(info.mChildrenAddress)) {
Node children = reverseNodeMap.get(info.mChildrenAddress);
if (null == children) {
- final long currentPosition = source.getFilePointer();
- source.seek(info.mChildrenAddress + headerSize);
- children = readNode(source, headerSize, reverseNodeMap, reverseGroupMap);
- source.seek(currentPosition);
+ final int currentPosition = buffer.position();
+ buffer.position(info.mChildrenAddress + headerSize);
+ children = readNode(
+ buffer, headerSize, reverseNodeMap, reverseGroupMap);
+ buffer.position(currentPosition);
}
nodeContents.add(
- new CharGroup(info.mCharacters, shortcutTargets, bigrams, info.mFrequency,
- children));
+ new CharGroup(info.mCharacters, shortcutTargets,
+ bigrams, info.mFrequency, children));
} else {
nodeContents.add(
- new CharGroup(info.mCharacters, shortcutTargets, bigrams, info.mFrequency));
+ new CharGroup(info.mCharacters, shortcutTargets,
+ bigrams, info.mFrequency));
}
groupOffset = info.mEndAddress;
}
@@ -1318,57 +1321,76 @@
/**
* Helper function to get the binary format version from the header.
+ * @throws IOException
*/
- private static int getFormatVersion(final RandomAccessFile source) throws IOException {
- final int magic_v1 = source.readUnsignedShort();
- if (VERSION_1_MAGIC_NUMBER == magic_v1) return source.readUnsignedByte();
- final int magic_v2 = (magic_v1 << 16) + source.readUnsignedShort();
- if (VERSION_2_MAGIC_NUMBER == magic_v2) return source.readUnsignedShort();
+ private static int getFormatVersion(final ByteBuffer buffer) throws IOException {
+ final int magic_v1 = readUnsignedShort(buffer);
+ if (VERSION_1_MAGIC_NUMBER == magic_v1) return readUnsignedByte(buffer);
+ final int magic_v2 = (magic_v1 << 16) + readUnsignedShort(buffer);
+ if (VERSION_2_MAGIC_NUMBER == magic_v2) return readUnsignedShort(buffer);
return NOT_A_VERSION_NUMBER;
}
/**
- * Reads a random access file and returns the memory representation of the dictionary.
+ * Reads options from a file and populate a map with their contents.
+ *
+ * The file is read at the current file pointer, so the caller must take care the pointer
+ * is in the right place before calling this.
+ */
+ public static void populateOptions(final ByteBuffer buffer, final int headerSize,
+ final HashMap<String, String> options) {
+ while (buffer.position() < headerSize) {
+ final String key = CharEncoding.readString(buffer);
+ final String value = CharEncoding.readString(buffer);
+ options.put(key, value);
+ }
+ }
+
+ /**
+ * Reads a byte buffer and returns the memory representation of the dictionary.
*
* This high-level method takes a binary file and reads its contents, populating a
* FusionDictionary structure. The optional dict argument is an existing dictionary to
* which words from the file should be added. If it is null, a new dictionary is created.
*
- * @param source the file to read.
+ * @param buffer the buffer to read.
* @param dict an optional dictionary to add words to, or null.
* @return the created (or merged) dictionary.
*/
- public static FusionDictionary readDictionaryBinary(final RandomAccessFile source,
+ public static FusionDictionary readDictionaryBinary(final ByteBuffer buffer,
final FusionDictionary dict) throws IOException, UnsupportedFormatException {
// Check file version
- final int version = getFormatVersion(source);
- if (version < MINIMUM_SUPPORTED_VERSION || version > MAXIMUM_SUPPORTED_VERSION ) {
+ final int version = getFormatVersion(buffer);
+ if (version < MINIMUM_SUPPORTED_VERSION || version > MAXIMUM_SUPPORTED_VERSION) {
throw new UnsupportedFormatException("This file has version " + version
+ ", but this implementation does not support versions above "
+ MAXIMUM_SUPPORTED_VERSION);
}
- // Read options
- final int optionsFlags = source.readUnsignedShort();
+ // clear cache
+ wordCache.clear();
- final long headerSize;
+ // Read options
+ final int optionsFlags = readUnsignedShort(buffer);
+
+ final int headerSize;
final HashMap<String, String> options = new HashMap<String, String>();
if (version < FIRST_VERSION_WITH_HEADER_SIZE) {
- headerSize = source.getFilePointer();
+ headerSize = buffer.position();
} else {
- headerSize = (source.readUnsignedByte() << 24) + (source.readUnsignedByte() << 16)
- + (source.readUnsignedByte() << 8) + source.readUnsignedByte();
- while (source.getFilePointer() < headerSize) {
- final String key = CharEncoding.readString(source);
- final String value = CharEncoding.readString(source);
- options.put(key, value);
- }
- source.seek(headerSize);
+ headerSize = buffer.getInt();
+ populateOptions(buffer, headerSize, options);
+ buffer.position(headerSize);
+ }
+
+ if (headerSize < 0) {
+ throw new UnsupportedFormatException("header size can't be negative.");
}
Map<Integer, Node> reverseNodeMapping = new TreeMap<Integer, Node>();
Map<Integer, CharGroup> reverseGroupMapping = new TreeMap<Integer, CharGroup>();
- final Node root = readNode(source, headerSize, reverseNodeMapping, reverseGroupMapping);
+ final Node root = readNode(
+ buffer, headerSize, reverseNodeMapping, reverseGroupMapping);
FusionDictionary newDict = new FusionDictionary(root,
new FusionDictionary.DictionaryOptions(options,
@@ -1392,6 +1414,28 @@
}
/**
+ * 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.
*
* Concretely this only tests the magic number.
@@ -1400,14 +1444,44 @@
* @return true if it's a binary dictionary, false otherwise
*/
public static boolean isBinaryDictionary(final String filename) {
+ FileInputStream inStream = null;
try {
- RandomAccessFile f = new RandomAccessFile(filename, "r");
- final int version = getFormatVersion(f);
+ final File file = new File(filename);
+ inStream = new FileInputStream(file);
+ final ByteBuffer buffer = inStream.getChannel().map(
+ FileChannel.MapMode.READ_ONLY, 0, file.length());
+ final int version = getFormatVersion(buffer);
return (version >= MINIMUM_SUPPORTED_VERSION && version <= MAXIMUM_SUPPORTED_VERSION);
} catch (FileNotFoundException e) {
return false;
} catch (IOException e) {
return false;
+ } finally {
+ if (inStream != null) {
+ try {
+ inStream.close();
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
}
}
+
+ /**
+ * Calculate bigram frequency from compressed value
+ *
+ * @see #makeBigramFlags
+ *
+ * @param unigramFrequency
+ * @param bigramFrequency compressed frequency
+ * @return approximate bigram frequency
+ */
+ public static int reconstructBigramFrequency(final int unigramFrequency,
+ final int bigramFrequency) {
+ final float stepSize = (MAX_TERMINAL_FREQUENCY - unigramFrequency)
+ / (1.5f + MAX_BIGRAM_FREQUENCY);
+ final float resultFreqFloat = (float)unigramFrequency
+ + stepSize * (bigramFrequency + 1.0f);
+ return (int)resultFreqFloat;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
index 5864db2..7c15ba5 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
@@ -516,13 +516,23 @@
int indexOfGroup = findIndexOfChar(node, s.codePointAt(index));
if (CHARACTER_NOT_FOUND == indexOfGroup) return null;
currentGroup = node.mData.get(indexOfGroup);
+
+ if (s.length() - index < currentGroup.mChars.length) return null;
+ int newIndex = index;
+ while (newIndex < s.length() && newIndex - index < currentGroup.mChars.length) {
+ if (currentGroup.mChars[newIndex - index] != s.codePointAt(newIndex)) return null;
+ newIndex++;
+ }
+ index = newIndex;
+
if (DBG) checker.append(new String(currentGroup.mChars, 0, currentGroup.mChars.length));
- index += currentGroup.mChars.length;
if (index < s.length()) {
node = currentGroup.mChildren;
}
} while (null != node && index < s.length());
+ if (index < s.length()) return null;
+ if (!currentGroup.isTerminal()) return null;
if (DBG && !s.equals(checker.toString())) return null;
return currentGroup;
}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 3bdfe1f..eef7a51 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -25,6 +25,7 @@
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.BinaryDictionary;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.ContactsBinaryDictionary;
import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.DictionaryCollection;
@@ -35,7 +36,6 @@
import com.android.inputmethod.latin.SynchronouslyLoadedContactsBinaryDictionary;
import com.android.inputmethod.latin.SynchronouslyLoadedUserBinaryDictionary;
import com.android.inputmethod.latin.UserBinaryDictionary;
-import com.android.inputmethod.latin.WhitelistDictionary;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -63,12 +63,9 @@
public static final int CAPITALIZE_ALL = 2; // All caps
private final static String[] EMPTY_STRING_ARRAY = new String[0];
- private Map<String, DictionaryPool> mDictionaryPools =
- Collections.synchronizedMap(new TreeMap<String, DictionaryPool>());
+ private Map<String, DictionaryPool> mDictionaryPools = CollectionUtils.newSynchronizedTreeMap();
private Map<String, UserBinaryDictionary> mUserDictionaries =
- Collections.synchronizedMap(new TreeMap<String, UserBinaryDictionary>());
- private Map<String, Dictionary> mWhitelistDictionaries =
- Collections.synchronizedMap(new TreeMap<String, Dictionary>());
+ CollectionUtils.newSynchronizedTreeMap();
private ContactsBinaryDictionary mContactsDictionary;
// The threshold for a candidate to be offered as a suggestion.
@@ -80,7 +77,7 @@
private final Object mUseContactsLock = new Object();
private final HashSet<WeakReference<DictionaryCollection>> mDictionaryCollectionsList =
- new HashSet<WeakReference<DictionaryCollection>>();
+ CollectionUtils.newHashSet();
public static final int SCRIPT_LATIN = 0;
public static final int SCRIPT_CYRILLIC = 1;
@@ -96,7 +93,7 @@
// proximity to pass to the dictionary descent algorithm.
// IMPORTANT: this only contains languages - do not write countries in there.
// Only the language is searched from the map.
- mLanguageToScript = new TreeMap<String, Integer>();
+ mLanguageToScript = CollectionUtils.newTreeMap();
mLanguageToScript.put("en", SCRIPT_LATIN);
mLanguageToScript.put("fr", SCRIPT_LATIN);
mLanguageToScript.put("de", SCRIPT_LATIN);
@@ -234,7 +231,7 @@
mSuggestionThreshold = suggestionThreshold;
mRecommendedThreshold = recommendedThreshold;
mMaxLength = maxLength;
- mSuggestions = new ArrayList<CharSequence>(maxLength + 1);
+ mSuggestions = CollectionUtils.newArrayList(maxLength + 1);
mScores = new int[mMaxLength];
}
@@ -362,12 +359,9 @@
private void closeAllDictionaries() {
final Map<String, DictionaryPool> oldPools = mDictionaryPools;
- mDictionaryPools = Collections.synchronizedMap(new TreeMap<String, DictionaryPool>());
+ mDictionaryPools = CollectionUtils.newSynchronizedTreeMap();
final Map<String, UserBinaryDictionary> oldUserDictionaries = mUserDictionaries;
- mUserDictionaries =
- Collections.synchronizedMap(new TreeMap<String, UserBinaryDictionary>());
- final Map<String, Dictionary> oldWhitelistDictionaries = mWhitelistDictionaries;
- mWhitelistDictionaries = Collections.synchronizedMap(new TreeMap<String, Dictionary>());
+ mUserDictionaries = CollectionUtils.newSynchronizedTreeMap();
new Thread("spellchecker_close_dicts") {
@Override
public void run() {
@@ -377,9 +371,6 @@
for (Dictionary dict : oldUserDictionaries.values()) {
dict.close();
}
- for (Dictionary dict : oldWhitelistDictionaries.values()) {
- dict.close();
- }
synchronized (mUseContactsLock) {
if (null != mContactsDictionary) {
// The synchronously loaded contacts dictionary should have been in one
@@ -423,12 +414,6 @@
mUserDictionaries.put(localeStr, userDictionary);
}
dictionaryCollection.addDictionary(userDictionary);
- Dictionary whitelistDictionary = mWhitelistDictionaries.get(localeStr);
- if (null == whitelistDictionary) {
- whitelistDictionary = new WhitelistDictionary(this, locale);
- mWhitelistDictionaries.put(localeStr, whitelistDictionary);
- }
- dictionaryCollection.addDictionary(whitelistDictionary);
synchronized (mUseContactsLock) {
if (mUseContactsDictionary) {
if (null == mContactsDictionary) {
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
index 501a0e2..5a1bd37 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
@@ -22,6 +22,8 @@
import android.view.textservice.SuggestionsInfo;
import android.view.textservice.TextInfo;
+import com.android.inputmethod.latin.CollectionUtils;
+
import java.util.ArrayList;
public class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheckerSession {
@@ -40,10 +42,10 @@
return null;
}
final int N = ssi.getSuggestionsCount();
- final ArrayList<Integer> additionalOffsets = new ArrayList<Integer>();
- final ArrayList<Integer> additionalLengths = new ArrayList<Integer>();
+ final ArrayList<Integer> additionalOffsets = CollectionUtils.newArrayList();
+ final ArrayList<Integer> additionalLengths = CollectionUtils.newArrayList();
final ArrayList<SuggestionsInfo> additionalSuggestionsInfos =
- new ArrayList<SuggestionsInfo>();
+ CollectionUtils.newArrayList();
String currentWord = null;
for (int i = 0; i < N; ++i) {
final SuggestionsInfo si = ssi.getSuggestionsInfoAt(i);
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index 0171dc0..f4784ff 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -24,6 +24,7 @@
import android.view.textservice.TextInfo;
import com.android.inputmethod.compat.SuggestionsInfoCompatUtils;
+import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.LocaleUtils;
import com.android.inputmethod.latin.WordComposer;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
@@ -193,8 +194,8 @@
if (shouldFilterOut(inText, mScript)) {
DictAndProximity dictInfo = null;
try {
- dictInfo = mDictionaryPool.takeOrGetNull();
- if (null == dictInfo) {
+ dictInfo = mDictionaryPool.pollWithDefaultTimeout();
+ if (!DictionaryPool.isAValidDictionary(dictInfo)) {
return AndroidSpellCheckerService.getNotInDictEmptySuggestions();
}
return dictInfo.mDictionary.isValidWord(inText)
@@ -225,8 +226,8 @@
final int xy = SpellCheckerProximityInfo.getXYForCodePointAndScript(
codePoint, mScript);
if (SpellCheckerProximityInfo.NOT_A_COORDINATE_PAIR == xy) {
- composer.add(codePoint, WordComposer.NOT_A_COORDINATE,
- WordComposer.NOT_A_COORDINATE);
+ composer.add(codePoint,
+ Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
} else {
composer.add(codePoint, xy & 0xFFFF, xy >> 16);
}
@@ -236,8 +237,8 @@
boolean isInDict = true;
DictAndProximity dictInfo = null;
try {
- dictInfo = mDictionaryPool.takeOrGetNull();
- if (null == dictInfo) {
+ dictInfo = mDictionaryPool.pollWithDefaultTimeout();
+ if (!DictionaryPool.isAValidDictionary(dictInfo)) {
return AndroidSpellCheckerService.getNotInDictEmptySuggestions();
}
final ArrayList<SuggestedWordInfo> suggestions =
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
index 8fc632e..53aa6c7 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
@@ -16,19 +16,56 @@
package com.android.inputmethod.latin.spellcheck;
+import android.util.Log;
+
+import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.WordComposer;
+
+import java.util.ArrayList;
import java.util.Locale;
import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
/**
* A blocking queue that creates dictionaries up to a certain limit as necessary.
+ * As a deadlock-detecting device, if waiting for more than TIMEOUT = 3 seconds, we
+ * will clear the queue and generate its contents again. This is transparent for
+ * the client code, but may help with sloppy clients.
*/
@SuppressWarnings("serial")
public class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> {
+ private final static String TAG = DictionaryPool.class.getSimpleName();
+ // How many seconds we wait for a dictionary to become available. Past this delay, we give up in
+ // fear some bug caused a deadlock, and reset the whole pool.
+ private final static int TIMEOUT = 3;
private final AndroidSpellCheckerService mService;
private final int mMaxSize;
private final Locale mLocale;
private int mSize;
private volatile boolean mClosed;
+ final static ArrayList<SuggestedWordInfo> noSuggestions = CollectionUtils.newArrayList();
+ private final static DictAndProximity dummyDict = new DictAndProximity(
+ new Dictionary(Dictionary.TYPE_MAIN) {
+ @Override
+ public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
+ final CharSequence prevWord, final ProximityInfo proximityInfo) {
+ return noSuggestions;
+ }
+ @Override
+ public boolean isValidWord(CharSequence word) {
+ // This is never called. However if for some strange reason it ever gets
+ // called, returning true is less destructive (it will not underline the
+ // word in red).
+ return true;
+ }
+ }, null);
+
+ static public boolean isAValidDictionary(final DictAndProximity dictInfo) {
+ return null != dictInfo && dummyDict != dictInfo;
+ }
public DictionaryPool(final int maxSize, final AndroidSpellCheckerService service,
final Locale locale) {
@@ -41,13 +78,23 @@
}
@Override
- public DictAndProximity take() throws InterruptedException {
+ public DictAndProximity poll(final long timeout, final TimeUnit unit)
+ throws InterruptedException {
final DictAndProximity dict = poll();
if (null != dict) return dict;
synchronized(this) {
if (mSize >= mMaxSize) {
- // Our pool is already full. Wait until some dictionary is ready.
- return super.take();
+ // Our pool is already full. Wait until some dictionary is ready, or TIMEOUT
+ // expires to avoid a deadlock.
+ final DictAndProximity result = super.poll(timeout, unit);
+ if (null == result) {
+ Log.e(TAG, "Deadlock detected ! Resetting dictionary pool");
+ clear();
+ mSize = 1;
+ return mService.createDictAndProximity(mLocale);
+ } else {
+ return result;
+ }
} else {
++mSize;
return mService.createDictAndProximity(mLocale);
@@ -56,9 +103,9 @@
}
// Convenience method
- public DictAndProximity takeOrGetNull() {
+ public DictAndProximity pollWithDefaultTimeout() {
try {
- return take();
+ return poll(TIMEOUT, TimeUnit.SECONDS);
} catch (InterruptedException e) {
return null;
}
@@ -78,7 +125,7 @@
public boolean offer(final DictAndProximity dict) {
if (mClosed) {
dict.mDictionary.close();
- return false;
+ return super.offer(dummyDict);
} else {
return super.offer(dict);
}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
index 0103e84..fe5225e 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
@@ -16,14 +16,15 @@
package com.android.inputmethod.latin.spellcheck;
-import com.android.inputmethod.keyboard.KeyDetector;
import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.Constants;
import java.util.TreeMap;
public class SpellCheckerProximityInfo {
/* public for test */
- final public static int NUL = KeyDetector.NOT_A_CODE;
+ final public static int NUL = Constants.NOT_A_CODE;
// This must be the same as MAX_PROXIMITY_CHARS_SIZE else it will not work inside
// native code - this value is passed at creation of the binary object and reused
@@ -59,7 +60,7 @@
// character.
// Since we need to build such an array, we want to be able to search in our big proximity
// data quickly by character, and a map is probably the best way to do this.
- final private static TreeMap<Integer, Integer> INDICES = new TreeMap<Integer, Integer>();
+ final private static TreeMap<Integer, Integer> INDICES = CollectionUtils.newTreeMap();
// The proximity here is the union of
// - the proximity for a QWERTY keyboard.
@@ -111,6 +112,7 @@
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
};
static {
buildProximityIndices(PROXIMITY, INDICES);
@@ -121,7 +123,7 @@
}
private static class Cyrillic {
- final private static TreeMap<Integer, Integer> INDICES = new TreeMap<Integer, Integer>();
+ final private static TreeMap<Integer, Integer> INDICES = CollectionUtils.newTreeMap();
// TODO: The following table is solely based on the keyboard layout. Consult with Russian
// speakers on commonly misspelled words/letters.
final static int[] PROXIMITY = {
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index b57ffd2..03263d2 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -58,6 +58,7 @@
import com.android.inputmethod.keyboard.PointerTracker;
import com.android.inputmethod.keyboard.ViewLayoutUtils;
import com.android.inputmethod.latin.AutoCorrection;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
@@ -72,7 +73,7 @@
OnLongClickListener {
public interface Listener {
public boolean addWordToUserDictionary(String word);
- public void pickSuggestionManually(int index, CharSequence word, int x, int y);
+ public void pickSuggestionManually(int index, CharSequence word);
}
// The maximum number of suggestions available. See {@link Suggest#mPrefMaxSuggestions}.
@@ -88,9 +89,9 @@
private final MoreSuggestions.Builder mMoreSuggestionsBuilder;
private final PopupWindow mMoreSuggestionsWindow;
- private final ArrayList<TextView> mWords = new ArrayList<TextView>();
- private final ArrayList<TextView> mInfos = new ArrayList<TextView>();
- private final ArrayList<View> mDividers = new ArrayList<View>();
+ private final ArrayList<TextView> mWords = CollectionUtils.newArrayList();
+ private final ArrayList<TextView> mInfos = CollectionUtils.newArrayList();
+ private final ArrayList<View> mDividers = CollectionUtils.newArrayList();
private final PopupWindow mPreviewPopup;
private final TextView mPreviewText;
@@ -131,7 +132,7 @@
private static class SuggestionStripViewParams {
private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3;
- private static final int DEFAULT_CENTER_SUGGESTION_PERCENTILE = 40;
+ private static final float DEFAULT_CENTER_SUGGESTION_PERCENTILE = 0.40f;
private static final int DEFAULT_MAX_MORE_SUGGESTIONS_ROW = 2;
private static final int PUNCTUATIONS_IN_STRIP = 5;
@@ -167,7 +168,7 @@
private final int mSuggestionStripOption;
- private final ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>();
+ private final ArrayList<CharSequence> mTexts = CollectionUtils.newArrayList();
public boolean mMoreSuggestionsAvailable;
@@ -195,16 +196,16 @@
R.styleable.SuggestionStripView, defStyle, R.style.SuggestionStripViewStyle);
mSuggestionStripOption = a.getInt(
R.styleable.SuggestionStripView_suggestionStripOption, 0);
- final float alphaValidTypedWord = getPercent(a,
- R.styleable.SuggestionStripView_alphaValidTypedWord, 100);
- final float alphaTypedWord = getPercent(a,
- R.styleable.SuggestionStripView_alphaTypedWord, 100);
- final float alphaAutoCorrect = getPercent(a,
- R.styleable.SuggestionStripView_alphaAutoCorrect, 100);
- final float alphaSuggested = getPercent(a,
- R.styleable.SuggestionStripView_alphaSuggested, 100);
- mAlphaObsoleted = getPercent(a,
- R.styleable.SuggestionStripView_alphaSuggested, 100);
+ final float alphaValidTypedWord = getFraction(a,
+ R.styleable.SuggestionStripView_alphaValidTypedWord, 1.0f);
+ final float alphaTypedWord = getFraction(a,
+ R.styleable.SuggestionStripView_alphaTypedWord, 1.0f);
+ final float alphaAutoCorrect = getFraction(a,
+ R.styleable.SuggestionStripView_alphaAutoCorrect, 1.0f);
+ final float alphaSuggested = getFraction(a,
+ R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
+ mAlphaObsoleted = getFraction(a,
+ R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
mColorValidTypedWord = applyAlpha(a.getColor(
R.styleable.SuggestionStripView_colorValidTypedWord, 0), alphaValidTypedWord);
mColorTypedWord = applyAlpha(a.getColor(
@@ -216,14 +217,14 @@
mSuggestionsCountInStrip = a.getInt(
R.styleable.SuggestionStripView_suggestionsCountInStrip,
DEFAULT_SUGGESTIONS_COUNT_IN_STRIP);
- mCenterSuggestionWeight = getPercent(a,
+ mCenterSuggestionWeight = getFraction(a,
R.styleable.SuggestionStripView_centerSuggestionPercentile,
DEFAULT_CENTER_SUGGESTION_PERCENTILE);
mMaxMoreSuggestionsRow = a.getInt(
R.styleable.SuggestionStripView_maxMoreSuggestionsRow,
DEFAULT_MAX_MORE_SUGGESTIONS_ROW);
- mMinMoreSuggestionsWidth = getRatio(a,
- R.styleable.SuggestionStripView_minMoreSuggestionsWidth);
+ mMinMoreSuggestionsWidth = getFraction(a,
+ R.styleable.SuggestionStripView_minMoreSuggestionsWidth, 1.0f);
a.recycle();
mMoreSuggestionsHint = getMoreSuggestionsHint(res,
@@ -277,14 +278,8 @@
return new BitmapDrawable(res, buffer);
}
- // Read integer value in TypedArray as percent.
- private static float getPercent(TypedArray a, int index, int defValue) {
- return a.getInt(index, defValue) / 100.0f;
- }
-
- // Read fraction value in TypedArray as float.
- private static float getRatio(TypedArray a, int index) {
- return a.getFraction(index, 1000, 1000, 1) / 1000.0f;
+ static float getFraction(final TypedArray a, final int index, final float defValue) {
+ return a.getFraction(index, 1, 1, defValue);
}
private CharSequence getStyledSuggestionWord(SuggestedWords suggestedWords, int pos) {
@@ -726,9 +721,7 @@
public boolean onCustomRequest(int requestCode) {
final int index = requestCode;
final CharSequence word = mSuggestedWords.getWord(index);
- // TODO: change caller path so coordinates are passed through here
- mListener.pickSuggestionManually(index, word, NOT_A_TOUCH_COORDINATE,
- NOT_A_TOUCH_COORDINATE);
+ mListener.pickSuggestionManually(index, word);
dismissMoreSuggestions();
return true;
}
@@ -874,7 +867,7 @@
return;
final CharSequence word = mSuggestedWords.getWord(index);
- mListener.pickSuggestionManually(index, word, mLastX, mLastY);
+ mListener.pickSuggestionManually(index, word);
}
@Override
diff --git a/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java b/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java
new file mode 100644
index 0000000..5124a35
--- /dev/null
+++ b/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.research;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * Arrange for the uploading service to be run on regular intervals.
+ */
+public final class BootBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
+ ResearchLogger.scheduleUploadingService(context);
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/research/FeedbackActivity.java b/java/src/com/android/inputmethod/research/FeedbackActivity.java
index c9f3b47..11eae88 100644
--- a/java/src/com/android/inputmethod/research/FeedbackActivity.java
+++ b/java/src/com/android/inputmethod/research/FeedbackActivity.java
@@ -18,10 +18,7 @@
import android.app.Activity;
import android.os.Bundle;
-import android.text.Editable;
-import android.view.View;
import android.widget.CheckBox;
-import android.widget.EditText;
import com.android.inputmethod.latin.R;
@@ -31,6 +28,11 @@
super.onCreate(savedInstanceState);
setContentView(R.layout.research_feedback_activity);
final FeedbackLayout layout = (FeedbackLayout) findViewById(R.id.research_feedback_layout);
+ final CheckBox checkbox = (CheckBox) findViewById(R.id.research_feedback_include_history);
+ final CharSequence cs = checkbox.getText();
+ final String actualString = String.format(cs.toString(),
+ ResearchLogger.FEEDBACK_WORD_BUFFER_SIZE);
+ checkbox.setText(actualString);
layout.setActivity(this);
}
diff --git a/java/src/com/android/inputmethod/research/LogBuffer.java b/java/src/com/android/inputmethod/research/LogBuffer.java
new file mode 100644
index 0000000..ae7b157
--- /dev/null
+++ b/java/src/com/android/inputmethod/research/LogBuffer.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.research;
+
+import com.android.inputmethod.latin.CollectionUtils;
+
+import java.util.LinkedList;
+
+/**
+ * A buffer that holds a fixed number of LogUnits.
+ *
+ * LogUnits are added in and shifted out in temporal order. Only a subset of the LogUnits are
+ * actual words; the other LogUnits do not count toward the word limit. Once the buffer reaches
+ * capacity, adding another LogUnit that is a word evicts the oldest LogUnits out one at a time to
+ * stay under the capacity limit.
+ */
+public class LogBuffer {
+ protected final LinkedList<LogUnit> mLogUnits;
+ /* package for test */ int mWordCapacity;
+ // The number of members of mLogUnits that are actual words.
+ protected int mNumActualWords;
+
+ /**
+ * Create a new LogBuffer that can hold a fixed number of LogUnits that are words (and
+ * unlimited number of non-word LogUnits), and that outputs its result to a researchLog.
+ *
+ * @param wordCapacity maximum number of words
+ */
+ LogBuffer(final int wordCapacity) {
+ if (wordCapacity <= 0) {
+ throw new IllegalArgumentException("wordCapacity must be 1 or greater.");
+ }
+ mLogUnits = CollectionUtils.newLinkedList();
+ mWordCapacity = wordCapacity;
+ mNumActualWords = 0;
+ }
+
+ /**
+ * Adds a new LogUnit to the front of the LIFO queue, evicting existing LogUnit's
+ * (oldest first) if word capacity is reached.
+ */
+ public void shiftIn(LogUnit newLogUnit) {
+ if (newLogUnit.getWord() == null) {
+ // This LogUnit isn't a word, so it doesn't count toward the word-limit.
+ mLogUnits.add(newLogUnit);
+ return;
+ }
+ if (mNumActualWords == mWordCapacity) {
+ shiftOutThroughFirstWord();
+ }
+ mLogUnits.add(newLogUnit);
+ mNumActualWords++; // Must be a word, or we wouldn't be here.
+ }
+
+ private void shiftOutThroughFirstWord() {
+ while (!mLogUnits.isEmpty()) {
+ final LogUnit logUnit = mLogUnits.removeFirst();
+ onShiftOut(logUnit);
+ if (logUnit.hasWord()) {
+ // Successfully shifted out a word-containing LogUnit and made space for the new
+ // LogUnit.
+ mNumActualWords--;
+ break;
+ }
+ }
+ }
+
+ /**
+ * Removes all LogUnits from the buffer without calling onShiftOut().
+ */
+ public void clear() {
+ mLogUnits.clear();
+ mNumActualWords = 0;
+ }
+
+ /**
+ * Called when a LogUnit is removed from the LogBuffer as a result of a shiftIn. LogUnits are
+ * removed in the order entered. This method is not called when shiftOut is called directly.
+ *
+ * Base class does nothing; subclasses may override.
+ */
+ protected void onShiftOut(LogUnit logUnit) {
+ }
+
+ /**
+ * Called to deliberately remove the oldest LogUnit. Usually called when draining the
+ * LogBuffer.
+ */
+ public LogUnit shiftOut() {
+ if (mLogUnits.isEmpty()) {
+ return null;
+ }
+ final LogUnit logUnit = mLogUnits.removeFirst();
+ if (logUnit.hasWord()) {
+ mNumActualWords--;
+ }
+ return logUnit;
+ }
+}
diff --git a/java/src/com/android/inputmethod/research/LogUnit.java b/java/src/com/android/inputmethod/research/LogUnit.java
new file mode 100644
index 0000000..d8b3a29
--- /dev/null
+++ b/java/src/com/android/inputmethod/research/LogUnit.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.research;
+
+import com.android.inputmethod.latin.CollectionUtils;
+
+import java.util.ArrayList;
+
+/**
+ * A group of log statements related to each other.
+ *
+ * A LogUnit is collection of LogStatements, each of which is generated by at a particular point
+ * in the code. (There is no LogStatement class; the data is stored across the instance variables
+ * here.) A single LogUnit's statements can correspond to all the calls made while in the same
+ * composing region, or all the calls between committing the last composing region, and the first
+ * character of the next composing region.
+ *
+ * Individual statements in a log may be marked as potentially private. If so, then they are only
+ * published to a ResearchLog if the ResearchLogger determines that publishing the entire LogUnit
+ * will not violate the user's privacy. Checks for this may include whether other LogUnits have
+ * been published recently, or whether the LogUnit contains numbers, etc.
+ */
+/* package */ class LogUnit {
+ private final ArrayList<String[]> mKeysList = CollectionUtils.newArrayList();
+ private final ArrayList<Object[]> mValuesList = CollectionUtils.newArrayList();
+ private final ArrayList<Boolean> mIsPotentiallyPrivate = CollectionUtils.newArrayList();
+ private String mWord;
+ private boolean mContainsDigit;
+
+ public void addLogStatement(final String[] keys, final Object[] values,
+ final Boolean isPotentiallyPrivate) {
+ mKeysList.add(keys);
+ mValuesList.add(values);
+ mIsPotentiallyPrivate.add(isPotentiallyPrivate);
+ }
+
+ public void publishTo(final ResearchLog researchLog, final boolean isIncludingPrivateData) {
+ final int size = mKeysList.size();
+ for (int i = 0; i < size; i++) {
+ if (!mIsPotentiallyPrivate.get(i) || isIncludingPrivateData) {
+ researchLog.outputEvent(mKeysList.get(i), mValuesList.get(i));
+ }
+ }
+ }
+
+ public void setWord(String word) {
+ mWord = word;
+ }
+
+ public String getWord() {
+ return mWord;
+ }
+
+ public boolean hasWord() {
+ return mWord != null;
+ }
+
+ public void setContainsDigit() {
+ mContainsDigit = true;
+ }
+
+ public boolean hasDigit() {
+ return mContainsDigit;
+ }
+
+ public boolean isEmpty() {
+ return mKeysList.isEmpty();
+ }
+}
diff --git a/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java
new file mode 100644
index 0000000..745768d
--- /dev/null
+++ b/java/src/com/android/inputmethod/research/MainLogBuffer.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.research;
+
+import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.Suggest;
+
+import java.util.Random;
+
+public class MainLogBuffer extends LogBuffer {
+ // The size of the n-grams logged. E.g. N_GRAM_SIZE = 2 means to sample bigrams.
+ private static final int N_GRAM_SIZE = 2;
+ // The number of words between n-grams to omit from the log.
+ private static final int DEFAULT_NUMBER_OF_WORDS_BETWEEN_SAMPLES = 18;
+
+ private final ResearchLog mResearchLog;
+ private Suggest mSuggest;
+
+ // The minimum periodicity with which n-grams can be sampled. E.g. mWinWordPeriod is 10 if
+ // every 10th bigram is sampled, i.e., words 1-8 are not, but the bigram at words 9 and 10, etc.
+ // for 11-18, and the bigram at words 19 and 20. If an n-gram is not safe (e.g. it contains a
+ // number in the middle or an out-of-vocabulary word), then sampling is delayed until a safe
+ // n-gram does appear.
+ /* package for test */ int mMinWordPeriod;
+
+ // Counter for words left to suppress before an n-gram can be sampled. Reset to mMinWordPeriod
+ // after a sample is taken.
+ /* package for test */ int mWordsUntilSafeToSample;
+
+ public MainLogBuffer(final ResearchLog researchLog) {
+ super(N_GRAM_SIZE);
+ mResearchLog = researchLog;
+ mMinWordPeriod = DEFAULT_NUMBER_OF_WORDS_BETWEEN_SAMPLES + N_GRAM_SIZE;
+ final Random random = new Random();
+ mWordsUntilSafeToSample = random.nextInt(mMinWordPeriod);
+ }
+
+ public void setSuggest(Suggest suggest) {
+ mSuggest = suggest;
+ }
+
+ @Override
+ public void shiftIn(final LogUnit newLogUnit) {
+ super.shiftIn(newLogUnit);
+ if (newLogUnit.hasWord()) {
+ if (mWordsUntilSafeToSample > 0) {
+ mWordsUntilSafeToSample--;
+ }
+ }
+ }
+
+ public void resetWordCounter() {
+ mWordsUntilSafeToSample = mMinWordPeriod;
+ }
+
+ /**
+ * Determines whether the content of the MainLogBuffer can be safely uploaded in its complete
+ * form and still protect the user's privacy.
+ *
+ * The size of the MainLogBuffer is just enough to hold one n-gram, its corrections, and any
+ * non-character data that is typed between words. The decision about privacy is made based on
+ * the buffer's entire content. If it is decided that the privacy risks are too great to upload
+ * the contents of this buffer, a censored version of the LogItems may still be uploaded. E.g.,
+ * the screen orientation and other characteristics about the device can be uploaded without
+ * revealing much about the user.
+ */
+ public boolean isSafeToLog() {
+ // Check that we are not sampling too frequently. Having sampled recently might disclose
+ // too much of the user's intended meaning.
+ if (mWordsUntilSafeToSample > 0) {
+ return false;
+ }
+ if (mSuggest == null || !mSuggest.hasMainDictionary()) {
+ // Main dictionary is unavailable. Since we cannot check it, we cannot tell if a word
+ // is out-of-vocabulary or not. Therefore, we must judge the entire buffer contents to
+ // potentially pose a privacy risk.
+ return false;
+ }
+ // Reload the dictionary in case it has changed (e.g., because the user has changed
+ // languages).
+ final Dictionary dictionary = mSuggest.getMainDictionary();
+ if (dictionary == null) {
+ return false;
+ }
+ // Check each word in the buffer. If any word poses a privacy threat, we cannot upload the
+ // complete buffer contents in detail.
+ final int length = mLogUnits.size();
+ for (int i = 0; i < length; i++) {
+ final LogUnit logUnit = mLogUnits.get(i);
+ final String word = logUnit.getWord();
+ if (word == null) {
+ // Digits outside words are a privacy threat.
+ if (logUnit.hasDigit()) {
+ return false;
+ }
+ } else {
+ // Words not in the dictionary are a privacy threat.
+ if (!(dictionary.isValidWord(word))) {
+ return false;
+ }
+ }
+ }
+ // All checks have passed; this buffer's content can be safely uploaded.
+ return true;
+ }
+
+ @Override
+ protected void onShiftOut(LogUnit logUnit) {
+ if (mResearchLog != null) {
+ mResearchLog.publish(logUnit, false /* isIncludingPrivateData */);
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java
index 18bf3c0..71a6d6a 100644
--- a/java/src/com/android/inputmethod/research/ResearchLog.java
+++ b/java/src/com/android/inputmethod/research/ResearchLog.java
@@ -26,7 +26,6 @@
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.define.ProductionFlag;
-import com.android.inputmethod.research.ResearchLogger.LogUnit;
import java.io.BufferedWriter;
import java.io.File;
@@ -37,6 +36,7 @@
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
+import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@@ -51,21 +51,22 @@
*/
public class ResearchLog {
private static final String TAG = ResearchLog.class.getSimpleName();
- private static final JsonWriter NULL_JSON_WRITER = new JsonWriter(
- new OutputStreamWriter(new NullOutputStream()));
+ private static final boolean DEBUG = false;
+ private static final long FLUSH_DELAY_IN_MS = 1000 * 5;
+ private static final int ABORT_TIMEOUT_IN_MS = 1000 * 4;
- final ScheduledExecutorService mExecutor;
+ /* package */ final ScheduledExecutorService mExecutor;
/* package */ final File mFile;
private JsonWriter mJsonWriter = NULL_JSON_WRITER;
+ // true if at least one byte of data has been written out to the log file. This must be
+ // remembered because JsonWriter requires that calls matching calls to beginObject and
+ // endObject, as well as beginArray and endArray, and the file is opened lazily, only when
+ // it is certain that data will be written. Alternatively, the matching call exceptions
+ // could be caught, but this might suppress other errors.
+ private boolean mHasWrittenData = false;
- private int mLoggingState;
- private static final int LOGGING_STATE_UNSTARTED = 0;
- private static final int LOGGING_STATE_READY = 1; // don't create file until necessary
- private static final int LOGGING_STATE_RUNNING = 2;
- private static final int LOGGING_STATE_STOPPING = 3;
- private static final int LOGGING_STATE_STOPPED = 4;
- private static final long FLUSH_DELAY_IN_MS = 1000 * 5;
-
+ private static final JsonWriter NULL_JSON_WRITER = new JsonWriter(
+ new OutputStreamWriter(new NullOutputStream()));
private static class NullOutputStream extends OutputStream {
/** {@inheritDoc} */
@Override
@@ -84,128 +85,81 @@
}
}
- public ResearchLog(File outputFile) {
- mExecutor = Executors.newSingleThreadScheduledExecutor();
+ public ResearchLog(final File outputFile) {
if (outputFile == null) {
throw new IllegalArgumentException();
}
+ mExecutor = Executors.newSingleThreadScheduledExecutor();
mFile = outputFile;
- mLoggingState = LOGGING_STATE_UNSTARTED;
}
- public synchronized void start() throws IOException {
- switch (mLoggingState) {
- case LOGGING_STATE_UNSTARTED:
- mLoggingState = LOGGING_STATE_READY;
- break;
- case LOGGING_STATE_READY:
- case LOGGING_STATE_RUNNING:
- case LOGGING_STATE_STOPPING:
- case LOGGING_STATE_STOPPED:
- break;
- }
- }
-
- public synchronized void stop() {
- switch (mLoggingState) {
- case LOGGING_STATE_UNSTARTED:
- mLoggingState = LOGGING_STATE_STOPPED;
- break;
- case LOGGING_STATE_READY:
- case LOGGING_STATE_RUNNING:
- mExecutor.submit(new Callable<Object>() {
- @Override
- public Object call() throws Exception {
- try {
- mJsonWriter.endArray();
- mJsonWriter.flush();
- mJsonWriter.close();
- } finally {
- boolean success = mFile.setWritable(false, false);
- mLoggingState = LOGGING_STATE_STOPPED;
- }
- return null;
+ public synchronized void close() {
+ mExecutor.submit(new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ try {
+ if (mHasWrittenData) {
+ mJsonWriter.endArray();
+ mJsonWriter.flush();
+ mJsonWriter.close();
+ mHasWrittenData = false;
}
- });
- removeAnyScheduledFlush();
- mExecutor.shutdown();
- mLoggingState = LOGGING_STATE_STOPPING;
- break;
- case LOGGING_STATE_STOPPING:
- case LOGGING_STATE_STOPPED:
- }
- }
-
- public boolean isAlive() {
- switch (mLoggingState) {
- case LOGGING_STATE_UNSTARTED:
- case LOGGING_STATE_READY:
- case LOGGING_STATE_RUNNING:
- return true;
- }
- return false;
- }
-
- public void waitUntilStopped(final int timeoutInMs) throws InterruptedException {
+ } catch (Exception e) {
+ Log.d(TAG, "error when closing ResearchLog:");
+ e.printStackTrace();
+ } finally {
+ if (mFile.exists()) {
+ mFile.setWritable(false, false);
+ }
+ }
+ return null;
+ }
+ });
removeAnyScheduledFlush();
mExecutor.shutdown();
- mExecutor.awaitTermination(timeoutInMs, TimeUnit.MILLISECONDS);
}
+ private boolean mIsAbortSuccessful;
+
public synchronized void abort() {
- switch (mLoggingState) {
- case LOGGING_STATE_UNSTARTED:
- mLoggingState = LOGGING_STATE_STOPPED;
- isAbortSuccessful = true;
- break;
- case LOGGING_STATE_READY:
- case LOGGING_STATE_RUNNING:
- mExecutor.submit(new Callable<Object>() {
- @Override
- public Object call() throws Exception {
- try {
- mJsonWriter.endArray();
- mJsonWriter.close();
- } finally {
- isAbortSuccessful = mFile.delete();
- }
- return null;
+ mExecutor.submit(new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ try {
+ if (mHasWrittenData) {
+ mJsonWriter.endArray();
+ mJsonWriter.close();
+ mHasWrittenData = false;
}
- });
- removeAnyScheduledFlush();
- mExecutor.shutdown();
- mLoggingState = LOGGING_STATE_STOPPING;
- break;
- case LOGGING_STATE_STOPPING:
- case LOGGING_STATE_STOPPED:
- }
+ } finally {
+ mIsAbortSuccessful = mFile.delete();
+ }
+ return null;
+ }
+ });
+ removeAnyScheduledFlush();
+ mExecutor.shutdown();
}
- private boolean isAbortSuccessful;
- public boolean isAbortSuccessful() {
- return isAbortSuccessful;
+ public boolean blockingAbort() throws InterruptedException {
+ abort();
+ mExecutor.awaitTermination(ABORT_TIMEOUT_IN_MS, TimeUnit.MILLISECONDS);
+ return mIsAbortSuccessful;
+ }
+
+ public void awaitTermination(int delay, TimeUnit timeUnit) throws InterruptedException {
+ mExecutor.awaitTermination(delay, timeUnit);
}
/* package */ synchronized void flush() {
- switch (mLoggingState) {
- case LOGGING_STATE_UNSTARTED:
- break;
- case LOGGING_STATE_READY:
- case LOGGING_STATE_RUNNING:
- removeAnyScheduledFlush();
- mExecutor.submit(mFlushCallable);
- break;
- case LOGGING_STATE_STOPPING:
- case LOGGING_STATE_STOPPED:
- }
+ removeAnyScheduledFlush();
+ mExecutor.submit(mFlushCallable);
}
- private Callable<Object> mFlushCallable = new Callable<Object>() {
+ private final Callable<Object> mFlushCallable = new Callable<Object>() {
@Override
public Object call() throws Exception {
- if (mLoggingState == LOGGING_STATE_RUNNING) {
- mJsonWriter.flush();
- }
+ mJsonWriter.flush();
return null;
}
};
@@ -224,56 +178,40 @@
mFlushFuture = mExecutor.schedule(mFlushCallable, FLUSH_DELAY_IN_MS, TimeUnit.MILLISECONDS);
}
- public synchronized void publishPublicEvents(final LogUnit logUnit) {
- switch (mLoggingState) {
- case LOGGING_STATE_UNSTARTED:
- break;
- case LOGGING_STATE_READY:
- case LOGGING_STATE_RUNNING:
- mExecutor.submit(new Callable<Object>() {
- @Override
- public Object call() throws Exception {
- logUnit.publishPublicEventsTo(ResearchLog.this);
- scheduleFlush();
- return null;
- }
- });
- break;
- case LOGGING_STATE_STOPPING:
- case LOGGING_STATE_STOPPED:
- }
- }
-
- public synchronized void publishAllEvents(final LogUnit logUnit) {
- switch (mLoggingState) {
- case LOGGING_STATE_UNSTARTED:
- break;
- case LOGGING_STATE_READY:
- case LOGGING_STATE_RUNNING:
- mExecutor.submit(new Callable<Object>() {
- @Override
- public Object call() throws Exception {
- logUnit.publishAllEventsTo(ResearchLog.this);
- scheduleFlush();
- return null;
- }
- });
- break;
- case LOGGING_STATE_STOPPING:
- case LOGGING_STATE_STOPPED:
+ public synchronized void publish(final LogUnit logUnit, final boolean isIncludingPrivateData) {
+ try {
+ mExecutor.submit(new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ logUnit.publishTo(ResearchLog.this, isIncludingPrivateData);
+ scheduleFlush();
+ return null;
+ }
+ });
+ } catch (RejectedExecutionException e) {
+ // TODO: Add code to record loss of data, and report.
}
}
private static final String CURRENT_TIME_KEY = "_ct";
private static final String UPTIME_KEY = "_ut";
private static final String EVENT_TYPE_KEY = "_ty";
+
void outputEvent(final String[] keys, final Object[] values) {
- // not thread safe.
+ // Not thread safe.
+ if (keys.length == 0) {
+ return;
+ }
+ if (DEBUG) {
+ if (keys.length != values.length + 1) {
+ Log.d(TAG, "Key and Value list sizes do not match. " + keys[0]);
+ }
+ }
try {
if (mJsonWriter == NULL_JSON_WRITER) {
mJsonWriter = new JsonWriter(new BufferedWriter(new FileWriter(mFile)));
- mJsonWriter.setLenient(true);
mJsonWriter.beginArray();
+ mHasWrittenData = true;
}
mJsonWriter.beginObject();
mJsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis());
@@ -283,8 +221,8 @@
for (int i = 0; i < length; i++) {
mJsonWriter.name(keys[i + 1]);
Object value = values[i];
- if (value instanceof String) {
- mJsonWriter.value((String) value);
+ if (value instanceof CharSequence) {
+ mJsonWriter.value(value.toString());
} else if (value instanceof Number) {
mJsonWriter.value((Number) value);
} else if (value instanceof Boolean) {
@@ -331,14 +269,11 @@
SuggestedWords words = (SuggestedWords) value;
mJsonWriter.beginObject();
mJsonWriter.name("typedWordValid").value(words.mTypedWordValid);
- mJsonWriter.name("willAutoCorrect")
- .value(words.mWillAutoCorrect);
+ mJsonWriter.name("willAutoCorrect").value(words.mWillAutoCorrect);
mJsonWriter.name("isPunctuationSuggestions")
- .value(words.mIsPunctuationSuggestions);
- mJsonWriter.name("isObsoleteSuggestions")
- .value(words.mIsObsoleteSuggestions);
- mJsonWriter.name("isPrediction")
- .value(words.mIsPrediction);
+ .value(words.mIsPunctuationSuggestions);
+ mJsonWriter.name("isObsoleteSuggestions").value(words.mIsObsoleteSuggestions);
+ mJsonWriter.name("isPrediction").value(words.mIsPrediction);
mJsonWriter.name("words");
mJsonWriter.beginArray();
final int size = words.size();
@@ -363,8 +298,8 @@
try {
mJsonWriter.close();
} catch (IllegalStateException e1) {
- // assume that this is just the json not being terminated properly.
- // ignore
+ // Assume that this is just the json not being terminated properly.
+ // Ignore
} catch (IOException e1) {
e1.printStackTrace();
} finally {
diff --git a/java/src/com/android/inputmethod/research/ResearchLogUploader.java b/java/src/com/android/inputmethod/research/ResearchLogUploader.java
deleted file mode 100644
index 3b12130..0000000
--- a/java/src/com/android/inputmethod/research/ResearchLogUploader.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.research;
-
-import android.Manifest;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.BatteryManager;
-import android.util.Log;
-
-import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.R.string;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileFilter;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-
-public final class ResearchLogUploader {
- private static final String TAG = ResearchLogUploader.class.getSimpleName();
- private static final int UPLOAD_INTERVAL_IN_MS = 1000 * 60 * 15; // every 15 min
- private static final int BUF_SIZE = 1024 * 8;
-
- private final boolean mCanUpload;
- private final Context mContext;
- private final File mFilesDir;
- private final URL mUrl;
- private final ScheduledExecutorService mExecutor;
-
- private Runnable doUploadRunnable = new UploadRunnable(null, false);
-
- public ResearchLogUploader(final Context context, final File filesDir) {
- mContext = context;
- mFilesDir = filesDir;
- final PackageManager packageManager = context.getPackageManager();
- final boolean hasPermission = packageManager.checkPermission(Manifest.permission.INTERNET,
- context.getPackageName()) == PackageManager.PERMISSION_GRANTED;
- if (!hasPermission) {
- mCanUpload = false;
- mUrl = null;
- mExecutor = null;
- return;
- }
- URL tempUrl = null;
- boolean canUpload = false;
- ScheduledExecutorService executor = null;
- try {
- final String urlString = context.getString(R.string.research_logger_upload_url);
- if (urlString == null || urlString.equals("")) {
- return;
- }
- tempUrl = new URL(urlString);
- canUpload = true;
- executor = Executors.newSingleThreadScheduledExecutor();
- } catch (MalformedURLException e) {
- tempUrl = null;
- e.printStackTrace();
- return;
- } finally {
- mCanUpload = canUpload;
- mUrl = tempUrl;
- mExecutor = executor;
- }
- }
-
- public void start() {
- if (mCanUpload) {
- Log.d(TAG, "scheduling regular uploading");
- mExecutor.scheduleWithFixedDelay(doUploadRunnable, UPLOAD_INTERVAL_IN_MS,
- UPLOAD_INTERVAL_IN_MS, TimeUnit.MILLISECONDS);
- } else {
- Log.d(TAG, "no permission to upload");
- }
- }
-
- public void uploadNow(final Callback callback) {
- // Perform an immediate upload. Note that this should happen even if there is
- // another upload happening right now, as it may have missed the latest changes.
- // TODO: Reschedule regular upload tests starting from now.
- if (mCanUpload) {
- mExecutor.submit(new UploadRunnable(callback, true));
- }
- }
-
- public interface Callback {
- public void onUploadCompleted(final boolean success);
- }
-
- private boolean isExternallyPowered() {
- final Intent intent = mContext.registerReceiver(null, new IntentFilter(
- Intent.ACTION_BATTERY_CHANGED));
- final int pluggedState = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
- return pluggedState == BatteryManager.BATTERY_PLUGGED_AC
- || pluggedState == BatteryManager.BATTERY_PLUGGED_USB;
- }
-
- private boolean hasWifiConnection() {
- final ConnectivityManager manager =
- (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
- final NetworkInfo wifiInfo = manager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
- return wifiInfo.isConnected();
- }
-
- class UploadRunnable implements Runnable {
- private final Callback mCallback;
- private final boolean mForceUpload;
-
- public UploadRunnable(final Callback callback, final boolean forceUpload) {
- mCallback = callback;
- mForceUpload = forceUpload;
- }
-
- @Override
- public void run() {
- doUpload();
- }
-
- private void doUpload() {
- if (!mForceUpload && (!isExternallyPowered() || !hasWifiConnection())) {
- return;
- }
- if (mFilesDir == null) {
- return;
- }
- final File[] files = mFilesDir.listFiles(new FileFilter() {
- @Override
- public boolean accept(File pathname) {
- return pathname.getName().startsWith(ResearchLogger.FILENAME_PREFIX)
- && !pathname.canWrite();
- }
- });
- boolean success = true;
- if (files.length == 0) {
- success = false;
- }
- for (final File file : files) {
- if (!uploadFile(file)) {
- success = false;
- }
- }
- if (mCallback != null) {
- mCallback.onUploadCompleted(success);
- }
- }
-
- private boolean uploadFile(File file) {
- Log.d(TAG, "attempting upload of " + file.getAbsolutePath());
- boolean success = false;
- final int contentLength = (int) file.length();
- HttpURLConnection connection = null;
- InputStream fileIs = null;
- try {
- fileIs = new FileInputStream(file);
- connection = (HttpURLConnection) mUrl.openConnection();
- connection.setRequestMethod("PUT");
- connection.setDoOutput(true);
- connection.setFixedLengthStreamingMode(contentLength);
- final OutputStream os = connection.getOutputStream();
- final byte[] buf = new byte[BUF_SIZE];
- int numBytesRead;
- while ((numBytesRead = fileIs.read(buf)) != -1) {
- os.write(buf, 0, numBytesRead);
- }
- if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
- Log.d(TAG, "upload failed: " + connection.getResponseCode());
- InputStream netIs = connection.getInputStream();
- BufferedReader reader = new BufferedReader(new InputStreamReader(netIs));
- String line;
- while ((line = reader.readLine()) != null) {
- Log.d(TAG, "| " + reader.readLine());
- }
- reader.close();
- return success;
- }
- file.delete();
- success = true;
- Log.d(TAG, "upload successful");
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- if (fileIs != null) {
- try {
- fileIs.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- if (connection != null) {
- connection.disconnect();
- }
- }
- return success;
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index cf6f31a..918fcf5 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -18,11 +18,14 @@
import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET;
+import android.app.AlarmManager;
import android.app.AlertDialog;
import android.app.Dialog;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
+import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.PackageInfo;
@@ -34,15 +37,18 @@
import android.inputmethodservice.InputMethodService;
import android.os.Build;
import android.os.IBinder;
+import android.os.SystemClock;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Log;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.CorrectionInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.widget.Button;
@@ -51,9 +57,10 @@
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardId;
-import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.keyboard.KeyboardView;
import com.android.inputmethod.keyboard.MainKeyboardView;
+import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.LatinIME;
import com.android.inputmethod.latin.R;
@@ -64,11 +71,8 @@
import com.android.inputmethod.latin.define.ProductionFlag;
import java.io.File;
-import java.io.IOException;
import java.text.SimpleDateFormat;
-import java.util.ArrayList;
import java.util.Date;
-import java.util.List;
import java.util.Locale;
import java.util.UUID;
@@ -94,24 +98,26 @@
new SimpleDateFormat("yyyyMMddHHmmssS", Locale.US);
private static final boolean IS_SHOWING_INDICATOR = true;
private static final boolean IS_SHOWING_INDICATOR_CLEARLY = false;
+ public static final int FEEDBACK_WORD_BUFFER_SIZE = 5;
// constants related to specific log points
private static final String WHITESPACE_SEPARATORS = " \t\n\r";
private static final int MAX_INPUTVIEW_LENGTH_TO_CAPTURE = 8192; // must be >=1
private static final String PREF_RESEARCH_LOGGER_UUID_STRING = "pref_research_logger_uuid";
- private static final int ABORT_TIMEOUT_IN_MS = 10 * 1000; // timeout to notify user
private static final ResearchLogger sInstance = new ResearchLogger();
// to write to a different filename, e.g., for testing, set mFile before calling start()
/* package */ File mFilesDir;
/* package */ String mUUIDString;
/* package */ ResearchLog mMainResearchLog;
- // The mIntentionalResearchLog records all events for the session, private or not (excepting
+ // mFeedbackLog records all events for the session, private or not (excepting
// passwords). It is written to permanent storage only if the user explicitly commands
// the system to do so.
- /* package */ ResearchLog mIntentionalResearchLog;
- // LogUnits are queued here and released only when the user requests the intentional log.
- private List<LogUnit> mIntentionalResearchLogQueue = new ArrayList<LogUnit>();
+ // LogUnits are queued in the LogBuffers and published to the ResearchLogs when words are
+ // complete.
+ /* package */ ResearchLog mFeedbackLog;
+ /* package */ MainLogBuffer mMainLogBuffer;
+ /* package */ LogBuffer mFeedbackLogBuffer;
private boolean mIsPasswordView = false;
private boolean mIsLoggingSuspended = false;
@@ -133,20 +139,24 @@
// used to check whether words are not unique
private Suggest mSuggest;
private Dictionary mDictionary;
- private KeyboardSwitcher mKeyboardSwitcher;
+ private MainKeyboardView mMainKeyboardView;
private InputMethodService mInputMethodService;
+ private final Statistics mStatistics;
- private ResearchLogUploader mResearchLogUploader;
+ private Intent mUploadIntent;
+ private PendingIntent mUploadPendingIntent;
+
+ private LogUnit mCurrentLogUnit = new LogUnit();
private ResearchLogger() {
+ mStatistics = Statistics.getInstance();
}
public static ResearchLogger getInstance() {
return sInstance;
}
- public void init(final InputMethodService ims, final SharedPreferences prefs,
- KeyboardSwitcher keyboardSwitcher) {
+ public void init(final InputMethodService ims, final SharedPreferences prefs) {
assert ims != null;
if (ims == null) {
Log.w(TAG, "IMS is null; logging is off");
@@ -176,11 +186,33 @@
e.apply();
}
}
- mResearchLogUploader = new ResearchLogUploader(ims, mFilesDir);
- mResearchLogUploader.start();
- mKeyboardSwitcher = keyboardSwitcher;
mInputMethodService = ims;
mPrefs = prefs;
+ mUploadIntent = new Intent(mInputMethodService, UploaderService.class);
+ mUploadPendingIntent = PendingIntent.getService(mInputMethodService, 0, mUploadIntent, 0);
+
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ scheduleUploadingService(mInputMethodService);
+ }
+ }
+
+ /**
+ * Arrange for the UploaderService to be run on a regular basis.
+ *
+ * Any existing scheduled invocation of UploaderService is removed and rescheduled. This may
+ * cause problems if this method is called often and frequent updates are required, but since
+ * the user will likely be sleeping at some point, if the interval is less that the expected
+ * sleep duration and this method is not called during that time, the service should be invoked
+ * at some point.
+ */
+ public static void scheduleUploadingService(Context context) {
+ final Intent intent = new Intent(context, UploaderService.class);
+ final PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0);
+ final AlarmManager manager =
+ (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ manager.cancel(pendingIntent);
+ manager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ UploaderService.RUN_INTERVAL, UploaderService.RUN_INTERVAL, pendingIntent);
}
private void cleanupLoggingDir(final File dir, final long time) {
@@ -192,10 +224,15 @@
}
}
- public void mainKeyboardView_onAttachedToWindow() {
+ public void mainKeyboardView_onAttachedToWindow(final MainKeyboardView mainKeyboardView) {
+ mMainKeyboardView = mainKeyboardView;
maybeShowSplashScreen();
}
+ public void mainKeyboardView_onDetachedFromWindow() {
+ mMainKeyboardView = null;
+ }
+
private boolean hasSeenSplash() {
return mPrefs.getBoolean(PREF_RESEARCH_HAS_SEEN_SPLASH, false);
}
@@ -209,7 +246,8 @@
if (mSplashDialog != null && mSplashDialog.isShowing()) {
return;
}
- final IBinder windowToken = mKeyboardSwitcher.getMainKeyboardView().getWindowToken();
+ final IBinder windowToken = mMainKeyboardView != null
+ ? mMainKeyboardView.getWindowToken() : null;
if (windowToken == null) {
return;
}
@@ -257,50 +295,7 @@
final Editor e = mPrefs.edit();
e.putBoolean(PREF_RESEARCH_HAS_SEEN_SPLASH, true);
e.apply();
- }
-
- private File createLogFile(File filesDir) {
- final StringBuilder sb = new StringBuilder();
- sb.append(FILENAME_PREFIX).append('-');
- sb.append(mUUIDString).append('-');
- sb.append(TIMESTAMP_DATEFORMAT.format(new Date()));
- sb.append(FILENAME_SUFFIX);
- return new File(filesDir, sb.toString());
- }
-
- private void start() {
- maybeShowSplashScreen();
- updateSuspendedState();
- requestIndicatorRedraw();
- if (!isAllowedToLog()) {
- // Log.w(TAG, "not in usability mode; not logging");
- return;
- }
- if (mFilesDir == null || !mFilesDir.exists()) {
- Log.w(TAG, "IME storage directory does not exist. Cannot start logging.");
- return;
- }
- try {
- if (mMainResearchLog == null || !mMainResearchLog.isAlive()) {
- mMainResearchLog = new ResearchLog(createLogFile(mFilesDir));
- }
- mMainResearchLog.start();
- if (mIntentionalResearchLog == null || !mIntentionalResearchLog.isAlive()) {
- mIntentionalResearchLog = new ResearchLog(createLogFile(mFilesDir));
- }
- mIntentionalResearchLog.start();
- } catch (IOException e) {
- Log.w(TAG, "Could not start ResearchLogger.");
- }
- }
-
- /* package */ void stop() {
- if (mMainResearchLog != null) {
- mMainResearchLog.stop();
- }
- if (mIntentionalResearchLog != null) {
- mIntentionalResearchLog.stop();
- }
+ restart();
}
private void setLoggingAllowed(boolean enableLogging) {
@@ -313,40 +308,104 @@
sIsLogging = enableLogging;
}
- public boolean abort() {
- boolean didAbortMainLog = false;
- if (mMainResearchLog != null) {
- mMainResearchLog.abort();
- try {
- mMainResearchLog.waitUntilStopped(ABORT_TIMEOUT_IN_MS);
- } catch (InterruptedException e) {
- // interrupted early. carry on.
- }
- if (mMainResearchLog.isAbortSuccessful()) {
- didAbortMainLog = true;
- }
- mMainResearchLog = null;
- }
- boolean didAbortIntentionalLog = false;
- if (mIntentionalResearchLog != null) {
- mIntentionalResearchLog.abort();
- try {
- mIntentionalResearchLog.waitUntilStopped(ABORT_TIMEOUT_IN_MS);
- } catch (InterruptedException e) {
- // interrupted early. carry on.
- }
- if (mIntentionalResearchLog.isAbortSuccessful()) {
- didAbortIntentionalLog = true;
- }
- mIntentionalResearchLog = null;
- }
- return didAbortMainLog && didAbortIntentionalLog;
+ private File createLogFile(File filesDir) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(FILENAME_PREFIX).append('-');
+ sb.append(mUUIDString).append('-');
+ sb.append(TIMESTAMP_DATEFORMAT.format(new Date()));
+ sb.append(FILENAME_SUFFIX);
+ return new File(filesDir, sb.toString());
}
- /* package */ void flush() {
- if (mMainResearchLog != null) {
- mMainResearchLog.flush();
+ private void checkForEmptyEditor() {
+ if (mInputMethodService == null) {
+ return;
}
+ final InputConnection ic = mInputMethodService.getCurrentInputConnection();
+ if (ic == null) {
+ return;
+ }
+ final CharSequence textBefore = ic.getTextBeforeCursor(1, 0);
+ if (!TextUtils.isEmpty(textBefore)) {
+ mStatistics.setIsEmptyUponStarting(false);
+ return;
+ }
+ final CharSequence textAfter = ic.getTextAfterCursor(1, 0);
+ if (!TextUtils.isEmpty(textAfter)) {
+ mStatistics.setIsEmptyUponStarting(false);
+ return;
+ }
+ if (textBefore != null && textAfter != null) {
+ mStatistics.setIsEmptyUponStarting(true);
+ }
+ }
+
+ private void start() {
+ maybeShowSplashScreen();
+ updateSuspendedState();
+ requestIndicatorRedraw();
+ mStatistics.reset();
+ checkForEmptyEditor();
+ if (!isAllowedToLog()) {
+ // Log.w(TAG, "not in usability mode; not logging");
+ return;
+ }
+ if (mFilesDir == null || !mFilesDir.exists()) {
+ Log.w(TAG, "IME storage directory does not exist. Cannot start logging.");
+ return;
+ }
+ if (mMainLogBuffer == null) {
+ mMainResearchLog = new ResearchLog(createLogFile(mFilesDir));
+ mMainLogBuffer = new MainLogBuffer(mMainResearchLog);
+ mMainLogBuffer.setSuggest(mSuggest);
+ }
+ if (mFeedbackLogBuffer == null) {
+ mFeedbackLog = new ResearchLog(createLogFile(mFilesDir));
+ // LogBuffer is one more than FEEDBACK_WORD_BUFFER_SIZE, because it must also hold
+ // the feedback LogUnit itself.
+ mFeedbackLogBuffer = new LogBuffer(FEEDBACK_WORD_BUFFER_SIZE + 1);
+ }
+ }
+
+ /* package */ void stop() {
+ logStatistics();
+ commitCurrentLogUnit();
+
+ if (mMainLogBuffer != null) {
+ publishLogBuffer(mMainLogBuffer, mMainResearchLog, false /* isIncludingPrivateData */);
+ mMainResearchLog.close();
+ mMainLogBuffer = null;
+ }
+ if (mFeedbackLogBuffer != null) {
+ mFeedbackLog.close();
+ mFeedbackLogBuffer = null;
+ }
+ }
+
+ public boolean abort() {
+ boolean didAbortMainLog = false;
+ if (mMainLogBuffer != null) {
+ mMainLogBuffer.clear();
+ try {
+ didAbortMainLog = mMainResearchLog.blockingAbort();
+ } catch (InterruptedException e) {
+ // Don't know whether this succeeded or not. We assume not; this is reported
+ // to the caller.
+ }
+ mMainLogBuffer = null;
+ }
+ boolean didAbortFeedbackLog = false;
+ if (mFeedbackLogBuffer != null) {
+ mFeedbackLogBuffer.clear();
+ try {
+ didAbortFeedbackLog = mFeedbackLog.blockingAbort();
+ } catch (InterruptedException e) {
+ // Don't know whether this succeeded or not. We assume not; this is reported
+ // to the caller.
+ }
+ mFeedbackLogBuffer = null;
+ }
+ return didAbortMainLog && didAbortFeedbackLog;
}
private void restart() {
@@ -387,6 +446,8 @@
abort();
}
requestIndicatorRedraw();
+ mPrefs = prefs;
+ prefsChanged(prefs);
}
public void presentResearchDialog(final LatinIME latinIME) {
@@ -450,79 +511,44 @@
latinIME.launchKeyboardedDialogActivity(FeedbackActivity.class);
}
- private ResearchLog mFeedbackLog;
- private List<LogUnit> mFeedbackQueue;
- private ResearchLog mSavedMainResearchLog;
- private ResearchLog mSavedIntentionalResearchLog;
- private List<LogUnit> mSavedIntentionalResearchLogQueue;
-
- private void saveLogsForFeedback() {
- mFeedbackLog = mIntentionalResearchLog;
- if (mIntentionalResearchLogQueue != null) {
- mFeedbackQueue = new ArrayList<LogUnit>(mIntentionalResearchLogQueue);
- } else {
- mFeedbackQueue = null;
+ private static final String[] EVENTKEYS_FEEDBACK = {
+ "UserTimestamp", "contents"
+ };
+ public void sendFeedback(final String feedbackContents, final boolean includeHistory) {
+ if (mFeedbackLogBuffer == null) {
+ return;
}
- mSavedMainResearchLog = mMainResearchLog;
- mSavedIntentionalResearchLog = mIntentionalResearchLog;
- mSavedIntentionalResearchLogQueue = mIntentionalResearchLogQueue;
-
- mMainResearchLog = null;
- mIntentionalResearchLog = null;
- mIntentionalResearchLogQueue = new ArrayList<LogUnit>();
+ if (includeHistory) {
+ commitCurrentLogUnit();
+ } else {
+ mFeedbackLogBuffer.clear();
+ }
+ final LogUnit feedbackLogUnit = new LogUnit();
+ final Object[] values = {
+ feedbackContents
+ };
+ feedbackLogUnit.addLogStatement(EVENTKEYS_FEEDBACK, values,
+ false /* isPotentiallyPrivate */);
+ mFeedbackLogBuffer.shiftIn(feedbackLogUnit);
+ publishLogBuffer(mFeedbackLogBuffer, mFeedbackLog, true /* isIncludingPrivateData */);
+ mFeedbackLog.close();
+ uploadNow();
+ mFeedbackLog = new ResearchLog(createLogFile(mFilesDir));
}
- private static final int LOG_DRAIN_TIMEOUT_IN_MS = 1000 * 5;
- public void sendFeedback(final String feedbackContents, final boolean includeHistory) {
- if (includeHistory && mFeedbackLog != null) {
- try {
- LogUnit headerLogUnit = new LogUnit();
- headerLogUnit.addLogAtom(EVENTKEYS_INTENTIONAL_LOG, EVENTKEYS_NULLVALUES, false);
- mFeedbackLog.publishAllEvents(headerLogUnit);
- for (LogUnit logUnit : mFeedbackQueue) {
- mFeedbackLog.publishAllEvents(logUnit);
- }
- userFeedback(mFeedbackLog, feedbackContents);
- mFeedbackLog.stop();
- try {
- mFeedbackLog.waitUntilStopped(LOG_DRAIN_TIMEOUT_IN_MS);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- mIntentionalResearchLog = new ResearchLog(createLogFile(mFilesDir));
- mIntentionalResearchLog.start();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- mIntentionalResearchLogQueue.clear();
- }
- mResearchLogUploader.uploadNow(null);
- } else {
- // create a separate ResearchLog just for feedback
- final ResearchLog feedbackLog = new ResearchLog(createLogFile(mFilesDir));
- try {
- feedbackLog.start();
- userFeedback(feedbackLog, feedbackContents);
- feedbackLog.stop();
- feedbackLog.waitUntilStopped(LOG_DRAIN_TIMEOUT_IN_MS);
- mResearchLogUploader.uploadNow(null);
- } catch (IOException e) {
- e.printStackTrace();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
+ public void uploadNow() {
+ mInputMethodService.startService(mUploadIntent);
}
public void onLeavingSendFeedbackDialog() {
mInFeedbackDialog = false;
- mMainResearchLog = mSavedMainResearchLog;
- mIntentionalResearchLog = mSavedIntentionalResearchLog;
- mIntentionalResearchLogQueue = mSavedIntentionalResearchLogQueue;
}
public void initSuggest(Suggest suggest) {
mSuggest = suggest;
+ if (mMainLogBuffer != null) {
+ mMainLogBuffer.setSuggest(mSuggest);
+ }
}
private void setIsPasswordView(boolean isPasswordView) {
@@ -530,21 +556,17 @@
}
private boolean isAllowedToLog() {
- return !mIsPasswordView && !mIsLoggingSuspended && sIsLogging;
+ return !mIsPasswordView && !mIsLoggingSuspended && sIsLogging && !mInFeedbackDialog;
}
public void requestIndicatorRedraw() {
if (!IS_SHOWING_INDICATOR) {
return;
}
- if (mKeyboardSwitcher == null) {
+ if (mMainKeyboardView == null) {
return;
}
- final KeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
- if (mainKeyboardView == null) {
- return;
- }
- mainKeyboardView.invalidateAllKeys();
+ mMainKeyboardView.invalidateAllKeys();
}
@@ -577,13 +599,8 @@
}
}
- private static final String CURRENT_TIME_KEY = "_ct";
- private static final String UPTIME_KEY = "_ut";
- private static final String EVENT_TYPE_KEY = "_ty";
private static final Object[] EVENTKEYS_NULLVALUES = {};
- private LogUnit mCurrentLogUnit = new LogUnit();
-
/**
* Buffer a research log event, flagging it as privacy-sensitive.
*
@@ -599,10 +616,14 @@
final Object[] values) {
assert values.length + 1 == keys.length;
if (isAllowedToLog()) {
- mCurrentLogUnit.addLogAtom(keys, values, true);
+ mCurrentLogUnit.addLogStatement(keys, values, true /* isPotentiallyPrivate */);
}
}
+ private void setCurrentLogUnitContainsDigitFlag() {
+ mCurrentLogUnit.setContainsDigit();
+ }
+
/**
* Buffer a research log event, flaggint it as not privacy-sensitive.
*
@@ -618,139 +639,54 @@
private synchronized void enqueueEvent(final String[] keys, final Object[] values) {
assert values.length + 1 == keys.length;
if (isAllowedToLog()) {
- mCurrentLogUnit.addLogAtom(keys, values, false);
+ mCurrentLogUnit.addLogStatement(keys, values, false /* isPotentiallyPrivate */);
}
}
- // Used to track how often words are logged. Too-frequent logging can leak
- // semantics, disclosing private data.
- /* package for test */ static class LoggingFrequencyState {
- private static final int DEFAULT_WORD_LOG_FREQUENCY = 10;
- private int mWordsRemainingToSkip;
- private final int mFrequency;
-
- /**
- * Tracks how often words may be uploaded.
- *
- * @param frequency 1=Every word, 2=Every other word, etc.
- */
- public LoggingFrequencyState(int frequency) {
- mFrequency = frequency;
- mWordsRemainingToSkip = mFrequency;
- }
-
- public void onWordLogged() {
- mWordsRemainingToSkip = mFrequency;
- }
-
- public void onWordNotLogged() {
- if (mWordsRemainingToSkip > 1) {
- mWordsRemainingToSkip--;
- }
- }
-
- public boolean isSafeToLog() {
- return mWordsRemainingToSkip <= 1;
- }
- }
-
- /* package for test */ LoggingFrequencyState mLoggingFrequencyState =
- new LoggingFrequencyState(LoggingFrequencyState.DEFAULT_WORD_LOG_FREQUENCY);
-
- /* package for test */ boolean isPrivacyThreat(String word) {
- // Current checks:
- // - Word not in dictionary
- // - Word contains numbers
- // - Privacy-safe word not logged recently
- if (TextUtils.isEmpty(word)) {
- return false;
- }
- if (!mLoggingFrequencyState.isSafeToLog()) {
- return true;
- }
- final int length = word.length();
- boolean hasLetter = false;
- for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
- final int codePoint = Character.codePointAt(word, i);
- if (Character.isDigit(codePoint)) {
- return true;
- }
- if (Character.isLetter(codePoint)) {
- hasLetter = true;
- break; // Word may contain digits, but will only be allowed if in the dictionary.
- }
- }
- if (hasLetter) {
- if (mDictionary == null && mSuggest != null && mSuggest.hasMainDictionary()) {
- mDictionary = mSuggest.getMainDictionary();
- }
- if (mDictionary == null) {
- // Can't access dictionary. Assume privacy threat.
- return true;
- }
- return !(mDictionary.isValidWord(word));
- }
- // No letters, no numbers. Punctuation, space, or something else.
- return false;
- }
-
- private void onWordComplete(String word) {
- if (isPrivacyThreat(word)) {
- publishLogUnit(mCurrentLogUnit, true);
- mLoggingFrequencyState.onWordNotLogged();
- } else {
- publishLogUnit(mCurrentLogUnit, false);
- mLoggingFrequencyState.onWordLogged();
- }
- mCurrentLogUnit = new LogUnit();
- }
-
- private void publishLogUnit(LogUnit logUnit, boolean isPrivacySensitive) {
- if (!isAllowedToLog()) {
- return;
- }
- if (mMainResearchLog == null) {
- return;
- }
- if (isPrivacySensitive) {
- mMainResearchLog.publishPublicEvents(logUnit);
- } else {
- mMainResearchLog.publishAllEvents(logUnit);
- }
- mIntentionalResearchLogQueue.add(logUnit);
- }
-
- /* package */ void publishCurrentLogUnit(ResearchLog researchLog, boolean isPrivacySensitive) {
- publishLogUnit(mCurrentLogUnit, isPrivacySensitive);
- }
-
- static class LogUnit {
- private final List<String[]> mKeysList = new ArrayList<String[]>();
- private final List<Object[]> mValuesList = new ArrayList<Object[]>();
- private final List<Boolean> mIsPotentiallyPrivate = new ArrayList<Boolean>();
-
- private void addLogAtom(final String[] keys, final Object[] values,
- final Boolean isPotentiallyPrivate) {
- mKeysList.add(keys);
- mValuesList.add(values);
- mIsPotentiallyPrivate.add(isPotentiallyPrivate);
- }
-
- public void publishPublicEventsTo(ResearchLog researchLog) {
- final int size = mKeysList.size();
- for (int i = 0; i < size; i++) {
- if (!mIsPotentiallyPrivate.get(i)) {
- researchLog.outputEvent(mKeysList.get(i), mValuesList.get(i));
+ /* package for test */ void commitCurrentLogUnit() {
+ if (!mCurrentLogUnit.isEmpty()) {
+ if (mMainLogBuffer != null) {
+ mMainLogBuffer.shiftIn(mCurrentLogUnit);
+ if (mMainLogBuffer.isSafeToLog() && mMainResearchLog != null) {
+ publishLogBuffer(mMainLogBuffer, mMainResearchLog,
+ true /* isIncludingPrivateData */);
+ mMainLogBuffer.resetWordCounter();
}
}
+ if (mFeedbackLogBuffer != null) {
+ mFeedbackLogBuffer.shiftIn(mCurrentLogUnit);
+ }
+ mCurrentLogUnit = new LogUnit();
+ Log.d(TAG, "commitCurrentLogUnit");
}
+ }
- public void publishAllEventsTo(ResearchLog researchLog) {
- final int size = mKeysList.size();
- for (int i = 0; i < size; i++) {
- researchLog.outputEvent(mKeysList.get(i), mValuesList.get(i));
+ /* package for test */ void publishLogBuffer(final LogBuffer logBuffer,
+ final ResearchLog researchLog, final boolean isIncludingPrivateData) {
+ LogUnit logUnit;
+ while ((logUnit = logBuffer.shiftOut()) != null) {
+ researchLog.publish(logUnit, isIncludingPrivateData);
+ }
+ }
+
+ private boolean hasOnlyLetters(final String word) {
+ final int length = word.length();
+ for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
+ final int codePoint = word.codePointAt(i);
+ if (!Character.isLetter(codePoint)) {
+ return false;
}
}
+ return true;
+ }
+
+ private void onWordComplete(final String word) {
+ Log.d(TAG, "onWordComplete: " + word);
+ if (word != null && word.length() > 0 && hasOnlyLetters(word)) {
+ mCurrentLogUnit.setWord(word);
+ mStatistics.recordWordEntered();
+ }
+ commitCurrentLogUnit();
}
private static int scrubDigitFromCodePoint(int codePoint) {
@@ -803,12 +739,6 @@
return WORD_REPLACEMENT_STRING;
}
- // Special methods related to startup, shutdown, logging itself
-
- private static final String[] EVENTKEYS_INTENTIONAL_LOG = {
- "IntentionalLog"
- };
-
private static final String[] EVENTKEYS_LATINIME_ONSTARTINPUTVIEWINTERNAL = {
"LatinIMEOnStartInputViewInternal", "uuid", "packageName", "inputType", "imeOptions",
"fieldId", "display", "model", "prefs", "versionCode", "versionName", "outputFormatVersion"
@@ -816,9 +746,6 @@
public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo,
final SharedPreferences prefs) {
final ResearchLogger researchLogger = getInstance();
- if (researchLogger.mInFeedbackDialog) {
- researchLogger.saveLogsForFeedback();
- }
researchLogger.start();
if (editorInfo != null) {
final Context context = researchLogger.mInputMethodService;
@@ -846,32 +773,19 @@
stop();
}
- private static final String[] EVENTKEYS_LATINIME_COMMITTEXT = {
- "LatinIMECommitText", "typedWord"
- };
-
- public static void latinIME_commitText(final CharSequence typedWord) {
- final String scrubbedWord = scrubDigitsFromString(typedWord.toString());
- final Object[] values = {
- scrubbedWord
- };
- final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_COMMITTEXT, values);
- researchLogger.onWordComplete(scrubbedWord);
- }
-
private static final String[] EVENTKEYS_USER_FEEDBACK = {
"UserFeedback", "FeedbackContents"
};
- private void userFeedback(ResearchLog researchLog, String feedbackContents) {
- // this method is special; it directs the feedbackContents to a particular researchLog
- final LogUnit logUnit = new LogUnit();
+ private static final String[] EVENTKEYS_PREFS_CHANGED = {
+ "PrefsChanged", "prefs"
+ };
+ public static void prefsChanged(final SharedPreferences prefs) {
+ final ResearchLogger researchLogger = getInstance();
final Object[] values = {
- feedbackContents
+ prefs
};
- logUnit.addLogAtom(EVENTKEYS_USER_FEEDBACK, values, false);
- researchLog.publishAllEvents(logUnit);
+ researchLogger.enqueueEvent(EVENTKEYS_PREFS_CHANGED, values);
}
// Regular logging methods
@@ -908,51 +822,16 @@
"LatinIMEOnCodeInput", "code", "x", "y"
};
public static void latinIME_onCodeInput(final int code, final int x, final int y) {
+ final long time = SystemClock.uptimeMillis();
+ final ResearchLogger researchLogger = getInstance();
final Object[] values = {
Keyboard.printableCode(scrubDigitFromCodePoint(code)), x, y
};
- getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONCODEINPUT, values);
- }
-
- private static final String[] EVENTKEYS_CORRECTION = {
- "LogCorrection", "subgroup", "before", "after", "position"
- };
- public static void logCorrection(final String subgroup, final String before, final String after,
- final int position) {
- final Object[] values = {
- subgroup, scrubDigitsFromString(before), scrubDigitsFromString(after), position
- };
- getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_CORRECTION, values);
- }
-
- private static final String[] EVENTKEYS_LATINIME_COMMITCURRENTAUTOCORRECTION = {
- "LatinIMECommitCurrentAutoCorrection", "typedWord", "autoCorrection"
- };
- public static void latinIME_commitCurrentAutoCorrection(final String typedWord,
- final String autoCorrection) {
- final Object[] values = {
- scrubDigitsFromString(typedWord), scrubDigitsFromString(autoCorrection)
- };
- final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueuePotentiallyPrivateEvent(
- EVENTKEYS_LATINIME_COMMITCURRENTAUTOCORRECTION, values);
- }
-
- private static final String[] EVENTKEYS_LATINIME_DELETESURROUNDINGTEXT = {
- "LatinIMEDeleteSurroundingText", "length"
- };
- public static void latinIME_deleteSurroundingText(final int length) {
- final Object[] values = {
- length
- };
- getInstance().enqueueEvent(EVENTKEYS_LATINIME_DELETESURROUNDINGTEXT, values);
- }
-
- private static final String[] EVENTKEYS_LATINIME_DOUBLESPACEAUTOPERIOD = {
- "LatinIMEDoubleSpaceAutoPeriod"
- };
- public static void latinIME_doubleSpaceAutoPeriod() {
- getInstance().enqueueEvent(EVENTKEYS_LATINIME_DOUBLESPACEAUTOPERIOD, EVENTKEYS_NULLVALUES);
+ researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONCODEINPUT, values);
+ if (Character.isDigit(code)) {
+ researchLogger.setCurrentLogUnitContainsDigitFlag();
+ }
+ researchLogger.mStatistics.recordChar(code, time);
}
private static final String[] EVENTKEYS_LATINIME_ONDISPLAYCOMPLETIONS = {
@@ -979,6 +858,10 @@
public static void latinIME_onWindowHidden(final int savedSelectionStart,
final int savedSelectionEnd, final InputConnection ic) {
if (ic != null) {
+ // Capture the TextView contents. This will trigger onUpdateSelection(), so we
+ // set sLatinIMEExpectingUpdateSelection so that when onUpdateSelection() is called,
+ // it can tell that it was generated by the logging code, and not by the user, and
+ // therefore keep user-visible state as is.
ic.beginBatchEdit();
ic.performContextMenuAction(android.R.id.selectAll);
CharSequence charSequence = ic.getSelectedText(0);
@@ -1013,9 +896,7 @@
}
final ResearchLogger researchLogger = getInstance();
researchLogger.enqueueEvent(EVENTKEYS_LATINIME_ONWINDOWHIDDEN, values);
- // Play it safe. Remove privacy-sensitive events.
- researchLogger.publishLogUnit(researchLogger.mCurrentLogUnit, true);
- researchLogger.mCurrentLogUnit = new LogUnit();
+ researchLogger.commitCurrentLogUnit();
getInstance().stop();
}
}
@@ -1048,37 +929,15 @@
researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONUPDATESELECTION, values);
}
- private static final String[] EVENTKEYS_LATINIME_PERFORMEDITORACTION = {
- "LatinIMEPerformEditorAction", "imeActionNext"
- };
- public static void latinIME_performEditorAction(final int imeActionNext) {
- final Object[] values = {
- imeActionNext
- };
- getInstance().enqueueEvent(EVENTKEYS_LATINIME_PERFORMEDITORACTION, values);
- }
-
- private static final String[] EVENTKEYS_LATINIME_PICKAPPLICATIONSPECIFIEDCOMPLETION = {
- "LatinIMEPickApplicationSpecifiedCompletion", "index", "text", "x", "y"
- };
- public static void latinIME_pickApplicationSpecifiedCompletion(final int index,
- final CharSequence cs, int x, int y) {
- final Object[] values = {
- index, cs, x, y
- };
- final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueuePotentiallyPrivateEvent(
- EVENTKEYS_LATINIME_PICKAPPLICATIONSPECIFIEDCOMPLETION, values);
- }
-
private static final String[] EVENTKEYS_LATINIME_PICKSUGGESTIONMANUALLY = {
"LatinIMEPickSuggestionManually", "replacedWord", "index", "suggestion", "x", "y"
};
public static void latinIME_pickSuggestionManually(final String replacedWord,
- final int index, CharSequence suggestion, int x, int y) {
+ final int index, CharSequence suggestion) {
final Object[] values = {
- scrubDigitsFromString(replacedWord), index, suggestion == null ? null :
- scrubDigitsFromString(suggestion.toString()), x, y
+ scrubDigitsFromString(replacedWord), index,
+ (suggestion == null ? null : scrubDigitsFromString(suggestion.toString())),
+ Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE
};
final ResearchLogger researchLogger = getInstance();
researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_PICKSUGGESTIONMANUALLY,
@@ -1089,28 +948,14 @@
"LatinIMEPunctuationSuggestion", "index", "suggestion", "x", "y"
};
public static void latinIME_punctuationSuggestion(final int index,
- final CharSequence suggestion, int x, int y) {
+ final CharSequence suggestion) {
final Object[] values = {
- index, suggestion, x, y
+ index, suggestion,
+ Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE
};
getInstance().enqueueEvent(EVENTKEYS_LATINIME_PUNCTUATIONSUGGESTION, values);
}
- private static final String[] EVENTKEYS_LATINIME_REVERTDOUBLESPACEWHILEINBATCHEDIT = {
- "LatinIMERevertDoubleSpaceWhileInBatchEdit"
- };
- public static void latinIME_revertDoubleSpaceWhileInBatchEdit() {
- getInstance().enqueueEvent(EVENTKEYS_LATINIME_REVERTDOUBLESPACEWHILEINBATCHEDIT,
- EVENTKEYS_NULLVALUES);
- }
-
- private static final String[] EVENTKEYS_LATINIME_REVERTSWAPPUNCTUATION = {
- "LatinIMERevertSwapPunctuation"
- };
- public static void latinIME_revertSwapPunctuation() {
- getInstance().enqueueEvent(EVENTKEYS_LATINIME_REVERTSWAPPUNCTUATION, EVENTKEYS_NULLVALUES);
- }
-
private static final String[] EVENTKEYS_LATINIME_SENDKEYCODEPOINT = {
"LatinIMESendKeyCodePoint", "code"
};
@@ -1118,15 +963,18 @@
final Object[] values = {
Keyboard.printableCode(scrubDigitFromCodePoint(code))
};
- getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_SENDKEYCODEPOINT, values);
+ final ResearchLogger researchLogger = getInstance();
+ researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_SENDKEYCODEPOINT, values);
+ if (Character.isDigit(code)) {
+ researchLogger.setCurrentLogUnitContainsDigitFlag();
+ }
}
- private static final String[] EVENTKEYS_LATINIME_SWAPSWAPPERANDSPACEWHILEINBATCHEDIT = {
- "LatinIMESwapSwapperAndSpaceWhileInBatchEdit"
+ private static final String[] EVENTKEYS_LATINIME_SWAPSWAPPERANDSPACE = {
+ "LatinIMESwapSwapperAndSpace"
};
- public static void latinIME_swapSwapperAndSpaceWhileInBatchEdit() {
- getInstance().enqueueEvent(EVENTKEYS_LATINIME_SWAPSWAPPERANDSPACEWHILEINBATCHEDIT,
- EVENTKEYS_NULLVALUES);
+ public static void latinIME_swapSwapperAndSpace() {
+ getInstance().enqueueEvent(EVENTKEYS_LATINIME_SWAPSWAPPERANDSPACE, EVENTKEYS_NULLVALUES);
}
private static final String[] EVENTKEYS_MAINKEYBOARDVIEW_ONLONGPRESS = {
@@ -1245,6 +1093,128 @@
getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_POINTERTRACKER_ONMOVEEVENT, values);
}
+ private static final String[] EVENTKEYS_RICHINPUTCONNECTION_COMMITCOMPLETION = {
+ "RichInputConnectionCommitCompletion", "completionInfo"
+ };
+ public static void richInputConnection_commitCompletion(final CompletionInfo completionInfo) {
+ final Object[] values = {
+ completionInfo
+ };
+ final ResearchLogger researchLogger = getInstance();
+ researchLogger.enqueuePotentiallyPrivateEvent(
+ EVENTKEYS_RICHINPUTCONNECTION_COMMITCOMPLETION, values);
+ }
+
+ // Disabled for privacy-protection reasons. Because this event comes after
+ // richInputConnection_commitText, which is the event used to separate LogUnits, the
+ // data in this event can be associated with the next LogUnit, revealing information
+ // about the current word even if it was supposed to be suppressed. The occurrance of
+ // autocorrection can be determined by examining the difference between the text strings in
+ // the last call to richInputConnection_setComposingText before
+ // richInputConnection_commitText, so it's not a data loss.
+ // TODO: Figure out how to log this event without loss of privacy.
+ /*
+ private static final String[] EVENTKEYS_RICHINPUTCONNECTION_COMMITCORRECTION = {
+ "RichInputConnectionCommitCorrection", "typedWord", "autoCorrection"
+ };
+ */
+ public static void richInputConnection_commitCorrection(CorrectionInfo correctionInfo) {
+ /*
+ final String typedWord = correctionInfo.getOldText().toString();
+ final String autoCorrection = correctionInfo.getNewText().toString();
+ final Object[] values = {
+ scrubDigitsFromString(typedWord), scrubDigitsFromString(autoCorrection)
+ };
+ final ResearchLogger researchLogger = getInstance();
+ researchLogger.enqueuePotentiallyPrivateEvent(
+ EVENTKEYS_RICHINPUTCONNECTION_COMMITCORRECTION, values);
+ */
+ }
+
+ private static final String[] EVENTKEYS_RICHINPUTCONNECTION_COMMITTEXT = {
+ "RichInputConnectionCommitText", "typedWord", "newCursorPosition"
+ };
+ public static void richInputConnection_commitText(final CharSequence typedWord,
+ final int newCursorPosition) {
+ final String scrubbedWord = scrubDigitsFromString(typedWord.toString());
+ final Object[] values = {
+ scrubbedWord, newCursorPosition
+ };
+ final ResearchLogger researchLogger = getInstance();
+ researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_RICHINPUTCONNECTION_COMMITTEXT,
+ values);
+ researchLogger.onWordComplete(scrubbedWord);
+ }
+
+ private static final String[] EVENTKEYS_RICHINPUTCONNECTION_DELETESURROUNDINGTEXT = {
+ "RichInputConnectionDeleteSurroundingText", "beforeLength", "afterLength"
+ };
+ public static void richInputConnection_deleteSurroundingText(final int beforeLength,
+ final int afterLength) {
+ final Object[] values = {
+ beforeLength, afterLength
+ };
+ getInstance().enqueuePotentiallyPrivateEvent(
+ EVENTKEYS_RICHINPUTCONNECTION_DELETESURROUNDINGTEXT, values);
+ }
+
+ private static final String[] EVENTKEYS_RICHINPUTCONNECTION_FINISHCOMPOSINGTEXT = {
+ "RichInputConnectionFinishComposingText"
+ };
+ public static void richInputConnection_finishComposingText() {
+ getInstance().enqueueEvent(EVENTKEYS_RICHINPUTCONNECTION_FINISHCOMPOSINGTEXT,
+ EVENTKEYS_NULLVALUES);
+ }
+
+ private static final String[] EVENTKEYS_RICHINPUTCONNECTION_PERFORMEDITORACTION = {
+ "RichInputConnectionPerformEditorAction", "imeActionNext"
+ };
+ public static void richInputConnection_performEditorAction(final int imeActionNext) {
+ final Object[] values = {
+ imeActionNext
+ };
+ getInstance().enqueueEvent(EVENTKEYS_RICHINPUTCONNECTION_PERFORMEDITORACTION, values);
+ }
+
+ private static final String[] EVENTKEYS_RICHINPUTCONNECTION_SENDKEYEVENT = {
+ "RichInputConnectionSendKeyEvent", "eventTime", "action", "code"
+ };
+ public static void richInputConnection_sendKeyEvent(final KeyEvent keyEvent) {
+ final Object[] values = {
+ keyEvent.getEventTime(),
+ keyEvent.getAction(),
+ keyEvent.getKeyCode()
+ };
+ getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_RICHINPUTCONNECTION_SENDKEYEVENT,
+ values);
+ }
+
+ private static final String[] EVENTKEYS_RICHINPUTCONNECTION_SETCOMPOSINGTEXT = {
+ "RichInputConnectionSetComposingText", "text", "newCursorPosition"
+ };
+ public static void richInputConnection_setComposingText(final CharSequence text,
+ final int newCursorPosition) {
+ if (text == null) {
+ throw new RuntimeException("setComposingText is null");
+ }
+ final Object[] values = {
+ text, newCursorPosition
+ };
+ getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_RICHINPUTCONNECTION_SETCOMPOSINGTEXT,
+ values);
+ }
+
+ private static final String[] EVENTKEYS_RICHINPUTCONNECTION_SETSELECTION = {
+ "RichInputConnectionSetSelection", "from", "to"
+ };
+ public static void richInputConnection_setSelection(final int from, final int to) {
+ final Object[] values = {
+ from, to
+ };
+ getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_RICHINPUTCONNECTION_SETSELECTION,
+ values);
+ }
+
private static final String[] EVENTKEYS_SUDDENJUMPINGTOUCHEVENTHANDLER_ONTOUCHEVENT = {
"SuddenJumpingTouchEventHandlerOnTouchEvent", "motionEvent"
};
@@ -1277,4 +1247,24 @@
public void userTimestamp() {
getInstance().enqueueEvent(EVENTKEYS_USER_TIMESTAMP, EVENTKEYS_NULLVALUES);
}
+
+ private static final String[] EVENTKEYS_STATISTICS = {
+ "Statistics", "charCount", "letterCount", "numberCount", "spaceCount", "deleteOpsCount",
+ "wordCount", "isEmptyUponStarting", "isEmptinessStateKnown", "averageTimeBetweenKeys",
+ "averageTimeBeforeDelete", "averageTimeDuringRepeatedDelete", "averageTimeAfterDelete"
+ };
+ private static void logStatistics() {
+ final ResearchLogger researchLogger = getInstance();
+ final Statistics statistics = researchLogger.mStatistics;
+ final Object[] values = {
+ statistics.mCharCount, statistics.mLetterCount, statistics.mNumberCount,
+ statistics.mSpaceCount, statistics.mDeleteKeyCount,
+ statistics.mWordCount, statistics.mIsEmptyUponStarting,
+ statistics.mIsEmptinessStateKnown, statistics.mKeyCounter.getAverageTime(),
+ statistics.mBeforeDeleteKeyCounter.getAverageTime(),
+ statistics.mDuringRepeatedDeleteKeysCounter.getAverageTime(),
+ statistics.mAfterDeleteKeyCounter.getAverageTime()
+ };
+ researchLogger.enqueueEvent(EVENTKEYS_STATISTICS, values);
+ }
}
diff --git a/java/src/com/android/inputmethod/research/Statistics.java b/java/src/com/android/inputmethod/research/Statistics.java
new file mode 100644
index 0000000..eab465a
--- /dev/null
+++ b/java/src/com/android/inputmethod/research/Statistics.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.research;
+
+import com.android.inputmethod.keyboard.Keyboard;
+
+public class Statistics {
+ // Number of characters entered during a typing session
+ int mCharCount;
+ // Number of letter characters entered during a typing session
+ int mLetterCount;
+ // Number of number characters entered
+ int mNumberCount;
+ // Number of space characters entered
+ int mSpaceCount;
+ // Number of delete operations entered (taps on the backspace key)
+ int mDeleteKeyCount;
+ // Number of words entered during a session.
+ int mWordCount;
+ // Whether the text field was empty upon editing
+ boolean mIsEmptyUponStarting;
+ boolean mIsEmptinessStateKnown;
+
+ // Timers to count average time to enter a key, first press a delete key,
+ // between delete keys, and then to return typing after a delete key.
+ final AverageTimeCounter mKeyCounter = new AverageTimeCounter();
+ final AverageTimeCounter mBeforeDeleteKeyCounter = new AverageTimeCounter();
+ final AverageTimeCounter mDuringRepeatedDeleteKeysCounter = new AverageTimeCounter();
+ final AverageTimeCounter mAfterDeleteKeyCounter = new AverageTimeCounter();
+
+ static class AverageTimeCounter {
+ int mCount;
+ int mTotalTime;
+
+ public void reset() {
+ mCount = 0;
+ mTotalTime = 0;
+ }
+
+ public void add(long deltaTime) {
+ mCount++;
+ mTotalTime += deltaTime;
+ }
+
+ public int getAverageTime() {
+ if (mCount == 0) {
+ return 0;
+ }
+ return mTotalTime / mCount;
+ }
+ }
+
+ // To account for the interruptions when the user's attention is directed elsewhere, times
+ // longer than MIN_TYPING_INTERMISSION are not counted when estimating this statistic.
+ public static final int MIN_TYPING_INTERMISSION = 2 * 1000; // in milliseconds
+ public static final int MIN_DELETION_INTERMISSION = 10 * 1000; // in milliseconds
+
+ // The last time that a tap was performed
+ private long mLastTapTime;
+ // The type of the last keypress (delete key or not)
+ boolean mIsLastKeyDeleteKey;
+
+ private static final Statistics sInstance = new Statistics();
+
+ public static Statistics getInstance() {
+ return sInstance;
+ }
+
+ private Statistics() {
+ reset();
+ }
+
+ public void reset() {
+ mCharCount = 0;
+ mLetterCount = 0;
+ mNumberCount = 0;
+ mSpaceCount = 0;
+ mDeleteKeyCount = 0;
+ mWordCount = 0;
+ mIsEmptyUponStarting = true;
+ mIsEmptinessStateKnown = false;
+ mKeyCounter.reset();
+ mBeforeDeleteKeyCounter.reset();
+ mDuringRepeatedDeleteKeysCounter.reset();
+ mAfterDeleteKeyCounter.reset();
+
+ mLastTapTime = 0;
+ mIsLastKeyDeleteKey = false;
+ }
+
+ public void recordChar(int codePoint, long time) {
+ final long delta = time - mLastTapTime;
+ if (codePoint == Keyboard.CODE_DELETE) {
+ mDeleteKeyCount++;
+ if (delta < MIN_DELETION_INTERMISSION) {
+ if (mIsLastKeyDeleteKey) {
+ mDuringRepeatedDeleteKeysCounter.add(delta);
+ } else {
+ mBeforeDeleteKeyCounter.add(delta);
+ }
+ }
+ mIsLastKeyDeleteKey = true;
+ } else {
+ mCharCount++;
+ if (Character.isDigit(codePoint)) {
+ mNumberCount++;
+ }
+ if (Character.isLetter(codePoint)) {
+ mLetterCount++;
+ }
+ if (Character.isSpaceChar(codePoint)) {
+ mSpaceCount++;
+ }
+ if (mIsLastKeyDeleteKey && delta < MIN_DELETION_INTERMISSION) {
+ mAfterDeleteKeyCounter.add(delta);
+ } else if (!mIsLastKeyDeleteKey && delta < MIN_TYPING_INTERMISSION) {
+ mKeyCounter.add(delta);
+ }
+ mIsLastKeyDeleteKey = false;
+ }
+ mLastTapTime = time;
+ }
+
+ public void recordWordEntered() {
+ mWordCount++;
+ }
+
+ public void setIsEmptyUponStarting(final boolean isEmpty) {
+ mIsEmptyUponStarting = isEmpty;
+ mIsEmptinessStateKnown = true;
+ }
+}
diff --git a/java/src/com/android/inputmethod/research/UploaderService.java b/java/src/com/android/inputmethod/research/UploaderService.java
new file mode 100644
index 0000000..7a57490
--- /dev/null
+++ b/java/src/com/android/inputmethod/research/UploaderService.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.research;
+
+import android.Manifest;
+import android.app.AlarmManager;
+import android.app.IntentService;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.BatteryManager;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.inputmethod.latin.R;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+public final class UploaderService extends IntentService {
+ private static final String TAG = UploaderService.class.getSimpleName();
+ public static final long RUN_INTERVAL = AlarmManager.INTERVAL_HOUR;
+ private static final String EXTRA_UPLOAD_UNCONDITIONALLY = UploaderService.class.getName()
+ + ".extra.UPLOAD_UNCONDITIONALLY";
+ private static final int BUF_SIZE = 1024 * 8;
+ protected static final int TIMEOUT_IN_MS = 1000 * 4;
+
+ private boolean mCanUpload;
+ private File mFilesDir;
+ private URL mUrl;
+
+ public UploaderService() {
+ super("Research Uploader Service");
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ mCanUpload = false;
+ mFilesDir = null;
+ mUrl = null;
+
+ final PackageManager packageManager = getPackageManager();
+ final boolean hasPermission = packageManager.checkPermission(Manifest.permission.INTERNET,
+ getPackageName()) == PackageManager.PERMISSION_GRANTED;
+ if (!hasPermission) {
+ return;
+ }
+
+ try {
+ final String urlString = getString(R.string.research_logger_upload_url);
+ if (urlString == null || urlString.equals("")) {
+ return;
+ }
+ mFilesDir = getFilesDir();
+ mUrl = new URL(urlString);
+ mCanUpload = true;
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ if (!mCanUpload) {
+ return;
+ }
+ boolean isUploadingUnconditionally = false;
+ Bundle bundle = intent.getExtras();
+ if (bundle != null && bundle.containsKey(EXTRA_UPLOAD_UNCONDITIONALLY)) {
+ isUploadingUnconditionally = bundle.getBoolean(EXTRA_UPLOAD_UNCONDITIONALLY);
+ }
+ doUpload(isUploadingUnconditionally);
+ }
+
+ private boolean isExternallyPowered() {
+ final Intent intent = registerReceiver(null, new IntentFilter(
+ Intent.ACTION_BATTERY_CHANGED));
+ final int pluggedState = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
+ return pluggedState == BatteryManager.BATTERY_PLUGGED_AC
+ || pluggedState == BatteryManager.BATTERY_PLUGGED_USB;
+ }
+
+ private boolean hasWifiConnection() {
+ final ConnectivityManager manager =
+ (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
+ final NetworkInfo wifiInfo = manager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+ return wifiInfo.isConnected();
+ }
+
+ private void doUpload(final boolean isUploadingUnconditionally) {
+ if (!isUploadingUnconditionally && (!isExternallyPowered() || !hasWifiConnection())) {
+ return;
+ }
+ if (mFilesDir == null) {
+ return;
+ }
+ final File[] files = mFilesDir.listFiles(new FileFilter() {
+ @Override
+ public boolean accept(File pathname) {
+ return pathname.getName().startsWith(ResearchLogger.FILENAME_PREFIX)
+ && !pathname.canWrite();
+ }
+ });
+ boolean success = true;
+ if (files.length == 0) {
+ success = false;
+ }
+ for (final File file : files) {
+ if (!uploadFile(file)) {
+ success = false;
+ }
+ }
+ }
+
+ private boolean uploadFile(File file) {
+ Log.d(TAG, "attempting upload of " + file.getAbsolutePath());
+ boolean success = false;
+ final int contentLength = (int) file.length();
+ HttpURLConnection connection = null;
+ InputStream fileInputStream = null;
+ try {
+ fileInputStream = new FileInputStream(file);
+ connection = (HttpURLConnection) mUrl.openConnection();
+ connection.setRequestMethod("PUT");
+ connection.setDoOutput(true);
+ connection.setFixedLengthStreamingMode(contentLength);
+ final OutputStream os = connection.getOutputStream();
+ final byte[] buf = new byte[BUF_SIZE];
+ int numBytesRead;
+ while ((numBytesRead = fileInputStream.read(buf)) != -1) {
+ os.write(buf, 0, numBytesRead);
+ }
+ if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
+ Log.d(TAG, "upload failed: " + connection.getResponseCode());
+ InputStream netInputStream = connection.getInputStream();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(netInputStream));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ Log.d(TAG, "| " + reader.readLine());
+ }
+ reader.close();
+ return success;
+ }
+ file.delete();
+ success = true;
+ Log.d(TAG, "upload successful");
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ if (fileInputStream != null) {
+ try {
+ fileInputStream.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ if (connection != null) {
+ connection.disconnect();
+ }
+ }
+ return success;
+ }
+}
diff --git a/native/jni/Android.mk b/native/jni/Android.mk
index 2fd8982..567648f 100644
--- a/native/jni/Android.mk
+++ b/native/jni/Android.mk
@@ -36,7 +36,7 @@
LATIN_IME_JNI_SRC_FILES := \
com_android_inputmethod_keyboard_ProximityInfo.cpp \
com_android_inputmethod_latin_BinaryDictionary.cpp \
- com_android_inputmethod_latin_NativeUtils.cpp \
+ com_android_inputmethod_latin_DicTraverseSession.cpp \
jni_common.cpp
LATIN_IME_CORE_SRC_FILES := \
@@ -46,6 +46,7 @@
char_utils.cpp \
correction.cpp \
dictionary.cpp \
+ dic_traverse_wrapper.cpp \
proximity_info.cpp \
proximity_info_state.cpp \
unigram_dictionary.cpp \
diff --git a/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp b/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
index 74390cc..560b3a5 100644
--- a/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
+++ b/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
@@ -16,8 +16,6 @@
#define LOG_TAG "LatinIME: jni: ProximityInfo"
-#include <string>
-
#include "com_android_inputmethod_keyboard_ProximityInfo.h"
#include "jni.h"
#include "jni_common.h"
@@ -26,53 +24,27 @@
namespace latinime {
static jlong latinime_Keyboard_setProximityInfo(JNIEnv *env, jobject object,
- jstring localejStr, jint maxProximityCharsSize, jint displayWidth, jint displayHeight,
- jint gridWidth, jint gridHeight, jint mostCommonkeyWidth, jintArray proximityCharsArray,
- jint keyCount, jintArray keyXCoordinateArray, jintArray keyYCoordinateArray,
- jintArray keyWidthArray, jintArray keyHeightArray, jintArray keyCharCodeArray,
- jfloatArray sweetSpotCenterXArray, jfloatArray sweetSpotCenterYArray,
- jfloatArray sweetSpotRadiusArray) {
- const char *localeStrPtr = env->GetStringUTFChars(localejStr, 0);
- const std::string localeStr(localeStrPtr);
- jint *proximityChars = env->GetIntArrayElements(proximityCharsArray, 0);
- jint *keyXCoordinates = safeGetIntArrayElements(env, keyXCoordinateArray);
- jint *keyYCoordinates = safeGetIntArrayElements(env, keyYCoordinateArray);
- jint *keyWidths = safeGetIntArrayElements(env, keyWidthArray);
- jint *keyHeights = safeGetIntArrayElements(env, keyHeightArray);
- jint *keyCharCodes = safeGetIntArrayElements(env, keyCharCodeArray);
- jfloat *sweetSpotCenterXs = safeGetFloatArrayElements(env, sweetSpotCenterXArray);
- jfloat *sweetSpotCenterYs = safeGetFloatArrayElements(env, sweetSpotCenterYArray);
- jfloat *sweetSpotRadii = safeGetFloatArrayElements(env, sweetSpotRadiusArray);
- ProximityInfo *proximityInfo = new ProximityInfo(
- localeStr, maxProximityCharsSize, displayWidth, displayHeight, gridWidth, gridHeight,
- mostCommonkeyWidth, (const int32_t*)proximityChars, keyCount,
- (const int32_t*)keyXCoordinates, (const int32_t*)keyYCoordinates,
- (const int32_t*)keyWidths, (const int32_t*)keyHeights, (const int32_t*)keyCharCodes,
- (const float*)sweetSpotCenterXs, (const float*)sweetSpotCenterYs,
- (const float*)sweetSpotRadii);
- safeReleaseFloatArrayElements(env, sweetSpotRadiusArray, sweetSpotRadii);
- safeReleaseFloatArrayElements(env, sweetSpotCenterYArray, sweetSpotCenterYs);
- safeReleaseFloatArrayElements(env, sweetSpotCenterXArray, sweetSpotCenterXs);
- safeReleaseIntArrayElements(env, keyCharCodeArray, keyCharCodes);
- safeReleaseIntArrayElements(env, keyHeightArray, keyHeights);
- safeReleaseIntArrayElements(env, keyWidthArray, keyWidths);
- safeReleaseIntArrayElements(env, keyYCoordinateArray, keyYCoordinates);
- safeReleaseIntArrayElements(env, keyXCoordinateArray, keyXCoordinates);
- env->ReleaseIntArrayElements(proximityCharsArray, proximityChars, 0);
- env->ReleaseStringUTFChars(localejStr, localeStrPtr);
- return (jlong)proximityInfo;
+ jstring localeJStr, jint maxProximityCharsSize, jint displayWidth, jint displayHeight,
+ jint gridWidth, jint gridHeight, jint mostCommonkeyWidth, jintArray proximityChars,
+ jint keyCount, jintArray keyXCoordinates, jintArray keyYCoordinates,
+ jintArray keyWidths, jintArray keyHeights, jintArray keyCharCodes,
+ jfloatArray sweetSpotCenterXs, jfloatArray sweetSpotCenterYs, jfloatArray sweetSpotRadii) {
+ ProximityInfo *proximityInfo = new ProximityInfo(env, localeJStr, maxProximityCharsSize,
+ displayWidth, displayHeight, gridWidth, gridHeight, mostCommonkeyWidth, proximityChars,
+ keyCount, keyXCoordinates, keyYCoordinates, keyWidths, keyHeights, keyCharCodes,
+ sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii);
+ return reinterpret_cast<jlong>(proximityInfo);
}
static void latinime_Keyboard_release(JNIEnv *env, jobject object, jlong proximityInfo) {
- ProximityInfo *pi = (ProximityInfo*)proximityInfo;
- if (!pi) return;
+ ProximityInfo *pi = reinterpret_cast<ProximityInfo *>(proximityInfo);
delete pi;
}
static JNINativeMethod sKeyboardMethods[] = {
{"setProximityInfoNative", "(Ljava/lang/String;IIIIII[II[I[I[I[I[I[F[F[F)J",
- (void*)latinime_Keyboard_setProximityInfo},
- {"releaseProximityInfoNative", "(J)V", (void*)latinime_Keyboard_release}
+ reinterpret_cast<void *>(latinime_Keyboard_setProximityInfo)},
+ {"releaseProximityInfoNative", "(J)V", reinterpret_cast<void *>(latinime_Keyboard_release)}
};
int register_ProximityInfo(JNIEnv *env) {
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index 776f5f7..a20958a 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -14,15 +14,12 @@
* limitations under the License.
*/
+
+#include <cstring> // for memset()
+
#define LOG_TAG "LatinIME: jni: BinaryDictionary"
-#include "binary_format.h"
-#include "com_android_inputmethod_latin_BinaryDictionary.h"
-#include "correction.h"
-#include "defines.h"
-#include "dictionary.h"
-#include "jni.h"
-#include "jni_common.h"
+#include "defines.h" // for macros below
#ifdef USE_MMAP_FOR_DICTIONARY
#include <cerrno>
@@ -30,13 +27,21 @@
#include <sys/mman.h>
#else // USE_MMAP_FOR_DICTIONARY
#include <cstdlib>
+#include <cstdio> // for fopen() etc.
#endif // USE_MMAP_FOR_DICTIONARY
+#include "binary_format.h"
+#include "com_android_inputmethod_latin_BinaryDictionary.h"
+#include "correction.h"
+#include "dictionary.h"
+#include "jni.h"
+#include "jni_common.h"
+
namespace latinime {
class ProximityInfo;
-void releaseDictBuf(void *dictBuf, const size_t length, int fd);
+static void releaseDictBuf(const void *dictBuf, const size_t length, const int fd);
static jlong latinime_BinaryDictionary_open(JNIEnv *env, jobject object,
jstring sourceDir, jlong dictOffset, jlong dictSize,
@@ -44,11 +49,14 @@
jint maxPredictions) {
PROF_OPEN;
PROF_START(66);
- const char *sourceDirChars = env->GetStringUTFChars(sourceDir, 0);
- if (sourceDirChars == 0) {
+ const jsize sourceDirUtf8Length = env->GetStringUTFLength(sourceDir);
+ if (sourceDirUtf8Length <= 0) {
AKLOGE("DICT: Can't get sourceDir string");
return 0;
}
+ char sourceDirChars[sourceDirUtf8Length + 1];
+ env->GetStringUTFRegion(sourceDir, 0, env->GetStringLength(sourceDir), sourceDirChars);
+ sourceDirChars[sourceDirUtf8Length] = '\0';
int fd = 0;
void *dictBuf = 0;
int adjust = 0;
@@ -68,7 +76,7 @@
AKLOGE("DICT: Can't mmap dictionary. errno=%d", errno);
return 0;
}
- dictBuf = (void *)((char *)dictBuf + adjust);
+ dictBuf = static_cast<char *>(dictBuf) + adjust;
#else // USE_MMAP_FOR_DICTIONARY
/* malloc version */
FILE *file = 0;
@@ -98,17 +106,16 @@
return 0;
}
#endif // USE_MMAP_FOR_DICTIONARY
- env->ReleaseStringUTFChars(sourceDir, sourceDirChars);
-
if (!dictBuf) {
AKLOGE("DICT: dictBuf is null");
return 0;
}
Dictionary *dictionary = 0;
- if (BinaryFormat::UNKNOWN_FORMAT == BinaryFormat::detectFormat((uint8_t*)dictBuf)) {
+ if (BinaryFormat::UNKNOWN_FORMAT
+ == BinaryFormat::detectFormat(static_cast<uint8_t *>(dictBuf))) {
AKLOGE("DICT: dictionary format is unknown, bad magic number");
#ifdef USE_MMAP_FOR_DICTIONARY
- releaseDictBuf(((char*)dictBuf) - adjust, adjDictSize, fd);
+ releaseDictBuf(static_cast<const char *>(dictBuf) - adjust, adjDictSize, fd);
#else // USE_MMAP_FOR_DICTIONARY
releaseDictBuf(dictBuf, 0, 0);
#endif // USE_MMAP_FOR_DICTIONARY
@@ -122,106 +129,131 @@
}
static int latinime_BinaryDictionary_getSuggestions(JNIEnv *env, jobject object, jlong dict,
- jlong proximityInfo, jintArray xCoordinatesArray, jintArray yCoordinatesArray,
- jintArray timesArray, jintArray pointerIdArray, jintArray inputArray, jint arraySize,
- jint commitPoint, jboolean isGesture,
- jintArray prevWordForBigrams, jboolean useFullEditDistance, jcharArray outputArray,
- jintArray frequencyArray, jintArray spaceIndexArray, jintArray outputTypesArray) {
- Dictionary *dictionary = (Dictionary*) dict;
+ jlong proximityInfo, jlong dicTraverseSession, jintArray xCoordinatesArray,
+ jintArray yCoordinatesArray, jintArray timesArray, jintArray pointerIdsArray,
+ jintArray inputCodePointsArray, jint arraySize, jint commitPoint, jboolean isGesture,
+ jintArray prevWordCodePointsForBigrams, jboolean useFullEditDistance,
+ jcharArray outputCharsArray, jintArray scoresArray, jintArray spaceIndicesArray,
+ jintArray outputTypesArray) {
+ Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
if (!dictionary) return 0;
- ProximityInfo *pInfo = (ProximityInfo*)proximityInfo;
- int *xCoordinates = env->GetIntArrayElements(xCoordinatesArray, 0);
- int *yCoordinates = env->GetIntArrayElements(yCoordinatesArray, 0);
- int *times = env->GetIntArrayElements(timesArray, 0);
- int *pointerIds = env->GetIntArrayElements(pointerIdArray, 0);
- int *frequencies = env->GetIntArrayElements(frequencyArray, 0);
- int *inputCodes = env->GetIntArrayElements(inputArray, 0);
- jchar *outputChars = env->GetCharArrayElements(outputArray, 0);
- int *spaceIndices = env->GetIntArrayElements(spaceIndexArray, 0);
- int *outputTypes = env->GetIntArrayElements(outputTypesArray, 0);
- jint *prevWordChars = prevWordForBigrams
- ? env->GetIntArrayElements(prevWordForBigrams, 0) : 0;
- jsize prevWordLength = prevWordChars ? env->GetArrayLength(prevWordForBigrams) : 0;
+ ProximityInfo *pInfo = reinterpret_cast<ProximityInfo *>(proximityInfo);
+ void *traverseSession = reinterpret_cast<void *>(dicTraverseSession);
+
+ // Input values
+ int xCoordinates[arraySize];
+ int yCoordinates[arraySize];
+ int times[arraySize];
+ int pointerIds[arraySize];
+ const jsize inputCodePointsLength = env->GetArrayLength(inputCodePointsArray);
+ int inputCodePoints[inputCodePointsLength];
+ const jsize prevWordCodePointsLength =
+ prevWordCodePointsForBigrams ? env->GetArrayLength(prevWordCodePointsForBigrams) : 0;
+ int prevWordCodePointsInternal[prevWordCodePointsLength];
+ int *prevWordCodePoints = 0;
+ env->GetIntArrayRegion(xCoordinatesArray, 0, arraySize, xCoordinates);
+ env->GetIntArrayRegion(yCoordinatesArray, 0, arraySize, yCoordinates);
+ env->GetIntArrayRegion(timesArray, 0, arraySize, times);
+ env->GetIntArrayRegion(pointerIdsArray, 0, arraySize, pointerIds);
+ env->GetIntArrayRegion(inputCodePointsArray, 0, inputCodePointsLength, inputCodePoints);
+ if (prevWordCodePointsForBigrams) {
+ env->GetIntArrayRegion(prevWordCodePointsForBigrams, 0, prevWordCodePointsLength,
+ prevWordCodePointsInternal);
+ prevWordCodePoints = prevWordCodePointsInternal;
+ }
+
+ // Output values
+ // TODO: Should be "outputCodePointsLength" and "int outputCodePoints[]"
+ const jsize outputCharsLength = env->GetArrayLength(outputCharsArray);
+ unsigned short outputChars[outputCharsLength];
+ const jsize scoresLength = env->GetArrayLength(scoresArray);
+ int scores[scoresLength];
+ const jsize spaceIndicesLength = env->GetArrayLength(spaceIndicesArray);
+ int spaceIndices[spaceIndicesLength];
+ const jsize outputTypesLength = env->GetArrayLength(outputTypesArray);
+ int outputTypes[outputTypesLength];
+ memset(outputChars, 0, sizeof(outputChars));
+ memset(scores, 0, sizeof(scores));
+ memset(spaceIndices, 0, sizeof(spaceIndices));
+ memset(outputTypes, 0, sizeof(outputTypes));
int count;
- if (isGesture || arraySize > 1) {
- count = dictionary->getSuggestions(pInfo, xCoordinates, yCoordinates, times, pointerIds,
- inputCodes, arraySize, prevWordChars, prevWordLength, commitPoint, isGesture,
- useFullEditDistance, (unsigned short*) outputChars, frequencies, spaceIndices,
- outputTypes);
+ if (isGesture || arraySize > 0) {
+ count = dictionary->getSuggestions(pInfo, traverseSession, xCoordinates, yCoordinates,
+ times, pointerIds, inputCodePoints, arraySize, prevWordCodePoints,
+ prevWordCodePointsLength, commitPoint, isGesture, useFullEditDistance, outputChars,
+ scores, spaceIndices, outputTypes);
} else {
- count = dictionary->getBigrams(prevWordChars, prevWordLength, inputCodes,
- arraySize, (unsigned short*) outputChars, frequencies, outputTypes);
+ count = dictionary->getBigrams(prevWordCodePoints, prevWordCodePointsLength,
+ inputCodePoints, arraySize, outputChars, scores, outputTypes);
}
- if (prevWordChars) {
- env->ReleaseIntArrayElements(prevWordForBigrams, prevWordChars, JNI_ABORT);
- }
- env->ReleaseIntArrayElements(outputTypesArray, outputTypes, 0);
- env->ReleaseIntArrayElements(spaceIndexArray, spaceIndices, 0);
- env->ReleaseCharArrayElements(outputArray, outputChars, 0);
- env->ReleaseIntArrayElements(inputArray, inputCodes, JNI_ABORT);
- env->ReleaseIntArrayElements(frequencyArray, frequencies, 0);
- env->ReleaseIntArrayElements(pointerIdArray, pointerIds, 0);
- env->ReleaseIntArrayElements(timesArray, times, 0);
- env->ReleaseIntArrayElements(yCoordinatesArray, yCoordinates, 0);
- env->ReleaseIntArrayElements(xCoordinatesArray, xCoordinates, 0);
+ // Copy back the output values
+ // TODO: Should be SetIntArrayRegion()
+ env->SetCharArrayRegion(outputCharsArray, 0, outputCharsLength, outputChars);
+ env->SetIntArrayRegion(scoresArray, 0, scoresLength, scores);
+ env->SetIntArrayRegion(spaceIndicesArray, 0, spaceIndicesLength, spaceIndices);
+ env->SetIntArrayRegion(outputTypesArray, 0, outputTypesLength, outputTypes);
+
return count;
}
static jint latinime_BinaryDictionary_getFrequency(JNIEnv *env, jobject object, jlong dict,
- jintArray wordArray, jint wordLength) {
- Dictionary *dictionary = (Dictionary*)dict;
- if (!dictionary) return (jboolean) false;
- jint *word = env->GetIntArrayElements(wordArray, 0);
- jint result = dictionary->getFrequency(word, wordLength);
- env->ReleaseIntArrayElements(wordArray, word, JNI_ABORT);
- return result;
+ jintArray wordArray) {
+ Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
+ if (!dictionary) return 0;
+ const jsize codePointLength = env->GetArrayLength(wordArray);
+ int codePoints[codePointLength];
+ env->GetIntArrayRegion(wordArray, 0, codePointLength, codePoints);
+ return dictionary->getFrequency(codePoints, codePointLength);
}
static jboolean latinime_BinaryDictionary_isValidBigram(JNIEnv *env, jobject object, jlong dict,
jintArray wordArray1, jintArray wordArray2) {
- Dictionary *dictionary = (Dictionary*)dict;
+ Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
if (!dictionary) return (jboolean) false;
- jint *word1 = env->GetIntArrayElements(wordArray1, 0);
- jint *word2 = env->GetIntArrayElements(wordArray2, 0);
- jsize length1 = word1 ? env->GetArrayLength(wordArray1) : 0;
- jsize length2 = word2 ? env->GetArrayLength(wordArray2) : 0;
- jboolean result = dictionary->isValidBigram(word1, length1, word2, length2);
- env->ReleaseIntArrayElements(wordArray2, word2, JNI_ABORT);
- env->ReleaseIntArrayElements(wordArray1, word1, JNI_ABORT);
- return result;
+ const jsize codePointLength1 = env->GetArrayLength(wordArray1);
+ const jsize codePointLength2 = env->GetArrayLength(wordArray2);
+ int codePoints1[codePointLength1];
+ int codePoints2[codePointLength2];
+ env->GetIntArrayRegion(wordArray1, 0, codePointLength1, codePoints1);
+ env->GetIntArrayRegion(wordArray2, 0, codePointLength2, codePoints2);
+ return dictionary->isValidBigram(codePoints1, codePointLength1, codePoints2, codePointLength2);
}
static jfloat latinime_BinaryDictionary_calcNormalizedScore(JNIEnv *env, jobject object,
- jcharArray before, jint beforeLength, jcharArray after, jint afterLength, jint score) {
- jchar *beforeChars = env->GetCharArrayElements(before, 0);
- jchar *afterChars = env->GetCharArrayElements(after, 0);
- jfloat result = Correction::RankingAlgorithm::calcNormalizedScore((unsigned short*)beforeChars,
- beforeLength, (unsigned short*)afterChars, afterLength, score);
- env->ReleaseCharArrayElements(after, afterChars, JNI_ABORT);
- env->ReleaseCharArrayElements(before, beforeChars, JNI_ABORT);
- return result;
+ jcharArray before, jcharArray after, jint score) {
+ jsize beforeLength = env->GetArrayLength(before);
+ jsize afterLength = env->GetArrayLength(after);
+ jchar beforeChars[beforeLength];
+ jchar afterChars[afterLength];
+ env->GetCharArrayRegion(before, 0, beforeLength, beforeChars);
+ env->GetCharArrayRegion(after, 0, afterLength, afterChars);
+ return Correction::RankingAlgorithm::calcNormalizedScore(
+ static_cast<unsigned short *>(beforeChars), beforeLength,
+ static_cast<unsigned short *>(afterChars), afterLength, score);
}
static jint latinime_BinaryDictionary_editDistance(JNIEnv *env, jobject object,
- jcharArray before, jint beforeLength, jcharArray after, jint afterLength) {
- jchar *beforeChars = env->GetCharArrayElements(before, 0);
- jchar *afterChars = env->GetCharArrayElements(after, 0);
- jint result = Correction::RankingAlgorithm::editDistance(
- (unsigned short*)beforeChars, beforeLength, (unsigned short*)afterChars, afterLength);
- env->ReleaseCharArrayElements(after, afterChars, JNI_ABORT);
- env->ReleaseCharArrayElements(before, beforeChars, JNI_ABORT);
- return result;
+ jcharArray before, jcharArray after) {
+ jsize beforeLength = env->GetArrayLength(before);
+ jsize afterLength = env->GetArrayLength(after);
+ jchar beforeChars[beforeLength];
+ jchar afterChars[afterLength];
+ env->GetCharArrayRegion(before, 0, beforeLength, beforeChars);
+ env->GetCharArrayRegion(after, 0, afterLength, afterChars);
+ return Correction::RankingAlgorithm::editDistance(
+ static_cast<unsigned short *>(beforeChars), beforeLength,
+ static_cast<unsigned short *>(afterChars), afterLength);
}
static void latinime_BinaryDictionary_close(JNIEnv *env, jobject object, jlong dict) {
- Dictionary *dictionary = (Dictionary*)dict;
+ Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
if (!dictionary) return;
- void *dictBuf = dictionary->getDict();
+ const void *dictBuf = dictionary->getDict();
if (!dictBuf) return;
#ifdef USE_MMAP_FOR_DICTIONARY
- releaseDictBuf((void *)((char *)dictBuf - dictionary->getDictBufAdjust()),
+ releaseDictBuf(static_cast<const char *>(dictBuf) - dictionary->getDictBufAdjust(),
dictionary->getDictSize() + dictionary->getDictBufAdjust(), dictionary->getMmapFd());
#else // USE_MMAP_FOR_DICTIONARY
releaseDictBuf(dictBuf, 0, 0);
@@ -229,9 +261,9 @@
delete dictionary;
}
-void releaseDictBuf(void *dictBuf, const size_t length, int fd) {
+static void releaseDictBuf(const void *dictBuf, const size_t length, const int fd) {
#ifdef USE_MMAP_FOR_DICTIONARY
- int ret = munmap(dictBuf, length);
+ int ret = munmap(const_cast<void *>(dictBuf), length);
if (ret != 0) {
AKLOGE("DICT: Failure in munmap. ret=%d errno=%d", ret, errno);
}
@@ -240,20 +272,24 @@
AKLOGE("DICT: Failure in close. ret=%d errno=%d", ret, errno);
}
#else // USE_MMAP_FOR_DICTIONARY
- free(dictBuf);
+ free(const_cast<void *>(dictBuf));
#endif // USE_MMAP_FOR_DICTIONARY
}
static JNINativeMethod sMethods[] = {
- {"openNative", "(Ljava/lang/String;JJIIIII)J", (void*)latinime_BinaryDictionary_open},
- {"closeNative", "(J)V", (void*)latinime_BinaryDictionary_close},
- {"getSuggestionsNative", "(JJ[I[I[I[I[IIIZ[IZ[C[I[I[I)I",
- (void*) latinime_BinaryDictionary_getSuggestions},
- {"getFrequencyNative", "(J[II)I", (void*)latinime_BinaryDictionary_getFrequency},
- {"isValidBigramNative", "(J[I[I)Z", (void*)latinime_BinaryDictionary_isValidBigram},
- {"calcNormalizedScoreNative", "([CI[CII)F",
- (void*)latinime_BinaryDictionary_calcNormalizedScore},
- {"editDistanceNative", "([CI[CI)I", (void*)latinime_BinaryDictionary_editDistance}
+ {"openNative", "(Ljava/lang/String;JJIIIII)J",
+ reinterpret_cast<void *>(latinime_BinaryDictionary_open)},
+ {"closeNative", "(J)V", reinterpret_cast<void *>(latinime_BinaryDictionary_close)},
+ {"getSuggestionsNative", "(JJJ[I[I[I[I[IIIZ[IZ[C[I[I[I)I",
+ reinterpret_cast<void *>(latinime_BinaryDictionary_getSuggestions)},
+ {"getFrequencyNative", "(J[I)I",
+ reinterpret_cast<void *>(latinime_BinaryDictionary_getFrequency)},
+ {"isValidBigramNative", "(J[I[I)Z",
+ reinterpret_cast<void *>(latinime_BinaryDictionary_isValidBigram)},
+ {"calcNormalizedScoreNative", "([C[CI)F",
+ reinterpret_cast<void *>(latinime_BinaryDictionary_calcNormalizedScore)},
+ {"editDistanceNative", "([C[C)I",
+ reinterpret_cast<void *>(latinime_BinaryDictionary_editDistance)}
};
int register_BinaryDictionary(JNIEnv *env) {
diff --git a/native/jni/com_android_inputmethod_latin_DicTraverseSession.cpp b/native/jni/com_android_inputmethod_latin_DicTraverseSession.cpp
new file mode 100644
index 0000000..5d405f1
--- /dev/null
+++ b/native/jni/com_android_inputmethod_latin_DicTraverseSession.cpp
@@ -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
diff --git a/native/jni/com_android_inputmethod_latin_NativeUtils.h b/native/jni/com_android_inputmethod_latin_DicTraverseSession.h
similarity index 73%
rename from native/jni/com_android_inputmethod_latin_NativeUtils.h
rename to native/jni/com_android_inputmethod_latin_DicTraverseSession.h
index d1ffb8f..37531e9 100644
--- a/native/jni/com_android_inputmethod_latin_NativeUtils.h
+++ b/native/jni/com_android_inputmethod_latin_DicTraverseSession.h
@@ -14,14 +14,13 @@
* limitations under the License.
*/
-#ifndef _COM_ANDROID_INPUTMETHOD_LATIN_NATIVEUTILS_H
-#define _COM_ANDROID_INPUTMETHOD_LATIN_NATIVEUTILS_H
+#ifndef _COM_ANDROID_INPUTMETHOD_LATIN_DICTRAVERSESESSION_H
+#define _COM_ANDROID_INPUTMETHOD_LATIN_DICTRAVERSESESSION_H
+#include "defines.h"
#include "jni.h"
namespace latinime {
-
-int register_NativeUtils(JNIEnv *env);
-
+int register_DicTraverseSession(JNIEnv *env);
} // namespace latinime
-#endif // _COM_ANDROID_INPUTMETHOD_LATIN_NATIVEUTILS_H
+#endif // _COM_ANDROID_INPUTMETHOD_LATIN_DICTRAVERSESESSION_H
diff --git a/native/jni/com_android_inputmethod_latin_NativeUtils.cpp b/native/jni/com_android_inputmethod_latin_NativeUtils.cpp
deleted file mode 100644
index 8f1afbe..0000000
--- a/native/jni/com_android_inputmethod_latin_NativeUtils.cpp
+++ /dev/null
@@ -1,38 +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.
- */
-
-#include "com_android_inputmethod_latin_NativeUtils.h"
-#include "jni.h"
-#include "jni_common.h"
-
-#include <cmath>
-
-namespace latinime {
-
-static float latinime_NativeUtils_powf(float x, float y) {
- return powf(x, y);
-}
-
-static JNINativeMethod sMethods[] = {
- {"powf", "(FF)F", (void*)latinime_NativeUtils_powf}
-};
-
-int register_NativeUtils(JNIEnv *env) {
- const char *const kClassPathName = "com/android/inputmethod/latin/NativeUtils";
- return registerNativeMethods(env, kClassPathName, sMethods,
- sizeof(sMethods) / sizeof(sMethods[0]));
-}
-} // namespace latinime
diff --git a/native/jni/jni_common.cpp b/native/jni/jni_common.cpp
index 105a4dc..0da1669 100644
--- a/native/jni/jni_common.cpp
+++ b/native/jni/jni_common.cpp
@@ -16,15 +16,15 @@
#define LOG_TAG "LatinIME: jni"
+#include <cassert>
+
#include "com_android_inputmethod_keyboard_ProximityInfo.h"
#include "com_android_inputmethod_latin_BinaryDictionary.h"
-#include "com_android_inputmethod_latin_NativeUtils.h"
+#include "com_android_inputmethod_latin_DicTraverseSession.h"
#include "defines.h"
#include "jni.h"
#include "jni_common.h"
-#include <cassert>
-
using namespace latinime;
/*
@@ -34,7 +34,7 @@
JNIEnv *env = 0;
jint result = -1;
- if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
AKLOGE("ERROR: GetEnv failed");
goto bail;
}
@@ -45,13 +45,13 @@
goto bail;
}
- if (!register_ProximityInfo(env)) {
- AKLOGE("ERROR: ProximityInfo native registration failed");
+ if (!register_DicTraverseSession(env)) {
+ AKLOGE("ERROR: DicTraverseSession native registration failed");
goto bail;
}
- if (!register_NativeUtils(env)) {
- AKLOGE("ERROR: NativeUtils native registration failed");
+ if (!register_ProximityInfo(env)) {
+ AKLOGE("ERROR: ProximityInfo native registration failed");
goto bail;
}
diff --git a/native/jni/jni_common.h b/native/jni/jni_common.h
index 658ff18..993f97e 100644
--- a/native/jni/jni_common.h
+++ b/native/jni/jni_common.h
@@ -24,32 +24,5 @@
int registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods,
int numMethods);
-inline jint *safeGetIntArrayElements(JNIEnv *env, jintArray jArray) {
- if (jArray) {
- return env->GetIntArrayElements(jArray, 0);
- } else {
- return 0;
- }
-}
-
-inline jfloat *safeGetFloatArrayElements(JNIEnv *env, jfloatArray jArray) {
- if (jArray) {
- return env->GetFloatArrayElements(jArray, 0);
- } else {
- return 0;
- }
-}
-
-inline void safeReleaseIntArrayElements(JNIEnv *env, jintArray jArray, jint *cArray) {
- if (jArray) {
- env->ReleaseIntArrayElements(jArray, cArray, 0);
- }
-}
-
-inline void safeReleaseFloatArrayElements(JNIEnv *env, jfloatArray jArray, jfloat *cArray) {
- if (jArray) {
- env->ReleaseFloatArrayElements(jArray, cArray, 0);
- }
-}
} // namespace latinime
#endif // LATINIME_JNI_COMMON_H
diff --git a/native/jni/src/additional_proximity_chars.cpp b/native/jni/src/additional_proximity_chars.cpp
index de87646..f594927 100644
--- a/native/jni/src/additional_proximity_chars.cpp
+++ b/native/jni/src/additional_proximity_chars.cpp
@@ -17,7 +17,9 @@
#include "additional_proximity_chars.h"
namespace latinime {
-const std::string AdditionalProximityChars::LOCALE_EN_US("en");
+// TODO: Stop using hardcoded additional proximity characters.
+// TODO: Have proximity character informations in each language's binary dictionary.
+const char *AdditionalProximityChars::LOCALE_EN_US = "en";
const int32_t AdditionalProximityChars::EN_US_ADDITIONAL_A[EN_US_ADDITIONAL_A_SIZE] = {
'e', 'i', 'o', 'u'
diff --git a/native/jni/src/additional_proximity_chars.h b/native/jni/src/additional_proximity_chars.h
index ba76cfc..1fe996d 100644
--- a/native/jni/src/additional_proximity_chars.h
+++ b/native/jni/src/additional_proximity_chars.h
@@ -17,8 +17,8 @@
#ifndef LATINIME_ADDITIONAL_PROXIMITY_CHARS_H
#define LATINIME_ADDITIONAL_PROXIMITY_CHARS_H
+#include <cstring>
#include <stdint.h>
-#include <string>
#include "defines.h"
@@ -27,7 +27,7 @@
class AdditionalProximityChars {
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(AdditionalProximityChars);
- static const std::string LOCALE_EN_US;
+ static const char *LOCALE_EN_US;
static const int EN_US_ADDITIONAL_A_SIZE = 4;
static const int32_t EN_US_ADDITIONAL_A[];
static const int EN_US_ADDITIONAL_E_SIZE = 4;
@@ -39,14 +39,15 @@
static const int EN_US_ADDITIONAL_U_SIZE = 4;
static const int32_t EN_US_ADDITIONAL_U[];
- static bool isEnLocale(const std::string *locale_str) {
- return locale_str && locale_str->size() >= LOCALE_EN_US.size()
- && LOCALE_EN_US.compare(0, LOCALE_EN_US.size(), *locale_str);
+ static bool isEnLocale(const char *localeStr) {
+ const size_t LOCALE_EN_US_SIZE = strlen(LOCALE_EN_US);
+ return localeStr && strlen(localeStr) >= LOCALE_EN_US_SIZE
+ && strncmp(localeStr, LOCALE_EN_US, LOCALE_EN_US_SIZE) == 0;
}
public:
- static int getAdditionalCharsSize(const std::string *locale_str, const int32_t c) {
- if (!isEnLocale(locale_str)) {
+ static int getAdditionalCharsSize(const char *localeStr, const int32_t c) {
+ if (!isEnLocale(localeStr)) {
return 0;
}
switch(c) {
@@ -65,8 +66,8 @@
}
}
- static const int32_t *getAdditionalChars(const std::string *locale_str, const int32_t c) {
- if (!isEnLocale(locale_str)) {
+ static const int32_t *getAdditionalChars(const char *localeStr, const int32_t c) {
+ if (!isEnLocale(localeStr)) {
return 0;
}
switch(c) {
@@ -84,10 +85,6 @@
return 0;
}
}
-
- static bool hasAdditionalChars(const std::string *locale_str, const int32_t c) {
- return getAdditionalCharsSize(locale_str, c) > 0;
- }
};
} // namespace latinime
#endif // LATINIME_ADDITIONAL_PROXIMITY_CHARS_H
diff --git a/native/jni/src/bigram_dictionary.cpp b/native/jni/src/bigram_dictionary.cpp
index 2201711..f1d5380 100644
--- a/native/jni/src/bigram_dictionary.cpp
+++ b/native/jni/src/bigram_dictionary.cpp
@@ -60,15 +60,15 @@
AKLOGI("Bigram: InsertAt -> %d MAX_PREDICTIONS: %d", insertAt, MAX_PREDICTIONS);
}
if (insertAt < MAX_PREDICTIONS) {
- memmove((char*) bigramFreq + (insertAt + 1) * sizeof(bigramFreq[0]),
- (char*) bigramFreq + insertAt * sizeof(bigramFreq[0]),
- (MAX_PREDICTIONS - insertAt - 1) * sizeof(bigramFreq[0]));
+ memmove(bigramFreq + (insertAt + 1),
+ bigramFreq + insertAt,
+ (MAX_PREDICTIONS - insertAt - 1) * sizeof(bigramFreq[0]));
bigramFreq[insertAt] = frequency;
outputTypes[insertAt] = Dictionary::KIND_PREDICTION;
- memmove((char*) bigramChars + (insertAt + 1) * MAX_WORD_LENGTH * sizeof(short),
- (char*) bigramChars + (insertAt ) * MAX_WORD_LENGTH * sizeof(short),
- (MAX_PREDICTIONS - insertAt - 1) * sizeof(short) * MAX_WORD_LENGTH);
- unsigned short *dest = bigramChars + (insertAt ) * MAX_WORD_LENGTH;
+ memmove(bigramChars + (insertAt + 1) * MAX_WORD_LENGTH,
+ bigramChars + insertAt * MAX_WORD_LENGTH,
+ (MAX_PREDICTIONS - insertAt - 1) * sizeof(bigramChars[0]) * MAX_WORD_LENGTH);
+ unsigned short *dest = bigramChars + insertAt * MAX_WORD_LENGTH;
while (length--) {
*dest++ = *word++;
}
diff --git a/native/jni/src/bigram_dictionary.h b/native/jni/src/bigram_dictionary.h
index d676cca..5f11ae8 100644
--- a/native/jni/src/bigram_dictionary.h
+++ b/native/jni/src/bigram_dictionary.h
@@ -29,8 +29,6 @@
BigramDictionary(const unsigned char *dict, int maxWordLength, int maxPredictions);
int getBigrams(const int32_t *word, int length, int *inputCodes, int codesSize,
unsigned short *outWords, int *frequencies, int *outputTypes) const;
- int getBigramListPositionForWord(const int32_t *prevWord, const int prevWordLength,
- const bool forceLowerCaseSearch) const;
void fillBigramAddressToFrequencyMapAndFilter(const int32_t *prevWord, const int prevWordLength,
std::map<int, int> *map, uint8_t *filter) const;
bool isValidBigram(const int32_t *word1, int length1, const int32_t *word2, int length2) const;
@@ -45,6 +43,8 @@
bool getFirstBitOfByte(int *pos) { return (DICT[*pos] & 0x80) > 0; }
bool getSecondBitOfByte(int *pos) { return (DICT[*pos] & 0x40) > 0; }
bool checkFirstCharacter(unsigned short *word, int *inputCodes) const;
+ int getBigramListPositionForWord(const int32_t *prevWord, const int prevWordLength,
+ const bool forceLowerCaseSearch) const;
const unsigned char *DICT;
const int MAX_WORD_LENGTH;
diff --git a/native/jni/src/binary_format.h b/native/jni/src/binary_format.h
index 2ee4077..d8f3e83 100644
--- a/native/jni/src/binary_format.h
+++ b/native/jni/src/binary_format.h
@@ -52,6 +52,8 @@
// Mask for attribute frequency, stored on 4 bits inside the flags byte.
static const int MASK_ATTRIBUTE_FREQUENCY = 0x0F;
+ // The numeric value of the shortcut frequency that means 'whitelist'.
+ static const int WHITELIST_SHORTCUT_FREQUENCY = 15;
// Mask and flags for attribute address type selection.
static const int MASK_ATTRIBUTE_ADDRESS_TYPE = 0x30;
@@ -59,13 +61,6 @@
static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES = 0x20;
static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES = 0x30;
- private:
- DISALLOW_IMPLICIT_CONSTRUCTORS(BinaryFormat);
- const static int32_t MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
- const static int32_t CHARACTER_ARRAY_TERMINATOR = 0x1F;
- const static int MULTIPLE_BYTE_CHARACTER_ADDITIONAL_SIZE = 2;
-
- public:
const static int UNKNOWN_FORMAT = -1;
// Originally, format version 1 had a 16-bit magic number, then the version number `01'
// then options that must be 0. Hence the first 32-bits of the format are always as follow
@@ -92,13 +87,13 @@
static int skipFrequency(const uint8_t flags, const int pos);
static int skipShortcuts(const uint8_t *const dict, const uint8_t flags, const int pos);
static int skipBigrams(const uint8_t *const dict, const uint8_t flags, const int pos);
- static int skipAllAttributes(const uint8_t *const dict, const uint8_t flags, const int pos);
static int skipChildrenPosAndAttributes(const uint8_t *const dict, const uint8_t flags,
const int pos);
static int readChildrenPosition(const uint8_t *const dict, const uint8_t flags, const int pos);
static bool hasChildrenInFlags(const uint8_t flags);
static int getAttributeAddressAndForwardPointer(const uint8_t *const dict, const uint8_t flags,
int *pos);
+ static int getAttributeFrequencyFromFlags(const int flags);
static int getTerminalPosition(const uint8_t *const root, const int32_t *const inWord,
const int length, const bool forceLowerCaseSearch);
static int getWordAtAddress(const uint8_t *const root, const int address, const int maxDepth,
@@ -115,6 +110,13 @@
REQUIRES_FRENCH_LIGATURES_PROCESSING = 0x4
};
const static unsigned int NO_FLAGS = 0;
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(BinaryFormat);
+ const static int32_t MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
+ const static int32_t CHARACTER_ARRAY_TERMINATOR = 0x1F;
+ const static int MULTIPLE_BYTE_CHARACTER_ADDITIONAL_SIZE = 2;
+ static int skipAllAttributes(const uint8_t *const dict, const uint8_t flags, const int pos);
};
inline int BinaryFormat::detectFormat(const uint8_t *const dict) {
@@ -340,6 +342,10 @@
}
}
+inline int BinaryFormat::getAttributeFrequencyFromFlags(const int flags) {
+ return flags & MASK_ATTRIBUTE_FREQUENCY;
+}
+
// This function gets the byte position of the last chargroup of the exact matching word in the
// dictionary. If no match is found, it returns NOT_VALID_WORD.
inline int BinaryFormat::getTerminalPosition(const uint8_t *const root,
diff --git a/native/jni/src/char_utils.cpp b/native/jni/src/char_utils.cpp
index 45d49b0..9d886da31 100644
--- a/native/jni/src/char_utils.cpp
+++ b/native/jni/src/char_utils.cpp
@@ -885,16 +885,16 @@
};
static int compare_pair_capital(const void *a, const void *b) {
- return (int)(*(unsigned short *)a)
- - (int)((struct LatinCapitalSmallPair*)b)->capital;
+ return static_cast<int>(*static_cast<const unsigned short *>(a))
+ - static_cast<int>((static_cast<const struct LatinCapitalSmallPair *>(b))->capital);
}
-unsigned short latin_tolower(unsigned short c) {
+unsigned short latin_tolower(const unsigned short c) {
struct LatinCapitalSmallPair *p =
- (struct LatinCapitalSmallPair *)bsearch(&c, SORTED_CHAR_MAP,
+ static_cast<struct LatinCapitalSmallPair *>(bsearch(&c, SORTED_CHAR_MAP,
sizeof(SORTED_CHAR_MAP) / sizeof(SORTED_CHAR_MAP[0]),
sizeof(SORTED_CHAR_MAP[0]),
- compare_pair_capital);
+ compare_pair_capital));
return p ? p->small : c;
}
} // namespace latinime
diff --git a/native/jni/src/char_utils.h b/native/jni/src/char_utils.h
index edd96bb..b30677f 100644
--- a/native/jni/src/char_utils.h
+++ b/native/jni/src/char_utils.h
@@ -17,21 +17,23 @@
#ifndef LATINIME_CHAR_UTILS_H
#define LATINIME_CHAR_UTILS_H
+#include <cctype>
+
namespace latinime {
-inline static int isAsciiUpper(unsigned short c) {
- return c >= 'A' && c <= 'Z';
+inline static bool isAsciiUpper(unsigned short c) {
+ return isupper(static_cast<int>(c)) != 0;
}
inline static unsigned short toAsciiLower(unsigned short c) {
return c - 'A' + 'a';
}
-inline static int isAscii(unsigned short c) {
- return c <= 127;
+inline static bool isAscii(unsigned short c) {
+ return isascii(static_cast<int>(c)) != 0;
}
-unsigned short latin_tolower(unsigned short c);
+unsigned short latin_tolower(const unsigned short c);
/**
* Table mapping most combined Latin, Greek, and Cyrillic characters
diff --git a/native/jni/src/correction.cpp b/native/jni/src/correction.cpp
index ea4bdda..9ad65b0 100644
--- a/native/jni/src/correction.cpp
+++ b/native/jni/src/correction.cpp
@@ -61,19 +61,19 @@
}
inline static void calcEditDistanceOneStep(int *editDistanceTable, const unsigned short *input,
- const int inputLength, const unsigned short *output, const int outputLength) {
+ const int inputSize, const unsigned short *output, const int outputLength) {
// TODO: Make sure that editDistance[0 ~ MAX_WORD_LENGTH_INTERNAL] is not touched.
- // Let dp[i][j] be editDistanceTable[i * (inputLength + 1) + j].
- // Assuming that dp[0][0] ... dp[outputLength - 1][inputLength] are already calculated,
- // and calculate dp[ouputLength][0] ... dp[outputLength][inputLength].
- int *const current = editDistanceTable + outputLength * (inputLength + 1);
- const int *const prev = editDistanceTable + (outputLength - 1) * (inputLength + 1);
+ // Let dp[i][j] be editDistanceTable[i * (inputSize + 1) + j].
+ // Assuming that dp[0][0] ... dp[outputLength - 1][inputSize] are already calculated,
+ // and calculate dp[ouputLength][0] ... dp[outputLength][inputSize].
+ int *const current = editDistanceTable + outputLength * (inputSize + 1);
+ const int *const prev = editDistanceTable + (outputLength - 1) * (inputSize + 1);
const int *const prevprev =
- outputLength >= 2 ? editDistanceTable + (outputLength - 2) * (inputLength + 1) : 0;
+ outputLength >= 2 ? editDistanceTable + (outputLength - 2) * (inputSize + 1) : 0;
current[0] = outputLength;
const uint32_t co = toBaseLowerCase(output[outputLength - 1]);
const uint32_t prevCO = outputLength >= 2 ? toBaseLowerCase(output[outputLength - 2]) : 0;
- for (int i = 1; i <= inputLength; ++i) {
+ for (int i = 1; i <= inputSize; ++i) {
const uint32_t ci = toBaseLowerCase(input[i - 1]);
const uint16_t cost = (ci == co) ? 0 : 1;
current[i] = min(current[i - 1] + 1, min(prev[i] + 1, prev[i - 1] + cost));
@@ -84,11 +84,11 @@
}
inline static int getCurrentEditDistance(int *editDistanceTable, const int editDistanceTableWidth,
- const int outputLength, const int inputLength) {
+ const int outputLength, const int inputSize) {
if (DEBUG_EDIT_DISTANCE) {
- AKLOGI("getCurrentEditDistance %d, %d", inputLength, outputLength);
+ AKLOGI("getCurrentEditDistance %d, %d", inputSize, outputLength);
}
- return editDistanceTable[(editDistanceTableWidth + 1) * (outputLength) + inputLength];
+ return editDistanceTable[(editDistanceTableWidth + 1) * (outputLength) + inputSize];
}
//////////////////////
@@ -109,12 +109,12 @@
mTotalTraverseCount = 0;
}
-void Correction::initCorrection(const ProximityInfo *pi, const int inputLength,
+void Correction::initCorrection(const ProximityInfo *pi, const int inputSize,
const int maxDepth) {
mProximityInfo = pi;
- mInputLength = inputLength;
+ mInputSize = inputSize;
mMaxDepth = maxDepth;
- mMaxEditDistance = mInputLength < 5 ? 2 : mInputLength / 2;
+ mMaxEditDistance = mInputSize < 5 ? 2 : mInputSize / 2;
// TODO: This is not supposed to be required. Check what's going wrong with
// editDistance[0 ~ MAX_WORD_LENGTH_INTERNAL]
initEditDistance(mEditDistanceTable);
@@ -154,11 +154,13 @@
if (mSkipPos >= 0) ++inputCount;
if (mExcessivePos >= 0) ++inputCount;
if (mTransposedPos >= 0) ++inputCount;
- // TODO: remove this assert
- assert(inputCount <= 1);
}
}
+bool Correction::sameAsTyped() {
+ return mProximityInfoState.sameAsTyped(mWord, mOutputIndex);
+}
+
int Correction::getFreqForSplitMultipleWords(const int *freqArray, const int *wordLengthArray,
const int wordCount, const bool isSpaceProximity, const unsigned short *word) {
return Correction::RankingAlgorithm::calcFreqForSplitMultipleWords(freqArray, wordLengthArray,
@@ -166,26 +168,22 @@
}
int Correction::getFinalProbability(const int probability, unsigned short **word, int *wordLength) {
- return getFinalProbabilityInternal(probability, word, wordLength, mInputLength);
+ return getFinalProbabilityInternal(probability, word, wordLength, mInputSize);
}
int Correction::getFinalProbabilityForSubQueue(const int probability, unsigned short **word,
- int *wordLength, const int inputLength) {
- return getFinalProbabilityInternal(probability, word, wordLength, inputLength);
+ int *wordLength, const int inputSize) {
+ return getFinalProbabilityInternal(probability, word, wordLength, inputSize);
}
int Correction::getFinalProbabilityInternal(const int probability, unsigned short **word,
- int *wordLength, const int inputLength) {
+ int *wordLength, const int inputSize) {
const int outputIndex = mTerminalOutputIndex;
const int inputIndex = mTerminalInputIndex;
*wordLength = outputIndex + 1;
- if (outputIndex < MIN_SUGGEST_DEPTH) {
- return NOT_A_PROBABILITY;
- }
-
*word = mWord;
int finalProbability= Correction::RankingAlgorithm::calculateFinalProbability(
- inputIndex, outputIndex, probability, mEditDistanceTable, this, inputLength);
+ inputIndex, outputIndex, probability, mEditDistanceTable, this, inputSize);
return finalProbability;
}
@@ -228,7 +226,7 @@
}
// TODO: remove
-int Correction::getInputIndex() {
+int Correction::getInputIndex() const {
return mInputIndex;
}
@@ -272,13 +270,13 @@
// TODO: use edit distance here
return mOutputIndex - 1 >= mMaxDepth || mProximityCount > mMaxEditDistance
// Allow one char longer word for missing character
- || (!mDoAutoCompletion && (mOutputIndex > mInputLength));
+ || (!mDoAutoCompletion && (mOutputIndex > mInputSize));
}
void Correction::addCharToCurrentWord(const int32_t c) {
mWord[mOutputIndex] = c;
const unsigned short *primaryInputWord = mProximityInfoState.getPrimaryInputWord();
- calcEditDistanceOneStep(mEditDistanceTable, primaryInputWord, mInputLength,
+ calcEditDistanceOneStep(mEditDistanceTable, primaryInputWord, mInputSize,
mWord, mOutputIndex + 1);
}
@@ -327,7 +325,7 @@
// Skip checking this node
if (mNeedsToTraverseAllNodes || isSingleQuote(c)) {
bool incremented = false;
- if (mLastCharExceeded && mInputIndex == mInputLength - 1) {
+ if (mLastCharExceeded && mInputIndex == mInputSize - 1) {
// TODO: Do not check the proximity if EditDistance exceeds the threshold
const ProximityType matchId = mProximityInfoState.getMatchedProximityId(
mInputIndex, c, true, &proximityIndex);
@@ -356,7 +354,7 @@
if (mExcessiveCount == 0 && mExcessivePos < mOutputIndex) {
mExcessivePos = mOutputIndex;
}
- if (mExcessivePos < mInputLength - 1) {
+ if (mExcessivePos < mInputSize - 1) {
mExceeding = mExcessivePos == mInputIndex && canTryCorrection;
}
}
@@ -375,7 +373,7 @@
if (mTransposedCount == 0 && mTransposedPos < mOutputIndex) {
mTransposedPos = mOutputIndex;
}
- if (mTransposedPos < mInputLength - 1) {
+ if (mTransposedPos < mInputSize - 1) {
mTransposing = mInputIndex == mTransposedPos && canTryCorrection;
}
}
@@ -394,7 +392,7 @@
} else {
--mTransposedCount;
if (DEBUG_CORRECTION
- && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+ && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
&& (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
|| MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
DUMP_WORD(mWord, mOutputIndex);
@@ -425,7 +423,7 @@
&& isEquivalentChar(mProximityInfoState.getMatchedProximityId(
mInputIndex, mWord[mOutputIndex - 1], false))) {
if (DEBUG_CORRECTION
- && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+ && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
&& (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
|| MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
AKLOGI("CONVERSION p->e %c", mWord[mOutputIndex - 1]);
@@ -455,7 +453,7 @@
// As the current char turned out to be an unrelated char,
// we will try other correction-types. Please note that mCorrectionStates[mOutputIndex]
// here refers to the previous state.
- if (mInputIndex < mInputLength - 1 && mOutputIndex > 0 && mTransposedCount > 0
+ if (mInputIndex < mInputSize - 1 && mOutputIndex > 0 && mTransposedCount > 0
&& !mCorrectionStates[mOutputIndex].mTransposing
&& mCorrectionStates[mOutputIndex - 1].mTransposing
&& isEquivalentChar(mProximityInfoState.getMatchedProximityId(
@@ -492,7 +490,7 @@
++mSkippedCount;
--mProximityCount;
return processSkipChar(c, isTerminal, false);
- } else if (mInputIndex - 1 < mInputLength
+ } else if (mInputIndex - 1 < mInputSize
&& mSkippedCount > 0
&& mCorrectionStates[mOutputIndex].mSkipping
&& mCorrectionStates[mOutputIndex].mAdditionalProximityMatching
@@ -504,7 +502,7 @@
mProximityMatching = true;
++mProximityCount;
mDistances[mOutputIndex] = ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO;
- } else if ((mExceeding || mTransposing) && mInputIndex - 1 < mInputLength
+ } else if ((mExceeding || mTransposing) && mInputIndex - 1 < mInputSize
&& isEquivalentChar(
mProximityInfoState.getMatchedProximityId(mInputIndex + 1, c, false))) {
// 1.2. Excessive or transpose correction
@@ -515,7 +513,7 @@
incrementInputIndex();
}
if (DEBUG_CORRECTION
- && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+ && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
&& (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
|| MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
DUMP_WORD(mWord, mOutputIndex);
@@ -531,7 +529,7 @@
// 3. Skip correction
++mSkippedCount;
if (DEBUG_CORRECTION
- && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+ && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
&& (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
|| MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
AKLOGI("SKIP: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
@@ -544,7 +542,7 @@
++mProximityCount;
mDistances[mOutputIndex] = ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO;
if (DEBUG_CORRECTION
- && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+ && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
&& (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
|| MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
AKLOGI("ADDITIONALPROX: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
@@ -552,7 +550,7 @@
}
} else {
if (DEBUG_CORRECTION
- && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+ && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
&& (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
|| MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
DUMP_WORD(mWord, mOutputIndex);
@@ -562,7 +560,7 @@
return processUnrelatedCorrectionType();
}
} else if (secondTransposing) {
- // If inputIndex is greater than mInputLength, that means there is no
+ // If inputIndex is greater than mInputSize, that means there is no
// proximity chars. So, we don't need to check proximity.
mMatching = true;
} else if (isEquivalentChar(matchedProximityCharId)) {
@@ -575,7 +573,7 @@
mDistances[mOutputIndex] =
mProximityInfoState.getNormalizedSquaredDistance(mInputIndex, proximityIndex);
if (DEBUG_CORRECTION
- && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+ && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
&& (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
|| MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
AKLOGI("PROX: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
@@ -587,8 +585,8 @@
// 4. Last char excessive correction
mLastCharExceeded = mExcessiveCount == 0 && mSkippedCount == 0 && mTransposedCount == 0
- && mProximityCount == 0 && (mInputIndex == mInputLength - 2);
- const bool isSameAsUserTypedLength = (mInputLength == mInputIndex + 1) || mLastCharExceeded;
+ && mProximityCount == 0 && (mInputIndex == mInputSize - 2);
+ const bool isSameAsUserTypedLength = (mInputSize == mInputIndex + 1) || mLastCharExceeded;
if (mLastCharExceeded) {
++mExcessiveCount;
}
@@ -599,7 +597,7 @@
}
const bool needsToTryOnTerminalForTheLastPossibleExcessiveChar =
- mExceeding && mInputIndex == mInputLength - 2;
+ mExceeding && mInputIndex == mInputSize - 2;
// Finally, we are ready to go to the next character, the next "virtual node".
// We should advance the input index.
@@ -615,7 +613,7 @@
mTerminalInputIndex = mInputIndex - 1;
mTerminalOutputIndex = mOutputIndex - 1;
if (DEBUG_CORRECTION
- && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+ && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
&& (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0 || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
DUMP_WORD(mWord, mOutputIndex);
AKLOGI("ONTERMINAL(1): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
@@ -629,9 +627,6 @@
}
}
-Correction::~Correction() {
-}
-
inline static int getQuoteCount(const unsigned short *word, const int length) {
int quoteCount = 0;
for (int i = 0; i < length; ++i) {
@@ -653,7 +648,7 @@
/* static */
int Correction::RankingAlgorithm::calculateFinalProbability(const int inputIndex,
const int outputIndex, const int freq, int *editDistanceTable, const Correction *correction,
- const int inputLength) {
+ const int inputSize) {
const int excessivePos = correction->getExcessivePos();
const int typedLetterMultiplier = correction->TYPED_LETTER_MULTIPLIER;
const int fullWordMultiplier = correction->FULL_WORD_MULTIPLIER;
@@ -665,55 +660,55 @@
const bool lastCharExceeded = correction->mLastCharExceeded;
const bool useFullEditDistance = correction->mUseFullEditDistance;
const int outputLength = outputIndex + 1;
- if (skippedCount >= inputLength || inputLength == 0) {
+ if (skippedCount >= inputSize || inputSize == 0) {
return -1;
}
// TODO: find more robust way
- bool sameLength = lastCharExceeded ? (inputLength == inputIndex + 2)
- : (inputLength == inputIndex + 1);
+ bool sameLength = lastCharExceeded ? (inputSize == inputIndex + 2)
+ : (inputSize == inputIndex + 1);
// TODO: use mExcessiveCount
- const int matchCount = inputLength - correction->mProximityCount - excessiveCount;
+ const int matchCount = inputSize - correction->mProximityCount - excessiveCount;
const unsigned short *word = correction->mWord;
const bool skipped = skippedCount > 0;
const int quoteDiffCount = max(0, getQuoteCount(word, outputLength)
- - getQuoteCount(proximityInfoState->getPrimaryInputWord(), inputLength));
+ - getQuoteCount(proximityInfoState->getPrimaryInputWord(), inputSize));
// TODO: Calculate edit distance for transposed and excessive
int ed = 0;
if (DEBUG_DICT_FULL) {
- dumpEditDistance10ForDebug(editDistanceTable, correction->mInputLength, outputLength);
+ dumpEditDistance10ForDebug(editDistanceTable, correction->mInputSize, outputLength);
}
int adjustedProximityMatchedCount = proximityMatchedCount;
int finalFreq = freq;
if (DEBUG_CORRECTION_FREQ
- && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == inputLength)) {
+ && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == inputSize)) {
AKLOGI("FinalFreq0: %d", finalFreq);
}
// TODO: Optimize this.
if (transposedCount > 0 || proximityMatchedCount > 0 || skipped || excessiveCount > 0) {
- ed = getCurrentEditDistance(editDistanceTable, correction->mInputLength, outputLength,
- inputLength) - transposedCount;
+ ed = getCurrentEditDistance(editDistanceTable, correction->mInputSize, outputLength,
+ inputSize) - transposedCount;
const int matchWeight = powerIntCapped(typedLetterMultiplier,
- max(inputLength, outputLength) - ed);
+ max(inputSize, outputLength) - ed);
multiplyIntCapped(matchWeight, &finalFreq);
// TODO: Demote further if there are two or more excessive chars with longer user input?
- if (inputLength > outputLength) {
+ if (inputSize > outputLength) {
multiplyRate(INPUT_EXCEEDS_OUTPUT_DEMOTION_RATE, &finalFreq);
}
ed = max(0, ed - quoteDiffCount);
- adjustedProximityMatchedCount = min(max(0, ed - (outputLength - inputLength)),
+ adjustedProximityMatchedCount = min(max(0, ed - (outputLength - inputSize)),
proximityMatchedCount);
if (transposedCount <= 0) {
- if (ed == 1 && (inputLength == outputLength - 1 || inputLength == outputLength + 1)) {
+ if (ed == 1 && (inputSize == outputLength - 1 || inputSize == outputLength + 1)) {
// Promote a word with just one skipped or excessive char
if (sameLength) {
multiplyRate(WORDS_WITH_JUST_ONE_CORRECTION_PROMOTION_RATE
@@ -742,8 +737,8 @@
// Demotion for a word with missing character
if (skipped) {
const int demotionRate = WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE
- * (10 * inputLength - WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X)
- / (10 * inputLength
+ * (10 * inputSize - WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X)
+ / (10 * inputSize
- WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X + 10);
if (DEBUG_DICT_FULL) {
AKLOGI("Demotion rate for missing character is %d.", demotionRate);
@@ -845,7 +840,7 @@
? adjustedProximityMatchedCount
: (proximityMatchedCount + transposedCount);
multiplyRate(
- 100 - CORRECTION_COUNT_RATE_DEMOTION_RATE_BASE * errorCount / inputLength, &finalFreq);
+ 100 - CORRECTION_COUNT_RATE_DEMOTION_RATE_BASE * errorCount / inputSize, &finalFreq);
// Promotion for an exactly matched word
if (ed == 0) {
@@ -880,7 +875,7 @@
e ... exceeding
p ... proximity matching
*/
- if (matchCount == inputLength && matchCount >= 2 && !skipped
+ if (matchCount == inputSize && matchCount >= 2 && !skipped
&& word[matchCount] == word[matchCount - 1]) {
multiplyRate(WORDS_WITH_MATCH_SKIP_PROMOTION_RATE, &finalFreq);
}
@@ -890,8 +885,8 @@
multiplyIntCapped(fullWordMultiplier, &finalFreq);
}
- if (useFullEditDistance && outputLength > inputLength + 1) {
- const int diff = outputLength - inputLength - 1;
+ if (useFullEditDistance && outputLength > inputSize + 1) {
+ const int diff = outputLength - inputSize - 1;
const int divider = diff < 31 ? 1 << diff : S_INT_MAX;
finalFreq = divider > finalFreq ? 1 : finalFreq / divider;
}
@@ -901,8 +896,8 @@
}
if (DEBUG_CORRECTION_FREQ
- && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == inputLength)) {
- DUMP_WORD(correction->getPrimaryInputWord(), inputLength);
+ && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == inputSize)) {
+ DUMP_WORD(correction->getPrimaryInputWord(), inputSize);
DUMP_WORD(correction->mWord, outputLength);
AKLOGI("FinalFreq: [P%d, S%d, T%d, E%d, A%d] %d, %d, %d, %d, %d, %d", proximityMatchedCount,
skippedCount, transposedCount, excessiveCount, additionalProximityCount,
@@ -1094,7 +1089,7 @@
// In dictionary.cpp, getSuggestion() method,
// suggestion scores are computed using the below formula.
// original score
-// := pow(mTypedLetterMultiplier (this is defined 2),
+// := powf(mTypedLetterMultiplier (this is defined 2),
// (the number of matched characters between typed word and suggested word))
// * (individual word's score which defined in the unigram dictionary,
// and this score is defined in range [0, 255].)
@@ -1106,11 +1101,11 @@
// capitalization, then treat it as if the score was 255.
// - If before.length() == after.length()
// => multiply by mFullWordMultiplier (this is defined 2))
-// So, maximum original score is pow(2, min(before.length(), after.length())) * 255 * 2 * 1.2
+// So, maximum original score is powf(2, min(before.length(), after.length())) * 255 * 2 * 1.2
// For historical reasons we ignore the 1.2 modifier (because the measure for a good
// autocorrection threshold was done at a time when it didn't exist). This doesn't change
// the result.
-// So, we can normalize original score by dividing pow(2, min(b.l(),a.l())) * 255 * 2.
+// So, we can normalize original score by dividing powf(2, min(b.l(),a.l())) * 255 * 2.
/* static */
float Correction::RankingAlgorithm::calcNormalizedScore(const unsigned short *before,
@@ -1132,7 +1127,7 @@
}
const float maxScore = score >= S_INT_MAX ? S_INT_MAX : MAX_INITIAL_SCORE
- * pow(static_cast<float>(TYPED_LETTER_MULTIPLIER),
+ * powf(static_cast<float>(TYPED_LETTER_MULTIPLIER),
static_cast<float>(min(beforeLength, afterLength - spaceCount)))
* FULL_WORD_MULTIPLIER;
diff --git a/native/jni/src/correction.h b/native/jni/src/correction.h
index 81623a4..f016d54 100644
--- a/native/jni/src/correction.h
+++ b/native/jni/src/correction.h
@@ -18,6 +18,7 @@
#define LATINIME_CORRECTION_H
#include <cassert>
+#include <cstring> // for memset()
#include <stdint.h>
#include "correction_state.h"
@@ -38,10 +39,108 @@
NOT_ON_TERMINAL
} CorrectionType;
+ Correction()
+ : mProximityInfo(0), mUseFullEditDistance(false), mDoAutoCompletion(false),
+ mMaxEditDistance(0), mMaxDepth(0), mInputSize(0), mSpaceProximityPos(0),
+ mMissingSpacePos(0), mTerminalInputIndex(0), mTerminalOutputIndex(0), mMaxErrors(0),
+ mTotalTraverseCount(0), mNeedsToTraverseAllNodes(false), mOutputIndex(0),
+ mInputIndex(0), mEquivalentCharCount(0), mProximityCount(0), mExcessiveCount(0),
+ mTransposedCount(0), mSkippedCount(0), mTransposedPos(0), mExcessivePos(0),
+ mSkipPos(0), mLastCharExceeded(false), mMatching(false), mProximityMatching(false),
+ mAdditionalProximityMatching(false), mExceeding(false), mTransposing(false),
+ mSkipping(false), mProximityInfoState() {
+ memset(mWord, 0, sizeof(mWord));
+ memset(mDistances, 0, sizeof(mDistances));
+ memset(mEditDistanceTable, 0, sizeof(mEditDistanceTable));
+ // NOTE: mCorrectionStates is an array of instances.
+ // No need to initialize it explicitly here.
+ }
+
+ virtual ~Correction() {}
+ void resetCorrection();
+ void initCorrection(
+ const ProximityInfo *pi, const int inputSize, const int maxWordLength);
+ void initCorrectionState(const int rootPos, const int childCount, const bool traverseAll);
+
+ // TODO: remove
+ void setCorrectionParams(const int skipPos, const int excessivePos, const int transposedPos,
+ const int spaceProximityPos, const int missingSpacePos, const bool useFullEditDistance,
+ const bool doAutoCompletion, const int maxErrors);
+ void checkState();
+ bool sameAsTyped();
+ bool initProcessState(const int index);
+
+ int getInputIndex() const;
+
+ bool needsToPrune() const;
+
+ int pushAndGetTotalTraverseCount() {
+ return ++mTotalTraverseCount;
+ }
+
+ int getFreqForSplitMultipleWords(
+ const int *freqArray, const int *wordLengthArray, const int wordCount,
+ const bool isSpaceProximity, const unsigned short *word);
+ int getFinalProbability(const int probability, unsigned short **word, int *wordLength);
+ int getFinalProbabilityForSubQueue(const int probability, unsigned short **word,
+ int *wordLength, const int inputSize);
+
+ CorrectionType processCharAndCalcState(const int32_t c, const bool isTerminal);
+
+ /////////////////////////
+ // Tree helper methods
+ int goDownTree(const int parentIndex, const int childCount, const int firstChildPos);
+
+ inline int getTreeSiblingPos(const int index) const {
+ return mCorrectionStates[index].mSiblingPos;
+ }
+
+ inline void setTreeSiblingPos(const int index, const int pos) {
+ mCorrectionStates[index].mSiblingPos = pos;
+ }
+
+ inline int getTreeParentIndex(const int index) const {
+ return mCorrectionStates[index].mParentIndex;
+ }
+
+ class RankingAlgorithm {
+ public:
+ static int calculateFinalProbability(const int inputIndex, const int depth,
+ const int probability, int *editDistanceTable, const Correction *correction,
+ const int inputSize);
+ static int calcFreqForSplitMultipleWords(const int *freqArray, const int *wordLengthArray,
+ const int wordCount, const Correction *correction, const bool isSpaceProximity,
+ const unsigned short *word);
+ static float calcNormalizedScore(const unsigned short *before, const int beforeLength,
+ const unsigned short *after, const int afterLength, const int score);
+ static int editDistance(const unsigned short *before,
+ const int beforeLength, const unsigned short *after, const int afterLength);
+ private:
+ static const int CODE_SPACE = ' ';
+ static const int MAX_INITIAL_SCORE = 255;
+ };
+
+ // proximity info state
+ void initInputParams(const ProximityInfo *proximityInfo, const int32_t *inputCodes,
+ const int inputSize, const int *xCoordinates, const int *yCoordinates) {
+ mProximityInfoState.initInputParams(0, MAX_POINT_TO_KEY_LENGTH,
+ proximityInfo, inputCodes, inputSize, xCoordinates, yCoordinates, 0, 0, false);
+ }
+
+ const unsigned short *getPrimaryInputWord() const {
+ return mProximityInfoState.getPrimaryInputWord();
+ }
+
+ unsigned short getPrimaryCharAt(const int index) const {
+ return mProximityInfoState.getPrimaryCharAt(index);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Correction);
+
/////////////////////////
// static inline utils //
/////////////////////////
-
static const int TWO_31ST_DIV_255 = S_INT_MAX / 255;
static inline int capped255MultForFullMatchAccentsOrCapitalizationDifference(const int num) {
return (num < TWO_31ST_DIV_255 ? 255 * num : S_INT_MAX);
@@ -94,106 +193,25 @@
}
}
- Correction() {};
- void resetCorrection();
- void initCorrection(
- const ProximityInfo *pi, const int inputLength, const int maxWordLength);
- void initCorrectionState(const int rootPos, const int childCount, const bool traverseAll);
-
- // TODO: remove
- void setCorrectionParams(const int skipPos, const int excessivePos, const int transposedPos,
- const int spaceProximityPos, const int missingSpacePos, const bool useFullEditDistance,
- const bool doAutoCompletion, const int maxErrors);
- void checkState();
- bool initProcessState(const int index);
-
- int getInputIndex();
-
- virtual ~Correction();
- int getSpaceProximityPos() const {
+ inline int getSpaceProximityPos() const {
return mSpaceProximityPos;
}
- int getMissingSpacePos() const {
+ inline int getMissingSpacePos() const {
return mMissingSpacePos;
}
- int getSkipPos() const {
+ inline int getSkipPos() const {
return mSkipPos;
}
- int getExcessivePos() const {
+ inline int getExcessivePos() const {
return mExcessivePos;
}
- int getTransposedPos() const {
+ inline int getTransposedPos() const {
return mTransposedPos;
}
- bool needsToPrune() const;
-
- int pushAndGetTotalTraverseCount() {
- return ++mTotalTraverseCount;
- }
-
- int getFreqForSplitMultipleWords(
- const int *freqArray, const int *wordLengthArray, const int wordCount,
- const bool isSpaceProximity, const unsigned short *word);
- int getFinalProbability(const int probability, unsigned short **word, int *wordLength);
- int getFinalProbabilityForSubQueue(const int probability, unsigned short **word,
- int *wordLength, const int inputLength);
-
- CorrectionType processCharAndCalcState(const int32_t c, const bool isTerminal);
-
- /////////////////////////
- // Tree helper methods
- int goDownTree(const int parentIndex, const int childCount, const int firstChildPos);
-
- inline int getTreeSiblingPos(const int index) const {
- return mCorrectionStates[index].mSiblingPos;
- }
-
- inline void setTreeSiblingPos(const int index, const int pos) {
- mCorrectionStates[index].mSiblingPos = pos;
- }
-
- inline int getTreeParentIndex(const int index) const {
- return mCorrectionStates[index].mParentIndex;
- }
-
- class RankingAlgorithm {
- public:
- static int calculateFinalProbability(const int inputIndex, const int depth,
- const int probability, int *editDistanceTable, const Correction *correction,
- const int inputLength);
- static int calcFreqForSplitMultipleWords(const int *freqArray, const int *wordLengthArray,
- const int wordCount, const Correction *correction, const bool isSpaceProximity,
- const unsigned short *word);
- static float calcNormalizedScore(const unsigned short *before, const int beforeLength,
- const unsigned short *after, const int afterLength, const int score);
- static int editDistance(const unsigned short *before,
- const int beforeLength, const unsigned short *after, const int afterLength);
- private:
- static const int CODE_SPACE = ' ';
- static const int MAX_INITIAL_SCORE = 255;
- };
-
- // proximity info state
- void initInputParams(const ProximityInfo *proximityInfo, const int32_t *inputCodes,
- const int inputLength, const int *xCoordinates, const int *yCoordinates) {
- mProximityInfoState.initInputParams(
- proximityInfo, inputCodes, inputLength, xCoordinates, yCoordinates);
- }
-
- const unsigned short *getPrimaryInputWord() const {
- return mProximityInfoState.getPrimaryInputWord();
- }
-
- unsigned short getPrimaryCharAt(const int index) const {
- return mProximityInfoState.getPrimaryCharAt(index);
- }
-
- private:
- DISALLOW_COPY_AND_ASSIGN(Correction);
inline void incrementInputIndex();
inline void incrementOutputIndex();
inline void startToTraverseAllNodes();
@@ -203,7 +221,7 @@
inline CorrectionType processUnrelatedCorrectionType();
inline void addCharToCurrentWord(const int32_t c);
inline int getFinalProbabilityInternal(const int probability, unsigned short **word,
- int *wordLength, const int inputLength);
+ int *wordLength, const int inputSize);
static const int TYPED_LETTER_MULTIPLIER = 2;
static const int FULL_WORD_MULTIPLIER = 2;
@@ -213,7 +231,7 @@
bool mDoAutoCompletion;
int mMaxEditDistance;
int mMaxDepth;
- int mInputLength;
+ int mInputSize;
int mSpaceProximityPos;
int mMissingSpacePos;
int mTerminalInputIndex;
diff --git a/native/jni/src/debug.h b/native/jni/src/debug.h
index 2168d66..8f6b69d 100644
--- a/native/jni/src/debug.h
+++ b/native/jni/src/debug.h
@@ -22,7 +22,7 @@
static inline unsigned char *convertToUnibyteString(unsigned short *input, unsigned char *output,
const unsigned int length) {
unsigned int i = 0;
- for (; i <= length && input[i] != 0; ++i)
+ for (; i < length && input[i] != 0; ++i)
output[i] = input[i] & 0xFF;
output[i] = 0;
return output;
@@ -31,7 +31,7 @@
static inline unsigned char *convertToUnibyteStringAndReplaceLastChar(unsigned short *input,
unsigned char *output, const unsigned int length, unsigned char c) {
unsigned int i = 0;
- for (; i <= length && input[i] != 0; ++i)
+ for (; i < length && input[i] != 0; ++i)
output[i] = input[i] & 0xFF;
if (i > 0) output[i-1] = c;
output[i] = 0;
@@ -58,11 +58,12 @@
}
static inline void printDebug(const char *tag, int *codes, int codesSize, int MAX_PROXIMITY_CHARS) {
- unsigned char *buf = (unsigned char*)malloc((1 + codesSize) * sizeof(*buf));
+ unsigned char *buf = static_cast<unsigned char *>(malloc((1 + codesSize) * sizeof(*buf)));
buf[codesSize] = 0;
- while (--codesSize >= 0)
- buf[codesSize] = (unsigned char)codes[codesSize * MAX_PROXIMITY_CHARS];
+ while (--codesSize >= 0) {
+ buf[codesSize] = static_cast<unsigned char>(codes[codesSize * MAX_PROXIMITY_CHARS]);
+ }
AKLOGI("%s, WORD = %s", tag, buf);
free(buf);
diff --git a/native/jni/src/defines.h b/native/jni/src/defines.h
index 31dd61e..9b53007 100644
--- a/native/jni/src/defines.h
+++ b/native/jni/src/defines.h
@@ -265,6 +265,9 @@
// This must be equal to ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE in KeyDetector.java
#define ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE 2
+// Assuming locale strings such as en_US, sr-Latn etc.
+#define MAX_LOCALE_STRING_LENGTH 10
+
// Word limit for sub queues used in WordsPriorityQueuePool. Sub queues are temporary queues used
// for better performance.
// Holds up to 1 candidate for each word
@@ -291,12 +294,13 @@
#define MAX_SPACES_INTERNAL 16
+// Max Distance between point to key
+#define MAX_POINT_TO_KEY_LENGTH 10000000
+
// TODO: Reduce this constant if possible; check the maximum number of digraphs in the same
// word in the dictionary for languages with digraphs, like German and French
#define DEFAULT_MAX_DIGRAPH_SEARCH_DEPTH 5
-// Minimum suggest depth for one word for all cases except for missing space suggestions.
-#define MIN_SUGGEST_DEPTH 1
#define MIN_USER_TYPED_LENGTH_FOR_MULTIPLE_WORD_SUGGESTION 3
#define MIN_USER_TYPED_LENGTH_FOR_EXCESSIVE_CHARACTER_SUGGESTION 3
diff --git a/native/jni/com_android_inputmethod_latin_NativeUtils.h b/native/jni/src/dic_traverse_wrapper.cpp
similarity index 64%
copy from native/jni/com_android_inputmethod_latin_NativeUtils.h
copy to native/jni/src/dic_traverse_wrapper.cpp
index d1ffb8f..88ca9fa 100644
--- a/native/jni/com_android_inputmethod_latin_NativeUtils.h
+++ b/native/jni/src/dic_traverse_wrapper.cpp
@@ -14,14 +14,13 @@
* limitations under the License.
*/
-#ifndef _COM_ANDROID_INPUTMETHOD_LATIN_NATIVEUTILS_H
-#define _COM_ANDROID_INPUTMETHOD_LATIN_NATIVEUTILS_H
+#define LOG_TAG "LatinIME: jni: Session"
-#include "jni.h"
+#include "dic_traverse_wrapper.h"
namespace latinime {
-
-int register_NativeUtils(JNIEnv *env);
-
+void *(*DicTraverseWrapper::sDicTraverseSessionFactoryMethod)(JNIEnv *, jstring) = 0;
+void (*DicTraverseWrapper::sDicTraverseSessionReleaseMethod)(void *) = 0;
+void (*DicTraverseWrapper::sDicTraverseSessionInitMethod)(
+ void *, const Dictionary *const, const int *, const int) = 0;
} // namespace latinime
-#endif // _COM_ANDROID_INPUTMETHOD_LATIN_NATIVEUTILS_H
diff --git a/native/jni/src/dic_traverse_wrapper.h b/native/jni/src/dic_traverse_wrapper.h
new file mode 100644
index 0000000..2923824
--- /dev/null
+++ b/native/jni/src/dic_traverse_wrapper.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2012, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_DIC_TRAVERSE_WRAPPER_H
+#define LATINIME_DIC_TRAVERSE_WRAPPER_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "jni.h"
+
+namespace latinime {
+class Dictionary;
+// TODO: Remove
+class DicTraverseWrapper {
+ public:
+ static void *getDicTraverseSession(JNIEnv *env, jstring locale) {
+ if (sDicTraverseSessionFactoryMethod) {
+ return sDicTraverseSessionFactoryMethod(env, locale);
+ }
+ return 0;
+ }
+ static void initDicTraverseSession(void *traverseSession,
+ const Dictionary *const dictionary, const int *prevWord, const int prevWordLength) {
+ if (sDicTraverseSessionInitMethod) {
+ sDicTraverseSessionInitMethod(traverseSession, dictionary, prevWord, prevWordLength);
+ }
+ }
+ static void releaseDicTraverseSession(void *traverseSession) {
+ if (sDicTraverseSessionReleaseMethod) {
+ sDicTraverseSessionReleaseMethod(traverseSession);
+ }
+ }
+ static void setTraverseSessionFactoryMethod(
+ void *(*factoryMethod)(JNIEnv *, jstring)) {
+ sDicTraverseSessionFactoryMethod = factoryMethod;
+ }
+ static void setTraverseSessionInitMethod(
+ void (*initMethod)(void *, const Dictionary *const, const int *, const int)) {
+ sDicTraverseSessionInitMethod = initMethod;
+ }
+ static void setTraverseSessionReleaseMethod(void (*releaseMethod)(void *)) {
+ sDicTraverseSessionReleaseMethod = releaseMethod;
+ }
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(DicTraverseWrapper);
+ static void *(*sDicTraverseSessionFactoryMethod)(JNIEnv *, jstring);
+ static void (*sDicTraverseSessionInitMethod)(
+ void *, const Dictionary *const, const int *, const int);
+ static void (*sDicTraverseSessionReleaseMethod)(void *);
+};
+int register_DicTraverseSession(JNIEnv *env);
+} // namespace latinime
+#endif // LATINIME_DIC_TRAVERSE_WRAPPER_H
diff --git a/native/jni/src/dictionary.cpp b/native/jni/src/dictionary.cpp
index ee55cfa..2fbe83e 100644
--- a/native/jni/src/dictionary.cpp
+++ b/native/jni/src/dictionary.cpp
@@ -22,6 +22,7 @@
#include "binary_format.h"
#include "defines.h"
#include "dictionary.h"
+#include "dic_traverse_wrapper.h"
#include "gesture_decoder_wrapper.h"
#include "unigram_dictionary.h"
@@ -29,10 +30,15 @@
// TODO: Change the type of all keyCodes to uint32_t
Dictionary::Dictionary(void *dict, int dictSize, int mmapFd, int dictBufAdjust,
- int typedLetterMultiplier, int fullWordMultiplier,
- int maxWordLength, int maxWords, int maxPredictions)
- : mDict((unsigned char*) dict), mDictSize(dictSize),
- mMmapFd(mmapFd), mDictBufAdjust(dictBufAdjust) {
+ int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength, int maxWords,
+ int maxPredictions)
+ : mDict(static_cast<unsigned char *>(dict)),
+ mOffsetDict((static_cast<unsigned char *>(dict)) + BinaryFormat::getHeaderSize(mDict)),
+ mDictSize(dictSize), mMmapFd(mmapFd), mDictBufAdjust(dictBufAdjust),
+ mUnigramDictionary(new UnigramDictionary(mOffsetDict, typedLetterMultiplier,
+ fullWordMultiplier, maxWordLength, maxWords, BinaryFormat::getFlags(mDict))),
+ mBigramDictionary(new BigramDictionary(mOffsetDict, maxWordLength, maxPredictions)),
+ mGestureDecoder(new GestureDecoderWrapper(maxWordLength, maxWords)) {
if (DEBUG_DICT) {
if (MAX_WORD_LENGTH_INTERNAL < maxWordLength) {
AKLOGI("Max word length (%d) is greater than %d",
@@ -40,14 +46,6 @@
AKLOGI("IN NATIVE SUGGEST Version: %d", (mDict[0] & 0xFF));
}
}
- const unsigned int headerSize = BinaryFormat::getHeaderSize(mDict);
- const unsigned int options = BinaryFormat::getFlags(mDict);
- mUnigramDictionary = new UnigramDictionary(mDict + headerSize, typedLetterMultiplier,
- fullWordMultiplier, maxWordLength, maxWords, options);
- mBigramDictionary = new BigramDictionary(mDict + headerSize, maxWordLength, maxPredictions);
- mGestureDecoder = new GestureDecoderWrapper(maxWordLength, maxWords);
- mGestureDecoder->setDict(mUnigramDictionary, mBigramDictionary,
- mDict + headerSize /* dict root */, 0 /* root pos */);
}
Dictionary::~Dictionary() {
@@ -56,16 +54,18 @@
delete mGestureDecoder;
}
-int Dictionary::getSuggestions(ProximityInfo *proximityInfo, int *xcoordinates, int *ycoordinates,
- int *times, int *pointerIds, int *codes, int codesSize, int *prevWordChars,
+int Dictionary::getSuggestions(ProximityInfo *proximityInfo, void *traverseSession,
+ int *xcoordinates, int *ycoordinates, int *times, int *pointerIds,
+ int *codes, int codesSize, int *prevWordChars,
int prevWordLength, int commitPoint, bool isGesture,
bool useFullEditDistance, unsigned short *outWords,
- int *frequencies, int *spaceIndices, int *outputTypes) {
+ int *frequencies, int *spaceIndices, int *outputTypes) const {
int result = 0;
if (isGesture) {
- mGestureDecoder->setPrevWord(prevWordChars, prevWordLength);
- result = mGestureDecoder->getSuggestions(proximityInfo, xcoordinates, ycoordinates,
- times, pointerIds, codes, codesSize, commitPoint,
+ DicTraverseWrapper::initDicTraverseSession(
+ traverseSession, this, prevWordChars, prevWordLength);
+ result = mGestureDecoder->getSuggestions(proximityInfo, traverseSession,
+ xcoordinates, ycoordinates, times, pointerIds, codes, codesSize, commitPoint,
outWords, frequencies, spaceIndices, outputTypes);
if (DEBUG_DICT) {
DUMP_RESULT(outWords, frequencies, 18 /* MAX_WORDS */, MAX_WORD_LENGTH_INTERNAL);
diff --git a/native/jni/src/dictionary.h b/native/jni/src/dictionary.h
index ab238c8..e9a03ce 100644
--- a/native/jni/src/dictionary.h
+++ b/native/jni/src/dictionary.h
@@ -44,18 +44,23 @@
Dictionary(void *dict, int dictSize, int mmapFd, int dictBufAdjust, int typedLetterMultipler,
int fullWordMultiplier, int maxWordLength, int maxWords, int maxPredictions);
- int getSuggestions(ProximityInfo *proximityInfo, int *xcoordinates, int *ycoordinates,
- int *times, int *pointerIds, int *codes, int codesSize, int *prevWordChars,
- int prevWordLength, int commitPoint, bool isGesture,
+ int getSuggestions(ProximityInfo *proximityInfo, void *traverseSession, int *xcoordinates,
+ int *ycoordinates, int *times, int *pointerIds, int *codes, int codesSize,
+ int *prevWordChars, int prevWordLength, int commitPoint, bool isGesture,
bool useFullEditDistance, unsigned short *outWords,
- int *frequencies, int *spaceIndices, int *outputTypes);
+ int *frequencies, int *spaceIndices, int *outputTypes) const;
int getBigrams(const int32_t *word, int length, int *codes, int codesSize,
unsigned short *outWords, int *frequencies, int *outputTypes) const;
int getFrequency(const int32_t *word, int length) const;
bool isValidBigram(const int32_t *word1, int length1, const int32_t *word2, int length2) const;
- void *getDict() const { return (void *)mDict; }
+ const uint8_t *getDict() const { // required to release dictionary buffer
+ return mDict;
+ }
+ const uint8_t *getOffsetDict() const {
+ return mOffsetDict;
+ }
int getDictSize() const { return mDictSize; }
int getMmapFd() const { return mMmapFd; }
int getDictBufAdjust() const { return mDictBufAdjust; }
@@ -67,7 +72,8 @@
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(Dictionary);
- const unsigned char *mDict;
+ const uint8_t *mDict;
+ const uint8_t *mOffsetDict;
// Used only for the mmap version of dictionary loading, but we use these as dummy variables
// also for the malloc version.
@@ -85,8 +91,9 @@
inline int Dictionary::wideStrLen(unsigned short *str) {
if (!str) return 0;
unsigned short *end = str;
- while (*end)
+ while (*end) {
end++;
+ }
return end - str;
}
} // namespace latinime
diff --git a/native/jni/src/geometry_utils.h b/native/jni/src/geometry_utils.h
new file mode 100644
index 0000000..f30e9fc
--- /dev/null
+++ b/native/jni/src/geometry_utils.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_GEOMETRY_UTILS_H
+#define LATINIME_GEOMETRY_UTILS_H
+
+#include <cmath>
+
+#define MAX_PATHS 2
+
+#define DEBUG_DECODER false
+
+#define M_PI_F 3.14159265f
+
+namespace latinime {
+
+static inline float squareFloat(float x) {
+ return x * x;
+}
+
+static inline float getSquaredDistanceFloat(float x1, float y1, float x2, float y2) {
+ return squareFloat(x1 - x2) + squareFloat(y1 - y2);
+}
+
+static inline float getDistanceFloat(float x1, float y1, float x2, float y2) {
+ return hypotf(x1 - x2, y1 - y2);
+}
+
+static inline int getDistanceInt(int x1, int y1, int x2, int y2) {
+ return static_cast<int>(getDistanceFloat(static_cast<float>(x1), static_cast<float>(y1),
+ static_cast<float>(x2), static_cast<float>(y2)));
+}
+
+static inline float getAngle(int x1, int y1, int x2, int y2) {
+ const int dx = x1 - x2;
+ const int dy = y1 - y2;
+ if (dx == 0 && dy == 0) return 0;
+ return atan2f(static_cast<float>(dy), static_cast<float>(dx));
+}
+
+static inline float getAngleDiff(float a1, float a2) {
+ const float diff = fabsf(a1 - a2);
+ if (diff > M_PI_F) {
+ return 2.0f * M_PI_F - diff;
+ }
+ return diff;
+}
+
+// static float pointToLineSegSquaredDistanceFloat(
+// float x, float y, float x1, float y1, float x2, float y2) {
+// float A = x - x1;
+// float B = y - y1;
+// float C = x2 - x1;
+// float D = y2 - y1;
+// return fabsf(A * D - C * B) / sqrtf(C * C + D * D);
+// }
+
+static inline float pointToLineSegSquaredDistanceFloat(
+ float x, float y, float x1, float y1, float x2, float y2) {
+ const float ray1x = x - x1;
+ const float ray1y = y - y1;
+ const float ray2x = x2 - x1;
+ const float ray2y = y2 - y1;
+
+ const float dotProduct = ray1x * ray2x + ray1y * ray2y;
+ const float lineLengthSqr = squareFloat(ray2x) + squareFloat(ray2y);
+ const float projectionLengthSqr = dotProduct / lineLengthSqr;
+
+ float projectionX;
+ float projectionY;
+ if (projectionLengthSqr < 0.0f) {
+ projectionX = x1;
+ projectionY = y1;
+ } else if (projectionLengthSqr > 1.0f) {
+ projectionX = x2;
+ projectionY = y2;
+ } else {
+ projectionX = x1 + projectionLengthSqr * ray2x;
+ projectionY = y1 + projectionLengthSqr * ray2y;
+ }
+ return getSquaredDistanceFloat(x, y, projectionX, projectionY);
+}
+} // namespace latinime
+#endif // LATINIME_GEOMETRY_UTILS_H
diff --git a/native/jni/src/gesture/gesture_decoder_wrapper.h b/native/jni/src/gesture/gesture_decoder_wrapper.h
index 03c84b5..92e1ded 100644
--- a/native/jni/src/gesture/gesture_decoder_wrapper.h
+++ b/native/jni/src/gesture/gesture_decoder_wrapper.h
@@ -29,45 +29,24 @@
class GestureDecoderWrapper : public IncrementalDecoderInterface {
public:
- GestureDecoderWrapper(const int maxWordLength, const int maxWords) {
- mIncrementalDecoderInterface = getGestureDecoderInstance(maxWordLength, maxWords);
+ GestureDecoderWrapper(const int maxWordLength, const int maxWords)
+ : mIncrementalDecoderInterface(getGestureDecoderInstance(maxWordLength, maxWords)) {
}
virtual ~GestureDecoderWrapper() {
delete mIncrementalDecoderInterface;
}
- int getSuggestions(ProximityInfo *pInfo, int *inputXs, int *inputYs, int *times,
- int *pointerIds, int *codes, int inputSize, int commitPoint,
- unsigned short *outWords, int *frequencies, int *outputIndices, int *outputTypes) {
+ int getSuggestions(ProximityInfo *pInfo, void *traverseSession, int *inputXs, int *inputYs,
+ int *times, int *pointerIds, int *codes, int inputSize, int commitPoint,
+ unsigned short *outWords, int *frequencies, int *outputIndices,
+ int *outputTypes) const {
if (!mIncrementalDecoderInterface) {
return 0;
}
return mIncrementalDecoderInterface->getSuggestions(
- pInfo, inputXs, inputYs, times, pointerIds, codes, inputSize, commitPoint,
- outWords, frequencies, outputIndices, outputTypes);
- }
-
- void reset() {
- if (!mIncrementalDecoderInterface) {
- return;
- }
- mIncrementalDecoderInterface->reset();
- }
-
- void setDict(const UnigramDictionary *dict, const BigramDictionary *bigram,
- const uint8_t *dictRoot, int rootPos) {
- if (!mIncrementalDecoderInterface) {
- return;
- }
- mIncrementalDecoderInterface->setDict(dict, bigram, dictRoot, rootPos);
- }
-
- void setPrevWord(const int32_t *prevWord, int prevWordLength) {
- if (!mIncrementalDecoderInterface) {
- return;
- }
- mIncrementalDecoderInterface->setPrevWord(prevWord, prevWordLength);
+ pInfo, traverseSession, inputXs, inputYs, times, pointerIds, codes,
+ inputSize, commitPoint, outWords, frequencies, outputIndices, outputTypes);
}
static void setGestureDecoderFactoryMethod(
@@ -76,7 +55,7 @@
}
private:
- DISALLOW_COPY_AND_ASSIGN(GestureDecoderWrapper);
+ DISALLOW_IMPLICIT_CONSTRUCTORS(GestureDecoderWrapper);
static IncrementalDecoderInterface *getGestureDecoderInstance(int maxWordLength, int maxWords) {
if (sGestureDecoderFactoryMethod) {
return sGestureDecoderFactoryMethod(maxWordLength, maxWords);
diff --git a/native/jni/src/gesture/incremental_decoder_interface.h b/native/jni/src/gesture/incremental_decoder_interface.h
index 6d2e273..d1395aa 100644
--- a/native/jni/src/gesture/incremental_decoder_interface.h
+++ b/native/jni/src/gesture/incremental_decoder_interface.h
@@ -28,14 +28,14 @@
class IncrementalDecoderInterface {
public:
- virtual int getSuggestions(ProximityInfo *pInfo, int *inputXs, int *inputYs, int *times,
- int *pointerIds, int *codes, int inputSize, int commitPoint,
- unsigned short *outWords, int *frequencies, int *outputIndices, int *outputTypes) = 0;
- virtual void reset() = 0;
- virtual void setDict(const UnigramDictionary *dict, const BigramDictionary *bigram,
- const uint8_t *dictRoot, int rootPos) = 0;
- virtual void setPrevWord(const int32_t *prevWord, int prevWordLength) = 0;
+ virtual int getSuggestions(ProximityInfo *pInfo, void *traverseSession,
+ int *inputXs, int *inputYs, int *times, int *pointerIds, int *codes,
+ int inputSize, int commitPoint, unsigned short *outWords, int *frequencies,
+ int *outputIndices, int *outputTypes) const = 0;
+ IncrementalDecoderInterface() { };
virtual ~IncrementalDecoderInterface() { };
+ private:
+ DISALLOW_COPY_AND_ASSIGN(IncrementalDecoderInterface);
};
} // namespace latinime
#endif // LATINIME_INCREMENTAL_DECODER_INTERFACE_H
diff --git a/native/jni/src/gesture/incremental_decoder_wrapper.h b/native/jni/src/gesture/incremental_decoder_wrapper.h
index 6980615..da7afdb 100644
--- a/native/jni/src/gesture/incremental_decoder_wrapper.h
+++ b/native/jni/src/gesture/incremental_decoder_wrapper.h
@@ -29,45 +29,24 @@
class IncrementalDecoderWrapper : public IncrementalDecoderInterface {
public:
- IncrementalDecoderWrapper(const int maxWordLength, const int maxWords) {
- mIncrementalDecoderInterface = getIncrementalDecoderInstance(maxWordLength, maxWords);
+ IncrementalDecoderWrapper(const int maxWordLength, const int maxWords)
+ : mIncrementalDecoderInterface(getIncrementalDecoderInstance(maxWordLength, maxWords)) {
}
virtual ~IncrementalDecoderWrapper() {
delete mIncrementalDecoderInterface;
}
- int getSuggestions(ProximityInfo *pInfo, int *inputXs, int *inputYs, int *times,
- int *pointerIds, int *codes, int inputSize, int commitPoint,
- unsigned short *outWords, int *frequencies, int *outputIndices, int *outputTypes) {
+ int getSuggestions(ProximityInfo *pInfo, void *traverseSession, int *inputXs, int *inputYs,
+ int *times, int *pointerIds, int *codes, int inputSize, int commitPoint,
+ unsigned short *outWords, int *frequencies, int *outputIndices,
+ int *outputTypes) const {
if (!mIncrementalDecoderInterface) {
return 0;
}
return mIncrementalDecoderInterface->getSuggestions(
- pInfo, inputXs, inputYs, times, pointerIds, codes, inputSize, commitPoint,
- outWords, frequencies, outputIndices, outputTypes);
- }
-
- void reset() {
- if (!mIncrementalDecoderInterface) {
- return;
- }
- mIncrementalDecoderInterface->reset();
- }
-
- void setDict(const UnigramDictionary *dict, const BigramDictionary *bigram,
- const uint8_t *dictRoot, int rootPos) {
- if (!mIncrementalDecoderInterface) {
- return;
- }
- mIncrementalDecoderInterface->setDict(dict, bigram, dictRoot, rootPos);
- }
-
- void setPrevWord(const int32_t *prevWord, int prevWordLength) {
- if (!mIncrementalDecoderInterface) {
- return;
- }
- mIncrementalDecoderInterface->setPrevWord(prevWord, prevWordLength);
+ pInfo, traverseSession, inputXs, inputYs, times, pointerIds, codes,
+ inputSize, commitPoint, outWords, frequencies, outputIndices, outputTypes);
}
static void setIncrementalDecoderFactoryMethod(
@@ -76,7 +55,7 @@
}
private:
- DISALLOW_COPY_AND_ASSIGN(IncrementalDecoderWrapper);
+ DISALLOW_IMPLICIT_CONSTRUCTORS(IncrementalDecoderWrapper);
static IncrementalDecoderInterface *getIncrementalDecoderInstance(int maxWordLength,
int maxWords) {
if (sIncrementalDecoderFactoryMethod) {
diff --git a/native/jni/src/hash_map_compat.h b/native/jni/src/hash_map_compat.h
new file mode 100644
index 0000000..116359a
--- /dev/null
+++ b/native/jni/src/hash_map_compat.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2012, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_HASH_MAP_COMPAT_H
+#define LATINIME_HASH_MAP_COMPAT_H
+
+// TODO: Use std::unordered_map that has been standardized in C++11
+
+#ifdef __APPLE__
+#include <ext/hash_map>
+#else // __APPLE__
+#include <hash_map>
+#endif // __APPLE__
+
+#ifdef __SGI_STL_PORT
+#define hash_map_compat stlport::hash_map
+#else // __SGI_STL_PORT
+#define hash_map_compat __gnu_cxx::hash_map
+#endif // __SGI_STL_PORT
+
+#endif // LATINIME_HASH_MAP_COMPAT_H
diff --git a/native/jni/src/proximity_info.cpp b/native/jni/src/proximity_info.cpp
index cee408d..e681f6f 100644
--- a/native/jni/src/proximity_info.cpp
+++ b/native/jni/src/proximity_info.cpp
@@ -17,34 +17,48 @@
#include <cassert>
#include <cmath>
#include <cstring>
-#include <string>
#define LOG_TAG "LatinIME: proximity_info.cpp"
#include "additional_proximity_chars.h"
#include "char_utils.h"
#include "defines.h"
+#include "geometry_utils.h"
+#include "jni.h"
#include "proximity_info.h"
namespace latinime {
-inline void copyOrFillZero(void *to, const void *from, size_t size) {
- if (from) {
- memcpy(to, from, size);
- } else {
- memset(to, 0, size);
+/* static */ const int ProximityInfo::NOT_A_CODE = -1;
+/* static */ const float ProximityInfo::NOT_A_DISTANCE_FLOAT = -1.0f;
+
+static inline void safeGetOrFillZeroIntArrayRegion(JNIEnv *env, jintArray jArray, jsize len,
+ jint *buffer) {
+ if (jArray && buffer) {
+ env->GetIntArrayRegion(jArray, 0, len, buffer);
+ } else if (buffer) {
+ memset(buffer, 0, len * sizeof(jint));
}
}
-ProximityInfo::ProximityInfo(const std::string localeStr, const int maxProximityCharsSize,
+static inline void safeGetOrFillZeroFloatArrayRegion(JNIEnv *env, jfloatArray jArray, jsize len,
+ jfloat *buffer) {
+ if (jArray && buffer) {
+ env->GetFloatArrayRegion(jArray, 0, len, buffer);
+ } else if (buffer) {
+ memset(buffer, 0, len * sizeof(jfloat));
+ }
+}
+
+ProximityInfo::ProximityInfo(JNIEnv *env, const jstring localeJStr, const int maxProximityCharsSize,
const int keyboardWidth, const int keyboardHeight, const int gridWidth,
- const int gridHeight, const int mostCommonKeyWidth,
- const int32_t *proximityCharsArray, const int 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)
- : MAX_PROXIMITY_CHARS_SIZE(maxProximityCharsSize), KEYBOARD_WIDTH(keyboardWidth),
- KEYBOARD_HEIGHT(keyboardHeight), GRID_WIDTH(gridWidth), GRID_HEIGHT(gridHeight),
+ const int gridHeight, const int mostCommonKeyWidth, const jintArray proximityChars,
+ const int keyCount, const jintArray keyXCoordinates, const jintArray keyYCoordinates,
+ const jintArray keyWidths, const jintArray keyHeights, const jintArray keyCharCodes,
+ const jfloatArray sweetSpotCenterXs, const jfloatArray sweetSpotCenterYs,
+ const jfloatArray sweetSpotRadii)
+ : MAX_PROXIMITY_CHARS_SIZE(maxProximityCharsSize), GRID_WIDTH(gridWidth),
+ GRID_HEIGHT(gridHeight), MOST_COMMON_KEY_WIDTH(mostCommonKeyWidth),
MOST_COMMON_KEY_WIDTH_SQUARE(mostCommonKeyWidth * mostCommonKeyWidth),
CELL_WIDTH((keyboardWidth + gridWidth - 1) / gridWidth),
CELL_HEIGHT((keyboardHeight + gridHeight - 1) / gridHeight),
@@ -52,27 +66,30 @@
HAS_TOUCH_POSITION_CORRECTION_DATA(keyCount > 0 && keyXCoordinates && keyYCoordinates
&& keyWidths && keyHeights && keyCharCodes && sweetSpotCenterXs
&& sweetSpotCenterYs && sweetSpotRadii),
- mLocaleStr(localeStr) {
+ mProximityCharsArray(new int32_t[GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE
+ /* proximityGridLength */]) {
const int proximityGridLength = GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE;
if (DEBUG_PROXIMITY_INFO) {
AKLOGI("Create proximity info array %d", proximityGridLength);
}
- mProximityCharsArray = new int32_t[proximityGridLength];
- memcpy(mProximityCharsArray, proximityCharsArray,
- proximityGridLength * sizeof(mProximityCharsArray[0]));
-
- copyOrFillZero(mKeyXCoordinates, keyXCoordinates, KEY_COUNT * sizeof(mKeyXCoordinates[0]));
- copyOrFillZero(mKeyYCoordinates, keyYCoordinates, KEY_COUNT * sizeof(mKeyYCoordinates[0]));
- copyOrFillZero(mKeyWidths, keyWidths, KEY_COUNT * sizeof(mKeyWidths[0]));
- copyOrFillZero(mKeyHeights, keyHeights, KEY_COUNT * sizeof(mKeyHeights[0]));
- copyOrFillZero(mKeyCharCodes, keyCharCodes, KEY_COUNT * sizeof(mKeyCharCodes[0]));
- copyOrFillZero(mSweetSpotCenterXs, sweetSpotCenterXs,
- KEY_COUNT * sizeof(mSweetSpotCenterXs[0]));
- copyOrFillZero(mSweetSpotCenterYs, sweetSpotCenterYs,
- KEY_COUNT * sizeof(mSweetSpotCenterYs[0]));
- copyOrFillZero(mSweetSpotRadii, sweetSpotRadii, KEY_COUNT * sizeof(mSweetSpotRadii[0]));
-
+ const jsize localeCStrUtf8Length = env->GetStringUTFLength(localeJStr);
+ if (localeCStrUtf8Length >= MAX_LOCALE_STRING_LENGTH) {
+ AKLOGI("Locale string length too long: length=%d", localeCStrUtf8Length);
+ assert(false);
+ }
+ memset(mLocaleStr, 0, sizeof(mLocaleStr));
+ env->GetStringUTFRegion(localeJStr, 0, env->GetStringLength(localeJStr), mLocaleStr);
+ safeGetOrFillZeroIntArrayRegion(env, proximityChars, proximityGridLength, mProximityCharsArray);
+ safeGetOrFillZeroIntArrayRegion(env, keyXCoordinates, KEY_COUNT, mKeyXCoordinates);
+ safeGetOrFillZeroIntArrayRegion(env, keyYCoordinates, KEY_COUNT, mKeyYCoordinates);
+ safeGetOrFillZeroIntArrayRegion(env, keyWidths, KEY_COUNT, mKeyWidths);
+ safeGetOrFillZeroIntArrayRegion(env, keyHeights, KEY_COUNT, mKeyHeights);
+ safeGetOrFillZeroIntArrayRegion(env, keyCharCodes, KEY_COUNT, mKeyCharCodes);
+ safeGetOrFillZeroFloatArrayRegion(env, sweetSpotCenterXs, KEY_COUNT, mSweetSpotCenterXs);
+ safeGetOrFillZeroFloatArrayRegion(env, sweetSpotCenterYs, KEY_COUNT, mSweetSpotCenterYs);
+ safeGetOrFillZeroFloatArrayRegion(env, sweetSpotRadii, KEY_COUNT, mSweetSpotRadii);
initializeCodeToKeyIndex();
+ initializeG();
}
// Build the reversed look up table from the char code to the index in mKeyXCoordinates,
@@ -121,6 +138,21 @@
return false;
}
+static inline float getNormalizedSquaredDistanceFloat(float x1, float y1, float x2, float y2,
+ float scale) {
+ return squareFloat((x1 - x2) / scale) + squareFloat((y1 - y2) / scale);
+}
+
+float ProximityInfo::getNormalizedSquaredDistanceFromCenterFloat(
+ const int keyId, const int x, const int y) const {
+ const float centerX = static_cast<float>(getKeyCenterXOfIdG(keyId));
+ const float centerY = static_cast<float>(getKeyCenterYOfIdG(keyId));
+ const float touchX = static_cast<float>(x);
+ const float touchY = static_cast<float>(y);
+ const float keyWidth = static_cast<float>(getMostCommonKeyWidth());
+ return getNormalizedSquaredDistanceFloat(centerX, centerY, touchX, touchY, keyWidth);
+}
+
int ProximityInfo::squaredDistanceToEdge(const int keyId, const int x, const int y) const {
if (keyId < 0) return true; // NOT_A_ID is -1, but return whenever < 0 just in case
const int left = mKeyXCoordinates[keyId];
@@ -160,7 +192,7 @@
}
}
const int additionalProximitySize =
- AdditionalProximityChars::getAdditionalCharsSize(&mLocaleStr, primaryKey);
+ AdditionalProximityChars::getAdditionalCharsSize(mLocaleStr, primaryKey);
if (additionalProximitySize > 0) {
inputCodes[insertPos++] = ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE;
if (insertPos >= MAX_PROXIMITY_CHARS_SIZE) {
@@ -171,7 +203,7 @@
}
const int32_t *additionalProximityChars =
- AdditionalProximityChars::getAdditionalChars(&mLocaleStr, primaryKey);
+ AdditionalProximityChars::getAdditionalChars(mLocaleStr, primaryKey);
for (int j = 0; j < additionalProximitySize; ++j) {
const int32_t ac = additionalProximityChars[j];
int k = 0;
@@ -211,24 +243,65 @@
return mCodeToKeyIndex[baseLowerC];
}
-// TODO: [Staging] Optimize
-void ProximityInfo::getCenters(int *centerXs, int *centerYs, int *codeToKeyIndex,
- int *keyToCodeIndex, int *keyCount, int *keyWidth) const {
- *keyCount = KEY_COUNT;
- *keyWidth = sqrt(static_cast<float>(MOST_COMMON_KEY_WIDTH_SQUARE));
+int ProximityInfo::getKeyCode(const int keyIndex) const {
+ if (keyIndex < 0 || keyIndex >= KEY_COUNT) {
+ return NOT_AN_INDEX;
+ }
+ return mKeyToCodeIndexG[keyIndex];
+}
+void ProximityInfo::initializeG() {
+ // TODO: Optimize
for (int i = 0; i < KEY_COUNT; ++i) {
const int code = mKeyCharCodes[i];
const int lowerCode = toBaseLowerCase(code);
- centerXs[i] = mKeyXCoordinates[i] + mKeyWidths[i] / 2;
- centerYs[i] = mKeyYCoordinates[i] + mKeyHeights[i] / 2;
- codeToKeyIndex[code] = i;
+ mCenterXsG[i] = mKeyXCoordinates[i] + mKeyWidths[i] / 2;
+ mCenterYsG[i] = mKeyYCoordinates[i] + mKeyHeights[i] / 2;
if (code != lowerCode && lowerCode >= 0 && lowerCode <= MAX_CHAR_CODE) {
- codeToKeyIndex[lowerCode] = i;
- keyToCodeIndex[i] = lowerCode;
+ mCodeToKeyIndex[lowerCode] = i;
+ mKeyToCodeIndexG[i] = lowerCode;
} else {
- keyToCodeIndex[i] = code;
+ mKeyToCodeIndexG[i] = code;
}
}
+ for (int i = 0; i < KEY_COUNT; i++) {
+ mKeyKeyDistancesG[i][i] = 0;
+ for (int j = i + 1; j < KEY_COUNT; j++) {
+ mKeyKeyDistancesG[i][j] = getDistanceInt(
+ mCenterXsG[i], mCenterYsG[i], mCenterXsG[j], mCenterYsG[j]);
+ mKeyKeyDistancesG[j][i] = mKeyKeyDistancesG[i][j];
+ }
+ }
+}
+
+float ProximityInfo::getKeyCenterXOfCharG(int charCode) const {
+ return getKeyCenterXOfIdG(getKeyIndex(charCode));
+}
+
+float ProximityInfo::getKeyCenterYOfCharG(int charCode) const {
+ return getKeyCenterYOfIdG(getKeyIndex(charCode));
+}
+
+float ProximityInfo::getKeyCenterXOfIdG(int keyId) const {
+ if (keyId >= 0) {
+ return mCenterXsG[keyId];
+ }
+ return 0;
+}
+
+float ProximityInfo::getKeyCenterYOfIdG(int keyId) const {
+ if (keyId >= 0) {
+ return mCenterYsG[keyId];
+ }
+ return 0;
+}
+
+int ProximityInfo::getKeyKeyDistanceG(int key0, int key1) const {
+ const int keyId0 = getKeyIndex(key0);
+ const int keyId1 = getKeyIndex(key1);
+ if (keyId0 >= 0 && keyId1 >= 0) {
+ return mKeyKeyDistancesG[keyId0][keyId1];
+ }
+ return 0;
}
} // namespace latinime
diff --git a/native/jni/src/proximity_info.h b/native/jni/src/proximity_info.h
index abd07dd..822909b 100644
--- a/native/jni/src/proximity_info.h
+++ b/native/jni/src/proximity_info.h
@@ -18,9 +18,9 @@
#define LATINIME_PROXIMITY_INFO_H
#include <stdint.h>
-#include <string>
#include "defines.h"
+#include "jni.h"
namespace latinime {
@@ -28,31 +28,25 @@
class ProximityInfo {
public:
- ProximityInfo(const std::string localeStr, const int maxProximityCharsSize,
+ ProximityInfo(JNIEnv *env, const jstring localeJStr, const int maxProximityCharsSize,
const int keyboardWidth, const int keyboardHeight, const int gridWidth,
- const int gridHeight, const int mostCommonkeyWidth,
- const int32_t *proximityCharsArray, const int 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);
+ const int gridHeight, const int mostCommonKeyWidth, const jintArray proximityChars,
+ const int keyCount, const jintArray keyXCoordinates, const jintArray keyYCoordinates,
+ const jintArray keyWidths, const jintArray keyHeights, const jintArray keyCharCodes,
+ const jfloatArray sweetSpotCenterXs, const jfloatArray sweetSpotCenterYs,
+ const jfloatArray sweetSpotRadii);
~ProximityInfo();
bool hasSpaceProximity(const int x, const int y) const;
int getNormalizedSquaredDistance(const int inputIndex, const int proximityIndex) const;
+ float getNormalizedSquaredDistanceFromCenterFloat(
+ const int keyId, const int x, const int y) const;
bool sameAsTyped(const unsigned short *word, int length) const;
- int squaredDistanceToEdge(const int keyId, const int x, const int y) const;
- bool isOnKey(const int keyId, const int x, const int y) const {
- if (keyId < 0) return true; // NOT_A_ID is -1, but return whenever < 0 just in case
- const int left = mKeyXCoordinates[keyId];
- const int top = mKeyYCoordinates[keyId];
- const int right = left + mKeyWidths[keyId] + 1;
- const int bottom = top + mKeyHeights[keyId];
- return left < right && top < bottom && x >= left && x < right && y >= top && y < bottom;
- }
int getKeyIndex(const int c) const;
+ int getKeyCode(const int keyIndex) const;
bool hasSweetSpotData(const int keyIndex) const {
// When there are no calibration data for a key,
// the radius of the key is assigned to zero.
- return mSweetSpotRadii[keyIndex] > 0.0;
+ return mSweetSpotRadii[keyIndex] > 0.0f;
}
float getSweetSpotRadiiAt(int keyIndex) const {
return mSweetSpotRadii[keyIndex];
@@ -70,11 +64,15 @@
return HAS_TOUCH_POSITION_CORRECTION_DATA;
}
+ int getMostCommonKeyWidth() const {
+ return MOST_COMMON_KEY_WIDTH;
+ }
+
int getMostCommonKeyWidthSquare() const {
return MOST_COMMON_KEY_WIDTH_SQUARE;
}
- std::string getLocaleStr() const {
+ const char *getLocaleStr() const {
return mLocaleStr;
}
@@ -98,9 +96,11 @@
return GRID_HEIGHT;
}
- // Returns the keyboard key-center information.
- void getCenters(int *centersX, int *centersY, int *codeToKeyIndex, int *keyToCodeIndex,
- int *keyCount, int *keyWidth) const;
+ float getKeyCenterXOfCharG(int charCode) const;
+ float getKeyCenterYOfCharG(int charCode) const;
+ float getKeyCenterXOfIdG(int keyId) const;
+ float getKeyCenterYOfIdG(int keyId) const;
+ int getKeyKeyDistanceG(int key0, int key1) const;
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(ProximityInfo);
@@ -108,27 +108,36 @@
static const int MAX_KEY_COUNT_IN_A_KEYBOARD = 64;
// The upper limit of the char code in mCodeToKeyIndex
static const int MAX_CHAR_CODE = 127;
- static const float NOT_A_DISTANCE_FLOAT = -1.0f;
- static const int NOT_A_CODE = -1;
+ static const int NOT_A_CODE;
+ static const float NOT_A_DISTANCE_FLOAT;
int getStartIndexFromCoordinates(const int x, const int y) const;
void initializeCodeToKeyIndex();
+ void initializeG();
float calculateNormalizedSquaredDistance(const int keyIndex, const int inputIndex) const;
float calculateSquaredDistanceFromSweetSpotCenter(
const int keyIndex, const int inputIndex) const;
bool hasInputCoordinates() const;
+ int squaredDistanceToEdge(const int keyId, const int x, const int y) const;
+ bool isOnKey(const int keyId, const int x, const int y) const {
+ if (keyId < 0) return true; // NOT_A_ID is -1, but return whenever < 0 just in case
+ const int left = mKeyXCoordinates[keyId];
+ const int top = mKeyYCoordinates[keyId];
+ const int right = left + mKeyWidths[keyId] + 1;
+ const int bottom = top + mKeyHeights[keyId];
+ return left < right && top < bottom && x >= left && x < right && y >= top && y < bottom;
+ }
const int MAX_PROXIMITY_CHARS_SIZE;
- const int KEYBOARD_WIDTH;
- const int KEYBOARD_HEIGHT;
const int GRID_WIDTH;
const int GRID_HEIGHT;
+ const int MOST_COMMON_KEY_WIDTH;
const int MOST_COMMON_KEY_WIDTH_SQUARE;
const int CELL_WIDTH;
const int CELL_HEIGHT;
const int KEY_COUNT;
const bool HAS_TOUCH_POSITION_CORRECTION_DATA;
- const std::string mLocaleStr;
+ char mLocaleStr[MAX_LOCALE_STRING_LENGTH];
int32_t *mProximityCharsArray;
int32_t mKeyXCoordinates[MAX_KEY_COUNT_IN_A_KEYBOARD];
int32_t mKeyYCoordinates[MAX_KEY_COUNT_IN_A_KEYBOARD];
@@ -139,6 +148,11 @@
float mSweetSpotCenterYs[MAX_KEY_COUNT_IN_A_KEYBOARD];
float mSweetSpotRadii[MAX_KEY_COUNT_IN_A_KEYBOARD];
int mCodeToKeyIndex[MAX_CHAR_CODE + 1];
+
+ int mKeyToCodeIndexG[MAX_KEY_COUNT_IN_A_KEYBOARD];
+ int mCenterXsG[MAX_KEY_COUNT_IN_A_KEYBOARD];
+ int mCenterYsG[MAX_KEY_COUNT_IN_A_KEYBOARD];
+ int mKeyKeyDistancesG[MAX_KEY_COUNT_IN_A_KEYBOARD][MAX_KEY_COUNT_IN_A_KEYBOARD];
// TODO: move to correction.h
};
} // namespace latinime
diff --git a/native/jni/src/proximity_info_state.cpp b/native/jni/src/proximity_info_state.cpp
index 86c8a69..f01b81e 100644
--- a/native/jni/src/proximity_info_state.cpp
+++ b/native/jni/src/proximity_info_state.cpp
@@ -20,13 +20,15 @@
#define LOG_TAG "LatinIME: proximity_info_state.cpp"
#include "defines.h"
+#include "geometry_utils.h"
#include "proximity_info.h"
#include "proximity_info_state.h"
namespace latinime {
-void ProximityInfoState::initInputParams(
- const ProximityInfo *proximityInfo, const int32_t *inputCodes, const int inputLength,
- const int *xCoordinates, const int *yCoordinates) {
+void ProximityInfoState::initInputParams(const int pointerId, const float maxPointToKeyLength,
+ const ProximityInfo *proximityInfo, const int32_t *const inputCodes, const int inputSize,
+ const int *const xCoordinates, const int *const yCoordinates, const int *const times,
+ const int *const pointerIds, const bool isGeometric) {
mProximityInfo = proximityInfo;
mHasTouchPositionCorrectionData = proximityInfo->hasTouchPositionCorrectionData();
mMostCommonKeyWidthSquare = proximityInfo->getMostCommonKeyWidthSquare();
@@ -36,76 +38,146 @@
mCellWidth = proximityInfo->getCellWidth();
mGridHeight = proximityInfo->getGridWidth();
mGridWidth = proximityInfo->getGridHeight();
- const int normalizedSquaredDistancesLength =
- MAX_PROXIMITY_CHARS_SIZE_INTERNAL * MAX_WORD_LENGTH_INTERNAL;
- for (int i = 0; i < normalizedSquaredDistancesLength; ++i) {
- mNormalizedSquaredDistances[i] = NOT_A_DISTANCE;
- }
- memset(mInputCodes, 0,
- MAX_WORD_LENGTH_INTERNAL * MAX_PROXIMITY_CHARS_SIZE_INTERNAL * sizeof(mInputCodes[0]));
+ memset(mInputCodes, 0, sizeof(mInputCodes));
- for (int i = 0; i < inputLength; ++i) {
- const int32_t primaryKey = inputCodes[i];
- const int x = xCoordinates[i];
- const int y = yCoordinates[i];
- int *proximities = &mInputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL];
- mProximityInfo->calculateNearbyKeyCodes(x, y, primaryKey, proximities);
- }
-
- if (DEBUG_PROXIMITY_CHARS) {
- for (int i = 0; i < inputLength; ++i) {
- AKLOGI("---");
- for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL; ++j) {
- int icc = mInputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j];
- int icfjc = inputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j];
- icc += 0;
- icfjc += 0;
- AKLOGI("--- (%d)%c,%c", i, icc, icfjc); AKLOGI("--- A<%d>,B<%d>", icc, icfjc);
- }
+ if (!isGeometric && pointerId == 0) {
+ // Initialize
+ // - mInputCodes
+ // - mNormalizedSquaredDistances
+ // TODO: Merge
+ for (int i = 0; i < inputSize; ++i) {
+ const int32_t primaryKey = inputCodes[i];
+ const int x = xCoordinates[i];
+ const int y = yCoordinates[i];
+ int *proximities = &mInputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL];
+ mProximityInfo->calculateNearbyKeyCodes(x, y, primaryKey, proximities);
}
- }
- mInputXCoordinates = xCoordinates;
- mInputYCoordinates = yCoordinates;
- mTouchPositionCorrectionEnabled =
- mHasTouchPositionCorrectionData && xCoordinates && yCoordinates;
- mInputLength = inputLength;
- for (int i = 0; i < inputLength; ++i) {
- mPrimaryInputWord[i] = getPrimaryCharAt(i);
- }
- mPrimaryInputWord[inputLength] = 0;
- if (DEBUG_PROXIMITY_CHARS) {
- AKLOGI("--- initInputParams");
- }
- for (int i = 0; i < mInputLength; ++i) {
- const int *proximityChars = getProximityCharsAt(i);
- const int primaryKey = proximityChars[0];
- const int x = xCoordinates[i];
- const int y = yCoordinates[i];
+
if (DEBUG_PROXIMITY_CHARS) {
- int a = x + y + primaryKey;
- a += 0;
- AKLOGI("--- Primary = %c, x = %d, y = %d", primaryKey, x, y);
- }
- for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL && proximityChars[j] > 0; ++j) {
- const int currentChar = proximityChars[j];
- const float squaredDistance =
- hasInputCoordinates() ? calculateNormalizedSquaredDistance(
- mProximityInfo->getKeyIndex(currentChar), i) :
- NOT_A_DISTANCE_FLOAT;
- if (squaredDistance >= 0.0f) {
- mNormalizedSquaredDistances[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j] =
- (int) (squaredDistance * NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR);
- } else {
- mNormalizedSquaredDistances[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j] =
- (j == 0) ? EQUIVALENT_CHAR_WITHOUT_DISTANCE_INFO :
- PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO;
- }
- if (DEBUG_PROXIMITY_CHARS) {
- AKLOGI("--- Proximity (%d) = %c", j, currentChar);
+ for (int i = 0; i < inputSize; ++i) {
+ AKLOGI("---");
+ for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL; ++j) {
+ int icc = mInputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j];
+ int icfjc = inputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j];
+ icc += 0;
+ icfjc += 0;
+ AKLOGI("--- (%d)%c,%c", i, icc, icfjc); AKLOGI("--- A<%d>,B<%d>", icc, icfjc);
+ }
}
}
}
+
+ ///////////////////////
+ // Setup touch points
+ mMaxPointToKeyLength = maxPointToKeyLength;
+ mInputXs.clear();
+ mInputYs.clear();
+ mTimes.clear();
+ mLengthCache.clear();
+ mDistanceCache.clear();
+
+ mInputSize = 0;
+ if (xCoordinates && yCoordinates) {
+ const bool proximityOnly = !isGeometric && (xCoordinates[0] < 0 || yCoordinates[0] < 0);
+ for (int i = 0; i < inputSize; ++i) {
+ // Assuming pointerId == 0 if pointerIds is null.
+ const int pid = pointerIds ? pointerIds[i] : 0;
+ if (pointerId == pid) {
+ const int c = isGeometric ? NOT_A_COORDINATE : getPrimaryCharAt(i);
+ const int x = proximityOnly ? NOT_A_COORDINATE : xCoordinates[i];
+ const int y = proximityOnly ? NOT_A_COORDINATE : yCoordinates[i];
+ const int time = times ? times[i] : -1;
+ if (pushTouchPoint(c, x, y, time, isGeometric)) {
+ ++mInputSize;
+ }
+ }
+ }
+ }
+
+ if (mInputSize > 0) {
+ const int keyCount = mProximityInfo->getKeyCount();
+ mDistanceCache.resize(mInputSize * keyCount);
+ for (int i = 0; i < mInputSize; ++i) {
+ for (int k = 0; k < keyCount; ++k) {
+ const int index = i * keyCount + k;
+ const int x = mInputXs[i];
+ const int y = mInputYs[i];
+ mDistanceCache[index] =
+ mProximityInfo->getNormalizedSquaredDistanceFromCenterFloat(k, x, y);
+ }
+ }
+ }
+
+ // end
+ ///////////////////////
+
+ memset(mNormalizedSquaredDistances, NOT_A_DISTANCE, sizeof(mNormalizedSquaredDistances));
+ memset(mPrimaryInputWord, 0, sizeof(mPrimaryInputWord));
+ mTouchPositionCorrectionEnabled = mInputSize > 0 && mHasTouchPositionCorrectionData
+ && xCoordinates && yCoordinates && !isGeometric;
+ if (!isGeometric && pointerId == 0) {
+ for (int i = 0; i < inputSize; ++i) {
+ mPrimaryInputWord[i] = getPrimaryCharAt(i);
+ }
+
+ for (int i = 0; i < mInputSize && mTouchPositionCorrectionEnabled; ++i) {
+ const int *proximityChars = getProximityCharsAt(i);
+ const int primaryKey = proximityChars[0];
+ const int x = xCoordinates[i];
+ const int y = yCoordinates[i];
+ if (DEBUG_PROXIMITY_CHARS) {
+ int a = x + y + primaryKey;
+ a += 0;
+ AKLOGI("--- Primary = %c, x = %d, y = %d", primaryKey, x, y);
+ }
+ for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL && proximityChars[j] > 0; ++j) {
+ const int currentChar = proximityChars[j];
+ const float squaredDistance =
+ hasInputCoordinates() ? calculateNormalizedSquaredDistance(
+ mProximityInfo->getKeyIndex(currentChar), i) :
+ NOT_A_DISTANCE_FLOAT;
+ if (squaredDistance >= 0.0f) {
+ mNormalizedSquaredDistances[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j] =
+ (int) (squaredDistance * NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR);
+ } else {
+ mNormalizedSquaredDistances[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j] =
+ (j == 0) ? EQUIVALENT_CHAR_WITHOUT_DISTANCE_INFO :
+ PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO;
+ }
+ if (DEBUG_PROXIMITY_CHARS) {
+ AKLOGI("--- Proximity (%d) = %c", j, currentChar);
+ }
+ }
+ }
+ }
+}
+
+bool ProximityInfoState::pushTouchPoint(const int nodeChar, int x, int y,
+ const int time, const bool sample) {
+ const uint32_t size = mInputXs.size();
+ // TODO: Should have a const variable for 10
+ const int sampleRate = mProximityInfo->getMostCommonKeyWidth() / 10;
+ if (size > 0) {
+ const int dist = getDistanceInt(x, y, mInputXs[size - 1], mInputYs[size - 1]);
+ if (sample && dist < sampleRate) {
+ return false;
+ }
+ mLengthCache.push_back(mLengthCache[size - 1] + dist);
+ } else {
+ mLengthCache.push_back(0);
+ }
+ if (nodeChar >= 0 && (x < 0 || y < 0)) {
+ const int keyId = mProximityInfo->getKeyIndex(nodeChar);
+ if (keyId >= 0) {
+ x = mProximityInfo->getKeyCenterXOfIdG(keyId);
+ y = mProximityInfo->getKeyCenterYOfIdG(keyId);
+ }
+ }
+ mInputXs.push_back(x);
+ mInputYs.push_back(y);
+ mTimes.push_back(time);
+ return true;
}
float ProximityInfoState::calculateNormalizedSquaredDistance(
@@ -116,7 +188,7 @@
if (!mProximityInfo->hasSweetSpotData(keyIndex)) {
return NOT_A_DISTANCE_FLOAT;
}
- if (NOT_A_COORDINATE == mInputXCoordinates[inputIndex]) {
+ if (NOT_A_COORDINATE == mInputXs[inputIndex]) {
return NOT_A_DISTANCE_FLOAT;
}
const float squaredDistance = calculateSquaredDistanceFromSweetSpotCenter(
@@ -125,12 +197,37 @@
return squaredDistance / squaredRadius;
}
+int ProximityInfoState::getDuration(const int index) const {
+ if (mInputSize > 0 && index > 0 && index < static_cast<int>(mInputSize) - 1) {
+ return mTimes[index + 1] - mTimes[index - 1];
+ }
+ return 0;
+}
+
+float ProximityInfoState::getPointToKeyLength(int inputIndex, int charCode, float scale) {
+ const int keyId = mProximityInfo->getKeyIndex(charCode);
+ if (keyId >= 0) {
+ const int index = inputIndex * mProximityInfo->getKeyCount() + keyId;
+ return min(mDistanceCache[index] * scale, mMaxPointToKeyLength);
+ }
+ return 0;
+}
+
+int ProximityInfoState::getKeyKeyDistance(int key0, int key1) {
+ return mProximityInfo->getKeyKeyDistanceG(key0, key1);
+}
+
+int ProximityInfoState::getSpaceY() {
+ const int keyId = mProximityInfo->getKeyIndex(' ');
+ return mProximityInfo->getKeyCenterYOfIdG(keyId);
+}
+
float ProximityInfoState::calculateSquaredDistanceFromSweetSpotCenter(
const int keyIndex, const int inputIndex) const {
const float sweetSpotCenterX = mProximityInfo->getSweetSpotCenterXAt(keyIndex);
const float sweetSpotCenterY = mProximityInfo->getSweetSpotCenterYAt(keyIndex);
- const float inputX = static_cast<float>(mInputXCoordinates[inputIndex]);
- const float inputY = static_cast<float>(mInputYCoordinates[inputIndex]);
+ const float inputX = static_cast<float>(mInputXs[inputIndex]);
+ const float inputY = static_cast<float>(mInputYs[inputIndex]);
return square(inputX - sweetSpotCenterX) + square(inputY - sweetSpotCenterY);
}
} // namespace latinime
diff --git a/native/jni/src/proximity_info_state.h b/native/jni/src/proximity_info_state.h
index 76d4551..26fd89b 100644
--- a/native/jni/src/proximity_info_state.h
+++ b/native/jni/src/proximity_info_state.h
@@ -17,8 +17,10 @@
#ifndef LATINIME_PROXIMITY_INFO_STATE_H
#define LATINIME_PROXIMITY_INFO_STATE_H
+#include <cstring> // for memset()
#include <stdint.h>
#include <string>
+#include <vector>
#include "char_utils.h"
#include "defines.h"
@@ -40,18 +42,27 @@
/////////////////////////////////////////
// Defined in proximity_info_state.cpp //
/////////////////////////////////////////
- void initInputParams(
- const ProximityInfo *proximityInfo, const int32_t *inputCodes, const int inputLength,
- const int *xCoordinates, const int *yCoordinates);
+ void initInputParams(const int pointerId, const float maxPointToKeyLength,
+ const ProximityInfo *proximityInfo, const int32_t *const inputCodes,
+ const int inputSize, const int *xCoordinates, const int *yCoordinates,
+ const int *const times, const int *const pointerIds, const bool isGeometric);
/////////////////////////////////////////
// Defined here //
/////////////////////////////////////////
- ProximityInfoState() {};
- inline const int *getProximityCharsAt(const int index) const {
- return mInputCodes + (index * MAX_PROXIMITY_CHARS_SIZE_INTERNAL);
+ ProximityInfoState()
+ : mProximityInfo(0), mMaxPointToKeyLength(0),
+ mHasTouchPositionCorrectionData(false), mMostCommonKeyWidthSquare(0), mLocaleStr(),
+ mKeyCount(0), mCellHeight(0), mCellWidth(0), mGridHeight(0), mGridWidth(0),
+ mInputXs(), mInputYs(), mTimes(), mDistanceCache(), mLengthCache(),
+ mTouchPositionCorrectionEnabled(false), mInputSize(0) {
+ memset(mInputCodes, 0, sizeof(mInputCodes));
+ memset(mNormalizedSquaredDistances, 0, sizeof(mNormalizedSquaredDistances));
+ memset(mPrimaryInputWord, 0, sizeof(mPrimaryInputWord));
}
+ virtual ~ProximityInfoState() {}
+
inline unsigned short getPrimaryCharAt(const int index) const {
return getProximityCharsAt(index)[0];
}
@@ -68,14 +79,14 @@
}
inline bool existsAdjacentProximityChars(const int index) const {
- if (index < 0 || index >= mInputLength) return false;
+ if (index < 0 || index >= mInputSize) return false;
const int currentChar = getPrimaryCharAt(index);
const int leftIndex = index - 1;
if (leftIndex >= 0 && existsCharInProximityAt(leftIndex, currentChar)) {
return true;
}
const int rightIndex = index + 1;
- if (rightIndex < mInputLength && existsCharInProximityAt(rightIndex, currentChar)) {
+ if (rightIndex < mInputSize && existsCharInProximityAt(rightIndex, currentChar)) {
return true;
}
return false;
@@ -160,6 +171,49 @@
return mTouchPositionCorrectionEnabled;
}
+ inline bool sameAsTyped(const unsigned short *word, int length) const {
+ if (length != mInputSize) {
+ return false;
+ }
+ const int *inputCodes = mInputCodes;
+ while (length--) {
+ if (static_cast<unsigned int>(*inputCodes) != static_cast<unsigned int>(*word)) {
+ return false;
+ }
+ inputCodes += MAX_PROXIMITY_CHARS_SIZE_INTERNAL;
+ word++;
+ }
+ return true;
+ }
+
+ int getDuration(const int index) const;
+
+ bool isUsed() const {
+ return mInputSize > 0;
+ }
+
+ uint32_t size() const {
+ return mInputSize;
+ }
+
+ int getInputX(int index) const {
+ return mInputXs[index];
+ }
+
+ int getInputY(int index) const {
+ return mInputYs[index];
+ }
+
+ int getLengthCache(int index) const {
+ return mLengthCache[index];
+ }
+
+ float getPointToKeyLength(int inputIndex, int charCode, float scale);
+
+ int getKeyKeyDistance(int key0, int key1);
+
+ int getSpaceY();
+
private:
DISALLOW_COPY_AND_ASSIGN(ProximityInfoState);
/////////////////////////////////////////
@@ -170,32 +224,23 @@
float calculateSquaredDistanceFromSweetSpotCenter(
const int keyIndex, const int inputIndex) const;
+ bool pushTouchPoint(const int nodeChar, int x, int y, const int time, const bool sample);
/////////////////////////////////////////
// Defined here //
/////////////////////////////////////////
inline float square(const float x) const { return x * x; }
bool hasInputCoordinates() const {
- return mInputXCoordinates && mInputYCoordinates;
+ return mInputXs.size() > 0 && mInputYs.size() > 0;
}
- bool sameAsTyped(const unsigned short *word, int length) const {
- if (length != mInputLength) {
- return false;
- }
- const int *inputCodes = mInputCodes;
- while (length--) {
- if ((unsigned int) *inputCodes != (unsigned int) *word) {
- return false;
- }
- inputCodes += MAX_PROXIMITY_CHARS_SIZE_INTERNAL;
- word++;
- }
- return true;
+ inline const int *getProximityCharsAt(const int index) const {
+ return mInputCodes + (index * MAX_PROXIMITY_CHARS_SIZE_INTERNAL);
}
// const
const ProximityInfo *mProximityInfo;
+ float mMaxPointToKeyLength;
bool mHasTouchPositionCorrectionData;
int mMostCommonKeyWidthSquare;
std::string mLocaleStr;
@@ -205,12 +250,15 @@
int mGridHeight;
int mGridWidth;
- const int *mInputXCoordinates;
- const int *mInputYCoordinates;
+ std::vector<int> mInputXs;
+ std::vector<int> mInputYs;
+ std::vector<int> mTimes;
+ std::vector<float> mDistanceCache;
+ std::vector<int> mLengthCache;
bool mTouchPositionCorrectionEnabled;
int32_t mInputCodes[MAX_PROXIMITY_CHARS_SIZE_INTERNAL * MAX_WORD_LENGTH_INTERNAL];
int mNormalizedSquaredDistances[MAX_PROXIMITY_CHARS_SIZE_INTERNAL * MAX_WORD_LENGTH_INTERNAL];
- int mInputLength;
+ int mInputSize;
unsigned short mPrimaryInputWord[MAX_WORD_LENGTH_INTERNAL];
};
} // namespace latinime
diff --git a/native/jni/src/terminal_attributes.h b/native/jni/src/terminal_attributes.h
index d633645..34ab8f0 100644
--- a/native/jni/src/terminal_attributes.h
+++ b/native/jni/src/terminal_attributes.h
@@ -30,13 +30,13 @@
public:
class ShortcutIterator {
const uint8_t *const mDict;
- bool mHasNextShortcutTarget;
int mPos;
+ bool mHasNextShortcutTarget;
public:
- ShortcutIterator(const uint8_t *dict, const int pos, const uint8_t flags) : mDict(dict),
- mPos(pos) {
- mHasNextShortcutTarget = (0 != (flags & BinaryFormat::FLAG_HAS_SHORTCUT_TARGETS));
+ ShortcutIterator(const uint8_t *dict, const int pos, const uint8_t flags)
+ : mDict(dict), mPos(pos),
+ mHasNextShortcutTarget(0 != (flags & BinaryFormat::FLAG_HAS_SHORTCUT_TARGETS)) {
}
inline bool hasNextShortcutTarget() const {
@@ -46,7 +46,7 @@
// Gets the shortcut target itself as a uint16_t string. For parameters and return value
// see BinaryFormat::getWordAtAddress.
// TODO: make the output an uint32_t* to handle the whole unicode range.
- inline int getNextShortcutTarget(const int maxDepth, uint16_t *outWord) {
+ inline int getNextShortcutTarget(const int maxDepth, uint16_t *outWord, int *outFreq) {
const int shortcutFlags = BinaryFormat::getFlagsAndForwardPointer(mDict, &mPos);
mHasNextShortcutTarget =
0 != (shortcutFlags & BinaryFormat::FLAG_ATTRIBUTE_HAS_NEXT);
@@ -56,18 +56,12 @@
if (NOT_A_CHARACTER == charCode) break;
outWord[i] = (uint16_t)charCode;
}
+ *outFreq = BinaryFormat::getAttributeFrequencyFromFlags(shortcutFlags);
mPos += BinaryFormat::CHARACTER_ARRAY_TERMINATOR_SIZE;
return i;
}
};
- private:
- DISALLOW_IMPLICIT_CONSTRUCTORS(TerminalAttributes);
- const uint8_t *const mDict;
- const uint8_t mFlags;
- const int mStartPos;
-
- public:
TerminalAttributes(const uint8_t *const dict, const uint8_t flags, const int pos) :
mDict(dict), mFlags(flags), mStartPos(pos) {
}
@@ -77,6 +71,12 @@
// skipped quickly, so we ignore it.
return ShortcutIterator(mDict, mStartPos + BinaryFormat::SHORTCUT_LIST_SIZE_SIZE, mFlags);
}
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(TerminalAttributes);
+ const uint8_t *const mDict;
+ const uint8_t mFlags;
+ const int mStartPos;
};
} // namespace latinime
#endif // LATINIME_TERMINAL_ATTRIBUTES_H
diff --git a/native/jni/src/unigram_dictionary.cpp b/native/jni/src/unigram_dictionary.cpp
index b6b0210..ba3c2db 100644
--- a/native/jni/src/unigram_dictionary.cpp
+++ b/native/jni/src/unigram_dictionary.cpp
@@ -63,8 +63,8 @@
// TODO: This needs to take a const unsigned short* and not tinker with its contents
static inline void addWord(
- unsigned short *word, int length, int frequency, WordsPriorityQueue *queue) {
- queue->push(frequency, word, length);
+ unsigned short *word, int length, int frequency, WordsPriorityQueue *queue, int type) {
+ queue->push(frequency, word, length, type);
}
// Return the replacement code point for a digraph, or 0 if none.
@@ -213,8 +213,8 @@
AKLOGI("Max normalized score = %f", ns);
}
const int suggestedWordsCount =
- queuePool.getMasterQueue()->outputSuggestions(
- masterCorrection.getPrimaryInputWord(), codesSize, frequencies, outWords);
+ queuePool.getMasterQueue()->outputSuggestions(masterCorrection.getPrimaryInputWord(),
+ codesSize, frequencies, outWords, outputTypes);
if (DEBUG_DICT) {
float ns = queuePool.getMasterQueue()->getHighestNormalizedScore(
@@ -237,7 +237,7 @@
void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo,
const int *xcoordinates, const int *ycoordinates, const int *codes,
- const int inputLength, const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
+ const int inputSize, const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
const bool useFullEditDistance, Correction *correction,
WordsPriorityQueuePool *queuePool) const {
@@ -247,7 +247,7 @@
PROF_START(1);
getOneWordSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, bigramMap, bigramFilter,
- useFullEditDistance, inputLength, correction, queuePool);
+ useFullEditDistance, inputSize, correction, queuePool);
PROF_END(1);
PROF_START(2);
@@ -263,7 +263,7 @@
WordsPriorityQueue *masterQueue = queuePool->getMasterQueue();
if (masterQueue->size() > 0) {
float nsForMaster = masterQueue->getHighestNormalizedScore(
- correction->getPrimaryInputWord(), inputLength, 0, 0, 0);
+ correction->getPrimaryInputWord(), inputSize, 0, 0, 0);
hasAutoCorrectionCandidate = (nsForMaster > START_TWO_WORDS_CORRECTION_THRESHOLD);
}
PROF_END(4);
@@ -271,9 +271,9 @@
PROF_START(5);
// Multiple word suggestions
if (SUGGEST_MULTIPLE_WORDS
- && inputLength >= MIN_USER_TYPED_LENGTH_FOR_MULTIPLE_WORD_SUGGESTION) {
+ && inputSize >= MIN_USER_TYPED_LENGTH_FOR_MULTIPLE_WORD_SUGGESTION) {
getSplitMultipleWordsSuggestions(proximityInfo, xcoordinates, ycoordinates, codes,
- useFullEditDistance, inputLength, correction, queuePool,
+ useFullEditDistance, inputSize, correction, queuePool,
hasAutoCorrectionCandidate);
}
PROF_END(5);
@@ -304,15 +304,15 @@
}
void UnigramDictionary::initSuggestions(ProximityInfo *proximityInfo, const int *xCoordinates,
- const int *yCoordinates, const int *codes, const int inputLength,
+ const int *yCoordinates, const int *codes, const int inputSize,
Correction *correction) const {
if (DEBUG_DICT) {
AKLOGI("initSuggest");
- DUMP_WORD_INT(codes, inputLength);
+ DUMP_WORD_INT(codes, inputSize);
}
- correction->initInputParams(proximityInfo, codes, inputLength, xCoordinates, yCoordinates);
- const int maxDepth = min(inputLength * MAX_DEPTH_MULTIPLIER, MAX_WORD_LENGTH);
- correction->initCorrection(proximityInfo, inputLength, maxDepth);
+ correction->initInputParams(proximityInfo, codes, inputSize, xCoordinates, yCoordinates);
+ const int maxDepth = min(inputSize * MAX_DEPTH_MULTIPLIER, MAX_WORD_LENGTH);
+ correction->initCorrection(proximityInfo, inputSize, maxDepth);
}
static const char QUOTE = '\'';
@@ -321,15 +321,15 @@
void UnigramDictionary::getOneWordSuggestions(ProximityInfo *proximityInfo,
const int *xcoordinates, const int *ycoordinates, const int *codes,
const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
- const bool useFullEditDistance, const int inputLength,
+ const bool useFullEditDistance, const int inputSize,
Correction *correction, WordsPriorityQueuePool *queuePool) const {
- initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, inputLength, correction);
- getSuggestionCandidates(useFullEditDistance, inputLength, bigramMap, bigramFilter, correction,
+ initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, inputSize, correction);
+ getSuggestionCandidates(useFullEditDistance, inputSize, bigramMap, bigramFilter, correction,
queuePool, true /* doAutoCompletion */, DEFAULT_MAX_ERRORS, FIRST_WORD_INDEX);
}
void UnigramDictionary::getSuggestionCandidates(const bool useFullEditDistance,
- const int inputLength, const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
+ const int inputSize, const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
Correction *correction, WordsPriorityQueuePool *queuePool,
const bool doAutoCompletion, const int maxErrors, const int currentWordIndex) const {
uint8_t totalTraverseCount = correction->pushAndGetTotalTraverseCount();
@@ -351,7 +351,7 @@
int childCount = BinaryFormat::getGroupCountAndForwardPointer(DICT_ROOT, &rootPosition);
int outputIndex = 0;
- correction->initCorrectionState(rootPosition, childCount, (inputLength <= 0));
+ correction->initCorrectionState(rootPosition, childCount, (inputSize <= 0));
// Depth first search
while (outputIndex >= 0) {
@@ -390,27 +390,42 @@
WordsPriorityQueue *masterQueue = queuePool->getMasterQueue();
const int finalProbability =
correction->getFinalProbability(probability, &wordPointer, &wordLength);
- if (finalProbability != NOT_A_PROBABILITY) {
- addWord(wordPointer, wordLength, finalProbability, masterQueue);
- const int shortcutProbability = finalProbability > 0 ? finalProbability - 1 : 0;
- // Please note that the shortcut candidates will be added to the master queue only.
- TerminalAttributes::ShortcutIterator iterator =
- terminalAttributes.getShortcutIterator();
- while (iterator.hasNextShortcutTarget()) {
- // TODO: addWord only supports weak ordering, meaning we have no means
- // to control the order of the shortcuts relative to one another or to the word.
- // We need to either modulate the probability of each shortcut according
- // to its own shortcut probability or to make the queue
- // so that the insert order is protected inside the queue for words
- // with the same score. For the moment we use -1 to make sure the shortcut will
- // never be in front of the word.
- uint16_t shortcutTarget[MAX_WORD_LENGTH_INTERNAL];
- const int shortcutTargetStringLength = iterator.getNextShortcutTarget(
- MAX_WORD_LENGTH_INTERNAL, shortcutTarget);
- addWord(shortcutTarget, shortcutTargetStringLength, shortcutProbability,
- masterQueue);
+ if (0 != finalProbability) {
+ // If the probability is 0, we don't want to add this word. However we still
+ // want to add its shortcuts (including a possible whitelist entry) if any.
+ addWord(wordPointer, wordLength, finalProbability, masterQueue,
+ Dictionary::KIND_CORRECTION);
+ }
+
+ const int shortcutProbability = finalProbability > 0 ? finalProbability - 1 : 0;
+ // Please note that the shortcut candidates will be added to the master queue only.
+ TerminalAttributes::ShortcutIterator iterator =
+ terminalAttributes.getShortcutIterator();
+ while (iterator.hasNextShortcutTarget()) {
+ // TODO: addWord only supports weak ordering, meaning we have no means
+ // to control the order of the shortcuts relative to one another or to the word.
+ // We need to either modulate the probability of each shortcut according
+ // to its own shortcut probability or to make the queue
+ // so that the insert order is protected inside the queue for words
+ // with the same score. For the moment we use -1 to make sure the shortcut will
+ // never be in front of the word.
+ uint16_t shortcutTarget[MAX_WORD_LENGTH_INTERNAL];
+ int shortcutFrequency;
+ const int shortcutTargetStringLength = iterator.getNextShortcutTarget(
+ MAX_WORD_LENGTH_INTERNAL, shortcutTarget, &shortcutFrequency);
+ int shortcutScore;
+ int kind;
+ if (shortcutFrequency == BinaryFormat::WHITELIST_SHORTCUT_FREQUENCY
+ && correction->sameAsTyped()) {
+ shortcutScore = S_INT_MAX;
+ kind = Dictionary::KIND_WHITELIST;
+ } else {
+ shortcutScore = shortcutProbability;
+ kind = Dictionary::KIND_CORRECTION;
}
+ addWord(shortcutTarget, shortcutTargetStringLength, shortcutScore,
+ masterQueue, kind);
}
}
@@ -424,14 +439,14 @@
}
const int finalProbability = correction->getFinalProbabilityForSubQueue(
probability, &wordPointer, &wordLength, inputIndex);
- addWord(wordPointer, wordLength, finalProbability, subQueue);
+ addWord(wordPointer, wordLength, finalProbability, subQueue, Dictionary::KIND_CORRECTION);
}
}
int UnigramDictionary::getSubStringSuggestion(
ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates,
const int *codes, const bool useFullEditDistance, Correction *correction,
- WordsPriorityQueuePool *queuePool, const int inputLength,
+ WordsPriorityQueuePool *queuePool, const int inputSize,
const bool hasAutoCorrectionCandidate, const int currentWordIndex,
const int inputWordStartPos, const int inputWordLength,
const int outputWordStartPos, const bool isSpaceProximity, int *freqArray,
@@ -482,7 +497,7 @@
int nextWordLength = 0;
// TODO: Optimize init suggestion
initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes,
- inputLength, correction);
+ inputSize, correction);
unsigned short word[MAX_WORD_LENGTH_INTERNAL];
int freq = getMostFrequentWordLike(
@@ -551,7 +566,7 @@
*outputWordLength = tempOutputWordLength;
}
- if ((inputWordStartPos + inputWordLength) < inputLength) {
+ if ((inputWordStartPos + inputWordLength) < inputSize) {
if (outputWordStartPos + nextWordLength >= MAX_WORD_LENGTH) {
return FLAG_MULTIPLE_SUGGEST_SKIP;
}
@@ -570,16 +585,17 @@
freqArray[i], wordLengthArray[i]);
}
AKLOGI("Split two words: freq = %d, length = %d, %d, isSpace ? %d", pairFreq,
- inputLength, tempOutputWordLength, isSpaceProximity);
+ inputSize, tempOutputWordLength, isSpaceProximity);
}
- addWord(outputWord, tempOutputWordLength, pairFreq, queuePool->getMasterQueue());
+ addWord(outputWord, tempOutputWordLength, pairFreq, queuePool->getMasterQueue(),
+ Dictionary::KIND_CORRECTION);
}
return FLAG_MULTIPLE_SUGGEST_CONTINUE;
}
void UnigramDictionary::getMultiWordsSuggestionRec(ProximityInfo *proximityInfo,
const int *xcoordinates, const int *ycoordinates, const int *codes,
- const bool useFullEditDistance, const int inputLength,
+ const bool useFullEditDistance, const int inputSize,
Correction *correction, WordsPriorityQueuePool *queuePool,
const bool hasAutoCorrectionCandidate, const int startInputPos, const int startWordIndex,
const int outputWordLength, int *freqArray, int *wordLengthArray,
@@ -590,11 +606,11 @@
}
if (startWordIndex >= 1
&& (hasAutoCorrectionCandidate
- || inputLength < MIN_INPUT_LENGTH_FOR_THREE_OR_MORE_WORDS_CORRECTION)) {
+ || inputSize < MIN_INPUT_LENGTH_FOR_THREE_OR_MORE_WORDS_CORRECTION)) {
// Do not suggest 3+ words if already has auto correction candidate
return;
}
- for (int i = startInputPos + 1; i < inputLength; ++i) {
+ for (int i = startInputPos + 1; i < inputSize; ++i) {
if (DEBUG_CORRECTION_FREQ) {
AKLOGI("Multi words(%d), start in %d sep %d start out %d",
startWordIndex, startInputPos, i, outputWordLength);
@@ -605,7 +621,7 @@
int inputWordStartPos = startInputPos;
int inputWordLength = i - startInputPos;
const int suggestionFlag = getSubStringSuggestion(proximityInfo, xcoordinates, ycoordinates,
- codes, useFullEditDistance, correction, queuePool, inputLength,
+ codes, useFullEditDistance, correction, queuePool, inputSize,
hasAutoCorrectionCandidate, startWordIndex, inputWordStartPos, inputWordLength,
outputWordLength, true /* not used */, freqArray, wordLengthArray, outputWord,
&tempOutputWordLength);
@@ -622,14 +638,14 @@
// Next word
// Missing space
inputWordStartPos = i;
- inputWordLength = inputLength - i;
+ inputWordLength = inputSize - i;
if(getSubStringSuggestion(proximityInfo, xcoordinates, ycoordinates, codes,
- useFullEditDistance, correction, queuePool, inputLength, hasAutoCorrectionCandidate,
+ useFullEditDistance, correction, queuePool, inputSize, hasAutoCorrectionCandidate,
startWordIndex + 1, inputWordStartPos, inputWordLength, tempOutputWordLength,
false /* missing space */, freqArray, wordLengthArray, outputWord, 0)
!= FLAG_MULTIPLE_SUGGEST_CONTINUE) {
getMultiWordsSuggestionRec(proximityInfo, xcoordinates, ycoordinates, codes,
- useFullEditDistance, inputLength, correction, queuePool,
+ useFullEditDistance, inputSize, correction, queuePool,
hasAutoCorrectionCandidate, inputWordStartPos, startWordIndex + 1,
tempOutputWordLength, freqArray, wordLengthArray, outputWord);
}
@@ -652,7 +668,7 @@
AKLOGI("Do mistyped space correction");
}
getSubStringSuggestion(proximityInfo, xcoordinates, ycoordinates, codes,
- useFullEditDistance, correction, queuePool, inputLength, hasAutoCorrectionCandidate,
+ useFullEditDistance, correction, queuePool, inputSize, hasAutoCorrectionCandidate,
startWordIndex + 1, inputWordStartPos, inputWordLength, tempOutputWordLength,
true /* mistyped space */, freqArray, wordLengthArray, outputWord, 0);
}
@@ -660,10 +676,10 @@
void UnigramDictionary::getSplitMultipleWordsSuggestions(ProximityInfo *proximityInfo,
const int *xcoordinates, const int *ycoordinates, const int *codes,
- const bool useFullEditDistance, const int inputLength,
+ const bool useFullEditDistance, const int inputSize,
Correction *correction, WordsPriorityQueuePool *queuePool,
const bool hasAutoCorrectionCandidate) const {
- if (inputLength >= MAX_WORD_LENGTH) return;
+ if (inputSize >= MAX_WORD_LENGTH) return;
if (DEBUG_DICT) {
AKLOGI("--- Suggest multiple words");
}
@@ -676,7 +692,7 @@
const int startInputPos = 0;
const int startWordIndex = 0;
getMultiWordsSuggestionRec(proximityInfo, xcoordinates, ycoordinates, codes,
- useFullEditDistance, inputLength, correction, queuePool, hasAutoCorrectionCandidate,
+ useFullEditDistance, inputSize, correction, queuePool, hasAutoCorrectionCandidate,
startInputPos, startWordIndex, outputWordLength, freqArray, wordLengthArray,
outputWord);
}
@@ -684,13 +700,13 @@
// Wrapper for getMostFrequentWordLikeInner, which matches it to the previous
// interface.
inline int UnigramDictionary::getMostFrequentWordLike(const int startInputIndex,
- const int inputLength, Correction *correction, unsigned short *word) const {
- uint16_t inWord[inputLength];
+ const int inputSize, Correction *correction, unsigned short *word) const {
+ uint16_t inWord[inputSize];
- for (int i = 0; i < inputLength; ++i) {
+ for (int i = 0; i < inputSize; ++i) {
inWord[i] = (uint16_t)correction->getPrimaryCharAt(startInputIndex + i);
}
- return getMostFrequentWordLikeInner(inWord, inputLength, word);
+ return getMostFrequentWordLikeInner(inWord, inputSize, word);
}
// This function will take the position of a character array within a CharGroup,
diff --git a/native/jni/src/unigram_dictionary.h b/native/jni/src/unigram_dictionary.h
index 6083f01..2c66222 100644
--- a/native/jni/src/unigram_dictionary.h
+++ b/native/jni/src/unigram_dictionary.h
@@ -53,7 +53,7 @@
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(UnigramDictionary);
void getWordSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
- const int *ycoordinates, const int *codes, const int inputLength,
+ const int *ycoordinates, const int *codes, const int inputSize,
const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
const bool useFullEditDistance, Correction *correction,
WordsPriorityQueuePool *queuePool) const;
@@ -72,16 +72,16 @@
Correction *correction) const;
void getOneWordSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
const int *ycoordinates, const int *codes, const std::map<int, int> *bigramMap,
- const uint8_t *bigramFilter, const bool useFullEditDistance, const int inputLength,
+ const uint8_t *bigramFilter, const bool useFullEditDistance, const int inputSize,
Correction *correction, WordsPriorityQueuePool *queuePool) const;
void getSuggestionCandidates(
- const bool useFullEditDistance, const int inputLength,
+ const bool useFullEditDistance, const int inputSize,
const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
Correction *correction, WordsPriorityQueuePool *queuePool, const bool doAutoCompletion,
const int maxErrors, const int currentWordIndex) const;
void getSplitMultipleWordsSuggestions(ProximityInfo *proximityInfo,
const int *xcoordinates, const int *ycoordinates, const int *codes,
- const bool useFullEditDistance, const int inputLength,
+ const bool useFullEditDistance, const int inputSize,
Correction *correction, WordsPriorityQueuePool *queuePool,
const bool hasAutoCorrectionCandidate) const;
void onTerminal(const int freq, const TerminalAttributes& terminalAttributes,
@@ -92,21 +92,21 @@
const uint8_t *bigramFilter, Correction *correction, int *newCount,
int *newChildPosition, int *nextSiblingPosition, WordsPriorityQueuePool *queuePool,
const int currentWordIndex) const;
- int getMostFrequentWordLike(const int startInputIndex, const int inputLength,
+ int getMostFrequentWordLike(const int startInputIndex, const int inputSize,
Correction *correction, unsigned short *word) const;
int getMostFrequentWordLikeInner(const uint16_t *const inWord, const int length,
short unsigned int *outWord) const;
int getSubStringSuggestion(
ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates,
const int *codes, const bool useFullEditDistance, Correction *correction,
- WordsPriorityQueuePool *queuePool, const int inputLength,
+ WordsPriorityQueuePool *queuePool, const int inputSize,
const bool hasAutoCorrectionCandidate, const int currentWordIndex,
const int inputWordStartPos, const int inputWordLength,
const int outputWordStartPos, const bool isSpaceProximity, int *freqArray,
int *wordLengthArray, unsigned short *outputWord, int *outputWordLength) const;
void getMultiWordsSuggestionRec(ProximityInfo *proximityInfo,
const int *xcoordinates, const int *ycoordinates, const int *codes,
- const bool useFullEditDistance, const int inputLength,
+ const bool useFullEditDistance, const int inputSize,
Correction *correction, WordsPriorityQueuePool *queuePool,
const bool hasAutoCorrectionCandidate, const int startPos, const int startWordIndex,
const int outputWordLength, int *freqArray, int *wordLengthArray,
diff --git a/native/jni/src/words_priority_queue.h b/native/jni/src/words_priority_queue.h
index c0dedb5..19efa5d 100644
--- a/native/jni/src/words_priority_queue.h
+++ b/native/jni/src/words_priority_queue.h
@@ -33,30 +33,31 @@
unsigned short mWord[MAX_WORD_LENGTH_INTERNAL];
int mWordLength;
bool mUsed;
+ int mType;
- void setParams(int score, unsigned short *word, int wordLength) {
+ void setParams(int score, unsigned short *word, int wordLength, int type) {
mScore = score;
mWordLength = wordLength;
memcpy(mWord, word, sizeof(unsigned short) * wordLength);
mUsed = true;
+ mType = type;
}
};
- WordsPriorityQueue(int maxWords, int maxWordLength) :
- MAX_WORDS((unsigned int) maxWords), MAX_WORD_LENGTH(
- (unsigned int) maxWordLength) {
- mSuggestedWords = new SuggestedWord[maxWordLength];
+ WordsPriorityQueue(int maxWords, int maxWordLength)
+ : mSuggestions(), MAX_WORDS(static_cast<unsigned int>(maxWords)),
+ MAX_WORD_LENGTH(static_cast<unsigned int>(maxWordLength)),
+ mSuggestedWords(new SuggestedWord[maxWordLength]), mHighestSuggestedWord(0) {
for (int i = 0; i < maxWordLength; ++i) {
mSuggestedWords[i].mUsed = false;
}
- mHighestSuggestedWord = 0;
}
- ~WordsPriorityQueue() {
+ virtual ~WordsPriorityQueue() {
delete[] mSuggestedWords;
}
- void push(int score, unsigned short *word, int wordLength) {
+ void push(int score, unsigned short *word, int wordLength, int type) {
SuggestedWord *sw = 0;
if (mSuggestions.size() >= MAX_WORDS) {
sw = mSuggestions.top();
@@ -69,9 +70,9 @@
}
}
if (sw == 0) {
- sw = getFreeSuggestedWord(score, word, wordLength);
+ sw = getFreeSuggestedWord(score, word, wordLength, type);
} else {
- sw->setParams(score, word, wordLength);
+ sw->setParams(score, word, wordLength, type);
}
if (sw == 0) {
AKLOGE("SuggestedWord is accidentally null.");
@@ -94,7 +95,7 @@
}
int outputSuggestions(const unsigned short *before, const int beforeLength,
- int *frequencies, unsigned short *outputChars) {
+ int *frequencies, unsigned short *outputChars, int* outputTypes) {
mHighestSuggestedWord = 0;
const unsigned int size = min(
MAX_WORDS, static_cast<unsigned int>(mSuggestions.size()));
@@ -127,7 +128,7 @@
}
}
if (maxIndex > 0 && nsMaxSw) {
- memmove(&swBuffer[1], &swBuffer[0], maxIndex * sizeof(SuggestedWord*));
+ memmove(&swBuffer[1], &swBuffer[0], maxIndex * sizeof(SuggestedWord *));
swBuffer[0] = nsMaxSw;
}
}
@@ -138,11 +139,12 @@
continue;
}
const unsigned int wordLength = sw->mWordLength;
- char *targetAdr = (char*) outputChars + i * MAX_WORD_LENGTH * sizeof(short);
+ unsigned short *targetAddress = outputChars + i * MAX_WORD_LENGTH;
frequencies[i] = sw->mScore;
- memcpy(targetAdr, sw->mWord, (wordLength) * sizeof(short));
+ outputTypes[i] = sw->mType;
+ memcpy(targetAddress, sw->mWord, wordLength * sizeof(unsigned short));
if (wordLength < MAX_WORD_LENGTH) {
- ((unsigned short*) targetAdr)[wordLength] = 0;
+ targetAddress[wordLength] = 0;
}
sw->mUsed = false;
}
@@ -191,10 +193,10 @@
};
SuggestedWord *getFreeSuggestedWord(int score, unsigned short *word,
- int wordLength) {
+ int wordLength, int type) {
for (unsigned int i = 0; i < MAX_WORD_LENGTH; ++i) {
if (!mSuggestedWords[i].mUsed) {
- mSuggestedWords[i].setParams(score, word, wordLength);
+ mSuggestedWords[i].setParams(score, word, wordLength, type);
return &mSuggestedWords[i];
}
}
@@ -219,7 +221,7 @@
before, beforeLength, word, wordLength, score);
}
- typedef std::priority_queue<SuggestedWord*, std::vector<SuggestedWord*>,
+ typedef std::priority_queue<SuggestedWord *, std::vector<SuggestedWord *>,
wordComparator> Suggestions;
Suggestions mSuggestions;
const unsigned int MAX_WORDS;
diff --git a/native/jni/src/words_priority_queue_pool.h b/native/jni/src/words_priority_queue_pool.h
index 3888729..c5de979 100644
--- a/native/jni/src/words_priority_queue_pool.h
+++ b/native/jni/src/words_priority_queue_pool.h
@@ -24,9 +24,10 @@
class WordsPriorityQueuePool {
public:
- WordsPriorityQueuePool(int mainQueueMaxWords, int subQueueMaxWords, int maxWordLength) {
- // Note: using placement new() requires the caller to call the destructor explicitly.
- mMasterQueue = new(mMasterQueueBuf) WordsPriorityQueue(mainQueueMaxWords, maxWordLength);
+ WordsPriorityQueuePool(int mainQueueMaxWords, int subQueueMaxWords, int maxWordLength)
+ // Note: using placement new() requires the caller to call the destructor explicitly.
+ : mMasterQueue(new(mMasterQueueBuf) WordsPriorityQueue(
+ mainQueueMaxWords, maxWordLength)) {
for (int i = 0, subQueueBufOffset = 0;
i < MULTIPLE_WORDS_SUGGESTION_MAX_WORDS * SUB_QUEUE_MAX_COUNT;
++i, subQueueBufOffset += sizeof(WordsPriorityQueue)) {
@@ -85,11 +86,11 @@
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(WordsPriorityQueuePool);
+ char mMasterQueueBuf[sizeof(WordsPriorityQueue)];
+ char mSubQueueBuf[SUB_QUEUE_MAX_COUNT * MULTIPLE_WORDS_SUGGESTION_MAX_WORDS
+ * sizeof(WordsPriorityQueue)];
WordsPriorityQueue *mMasterQueue;
WordsPriorityQueue *mSubQueues[SUB_QUEUE_MAX_COUNT * MULTIPLE_WORDS_SUGGESTION_MAX_WORDS];
- char mMasterQueueBuf[sizeof(WordsPriorityQueue)];
- char mSubQueueBuf[MULTIPLE_WORDS_SUGGESTION_MAX_WORDS
- * SUB_QUEUE_MAX_COUNT * sizeof(WordsPriorityQueue)];
};
} // namespace latinime
#endif // LATINIME_WORDS_PRIORITY_QUEUE_POOL_H
diff --git a/tests/src/com/android/inputmethod/keyboard/SpacebarTextTests.java b/tests/src/com/android/inputmethod/keyboard/SpacebarTextTests.java
index 87501ee..bc50439 100644
--- a/tests/src/com/android/inputmethod/keyboard/SpacebarTextTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/SpacebarTextTests.java
@@ -22,6 +22,7 @@
import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.latin.AdditionalSubtype;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.ImfUtils;
import com.android.inputmethod.latin.StringUtils;
import com.android.inputmethod.latin.SubtypeLocale;
@@ -31,7 +32,7 @@
public class SpacebarTextTests extends AndroidTestCase {
// Locale to subtypes list.
- private final ArrayList<InputMethodSubtype> mSubtypesList = new ArrayList<InputMethodSubtype>();
+ private final ArrayList<InputMethodSubtype> mSubtypesList = CollectionUtils.newArrayList();
private Resources mRes;
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java
index 3dc4543..1346c00 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java
@@ -19,6 +19,8 @@
import android.app.Instrumentation;
import android.test.InstrumentationTestCase;
+import com.android.inputmethod.latin.CollectionUtils;
+
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
@@ -42,7 +44,7 @@
}
private static String[] getAllResourceIdNames(final Class<?> resourceIdClass) {
- final ArrayList<String> names = new ArrayList<String>();
+ final ArrayList<String> names = CollectionUtils.newArrayList();
for (final Field field : resourceIdClass.getFields()) {
if (field.getType() == Integer.TYPE) {
names.add(field.getName());
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueueTests.java b/tests/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueueTests.java
index 99fbc96..8fed28f 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueueTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueueTests.java
@@ -19,7 +19,7 @@
import android.test.AndroidTestCase;
public class PointerTrackerQueueTests extends AndroidTestCase {
- public static class Element implements PointerTrackerQueue.ElementActions {
+ public static class Element implements PointerTrackerQueue.Element {
public static int sPhantomUpCount;
public static final long NOT_HAPPENED = -1;
diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictIOTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictIOTests.java
new file mode 100644
index 0000000..0094db8
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/BinaryDictIOTests.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
+import com.android.inputmethod.latin.makedict.FusionDictionary;
+import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup;
+import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+
+/**
+ * Unit tests for BinaryDictInputOutput
+ */
+public class BinaryDictIOTests extends AndroidTestCase {
+ private static final String TAG = BinaryDictIOTests.class.getSimpleName();
+ private static final int MAX_UNIGRAMS = 1000;
+ private static final int UNIGRAM_FREQ = 10;
+ private static final int BIGRAM_FREQ = 50;
+
+ private static final String[] CHARACTERS =
+ {
+ "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
+ "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"
+ };
+
+ /**
+ * Generates a random word.
+ */
+ private String generateWord(final int value) {
+ final int lengthOfChars = CHARACTERS.length;
+ StringBuilder builder = new StringBuilder("a");
+ long lvalue = Math.abs((long)value);
+ while (lvalue > 0) {
+ builder.append(CHARACTERS[(int)(lvalue % lengthOfChars)]);
+ lvalue /= lengthOfChars;
+ }
+ return builder.toString();
+ }
+
+ private List<String> generateWords(final int number, final Random random) {
+ final Set<String> wordSet = CollectionUtils.newHashSet();
+ while (wordSet.size() < number) {
+ wordSet.add(generateWord(random.nextInt()));
+ }
+ return new ArrayList<String>(wordSet);
+ }
+
+ private void addUnigrams(final int number,
+ final FusionDictionary dict,
+ final List<String> words) {
+ for (int i = 0; i < number; ++i) {
+ final String word = words.get(i);
+ dict.add(word, UNIGRAM_FREQ, null);
+ }
+ }
+
+ private void addBigrams(final FusionDictionary dict,
+ final List<String> words,
+ final SparseArray<List<Integer>> sparseArray) {
+ for (int i = 0; i < sparseArray.size(); ++i) {
+ final int w1 = sparseArray.keyAt(i);
+ for (int w2 : sparseArray.valueAt(i)) {
+ dict.setBigram(words.get(w1), words.get(w2), BIGRAM_FREQ);
+ }
+ }
+ }
+
+ private long timeWritingDictToFile(final String fileName,
+ final FusionDictionary dict) {
+
+ final File file = new File(getContext().getFilesDir(), fileName);
+ long now = -1, diff = -1;
+
+ try {
+ final FileOutputStream out = new FileOutputStream(file);
+
+ now = System.currentTimeMillis();
+ BinaryDictInputOutput.writeDictionaryBinary(out, dict, 2);
+ diff = System.currentTimeMillis() - now;
+
+ out.flush();
+ out.close();
+ } catch (IOException e) {
+ Log.e(TAG, "IO exception while writing file: " + e);
+ } catch (UnsupportedFormatException e) {
+ Log.e(TAG, "UnsupportedFormatException: " + e);
+ }
+
+ return diff;
+ }
+
+ private void checkDictionary(final FusionDictionary dict,
+ final List<String> words,
+ final SparseArray<List<Integer>> bigrams) {
+ assertNotNull(dict);
+
+ // check unigram
+ for (final String word : words) {
+ final CharGroup cg = FusionDictionary.findWordInTree(dict.mRoot, word);
+ assertNotNull(cg);
+ }
+
+ // check bigram
+ for (int i = 0; i < bigrams.size(); ++i) {
+ final int w1 = bigrams.keyAt(i);
+ for (final int w2 : bigrams.valueAt(i)) {
+ final CharGroup cg = FusionDictionary.findWordInTree(dict.mRoot, words.get(w1));
+ assertNotNull(words.get(w1) + "," + words.get(w2), cg.getBigram(words.get(w2)));
+ }
+ }
+ }
+
+ private long timeReadingAndCheckDict(final String fileName,
+ final List<String> words,
+ final SparseArray<List<Integer>> bigrams) {
+
+ long now, diff = -1;
+
+ try {
+ final File file = new File(getContext().getFilesDir(), fileName);
+ final FileInputStream inStream = new FileInputStream(file);
+ final ByteBuffer buffer = inStream.getChannel().map(
+ FileChannel.MapMode.READ_ONLY, 0, file.length());
+
+ now = System.currentTimeMillis();
+
+ final FusionDictionary dict =
+ BinaryDictInputOutput.readDictionaryBinary(buffer, null);
+
+ diff = System.currentTimeMillis() - now;
+
+ checkDictionary(dict, words, bigrams);
+ return diff;
+
+ } catch (IOException e) {
+ Log.e(TAG, "raise IOException while reading file " + e);
+ } catch (UnsupportedFormatException e) {
+ Log.e(TAG, "Unsupported format: " + e);
+ }
+
+ return diff;
+ }
+
+ private String runReadAndWrite(final List<String> words,
+ final SparseArray<List<Integer>> bigrams,
+ final String message) {
+ final FusionDictionary dict = new FusionDictionary(new Node(),
+ new FusionDictionary.DictionaryOptions(
+ new HashMap<String,String>(), false, false));
+
+ final String fileName = generateWord((int)System.currentTimeMillis()) + ".dict";
+
+ addUnigrams(words.size(), dict, words);
+ addBigrams(dict, words, bigrams);
+ // check original dictionary
+ checkDictionary(dict, words, bigrams);
+
+ final long write = timeWritingDictToFile(fileName, dict);
+ final long read = timeReadingAndCheckDict(fileName, words, bigrams);
+ deleteFile(fileName);
+
+ return "PROF: read=" + read + "ms, write=" + write + "ms :" + message;
+ }
+
+ private void deleteFile(final String fileName) {
+ final File file = new File(getContext().getFilesDir(), fileName);
+ file.delete();
+ }
+
+ public void testReadAndWrite() {
+ final List<String> results = new ArrayList<String>();
+
+ final Random random = new Random(123456);
+ final List<String> words = generateWords(MAX_UNIGRAMS, random);
+ final SparseArray<List<Integer>> emptyArray = CollectionUtils.newSparseArray();
+
+ final SparseArray<List<Integer>> chain = CollectionUtils.newSparseArray();
+ for (int i = 0; i < words.size(); ++i) chain.put(i, new ArrayList<Integer>());
+ for (int i = 1; i < words.size(); ++i) chain.get(i-1).add(i);
+
+ final SparseArray<List<Integer>> star = CollectionUtils.newSparseArray();
+ final List<Integer> list0 = CollectionUtils.newArrayList();
+ star.put(0, list0);
+ for (int i = 1; i < words.size(); ++i) star.get(0).add(i);
+
+ results.add(runReadAndWrite(words, emptyArray, "only unigram"));
+ results.add(runReadAndWrite(words, chain, "chain"));
+ results.add(runReadAndWrite(words, star, "star"));
+
+ for (final String result : results) {
+ Log.d(TAG, result);
+ }
+ }
+}
diff --git a/tests/src/com/android/inputmethod/latin/FusionDictionaryTests.java b/tests/src/com/android/inputmethod/latin/FusionDictionaryTests.java
new file mode 100644
index 0000000..8ecdcc3
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/FusionDictionaryTests.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.test.AndroidTestCase;
+
+import com.android.inputmethod.latin.makedict.FusionDictionary;
+import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
+
+import java.util.HashMap;
+
+/**
+ * Unit test for FusionDictionary
+ */
+public class FusionDictionaryTests extends AndroidTestCase {
+ public void testFindWordInTree() {
+ FusionDictionary dict = new FusionDictionary(new Node(),
+ new FusionDictionary.DictionaryOptions(new HashMap<String,String>(), false, false));
+
+ dict.add("abc", 10, null);
+ assertNull(FusionDictionary.findWordInTree(dict.mRoot, "aaa"));
+ assertNotNull(FusionDictionary.findWordInTree(dict.mRoot, "abc"));
+
+ dict.add("aa", 10, null);
+ assertNull(FusionDictionary.findWordInTree(dict.mRoot, "aaa"));
+ assertNotNull(FusionDictionary.findWordInTree(dict.mRoot, "aa"));
+
+ dict.add("babcd", 10, null);
+ dict.add("bacde", 10, null);
+ assertNull(FusionDictionary.findWordInTree(dict.mRoot, "ba"));
+ assertNotNull(FusionDictionary.findWordInTree(dict.mRoot, "babcd"));
+ assertNotNull(FusionDictionary.findWordInTree(dict.mRoot, "bacde"));
+ }
+}
diff --git a/tests/src/com/android/inputmethod/latin/InputPointersTests.java b/tests/src/com/android/inputmethod/latin/InputPointersTests.java
index 6f04f3e..cc55076 100644
--- a/tests/src/com/android/inputmethod/latin/InputPointersTests.java
+++ b/tests/src/com/android/inputmethod/latin/InputPointersTests.java
@@ -18,6 +18,8 @@
import android.test.AndroidTestCase;
+import java.util.Arrays;
+
public class InputPointersTests extends AndroidTestCase {
private static final int DEFAULT_CAPACITY = 48;
@@ -162,6 +164,61 @@
src.getTimes(), 0, dst.getTimes(), dstLen, srcLen);
}
+ public void testAppendResizableIntArray() {
+ final int srcLen = 100;
+ final int srcPointerId = 1;
+ final int[] srcPointerIds = new int[srcLen];
+ Arrays.fill(srcPointerIds, srcPointerId);
+ final ResizableIntArray srcTimes = new ResizableIntArray(DEFAULT_CAPACITY);
+ final ResizableIntArray srcXCoords = new ResizableIntArray(DEFAULT_CAPACITY);
+ final ResizableIntArray srcYCoords= new ResizableIntArray(DEFAULT_CAPACITY);
+ for (int i = 0; i < srcLen; i++) {
+ srcTimes.add(i * 2);
+ srcXCoords.add(i * 3);
+ srcYCoords.add(i * 4);
+ }
+ final int dstLen = 50;
+ final InputPointers dst = new InputPointers(DEFAULT_CAPACITY);
+ for (int i = 0; i < dstLen; i++) {
+ final int value = -i - 1;
+ dst.addPointer(value * 4, value * 3, value * 2, value);
+ }
+ final InputPointers dstCopy = new InputPointers(DEFAULT_CAPACITY);
+ dstCopy.copy(dst);
+
+ dst.append(srcPointerId, srcTimes, srcXCoords, srcYCoords, 0, 0);
+ assertEquals("size after append zero", dstLen, dst.getPointerSize());
+ assertArrayEquals("xCoordinates after append zero",
+ dstCopy.getXCoordinates(), 0, dst.getXCoordinates(), 0, dstLen);
+ assertArrayEquals("yCoordinates after append zero",
+ dstCopy.getYCoordinates(), 0, dst.getYCoordinates(), 0, dstLen);
+ assertArrayEquals("pointerIds after append zero",
+ dstCopy.getPointerIds(), 0, dst.getPointerIds(), 0, dstLen);
+ assertArrayEquals("times after append zero",
+ dstCopy.getTimes(), 0, dst.getTimes(), 0, dstLen);
+
+ dst.append(srcPointerId, srcTimes, srcXCoords, srcYCoords, 0, srcLen);
+ assertEquals("size after append", dstLen + srcLen, dst.getPointerSize());
+ assertTrue("primitive length after append",
+ dst.getPointerIds().length >= dstLen + srcLen);
+ assertArrayEquals("original xCoordinates values after append",
+ dstCopy.getXCoordinates(), 0, dst.getXCoordinates(), 0, dstLen);
+ assertArrayEquals("original yCoordinates values after append",
+ dstCopy.getYCoordinates(), 0, dst.getYCoordinates(), 0, dstLen);
+ assertArrayEquals("original pointerIds values after append",
+ dstCopy.getPointerIds(), 0, dst.getPointerIds(), 0, dstLen);
+ assertArrayEquals("original times values after append",
+ dstCopy.getTimes(), 0, dst.getTimes(), 0, dstLen);
+ assertArrayEquals("appended xCoordinates values after append",
+ srcXCoords.getPrimitiveArray(), 0, dst.getXCoordinates(), dstLen, srcLen);
+ assertArrayEquals("appended yCoordinates values after append",
+ srcYCoords.getPrimitiveArray(), 0, dst.getYCoordinates(), dstLen, srcLen);
+ assertArrayEquals("appended pointerIds values after append",
+ srcPointerIds, 0, dst.getPointerIds(), dstLen, srcLen);
+ assertArrayEquals("appended times values after append",
+ srcTimes.getPrimitiveArray(), 0, dst.getTimes(), dstLen, srcLen);
+ }
+
private static void assertArrayEquals(String message, int[] expecteds, int expectedPos,
int[] actuals, int actualPos, int length) {
if (expecteds == null && actuals == null) {
diff --git a/tests/src/com/android/inputmethod/latin/InputTestsBase.java b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
index c672d51..ffd95f5 100644
--- a/tests/src/com/android/inputmethod/latin/InputTestsBase.java
+++ b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
@@ -39,7 +39,6 @@
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.KeyboardActionListener;
import java.util.HashMap;
@@ -136,7 +135,6 @@
mLatinIME.onCreateInputView();
mLatinIME.onStartInputView(ei, false);
mInputConnection = ic;
- mKeyboard = mLatinIME.mKeyboardSwitcher.getKeyboard();
changeLanguage("en_US");
}
@@ -222,9 +220,7 @@
return;
}
}
- mLatinIME.onCodeInput(codePoint,
- KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
- KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
+ mLatinIME.onCodeInput(codePoint, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
//mLatinIME.onReleaseKey(codePoint, false);
}
@@ -256,13 +252,13 @@
fail("InputMethodSubtype for locale " + locale + " is not enabled");
}
SubtypeSwitcher.getInstance().updateSubtype(subtype);
+ mLatinIME.loadKeyboard();
+ mKeyboard = mLatinIME.mKeyboardSwitcher.getKeyboard();
waitForDictionaryToBeLoaded();
}
protected void pickSuggestionManually(final int index, final CharSequence suggestion) {
- mLatinIME.pickSuggestionManually(index, suggestion,
- KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
- KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
+ mLatinIME.pickSuggestionManually(index, suggestion);
}
// Helper to avoid writing the try{}catch block each time
diff --git a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
index c70c2fd..52a3745 100644
--- a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
+++ b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
@@ -28,7 +28,7 @@
public class SubtypeLocaleTests extends AndroidTestCase {
// Locale to subtypes list.
- private final ArrayList<InputMethodSubtype> mSubtypesList = new ArrayList<InputMethodSubtype>();
+ private final ArrayList<InputMethodSubtype> mSubtypesList = CollectionUtils.newArrayList();
private Resources mRes;
diff --git a/tools/dicttool/Android.mk b/tools/dicttool/Android.mk
index e9c11ac..b0b47af 100644
--- a/tools/dicttool/Android.mk
+++ b/tools/dicttool/Android.mk
@@ -24,9 +24,8 @@
$(filter-out $(addprefix %/, $(notdir $(LOCAL_TOOL_SRC_FILES))), $(LOCAL_MAIN_SRC_FILES)) \
$(call all-java-files-under,tests)
LOCAL_JAR_MANIFEST := etc/manifest.txt
-LOCAL_MODULE := dicttool
+LOCAL_MODULE := dicttool_aosp
LOCAL_JAVA_LIBRARIES := junit
-LOCAL_MODULE_TAGS := eng
include $(BUILD_HOST_JAVA_LIBRARY)
include $(LOCAL_PATH)/etc/Android.mk
diff --git a/tools/dicttool/etc/Android.mk b/tools/dicttool/etc/Android.mk
index 1eab70f..0c611b7 100644
--- a/tools/dicttool/etc/Android.mk
+++ b/tools/dicttool/etc/Android.mk
@@ -15,6 +15,5 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := eng
-LOCAL_PREBUILT_EXECUTABLES := dicttool makedict
+LOCAL_PREBUILT_EXECUTABLES := dicttool_aosp makedict_aosp
include $(BUILD_HOST_PREBUILT)
diff --git a/tools/dicttool/etc/dicttool b/tools/dicttool/etc/dicttool_aosp
similarity index 98%
rename from tools/dicttool/etc/dicttool
rename to tools/dicttool/etc/dicttool_aosp
index 8a39694..a4879a2 100755
--- a/tools/dicttool/etc/dicttool
+++ b/tools/dicttool/etc/dicttool_aosp
@@ -33,7 +33,7 @@
prog="${progdir}"/`basename "${prog}"`
cd "${oldwd}"
-jarfile=dicttool.jar
+jarfile=dicttool_aosp.jar
frameworkdir="$progdir"
if [ ! -r "$frameworkdir/$jarfile" ]
then
diff --git a/tools/dicttool/etc/makedict b/tools/dicttool/etc/makedict_aosp
similarity index 96%
rename from tools/dicttool/etc/makedict
rename to tools/dicttool/etc/makedict_aosp
index fffeb23..095c505 100755
--- a/tools/dicttool/etc/makedict
+++ b/tools/dicttool/etc/makedict_aosp
@@ -15,4 +15,4 @@
# Dicttool supports making the dictionary using the 'makedict' command and
# the same arguments that the old 'makedict' command used to accept.
-dicttool makedict $@
+dicttool_aosp makedict $@
diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/AdditionalCommandList.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/AdditionalCommandList.java
new file mode 100644
index 0000000..8d4eb75
--- /dev/null
+++ b/tools/dicttool/src/android/inputmethod/latin/dicttool/AdditionalCommandList.java
@@ -0,0 +1,22 @@
+/**
+ * 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.dicttool;
+
+public class AdditionalCommandList {
+ public static void populate() {
+ }
+}
diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/CommandList.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/CommandList.java
new file mode 100644
index 0000000..d16b069
--- /dev/null
+++ b/tools/dicttool/src/android/inputmethod/latin/dicttool/CommandList.java
@@ -0,0 +1,26 @@
+/**
+ * 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.dicttool;
+
+public class CommandList {
+ public static void populate() {
+ Dicttool.addCommand("info", Info.class);
+ Dicttool.addCommand("compress", Compress.Compressor.class);
+ Dicttool.addCommand("uncompress", Compress.Uncompressor.class);
+ Dicttool.addCommand("makedict", Makedict.class);
+ }
+}
diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/Compress.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/Compress.java
index a76ec50..3cb0a12 100644
--- a/tools/dicttool/src/android/inputmethod/latin/dicttool/Compress.java
+++ b/tools/dicttool/src/android/inputmethod/latin/dicttool/Compress.java
@@ -46,46 +46,52 @@
static public class Compressor extends Dicttool.Command {
public static final String COMMAND = "compress";
- private static final String SUFFIX = ".compressed";
+ public static final String STDIN_OR_STDOUT = "-";
public Compressor() {
}
public String getHelp() {
- return "compress <filename>: Compresses a file using gzip compression";
+ return COMMAND + " <src_filename> <dst_filename>: "
+ + "Compresses a file using gzip compression";
}
public void run() throws IOException {
- if (mArgs.length < 1) {
- throw new RuntimeException("Not enough arguments for command " + COMMAND);
+ if (mArgs.length > 2) {
+ throw new RuntimeException("Too many arguments for command " + COMMAND);
}
- final String inFilename = mArgs[0];
- final String outFilename = inFilename + SUFFIX;
- final FileInputStream input = new FileInputStream(new File(inFilename));
- final FileOutputStream output = new FileOutputStream(new File(outFilename));
+ final String inFilename = mArgs.length >= 1 ? mArgs[0] : STDIN_OR_STDOUT;
+ final String outFilename = mArgs.length >= 2 ? mArgs[1] : STDIN_OR_STDOUT;
+ final InputStream input = inFilename.equals(STDIN_OR_STDOUT) ? System.in
+ : new FileInputStream(new File(inFilename));
+ final OutputStream output = outFilename.equals(STDIN_OR_STDOUT) ? System.out
+ : new FileOutputStream(new File(outFilename));
copy(input, new GZIPOutputStream(output));
}
}
static public class Uncompressor extends Dicttool.Command {
public static final String COMMAND = "uncompress";
- private static final String SUFFIX = ".uncompressed";
+ public static final String STDIN_OR_STDOUT = "-";
public Uncompressor() {
}
public String getHelp() {
- return "uncompress <filename>: Uncompresses a file compressed with gzip compression";
+ return COMMAND + " <src_filename> <dst_filename>: "
+ + "Uncompresses a file compressed with gzip compression";
}
public void run() throws IOException {
- if (mArgs.length < 1) {
- throw new RuntimeException("Not enough arguments for command " + COMMAND);
+ if (mArgs.length > 2) {
+ throw new RuntimeException("Too many arguments for command " + COMMAND);
}
- final String inFilename = mArgs[0];
- final String outFilename = inFilename + SUFFIX;
- final FileInputStream input = new FileInputStream(new File(inFilename));
- final FileOutputStream output = new FileOutputStream(new File(outFilename));
+ final String inFilename = mArgs.length >= 1 ? mArgs[0] : STDIN_OR_STDOUT;
+ final String outFilename = mArgs.length >= 2 ? mArgs[1] : STDIN_OR_STDOUT;
+ final InputStream input = inFilename.equals(STDIN_OR_STDOUT) ? System.in
+ : new FileInputStream(new File(inFilename));
+ final OutputStream output = outFilename.equals(STDIN_OR_STDOUT) ? System.out
+ : new FileOutputStream(new File(outFilename));
copy(new GZIPInputStream(input), output);
}
}
diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/DictionaryMaker.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/DictionaryMaker.java
index 9ebd3bb..fbfc1da 100644
--- a/tools/dicttool/src/android/inputmethod/latin/dicttool/DictionaryMaker.java
+++ b/tools/dicttool/src/android/inputmethod/latin/dicttool/DictionaryMaker.java
@@ -27,7 +27,8 @@
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
-import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
import java.util.Arrays;
import java.util.LinkedList;
@@ -112,7 +113,7 @@
public static String getHelp() {
return "Usage: makedict "
- + "[-s <unigrams.xml> [-b <bigrams.xml>] [-c <shortcuts.xml>] "
+ + "[-s <unigrams.xml> [-b <bigrams.xml>] [-c <shortcuts_and_whitelist.xml>] "
+ "| -s <binary input>] [-d <binary output format version 2>] "
+ "[-d1 <binary output format version 1>] [-x <xml output>] [-2]\n"
+ "\n"
@@ -238,15 +239,30 @@
*/
private static FusionDictionary readBinaryFile(final String binaryFilename)
throws FileNotFoundException, IOException, UnsupportedFormatException {
- final RandomAccessFile inputFile = new RandomAccessFile(binaryFilename, "r");
- return BinaryDictInputOutput.readDictionaryBinary(inputFile, null);
+ FileInputStream inStream = null;
+
+ try {
+ final File file = new File(binaryFilename);
+ inStream = new FileInputStream(file);
+ final ByteBuffer buffer = inStream.getChannel().map(
+ FileChannel.MapMode.READ_ONLY, 0, file.length());
+ return BinaryDictInputOutput.readDictionaryBinary(buffer, null);
+ } finally {
+ if (inStream != null) {
+ try {
+ inStream.close();
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
+ }
}
/**
* Read a dictionary from a unigram XML file, and optionally a bigram XML file.
*
* @param unigramXmlFilename the name of the unigram XML file. May not be null.
- * @param shortcutXmlFilename the name of the shortcut XML file, or null if there is none.
+ * @param shortcutXmlFilename the name of the shortcut/whitelist XML file, or null if none.
* @param bigramXmlFilename the name of the bigram XML file. Pass null if there are no bigrams.
* @return the read dictionary.
* @throws FileNotFoundException if one of the files can't be found
diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/Dicttool.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/Dicttool.java
index c14ce7b..bf417fb 100644
--- a/tools/dicttool/src/android/inputmethod/latin/dicttool/Dicttool.java
+++ b/tools/dicttool/src/android/inputmethod/latin/dicttool/Dicttool.java
@@ -32,10 +32,11 @@
static HashMap<String, Class<? extends Command>> sCommands =
new HashMap<String, Class<? extends Command>>();
static {
- sCommands.put("info", Info.class);
- sCommands.put("compress", Compress.Compressor.class);
- sCommands.put("uncompress", Compress.Uncompressor.class);
- sCommands.put("makedict", Makedict.class);
+ CommandList.populate();
+ AdditionalCommandList.populate();
+ }
+ public static void addCommand(final String commandName, final Class<? extends Command> cls) {
+ sCommands.put(commandName, cls);
}
private static Command getCommandInstance(final String commandName) {
diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/XmlDictInputOutput.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/XmlDictInputOutput.java
index 8e2e735..9ce8c49 100644
--- a/tools/dicttool/src/android/inputmethod/latin/dicttool/XmlDictInputOutput.java
+++ b/tools/dicttool/src/android/inputmethod/latin/dicttool/XmlDictInputOutput.java
@@ -90,6 +90,10 @@
public FusionDictionary getFinalDictionary() {
final FusionDictionary dict = mDictionary;
+ for (final String shortcutOnly : mShortcutsMap.keySet()) {
+ if (dict.hasWord(shortcutOnly)) continue;
+ dict.add(shortcutOnly, 0, mShortcutsMap.get(shortcutOnly));
+ }
mDictionary = null;
mShortcutsMap.clear();
mWord = "";
@@ -179,7 +183,7 @@
mSrc = attrs.getValue(uri, SRC_ATTRIBUTE);
} else if (DST_TAG.equals(localName)) {
String dst = attrs.getValue(uri, DST_ATTRIBUTE);
- int freq = Integer.parseInt(attrs.getValue(uri, DST_FREQ));
+ int freq = getValueFromFreqString(attrs.getValue(uri, DST_FREQ));
WeightedString bigram = new WeightedString(dst, freq / XML_TO_MEMORY_RATIO);
ArrayList<WeightedString> bigramList = mAssocMap.get(mSrc);
if (null == bigramList) bigramList = new ArrayList<WeightedString>();
@@ -188,6 +192,10 @@
}
}
+ protected int getValueFromFreqString(final String freqString) {
+ return Integer.parseInt(freqString);
+ }
+
// This may return an empty map, but will never return null.
public HashMap<String, ArrayList<WeightedString>> getAssocMap() {
return mAssocMap;
@@ -216,22 +224,40 @@
}
/**
- * SAX handler for a shortcut XML file.
+ * SAX handler for a shortcut & whitelist XML file.
*/
- static private class ShortcutHandler extends AssociativeListHandler {
+ static private class ShortcutAndWhitelistHandler extends AssociativeListHandler {
private final static String ENTRY_TAG = "entry";
private final static String ENTRY_ATTRIBUTE = "shortcut";
private final static String TARGET_TAG = "target";
private final static String REPLACEMENT_ATTRIBUTE = "replacement";
private final static String TARGET_PRIORITY_ATTRIBUTE = "priority";
+ private final static String WHITELIST_MARKER = "whitelist";
+ private final static int WHITELIST_FREQ_VALUE = 15;
+ private final static int MIN_FREQ = 0;
+ private final static int MAX_FREQ = 14;
- public ShortcutHandler() {
+ public ShortcutAndWhitelistHandler() {
super(ENTRY_TAG, ENTRY_ATTRIBUTE, TARGET_TAG, REPLACEMENT_ATTRIBUTE,
TARGET_PRIORITY_ATTRIBUTE);
}
+ @Override
+ protected int getValueFromFreqString(final String freqString) {
+ if (WHITELIST_MARKER.equals(freqString)) {
+ return WHITELIST_FREQ_VALUE;
+ } else {
+ final int intValue = super.getValueFromFreqString(freqString);
+ if (intValue < MIN_FREQ || intValue > MAX_FREQ) {
+ throw new RuntimeException("Shortcut freq out of range. Accepted range is "
+ + MIN_FREQ + ".." + MAX_FREQ);
+ }
+ return intValue;
+ }
+ }
+
// As per getAssocMap(), this never returns null.
- public HashMap<String, ArrayList<WeightedString>> getShortcutMap() {
+ public HashMap<String, ArrayList<WeightedString>> getShortcutAndWhitelistMap() {
return getAssocMap();
}
}
@@ -243,7 +269,7 @@
* representation.
*
* @param unigrams the file to read the data from.
- * @param shortcuts the file to read the shortcuts from, or null.
+ * @param shortcuts the file to read the shortcuts & whitelist from, or null.
* @param bigrams the file to read the bigrams from, or null.
* @return the in-memory representation of the dictionary.
*/
@@ -256,11 +282,12 @@
final BigramHandler bigramHandler = new BigramHandler();
if (null != bigrams) parser.parse(bigrams, bigramHandler);
- final ShortcutHandler shortcutHandler = new ShortcutHandler();
- if (null != shortcuts) parser.parse(shortcuts, shortcutHandler);
+ final ShortcutAndWhitelistHandler shortcutAndWhitelistHandler =
+ new ShortcutAndWhitelistHandler();
+ if (null != shortcuts) parser.parse(shortcuts, shortcutAndWhitelistHandler);
final UnigramHandler unigramHandler =
- new UnigramHandler(shortcutHandler.getShortcutMap());
+ new UnigramHandler(shortcutAndWhitelistHandler.getShortcutAndWhitelistMap());
parser.parse(unigrams, unigramHandler);
final FusionDictionary dict = unigramHandler.getFinalDictionary();
final HashMap<String, ArrayList<WeightedString>> bigramMap = bigramHandler.getBigramMap();
@@ -280,7 +307,7 @@
*
* This method reads data from the parser and creates a new FusionDictionary with it.
* The format parsed by this method is the format used before Ice Cream Sandwich,
- * which has no support for bigrams or shortcuts.
+ * which has no support for bigrams or shortcuts/whitelist.
* It is important to note that this method expects the parser to have already eaten
* the first, all-encompassing tag.
*
@@ -291,7 +318,7 @@
/**
* Writes a dictionary to an XML file.
*
- * The output format is the "second" format, which supports bigrams and shortcuts.
+ * The output format is the "second" format, which supports bigrams and shortcuts/whitelist.
*
* @param destination a destination stream to write to.
* @param dict the dictionary to write.
diff --git a/tools/maketext/Android.mk b/tools/maketext/Android.mk
index 98731b7..77914ca 100644
--- a/tools/maketext/Android.mk
+++ b/tools/maketext/Android.mk
@@ -19,7 +19,6 @@
LOCAL_SRC_FILES += $(call all-java-files-under,src)
LOCAL_JAR_MANIFEST := etc/manifest.txt
LOCAL_JAVA_RESOURCE_DIRS := res
-LOCAL_MODULE_TAGS := eng
LOCAL_MODULE := maketext
include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/maketext/etc/Android.mk b/tools/maketext/etc/Android.mk
index 4fa194b..475676b 100644
--- a/tools/maketext/etc/Android.mk
+++ b/tools/maketext/etc/Android.mk
@@ -15,7 +15,6 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := eng
-
LOCAL_PREBUILT_EXECUTABLES := maketext
+
include $(BUILD_HOST_PREBUILT)
diff --git a/tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl b/tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl
index f6c84ea..774094c 100644
--- a/tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl
+++ b/tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.res.Resources;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.R;
import java.util.HashMap;
@@ -45,14 +46,12 @@
*/
public final class KeyboardTextsSet {
// Language to texts map.
- private static final HashMap<String, String[]> sLocaleToTextsMap =
- new HashMap<String, String[]>();
- private static final HashMap<String, Integer> sNameToIdsMap =
- new HashMap<String, Integer>();
+ private static final HashMap<String, String[]> sLocaleToTextsMap = CollectionUtils.newHashMap();
+ private static final HashMap<String, Integer> sNameToIdsMap = CollectionUtils.newHashMap();
private String[] mTexts;
// Resource name to text map.
- private HashMap<String, String> mResourceNameToTextsMap = new HashMap<String, String>();
+ private HashMap<String, String> mResourceNameToTextsMap = CollectionUtils.newHashMap();
public void setLanguage(final String language) {
mTexts = sLocaleToTextsMap.get(language);