Merge remote-tracking branch 'goog/jb-mr1-dev' into mergescriptpackage
commit
9761fa5786
|
@ -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>
|
|
@ -44,6 +44,10 @@
|
||||||
<init>(...);
|
<init>(...);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-keepclasseswithmembernames class * {
|
||||||
|
native <methods>;
|
||||||
|
}
|
||||||
|
|
||||||
-keep class com.android.inputmethod.research.ResearchLogger {
|
-keep class com.android.inputmethod.research.ResearchLogger {
|
||||||
void flush();
|
void flush();
|
||||||
void publishCurrentLogUnit(...);
|
void publishCurrentLogUnit(...);
|
||||||
|
|
|
@ -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>
|
|
|
@ -41,26 +41,24 @@
|
||||||
checkable+checked+pressed. -->
|
checkable+checked+pressed. -->
|
||||||
<attr name="keyBackground" format="reference" />
|
<attr name="keyBackground" format="reference" />
|
||||||
|
|
||||||
<!-- Size of the text for one letter keys. If not defined, keyLetterRatio takes effect. -->
|
<!-- Size of the text for one letter keys. If specified as fraction, the text size is
|
||||||
<attr name="keyLetterSize" format="dimension" />
|
measured in the proportion of key height. -->
|
||||||
<!-- Size of the text for keys with multiple letters. If not defined, keyLabelRatio takes
|
<attr name="keyLetterSize" format="dimension|fraction" />
|
||||||
effect. -->
|
<!-- Size of the text for keys with multiple letters. If specified as fraction, the text
|
||||||
<attr name="keyLabelSize" format="dimension" />
|
size is measured in the proportion of key height. -->
|
||||||
<!-- Size of the text for one letter keys, in the proportion of key height. -->
|
<attr name="keyLabelSize" format="dimension|fraction" />
|
||||||
<attr name="keyLetterRatio" format="float" />
|
|
||||||
<!-- Large size of the text for one letter keys, in the proportion of key height. -->
|
<!-- Large size of the text for one letter keys, in the proportion of key height. -->
|
||||||
<attr name="keyLargeLetterRatio" format="float" />
|
<attr name="keyLargeLetterRatio" format="fraction" />
|
||||||
<!-- Size of the text for keys with multiple letters, in the proportion of key height. -->
|
|
||||||
<attr name="keyLabelRatio" format="float" />
|
|
||||||
<!-- Large size of the text for keys with multiple letters, in the proportion of key height. -->
|
<!-- Large size of the text for keys with multiple letters, in the proportion of key height. -->
|
||||||
<attr name="keyLargeLabelRatio" format="float" />
|
<attr name="keyLargeLabelRatio" format="fraction" />
|
||||||
<!-- Size of the text for hint letter (= one character hint label), in the proportion of
|
<!-- Size of the text for hint letter (= one character hint label), in the proportion of
|
||||||
key height. -->
|
key height. -->
|
||||||
<attr name="keyHintLetterRatio" format="float" />
|
<attr name="keyHintLetterRatio" format="fraction" />
|
||||||
<!-- Size of the text for hint label, in the proportion of key height. -->
|
<!-- Size of the text for hint label, in the proportion of key height. -->
|
||||||
<attr name="keyHintLabelRatio" format="float" />
|
<attr name="keyHintLabelRatio" format="fraction" />
|
||||||
<!-- Size of the text for shifted letter hint, in the proportion of key height. -->
|
<!-- Size of the text for shifted letter hint, in the proportion of key height. -->
|
||||||
<attr name="keyShiftedLetterHintRatio" format="float" />
|
<attr name="keyShiftedLetterHintRatio" format="dimension|fraction" />
|
||||||
|
|
||||||
<!-- Horizontal padding of left/right aligned key label to the edge of the key. -->
|
<!-- Horizontal padding of left/right aligned key label to the edge of the key. -->
|
||||||
<attr name="keyLabelHorizontalPadding" format="dimension" />
|
<attr name="keyLabelHorizontalPadding" format="dimension" />
|
||||||
<!-- Right padding of hint letter to the edge of the key.-->
|
<!-- Right padding of hint letter to the edge of the key.-->
|
||||||
|
@ -96,8 +94,8 @@
|
||||||
<attr name="keyPreviewOffset" format="dimension" />
|
<attr name="keyPreviewOffset" format="dimension" />
|
||||||
<!-- Height of the key press feedback popup. -->
|
<!-- Height of the key press feedback popup. -->
|
||||||
<attr name="keyPreviewHeight" format="dimension" />
|
<attr name="keyPreviewHeight" format="dimension" />
|
||||||
<!-- Size of the text for key press feedback popup, int the proportion of key height -->
|
<!-- Size of the text for key press feedback popup, in the proportion of key height. -->
|
||||||
<attr name="keyPreviewTextRatio" format="float" />
|
<attr name="keyPreviewTextRatio" format="fraction" />
|
||||||
<!-- Delay after key releasing and key press feedback dismissing in millisecond -->
|
<!-- Delay after key releasing and key press feedback dismissing in millisecond -->
|
||||||
<attr name="keyPreviewLingerTimeout" format="integer" />
|
<attr name="keyPreviewLingerTimeout" format="integer" />
|
||||||
|
|
||||||
|
@ -131,6 +129,12 @@
|
||||||
<attr name="gestureFloatingPreviewTextConnectorWidth" format="dimension" />
|
<attr name="gestureFloatingPreviewTextConnectorWidth" format="dimension" />
|
||||||
<!-- Delay after gesture input and gesture floating preview text dismissing in millisecond -->
|
<!-- Delay after gesture input and gesture floating preview text dismissing in millisecond -->
|
||||||
<attr name="gestureFloatingPreviewTextLingerTimeout" format="integer" />
|
<attr name="gestureFloatingPreviewTextLingerTimeout" format="integer" />
|
||||||
|
<!-- Delay after gesture trail starts fading out in millisecond. -->
|
||||||
|
<attr name="gesturePreviewTrailFadeoutStartDelay" format="integer" />
|
||||||
|
<!-- Duration while gesture preview trail is fading out in millisecond. -->
|
||||||
|
<attr name="gesturePreviewTrailFadeoutDuration" format="integer" />
|
||||||
|
<!-- Interval of updating gesture preview trail in millisecond. -->
|
||||||
|
<attr name="gesturePreviewTrailUpdateInterval" format="integer" />
|
||||||
<attr name="gesturePreviewTrailColor" format="color" />
|
<attr name="gesturePreviewTrailColor" format="color" />
|
||||||
<attr name="gesturePreviewTrailWidth" format="dimension" />
|
<attr name="gesturePreviewTrailWidth" format="dimension" />
|
||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
|
@ -181,13 +185,13 @@
|
||||||
<attr name="colorTypedWord" format="color" />
|
<attr name="colorTypedWord" format="color" />
|
||||||
<attr name="colorAutoCorrect" format="color" />
|
<attr name="colorAutoCorrect" format="color" />
|
||||||
<attr name="colorSuggested" format="color" />
|
<attr name="colorSuggested" format="color" />
|
||||||
<attr name="alphaValidTypedWord" format="integer" />
|
<attr name="alphaValidTypedWord" format="fraction" />
|
||||||
<attr name="alphaTypedWord" format="integer" />
|
<attr name="alphaTypedWord" format="fraction" />
|
||||||
<attr name="alphaAutoCorrect" format="integer" />
|
<attr name="alphaAutoCorrect" format="fraction" />
|
||||||
<attr name="alphaSuggested" format="integer" />
|
<attr name="alphaSuggested" format="fraction" />
|
||||||
<attr name="alphaObsoleted" format="integer" />
|
<attr name="alphaObsoleted" format="fraction" />
|
||||||
<attr name="suggestionsCountInStrip" format="integer" />
|
<attr name="suggestionsCountInStrip" format="integer" />
|
||||||
<attr name="centerSuggestionPercentile" format="integer" />
|
<attr name="centerSuggestionPercentile" format="fraction" />
|
||||||
<attr name="maxMoreSuggestionsRow" format="integer" />
|
<attr name="maxMoreSuggestionsRow" format="integer" />
|
||||||
<attr name="minMoreSuggestionsWidth" format="float" />
|
<attr name="minMoreSuggestionsWidth" format="float" />
|
||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
|
|
|
@ -50,6 +50,9 @@
|
||||||
-->
|
-->
|
||||||
<integer name="config_key_preview_linger_timeout">70</integer>
|
<integer name="config_key_preview_linger_timeout">70</integer>
|
||||||
<integer name="config_gesture_floating_preview_text_linger_timeout">200</integer>
|
<integer name="config_gesture_floating_preview_text_linger_timeout">200</integer>
|
||||||
|
<integer name="config_gesture_preview_trail_fadeout_start_delay">100</integer>
|
||||||
|
<integer name="config_gesture_preview_trail_fadeout_duration">800</integer>
|
||||||
|
<integer name="config_gesture_preview_trail_update_interval">20</integer>
|
||||||
<!--
|
<!--
|
||||||
Configuration for MainKeyboardView
|
Configuration for MainKeyboardView
|
||||||
-->
|
-->
|
||||||
|
|
|
@ -92,7 +92,7 @@
|
||||||
<dimen name="suggestion_text_size">18dp</dimen>
|
<dimen name="suggestion_text_size">18dp</dimen>
|
||||||
<dimen name="more_suggestions_hint_text_size">27dp</dimen>
|
<dimen name="more_suggestions_hint_text_size">27dp</dimen>
|
||||||
<integer name="suggestions_count_in_strip">3</integer>
|
<integer name="suggestions_count_in_strip">3</integer>
|
||||||
<integer name="center_suggestion_percentile">36</integer>
|
<fraction name="center_suggestion_percentile">36%</fraction>
|
||||||
|
|
||||||
<!-- Gesture preview parameters -->
|
<!-- Gesture preview parameters -->
|
||||||
<dimen name="gesture_preview_trail_width">2.5dp</dimen>
|
<dimen name="gesture_preview_trail_width">2.5dp</dimen>
|
||||||
|
@ -101,4 +101,7 @@
|
||||||
<dimen name="gesture_floating_preview_text_shadow_border">17.5dp</dimen>
|
<dimen name="gesture_floating_preview_text_shadow_border">17.5dp</dimen>
|
||||||
<dimen name="gesture_floating_preview_text_shading_border">7.5dp</dimen>
|
<dimen name="gesture_floating_preview_text_shading_border">7.5dp</dimen>
|
||||||
<dimen name="gesture_floating_preview_text_connector_width">1.0dp</dimen>
|
<dimen name="gesture_floating_preview_text_connector_width">1.0dp</dimen>
|
||||||
|
|
||||||
|
<!-- Inset used in Accessibility mode to avoid accidental key presses when a finger slides off the screen. -->
|
||||||
|
<dimen name="accessibility_edge_slop">8dp</dimen>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -22,5 +22,6 @@
|
||||||
<!-- Build.HARDWARE,duration_in_milliseconds -->
|
<!-- Build.HARDWARE,duration_in_milliseconds -->
|
||||||
<item>herring,5</item>
|
<item>herring,5</item>
|
||||||
<item>tuna,5</item>
|
<item>tuna,5</item>
|
||||||
|
<item>mako,20</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -24,5 +24,6 @@
|
||||||
<item>tuna,0.5</item>
|
<item>tuna,0.5</item>
|
||||||
<item>stingray,0.4</item>
|
<item>stingray,0.4</item>
|
||||||
<item>grouper,0.3</item>
|
<item>grouper,0.3</item>
|
||||||
|
<item>mako,0.3</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -261,7 +261,8 @@
|
||||||
<string name="research_feedback_dialog_title" translatable="false">Send feedback</string>
|
<string name="research_feedback_dialog_title" translatable="false">Send feedback</string>
|
||||||
<!-- Text for checkbox option to include user data in feedback for research purposes [CHAR LIMIT=50] -->
|
<!-- Text for checkbox option to include user data in feedback for research purposes [CHAR LIMIT=50] -->
|
||||||
<!-- TODO: remove translatable=false attribute once text is stable -->
|
<!-- TODO: remove translatable=false attribute once text is stable -->
|
||||||
<string name="research_feedback_include_history_label" translatable="false">Include last 5 words entered</string>
|
<!-- TODO: handle multilingual plurals -->
|
||||||
|
<string name="research_feedback_include_history_label" translatable="false">Include last <xliff:g id="word">%d</xliff:g> words entered</string>
|
||||||
<!-- Hint to user about the text entry field where they should enter research feedback [CHAR LIMIT=40] -->
|
<!-- Hint to user about the text entry field where they should enter research feedback [CHAR LIMIT=40] -->
|
||||||
<!-- TODO: remove translatable=false attribute once text is stable -->
|
<!-- TODO: remove translatable=false attribute once text is stable -->
|
||||||
<string name="research_feedback_hint" translatable="false">Enter your feedback here.</string>
|
<string name="research_feedback_hint" translatable="false">Enter your feedback here.</string>
|
||||||
|
@ -288,6 +289,10 @@
|
||||||
<!-- TODO: remove translatable=false attribute once text is stable -->
|
<!-- TODO: remove translatable=false attribute once text is stable -->
|
||||||
<string name="research_send_usage_info" translatable="false">Send usage info</string>
|
<string name="research_send_usage_info" translatable="false">Send usage info</string>
|
||||||
|
|
||||||
|
<!-- Name for the research uploading service to be displayed to users. [CHAR LIMIT=50] -->
|
||||||
|
<!-- TODO: remove translatable=false attribute once text is stable -->
|
||||||
|
<string name="research_log_uploader_name" translatable="false">Research Uploader Service</string>
|
||||||
|
|
||||||
<!-- Preference for input language selection -->
|
<!-- Preference for input language selection -->
|
||||||
<string name="select_language">Input languages</string>
|
<string name="select_language">Input languages</string>
|
||||||
|
|
||||||
|
|
|
@ -35,9 +35,9 @@
|
||||||
<style name="KeyboardView">
|
<style name="KeyboardView">
|
||||||
<item name="android:background">@drawable/keyboard_background</item>
|
<item name="android:background">@drawable/keyboard_background</item>
|
||||||
<item name="keyBackground">@drawable/btn_keyboard_key</item>
|
<item name="keyBackground">@drawable/btn_keyboard_key</item>
|
||||||
<item name="keyLetterRatio">@fraction/key_letter_ratio</item>
|
<item name="keyLetterSize">@fraction/key_letter_ratio</item>
|
||||||
<item name="keyLargeLetterRatio">@fraction/key_large_letter_ratio</item>
|
<item name="keyLargeLetterRatio">@fraction/key_large_letter_ratio</item>
|
||||||
<item name="keyLabelRatio">@fraction/key_label_ratio</item>
|
<item name="keyLabelSize">@fraction/key_label_ratio</item>
|
||||||
<item name="keyLargeLabelRatio">@fraction/key_large_label_ratio</item>
|
<item name="keyLargeLabelRatio">@fraction/key_large_label_ratio</item>
|
||||||
<item name="keyHintLetterRatio">@fraction/key_hint_letter_ratio</item>
|
<item name="keyHintLetterRatio">@fraction/key_hint_letter_ratio</item>
|
||||||
<item name="keyHintLabelRatio">@fraction/key_hint_label_ratio</item>
|
<item name="keyHintLabelRatio">@fraction/key_hint_label_ratio</item>
|
||||||
|
@ -78,6 +78,9 @@
|
||||||
<item name="gestureFloatingPreviewTextConnectorColor">@android:color/white</item>
|
<item name="gestureFloatingPreviewTextConnectorColor">@android:color/white</item>
|
||||||
<item name="gestureFloatingPreviewTextConnectorWidth">@dimen/gesture_floating_preview_text_connector_width</item>
|
<item name="gestureFloatingPreviewTextConnectorWidth">@dimen/gesture_floating_preview_text_connector_width</item>
|
||||||
<item name="gestureFloatingPreviewTextLingerTimeout">@integer/config_gesture_floating_preview_text_linger_timeout</item>
|
<item name="gestureFloatingPreviewTextLingerTimeout">@integer/config_gesture_floating_preview_text_linger_timeout</item>
|
||||||
|
<item name="gesturePreviewTrailFadeoutStartDelay">@integer/config_gesture_preview_trail_fadeout_start_delay</item>
|
||||||
|
<item name="gesturePreviewTrailFadeoutDuration">@integer/config_gesture_preview_trail_fadeout_duration</item>
|
||||||
|
<item name="gesturePreviewTrailUpdateInterval">@integer/config_gesture_preview_trail_update_interval</item>
|
||||||
<item name="gesturePreviewTrailColor">@android:color/holo_blue_light</item>
|
<item name="gesturePreviewTrailColor">@android:color/holo_blue_light</item>
|
||||||
<item name="gesturePreviewTrailWidth">@dimen/gesture_preview_trail_width</item>
|
<item name="gesturePreviewTrailWidth">@dimen/gesture_preview_trail_width</item>
|
||||||
<!-- Common attributes of MainKeyboardView -->
|
<!-- Common attributes of MainKeyboardView -->
|
||||||
|
@ -135,9 +138,9 @@
|
||||||
<item name="colorTypedWord">@android:color/white</item>
|
<item name="colorTypedWord">@android:color/white</item>
|
||||||
<item name="colorAutoCorrect">#FFFCAE00</item>
|
<item name="colorAutoCorrect">#FFFCAE00</item>
|
||||||
<item name="colorSuggested">#FFFCAE00</item>
|
<item name="colorSuggested">#FFFCAE00</item>
|
||||||
<item name="alphaObsoleted">50</item>
|
<item name="alphaObsoleted">50%</item>
|
||||||
<item name="suggestionsCountInStrip">@integer/suggestions_count_in_strip</item>
|
<item name="suggestionsCountInStrip">@integer/suggestions_count_in_strip</item>
|
||||||
<item name="centerSuggestionPercentile">@integer/center_suggestion_percentile</item>
|
<item name="centerSuggestionPercentile">@fraction/center_suggestion_percentile</item>
|
||||||
<item name="maxMoreSuggestionsRow">@integer/max_more_suggestions_row</item>
|
<item name="maxMoreSuggestionsRow">@integer/max_more_suggestions_row</item>
|
||||||
<item name="minMoreSuggestionsWidth">@fraction/min_more_suggestions_width</item>
|
<item name="minMoreSuggestionsWidth">@fraction/min_more_suggestions_width</item>
|
||||||
</style>
|
</style>
|
||||||
|
@ -370,12 +373,12 @@
|
||||||
<item name="colorTypedWord">@android:color/holo_blue_light</item>
|
<item name="colorTypedWord">@android:color/holo_blue_light</item>
|
||||||
<item name="colorAutoCorrect">@android:color/holo_blue_light</item>
|
<item name="colorAutoCorrect">@android:color/holo_blue_light</item>
|
||||||
<item name="colorSuggested">@android:color/holo_blue_light</item>
|
<item name="colorSuggested">@android:color/holo_blue_light</item>
|
||||||
<item name="alphaValidTypedWord">85</item>
|
<item name="alphaValidTypedWord">85%</item>
|
||||||
<item name="alphaTypedWord">85</item>
|
<item name="alphaTypedWord">85%</item>
|
||||||
<item name="alphaSuggested">70</item>
|
<item name="alphaSuggested">70%</item>
|
||||||
<item name="alphaObsoleted">70</item>
|
<item name="alphaObsoleted">70%</item>
|
||||||
<item name="suggestionsCountInStrip">@integer/suggestions_count_in_strip</item>
|
<item name="suggestionsCountInStrip">@integer/suggestions_count_in_strip</item>
|
||||||
<item name="centerSuggestionPercentile">@integer/center_suggestion_percentile</item>
|
<item name="centerSuggestionPercentile">@fraction/center_suggestion_percentile</item>
|
||||||
<item name="maxMoreSuggestionsRow">@integer/max_more_suggestions_row</item>
|
<item name="maxMoreSuggestionsRow">@integer/max_more_suggestions_row</item>
|
||||||
<item name="minMoreSuggestionsWidth">@fraction/min_more_suggestions_width</item>
|
<item name="minMoreSuggestionsWidth">@fraction/min_more_suggestions_width</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -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>
|
|
|
@ -35,6 +35,7 @@ import android.view.inputmethod.EditorInfo;
|
||||||
import com.android.inputmethod.keyboard.Key;
|
import com.android.inputmethod.keyboard.Key;
|
||||||
import com.android.inputmethod.keyboard.Keyboard;
|
import com.android.inputmethod.keyboard.Keyboard;
|
||||||
import com.android.inputmethod.keyboard.KeyboardView;
|
import com.android.inputmethod.keyboard.KeyboardView;
|
||||||
|
import com.android.inputmethod.latin.CollectionUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exposes a virtual view sub-tree for {@link KeyboardView} and generates
|
* Exposes a virtual view sub-tree for {@link KeyboardView} and generates
|
||||||
|
@ -55,7 +56,7 @@ public class AccessibilityEntityProvider extends AccessibilityNodeProviderCompat
|
||||||
private final AccessibilityUtils mAccessibilityUtils;
|
private final AccessibilityUtils mAccessibilityUtils;
|
||||||
|
|
||||||
/** A map of integer IDs to {@link Key}s. */
|
/** A map of integer IDs to {@link Key}s. */
|
||||||
private final SparseArray<Key> mVirtualViewIdToKey = new SparseArray<Key>();
|
private final SparseArray<Key> mVirtualViewIdToKey = CollectionUtils.newSparseArray();
|
||||||
|
|
||||||
/** Temporary rect used to calculate in-screen bounds. */
|
/** Temporary rect used to calculate in-screen bounds. */
|
||||||
private final Rect mTempBoundsInScreen = new Rect();
|
private final Rect mTempBoundsInScreen = new Rect();
|
||||||
|
|
|
@ -19,10 +19,15 @@ package com.android.inputmethod.accessibility;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.inputmethodservice.InputMethodService;
|
import android.inputmethodservice.InputMethodService;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
|
import android.support.v4.view.accessibility.AccessibilityEventCompat;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.ViewParent;
|
||||||
import android.view.accessibility.AccessibilityEvent;
|
import android.view.accessibility.AccessibilityEvent;
|
||||||
import android.view.accessibility.AccessibilityManager;
|
import android.view.accessibility.AccessibilityManager;
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
|
@ -138,9 +143,10 @@ public class AccessibilityUtils {
|
||||||
* Sends the specified text to the {@link AccessibilityManager} to be
|
* Sends the specified text to the {@link AccessibilityManager} to be
|
||||||
* spoken.
|
* spoken.
|
||||||
*
|
*
|
||||||
* @param text the text to speak
|
* @param view The source view.
|
||||||
|
* @param text The text to speak.
|
||||||
*/
|
*/
|
||||||
public void speak(CharSequence text) {
|
public void announceForAccessibility(View view, CharSequence text) {
|
||||||
if (!mAccessibilityManager.isEnabled()) {
|
if (!mAccessibilityManager.isEnabled()) {
|
||||||
Log.e(TAG, "Attempted to speak when accessibility was disabled!");
|
Log.e(TAG, "Attempted to speak when accessibility was disabled!");
|
||||||
return;
|
return;
|
||||||
|
@ -149,8 +155,7 @@ public class AccessibilityUtils {
|
||||||
// The following is a hack to avoid using the heavy-weight TextToSpeech
|
// The following is a hack to avoid using the heavy-weight TextToSpeech
|
||||||
// class. Instead, we're just forcing a fake AccessibilityEvent into
|
// class. Instead, we're just forcing a fake AccessibilityEvent into
|
||||||
// the screen reader to make it speak.
|
// the screen reader to make it speak.
|
||||||
final AccessibilityEvent event = AccessibilityEvent
|
final AccessibilityEvent event = AccessibilityEvent.obtain();
|
||||||
.obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED);
|
|
||||||
|
|
||||||
event.setPackageName(PACKAGE);
|
event.setPackageName(PACKAGE);
|
||||||
event.setClassName(CLASS);
|
event.setClassName(CLASS);
|
||||||
|
@ -158,20 +163,34 @@ public class AccessibilityUtils {
|
||||||
event.setEnabled(true);
|
event.setEnabled(true);
|
||||||
event.getText().add(text);
|
event.getText().add(text);
|
||||||
|
|
||||||
mAccessibilityManager.sendAccessibilityEvent(event);
|
// Platforms starting at SDK 16 should use announce events.
|
||||||
|
if (Build.VERSION.SDK_INT >= 16) {
|
||||||
|
event.setEventType(AccessibilityEventCompat.TYPE_ANNOUNCEMENT);
|
||||||
|
} else {
|
||||||
|
event.setEventType(AccessibilityEvent.TYPE_VIEW_FOCUSED);
|
||||||
|
}
|
||||||
|
|
||||||
|
final ViewParent viewParent = view.getParent();
|
||||||
|
if ((viewParent == null) || !(viewParent instanceof ViewGroup)) {
|
||||||
|
Log.e(TAG, "Failed to obtain ViewParent in announceForAccessibility");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
viewParent.requestSendAccessibilityEvent(view, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles speaking the "connect a headset to hear passwords" notification
|
* Handles speaking the "connect a headset to hear passwords" notification
|
||||||
* when connecting to a password field.
|
* when connecting to a password field.
|
||||||
*
|
*
|
||||||
|
* @param view The source view.
|
||||||
* @param editorInfo The input connection's editor info attribute.
|
* @param editorInfo The input connection's editor info attribute.
|
||||||
* @param restarting Whether the connection is being restarted.
|
* @param restarting Whether the connection is being restarted.
|
||||||
*/
|
*/
|
||||||
public void onStartInputViewInternal(EditorInfo editorInfo, boolean restarting) {
|
public void onStartInputViewInternal(View view, EditorInfo editorInfo, boolean restarting) {
|
||||||
if (shouldObscureInput(editorInfo)) {
|
if (shouldObscureInput(editorInfo)) {
|
||||||
final CharSequence text = mContext.getText(R.string.spoken_use_headphones);
|
final CharSequence text = mContext.getText(R.string.spoken_use_headphones);
|
||||||
speak(text);
|
announceForAccessibility(view, text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,6 @@ import android.support.v4.view.accessibility.AccessibilityEventCompat;
|
||||||
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
|
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewConfiguration;
|
|
||||||
|
|
||||||
import com.android.inputmethod.keyboard.Key;
|
import com.android.inputmethod.keyboard.Key;
|
||||||
import com.android.inputmethod.keyboard.Keyboard;
|
import com.android.inputmethod.keyboard.Keyboard;
|
||||||
|
@ -44,7 +43,7 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inset in pixels to look for keys when the user's finger exits the
|
* Inset in pixels to look for keys when the user's finger exits the
|
||||||
* keyboard area. See {@link ViewConfiguration#getScaledEdgeSlop()}.
|
* keyboard area.
|
||||||
*/
|
*/
|
||||||
private int mEdgeSlop;
|
private int mEdgeSlop;
|
||||||
|
|
||||||
|
@ -62,7 +61,8 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
|
||||||
|
|
||||||
private void initInternal(InputMethodService inputMethod) {
|
private void initInternal(InputMethodService inputMethod) {
|
||||||
mInputMethod = inputMethod;
|
mInputMethod = inputMethod;
|
||||||
mEdgeSlop = ViewConfiguration.get(inputMethod).getScaledEdgeSlop();
|
mEdgeSlop = inputMethod.getResources().getDimensionPixelSize(
|
||||||
|
R.dimen.accessibility_edge_slop);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -114,8 +114,14 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
|
||||||
public boolean dispatchHoverEvent(MotionEvent event, PointerTracker tracker) {
|
public boolean dispatchHoverEvent(MotionEvent event, PointerTracker tracker) {
|
||||||
final int x = (int) event.getX();
|
final int x = (int) event.getX();
|
||||||
final int y = (int) event.getY();
|
final int y = (int) event.getY();
|
||||||
final Key key = tracker.getKeyOn(x, y);
|
|
||||||
final Key previousKey = mLastHoverKey;
|
final Key previousKey = mLastHoverKey;
|
||||||
|
final Key key;
|
||||||
|
|
||||||
|
if (pointInView(x, y)) {
|
||||||
|
key = tracker.getKeyOn(x, y);
|
||||||
|
} else {
|
||||||
|
key = null;
|
||||||
|
}
|
||||||
|
|
||||||
mLastHoverKey = key;
|
mLastHoverKey = key;
|
||||||
|
|
||||||
|
@ -123,7 +129,7 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
|
||||||
case MotionEvent.ACTION_HOVER_EXIT:
|
case MotionEvent.ACTION_HOVER_EXIT:
|
||||||
// Make sure we're not getting an EXIT event because the user slid
|
// Make sure we're not getting an EXIT event because the user slid
|
||||||
// off the keyboard area, then force a key press.
|
// off the keyboard area, then force a key press.
|
||||||
if (pointInView(x, y) && (key != null)) {
|
if (key != null) {
|
||||||
getAccessibilityNodeProvider().simulateKeyPress(key);
|
getAccessibilityNodeProvider().simulateKeyPress(key);
|
||||||
}
|
}
|
||||||
//$FALL-THROUGH$
|
//$FALL-THROUGH$
|
||||||
|
@ -250,7 +256,7 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
|
||||||
text = context.getText(R.string.spoken_description_shiftmode_off);
|
text = context.getText(R.string.spoken_description_shiftmode_off);
|
||||||
}
|
}
|
||||||
|
|
||||||
AccessibilityUtils.getInstance().speak(text);
|
AccessibilityUtils.getInstance().announceForAccessibility(mView, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -290,6 +296,6 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
|
||||||
}
|
}
|
||||||
|
|
||||||
final String text = context.getString(resId);
|
final String text = context.getString(resId);
|
||||||
AccessibilityUtils.getInstance().speak(text);
|
AccessibilityUtils.getInstance().announceForAccessibility(mView, text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import android.view.inputmethod.EditorInfo;
|
||||||
import com.android.inputmethod.keyboard.Key;
|
import com.android.inputmethod.keyboard.Key;
|
||||||
import com.android.inputmethod.keyboard.Keyboard;
|
import com.android.inputmethod.keyboard.Keyboard;
|
||||||
import com.android.inputmethod.keyboard.KeyboardId;
|
import com.android.inputmethod.keyboard.KeyboardId;
|
||||||
|
import com.android.inputmethod.latin.CollectionUtils;
|
||||||
import com.android.inputmethod.latin.R;
|
import com.android.inputmethod.latin.R;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -38,7 +39,7 @@ public class KeyCodeDescriptionMapper {
|
||||||
private static KeyCodeDescriptionMapper sInstance = new KeyCodeDescriptionMapper();
|
private static KeyCodeDescriptionMapper sInstance = new KeyCodeDescriptionMapper();
|
||||||
|
|
||||||
// Map of key labels to spoken description resource IDs
|
// Map of key labels to spoken description resource IDs
|
||||||
private final HashMap<CharSequence, Integer> mKeyLabelMap;
|
private final HashMap<CharSequence, Integer> mKeyLabelMap = CollectionUtils.newHashMap();
|
||||||
|
|
||||||
// Sparse array of spoken description resource IDs indexed by key codes
|
// Sparse array of spoken description resource IDs indexed by key codes
|
||||||
private final SparseIntArray mKeyCodeMap;
|
private final SparseIntArray mKeyCodeMap;
|
||||||
|
@ -52,7 +53,6 @@ public class KeyCodeDescriptionMapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
private KeyCodeDescriptionMapper() {
|
private KeyCodeDescriptionMapper() {
|
||||||
mKeyLabelMap = new HashMap<CharSequence, Integer>();
|
|
||||||
mKeyCodeMap = new SparseIntArray();
|
mKeyCodeMap = new SparseIntArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,10 +16,6 @@
|
||||||
|
|
||||||
package com.android.inputmethod.compat;
|
package com.android.inputmethod.compat;
|
||||||
|
|
||||||
import com.android.inputmethod.latin.LatinImeLogger;
|
|
||||||
import com.android.inputmethod.latin.SuggestedWords;
|
|
||||||
import com.android.inputmethod.latin.SuggestionSpanPickedNotificationReceiver;
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
|
@ -27,6 +23,11 @@ import android.text.Spanned;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.android.inputmethod.latin.CollectionUtils;
|
||||||
|
import com.android.inputmethod.latin.LatinImeLogger;
|
||||||
|
import com.android.inputmethod.latin.SuggestedWords;
|
||||||
|
import com.android.inputmethod.latin.SuggestionSpanPickedNotificationReceiver;
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -119,7 +120,7 @@ public class SuggestionSpanUtils {
|
||||||
} else {
|
} else {
|
||||||
spannable = new SpannableString(pickedWord);
|
spannable = new SpannableString(pickedWord);
|
||||||
}
|
}
|
||||||
final ArrayList<String> suggestionsList = new ArrayList<String>();
|
final ArrayList<String> suggestionsList = CollectionUtils.newArrayList();
|
||||||
for (int i = 0; i < suggestedWords.size(); ++i) {
|
for (int i = 0; i < suggestedWords.size(); ++i) {
|
||||||
if (suggestionsList.size() >= OBJ_SUGGESTIONS_MAX_SIZE) {
|
if (suggestionsList.size() >= OBJ_SUGGESTIONS_MAX_SIZE) {
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -16,10 +16,10 @@
|
||||||
|
|
||||||
package com.android.inputmethod.keyboard;
|
package com.android.inputmethod.keyboard;
|
||||||
|
|
||||||
|
import com.android.inputmethod.latin.Constants;
|
||||||
|
|
||||||
|
|
||||||
public class KeyDetector {
|
public class KeyDetector {
|
||||||
public static final int NOT_A_CODE = -1;
|
|
||||||
|
|
||||||
private final int mKeyHysteresisDistanceSquared;
|
private final int mKeyHysteresisDistanceSquared;
|
||||||
|
|
||||||
private Keyboard mKeyboard;
|
private Keyboard mKeyboard;
|
||||||
|
@ -59,6 +59,9 @@ public class KeyDetector {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Keyboard getKeyboard() {
|
public Keyboard getKeyboard() {
|
||||||
|
if (mKeyboard == null) {
|
||||||
|
throw new IllegalStateException("keyboard isn't set");
|
||||||
|
}
|
||||||
return mKeyboard;
|
return mKeyboard;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,7 +103,7 @@ public class KeyDetector {
|
||||||
final StringBuilder sb = new StringBuilder();
|
final StringBuilder sb = new StringBuilder();
|
||||||
boolean addDelimiter = false;
|
boolean addDelimiter = false;
|
||||||
for (final int code : codes) {
|
for (final int code : codes) {
|
||||||
if (code == NOT_A_CODE) break;
|
if (code == Constants.NOT_A_CODE) break;
|
||||||
if (addDelimiter) sb.append(", ");
|
if (addDelimiter) sb.append(", ");
|
||||||
sb.append(Keyboard.printableCode(code));
|
sb.append(Keyboard.printableCode(code));
|
||||||
addDelimiter = true;
|
addDelimiter = true;
|
||||||
|
|
|
@ -33,6 +33,7 @@ import com.android.inputmethod.keyboard.internal.KeyStyles;
|
||||||
import com.android.inputmethod.keyboard.internal.KeyboardCodesSet;
|
import com.android.inputmethod.keyboard.internal.KeyboardCodesSet;
|
||||||
import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
|
import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
|
||||||
import com.android.inputmethod.keyboard.internal.KeyboardTextsSet;
|
import com.android.inputmethod.keyboard.internal.KeyboardTextsSet;
|
||||||
|
import com.android.inputmethod.latin.CollectionUtils;
|
||||||
import com.android.inputmethod.latin.LatinImeLogger;
|
import com.android.inputmethod.latin.LatinImeLogger;
|
||||||
import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
|
import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
|
||||||
import com.android.inputmethod.latin.R;
|
import com.android.inputmethod.latin.R;
|
||||||
|
@ -134,7 +135,7 @@ public class Keyboard {
|
||||||
public final Key[] mAltCodeKeysWhileTyping;
|
public final Key[] mAltCodeKeysWhileTyping;
|
||||||
public final KeyboardIconsSet mIconsSet;
|
public final KeyboardIconsSet mIconsSet;
|
||||||
|
|
||||||
private final SparseArray<Key> mKeyCache = new SparseArray<Key>();
|
private final SparseArray<Key> mKeyCache = CollectionUtils.newSparseArray();
|
||||||
|
|
||||||
private final ProximityInfo mProximityInfo;
|
private final ProximityInfo mProximityInfo;
|
||||||
private final boolean mProximityCharsCorrectionEnabled;
|
private final boolean mProximityCharsCorrectionEnabled;
|
||||||
|
@ -219,6 +220,11 @@ public class Keyboard {
|
||||||
return code >= CODE_SPACE;
|
return code >= CODE_SPACE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return mId.toString();
|
||||||
|
}
|
||||||
|
|
||||||
public static class Params {
|
public static class Params {
|
||||||
public KeyboardId mId;
|
public KeyboardId mId;
|
||||||
public int mThemeId;
|
public int mThemeId;
|
||||||
|
@ -249,9 +255,9 @@ public class Keyboard {
|
||||||
public int GRID_WIDTH;
|
public int GRID_WIDTH;
|
||||||
public int GRID_HEIGHT;
|
public int GRID_HEIGHT;
|
||||||
|
|
||||||
public final HashSet<Key> mKeys = new HashSet<Key>();
|
public final HashSet<Key> mKeys = CollectionUtils.newHashSet();
|
||||||
public final ArrayList<Key> mShiftKeys = new ArrayList<Key>();
|
public final ArrayList<Key> mShiftKeys = CollectionUtils.newArrayList();
|
||||||
public final ArrayList<Key> mAltCodeKeysWhileTyping = new ArrayList<Key>();
|
public final ArrayList<Key> mAltCodeKeysWhileTyping = CollectionUtils.newArrayList();
|
||||||
public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet();
|
public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet();
|
||||||
public final KeyboardCodesSet mCodesSet = new KeyboardCodesSet();
|
public final KeyboardCodesSet mCodesSet = new KeyboardCodesSet();
|
||||||
public final KeyboardTextsSet mTextsSet = new KeyboardTextsSet();
|
public final KeyboardTextsSet mTextsSet = new KeyboardTextsSet();
|
||||||
|
@ -278,9 +284,10 @@ public class Keyboard {
|
||||||
public void load(String[] data) {
|
public void load(String[] data) {
|
||||||
final int dataLength = data.length;
|
final int dataLength = data.length;
|
||||||
if (dataLength % TOUCH_POSITION_CORRECTION_RECORD_SIZE != 0) {
|
if (dataLength % TOUCH_POSITION_CORRECTION_RECORD_SIZE != 0) {
|
||||||
if (LatinImeLogger.sDBG)
|
if (LatinImeLogger.sDBG) {
|
||||||
throw new RuntimeException(
|
throw new RuntimeException(
|
||||||
"the size of touch position correction data is invalid");
|
"the size of touch position correction data is invalid");
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -865,10 +872,12 @@ public class Keyboard {
|
||||||
final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
|
final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
|
||||||
R.styleable.Keyboard);
|
R.styleable.Keyboard);
|
||||||
try {
|
try {
|
||||||
if (a.hasValue(R.styleable.Keyboard_horizontalGap))
|
if (a.hasValue(R.styleable.Keyboard_horizontalGap)) {
|
||||||
throw new XmlParseUtils.IllegalAttribute(parser, "horizontalGap");
|
throw new XmlParseUtils.IllegalAttribute(parser, "horizontalGap");
|
||||||
if (a.hasValue(R.styleable.Keyboard_verticalGap))
|
}
|
||||||
|
if (a.hasValue(R.styleable.Keyboard_verticalGap)) {
|
||||||
throw new XmlParseUtils.IllegalAttribute(parser, "verticalGap");
|
throw new XmlParseUtils.IllegalAttribute(parser, "verticalGap");
|
||||||
|
}
|
||||||
return new Row(mResources, mParams, parser, mCurrentY);
|
return new Row(mResources, mParams, parser, mCurrentY);
|
||||||
} finally {
|
} finally {
|
||||||
a.recycle();
|
a.recycle();
|
||||||
|
@ -916,7 +925,9 @@ public class Keyboard {
|
||||||
throws XmlPullParserException, IOException {
|
throws XmlPullParserException, IOException {
|
||||||
if (skip) {
|
if (skip) {
|
||||||
XmlParseUtils.checkEndTag(TAG_KEY, parser);
|
XmlParseUtils.checkEndTag(TAG_KEY, parser);
|
||||||
if (DEBUG) startEndTag("<%s /> skipped", TAG_KEY);
|
if (DEBUG) {
|
||||||
|
startEndTag("<%s /> skipped", TAG_KEY);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
final Key key = new Key(mResources, mParams, row, parser);
|
final Key key = new Key(mResources, mParams, row, parser);
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
|
@ -1094,9 +1105,9 @@ public class Keyboard {
|
||||||
|
|
||||||
private boolean parseCaseCondition(XmlPullParser parser) {
|
private boolean parseCaseCondition(XmlPullParser parser) {
|
||||||
final KeyboardId id = mParams.mId;
|
final KeyboardId id = mParams.mId;
|
||||||
if (id == null)
|
if (id == null) {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
|
final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
|
||||||
R.styleable.Keyboard_Case);
|
R.styleable.Keyboard_Case);
|
||||||
try {
|
try {
|
||||||
|
@ -1200,9 +1211,9 @@ public class Keyboard {
|
||||||
// If <case> does not have "index" attribute, that means this <case> is wild-card for
|
// If <case> does not have "index" attribute, that means this <case> is wild-card for
|
||||||
// the attribute.
|
// the attribute.
|
||||||
final TypedValue v = a.peekValue(index);
|
final TypedValue v = a.peekValue(index);
|
||||||
if (v == null)
|
if (v == null) {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
if (isIntegerValue(v)) {
|
if (isIntegerValue(v)) {
|
||||||
return intValue == a.getInt(index, 0);
|
return intValue == a.getInt(index, 0);
|
||||||
} else if (isStringValue(v)) {
|
} else if (isStringValue(v)) {
|
||||||
|
@ -1213,9 +1224,10 @@ public class Keyboard {
|
||||||
|
|
||||||
private static boolean stringArrayContains(String[] array, String value) {
|
private static boolean stringArrayContains(String[] array, String value) {
|
||||||
for (final String elem : array) {
|
for (final String elem : array) {
|
||||||
if (elem.equals(value))
|
if (elem.equals(value)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1237,16 +1249,18 @@ public class Keyboard {
|
||||||
TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser),
|
TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser),
|
||||||
R.styleable.Keyboard_Key);
|
R.styleable.Keyboard_Key);
|
||||||
try {
|
try {
|
||||||
if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName))
|
if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName)) {
|
||||||
throw new XmlParseUtils.ParseException("<" + TAG_KEY_STYLE
|
throw new XmlParseUtils.ParseException("<" + TAG_KEY_STYLE
|
||||||
+ "/> needs styleName attribute", parser);
|
+ "/> needs styleName attribute", parser);
|
||||||
|
}
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
startEndTag("<%s styleName=%s />%s", TAG_KEY_STYLE,
|
startEndTag("<%s styleName=%s />%s", TAG_KEY_STYLE,
|
||||||
keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName),
|
keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName),
|
||||||
skip ? " skipped" : "");
|
skip ? " skipped" : "");
|
||||||
}
|
}
|
||||||
if (!skip)
|
if (!skip) {
|
||||||
mParams.mKeyStyles.parseKeyStyleAttributes(keyStyleAttr, keyAttrs, parser);
|
mParams.mKeyStyles.parseKeyStyleAttributes(keyStyleAttr, keyAttrs, parser);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
keyStyleAttr.recycle();
|
keyStyleAttr.recycle();
|
||||||
keyAttrs.recycle();
|
keyAttrs.recycle();
|
||||||
|
@ -1267,8 +1281,9 @@ public class Keyboard {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void endRow(Row row) {
|
private void endRow(Row row) {
|
||||||
if (mCurrentRow == null)
|
if (mCurrentRow == null) {
|
||||||
throw new InflateException("orphan end row tag");
|
throw new InflateException("orphan end row tag");
|
||||||
|
}
|
||||||
if (mRightEdgeKey != null) {
|
if (mRightEdgeKey != null) {
|
||||||
mRightEdgeKey.markAsRightEdge(mParams);
|
mRightEdgeKey.markAsRightEdge(mParams);
|
||||||
mRightEdgeKey = null;
|
mRightEdgeKey = null;
|
||||||
|
@ -1304,8 +1319,9 @@ public class Keyboard {
|
||||||
public static float getDimensionOrFraction(TypedArray a, int index, int base,
|
public static float getDimensionOrFraction(TypedArray a, int index, int base,
|
||||||
float defValue) {
|
float defValue) {
|
||||||
final TypedValue value = a.peekValue(index);
|
final TypedValue value = a.peekValue(index);
|
||||||
if (value == null)
|
if (value == null) {
|
||||||
return defValue;
|
return defValue;
|
||||||
|
}
|
||||||
if (isFractionValue(value)) {
|
if (isFractionValue(value)) {
|
||||||
return a.getFraction(index, base, base, defValue);
|
return a.getFraction(index, base, base, defValue);
|
||||||
} else if (isDimensionValue(value)) {
|
} else if (isDimensionValue(value)) {
|
||||||
|
@ -1316,8 +1332,9 @@ public class Keyboard {
|
||||||
|
|
||||||
public static int getEnumValue(TypedArray a, int index, int defValue) {
|
public static int getEnumValue(TypedArray a, int index, int defValue) {
|
||||||
final TypedValue value = a.peekValue(index);
|
final TypedValue value = a.peekValue(index);
|
||||||
if (value == null)
|
if (value == null) {
|
||||||
return defValue;
|
return defValue;
|
||||||
|
}
|
||||||
if (isIntegerValue(value)) {
|
if (isIntegerValue(value)) {
|
||||||
return a.getInt(index, defValue);
|
return a.getInt(index, defValue);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package com.android.inputmethod.keyboard;
|
package com.android.inputmethod.keyboard;
|
||||||
|
|
||||||
|
import com.android.inputmethod.latin.Constants;
|
||||||
import com.android.inputmethod.latin.InputPointers;
|
import com.android.inputmethod.latin.InputPointers;
|
||||||
|
|
||||||
public interface KeyboardActionListener {
|
public interface KeyboardActionListener {
|
||||||
|
@ -44,21 +45,16 @@ public interface KeyboardActionListener {
|
||||||
*
|
*
|
||||||
* @param primaryCode this is the code of the key that was pressed
|
* @param primaryCode this is the code of the key that was pressed
|
||||||
* @param x x-coordinate pixel of touched event. If {@link #onCodeInput} is not called by
|
* @param x x-coordinate pixel of touched event. If {@link #onCodeInput} is not called by
|
||||||
* {@link PointerTracker} or so, the value should be {@link #NOT_A_TOUCH_COORDINATE}.
|
* {@link PointerTracker} or so, the value should be
|
||||||
* If it's called on insertion from the suggestion strip, it should be
|
* {@link Constants#NOT_A_COORDINATE}. If it's called on insertion from the
|
||||||
* {@link #SUGGESTION_STRIP_COORDINATE}.
|
* suggestion strip, it should be {@link Constants#SUGGESTION_STRIP_COORDINATE}.
|
||||||
* @param y y-coordinate pixel of touched event. If {@link #onCodeInput} is not called by
|
* @param y y-coordinate pixel of touched event. If {@link #onCodeInput} is not called by
|
||||||
* {@link PointerTracker} or so, the value should be {@link #NOT_A_TOUCH_COORDINATE}.
|
* {@link PointerTracker} or so, the value should be
|
||||||
* If it's called on insertion from the suggestion strip, it should be
|
* {@link Constants#NOT_A_COORDINATE}.If it's called on insertion from the
|
||||||
* {@link #SUGGESTION_STRIP_COORDINATE}.
|
* suggestion strip, it should be {@link Constants#SUGGESTION_STRIP_COORDINATE}.
|
||||||
*/
|
*/
|
||||||
public void onCodeInput(int primaryCode, int x, int y);
|
public void onCodeInput(int primaryCode, int x, int y);
|
||||||
|
|
||||||
// See {@link Adapter#isInvalidCoordinate(int)}.
|
|
||||||
public static final int NOT_A_TOUCH_COORDINATE = -1;
|
|
||||||
public static final int SUGGESTION_STRIP_COORDINATE = -2;
|
|
||||||
public static final int SPELL_CHECKER_COORDINATE = -3;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a sequence of characters to the listener.
|
* Sends a sequence of characters to the listener.
|
||||||
*
|
*
|
||||||
|
@ -119,9 +115,9 @@ public interface KeyboardActionListener {
|
||||||
|
|
||||||
// TODO: Remove this method when the vertical correction is removed.
|
// TODO: Remove this method when the vertical correction is removed.
|
||||||
public static boolean isInvalidCoordinate(int coordinate) {
|
public static boolean isInvalidCoordinate(int coordinate) {
|
||||||
// Detect {@link KeyboardActionListener#NOT_A_TOUCH_COORDINATE},
|
// Detect {@link Constants#NOT_A_COORDINATE},
|
||||||
// {@link KeyboardActionListener#SUGGESTION_STRIP_COORDINATE}, and
|
// {@link Constants#SUGGESTION_STRIP_COORDINATE}, and
|
||||||
// {@link KeyboardActionListener#SPELL_CHECKER_COORDINATE}.
|
// {@link Constants#SPELL_CHECKER_COORDINATE}.
|
||||||
return coordinate < 0;
|
return coordinate < 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ import android.view.inputmethod.InputMethodSubtype;
|
||||||
|
|
||||||
import com.android.inputmethod.compat.EditorInfoCompatUtils;
|
import com.android.inputmethod.compat.EditorInfoCompatUtils;
|
||||||
import com.android.inputmethod.keyboard.KeyboardLayoutSet.Params.ElementParams;
|
import com.android.inputmethod.keyboard.KeyboardLayoutSet.Params.ElementParams;
|
||||||
|
import com.android.inputmethod.latin.CollectionUtils;
|
||||||
import com.android.inputmethod.latin.InputAttributes;
|
import com.android.inputmethod.latin.InputAttributes;
|
||||||
import com.android.inputmethod.latin.InputTypeUtils;
|
import com.android.inputmethod.latin.InputTypeUtils;
|
||||||
import com.android.inputmethod.latin.LatinImeLogger;
|
import com.android.inputmethod.latin.LatinImeLogger;
|
||||||
|
@ -71,7 +72,7 @@ public class KeyboardLayoutSet {
|
||||||
private final Params mParams;
|
private final Params mParams;
|
||||||
|
|
||||||
private static final HashMap<KeyboardId, SoftReference<Keyboard>> sKeyboardCache =
|
private static final HashMap<KeyboardId, SoftReference<Keyboard>> sKeyboardCache =
|
||||||
new HashMap<KeyboardId, SoftReference<Keyboard>>();
|
CollectionUtils.newHashMap();
|
||||||
private static final KeysCache sKeysCache = new KeysCache();
|
private static final KeysCache sKeysCache = new KeysCache();
|
||||||
|
|
||||||
public static class KeyboardLayoutSetException extends RuntimeException {
|
public static class KeyboardLayoutSetException extends RuntimeException {
|
||||||
|
@ -84,11 +85,7 @@ public class KeyboardLayoutSet {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class KeysCache {
|
public static class KeysCache {
|
||||||
private final HashMap<Key, Key> mMap;
|
private final HashMap<Key, Key> mMap = CollectionUtils.newHashMap();
|
||||||
|
|
||||||
public KeysCache() {
|
|
||||||
mMap = new HashMap<Key, Key>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clear() {
|
public void clear() {
|
||||||
mMap.clear();
|
mMap.clear();
|
||||||
|
@ -120,7 +117,7 @@ public class KeyboardLayoutSet {
|
||||||
int mWidth;
|
int mWidth;
|
||||||
// Sparse array of KeyboardLayoutSet element parameters indexed by element's id.
|
// Sparse array of KeyboardLayoutSet element parameters indexed by element's id.
|
||||||
final SparseArray<ElementParams> mKeyboardLayoutSetElementIdToParamsMap =
|
final SparseArray<ElementParams> mKeyboardLayoutSetElementIdToParamsMap =
|
||||||
new SparseArray<ElementParams>();
|
CollectionUtils.newSparseArray();
|
||||||
|
|
||||||
static class ElementParams {
|
static class ElementParams {
|
||||||
int mKeyboardXmlId;
|
int mKeyboardXmlId;
|
||||||
|
|
|
@ -21,7 +21,6 @@ import android.content.SharedPreferences;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.ContextThemeWrapper;
|
import android.view.ContextThemeWrapper;
|
||||||
import android.view.InflateException;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
|
@ -38,7 +37,7 @@ import com.android.inputmethod.latin.LatinImeLogger;
|
||||||
import com.android.inputmethod.latin.R;
|
import com.android.inputmethod.latin.R;
|
||||||
import com.android.inputmethod.latin.SettingsValues;
|
import com.android.inputmethod.latin.SettingsValues;
|
||||||
import com.android.inputmethod.latin.SubtypeSwitcher;
|
import com.android.inputmethod.latin.SubtypeSwitcher;
|
||||||
import com.android.inputmethod.latin.Utils;
|
import com.android.inputmethod.latin.WordComposer;
|
||||||
|
|
||||||
public class KeyboardSwitcher implements KeyboardState.SwitchActions {
|
public class KeyboardSwitcher implements KeyboardState.SwitchActions {
|
||||||
private static final String TAG = KeyboardSwitcher.class.getSimpleName();
|
private static final String TAG = KeyboardSwitcher.class.getSimpleName();
|
||||||
|
@ -46,24 +45,24 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions {
|
||||||
public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20110916";
|
public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20110916";
|
||||||
|
|
||||||
static class KeyboardTheme {
|
static class KeyboardTheme {
|
||||||
public final String mName;
|
|
||||||
public final int mThemeId;
|
public final int mThemeId;
|
||||||
public final int mStyleId;
|
public final int mStyleId;
|
||||||
|
|
||||||
public KeyboardTheme(String name, int themeId, int styleId) {
|
// Note: The themeId should be aligned with "themeId" attribute of Keyboard style
|
||||||
mName = name;
|
// in values/style.xml.
|
||||||
|
public KeyboardTheme(int themeId, int styleId) {
|
||||||
mThemeId = themeId;
|
mThemeId = themeId;
|
||||||
mStyleId = styleId;
|
mStyleId = styleId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final KeyboardTheme[] KEYBOARD_THEMES = {
|
private static final KeyboardTheme[] KEYBOARD_THEMES = {
|
||||||
new KeyboardTheme("Basic", 0, R.style.KeyboardTheme),
|
new KeyboardTheme(0, R.style.KeyboardTheme),
|
||||||
new KeyboardTheme("HighContrast", 1, R.style.KeyboardTheme_HighContrast),
|
new KeyboardTheme(1, R.style.KeyboardTheme_HighContrast),
|
||||||
new KeyboardTheme("Stone", 6, R.style.KeyboardTheme_Stone),
|
new KeyboardTheme(6, R.style.KeyboardTheme_Stone),
|
||||||
new KeyboardTheme("Stone.Bold", 7, R.style.KeyboardTheme_Stone_Bold),
|
new KeyboardTheme(7, R.style.KeyboardTheme_Stone_Bold),
|
||||||
new KeyboardTheme("GingerBread", 8, R.style.KeyboardTheme_Gingerbread),
|
new KeyboardTheme(8, R.style.KeyboardTheme_Gingerbread),
|
||||||
new KeyboardTheme("IceCreamSandwich", 5, R.style.KeyboardTheme_IceCreamSandwich),
|
new KeyboardTheme(5, R.style.KeyboardTheme_IceCreamSandwich),
|
||||||
};
|
};
|
||||||
|
|
||||||
private SubtypeSwitcher mSubtypeSwitcher;
|
private SubtypeSwitcher mSubtypeSwitcher;
|
||||||
|
@ -354,22 +353,9 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions {
|
||||||
mKeyboardView.closing();
|
mKeyboardView.closing();
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils.GCUtils.getInstance().reset();
|
|
||||||
boolean tryGC = true;
|
|
||||||
for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
|
|
||||||
try {
|
|
||||||
setContextThemeWrapper(mLatinIME, mKeyboardTheme);
|
setContextThemeWrapper(mLatinIME, mKeyboardTheme);
|
||||||
mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
|
mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
|
||||||
R.layout.input_view, null);
|
R.layout.input_view, null);
|
||||||
tryGC = false;
|
|
||||||
} catch (OutOfMemoryError e) {
|
|
||||||
Log.w(TAG, "load keyboard failed: " + e);
|
|
||||||
tryGC = Utils.GCUtils.getInstance().tryGCOrWait(mKeyboardTheme.mName, e);
|
|
||||||
} catch (InflateException e) {
|
|
||||||
Log.w(TAG, "load keyboard failed: " + e);
|
|
||||||
tryGC = Utils.GCUtils.getInstance().tryGCOrWait(mKeyboardTheme.mName, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mKeyboardView = (MainKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view);
|
mKeyboardView = (MainKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view);
|
||||||
if (isHardwareAcceleratedDrawingEnabled) {
|
if (isHardwareAcceleratedDrawingEnabled) {
|
||||||
|
@ -402,4 +388,16 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getManualCapsMode() {
|
||||||
|
switch (getKeyboard().mId.mElementId) {
|
||||||
|
case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
|
||||||
|
case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
|
||||||
|
return WordComposer.CAPS_MODE_MANUAL_SHIFT_LOCKED;
|
||||||
|
case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
|
||||||
|
return WordComposer.CAPS_MODE_MANUAL_SHIFTED;
|
||||||
|
default:
|
||||||
|
return WordComposer.CAPS_MODE_OFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ import android.graphics.Typeface;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Log;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
@ -38,6 +39,7 @@ import android.view.ViewGroup;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.android.inputmethod.keyboard.internal.PreviewPlacerView;
|
import com.android.inputmethod.keyboard.internal.PreviewPlacerView;
|
||||||
|
import com.android.inputmethod.latin.CollectionUtils;
|
||||||
import com.android.inputmethod.latin.Constants;
|
import com.android.inputmethod.latin.Constants;
|
||||||
import com.android.inputmethod.latin.LatinImeLogger;
|
import com.android.inputmethod.latin.LatinImeLogger;
|
||||||
import com.android.inputmethod.latin.R;
|
import com.android.inputmethod.latin.R;
|
||||||
|
@ -78,8 +80,12 @@ import java.util.HashSet;
|
||||||
* @attr ref R.styleable#KeyboardView_shadowRadius
|
* @attr ref R.styleable#KeyboardView_shadowRadius
|
||||||
*/
|
*/
|
||||||
public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
||||||
|
private static final String TAG = KeyboardView.class.getSimpleName();
|
||||||
|
|
||||||
// Miscellaneous constants
|
// Miscellaneous constants
|
||||||
private static final int[] LONG_PRESSABLE_STATE_SET = { android.R.attr.state_long_pressable };
|
private static final int[] LONG_PRESSABLE_STATE_SET = { android.R.attr.state_long_pressable };
|
||||||
|
private static final float UNDEFINED_RATIO = -1.0f;
|
||||||
|
private static final int UNDEFINED_DIMENSION = -1;
|
||||||
|
|
||||||
// XML attributes
|
// XML attributes
|
||||||
protected final float mVerticalCorrection;
|
protected final float mVerticalCorrection;
|
||||||
|
@ -103,23 +109,19 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
||||||
|
|
||||||
// Key preview
|
// Key preview
|
||||||
private final int mKeyPreviewLayoutId;
|
private final int mKeyPreviewLayoutId;
|
||||||
|
private final SparseArray<TextView> mKeyPreviewTexts = CollectionUtils.newSparseArray();
|
||||||
protected final KeyPreviewDrawParams mKeyPreviewDrawParams;
|
protected final KeyPreviewDrawParams mKeyPreviewDrawParams;
|
||||||
private boolean mShowKeyPreviewPopup = true;
|
private boolean mShowKeyPreviewPopup = true;
|
||||||
private int mDelayAfterPreview;
|
private int mDelayAfterPreview;
|
||||||
private final PreviewPlacerView mPreviewPlacerView;
|
private final PreviewPlacerView mPreviewPlacerView;
|
||||||
|
|
||||||
/** True if {@link KeyboardView} should handle gesture events. */
|
|
||||||
protected boolean mShouldHandleGesture;
|
|
||||||
|
|
||||||
// Drawing
|
// Drawing
|
||||||
/** True if the entire keyboard needs to be dimmed. */
|
/** True if the entire keyboard needs to be dimmed. */
|
||||||
private boolean mNeedsToDimEntireKeyboard;
|
private boolean mNeedsToDimEntireKeyboard;
|
||||||
/** Whether the keyboard bitmap buffer needs to be redrawn before it's blitted. **/
|
|
||||||
private boolean mBufferNeedsUpdate;
|
|
||||||
/** True if all keys should be drawn */
|
/** True if all keys should be drawn */
|
||||||
private boolean mInvalidateAllKeys;
|
private boolean mInvalidateAllKeys;
|
||||||
/** The keys that should be drawn */
|
/** The keys that should be drawn */
|
||||||
private final HashSet<Key> mInvalidatedKeys = new HashSet<Key>();
|
private final HashSet<Key> mInvalidatedKeys = CollectionUtils.newHashSet();
|
||||||
/** The working rectangle variable */
|
/** The working rectangle variable */
|
||||||
private final Rect mWorkingRect = new Rect();
|
private final Rect mWorkingRect = new Rect();
|
||||||
/** The keyboard bitmap buffer for faster updates */
|
/** The keyboard bitmap buffer for faster updates */
|
||||||
|
@ -131,9 +133,9 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
||||||
private final Paint mPaint = new Paint();
|
private final Paint mPaint = new Paint();
|
||||||
private final Paint.FontMetrics mFontMetrics = new Paint.FontMetrics();
|
private final Paint.FontMetrics mFontMetrics = new Paint.FontMetrics();
|
||||||
// This sparse array caches key label text height in pixel indexed by key label text size.
|
// This sparse array caches key label text height in pixel indexed by key label text size.
|
||||||
private static final SparseArray<Float> sTextHeightCache = new SparseArray<Float>();
|
private static final SparseArray<Float> sTextHeightCache = CollectionUtils.newSparseArray();
|
||||||
// This sparse array caches key label text width in pixel indexed by key label text size.
|
// This sparse array caches key label text width in pixel indexed by key label text size.
|
||||||
private static final SparseArray<Float> sTextWidthCache = new SparseArray<Float>();
|
private static final SparseArray<Float> sTextWidthCache = CollectionUtils.newSparseArray();
|
||||||
private static final char[] KEY_LABEL_REFERENCE_CHAR = { 'M' };
|
private static final char[] KEY_LABEL_REFERENCE_CHAR = { 'M' };
|
||||||
private static final char[] KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR = { '8' };
|
private static final char[] KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR = { '8' };
|
||||||
|
|
||||||
|
@ -153,7 +155,10 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
||||||
final PointerTracker tracker = (PointerTracker) msg.obj;
|
final PointerTracker tracker = (PointerTracker) msg.obj;
|
||||||
switch (msg.what) {
|
switch (msg.what) {
|
||||||
case MSG_DISMISS_KEY_PREVIEW:
|
case MSG_DISMISS_KEY_PREVIEW:
|
||||||
tracker.getKeyPreviewText().setVisibility(View.INVISIBLE);
|
final TextView previewText = keyboardView.mKeyPreviewTexts.get(tracker.mPointerId);
|
||||||
|
if (previewText != null) {
|
||||||
|
previewText.setVisibility(INVISIBLE);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -166,7 +171,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
||||||
removeMessages(MSG_DISMISS_KEY_PREVIEW, tracker);
|
removeMessages(MSG_DISMISS_KEY_PREVIEW, tracker);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancelAllDismissKeyPreviews() {
|
private void cancelAllDismissKeyPreviews() {
|
||||||
removeMessages(MSG_DISMISS_KEY_PREVIEW);
|
removeMessages(MSG_DISMISS_KEY_PREVIEW);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,7 +204,6 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
||||||
private final float mKeyHintLetterRatio;
|
private final float mKeyHintLetterRatio;
|
||||||
private final float mKeyShiftedLetterHintRatio;
|
private final float mKeyShiftedLetterHintRatio;
|
||||||
private final float mKeyHintLabelRatio;
|
private final float mKeyHintLabelRatio;
|
||||||
private static final float UNDEFINED_RATIO = -1.0f;
|
|
||||||
|
|
||||||
public final Rect mPadding = new Rect();
|
public final Rect mPadding = new Rect();
|
||||||
public int mKeyLetterSize;
|
public int mKeyLetterSize;
|
||||||
|
@ -211,26 +215,22 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
||||||
public int mKeyHintLabelSize;
|
public int mKeyHintLabelSize;
|
||||||
public int mAnimAlpha;
|
public int mAnimAlpha;
|
||||||
|
|
||||||
public KeyDrawParams(TypedArray a) {
|
public KeyDrawParams(final TypedArray a) {
|
||||||
mKeyBackground = a.getDrawable(R.styleable.KeyboardView_keyBackground);
|
mKeyBackground = a.getDrawable(R.styleable.KeyboardView_keyBackground);
|
||||||
if (a.hasValue(R.styleable.KeyboardView_keyLetterSize)) {
|
if (!isValidFraction(mKeyLetterRatio = getFraction(a,
|
||||||
mKeyLetterRatio = UNDEFINED_RATIO;
|
R.styleable.KeyboardView_keyLetterSize))) {
|
||||||
mKeyLetterSize = a.getDimensionPixelSize(R.styleable.KeyboardView_keyLetterSize, 0);
|
mKeyLetterSize = getDimensionPixelSize(a, R.styleable.KeyboardView_keyLetterSize);
|
||||||
} else {
|
|
||||||
mKeyLetterRatio = getRatio(a, R.styleable.KeyboardView_keyLetterRatio);
|
|
||||||
}
|
}
|
||||||
if (a.hasValue(R.styleable.KeyboardView_keyLabelSize)) {
|
if (!isValidFraction(mKeyLabelRatio = getFraction(a,
|
||||||
mKeyLabelRatio = UNDEFINED_RATIO;
|
R.styleable.KeyboardView_keyLabelSize))) {
|
||||||
mKeyLabelSize = a.getDimensionPixelSize(R.styleable.KeyboardView_keyLabelSize, 0);
|
mKeyLabelSize = getDimensionPixelSize(a, R.styleable.KeyboardView_keyLabelSize);
|
||||||
} else {
|
|
||||||
mKeyLabelRatio = getRatio(a, R.styleable.KeyboardView_keyLabelRatio);
|
|
||||||
}
|
}
|
||||||
mKeyLargeLabelRatio = getRatio(a, R.styleable.KeyboardView_keyLargeLabelRatio);
|
mKeyLargeLabelRatio = getFraction(a, R.styleable.KeyboardView_keyLargeLabelRatio);
|
||||||
mKeyLargeLetterRatio = getRatio(a, R.styleable.KeyboardView_keyLargeLetterRatio);
|
mKeyLargeLetterRatio = getFraction(a, R.styleable.KeyboardView_keyLargeLetterRatio);
|
||||||
mKeyHintLetterRatio = getRatio(a, R.styleable.KeyboardView_keyHintLetterRatio);
|
mKeyHintLetterRatio = getFraction(a, R.styleable.KeyboardView_keyHintLetterRatio);
|
||||||
mKeyShiftedLetterHintRatio = getRatio(a,
|
mKeyShiftedLetterHintRatio = getFraction(a,
|
||||||
R.styleable.KeyboardView_keyShiftedLetterHintRatio);
|
R.styleable.KeyboardView_keyShiftedLetterHintRatio);
|
||||||
mKeyHintLabelRatio = getRatio(a, R.styleable.KeyboardView_keyHintLabelRatio);
|
mKeyHintLabelRatio = getFraction(a, R.styleable.KeyboardView_keyHintLabelRatio);
|
||||||
mKeyLabelHorizontalPadding = a.getDimension(
|
mKeyLabelHorizontalPadding = a.getDimension(
|
||||||
R.styleable.KeyboardView_keyLabelHorizontalPadding, 0);
|
R.styleable.KeyboardView_keyLabelHorizontalPadding, 0);
|
||||||
mKeyHintLetterPadding = a.getDimension(
|
mKeyHintLetterPadding = a.getDimension(
|
||||||
|
@ -257,10 +257,10 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateKeyHeight(int keyHeight) {
|
public void updateKeyHeight(int keyHeight) {
|
||||||
if (mKeyLetterRatio >= 0.0f) {
|
if (isValidFraction(mKeyLetterRatio)) {
|
||||||
mKeyLetterSize = (int)(keyHeight * mKeyLetterRatio);
|
mKeyLetterSize = (int)(keyHeight * mKeyLetterRatio);
|
||||||
}
|
}
|
||||||
if (mKeyLabelRatio >= 0.0f) {
|
if (isValidFraction(mKeyLabelRatio)) {
|
||||||
mKeyLabelSize = (int)(keyHeight * mKeyLabelRatio);
|
mKeyLabelSize = (int)(keyHeight * mKeyLabelRatio);
|
||||||
}
|
}
|
||||||
mKeyLargeLabelSize = (int)(keyHeight * mKeyLargeLabelRatio);
|
mKeyLargeLabelSize = (int)(keyHeight * mKeyLargeLabelRatio);
|
||||||
|
@ -335,7 +335,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
||||||
R.styleable.KeyboardView_keyPreviewOffset, 0);
|
R.styleable.KeyboardView_keyPreviewOffset, 0);
|
||||||
mPreviewHeight = a.getDimensionPixelSize(
|
mPreviewHeight = a.getDimensionPixelSize(
|
||||||
R.styleable.KeyboardView_keyPreviewHeight, 80);
|
R.styleable.KeyboardView_keyPreviewHeight, 80);
|
||||||
mPreviewTextRatio = getRatio(a, R.styleable.KeyboardView_keyPreviewTextRatio);
|
mPreviewTextRatio = getFraction(a, R.styleable.KeyboardView_keyPreviewTextRatio);
|
||||||
mPreviewTextColor = a.getColor(R.styleable.KeyboardView_keyPreviewTextColor, 0);
|
mPreviewTextColor = a.getColor(R.styleable.KeyboardView_keyPreviewTextColor, 0);
|
||||||
mLingerTimeout = a.getInt(R.styleable.KeyboardView_keyPreviewLingerTimeout, 0);
|
mLingerTimeout = a.getInt(R.styleable.KeyboardView_keyPreviewLingerTimeout, 0);
|
||||||
|
|
||||||
|
@ -367,9 +367,9 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
||||||
|
|
||||||
final TypedArray a = context.obtainStyledAttributes(
|
final TypedArray a = context.obtainStyledAttributes(
|
||||||
attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
|
attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
|
||||||
|
|
||||||
mKeyDrawParams = new KeyDrawParams(a);
|
mKeyDrawParams = new KeyDrawParams(a);
|
||||||
mKeyPreviewDrawParams = new KeyPreviewDrawParams(a, mKeyDrawParams);
|
mKeyPreviewDrawParams = new KeyPreviewDrawParams(a, mKeyDrawParams);
|
||||||
|
mDelayAfterPreview = mKeyPreviewDrawParams.mLingerTimeout;
|
||||||
mKeyPreviewLayoutId = a.getResourceId(R.styleable.KeyboardView_keyPreviewLayout, 0);
|
mKeyPreviewLayoutId = a.getResourceId(R.styleable.KeyboardView_keyPreviewLayout, 0);
|
||||||
if (mKeyPreviewLayoutId == 0) {
|
if (mKeyPreviewLayoutId == 0) {
|
||||||
mShowKeyPreviewPopup = false;
|
mShowKeyPreviewPopup = false;
|
||||||
|
@ -378,17 +378,30 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
||||||
R.styleable.KeyboardView_verticalCorrection, 0);
|
R.styleable.KeyboardView_verticalCorrection, 0);
|
||||||
mMoreKeysLayout = a.getResourceId(R.styleable.KeyboardView_moreKeysLayout, 0);
|
mMoreKeysLayout = a.getResourceId(R.styleable.KeyboardView_moreKeysLayout, 0);
|
||||||
mBackgroundDimAlpha = a.getInt(R.styleable.KeyboardView_backgroundDimAlpha, 0);
|
mBackgroundDimAlpha = a.getInt(R.styleable.KeyboardView_backgroundDimAlpha, 0);
|
||||||
mPreviewPlacerView = new PreviewPlacerView(context, a);
|
|
||||||
a.recycle();
|
a.recycle();
|
||||||
|
|
||||||
mDelayAfterPreview = mKeyPreviewDrawParams.mLingerTimeout;
|
mPreviewPlacerView = new PreviewPlacerView(context, attrs);
|
||||||
|
|
||||||
mPaint.setAntiAlias(true);
|
mPaint.setAntiAlias(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read fraction value in TypedArray as float.
|
static boolean isValidFraction(final float fraction) {
|
||||||
/* package */ static float getRatio(TypedArray a, int index) {
|
return fraction >= 0.0f;
|
||||||
return a.getFraction(index, 1000, 1000, 1) / 1000.0f;
|
}
|
||||||
|
|
||||||
|
static float getFraction(final TypedArray a, final int index) {
|
||||||
|
final TypedValue value = a.peekValue(index);
|
||||||
|
if (value == null || value.type != TypedValue.TYPE_FRACTION) {
|
||||||
|
return UNDEFINED_RATIO;
|
||||||
|
}
|
||||||
|
return a.getFraction(index, 1, 1, UNDEFINED_RATIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getDimensionPixelSize(final TypedArray a, final int index) {
|
||||||
|
final TypedValue value = a.peekValue(index);
|
||||||
|
if (value == null || value.type != TypedValue.TYPE_DIMENSION) {
|
||||||
|
return UNDEFINED_DIMENSION;
|
||||||
|
}
|
||||||
|
return a.getDimensionPixelSize(index, UNDEFINED_DIMENSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -438,9 +451,8 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
||||||
return mShowKeyPreviewPopup;
|
return mShowKeyPreviewPopup;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setGestureHandlingMode(boolean shouldHandleGesture,
|
public void setGesturePreviewMode(boolean drawsGesturePreviewTrail,
|
||||||
boolean drawsGesturePreviewTrail, boolean drawsGestureFloatingPreviewText) {
|
boolean drawsGestureFloatingPreviewText) {
|
||||||
mShouldHandleGesture = shouldHandleGesture;
|
|
||||||
mPreviewPlacerView.setGesturePreviewMode(
|
mPreviewPlacerView.setGesturePreviewMode(
|
||||||
drawsGesturePreviewTrail, drawsGestureFloatingPreviewText);
|
drawsGesturePreviewTrail, drawsGestureFloatingPreviewText);
|
||||||
}
|
}
|
||||||
|
@ -463,16 +475,12 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
||||||
onDrawKeyboard(canvas);
|
onDrawKeyboard(canvas);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (mBufferNeedsUpdate || mOffscreenBuffer == null) {
|
|
||||||
mBufferNeedsUpdate = false;
|
final boolean bufferNeedsUpdates = mInvalidateAllKeys || !mInvalidatedKeys.isEmpty();
|
||||||
|
if (bufferNeedsUpdates || mOffscreenBuffer == null) {
|
||||||
if (maybeAllocateOffscreenBuffer()) {
|
if (maybeAllocateOffscreenBuffer()) {
|
||||||
mInvalidateAllKeys = true;
|
mInvalidateAllKeys = true;
|
||||||
// TODO: Stop using the offscreen canvas even when in software rendering
|
maybeCreateOffscreenCanvas();
|
||||||
if (mOffscreenCanvas != null) {
|
|
||||||
mOffscreenCanvas.setBitmap(mOffscreenBuffer);
|
|
||||||
} else {
|
|
||||||
mOffscreenCanvas = new Canvas(mOffscreenBuffer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
onDrawKeyboard(mOffscreenCanvas);
|
onDrawKeyboard(mOffscreenCanvas);
|
||||||
}
|
}
|
||||||
|
@ -501,6 +509,15 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void maybeCreateOffscreenCanvas() {
|
||||||
|
// TODO: Stop using the offscreen canvas even when in software rendering
|
||||||
|
if (mOffscreenCanvas != null) {
|
||||||
|
mOffscreenCanvas.setBitmap(mOffscreenBuffer);
|
||||||
|
} else {
|
||||||
|
mOffscreenCanvas = new Canvas(mOffscreenBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void onDrawKeyboard(final Canvas canvas) {
|
private void onDrawKeyboard(final Canvas canvas) {
|
||||||
if (mKeyboard == null) return;
|
if (mKeyboard == null) return;
|
||||||
|
|
||||||
|
@ -528,14 +545,13 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
||||||
}
|
}
|
||||||
if (!isHardwareAccelerated) {
|
if (!isHardwareAccelerated) {
|
||||||
canvas.clipRegion(mClipRegion, Region.Op.REPLACE);
|
canvas.clipRegion(mClipRegion, Region.Op.REPLACE);
|
||||||
}
|
|
||||||
|
|
||||||
// Draw keyboard background.
|
// Draw keyboard background.
|
||||||
canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
|
canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
|
||||||
final Drawable background = getBackground();
|
final Drawable background = getBackground();
|
||||||
if (background != null) {
|
if (background != null) {
|
||||||
background.draw(canvas);
|
background.draw(canvas);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Confirm if it's really required to draw all keys when hardware acceleration is on.
|
// TODO: Confirm if it's really required to draw all keys when hardware acceleration is on.
|
||||||
if (drawAllKeys || isHardwareAccelerated) {
|
if (drawAllKeys || isHardwareAccelerated) {
|
||||||
|
@ -907,15 +923,30 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called by {@link PointerTracker} constructor to create a TextView.
|
private TextView getKeyPreviewText(final int pointerId) {
|
||||||
@Override
|
TextView previewText = mKeyPreviewTexts.get(pointerId);
|
||||||
public TextView inflateKeyPreviewText() {
|
if (previewText != null) {
|
||||||
|
return previewText;
|
||||||
|
}
|
||||||
final Context context = getContext();
|
final Context context = getContext();
|
||||||
if (mKeyPreviewLayoutId != 0) {
|
if (mKeyPreviewLayoutId != 0) {
|
||||||
return (TextView)LayoutInflater.from(context).inflate(mKeyPreviewLayoutId, null);
|
previewText = (TextView)LayoutInflater.from(context).inflate(mKeyPreviewLayoutId, null);
|
||||||
} else {
|
} else {
|
||||||
return new TextView(context);
|
previewText = new TextView(context);
|
||||||
}
|
}
|
||||||
|
mKeyPreviewTexts.put(pointerId, previewText);
|
||||||
|
return previewText;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dismissAllKeyPreviews() {
|
||||||
|
final int pointerCount = mKeyPreviewTexts.size();
|
||||||
|
for (int id = 0; id < pointerCount; id++) {
|
||||||
|
final TextView previewText = mKeyPreviewTexts.get(id);
|
||||||
|
if (previewText != null) {
|
||||||
|
previewText.setVisibility(INVISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PointerTracker.setReleasedKeyGraphicsToAllKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -936,10 +967,19 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
||||||
final int[] viewOrigin = new int[2];
|
final int[] viewOrigin = new int[2];
|
||||||
getLocationInWindow(viewOrigin);
|
getLocationInWindow(viewOrigin);
|
||||||
mPreviewPlacerView.setOrigin(viewOrigin[0], viewOrigin[1]);
|
mPreviewPlacerView.setOrigin(viewOrigin[0], viewOrigin[1]);
|
||||||
final ViewGroup windowContentView =
|
final View rootView = getRootView();
|
||||||
(ViewGroup)getRootView().findViewById(android.R.id.content);
|
if (rootView == null) {
|
||||||
|
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);
|
windowContentView.addView(mPreviewPlacerView);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void showGestureFloatingPreviewText(String gestureFloatingPreviewText) {
|
public void showGestureFloatingPreviewText(String gestureFloatingPreviewText) {
|
||||||
locatePreviewPlacerView();
|
locatePreviewPlacerView();
|
||||||
|
@ -952,7 +992,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showGestureTrail(PointerTracker tracker) {
|
public void showGesturePreviewTrail(PointerTracker tracker) {
|
||||||
locatePreviewPlacerView();
|
locatePreviewPlacerView();
|
||||||
mPreviewPlacerView.invalidatePointer(tracker);
|
mPreviewPlacerView.invalidatePointer(tracker);
|
||||||
}
|
}
|
||||||
|
@ -962,7 +1002,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
||||||
public void showKeyPreview(PointerTracker tracker) {
|
public void showKeyPreview(PointerTracker tracker) {
|
||||||
if (!mShowKeyPreviewPopup) return;
|
if (!mShowKeyPreviewPopup) return;
|
||||||
|
|
||||||
final TextView previewText = tracker.getKeyPreviewText();
|
final TextView previewText = getKeyPreviewText(tracker.mPointerId);
|
||||||
// If the key preview has no parent view yet, add it to the ViewGroup which can place
|
// If the key preview has no parent view yet, add it to the ViewGroup which can place
|
||||||
// key preview absolutely in SoftInputWindow.
|
// key preview absolutely in SoftInputWindow.
|
||||||
if (previewText.getParent() == null) {
|
if (previewText.getParent() == null) {
|
||||||
|
@ -1052,7 +1092,6 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
||||||
public void invalidateAllKeys() {
|
public void invalidateAllKeys() {
|
||||||
mInvalidatedKeys.clear();
|
mInvalidatedKeys.clear();
|
||||||
mInvalidateAllKeys = true;
|
mInvalidateAllKeys = true;
|
||||||
mBufferNeedsUpdate = true;
|
|
||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1070,13 +1109,11 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
||||||
mInvalidatedKeys.add(key);
|
mInvalidatedKeys.add(key);
|
||||||
final int x = key.mX + getPaddingLeft();
|
final int x = key.mX + getPaddingLeft();
|
||||||
final int y = key.mY + getPaddingTop();
|
final int y = key.mY + getPaddingTop();
|
||||||
mWorkingRect.set(x, y, x + key.mWidth, y + key.mHeight);
|
invalidate(x, y, x + key.mWidth, y + key.mHeight);
|
||||||
mBufferNeedsUpdate = true;
|
|
||||||
invalidate(mWorkingRect);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void closing() {
|
public void closing() {
|
||||||
PointerTracker.dismissAllKeyPreviews();
|
dismissAllKeyPreviews();
|
||||||
cancelAllMessages();
|
cancelAllMessages();
|
||||||
|
|
||||||
mInvalidateAllKeys = true;
|
mInvalidateAllKeys = true;
|
||||||
|
|
|
@ -110,7 +110,6 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
|
||||||
new WeakHashMap<Key, MoreKeysPanel>();
|
new WeakHashMap<Key, MoreKeysPanel>();
|
||||||
private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint;
|
private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint;
|
||||||
|
|
||||||
private final PointerTrackerParams mPointerTrackerParams;
|
|
||||||
private final SuddenJumpingTouchEventHandler mTouchScreenRegulator;
|
private final SuddenJumpingTouchEventHandler mTouchScreenRegulator;
|
||||||
|
|
||||||
protected KeyDetector mKeyDetector;
|
protected KeyDetector mKeyDetector;
|
||||||
|
@ -127,11 +126,26 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
|
||||||
private static final int MSG_LONGPRESS_KEY = 2;
|
private static final int MSG_LONGPRESS_KEY = 2;
|
||||||
private static final int MSG_DOUBLE_TAP = 3;
|
private static final int MSG_DOUBLE_TAP = 3;
|
||||||
|
|
||||||
private final KeyTimerParams mParams;
|
private final int mKeyRepeatStartTimeout;
|
||||||
|
private final int mKeyRepeatInterval;
|
||||||
|
private final int mLongPressKeyTimeout;
|
||||||
|
private final int mLongPressShiftKeyTimeout;
|
||||||
|
private final int mIgnoreAltCodeKeyTimeout;
|
||||||
|
|
||||||
public KeyTimerHandler(MainKeyboardView outerInstance, KeyTimerParams params) {
|
public KeyTimerHandler(final MainKeyboardView outerInstance,
|
||||||
|
final TypedArray mainKeyboardViewAttr) {
|
||||||
super(outerInstance);
|
super(outerInstance);
|
||||||
mParams = params;
|
|
||||||
|
mKeyRepeatStartTimeout = mainKeyboardViewAttr.getInt(
|
||||||
|
R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0);
|
||||||
|
mKeyRepeatInterval = mainKeyboardViewAttr.getInt(
|
||||||
|
R.styleable.MainKeyboardView_keyRepeatInterval, 0);
|
||||||
|
mLongPressKeyTimeout = mainKeyboardViewAttr.getInt(
|
||||||
|
R.styleable.MainKeyboardView_longPressKeyTimeout, 0);
|
||||||
|
mLongPressShiftKeyTimeout = mainKeyboardViewAttr.getInt(
|
||||||
|
R.styleable.MainKeyboardView_longPressShiftKeyTimeout, 0);
|
||||||
|
mIgnoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt(
|
||||||
|
R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -146,7 +160,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
|
||||||
final Key currentKey = tracker.getKey();
|
final Key currentKey = tracker.getKey();
|
||||||
if (currentKey != null && currentKey.mCode == msg.arg1) {
|
if (currentKey != null && currentKey.mCode == msg.arg1) {
|
||||||
tracker.onRegisterKey(currentKey);
|
tracker.onRegisterKey(currentKey);
|
||||||
startKeyRepeatTimer(tracker, mParams.mKeyRepeatInterval);
|
startKeyRepeatTimer(tracker, mKeyRepeatInterval);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case MSG_LONGPRESS_KEY:
|
case MSG_LONGPRESS_KEY:
|
||||||
|
@ -167,7 +181,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startKeyRepeatTimer(PointerTracker tracker) {
|
public void startKeyRepeatTimer(PointerTracker tracker) {
|
||||||
startKeyRepeatTimer(tracker, mParams.mKeyRepeatStartTimeout);
|
startKeyRepeatTimer(tracker, mKeyRepeatStartTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancelKeyRepeatTimer() {
|
public void cancelKeyRepeatTimer() {
|
||||||
|
@ -185,7 +199,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
|
||||||
final int delay;
|
final int delay;
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case Keyboard.CODE_SHIFT:
|
case Keyboard.CODE_SHIFT:
|
||||||
delay = mParams.mLongPressShiftKeyTimeout;
|
delay = mLongPressShiftKeyTimeout;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
delay = 0;
|
delay = 0;
|
||||||
|
@ -206,15 +220,15 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
|
||||||
final int delay;
|
final int delay;
|
||||||
switch (key.mCode) {
|
switch (key.mCode) {
|
||||||
case Keyboard.CODE_SHIFT:
|
case Keyboard.CODE_SHIFT:
|
||||||
delay = mParams.mLongPressShiftKeyTimeout;
|
delay = mLongPressShiftKeyTimeout;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (KeyboardSwitcher.getInstance().isInMomentarySwitchState()) {
|
if (KeyboardSwitcher.getInstance().isInMomentarySwitchState()) {
|
||||||
// We use longer timeout for sliding finger input started from the symbols
|
// We use longer timeout for sliding finger input started from the symbols
|
||||||
// mode key.
|
// mode key.
|
||||||
delay = mParams.mLongPressKeyTimeout * 3;
|
delay = mLongPressKeyTimeout * 3;
|
||||||
} else {
|
} else {
|
||||||
delay = mParams.mLongPressKeyTimeout;
|
delay = mLongPressKeyTimeout;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -268,7 +282,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMessageDelayed(
|
sendMessageDelayed(
|
||||||
obtainMessage(MSG_TYPING_STATE_EXPIRED), mParams.mIgnoreAltCodeKeyTimeout);
|
obtainMessage(MSG_TYPING_STATE_EXPIRED), mIgnoreAltCodeKeyTimeout);
|
||||||
if (isTyping) {
|
if (isTyping) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -307,50 +321,6 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class PointerTrackerParams {
|
|
||||||
public final boolean mSlidingKeyInputEnabled;
|
|
||||||
public final int mTouchNoiseThresholdTime;
|
|
||||||
public final float mTouchNoiseThresholdDistance;
|
|
||||||
|
|
||||||
public static final PointerTrackerParams DEFAULT = new PointerTrackerParams();
|
|
||||||
|
|
||||||
private PointerTrackerParams() {
|
|
||||||
mSlidingKeyInputEnabled = false;
|
|
||||||
mTouchNoiseThresholdTime =0;
|
|
||||||
mTouchNoiseThresholdDistance = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PointerTrackerParams(TypedArray mainKeyboardViewAttr) {
|
|
||||||
mSlidingKeyInputEnabled = mainKeyboardViewAttr.getBoolean(
|
|
||||||
R.styleable.MainKeyboardView_slidingKeyInputEnable, false);
|
|
||||||
mTouchNoiseThresholdTime = mainKeyboardViewAttr.getInt(
|
|
||||||
R.styleable.MainKeyboardView_touchNoiseThresholdTime, 0);
|
|
||||||
mTouchNoiseThresholdDistance = mainKeyboardViewAttr.getDimension(
|
|
||||||
R.styleable.MainKeyboardView_touchNoiseThresholdDistance, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class KeyTimerParams {
|
|
||||||
public final int mKeyRepeatStartTimeout;
|
|
||||||
public final int mKeyRepeatInterval;
|
|
||||||
public final int mLongPressKeyTimeout;
|
|
||||||
public final int mLongPressShiftKeyTimeout;
|
|
||||||
public final int mIgnoreAltCodeKeyTimeout;
|
|
||||||
|
|
||||||
public KeyTimerParams(TypedArray mainKeyboardViewAttr) {
|
|
||||||
mKeyRepeatStartTimeout = mainKeyboardViewAttr.getInt(
|
|
||||||
R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0);
|
|
||||||
mKeyRepeatInterval = mainKeyboardViewAttr.getInt(
|
|
||||||
R.styleable.MainKeyboardView_keyRepeatInterval, 0);
|
|
||||||
mLongPressKeyTimeout = mainKeyboardViewAttr.getInt(
|
|
||||||
R.styleable.MainKeyboardView_longPressKeyTimeout, 0);
|
|
||||||
mLongPressShiftKeyTimeout = mainKeyboardViewAttr.getInt(
|
|
||||||
R.styleable.MainKeyboardView_longPressShiftKeyTimeout, 0);
|
|
||||||
mIgnoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt(
|
|
||||||
R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public MainKeyboardView(Context context, AttributeSet attrs) {
|
public MainKeyboardView(Context context, AttributeSet attrs) {
|
||||||
this(context, attrs, R.attr.mainKeyboardViewStyle);
|
this(context, attrs, R.attr.mainKeyboardViewStyle);
|
||||||
}
|
}
|
||||||
|
@ -374,8 +344,8 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
|
||||||
R.styleable.MainKeyboardView_autoCorrectionSpacebarLedEnabled, false);
|
R.styleable.MainKeyboardView_autoCorrectionSpacebarLedEnabled, false);
|
||||||
mAutoCorrectionSpacebarLedIcon = a.getDrawable(
|
mAutoCorrectionSpacebarLedIcon = a.getDrawable(
|
||||||
R.styleable.MainKeyboardView_autoCorrectionSpacebarLedIcon);
|
R.styleable.MainKeyboardView_autoCorrectionSpacebarLedIcon);
|
||||||
mSpacebarTextRatio = a.getFraction(R.styleable.MainKeyboardView_spacebarTextRatio,
|
mSpacebarTextRatio = a.getFraction(
|
||||||
1000, 1000, 1) / 1000.0f;
|
R.styleable.MainKeyboardView_spacebarTextRatio, 1, 1, 1.0f);
|
||||||
mSpacebarTextColor = a.getColor(R.styleable.MainKeyboardView_spacebarTextColor, 0);
|
mSpacebarTextColor = a.getColor(R.styleable.MainKeyboardView_spacebarTextColor, 0);
|
||||||
mSpacebarTextShadowColor = a.getColor(
|
mSpacebarTextShadowColor = a.getColor(
|
||||||
R.styleable.MainKeyboardView_spacebarTextShadowColor, 0);
|
R.styleable.MainKeyboardView_spacebarTextShadowColor, 0);
|
||||||
|
@ -389,19 +359,15 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
|
||||||
final int altCodeKeyWhileTypingFadeinAnimatorResId = a.getResourceId(
|
final int altCodeKeyWhileTypingFadeinAnimatorResId = a.getResourceId(
|
||||||
R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0);
|
R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0);
|
||||||
|
|
||||||
final KeyTimerParams keyTimerParams = new KeyTimerParams(a);
|
|
||||||
mPointerTrackerParams = new PointerTrackerParams(a);
|
|
||||||
|
|
||||||
final float keyHysteresisDistance = a.getDimension(
|
final float keyHysteresisDistance = a.getDimension(
|
||||||
R.styleable.MainKeyboardView_keyHysteresisDistance, 0);
|
R.styleable.MainKeyboardView_keyHysteresisDistance, 0);
|
||||||
mKeyDetector = new KeyDetector(keyHysteresisDistance);
|
mKeyDetector = new KeyDetector(keyHysteresisDistance);
|
||||||
mKeyTimerHandler = new KeyTimerHandler(this, keyTimerParams);
|
mKeyTimerHandler = new KeyTimerHandler(this, a);
|
||||||
mConfigShowMoreKeysKeyboardAtTouchedPoint = a.getBoolean(
|
mConfigShowMoreKeysKeyboardAtTouchedPoint = a.getBoolean(
|
||||||
R.styleable.MainKeyboardView_showMoreKeysKeyboardAtTouchedPoint, false);
|
R.styleable.MainKeyboardView_showMoreKeysKeyboardAtTouchedPoint, false);
|
||||||
|
PointerTracker.setParameters(a);
|
||||||
a.recycle();
|
a.recycle();
|
||||||
|
|
||||||
PointerTracker.setParameters(mPointerTrackerParams);
|
|
||||||
|
|
||||||
mLanguageOnSpacebarFadeoutAnimator = loadObjectAnimator(
|
mLanguageOnSpacebarFadeoutAnimator = loadObjectAnimator(
|
||||||
languageOnSpacebarFadeoutAnimatorResId, this);
|
languageOnSpacebarFadeoutAnimatorResId, this);
|
||||||
mAltCodeKeyWhileTypingFadeoutAnimator = loadObjectAnimator(
|
mAltCodeKeyWhileTypingFadeoutAnimator = loadObjectAnimator(
|
||||||
|
@ -482,7 +448,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
|
||||||
super.setKeyboard(keyboard);
|
super.setKeyboard(keyboard);
|
||||||
mKeyDetector.setKeyboard(
|
mKeyDetector.setKeyboard(
|
||||||
keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection);
|
keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection);
|
||||||
PointerTracker.setKeyDetector(mKeyDetector, mShouldHandleGesture);
|
PointerTracker.setKeyDetector(mKeyDetector);
|
||||||
mTouchScreenRegulator.setKeyboard(keyboard);
|
mTouchScreenRegulator.setKeyboard(keyboard);
|
||||||
mMoreKeysPanelCache.clear();
|
mMoreKeysPanelCache.clear();
|
||||||
|
|
||||||
|
@ -500,12 +466,13 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
|
||||||
AccessibleKeyboardViewProxy.getInstance().setKeyboard(keyboard);
|
AccessibleKeyboardViewProxy.getInstance().setKeyboard(keyboard);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
// Note that this method is called from a non-UI thread.
|
||||||
public void setGestureHandlingMode(final boolean shouldHandleGesture,
|
public void setMainDictionaryAvailability(boolean mainDictionaryAvailable) {
|
||||||
boolean drawsGesturePreviewTrail, boolean drawsGestureFloatingPreviewText) {
|
PointerTracker.setMainDictionaryAvailability(mainDictionaryAvailable);
|
||||||
super.setGestureHandlingMode(shouldHandleGesture, drawsGesturePreviewTrail,
|
}
|
||||||
drawsGestureFloatingPreviewText);
|
|
||||||
PointerTracker.setKeyDetector(mKeyDetector, shouldHandleGesture);
|
public void setGestureHandlingEnabledByUser(boolean gestureHandlingEnabledByUser) {
|
||||||
|
PointerTracker.setGestureHandlingEnabledByUser(gestureHandlingEnabledByUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -527,7 +494,17 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
|
||||||
// to properly show the splash screen, which requires that the window token of the
|
// to properly show the splash screen, which requires that the window token of the
|
||||||
// KeyboardView be non-null.
|
// KeyboardView be non-null.
|
||||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||||
ResearchLogger.getInstance().mainKeyboardView_onAttachedToWindow();
|
ResearchLogger.getInstance().mainKeyboardView_onAttachedToWindow(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDetachedFromWindow() {
|
||||||
|
super.onDetachedFromWindow();
|
||||||
|
// Notify the research logger that the keyboard view has been detached. This is needed
|
||||||
|
// to invalidate the reference of {@link MainKeyboardView} to null.
|
||||||
|
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||||
|
ResearchLogger.getInstance().mainKeyboardView_onDetachedFromWindow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -607,9 +584,8 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
|
||||||
}
|
}
|
||||||
|
|
||||||
private void invokeCodeInput(int primaryCode) {
|
private void invokeCodeInput(int primaryCode) {
|
||||||
mKeyboardActionListener.onCodeInput(primaryCode,
|
mKeyboardActionListener.onCodeInput(
|
||||||
KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
|
primaryCode, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
|
||||||
KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void invokeReleaseKey(int primaryCode) {
|
private void invokeReleaseKey(int primaryCode) {
|
||||||
|
@ -834,20 +810,6 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void draw(Canvas c) {
|
|
||||||
Utils.GCUtils.getInstance().reset();
|
|
||||||
boolean tryGC = true;
|
|
||||||
for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
|
|
||||||
try {
|
|
||||||
super.draw(c);
|
|
||||||
tryGC = false;
|
|
||||||
} catch (OutOfMemoryError e) {
|
|
||||||
tryGC = Utils.GCUtils.getInstance().tryGCOrWait(TAG, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Receives hover events from the input framework.
|
* Receives hover events from the input framework.
|
||||||
*
|
*
|
||||||
|
|
|
@ -39,11 +39,7 @@ public class MoreKeysDetector extends KeyDetector {
|
||||||
|
|
||||||
Key nearestKey = null;
|
Key nearestKey = null;
|
||||||
int nearestDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare;
|
int nearestDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare;
|
||||||
final Keyboard keyboard = getKeyboard();
|
for (final Key key : getKeyboard().mKeys) {
|
||||||
if (keyboard == null) {
|
|
||||||
throw new NullPointerException("Keyboard isn't set");
|
|
||||||
}
|
|
||||||
for (final Key key : keyboard.mKeys) {
|
|
||||||
final int dist = key.squaredDistanceToEdge(touchX, touchY);
|
final int dist = key.squaredDistanceToEdge(touchX, touchY);
|
||||||
if (dist < nearestDist) {
|
if (dist < nearestDist) {
|
||||||
nearestKey = key;
|
nearestKey = key;
|
||||||
|
|
|
@ -25,6 +25,7 @@ import android.widget.PopupWindow;
|
||||||
|
|
||||||
import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy;
|
import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy;
|
||||||
import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
|
import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
|
||||||
|
import com.android.inputmethod.latin.Constants;
|
||||||
import com.android.inputmethod.latin.InputPointers;
|
import com.android.inputmethod.latin.InputPointers;
|
||||||
import com.android.inputmethod.latin.R;
|
import com.android.inputmethod.latin.R;
|
||||||
|
|
||||||
|
@ -50,7 +51,8 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
|
||||||
public void onCodeInput(int primaryCode, int x, int y) {
|
public void onCodeInput(int primaryCode, int x, int y) {
|
||||||
// Because a more keys keyboard doesn't need proximity characters correction, we don't
|
// Because a more keys keyboard doesn't need proximity characters correction, we don't
|
||||||
// send touch event coordinates.
|
// send touch event coordinates.
|
||||||
mListener.onCodeInput(primaryCode, NOT_A_TOUCH_COORDINATE, NOT_A_TOUCH_COORDINATE);
|
mListener.onCodeInput(
|
||||||
|
primaryCode, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -16,25 +16,25 @@
|
||||||
|
|
||||||
package com.android.inputmethod.keyboard;
|
package com.android.inputmethod.keyboard;
|
||||||
|
|
||||||
import android.graphics.Canvas;
|
import android.content.res.TypedArray;
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.android.inputmethod.accessibility.AccessibilityUtils;
|
import com.android.inputmethod.accessibility.AccessibilityUtils;
|
||||||
import com.android.inputmethod.keyboard.internal.GestureStroke;
|
import com.android.inputmethod.keyboard.internal.GestureStroke;
|
||||||
|
import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewTrail;
|
||||||
import com.android.inputmethod.keyboard.internal.PointerTrackerQueue;
|
import com.android.inputmethod.keyboard.internal.PointerTrackerQueue;
|
||||||
|
import com.android.inputmethod.latin.CollectionUtils;
|
||||||
import com.android.inputmethod.latin.InputPointers;
|
import com.android.inputmethod.latin.InputPointers;
|
||||||
import com.android.inputmethod.latin.LatinImeLogger;
|
import com.android.inputmethod.latin.LatinImeLogger;
|
||||||
|
import com.android.inputmethod.latin.R;
|
||||||
import com.android.inputmethod.latin.define.ProductionFlag;
|
import com.android.inputmethod.latin.define.ProductionFlag;
|
||||||
import com.android.inputmethod.research.ResearchLogger;
|
import com.android.inputmethod.research.ResearchLogger;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
public class PointerTracker implements PointerTrackerQueue.Element {
|
||||||
private static final String TAG = PointerTracker.class.getSimpleName();
|
private static final String TAG = PointerTracker.class.getSimpleName();
|
||||||
private static final boolean DEBUG_EVENT = false;
|
private static final boolean DEBUG_EVENT = false;
|
||||||
private static final boolean DEBUG_MOVE_EVENT = false;
|
private static final boolean DEBUG_MOVE_EVENT = false;
|
||||||
|
@ -43,6 +43,9 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
|
|
||||||
/** True if {@link PointerTracker}s should handle gesture events. */
|
/** True if {@link PointerTracker}s should handle gesture events. */
|
||||||
private static boolean sShouldHandleGesture = false;
|
private static boolean sShouldHandleGesture = false;
|
||||||
|
private static boolean sMainDictionaryAvailable = false;
|
||||||
|
private static boolean sGestureHandlingEnabledByInputField = false;
|
||||||
|
private static boolean sGestureHandlingEnabledByUser = false;
|
||||||
|
|
||||||
private static final int MIN_GESTURE_RECOGNITION_TIME = 100; // msec
|
private static final int MIN_GESTURE_RECOGNITION_TIME = 100; // msec
|
||||||
|
|
||||||
|
@ -75,10 +78,9 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
|
|
||||||
public interface DrawingProxy extends MoreKeysPanel.Controller {
|
public interface DrawingProxy extends MoreKeysPanel.Controller {
|
||||||
public void invalidateKey(Key key);
|
public void invalidateKey(Key key);
|
||||||
public TextView inflateKeyPreviewText();
|
|
||||||
public void showKeyPreview(PointerTracker tracker);
|
public void showKeyPreview(PointerTracker tracker);
|
||||||
public void dismissKeyPreview(PointerTracker tracker);
|
public void dismissKeyPreview(PointerTracker tracker);
|
||||||
public void showGestureTrail(PointerTracker tracker);
|
public void showGesturePreviewTrail(PointerTracker tracker);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface TimerProxy {
|
public interface TimerProxy {
|
||||||
|
@ -117,19 +119,40 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class PointerTrackerParams {
|
||||||
|
public final boolean mSlidingKeyInputEnabled;
|
||||||
|
public final int mTouchNoiseThresholdTime;
|
||||||
|
public final float mTouchNoiseThresholdDistance;
|
||||||
|
public final int mTouchNoiseThresholdDistanceSquared;
|
||||||
|
|
||||||
|
public static final PointerTrackerParams DEFAULT = new PointerTrackerParams();
|
||||||
|
|
||||||
|
private PointerTrackerParams() {
|
||||||
|
mSlidingKeyInputEnabled = false;
|
||||||
|
mTouchNoiseThresholdTime = 0;
|
||||||
|
mTouchNoiseThresholdDistance = 0.0f;
|
||||||
|
mTouchNoiseThresholdDistanceSquared = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PointerTrackerParams(TypedArray mainKeyboardViewAttr) {
|
||||||
|
mSlidingKeyInputEnabled = mainKeyboardViewAttr.getBoolean(
|
||||||
|
R.styleable.MainKeyboardView_slidingKeyInputEnable, false);
|
||||||
|
mTouchNoiseThresholdTime = mainKeyboardViewAttr.getInt(
|
||||||
|
R.styleable.MainKeyboardView_touchNoiseThresholdTime, 0);
|
||||||
|
final float touchNouseThresholdDistance = mainKeyboardViewAttr.getDimension(
|
||||||
|
R.styleable.MainKeyboardView_touchNoiseThresholdDistance, 0);
|
||||||
|
mTouchNoiseThresholdDistance = touchNouseThresholdDistance;
|
||||||
|
mTouchNoiseThresholdDistanceSquared =
|
||||||
|
(int)(touchNouseThresholdDistance * touchNouseThresholdDistance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Parameters for pointer handling.
|
// Parameters for pointer handling.
|
||||||
private static MainKeyboardView.PointerTrackerParams sParams;
|
private static PointerTrackerParams sParams;
|
||||||
private static int sTouchNoiseThresholdDistanceSquared;
|
|
||||||
private static boolean sNeedsPhantomSuddenMoveEventHack;
|
private static boolean sNeedsPhantomSuddenMoveEventHack;
|
||||||
|
|
||||||
private static final ArrayList<PointerTracker> sTrackers = new ArrayList<PointerTracker>();
|
private static final ArrayList<PointerTracker> sTrackers = CollectionUtils.newArrayList();
|
||||||
private static final InputPointers sAggregratedPointers = new InputPointers(
|
|
||||||
GestureStroke.DEFAULT_CAPACITY);
|
|
||||||
private static PointerTrackerQueue sPointerTrackerQueue;
|
private static PointerTrackerQueue sPointerTrackerQueue;
|
||||||
// HACK: Change gesture detection criteria depending on this variable.
|
|
||||||
// TODO: Find more comprehensive ways to detect a gesture start.
|
|
||||||
// True when the previous user input was a gesture input, not a typing input.
|
|
||||||
private static boolean sWasInGesture;
|
|
||||||
|
|
||||||
public final int mPointerId;
|
public final int mPointerId;
|
||||||
|
|
||||||
|
@ -140,15 +163,14 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
|
|
||||||
private Keyboard mKeyboard;
|
private Keyboard mKeyboard;
|
||||||
private int mKeyQuarterWidthSquared;
|
private int mKeyQuarterWidthSquared;
|
||||||
private final TextView mKeyPreviewText;
|
|
||||||
|
|
||||||
private boolean mIsAlphabetKeyboard;
|
private boolean mIsDetectingGesture = false; // per PointerTracker.
|
||||||
private boolean mIsPossibleGesture = false;
|
private static boolean sInGesture = false;
|
||||||
private boolean mInGesture = false;
|
private static long sGestureFirstDownTime;
|
||||||
|
private static final InputPointers sAggregratedPointers = new InputPointers(
|
||||||
// TODO: Remove these variables
|
GestureStroke.DEFAULT_CAPACITY);
|
||||||
private int mLastRecognitionPointSize = 0;
|
private static int sLastRecognitionPointSize = 0;
|
||||||
private long mLastRecognitionTime = 0;
|
private static long sLastRecognitionTime = 0;
|
||||||
|
|
||||||
// The position and time at which first down event occurred.
|
// The position and time at which first down event occurred.
|
||||||
private long mDownTime;
|
private long mDownTime;
|
||||||
|
@ -186,7 +208,7 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
private static final KeyboardActionListener EMPTY_LISTENER =
|
private static final KeyboardActionListener EMPTY_LISTENER =
|
||||||
new KeyboardActionListener.Adapter();
|
new KeyboardActionListener.Adapter();
|
||||||
|
|
||||||
private final GestureStroke mGestureStroke;
|
private final GestureStrokeWithPreviewTrail mGestureStrokeWithPreviewTrail;
|
||||||
|
|
||||||
public static void init(boolean hasDistinctMultitouch,
|
public static void init(boolean hasDistinctMultitouch,
|
||||||
boolean needsPhantomSuddenMoveEventHack) {
|
boolean needsPhantomSuddenMoveEventHack) {
|
||||||
|
@ -196,28 +218,32 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
sPointerTrackerQueue = null;
|
sPointerTrackerQueue = null;
|
||||||
}
|
}
|
||||||
sNeedsPhantomSuddenMoveEventHack = needsPhantomSuddenMoveEventHack;
|
sNeedsPhantomSuddenMoveEventHack = needsPhantomSuddenMoveEventHack;
|
||||||
|
sParams = PointerTrackerParams.DEFAULT;
|
||||||
setParameters(MainKeyboardView.PointerTrackerParams.DEFAULT);
|
|
||||||
updateGestureHandlingMode(null, false /* shouldHandleGesture */);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setParameters(MainKeyboardView.PointerTrackerParams params) {
|
public static void setParameters(final TypedArray mainKeyboardViewAttr) {
|
||||||
sParams = params;
|
sParams = new PointerTrackerParams(mainKeyboardViewAttr);
|
||||||
sTouchNoiseThresholdDistanceSquared = (int)(
|
|
||||||
params.mTouchNoiseThresholdDistance * params.mTouchNoiseThresholdDistance);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void updateGestureHandlingMode(Keyboard keyboard, boolean shouldHandleGesture) {
|
private static void updateGestureHandlingMode() {
|
||||||
if (!shouldHandleGesture
|
sShouldHandleGesture = sMainDictionaryAvailable
|
||||||
|| AccessibilityUtils.getInstance().isTouchExplorationEnabled()
|
&& sGestureHandlingEnabledByInputField
|
||||||
|| (keyboard != null && keyboard.mId.passwordInput())) {
|
&& sGestureHandlingEnabledByUser
|
||||||
sShouldHandleGesture = false;
|
&& !AccessibilityUtils.getInstance().isTouchExplorationEnabled();
|
||||||
} else {
|
|
||||||
sShouldHandleGesture = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PointerTracker getPointerTracker(final int id, KeyEventHandler handler) {
|
// Note that this method is called from a non-UI thread.
|
||||||
|
public static void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) {
|
||||||
|
sMainDictionaryAvailable = mainDictionaryAvailable;
|
||||||
|
updateGestureHandlingMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setGestureHandlingEnabledByUser(final boolean gestureHandlingEnabledByUser) {
|
||||||
|
sGestureHandlingEnabledByUser = gestureHandlingEnabledByUser;
|
||||||
|
updateGestureHandlingMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PointerTracker getPointerTracker(final int id, final KeyEventHandler handler) {
|
||||||
final ArrayList<PointerTracker> trackers = sTrackers;
|
final ArrayList<PointerTracker> trackers = sTrackers;
|
||||||
|
|
||||||
// Create pointer trackers until we can get 'id+1'-th tracker, if needed.
|
// Create pointer trackers until we can get 'id+1'-th tracker, if needed.
|
||||||
|
@ -233,7 +259,7 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
return sPointerTrackerQueue != null ? sPointerTrackerQueue.isAnyInSlidingKeyInput() : false;
|
return sPointerTrackerQueue != null ? sPointerTrackerQueue.isAnyInSlidingKeyInput() : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setKeyboardActionListener(KeyboardActionListener listener) {
|
public static void setKeyboardActionListener(final KeyboardActionListener listener) {
|
||||||
final int trackersSize = sTrackers.size();
|
final int trackersSize = sTrackers.size();
|
||||||
for (int i = 0; i < trackersSize; ++i) {
|
for (int i = 0; i < trackersSize; ++i) {
|
||||||
final PointerTracker tracker = sTrackers.get(i);
|
final PointerTracker tracker = sTrackers.get(i);
|
||||||
|
@ -241,7 +267,7 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setKeyDetector(KeyDetector keyDetector, boolean shouldHandleGesture) {
|
public static void setKeyDetector(final KeyDetector keyDetector) {
|
||||||
final int trackersSize = sTrackers.size();
|
final int trackersSize = sTrackers.size();
|
||||||
for (int i = 0; i < trackersSize; ++i) {
|
for (int i = 0; i < trackersSize; ++i) {
|
||||||
final PointerTracker tracker = sTrackers.get(i);
|
final PointerTracker tracker = sTrackers.get(i);
|
||||||
|
@ -250,70 +276,33 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
tracker.mKeyboardLayoutHasBeenChanged = true;
|
tracker.mKeyboardLayoutHasBeenChanged = true;
|
||||||
}
|
}
|
||||||
final Keyboard keyboard = keyDetector.getKeyboard();
|
final Keyboard keyboard = keyDetector.getKeyboard();
|
||||||
updateGestureHandlingMode(keyboard, shouldHandleGesture);
|
sGestureHandlingEnabledByInputField = !keyboard.mId.passwordInput();
|
||||||
|
updateGestureHandlingMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void dismissAllKeyPreviews() {
|
public static void setReleasedKeyGraphicsToAllKeys() {
|
||||||
final int trackersSize = sTrackers.size();
|
final int trackersSize = sTrackers.size();
|
||||||
for (int i = 0; i < trackersSize; ++i) {
|
for (int i = 0; i < trackersSize; ++i) {
|
||||||
final PointerTracker tracker = sTrackers.get(i);
|
final PointerTracker tracker = sTrackers.get(i);
|
||||||
tracker.getKeyPreviewText().setVisibility(View.INVISIBLE);
|
|
||||||
tracker.setReleasedKeyGraphics(tracker.mCurrentKey);
|
tracker.setReleasedKeyGraphics(tracker.mCurrentKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: To handle multi-touch gestures we may want to move this method to
|
private PointerTracker(final int id, final KeyEventHandler handler) {
|
||||||
// {@link PointerTrackerQueue}.
|
if (handler == null) {
|
||||||
private static InputPointers getIncrementalBatchPoints() {
|
|
||||||
final int trackersSize = sTrackers.size();
|
|
||||||
for (int i = 0; i < trackersSize; ++i) {
|
|
||||||
final PointerTracker tracker = sTrackers.get(i);
|
|
||||||
tracker.mGestureStroke.appendIncrementalBatchPoints(sAggregratedPointers);
|
|
||||||
}
|
|
||||||
return sAggregratedPointers;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: To handle multi-touch gestures we may want to move this method to
|
|
||||||
// {@link PointerTrackerQueue}.
|
|
||||||
private static InputPointers getAllBatchPoints() {
|
|
||||||
final int trackersSize = sTrackers.size();
|
|
||||||
for (int i = 0; i < trackersSize; ++i) {
|
|
||||||
final PointerTracker tracker = sTrackers.get(i);
|
|
||||||
tracker.mGestureStroke.appendAllBatchPoints(sAggregratedPointers);
|
|
||||||
}
|
|
||||||
return sAggregratedPointers;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: To handle multi-touch gestures we may want to move this method to
|
|
||||||
// {@link PointerTrackerQueue}.
|
|
||||||
public static void clearBatchInputPointsOfAllPointerTrackers() {
|
|
||||||
final int trackersSize = sTrackers.size();
|
|
||||||
for (int i = 0; i < trackersSize; ++i) {
|
|
||||||
final PointerTracker tracker = sTrackers.get(i);
|
|
||||||
tracker.mGestureStroke.reset();
|
|
||||||
}
|
|
||||||
sAggregratedPointers.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
private PointerTracker(int id, KeyEventHandler handler) {
|
|
||||||
if (handler == null)
|
|
||||||
throw new NullPointerException();
|
throw new NullPointerException();
|
||||||
|
}
|
||||||
mPointerId = id;
|
mPointerId = id;
|
||||||
mGestureStroke = new GestureStroke(id);
|
mGestureStrokeWithPreviewTrail = new GestureStrokeWithPreviewTrail(id);
|
||||||
setKeyDetectorInner(handler.getKeyDetector());
|
setKeyDetectorInner(handler.getKeyDetector());
|
||||||
mListener = handler.getKeyboardActionListener();
|
mListener = handler.getKeyboardActionListener();
|
||||||
mDrawingProxy = handler.getDrawingProxy();
|
mDrawingProxy = handler.getDrawingProxy();
|
||||||
mTimerProxy = handler.getTimerProxy();
|
mTimerProxy = handler.getTimerProxy();
|
||||||
mKeyPreviewText = mDrawingProxy.inflateKeyPreviewText();
|
|
||||||
}
|
|
||||||
|
|
||||||
public TextView getKeyPreviewText() {
|
|
||||||
return mKeyPreviewText;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if keyboard has been changed by this callback.
|
// Returns true if keyboard has been changed by this callback.
|
||||||
private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key) {
|
private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key) {
|
||||||
if (mInGesture) {
|
if (sInGesture) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
|
final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
|
||||||
|
@ -337,7 +326,8 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
|
|
||||||
// Note that we need primaryCode argument because the keyboard may in shifted state and the
|
// Note that we need primaryCode argument because the keyboard may in shifted state and the
|
||||||
// primaryCode is different from {@link Key#mCode}.
|
// primaryCode is different from {@link Key#mCode}.
|
||||||
private void callListenerOnCodeInput(Key key, int primaryCode, int x, int y) {
|
private void callListenerOnCodeInput(final Key key, final int primaryCode, final int x,
|
||||||
|
final int y) {
|
||||||
final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
|
final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
|
||||||
final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState();
|
final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState();
|
||||||
final int code = altersCode ? key.mAltCode : primaryCode;
|
final int code = altersCode ? key.mAltCode : primaryCode;
|
||||||
|
@ -366,8 +356,9 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
|
|
||||||
// Note that we need primaryCode argument because the keyboard may in shifted state and the
|
// Note that we need primaryCode argument because the keyboard may in shifted state and the
|
||||||
// primaryCode is different from {@link Key#mCode}.
|
// primaryCode is different from {@link Key#mCode}.
|
||||||
private void callListenerOnRelease(Key key, int primaryCode, boolean withSliding) {
|
private void callListenerOnRelease(final Key key, final int primaryCode,
|
||||||
if (mInGesture) {
|
final boolean withSliding) {
|
||||||
|
if (sInGesture) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
|
final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
|
||||||
|
@ -389,20 +380,19 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void callListenerOnCancelInput() {
|
private void callListenerOnCancelInput() {
|
||||||
if (DEBUG_LISTENER)
|
if (DEBUG_LISTENER) {
|
||||||
Log.d(TAG, "onCancelInput");
|
Log.d(TAG, "onCancelInput");
|
||||||
|
}
|
||||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||||
ResearchLogger.pointerTracker_callListenerOnCancelInput();
|
ResearchLogger.pointerTracker_callListenerOnCancelInput();
|
||||||
}
|
}
|
||||||
mListener.onCancelInput();
|
mListener.onCancelInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setKeyDetectorInner(KeyDetector keyDetector) {
|
private void setKeyDetectorInner(final KeyDetector keyDetector) {
|
||||||
mKeyDetector = keyDetector;
|
mKeyDetector = keyDetector;
|
||||||
mKeyboard = keyDetector.getKeyboard();
|
mKeyboard = keyDetector.getKeyboard();
|
||||||
mIsAlphabetKeyboard = mKeyboard.mId.isAlphabetKeyboard();
|
mGestureStrokeWithPreviewTrail.setGestureSampleLength(mKeyboard.mMostCommonKeyWidth);
|
||||||
mGestureStroke.setGestureSampleLength(
|
|
||||||
mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight);
|
|
||||||
final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY);
|
final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY);
|
||||||
if (newKey != mCurrentKey) {
|
if (newKey != mCurrentKey) {
|
||||||
if (mDrawingProxy != null) {
|
if (mDrawingProxy != null) {
|
||||||
|
@ -428,11 +418,11 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
return mCurrentKey != null && mCurrentKey.isModifier();
|
return mCurrentKey != null && mCurrentKey.isModifier();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Key getKeyOn(int x, int y) {
|
public Key getKeyOn(final int x, final int y) {
|
||||||
return mKeyDetector.detectHitKey(x, y);
|
return mKeyDetector.detectHitKey(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setReleasedKeyGraphics(Key key) {
|
private void setReleasedKeyGraphics(final Key key) {
|
||||||
mDrawingProxy.dismissKeyPreview(this);
|
mDrawingProxy.dismissKeyPreview(this);
|
||||||
if (key == null) {
|
if (key == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -463,7 +453,7 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setPressedKeyGraphics(Key key) {
|
private void setPressedKeyGraphics(final Key key) {
|
||||||
if (key == null) {
|
if (key == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -475,7 +465,7 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!key.noKeyPreview() && !mInGesture) {
|
if (!key.noKeyPreview() && !sInGesture) {
|
||||||
mDrawingProxy.showKeyPreview(this);
|
mDrawingProxy.showKeyPreview(this);
|
||||||
}
|
}
|
||||||
updatePressKeyGraphics(key);
|
updatePressKeyGraphics(key);
|
||||||
|
@ -502,20 +492,18 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateReleaseKeyGraphics(Key key) {
|
private void updateReleaseKeyGraphics(final Key key) {
|
||||||
key.onReleased();
|
key.onReleased();
|
||||||
mDrawingProxy.invalidateKey(key);
|
mDrawingProxy.invalidateKey(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePressKeyGraphics(Key key) {
|
private void updatePressKeyGraphics(final Key key) {
|
||||||
key.onPressed();
|
key.onPressed();
|
||||||
mDrawingProxy.invalidateKey(key);
|
mDrawingProxy.invalidateKey(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void drawGestureTrail(Canvas canvas, Paint paint) {
|
public GestureStrokeWithPreviewTrail getGestureStrokeWithPreviewTrail() {
|
||||||
if (mInGesture) {
|
return mGestureStrokeWithPreviewTrail;
|
||||||
mGestureStroke.drawGestureTrail(canvas, paint, mLastX, mLastY);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getLastX() {
|
public int getLastX() {
|
||||||
|
@ -530,77 +518,91 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
return mDownTime;
|
return mDownTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Key onDownKey(int x, int y, long eventTime) {
|
private Key onDownKey(final int x, final int y, final long eventTime) {
|
||||||
mDownTime = eventTime;
|
mDownTime = eventTime;
|
||||||
return onMoveToNewKey(onMoveKeyInternal(x, y), x, y);
|
return onMoveToNewKey(onMoveKeyInternal(x, y), x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Key onMoveKeyInternal(int x, int y) {
|
private Key onMoveKeyInternal(final int x, final int y) {
|
||||||
mLastX = x;
|
mLastX = x;
|
||||||
mLastY = y;
|
mLastY = y;
|
||||||
return mKeyDetector.detectHitKey(x, y);
|
return mKeyDetector.detectHitKey(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Key onMoveKey(int x, int y) {
|
private Key onMoveKey(final int x, final int y) {
|
||||||
return onMoveKeyInternal(x, y);
|
return onMoveKeyInternal(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Key onMoveToNewKey(Key newKey, int x, int y) {
|
private Key onMoveToNewKey(final Key newKey, final int x, final int y) {
|
||||||
mCurrentKey = newKey;
|
mCurrentKey = newKey;
|
||||||
mKeyX = x;
|
mKeyX = x;
|
||||||
mKeyY = y;
|
mKeyY = y;
|
||||||
return newKey;
|
return newKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int getActivePointerTrackerCount() {
|
||||||
|
return (sPointerTrackerQueue == null) ? 1 : sPointerTrackerQueue.size();
|
||||||
|
}
|
||||||
|
|
||||||
private void startBatchInput() {
|
private void startBatchInput() {
|
||||||
if (DEBUG_LISTENER) {
|
if (DEBUG_LISTENER) {
|
||||||
Log.d(TAG, "onStartBatchInput");
|
Log.d(TAG, "onStartBatchInput");
|
||||||
}
|
}
|
||||||
mInGesture = true;
|
sInGesture = true;
|
||||||
mListener.onStartBatchInput();
|
mListener.onStartBatchInput();
|
||||||
|
mDrawingProxy.showGesturePreviewTrail(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateBatchInput(InputPointers batchPoints) {
|
private void updateBatchInput(final long eventTime) {
|
||||||
|
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) {
|
if (DEBUG_LISTENER) {
|
||||||
Log.d(TAG, "onUpdateBatchInput: batchPoints=" + batchPoints.getPointerSize());
|
Log.d(TAG, "onUpdateBatchInput: batchPoints=" + size);
|
||||||
}
|
}
|
||||||
mListener.onUpdateBatchInput(batchPoints);
|
mListener.onUpdateBatchInput(sAggregratedPointers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mDrawingProxy.showGesturePreviewTrail(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void endBatchInput(InputPointers batchPoints) {
|
private void endBatchInput() {
|
||||||
|
synchronized (sAggregratedPointers) {
|
||||||
|
mGestureStrokeWithPreviewTrail.appendAllBatchPoints(sAggregratedPointers);
|
||||||
|
if (getActivePointerTrackerCount() == 1) {
|
||||||
if (DEBUG_LISTENER) {
|
if (DEBUG_LISTENER) {
|
||||||
Log.d(TAG, "onEndBatchInput: batchPoints=" + batchPoints.getPointerSize());
|
Log.d(TAG, "onEndBatchInput: batchPoints="
|
||||||
|
+ sAggregratedPointers.getPointerSize());
|
||||||
}
|
}
|
||||||
mListener.onEndBatchInput(batchPoints);
|
sInGesture = false;
|
||||||
clearBatchInputRecognitionStateOfThisPointerTracker();
|
mListener.onEndBatchInput(sAggregratedPointers);
|
||||||
clearBatchInputPointsOfAllPointerTrackers();
|
clearBatchInputPointsOfAllPointerTrackers();
|
||||||
sWasInGesture = true;
|
}
|
||||||
|
}
|
||||||
|
mDrawingProxy.showGesturePreviewTrail(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void abortBatchInput() {
|
private static void abortBatchInput() {
|
||||||
clearBatchInputRecognitionStateOfThisPointerTracker();
|
|
||||||
clearBatchInputPointsOfAllPointerTrackers();
|
clearBatchInputPointsOfAllPointerTrackers();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearBatchInputRecognitionStateOfThisPointerTracker() {
|
private static void clearBatchInputPointsOfAllPointerTrackers() {
|
||||||
mIsPossibleGesture = false;
|
final int trackersSize = sTrackers.size();
|
||||||
mInGesture = false;
|
for (int i = 0; i < trackersSize; ++i) {
|
||||||
mLastRecognitionPointSize = 0;
|
final PointerTracker tracker = sTrackers.get(i);
|
||||||
mLastRecognitionTime = 0;
|
tracker.mGestureStrokeWithPreviewTrail.reset();
|
||||||
|
}
|
||||||
|
sAggregratedPointers.reset();
|
||||||
|
sLastRecognitionPointSize = 0;
|
||||||
|
sLastRecognitionTime = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean updateBatchInputRecognitionState(long eventTime, int size) {
|
public void processMotionEvent(final int action, final int x, final int y, final long eventTime,
|
||||||
if (size > mLastRecognitionPointSize
|
final KeyEventHandler handler) {
|
||||||
&& eventTime > mLastRecognitionTime + MIN_GESTURE_RECOGNITION_TIME) {
|
|
||||||
mLastRecognitionPointSize = size;
|
|
||||||
mLastRecognitionTime = eventTime;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void processMotionEvent(int action, int x, int y, long eventTime,
|
|
||||||
KeyEventHandler handler) {
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case MotionEvent.ACTION_DOWN:
|
case MotionEvent.ACTION_DOWN:
|
||||||
case MotionEvent.ACTION_POINTER_DOWN:
|
case MotionEvent.ACTION_POINTER_DOWN:
|
||||||
|
@ -619,9 +621,11 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onDownEvent(int x, int y, long eventTime, KeyEventHandler handler) {
|
public void onDownEvent(final int x, final int y, final long eventTime,
|
||||||
if (DEBUG_EVENT)
|
final KeyEventHandler handler) {
|
||||||
|
if (DEBUG_EVENT) {
|
||||||
printTouchEvent("onDownEvent:", x, y, eventTime);
|
printTouchEvent("onDownEvent:", x, y, eventTime);
|
||||||
|
}
|
||||||
|
|
||||||
mDrawingProxy = handler.getDrawingProxy();
|
mDrawingProxy = handler.getDrawingProxy();
|
||||||
mTimerProxy = handler.getTimerProxy();
|
mTimerProxy = handler.getTimerProxy();
|
||||||
|
@ -633,7 +637,7 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
final int dx = x - mLastX;
|
final int dx = x - mLastX;
|
||||||
final int dy = y - mLastY;
|
final int dy = y - mLastY;
|
||||||
final int distanceSquared = (dx * dx + dy * dy);
|
final int distanceSquared = (dx * dx + dy * dy);
|
||||||
if (distanceSquared < sTouchNoiseThresholdDistanceSquared) {
|
if (distanceSquared < sParams.mTouchNoiseThresholdDistanceSquared) {
|
||||||
if (DEBUG_MODE)
|
if (DEBUG_MODE)
|
||||||
Log.w(TAG, "onDownEvent: ignore potential noise: time=" + deltaT
|
Log.w(TAG, "onDownEvent: ignore potential noise: time=" + deltaT
|
||||||
+ " distance=" + distanceSquared);
|
+ " distance=" + distanceSquared);
|
||||||
|
@ -645,8 +649,8 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final PointerTrackerQueue queue = sPointerTrackerQueue;
|
|
||||||
final Key key = getKeyOn(x, y);
|
final Key key = getKeyOn(x, y);
|
||||||
|
final PointerTrackerQueue queue = sPointerTrackerQueue;
|
||||||
if (queue != null) {
|
if (queue != null) {
|
||||||
if (key != null && key.isModifier()) {
|
if (key != null && key.isModifier()) {
|
||||||
// Before processing a down event of modifier key, all pointers already being
|
// Before processing a down event of modifier key, all pointers already being
|
||||||
|
@ -656,20 +660,30 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
queue.add(this);
|
queue.add(this);
|
||||||
}
|
}
|
||||||
onDownEventInternal(x, y, eventTime);
|
onDownEventInternal(x, y, eventTime);
|
||||||
if (queue != null && queue.size() == 1) {
|
if (!sShouldHandleGesture) {
|
||||||
mIsPossibleGesture = false;
|
return;
|
||||||
// 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 int activePointerTrackerCount = getActivePointerTrackerCount();
|
||||||
|
if (activePointerTrackerCount == 1) {
|
||||||
|
mIsDetectingGesture = false;
|
||||||
|
// A gesture should start only from the letter key.
|
||||||
|
final boolean isAlphabetKeyboard = (mKeyboard != null)
|
||||||
|
&& mKeyboard.mId.isAlphabetKeyboard();
|
||||||
|
if (isAlphabetKeyboard && !mIsShowingMoreKeysPanel && key != null
|
||||||
|
&& Keyboard.isLetterCode(key.mCode)) {
|
||||||
|
mIsDetectingGesture = true;
|
||||||
|
sGestureFirstDownTime = eventTime;
|
||||||
|
mGestureStrokeWithPreviewTrail.addPoint(x, y, 0, false /* isHistorical */);
|
||||||
|
}
|
||||||
|
} else if (sInGesture && activePointerTrackerCount > 1) {
|
||||||
|
mIsDetectingGesture = true;
|
||||||
|
final int elapsedTimeFromFirstDown = (int)(eventTime - sGestureFirstDownTime);
|
||||||
|
mGestureStrokeWithPreviewTrail.addPoint(x, y, elapsedTimeFromFirstDown,
|
||||||
|
false /* isHistorical */);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onDownEventInternal(int x, int y, long eventTime) {
|
private void onDownEventInternal(final int x, final int y, final long eventTime) {
|
||||||
Key key = onDownKey(x, y, eventTime);
|
Key key = onDownKey(x, y, eventTime);
|
||||||
// Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding
|
// Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding
|
||||||
// from modifier key, or 3) this pointer's KeyDetector always allows sliding input.
|
// from modifier key, or 3) this pointer's KeyDetector always allows sliding input.
|
||||||
|
@ -694,40 +708,38 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startSlidingKeyInput(Key key) {
|
private void startSlidingKeyInput(final Key key) {
|
||||||
if (!mIsInSlidingKeyInput) {
|
if (!mIsInSlidingKeyInput) {
|
||||||
mIgnoreModifierKey = key.isModifier();
|
mIgnoreModifierKey = key.isModifier();
|
||||||
}
|
}
|
||||||
mIsInSlidingKeyInput = true;
|
mIsInSlidingKeyInput = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onGestureMoveEvent(PointerTracker tracker, int x, int y, long eventTime,
|
private void onGestureMoveEvent(final int x, final int y, final long eventTime,
|
||||||
boolean isHistorical, Key key) {
|
final boolean isHistorical, final Key key) {
|
||||||
final int gestureTime = (int)(eventTime - tracker.getDownTime());
|
final int gestureTime = (int)(eventTime - sGestureFirstDownTime);
|
||||||
if (sShouldHandleGesture && mIsPossibleGesture) {
|
if (mIsDetectingGesture) {
|
||||||
final GestureStroke stroke = mGestureStroke;
|
final GestureStroke stroke = mGestureStrokeWithPreviewTrail;
|
||||||
stroke.addPoint(x, y, gestureTime, isHistorical);
|
stroke.addPoint(x, y, gestureTime, isHistorical);
|
||||||
if (!mInGesture && stroke.isStartOfAGesture(gestureTime, sWasInGesture)) {
|
if (!sInGesture && stroke.isStartOfAGesture()) {
|
||||||
startBatchInput();
|
startBatchInput();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (key != null && mInGesture) {
|
if (sInGesture && key != null) {
|
||||||
final InputPointers batchPoints = getIncrementalBatchPoints();
|
updateBatchInput(eventTime);
|
||||||
mDrawingProxy.showGestureTrail(this);
|
|
||||||
if (updateBatchInputRecognitionState(eventTime, batchPoints.getPointerSize())) {
|
|
||||||
updateBatchInput(batchPoints);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onMoveEvent(int x, int y, long eventTime, MotionEvent me) {
|
public void onMoveEvent(final int x, final int y, final long eventTime, final MotionEvent me) {
|
||||||
if (DEBUG_MOVE_EVENT)
|
if (DEBUG_MOVE_EVENT) {
|
||||||
printTouchEvent("onMoveEvent:", x, y, eventTime);
|
printTouchEvent("onMoveEvent:", x, y, eventTime);
|
||||||
if (mKeyAlreadyProcessed)
|
}
|
||||||
|
if (mKeyAlreadyProcessed) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (me != null) {
|
if (sShouldHandleGesture && me != null) {
|
||||||
// Add historical points to gesture path.
|
// Add historical points to gesture path.
|
||||||
final int pointerIndex = me.findPointerIndex(mPointerId);
|
final int pointerIndex = me.findPointerIndex(mPointerId);
|
||||||
final int historicalSize = me.getHistorySize();
|
final int historicalSize = me.getHistorySize();
|
||||||
|
@ -735,24 +747,31 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
final int historicalX = (int)me.getHistoricalX(pointerIndex, h);
|
final int historicalX = (int)me.getHistoricalX(pointerIndex, h);
|
||||||
final int historicalY = (int)me.getHistoricalY(pointerIndex, h);
|
final int historicalY = (int)me.getHistoricalY(pointerIndex, h);
|
||||||
final long historicalTime = me.getHistoricalEventTime(h);
|
final long historicalTime = me.getHistoricalEventTime(h);
|
||||||
onGestureMoveEvent(this, historicalX, historicalY, historicalTime,
|
onGestureMoveEvent(historicalX, historicalY, historicalTime,
|
||||||
true /* isHistorical */, null);
|
true /* isHistorical */, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMoveEventInternal(x, y, eventTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onMoveEventInternal(final int x, final int y, final long eventTime) {
|
||||||
final int lastX = mLastX;
|
final int lastX = mLastX;
|
||||||
final int lastY = mLastY;
|
final int lastY = mLastY;
|
||||||
final Key oldKey = mCurrentKey;
|
final Key oldKey = mCurrentKey;
|
||||||
Key key = onMoveKey(x, y);
|
Key key = onMoveKey(x, y);
|
||||||
|
|
||||||
|
if (sShouldHandleGesture) {
|
||||||
// Register move event on gesture tracker.
|
// Register move event on gesture tracker.
|
||||||
onGestureMoveEvent(this, x, y, eventTime, false /* isHistorical */, key);
|
onGestureMoveEvent(x, y, eventTime, false /* isHistorical */, key);
|
||||||
if (mInGesture) {
|
if (sInGesture) {
|
||||||
mIgnoreModifierKey = true;
|
mIgnoreModifierKey = true;
|
||||||
mTimerProxy.cancelLongPressTimer();
|
mTimerProxy.cancelLongPressTimer();
|
||||||
mIsInSlidingKeyInput = true;
|
mIsInSlidingKeyInput = true;
|
||||||
mCurrentKey = null;
|
mCurrentKey = null;
|
||||||
setReleasedKeyGraphics(oldKey);
|
setReleasedKeyGraphics(oldKey);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
|
@ -797,7 +816,7 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
// TODO: Should find a way to balance gesture detection and this hack.
|
// TODO: Should find a way to balance gesture detection and this hack.
|
||||||
if (sNeedsPhantomSuddenMoveEventHack
|
if (sNeedsPhantomSuddenMoveEventHack
|
||||||
&& lastMoveSquared >= mKeyQuarterWidthSquared
|
&& lastMoveSquared >= mKeyQuarterWidthSquared
|
||||||
&& !mIsPossibleGesture) {
|
&& !mIsDetectingGesture) {
|
||||||
if (DEBUG_MODE) {
|
if (DEBUG_MODE) {
|
||||||
Log.w(TAG, String.format("onMoveEvent:"
|
Log.w(TAG, String.format("onMoveEvent:"
|
||||||
+ " phantom sudden move event is translated to "
|
+ " phantom sudden move event is translated to "
|
||||||
|
@ -815,11 +834,11 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
// touch panels when there are close multiple touches.
|
// touch panels when there are close multiple touches.
|
||||||
// Caveat: When in chording input mode with a modifier key, we don't use
|
// Caveat: When in chording input mode with a modifier key, we don't use
|
||||||
// this hack.
|
// this hack.
|
||||||
if (me != null && me.getPointerCount() > 1
|
if (getActivePointerTrackerCount() > 1 && sPointerTrackerQueue != null
|
||||||
&& !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) {
|
&& !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) {
|
||||||
onUpEventInternal();
|
onUpEventInternal();
|
||||||
}
|
}
|
||||||
if (!mIsPossibleGesture) {
|
if (!mIsDetectingGesture) {
|
||||||
mKeyAlreadyProcessed = true;
|
mKeyAlreadyProcessed = true;
|
||||||
}
|
}
|
||||||
setReleasedKeyGraphics(oldKey);
|
setReleasedKeyGraphics(oldKey);
|
||||||
|
@ -837,7 +856,7 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
if (mIsAllowedSlidingKeyInput) {
|
if (mIsAllowedSlidingKeyInput) {
|
||||||
onMoveToNewKey(key, x, y);
|
onMoveToNewKey(key, x, y);
|
||||||
} else {
|
} else {
|
||||||
if (!mIsPossibleGesture) {
|
if (!mIsDetectingGesture) {
|
||||||
mKeyAlreadyProcessed = true;
|
mKeyAlreadyProcessed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -845,13 +864,14 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onUpEvent(int x, int y, long eventTime) {
|
public void onUpEvent(final int x, final int y, final long eventTime) {
|
||||||
if (DEBUG_EVENT)
|
if (DEBUG_EVENT) {
|
||||||
printTouchEvent("onUpEvent :", x, y, eventTime);
|
printTouchEvent("onUpEvent :", x, y, eventTime);
|
||||||
|
}
|
||||||
|
|
||||||
final PointerTrackerQueue queue = sPointerTrackerQueue;
|
final PointerTrackerQueue queue = sPointerTrackerQueue;
|
||||||
if (queue != null) {
|
if (queue != null) {
|
||||||
if (!mInGesture) {
|
if (!sInGesture) {
|
||||||
if (mCurrentKey != null && mCurrentKey.isModifier()) {
|
if (mCurrentKey != null && mCurrentKey.isModifier()) {
|
||||||
// Before processing an up event of modifier key, all pointers already being
|
// Before processing an up event of modifier key, all pointers already being
|
||||||
// tracked should be released.
|
// tracked should be released.
|
||||||
|
@ -860,18 +880,21 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
queue.releaseAllPointersOlderThan(this, eventTime);
|
queue.releaseAllPointersOlderThan(this, eventTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
queue.remove(this);
|
|
||||||
}
|
}
|
||||||
onUpEventInternal();
|
onUpEventInternal();
|
||||||
|
if (queue != null) {
|
||||||
|
queue.remove(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let this pointer tracker know that one of newer-than-this pointer trackers got an up event.
|
// Let this pointer tracker know that one of newer-than-this pointer trackers got an up event.
|
||||||
// This pointer tracker needs to keep the key top graphics "pressed", but needs to get a
|
// This pointer tracker needs to keep the key top graphics "pressed", but needs to get a
|
||||||
// "virtual" up event.
|
// "virtual" up event.
|
||||||
@Override
|
@Override
|
||||||
public void onPhantomUpEvent(long eventTime) {
|
public void onPhantomUpEvent(final long eventTime) {
|
||||||
if (DEBUG_EVENT)
|
if (DEBUG_EVENT) {
|
||||||
printTouchEvent("onPhntEvent:", getLastX(), getLastY(), eventTime);
|
printTouchEvent("onPhntEvent:", getLastX(), getLastY(), eventTime);
|
||||||
|
}
|
||||||
onUpEventInternal();
|
onUpEventInternal();
|
||||||
mKeyAlreadyProcessed = true;
|
mKeyAlreadyProcessed = true;
|
||||||
}
|
}
|
||||||
|
@ -879,37 +902,35 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
private void onUpEventInternal() {
|
private void onUpEventInternal() {
|
||||||
mTimerProxy.cancelKeyTimers();
|
mTimerProxy.cancelKeyTimers();
|
||||||
mIsInSlidingKeyInput = false;
|
mIsInSlidingKeyInput = false;
|
||||||
mIsPossibleGesture = false;
|
mIsDetectingGesture = false;
|
||||||
|
final Key currentKey = mCurrentKey;
|
||||||
|
mCurrentKey = null;
|
||||||
// Release the last pressed key.
|
// Release the last pressed key.
|
||||||
setReleasedKeyGraphics(mCurrentKey);
|
setReleasedKeyGraphics(currentKey);
|
||||||
if (mIsShowingMoreKeysPanel) {
|
if (mIsShowingMoreKeysPanel) {
|
||||||
mDrawingProxy.dismissMoreKeysPanel();
|
mDrawingProxy.dismissMoreKeysPanel();
|
||||||
mIsShowingMoreKeysPanel = false;
|
mIsShowingMoreKeysPanel = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mInGesture) {
|
if (sInGesture) {
|
||||||
// Register up event on gesture tracker.
|
if (currentKey != null) {
|
||||||
// TODO: Figure out how to deal with multiple fingers that are in gesture, sliding,
|
callListenerOnRelease(currentKey, currentKey.mCode, true);
|
||||||
// and/or tapping mode?
|
|
||||||
endBatchInput(getAllBatchPoints());
|
|
||||||
if (mCurrentKey != null) {
|
|
||||||
callListenerOnRelease(mCurrentKey, mCurrentKey.mCode, true);
|
|
||||||
mCurrentKey = null;
|
|
||||||
}
|
}
|
||||||
mDrawingProxy.showGestureTrail(this);
|
endBatchInput();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// This event will be recognized as a regular code input. Clear unused batch points so they
|
// This event will be recognized as a regular code input. Clear unused possible batch points
|
||||||
// are not mistakenly included in the next batch event.
|
// so they are not mistakenly displayed as preview.
|
||||||
clearBatchInputPointsOfAllPointerTrackers();
|
clearBatchInputPointsOfAllPointerTrackers();
|
||||||
if (mKeyAlreadyProcessed)
|
if (mKeyAlreadyProcessed) {
|
||||||
return;
|
return;
|
||||||
if (mCurrentKey != null && !mCurrentKey.isRepeatable()) {
|
}
|
||||||
detectAndSendKey(mCurrentKey, mKeyX, mKeyY);
|
if (currentKey != null && !currentKey.isRepeatable()) {
|
||||||
|
detectAndSendKey(currentKey, mKeyX, mKeyY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onShowMoreKeysPanel(int x, int y, KeyEventHandler handler) {
|
public void onShowMoreKeysPanel(final int x, final int y, final KeyEventHandler handler) {
|
||||||
abortBatchInput();
|
abortBatchInput();
|
||||||
onLongPressed();
|
onLongPressed();
|
||||||
mIsShowingMoreKeysPanel = true;
|
mIsShowingMoreKeysPanel = true;
|
||||||
|
@ -925,9 +946,10 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onCancelEvent(int x, int y, long eventTime) {
|
public void onCancelEvent(final int x, final int y, final long eventTime) {
|
||||||
if (DEBUG_EVENT)
|
if (DEBUG_EVENT) {
|
||||||
printTouchEvent("onCancelEvt:", x, y, eventTime);
|
printTouchEvent("onCancelEvt:", x, y, eventTime);
|
||||||
|
}
|
||||||
|
|
||||||
final PointerTrackerQueue queue = sPointerTrackerQueue;
|
final PointerTrackerQueue queue = sPointerTrackerQueue;
|
||||||
if (queue != null) {
|
if (queue != null) {
|
||||||
|
@ -947,24 +969,25 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startRepeatKey(Key key) {
|
private void startRepeatKey(final Key key) {
|
||||||
if (key != null && key.isRepeatable() && !mInGesture) {
|
if (key != null && key.isRepeatable() && !sInGesture) {
|
||||||
onRegisterKey(key);
|
onRegisterKey(key);
|
||||||
mTimerProxy.startKeyRepeatTimer(this);
|
mTimerProxy.startKeyRepeatTimer(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onRegisterKey(Key key) {
|
public void onRegisterKey(final Key key) {
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
detectAndSendKey(key, key.mX, key.mY);
|
detectAndSendKey(key, key.mX, key.mY);
|
||||||
mTimerProxy.startTypingStateTimer(key);
|
mTimerProxy.startTypingStateTimer(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isMajorEnoughMoveToBeOnNewKey(int x, int y, Key newKey) {
|
private boolean isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final Key newKey) {
|
||||||
if (mKeyDetector == null)
|
if (mKeyDetector == null) {
|
||||||
throw new NullPointerException("keyboard and/or key detector not set");
|
throw new NullPointerException("keyboard and/or key detector not set");
|
||||||
Key curKey = mCurrentKey;
|
}
|
||||||
|
final Key curKey = mCurrentKey;
|
||||||
if (newKey == curKey) {
|
if (newKey == curKey) {
|
||||||
return false;
|
return false;
|
||||||
} else if (curKey != null) {
|
} else if (curKey != null) {
|
||||||
|
@ -975,25 +998,25 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startLongPressTimer(Key key) {
|
private void startLongPressTimer(final Key key) {
|
||||||
if (key != null && key.isLongPressEnabled() && !mInGesture) {
|
if (key != null && key.isLongPressEnabled() && !sInGesture) {
|
||||||
mTimerProxy.startLongPressTimer(this);
|
mTimerProxy.startLongPressTimer(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void detectAndSendKey(Key key, int x, int y) {
|
private void detectAndSendKey(final Key key, final int x, final int y) {
|
||||||
if (key == null) {
|
if (key == null) {
|
||||||
callListenerOnCancelInput();
|
callListenerOnCancelInput();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int code = key.mCode;
|
final int code = key.mCode;
|
||||||
callListenerOnCodeInput(key, code, x, y);
|
callListenerOnCodeInput(key, code, x, y);
|
||||||
callListenerOnRelease(key, code, false);
|
callListenerOnRelease(key, code, false);
|
||||||
sWasInGesture = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void printTouchEvent(String title, int x, int y, long eventTime) {
|
private void printTouchEvent(final String title, final int x, final int y,
|
||||||
|
final long eventTime) {
|
||||||
final Key key = mKeyDetector.detectHitKey(x, y);
|
final Key key = mKeyDetector.detectHitKey(x, y);
|
||||||
final String code = KeyDetector.printableCode(key);
|
final String code = KeyDetector.printableCode(key);
|
||||||
Log.d(TAG, String.format("%s%s[%d] %4d %4d %5d %s", title,
|
Log.d(TAG, String.format("%s%s[%d] %4d %4d %5d %s", title,
|
||||||
|
|
|
@ -18,9 +18,9 @@ package com.android.inputmethod.keyboard;
|
||||||
|
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.FloatMath;
|
|
||||||
|
|
||||||
import com.android.inputmethod.keyboard.Keyboard.Params.TouchPositionCorrection;
|
import com.android.inputmethod.keyboard.Keyboard.Params.TouchPositionCorrection;
|
||||||
|
import com.android.inputmethod.latin.Constants;
|
||||||
import com.android.inputmethod.latin.JniUtils;
|
import com.android.inputmethod.latin.JniUtils;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -112,7 +112,7 @@ public class ProximityInfo {
|
||||||
final Key[] keys = mKeys;
|
final Key[] keys = mKeys;
|
||||||
final TouchPositionCorrection touchPositionCorrection = mTouchPositionCorrection;
|
final TouchPositionCorrection touchPositionCorrection = mTouchPositionCorrection;
|
||||||
final int[] proximityCharsArray = new int[mGridSize * MAX_PROXIMITY_CHARS_SIZE];
|
final int[] proximityCharsArray = new int[mGridSize * MAX_PROXIMITY_CHARS_SIZE];
|
||||||
Arrays.fill(proximityCharsArray, KeyDetector.NOT_A_CODE);
|
Arrays.fill(proximityCharsArray, Constants.NOT_A_CODE);
|
||||||
for (int i = 0; i < mGridSize; ++i) {
|
for (int i = 0; i < mGridSize; ++i) {
|
||||||
final int proximityCharsLength = gridNeighborKeys[i].length;
|
final int proximityCharsLength = gridNeighborKeys[i].length;
|
||||||
for (int j = 0; j < proximityCharsLength; ++j) {
|
for (int j = 0; j < proximityCharsLength; ++j) {
|
||||||
|
@ -155,7 +155,9 @@ public class ProximityInfo {
|
||||||
final float radius = touchPositionCorrection.mRadii[row];
|
final float radius = touchPositionCorrection.mRadii[row];
|
||||||
sweetSpotCenterXs[i] = hitBox.exactCenterX() + x * hitBoxWidth;
|
sweetSpotCenterXs[i] = hitBox.exactCenterX() + x * hitBoxWidth;
|
||||||
sweetSpotCenterYs[i] = hitBox.exactCenterY() + y * hitBoxHeight;
|
sweetSpotCenterYs[i] = hitBox.exactCenterY() + y * hitBoxHeight;
|
||||||
sweetSpotRadii[i] = radius * FloatMath.sqrt(
|
// Note that, in recent versions of Android, FloatMath is actually slower than
|
||||||
|
// java.lang.Math due to the way the JIT optimizes java.lang.Math.
|
||||||
|
sweetSpotRadii[i] = radius * (float)Math.sqrt(
|
||||||
hitBoxWidth * hitBoxWidth + hitBoxHeight * hitBoxHeight);
|
hitBoxWidth * hitBoxWidth + hitBoxHeight * hitBoxHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,7 +235,7 @@ public class ProximityInfo {
|
||||||
dest[index++] = code;
|
dest[index++] = code;
|
||||||
}
|
}
|
||||||
if (index < destLength) {
|
if (index < destLength) {
|
||||||
dest[index] = KeyDetector.NOT_A_CODE;
|
dest[index] = Constants.NOT_A_CODE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,11 +14,6 @@
|
||||||
|
|
||||||
package com.android.inputmethod.keyboard.internal;
|
package com.android.inputmethod.keyboard.internal;
|
||||||
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.util.FloatMath;
|
|
||||||
|
|
||||||
import com.android.inputmethod.latin.Constants;
|
|
||||||
import com.android.inputmethod.latin.InputPointers;
|
import com.android.inputmethod.latin.InputPointers;
|
||||||
import com.android.inputmethod.latin.ResizableIntArray;
|
import com.android.inputmethod.latin.ResizableIntArray;
|
||||||
|
|
||||||
|
@ -38,44 +33,30 @@ public class GestureStroke {
|
||||||
private int mLastPointY;
|
private int mLastPointY;
|
||||||
|
|
||||||
private int mMinGestureLength;
|
private int mMinGestureLength;
|
||||||
private int mMinGestureLengthWhileInGesture;
|
|
||||||
private int mMinGestureSampleLength;
|
private int mMinGestureSampleLength;
|
||||||
|
|
||||||
// TODO: Move some of these to resource.
|
// TODO: Move some of these to resource.
|
||||||
private static final float MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH = 1.0f;
|
private static final float MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH = 0.75f;
|
||||||
private static final float MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH_WHILE_IN_GESTURE = 0.5f;
|
private static final int MIN_GESTURE_DURATION = 100; // msec
|
||||||
private static final int MIN_GESTURE_DURATION = 150; // msec
|
private static final float MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH = 1.0f / 6.0f;
|
||||||
private static final int MIN_GESTURE_DURATION_WHILE_IN_GESTURE = 75; // msec
|
|
||||||
private static final float MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT = 1.0f / 6.0f;
|
|
||||||
private static final float GESTURE_RECOG_SPEED_THRESHOLD = 0.4f; // dip/msec
|
private static final float GESTURE_RECOG_SPEED_THRESHOLD = 0.4f; // dip/msec
|
||||||
private static final float GESTURE_RECOG_CURVATURE_THRESHOLD = (float)(Math.PI / 4.0f);
|
private static final float GESTURE_RECOG_CURVATURE_THRESHOLD = (float)(Math.PI / 4.0f);
|
||||||
|
|
||||||
private static final float DOUBLE_PI = (float)(2 * Math.PI);
|
private static final float DOUBLE_PI = (float)(2.0f * Math.PI);
|
||||||
|
|
||||||
// Fade based on number of gesture samples, see MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT
|
public GestureStroke(final int pointerId) {
|
||||||
private static final int DRAWING_GESTURE_FADE_START = 10;
|
|
||||||
private static final int DRAWING_GESTURE_FADE_RATE = 6;
|
|
||||||
|
|
||||||
public GestureStroke(int pointerId) {
|
|
||||||
mPointerId = pointerId;
|
mPointerId = pointerId;
|
||||||
reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setGestureSampleLength(final int keyWidth, final int keyHeight) {
|
public void setGestureSampleLength(final int keyWidth) {
|
||||||
// TODO: Find an appropriate base metric for these length. Maybe diagonal length of the key?
|
// TODO: Find an appropriate base metric for these length. Maybe diagonal length of the key?
|
||||||
mMinGestureLength = (int)(keyWidth * MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH);
|
mMinGestureLength = (int)(keyWidth * MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH);
|
||||||
mMinGestureLengthWhileInGesture = (int)(
|
mMinGestureSampleLength = (int)(keyWidth * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH);
|
||||||
keyWidth * MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH_WHILE_IN_GESTURE);
|
|
||||||
mMinGestureSampleLength = (int)(keyHeight * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isStartOfAGesture(final int downDuration, final boolean wasInGesture) {
|
public boolean isStartOfAGesture() {
|
||||||
// The tolerance of the time duration and the stroke length to detect the start of a
|
final int size = mEventTimes.getLength();
|
||||||
// gesture stroke should be eased when the previous input was a gesture input.
|
final int downDuration = (size > 0) ? mEventTimes.get(size - 1) : 0;
|
||||||
if (wasInGesture) {
|
|
||||||
return downDuration > MIN_GESTURE_DURATION_WHILE_IN_GESTURE
|
|
||||||
&& mLength > mMinGestureLengthWhileInGesture;
|
|
||||||
}
|
|
||||||
return downDuration > MIN_GESTURE_DURATION && mLength > mMinGestureLength;
|
return downDuration > MIN_GESTURE_DURATION && mLength > mMinGestureLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,23 +130,29 @@ public class GestureStroke {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void appendBatchPoints(final InputPointers out, final int size) {
|
private void appendBatchPoints(final InputPointers out, final int size) {
|
||||||
|
final int length = size - mLastIncrementalBatchSize;
|
||||||
|
if (length <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
out.append(mPointerId, mEventTimes, mXCoordinates, mYCoordinates,
|
out.append(mPointerId, mEventTimes, mXCoordinates, mYCoordinates,
|
||||||
mLastIncrementalBatchSize, size - mLastIncrementalBatchSize);
|
mLastIncrementalBatchSize, length);
|
||||||
mLastIncrementalBatchSize = size;
|
mLastIncrementalBatchSize = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static float getDistance(final int p1x, final int p1y,
|
private static float getDistance(final int x1, final int y1, final int x2, final int y2) {
|
||||||
final int p2x, final int p2y) {
|
final float dx = x1 - x2;
|
||||||
final float dx = p1x - p2x;
|
final float dy = y1 - y2;
|
||||||
final float dy = p1y - p2y;
|
// Note that, in recent versions of Android, FloatMath is actually slower than
|
||||||
// TODO: Optimize out this {@link FloatMath#sqrt(float)} call.
|
// java.lang.Math due to the way the JIT optimizes java.lang.Math.
|
||||||
return FloatMath.sqrt(dx * dx + dy * dy);
|
return (float)Math.sqrt(dx * dx + dy * dy);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static float getAngle(final int p1x, final int p1y, final int p2x, final int p2y) {
|
private static float getAngle(final int x1, final int y1, final int x2, final int y2) {
|
||||||
final int dx = p1x - p2x;
|
final int dx = x1 - x2;
|
||||||
final int dy = p1y - p2y;
|
final int dy = y1 - y2;
|
||||||
if (dx == 0 && dy == 0) return 0;
|
if (dx == 0 && dy == 0) return 0;
|
||||||
|
// Would it be faster to call atan2f() directly via JNI? Not sure about what the JIT
|
||||||
|
// does with Math.atan2().
|
||||||
return (float)Math.atan2(dy, dx);
|
return (float)Math.atan2(dy, dx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,23 +163,4 @@ public class GestureStroke {
|
||||||
}
|
}
|
||||||
return diff;
|
return diff;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void drawGestureTrail(Canvas canvas, Paint paint, int lastX, int lastY) {
|
|
||||||
// TODO: These paint parameter interpolation should be tunable, possibly introduce an object
|
|
||||||
// that implements an interface such as Paint getPaint(int step, int strokePoints)
|
|
||||||
final int size = mXCoordinates.getLength();
|
|
||||||
int[] xCoords = mXCoordinates.getPrimitiveArray();
|
|
||||||
int[] yCoords = mYCoordinates.getPrimitiveArray();
|
|
||||||
int alpha = Constants.Color.ALPHA_OPAQUE;
|
|
||||||
for (int i = size - 1; i > 0 && alpha > 0; i--) {
|
|
||||||
paint.setAlpha(alpha);
|
|
||||||
if (size - i > DRAWING_GESTURE_FADE_START) {
|
|
||||||
alpha -= DRAWING_GESTURE_FADE_RATE;
|
|
||||||
}
|
|
||||||
canvas.drawLine(xCoords[i - 1], yCoords[i - 1], xCoords[i], yCoords[i], paint);
|
|
||||||
if (i == size - 1) {
|
|
||||||
canvas.drawLine(lastX, lastY, xCoords[i], yCoords[i], paint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,6 +21,7 @@ import static com.android.inputmethod.keyboard.Keyboard.CODE_UNSPECIFIED;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import com.android.inputmethod.keyboard.Keyboard;
|
import com.android.inputmethod.keyboard.Keyboard;
|
||||||
|
import com.android.inputmethod.latin.CollectionUtils;
|
||||||
import com.android.inputmethod.latin.LatinImeLogger;
|
import com.android.inputmethod.latin.LatinImeLogger;
|
||||||
import com.android.inputmethod.latin.StringUtils;
|
import com.android.inputmethod.latin.StringUtils;
|
||||||
|
|
||||||
|
@ -258,7 +259,7 @@ public class KeySpecParser {
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
}
|
}
|
||||||
|
|
||||||
final ArrayList<T> list = new ArrayList<T>(end - start);
|
final ArrayList<T> list = CollectionUtils.newArrayList(end - start);
|
||||||
for (int i = start; i < end; i++) {
|
for (int i = start; i < end; i++) {
|
||||||
list.add(array[i]);
|
list.add(array[i]);
|
||||||
}
|
}
|
||||||
|
@ -438,7 +439,7 @@ public class KeySpecParser {
|
||||||
// Skip empty entry.
|
// Skip empty entry.
|
||||||
if (pos - start > 0) {
|
if (pos - start > 0) {
|
||||||
if (list == null) {
|
if (list == null) {
|
||||||
list = new ArrayList<String>();
|
list = CollectionUtils.newArrayList();
|
||||||
}
|
}
|
||||||
list.add(text.substring(start, pos));
|
list.add(text.substring(start, pos));
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import android.util.Log;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
|
|
||||||
import com.android.inputmethod.keyboard.Keyboard;
|
import com.android.inputmethod.keyboard.Keyboard;
|
||||||
|
import com.android.inputmethod.latin.CollectionUtils;
|
||||||
import com.android.inputmethod.latin.R;
|
import com.android.inputmethod.latin.R;
|
||||||
import com.android.inputmethod.latin.XmlParseUtils;
|
import com.android.inputmethod.latin.XmlParseUtils;
|
||||||
|
|
||||||
|
@ -33,7 +34,7 @@ public class KeyStyles {
|
||||||
private static final String TAG = KeyStyles.class.getSimpleName();
|
private static final String TAG = KeyStyles.class.getSimpleName();
|
||||||
private static final boolean DEBUG = false;
|
private static final boolean DEBUG = false;
|
||||||
|
|
||||||
final HashMap<String, KeyStyle> mStyles = new HashMap<String, KeyStyle>();
|
final HashMap<String, KeyStyle> mStyles = CollectionUtils.newHashMap();
|
||||||
|
|
||||||
final KeyboardTextsSet mTextsSet;
|
final KeyboardTextsSet mTextsSet;
|
||||||
private final KeyStyle mEmptyKeyStyle;
|
private final KeyStyle mEmptyKeyStyle;
|
||||||
|
@ -90,7 +91,7 @@ public class KeyStyles {
|
||||||
|
|
||||||
private class DeclaredKeyStyle extends KeyStyle {
|
private class DeclaredKeyStyle extends KeyStyle {
|
||||||
private final String mParentStyleName;
|
private final String mParentStyleName;
|
||||||
private final SparseArray<Object> mStyleAttributes = new SparseArray<Object>();
|
private final SparseArray<Object> mStyleAttributes = CollectionUtils.newSparseArray();
|
||||||
|
|
||||||
public DeclaredKeyStyle(String parentStyleName) {
|
public DeclaredKeyStyle(String parentStyleName) {
|
||||||
mParentStyleName = parentStyleName;
|
mParentStyleName = parentStyleName;
|
||||||
|
|
|
@ -17,13 +17,13 @@
|
||||||
package com.android.inputmethod.keyboard.internal;
|
package com.android.inputmethod.keyboard.internal;
|
||||||
|
|
||||||
import com.android.inputmethod.keyboard.Keyboard;
|
import com.android.inputmethod.keyboard.Keyboard;
|
||||||
|
import com.android.inputmethod.latin.CollectionUtils;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
public class KeyboardCodesSet {
|
public class KeyboardCodesSet {
|
||||||
private static final HashMap<String, int[]> sLanguageToCodesMap =
|
private static final HashMap<String, int[]> sLanguageToCodesMap = CollectionUtils.newHashMap();
|
||||||
new HashMap<String, int[]>();
|
private static final HashMap<String, Integer> sNameToIdMap = CollectionUtils.newHashMap();
|
||||||
private static final HashMap<String, Integer> sNameToIdMap = new HashMap<String, Integer>();
|
|
||||||
|
|
||||||
private int[] mCodes = DEFAULT;
|
private int[] mCodes = DEFAULT;
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ import android.graphics.drawable.Drawable;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.SparseIntArray;
|
import android.util.SparseIntArray;
|
||||||
|
|
||||||
|
import com.android.inputmethod.latin.CollectionUtils;
|
||||||
import com.android.inputmethod.latin.R;
|
import com.android.inputmethod.latin.R;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -35,7 +36,7 @@ public class KeyboardIconsSet {
|
||||||
private static final SparseIntArray ATTR_ID_TO_ICON_ID = new SparseIntArray();
|
private static final SparseIntArray ATTR_ID_TO_ICON_ID = new SparseIntArray();
|
||||||
|
|
||||||
// Icon name to icon id map.
|
// Icon name to icon id map.
|
||||||
private static final HashMap<String, Integer> sNameToIdsMap = new HashMap<String, Integer>();
|
private static final HashMap<String, Integer> sNameToIdsMap = CollectionUtils.newHashMap();
|
||||||
|
|
||||||
private static final Object[] NAMES_AND_ATTR_IDS = {
|
private static final Object[] NAMES_AND_ATTR_IDS = {
|
||||||
"undefined", ATTR_UNDEFINED,
|
"undefined", ATTR_UNDEFINED,
|
||||||
|
|
|
@ -19,6 +19,7 @@ package com.android.inputmethod.keyboard.internal;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
|
|
||||||
|
import com.android.inputmethod.latin.CollectionUtils;
|
||||||
import com.android.inputmethod.latin.R;
|
import com.android.inputmethod.latin.R;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -45,14 +46,12 @@ import java.util.HashMap;
|
||||||
*/
|
*/
|
||||||
public final class KeyboardTextsSet {
|
public final class KeyboardTextsSet {
|
||||||
// Language to texts map.
|
// Language to texts map.
|
||||||
private static final HashMap<String, String[]> sLocaleToTextsMap =
|
private static final HashMap<String, String[]> sLocaleToTextsMap = CollectionUtils.newHashMap();
|
||||||
new HashMap<String, String[]>();
|
private static final HashMap<String, Integer> sNameToIdsMap = CollectionUtils.newHashMap();
|
||||||
private static final HashMap<String, Integer> sNameToIdsMap =
|
|
||||||
new HashMap<String, Integer>();
|
|
||||||
|
|
||||||
private String[] mTexts;
|
private String[] mTexts;
|
||||||
// Resource name to text map.
|
// Resource name to text map.
|
||||||
private HashMap<String, String> mResourceNameToTextsMap = new HashMap<String, String>();
|
private HashMap<String, String> mResourceNameToTextsMap = CollectionUtils.newHashMap();
|
||||||
|
|
||||||
public void setLanguage(final String language) {
|
public void setLanguage(final String language) {
|
||||||
mTexts = sLocaleToTextsMap.get(language);
|
mTexts = sLocaleToTextsMap.get(language);
|
||||||
|
|
|
@ -18,85 +18,148 @@ package com.android.inputmethod.keyboard.internal;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.util.Iterator;
|
import com.android.inputmethod.latin.CollectionUtils;
|
||||||
import java.util.LinkedList;
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
public class PointerTrackerQueue {
|
public class PointerTrackerQueue {
|
||||||
private static final String TAG = PointerTrackerQueue.class.getSimpleName();
|
private static final String TAG = PointerTrackerQueue.class.getSimpleName();
|
||||||
private static final boolean DEBUG = false;
|
private static final boolean DEBUG = false;
|
||||||
|
|
||||||
public interface ElementActions {
|
public interface Element {
|
||||||
public boolean isModifier();
|
public boolean isModifier();
|
||||||
public boolean isInSlidingKeyInput();
|
public boolean isInSlidingKeyInput();
|
||||||
public void onPhantomUpEvent(long eventTime);
|
public void onPhantomUpEvent(long eventTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Use ring buffer instead of {@link LinkedList}.
|
private static final int INITIAL_CAPACITY = 10;
|
||||||
private final LinkedList<ElementActions> mQueue = new LinkedList<ElementActions>();
|
private final ArrayList<Element> mExpandableArrayOfActivePointers =
|
||||||
|
CollectionUtils.newArrayList(INITIAL_CAPACITY);
|
||||||
|
private int mArraySize = 0;
|
||||||
|
|
||||||
public int size() {
|
public synchronized int size() {
|
||||||
return mQueue.size();
|
return mArraySize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void add(ElementActions tracker) {
|
public synchronized void add(final Element pointer) {
|
||||||
mQueue.add(tracker);
|
final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
|
||||||
|
final int arraySize = mArraySize;
|
||||||
|
if (arraySize < expandableArray.size()) {
|
||||||
|
expandableArray.set(arraySize, pointer);
|
||||||
|
} else {
|
||||||
|
expandableArray.add(pointer);
|
||||||
|
}
|
||||||
|
mArraySize = arraySize + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void remove(ElementActions tracker) {
|
public synchronized void remove(final Element pointer) {
|
||||||
mQueue.remove(tracker);
|
final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
|
||||||
|
final int arraySize = mArraySize;
|
||||||
|
int newSize = 0;
|
||||||
|
for (int index = 0; index < arraySize; index++) {
|
||||||
|
final Element element = expandableArray.get(index);
|
||||||
|
if (element == pointer) {
|
||||||
|
if (newSize != index) {
|
||||||
|
Log.w(TAG, "Found duplicated element in remove: " + pointer);
|
||||||
|
}
|
||||||
|
continue; // Remove this element from the expandableArray.
|
||||||
|
}
|
||||||
|
if (newSize != index) {
|
||||||
|
// Shift this element toward the beginning of the expandableArray.
|
||||||
|
expandableArray.set(newSize, element);
|
||||||
|
}
|
||||||
|
newSize++;
|
||||||
|
}
|
||||||
|
mArraySize = newSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void releaseAllPointersOlderThan(ElementActions tracker,
|
public synchronized void releaseAllPointersOlderThan(final Element pointer,
|
||||||
long eventTime) {
|
final long eventTime) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "releaseAllPoniterOlderThan: " + tracker + " " + this);
|
Log.d(TAG, "releaseAllPoniterOlderThan: " + pointer + " " + this);
|
||||||
}
|
}
|
||||||
if (!mQueue.contains(tracker)) {
|
final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
|
||||||
return;
|
final int arraySize = mArraySize;
|
||||||
|
int newSize, index;
|
||||||
|
for (newSize = index = 0; index < arraySize; index++) {
|
||||||
|
final Element element = expandableArray.get(index);
|
||||||
|
if (element == pointer) {
|
||||||
|
break; // Stop releasing elements.
|
||||||
}
|
}
|
||||||
final Iterator<ElementActions> it = mQueue.iterator();
|
if (!element.isModifier()) {
|
||||||
while (it.hasNext()) {
|
element.onPhantomUpEvent(eventTime);
|
||||||
final ElementActions t = it.next();
|
continue; // Remove this element from the expandableArray.
|
||||||
if (t == tracker) {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
if (!t.isModifier()) {
|
if (newSize != index) {
|
||||||
t.onPhantomUpEvent(eventTime);
|
// Shift this element toward the beginning of the expandableArray.
|
||||||
it.remove();
|
expandableArray.set(newSize, element);
|
||||||
|
}
|
||||||
|
newSize++;
|
||||||
|
}
|
||||||
|
// Shift rest of the expandableArray.
|
||||||
|
int count = 0;
|
||||||
|
for (; index < arraySize; index++) {
|
||||||
|
final Element element = expandableArray.get(index);
|
||||||
|
if (element == pointer) {
|
||||||
|
if (count > 0) {
|
||||||
|
Log.w(TAG, "Found duplicated element in releaseAllPointersOlderThan: "
|
||||||
|
+ pointer);
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
if (newSize != index) {
|
||||||
|
expandableArray.set(newSize, expandableArray.get(index));
|
||||||
|
newSize++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mArraySize = newSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void releaseAllPointers(long eventTime) {
|
public void releaseAllPointers(final long eventTime) {
|
||||||
releaseAllPointersExcept(null, eventTime);
|
releaseAllPointersExcept(null, eventTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void releaseAllPointersExcept(ElementActions tracker, long eventTime) {
|
public synchronized void releaseAllPointersExcept(final Element pointer,
|
||||||
|
final long eventTime) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
if (tracker == null) {
|
if (pointer == null) {
|
||||||
Log.d(TAG, "releaseAllPoniters: " + this);
|
Log.d(TAG, "releaseAllPoniters: " + this);
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "releaseAllPoniterExcept: " + tracker + " " + this);
|
Log.d(TAG, "releaseAllPoniterExcept: " + pointer + " " + this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final Iterator<ElementActions> it = mQueue.iterator();
|
final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
|
||||||
while (it.hasNext()) {
|
final int arraySize = mArraySize;
|
||||||
final ElementActions t = it.next();
|
int newSize = 0, count = 0;
|
||||||
if (t != tracker) {
|
for (int index = 0; index < arraySize; index++) {
|
||||||
t.onPhantomUpEvent(eventTime);
|
final Element element = expandableArray.get(index);
|
||||||
it.remove();
|
if (element == pointer) {
|
||||||
|
if (count > 0) {
|
||||||
|
Log.w(TAG, "Found duplicated element in releaseAllPointersExcept: " + pointer);
|
||||||
}
|
}
|
||||||
|
count++;
|
||||||
|
} else {
|
||||||
|
element.onPhantomUpEvent(eventTime);
|
||||||
|
continue; // Remove this element from the expandableArray.
|
||||||
}
|
}
|
||||||
|
if (newSize != index) {
|
||||||
|
// Shift this element toward the beginning of the expandableArray.
|
||||||
|
expandableArray.set(newSize, element);
|
||||||
|
}
|
||||||
|
newSize++;
|
||||||
|
}
|
||||||
|
mArraySize = newSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized boolean hasModifierKeyOlderThan(ElementActions tracker) {
|
public synchronized boolean hasModifierKeyOlderThan(final Element pointer) {
|
||||||
final Iterator<ElementActions> it = mQueue.iterator();
|
final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
|
||||||
while (it.hasNext()) {
|
final int arraySize = mArraySize;
|
||||||
final ElementActions t = it.next();
|
for (int index = 0; index < arraySize; index++) {
|
||||||
if (t == tracker) {
|
final Element element = expandableArray.get(index);
|
||||||
break;
|
if (element == pointer) {
|
||||||
|
return false; // Stop searching modifier key.
|
||||||
}
|
}
|
||||||
if (t.isModifier()) {
|
if (element.isModifier()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,8 +167,11 @@ public class PointerTrackerQueue {
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized boolean isAnyInSlidingKeyInput() {
|
public synchronized boolean isAnyInSlidingKeyInput() {
|
||||||
for (final ElementActions tracker : mQueue) {
|
final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
|
||||||
if (tracker.isInSlidingKeyInput()) {
|
final int arraySize = mArraySize;
|
||||||
|
for (int index = 0; index < arraySize; index++) {
|
||||||
|
final Element element = expandableArray.get(index);
|
||||||
|
if (element.isInSlidingKeyInput()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,12 +179,15 @@ public class PointerTrackerQueue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public synchronized String toString() {
|
||||||
final StringBuilder sb = new StringBuilder();
|
final StringBuilder sb = new StringBuilder();
|
||||||
for (final ElementActions tracker : mQueue) {
|
final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
|
||||||
|
final int arraySize = mArraySize;
|
||||||
|
for (int index = 0; index < arraySize; index++) {
|
||||||
|
final Element element = expandableArray.get(index);
|
||||||
if (sb.length() > 0)
|
if (sb.length() > 0)
|
||||||
sb.append(" ");
|
sb.append(" ");
|
||||||
sb.append(tracker.toString());
|
sb.append(element.toString());
|
||||||
}
|
}
|
||||||
return "[" + sb.toString() + "]";
|
return "[" + sb.toString() + "]";
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,10 +23,13 @@ import android.graphics.Paint;
|
||||||
import android.graphics.Paint.Align;
|
import android.graphics.Paint.Align;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.util.AttributeSet;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
|
|
||||||
import com.android.inputmethod.keyboard.PointerTracker;
|
import com.android.inputmethod.keyboard.PointerTracker;
|
||||||
|
import com.android.inputmethod.keyboard.internal.GesturePreviewTrail.GesturePreviewTrailParams;
|
||||||
|
import com.android.inputmethod.latin.CollectionUtils;
|
||||||
import com.android.inputmethod.latin.R;
|
import com.android.inputmethod.latin.R;
|
||||||
import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
|
import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
|
||||||
|
|
||||||
|
@ -46,29 +49,42 @@ public class PreviewPlacerView extends RelativeLayout {
|
||||||
private int mXOrigin;
|
private int mXOrigin;
|
||||||
private int mYOrigin;
|
private int mYOrigin;
|
||||||
|
|
||||||
private final SparseArray<PointerTracker> mPointers = new SparseArray<PointerTracker>();
|
private final SparseArray<GesturePreviewTrail> mGesturePreviewTrails =
|
||||||
|
CollectionUtils.newSparseArray();
|
||||||
|
private final GesturePreviewTrailParams mGesturePreviewTrailParams;
|
||||||
|
|
||||||
private String mGestureFloatingPreviewText;
|
private String mGestureFloatingPreviewText;
|
||||||
|
private int mLastPointerX;
|
||||||
|
private int mLastPointerY;
|
||||||
|
|
||||||
private boolean mDrawsGesturePreviewTrail;
|
private boolean mDrawsGesturePreviewTrail;
|
||||||
private boolean mDrawsGestureFloatingPreviewText;
|
private boolean mDrawsGestureFloatingPreviewText;
|
||||||
|
|
||||||
private final DrawingHandler mDrawingHandler = new DrawingHandler(this);
|
private final DrawingHandler mDrawingHandler;
|
||||||
|
|
||||||
private static class DrawingHandler extends StaticInnerHandlerWrapper<PreviewPlacerView> {
|
private static class DrawingHandler extends StaticInnerHandlerWrapper<PreviewPlacerView> {
|
||||||
private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 0;
|
private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 0;
|
||||||
|
private static final int MSG_UPDATE_GESTURE_PREVIEW_TRAIL = 1;
|
||||||
|
|
||||||
public DrawingHandler(PreviewPlacerView outerInstance) {
|
private final GesturePreviewTrailParams mGesturePreviewTrailParams;
|
||||||
|
|
||||||
|
public DrawingHandler(final PreviewPlacerView outerInstance,
|
||||||
|
final GesturePreviewTrailParams gesturePreviewTrailParams) {
|
||||||
super(outerInstance);
|
super(outerInstance);
|
||||||
|
mGesturePreviewTrailParams = gesturePreviewTrailParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleMessage(Message msg) {
|
public void handleMessage(final Message msg) {
|
||||||
final PreviewPlacerView placerView = getOuterInstance();
|
final PreviewPlacerView placerView = getOuterInstance();
|
||||||
if (placerView == null) return;
|
if (placerView == null) return;
|
||||||
switch (msg.what) {
|
switch (msg.what) {
|
||||||
case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT:
|
case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT:
|
||||||
placerView.setGestureFloatingPreviewText(null);
|
placerView.setGestureFloatingPreviewText(null);
|
||||||
break;
|
break;
|
||||||
|
case MSG_UPDATE_GESTURE_PREVIEW_TRAIL:
|
||||||
|
placerView.invalidate();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,15 +100,32 @@ public class PreviewPlacerView extends RelativeLayout {
|
||||||
placerView.mGestureFloatingPreviewTextLingerTimeout);
|
placerView.mGestureFloatingPreviewTextLingerTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void cancelUpdateGestureTrailPreview() {
|
||||||
|
removeMessages(MSG_UPDATE_GESTURE_PREVIEW_TRAIL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void postUpdateGestureTrailPreview() {
|
||||||
|
cancelUpdateGestureTrailPreview();
|
||||||
|
sendMessageDelayed(obtainMessage(MSG_UPDATE_GESTURE_PREVIEW_TRAIL),
|
||||||
|
mGesturePreviewTrailParams.mUpdateInterval);
|
||||||
|
}
|
||||||
|
|
||||||
public void cancelAllMessages() {
|
public void cancelAllMessages() {
|
||||||
cancelDismissGestureFloatingPreviewText();
|
cancelDismissGestureFloatingPreviewText();
|
||||||
|
cancelUpdateGestureTrailPreview();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public PreviewPlacerView(Context context, TypedArray keyboardViewAttr) {
|
public PreviewPlacerView(final Context context, final AttributeSet attrs) {
|
||||||
|
this(context, attrs, R.attr.keyboardViewStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PreviewPlacerView(final Context context, final AttributeSet attrs, final int defStyle) {
|
||||||
super(context);
|
super(context);
|
||||||
setWillNotDraw(false);
|
setWillNotDraw(false);
|
||||||
|
|
||||||
|
final TypedArray keyboardViewAttr = context.obtainStyledAttributes(
|
||||||
|
attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
|
||||||
final int gestureFloatingPreviewTextSize = keyboardViewAttr.getDimensionPixelSize(
|
final int gestureFloatingPreviewTextSize = keyboardViewAttr.getDimensionPixelSize(
|
||||||
R.styleable.KeyboardView_gestureFloatingPreviewTextSize, 0);
|
R.styleable.KeyboardView_gestureFloatingPreviewTextSize, 0);
|
||||||
mGestureFloatingPreviewTextColor = keyboardViewAttr.getColor(
|
mGestureFloatingPreviewTextColor = keyboardViewAttr.getColor(
|
||||||
|
@ -117,6 +150,10 @@ public class PreviewPlacerView extends RelativeLayout {
|
||||||
R.styleable.KeyboardView_gesturePreviewTrailColor, 0);
|
R.styleable.KeyboardView_gesturePreviewTrailColor, 0);
|
||||||
final int gesturePreviewTrailWidth = keyboardViewAttr.getDimensionPixelSize(
|
final int gesturePreviewTrailWidth = keyboardViewAttr.getDimensionPixelSize(
|
||||||
R.styleable.KeyboardView_gesturePreviewTrailWidth, 0);
|
R.styleable.KeyboardView_gesturePreviewTrailWidth, 0);
|
||||||
|
mGesturePreviewTrailParams = new GesturePreviewTrailParams(keyboardViewAttr);
|
||||||
|
keyboardViewAttr.recycle();
|
||||||
|
|
||||||
|
mDrawingHandler = new DrawingHandler(this, mGesturePreviewTrailParams);
|
||||||
|
|
||||||
mGesturePaint = new Paint();
|
mGesturePaint = new Paint();
|
||||||
mGesturePaint.setAntiAlias(true);
|
mGesturePaint.setAntiAlias(true);
|
||||||
|
@ -132,48 +169,60 @@ public class PreviewPlacerView extends RelativeLayout {
|
||||||
mTextPaint.setTextSize(gestureFloatingPreviewTextSize);
|
mTextPaint.setTextSize(gestureFloatingPreviewTextSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOrigin(int x, int y) {
|
public void setOrigin(final int x, final int y) {
|
||||||
mXOrigin = x;
|
mXOrigin = x;
|
||||||
mYOrigin = y;
|
mYOrigin = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setGesturePreviewMode(boolean drawsGesturePreviewTrail,
|
public void setGesturePreviewMode(final boolean drawsGesturePreviewTrail,
|
||||||
boolean drawsGestureFloatingPreviewText) {
|
final boolean drawsGestureFloatingPreviewText) {
|
||||||
mDrawsGesturePreviewTrail = drawsGesturePreviewTrail;
|
mDrawsGesturePreviewTrail = drawsGesturePreviewTrail;
|
||||||
mDrawsGestureFloatingPreviewText = drawsGestureFloatingPreviewText;
|
mDrawsGestureFloatingPreviewText = drawsGestureFloatingPreviewText;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void invalidatePointer(PointerTracker tracker) {
|
public void invalidatePointer(final PointerTracker tracker) {
|
||||||
synchronized (mPointers) {
|
GesturePreviewTrail trail;
|
||||||
mPointers.put(tracker.mPointerId, tracker);
|
synchronized (mGesturePreviewTrails) {
|
||||||
|
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.
|
// TODO: Should narrow the invalidate region.
|
||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDraw(Canvas canvas) {
|
public void onDraw(final Canvas canvas) {
|
||||||
super.onDraw(canvas);
|
super.onDraw(canvas);
|
||||||
synchronized (mPointers) {
|
|
||||||
canvas.translate(mXOrigin, mYOrigin);
|
canvas.translate(mXOrigin, mYOrigin);
|
||||||
final int trackerCount = mPointers.size();
|
|
||||||
boolean hasDrawnFloatingPreviewText = false;
|
|
||||||
for (int index = 0; index < trackerCount; index++) {
|
|
||||||
final PointerTracker tracker = mPointers.valueAt(index);
|
|
||||||
if (mDrawsGesturePreviewTrail) {
|
if (mDrawsGesturePreviewTrail) {
|
||||||
tracker.drawGestureTrail(canvas, mGesturePaint);
|
boolean needsUpdatingGesturePreviewTrail = false;
|
||||||
|
synchronized (mGesturePreviewTrails) {
|
||||||
|
// Trails count == fingers count that have ever been active.
|
||||||
|
final int trailsCount = mGesturePreviewTrails.size();
|
||||||
|
for (int index = 0; index < trailsCount; index++) {
|
||||||
|
final GesturePreviewTrail trail = mGesturePreviewTrails.valueAt(index);
|
||||||
|
needsUpdatingGesturePreviewTrail |=
|
||||||
|
trail.drawGestureTrail(canvas, mGesturePaint);
|
||||||
}
|
}
|
||||||
// TODO: Figure out more cleaner way to draw gesture preview text.
|
|
||||||
if (mDrawsGestureFloatingPreviewText && !hasDrawnFloatingPreviewText) {
|
|
||||||
drawGestureFloatingPreviewText(canvas, tracker, mGestureFloatingPreviewText);
|
|
||||||
hasDrawnFloatingPreviewText = true;
|
|
||||||
}
|
}
|
||||||
|
if (needsUpdatingGesturePreviewTrail) {
|
||||||
|
mDrawingHandler.postUpdateGestureTrailPreview();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mDrawsGestureFloatingPreviewText) {
|
||||||
|
drawGestureFloatingPreviewText(canvas, mGestureFloatingPreviewText);
|
||||||
}
|
}
|
||||||
canvas.translate(-mXOrigin, -mYOrigin);
|
canvas.translate(-mXOrigin, -mYOrigin);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void setGestureFloatingPreviewText(String gestureFloatingPreviewText) {
|
public void setGestureFloatingPreviewText(final String gestureFloatingPreviewText) {
|
||||||
mGestureFloatingPreviewText = gestureFloatingPreviewText;
|
mGestureFloatingPreviewText = gestureFloatingPreviewText;
|
||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
|
@ -186,15 +235,17 @@ public class PreviewPlacerView extends RelativeLayout {
|
||||||
mDrawingHandler.cancelAllMessages();
|
mDrawingHandler.cancelAllMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawGestureFloatingPreviewText(Canvas canvas, PointerTracker tracker,
|
private void drawGestureFloatingPreviewText(final Canvas canvas,
|
||||||
String gestureFloatingPreviewText) {
|
final String gestureFloatingPreviewText) {
|
||||||
if (TextUtils.isEmpty(gestureFloatingPreviewText)) {
|
if (TextUtils.isEmpty(gestureFloatingPreviewText)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Paint paint = mTextPaint;
|
final Paint paint = mTextPaint;
|
||||||
final int lastX = tracker.getLastX();
|
// TODO: Figure out how we should deal with the floating preview text with multiple moving
|
||||||
final int lastY = tracker.getLastY();
|
// fingers.
|
||||||
|
final int lastX = mLastPointerX;
|
||||||
|
final int lastY = mLastPointerY;
|
||||||
final int textSize = (int)paint.getTextSize();
|
final int textSize = (int)paint.getTextSize();
|
||||||
final int canvasWidth = canvas.getWidth();
|
final int canvasWidth = canvas.getWidth();
|
||||||
|
|
||||||
|
|
|
@ -91,7 +91,7 @@ public class AdditionalSubtype {
|
||||||
}
|
}
|
||||||
final String[] prefSubtypeArray = prefSubtypes.split(PREF_SUBTYPE_SEPARATOR);
|
final String[] prefSubtypeArray = prefSubtypes.split(PREF_SUBTYPE_SEPARATOR);
|
||||||
final ArrayList<InputMethodSubtype> subtypesList =
|
final ArrayList<InputMethodSubtype> subtypesList =
|
||||||
new ArrayList<InputMethodSubtype>(prefSubtypeArray.length);
|
CollectionUtils.newArrayList(prefSubtypeArray.length);
|
||||||
for (final String prefSubtype : prefSubtypeArray) {
|
for (final String prefSubtype : prefSubtypeArray) {
|
||||||
final InputMethodSubtype subtype = createAdditionalSubtype(prefSubtype);
|
final InputMethodSubtype subtype = createAdditionalSubtype(prefSubtype);
|
||||||
if (subtype.getNameResId() == SubtypeLocale.UNKNOWN_KEYBOARD_LAYOUT) {
|
if (subtype.getNameResId() == SubtypeLocale.UNKNOWN_KEYBOARD_LAYOUT) {
|
||||||
|
|
|
@ -89,7 +89,7 @@ public class AdditionalSubtypeSettings extends PreferenceFragment {
|
||||||
super(context, android.R.layout.simple_spinner_item);
|
super(context, android.R.layout.simple_spinner_item);
|
||||||
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
|
|
||||||
final TreeSet<SubtypeLocaleItem> items = new TreeSet<SubtypeLocaleItem>();
|
final TreeSet<SubtypeLocaleItem> items = CollectionUtils.newTreeSet();
|
||||||
final InputMethodInfo imi = ImfUtils.getInputMethodInfoOfThisIme(context);
|
final InputMethodInfo imi = ImfUtils.getInputMethodInfoOfThisIme(context);
|
||||||
final int count = imi.getSubtypeCount();
|
final int count = imi.getSubtypeCount();
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
|
@ -533,7 +533,7 @@ public class AdditionalSubtypeSettings extends PreferenceFragment {
|
||||||
|
|
||||||
private InputMethodSubtype[] getSubtypes() {
|
private InputMethodSubtype[] getSubtypes() {
|
||||||
final PreferenceGroup group = getPreferenceScreen();
|
final PreferenceGroup group = getPreferenceScreen();
|
||||||
final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
|
final ArrayList<InputMethodSubtype> subtypes = CollectionUtils.newArrayList();
|
||||||
final int count = group.getPreferenceCount();
|
final int count = group.getPreferenceCount();
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
final Preference pref = group.getPreference(i);
|
final Preference pref = group.getPreference(i);
|
||||||
|
|
|
@ -39,7 +39,6 @@ public class AutoCorrection {
|
||||||
}
|
}
|
||||||
final CharSequence lowerCasedWord = word.toString().toLowerCase();
|
final CharSequence lowerCasedWord = word.toString().toLowerCase();
|
||||||
for (final String key : dictionaries.keySet()) {
|
for (final String key : dictionaries.keySet()) {
|
||||||
if (key.equals(Dictionary.TYPE_WHITELIST)) continue;
|
|
||||||
final Dictionary dictionary = dictionaries.get(key);
|
final Dictionary dictionary = dictionaries.get(key);
|
||||||
// It's unclear how realistically 'dictionary' can be null, but the monkey is somehow
|
// It's unclear how realistically 'dictionary' can be null, but the monkey is somehow
|
||||||
// managing to get null in here. Presumably the language is changing to a language with
|
// managing to get null in here. Presumably the language is changing to a language with
|
||||||
|
@ -64,7 +63,6 @@ public class AutoCorrection {
|
||||||
}
|
}
|
||||||
int maxFreq = -1;
|
int maxFreq = -1;
|
||||||
for (final String key : dictionaries.keySet()) {
|
for (final String key : dictionaries.keySet()) {
|
||||||
if (key.equals(Dictionary.TYPE_WHITELIST)) continue;
|
|
||||||
final Dictionary dictionary = dictionaries.get(key);
|
final Dictionary dictionary = dictionaries.get(key);
|
||||||
if (null == dictionary) continue;
|
if (null == dictionary) continue;
|
||||||
final int tempFreq = dictionary.getFrequency(word);
|
final int tempFreq = dictionary.getFrequency(word);
|
||||||
|
@ -75,17 +73,10 @@ public class AutoCorrection {
|
||||||
return maxFreq;
|
return maxFreq;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if this is a whitelist entry, or it isn't in any dictionary.
|
// Returns true if this isn't in any dictionary.
|
||||||
public static boolean isWhitelistedOrNotAWord(
|
public static boolean isNotAWord(
|
||||||
final ConcurrentHashMap<String, Dictionary> dictionaries,
|
final ConcurrentHashMap<String, Dictionary> dictionaries,
|
||||||
final CharSequence word, final boolean ignoreCase) {
|
final CharSequence word, final boolean ignoreCase) {
|
||||||
final WhitelistDictionary whitelistDictionary =
|
|
||||||
(WhitelistDictionary)dictionaries.get(Dictionary.TYPE_WHITELIST);
|
|
||||||
// If "word" is in the whitelist dictionary, it should not be auto corrected.
|
|
||||||
if (whitelistDictionary != null
|
|
||||||
&& whitelistDictionary.shouldForciblyAutoCorrectFrom(word)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return !isValidWord(dictionaries, word, ignoreCase);
|
return !isValidWord(dictionaries, word, ignoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ package com.android.inputmethod.latin;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
|
||||||
import com.android.inputmethod.keyboard.ProximityInfo;
|
import com.android.inputmethod.keyboard.ProximityInfo;
|
||||||
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
|
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
|
||||||
|
@ -51,7 +52,9 @@ public class BinaryDictionary extends Dictionary {
|
||||||
private static final int TYPED_LETTER_MULTIPLIER = 2;
|
private static final int TYPED_LETTER_MULTIPLIER = 2;
|
||||||
|
|
||||||
private long mNativeDict;
|
private long mNativeDict;
|
||||||
private final int[] mInputCodes = new int[MAX_WORD_LENGTH];
|
private final Locale mLocale;
|
||||||
|
private final int[] mInputCodePoints = new int[MAX_WORD_LENGTH];
|
||||||
|
// TODO: The below should be int[] mOutputCodePoints
|
||||||
private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_RESULTS];
|
private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_RESULTS];
|
||||||
private final int[] mSpaceIndices = new int[MAX_SPACES];
|
private final int[] mSpaceIndices = new int[MAX_SPACES];
|
||||||
private final int[] mOutputScores = new int[MAX_RESULTS];
|
private final int[] mOutputScores = new int[MAX_RESULTS];
|
||||||
|
@ -59,6 +62,25 @@ public class BinaryDictionary extends Dictionary {
|
||||||
|
|
||||||
private final boolean mUseFullEditDistance;
|
private final boolean mUseFullEditDistance;
|
||||||
|
|
||||||
|
private final SparseArray<DicTraverseSession> mDicTraverseSessions =
|
||||||
|
CollectionUtils.newSparseArray();
|
||||||
|
|
||||||
|
// TODO: There should be a way to remove used DicTraverseSession objects from
|
||||||
|
// {@code mDicTraverseSessions}.
|
||||||
|
private DicTraverseSession getTraverseSession(int traverseSessionId) {
|
||||||
|
synchronized(mDicTraverseSessions) {
|
||||||
|
DicTraverseSession traverseSession = mDicTraverseSessions.get(traverseSessionId);
|
||||||
|
if (traverseSession == null) {
|
||||||
|
traverseSession = mDicTraverseSessions.get(traverseSessionId);
|
||||||
|
if (traverseSession == null) {
|
||||||
|
traverseSession = new DicTraverseSession(mLocale, mNativeDict);
|
||||||
|
mDicTraverseSessions.put(traverseSessionId, traverseSession);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return traverseSession;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for the binary dictionary. This is supposed to be called from the
|
* Constructor for the binary dictionary. This is supposed to be called from the
|
||||||
* dictionary factory.
|
* dictionary factory.
|
||||||
|
@ -74,6 +96,7 @@ public class BinaryDictionary extends Dictionary {
|
||||||
final String filename, final long offset, final long length,
|
final String filename, final long offset, final long length,
|
||||||
final boolean useFullEditDistance, final Locale locale, final String dictType) {
|
final boolean useFullEditDistance, final Locale locale, final String dictType) {
|
||||||
super(dictType);
|
super(dictType);
|
||||||
|
mLocale = locale;
|
||||||
mUseFullEditDistance = useFullEditDistance;
|
mUseFullEditDistance = useFullEditDistance;
|
||||||
loadDictionary(filename, offset, length);
|
loadDictionary(filename, offset, length);
|
||||||
}
|
}
|
||||||
|
@ -86,18 +109,17 @@ public class BinaryDictionary extends Dictionary {
|
||||||
int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength, int maxWords,
|
int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength, int maxWords,
|
||||||
int maxPredictions);
|
int maxPredictions);
|
||||||
private native void closeNative(long dict);
|
private native void closeNative(long dict);
|
||||||
private native int getFrequencyNative(long dict, int[] word, int wordLength);
|
private native int getFrequencyNative(long dict, int[] word);
|
||||||
private native boolean isValidBigramNative(long dict, int[] word1, int[] word2);
|
private native boolean isValidBigramNative(long dict, int[] word1, int[] word2);
|
||||||
private native int getSuggestionsNative(long dict, long proximityInfo, int[] xCoordinates,
|
private native int getSuggestionsNative(long dict, long proximityInfo, long traverseSession,
|
||||||
int[] yCoordinates, int[] times, int[] pointerIds, int[] inputCodes, int codesSize,
|
int[] xCoordinates, int[] yCoordinates, int[] times, int[] pointerIds,
|
||||||
int commitPoint, boolean isGesture,
|
int[] inputCodePoints, int codesSize, int commitPoint, boolean isGesture,
|
||||||
int[] prevWordCodePointArray, boolean useFullEditDistance, char[] outputChars,
|
int[] prevWordCodePointArray, boolean useFullEditDistance, char[] outputChars,
|
||||||
int[] outputScores, int[] outputIndices, int[] outputTypes);
|
int[] outputScores, int[] outputIndices, int[] outputTypes);
|
||||||
private static native float calcNormalizedScoreNative(
|
private static native float calcNormalizedScoreNative(char[] before, char[] after, int score);
|
||||||
char[] before, int beforeLength, char[] after, int afterLength, int score);
|
private static native int editDistanceNative(char[] before, char[] after);
|
||||||
private static native int editDistanceNative(
|
|
||||||
char[] before, int beforeLength, char[] after, int afterLength);
|
|
||||||
|
|
||||||
|
// TODO: Move native dict into session
|
||||||
private final void loadDictionary(String path, long startOffset, long length) {
|
private final void loadDictionary(String path, long startOffset, long length) {
|
||||||
mNativeDict = openNative(path, startOffset, length, TYPED_LETTER_MULTIPLIER,
|
mNativeDict = openNative(path, startOffset, length, TYPED_LETTER_MULTIPLIER,
|
||||||
FULL_WORD_SCORE_MULTIPLIER, MAX_WORD_LENGTH, MAX_WORDS, MAX_PREDICTIONS);
|
FULL_WORD_SCORE_MULTIPLIER, MAX_WORD_LENGTH, MAX_WORDS, MAX_PREDICTIONS);
|
||||||
|
@ -106,10 +128,15 @@ public class BinaryDictionary extends Dictionary {
|
||||||
@Override
|
@Override
|
||||||
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
|
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
|
||||||
final CharSequence prevWord, final ProximityInfo proximityInfo) {
|
final CharSequence prevWord, final ProximityInfo proximityInfo) {
|
||||||
|
return getSuggestionsWithSessionId(composer, prevWord, proximityInfo, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
|
||||||
|
final CharSequence prevWord, final ProximityInfo proximityInfo, int sessionId) {
|
||||||
if (!isValidDictionary()) return null;
|
if (!isValidDictionary()) return null;
|
||||||
Arrays.fill(mInputCodes, WordComposer.NOT_A_CODE);
|
|
||||||
Arrays.fill(mOutputChars, (char) 0);
|
Arrays.fill(mInputCodePoints, Constants.NOT_A_CODE);
|
||||||
Arrays.fill(mOutputScores, 0);
|
|
||||||
// TODO: toLowerCase in the native code
|
// TODO: toLowerCase in the native code
|
||||||
final int[] prevWordCodePointArray = (null == prevWord)
|
final int[] prevWordCodePointArray = (null == prevWord)
|
||||||
? null : StringUtils.toCodePointArray(prevWord.toString());
|
? null : StringUtils.toCodePointArray(prevWord.toString());
|
||||||
|
@ -119,7 +146,7 @@ public class BinaryDictionary extends Dictionary {
|
||||||
if (composerSize <= 1 || !isGesture) {
|
if (composerSize <= 1 || !isGesture) {
|
||||||
if (composerSize > MAX_WORD_LENGTH - 1) return null;
|
if (composerSize > MAX_WORD_LENGTH - 1) return null;
|
||||||
for (int i = 0; i < composerSize; i++) {
|
for (int i = 0; i < composerSize; i++) {
|
||||||
mInputCodes[i] = composer.getCodeAt(i);
|
mInputCodePoints[i] = composer.getCodeAt(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,13 +154,13 @@ public class BinaryDictionary extends Dictionary {
|
||||||
final int codesSize = isGesture ? ips.getPointerSize() : composerSize;
|
final int codesSize = isGesture ? ips.getPointerSize() : composerSize;
|
||||||
// proximityInfo and/or prevWordForBigrams may not be null.
|
// proximityInfo and/or prevWordForBigrams may not be null.
|
||||||
final int tmpCount = getSuggestionsNative(mNativeDict,
|
final int tmpCount = getSuggestionsNative(mNativeDict,
|
||||||
proximityInfo.getNativeProximityInfo(), ips.getXCoordinates(),
|
proximityInfo.getNativeProximityInfo(), getTraverseSession(sessionId).getSession(),
|
||||||
ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(),
|
ips.getXCoordinates(), ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(),
|
||||||
mInputCodes, codesSize, 0 /* commitPoint */, isGesture, prevWordCodePointArray,
|
mInputCodePoints, codesSize, 0 /* commitPoint */, isGesture, prevWordCodePointArray,
|
||||||
mUseFullEditDistance, mOutputChars, mOutputScores, mSpaceIndices, mOutputTypes);
|
mUseFullEditDistance, mOutputChars, mOutputScores, mSpaceIndices, mOutputTypes);
|
||||||
final int count = Math.min(tmpCount, MAX_PREDICTIONS);
|
final int count = Math.min(tmpCount, MAX_PREDICTIONS);
|
||||||
|
|
||||||
final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>();
|
final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
|
||||||
for (int j = 0; j < count; ++j) {
|
for (int j = 0; j < count; ++j) {
|
||||||
if (composerSize > 0 && mOutputScores[j] < 1) break;
|
if (composerSize > 0 && mOutputScores[j] < 1) break;
|
||||||
final int start = j * MAX_WORD_LENGTH;
|
final int start = j * MAX_WORD_LENGTH;
|
||||||
|
@ -142,9 +169,10 @@ public class BinaryDictionary extends Dictionary {
|
||||||
++len;
|
++len;
|
||||||
}
|
}
|
||||||
if (len > 0) {
|
if (len > 0) {
|
||||||
|
final int score = SuggestedWordInfo.KIND_WHITELIST == mOutputTypes[j]
|
||||||
|
? SuggestedWordInfo.MAX_SCORE : mOutputScores[j];
|
||||||
suggestions.add(new SuggestedWordInfo(
|
suggestions.add(new SuggestedWordInfo(
|
||||||
new String(mOutputChars, start, len),
|
new String(mOutputChars, start, len), score, mOutputTypes[j], mDictType));
|
||||||
mOutputScores[j], SuggestedWordInfo.KIND_CORRECTION, mDictType));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return suggestions;
|
return suggestions;
|
||||||
|
@ -155,13 +183,11 @@ public class BinaryDictionary extends Dictionary {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static float calcNormalizedScore(String before, String after, int score) {
|
public static float calcNormalizedScore(String before, String after, int score) {
|
||||||
return calcNormalizedScoreNative(before.toCharArray(), before.length(),
|
return calcNormalizedScoreNative(before.toCharArray(), after.toCharArray(), score);
|
||||||
after.toCharArray(), after.length(), score);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int editDistance(String before, String after) {
|
public static int editDistance(String before, String after) {
|
||||||
return editDistanceNative(
|
return editDistanceNative(before.toCharArray(), after.toCharArray());
|
||||||
before.toCharArray(), before.length(), after.toCharArray(), after.length());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -172,8 +198,8 @@ public class BinaryDictionary extends Dictionary {
|
||||||
@Override
|
@Override
|
||||||
public int getFrequency(CharSequence word) {
|
public int getFrequency(CharSequence word) {
|
||||||
if (word == null) return -1;
|
if (word == null) return -1;
|
||||||
int[] chars = StringUtils.toCodePointArray(word.toString());
|
int[] codePoints = StringUtils.toCodePointArray(word.toString());
|
||||||
return getFrequencyNative(mNativeDict, chars, chars.length);
|
return getFrequencyNative(mNativeDict, codePoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add a batch process version (isValidBigramMultiple?) to avoid excessive numbers of jni
|
// TODO: Add a batch process version (isValidBigramMultiple?) to avoid excessive numbers of jni
|
||||||
|
@ -186,11 +212,20 @@ public class BinaryDictionary extends Dictionary {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void close() {
|
public void close() {
|
||||||
|
synchronized (mDicTraverseSessions) {
|
||||||
|
final int sessionsSize = mDicTraverseSessions.size();
|
||||||
|
for (int index = 0; index < sessionsSize; ++index) {
|
||||||
|
final DicTraverseSession traverseSession = mDicTraverseSessions.valueAt(index);
|
||||||
|
if (traverseSession != null) {
|
||||||
|
traverseSession.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
closeInternal();
|
closeInternal();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void closeInternal() {
|
private synchronized void closeInternal() {
|
||||||
if (mNativeDict != 0) {
|
if (mNativeDict != 0) {
|
||||||
closeNative(mNativeDict);
|
closeNative(mNativeDict);
|
||||||
mNativeDict = 0;
|
mNativeDict = 0;
|
||||||
|
|
|
@ -99,7 +99,7 @@ public class BinaryDictionaryFileDumper {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final List<WordListInfo> list = new ArrayList<WordListInfo>();
|
final List<WordListInfo> list = CollectionUtils.newArrayList();
|
||||||
do {
|
do {
|
||||||
final String wordListId = c.getString(0);
|
final String wordListId = c.getString(0);
|
||||||
final String wordListLocale = c.getString(1);
|
final String wordListLocale = c.getString(1);
|
||||||
|
@ -267,7 +267,7 @@ public class BinaryDictionaryFileDumper {
|
||||||
final ContentResolver resolver = context.getContentResolver();
|
final ContentResolver resolver = context.getContentResolver();
|
||||||
final List<WordListInfo> idList = getWordListWordListInfos(locale, context,
|
final List<WordListInfo> idList = getWordListWordListInfos(locale, context,
|
||||||
hasDefaultWordList);
|
hasDefaultWordList);
|
||||||
final List<AssetFileAddress> fileAddressList = new ArrayList<AssetFileAddress>();
|
final List<AssetFileAddress> fileAddressList = CollectionUtils.newArrayList();
|
||||||
for (WordListInfo id : idList) {
|
for (WordListInfo id : idList) {
|
||||||
final AssetFileAddress afd = cacheWordList(id.mId, id.mLocale, resolver, context);
|
final AssetFileAddress afd = cacheWordList(id.mId, id.mLocale, resolver, context);
|
||||||
if (null != afd) {
|
if (null != afd) {
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package com.android.inputmethod.latin;
|
package com.android.inputmethod.latin;
|
||||||
|
|
||||||
|
import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager.NameNotFoundException;
|
import android.content.pm.PackageManager.NameNotFoundException;
|
||||||
|
@ -23,6 +25,10 @@ import android.content.res.AssetFileDescriptor;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
@ -51,6 +57,9 @@ class BinaryDictionaryGetter {
|
||||||
private static final String MAIN_DICTIONARY_CATEGORY = "main";
|
private static final String MAIN_DICTIONARY_CATEGORY = "main";
|
||||||
public static final String ID_CATEGORY_SEPARATOR = ":";
|
public static final String ID_CATEGORY_SEPARATOR = ":";
|
||||||
|
|
||||||
|
// The key considered to read the version attribute in a dictionary file.
|
||||||
|
private static String VERSION_KEY = "version";
|
||||||
|
|
||||||
// Prevents this from being instantiated
|
// Prevents this from being instantiated
|
||||||
private BinaryDictionaryGetter() {}
|
private BinaryDictionaryGetter() {}
|
||||||
|
|
||||||
|
@ -254,8 +263,7 @@ class BinaryDictionaryGetter {
|
||||||
final Context context) {
|
final Context context) {
|
||||||
final File[] directoryList = getCachedDirectoryList(context);
|
final File[] directoryList = getCachedDirectoryList(context);
|
||||||
if (null == directoryList) return EMPTY_FILE_ARRAY;
|
if (null == directoryList) return EMPTY_FILE_ARRAY;
|
||||||
final HashMap<String, FileAndMatchLevel> cacheFiles =
|
final HashMap<String, FileAndMatchLevel> cacheFiles = CollectionUtils.newHashMap();
|
||||||
new HashMap<String, FileAndMatchLevel>();
|
|
||||||
for (File directory : directoryList) {
|
for (File directory : directoryList) {
|
||||||
if (!directory.isDirectory()) continue;
|
if (!directory.isDirectory()) continue;
|
||||||
final String dirLocale = getWordListIdFromFileName(directory.getName());
|
final String dirLocale = getWordListIdFromFileName(directory.getName());
|
||||||
|
@ -336,6 +344,54 @@ class BinaryDictionaryGetter {
|
||||||
return MAIN_DICTIONARY_CATEGORY.equals(idArray[0]);
|
return MAIN_DICTIONARY_CATEGORY.equals(idArray[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ## HACK ## we prevent usage of a dictionary before version 18 for English only. The reason
|
||||||
|
// for this is, since those do not include whitelist entries, the new code with an old version
|
||||||
|
// of the dictionary would lose whitelist functionality.
|
||||||
|
private static boolean hackCanUseDictionaryFile(final Locale locale, final File f) {
|
||||||
|
// Only for English - other languages didn't have a whitelist, hence this
|
||||||
|
// ad-hock ## HACK ##
|
||||||
|
if (!Locale.ENGLISH.getLanguage().equals(locale.getLanguage())) return true;
|
||||||
|
|
||||||
|
FileInputStream inStream = null;
|
||||||
|
try {
|
||||||
|
// Read the version of the file
|
||||||
|
inStream = new FileInputStream(f);
|
||||||
|
final ByteBuffer buffer = inStream.getChannel().map(
|
||||||
|
FileChannel.MapMode.READ_ONLY, 0, f.length());
|
||||||
|
final int magic = buffer.getInt();
|
||||||
|
if (magic != BinaryDictInputOutput.VERSION_2_MAGIC_NUMBER) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final int formatVersion = buffer.getInt();
|
||||||
|
final int headerSize = buffer.getInt();
|
||||||
|
final HashMap<String, String> options = CollectionUtils.newHashMap();
|
||||||
|
BinaryDictInputOutput.populateOptions(buffer, headerSize, options);
|
||||||
|
|
||||||
|
final String version = options.get(VERSION_KEY);
|
||||||
|
if (null == version) {
|
||||||
|
// No version in the options : the format is unexpected
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Version 18 is the first one to include the whitelist
|
||||||
|
// Obviously this is a big ## HACK ##
|
||||||
|
return Integer.parseInt(version) >= 18;
|
||||||
|
} catch (java.io.FileNotFoundException e) {
|
||||||
|
return false;
|
||||||
|
} catch (java.io.IOException e) {
|
||||||
|
return false;
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
if (inStream != null) {
|
||||||
|
try {
|
||||||
|
inStream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list of file addresses for a given locale, trying relevant methods in order.
|
* Returns a list of file addresses for a given locale, trying relevant methods in order.
|
||||||
*
|
*
|
||||||
|
@ -362,18 +418,19 @@ class BinaryDictionaryGetter {
|
||||||
final DictPackSettings dictPackSettings = new DictPackSettings(context);
|
final DictPackSettings dictPackSettings = new DictPackSettings(context);
|
||||||
|
|
||||||
boolean foundMainDict = false;
|
boolean foundMainDict = false;
|
||||||
final ArrayList<AssetFileAddress> fileList = new ArrayList<AssetFileAddress>();
|
final ArrayList<AssetFileAddress> fileList = CollectionUtils.newArrayList();
|
||||||
// cachedWordLists may not be null, see doc for getCachedDictionaryList
|
// cachedWordLists may not be null, see doc for getCachedDictionaryList
|
||||||
for (final File f : cachedWordLists) {
|
for (final File f : cachedWordLists) {
|
||||||
final String wordListId = getWordListIdFromFileName(f.getName());
|
final String wordListId = getWordListIdFromFileName(f.getName());
|
||||||
if (isMainWordListId(wordListId)) {
|
final boolean canUse = f.canRead() && hackCanUseDictionaryFile(locale, f);
|
||||||
|
if (canUse && isMainWordListId(wordListId)) {
|
||||||
foundMainDict = true;
|
foundMainDict = true;
|
||||||
}
|
}
|
||||||
if (!dictPackSettings.isWordListActive(wordListId)) continue;
|
if (!dictPackSettings.isWordListActive(wordListId)) continue;
|
||||||
if (f.canRead()) {
|
if (canUse) {
|
||||||
fileList.add(AssetFileAddress.makeFromFileName(f.getPath()));
|
fileList.add(AssetFileAddress.makeFromFileName(f.getPath()));
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "Found a cached dictionary file but cannot read it");
|
Log.e(TAG, "Found a cached dictionary file but cannot read or use it");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>();
|
||||||
|
}
|
||||||
|
}
|
|
@ -128,6 +128,13 @@ public final class Constants {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final int NOT_A_CODE = -1;
|
||||||
|
|
||||||
|
// See {@link KeyboardActionListener.Adapter#isInvalidCoordinate(int)}.
|
||||||
|
public static final int NOT_A_COORDINATE = -1;
|
||||||
|
public static final int SUGGESTION_STRIP_COORDINATE = -2;
|
||||||
|
public static final int SPELL_CHECKER_COORDINATE = -3;
|
||||||
|
|
||||||
private Constants() {
|
private Constants() {
|
||||||
// This utility class is not publicly instantiable.
|
// This utility class is not publicly instantiable.
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -42,7 +42,6 @@ public abstract class Dictionary {
|
||||||
public static final String TYPE_USER = "user";
|
public static final String TYPE_USER = "user";
|
||||||
// User history dictionary internal to LatinIME.
|
// User history dictionary internal to LatinIME.
|
||||||
public static final String TYPE_USER_HISTORY = "history";
|
public static final String TYPE_USER_HISTORY = "history";
|
||||||
public static final String TYPE_WHITELIST ="whitelist";
|
|
||||||
protected final String mDictType;
|
protected final String mDictType;
|
||||||
|
|
||||||
public Dictionary(final String dictType) {
|
public Dictionary(final String dictType) {
|
||||||
|
@ -62,6 +61,13 @@ public abstract class Dictionary {
|
||||||
abstract public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
|
abstract public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
|
||||||
final CharSequence prevWord, final ProximityInfo proximityInfo);
|
final CharSequence prevWord, final ProximityInfo proximityInfo);
|
||||||
|
|
||||||
|
// The default implementation of this method ignores sessionId.
|
||||||
|
// Subclasses that want to use sessionId need to override this method.
|
||||||
|
public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
|
||||||
|
final CharSequence prevWord, final ProximityInfo proximityInfo, int sessionId) {
|
||||||
|
return getSuggestions(composer, prevWord, proximityInfo);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the given word occurs in the dictionary
|
* Checks if the given word occurs in the dictionary
|
||||||
* @param word the word to search for. The search should be case-insensitive.
|
* @param word the word to search for. The search should be case-insensitive.
|
||||||
|
|
|
@ -35,22 +35,22 @@ public class DictionaryCollection extends Dictionary {
|
||||||
|
|
||||||
public DictionaryCollection(final String dictType) {
|
public DictionaryCollection(final String dictType) {
|
||||||
super(dictType);
|
super(dictType);
|
||||||
mDictionaries = new CopyOnWriteArrayList<Dictionary>();
|
mDictionaries = CollectionUtils.newCopyOnWriteArrayList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public DictionaryCollection(final String dictType, Dictionary... dictionaries) {
|
public DictionaryCollection(final String dictType, Dictionary... dictionaries) {
|
||||||
super(dictType);
|
super(dictType);
|
||||||
if (null == dictionaries) {
|
if (null == dictionaries) {
|
||||||
mDictionaries = new CopyOnWriteArrayList<Dictionary>();
|
mDictionaries = CollectionUtils.newCopyOnWriteArrayList();
|
||||||
} else {
|
} else {
|
||||||
mDictionaries = new CopyOnWriteArrayList<Dictionary>(dictionaries);
|
mDictionaries = CollectionUtils.newCopyOnWriteArrayList(dictionaries);
|
||||||
mDictionaries.removeAll(Collections.singleton(null));
|
mDictionaries.removeAll(Collections.singleton(null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public DictionaryCollection(final String dictType, Collection<Dictionary> dictionaries) {
|
public DictionaryCollection(final String dictType, Collection<Dictionary> dictionaries) {
|
||||||
super(dictType);
|
super(dictType);
|
||||||
mDictionaries = new CopyOnWriteArrayList<Dictionary>(dictionaries);
|
mDictionaries = CollectionUtils.newCopyOnWriteArrayList(dictionaries);
|
||||||
mDictionaries.removeAll(Collections.singleton(null));
|
mDictionaries.removeAll(Collections.singleton(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ public class DictionaryCollection extends Dictionary {
|
||||||
// dictionary and add the rest to it if not null, hence the get(0)
|
// dictionary and add the rest to it if not null, hence the get(0)
|
||||||
ArrayList<SuggestedWordInfo> suggestions = dictionaries.get(0).getSuggestions(composer,
|
ArrayList<SuggestedWordInfo> suggestions = dictionaries.get(0).getSuggestions(composer,
|
||||||
prevWord, proximityInfo);
|
prevWord, proximityInfo);
|
||||||
if (null == suggestions) suggestions = new ArrayList<SuggestedWordInfo>();
|
if (null == suggestions) suggestions = CollectionUtils.newArrayList();
|
||||||
final int length = dictionaries.size();
|
final int length = dictionaries.size();
|
||||||
for (int i = 1; i < length; ++ i) {
|
for (int i = 1; i < length; ++ i) {
|
||||||
final ArrayList<SuggestedWordInfo> sugg = dictionaries.get(i).getSuggestions(composer,
|
final ArrayList<SuggestedWordInfo> sugg = dictionaries.get(i).getSuggestions(composer,
|
||||||
|
|
|
@ -53,7 +53,7 @@ public class DictionaryFactory {
|
||||||
createBinaryDictionary(context, locale));
|
createBinaryDictionary(context, locale));
|
||||||
}
|
}
|
||||||
|
|
||||||
final LinkedList<Dictionary> dictList = new LinkedList<Dictionary>();
|
final LinkedList<Dictionary> dictList = CollectionUtils.newLinkedList();
|
||||||
final ArrayList<AssetFileAddress> assetFileList =
|
final ArrayList<AssetFileAddress> assetFileList =
|
||||||
BinaryDictionaryGetter.getDictionaryFiles(locale, context);
|
BinaryDictionaryGetter.getDictionaryFiles(locale, context);
|
||||||
if (null != assetFileList) {
|
if (null != assetFileList) {
|
||||||
|
|
|
@ -62,7 +62,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
|
||||||
* that filename.
|
* that filename.
|
||||||
*/
|
*/
|
||||||
private static final HashMap<String, DictionaryController> sSharedDictionaryControllers =
|
private static final HashMap<String, DictionaryController> sSharedDictionaryControllers =
|
||||||
new HashMap<String, DictionaryController>();
|
CollectionUtils.newHashMap();
|
||||||
|
|
||||||
/** The application context. */
|
/** The application context. */
|
||||||
protected final Context mContext;
|
protected final Context mContext;
|
||||||
|
@ -159,9 +159,9 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
|
||||||
* the native side.
|
* the native side.
|
||||||
*/
|
*/
|
||||||
public void clearFusionDictionary() {
|
public void clearFusionDictionary() {
|
||||||
|
final HashMap<String, String> attributes = CollectionUtils.newHashMap();
|
||||||
mFusionDictionary = new FusionDictionary(new Node(),
|
mFusionDictionary = new FusionDictionary(new Node(),
|
||||||
new FusionDictionary.DictionaryOptions(new HashMap<String, String>(), false,
|
new FusionDictionary.DictionaryOptions(attributes, false, false));
|
||||||
false));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -175,7 +175,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
|
||||||
mFusionDictionary.add(word, frequency, null);
|
mFusionDictionary.add(word, frequency, null);
|
||||||
} else {
|
} else {
|
||||||
// TODO: Do this in the subclass, with this class taking an arraylist.
|
// TODO: Do this in the subclass, with this class taking an arraylist.
|
||||||
final ArrayList<WeightedString> shortcutTargets = new ArrayList<WeightedString>();
|
final ArrayList<WeightedString> shortcutTargets = CollectionUtils.newArrayList();
|
||||||
shortcutTargets.add(new WeightedString(shortcutTarget, frequency));
|
shortcutTargets.add(new WeightedString(shortcutTarget, frequency));
|
||||||
mFusionDictionary.add(word, frequency, shortcutTargets);
|
mFusionDictionary.add(word, frequency, shortcutTargets);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ package com.android.inputmethod.latin;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import com.android.inputmethod.keyboard.KeyDetector;
|
|
||||||
import com.android.inputmethod.keyboard.Keyboard;
|
import com.android.inputmethod.keyboard.Keyboard;
|
||||||
import com.android.inputmethod.keyboard.ProximityInfo;
|
import com.android.inputmethod.keyboard.ProximityInfo;
|
||||||
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
|
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
|
||||||
|
@ -231,7 +230,7 @@ public class ExpandableDictionary extends Dictionary {
|
||||||
childNode.mTerminal = true;
|
childNode.mTerminal = true;
|
||||||
if (isShortcutOnly) {
|
if (isShortcutOnly) {
|
||||||
if (null == childNode.mShortcutTargets) {
|
if (null == childNode.mShortcutTargets) {
|
||||||
childNode.mShortcutTargets = new ArrayList<char[]>();
|
childNode.mShortcutTargets = CollectionUtils.newArrayList();
|
||||||
}
|
}
|
||||||
childNode.mShortcutTargets.add(shortcutTarget.toCharArray());
|
childNode.mShortcutTargets.add(shortcutTarget.toCharArray());
|
||||||
} else {
|
} else {
|
||||||
|
@ -251,7 +250,7 @@ public class ExpandableDictionary extends Dictionary {
|
||||||
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
|
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
|
||||||
final CharSequence prevWord, final ProximityInfo proximityInfo) {
|
final CharSequence prevWord, final ProximityInfo proximityInfo) {
|
||||||
if (reloadDictionaryIfRequired()) return null;
|
if (reloadDictionaryIfRequired()) return null;
|
||||||
if (composer.size() <= 1) {
|
if (composer.size() > 1) {
|
||||||
if (composer.size() >= BinaryDictionary.MAX_WORD_LENGTH) {
|
if (composer.size() >= BinaryDictionary.MAX_WORD_LENGTH) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -260,7 +259,7 @@ public class ExpandableDictionary extends Dictionary {
|
||||||
return suggestions;
|
return suggestions;
|
||||||
} else {
|
} else {
|
||||||
if (TextUtils.isEmpty(prevWord)) return null;
|
if (TextUtils.isEmpty(prevWord)) return null;
|
||||||
final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>();
|
final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
|
||||||
runBigramReverseLookUp(prevWord, suggestions);
|
runBigramReverseLookUp(prevWord, suggestions);
|
||||||
return suggestions;
|
return suggestions;
|
||||||
}
|
}
|
||||||
|
@ -279,7 +278,7 @@ public class ExpandableDictionary extends Dictionary {
|
||||||
|
|
||||||
protected ArrayList<SuggestedWordInfo> getWordsInner(final WordComposer codes,
|
protected ArrayList<SuggestedWordInfo> getWordsInner(final WordComposer codes,
|
||||||
final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) {
|
final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) {
|
||||||
final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>();
|
final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
|
||||||
mInputLength = codes.size();
|
mInputLength = codes.size();
|
||||||
if (mCodes.length < mInputLength) mCodes = new int[mInputLength][];
|
if (mCodes.length < mInputLength) mCodes = new int[mInputLength][];
|
||||||
final InputPointers ips = codes.getInputPointers();
|
final InputPointers ips = codes.getInputPointers();
|
||||||
|
@ -292,9 +291,9 @@ public class ExpandableDictionary extends Dictionary {
|
||||||
mCodes[i] = new int[ProximityInfo.MAX_PROXIMITY_CHARS_SIZE];
|
mCodes[i] = new int[ProximityInfo.MAX_PROXIMITY_CHARS_SIZE];
|
||||||
}
|
}
|
||||||
final int x = xCoordinates != null && i < xCoordinates.length ?
|
final int x = xCoordinates != null && i < xCoordinates.length ?
|
||||||
xCoordinates[i] : WordComposer.NOT_A_COORDINATE;
|
xCoordinates[i] : Constants.NOT_A_COORDINATE;
|
||||||
final int y = xCoordinates != null && i < yCoordinates.length ?
|
final int y = xCoordinates != null && i < yCoordinates.length ?
|
||||||
yCoordinates[i] : WordComposer.NOT_A_COORDINATE;
|
yCoordinates[i] : Constants.NOT_A_COORDINATE;
|
||||||
proximityInfo.fillArrayWithNearestKeyCodes(x, y, codes.getCodeAt(i), mCodes[i]);
|
proximityInfo.fillArrayWithNearestKeyCodes(x, y, codes.getCodeAt(i), mCodes[i]);
|
||||||
}
|
}
|
||||||
mMaxDepth = mInputLength * 3;
|
mMaxDepth = mInputLength * 3;
|
||||||
|
@ -487,7 +486,7 @@ public class ExpandableDictionary extends Dictionary {
|
||||||
for (int j = 0; j < alternativesSize; j++) {
|
for (int j = 0; j < alternativesSize; j++) {
|
||||||
final int addedAttenuation = (j > 0 ? 1 : 2);
|
final int addedAttenuation = (j > 0 ? 1 : 2);
|
||||||
final int currentChar = currentChars[j];
|
final int currentChar = currentChars[j];
|
||||||
if (currentChar == KeyDetector.NOT_A_CODE) {
|
if (currentChar == Constants.NOT_A_CODE) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (currentChar == lowerC || currentChar == c) {
|
if (currentChar == lowerC || currentChar == c) {
|
||||||
|
@ -551,7 +550,7 @@ public class ExpandableDictionary extends Dictionary {
|
||||||
Node secondWord = searchWord(mRoots, word2, 0, null);
|
Node secondWord = searchWord(mRoots, word2, 0, null);
|
||||||
LinkedList<NextWord> bigrams = firstWord.mNGrams;
|
LinkedList<NextWord> bigrams = firstWord.mNGrams;
|
||||||
if (bigrams == null || bigrams.size() == 0) {
|
if (bigrams == null || bigrams.size() == 0) {
|
||||||
firstWord.mNGrams = new LinkedList<NextWord>();
|
firstWord.mNGrams = CollectionUtils.newLinkedList();
|
||||||
bigrams = firstWord.mNGrams;
|
bigrams = firstWord.mNGrams;
|
||||||
} else {
|
} else {
|
||||||
for (NextWord nw : bigrams) {
|
for (NextWord nw : bigrams) {
|
||||||
|
|
|
@ -29,10 +29,12 @@ public class InputAttributes {
|
||||||
final public boolean mInputTypeNoAutoCorrect;
|
final public boolean mInputTypeNoAutoCorrect;
|
||||||
final public boolean mIsSettingsSuggestionStripOn;
|
final public boolean mIsSettingsSuggestionStripOn;
|
||||||
final public boolean mApplicationSpecifiedCompletionOn;
|
final public boolean mApplicationSpecifiedCompletionOn;
|
||||||
|
final private int mInputType;
|
||||||
|
|
||||||
public InputAttributes(final EditorInfo editorInfo, final boolean isFullscreenMode) {
|
public InputAttributes(final EditorInfo editorInfo, final boolean isFullscreenMode) {
|
||||||
final int inputType = null != editorInfo ? editorInfo.inputType : 0;
|
final int inputType = null != editorInfo ? editorInfo.inputType : 0;
|
||||||
final int inputClass = inputType & InputType.TYPE_MASK_CLASS;
|
final int inputClass = inputType & InputType.TYPE_MASK_CLASS;
|
||||||
|
mInputType = inputType;
|
||||||
if (inputClass != InputType.TYPE_CLASS_TEXT) {
|
if (inputClass != InputType.TYPE_CLASS_TEXT) {
|
||||||
// If we are not looking at a TYPE_CLASS_TEXT field, the following strange
|
// If we are not looking at a TYPE_CLASS_TEXT field, the following strange
|
||||||
// cases may arise, so we do a couple sanity checks for them. If it's a
|
// cases may arise, so we do a couple sanity checks for them. If it's a
|
||||||
|
@ -93,6 +95,10 @@ public class InputAttributes {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isSameInputType(final EditorInfo editorInfo) {
|
||||||
|
return editorInfo.inputType == mInputType;
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private void dumpFlags(final int inputType) {
|
private void dumpFlags(final int inputType) {
|
||||||
Log.i(TAG, "Input class:");
|
Log.i(TAG, "Input class:");
|
||||||
|
|
|
@ -93,7 +93,7 @@ public class InputPointers {
|
||||||
}
|
}
|
||||||
mXCoordinates.append(xCoordinates, startPos, length);
|
mXCoordinates.append(xCoordinates, startPos, length);
|
||||||
mYCoordinates.append(yCoordinates, startPos, length);
|
mYCoordinates.append(yCoordinates, startPos, length);
|
||||||
mPointerIds.fill(pointerId, startPos, length);
|
mPointerIds.fill(pointerId, mPointerIds.getLength(), length);
|
||||||
mTimes.append(times, startPos, length);
|
mTimes.append(times, startPos, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,4 +124,10 @@ public class InputPointers {
|
||||||
public int[] getTimes() {
|
public int[] getTimes() {
|
||||||
return mTimes.getPrimitiveArray();
|
return mTimes.getPrimitiveArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "size=" + getPointerSize() + " id=" + mPointerIds + " time=" + mTimes
|
||||||
|
+ " x=" + mXCoordinates + " y=" + mYCoordinates;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -361,7 +361,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
mPrefs = prefs;
|
mPrefs = prefs;
|
||||||
LatinImeLogger.init(this, prefs);
|
LatinImeLogger.init(this, prefs);
|
||||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||||
ResearchLogger.getInstance().init(this, prefs, mKeyboardSwitcher);
|
ResearchLogger.getInstance().init(this, prefs);
|
||||||
}
|
}
|
||||||
InputMethodManagerCompatWrapper.init(this);
|
InputMethodManagerCompatWrapper.init(this);
|
||||||
SubtypeSwitcher.init(this);
|
SubtypeSwitcher.init(this);
|
||||||
|
@ -381,18 +381,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
|
|
||||||
ImfUtils.setAdditionalInputMethodSubtypes(this, mCurrentSettings.getAdditionalSubtypes());
|
ImfUtils.setAdditionalInputMethodSubtypes(this, mCurrentSettings.getAdditionalSubtypes());
|
||||||
|
|
||||||
Utils.GCUtils.getInstance().reset();
|
|
||||||
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();
|
initSuggest();
|
||||||
tryGC = false;
|
|
||||||
} catch (OutOfMemoryError e) {
|
|
||||||
tryGC = Utils.GCUtils.getInstance().tryGCOrWait("InitSuggest", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mDisplayOrientation = res.getConfiguration().orientation;
|
mDisplayOrientation = res.getConfiguration().orientation;
|
||||||
|
|
||||||
|
@ -416,7 +405,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
}
|
}
|
||||||
|
|
||||||
// Has to be package-visible for unit tests
|
// Has to be package-visible for unit tests
|
||||||
/* package */ void loadSettings() {
|
/* package for test */
|
||||||
|
void loadSettings() {
|
||||||
// Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged()
|
// Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged()
|
||||||
// is not guaranteed. It may even be called at the same time on a different thread.
|
// is not guaranteed. It may even be called at the same time on a different thread.
|
||||||
if (null == mPrefs) mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
|
if (null == mPrefs) mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
|
@ -433,10 +423,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary());
|
resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note that this method is called from a non-UI thread.
|
||||||
@Override
|
@Override
|
||||||
public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable) {
|
public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable) {
|
||||||
mIsMainDictionaryAvailable = isMainDictionaryAvailable;
|
mIsMainDictionaryAvailable = isMainDictionaryAvailable;
|
||||||
updateKeyboardViewGestureHandlingModeByMainDictionaryAvailability();
|
final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
|
||||||
|
if (mainKeyboardView != null) {
|
||||||
|
mainKeyboardView.setMainDictionaryAvailability(isMainDictionaryAvailable);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initSuggest() {
|
private void initSuggest() {
|
||||||
|
@ -517,7 +511,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
|
|
||||||
/* package private */ void resetSuggestMainDict() {
|
/* package private */ void resetSuggestMainDict() {
|
||||||
final Locale subtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
|
final Locale subtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
|
||||||
mSuggest.resetMainDict(this, subtypeLocale);
|
mSuggest.resetMainDict(this, subtypeLocale, this /* SuggestInitializationListener */);
|
||||||
mIsMainDictionaryAvailable = DictionaryFactory.isDictionaryAvailable(this, subtypeLocale);
|
mIsMainDictionaryAvailable = DictionaryFactory.isDictionaryAvailable(this, subtypeLocale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -536,7 +530,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConfigurationChanged(Configuration conf) {
|
public void onConfigurationChanged(Configuration conf) {
|
||||||
mSubtypeSwitcher.onConfigurationChanged(conf);
|
// System locale has been changed. Needs to reload keyboard.
|
||||||
|
if (mSubtypeSwitcher.onConfigurationChanged(conf, this)) {
|
||||||
|
loadKeyboard();
|
||||||
|
}
|
||||||
// If orientation changed while predicting, commit the change
|
// If orientation changed while predicting, commit the change
|
||||||
if (mDisplayOrientation != conf.orientation) {
|
if (mDisplayOrientation != conf.orientation) {
|
||||||
mDisplayOrientation = conf.orientation;
|
mDisplayOrientation = conf.orientation;
|
||||||
|
@ -603,6 +600,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
// Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged()
|
// Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged()
|
||||||
// is not guaranteed. It may even be called at the same time on a different thread.
|
// is not guaranteed. It may even be called at the same time on a different thread.
|
||||||
mSubtypeSwitcher.updateSubtype(subtype);
|
mSubtypeSwitcher.updateSubtype(subtype);
|
||||||
|
loadKeyboard();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onStartInputInternal(EditorInfo editorInfo, boolean restarting) {
|
private void onStartInputInternal(EditorInfo editorInfo, boolean restarting) {
|
||||||
|
@ -663,11 +661,23 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
// Forward this event to the accessibility utilities, if enabled.
|
// Forward this event to the accessibility utilities, if enabled.
|
||||||
final AccessibilityUtils accessUtils = AccessibilityUtils.getInstance();
|
final AccessibilityUtils accessUtils = AccessibilityUtils.getInstance();
|
||||||
if (accessUtils.isTouchExplorationEnabled()) {
|
if (accessUtils.isTouchExplorationEnabled()) {
|
||||||
accessUtils.onStartInputViewInternal(editorInfo, restarting);
|
accessUtils.onStartInputViewInternal(mainKeyboardView, editorInfo, restarting);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!restarting) {
|
final boolean selectionChanged = mLastSelectionStart != editorInfo.initialSelStart
|
||||||
mSubtypeSwitcher.updateParametersOnStartInputView();
|
|| mLastSelectionEnd != editorInfo.initialSelEnd;
|
||||||
|
final boolean inputTypeChanged = !mCurrentSettings.isSameInputType(editorInfo);
|
||||||
|
final boolean isDifferentTextField = !restarting || inputTypeChanged;
|
||||||
|
if (isDifferentTextField) {
|
||||||
|
final boolean currentSubtypeEnabled = mSubtypeSwitcher
|
||||||
|
.updateParametersOnStartInputViewAndReturnIfCurrentSubtypeEnabled();
|
||||||
|
if (!currentSubtypeEnabled) {
|
||||||
|
// Current subtype is disabled. Needs to update subtype and keyboard.
|
||||||
|
final InputMethodSubtype newSubtype = ImfUtils.getCurrentInputMethodSubtype(
|
||||||
|
this, mSubtypeSwitcher.getNoLanguageSubtype());
|
||||||
|
mSubtypeSwitcher.updateSubtype(newSubtype);
|
||||||
|
loadKeyboard();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The EditorInfo might have a flag that affects fullscreen mode.
|
// The EditorInfo might have a flag that affects fullscreen mode.
|
||||||
|
@ -675,9 +685,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
updateFullscreenMode();
|
updateFullscreenMode();
|
||||||
mApplicationSpecifiedCompletions = null;
|
mApplicationSpecifiedCompletions = null;
|
||||||
|
|
||||||
final boolean selectionChanged = mLastSelectionStart != editorInfo.initialSelStart
|
if (isDifferentTextField || selectionChanged) {
|
||||||
|| mLastSelectionEnd != editorInfo.initialSelEnd;
|
|
||||||
if (!restarting || selectionChanged) {
|
|
||||||
// If the selection changed, we reset the input state. Essentially, we come here with
|
// If the selection changed, we reset the input state. Essentially, we come here with
|
||||||
// restarting == true when the app called setText() or similar. We should reset the
|
// restarting == true when the app called setText() or similar. We should reset the
|
||||||
// state if the app set the text to something else, but keep it if it set a suggestion
|
// state if the app set the text to something else, but keep it if it set a suggestion
|
||||||
|
@ -692,7 +700,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!restarting) {
|
if (isDifferentTextField) {
|
||||||
mainKeyboardView.closing();
|
mainKeyboardView.closing();
|
||||||
loadSettings();
|
loadSettings();
|
||||||
|
|
||||||
|
@ -701,7 +709,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
}
|
}
|
||||||
|
|
||||||
switcher.loadKeyboard(editorInfo, mCurrentSettings);
|
switcher.loadKeyboard(editorInfo, mCurrentSettings);
|
||||||
updateKeyboardViewGestureHandlingModeByMainDictionaryAvailability();
|
|
||||||
}
|
}
|
||||||
setSuggestionStripShownInternal(
|
setSuggestionStripShownInternal(
|
||||||
isSuggestionsStripVisible(), /* needsInputViewShown */ false);
|
isSuggestionsStripVisible(), /* needsInputViewShown */ false);
|
||||||
|
@ -719,8 +726,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
mHandler.cancelUpdateSuggestionStrip();
|
mHandler.cancelUpdateSuggestionStrip();
|
||||||
mHandler.cancelDoubleSpacesTimer();
|
mHandler.cancelDoubleSpacesTimer();
|
||||||
|
|
||||||
|
mainKeyboardView.setMainDictionaryAvailability(mIsMainDictionaryAvailable);
|
||||||
mainKeyboardView.setKeyPreviewPopupEnabled(mCurrentSettings.mKeyPreviewPopupOn,
|
mainKeyboardView.setKeyPreviewPopupEnabled(mCurrentSettings.mKeyPreviewPopupOn,
|
||||||
mCurrentSettings.mKeyPreviewPopupDismissDelay);
|
mCurrentSettings.mKeyPreviewPopupDismissDelay);
|
||||||
|
mainKeyboardView.setGestureHandlingEnabledByUser(mCurrentSettings.mGestureInputEnabled);
|
||||||
|
mainKeyboardView.setGesturePreviewMode(mCurrentSettings.mGesturePreviewTrailEnabled,
|
||||||
|
mCurrentSettings.mGestureFloatingPreviewTextEnabled);
|
||||||
|
|
||||||
if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
|
if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
|
||||||
}
|
}
|
||||||
|
@ -898,13 +909,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
|
||||||
ResearchLogger.latinIME_onDisplayCompletions(applicationSpecifiedCompletions);
|
|
||||||
}
|
|
||||||
if (!mCurrentSettings.isApplicationSpecifiedCompletionsOn()) return;
|
if (!mCurrentSettings.isApplicationSpecifiedCompletionsOn()) return;
|
||||||
mApplicationSpecifiedCompletions = applicationSpecifiedCompletions;
|
mApplicationSpecifiedCompletions = applicationSpecifiedCompletions;
|
||||||
if (applicationSpecifiedCompletions == null) {
|
if (applicationSpecifiedCompletions == null) {
|
||||||
clearSuggestionStrip();
|
clearSuggestionStrip();
|
||||||
|
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||||
|
ResearchLogger.latinIME_onDisplayCompletions(null);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -926,6 +937,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
// this case? This says to keep whatever the user typed.
|
// this case? This says to keep whatever the user typed.
|
||||||
mWordComposer.setAutoCorrection(mWordComposer.getTypedWord());
|
mWordComposer.setAutoCorrection(mWordComposer.getTypedWord());
|
||||||
setSuggestionStripShown(true);
|
setSuggestionStripShown(true);
|
||||||
|
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||||
|
ResearchLogger.latinIME_onDisplayCompletions(applicationSpecifiedCompletions);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSuggestionStripShownInternal(boolean shown, boolean needsInputViewShown) {
|
private void setSuggestionStripShownInternal(boolean shown, boolean needsInputViewShown) {
|
||||||
|
@ -1046,9 +1060,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
final CharSequence typedWord = mWordComposer.getTypedWord();
|
final CharSequence typedWord = mWordComposer.getTypedWord();
|
||||||
if (typedWord.length() > 0) {
|
if (typedWord.length() > 0) {
|
||||||
mConnection.commitText(typedWord, 1);
|
mConnection.commitText(typedWord, 1);
|
||||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
|
||||||
ResearchLogger.latinIME_commitText(typedWord);
|
|
||||||
}
|
|
||||||
final CharSequence prevWord = addToUserHistoryDictionary(typedWord);
|
final CharSequence prevWord = addToUserHistoryDictionary(typedWord);
|
||||||
mLastComposedWord = mWordComposer.commitWord(
|
mLastComposedWord = mWordComposer.commitWord(
|
||||||
LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, typedWord.toString(),
|
LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, typedWord.toString(),
|
||||||
|
@ -1084,18 +1095,27 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
return mConnection.getCursorCapsMode(inputType);
|
return mConnection.getCursorCapsMode(inputType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Factor in auto-caps and manual caps and compute the current caps mode.
|
||||||
|
private int getActualCapsMode() {
|
||||||
|
final int manual = mKeyboardSwitcher.getManualCapsMode();
|
||||||
|
if (manual != WordComposer.CAPS_MODE_OFF) return manual;
|
||||||
|
final int auto = getCurrentAutoCapsState();
|
||||||
|
if (0 != (auto & TextUtils.CAP_MODE_CHARACTERS)) {
|
||||||
|
return WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED;
|
||||||
|
}
|
||||||
|
if (0 != auto) return WordComposer.CAPS_MODE_AUTO_SHIFTED;
|
||||||
|
return WordComposer.CAPS_MODE_OFF;
|
||||||
|
}
|
||||||
|
|
||||||
private void swapSwapperAndSpace() {
|
private void swapSwapperAndSpace() {
|
||||||
CharSequence lastTwo = mConnection.getTextBeforeCursor(2, 0);
|
CharSequence lastTwo = mConnection.getTextBeforeCursor(2, 0);
|
||||||
// It is guaranteed lastTwo.charAt(1) is a swapper - else this method is not called.
|
// It is guaranteed lastTwo.charAt(1) is a swapper - else this method is not called.
|
||||||
if (lastTwo != null && lastTwo.length() == 2
|
if (lastTwo != null && lastTwo.length() == 2
|
||||||
&& lastTwo.charAt(0) == Keyboard.CODE_SPACE) {
|
&& lastTwo.charAt(0) == Keyboard.CODE_SPACE) {
|
||||||
mConnection.deleteSurroundingText(2, 0);
|
mConnection.deleteSurroundingText(2, 0);
|
||||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
|
||||||
ResearchLogger.latinIME_deleteSurroundingText(2);
|
|
||||||
}
|
|
||||||
mConnection.commitText(lastTwo.charAt(1) + " ", 1);
|
mConnection.commitText(lastTwo.charAt(1) + " ", 1);
|
||||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||||
ResearchLogger.latinIME_swapSwapperAndSpaceWhileInBatchEdit();
|
ResearchLogger.latinIME_swapSwapperAndSpace();
|
||||||
}
|
}
|
||||||
mKeyboardSwitcher.updateShiftState();
|
mKeyboardSwitcher.updateShiftState();
|
||||||
}
|
}
|
||||||
|
@ -1112,9 +1132,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
mHandler.cancelDoubleSpacesTimer();
|
mHandler.cancelDoubleSpacesTimer();
|
||||||
mConnection.deleteSurroundingText(2, 0);
|
mConnection.deleteSurroundingText(2, 0);
|
||||||
mConnection.commitText(". ", 1);
|
mConnection.commitText(". ", 1);
|
||||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
|
||||||
ResearchLogger.latinIME_doubleSpaceAutoPeriod();
|
|
||||||
}
|
|
||||||
mKeyboardSwitcher.updateShiftState();
|
mKeyboardSwitcher.updateShiftState();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1178,9 +1195,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
|
|
||||||
private void performEditorAction(int actionId) {
|
private void performEditorAction(int actionId) {
|
||||||
mConnection.performEditorAction(actionId);
|
mConnection.performEditorAction(actionId);
|
||||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
|
||||||
ResearchLogger.latinIME_performEditorAction(actionId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleLanguageSwitchKey() {
|
private void handleLanguageSwitchKey() {
|
||||||
|
@ -1217,6 +1231,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
// For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}.
|
// For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}.
|
||||||
if (code >= '0' && code <= '9') {
|
if (code >= '0' && code <= '9') {
|
||||||
super.sendKeyChar((char)code);
|
super.sendKeyChar((char)code);
|
||||||
|
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||||
|
ResearchLogger.latinIME_sendKeyCodePoint(code);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1233,9 +1250,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
final String text = new String(new int[] { code }, 0, 1);
|
final String text = new String(new int[] { code }, 0, 1);
|
||||||
mConnection.commitText(text, text.length());
|
mConnection.commitText(text, text.length());
|
||||||
}
|
}
|
||||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
|
||||||
ResearchLogger.latinIME_sendKeyCodePoint(code);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implementation of {@link KeyboardActionListener}.
|
// Implementation of {@link KeyboardActionListener}.
|
||||||
|
@ -1247,11 +1261,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
}
|
}
|
||||||
mLastKeyTime = when;
|
mLastKeyTime = when;
|
||||||
mConnection.beginBatchEdit();
|
mConnection.beginBatchEdit();
|
||||||
|
|
||||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
|
||||||
ResearchLogger.latinIME_onCodeInput(primaryCode, x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
final KeyboardSwitcher switcher = mKeyboardSwitcher;
|
final KeyboardSwitcher switcher = mKeyboardSwitcher;
|
||||||
// The space state depends only on the last character pressed and its own previous
|
// The space state depends only on the last character pressed and its own previous
|
||||||
// state. Here, we revert the space state to neutral if the key is actually modifying
|
// state. Here, we revert the space state to neutral if the key is actually modifying
|
||||||
|
@ -1284,7 +1293,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
onSettingsKeyPressed();
|
onSettingsKeyPressed();
|
||||||
break;
|
break;
|
||||||
case Keyboard.CODE_SHORTCUT:
|
case Keyboard.CODE_SHORTCUT:
|
||||||
mSubtypeSwitcher.switchToShortcutIME();
|
mSubtypeSwitcher.switchToShortcutIME(this);
|
||||||
break;
|
break;
|
||||||
case Keyboard.CODE_ACTION_ENTER:
|
case Keyboard.CODE_ACTION_ENTER:
|
||||||
performEditorAction(getActionId(switcher.getKeyboard()));
|
performEditorAction(getActionId(switcher.getKeyboard()));
|
||||||
|
@ -1317,8 +1326,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
keyX = x;
|
keyX = x;
|
||||||
keyY = y;
|
keyY = y;
|
||||||
} else {
|
} else {
|
||||||
keyX = NOT_A_TOUCH_COORDINATE;
|
keyX = Constants.NOT_A_COORDINATE;
|
||||||
keyY = NOT_A_TOUCH_COORDINATE;
|
keyY = Constants.NOT_A_COORDINATE;
|
||||||
}
|
}
|
||||||
handleCharacter(primaryCode, keyX, keyY, spaceState);
|
handleCharacter(primaryCode, keyX, keyY, spaceState);
|
||||||
}
|
}
|
||||||
|
@ -1333,22 +1342,24 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
mLastComposedWord.deactivate();
|
mLastComposedWord.deactivate();
|
||||||
mEnteredText = null;
|
mEnteredText = null;
|
||||||
mConnection.endBatchEdit();
|
mConnection.endBatchEdit();
|
||||||
|
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||||
|
ResearchLogger.latinIME_onCodeInput(primaryCode, x, y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called from PointerTracker through the KeyboardActionListener interface
|
// Called from PointerTracker through the KeyboardActionListener interface
|
||||||
@Override
|
@Override
|
||||||
public void onTextInput(CharSequence rawText) {
|
public void onTextInput(CharSequence rawText) {
|
||||||
mConnection.beginBatchEdit();
|
mConnection.beginBatchEdit();
|
||||||
commitTyped(LastComposedWord.NOT_A_SEPARATOR);
|
if (mWordComposer.isComposingWord()) {
|
||||||
|
commitCurrentAutoCorrection(LastComposedWord.NOT_A_SEPARATOR);
|
||||||
|
}
|
||||||
mHandler.postUpdateSuggestionStrip();
|
mHandler.postUpdateSuggestionStrip();
|
||||||
final CharSequence text = specificTldProcessingOnTextInput(rawText);
|
final CharSequence text = specificTldProcessingOnTextInput(rawText);
|
||||||
if (SPACE_STATE_PHANTOM == mSpaceState) {
|
if (SPACE_STATE_PHANTOM == mSpaceState) {
|
||||||
sendKeyCodePoint(Keyboard.CODE_SPACE);
|
sendKeyCodePoint(Keyboard.CODE_SPACE);
|
||||||
}
|
}
|
||||||
mConnection.commitText(text, 1);
|
mConnection.commitText(text, 1);
|
||||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
|
||||||
ResearchLogger.latinIME_commitText(text);
|
|
||||||
}
|
|
||||||
mConnection.endBatchEdit();
|
mConnection.endBatchEdit();
|
||||||
mKeyboardSwitcher.updateShiftState();
|
mKeyboardSwitcher.updateShiftState();
|
||||||
mKeyboardSwitcher.onCodeInput(Keyboard.CODE_OUTPUT_TEXT);
|
mKeyboardSwitcher.onCodeInput(Keyboard.CODE_OUTPUT_TEXT);
|
||||||
|
@ -1361,15 +1372,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
public void onStartBatchInput() {
|
public void onStartBatchInput() {
|
||||||
mConnection.beginBatchEdit();
|
mConnection.beginBatchEdit();
|
||||||
if (mWordComposer.isComposingWord()) {
|
if (mWordComposer.isComposingWord()) {
|
||||||
commitTyped(LastComposedWord.NOT_A_SEPARATOR);
|
commitCurrentAutoCorrection(LastComposedWord.NOT_A_SEPARATOR);
|
||||||
mExpectingUpdateSelection = true;
|
mExpectingUpdateSelection = true;
|
||||||
// TODO: Can we remove this?
|
// TODO: Can we remove this?
|
||||||
mSpaceState = SPACE_STATE_PHANTOM;
|
mSpaceState = SPACE_STATE_PHANTOM;
|
||||||
}
|
}
|
||||||
mConnection.endBatchEdit();
|
mConnection.endBatchEdit();
|
||||||
// TODO: Should handle TextUtils.CAP_MODE_CHARACTER.
|
// TODO: Should handle TextUtils.CAP_MODE_CHARACTER.
|
||||||
mWordComposer.setAutoCapitalized(
|
mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode());
|
||||||
getCurrentAutoCapsState() != Constants.TextUtils.CAP_MODE_OFF);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1447,9 +1457,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
// like the smiley key or the .com key.
|
// like the smiley key or the .com key.
|
||||||
final int length = mEnteredText.length();
|
final int length = mEnteredText.length();
|
||||||
mConnection.deleteSurroundingText(length, 0);
|
mConnection.deleteSurroundingText(length, 0);
|
||||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
|
||||||
ResearchLogger.latinIME_deleteSurroundingText(length);
|
|
||||||
}
|
|
||||||
// If we have mEnteredText, then we know that mHasUncommittedTypedChars == false.
|
// If we have mEnteredText, then we know that mHasUncommittedTypedChars == false.
|
||||||
// In addition we know that spaceState is false, and that we should not be
|
// In addition we know that spaceState is false, and that we should not be
|
||||||
// reverting any autocorrect at this point. So we can safely return.
|
// reverting any autocorrect at this point. So we can safely return.
|
||||||
|
@ -1469,9 +1476,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
mHandler.postUpdateSuggestionStrip();
|
mHandler.postUpdateSuggestionStrip();
|
||||||
} else {
|
} else {
|
||||||
mConnection.deleteSurroundingText(1, 0);
|
mConnection.deleteSurroundingText(1, 0);
|
||||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
|
||||||
ResearchLogger.latinIME_deleteSurroundingText(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (mLastComposedWord.canRevertCommit()) {
|
if (mLastComposedWord.canRevertCommit()) {
|
||||||
|
@ -1500,9 +1504,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
final int lengthToDelete = mLastSelectionEnd - mLastSelectionStart;
|
final int lengthToDelete = mLastSelectionEnd - mLastSelectionStart;
|
||||||
mConnection.setSelection(mLastSelectionEnd, mLastSelectionEnd);
|
mConnection.setSelection(mLastSelectionEnd, mLastSelectionEnd);
|
||||||
mConnection.deleteSurroundingText(lengthToDelete, 0);
|
mConnection.deleteSurroundingText(lengthToDelete, 0);
|
||||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
|
||||||
ResearchLogger.latinIME_deleteSurroundingText(lengthToDelete);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// There is no selection, just delete one character.
|
// There is no selection, just delete one character.
|
||||||
if (NOT_A_CURSOR_POSITION == mLastSelectionEnd) {
|
if (NOT_A_CURSOR_POSITION == mLastSelectionEnd) {
|
||||||
|
@ -1521,14 +1522,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
} else {
|
} else {
|
||||||
mConnection.deleteSurroundingText(1, 0);
|
mConnection.deleteSurroundingText(1, 0);
|
||||||
}
|
}
|
||||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
|
||||||
ResearchLogger.latinIME_deleteSurroundingText(1);
|
|
||||||
}
|
|
||||||
if (mDeleteCount > DELETE_ACCELERATE_AT) {
|
if (mDeleteCount > DELETE_ACCELERATE_AT) {
|
||||||
mConnection.deleteSurroundingText(1, 0);
|
mConnection.deleteSurroundingText(1, 0);
|
||||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
|
||||||
ResearchLogger.latinIME_deleteSurroundingText(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mCurrentSettings.isSuggestionsRequested(mDisplayOrientation)) {
|
if (mCurrentSettings.isSuggestionsRequested(mDisplayOrientation)) {
|
||||||
|
@ -1604,13 +1599,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
mWordComposer.add(primaryCode, keyX, keyY);
|
mWordComposer.add(primaryCode, keyX, keyY);
|
||||||
// If it's the first letter, make note of auto-caps state
|
// If it's the first letter, make note of auto-caps state
|
||||||
if (mWordComposer.size() == 1) {
|
if (mWordComposer.size() == 1) {
|
||||||
mWordComposer.setAutoCapitalized(
|
mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode());
|
||||||
getCurrentAutoCapsState() != Constants.TextUtils.CAP_MODE_OFF);
|
|
||||||
}
|
}
|
||||||
mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
|
mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
|
||||||
} else {
|
} else {
|
||||||
final boolean swapWeakSpace = maybeStripSpace(primaryCode,
|
final boolean swapWeakSpace = maybeStripSpace(primaryCode,
|
||||||
spaceState, KeyboardActionListener.SUGGESTION_STRIP_COORDINATE == x);
|
spaceState, Constants.SUGGESTION_STRIP_COORDINATE == x);
|
||||||
|
|
||||||
sendKeyCodePoint(primaryCode);
|
sendKeyCodePoint(primaryCode);
|
||||||
|
|
||||||
|
@ -1640,7 +1634,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean swapWeakSpace = maybeStripSpace(primaryCode, spaceState,
|
final boolean swapWeakSpace = maybeStripSpace(primaryCode, spaceState,
|
||||||
KeyboardActionListener.SUGGESTION_STRIP_COORDINATE == x);
|
Constants.SUGGESTION_STRIP_COORDINATE == x);
|
||||||
|
|
||||||
if (SPACE_STATE_PHANTOM == spaceState &&
|
if (SPACE_STATE_PHANTOM == spaceState &&
|
||||||
mCurrentSettings.isPhantomSpacePromotingSymbol(primaryCode)) {
|
mCurrentSettings.isPhantomSpacePromotingSymbol(primaryCode)) {
|
||||||
|
@ -1687,6 +1681,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
|
|
||||||
Utils.Stats.onSeparator((char)primaryCode, x, y);
|
Utils.Stats.onSeparator((char)primaryCode, x, y);
|
||||||
|
|
||||||
|
mHandler.postUpdateShiftState();
|
||||||
return didAutoCorrect;
|
return didAutoCorrect;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1707,7 +1702,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
|
|
||||||
// TODO: make this private
|
// TODO: make this private
|
||||||
// Outside LatinIME, only used by the test suite.
|
// Outside LatinIME, only used by the test suite.
|
||||||
/* package for tests */ boolean isShowingPunctuationList() {
|
/* package for tests */
|
||||||
|
boolean isShowingPunctuationList() {
|
||||||
if (mSuggestionStripView == null) return false;
|
if (mSuggestionStripView == null) return false;
|
||||||
return mCurrentSettings.mSuggestPuncList == mSuggestionStripView.getSuggestions();
|
return mCurrentSettings.mSuggestPuncList == mSuggestionStripView.getSuggestions();
|
||||||
}
|
}
|
||||||
|
@ -1853,10 +1849,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
+ "is empty? Impossible! I must commit suicide.");
|
+ "is empty? Impossible! I must commit suicide.");
|
||||||
}
|
}
|
||||||
Utils.Stats.onAutoCorrection(typedWord, autoCorrection.toString(), separatorCodePoint);
|
Utils.Stats.onAutoCorrection(typedWord, autoCorrection.toString(), separatorCodePoint);
|
||||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
|
||||||
ResearchLogger.latinIME_commitCurrentAutoCorrection(typedWord,
|
|
||||||
autoCorrection.toString());
|
|
||||||
}
|
|
||||||
mExpectingUpdateSelection = true;
|
mExpectingUpdateSelection = true;
|
||||||
commitChosenWord(autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD,
|
commitChosenWord(autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD,
|
||||||
separatorCodePoint);
|
separatorCodePoint);
|
||||||
|
@ -1873,8 +1865,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
// Called from {@link SuggestionStripView} through the {@link SuggestionStripView#Listener}
|
// Called from {@link SuggestionStripView} through the {@link SuggestionStripView#Listener}
|
||||||
// interface
|
// interface
|
||||||
@Override
|
@Override
|
||||||
public void pickSuggestionManually(final int index, final CharSequence suggestion,
|
public void pickSuggestionManually(final int index, final CharSequence suggestion) {
|
||||||
final int x, final int y) {
|
|
||||||
final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions();
|
final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions();
|
||||||
// If this is a punctuation picked from the suggestion strip, pass it to onCodeInput
|
// If this is a punctuation picked from the suggestion strip, pass it to onCodeInput
|
||||||
if (suggestion.length() == 1 && isShowingPunctuationList()) {
|
if (suggestion.length() == 1 && isShowingPunctuationList()) {
|
||||||
|
@ -1882,13 +1873,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
// So, LatinImeLogger logs "" as a user's input.
|
// So, LatinImeLogger logs "" as a user's input.
|
||||||
LatinImeLogger.logOnManualSuggestion("", suggestion.toString(), index, suggestedWords);
|
LatinImeLogger.logOnManualSuggestion("", suggestion.toString(), index, suggestedWords);
|
||||||
// Rely on onCodeInput to do the complicated swapping/stripping logic consistently.
|
// Rely on onCodeInput to do the complicated swapping/stripping logic consistently.
|
||||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
|
||||||
ResearchLogger.latinIME_punctuationSuggestion(index, suggestion, x, y);
|
|
||||||
}
|
|
||||||
final int primaryCode = suggestion.charAt(0);
|
final int primaryCode = suggestion.charAt(0);
|
||||||
onCodeInput(primaryCode,
|
onCodeInput(primaryCode,
|
||||||
KeyboardActionListener.SUGGESTION_STRIP_COORDINATE,
|
Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE);
|
||||||
KeyboardActionListener.SUGGESTION_STRIP_COORDINATE);
|
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||||
|
ResearchLogger.latinIME_punctuationSuggestion(index, suggestion);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1915,10 +1905,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
final CompletionInfo completionInfo = mApplicationSpecifiedCompletions[index];
|
final CompletionInfo completionInfo = mApplicationSpecifiedCompletions[index];
|
||||||
mConnection.commitCompletion(completionInfo);
|
mConnection.commitCompletion(completionInfo);
|
||||||
mConnection.endBatchEdit();
|
mConnection.endBatchEdit();
|
||||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
|
||||||
ResearchLogger.latinIME_pickApplicationSpecifiedCompletion(index,
|
|
||||||
completionInfo.getText(), x, y);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1927,12 +1913,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
final String replacedWord = mWordComposer.getTypedWord().toString();
|
final String replacedWord = mWordComposer.getTypedWord().toString();
|
||||||
LatinImeLogger.logOnManualSuggestion(replacedWord,
|
LatinImeLogger.logOnManualSuggestion(replacedWord,
|
||||||
suggestion.toString(), index, suggestedWords);
|
suggestion.toString(), index, suggestedWords);
|
||||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
|
||||||
ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion, x, y);
|
|
||||||
}
|
|
||||||
mExpectingUpdateSelection = true;
|
mExpectingUpdateSelection = true;
|
||||||
commitChosenWord(suggestion, LastComposedWord.COMMIT_TYPE_MANUAL_PICK,
|
commitChosenWord(suggestion, LastComposedWord.COMMIT_TYPE_MANUAL_PICK,
|
||||||
LastComposedWord.NOT_A_SEPARATOR);
|
LastComposedWord.NOT_A_SEPARATOR);
|
||||||
|
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||||
|
ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion);
|
||||||
|
}
|
||||||
mConnection.endBatchEdit();
|
mConnection.endBatchEdit();
|
||||||
// Don't allow cancellation of manual pick
|
// Don't allow cancellation of manual pick
|
||||||
mLastComposedWord.deactivate();
|
mLastComposedWord.deactivate();
|
||||||
|
@ -1948,8 +1934,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
// If the suggestion is not in the dictionary, the hint should be shown.
|
// If the suggestion is not in the dictionary, the hint should be shown.
|
||||||
&& !AutoCorrection.isValidWord(mSuggest.getUnigramDictionaries(), suggestion, true);
|
&& !AutoCorrection.isValidWord(mSuggest.getUnigramDictionaries(), suggestion, true);
|
||||||
|
|
||||||
Utils.Stats.onSeparator((char)Keyboard.CODE_SPACE, WordComposer.NOT_A_COORDINATE,
|
Utils.Stats.onSeparator((char)Keyboard.CODE_SPACE,
|
||||||
WordComposer.NOT_A_COORDINATE);
|
Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
|
||||||
if (showingAddToDictionaryHint && mIsUserDictionaryAvailable) {
|
if (showingAddToDictionaryHint && mIsUserDictionaryAvailable) {
|
||||||
mSuggestionStripView.showAddToDictionaryHint(
|
mSuggestionStripView.showAddToDictionaryHint(
|
||||||
suggestion, mCurrentSettings.mHintToSaveText);
|
suggestion, mCurrentSettings.mHintToSaveText);
|
||||||
|
@ -1967,9 +1953,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions();
|
final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions();
|
||||||
mConnection.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan(
|
mConnection.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan(
|
||||||
this, chosenWord, suggestedWords, mIsMainDictionaryAvailable), 1);
|
this, chosenWord, suggestedWords, mIsMainDictionaryAvailable), 1);
|
||||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
|
||||||
ResearchLogger.latinIME_commitText(chosenWord);
|
|
||||||
}
|
|
||||||
// Add the word to the user history dictionary
|
// Add the word to the user history dictionary
|
||||||
final CharSequence prevWord = addToUserHistoryDictionary(chosenWord);
|
final CharSequence prevWord = addToUserHistoryDictionary(chosenWord);
|
||||||
// TODO: figure out here if this is an auto-correct or if the best word is actually
|
// TODO: figure out here if this is an auto-correct or if the best word is actually
|
||||||
|
@ -1992,6 +1975,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
|
|
||||||
private CharSequence addToUserHistoryDictionary(final CharSequence suggestion) {
|
private CharSequence addToUserHistoryDictionary(final CharSequence suggestion) {
|
||||||
if (TextUtils.isEmpty(suggestion)) return null;
|
if (TextUtils.isEmpty(suggestion)) return null;
|
||||||
|
if (mSuggest == null) return null;
|
||||||
|
|
||||||
// If correction is not enabled, we don't add words to the user history dictionary.
|
// If correction is not enabled, we don't add words to the user history dictionary.
|
||||||
// That's to avoid unintended additions in some sensitive fields, or fields that
|
// That's to avoid unintended additions in some sensitive fields, or fields that
|
||||||
|
@ -2003,7 +1987,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
final CharSequence prevWord
|
final CharSequence prevWord
|
||||||
= mConnection.getNthPreviousWord(mCurrentSettings.mWordSeparators, 2);
|
= mConnection.getNthPreviousWord(mCurrentSettings.mWordSeparators, 2);
|
||||||
final String secondWord;
|
final String secondWord;
|
||||||
if (mWordComposer.isAutoCapitalized() && !mWordComposer.isMostlyCaps()) {
|
if (mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps()) {
|
||||||
secondWord = suggestion.toString().toLowerCase(
|
secondWord = suggestion.toString().toLowerCase(
|
||||||
mSubtypeSwitcher.getCurrentSubtypeLocale());
|
mSubtypeSwitcher.getCurrentSubtypeLocale());
|
||||||
} else {
|
} else {
|
||||||
|
@ -2036,9 +2020,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
mWordComposer.setComposingWord(word, mKeyboardSwitcher.getKeyboard());
|
mWordComposer.setComposingWord(word, mKeyboardSwitcher.getKeyboard());
|
||||||
final int length = word.length();
|
final int length = word.length();
|
||||||
mConnection.deleteSurroundingText(length, 0);
|
mConnection.deleteSurroundingText(length, 0);
|
||||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
|
||||||
ResearchLogger.latinIME_deleteSurroundingText(length);
|
|
||||||
}
|
|
||||||
mConnection.setComposingText(word, 1);
|
mConnection.setComposingText(word, 1);
|
||||||
mHandler.postUpdateSuggestionStrip();
|
mHandler.postUpdateSuggestionStrip();
|
||||||
}
|
}
|
||||||
|
@ -2066,9 +2047,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mConnection.deleteSurroundingText(deleteLength, 0);
|
mConnection.deleteSurroundingText(deleteLength, 0);
|
||||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
|
||||||
ResearchLogger.latinIME_deleteSurroundingText(deleteLength);
|
|
||||||
}
|
|
||||||
if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) {
|
if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) {
|
||||||
mUserHistoryDictionary.cancelAddingUserHistory(
|
mUserHistoryDictionary.cancelAddingUserHistory(
|
||||||
previousWord.toString(), committedWord.toString());
|
previousWord.toString(), committedWord.toString());
|
||||||
|
@ -2076,8 +2054,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
mConnection.commitText(originallyTypedWord, 1);
|
mConnection.commitText(originallyTypedWord, 1);
|
||||||
// Re-insert the separator
|
// Re-insert the separator
|
||||||
sendKeyCodePoint(mLastComposedWord.mSeparatorCode);
|
sendKeyCodePoint(mLastComposedWord.mSeparatorCode);
|
||||||
Utils.Stats.onSeparator(mLastComposedWord.mSeparatorCode, WordComposer.NOT_A_COORDINATE,
|
Utils.Stats.onSeparator(mLastComposedWord.mSeparatorCode,
|
||||||
WordComposer.NOT_A_COORDINATE);
|
Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
|
||||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||||
ResearchLogger.latinIME_revertCommit(originallyTypedWord);
|
ResearchLogger.latinIME_revertCommit(originallyTypedWord);
|
||||||
}
|
}
|
||||||
|
@ -2093,9 +2071,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
return mCurrentSettings.isWordSeparator(code);
|
return mCurrentSettings.isWordSeparator(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify that language or mode have been changed and toggleLanguage will update KeyboardID
|
// TODO: Make this private
|
||||||
// according to new language or mode. Called from SubtypeSwitcher.
|
// Outside LatinIME, only used by the {@link InputTestsBase} test suite.
|
||||||
public void onRefreshKeyboard() {
|
/* package for test */
|
||||||
|
void loadKeyboard() {
|
||||||
// When the device locale is changed in SetupWizard etc., this method may get called via
|
// When the device locale is changed in SetupWizard etc., this method may get called via
|
||||||
// onConfigurationChanged before SoftInputWindow is shown.
|
// onConfigurationChanged before SoftInputWindow is shown.
|
||||||
initSuggest();
|
initSuggest();
|
||||||
|
@ -2103,7 +2082,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
if (mKeyboardSwitcher.getMainKeyboardView() != null) {
|
if (mKeyboardSwitcher.getMainKeyboardView() != null) {
|
||||||
// Reload keyboard because the current language has been changed.
|
// Reload keyboard because the current language has been changed.
|
||||||
mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mCurrentSettings);
|
mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mCurrentSettings);
|
||||||
updateKeyboardViewGestureHandlingModeByMainDictionaryAvailability();
|
|
||||||
}
|
}
|
||||||
// Since we just changed languages, we should re-evaluate suggestions with whatever word
|
// Since we just changed languages, we should re-evaluate suggestions with whatever word
|
||||||
// we are currently composing. If we are not composing anything, we may want to display
|
// we are currently composing. If we are not composing anything, we may want to display
|
||||||
|
@ -2111,17 +2089,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
||||||
mHandler.postUpdateSuggestionStrip();
|
mHandler.postUpdateSuggestionStrip();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateKeyboardViewGestureHandlingModeByMainDictionaryAvailability() {
|
|
||||||
final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
|
|
||||||
if (mainKeyboardView != null) {
|
|
||||||
final boolean shouldHandleGesture = mCurrentSettings.mGestureInputEnabled
|
|
||||||
&& mIsMainDictionaryAvailable;
|
|
||||||
mainKeyboardView.setGestureHandlingMode(shouldHandleGesture,
|
|
||||||
mCurrentSettings.mGesturePreviewTrailEnabled,
|
|
||||||
mCurrentSettings.mGestureFloatingPreviewTextEnabled);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Remove this method from {@link LatinIME} and move {@link FeedbackManager} to
|
// TODO: Remove this method from {@link LatinIME} and move {@link FeedbackManager} to
|
||||||
// {@link KeyboardSwitcher}. Called from KeyboardSwitcher
|
// {@link KeyboardSwitcher}. Called from KeyboardSwitcher
|
||||||
public void hapticAndAudioFeedback(final int primaryCode) {
|
public void hapticAndAudioFeedback(final int primaryCode) {
|
||||||
|
|
|
@ -193,7 +193,7 @@ public class LocaleUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final HashMap<String, Locale> sLocaleCache = new HashMap<String, Locale>();
|
private static final HashMap<String, Locale> sLocaleCache = CollectionUtils.newHashMap();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a locale from a string specification.
|
* Creates a locale from a string specification.
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -131,4 +131,16 @@ public class ResizableIntArray {
|
||||||
mLength = endPos;
|
mLength = endPos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < mLength; i++) {
|
||||||
|
if (i != 0) {
|
||||||
|
sb.append(",");
|
||||||
|
}
|
||||||
|
sb.append(mArray[i]);
|
||||||
|
}
|
||||||
|
return "[" + sb + "]";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,9 @@ public class RichInputConnection {
|
||||||
public void beginBatchEdit() {
|
public void beginBatchEdit() {
|
||||||
if (++mNestLevel == 1) {
|
if (++mNestLevel == 1) {
|
||||||
mIC = mParent.getCurrentInputConnection();
|
mIC = mParent.getCurrentInputConnection();
|
||||||
if (null != mIC) mIC.beginBatchEdit();
|
if (null != mIC) {
|
||||||
|
mIC.beginBatchEdit();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (DBG) {
|
if (DBG) {
|
||||||
throw new RuntimeException("Nest level too deep");
|
throw new RuntimeException("Nest level too deep");
|
||||||
|
@ -66,7 +68,9 @@ public class RichInputConnection {
|
||||||
}
|
}
|
||||||
public void endBatchEdit() {
|
public void endBatchEdit() {
|
||||||
if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead
|
if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead
|
||||||
if (--mNestLevel == 0 && null != mIC) mIC.endBatchEdit();
|
if (--mNestLevel == 0 && null != mIC) {
|
||||||
|
mIC.endBatchEdit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkBatchEdit() {
|
private void checkBatchEdit() {
|
||||||
|
@ -79,12 +83,22 @@ public class RichInputConnection {
|
||||||
|
|
||||||
public void finishComposingText() {
|
public void finishComposingText() {
|
||||||
checkBatchEdit();
|
checkBatchEdit();
|
||||||
if (null != mIC) mIC.finishComposingText();
|
if (null != mIC) {
|
||||||
|
mIC.finishComposingText();
|
||||||
|
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||||
|
ResearchLogger.richInputConnection_finishComposingText();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void commitText(final CharSequence text, final int i) {
|
public void commitText(final CharSequence text, final int i) {
|
||||||
checkBatchEdit();
|
checkBatchEdit();
|
||||||
if (null != mIC) mIC.commitText(text, i);
|
if (null != mIC) {
|
||||||
|
mIC.commitText(text, i);
|
||||||
|
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||||
|
ResearchLogger.richInputConnection_commitText(text, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getCursorCapsMode(final int inputType) {
|
public int getCursorCapsMode(final int inputType) {
|
||||||
|
@ -107,37 +121,72 @@ public class RichInputConnection {
|
||||||
|
|
||||||
public void deleteSurroundingText(final int i, final int j) {
|
public void deleteSurroundingText(final int i, final int j) {
|
||||||
checkBatchEdit();
|
checkBatchEdit();
|
||||||
if (null != mIC) mIC.deleteSurroundingText(i, j);
|
if (null != mIC) {
|
||||||
|
mIC.deleteSurroundingText(i, j);
|
||||||
|
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||||
|
ResearchLogger.richInputConnection_deleteSurroundingText(i, j);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void performEditorAction(final int actionId) {
|
public void performEditorAction(final int actionId) {
|
||||||
mIC = mParent.getCurrentInputConnection();
|
mIC = mParent.getCurrentInputConnection();
|
||||||
if (null != mIC) mIC.performEditorAction(actionId);
|
if (null != mIC) {
|
||||||
|
mIC.performEditorAction(actionId);
|
||||||
|
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||||
|
ResearchLogger.richInputConnection_performEditorAction(actionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendKeyEvent(final KeyEvent keyEvent) {
|
public void sendKeyEvent(final KeyEvent keyEvent) {
|
||||||
checkBatchEdit();
|
checkBatchEdit();
|
||||||
if (null != mIC) mIC.sendKeyEvent(keyEvent);
|
if (null != mIC) {
|
||||||
|
mIC.sendKeyEvent(keyEvent);
|
||||||
|
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||||
|
ResearchLogger.richInputConnection_sendKeyEvent(keyEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setComposingText(final CharSequence text, final int i) {
|
public void setComposingText(final CharSequence text, final int i) {
|
||||||
checkBatchEdit();
|
checkBatchEdit();
|
||||||
if (null != mIC) mIC.setComposingText(text, i);
|
if (null != mIC) {
|
||||||
|
mIC.setComposingText(text, i);
|
||||||
|
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||||
|
ResearchLogger.richInputConnection_setComposingText(text, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSelection(final int from, final int to) {
|
public void setSelection(final int from, final int to) {
|
||||||
checkBatchEdit();
|
checkBatchEdit();
|
||||||
if (null != mIC) mIC.setSelection(from, to);
|
if (null != mIC) {
|
||||||
|
mIC.setSelection(from, to);
|
||||||
|
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||||
|
ResearchLogger.richInputConnection_setSelection(from, to);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void commitCorrection(final CorrectionInfo correctionInfo) {
|
public void commitCorrection(final CorrectionInfo correctionInfo) {
|
||||||
checkBatchEdit();
|
checkBatchEdit();
|
||||||
if (null != mIC) mIC.commitCorrection(correctionInfo);
|
if (null != mIC) {
|
||||||
|
mIC.commitCorrection(correctionInfo);
|
||||||
|
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||||
|
ResearchLogger.richInputConnection_commitCorrection(correctionInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void commitCompletion(final CompletionInfo completionInfo) {
|
public void commitCompletion(final CompletionInfo completionInfo) {
|
||||||
checkBatchEdit();
|
checkBatchEdit();
|
||||||
if (null != mIC) mIC.commitCompletion(completionInfo);
|
if (null != mIC) {
|
||||||
|
mIC.commitCompletion(completionInfo);
|
||||||
|
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||||
|
ResearchLogger.richInputConnection_commitCompletion(completionInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public CharSequence getNthPreviousWord(final String sentenceSeperators, final int n) {
|
public CharSequence getNthPreviousWord(final String sentenceSeperators, final int n) {
|
||||||
|
@ -315,9 +364,6 @@ public class RichInputConnection {
|
||||||
if (lastOne != null && lastOne.length() == 1
|
if (lastOne != null && lastOne.length() == 1
|
||||||
&& lastOne.charAt(0) == Keyboard.CODE_SPACE) {
|
&& lastOne.charAt(0) == Keyboard.CODE_SPACE) {
|
||||||
deleteSurroundingText(1, 0);
|
deleteSurroundingText(1, 0);
|
||||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
|
||||||
ResearchLogger.latinIME_deleteSurroundingText(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -382,13 +428,7 @@ public class RichInputConnection {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
deleteSurroundingText(2, 0);
|
deleteSurroundingText(2, 0);
|
||||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
|
||||||
ResearchLogger.latinIME_deleteSurroundingText(2);
|
|
||||||
}
|
|
||||||
commitText(" ", 1);
|
commitText(" ", 1);
|
||||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
|
||||||
ResearchLogger.latinIME_revertDoubleSpaceWhileInBatchEdit();
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -409,13 +449,7 @@ public class RichInputConnection {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
deleteSurroundingText(2, 0);
|
deleteSurroundingText(2, 0);
|
||||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
|
||||||
ResearchLogger.latinIME_deleteSurroundingText(2);
|
|
||||||
}
|
|
||||||
commitText(" " + textBeforeCursor.subSequence(0, 1), 1);
|
commitText(" " + textBeforeCursor.subSequence(0, 1), 1);
|
||||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
|
||||||
ResearchLogger.latinIME_revertSwapPunctuation();
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,7 +185,7 @@ public class SettingsValues {
|
||||||
|
|
||||||
// Helper functions to create member values.
|
// Helper functions to create member values.
|
||||||
private static SuggestedWords createSuggestPuncList(final String[] puncs) {
|
private static SuggestedWords createSuggestPuncList(final String[] puncs) {
|
||||||
final ArrayList<SuggestedWordInfo> puncList = new ArrayList<SuggestedWordInfo>();
|
final ArrayList<SuggestedWordInfo> puncList = CollectionUtils.newArrayList();
|
||||||
if (puncs != null) {
|
if (puncs != null) {
|
||||||
for (final String puncSpec : puncs) {
|
for (final String puncSpec : puncs) {
|
||||||
puncList.add(new SuggestedWordInfo(KeySpecParser.getLabel(puncSpec),
|
puncList.add(new SuggestedWordInfo(KeySpecParser.getLabel(puncSpec),
|
||||||
|
@ -417,6 +417,10 @@ public class SettingsValues {
|
||||||
prefs.edit().putString(Settings.PREF_LAST_USER_DICTIONARY_WRITE_TIME, newStr).apply();
|
prefs.edit().putString(Settings.PREF_LAST_USER_DICTIONARY_WRITE_TIME, newStr).apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isSameInputType(final EditorInfo editorInfo) {
|
||||||
|
return mInputAttributes.isSameInputType(editorInfo);
|
||||||
|
}
|
||||||
|
|
||||||
// For debug.
|
// For debug.
|
||||||
public String getInputAttributesDebugString() {
|
public String getInputAttributesDebugString() {
|
||||||
return mInputAttributes.toString();
|
return mInputAttributes.toString();
|
||||||
|
|
|
@ -53,7 +53,7 @@ public class StringUtils {
|
||||||
if (TextUtils.isEmpty(csv)) return "";
|
if (TextUtils.isEmpty(csv)) return "";
|
||||||
final String[] elements = csv.split(",");
|
final String[] elements = csv.split(",");
|
||||||
if (!containsInArray(key, elements)) return csv;
|
if (!containsInArray(key, elements)) return csv;
|
||||||
final ArrayList<String> result = new ArrayList<String>(elements.length - 1);
|
final ArrayList<String> result = CollectionUtils.newArrayList(elements.length - 1);
|
||||||
for (final String element : elements) {
|
for (final String element : elements) {
|
||||||
if (!key.equals(element)) result.add(element);
|
if (!key.equals(element)) result.add(element);
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,13 +45,13 @@ public class SubtypeLocale {
|
||||||
private static String[] sPredefinedKeyboardLayoutSet;
|
private static String[] sPredefinedKeyboardLayoutSet;
|
||||||
// Keyboard layout to its display name map.
|
// Keyboard layout to its display name map.
|
||||||
private static final HashMap<String, String> sKeyboardLayoutToDisplayNameMap =
|
private static final HashMap<String, String> sKeyboardLayoutToDisplayNameMap =
|
||||||
new HashMap<String, String>();
|
CollectionUtils.newHashMap();
|
||||||
// Keyboard layout to subtype name resource id map.
|
// Keyboard layout to subtype name resource id map.
|
||||||
private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap =
|
private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap =
|
||||||
new HashMap<String, Integer>();
|
CollectionUtils.newHashMap();
|
||||||
// Exceptional locale to subtype name resource id map.
|
// Exceptional locale to subtype name resource id map.
|
||||||
private static final HashMap<String, Integer> sExceptionalLocaleToWithLayoutNameIdsMap =
|
private static final HashMap<String, Integer> sExceptionalLocaleToWithLayoutNameIdsMap =
|
||||||
new HashMap<String, Integer>();
|
CollectionUtils.newHashMap();
|
||||||
private static final String SUBTYPE_NAME_RESOURCE_GENERIC_PREFIX =
|
private static final String SUBTYPE_NAME_RESOURCE_GENERIC_PREFIX =
|
||||||
"string/subtype_generic_";
|
"string/subtype_generic_";
|
||||||
private static final String SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX =
|
private static final String SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX =
|
||||||
|
@ -60,11 +60,11 @@ public class SubtypeLocale {
|
||||||
"string/subtype_no_language_";
|
"string/subtype_no_language_";
|
||||||
// Exceptional locales to display name map.
|
// Exceptional locales to display name map.
|
||||||
private static final HashMap<String, String> sExceptionalDisplayNamesMap =
|
private static final HashMap<String, String> sExceptionalDisplayNamesMap =
|
||||||
new HashMap<String, String>();
|
CollectionUtils.newHashMap();
|
||||||
// Keyboard layout set name for the subtypes that don't have a keyboardLayoutSet extra value.
|
// Keyboard layout set name for the subtypes that don't have a keyboardLayoutSet extra value.
|
||||||
// This is for compatibility to keep the same subtype ids as pre-JellyBean.
|
// This is for compatibility to keep the same subtype ids as pre-JellyBean.
|
||||||
private static final HashMap<String,String> sLocaleAndExtraValueToKeyboardLayoutSetMap =
|
private static final HashMap<String,String> sLocaleAndExtraValueToKeyboardLayoutSetMap =
|
||||||
new HashMap<String,String>();
|
CollectionUtils.newHashMap();
|
||||||
|
|
||||||
private SubtypeLocale() {
|
private SubtypeLocale() {
|
||||||
// Intentional empty constructor for utility class.
|
// Intentional empty constructor for utility class.
|
||||||
|
|
|
@ -22,6 +22,7 @@ import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
|
import android.inputmethodservice.InputMethodService;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.net.NetworkInfo;
|
import android.net.NetworkInfo;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
|
@ -42,7 +43,6 @@ public class SubtypeSwitcher {
|
||||||
private static final String TAG = SubtypeSwitcher.class.getSimpleName();
|
private static final String TAG = SubtypeSwitcher.class.getSimpleName();
|
||||||
|
|
||||||
private static final SubtypeSwitcher sInstance = new SubtypeSwitcher();
|
private static final SubtypeSwitcher sInstance = new SubtypeSwitcher();
|
||||||
private /* final */ LatinIME mService;
|
|
||||||
private /* final */ InputMethodManager mImm;
|
private /* final */ InputMethodManager mImm;
|
||||||
private /* final */ Resources mResources;
|
private /* final */ Resources mResources;
|
||||||
private /* final */ ConnectivityManager mConnectivityManager;
|
private /* final */ ConnectivityManager mConnectivityManager;
|
||||||
|
@ -68,11 +68,11 @@ public class SubtypeSwitcher {
|
||||||
return mEnabledSubtypeCount >= 2 || !mIsSystemLanguageSameAsInputLanguage;
|
return mEnabledSubtypeCount >= 2 || !mIsSystemLanguageSameAsInputLanguage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateEnabledSubtypeCount(int count) {
|
public void updateEnabledSubtypeCount(final int count) {
|
||||||
mEnabledSubtypeCount = count;
|
mEnabledSubtypeCount = count;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateIsSystemLanguageSameAsInputLanguage(boolean isSame) {
|
public void updateIsSystemLanguageSameAsInputLanguage(final boolean isSame) {
|
||||||
mIsSystemLanguageSameAsInputLanguage = isSame;
|
mIsSystemLanguageSameAsInputLanguage = isSame;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,18 +81,17 @@ public class SubtypeSwitcher {
|
||||||
return sInstance;
|
return sInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void init(LatinIME service) {
|
public static void init(final Context context) {
|
||||||
SubtypeLocale.init(service);
|
SubtypeLocale.init(context);
|
||||||
sInstance.initialize(service);
|
sInstance.initialize(context);
|
||||||
sInstance.updateAllParameters();
|
sInstance.updateAllParameters(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
private SubtypeSwitcher() {
|
private SubtypeSwitcher() {
|
||||||
// Intentional empty constructor for singleton.
|
// Intentional empty constructor for singleton.
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initialize(LatinIME service) {
|
private void initialize(final Context service) {
|
||||||
mService = service;
|
|
||||||
mResources = service.getResources();
|
mResources = service.getResources();
|
||||||
mImm = ImfUtils.getInputMethodManager(service);
|
mImm = ImfUtils.getInputMethodManager(service);
|
||||||
mConnectivityManager = (ConnectivityManager) service.getSystemService(
|
mConnectivityManager = (ConnectivityManager) service.getSystemService(
|
||||||
|
@ -111,39 +110,46 @@ public class SubtypeSwitcher {
|
||||||
|
|
||||||
// Update all parameters stored in SubtypeSwitcher.
|
// Update all parameters stored in SubtypeSwitcher.
|
||||||
// Only configuration changed event is allowed to call this because this is heavy.
|
// Only configuration changed event is allowed to call this because this is heavy.
|
||||||
private void updateAllParameters() {
|
private void updateAllParameters(final Context context) {
|
||||||
mCurrentSystemLocale = mResources.getConfiguration().locale;
|
mCurrentSystemLocale = mResources.getConfiguration().locale;
|
||||||
updateSubtype(ImfUtils.getCurrentInputMethodSubtype(mService, mNoLanguageSubtype));
|
updateSubtype(ImfUtils.getCurrentInputMethodSubtype(context, mNoLanguageSubtype));
|
||||||
updateParametersOnStartInputView();
|
updateParametersOnStartInputViewAndReturnIfCurrentSubtypeEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update parameters which are changed outside LatinIME. This parameters affect UI so they
|
/**
|
||||||
// should be updated every time onStartInputview.
|
* Update parameters which are changed outside LatinIME. This parameters affect UI so they
|
||||||
public void updateParametersOnStartInputView() {
|
* should be updated every time onStartInputView.
|
||||||
updateEnabledSubtypes();
|
*
|
||||||
|
* @return true if the current subtype is enabled.
|
||||||
|
*/
|
||||||
|
public boolean updateParametersOnStartInputViewAndReturnIfCurrentSubtypeEnabled() {
|
||||||
|
final boolean currentSubtypeEnabled =
|
||||||
|
updateEnabledSubtypesAndReturnIfEnabled(mCurrentSubtype);
|
||||||
updateShortcutIME();
|
updateShortcutIME();
|
||||||
|
return currentSubtypeEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload enabledSubtypes from the framework.
|
/**
|
||||||
private void updateEnabledSubtypes() {
|
* Update enabled subtypes from the framework.
|
||||||
final InputMethodSubtype currentSubtype = mCurrentSubtype;
|
*
|
||||||
boolean foundCurrentSubtypeBecameDisabled = true;
|
* @param subtype the subtype to be checked
|
||||||
|
* @return true if the {@code subtype} is enabled.
|
||||||
|
*/
|
||||||
|
private boolean updateEnabledSubtypesAndReturnIfEnabled(final InputMethodSubtype subtype) {
|
||||||
final List<InputMethodSubtype> enabledSubtypesOfThisIme =
|
final List<InputMethodSubtype> enabledSubtypesOfThisIme =
|
||||||
mImm.getEnabledInputMethodSubtypeList(null, true);
|
mImm.getEnabledInputMethodSubtypeList(null, true);
|
||||||
for (InputMethodSubtype ims : enabledSubtypesOfThisIme) {
|
|
||||||
if (ims.equals(currentSubtype)) {
|
|
||||||
foundCurrentSubtypeBecameDisabled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mNeedsToDisplayLanguage.updateEnabledSubtypeCount(enabledSubtypesOfThisIme.size());
|
mNeedsToDisplayLanguage.updateEnabledSubtypeCount(enabledSubtypesOfThisIme.size());
|
||||||
if (foundCurrentSubtypeBecameDisabled) {
|
|
||||||
|
for (final InputMethodSubtype ims : enabledSubtypesOfThisIme) {
|
||||||
|
if (ims.equals(subtype)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (DBG) {
|
if (DBG) {
|
||||||
Log.w(TAG, "Last subtype: "
|
Log.w(TAG, "Subtype: " + subtype.getLocale() + "/" + subtype.getExtraValue()
|
||||||
+ currentSubtype.getLocale() + "/" + currentSubtype.getExtraValue());
|
+ " was disabled");
|
||||||
Log.w(TAG, "Last subtype was disabled. Update to the current one.");
|
|
||||||
}
|
|
||||||
updateSubtype(ImfUtils.getCurrentInputMethodSubtype(mService, mNoLanguageSubtype));
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateShortcutIME() {
|
private void updateShortcutIME() {
|
||||||
|
@ -159,8 +165,8 @@ public class SubtypeSwitcher {
|
||||||
mImm.getShortcutInputMethodsAndSubtypes();
|
mImm.getShortcutInputMethodsAndSubtypes();
|
||||||
mShortcutInputMethodInfo = null;
|
mShortcutInputMethodInfo = null;
|
||||||
mShortcutSubtype = null;
|
mShortcutSubtype = null;
|
||||||
for (InputMethodInfo imi : shortcuts.keySet()) {
|
for (final InputMethodInfo imi : shortcuts.keySet()) {
|
||||||
List<InputMethodSubtype> subtypes = shortcuts.get(imi);
|
final List<InputMethodSubtype> subtypes = shortcuts.get(imi);
|
||||||
// TODO: Returns the first found IMI for now. Should handle all shortcuts as
|
// TODO: Returns the first found IMI for now. Should handle all shortcuts as
|
||||||
// appropriate.
|
// appropriate.
|
||||||
mShortcutInputMethodInfo = imi;
|
mShortcutInputMethodInfo = imi;
|
||||||
|
@ -194,24 +200,24 @@ public class SubtypeSwitcher {
|
||||||
|
|
||||||
mCurrentSubtype = newSubtype;
|
mCurrentSubtype = newSubtype;
|
||||||
updateShortcutIME();
|
updateShortcutIME();
|
||||||
mService.onRefreshKeyboard();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////
|
////////////////////////////
|
||||||
// Shortcut IME functions //
|
// Shortcut IME functions //
|
||||||
////////////////////////////
|
////////////////////////////
|
||||||
|
|
||||||
public void switchToShortcutIME() {
|
public void switchToShortcutIME(final InputMethodService context) {
|
||||||
if (mShortcutInputMethodInfo == null) {
|
if (mShortcutInputMethodInfo == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final String imiId = mShortcutInputMethodInfo.getId();
|
final String imiId = mShortcutInputMethodInfo.getId();
|
||||||
switchToTargetIME(imiId, mShortcutSubtype);
|
switchToTargetIME(imiId, mShortcutSubtype, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void switchToTargetIME(final String imiId, final InputMethodSubtype subtype) {
|
private void switchToTargetIME(final String imiId, final InputMethodSubtype subtype,
|
||||||
final IBinder token = mService.getWindow().getWindow().getAttributes().token;
|
final InputMethodService context) {
|
||||||
|
final IBinder token = context.getWindow().getWindow().getAttributes().token;
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -253,7 +259,7 @@ public class SubtypeSwitcher {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onNetworkStateChanged(Intent intent) {
|
public void onNetworkStateChanged(final Intent intent) {
|
||||||
final boolean noConnection = intent.getBooleanExtra(
|
final boolean noConnection = intent.getBooleanExtra(
|
||||||
ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
|
ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
|
||||||
mIsNetworkConnected = !noConnection;
|
mIsNetworkConnected = !noConnection;
|
||||||
|
@ -265,7 +271,7 @@ public class SubtypeSwitcher {
|
||||||
// Subtype Switching functions //
|
// Subtype Switching functions //
|
||||||
//////////////////////////////////
|
//////////////////////////////////
|
||||||
|
|
||||||
public boolean needsToDisplayLanguage(Locale keyboardLocale) {
|
public boolean needsToDisplayLanguage(final Locale keyboardLocale) {
|
||||||
if (keyboardLocale.toString().equals(SubtypeLocale.NO_LANGUAGE)) {
|
if (keyboardLocale.toString().equals(SubtypeLocale.NO_LANGUAGE)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -279,12 +285,14 @@ public class SubtypeSwitcher {
|
||||||
return SubtypeLocale.getSubtypeLocale(mCurrentSubtype);
|
return SubtypeLocale.getSubtypeLocale(mCurrentSubtype);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onConfigurationChanged(Configuration conf) {
|
public boolean onConfigurationChanged(final Configuration conf, final Context context) {
|
||||||
final Locale systemLocale = conf.locale;
|
final Locale systemLocale = conf.locale;
|
||||||
|
final boolean systemLocaleChanged = !systemLocale.equals(mCurrentSystemLocale);
|
||||||
// If system configuration was changed, update all parameters.
|
// If system configuration was changed, update all parameters.
|
||||||
if (!systemLocale.equals(mCurrentSystemLocale)) {
|
if (systemLocaleChanged) {
|
||||||
updateAllParameters();
|
updateAllParameters(context);
|
||||||
}
|
}
|
||||||
|
return systemLocaleChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
public InputMethodSubtype getCurrentSubtype() {
|
public InputMethodSubtype getCurrentSubtype() {
|
||||||
|
|
|
@ -50,9 +50,8 @@ public class Suggest {
|
||||||
|
|
||||||
private Dictionary mMainDictionary;
|
private Dictionary mMainDictionary;
|
||||||
private ContactsBinaryDictionary mContactsDict;
|
private ContactsBinaryDictionary mContactsDict;
|
||||||
private WhitelistDictionary mWhiteListDictionary;
|
|
||||||
private final ConcurrentHashMap<String, Dictionary> mDictionaries =
|
private final ConcurrentHashMap<String, Dictionary> mDictionaries =
|
||||||
new ConcurrentHashMap<String, Dictionary>();
|
CollectionUtils.newConcurrentHashMap();
|
||||||
|
|
||||||
public static final int MAX_SUGGESTIONS = 18;
|
public static final int MAX_SUGGESTIONS = 18;
|
||||||
|
|
||||||
|
@ -60,13 +59,11 @@ public class Suggest {
|
||||||
|
|
||||||
// Locale used for upper- and title-casing words
|
// Locale used for upper- and title-casing words
|
||||||
private final Locale mLocale;
|
private final Locale mLocale;
|
||||||
private final SuggestInitializationListener mListener;
|
|
||||||
|
|
||||||
public Suggest(final Context context, final Locale locale,
|
public Suggest(final Context context, final Locale locale,
|
||||||
final SuggestInitializationListener listener) {
|
final SuggestInitializationListener listener) {
|
||||||
initAsynchronously(context, locale);
|
initAsynchronously(context, locale, listener);
|
||||||
mLocale = locale;
|
mLocale = locale;
|
||||||
mListener = listener;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package for test */ Suggest(final Context context, final File dictionary,
|
/* package for test */ Suggest(final Context context, final File dictionary,
|
||||||
|
@ -74,23 +71,13 @@ public class Suggest {
|
||||||
final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(context, dictionary,
|
final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(context, dictionary,
|
||||||
startOffset, length /* useFullEditDistance */, false, locale);
|
startOffset, length /* useFullEditDistance */, false, locale);
|
||||||
mLocale = locale;
|
mLocale = locale;
|
||||||
mListener = null;
|
|
||||||
mMainDictionary = mainDict;
|
mMainDictionary = mainDict;
|
||||||
addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, mainDict);
|
addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, mainDict);
|
||||||
initWhitelistAndAutocorrectAndPool(context, locale);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initWhitelistAndAutocorrectAndPool(final Context context, final Locale locale) {
|
private void initAsynchronously(final Context context, final Locale locale,
|
||||||
mWhiteListDictionary = new WhitelistDictionary(context, locale);
|
final SuggestInitializationListener listener) {
|
||||||
addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_WHITELIST, mWhiteListDictionary);
|
resetMainDict(context, locale, listener);
|
||||||
}
|
|
||||||
|
|
||||||
private void initAsynchronously(final Context context, final Locale locale) {
|
|
||||||
resetMainDict(context, locale);
|
|
||||||
|
|
||||||
// TODO: read the whitelist and init the pool asynchronously too.
|
|
||||||
// initPool should be done asynchronously now that the pool is thread-safe.
|
|
||||||
initWhitelistAndAutocorrectAndPool(context, locale);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addOrReplaceDictionary(
|
private static void addOrReplaceDictionary(
|
||||||
|
@ -104,10 +91,11 @@ public class Suggest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resetMainDict(final Context context, final Locale locale) {
|
public void resetMainDict(final Context context, final Locale locale,
|
||||||
|
final SuggestInitializationListener listener) {
|
||||||
mMainDictionary = null;
|
mMainDictionary = null;
|
||||||
if (mListener != null) {
|
if (listener != null) {
|
||||||
mListener.onUpdateMainDictionaryAvailability(hasMainDictionary());
|
listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
|
||||||
}
|
}
|
||||||
new Thread("InitializeBinaryDictionary") {
|
new Thread("InitializeBinaryDictionary") {
|
||||||
@Override
|
@Override
|
||||||
|
@ -116,8 +104,8 @@ public class Suggest {
|
||||||
DictionaryFactory.createMainDictionaryFromManager(context, locale);
|
DictionaryFactory.createMainDictionaryFromManager(context, locale);
|
||||||
addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, newMainDict);
|
addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, newMainDict);
|
||||||
mMainDictionary = newMainDict;
|
mMainDictionary = newMainDict;
|
||||||
if (mListener != null) {
|
if (listener != null) {
|
||||||
mListener.onUpdateMainDictionaryAvailability(hasMainDictionary());
|
listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.start();
|
}.start();
|
||||||
|
@ -170,9 +158,17 @@ public class Suggest {
|
||||||
public SuggestedWords getSuggestedWords(
|
public SuggestedWords getSuggestedWords(
|
||||||
final WordComposer wordComposer, CharSequence prevWordForBigram,
|
final WordComposer wordComposer, CharSequence prevWordForBigram,
|
||||||
final ProximityInfo proximityInfo, final boolean isCorrectionEnabled) {
|
final ProximityInfo proximityInfo, final boolean isCorrectionEnabled) {
|
||||||
|
return getSuggestedWordsWithSessionId(
|
||||||
|
wordComposer, prevWordForBigram, proximityInfo, isCorrectionEnabled, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SuggestedWords getSuggestedWordsWithSessionId(
|
||||||
|
final WordComposer wordComposer, CharSequence prevWordForBigram,
|
||||||
|
final ProximityInfo proximityInfo, final boolean isCorrectionEnabled, int sessionId) {
|
||||||
LatinImeLogger.onStartSuggestion(prevWordForBigram);
|
LatinImeLogger.onStartSuggestion(prevWordForBigram);
|
||||||
if (wordComposer.isBatchMode()) {
|
if (wordComposer.isBatchMode()) {
|
||||||
return getSuggestedWordsForBatchInput(wordComposer, prevWordForBigram, proximityInfo);
|
return getSuggestedWordsForBatchInput(
|
||||||
|
wordComposer, prevWordForBigram, proximityInfo, sessionId);
|
||||||
} else {
|
} else {
|
||||||
return getSuggestedWordsForTypingInput(wordComposer, prevWordForBigram, proximityInfo,
|
return getSuggestedWordsForTypingInput(wordComposer, prevWordForBigram, proximityInfo,
|
||||||
isCorrectionEnabled);
|
isCorrectionEnabled);
|
||||||
|
@ -209,23 +205,20 @@ public class Suggest {
|
||||||
wordComposerForLookup, prevWordForBigram, proximityInfo));
|
wordComposerForLookup, prevWordForBigram, proximityInfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Change this scheme - a boolean is not enough. A whitelisted word may be "valid"
|
final CharSequence whitelistedWord;
|
||||||
// but still autocorrected from - in the case the whitelist only capitalizes the word.
|
if (suggestionsSet.isEmpty()) {
|
||||||
// The whitelist should be case-insensitive, so it's not possible to be consistent with
|
whitelistedWord = null;
|
||||||
// a boolean flag. Right now this is handled with a slight hack in
|
} else if (SuggestedWordInfo.KIND_WHITELIST != suggestionsSet.first().mKind) {
|
||||||
// WhitelistDictionary#shouldForciblyAutoCorrectFrom.
|
whitelistedWord = null;
|
||||||
final boolean allowsToBeAutoCorrected = AutoCorrection.isWhitelistedOrNotAWord(
|
} else {
|
||||||
mDictionaries, consideredWord, wordComposer.isFirstCharCapitalized());
|
whitelistedWord = suggestionsSet.first().mWord;
|
||||||
|
|
||||||
final CharSequence whitelistedWord =
|
|
||||||
mWhiteListDictionary.getWhitelistedWord(consideredWord);
|
|
||||||
if (whitelistedWord != null) {
|
|
||||||
// MAX_SCORE ensures this will be considered strong enough to be auto-corrected
|
|
||||||
suggestionsSet.add(new SuggestedWordInfo(whitelistedWord,
|
|
||||||
SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_WHITELIST,
|
|
||||||
Dictionary.TYPE_WHITELIST));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final boolean allowsToBeAutoCorrected = (null != whitelistedWord
|
||||||
|
&& !whitelistedWord.equals(consideredWord))
|
||||||
|
|| AutoCorrection.isNotAWord(mDictionaries, consideredWord,
|
||||||
|
wordComposer.isFirstCharCapitalized());
|
||||||
|
|
||||||
final boolean hasAutoCorrection;
|
final boolean hasAutoCorrection;
|
||||||
// TODO: using isCorrectionEnabled here is not very good. It's probably useless, because
|
// TODO: using isCorrectionEnabled here is not very good. It's probably useless, because
|
||||||
// any attempt to do auto-correction is already shielded with a test for this flag; at the
|
// any attempt to do auto-correction is already shielded with a test for this flag; at the
|
||||||
|
@ -249,7 +242,7 @@ public class Suggest {
|
||||||
}
|
}
|
||||||
|
|
||||||
final ArrayList<SuggestedWordInfo> suggestionsContainer =
|
final ArrayList<SuggestedWordInfo> suggestionsContainer =
|
||||||
new ArrayList<SuggestedWordInfo>(suggestionsSet);
|
CollectionUtils.newArrayList(suggestionsSet);
|
||||||
final int suggestionsCount = suggestionsContainer.size();
|
final int suggestionsCount = suggestionsContainer.size();
|
||||||
final boolean isFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
|
final boolean isFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
|
||||||
final boolean isAllUpperCase = wordComposer.isAllUpperCase();
|
final boolean isAllUpperCase = wordComposer.isAllUpperCase();
|
||||||
|
@ -296,29 +289,28 @@ public class Suggest {
|
||||||
// Retrieves suggestions for the batch input.
|
// Retrieves suggestions for the batch input.
|
||||||
private SuggestedWords getSuggestedWordsForBatchInput(
|
private SuggestedWords getSuggestedWordsForBatchInput(
|
||||||
final WordComposer wordComposer, CharSequence prevWordForBigram,
|
final WordComposer wordComposer, CharSequence prevWordForBigram,
|
||||||
final ProximityInfo proximityInfo) {
|
final ProximityInfo proximityInfo, int sessionId) {
|
||||||
final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator,
|
final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator,
|
||||||
MAX_SUGGESTIONS);
|
MAX_SUGGESTIONS);
|
||||||
|
|
||||||
// At second character typed, search the unigrams (scores being affected by bigrams)
|
// At second character typed, search the unigrams (scores being affected by bigrams)
|
||||||
for (final String key : mDictionaries.keySet()) {
|
for (final String key : mDictionaries.keySet()) {
|
||||||
// Skip UserUnigramDictionary and WhitelistDictionary to lookup
|
// Skip User history dictionary for lookup
|
||||||
if (key.equals(Dictionary.TYPE_USER_HISTORY)
|
// TODO: The user history dictionary should just override getSuggestionsWithSessionId
|
||||||
|| key.equals(Dictionary.TYPE_WHITELIST)) {
|
// to make sure it doesn't return anything and we should remove this test
|
||||||
|
if (key.equals(Dictionary.TYPE_USER_HISTORY)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final Dictionary dictionary = mDictionaries.get(key);
|
final Dictionary dictionary = mDictionaries.get(key);
|
||||||
suggestionsSet.addAll(dictionary.getSuggestions(
|
suggestionsSet.addAll(dictionary.getSuggestionsWithSessionId(
|
||||||
wordComposer, prevWordForBigram, proximityInfo));
|
wordComposer, prevWordForBigram, proximityInfo, sessionId));
|
||||||
}
|
}
|
||||||
|
|
||||||
final ArrayList<SuggestedWordInfo> suggestionsContainer =
|
final ArrayList<SuggestedWordInfo> suggestionsContainer =
|
||||||
new ArrayList<SuggestedWordInfo>(suggestionsSet);
|
CollectionUtils.newArrayList(suggestionsSet);
|
||||||
final int suggestionsCount = suggestionsContainer.size();
|
final int suggestionsCount = suggestionsContainer.size();
|
||||||
final boolean isFirstCharCapitalized = wordComposer.isAutoCapitalized();
|
final boolean isFirstCharCapitalized = wordComposer.wasShiftedNoLock();
|
||||||
// TODO: Handle the manual temporary shifted mode.
|
final boolean isAllUpperCase = wordComposer.isAllUpperCase();
|
||||||
// TODO: Should handle TextUtils.CAP_MODE_CHARACTER.
|
|
||||||
final boolean isAllUpperCase = false;
|
|
||||||
if (isFirstCharCapitalized || isAllUpperCase) {
|
if (isFirstCharCapitalized || isAllUpperCase) {
|
||||||
for (int i = 0; i < suggestionsCount; ++i) {
|
for (int i = 0; i < suggestionsCount; ++i) {
|
||||||
final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
|
final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
|
||||||
|
@ -346,7 +338,7 @@ public class Suggest {
|
||||||
typedWordInfo.setDebugString("+");
|
typedWordInfo.setDebugString("+");
|
||||||
final int suggestionsSize = suggestions.size();
|
final int suggestionsSize = suggestions.size();
|
||||||
final ArrayList<SuggestedWordInfo> suggestionsList =
|
final ArrayList<SuggestedWordInfo> suggestionsList =
|
||||||
new ArrayList<SuggestedWordInfo>(suggestionsSize);
|
CollectionUtils.newArrayList(suggestionsSize);
|
||||||
suggestionsList.add(typedWordInfo);
|
suggestionsList.add(typedWordInfo);
|
||||||
// Note: i here is the index in mScores[], but the index in mSuggestions is one more
|
// Note: i here is the index in mScores[], but the index in mSuggestions is one more
|
||||||
// than i because we added the typed word to mSuggestions without touching mScores.
|
// than i because we added the typed word to mSuggestions without touching mScores.
|
||||||
|
@ -399,7 +391,7 @@ public class Suggest {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void close() {
|
public void close() {
|
||||||
final HashSet<Dictionary> dictionaries = new HashSet<Dictionary>();
|
final HashSet<Dictionary> dictionaries = CollectionUtils.newHashSet();
|
||||||
dictionaries.addAll(mDictionaries.values());
|
dictionaries.addAll(mDictionaries.values());
|
||||||
for (final Dictionary dictionary : dictionaries) {
|
for (final Dictionary dictionary : dictionaries) {
|
||||||
dictionary.close();
|
dictionary.close();
|
||||||
|
|
|
@ -24,8 +24,10 @@ import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
|
||||||
public class SuggestedWords {
|
public class SuggestedWords {
|
||||||
|
private static final ArrayList<SuggestedWordInfo> EMPTY_WORD_INFO_LIST =
|
||||||
|
CollectionUtils.newArrayList(0);
|
||||||
public static final SuggestedWords EMPTY = new SuggestedWords(
|
public static final SuggestedWords EMPTY = new SuggestedWords(
|
||||||
new ArrayList<SuggestedWordInfo>(0), false, false, false, false, false);
|
EMPTY_WORD_INFO_LIST, false, false, false, false, false);
|
||||||
|
|
||||||
public final boolean mTypedWordValid;
|
public final boolean mTypedWordValid;
|
||||||
// Note: this INCLUDES cases where the word will auto-correct to itself. A good definition
|
// Note: this INCLUDES cases where the word will auto-correct to itself. A good definition
|
||||||
|
@ -83,7 +85,7 @@ public class SuggestedWords {
|
||||||
|
|
||||||
public static ArrayList<SuggestedWordInfo> getFromApplicationSpecifiedCompletions(
|
public static ArrayList<SuggestedWordInfo> getFromApplicationSpecifiedCompletions(
|
||||||
final CompletionInfo[] infos) {
|
final CompletionInfo[] infos) {
|
||||||
final ArrayList<SuggestedWordInfo> result = new ArrayList<SuggestedWordInfo>();
|
final ArrayList<SuggestedWordInfo> result = CollectionUtils.newArrayList();
|
||||||
for (CompletionInfo info : infos) {
|
for (CompletionInfo info : infos) {
|
||||||
if (null != info && info.getText() != null) {
|
if (null != info && info.getText() != null) {
|
||||||
result.add(new SuggestedWordInfo(info.getText(), SuggestedWordInfo.MAX_SCORE,
|
result.add(new SuggestedWordInfo(info.getText(), SuggestedWordInfo.MAX_SCORE,
|
||||||
|
@ -97,8 +99,8 @@ public class SuggestedWords {
|
||||||
// and replace it with what the user currently typed.
|
// and replace it with what the user currently typed.
|
||||||
public static ArrayList<SuggestedWordInfo> getTypedWordAndPreviousSuggestions(
|
public static ArrayList<SuggestedWordInfo> getTypedWordAndPreviousSuggestions(
|
||||||
final CharSequence typedWord, final SuggestedWords previousSuggestions) {
|
final CharSequence typedWord, final SuggestedWords previousSuggestions) {
|
||||||
final ArrayList<SuggestedWordInfo> suggestionsList = new ArrayList<SuggestedWordInfo>();
|
final ArrayList<SuggestedWordInfo> suggestionsList = CollectionUtils.newArrayList();
|
||||||
final HashSet<String> alreadySeen = new HashSet<String>();
|
final HashSet<String> alreadySeen = CollectionUtils.newHashSet();
|
||||||
suggestionsList.add(new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE,
|
suggestionsList.add(new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE,
|
||||||
SuggestedWordInfo.KIND_TYPED, Dictionary.TYPE_USER_TYPED));
|
SuggestedWordInfo.KIND_TYPED, Dictionary.TYPE_USER_TYPED));
|
||||||
alreadySeen.add(typedWord.toString());
|
alreadySeen.add(typedWord.toString());
|
||||||
|
|
|
@ -52,14 +52,14 @@ public class UserHistoryDictionary extends ExpandableDictionary {
|
||||||
private static final int FREQUENCY_FOR_TYPED = 2;
|
private static final int FREQUENCY_FOR_TYPED = 2;
|
||||||
|
|
||||||
/** Maximum number of pairs. Pruning will start when databases goes above this number. */
|
/** Maximum number of pairs. Pruning will start when databases goes above this number. */
|
||||||
private static int sMaxHistoryBigrams = 10000;
|
public static final int sMaxHistoryBigrams = 10000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When it hits maximum bigram pair, it will delete until you are left with
|
* When it hits maximum bigram pair, it will delete until you are left with
|
||||||
* only (sMaxHistoryBigrams - sDeleteHistoryBigrams) pairs.
|
* only (sMaxHistoryBigrams - sDeleteHistoryBigrams) pairs.
|
||||||
* Do not keep this number small to avoid deleting too often.
|
* Do not keep this number small to avoid deleting too often.
|
||||||
*/
|
*/
|
||||||
private static int sDeleteHistoryBigrams = 1000;
|
public static final int sDeleteHistoryBigrams = 1000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Database version should increase if the database structure changes
|
* Database version should increase if the database structure changes
|
||||||
|
@ -93,10 +93,10 @@ public class UserHistoryDictionary extends ExpandableDictionary {
|
||||||
|
|
||||||
private final static HashMap<String, String> sDictProjectionMap;
|
private final static HashMap<String, String> sDictProjectionMap;
|
||||||
private final static ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>
|
private final static ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>
|
||||||
sLangDictCache = new ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>();
|
sLangDictCache = CollectionUtils.newConcurrentHashMap();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
sDictProjectionMap = new HashMap<String, String>();
|
sDictProjectionMap = CollectionUtils.newHashMap();
|
||||||
sDictProjectionMap.put(MAIN_COLUMN_ID, MAIN_COLUMN_ID);
|
sDictProjectionMap.put(MAIN_COLUMN_ID, MAIN_COLUMN_ID);
|
||||||
sDictProjectionMap.put(MAIN_COLUMN_WORD1, MAIN_COLUMN_WORD1);
|
sDictProjectionMap.put(MAIN_COLUMN_WORD1, MAIN_COLUMN_WORD1);
|
||||||
sDictProjectionMap.put(MAIN_COLUMN_WORD2, MAIN_COLUMN_WORD2);
|
sDictProjectionMap.put(MAIN_COLUMN_WORD2, MAIN_COLUMN_WORD2);
|
||||||
|
@ -109,12 +109,8 @@ public class UserHistoryDictionary extends ExpandableDictionary {
|
||||||
|
|
||||||
private static DatabaseHelper sOpenHelper = null;
|
private static DatabaseHelper sOpenHelper = null;
|
||||||
|
|
||||||
public void setDatabaseMax(int maxHistoryBigram) {
|
public String getLocale() {
|
||||||
sMaxHistoryBigrams = maxHistoryBigram;
|
return mLocale;
|
||||||
}
|
|
||||||
|
|
||||||
public void setDatabaseDelete(int deleteHistoryBigram) {
|
|
||||||
sDeleteHistoryBigrams = deleteHistoryBigram;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized static UserHistoryDictionary getInstance(
|
public synchronized static UserHistoryDictionary getInstance(
|
||||||
|
@ -502,9 +498,11 @@ public class UserHistoryDictionary extends ExpandableDictionary {
|
||||||
needsToSave(fc, isValid, addLevel0Bigram)) {
|
needsToSave(fc, isValid, addLevel0Bigram)) {
|
||||||
freq = fc;
|
freq = fc;
|
||||||
} else {
|
} else {
|
||||||
|
// Delete this entry
|
||||||
freq = -1;
|
freq = -1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Delete this entry
|
||||||
freq = -1;
|
freq = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -541,6 +539,7 @@ public class UserHistoryDictionary extends ExpandableDictionary {
|
||||||
getContentValues(word1, word2, mLocale));
|
getContentValues(word1, word2, mLocale));
|
||||||
pairId = pairIdLong.intValue();
|
pairId = pairIdLong.intValue();
|
||||||
}
|
}
|
||||||
|
// Eliminate freq == 0 because that word is profanity.
|
||||||
if (freq > 0) {
|
if (freq > 0) {
|
||||||
if (PROFILE_SAVE_RESTORE) {
|
if (PROFILE_SAVE_RESTORE) {
|
||||||
++profInsert;
|
++profInsert;
|
||||||
|
|
|
@ -29,9 +29,8 @@ import java.util.Set;
|
||||||
public class UserHistoryDictionaryBigramList {
|
public class UserHistoryDictionaryBigramList {
|
||||||
public static final byte FORGETTING_CURVE_INITIAL_VALUE = 0;
|
public static final byte FORGETTING_CURVE_INITIAL_VALUE = 0;
|
||||||
private static final String TAG = UserHistoryDictionaryBigramList.class.getSimpleName();
|
private static final String TAG = UserHistoryDictionaryBigramList.class.getSimpleName();
|
||||||
private static final HashMap<String, Byte> EMPTY_BIGRAM_MAP = new HashMap<String, Byte>();
|
private static final HashMap<String, Byte> EMPTY_BIGRAM_MAP = CollectionUtils.newHashMap();
|
||||||
private final HashMap<String, HashMap<String, Byte>> mBigramMap =
|
private final HashMap<String, HashMap<String, Byte>> mBigramMap = CollectionUtils.newHashMap();
|
||||||
new HashMap<String, HashMap<String, Byte>>();
|
|
||||||
private int mSize = 0;
|
private int mSize = 0;
|
||||||
|
|
||||||
public void evictAll() {
|
public void evictAll() {
|
||||||
|
@ -57,7 +56,7 @@ public class UserHistoryDictionaryBigramList {
|
||||||
if (mBigramMap.containsKey(word1)) {
|
if (mBigramMap.containsKey(word1)) {
|
||||||
map = mBigramMap.get(word1);
|
map = mBigramMap.get(word1);
|
||||||
} else {
|
} else {
|
||||||
map = new HashMap<String, Byte>();
|
map = CollectionUtils.newHashMap();
|
||||||
mBigramMap.put(word1, map);
|
mBigramMap.put(word1, map);
|
||||||
}
|
}
|
||||||
if (!map.containsKey(word2)) {
|
if (!map.containsKey(word2)) {
|
||||||
|
|
|
@ -212,7 +212,7 @@ public class UserHistoryForgettingCurveUtils {
|
||||||
for (int j = 0; j < ELAPSED_TIME_MAX; ++j) {
|
for (int j = 0; j < ELAPSED_TIME_MAX; ++j) {
|
||||||
final float elapsedHours = j * ELAPSED_TIME_INTERVAL_HOURS;
|
final float elapsedHours = j * ELAPSED_TIME_INTERVAL_HOURS;
|
||||||
final float freq = initialFreq
|
final float freq = initialFreq
|
||||||
* NativeUtils.powf(initialFreq, elapsedHours / HALF_LIFE_HOURS);
|
* (float)Math.pow(initialFreq, elapsedHours / HALF_LIFE_HOURS);
|
||||||
final int intFreq = Math.min(FC_FREQ_MAX, Math.max(0, (int)freq));
|
final int intFreq = Math.min(FC_FREQ_MAX, Math.max(0, (int)freq));
|
||||||
SCORE_TABLE[i][j] = intFreq;
|
SCORE_TABLE[i][j] = intFreq;
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,44 +65,6 @@ public class Utils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class GCUtils {
|
|
||||||
private static final String GC_TAG = GCUtils.class.getSimpleName();
|
|
||||||
public static final int GC_TRY_COUNT = 2;
|
|
||||||
// GC_TRY_LOOP_MAX is used for the hard limit of GC wait,
|
|
||||||
// GC_TRY_LOOP_MAX should be greater than GC_TRY_COUNT.
|
|
||||||
public static final int GC_TRY_LOOP_MAX = 5;
|
|
||||||
private static final long GC_INTERVAL = DateUtils.SECOND_IN_MILLIS;
|
|
||||||
private static GCUtils sInstance = new GCUtils();
|
|
||||||
private int mGCTryCount = 0;
|
|
||||||
|
|
||||||
public static GCUtils getInstance() {
|
|
||||||
return sInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reset() {
|
|
||||||
mGCTryCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean tryGCOrWait(String metaData, Throwable t) {
|
|
||||||
if (mGCTryCount == 0) {
|
|
||||||
System.gc();
|
|
||||||
}
|
|
||||||
if (++mGCTryCount > GC_TRY_COUNT) {
|
|
||||||
LatinImeLogger.logOnException(metaData, t);
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
Thread.sleep(GC_INTERVAL);
|
|
||||||
return true;
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Log.e(GC_TAG, "Sleep was interrupted.");
|
|
||||||
LatinImeLogger.logOnException(metaData, t);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* package */ static class RingCharBuffer {
|
/* package */ static class RingCharBuffer {
|
||||||
private static RingCharBuffer sRingCharBuffer = new RingCharBuffer();
|
private static RingCharBuffer sRingCharBuffer = new RingCharBuffer();
|
||||||
private static final char PLACEHOLDER_DELIMITER_CHAR = '\uFFFC';
|
private static final char PLACEHOLDER_DELIMITER_CHAR = '\uFFFC';
|
||||||
|
@ -477,7 +439,7 @@ public class Utils {
|
||||||
|
|
||||||
private static final String HARDWARE_PREFIX = Build.HARDWARE + ",";
|
private static final String HARDWARE_PREFIX = Build.HARDWARE + ",";
|
||||||
private static final HashMap<String, String> sDeviceOverrideValueMap =
|
private static final HashMap<String, String> sDeviceOverrideValueMap =
|
||||||
new HashMap<String, String>();
|
CollectionUtils.newHashMap();
|
||||||
|
|
||||||
public static String getDeviceOverrideValue(Resources res, int overrideResId, String defValue) {
|
public static String getDeviceOverrideValue(Resources res, int overrideResId, String defValue) {
|
||||||
final int orientation = res.getConfiguration().orientation;
|
final int orientation = res.getConfiguration().orientation;
|
||||||
|
@ -495,7 +457,7 @@ public class Utils {
|
||||||
return sDeviceOverrideValueMap.get(key);
|
return sDeviceOverrideValueMap.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final HashMap<String, Long> EMPTY_LT_HASH_MAP = new HashMap<String, Long>();
|
private static final HashMap<String, Long> EMPTY_LT_HASH_MAP = CollectionUtils.newHashMap();
|
||||||
private static final String LOCALE_AND_TIME_STR_SEPARATER = ",";
|
private static final String LOCALE_AND_TIME_STR_SEPARATER = ",";
|
||||||
public static HashMap<String, Long> localeAndTimeStrToHashMap(String str) {
|
public static HashMap<String, Long> localeAndTimeStrToHashMap(String str) {
|
||||||
if (TextUtils.isEmpty(str)) {
|
if (TextUtils.isEmpty(str)) {
|
||||||
|
@ -506,7 +468,7 @@ public class Utils {
|
||||||
if (N < 2 || N % 2 != 0) {
|
if (N < 2 || N % 2 != 0) {
|
||||||
return EMPTY_LT_HASH_MAP;
|
return EMPTY_LT_HASH_MAP;
|
||||||
}
|
}
|
||||||
final HashMap<String, Long> retval = new HashMap<String, Long>();
|
final HashMap<String, Long> retval = CollectionUtils.newHashMap();
|
||||||
for (int i = 0; i < N / 2; ++i) {
|
for (int i = 0; i < N / 2; ++i) {
|
||||||
final String localeStr = ss[i * 2];
|
final String localeStr = ss[i * 2];
|
||||||
final long time = Long.valueOf(ss[i * 2 + 1]);
|
final long time = Long.valueOf(ss[i * 2 + 1]);
|
||||||
|
|
|
@ -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.
|
|
||||||
}
|
|
|
@ -17,7 +17,6 @@
|
||||||
package com.android.inputmethod.latin;
|
package com.android.inputmethod.latin;
|
||||||
|
|
||||||
import com.android.inputmethod.keyboard.Key;
|
import com.android.inputmethod.keyboard.Key;
|
||||||
import com.android.inputmethod.keyboard.KeyDetector;
|
|
||||||
import com.android.inputmethod.keyboard.Keyboard;
|
import com.android.inputmethod.keyboard.Keyboard;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -26,12 +25,16 @@ import java.util.Arrays;
|
||||||
* A place to store the currently composing word with information such as adjacent key codes as well
|
* A place to store the currently composing word with information such as adjacent key codes as well
|
||||||
*/
|
*/
|
||||||
public class WordComposer {
|
public class WordComposer {
|
||||||
|
|
||||||
public static final int NOT_A_CODE = KeyDetector.NOT_A_CODE;
|
|
||||||
public static final int NOT_A_COORDINATE = -1;
|
|
||||||
|
|
||||||
private static final int N = BinaryDictionary.MAX_WORD_LENGTH;
|
private static final int N = BinaryDictionary.MAX_WORD_LENGTH;
|
||||||
|
|
||||||
|
public static final int CAPS_MODE_OFF = 0;
|
||||||
|
// 1 is shift bit, 2 is caps bit, 4 is auto bit but this is just a convention as these bits
|
||||||
|
// aren't used anywhere in the code
|
||||||
|
public static final int CAPS_MODE_MANUAL_SHIFTED = 0x1;
|
||||||
|
public static final int CAPS_MODE_MANUAL_SHIFT_LOCKED = 0x3;
|
||||||
|
public static final int CAPS_MODE_AUTO_SHIFTED = 0x5;
|
||||||
|
public static final int CAPS_MODE_AUTO_SHIFT_LOCKED = 0x7;
|
||||||
|
|
||||||
private int[] mPrimaryKeyCodes;
|
private int[] mPrimaryKeyCodes;
|
||||||
private final InputPointers mInputPointers = new InputPointers(N);
|
private final InputPointers mInputPointers = new InputPointers(N);
|
||||||
private final StringBuilder mTypedWord;
|
private final StringBuilder mTypedWord;
|
||||||
|
@ -42,7 +45,7 @@ public class WordComposer {
|
||||||
// Cache these values for performance
|
// Cache these values for performance
|
||||||
private int mCapsCount;
|
private int mCapsCount;
|
||||||
private int mDigitsCount;
|
private int mDigitsCount;
|
||||||
private boolean mAutoCapitalized;
|
private int mCapitalizedMode;
|
||||||
private int mTrailingSingleQuotesCount;
|
private int mTrailingSingleQuotesCount;
|
||||||
private int mCodePointSize;
|
private int mCodePointSize;
|
||||||
|
|
||||||
|
@ -68,7 +71,7 @@ public class WordComposer {
|
||||||
mCapsCount = source.mCapsCount;
|
mCapsCount = source.mCapsCount;
|
||||||
mDigitsCount = source.mDigitsCount;
|
mDigitsCount = source.mDigitsCount;
|
||||||
mIsFirstCharCapitalized = source.mIsFirstCharCapitalized;
|
mIsFirstCharCapitalized = source.mIsFirstCharCapitalized;
|
||||||
mAutoCapitalized = source.mAutoCapitalized;
|
mCapitalizedMode = source.mCapitalizedMode;
|
||||||
mTrailingSingleQuotesCount = source.mTrailingSingleQuotesCount;
|
mTrailingSingleQuotesCount = source.mTrailingSingleQuotesCount;
|
||||||
mIsResumed = source.mIsResumed;
|
mIsResumed = source.mIsResumed;
|
||||||
mIsBatchMode = source.mIsBatchMode;
|
mIsBatchMode = source.mIsBatchMode;
|
||||||
|
@ -166,7 +169,7 @@ public class WordComposer {
|
||||||
final int codePoint = Character.codePointAt(word, i);
|
final int codePoint = Character.codePointAt(word, i);
|
||||||
// We don't want to override the batch input points that are held in mInputPointers
|
// We don't want to override the batch input points that are held in mInputPointers
|
||||||
// (See {@link #add(int,int,int)}).
|
// (See {@link #add(int,int,int)}).
|
||||||
add(codePoint, NOT_A_COORDINATE, NOT_A_COORDINATE);
|
add(codePoint, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,7 +184,7 @@ public class WordComposer {
|
||||||
add(codePoint, x, y);
|
add(codePoint, x, y);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
add(codePoint, NOT_A_COORDINATE, NOT_A_COORDINATE);
|
add(codePoint, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -262,7 +265,14 @@ public class WordComposer {
|
||||||
* @return true if all user typed chars are upper case, false otherwise
|
* @return true if all user typed chars are upper case, false otherwise
|
||||||
*/
|
*/
|
||||||
public boolean isAllUpperCase() {
|
public boolean isAllUpperCase() {
|
||||||
return (mCapsCount > 0) && (mCapsCount == size());
|
return mCapitalizedMode == CAPS_MODE_AUTO_SHIFT_LOCKED
|
||||||
|
|| mCapitalizedMode == CAPS_MODE_MANUAL_SHIFT_LOCKED
|
||||||
|
|| (mCapsCount > 0) && (mCapsCount == size());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean wasShiftedNoLock() {
|
||||||
|
return mCapitalizedMode == CAPS_MODE_AUTO_SHIFTED
|
||||||
|
|| mCapitalizedMode == CAPS_MODE_MANUAL_SHIFTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -280,20 +290,27 @@ public class WordComposer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves the reason why the word is capitalized - whether it was automatic or
|
* Saves the caps mode at the start of composing.
|
||||||
* due to the user hitting shift in the middle of a sentence.
|
*
|
||||||
* @param auto whether it was an automatic capitalization due to start of sentence
|
* WordComposer needs to know about this for several reasons. The first is, we need to know
|
||||||
|
* after the fact what the reason was, to register the correct form into the user history
|
||||||
|
* dictionary: if the word was automatically capitalized, we should insert it in all-lower
|
||||||
|
* case but if it's a manual pressing of shift, then it should be inserted as is.
|
||||||
|
* Also, batch input needs to know about the current caps mode to display correctly
|
||||||
|
* capitalized suggestions.
|
||||||
|
* @param mode the mode at the time of start
|
||||||
*/
|
*/
|
||||||
public void setAutoCapitalized(boolean auto) {
|
public void setCapitalizedModeAtStartComposingTime(final int mode) {
|
||||||
mAutoCapitalized = auto;
|
mCapitalizedMode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the word was automatically capitalized.
|
* Returns whether the word was automatically capitalized.
|
||||||
* @return whether the word was automatically capitalized
|
* @return whether the word was automatically capitalized
|
||||||
*/
|
*/
|
||||||
public boolean isAutoCapitalized() {
|
public boolean wasAutoCapitalized() {
|
||||||
return mAutoCapitalized;
|
return mCapitalizedMode == CAPS_MODE_AUTO_SHIFT_LOCKED
|
||||||
|
|| mCapitalizedMode == CAPS_MODE_AUTO_SHIFTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -22,10 +22,13 @@ import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
|
||||||
import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
|
import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.RandomAccessFile;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -124,7 +127,7 @@ public class BinaryDictInputOutput {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
private static final int VERSION_1_MAGIC_NUMBER = 0x78B1;
|
private static final int VERSION_1_MAGIC_NUMBER = 0x78B1;
|
||||||
private static final int VERSION_2_MAGIC_NUMBER = 0x9BC13AFE;
|
public static final int VERSION_2_MAGIC_NUMBER = 0x9BC13AFE;
|
||||||
private static final int MINIMUM_SUPPORTED_VERSION = 1;
|
private static final int MINIMUM_SUPPORTED_VERSION = 1;
|
||||||
private static final int MAXIMUM_SUPPORTED_VERSION = 2;
|
private static final int MAXIMUM_SUPPORTED_VERSION = 2;
|
||||||
private static final int NOT_A_VERSION_NUMBER = -1;
|
private static final int NOT_A_VERSION_NUMBER = -1;
|
||||||
|
@ -307,33 +310,32 @@ public class BinaryDictInputOutput {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a string from a RandomAccessFile. This is the converse of the above method.
|
* Reads a string from a ByteBuffer. This is the converse of the above method.
|
||||||
*/
|
*/
|
||||||
private static String readString(final RandomAccessFile source) throws IOException {
|
private static String readString(final ByteBuffer buffer) {
|
||||||
final StringBuilder s = new StringBuilder();
|
final StringBuilder s = new StringBuilder();
|
||||||
int character = readChar(source);
|
int character = readChar(buffer);
|
||||||
while (character != INVALID_CHARACTER) {
|
while (character != INVALID_CHARACTER) {
|
||||||
s.appendCodePoint(character);
|
s.appendCodePoint(character);
|
||||||
character = readChar(source);
|
character = readChar(buffer);
|
||||||
}
|
}
|
||||||
return s.toString();
|
return s.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a character from the file.
|
* Reads a character from the ByteBuffer.
|
||||||
*
|
*
|
||||||
* This follows the character format documented earlier in this source file.
|
* This follows the character format documented earlier in this source file.
|
||||||
*
|
*
|
||||||
* @param source the file, positioned over an encoded character.
|
* @param buffer the buffer, positioned over an encoded character.
|
||||||
* @return the character code.
|
* @return the character code.
|
||||||
*/
|
*/
|
||||||
private static int readChar(RandomAccessFile source) throws IOException {
|
private static int readChar(final ByteBuffer buffer) {
|
||||||
int character = source.readUnsignedByte();
|
int character = readUnsignedByte(buffer);
|
||||||
if (!fitsOnOneByte(character)) {
|
if (!fitsOnOneByte(character)) {
|
||||||
if (GROUP_CHARACTERS_TERMINATOR == character)
|
if (GROUP_CHARACTERS_TERMINATOR == character) return INVALID_CHARACTER;
|
||||||
return INVALID_CHARACTER;
|
|
||||||
character <<= 16;
|
character <<= 16;
|
||||||
character += source.readUnsignedShort();
|
character += readUnsignedShort(buffer);
|
||||||
}
|
}
|
||||||
return character;
|
return character;
|
||||||
}
|
}
|
||||||
|
@ -783,10 +785,10 @@ public class BinaryDictInputOutput {
|
||||||
// their lower bound and exclude their higher bound so we need to have the first step
|
// their lower bound and exclude their higher bound so we need to have the first step
|
||||||
// start at exactly 1 unit higher than floor(unigramFreq + half a step).
|
// start at exactly 1 unit higher than floor(unigramFreq + half a step).
|
||||||
// Note : to reconstruct the score, the dictionary reader will need to divide
|
// Note : to reconstruct the score, the dictionary reader will need to divide
|
||||||
// MAX_TERMINAL_FREQUENCY - unigramFreq by 16.5 likewise, and add
|
// MAX_TERMINAL_FREQUENCY - unigramFreq by 16.5 likewise to get the value of the step,
|
||||||
// (discretizedFrequency + 0.5) times this value to get the median value of the step,
|
// and add (discretizedFrequency + 0.5 + 0.5) times this value to get the best
|
||||||
// which is the best approximation. This is how we get the most precise result with
|
// approximation. (0.5 to get the first step start, and 0.5 to get the middle of the
|
||||||
// only four bits.
|
// step pointed by the discretized frequency.
|
||||||
final float stepSize =
|
final float stepSize =
|
||||||
(MAX_TERMINAL_FREQUENCY - unigramFrequency) / (1.5f + MAX_BIGRAM_FREQUENCY);
|
(MAX_TERMINAL_FREQUENCY - unigramFrequency) / (1.5f + MAX_BIGRAM_FREQUENCY);
|
||||||
final float firstStepStart = 1 + unigramFrequency + (stepSize / 2.0f);
|
final float firstStepStart = 1 + unigramFrequency + (stepSize / 2.0f);
|
||||||
|
@ -1091,46 +1093,46 @@ public class BinaryDictInputOutput {
|
||||||
// readDictionaryBinary is the public entry point for them.
|
// readDictionaryBinary is the public entry point for them.
|
||||||
|
|
||||||
static final int[] characterBuffer = new int[MAX_WORD_LENGTH];
|
static final int[] characterBuffer = new int[MAX_WORD_LENGTH];
|
||||||
private static CharGroupInfo readCharGroup(RandomAccessFile source,
|
private static CharGroupInfo readCharGroup(final ByteBuffer buffer,
|
||||||
final int originalGroupAddress) throws IOException {
|
final int originalGroupAddress) {
|
||||||
int addressPointer = originalGroupAddress;
|
int addressPointer = originalGroupAddress;
|
||||||
final int flags = source.readUnsignedByte();
|
final int flags = readUnsignedByte(buffer);
|
||||||
++addressPointer;
|
++addressPointer;
|
||||||
final int characters[];
|
final int characters[];
|
||||||
if (0 != (flags & FLAG_HAS_MULTIPLE_CHARS)) {
|
if (0 != (flags & FLAG_HAS_MULTIPLE_CHARS)) {
|
||||||
int index = 0;
|
int index = 0;
|
||||||
int character = CharEncoding.readChar(source);
|
int character = CharEncoding.readChar(buffer);
|
||||||
addressPointer += CharEncoding.getCharSize(character);
|
addressPointer += CharEncoding.getCharSize(character);
|
||||||
while (-1 != character) {
|
while (-1 != character) {
|
||||||
characterBuffer[index++] = character;
|
characterBuffer[index++] = character;
|
||||||
character = CharEncoding.readChar(source);
|
character = CharEncoding.readChar(buffer);
|
||||||
addressPointer += CharEncoding.getCharSize(character);
|
addressPointer += CharEncoding.getCharSize(character);
|
||||||
}
|
}
|
||||||
characters = Arrays.copyOfRange(characterBuffer, 0, index);
|
characters = Arrays.copyOfRange(characterBuffer, 0, index);
|
||||||
} else {
|
} else {
|
||||||
final int character = CharEncoding.readChar(source);
|
final int character = CharEncoding.readChar(buffer);
|
||||||
addressPointer += CharEncoding.getCharSize(character);
|
addressPointer += CharEncoding.getCharSize(character);
|
||||||
characters = new int[] { character };
|
characters = new int[] { character };
|
||||||
}
|
}
|
||||||
final int frequency;
|
final int frequency;
|
||||||
if (0 != (FLAG_IS_TERMINAL & flags)) {
|
if (0 != (FLAG_IS_TERMINAL & flags)) {
|
||||||
++addressPointer;
|
++addressPointer;
|
||||||
frequency = source.readUnsignedByte();
|
frequency = readUnsignedByte(buffer);
|
||||||
} else {
|
} else {
|
||||||
frequency = CharGroup.NOT_A_TERMINAL;
|
frequency = CharGroup.NOT_A_TERMINAL;
|
||||||
}
|
}
|
||||||
int childrenAddress = addressPointer;
|
int childrenAddress = addressPointer;
|
||||||
switch (flags & MASK_GROUP_ADDRESS_TYPE) {
|
switch (flags & MASK_GROUP_ADDRESS_TYPE) {
|
||||||
case FLAG_GROUP_ADDRESS_TYPE_ONEBYTE:
|
case FLAG_GROUP_ADDRESS_TYPE_ONEBYTE:
|
||||||
childrenAddress += source.readUnsignedByte();
|
childrenAddress += readUnsignedByte(buffer);
|
||||||
addressPointer += 1;
|
addressPointer += 1;
|
||||||
break;
|
break;
|
||||||
case FLAG_GROUP_ADDRESS_TYPE_TWOBYTES:
|
case FLAG_GROUP_ADDRESS_TYPE_TWOBYTES:
|
||||||
childrenAddress += source.readUnsignedShort();
|
childrenAddress += readUnsignedShort(buffer);
|
||||||
addressPointer += 2;
|
addressPointer += 2;
|
||||||
break;
|
break;
|
||||||
case FLAG_GROUP_ADDRESS_TYPE_THREEBYTES:
|
case FLAG_GROUP_ADDRESS_TYPE_THREEBYTES:
|
||||||
childrenAddress += (source.readUnsignedByte() << 16) + source.readUnsignedShort();
|
childrenAddress += readUnsignedInt24(buffer);
|
||||||
addressPointer += 3;
|
addressPointer += 3;
|
||||||
break;
|
break;
|
||||||
case FLAG_GROUP_ADDRESS_TYPE_NOADDRESS:
|
case FLAG_GROUP_ADDRESS_TYPE_NOADDRESS:
|
||||||
|
@ -1140,38 +1142,38 @@ public class BinaryDictInputOutput {
|
||||||
}
|
}
|
||||||
ArrayList<WeightedString> shortcutTargets = null;
|
ArrayList<WeightedString> shortcutTargets = null;
|
||||||
if (0 != (flags & FLAG_HAS_SHORTCUT_TARGETS)) {
|
if (0 != (flags & FLAG_HAS_SHORTCUT_TARGETS)) {
|
||||||
final long pointerBefore = source.getFilePointer();
|
final int pointerBefore = buffer.position();
|
||||||
shortcutTargets = new ArrayList<WeightedString>();
|
shortcutTargets = new ArrayList<WeightedString>();
|
||||||
source.readUnsignedShort(); // Skip the size
|
buffer.getShort(); // Skip the size
|
||||||
while (true) {
|
while (true) {
|
||||||
final int targetFlags = source.readUnsignedByte();
|
final int targetFlags = readUnsignedByte(buffer);
|
||||||
final String word = CharEncoding.readString(source);
|
final String word = CharEncoding.readString(buffer);
|
||||||
shortcutTargets.add(new WeightedString(word,
|
shortcutTargets.add(new WeightedString(word,
|
||||||
targetFlags & FLAG_ATTRIBUTE_FREQUENCY));
|
targetFlags & FLAG_ATTRIBUTE_FREQUENCY));
|
||||||
if (0 == (targetFlags & FLAG_ATTRIBUTE_HAS_NEXT)) break;
|
if (0 == (targetFlags & FLAG_ATTRIBUTE_HAS_NEXT)) break;
|
||||||
}
|
}
|
||||||
addressPointer += (source.getFilePointer() - pointerBefore);
|
addressPointer += buffer.position() - pointerBefore;
|
||||||
}
|
}
|
||||||
ArrayList<PendingAttribute> bigrams = null;
|
ArrayList<PendingAttribute> bigrams = null;
|
||||||
if (0 != (flags & FLAG_HAS_BIGRAMS)) {
|
if (0 != (flags & FLAG_HAS_BIGRAMS)) {
|
||||||
bigrams = new ArrayList<PendingAttribute>();
|
bigrams = new ArrayList<PendingAttribute>();
|
||||||
while (true) {
|
while (true) {
|
||||||
final int bigramFlags = source.readUnsignedByte();
|
final int bigramFlags = readUnsignedByte(buffer);
|
||||||
++addressPointer;
|
++addressPointer;
|
||||||
final int sign = 0 == (bigramFlags & FLAG_ATTRIBUTE_OFFSET_NEGATIVE) ? 1 : -1;
|
final int sign = 0 == (bigramFlags & FLAG_ATTRIBUTE_OFFSET_NEGATIVE) ? 1 : -1;
|
||||||
int bigramAddress = addressPointer;
|
int bigramAddress = addressPointer;
|
||||||
switch (bigramFlags & MASK_ATTRIBUTE_ADDRESS_TYPE) {
|
switch (bigramFlags & MASK_ATTRIBUTE_ADDRESS_TYPE) {
|
||||||
case FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE:
|
case FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE:
|
||||||
bigramAddress += sign * source.readUnsignedByte();
|
bigramAddress += sign * readUnsignedByte(buffer);
|
||||||
addressPointer += 1;
|
addressPointer += 1;
|
||||||
break;
|
break;
|
||||||
case FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES:
|
case FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES:
|
||||||
bigramAddress += sign * source.readUnsignedShort();
|
bigramAddress += sign * readUnsignedShort(buffer);
|
||||||
addressPointer += 2;
|
addressPointer += 2;
|
||||||
break;
|
break;
|
||||||
case FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES:
|
case FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES:
|
||||||
final int offset = ((source.readUnsignedByte() << 16)
|
final int offset = (readUnsignedByte(buffer) << 16)
|
||||||
+ source.readUnsignedShort());
|
+ readUnsignedShort(buffer);
|
||||||
bigramAddress += sign * offset;
|
bigramAddress += sign * offset;
|
||||||
addressPointer += 3;
|
addressPointer += 3;
|
||||||
break;
|
break;
|
||||||
|
@ -1188,15 +1190,15 @@ public class BinaryDictInputOutput {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads and returns the char group count out of a file and forwards the pointer.
|
* Reads and returns the char group count out of a buffer and forwards the pointer.
|
||||||
*/
|
*/
|
||||||
private static int readCharGroupCount(RandomAccessFile source) throws IOException {
|
private static int readCharGroupCount(final ByteBuffer buffer) {
|
||||||
final int msb = source.readUnsignedByte();
|
final int msb = readUnsignedByte(buffer);
|
||||||
if (MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT >= msb) {
|
if (MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT >= msb) {
|
||||||
return msb;
|
return msb;
|
||||||
} else {
|
} else {
|
||||||
return ((MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT & msb) << 8)
|
return ((MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT & msb) << 8)
|
||||||
+ source.readUnsignedByte();
|
+ readUnsignedByte(buffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1204,31 +1206,29 @@ public class BinaryDictInputOutput {
|
||||||
// of this method. Since it performs direct, unbuffered random access to the file and
|
// of this method. Since it performs direct, unbuffered random access to the file and
|
||||||
// may be called hundreds of thousands of times, the resulting performance is not
|
// may be called hundreds of thousands of times, the resulting performance is not
|
||||||
// reasonable without some kind of cache. Thus:
|
// reasonable without some kind of cache. Thus:
|
||||||
// TODO: perform buffered I/O here and in other places in the code.
|
|
||||||
private static TreeMap<Integer, String> wordCache = new TreeMap<Integer, String>();
|
private static TreeMap<Integer, String> wordCache = new TreeMap<Integer, String>();
|
||||||
/**
|
/**
|
||||||
* Finds, as a string, the word at the address passed as an argument.
|
* Finds, as a string, the word at the address passed as an argument.
|
||||||
*
|
*
|
||||||
* @param source the file to read from.
|
* @param buffer the buffer to read from.
|
||||||
* @param headerSize the size of the header.
|
* @param headerSize the size of the header.
|
||||||
* @param address the address to seek.
|
* @param address the address to seek.
|
||||||
* @return the word, as a string.
|
* @return the word, as a string.
|
||||||
* @throws IOException if the file can't be read.
|
|
||||||
*/
|
*/
|
||||||
private static String getWordAtAddress(final RandomAccessFile source, final long headerSize,
|
private static String getWordAtAddress(final ByteBuffer buffer, final int headerSize,
|
||||||
int address) throws IOException {
|
final int address) {
|
||||||
final String cachedString = wordCache.get(address);
|
final String cachedString = wordCache.get(address);
|
||||||
if (null != cachedString) return cachedString;
|
if (null != cachedString) return cachedString;
|
||||||
final long originalPointer = source.getFilePointer();
|
final int originalPointer = buffer.position();
|
||||||
source.seek(headerSize);
|
buffer.position(headerSize);
|
||||||
final int count = readCharGroupCount(source);
|
final int count = readCharGroupCount(buffer);
|
||||||
int groupOffset = getGroupCountSize(count);
|
int groupOffset = getGroupCountSize(count);
|
||||||
final StringBuilder builder = new StringBuilder();
|
final StringBuilder builder = new StringBuilder();
|
||||||
String result = null;
|
String result = null;
|
||||||
|
|
||||||
CharGroupInfo last = null;
|
CharGroupInfo last = null;
|
||||||
for (int i = count - 1; i >= 0; --i) {
|
for (int i = count - 1; i >= 0; --i) {
|
||||||
CharGroupInfo info = readCharGroup(source, groupOffset);
|
CharGroupInfo info = readCharGroup(buffer, groupOffset);
|
||||||
groupOffset = info.mEndAddress;
|
groupOffset = info.mEndAddress;
|
||||||
if (info.mOriginalAddress == address) {
|
if (info.mOriginalAddress == address) {
|
||||||
builder.append(new String(info.mCharacters, 0, info.mCharacters.length));
|
builder.append(new String(info.mCharacters, 0, info.mCharacters.length));
|
||||||
|
@ -1239,9 +1239,9 @@ public class BinaryDictInputOutput {
|
||||||
if (info.mChildrenAddress > address) {
|
if (info.mChildrenAddress > address) {
|
||||||
if (null == last) continue;
|
if (null == last) continue;
|
||||||
builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
|
builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
|
||||||
source.seek(last.mChildrenAddress + headerSize);
|
buffer.position(last.mChildrenAddress + headerSize);
|
||||||
groupOffset = last.mChildrenAddress + 1;
|
groupOffset = last.mChildrenAddress + 1;
|
||||||
i = source.readUnsignedByte();
|
i = readUnsignedByte(buffer);
|
||||||
last = null;
|
last = null;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1249,14 +1249,14 @@ public class BinaryDictInputOutput {
|
||||||
}
|
}
|
||||||
if (0 == i && hasChildrenAddress(last.mChildrenAddress)) {
|
if (0 == i && hasChildrenAddress(last.mChildrenAddress)) {
|
||||||
builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
|
builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
|
||||||
source.seek(last.mChildrenAddress + headerSize);
|
buffer.position(last.mChildrenAddress + headerSize);
|
||||||
groupOffset = last.mChildrenAddress + 1;
|
groupOffset = last.mChildrenAddress + 1;
|
||||||
i = source.readUnsignedByte();
|
i = readUnsignedByte(buffer);
|
||||||
last = null;
|
last = null;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
source.seek(originalPointer);
|
buffer.position(originalPointer);
|
||||||
wordCache.put(address, result);
|
wordCache.put(address, result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -1269,44 +1269,47 @@ public class BinaryDictInputOutput {
|
||||||
* This will recursively read other nodes into the structure, populating the reverse
|
* This will recursively read other nodes into the structure, populating the reverse
|
||||||
* maps on the fly and using them to keep track of already read nodes.
|
* maps on the fly and using them to keep track of already read nodes.
|
||||||
*
|
*
|
||||||
* @param source the data file, correctly positioned at the start of a node.
|
* @param buffer the buffer, correctly positioned at the start of a node.
|
||||||
* @param headerSize the size, in bytes, of the file header.
|
* @param headerSize the size, in bytes, of the file header.
|
||||||
* @param reverseNodeMap a mapping from addresses to already read nodes.
|
* @param reverseNodeMap a mapping from addresses to already read nodes.
|
||||||
* @param reverseGroupMap a mapping from addresses to already read character groups.
|
* @param reverseGroupMap a mapping from addresses to already read character groups.
|
||||||
* @return the read node with all his children already read.
|
* @return the read node with all his children already read.
|
||||||
*/
|
*/
|
||||||
private static Node readNode(RandomAccessFile source, long headerSize,
|
private static Node readNode(final ByteBuffer buffer, final int headerSize,
|
||||||
Map<Integer, Node> reverseNodeMap, Map<Integer, CharGroup> reverseGroupMap)
|
final Map<Integer, Node> reverseNodeMap, final Map<Integer, CharGroup> reverseGroupMap)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
final int nodeOrigin = (int)(source.getFilePointer() - headerSize);
|
final int nodeOrigin = buffer.position() - headerSize;
|
||||||
final int count = readCharGroupCount(source);
|
final int count = readCharGroupCount(buffer);
|
||||||
final ArrayList<CharGroup> nodeContents = new ArrayList<CharGroup>();
|
final ArrayList<CharGroup> nodeContents = new ArrayList<CharGroup>();
|
||||||
int groupOffset = nodeOrigin + getGroupCountSize(count);
|
int groupOffset = nodeOrigin + getGroupCountSize(count);
|
||||||
for (int i = count; i > 0; --i) {
|
for (int i = count; i > 0; --i) {
|
||||||
CharGroupInfo info = readCharGroup(source, groupOffset);
|
CharGroupInfo info =readCharGroup(buffer, groupOffset);
|
||||||
ArrayList<WeightedString> shortcutTargets = info.mShortcutTargets;
|
ArrayList<WeightedString> shortcutTargets = info.mShortcutTargets;
|
||||||
ArrayList<WeightedString> bigrams = null;
|
ArrayList<WeightedString> bigrams = null;
|
||||||
if (null != info.mBigrams) {
|
if (null != info.mBigrams) {
|
||||||
bigrams = new ArrayList<WeightedString>();
|
bigrams = new ArrayList<WeightedString>();
|
||||||
for (PendingAttribute bigram : info.mBigrams) {
|
for (PendingAttribute bigram : info.mBigrams) {
|
||||||
final String word = getWordAtAddress(source, headerSize, bigram.mAddress);
|
final String word = getWordAtAddress(
|
||||||
|
buffer, headerSize, bigram.mAddress);
|
||||||
bigrams.add(new WeightedString(word, bigram.mFrequency));
|
bigrams.add(new WeightedString(word, bigram.mFrequency));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hasChildrenAddress(info.mChildrenAddress)) {
|
if (hasChildrenAddress(info.mChildrenAddress)) {
|
||||||
Node children = reverseNodeMap.get(info.mChildrenAddress);
|
Node children = reverseNodeMap.get(info.mChildrenAddress);
|
||||||
if (null == children) {
|
if (null == children) {
|
||||||
final long currentPosition = source.getFilePointer();
|
final int currentPosition = buffer.position();
|
||||||
source.seek(info.mChildrenAddress + headerSize);
|
buffer.position(info.mChildrenAddress + headerSize);
|
||||||
children = readNode(source, headerSize, reverseNodeMap, reverseGroupMap);
|
children = readNode(
|
||||||
source.seek(currentPosition);
|
buffer, headerSize, reverseNodeMap, reverseGroupMap);
|
||||||
|
buffer.position(currentPosition);
|
||||||
}
|
}
|
||||||
nodeContents.add(
|
nodeContents.add(
|
||||||
new CharGroup(info.mCharacters, shortcutTargets, bigrams, info.mFrequency,
|
new CharGroup(info.mCharacters, shortcutTargets,
|
||||||
children));
|
bigrams, info.mFrequency, children));
|
||||||
} else {
|
} else {
|
||||||
nodeContents.add(
|
nodeContents.add(
|
||||||
new CharGroup(info.mCharacters, shortcutTargets, bigrams, info.mFrequency));
|
new CharGroup(info.mCharacters, shortcutTargets,
|
||||||
|
bigrams, info.mFrequency));
|
||||||
}
|
}
|
||||||
groupOffset = info.mEndAddress;
|
groupOffset = info.mEndAddress;
|
||||||
}
|
}
|
||||||
|
@ -1318,57 +1321,76 @@ public class BinaryDictInputOutput {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function to get the binary format version from the header.
|
* Helper function to get the binary format version from the header.
|
||||||
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
private static int getFormatVersion(final RandomAccessFile source) throws IOException {
|
private static int getFormatVersion(final ByteBuffer buffer) throws IOException {
|
||||||
final int magic_v1 = source.readUnsignedShort();
|
final int magic_v1 = readUnsignedShort(buffer);
|
||||||
if (VERSION_1_MAGIC_NUMBER == magic_v1) return source.readUnsignedByte();
|
if (VERSION_1_MAGIC_NUMBER == magic_v1) return readUnsignedByte(buffer);
|
||||||
final int magic_v2 = (magic_v1 << 16) + source.readUnsignedShort();
|
final int magic_v2 = (magic_v1 << 16) + readUnsignedShort(buffer);
|
||||||
if (VERSION_2_MAGIC_NUMBER == magic_v2) return source.readUnsignedShort();
|
if (VERSION_2_MAGIC_NUMBER == magic_v2) return readUnsignedShort(buffer);
|
||||||
return NOT_A_VERSION_NUMBER;
|
return NOT_A_VERSION_NUMBER;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a random access file and returns the memory representation of the dictionary.
|
* Reads options from a file and populate a map with their contents.
|
||||||
|
*
|
||||||
|
* The file is read at the current file pointer, so the caller must take care the pointer
|
||||||
|
* is in the right place before calling this.
|
||||||
|
*/
|
||||||
|
public static void populateOptions(final ByteBuffer buffer, final int headerSize,
|
||||||
|
final HashMap<String, String> options) {
|
||||||
|
while (buffer.position() < headerSize) {
|
||||||
|
final String key = CharEncoding.readString(buffer);
|
||||||
|
final String value = CharEncoding.readString(buffer);
|
||||||
|
options.put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a byte buffer and returns the memory representation of the dictionary.
|
||||||
*
|
*
|
||||||
* This high-level method takes a binary file and reads its contents, populating a
|
* This high-level method takes a binary file and reads its contents, populating a
|
||||||
* FusionDictionary structure. The optional dict argument is an existing dictionary to
|
* FusionDictionary structure. The optional dict argument is an existing dictionary to
|
||||||
* which words from the file should be added. If it is null, a new dictionary is created.
|
* which words from the file should be added. If it is null, a new dictionary is created.
|
||||||
*
|
*
|
||||||
* @param source the file to read.
|
* @param buffer the buffer to read.
|
||||||
* @param dict an optional dictionary to add words to, or null.
|
* @param dict an optional dictionary to add words to, or null.
|
||||||
* @return the created (or merged) dictionary.
|
* @return the created (or merged) dictionary.
|
||||||
*/
|
*/
|
||||||
public static FusionDictionary readDictionaryBinary(final RandomAccessFile source,
|
public static FusionDictionary readDictionaryBinary(final ByteBuffer buffer,
|
||||||
final FusionDictionary dict) throws IOException, UnsupportedFormatException {
|
final FusionDictionary dict) throws IOException, UnsupportedFormatException {
|
||||||
// Check file version
|
// Check file version
|
||||||
final int version = getFormatVersion(source);
|
final int version = getFormatVersion(buffer);
|
||||||
if (version < MINIMUM_SUPPORTED_VERSION || version > MAXIMUM_SUPPORTED_VERSION ) {
|
if (version < MINIMUM_SUPPORTED_VERSION || version > MAXIMUM_SUPPORTED_VERSION) {
|
||||||
throw new UnsupportedFormatException("This file has version " + version
|
throw new UnsupportedFormatException("This file has version " + version
|
||||||
+ ", but this implementation does not support versions above "
|
+ ", but this implementation does not support versions above "
|
||||||
+ MAXIMUM_SUPPORTED_VERSION);
|
+ MAXIMUM_SUPPORTED_VERSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read options
|
// clear cache
|
||||||
final int optionsFlags = source.readUnsignedShort();
|
wordCache.clear();
|
||||||
|
|
||||||
final long headerSize;
|
// Read options
|
||||||
|
final int optionsFlags = readUnsignedShort(buffer);
|
||||||
|
|
||||||
|
final int headerSize;
|
||||||
final HashMap<String, String> options = new HashMap<String, String>();
|
final HashMap<String, String> options = new HashMap<String, String>();
|
||||||
if (version < FIRST_VERSION_WITH_HEADER_SIZE) {
|
if (version < FIRST_VERSION_WITH_HEADER_SIZE) {
|
||||||
headerSize = source.getFilePointer();
|
headerSize = buffer.position();
|
||||||
} else {
|
} else {
|
||||||
headerSize = (source.readUnsignedByte() << 24) + (source.readUnsignedByte() << 16)
|
headerSize = buffer.getInt();
|
||||||
+ (source.readUnsignedByte() << 8) + source.readUnsignedByte();
|
populateOptions(buffer, headerSize, options);
|
||||||
while (source.getFilePointer() < headerSize) {
|
buffer.position(headerSize);
|
||||||
final String key = CharEncoding.readString(source);
|
|
||||||
final String value = CharEncoding.readString(source);
|
|
||||||
options.put(key, value);
|
|
||||||
}
|
}
|
||||||
source.seek(headerSize);
|
|
||||||
|
if (headerSize < 0) {
|
||||||
|
throw new UnsupportedFormatException("header size can't be negative.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<Integer, Node> reverseNodeMapping = new TreeMap<Integer, Node>();
|
Map<Integer, Node> reverseNodeMapping = new TreeMap<Integer, Node>();
|
||||||
Map<Integer, CharGroup> reverseGroupMapping = new TreeMap<Integer, CharGroup>();
|
Map<Integer, CharGroup> reverseGroupMapping = new TreeMap<Integer, CharGroup>();
|
||||||
final Node root = readNode(source, headerSize, reverseNodeMapping, reverseGroupMapping);
|
final Node root = readNode(
|
||||||
|
buffer, headerSize, reverseNodeMapping, reverseGroupMapping);
|
||||||
|
|
||||||
FusionDictionary newDict = new FusionDictionary(root,
|
FusionDictionary newDict = new FusionDictionary(root,
|
||||||
new FusionDictionary.DictionaryOptions(options,
|
new FusionDictionary.DictionaryOptions(options,
|
||||||
|
@ -1391,6 +1413,28 @@ public class BinaryDictInputOutput {
|
||||||
return newDict;
|
return newDict;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to read one byte from ByteBuffer.
|
||||||
|
*/
|
||||||
|
private static int readUnsignedByte(final ByteBuffer buffer) {
|
||||||
|
return ((int)buffer.get()) & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to read two byte from ByteBuffer.
|
||||||
|
*/
|
||||||
|
private static int readUnsignedShort(final ByteBuffer buffer) {
|
||||||
|
return ((int)buffer.getShort()) & 0xFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to read three byte from ByteBuffer.
|
||||||
|
*/
|
||||||
|
private static int readUnsignedInt24(final ByteBuffer buffer) {
|
||||||
|
final int value = readUnsignedByte(buffer) << 16;
|
||||||
|
return value + readUnsignedShort(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basic test to find out whether the file is a binary dictionary or not.
|
* Basic test to find out whether the file is a binary dictionary or not.
|
||||||
*
|
*
|
||||||
|
@ -1400,14 +1444,44 @@ public class BinaryDictInputOutput {
|
||||||
* @return true if it's a binary dictionary, false otherwise
|
* @return true if it's a binary dictionary, false otherwise
|
||||||
*/
|
*/
|
||||||
public static boolean isBinaryDictionary(final String filename) {
|
public static boolean isBinaryDictionary(final String filename) {
|
||||||
|
FileInputStream inStream = null;
|
||||||
try {
|
try {
|
||||||
RandomAccessFile f = new RandomAccessFile(filename, "r");
|
final File file = new File(filename);
|
||||||
final int version = getFormatVersion(f);
|
inStream = new FileInputStream(file);
|
||||||
|
final ByteBuffer buffer = inStream.getChannel().map(
|
||||||
|
FileChannel.MapMode.READ_ONLY, 0, file.length());
|
||||||
|
final int version = getFormatVersion(buffer);
|
||||||
return (version >= MINIMUM_SUPPORTED_VERSION && version <= MAXIMUM_SUPPORTED_VERSION);
|
return (version >= MINIMUM_SUPPORTED_VERSION && version <= MAXIMUM_SUPPORTED_VERSION);
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
return false;
|
return false;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
return false;
|
return false;
|
||||||
|
} finally {
|
||||||
|
if (inStream != null) {
|
||||||
|
try {
|
||||||
|
inStream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate bigram frequency from compressed value
|
||||||
|
*
|
||||||
|
* @see #makeBigramFlags
|
||||||
|
*
|
||||||
|
* @param unigramFrequency
|
||||||
|
* @param bigramFrequency compressed frequency
|
||||||
|
* @return approximate bigram frequency
|
||||||
|
*/
|
||||||
|
public static int reconstructBigramFrequency(final int unigramFrequency,
|
||||||
|
final int bigramFrequency) {
|
||||||
|
final float stepSize = (MAX_TERMINAL_FREQUENCY - unigramFrequency)
|
||||||
|
/ (1.5f + MAX_BIGRAM_FREQUENCY);
|
||||||
|
final float resultFreqFloat = (float)unigramFrequency
|
||||||
|
+ stepSize * (bigramFrequency + 1.0f);
|
||||||
|
return (int)resultFreqFloat;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -516,13 +516,23 @@ public class FusionDictionary implements Iterable<Word> {
|
||||||
int indexOfGroup = findIndexOfChar(node, s.codePointAt(index));
|
int indexOfGroup = findIndexOfChar(node, s.codePointAt(index));
|
||||||
if (CHARACTER_NOT_FOUND == indexOfGroup) return null;
|
if (CHARACTER_NOT_FOUND == indexOfGroup) return null;
|
||||||
currentGroup = node.mData.get(indexOfGroup);
|
currentGroup = node.mData.get(indexOfGroup);
|
||||||
|
|
||||||
|
if (s.length() - index < currentGroup.mChars.length) return null;
|
||||||
|
int newIndex = index;
|
||||||
|
while (newIndex < s.length() && newIndex - index < currentGroup.mChars.length) {
|
||||||
|
if (currentGroup.mChars[newIndex - index] != s.codePointAt(newIndex)) return null;
|
||||||
|
newIndex++;
|
||||||
|
}
|
||||||
|
index = newIndex;
|
||||||
|
|
||||||
if (DBG) checker.append(new String(currentGroup.mChars, 0, currentGroup.mChars.length));
|
if (DBG) checker.append(new String(currentGroup.mChars, 0, currentGroup.mChars.length));
|
||||||
index += currentGroup.mChars.length;
|
|
||||||
if (index < s.length()) {
|
if (index < s.length()) {
|
||||||
node = currentGroup.mChildren;
|
node = currentGroup.mChildren;
|
||||||
}
|
}
|
||||||
} while (null != node && index < s.length());
|
} while (null != node && index < s.length());
|
||||||
|
|
||||||
|
if (index < s.length()) return null;
|
||||||
|
if (!currentGroup.isTerminal()) return null;
|
||||||
if (DBG && !s.equals(checker.toString())) return null;
|
if (DBG && !s.equals(checker.toString())) return null;
|
||||||
return currentGroup;
|
return currentGroup;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import android.view.textservice.SuggestionsInfo;
|
||||||
|
|
||||||
import com.android.inputmethod.keyboard.ProximityInfo;
|
import com.android.inputmethod.keyboard.ProximityInfo;
|
||||||
import com.android.inputmethod.latin.BinaryDictionary;
|
import com.android.inputmethod.latin.BinaryDictionary;
|
||||||
|
import com.android.inputmethod.latin.CollectionUtils;
|
||||||
import com.android.inputmethod.latin.ContactsBinaryDictionary;
|
import com.android.inputmethod.latin.ContactsBinaryDictionary;
|
||||||
import com.android.inputmethod.latin.Dictionary;
|
import com.android.inputmethod.latin.Dictionary;
|
||||||
import com.android.inputmethod.latin.DictionaryCollection;
|
import com.android.inputmethod.latin.DictionaryCollection;
|
||||||
|
@ -35,7 +36,6 @@ import com.android.inputmethod.latin.StringUtils;
|
||||||
import com.android.inputmethod.latin.SynchronouslyLoadedContactsBinaryDictionary;
|
import com.android.inputmethod.latin.SynchronouslyLoadedContactsBinaryDictionary;
|
||||||
import com.android.inputmethod.latin.SynchronouslyLoadedUserBinaryDictionary;
|
import com.android.inputmethod.latin.SynchronouslyLoadedUserBinaryDictionary;
|
||||||
import com.android.inputmethod.latin.UserBinaryDictionary;
|
import com.android.inputmethod.latin.UserBinaryDictionary;
|
||||||
import com.android.inputmethod.latin.WhitelistDictionary;
|
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -63,12 +63,9 @@ public class AndroidSpellCheckerService extends SpellCheckerService
|
||||||
public static final int CAPITALIZE_ALL = 2; // All caps
|
public static final int CAPITALIZE_ALL = 2; // All caps
|
||||||
|
|
||||||
private final static String[] EMPTY_STRING_ARRAY = new String[0];
|
private final static String[] EMPTY_STRING_ARRAY = new String[0];
|
||||||
private Map<String, DictionaryPool> mDictionaryPools =
|
private Map<String, DictionaryPool> mDictionaryPools = CollectionUtils.newSynchronizedTreeMap();
|
||||||
Collections.synchronizedMap(new TreeMap<String, DictionaryPool>());
|
|
||||||
private Map<String, UserBinaryDictionary> mUserDictionaries =
|
private Map<String, UserBinaryDictionary> mUserDictionaries =
|
||||||
Collections.synchronizedMap(new TreeMap<String, UserBinaryDictionary>());
|
CollectionUtils.newSynchronizedTreeMap();
|
||||||
private Map<String, Dictionary> mWhitelistDictionaries =
|
|
||||||
Collections.synchronizedMap(new TreeMap<String, Dictionary>());
|
|
||||||
private ContactsBinaryDictionary mContactsDictionary;
|
private ContactsBinaryDictionary mContactsDictionary;
|
||||||
|
|
||||||
// The threshold for a candidate to be offered as a suggestion.
|
// The threshold for a candidate to be offered as a suggestion.
|
||||||
|
@ -80,7 +77,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService
|
||||||
private final Object mUseContactsLock = new Object();
|
private final Object mUseContactsLock = new Object();
|
||||||
|
|
||||||
private final HashSet<WeakReference<DictionaryCollection>> mDictionaryCollectionsList =
|
private final HashSet<WeakReference<DictionaryCollection>> mDictionaryCollectionsList =
|
||||||
new HashSet<WeakReference<DictionaryCollection>>();
|
CollectionUtils.newHashSet();
|
||||||
|
|
||||||
public static final int SCRIPT_LATIN = 0;
|
public static final int SCRIPT_LATIN = 0;
|
||||||
public static final int SCRIPT_CYRILLIC = 1;
|
public static final int SCRIPT_CYRILLIC = 1;
|
||||||
|
@ -96,7 +93,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService
|
||||||
// proximity to pass to the dictionary descent algorithm.
|
// proximity to pass to the dictionary descent algorithm.
|
||||||
// IMPORTANT: this only contains languages - do not write countries in there.
|
// IMPORTANT: this only contains languages - do not write countries in there.
|
||||||
// Only the language is searched from the map.
|
// Only the language is searched from the map.
|
||||||
mLanguageToScript = new TreeMap<String, Integer>();
|
mLanguageToScript = CollectionUtils.newTreeMap();
|
||||||
mLanguageToScript.put("en", SCRIPT_LATIN);
|
mLanguageToScript.put("en", SCRIPT_LATIN);
|
||||||
mLanguageToScript.put("fr", SCRIPT_LATIN);
|
mLanguageToScript.put("fr", SCRIPT_LATIN);
|
||||||
mLanguageToScript.put("de", SCRIPT_LATIN);
|
mLanguageToScript.put("de", SCRIPT_LATIN);
|
||||||
|
@ -234,7 +231,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService
|
||||||
mSuggestionThreshold = suggestionThreshold;
|
mSuggestionThreshold = suggestionThreshold;
|
||||||
mRecommendedThreshold = recommendedThreshold;
|
mRecommendedThreshold = recommendedThreshold;
|
||||||
mMaxLength = maxLength;
|
mMaxLength = maxLength;
|
||||||
mSuggestions = new ArrayList<CharSequence>(maxLength + 1);
|
mSuggestions = CollectionUtils.newArrayList(maxLength + 1);
|
||||||
mScores = new int[mMaxLength];
|
mScores = new int[mMaxLength];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -362,12 +359,9 @@ public class AndroidSpellCheckerService extends SpellCheckerService
|
||||||
|
|
||||||
private void closeAllDictionaries() {
|
private void closeAllDictionaries() {
|
||||||
final Map<String, DictionaryPool> oldPools = mDictionaryPools;
|
final Map<String, DictionaryPool> oldPools = mDictionaryPools;
|
||||||
mDictionaryPools = Collections.synchronizedMap(new TreeMap<String, DictionaryPool>());
|
mDictionaryPools = CollectionUtils.newSynchronizedTreeMap();
|
||||||
final Map<String, UserBinaryDictionary> oldUserDictionaries = mUserDictionaries;
|
final Map<String, UserBinaryDictionary> oldUserDictionaries = mUserDictionaries;
|
||||||
mUserDictionaries =
|
mUserDictionaries = CollectionUtils.newSynchronizedTreeMap();
|
||||||
Collections.synchronizedMap(new TreeMap<String, UserBinaryDictionary>());
|
|
||||||
final Map<String, Dictionary> oldWhitelistDictionaries = mWhitelistDictionaries;
|
|
||||||
mWhitelistDictionaries = Collections.synchronizedMap(new TreeMap<String, Dictionary>());
|
|
||||||
new Thread("spellchecker_close_dicts") {
|
new Thread("spellchecker_close_dicts") {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
@ -377,9 +371,6 @@ public class AndroidSpellCheckerService extends SpellCheckerService
|
||||||
for (Dictionary dict : oldUserDictionaries.values()) {
|
for (Dictionary dict : oldUserDictionaries.values()) {
|
||||||
dict.close();
|
dict.close();
|
||||||
}
|
}
|
||||||
for (Dictionary dict : oldWhitelistDictionaries.values()) {
|
|
||||||
dict.close();
|
|
||||||
}
|
|
||||||
synchronized (mUseContactsLock) {
|
synchronized (mUseContactsLock) {
|
||||||
if (null != mContactsDictionary) {
|
if (null != mContactsDictionary) {
|
||||||
// The synchronously loaded contacts dictionary should have been in one
|
// The synchronously loaded contacts dictionary should have been in one
|
||||||
|
@ -423,12 +414,6 @@ public class AndroidSpellCheckerService extends SpellCheckerService
|
||||||
mUserDictionaries.put(localeStr, userDictionary);
|
mUserDictionaries.put(localeStr, userDictionary);
|
||||||
}
|
}
|
||||||
dictionaryCollection.addDictionary(userDictionary);
|
dictionaryCollection.addDictionary(userDictionary);
|
||||||
Dictionary whitelistDictionary = mWhitelistDictionaries.get(localeStr);
|
|
||||||
if (null == whitelistDictionary) {
|
|
||||||
whitelistDictionary = new WhitelistDictionary(this, locale);
|
|
||||||
mWhitelistDictionaries.put(localeStr, whitelistDictionary);
|
|
||||||
}
|
|
||||||
dictionaryCollection.addDictionary(whitelistDictionary);
|
|
||||||
synchronized (mUseContactsLock) {
|
synchronized (mUseContactsLock) {
|
||||||
if (mUseContactsDictionary) {
|
if (mUseContactsDictionary) {
|
||||||
if (null == mContactsDictionary) {
|
if (null == mContactsDictionary) {
|
||||||
|
|
|
@ -22,6 +22,8 @@ import android.view.textservice.SentenceSuggestionsInfo;
|
||||||
import android.view.textservice.SuggestionsInfo;
|
import android.view.textservice.SuggestionsInfo;
|
||||||
import android.view.textservice.TextInfo;
|
import android.view.textservice.TextInfo;
|
||||||
|
|
||||||
|
import com.android.inputmethod.latin.CollectionUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
public class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheckerSession {
|
public class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheckerSession {
|
||||||
|
@ -40,10 +42,10 @@ public class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheckerSess
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final int N = ssi.getSuggestionsCount();
|
final int N = ssi.getSuggestionsCount();
|
||||||
final ArrayList<Integer> additionalOffsets = new ArrayList<Integer>();
|
final ArrayList<Integer> additionalOffsets = CollectionUtils.newArrayList();
|
||||||
final ArrayList<Integer> additionalLengths = new ArrayList<Integer>();
|
final ArrayList<Integer> additionalLengths = CollectionUtils.newArrayList();
|
||||||
final ArrayList<SuggestionsInfo> additionalSuggestionsInfos =
|
final ArrayList<SuggestionsInfo> additionalSuggestionsInfos =
|
||||||
new ArrayList<SuggestionsInfo>();
|
CollectionUtils.newArrayList();
|
||||||
String currentWord = null;
|
String currentWord = null;
|
||||||
for (int i = 0; i < N; ++i) {
|
for (int i = 0; i < N; ++i) {
|
||||||
final SuggestionsInfo si = ssi.getSuggestionsInfoAt(i);
|
final SuggestionsInfo si = ssi.getSuggestionsInfoAt(i);
|
||||||
|
|
|
@ -24,6 +24,7 @@ import android.view.textservice.SuggestionsInfo;
|
||||||
import android.view.textservice.TextInfo;
|
import android.view.textservice.TextInfo;
|
||||||
|
|
||||||
import com.android.inputmethod.compat.SuggestionsInfoCompatUtils;
|
import com.android.inputmethod.compat.SuggestionsInfoCompatUtils;
|
||||||
|
import com.android.inputmethod.latin.Constants;
|
||||||
import com.android.inputmethod.latin.LocaleUtils;
|
import com.android.inputmethod.latin.LocaleUtils;
|
||||||
import com.android.inputmethod.latin.WordComposer;
|
import com.android.inputmethod.latin.WordComposer;
|
||||||
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
|
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
|
||||||
|
@ -193,8 +194,8 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
|
||||||
if (shouldFilterOut(inText, mScript)) {
|
if (shouldFilterOut(inText, mScript)) {
|
||||||
DictAndProximity dictInfo = null;
|
DictAndProximity dictInfo = null;
|
||||||
try {
|
try {
|
||||||
dictInfo = mDictionaryPool.takeOrGetNull();
|
dictInfo = mDictionaryPool.pollWithDefaultTimeout();
|
||||||
if (null == dictInfo) {
|
if (!DictionaryPool.isAValidDictionary(dictInfo)) {
|
||||||
return AndroidSpellCheckerService.getNotInDictEmptySuggestions();
|
return AndroidSpellCheckerService.getNotInDictEmptySuggestions();
|
||||||
}
|
}
|
||||||
return dictInfo.mDictionary.isValidWord(inText)
|
return dictInfo.mDictionary.isValidWord(inText)
|
||||||
|
@ -225,8 +226,8 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
|
||||||
final int xy = SpellCheckerProximityInfo.getXYForCodePointAndScript(
|
final int xy = SpellCheckerProximityInfo.getXYForCodePointAndScript(
|
||||||
codePoint, mScript);
|
codePoint, mScript);
|
||||||
if (SpellCheckerProximityInfo.NOT_A_COORDINATE_PAIR == xy) {
|
if (SpellCheckerProximityInfo.NOT_A_COORDINATE_PAIR == xy) {
|
||||||
composer.add(codePoint, WordComposer.NOT_A_COORDINATE,
|
composer.add(codePoint,
|
||||||
WordComposer.NOT_A_COORDINATE);
|
Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
|
||||||
} else {
|
} else {
|
||||||
composer.add(codePoint, xy & 0xFFFF, xy >> 16);
|
composer.add(codePoint, xy & 0xFFFF, xy >> 16);
|
||||||
}
|
}
|
||||||
|
@ -236,8 +237,8 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
|
||||||
boolean isInDict = true;
|
boolean isInDict = true;
|
||||||
DictAndProximity dictInfo = null;
|
DictAndProximity dictInfo = null;
|
||||||
try {
|
try {
|
||||||
dictInfo = mDictionaryPool.takeOrGetNull();
|
dictInfo = mDictionaryPool.pollWithDefaultTimeout();
|
||||||
if (null == dictInfo) {
|
if (!DictionaryPool.isAValidDictionary(dictInfo)) {
|
||||||
return AndroidSpellCheckerService.getNotInDictEmptySuggestions();
|
return AndroidSpellCheckerService.getNotInDictEmptySuggestions();
|
||||||
}
|
}
|
||||||
final ArrayList<SuggestedWordInfo> suggestions =
|
final ArrayList<SuggestedWordInfo> suggestions =
|
||||||
|
|
|
@ -16,19 +16,56 @@
|
||||||
|
|
||||||
package com.android.inputmethod.latin.spellcheck;
|
package com.android.inputmethod.latin.spellcheck;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.android.inputmethod.keyboard.ProximityInfo;
|
||||||
|
import com.android.inputmethod.latin.CollectionUtils;
|
||||||
|
import com.android.inputmethod.latin.Dictionary;
|
||||||
|
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
|
||||||
|
import com.android.inputmethod.latin.WordComposer;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A blocking queue that creates dictionaries up to a certain limit as necessary.
|
* A blocking queue that creates dictionaries up to a certain limit as necessary.
|
||||||
|
* As a deadlock-detecting device, if waiting for more than TIMEOUT = 3 seconds, we
|
||||||
|
* will clear the queue and generate its contents again. This is transparent for
|
||||||
|
* the client code, but may help with sloppy clients.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
public class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> {
|
public class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> {
|
||||||
|
private final static String TAG = DictionaryPool.class.getSimpleName();
|
||||||
|
// How many seconds we wait for a dictionary to become available. Past this delay, we give up in
|
||||||
|
// fear some bug caused a deadlock, and reset the whole pool.
|
||||||
|
private final static int TIMEOUT = 3;
|
||||||
private final AndroidSpellCheckerService mService;
|
private final AndroidSpellCheckerService mService;
|
||||||
private final int mMaxSize;
|
private final int mMaxSize;
|
||||||
private final Locale mLocale;
|
private final Locale mLocale;
|
||||||
private int mSize;
|
private int mSize;
|
||||||
private volatile boolean mClosed;
|
private volatile boolean mClosed;
|
||||||
|
final static ArrayList<SuggestedWordInfo> noSuggestions = CollectionUtils.newArrayList();
|
||||||
|
private final static DictAndProximity dummyDict = new DictAndProximity(
|
||||||
|
new Dictionary(Dictionary.TYPE_MAIN) {
|
||||||
|
@Override
|
||||||
|
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
|
||||||
|
final CharSequence prevWord, final ProximityInfo proximityInfo) {
|
||||||
|
return noSuggestions;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public boolean isValidWord(CharSequence word) {
|
||||||
|
// This is never called. However if for some strange reason it ever gets
|
||||||
|
// called, returning true is less destructive (it will not underline the
|
||||||
|
// word in red).
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}, null);
|
||||||
|
|
||||||
|
static public boolean isAValidDictionary(final DictAndProximity dictInfo) {
|
||||||
|
return null != dictInfo && dummyDict != dictInfo;
|
||||||
|
}
|
||||||
|
|
||||||
public DictionaryPool(final int maxSize, final AndroidSpellCheckerService service,
|
public DictionaryPool(final int maxSize, final AndroidSpellCheckerService service,
|
||||||
final Locale locale) {
|
final Locale locale) {
|
||||||
|
@ -41,13 +78,23 @@ public class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DictAndProximity take() throws InterruptedException {
|
public DictAndProximity poll(final long timeout, final TimeUnit unit)
|
||||||
|
throws InterruptedException {
|
||||||
final DictAndProximity dict = poll();
|
final DictAndProximity dict = poll();
|
||||||
if (null != dict) return dict;
|
if (null != dict) return dict;
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
if (mSize >= mMaxSize) {
|
if (mSize >= mMaxSize) {
|
||||||
// Our pool is already full. Wait until some dictionary is ready.
|
// Our pool is already full. Wait until some dictionary is ready, or TIMEOUT
|
||||||
return super.take();
|
// expires to avoid a deadlock.
|
||||||
|
final DictAndProximity result = super.poll(timeout, unit);
|
||||||
|
if (null == result) {
|
||||||
|
Log.e(TAG, "Deadlock detected ! Resetting dictionary pool");
|
||||||
|
clear();
|
||||||
|
mSize = 1;
|
||||||
|
return mService.createDictAndProximity(mLocale);
|
||||||
|
} else {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
++mSize;
|
++mSize;
|
||||||
return mService.createDictAndProximity(mLocale);
|
return mService.createDictAndProximity(mLocale);
|
||||||
|
@ -56,9 +103,9 @@ public class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convenience method
|
// Convenience method
|
||||||
public DictAndProximity takeOrGetNull() {
|
public DictAndProximity pollWithDefaultTimeout() {
|
||||||
try {
|
try {
|
||||||
return take();
|
return poll(TIMEOUT, TimeUnit.SECONDS);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -78,7 +125,7 @@ public class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> {
|
||||||
public boolean offer(final DictAndProximity dict) {
|
public boolean offer(final DictAndProximity dict) {
|
||||||
if (mClosed) {
|
if (mClosed) {
|
||||||
dict.mDictionary.close();
|
dict.mDictionary.close();
|
||||||
return false;
|
return super.offer(dummyDict);
|
||||||
} else {
|
} else {
|
||||||
return super.offer(dict);
|
return super.offer(dict);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,14 +16,15 @@
|
||||||
|
|
||||||
package com.android.inputmethod.latin.spellcheck;
|
package com.android.inputmethod.latin.spellcheck;
|
||||||
|
|
||||||
import com.android.inputmethod.keyboard.KeyDetector;
|
|
||||||
import com.android.inputmethod.keyboard.ProximityInfo;
|
import com.android.inputmethod.keyboard.ProximityInfo;
|
||||||
|
import com.android.inputmethod.latin.CollectionUtils;
|
||||||
|
import com.android.inputmethod.latin.Constants;
|
||||||
|
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
|
||||||
public class SpellCheckerProximityInfo {
|
public class SpellCheckerProximityInfo {
|
||||||
/* public for test */
|
/* public for test */
|
||||||
final public static int NUL = KeyDetector.NOT_A_CODE;
|
final public static int NUL = Constants.NOT_A_CODE;
|
||||||
|
|
||||||
// This must be the same as MAX_PROXIMITY_CHARS_SIZE else it will not work inside
|
// This must be the same as MAX_PROXIMITY_CHARS_SIZE else it will not work inside
|
||||||
// native code - this value is passed at creation of the binary object and reused
|
// native code - this value is passed at creation of the binary object and reused
|
||||||
|
@ -59,7 +60,7 @@ public class SpellCheckerProximityInfo {
|
||||||
// character.
|
// character.
|
||||||
// Since we need to build such an array, we want to be able to search in our big proximity
|
// Since we need to build such an array, we want to be able to search in our big proximity
|
||||||
// data quickly by character, and a map is probably the best way to do this.
|
// data quickly by character, and a map is probably the best way to do this.
|
||||||
final private static TreeMap<Integer, Integer> INDICES = new TreeMap<Integer, Integer>();
|
final private static TreeMap<Integer, Integer> INDICES = CollectionUtils.newTreeMap();
|
||||||
|
|
||||||
// The proximity here is the union of
|
// The proximity here is the union of
|
||||||
// - the proximity for a QWERTY keyboard.
|
// - the proximity for a QWERTY keyboard.
|
||||||
|
@ -111,6 +112,7 @@ public class SpellCheckerProximityInfo {
|
||||||
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
|
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
|
||||||
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
|
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
|
||||||
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
|
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
|
||||||
|
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
|
||||||
};
|
};
|
||||||
static {
|
static {
|
||||||
buildProximityIndices(PROXIMITY, INDICES);
|
buildProximityIndices(PROXIMITY, INDICES);
|
||||||
|
@ -121,7 +123,7 @@ public class SpellCheckerProximityInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Cyrillic {
|
private static class Cyrillic {
|
||||||
final private static TreeMap<Integer, Integer> INDICES = new TreeMap<Integer, Integer>();
|
final private static TreeMap<Integer, Integer> INDICES = CollectionUtils.newTreeMap();
|
||||||
// TODO: The following table is solely based on the keyboard layout. Consult with Russian
|
// TODO: The following table is solely based on the keyboard layout. Consult with Russian
|
||||||
// speakers on commonly misspelled words/letters.
|
// speakers on commonly misspelled words/letters.
|
||||||
final static int[] PROXIMITY = {
|
final static int[] PROXIMITY = {
|
||||||
|
|
|
@ -58,6 +58,7 @@ import com.android.inputmethod.keyboard.MoreKeysPanel;
|
||||||
import com.android.inputmethod.keyboard.PointerTracker;
|
import com.android.inputmethod.keyboard.PointerTracker;
|
||||||
import com.android.inputmethod.keyboard.ViewLayoutUtils;
|
import com.android.inputmethod.keyboard.ViewLayoutUtils;
|
||||||
import com.android.inputmethod.latin.AutoCorrection;
|
import com.android.inputmethod.latin.AutoCorrection;
|
||||||
|
import com.android.inputmethod.latin.CollectionUtils;
|
||||||
import com.android.inputmethod.latin.LatinImeLogger;
|
import com.android.inputmethod.latin.LatinImeLogger;
|
||||||
import com.android.inputmethod.latin.R;
|
import com.android.inputmethod.latin.R;
|
||||||
import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
|
import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
|
||||||
|
@ -72,7 +73,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
|
||||||
OnLongClickListener {
|
OnLongClickListener {
|
||||||
public interface Listener {
|
public interface Listener {
|
||||||
public boolean addWordToUserDictionary(String word);
|
public boolean addWordToUserDictionary(String word);
|
||||||
public void pickSuggestionManually(int index, CharSequence word, int x, int y);
|
public void pickSuggestionManually(int index, CharSequence word);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The maximum number of suggestions available. See {@link Suggest#mPrefMaxSuggestions}.
|
// The maximum number of suggestions available. See {@link Suggest#mPrefMaxSuggestions}.
|
||||||
|
@ -88,9 +89,9 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
|
||||||
private final MoreSuggestions.Builder mMoreSuggestionsBuilder;
|
private final MoreSuggestions.Builder mMoreSuggestionsBuilder;
|
||||||
private final PopupWindow mMoreSuggestionsWindow;
|
private final PopupWindow mMoreSuggestionsWindow;
|
||||||
|
|
||||||
private final ArrayList<TextView> mWords = new ArrayList<TextView>();
|
private final ArrayList<TextView> mWords = CollectionUtils.newArrayList();
|
||||||
private final ArrayList<TextView> mInfos = new ArrayList<TextView>();
|
private final ArrayList<TextView> mInfos = CollectionUtils.newArrayList();
|
||||||
private final ArrayList<View> mDividers = new ArrayList<View>();
|
private final ArrayList<View> mDividers = CollectionUtils.newArrayList();
|
||||||
|
|
||||||
private final PopupWindow mPreviewPopup;
|
private final PopupWindow mPreviewPopup;
|
||||||
private final TextView mPreviewText;
|
private final TextView mPreviewText;
|
||||||
|
@ -131,7 +132,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
|
||||||
|
|
||||||
private static class SuggestionStripViewParams {
|
private static class SuggestionStripViewParams {
|
||||||
private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3;
|
private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3;
|
||||||
private static final int DEFAULT_CENTER_SUGGESTION_PERCENTILE = 40;
|
private static final float DEFAULT_CENTER_SUGGESTION_PERCENTILE = 0.40f;
|
||||||
private static final int DEFAULT_MAX_MORE_SUGGESTIONS_ROW = 2;
|
private static final int DEFAULT_MAX_MORE_SUGGESTIONS_ROW = 2;
|
||||||
private static final int PUNCTUATIONS_IN_STRIP = 5;
|
private static final int PUNCTUATIONS_IN_STRIP = 5;
|
||||||
|
|
||||||
|
@ -167,7 +168,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
|
||||||
|
|
||||||
private final int mSuggestionStripOption;
|
private final int mSuggestionStripOption;
|
||||||
|
|
||||||
private final ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>();
|
private final ArrayList<CharSequence> mTexts = CollectionUtils.newArrayList();
|
||||||
|
|
||||||
public boolean mMoreSuggestionsAvailable;
|
public boolean mMoreSuggestionsAvailable;
|
||||||
|
|
||||||
|
@ -195,16 +196,16 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
|
||||||
R.styleable.SuggestionStripView, defStyle, R.style.SuggestionStripViewStyle);
|
R.styleable.SuggestionStripView, defStyle, R.style.SuggestionStripViewStyle);
|
||||||
mSuggestionStripOption = a.getInt(
|
mSuggestionStripOption = a.getInt(
|
||||||
R.styleable.SuggestionStripView_suggestionStripOption, 0);
|
R.styleable.SuggestionStripView_suggestionStripOption, 0);
|
||||||
final float alphaValidTypedWord = getPercent(a,
|
final float alphaValidTypedWord = getFraction(a,
|
||||||
R.styleable.SuggestionStripView_alphaValidTypedWord, 100);
|
R.styleable.SuggestionStripView_alphaValidTypedWord, 1.0f);
|
||||||
final float alphaTypedWord = getPercent(a,
|
final float alphaTypedWord = getFraction(a,
|
||||||
R.styleable.SuggestionStripView_alphaTypedWord, 100);
|
R.styleable.SuggestionStripView_alphaTypedWord, 1.0f);
|
||||||
final float alphaAutoCorrect = getPercent(a,
|
final float alphaAutoCorrect = getFraction(a,
|
||||||
R.styleable.SuggestionStripView_alphaAutoCorrect, 100);
|
R.styleable.SuggestionStripView_alphaAutoCorrect, 1.0f);
|
||||||
final float alphaSuggested = getPercent(a,
|
final float alphaSuggested = getFraction(a,
|
||||||
R.styleable.SuggestionStripView_alphaSuggested, 100);
|
R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
|
||||||
mAlphaObsoleted = getPercent(a,
|
mAlphaObsoleted = getFraction(a,
|
||||||
R.styleable.SuggestionStripView_alphaSuggested, 100);
|
R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
|
||||||
mColorValidTypedWord = applyAlpha(a.getColor(
|
mColorValidTypedWord = applyAlpha(a.getColor(
|
||||||
R.styleable.SuggestionStripView_colorValidTypedWord, 0), alphaValidTypedWord);
|
R.styleable.SuggestionStripView_colorValidTypedWord, 0), alphaValidTypedWord);
|
||||||
mColorTypedWord = applyAlpha(a.getColor(
|
mColorTypedWord = applyAlpha(a.getColor(
|
||||||
|
@ -216,14 +217,14 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
|
||||||
mSuggestionsCountInStrip = a.getInt(
|
mSuggestionsCountInStrip = a.getInt(
|
||||||
R.styleable.SuggestionStripView_suggestionsCountInStrip,
|
R.styleable.SuggestionStripView_suggestionsCountInStrip,
|
||||||
DEFAULT_SUGGESTIONS_COUNT_IN_STRIP);
|
DEFAULT_SUGGESTIONS_COUNT_IN_STRIP);
|
||||||
mCenterSuggestionWeight = getPercent(a,
|
mCenterSuggestionWeight = getFraction(a,
|
||||||
R.styleable.SuggestionStripView_centerSuggestionPercentile,
|
R.styleable.SuggestionStripView_centerSuggestionPercentile,
|
||||||
DEFAULT_CENTER_SUGGESTION_PERCENTILE);
|
DEFAULT_CENTER_SUGGESTION_PERCENTILE);
|
||||||
mMaxMoreSuggestionsRow = a.getInt(
|
mMaxMoreSuggestionsRow = a.getInt(
|
||||||
R.styleable.SuggestionStripView_maxMoreSuggestionsRow,
|
R.styleable.SuggestionStripView_maxMoreSuggestionsRow,
|
||||||
DEFAULT_MAX_MORE_SUGGESTIONS_ROW);
|
DEFAULT_MAX_MORE_SUGGESTIONS_ROW);
|
||||||
mMinMoreSuggestionsWidth = getRatio(a,
|
mMinMoreSuggestionsWidth = getFraction(a,
|
||||||
R.styleable.SuggestionStripView_minMoreSuggestionsWidth);
|
R.styleable.SuggestionStripView_minMoreSuggestionsWidth, 1.0f);
|
||||||
a.recycle();
|
a.recycle();
|
||||||
|
|
||||||
mMoreSuggestionsHint = getMoreSuggestionsHint(res,
|
mMoreSuggestionsHint = getMoreSuggestionsHint(res,
|
||||||
|
@ -277,14 +278,8 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
|
||||||
return new BitmapDrawable(res, buffer);
|
return new BitmapDrawable(res, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read integer value in TypedArray as percent.
|
static float getFraction(final TypedArray a, final int index, final float defValue) {
|
||||||
private static float getPercent(TypedArray a, int index, int defValue) {
|
return a.getFraction(index, 1, 1, defValue);
|
||||||
return a.getInt(index, defValue) / 100.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read fraction value in TypedArray as float.
|
|
||||||
private static float getRatio(TypedArray a, int index) {
|
|
||||||
return a.getFraction(index, 1000, 1000, 1) / 1000.0f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private CharSequence getStyledSuggestionWord(SuggestedWords suggestedWords, int pos) {
|
private CharSequence getStyledSuggestionWord(SuggestedWords suggestedWords, int pos) {
|
||||||
|
@ -726,9 +721,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
|
||||||
public boolean onCustomRequest(int requestCode) {
|
public boolean onCustomRequest(int requestCode) {
|
||||||
final int index = requestCode;
|
final int index = requestCode;
|
||||||
final CharSequence word = mSuggestedWords.getWord(index);
|
final CharSequence word = mSuggestedWords.getWord(index);
|
||||||
// TODO: change caller path so coordinates are passed through here
|
mListener.pickSuggestionManually(index, word);
|
||||||
mListener.pickSuggestionManually(index, word, NOT_A_TOUCH_COORDINATE,
|
|
||||||
NOT_A_TOUCH_COORDINATE);
|
|
||||||
dismissMoreSuggestions();
|
dismissMoreSuggestions();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -874,7 +867,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
|
||||||
return;
|
return;
|
||||||
|
|
||||||
final CharSequence word = mSuggestedWords.getWord(index);
|
final CharSequence word = mSuggestedWords.getWord(index);
|
||||||
mListener.pickSuggestionManually(index, word, mLastX, mLastY);
|
mListener.pickSuggestionManually(index, word);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,10 +18,7 @@ package com.android.inputmethod.research;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Editable;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
import android.widget.EditText;
|
|
||||||
|
|
||||||
import com.android.inputmethod.latin.R;
|
import com.android.inputmethod.latin.R;
|
||||||
|
|
||||||
|
@ -31,6 +28,11 @@ public class FeedbackActivity extends Activity {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.research_feedback_activity);
|
setContentView(R.layout.research_feedback_activity);
|
||||||
final FeedbackLayout layout = (FeedbackLayout) findViewById(R.id.research_feedback_layout);
|
final FeedbackLayout layout = (FeedbackLayout) findViewById(R.id.research_feedback_layout);
|
||||||
|
final CheckBox checkbox = (CheckBox) findViewById(R.id.research_feedback_include_history);
|
||||||
|
final CharSequence cs = checkbox.getText();
|
||||||
|
final String actualString = String.format(cs.toString(),
|
||||||
|
ResearchLogger.FEEDBACK_WORD_BUFFER_SIZE);
|
||||||
|
checkbox.setText(actualString);
|
||||||
layout.setActivity(this);
|
layout.setActivity(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 */);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,7 +26,6 @@ import com.android.inputmethod.keyboard.Key;
|
||||||
import com.android.inputmethod.latin.SuggestedWords;
|
import com.android.inputmethod.latin.SuggestedWords;
|
||||||
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
|
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
|
||||||
import com.android.inputmethod.latin.define.ProductionFlag;
|
import com.android.inputmethod.latin.define.ProductionFlag;
|
||||||
import com.android.inputmethod.research.ResearchLogger.LogUnit;
|
|
||||||
|
|
||||||
import java.io.BufferedWriter;
|
import java.io.BufferedWriter;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -37,6 +36,7 @@ import java.io.OutputStreamWriter;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
@ -51,21 +51,22 @@ import java.util.concurrent.TimeUnit;
|
||||||
*/
|
*/
|
||||||
public class ResearchLog {
|
public class ResearchLog {
|
||||||
private static final String TAG = ResearchLog.class.getSimpleName();
|
private static final String TAG = ResearchLog.class.getSimpleName();
|
||||||
private static final JsonWriter NULL_JSON_WRITER = new JsonWriter(
|
private static final boolean DEBUG = false;
|
||||||
new OutputStreamWriter(new NullOutputStream()));
|
private static final long FLUSH_DELAY_IN_MS = 1000 * 5;
|
||||||
|
private static final int ABORT_TIMEOUT_IN_MS = 1000 * 4;
|
||||||
|
|
||||||
final ScheduledExecutorService mExecutor;
|
/* package */ final ScheduledExecutorService mExecutor;
|
||||||
/* package */ final File mFile;
|
/* package */ final File mFile;
|
||||||
private JsonWriter mJsonWriter = NULL_JSON_WRITER;
|
private JsonWriter mJsonWriter = NULL_JSON_WRITER;
|
||||||
|
// true if at least one byte of data has been written out to the log file. This must be
|
||||||
|
// remembered because JsonWriter requires that calls matching calls to beginObject and
|
||||||
|
// endObject, as well as beginArray and endArray, and the file is opened lazily, only when
|
||||||
|
// it is certain that data will be written. Alternatively, the matching call exceptions
|
||||||
|
// could be caught, but this might suppress other errors.
|
||||||
|
private boolean mHasWrittenData = false;
|
||||||
|
|
||||||
private int mLoggingState;
|
private static final JsonWriter NULL_JSON_WRITER = new JsonWriter(
|
||||||
private static final int LOGGING_STATE_UNSTARTED = 0;
|
new OutputStreamWriter(new NullOutputStream()));
|
||||||
private static final int LOGGING_STATE_READY = 1; // don't create file until necessary
|
|
||||||
private static final int LOGGING_STATE_RUNNING = 2;
|
|
||||||
private static final int LOGGING_STATE_STOPPING = 3;
|
|
||||||
private static final int LOGGING_STATE_STOPPED = 4;
|
|
||||||
private static final long FLUSH_DELAY_IN_MS = 1000 * 5;
|
|
||||||
|
|
||||||
private static class NullOutputStream extends OutputStream {
|
private static class NullOutputStream extends OutputStream {
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
@Override
|
@Override
|
||||||
|
@ -84,128 +85,81 @@ public class ResearchLog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResearchLog(File outputFile) {
|
public ResearchLog(final File outputFile) {
|
||||||
mExecutor = Executors.newSingleThreadScheduledExecutor();
|
|
||||||
if (outputFile == null) {
|
if (outputFile == null) {
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
}
|
}
|
||||||
|
mExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||||
mFile = outputFile;
|
mFile = outputFile;
|
||||||
mLoggingState = LOGGING_STATE_UNSTARTED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void start() throws IOException {
|
public synchronized void close() {
|
||||||
switch (mLoggingState) {
|
|
||||||
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>() {
|
mExecutor.submit(new Callable<Object>() {
|
||||||
@Override
|
@Override
|
||||||
public Object call() throws Exception {
|
public Object call() throws Exception {
|
||||||
try {
|
try {
|
||||||
|
if (mHasWrittenData) {
|
||||||
mJsonWriter.endArray();
|
mJsonWriter.endArray();
|
||||||
mJsonWriter.flush();
|
mJsonWriter.flush();
|
||||||
mJsonWriter.close();
|
mJsonWriter.close();
|
||||||
|
mHasWrittenData = false;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.d(TAG, "error when closing ResearchLog:");
|
||||||
|
e.printStackTrace();
|
||||||
} finally {
|
} finally {
|
||||||
boolean success = mFile.setWritable(false, false);
|
if (mFile.exists()) {
|
||||||
mLoggingState = LOGGING_STATE_STOPPED;
|
mFile.setWritable(false, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
removeAnyScheduledFlush();
|
removeAnyScheduledFlush();
|
||||||
mExecutor.shutdown();
|
mExecutor.shutdown();
|
||||||
mLoggingState = LOGGING_STATE_STOPPING;
|
|
||||||
break;
|
|
||||||
case LOGGING_STATE_STOPPING:
|
|
||||||
case LOGGING_STATE_STOPPED:
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAlive() {
|
private boolean mIsAbortSuccessful;
|
||||||
switch (mLoggingState) {
|
|
||||||
case LOGGING_STATE_UNSTARTED:
|
|
||||||
case LOGGING_STATE_READY:
|
|
||||||
case LOGGING_STATE_RUNNING:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void waitUntilStopped(final int timeoutInMs) throws InterruptedException {
|
|
||||||
removeAnyScheduledFlush();
|
|
||||||
mExecutor.shutdown();
|
|
||||||
mExecutor.awaitTermination(timeoutInMs, TimeUnit.MILLISECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void abort() {
|
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>() {
|
mExecutor.submit(new Callable<Object>() {
|
||||||
@Override
|
@Override
|
||||||
public Object call() throws Exception {
|
public Object call() throws Exception {
|
||||||
try {
|
try {
|
||||||
|
if (mHasWrittenData) {
|
||||||
mJsonWriter.endArray();
|
mJsonWriter.endArray();
|
||||||
mJsonWriter.close();
|
mJsonWriter.close();
|
||||||
|
mHasWrittenData = false;
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
isAbortSuccessful = mFile.delete();
|
mIsAbortSuccessful = mFile.delete();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
removeAnyScheduledFlush();
|
removeAnyScheduledFlush();
|
||||||
mExecutor.shutdown();
|
mExecutor.shutdown();
|
||||||
mLoggingState = LOGGING_STATE_STOPPING;
|
|
||||||
break;
|
|
||||||
case LOGGING_STATE_STOPPING:
|
|
||||||
case LOGGING_STATE_STOPPED:
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isAbortSuccessful;
|
public boolean blockingAbort() throws InterruptedException {
|
||||||
public boolean isAbortSuccessful() {
|
abort();
|
||||||
return isAbortSuccessful;
|
mExecutor.awaitTermination(ABORT_TIMEOUT_IN_MS, TimeUnit.MILLISECONDS);
|
||||||
|
return mIsAbortSuccessful;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void awaitTermination(int delay, TimeUnit timeUnit) throws InterruptedException {
|
||||||
|
mExecutor.awaitTermination(delay, timeUnit);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ synchronized void flush() {
|
/* package */ synchronized void flush() {
|
||||||
switch (mLoggingState) {
|
|
||||||
case LOGGING_STATE_UNSTARTED:
|
|
||||||
break;
|
|
||||||
case LOGGING_STATE_READY:
|
|
||||||
case LOGGING_STATE_RUNNING:
|
|
||||||
removeAnyScheduledFlush();
|
removeAnyScheduledFlush();
|
||||||
mExecutor.submit(mFlushCallable);
|
mExecutor.submit(mFlushCallable);
|
||||||
break;
|
|
||||||
case LOGGING_STATE_STOPPING:
|
|
||||||
case LOGGING_STATE_STOPPED:
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Callable<Object> mFlushCallable = new Callable<Object>() {
|
private final Callable<Object> mFlushCallable = new Callable<Object>() {
|
||||||
@Override
|
@Override
|
||||||
public Object call() throws Exception {
|
public Object call() throws Exception {
|
||||||
if (mLoggingState == LOGGING_STATE_RUNNING) {
|
|
||||||
mJsonWriter.flush();
|
mJsonWriter.flush();
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -224,56 +178,40 @@ public class ResearchLog {
|
||||||
mFlushFuture = mExecutor.schedule(mFlushCallable, FLUSH_DELAY_IN_MS, TimeUnit.MILLISECONDS);
|
mFlushFuture = mExecutor.schedule(mFlushCallable, FLUSH_DELAY_IN_MS, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void publishPublicEvents(final LogUnit logUnit) {
|
public synchronized void publish(final LogUnit logUnit, final boolean isIncludingPrivateData) {
|
||||||
switch (mLoggingState) {
|
try {
|
||||||
case LOGGING_STATE_UNSTARTED:
|
|
||||||
break;
|
|
||||||
case LOGGING_STATE_READY:
|
|
||||||
case LOGGING_STATE_RUNNING:
|
|
||||||
mExecutor.submit(new Callable<Object>() {
|
mExecutor.submit(new Callable<Object>() {
|
||||||
@Override
|
@Override
|
||||||
public Object call() throws Exception {
|
public Object call() throws Exception {
|
||||||
logUnit.publishPublicEventsTo(ResearchLog.this);
|
logUnit.publishTo(ResearchLog.this, isIncludingPrivateData);
|
||||||
scheduleFlush();
|
scheduleFlush();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
break;
|
} catch (RejectedExecutionException e) {
|
||||||
case LOGGING_STATE_STOPPING:
|
// TODO: Add code to record loss of data, and report.
|
||||||
case LOGGING_STATE_STOPPED:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void publishAllEvents(final LogUnit logUnit) {
|
|
||||||
switch (mLoggingState) {
|
|
||||||
case LOGGING_STATE_UNSTARTED:
|
|
||||||
break;
|
|
||||||
case LOGGING_STATE_READY:
|
|
||||||
case LOGGING_STATE_RUNNING:
|
|
||||||
mExecutor.submit(new Callable<Object>() {
|
|
||||||
@Override
|
|
||||||
public Object call() throws Exception {
|
|
||||||
logUnit.publishAllEventsTo(ResearchLog.this);
|
|
||||||
scheduleFlush();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case LOGGING_STATE_STOPPING:
|
|
||||||
case LOGGING_STATE_STOPPED:
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String CURRENT_TIME_KEY = "_ct";
|
private static final String CURRENT_TIME_KEY = "_ct";
|
||||||
private static final String UPTIME_KEY = "_ut";
|
private static final String UPTIME_KEY = "_ut";
|
||||||
private static final String EVENT_TYPE_KEY = "_ty";
|
private static final String EVENT_TYPE_KEY = "_ty";
|
||||||
|
|
||||||
void outputEvent(final String[] keys, final Object[] values) {
|
void outputEvent(final String[] keys, final Object[] values) {
|
||||||
// not thread safe.
|
// Not thread safe.
|
||||||
|
if (keys.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (DEBUG) {
|
||||||
|
if (keys.length != values.length + 1) {
|
||||||
|
Log.d(TAG, "Key and Value list sizes do not match. " + keys[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
if (mJsonWriter == NULL_JSON_WRITER) {
|
if (mJsonWriter == NULL_JSON_WRITER) {
|
||||||
mJsonWriter = new JsonWriter(new BufferedWriter(new FileWriter(mFile)));
|
mJsonWriter = new JsonWriter(new BufferedWriter(new FileWriter(mFile)));
|
||||||
mJsonWriter.setLenient(true);
|
|
||||||
mJsonWriter.beginArray();
|
mJsonWriter.beginArray();
|
||||||
|
mHasWrittenData = true;
|
||||||
}
|
}
|
||||||
mJsonWriter.beginObject();
|
mJsonWriter.beginObject();
|
||||||
mJsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis());
|
mJsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis());
|
||||||
|
@ -283,8 +221,8 @@ public class ResearchLog {
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
mJsonWriter.name(keys[i + 1]);
|
mJsonWriter.name(keys[i + 1]);
|
||||||
Object value = values[i];
|
Object value = values[i];
|
||||||
if (value instanceof String) {
|
if (value instanceof CharSequence) {
|
||||||
mJsonWriter.value((String) value);
|
mJsonWriter.value(value.toString());
|
||||||
} else if (value instanceof Number) {
|
} else if (value instanceof Number) {
|
||||||
mJsonWriter.value((Number) value);
|
mJsonWriter.value((Number) value);
|
||||||
} else if (value instanceof Boolean) {
|
} else if (value instanceof Boolean) {
|
||||||
|
@ -331,14 +269,11 @@ public class ResearchLog {
|
||||||
SuggestedWords words = (SuggestedWords) value;
|
SuggestedWords words = (SuggestedWords) value;
|
||||||
mJsonWriter.beginObject();
|
mJsonWriter.beginObject();
|
||||||
mJsonWriter.name("typedWordValid").value(words.mTypedWordValid);
|
mJsonWriter.name("typedWordValid").value(words.mTypedWordValid);
|
||||||
mJsonWriter.name("willAutoCorrect")
|
mJsonWriter.name("willAutoCorrect").value(words.mWillAutoCorrect);
|
||||||
.value(words.mWillAutoCorrect);
|
|
||||||
mJsonWriter.name("isPunctuationSuggestions")
|
mJsonWriter.name("isPunctuationSuggestions")
|
||||||
.value(words.mIsPunctuationSuggestions);
|
.value(words.mIsPunctuationSuggestions);
|
||||||
mJsonWriter.name("isObsoleteSuggestions")
|
mJsonWriter.name("isObsoleteSuggestions").value(words.mIsObsoleteSuggestions);
|
||||||
.value(words.mIsObsoleteSuggestions);
|
mJsonWriter.name("isPrediction").value(words.mIsPrediction);
|
||||||
mJsonWriter.name("isPrediction")
|
|
||||||
.value(words.mIsPrediction);
|
|
||||||
mJsonWriter.name("words");
|
mJsonWriter.name("words");
|
||||||
mJsonWriter.beginArray();
|
mJsonWriter.beginArray();
|
||||||
final int size = words.size();
|
final int size = words.size();
|
||||||
|
@ -363,8 +298,8 @@ public class ResearchLog {
|
||||||
try {
|
try {
|
||||||
mJsonWriter.close();
|
mJsonWriter.close();
|
||||||
} catch (IllegalStateException e1) {
|
} catch (IllegalStateException e1) {
|
||||||
// assume that this is just the json not being terminated properly.
|
// Assume that this is just the json not being terminated properly.
|
||||||
// ignore
|
// Ignore
|
||||||
} catch (IOException e1) {
|
} catch (IOException e1) {
|
||||||
e1.printStackTrace();
|
e1.printStackTrace();
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -1,223 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2012 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.inputmethod.research;
|
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.IntentFilter;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.net.ConnectivityManager;
|
|
||||||
import android.net.NetworkInfo;
|
|
||||||
import android.os.BatteryManager;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.android.inputmethod.latin.R;
|
|
||||||
import com.android.inputmethod.latin.R.string;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileFilter;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
public final class ResearchLogUploader {
|
|
||||||
private static final String TAG = ResearchLogUploader.class.getSimpleName();
|
|
||||||
private static final int UPLOAD_INTERVAL_IN_MS = 1000 * 60 * 15; // every 15 min
|
|
||||||
private static final int BUF_SIZE = 1024 * 8;
|
|
||||||
|
|
||||||
private final boolean mCanUpload;
|
|
||||||
private final Context mContext;
|
|
||||||
private final File mFilesDir;
|
|
||||||
private final URL mUrl;
|
|
||||||
private final ScheduledExecutorService mExecutor;
|
|
||||||
|
|
||||||
private Runnable doUploadRunnable = new UploadRunnable(null, false);
|
|
||||||
|
|
||||||
public ResearchLogUploader(final Context context, final File filesDir) {
|
|
||||||
mContext = context;
|
|
||||||
mFilesDir = filesDir;
|
|
||||||
final PackageManager packageManager = context.getPackageManager();
|
|
||||||
final boolean hasPermission = packageManager.checkPermission(Manifest.permission.INTERNET,
|
|
||||||
context.getPackageName()) == PackageManager.PERMISSION_GRANTED;
|
|
||||||
if (!hasPermission) {
|
|
||||||
mCanUpload = false;
|
|
||||||
mUrl = null;
|
|
||||||
mExecutor = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
URL tempUrl = null;
|
|
||||||
boolean canUpload = false;
|
|
||||||
ScheduledExecutorService executor = null;
|
|
||||||
try {
|
|
||||||
final String urlString = context.getString(R.string.research_logger_upload_url);
|
|
||||||
if (urlString == null || urlString.equals("")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
tempUrl = new URL(urlString);
|
|
||||||
canUpload = true;
|
|
||||||
executor = Executors.newSingleThreadScheduledExecutor();
|
|
||||||
} catch (MalformedURLException e) {
|
|
||||||
tempUrl = null;
|
|
||||||
e.printStackTrace();
|
|
||||||
return;
|
|
||||||
} finally {
|
|
||||||
mCanUpload = canUpload;
|
|
||||||
mUrl = tempUrl;
|
|
||||||
mExecutor = executor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start() {
|
|
||||||
if (mCanUpload) {
|
|
||||||
Log.d(TAG, "scheduling regular uploading");
|
|
||||||
mExecutor.scheduleWithFixedDelay(doUploadRunnable, UPLOAD_INTERVAL_IN_MS,
|
|
||||||
UPLOAD_INTERVAL_IN_MS, TimeUnit.MILLISECONDS);
|
|
||||||
} else {
|
|
||||||
Log.d(TAG, "no permission to upload");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void uploadNow(final Callback callback) {
|
|
||||||
// Perform an immediate upload. Note that this should happen even if there is
|
|
||||||
// another upload happening right now, as it may have missed the latest changes.
|
|
||||||
// TODO: Reschedule regular upload tests starting from now.
|
|
||||||
if (mCanUpload) {
|
|
||||||
mExecutor.submit(new UploadRunnable(callback, true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface Callback {
|
|
||||||
public void onUploadCompleted(final boolean success);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isExternallyPowered() {
|
|
||||||
final Intent intent = mContext.registerReceiver(null, new IntentFilter(
|
|
||||||
Intent.ACTION_BATTERY_CHANGED));
|
|
||||||
final int pluggedState = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
|
|
||||||
return pluggedState == BatteryManager.BATTERY_PLUGGED_AC
|
|
||||||
|| pluggedState == BatteryManager.BATTERY_PLUGGED_USB;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasWifiConnection() {
|
|
||||||
final ConnectivityManager manager =
|
|
||||||
(ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
|
|
||||||
final NetworkInfo wifiInfo = manager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
|
|
||||||
return wifiInfo.isConnected();
|
|
||||||
}
|
|
||||||
|
|
||||||
class UploadRunnable implements Runnable {
|
|
||||||
private final Callback mCallback;
|
|
||||||
private final boolean mForceUpload;
|
|
||||||
|
|
||||||
public UploadRunnable(final Callback callback, final boolean forceUpload) {
|
|
||||||
mCallback = callback;
|
|
||||||
mForceUpload = forceUpload;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
doUpload();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doUpload() {
|
|
||||||
if (!mForceUpload && (!isExternallyPowered() || !hasWifiConnection())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (mFilesDir == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final File[] files = mFilesDir.listFiles(new FileFilter() {
|
|
||||||
@Override
|
|
||||||
public boolean accept(File pathname) {
|
|
||||||
return pathname.getName().startsWith(ResearchLogger.FILENAME_PREFIX)
|
|
||||||
&& !pathname.canWrite();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
boolean success = true;
|
|
||||||
if (files.length == 0) {
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
for (final File file : files) {
|
|
||||||
if (!uploadFile(file)) {
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (mCallback != null) {
|
|
||||||
mCallback.onUploadCompleted(success);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean uploadFile(File file) {
|
|
||||||
Log.d(TAG, "attempting upload of " + file.getAbsolutePath());
|
|
||||||
boolean success = false;
|
|
||||||
final int contentLength = (int) file.length();
|
|
||||||
HttpURLConnection connection = null;
|
|
||||||
InputStream fileIs = null;
|
|
||||||
try {
|
|
||||||
fileIs = new FileInputStream(file);
|
|
||||||
connection = (HttpURLConnection) mUrl.openConnection();
|
|
||||||
connection.setRequestMethod("PUT");
|
|
||||||
connection.setDoOutput(true);
|
|
||||||
connection.setFixedLengthStreamingMode(contentLength);
|
|
||||||
final OutputStream os = connection.getOutputStream();
|
|
||||||
final byte[] buf = new byte[BUF_SIZE];
|
|
||||||
int numBytesRead;
|
|
||||||
while ((numBytesRead = fileIs.read(buf)) != -1) {
|
|
||||||
os.write(buf, 0, numBytesRead);
|
|
||||||
}
|
|
||||||
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
|
||||||
Log.d(TAG, "upload failed: " + connection.getResponseCode());
|
|
||||||
InputStream netIs = connection.getInputStream();
|
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(netIs));
|
|
||||||
String line;
|
|
||||||
while ((line = reader.readLine()) != null) {
|
|
||||||
Log.d(TAG, "| " + reader.readLine());
|
|
||||||
}
|
|
||||||
reader.close();
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
file.delete();
|
|
||||||
success = true;
|
|
||||||
Log.d(TAG, "upload successful");
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} finally {
|
|
||||||
if (fileIs != null) {
|
|
||||||
try {
|
|
||||||
fileIs.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (connection != null) {
|
|
||||||
connection.disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -36,7 +36,7 @@ LOCAL_CFLAGS += -Wno-unused-parameter -Wno-unused-function
|
||||||
LATIN_IME_JNI_SRC_FILES := \
|
LATIN_IME_JNI_SRC_FILES := \
|
||||||
com_android_inputmethod_keyboard_ProximityInfo.cpp \
|
com_android_inputmethod_keyboard_ProximityInfo.cpp \
|
||||||
com_android_inputmethod_latin_BinaryDictionary.cpp \
|
com_android_inputmethod_latin_BinaryDictionary.cpp \
|
||||||
com_android_inputmethod_latin_NativeUtils.cpp \
|
com_android_inputmethod_latin_DicTraverseSession.cpp \
|
||||||
jni_common.cpp
|
jni_common.cpp
|
||||||
|
|
||||||
LATIN_IME_CORE_SRC_FILES := \
|
LATIN_IME_CORE_SRC_FILES := \
|
||||||
|
@ -46,6 +46,7 @@ LATIN_IME_CORE_SRC_FILES := \
|
||||||
char_utils.cpp \
|
char_utils.cpp \
|
||||||
correction.cpp \
|
correction.cpp \
|
||||||
dictionary.cpp \
|
dictionary.cpp \
|
||||||
|
dic_traverse_wrapper.cpp \
|
||||||
proximity_info.cpp \
|
proximity_info.cpp \
|
||||||
proximity_info_state.cpp \
|
proximity_info_state.cpp \
|
||||||
unigram_dictionary.cpp \
|
unigram_dictionary.cpp \
|
||||||
|
|
|
@ -16,8 +16,6 @@
|
||||||
|
|
||||||
#define LOG_TAG "LatinIME: jni: ProximityInfo"
|
#define LOG_TAG "LatinIME: jni: ProximityInfo"
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "com_android_inputmethod_keyboard_ProximityInfo.h"
|
#include "com_android_inputmethod_keyboard_ProximityInfo.h"
|
||||||
#include "jni.h"
|
#include "jni.h"
|
||||||
#include "jni_common.h"
|
#include "jni_common.h"
|
||||||
|
@ -26,53 +24,27 @@
|
||||||
namespace latinime {
|
namespace latinime {
|
||||||
|
|
||||||
static jlong latinime_Keyboard_setProximityInfo(JNIEnv *env, jobject object,
|
static jlong latinime_Keyboard_setProximityInfo(JNIEnv *env, jobject object,
|
||||||
jstring localejStr, jint maxProximityCharsSize, jint displayWidth, jint displayHeight,
|
jstring localeJStr, jint maxProximityCharsSize, jint displayWidth, jint displayHeight,
|
||||||
jint gridWidth, jint gridHeight, jint mostCommonkeyWidth, jintArray proximityCharsArray,
|
jint gridWidth, jint gridHeight, jint mostCommonkeyWidth, jintArray proximityChars,
|
||||||
jint keyCount, jintArray keyXCoordinateArray, jintArray keyYCoordinateArray,
|
jint keyCount, jintArray keyXCoordinates, jintArray keyYCoordinates,
|
||||||
jintArray keyWidthArray, jintArray keyHeightArray, jintArray keyCharCodeArray,
|
jintArray keyWidths, jintArray keyHeights, jintArray keyCharCodes,
|
||||||
jfloatArray sweetSpotCenterXArray, jfloatArray sweetSpotCenterYArray,
|
jfloatArray sweetSpotCenterXs, jfloatArray sweetSpotCenterYs, jfloatArray sweetSpotRadii) {
|
||||||
jfloatArray sweetSpotRadiusArray) {
|
ProximityInfo *proximityInfo = new ProximityInfo(env, localeJStr, maxProximityCharsSize,
|
||||||
const char *localeStrPtr = env->GetStringUTFChars(localejStr, 0);
|
displayWidth, displayHeight, gridWidth, gridHeight, mostCommonkeyWidth, proximityChars,
|
||||||
const std::string localeStr(localeStrPtr);
|
keyCount, keyXCoordinates, keyYCoordinates, keyWidths, keyHeights, keyCharCodes,
|
||||||
jint *proximityChars = env->GetIntArrayElements(proximityCharsArray, 0);
|
sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii);
|
||||||
jint *keyXCoordinates = safeGetIntArrayElements(env, keyXCoordinateArray);
|
return reinterpret_cast<jlong>(proximityInfo);
|
||||||
jint *keyYCoordinates = safeGetIntArrayElements(env, keyYCoordinateArray);
|
|
||||||
jint *keyWidths = safeGetIntArrayElements(env, keyWidthArray);
|
|
||||||
jint *keyHeights = safeGetIntArrayElements(env, keyHeightArray);
|
|
||||||
jint *keyCharCodes = safeGetIntArrayElements(env, keyCharCodeArray);
|
|
||||||
jfloat *sweetSpotCenterXs = safeGetFloatArrayElements(env, sweetSpotCenterXArray);
|
|
||||||
jfloat *sweetSpotCenterYs = safeGetFloatArrayElements(env, sweetSpotCenterYArray);
|
|
||||||
jfloat *sweetSpotRadii = safeGetFloatArrayElements(env, sweetSpotRadiusArray);
|
|
||||||
ProximityInfo *proximityInfo = new ProximityInfo(
|
|
||||||
localeStr, maxProximityCharsSize, displayWidth, displayHeight, gridWidth, gridHeight,
|
|
||||||
mostCommonkeyWidth, (const int32_t*)proximityChars, keyCount,
|
|
||||||
(const int32_t*)keyXCoordinates, (const int32_t*)keyYCoordinates,
|
|
||||||
(const int32_t*)keyWidths, (const int32_t*)keyHeights, (const int32_t*)keyCharCodes,
|
|
||||||
(const float*)sweetSpotCenterXs, (const float*)sweetSpotCenterYs,
|
|
||||||
(const float*)sweetSpotRadii);
|
|
||||||
safeReleaseFloatArrayElements(env, sweetSpotRadiusArray, sweetSpotRadii);
|
|
||||||
safeReleaseFloatArrayElements(env, sweetSpotCenterYArray, sweetSpotCenterYs);
|
|
||||||
safeReleaseFloatArrayElements(env, sweetSpotCenterXArray, sweetSpotCenterXs);
|
|
||||||
safeReleaseIntArrayElements(env, keyCharCodeArray, keyCharCodes);
|
|
||||||
safeReleaseIntArrayElements(env, keyHeightArray, keyHeights);
|
|
||||||
safeReleaseIntArrayElements(env, keyWidthArray, keyWidths);
|
|
||||||
safeReleaseIntArrayElements(env, keyYCoordinateArray, keyYCoordinates);
|
|
||||||
safeReleaseIntArrayElements(env, keyXCoordinateArray, keyXCoordinates);
|
|
||||||
env->ReleaseIntArrayElements(proximityCharsArray, proximityChars, 0);
|
|
||||||
env->ReleaseStringUTFChars(localejStr, localeStrPtr);
|
|
||||||
return (jlong)proximityInfo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void latinime_Keyboard_release(JNIEnv *env, jobject object, jlong proximityInfo) {
|
static void latinime_Keyboard_release(JNIEnv *env, jobject object, jlong proximityInfo) {
|
||||||
ProximityInfo *pi = (ProximityInfo*)proximityInfo;
|
ProximityInfo *pi = reinterpret_cast<ProximityInfo *>(proximityInfo);
|
||||||
if (!pi) return;
|
|
||||||
delete pi;
|
delete pi;
|
||||||
}
|
}
|
||||||
|
|
||||||
static JNINativeMethod sKeyboardMethods[] = {
|
static JNINativeMethod sKeyboardMethods[] = {
|
||||||
{"setProximityInfoNative", "(Ljava/lang/String;IIIIII[II[I[I[I[I[I[F[F[F)J",
|
{"setProximityInfoNative", "(Ljava/lang/String;IIIIII[II[I[I[I[I[I[F[F[F)J",
|
||||||
(void*)latinime_Keyboard_setProximityInfo},
|
reinterpret_cast<void *>(latinime_Keyboard_setProximityInfo)},
|
||||||
{"releaseProximityInfoNative", "(J)V", (void*)latinime_Keyboard_release}
|
{"releaseProximityInfoNative", "(J)V", reinterpret_cast<void *>(latinime_Keyboard_release)}
|
||||||
};
|
};
|
||||||
|
|
||||||
int register_ProximityInfo(JNIEnv *env) {
|
int register_ProximityInfo(JNIEnv *env) {
|
||||||
|
|
|
@ -14,15 +14,12 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <cstring> // for memset()
|
||||||
|
|
||||||
#define LOG_TAG "LatinIME: jni: BinaryDictionary"
|
#define LOG_TAG "LatinIME: jni: BinaryDictionary"
|
||||||
|
|
||||||
#include "binary_format.h"
|
#include "defines.h" // for macros below
|
||||||
#include "com_android_inputmethod_latin_BinaryDictionary.h"
|
|
||||||
#include "correction.h"
|
|
||||||
#include "defines.h"
|
|
||||||
#include "dictionary.h"
|
|
||||||
#include "jni.h"
|
|
||||||
#include "jni_common.h"
|
|
||||||
|
|
||||||
#ifdef USE_MMAP_FOR_DICTIONARY
|
#ifdef USE_MMAP_FOR_DICTIONARY
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
|
@ -30,13 +27,21 @@
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#else // USE_MMAP_FOR_DICTIONARY
|
#else // USE_MMAP_FOR_DICTIONARY
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <cstdio> // for fopen() etc.
|
||||||
#endif // USE_MMAP_FOR_DICTIONARY
|
#endif // USE_MMAP_FOR_DICTIONARY
|
||||||
|
|
||||||
|
#include "binary_format.h"
|
||||||
|
#include "com_android_inputmethod_latin_BinaryDictionary.h"
|
||||||
|
#include "correction.h"
|
||||||
|
#include "dictionary.h"
|
||||||
|
#include "jni.h"
|
||||||
|
#include "jni_common.h"
|
||||||
|
|
||||||
namespace latinime {
|
namespace latinime {
|
||||||
|
|
||||||
class ProximityInfo;
|
class ProximityInfo;
|
||||||
|
|
||||||
void releaseDictBuf(void *dictBuf, const size_t length, int fd);
|
static void releaseDictBuf(const void *dictBuf, const size_t length, const int fd);
|
||||||
|
|
||||||
static jlong latinime_BinaryDictionary_open(JNIEnv *env, jobject object,
|
static jlong latinime_BinaryDictionary_open(JNIEnv *env, jobject object,
|
||||||
jstring sourceDir, jlong dictOffset, jlong dictSize,
|
jstring sourceDir, jlong dictOffset, jlong dictSize,
|
||||||
|
@ -44,11 +49,14 @@ static jlong latinime_BinaryDictionary_open(JNIEnv *env, jobject object,
|
||||||
jint maxPredictions) {
|
jint maxPredictions) {
|
||||||
PROF_OPEN;
|
PROF_OPEN;
|
||||||
PROF_START(66);
|
PROF_START(66);
|
||||||
const char *sourceDirChars = env->GetStringUTFChars(sourceDir, 0);
|
const jsize sourceDirUtf8Length = env->GetStringUTFLength(sourceDir);
|
||||||
if (sourceDirChars == 0) {
|
if (sourceDirUtf8Length <= 0) {
|
||||||
AKLOGE("DICT: Can't get sourceDir string");
|
AKLOGE("DICT: Can't get sourceDir string");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
char sourceDirChars[sourceDirUtf8Length + 1];
|
||||||
|
env->GetStringUTFRegion(sourceDir, 0, env->GetStringLength(sourceDir), sourceDirChars);
|
||||||
|
sourceDirChars[sourceDirUtf8Length] = '\0';
|
||||||
int fd = 0;
|
int fd = 0;
|
||||||
void *dictBuf = 0;
|
void *dictBuf = 0;
|
||||||
int adjust = 0;
|
int adjust = 0;
|
||||||
|
@ -68,7 +76,7 @@ static jlong latinime_BinaryDictionary_open(JNIEnv *env, jobject object,
|
||||||
AKLOGE("DICT: Can't mmap dictionary. errno=%d", errno);
|
AKLOGE("DICT: Can't mmap dictionary. errno=%d", errno);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
dictBuf = (void *)((char *)dictBuf + adjust);
|
dictBuf = static_cast<char *>(dictBuf) + adjust;
|
||||||
#else // USE_MMAP_FOR_DICTIONARY
|
#else // USE_MMAP_FOR_DICTIONARY
|
||||||
/* malloc version */
|
/* malloc version */
|
||||||
FILE *file = 0;
|
FILE *file = 0;
|
||||||
|
@ -98,17 +106,16 @@ static jlong latinime_BinaryDictionary_open(JNIEnv *env, jobject object,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
#endif // USE_MMAP_FOR_DICTIONARY
|
#endif // USE_MMAP_FOR_DICTIONARY
|
||||||
env->ReleaseStringUTFChars(sourceDir, sourceDirChars);
|
|
||||||
|
|
||||||
if (!dictBuf) {
|
if (!dictBuf) {
|
||||||
AKLOGE("DICT: dictBuf is null");
|
AKLOGE("DICT: dictBuf is null");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
Dictionary *dictionary = 0;
|
Dictionary *dictionary = 0;
|
||||||
if (BinaryFormat::UNKNOWN_FORMAT == BinaryFormat::detectFormat((uint8_t*)dictBuf)) {
|
if (BinaryFormat::UNKNOWN_FORMAT
|
||||||
|
== BinaryFormat::detectFormat(static_cast<uint8_t *>(dictBuf))) {
|
||||||
AKLOGE("DICT: dictionary format is unknown, bad magic number");
|
AKLOGE("DICT: dictionary format is unknown, bad magic number");
|
||||||
#ifdef USE_MMAP_FOR_DICTIONARY
|
#ifdef USE_MMAP_FOR_DICTIONARY
|
||||||
releaseDictBuf(((char*)dictBuf) - adjust, adjDictSize, fd);
|
releaseDictBuf(static_cast<const char *>(dictBuf) - adjust, adjDictSize, fd);
|
||||||
#else // USE_MMAP_FOR_DICTIONARY
|
#else // USE_MMAP_FOR_DICTIONARY
|
||||||
releaseDictBuf(dictBuf, 0, 0);
|
releaseDictBuf(dictBuf, 0, 0);
|
||||||
#endif // USE_MMAP_FOR_DICTIONARY
|
#endif // USE_MMAP_FOR_DICTIONARY
|
||||||
|
@ -122,106 +129,131 @@ static jlong latinime_BinaryDictionary_open(JNIEnv *env, jobject object,
|
||||||
}
|
}
|
||||||
|
|
||||||
static int latinime_BinaryDictionary_getSuggestions(JNIEnv *env, jobject object, jlong dict,
|
static int latinime_BinaryDictionary_getSuggestions(JNIEnv *env, jobject object, jlong dict,
|
||||||
jlong proximityInfo, jintArray xCoordinatesArray, jintArray yCoordinatesArray,
|
jlong proximityInfo, jlong dicTraverseSession, jintArray xCoordinatesArray,
|
||||||
jintArray timesArray, jintArray pointerIdArray, jintArray inputArray, jint arraySize,
|
jintArray yCoordinatesArray, jintArray timesArray, jintArray pointerIdsArray,
|
||||||
jint commitPoint, jboolean isGesture,
|
jintArray inputCodePointsArray, jint arraySize, jint commitPoint, jboolean isGesture,
|
||||||
jintArray prevWordForBigrams, jboolean useFullEditDistance, jcharArray outputArray,
|
jintArray prevWordCodePointsForBigrams, jboolean useFullEditDistance,
|
||||||
jintArray frequencyArray, jintArray spaceIndexArray, jintArray outputTypesArray) {
|
jcharArray outputCharsArray, jintArray scoresArray, jintArray spaceIndicesArray,
|
||||||
Dictionary *dictionary = (Dictionary*) dict;
|
jintArray outputTypesArray) {
|
||||||
|
Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
|
||||||
if (!dictionary) return 0;
|
if (!dictionary) return 0;
|
||||||
ProximityInfo *pInfo = (ProximityInfo*)proximityInfo;
|
ProximityInfo *pInfo = reinterpret_cast<ProximityInfo *>(proximityInfo);
|
||||||
int *xCoordinates = env->GetIntArrayElements(xCoordinatesArray, 0);
|
void *traverseSession = reinterpret_cast<void *>(dicTraverseSession);
|
||||||
int *yCoordinates = env->GetIntArrayElements(yCoordinatesArray, 0);
|
|
||||||
int *times = env->GetIntArrayElements(timesArray, 0);
|
// Input values
|
||||||
int *pointerIds = env->GetIntArrayElements(pointerIdArray, 0);
|
int xCoordinates[arraySize];
|
||||||
int *frequencies = env->GetIntArrayElements(frequencyArray, 0);
|
int yCoordinates[arraySize];
|
||||||
int *inputCodes = env->GetIntArrayElements(inputArray, 0);
|
int times[arraySize];
|
||||||
jchar *outputChars = env->GetCharArrayElements(outputArray, 0);
|
int pointerIds[arraySize];
|
||||||
int *spaceIndices = env->GetIntArrayElements(spaceIndexArray, 0);
|
const jsize inputCodePointsLength = env->GetArrayLength(inputCodePointsArray);
|
||||||
int *outputTypes = env->GetIntArrayElements(outputTypesArray, 0);
|
int inputCodePoints[inputCodePointsLength];
|
||||||
jint *prevWordChars = prevWordForBigrams
|
const jsize prevWordCodePointsLength =
|
||||||
? env->GetIntArrayElements(prevWordForBigrams, 0) : 0;
|
prevWordCodePointsForBigrams ? env->GetArrayLength(prevWordCodePointsForBigrams) : 0;
|
||||||
jsize prevWordLength = prevWordChars ? env->GetArrayLength(prevWordForBigrams) : 0;
|
int prevWordCodePointsInternal[prevWordCodePointsLength];
|
||||||
|
int *prevWordCodePoints = 0;
|
||||||
|
env->GetIntArrayRegion(xCoordinatesArray, 0, arraySize, xCoordinates);
|
||||||
|
env->GetIntArrayRegion(yCoordinatesArray, 0, arraySize, yCoordinates);
|
||||||
|
env->GetIntArrayRegion(timesArray, 0, arraySize, times);
|
||||||
|
env->GetIntArrayRegion(pointerIdsArray, 0, arraySize, pointerIds);
|
||||||
|
env->GetIntArrayRegion(inputCodePointsArray, 0, inputCodePointsLength, inputCodePoints);
|
||||||
|
if (prevWordCodePointsForBigrams) {
|
||||||
|
env->GetIntArrayRegion(prevWordCodePointsForBigrams, 0, prevWordCodePointsLength,
|
||||||
|
prevWordCodePointsInternal);
|
||||||
|
prevWordCodePoints = prevWordCodePointsInternal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output values
|
||||||
|
// TODO: Should be "outputCodePointsLength" and "int outputCodePoints[]"
|
||||||
|
const jsize outputCharsLength = env->GetArrayLength(outputCharsArray);
|
||||||
|
unsigned short outputChars[outputCharsLength];
|
||||||
|
const jsize scoresLength = env->GetArrayLength(scoresArray);
|
||||||
|
int scores[scoresLength];
|
||||||
|
const jsize spaceIndicesLength = env->GetArrayLength(spaceIndicesArray);
|
||||||
|
int spaceIndices[spaceIndicesLength];
|
||||||
|
const jsize outputTypesLength = env->GetArrayLength(outputTypesArray);
|
||||||
|
int outputTypes[outputTypesLength];
|
||||||
|
memset(outputChars, 0, sizeof(outputChars));
|
||||||
|
memset(scores, 0, sizeof(scores));
|
||||||
|
memset(spaceIndices, 0, sizeof(spaceIndices));
|
||||||
|
memset(outputTypes, 0, sizeof(outputTypes));
|
||||||
|
|
||||||
int count;
|
int count;
|
||||||
if (isGesture || arraySize > 1) {
|
if (isGesture || arraySize > 0) {
|
||||||
count = dictionary->getSuggestions(pInfo, xCoordinates, yCoordinates, times, pointerIds,
|
count = dictionary->getSuggestions(pInfo, traverseSession, xCoordinates, yCoordinates,
|
||||||
inputCodes, arraySize, prevWordChars, prevWordLength, commitPoint, isGesture,
|
times, pointerIds, inputCodePoints, arraySize, prevWordCodePoints,
|
||||||
useFullEditDistance, (unsigned short*) outputChars, frequencies, spaceIndices,
|
prevWordCodePointsLength, commitPoint, isGesture, useFullEditDistance, outputChars,
|
||||||
outputTypes);
|
scores, spaceIndices, outputTypes);
|
||||||
} else {
|
} else {
|
||||||
count = dictionary->getBigrams(prevWordChars, prevWordLength, inputCodes,
|
count = dictionary->getBigrams(prevWordCodePoints, prevWordCodePointsLength,
|
||||||
arraySize, (unsigned short*) outputChars, frequencies, outputTypes);
|
inputCodePoints, arraySize, outputChars, scores, outputTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prevWordChars) {
|
// Copy back the output values
|
||||||
env->ReleaseIntArrayElements(prevWordForBigrams, prevWordChars, JNI_ABORT);
|
// TODO: Should be SetIntArrayRegion()
|
||||||
}
|
env->SetCharArrayRegion(outputCharsArray, 0, outputCharsLength, outputChars);
|
||||||
env->ReleaseIntArrayElements(outputTypesArray, outputTypes, 0);
|
env->SetIntArrayRegion(scoresArray, 0, scoresLength, scores);
|
||||||
env->ReleaseIntArrayElements(spaceIndexArray, spaceIndices, 0);
|
env->SetIntArrayRegion(spaceIndicesArray, 0, spaceIndicesLength, spaceIndices);
|
||||||
env->ReleaseCharArrayElements(outputArray, outputChars, 0);
|
env->SetIntArrayRegion(outputTypesArray, 0, outputTypesLength, outputTypes);
|
||||||
env->ReleaseIntArrayElements(inputArray, inputCodes, JNI_ABORT);
|
|
||||||
env->ReleaseIntArrayElements(frequencyArray, frequencies, 0);
|
|
||||||
env->ReleaseIntArrayElements(pointerIdArray, pointerIds, 0);
|
|
||||||
env->ReleaseIntArrayElements(timesArray, times, 0);
|
|
||||||
env->ReleaseIntArrayElements(yCoordinatesArray, yCoordinates, 0);
|
|
||||||
env->ReleaseIntArrayElements(xCoordinatesArray, xCoordinates, 0);
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
static jint latinime_BinaryDictionary_getFrequency(JNIEnv *env, jobject object, jlong dict,
|
static jint latinime_BinaryDictionary_getFrequency(JNIEnv *env, jobject object, jlong dict,
|
||||||
jintArray wordArray, jint wordLength) {
|
jintArray wordArray) {
|
||||||
Dictionary *dictionary = (Dictionary*)dict;
|
Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
|
||||||
if (!dictionary) return (jboolean) false;
|
if (!dictionary) return 0;
|
||||||
jint *word = env->GetIntArrayElements(wordArray, 0);
|
const jsize codePointLength = env->GetArrayLength(wordArray);
|
||||||
jint result = dictionary->getFrequency(word, wordLength);
|
int codePoints[codePointLength];
|
||||||
env->ReleaseIntArrayElements(wordArray, word, JNI_ABORT);
|
env->GetIntArrayRegion(wordArray, 0, codePointLength, codePoints);
|
||||||
return result;
|
return dictionary->getFrequency(codePoints, codePointLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
static jboolean latinime_BinaryDictionary_isValidBigram(JNIEnv *env, jobject object, jlong dict,
|
static jboolean latinime_BinaryDictionary_isValidBigram(JNIEnv *env, jobject object, jlong dict,
|
||||||
jintArray wordArray1, jintArray wordArray2) {
|
jintArray wordArray1, jintArray wordArray2) {
|
||||||
Dictionary *dictionary = (Dictionary*)dict;
|
Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
|
||||||
if (!dictionary) return (jboolean) false;
|
if (!dictionary) return (jboolean) false;
|
||||||
jint *word1 = env->GetIntArrayElements(wordArray1, 0);
|
const jsize codePointLength1 = env->GetArrayLength(wordArray1);
|
||||||
jint *word2 = env->GetIntArrayElements(wordArray2, 0);
|
const jsize codePointLength2 = env->GetArrayLength(wordArray2);
|
||||||
jsize length1 = word1 ? env->GetArrayLength(wordArray1) : 0;
|
int codePoints1[codePointLength1];
|
||||||
jsize length2 = word2 ? env->GetArrayLength(wordArray2) : 0;
|
int codePoints2[codePointLength2];
|
||||||
jboolean result = dictionary->isValidBigram(word1, length1, word2, length2);
|
env->GetIntArrayRegion(wordArray1, 0, codePointLength1, codePoints1);
|
||||||
env->ReleaseIntArrayElements(wordArray2, word2, JNI_ABORT);
|
env->GetIntArrayRegion(wordArray2, 0, codePointLength2, codePoints2);
|
||||||
env->ReleaseIntArrayElements(wordArray1, word1, JNI_ABORT);
|
return dictionary->isValidBigram(codePoints1, codePointLength1, codePoints2, codePointLength2);
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static jfloat latinime_BinaryDictionary_calcNormalizedScore(JNIEnv *env, jobject object,
|
static jfloat latinime_BinaryDictionary_calcNormalizedScore(JNIEnv *env, jobject object,
|
||||||
jcharArray before, jint beforeLength, jcharArray after, jint afterLength, jint score) {
|
jcharArray before, jcharArray after, jint score) {
|
||||||
jchar *beforeChars = env->GetCharArrayElements(before, 0);
|
jsize beforeLength = env->GetArrayLength(before);
|
||||||
jchar *afterChars = env->GetCharArrayElements(after, 0);
|
jsize afterLength = env->GetArrayLength(after);
|
||||||
jfloat result = Correction::RankingAlgorithm::calcNormalizedScore((unsigned short*)beforeChars,
|
jchar beforeChars[beforeLength];
|
||||||
beforeLength, (unsigned short*)afterChars, afterLength, score);
|
jchar afterChars[afterLength];
|
||||||
env->ReleaseCharArrayElements(after, afterChars, JNI_ABORT);
|
env->GetCharArrayRegion(before, 0, beforeLength, beforeChars);
|
||||||
env->ReleaseCharArrayElements(before, beforeChars, JNI_ABORT);
|
env->GetCharArrayRegion(after, 0, afterLength, afterChars);
|
||||||
return result;
|
return Correction::RankingAlgorithm::calcNormalizedScore(
|
||||||
|
static_cast<unsigned short *>(beforeChars), beforeLength,
|
||||||
|
static_cast<unsigned short *>(afterChars), afterLength, score);
|
||||||
}
|
}
|
||||||
|
|
||||||
static jint latinime_BinaryDictionary_editDistance(JNIEnv *env, jobject object,
|
static jint latinime_BinaryDictionary_editDistance(JNIEnv *env, jobject object,
|
||||||
jcharArray before, jint beforeLength, jcharArray after, jint afterLength) {
|
jcharArray before, jcharArray after) {
|
||||||
jchar *beforeChars = env->GetCharArrayElements(before, 0);
|
jsize beforeLength = env->GetArrayLength(before);
|
||||||
jchar *afterChars = env->GetCharArrayElements(after, 0);
|
jsize afterLength = env->GetArrayLength(after);
|
||||||
jint result = Correction::RankingAlgorithm::editDistance(
|
jchar beforeChars[beforeLength];
|
||||||
(unsigned short*)beforeChars, beforeLength, (unsigned short*)afterChars, afterLength);
|
jchar afterChars[afterLength];
|
||||||
env->ReleaseCharArrayElements(after, afterChars, JNI_ABORT);
|
env->GetCharArrayRegion(before, 0, beforeLength, beforeChars);
|
||||||
env->ReleaseCharArrayElements(before, beforeChars, JNI_ABORT);
|
env->GetCharArrayRegion(after, 0, afterLength, afterChars);
|
||||||
return result;
|
return Correction::RankingAlgorithm::editDistance(
|
||||||
|
static_cast<unsigned short *>(beforeChars), beforeLength,
|
||||||
|
static_cast<unsigned short *>(afterChars), afterLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void latinime_BinaryDictionary_close(JNIEnv *env, jobject object, jlong dict) {
|
static void latinime_BinaryDictionary_close(JNIEnv *env, jobject object, jlong dict) {
|
||||||
Dictionary *dictionary = (Dictionary*)dict;
|
Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
|
||||||
if (!dictionary) return;
|
if (!dictionary) return;
|
||||||
void *dictBuf = dictionary->getDict();
|
const void *dictBuf = dictionary->getDict();
|
||||||
if (!dictBuf) return;
|
if (!dictBuf) return;
|
||||||
#ifdef USE_MMAP_FOR_DICTIONARY
|
#ifdef USE_MMAP_FOR_DICTIONARY
|
||||||
releaseDictBuf((void *)((char *)dictBuf - dictionary->getDictBufAdjust()),
|
releaseDictBuf(static_cast<const char *>(dictBuf) - dictionary->getDictBufAdjust(),
|
||||||
dictionary->getDictSize() + dictionary->getDictBufAdjust(), dictionary->getMmapFd());
|
dictionary->getDictSize() + dictionary->getDictBufAdjust(), dictionary->getMmapFd());
|
||||||
#else // USE_MMAP_FOR_DICTIONARY
|
#else // USE_MMAP_FOR_DICTIONARY
|
||||||
releaseDictBuf(dictBuf, 0, 0);
|
releaseDictBuf(dictBuf, 0, 0);
|
||||||
|
@ -229,9 +261,9 @@ static void latinime_BinaryDictionary_close(JNIEnv *env, jobject object, jlong d
|
||||||
delete dictionary;
|
delete dictionary;
|
||||||
}
|
}
|
||||||
|
|
||||||
void releaseDictBuf(void *dictBuf, const size_t length, int fd) {
|
static void releaseDictBuf(const void *dictBuf, const size_t length, const int fd) {
|
||||||
#ifdef USE_MMAP_FOR_DICTIONARY
|
#ifdef USE_MMAP_FOR_DICTIONARY
|
||||||
int ret = munmap(dictBuf, length);
|
int ret = munmap(const_cast<void *>(dictBuf), length);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
AKLOGE("DICT: Failure in munmap. ret=%d errno=%d", ret, errno);
|
AKLOGE("DICT: Failure in munmap. ret=%d errno=%d", ret, errno);
|
||||||
}
|
}
|
||||||
|
@ -240,20 +272,24 @@ void releaseDictBuf(void *dictBuf, const size_t length, int fd) {
|
||||||
AKLOGE("DICT: Failure in close. ret=%d errno=%d", ret, errno);
|
AKLOGE("DICT: Failure in close. ret=%d errno=%d", ret, errno);
|
||||||
}
|
}
|
||||||
#else // USE_MMAP_FOR_DICTIONARY
|
#else // USE_MMAP_FOR_DICTIONARY
|
||||||
free(dictBuf);
|
free(const_cast<void *>(dictBuf));
|
||||||
#endif // USE_MMAP_FOR_DICTIONARY
|
#endif // USE_MMAP_FOR_DICTIONARY
|
||||||
}
|
}
|
||||||
|
|
||||||
static JNINativeMethod sMethods[] = {
|
static JNINativeMethod sMethods[] = {
|
||||||
{"openNative", "(Ljava/lang/String;JJIIIII)J", (void*)latinime_BinaryDictionary_open},
|
{"openNative", "(Ljava/lang/String;JJIIIII)J",
|
||||||
{"closeNative", "(J)V", (void*)latinime_BinaryDictionary_close},
|
reinterpret_cast<void *>(latinime_BinaryDictionary_open)},
|
||||||
{"getSuggestionsNative", "(JJ[I[I[I[I[IIIZ[IZ[C[I[I[I)I",
|
{"closeNative", "(J)V", reinterpret_cast<void *>(latinime_BinaryDictionary_close)},
|
||||||
(void*) latinime_BinaryDictionary_getSuggestions},
|
{"getSuggestionsNative", "(JJJ[I[I[I[I[IIIZ[IZ[C[I[I[I)I",
|
||||||
{"getFrequencyNative", "(J[II)I", (void*)latinime_BinaryDictionary_getFrequency},
|
reinterpret_cast<void *>(latinime_BinaryDictionary_getSuggestions)},
|
||||||
{"isValidBigramNative", "(J[I[I)Z", (void*)latinime_BinaryDictionary_isValidBigram},
|
{"getFrequencyNative", "(J[I)I",
|
||||||
{"calcNormalizedScoreNative", "([CI[CII)F",
|
reinterpret_cast<void *>(latinime_BinaryDictionary_getFrequency)},
|
||||||
(void*)latinime_BinaryDictionary_calcNormalizedScore},
|
{"isValidBigramNative", "(J[I[I)Z",
|
||||||
{"editDistanceNative", "([CI[CI)I", (void*)latinime_BinaryDictionary_editDistance}
|
reinterpret_cast<void *>(latinime_BinaryDictionary_isValidBigram)},
|
||||||
|
{"calcNormalizedScoreNative", "([C[CI)F",
|
||||||
|
reinterpret_cast<void *>(latinime_BinaryDictionary_calcNormalizedScore)},
|
||||||
|
{"editDistanceNative", "([C[C)I",
|
||||||
|
reinterpret_cast<void *>(latinime_BinaryDictionary_editDistance)}
|
||||||
};
|
};
|
||||||
|
|
||||||
int register_BinaryDictionary(JNIEnv *env) {
|
int register_BinaryDictionary(JNIEnv *env) {
|
||||||
|
|
|
@ -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
|
|
@ -14,14 +14,13 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef _COM_ANDROID_INPUTMETHOD_LATIN_NATIVEUTILS_H
|
#ifndef _COM_ANDROID_INPUTMETHOD_LATIN_DICTRAVERSESESSION_H
|
||||||
#define _COM_ANDROID_INPUTMETHOD_LATIN_NATIVEUTILS_H
|
#define _COM_ANDROID_INPUTMETHOD_LATIN_DICTRAVERSESESSION_H
|
||||||
|
|
||||||
|
#include "defines.h"
|
||||||
#include "jni.h"
|
#include "jni.h"
|
||||||
|
|
||||||
namespace latinime {
|
namespace latinime {
|
||||||
|
int register_DicTraverseSession(JNIEnv *env);
|
||||||
int register_NativeUtils(JNIEnv *env);
|
|
||||||
|
|
||||||
} // namespace latinime
|
} // namespace latinime
|
||||||
#endif // _COM_ANDROID_INPUTMETHOD_LATIN_NATIVEUTILS_H
|
#endif // _COM_ANDROID_INPUTMETHOD_LATIN_DICTRAVERSESESSION_H
|
|
@ -16,15 +16,15 @@
|
||||||
|
|
||||||
#define LOG_TAG "LatinIME: jni"
|
#define LOG_TAG "LatinIME: jni"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
#include "com_android_inputmethod_keyboard_ProximityInfo.h"
|
#include "com_android_inputmethod_keyboard_ProximityInfo.h"
|
||||||
#include "com_android_inputmethod_latin_BinaryDictionary.h"
|
#include "com_android_inputmethod_latin_BinaryDictionary.h"
|
||||||
#include "com_android_inputmethod_latin_NativeUtils.h"
|
#include "com_android_inputmethod_latin_DicTraverseSession.h"
|
||||||
#include "defines.h"
|
#include "defines.h"
|
||||||
#include "jni.h"
|
#include "jni.h"
|
||||||
#include "jni_common.h"
|
#include "jni_common.h"
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
|
|
||||||
using namespace latinime;
|
using namespace latinime;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -34,7 +34,7 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
|
||||||
JNIEnv *env = 0;
|
JNIEnv *env = 0;
|
||||||
jint result = -1;
|
jint result = -1;
|
||||||
|
|
||||||
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
|
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
|
||||||
AKLOGE("ERROR: GetEnv failed");
|
AKLOGE("ERROR: GetEnv failed");
|
||||||
goto bail;
|
goto bail;
|
||||||
}
|
}
|
||||||
|
@ -45,13 +45,13 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
|
||||||
goto bail;
|
goto bail;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!register_ProximityInfo(env)) {
|
if (!register_DicTraverseSession(env)) {
|
||||||
AKLOGE("ERROR: ProximityInfo native registration failed");
|
AKLOGE("ERROR: DicTraverseSession native registration failed");
|
||||||
goto bail;
|
goto bail;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!register_NativeUtils(env)) {
|
if (!register_ProximityInfo(env)) {
|
||||||
AKLOGE("ERROR: NativeUtils native registration failed");
|
AKLOGE("ERROR: ProximityInfo native registration failed");
|
||||||
goto bail;
|
goto bail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,32 +24,5 @@ namespace latinime {
|
||||||
int registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods,
|
int registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods,
|
||||||
int numMethods);
|
int numMethods);
|
||||||
|
|
||||||
inline jint *safeGetIntArrayElements(JNIEnv *env, jintArray jArray) {
|
|
||||||
if (jArray) {
|
|
||||||
return env->GetIntArrayElements(jArray, 0);
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline jfloat *safeGetFloatArrayElements(JNIEnv *env, jfloatArray jArray) {
|
|
||||||
if (jArray) {
|
|
||||||
return env->GetFloatArrayElements(jArray, 0);
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void safeReleaseIntArrayElements(JNIEnv *env, jintArray jArray, jint *cArray) {
|
|
||||||
if (jArray) {
|
|
||||||
env->ReleaseIntArrayElements(jArray, cArray, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void safeReleaseFloatArrayElements(JNIEnv *env, jfloatArray jArray, jfloat *cArray) {
|
|
||||||
if (jArray) {
|
|
||||||
env->ReleaseFloatArrayElements(jArray, cArray, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace latinime
|
} // namespace latinime
|
||||||
#endif // LATINIME_JNI_COMMON_H
|
#endif // LATINIME_JNI_COMMON_H
|
||||||
|
|
|
@ -17,7 +17,9 @@
|
||||||
#include "additional_proximity_chars.h"
|
#include "additional_proximity_chars.h"
|
||||||
|
|
||||||
namespace latinime {
|
namespace latinime {
|
||||||
const std::string AdditionalProximityChars::LOCALE_EN_US("en");
|
// TODO: Stop using hardcoded additional proximity characters.
|
||||||
|
// TODO: Have proximity character informations in each language's binary dictionary.
|
||||||
|
const char *AdditionalProximityChars::LOCALE_EN_US = "en";
|
||||||
|
|
||||||
const int32_t AdditionalProximityChars::EN_US_ADDITIONAL_A[EN_US_ADDITIONAL_A_SIZE] = {
|
const int32_t AdditionalProximityChars::EN_US_ADDITIONAL_A[EN_US_ADDITIONAL_A_SIZE] = {
|
||||||
'e', 'i', 'o', 'u'
|
'e', 'i', 'o', 'u'
|
||||||
|
|
|
@ -17,8 +17,8 @@
|
||||||
#ifndef LATINIME_ADDITIONAL_PROXIMITY_CHARS_H
|
#ifndef LATINIME_ADDITIONAL_PROXIMITY_CHARS_H
|
||||||
#define LATINIME_ADDITIONAL_PROXIMITY_CHARS_H
|
#define LATINIME_ADDITIONAL_PROXIMITY_CHARS_H
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "defines.h"
|
#include "defines.h"
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ namespace latinime {
|
||||||
class AdditionalProximityChars {
|
class AdditionalProximityChars {
|
||||||
private:
|
private:
|
||||||
DISALLOW_IMPLICIT_CONSTRUCTORS(AdditionalProximityChars);
|
DISALLOW_IMPLICIT_CONSTRUCTORS(AdditionalProximityChars);
|
||||||
static const std::string LOCALE_EN_US;
|
static const char *LOCALE_EN_US;
|
||||||
static const int EN_US_ADDITIONAL_A_SIZE = 4;
|
static const int EN_US_ADDITIONAL_A_SIZE = 4;
|
||||||
static const int32_t EN_US_ADDITIONAL_A[];
|
static const int32_t EN_US_ADDITIONAL_A[];
|
||||||
static const int EN_US_ADDITIONAL_E_SIZE = 4;
|
static const int EN_US_ADDITIONAL_E_SIZE = 4;
|
||||||
|
@ -39,14 +39,15 @@ class AdditionalProximityChars {
|
||||||
static const int EN_US_ADDITIONAL_U_SIZE = 4;
|
static const int EN_US_ADDITIONAL_U_SIZE = 4;
|
||||||
static const int32_t EN_US_ADDITIONAL_U[];
|
static const int32_t EN_US_ADDITIONAL_U[];
|
||||||
|
|
||||||
static bool isEnLocale(const std::string *locale_str) {
|
static bool isEnLocale(const char *localeStr) {
|
||||||
return locale_str && locale_str->size() >= LOCALE_EN_US.size()
|
const size_t LOCALE_EN_US_SIZE = strlen(LOCALE_EN_US);
|
||||||
&& LOCALE_EN_US.compare(0, LOCALE_EN_US.size(), *locale_str);
|
return localeStr && strlen(localeStr) >= LOCALE_EN_US_SIZE
|
||||||
|
&& strncmp(localeStr, LOCALE_EN_US, LOCALE_EN_US_SIZE) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static int getAdditionalCharsSize(const std::string *locale_str, const int32_t c) {
|
static int getAdditionalCharsSize(const char *localeStr, const int32_t c) {
|
||||||
if (!isEnLocale(locale_str)) {
|
if (!isEnLocale(localeStr)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
switch(c) {
|
switch(c) {
|
||||||
|
@ -65,8 +66,8 @@ class AdditionalProximityChars {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const int32_t *getAdditionalChars(const std::string *locale_str, const int32_t c) {
|
static const int32_t *getAdditionalChars(const char *localeStr, const int32_t c) {
|
||||||
if (!isEnLocale(locale_str)) {
|
if (!isEnLocale(localeStr)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
switch(c) {
|
switch(c) {
|
||||||
|
@ -84,10 +85,6 @@ class AdditionalProximityChars {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool hasAdditionalChars(const std::string *locale_str, const int32_t c) {
|
|
||||||
return getAdditionalCharsSize(locale_str, c) > 0;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
} // namespace latinime
|
} // namespace latinime
|
||||||
#endif // LATINIME_ADDITIONAL_PROXIMITY_CHARS_H
|
#endif // LATINIME_ADDITIONAL_PROXIMITY_CHARS_H
|
||||||
|
|
|
@ -60,15 +60,15 @@ bool BigramDictionary::addWordBigram(unsigned short *word, int length, int frequ
|
||||||
AKLOGI("Bigram: InsertAt -> %d MAX_PREDICTIONS: %d", insertAt, MAX_PREDICTIONS);
|
AKLOGI("Bigram: InsertAt -> %d MAX_PREDICTIONS: %d", insertAt, MAX_PREDICTIONS);
|
||||||
}
|
}
|
||||||
if (insertAt < MAX_PREDICTIONS) {
|
if (insertAt < MAX_PREDICTIONS) {
|
||||||
memmove((char*) bigramFreq + (insertAt + 1) * sizeof(bigramFreq[0]),
|
memmove(bigramFreq + (insertAt + 1),
|
||||||
(char*) bigramFreq + insertAt * sizeof(bigramFreq[0]),
|
bigramFreq + insertAt,
|
||||||
(MAX_PREDICTIONS - insertAt - 1) * sizeof(bigramFreq[0]));
|
(MAX_PREDICTIONS - insertAt - 1) * sizeof(bigramFreq[0]));
|
||||||
bigramFreq[insertAt] = frequency;
|
bigramFreq[insertAt] = frequency;
|
||||||
outputTypes[insertAt] = Dictionary::KIND_PREDICTION;
|
outputTypes[insertAt] = Dictionary::KIND_PREDICTION;
|
||||||
memmove((char*) bigramChars + (insertAt + 1) * MAX_WORD_LENGTH * sizeof(short),
|
memmove(bigramChars + (insertAt + 1) * MAX_WORD_LENGTH,
|
||||||
(char*) bigramChars + (insertAt ) * MAX_WORD_LENGTH * sizeof(short),
|
bigramChars + insertAt * MAX_WORD_LENGTH,
|
||||||
(MAX_PREDICTIONS - insertAt - 1) * sizeof(short) * MAX_WORD_LENGTH);
|
(MAX_PREDICTIONS - insertAt - 1) * sizeof(bigramChars[0]) * MAX_WORD_LENGTH);
|
||||||
unsigned short *dest = bigramChars + (insertAt ) * MAX_WORD_LENGTH;
|
unsigned short *dest = bigramChars + insertAt * MAX_WORD_LENGTH;
|
||||||
while (length--) {
|
while (length--) {
|
||||||
*dest++ = *word++;
|
*dest++ = *word++;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,8 +29,6 @@ class BigramDictionary {
|
||||||
BigramDictionary(const unsigned char *dict, int maxWordLength, int maxPredictions);
|
BigramDictionary(const unsigned char *dict, int maxWordLength, int maxPredictions);
|
||||||
int getBigrams(const int32_t *word, int length, int *inputCodes, int codesSize,
|
int getBigrams(const int32_t *word, int length, int *inputCodes, int codesSize,
|
||||||
unsigned short *outWords, int *frequencies, int *outputTypes) const;
|
unsigned short *outWords, int *frequencies, int *outputTypes) const;
|
||||||
int getBigramListPositionForWord(const int32_t *prevWord, const int prevWordLength,
|
|
||||||
const bool forceLowerCaseSearch) const;
|
|
||||||
void fillBigramAddressToFrequencyMapAndFilter(const int32_t *prevWord, const int prevWordLength,
|
void fillBigramAddressToFrequencyMapAndFilter(const int32_t *prevWord, const int prevWordLength,
|
||||||
std::map<int, int> *map, uint8_t *filter) const;
|
std::map<int, int> *map, uint8_t *filter) const;
|
||||||
bool isValidBigram(const int32_t *word1, int length1, const int32_t *word2, int length2) const;
|
bool isValidBigram(const int32_t *word1, int length1, const int32_t *word2, int length2) const;
|
||||||
|
@ -45,6 +43,8 @@ class BigramDictionary {
|
||||||
bool getFirstBitOfByte(int *pos) { return (DICT[*pos] & 0x80) > 0; }
|
bool getFirstBitOfByte(int *pos) { return (DICT[*pos] & 0x80) > 0; }
|
||||||
bool getSecondBitOfByte(int *pos) { return (DICT[*pos] & 0x40) > 0; }
|
bool getSecondBitOfByte(int *pos) { return (DICT[*pos] & 0x40) > 0; }
|
||||||
bool checkFirstCharacter(unsigned short *word, int *inputCodes) const;
|
bool checkFirstCharacter(unsigned short *word, int *inputCodes) const;
|
||||||
|
int getBigramListPositionForWord(const int32_t *prevWord, const int prevWordLength,
|
||||||
|
const bool forceLowerCaseSearch) const;
|
||||||
|
|
||||||
const unsigned char *DICT;
|
const unsigned char *DICT;
|
||||||
const int MAX_WORD_LENGTH;
|
const int MAX_WORD_LENGTH;
|
||||||
|
|
|
@ -52,6 +52,8 @@ class BinaryFormat {
|
||||||
|
|
||||||
// Mask for attribute frequency, stored on 4 bits inside the flags byte.
|
// Mask for attribute frequency, stored on 4 bits inside the flags byte.
|
||||||
static const int MASK_ATTRIBUTE_FREQUENCY = 0x0F;
|
static const int MASK_ATTRIBUTE_FREQUENCY = 0x0F;
|
||||||
|
// The numeric value of the shortcut frequency that means 'whitelist'.
|
||||||
|
static const int WHITELIST_SHORTCUT_FREQUENCY = 15;
|
||||||
|
|
||||||
// Mask and flags for attribute address type selection.
|
// Mask and flags for attribute address type selection.
|
||||||
static const int MASK_ATTRIBUTE_ADDRESS_TYPE = 0x30;
|
static const int MASK_ATTRIBUTE_ADDRESS_TYPE = 0x30;
|
||||||
|
@ -59,13 +61,6 @@ class BinaryFormat {
|
||||||
static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES = 0x20;
|
static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES = 0x20;
|
||||||
static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES = 0x30;
|
static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES = 0x30;
|
||||||
|
|
||||||
private:
|
|
||||||
DISALLOW_IMPLICIT_CONSTRUCTORS(BinaryFormat);
|
|
||||||
const static int32_t MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
|
|
||||||
const static int32_t CHARACTER_ARRAY_TERMINATOR = 0x1F;
|
|
||||||
const static int MULTIPLE_BYTE_CHARACTER_ADDITIONAL_SIZE = 2;
|
|
||||||
|
|
||||||
public:
|
|
||||||
const static int UNKNOWN_FORMAT = -1;
|
const static int UNKNOWN_FORMAT = -1;
|
||||||
// Originally, format version 1 had a 16-bit magic number, then the version number `01'
|
// Originally, format version 1 had a 16-bit magic number, then the version number `01'
|
||||||
// then options that must be 0. Hence the first 32-bits of the format are always as follow
|
// then options that must be 0. Hence the first 32-bits of the format are always as follow
|
||||||
|
@ -92,13 +87,13 @@ class BinaryFormat {
|
||||||
static int skipFrequency(const uint8_t flags, const int pos);
|
static int skipFrequency(const uint8_t flags, const int pos);
|
||||||
static int skipShortcuts(const uint8_t *const dict, const uint8_t flags, const int pos);
|
static int skipShortcuts(const uint8_t *const dict, const uint8_t flags, const int pos);
|
||||||
static int skipBigrams(const uint8_t *const dict, const uint8_t flags, const int pos);
|
static int skipBigrams(const uint8_t *const dict, const uint8_t flags, const int pos);
|
||||||
static int skipAllAttributes(const uint8_t *const dict, const uint8_t flags, const int pos);
|
|
||||||
static int skipChildrenPosAndAttributes(const uint8_t *const dict, const uint8_t flags,
|
static int skipChildrenPosAndAttributes(const uint8_t *const dict, const uint8_t flags,
|
||||||
const int pos);
|
const int pos);
|
||||||
static int readChildrenPosition(const uint8_t *const dict, const uint8_t flags, const int pos);
|
static int readChildrenPosition(const uint8_t *const dict, const uint8_t flags, const int pos);
|
||||||
static bool hasChildrenInFlags(const uint8_t flags);
|
static bool hasChildrenInFlags(const uint8_t flags);
|
||||||
static int getAttributeAddressAndForwardPointer(const uint8_t *const dict, const uint8_t flags,
|
static int getAttributeAddressAndForwardPointer(const uint8_t *const dict, const uint8_t flags,
|
||||||
int *pos);
|
int *pos);
|
||||||
|
static int getAttributeFrequencyFromFlags(const int flags);
|
||||||
static int getTerminalPosition(const uint8_t *const root, const int32_t *const inWord,
|
static int getTerminalPosition(const uint8_t *const root, const int32_t *const inWord,
|
||||||
const int length, const bool forceLowerCaseSearch);
|
const int length, const bool forceLowerCaseSearch);
|
||||||
static int getWordAtAddress(const uint8_t *const root, const int address, const int maxDepth,
|
static int getWordAtAddress(const uint8_t *const root, const int address, const int maxDepth,
|
||||||
|
@ -115,6 +110,13 @@ class BinaryFormat {
|
||||||
REQUIRES_FRENCH_LIGATURES_PROCESSING = 0x4
|
REQUIRES_FRENCH_LIGATURES_PROCESSING = 0x4
|
||||||
};
|
};
|
||||||
const static unsigned int NO_FLAGS = 0;
|
const static unsigned int NO_FLAGS = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
DISALLOW_IMPLICIT_CONSTRUCTORS(BinaryFormat);
|
||||||
|
const static int32_t MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
|
||||||
|
const static int32_t CHARACTER_ARRAY_TERMINATOR = 0x1F;
|
||||||
|
const static int MULTIPLE_BYTE_CHARACTER_ADDITIONAL_SIZE = 2;
|
||||||
|
static int skipAllAttributes(const uint8_t *const dict, const uint8_t flags, const int pos);
|
||||||
};
|
};
|
||||||
|
|
||||||
inline int BinaryFormat::detectFormat(const uint8_t *const dict) {
|
inline int BinaryFormat::detectFormat(const uint8_t *const dict) {
|
||||||
|
@ -340,6 +342,10 @@ inline int BinaryFormat::getAttributeAddressAndForwardPointer(const uint8_t *con
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline int BinaryFormat::getAttributeFrequencyFromFlags(const int flags) {
|
||||||
|
return flags & MASK_ATTRIBUTE_FREQUENCY;
|
||||||
|
}
|
||||||
|
|
||||||
// This function gets the byte position of the last chargroup of the exact matching word in the
|
// This function gets the byte position of the last chargroup of the exact matching word in the
|
||||||
// dictionary. If no match is found, it returns NOT_VALID_WORD.
|
// dictionary. If no match is found, it returns NOT_VALID_WORD.
|
||||||
inline int BinaryFormat::getTerminalPosition(const uint8_t *const root,
|
inline int BinaryFormat::getTerminalPosition(const uint8_t *const root,
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue