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>(...);
|
||||
}
|
||||
|
||||
-keepclasseswithmembernames class * {
|
||||
native <methods>;
|
||||
}
|
||||
|
||||
-keep class com.android.inputmethod.research.ResearchLogger {
|
||||
void flush();
|
||||
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. -->
|
||||
<attr name="keyBackground" format="reference" />
|
||||
|
||||
<!-- Size of the text for one letter keys. If not defined, keyLetterRatio takes effect. -->
|
||||
<attr name="keyLetterSize" format="dimension" />
|
||||
<!-- Size of the text for keys with multiple letters. If not defined, keyLabelRatio takes
|
||||
effect. -->
|
||||
<attr name="keyLabelSize" format="dimension" />
|
||||
<!-- Size of the text for one letter keys, in the proportion of key height. -->
|
||||
<attr name="keyLetterRatio" format="float" />
|
||||
<!-- Size of the text for one letter keys. If specified as fraction, the text size is
|
||||
measured in the proportion of key height. -->
|
||||
<attr name="keyLetterSize" format="dimension|fraction" />
|
||||
<!-- Size of the text for keys with multiple letters. If specified as fraction, the text
|
||||
size is measured in the proportion of key height. -->
|
||||
<attr name="keyLabelSize" format="dimension|fraction" />
|
||||
<!-- Large size of the text for one letter keys, in the proportion of key height. -->
|
||||
<attr name="keyLargeLetterRatio" format="float" />
|
||||
<!-- Size of the text for keys with multiple letters, in the proportion of key height. -->
|
||||
<attr name="keyLabelRatio" format="float" />
|
||||
<attr name="keyLargeLetterRatio" format="fraction" />
|
||||
<!-- Large size of the text for keys with multiple letters, in the proportion of key height. -->
|
||||
<attr name="keyLargeLabelRatio" format="float" />
|
||||
<attr name="keyLargeLabelRatio" format="fraction" />
|
||||
<!-- Size of the text for hint letter (= one character hint label), in the proportion of
|
||||
key height. -->
|
||||
<attr name="keyHintLetterRatio" format="float" />
|
||||
<attr name="keyHintLetterRatio" format="fraction" />
|
||||
<!-- Size of the text for hint label, in the proportion of key height. -->
|
||||
<attr name="keyHintLabelRatio" format="float" />
|
||||
<attr name="keyHintLabelRatio" format="fraction" />
|
||||
<!-- Size of the text for shifted letter hint, in the proportion of key height. -->
|
||||
<attr name="keyShiftedLetterHintRatio" format="float" />
|
||||
<attr name="keyShiftedLetterHintRatio" format="dimension|fraction" />
|
||||
|
||||
<!-- Horizontal padding of left/right aligned key label to the edge of the key. -->
|
||||
<attr name="keyLabelHorizontalPadding" format="dimension" />
|
||||
<!-- Right padding of hint letter to the edge of the key.-->
|
||||
|
@ -96,8 +94,8 @@
|
|||
<attr name="keyPreviewOffset" format="dimension" />
|
||||
<!-- Height of the key press feedback popup. -->
|
||||
<attr name="keyPreviewHeight" format="dimension" />
|
||||
<!-- Size of the text for key press feedback popup, int the proportion of key height -->
|
||||
<attr name="keyPreviewTextRatio" format="float" />
|
||||
<!-- Size of the text for key press feedback popup, in the proportion of key height. -->
|
||||
<attr name="keyPreviewTextRatio" format="fraction" />
|
||||
<!-- Delay after key releasing and key press feedback dismissing in millisecond -->
|
||||
<attr name="keyPreviewLingerTimeout" format="integer" />
|
||||
|
||||
|
@ -131,6 +129,12 @@
|
|||
<attr name="gestureFloatingPreviewTextConnectorWidth" format="dimension" />
|
||||
<!-- Delay after gesture input and gesture floating preview text dismissing in millisecond -->
|
||||
<attr name="gestureFloatingPreviewTextLingerTimeout" format="integer" />
|
||||
<!-- Delay after gesture trail starts fading out in millisecond. -->
|
||||
<attr name="gesturePreviewTrailFadeoutStartDelay" format="integer" />
|
||||
<!-- Duration while gesture preview trail is fading out in millisecond. -->
|
||||
<attr name="gesturePreviewTrailFadeoutDuration" format="integer" />
|
||||
<!-- Interval of updating gesture preview trail in millisecond. -->
|
||||
<attr name="gesturePreviewTrailUpdateInterval" format="integer" />
|
||||
<attr name="gesturePreviewTrailColor" format="color" />
|
||||
<attr name="gesturePreviewTrailWidth" format="dimension" />
|
||||
</declare-styleable>
|
||||
|
@ -181,13 +185,13 @@
|
|||
<attr name="colorTypedWord" format="color" />
|
||||
<attr name="colorAutoCorrect" format="color" />
|
||||
<attr name="colorSuggested" format="color" />
|
||||
<attr name="alphaValidTypedWord" format="integer" />
|
||||
<attr name="alphaTypedWord" format="integer" />
|
||||
<attr name="alphaAutoCorrect" format="integer" />
|
||||
<attr name="alphaSuggested" format="integer" />
|
||||
<attr name="alphaObsoleted" format="integer" />
|
||||
<attr name="alphaValidTypedWord" format="fraction" />
|
||||
<attr name="alphaTypedWord" format="fraction" />
|
||||
<attr name="alphaAutoCorrect" format="fraction" />
|
||||
<attr name="alphaSuggested" format="fraction" />
|
||||
<attr name="alphaObsoleted" format="fraction" />
|
||||
<attr name="suggestionsCountInStrip" format="integer" />
|
||||
<attr name="centerSuggestionPercentile" format="integer" />
|
||||
<attr name="centerSuggestionPercentile" format="fraction" />
|
||||
<attr name="maxMoreSuggestionsRow" format="integer" />
|
||||
<attr name="minMoreSuggestionsWidth" format="float" />
|
||||
</declare-styleable>
|
||||
|
|
|
@ -50,6 +50,9 @@
|
|||
-->
|
||||
<integer name="config_key_preview_linger_timeout">70</integer>
|
||||
<integer name="config_gesture_floating_preview_text_linger_timeout">200</integer>
|
||||
<integer name="config_gesture_preview_trail_fadeout_start_delay">100</integer>
|
||||
<integer name="config_gesture_preview_trail_fadeout_duration">800</integer>
|
||||
<integer name="config_gesture_preview_trail_update_interval">20</integer>
|
||||
<!--
|
||||
Configuration for MainKeyboardView
|
||||
-->
|
||||
|
|
|
@ -92,7 +92,7 @@
|
|||
<dimen name="suggestion_text_size">18dp</dimen>
|
||||
<dimen name="more_suggestions_hint_text_size">27dp</dimen>
|
||||
<integer name="suggestions_count_in_strip">3</integer>
|
||||
<integer name="center_suggestion_percentile">36</integer>
|
||||
<fraction name="center_suggestion_percentile">36%</fraction>
|
||||
|
||||
<!-- Gesture preview parameters -->
|
||||
<dimen name="gesture_preview_trail_width">2.5dp</dimen>
|
||||
|
@ -101,4 +101,7 @@
|
|||
<dimen name="gesture_floating_preview_text_shadow_border">17.5dp</dimen>
|
||||
<dimen name="gesture_floating_preview_text_shading_border">7.5dp</dimen>
|
||||
<dimen name="gesture_floating_preview_text_connector_width">1.0dp</dimen>
|
||||
|
||||
<!-- Inset used in Accessibility mode to avoid accidental key presses when a finger slides off the screen. -->
|
||||
<dimen name="accessibility_edge_slop">8dp</dimen>
|
||||
</resources>
|
||||
|
|
|
@ -22,5 +22,6 @@
|
|||
<!-- Build.HARDWARE,duration_in_milliseconds -->
|
||||
<item>herring,5</item>
|
||||
<item>tuna,5</item>
|
||||
<item>mako,20</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
|
|
|
@ -24,5 +24,6 @@
|
|||
<item>tuna,0.5</item>
|
||||
<item>stingray,0.4</item>
|
||||
<item>grouper,0.3</item>
|
||||
<item>mako,0.3</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
|
|
|
@ -261,7 +261,8 @@
|
|||
<string name="research_feedback_dialog_title" translatable="false">Send feedback</string>
|
||||
<!-- Text for checkbox option to include user data in feedback for research purposes [CHAR LIMIT=50] -->
|
||||
<!-- TODO: remove translatable=false attribute once text is stable -->
|
||||
<string name="research_feedback_include_history_label" translatable="false">Include last 5 words entered</string>
|
||||
<!-- TODO: handle multilingual plurals -->
|
||||
<string name="research_feedback_include_history_label" translatable="false">Include last <xliff:g id="word">%d</xliff:g> words entered</string>
|
||||
<!-- Hint to user about the text entry field where they should enter research feedback [CHAR LIMIT=40] -->
|
||||
<!-- TODO: remove translatable=false attribute once text is stable -->
|
||||
<string name="research_feedback_hint" translatable="false">Enter your feedback here.</string>
|
||||
|
@ -288,6 +289,10 @@
|
|||
<!-- TODO: remove translatable=false attribute once text is stable -->
|
||||
<string name="research_send_usage_info" translatable="false">Send usage info</string>
|
||||
|
||||
<!-- Name for the research uploading service to be displayed to users. [CHAR LIMIT=50] -->
|
||||
<!-- TODO: remove translatable=false attribute once text is stable -->
|
||||
<string name="research_log_uploader_name" translatable="false">Research Uploader Service</string>
|
||||
|
||||
<!-- Preference for input language selection -->
|
||||
<string name="select_language">Input languages</string>
|
||||
|
||||
|
|
|
@ -35,9 +35,9 @@
|
|||
<style name="KeyboardView">
|
||||
<item name="android:background">@drawable/keyboard_background</item>
|
||||
<item name="keyBackground">@drawable/btn_keyboard_key</item>
|
||||
<item name="keyLetterRatio">@fraction/key_letter_ratio</item>
|
||||
<item name="keyLetterSize">@fraction/key_letter_ratio</item>
|
||||
<item name="keyLargeLetterRatio">@fraction/key_large_letter_ratio</item>
|
||||
<item name="keyLabelRatio">@fraction/key_label_ratio</item>
|
||||
<item name="keyLabelSize">@fraction/key_label_ratio</item>
|
||||
<item name="keyLargeLabelRatio">@fraction/key_large_label_ratio</item>
|
||||
<item name="keyHintLetterRatio">@fraction/key_hint_letter_ratio</item>
|
||||
<item name="keyHintLabelRatio">@fraction/key_hint_label_ratio</item>
|
||||
|
@ -78,6 +78,9 @@
|
|||
<item name="gestureFloatingPreviewTextConnectorColor">@android:color/white</item>
|
||||
<item name="gestureFloatingPreviewTextConnectorWidth">@dimen/gesture_floating_preview_text_connector_width</item>
|
||||
<item name="gestureFloatingPreviewTextLingerTimeout">@integer/config_gesture_floating_preview_text_linger_timeout</item>
|
||||
<item name="gesturePreviewTrailFadeoutStartDelay">@integer/config_gesture_preview_trail_fadeout_start_delay</item>
|
||||
<item name="gesturePreviewTrailFadeoutDuration">@integer/config_gesture_preview_trail_fadeout_duration</item>
|
||||
<item name="gesturePreviewTrailUpdateInterval">@integer/config_gesture_preview_trail_update_interval</item>
|
||||
<item name="gesturePreviewTrailColor">@android:color/holo_blue_light</item>
|
||||
<item name="gesturePreviewTrailWidth">@dimen/gesture_preview_trail_width</item>
|
||||
<!-- Common attributes of MainKeyboardView -->
|
||||
|
@ -135,9 +138,9 @@
|
|||
<item name="colorTypedWord">@android:color/white</item>
|
||||
<item name="colorAutoCorrect">#FFFCAE00</item>
|
||||
<item name="colorSuggested">#FFFCAE00</item>
|
||||
<item name="alphaObsoleted">50</item>
|
||||
<item name="alphaObsoleted">50%</item>
|
||||
<item name="suggestionsCountInStrip">@integer/suggestions_count_in_strip</item>
|
||||
<item name="centerSuggestionPercentile">@integer/center_suggestion_percentile</item>
|
||||
<item name="centerSuggestionPercentile">@fraction/center_suggestion_percentile</item>
|
||||
<item name="maxMoreSuggestionsRow">@integer/max_more_suggestions_row</item>
|
||||
<item name="minMoreSuggestionsWidth">@fraction/min_more_suggestions_width</item>
|
||||
</style>
|
||||
|
@ -370,12 +373,12 @@
|
|||
<item name="colorTypedWord">@android:color/holo_blue_light</item>
|
||||
<item name="colorAutoCorrect">@android:color/holo_blue_light</item>
|
||||
<item name="colorSuggested">@android:color/holo_blue_light</item>
|
||||
<item name="alphaValidTypedWord">85</item>
|
||||
<item name="alphaTypedWord">85</item>
|
||||
<item name="alphaSuggested">70</item>
|
||||
<item name="alphaObsoleted">70</item>
|
||||
<item name="alphaValidTypedWord">85%</item>
|
||||
<item name="alphaTypedWord">85%</item>
|
||||
<item name="alphaSuggested">70%</item>
|
||||
<item name="alphaObsoleted">70%</item>
|
||||
<item name="suggestionsCountInStrip">@integer/suggestions_count_in_strip</item>
|
||||
<item name="centerSuggestionPercentile">@integer/center_suggestion_percentile</item>
|
||||
<item name="centerSuggestionPercentile">@fraction/center_suggestion_percentile</item>
|
||||
<item name="maxMoreSuggestionsRow">@integer/max_more_suggestions_row</item>
|
||||
<item name="minMoreSuggestionsWidth">@fraction/min_more_suggestions_width</item>
|
||||
</style>
|
||||
|
|
|
@ -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.Keyboard;
|
||||
import com.android.inputmethod.keyboard.KeyboardView;
|
||||
import com.android.inputmethod.latin.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Exposes a virtual view sub-tree for {@link KeyboardView} and generates
|
||||
|
@ -55,7 +56,7 @@ public class AccessibilityEntityProvider extends AccessibilityNodeProviderCompat
|
|||
private final AccessibilityUtils mAccessibilityUtils;
|
||||
|
||||
/** A map of integer IDs to {@link Key}s. */
|
||||
private final SparseArray<Key> mVirtualViewIdToKey = new SparseArray<Key>();
|
||||
private final SparseArray<Key> mVirtualViewIdToKey = CollectionUtils.newSparseArray();
|
||||
|
||||
/** Temporary rect used to calculate in-screen bounds. */
|
||||
private final Rect mTempBoundsInScreen = new Rect();
|
||||
|
|
|
@ -19,10 +19,15 @@ package com.android.inputmethod.accessibility;
|
|||
import android.content.Context;
|
||||
import android.inputmethodservice.InputMethodService;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
import android.os.SystemClock;
|
||||
import android.provider.Settings;
|
||||
import android.support.v4.view.accessibility.AccessibilityEventCompat;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
|
@ -138,9 +143,10 @@ public class AccessibilityUtils {
|
|||
* Sends the specified text to the {@link AccessibilityManager} to be
|
||||
* spoken.
|
||||
*
|
||||
* @param text the text to speak
|
||||
* @param view The source view.
|
||||
* @param text The text to speak.
|
||||
*/
|
||||
public void speak(CharSequence text) {
|
||||
public void announceForAccessibility(View view, CharSequence text) {
|
||||
if (!mAccessibilityManager.isEnabled()) {
|
||||
Log.e(TAG, "Attempted to speak when accessibility was disabled!");
|
||||
return;
|
||||
|
@ -149,8 +155,7 @@ public class AccessibilityUtils {
|
|||
// The following is a hack to avoid using the heavy-weight TextToSpeech
|
||||
// class. Instead, we're just forcing a fake AccessibilityEvent into
|
||||
// the screen reader to make it speak.
|
||||
final AccessibilityEvent event = AccessibilityEvent
|
||||
.obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED);
|
||||
final AccessibilityEvent event = AccessibilityEvent.obtain();
|
||||
|
||||
event.setPackageName(PACKAGE);
|
||||
event.setClassName(CLASS);
|
||||
|
@ -158,20 +163,34 @@ public class AccessibilityUtils {
|
|||
event.setEnabled(true);
|
||||
event.getText().add(text);
|
||||
|
||||
mAccessibilityManager.sendAccessibilityEvent(event);
|
||||
// Platforms starting at SDK 16 should use announce events.
|
||||
if (Build.VERSION.SDK_INT >= 16) {
|
||||
event.setEventType(AccessibilityEventCompat.TYPE_ANNOUNCEMENT);
|
||||
} else {
|
||||
event.setEventType(AccessibilityEvent.TYPE_VIEW_FOCUSED);
|
||||
}
|
||||
|
||||
final ViewParent viewParent = view.getParent();
|
||||
if ((viewParent == null) || !(viewParent instanceof ViewGroup)) {
|
||||
Log.e(TAG, "Failed to obtain ViewParent in announceForAccessibility");
|
||||
return;
|
||||
}
|
||||
|
||||
viewParent.requestSendAccessibilityEvent(view, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles speaking the "connect a headset to hear passwords" notification
|
||||
* when connecting to a password field.
|
||||
*
|
||||
* @param view The source view.
|
||||
* @param editorInfo The input connection's editor info attribute.
|
||||
* @param restarting Whether the connection is being restarted.
|
||||
*/
|
||||
public void onStartInputViewInternal(EditorInfo editorInfo, boolean restarting) {
|
||||
public void onStartInputViewInternal(View view, EditorInfo editorInfo, boolean restarting) {
|
||||
if (shouldObscureInput(editorInfo)) {
|
||||
final CharSequence text = mContext.getText(R.string.spoken_use_headphones);
|
||||
speak(text);
|
||||
announceForAccessibility(view, text);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ import android.support.v4.view.accessibility.AccessibilityEventCompat;
|
|||
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
import com.android.inputmethod.keyboard.Key;
|
||||
import com.android.inputmethod.keyboard.Keyboard;
|
||||
|
@ -44,7 +43,7 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
|
|||
|
||||
/**
|
||||
* Inset in pixels to look for keys when the user's finger exits the
|
||||
* keyboard area. See {@link ViewConfiguration#getScaledEdgeSlop()}.
|
||||
* keyboard area.
|
||||
*/
|
||||
private int mEdgeSlop;
|
||||
|
||||
|
@ -62,7 +61,8 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
|
|||
|
||||
private void initInternal(InputMethodService inputMethod) {
|
||||
mInputMethod = inputMethod;
|
||||
mEdgeSlop = ViewConfiguration.get(inputMethod).getScaledEdgeSlop();
|
||||
mEdgeSlop = inputMethod.getResources().getDimensionPixelSize(
|
||||
R.dimen.accessibility_edge_slop);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -114,8 +114,14 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
|
|||
public boolean dispatchHoverEvent(MotionEvent event, PointerTracker tracker) {
|
||||
final int x = (int) event.getX();
|
||||
final int y = (int) event.getY();
|
||||
final Key key = tracker.getKeyOn(x, y);
|
||||
final Key previousKey = mLastHoverKey;
|
||||
final Key key;
|
||||
|
||||
if (pointInView(x, y)) {
|
||||
key = tracker.getKeyOn(x, y);
|
||||
} else {
|
||||
key = null;
|
||||
}
|
||||
|
||||
mLastHoverKey = key;
|
||||
|
||||
|
@ -123,7 +129,7 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
|
|||
case MotionEvent.ACTION_HOVER_EXIT:
|
||||
// Make sure we're not getting an EXIT event because the user slid
|
||||
// off the keyboard area, then force a key press.
|
||||
if (pointInView(x, y) && (key != null)) {
|
||||
if (key != null) {
|
||||
getAccessibilityNodeProvider().simulateKeyPress(key);
|
||||
}
|
||||
//$FALL-THROUGH$
|
||||
|
@ -250,7 +256,7 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
|
|||
text = context.getText(R.string.spoken_description_shiftmode_off);
|
||||
}
|
||||
|
||||
AccessibilityUtils.getInstance().speak(text);
|
||||
AccessibilityUtils.getInstance().announceForAccessibility(mView, text);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -290,6 +296,6 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
|
|||
}
|
||||
|
||||
final String text = context.getString(resId);
|
||||
AccessibilityUtils.getInstance().speak(text);
|
||||
AccessibilityUtils.getInstance().announceForAccessibility(mView, text);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import android.view.inputmethod.EditorInfo;
|
|||
import com.android.inputmethod.keyboard.Key;
|
||||
import com.android.inputmethod.keyboard.Keyboard;
|
||||
import com.android.inputmethod.keyboard.KeyboardId;
|
||||
import com.android.inputmethod.latin.CollectionUtils;
|
||||
import com.android.inputmethod.latin.R;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
@ -38,7 +39,7 @@ public class KeyCodeDescriptionMapper {
|
|||
private static KeyCodeDescriptionMapper sInstance = new KeyCodeDescriptionMapper();
|
||||
|
||||
// Map of key labels to spoken description resource IDs
|
||||
private final HashMap<CharSequence, Integer> mKeyLabelMap;
|
||||
private final HashMap<CharSequence, Integer> mKeyLabelMap = CollectionUtils.newHashMap();
|
||||
|
||||
// Sparse array of spoken description resource IDs indexed by key codes
|
||||
private final SparseIntArray mKeyCodeMap;
|
||||
|
@ -52,7 +53,6 @@ public class KeyCodeDescriptionMapper {
|
|||
}
|
||||
|
||||
private KeyCodeDescriptionMapper() {
|
||||
mKeyLabelMap = new HashMap<CharSequence, Integer>();
|
||||
mKeyCodeMap = new SparseIntArray();
|
||||
}
|
||||
|
||||
|
|
|
@ -16,10 +16,6 @@
|
|||
|
||||
package com.android.inputmethod.compat;
|
||||
|
||||
import com.android.inputmethod.latin.LatinImeLogger;
|
||||
import com.android.inputmethod.latin.SuggestedWords;
|
||||
import com.android.inputmethod.latin.SuggestionSpanPickedNotificationReceiver;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
|
@ -27,6 +23,11 @@ import android.text.Spanned;
|
|||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.inputmethod.latin.CollectionUtils;
|
||||
import com.android.inputmethod.latin.LatinImeLogger;
|
||||
import com.android.inputmethod.latin.SuggestedWords;
|
||||
import com.android.inputmethod.latin.SuggestionSpanPickedNotificationReceiver;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
|
@ -119,7 +120,7 @@ public class SuggestionSpanUtils {
|
|||
} else {
|
||||
spannable = new SpannableString(pickedWord);
|
||||
}
|
||||
final ArrayList<String> suggestionsList = new ArrayList<String>();
|
||||
final ArrayList<String> suggestionsList = CollectionUtils.newArrayList();
|
||||
for (int i = 0; i < suggestedWords.size(); ++i) {
|
||||
if (suggestionsList.size() >= OBJ_SUGGESTIONS_MAX_SIZE) {
|
||||
break;
|
||||
|
|
|
@ -16,10 +16,10 @@
|
|||
|
||||
package com.android.inputmethod.keyboard;
|
||||
|
||||
import com.android.inputmethod.latin.Constants;
|
||||
|
||||
|
||||
public class KeyDetector {
|
||||
public static final int NOT_A_CODE = -1;
|
||||
|
||||
private final int mKeyHysteresisDistanceSquared;
|
||||
|
||||
private Keyboard mKeyboard;
|
||||
|
@ -59,6 +59,9 @@ public class KeyDetector {
|
|||
}
|
||||
|
||||
public Keyboard getKeyboard() {
|
||||
if (mKeyboard == null) {
|
||||
throw new IllegalStateException("keyboard isn't set");
|
||||
}
|
||||
return mKeyboard;
|
||||
}
|
||||
|
||||
|
@ -100,7 +103,7 @@ public class KeyDetector {
|
|||
final StringBuilder sb = new StringBuilder();
|
||||
boolean addDelimiter = false;
|
||||
for (final int code : codes) {
|
||||
if (code == NOT_A_CODE) break;
|
||||
if (code == Constants.NOT_A_CODE) break;
|
||||
if (addDelimiter) sb.append(", ");
|
||||
sb.append(Keyboard.printableCode(code));
|
||||
addDelimiter = true;
|
||||
|
|
|
@ -33,6 +33,7 @@ import com.android.inputmethod.keyboard.internal.KeyStyles;
|
|||
import com.android.inputmethod.keyboard.internal.KeyboardCodesSet;
|
||||
import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
|
||||
import com.android.inputmethod.keyboard.internal.KeyboardTextsSet;
|
||||
import com.android.inputmethod.latin.CollectionUtils;
|
||||
import com.android.inputmethod.latin.LatinImeLogger;
|
||||
import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
|
||||
import com.android.inputmethod.latin.R;
|
||||
|
@ -134,7 +135,7 @@ public class Keyboard {
|
|||
public final Key[] mAltCodeKeysWhileTyping;
|
||||
public final KeyboardIconsSet mIconsSet;
|
||||
|
||||
private final SparseArray<Key> mKeyCache = new SparseArray<Key>();
|
||||
private final SparseArray<Key> mKeyCache = CollectionUtils.newSparseArray();
|
||||
|
||||
private final ProximityInfo mProximityInfo;
|
||||
private final boolean mProximityCharsCorrectionEnabled;
|
||||
|
@ -219,6 +220,11 @@ public class Keyboard {
|
|||
return code >= CODE_SPACE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mId.toString();
|
||||
}
|
||||
|
||||
public static class Params {
|
||||
public KeyboardId mId;
|
||||
public int mThemeId;
|
||||
|
@ -249,9 +255,9 @@ public class Keyboard {
|
|||
public int GRID_WIDTH;
|
||||
public int GRID_HEIGHT;
|
||||
|
||||
public final HashSet<Key> mKeys = new HashSet<Key>();
|
||||
public final ArrayList<Key> mShiftKeys = new ArrayList<Key>();
|
||||
public final ArrayList<Key> mAltCodeKeysWhileTyping = new ArrayList<Key>();
|
||||
public final HashSet<Key> mKeys = CollectionUtils.newHashSet();
|
||||
public final ArrayList<Key> mShiftKeys = CollectionUtils.newArrayList();
|
||||
public final ArrayList<Key> mAltCodeKeysWhileTyping = CollectionUtils.newArrayList();
|
||||
public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet();
|
||||
public final KeyboardCodesSet mCodesSet = new KeyboardCodesSet();
|
||||
public final KeyboardTextsSet mTextsSet = new KeyboardTextsSet();
|
||||
|
@ -278,9 +284,10 @@ public class Keyboard {
|
|||
public void load(String[] data) {
|
||||
final int dataLength = data.length;
|
||||
if (dataLength % TOUCH_POSITION_CORRECTION_RECORD_SIZE != 0) {
|
||||
if (LatinImeLogger.sDBG)
|
||||
if (LatinImeLogger.sDBG) {
|
||||
throw new RuntimeException(
|
||||
"the size of touch position correction data is invalid");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -865,10 +872,12 @@ public class Keyboard {
|
|||
final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
|
||||
R.styleable.Keyboard);
|
||||
try {
|
||||
if (a.hasValue(R.styleable.Keyboard_horizontalGap))
|
||||
if (a.hasValue(R.styleable.Keyboard_horizontalGap)) {
|
||||
throw new XmlParseUtils.IllegalAttribute(parser, "horizontalGap");
|
||||
if (a.hasValue(R.styleable.Keyboard_verticalGap))
|
||||
}
|
||||
if (a.hasValue(R.styleable.Keyboard_verticalGap)) {
|
||||
throw new XmlParseUtils.IllegalAttribute(parser, "verticalGap");
|
||||
}
|
||||
return new Row(mResources, mParams, parser, mCurrentY);
|
||||
} finally {
|
||||
a.recycle();
|
||||
|
@ -916,7 +925,9 @@ public class Keyboard {
|
|||
throws XmlPullParserException, IOException {
|
||||
if (skip) {
|
||||
XmlParseUtils.checkEndTag(TAG_KEY, parser);
|
||||
if (DEBUG) startEndTag("<%s /> skipped", TAG_KEY);
|
||||
if (DEBUG) {
|
||||
startEndTag("<%s /> skipped", TAG_KEY);
|
||||
}
|
||||
} else {
|
||||
final Key key = new Key(mResources, mParams, row, parser);
|
||||
if (DEBUG) {
|
||||
|
@ -1094,9 +1105,9 @@ public class Keyboard {
|
|||
|
||||
private boolean parseCaseCondition(XmlPullParser parser) {
|
||||
final KeyboardId id = mParams.mId;
|
||||
if (id == null)
|
||||
if (id == null) {
|
||||
return true;
|
||||
|
||||
}
|
||||
final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
|
||||
R.styleable.Keyboard_Case);
|
||||
try {
|
||||
|
@ -1200,9 +1211,9 @@ public class Keyboard {
|
|||
// If <case> does not have "index" attribute, that means this <case> is wild-card for
|
||||
// the attribute.
|
||||
final TypedValue v = a.peekValue(index);
|
||||
if (v == null)
|
||||
if (v == null) {
|
||||
return true;
|
||||
|
||||
}
|
||||
if (isIntegerValue(v)) {
|
||||
return intValue == a.getInt(index, 0);
|
||||
} else if (isStringValue(v)) {
|
||||
|
@ -1213,9 +1224,10 @@ public class Keyboard {
|
|||
|
||||
private static boolean stringArrayContains(String[] array, String value) {
|
||||
for (final String elem : array) {
|
||||
if (elem.equals(value))
|
||||
if (elem.equals(value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1237,16 +1249,18 @@ public class Keyboard {
|
|||
TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser),
|
||||
R.styleable.Keyboard_Key);
|
||||
try {
|
||||
if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName))
|
||||
if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName)) {
|
||||
throw new XmlParseUtils.ParseException("<" + TAG_KEY_STYLE
|
||||
+ "/> needs styleName attribute", parser);
|
||||
}
|
||||
if (DEBUG) {
|
||||
startEndTag("<%s styleName=%s />%s", TAG_KEY_STYLE,
|
||||
keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName),
|
||||
skip ? " skipped" : "");
|
||||
}
|
||||
if (!skip)
|
||||
if (!skip) {
|
||||
mParams.mKeyStyles.parseKeyStyleAttributes(keyStyleAttr, keyAttrs, parser);
|
||||
}
|
||||
} finally {
|
||||
keyStyleAttr.recycle();
|
||||
keyAttrs.recycle();
|
||||
|
@ -1267,8 +1281,9 @@ public class Keyboard {
|
|||
}
|
||||
|
||||
private void endRow(Row row) {
|
||||
if (mCurrentRow == null)
|
||||
if (mCurrentRow == null) {
|
||||
throw new InflateException("orphan end row tag");
|
||||
}
|
||||
if (mRightEdgeKey != null) {
|
||||
mRightEdgeKey.markAsRightEdge(mParams);
|
||||
mRightEdgeKey = null;
|
||||
|
@ -1304,8 +1319,9 @@ public class Keyboard {
|
|||
public static float getDimensionOrFraction(TypedArray a, int index, int base,
|
||||
float defValue) {
|
||||
final TypedValue value = a.peekValue(index);
|
||||
if (value == null)
|
||||
if (value == null) {
|
||||
return defValue;
|
||||
}
|
||||
if (isFractionValue(value)) {
|
||||
return a.getFraction(index, base, base, defValue);
|
||||
} else if (isDimensionValue(value)) {
|
||||
|
@ -1316,8 +1332,9 @@ public class Keyboard {
|
|||
|
||||
public static int getEnumValue(TypedArray a, int index, int defValue) {
|
||||
final TypedValue value = a.peekValue(index);
|
||||
if (value == null)
|
||||
if (value == null) {
|
||||
return defValue;
|
||||
}
|
||||
if (isIntegerValue(value)) {
|
||||
return a.getInt(index, defValue);
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package com.android.inputmethod.keyboard;
|
||||
|
||||
import com.android.inputmethod.latin.Constants;
|
||||
import com.android.inputmethod.latin.InputPointers;
|
||||
|
||||
public interface KeyboardActionListener {
|
||||
|
@ -44,21 +45,16 @@ public interface KeyboardActionListener {
|
|||
*
|
||||
* @param primaryCode this is the code of the key that was pressed
|
||||
* @param x x-coordinate pixel of touched event. If {@link #onCodeInput} is not called by
|
||||
* {@link PointerTracker} or so, the value should be {@link #NOT_A_TOUCH_COORDINATE}.
|
||||
* If it's called on insertion from the suggestion strip, it should be
|
||||
* {@link #SUGGESTION_STRIP_COORDINATE}.
|
||||
* {@link PointerTracker} or so, the value should be
|
||||
* {@link Constants#NOT_A_COORDINATE}. If it's called on insertion from the
|
||||
* suggestion strip, it should be {@link Constants#SUGGESTION_STRIP_COORDINATE}.
|
||||
* @param y y-coordinate pixel of touched event. If {@link #onCodeInput} is not called by
|
||||
* {@link PointerTracker} or so, the value should be {@link #NOT_A_TOUCH_COORDINATE}.
|
||||
* If it's called on insertion from the suggestion strip, it should be
|
||||
* {@link #SUGGESTION_STRIP_COORDINATE}.
|
||||
* {@link PointerTracker} or so, the value should be
|
||||
* {@link Constants#NOT_A_COORDINATE}.If it's called on insertion from the
|
||||
* suggestion strip, it should be {@link Constants#SUGGESTION_STRIP_COORDINATE}.
|
||||
*/
|
||||
public void onCodeInput(int primaryCode, int x, int y);
|
||||
|
||||
// See {@link Adapter#isInvalidCoordinate(int)}.
|
||||
public static final int NOT_A_TOUCH_COORDINATE = -1;
|
||||
public static final int SUGGESTION_STRIP_COORDINATE = -2;
|
||||
public static final int SPELL_CHECKER_COORDINATE = -3;
|
||||
|
||||
/**
|
||||
* Sends a sequence of characters to the listener.
|
||||
*
|
||||
|
@ -119,9 +115,9 @@ public interface KeyboardActionListener {
|
|||
|
||||
// TODO: Remove this method when the vertical correction is removed.
|
||||
public static boolean isInvalidCoordinate(int coordinate) {
|
||||
// Detect {@link KeyboardActionListener#NOT_A_TOUCH_COORDINATE},
|
||||
// {@link KeyboardActionListener#SUGGESTION_STRIP_COORDINATE}, and
|
||||
// {@link KeyboardActionListener#SPELL_CHECKER_COORDINATE}.
|
||||
// Detect {@link Constants#NOT_A_COORDINATE},
|
||||
// {@link Constants#SUGGESTION_STRIP_COORDINATE}, and
|
||||
// {@link Constants#SPELL_CHECKER_COORDINATE}.
|
||||
return coordinate < 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ import android.view.inputmethod.InputMethodSubtype;
|
|||
|
||||
import com.android.inputmethod.compat.EditorInfoCompatUtils;
|
||||
import com.android.inputmethod.keyboard.KeyboardLayoutSet.Params.ElementParams;
|
||||
import com.android.inputmethod.latin.CollectionUtils;
|
||||
import com.android.inputmethod.latin.InputAttributes;
|
||||
import com.android.inputmethod.latin.InputTypeUtils;
|
||||
import com.android.inputmethod.latin.LatinImeLogger;
|
||||
|
@ -71,7 +72,7 @@ public class KeyboardLayoutSet {
|
|||
private final Params mParams;
|
||||
|
||||
private static final HashMap<KeyboardId, SoftReference<Keyboard>> sKeyboardCache =
|
||||
new HashMap<KeyboardId, SoftReference<Keyboard>>();
|
||||
CollectionUtils.newHashMap();
|
||||
private static final KeysCache sKeysCache = new KeysCache();
|
||||
|
||||
public static class KeyboardLayoutSetException extends RuntimeException {
|
||||
|
@ -84,11 +85,7 @@ public class KeyboardLayoutSet {
|
|||
}
|
||||
|
||||
public static class KeysCache {
|
||||
private final HashMap<Key, Key> mMap;
|
||||
|
||||
public KeysCache() {
|
||||
mMap = new HashMap<Key, Key>();
|
||||
}
|
||||
private final HashMap<Key, Key> mMap = CollectionUtils.newHashMap();
|
||||
|
||||
public void clear() {
|
||||
mMap.clear();
|
||||
|
@ -120,7 +117,7 @@ public class KeyboardLayoutSet {
|
|||
int mWidth;
|
||||
// Sparse array of KeyboardLayoutSet element parameters indexed by element's id.
|
||||
final SparseArray<ElementParams> mKeyboardLayoutSetElementIdToParamsMap =
|
||||
new SparseArray<ElementParams>();
|
||||
CollectionUtils.newSparseArray();
|
||||
|
||||
static class ElementParams {
|
||||
int mKeyboardXmlId;
|
||||
|
|
|
@ -21,7 +21,6 @@ import android.content.SharedPreferences;
|
|||
import android.content.res.Resources;
|
||||
import android.util.Log;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.InflateException;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
|
@ -38,7 +37,7 @@ import com.android.inputmethod.latin.LatinImeLogger;
|
|||
import com.android.inputmethod.latin.R;
|
||||
import com.android.inputmethod.latin.SettingsValues;
|
||||
import com.android.inputmethod.latin.SubtypeSwitcher;
|
||||
import com.android.inputmethod.latin.Utils;
|
||||
import com.android.inputmethod.latin.WordComposer;
|
||||
|
||||
public class KeyboardSwitcher implements KeyboardState.SwitchActions {
|
||||
private static final String TAG = KeyboardSwitcher.class.getSimpleName();
|
||||
|
@ -46,24 +45,24 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions {
|
|||
public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20110916";
|
||||
|
||||
static class KeyboardTheme {
|
||||
public final String mName;
|
||||
public final int mThemeId;
|
||||
public final int mStyleId;
|
||||
|
||||
public KeyboardTheme(String name, int themeId, int styleId) {
|
||||
mName = name;
|
||||
// Note: The themeId should be aligned with "themeId" attribute of Keyboard style
|
||||
// in values/style.xml.
|
||||
public KeyboardTheme(int themeId, int styleId) {
|
||||
mThemeId = themeId;
|
||||
mStyleId = styleId;
|
||||
}
|
||||
}
|
||||
|
||||
private static final KeyboardTheme[] KEYBOARD_THEMES = {
|
||||
new KeyboardTheme("Basic", 0, R.style.KeyboardTheme),
|
||||
new KeyboardTheme("HighContrast", 1, R.style.KeyboardTheme_HighContrast),
|
||||
new KeyboardTheme("Stone", 6, R.style.KeyboardTheme_Stone),
|
||||
new KeyboardTheme("Stone.Bold", 7, R.style.KeyboardTheme_Stone_Bold),
|
||||
new KeyboardTheme("GingerBread", 8, R.style.KeyboardTheme_Gingerbread),
|
||||
new KeyboardTheme("IceCreamSandwich", 5, R.style.KeyboardTheme_IceCreamSandwich),
|
||||
new KeyboardTheme(0, R.style.KeyboardTheme),
|
||||
new KeyboardTheme(1, R.style.KeyboardTheme_HighContrast),
|
||||
new KeyboardTheme(6, R.style.KeyboardTheme_Stone),
|
||||
new KeyboardTheme(7, R.style.KeyboardTheme_Stone_Bold),
|
||||
new KeyboardTheme(8, R.style.KeyboardTheme_Gingerbread),
|
||||
new KeyboardTheme(5, R.style.KeyboardTheme_IceCreamSandwich),
|
||||
};
|
||||
|
||||
private SubtypeSwitcher mSubtypeSwitcher;
|
||||
|
@ -354,22 +353,9 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions {
|
|||
mKeyboardView.closing();
|
||||
}
|
||||
|
||||
Utils.GCUtils.getInstance().reset();
|
||||
boolean tryGC = true;
|
||||
for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
|
||||
try {
|
||||
setContextThemeWrapper(mLatinIME, mKeyboardTheme);
|
||||
mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
|
||||
R.layout.input_view, null);
|
||||
tryGC = false;
|
||||
} catch (OutOfMemoryError e) {
|
||||
Log.w(TAG, "load keyboard failed: " + e);
|
||||
tryGC = Utils.GCUtils.getInstance().tryGCOrWait(mKeyboardTheme.mName, e);
|
||||
} catch (InflateException e) {
|
||||
Log.w(TAG, "load keyboard failed: " + e);
|
||||
tryGC = Utils.GCUtils.getInstance().tryGCOrWait(mKeyboardTheme.mName, e);
|
||||
}
|
||||
}
|
||||
|
||||
mKeyboardView = (MainKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view);
|
||||
if (isHardwareAcceleratedDrawingEnabled) {
|
||||
|
@ -402,4 +388,16 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getManualCapsMode() {
|
||||
switch (getKeyboard().mId.mElementId) {
|
||||
case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
|
||||
case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
|
||||
return WordComposer.CAPS_MODE_MANUAL_SHIFT_LOCKED;
|
||||
case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
|
||||
return WordComposer.CAPS_MODE_MANUAL_SHIFTED;
|
||||
default:
|
||||
return WordComposer.CAPS_MODE_OFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ import android.graphics.Typeface;
|
|||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Message;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
|
@ -38,6 +39,7 @@ import android.view.ViewGroup;
|
|||
import android.widget.TextView;
|
||||
|
||||
import com.android.inputmethod.keyboard.internal.PreviewPlacerView;
|
||||
import com.android.inputmethod.latin.CollectionUtils;
|
||||
import com.android.inputmethod.latin.Constants;
|
||||
import com.android.inputmethod.latin.LatinImeLogger;
|
||||
import com.android.inputmethod.latin.R;
|
||||
|
@ -78,8 +80,12 @@ import java.util.HashSet;
|
|||
* @attr ref R.styleable#KeyboardView_shadowRadius
|
||||
*/
|
||||
public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
||||
private static final String TAG = KeyboardView.class.getSimpleName();
|
||||
|
||||
// Miscellaneous constants
|
||||
private static final int[] LONG_PRESSABLE_STATE_SET = { android.R.attr.state_long_pressable };
|
||||
private static final float UNDEFINED_RATIO = -1.0f;
|
||||
private static final int UNDEFINED_DIMENSION = -1;
|
||||
|
||||
// XML attributes
|
||||
protected final float mVerticalCorrection;
|
||||
|
@ -103,23 +109,19 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
|||
|
||||
// Key preview
|
||||
private final int mKeyPreviewLayoutId;
|
||||
private final SparseArray<TextView> mKeyPreviewTexts = CollectionUtils.newSparseArray();
|
||||
protected final KeyPreviewDrawParams mKeyPreviewDrawParams;
|
||||
private boolean mShowKeyPreviewPopup = true;
|
||||
private int mDelayAfterPreview;
|
||||
private final PreviewPlacerView mPreviewPlacerView;
|
||||
|
||||
/** True if {@link KeyboardView} should handle gesture events. */
|
||||
protected boolean mShouldHandleGesture;
|
||||
|
||||
// Drawing
|
||||
/** True if the entire keyboard needs to be dimmed. */
|
||||
private boolean mNeedsToDimEntireKeyboard;
|
||||
/** Whether the keyboard bitmap buffer needs to be redrawn before it's blitted. **/
|
||||
private boolean mBufferNeedsUpdate;
|
||||
/** True if all keys should be drawn */
|
||||
private boolean mInvalidateAllKeys;
|
||||
/** The keys that should be drawn */
|
||||
private final HashSet<Key> mInvalidatedKeys = new HashSet<Key>();
|
||||
private final HashSet<Key> mInvalidatedKeys = CollectionUtils.newHashSet();
|
||||
/** The working rectangle variable */
|
||||
private final Rect mWorkingRect = new Rect();
|
||||
/** The keyboard bitmap buffer for faster updates */
|
||||
|
@ -131,9 +133,9 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
|||
private final Paint mPaint = new Paint();
|
||||
private final Paint.FontMetrics mFontMetrics = new Paint.FontMetrics();
|
||||
// This sparse array caches key label text height in pixel indexed by key label text size.
|
||||
private static final SparseArray<Float> sTextHeightCache = new SparseArray<Float>();
|
||||
private static final SparseArray<Float> sTextHeightCache = CollectionUtils.newSparseArray();
|
||||
// This sparse array caches key label text width in pixel indexed by key label text size.
|
||||
private static final SparseArray<Float> sTextWidthCache = new SparseArray<Float>();
|
||||
private static final SparseArray<Float> sTextWidthCache = CollectionUtils.newSparseArray();
|
||||
private static final char[] KEY_LABEL_REFERENCE_CHAR = { 'M' };
|
||||
private static final char[] KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR = { '8' };
|
||||
|
||||
|
@ -153,7 +155,10 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
|||
final PointerTracker tracker = (PointerTracker) msg.obj;
|
||||
switch (msg.what) {
|
||||
case MSG_DISMISS_KEY_PREVIEW:
|
||||
tracker.getKeyPreviewText().setVisibility(View.INVISIBLE);
|
||||
final TextView previewText = keyboardView.mKeyPreviewTexts.get(tracker.mPointerId);
|
||||
if (previewText != null) {
|
||||
previewText.setVisibility(INVISIBLE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -166,7 +171,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
|||
removeMessages(MSG_DISMISS_KEY_PREVIEW, tracker);
|
||||
}
|
||||
|
||||
public void cancelAllDismissKeyPreviews() {
|
||||
private void cancelAllDismissKeyPreviews() {
|
||||
removeMessages(MSG_DISMISS_KEY_PREVIEW);
|
||||
}
|
||||
|
||||
|
@ -199,7 +204,6 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
|||
private final float mKeyHintLetterRatio;
|
||||
private final float mKeyShiftedLetterHintRatio;
|
||||
private final float mKeyHintLabelRatio;
|
||||
private static final float UNDEFINED_RATIO = -1.0f;
|
||||
|
||||
public final Rect mPadding = new Rect();
|
||||
public int mKeyLetterSize;
|
||||
|
@ -211,26 +215,22 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
|||
public int mKeyHintLabelSize;
|
||||
public int mAnimAlpha;
|
||||
|
||||
public KeyDrawParams(TypedArray a) {
|
||||
public KeyDrawParams(final TypedArray a) {
|
||||
mKeyBackground = a.getDrawable(R.styleable.KeyboardView_keyBackground);
|
||||
if (a.hasValue(R.styleable.KeyboardView_keyLetterSize)) {
|
||||
mKeyLetterRatio = UNDEFINED_RATIO;
|
||||
mKeyLetterSize = a.getDimensionPixelSize(R.styleable.KeyboardView_keyLetterSize, 0);
|
||||
} else {
|
||||
mKeyLetterRatio = getRatio(a, R.styleable.KeyboardView_keyLetterRatio);
|
||||
if (!isValidFraction(mKeyLetterRatio = getFraction(a,
|
||||
R.styleable.KeyboardView_keyLetterSize))) {
|
||||
mKeyLetterSize = getDimensionPixelSize(a, R.styleable.KeyboardView_keyLetterSize);
|
||||
}
|
||||
if (a.hasValue(R.styleable.KeyboardView_keyLabelSize)) {
|
||||
mKeyLabelRatio = UNDEFINED_RATIO;
|
||||
mKeyLabelSize = a.getDimensionPixelSize(R.styleable.KeyboardView_keyLabelSize, 0);
|
||||
} else {
|
||||
mKeyLabelRatio = getRatio(a, R.styleable.KeyboardView_keyLabelRatio);
|
||||
if (!isValidFraction(mKeyLabelRatio = getFraction(a,
|
||||
R.styleable.KeyboardView_keyLabelSize))) {
|
||||
mKeyLabelSize = getDimensionPixelSize(a, R.styleable.KeyboardView_keyLabelSize);
|
||||
}
|
||||
mKeyLargeLabelRatio = getRatio(a, R.styleable.KeyboardView_keyLargeLabelRatio);
|
||||
mKeyLargeLetterRatio = getRatio(a, R.styleable.KeyboardView_keyLargeLetterRatio);
|
||||
mKeyHintLetterRatio = getRatio(a, R.styleable.KeyboardView_keyHintLetterRatio);
|
||||
mKeyShiftedLetterHintRatio = getRatio(a,
|
||||
mKeyLargeLabelRatio = getFraction(a, R.styleable.KeyboardView_keyLargeLabelRatio);
|
||||
mKeyLargeLetterRatio = getFraction(a, R.styleable.KeyboardView_keyLargeLetterRatio);
|
||||
mKeyHintLetterRatio = getFraction(a, R.styleable.KeyboardView_keyHintLetterRatio);
|
||||
mKeyShiftedLetterHintRatio = getFraction(a,
|
||||
R.styleable.KeyboardView_keyShiftedLetterHintRatio);
|
||||
mKeyHintLabelRatio = getRatio(a, R.styleable.KeyboardView_keyHintLabelRatio);
|
||||
mKeyHintLabelRatio = getFraction(a, R.styleable.KeyboardView_keyHintLabelRatio);
|
||||
mKeyLabelHorizontalPadding = a.getDimension(
|
||||
R.styleable.KeyboardView_keyLabelHorizontalPadding, 0);
|
||||
mKeyHintLetterPadding = a.getDimension(
|
||||
|
@ -257,10 +257,10 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
|||
}
|
||||
|
||||
public void updateKeyHeight(int keyHeight) {
|
||||
if (mKeyLetterRatio >= 0.0f) {
|
||||
if (isValidFraction(mKeyLetterRatio)) {
|
||||
mKeyLetterSize = (int)(keyHeight * mKeyLetterRatio);
|
||||
}
|
||||
if (mKeyLabelRatio >= 0.0f) {
|
||||
if (isValidFraction(mKeyLabelRatio)) {
|
||||
mKeyLabelSize = (int)(keyHeight * mKeyLabelRatio);
|
||||
}
|
||||
mKeyLargeLabelSize = (int)(keyHeight * mKeyLargeLabelRatio);
|
||||
|
@ -335,7 +335,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
|||
R.styleable.KeyboardView_keyPreviewOffset, 0);
|
||||
mPreviewHeight = a.getDimensionPixelSize(
|
||||
R.styleable.KeyboardView_keyPreviewHeight, 80);
|
||||
mPreviewTextRatio = getRatio(a, R.styleable.KeyboardView_keyPreviewTextRatio);
|
||||
mPreviewTextRatio = getFraction(a, R.styleable.KeyboardView_keyPreviewTextRatio);
|
||||
mPreviewTextColor = a.getColor(R.styleable.KeyboardView_keyPreviewTextColor, 0);
|
||||
mLingerTimeout = a.getInt(R.styleable.KeyboardView_keyPreviewLingerTimeout, 0);
|
||||
|
||||
|
@ -367,9 +367,9 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
|||
|
||||
final TypedArray a = context.obtainStyledAttributes(
|
||||
attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
|
||||
|
||||
mKeyDrawParams = new KeyDrawParams(a);
|
||||
mKeyPreviewDrawParams = new KeyPreviewDrawParams(a, mKeyDrawParams);
|
||||
mDelayAfterPreview = mKeyPreviewDrawParams.mLingerTimeout;
|
||||
mKeyPreviewLayoutId = a.getResourceId(R.styleable.KeyboardView_keyPreviewLayout, 0);
|
||||
if (mKeyPreviewLayoutId == 0) {
|
||||
mShowKeyPreviewPopup = false;
|
||||
|
@ -378,17 +378,30 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
|||
R.styleable.KeyboardView_verticalCorrection, 0);
|
||||
mMoreKeysLayout = a.getResourceId(R.styleable.KeyboardView_moreKeysLayout, 0);
|
||||
mBackgroundDimAlpha = a.getInt(R.styleable.KeyboardView_backgroundDimAlpha, 0);
|
||||
mPreviewPlacerView = new PreviewPlacerView(context, a);
|
||||
a.recycle();
|
||||
|
||||
mDelayAfterPreview = mKeyPreviewDrawParams.mLingerTimeout;
|
||||
|
||||
mPreviewPlacerView = new PreviewPlacerView(context, attrs);
|
||||
mPaint.setAntiAlias(true);
|
||||
}
|
||||
|
||||
// Read fraction value in TypedArray as float.
|
||||
/* package */ static float getRatio(TypedArray a, int index) {
|
||||
return a.getFraction(index, 1000, 1000, 1) / 1000.0f;
|
||||
static boolean isValidFraction(final float fraction) {
|
||||
return fraction >= 0.0f;
|
||||
}
|
||||
|
||||
static float getFraction(final TypedArray a, final int index) {
|
||||
final TypedValue value = a.peekValue(index);
|
||||
if (value == null || value.type != TypedValue.TYPE_FRACTION) {
|
||||
return UNDEFINED_RATIO;
|
||||
}
|
||||
return a.getFraction(index, 1, 1, UNDEFINED_RATIO);
|
||||
}
|
||||
|
||||
public static int getDimensionPixelSize(final TypedArray a, final int index) {
|
||||
final TypedValue value = a.peekValue(index);
|
||||
if (value == null || value.type != TypedValue.TYPE_DIMENSION) {
|
||||
return UNDEFINED_DIMENSION;
|
||||
}
|
||||
return a.getDimensionPixelSize(index, UNDEFINED_DIMENSION);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -438,9 +451,8 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
|||
return mShowKeyPreviewPopup;
|
||||
}
|
||||
|
||||
public void setGestureHandlingMode(boolean shouldHandleGesture,
|
||||
boolean drawsGesturePreviewTrail, boolean drawsGestureFloatingPreviewText) {
|
||||
mShouldHandleGesture = shouldHandleGesture;
|
||||
public void setGesturePreviewMode(boolean drawsGesturePreviewTrail,
|
||||
boolean drawsGestureFloatingPreviewText) {
|
||||
mPreviewPlacerView.setGesturePreviewMode(
|
||||
drawsGesturePreviewTrail, drawsGestureFloatingPreviewText);
|
||||
}
|
||||
|
@ -463,16 +475,12 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
|||
onDrawKeyboard(canvas);
|
||||
return;
|
||||
}
|
||||
if (mBufferNeedsUpdate || mOffscreenBuffer == null) {
|
||||
mBufferNeedsUpdate = false;
|
||||
|
||||
final boolean bufferNeedsUpdates = mInvalidateAllKeys || !mInvalidatedKeys.isEmpty();
|
||||
if (bufferNeedsUpdates || mOffscreenBuffer == null) {
|
||||
if (maybeAllocateOffscreenBuffer()) {
|
||||
mInvalidateAllKeys = true;
|
||||
// TODO: Stop using the offscreen canvas even when in software rendering
|
||||
if (mOffscreenCanvas != null) {
|
||||
mOffscreenCanvas.setBitmap(mOffscreenBuffer);
|
||||
} else {
|
||||
mOffscreenCanvas = new Canvas(mOffscreenBuffer);
|
||||
}
|
||||
maybeCreateOffscreenCanvas();
|
||||
}
|
||||
onDrawKeyboard(mOffscreenCanvas);
|
||||
}
|
||||
|
@ -501,6 +509,15 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
|||
}
|
||||
}
|
||||
|
||||
private void maybeCreateOffscreenCanvas() {
|
||||
// TODO: Stop using the offscreen canvas even when in software rendering
|
||||
if (mOffscreenCanvas != null) {
|
||||
mOffscreenCanvas.setBitmap(mOffscreenBuffer);
|
||||
} else {
|
||||
mOffscreenCanvas = new Canvas(mOffscreenBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
private void onDrawKeyboard(final Canvas canvas) {
|
||||
if (mKeyboard == null) return;
|
||||
|
||||
|
@ -528,14 +545,13 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
|||
}
|
||||
if (!isHardwareAccelerated) {
|
||||
canvas.clipRegion(mClipRegion, Region.Op.REPLACE);
|
||||
}
|
||||
|
||||
// Draw keyboard background.
|
||||
canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
|
||||
final Drawable background = getBackground();
|
||||
if (background != null) {
|
||||
background.draw(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Confirm if it's really required to draw all keys when hardware acceleration is on.
|
||||
if (drawAllKeys || isHardwareAccelerated) {
|
||||
|
@ -907,15 +923,30 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
|||
}
|
||||
}
|
||||
|
||||
// Called by {@link PointerTracker} constructor to create a TextView.
|
||||
@Override
|
||||
public TextView inflateKeyPreviewText() {
|
||||
private TextView getKeyPreviewText(final int pointerId) {
|
||||
TextView previewText = mKeyPreviewTexts.get(pointerId);
|
||||
if (previewText != null) {
|
||||
return previewText;
|
||||
}
|
||||
final Context context = getContext();
|
||||
if (mKeyPreviewLayoutId != 0) {
|
||||
return (TextView)LayoutInflater.from(context).inflate(mKeyPreviewLayoutId, null);
|
||||
previewText = (TextView)LayoutInflater.from(context).inflate(mKeyPreviewLayoutId, null);
|
||||
} else {
|
||||
return new TextView(context);
|
||||
previewText = new TextView(context);
|
||||
}
|
||||
mKeyPreviewTexts.put(pointerId, previewText);
|
||||
return previewText;
|
||||
}
|
||||
|
||||
private void dismissAllKeyPreviews() {
|
||||
final int pointerCount = mKeyPreviewTexts.size();
|
||||
for (int id = 0; id < pointerCount; id++) {
|
||||
final TextView previewText = mKeyPreviewTexts.get(id);
|
||||
if (previewText != null) {
|
||||
previewText.setVisibility(INVISIBLE);
|
||||
}
|
||||
}
|
||||
PointerTracker.setReleasedKeyGraphicsToAllKeys();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -936,10 +967,19 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
|||
final int[] viewOrigin = new int[2];
|
||||
getLocationInWindow(viewOrigin);
|
||||
mPreviewPlacerView.setOrigin(viewOrigin[0], viewOrigin[1]);
|
||||
final ViewGroup windowContentView =
|
||||
(ViewGroup)getRootView().findViewById(android.R.id.content);
|
||||
final View rootView = getRootView();
|
||||
if (rootView == null) {
|
||||
Log.w(TAG, "Cannot find root view");
|
||||
return;
|
||||
}
|
||||
final ViewGroup windowContentView = (ViewGroup)rootView.findViewById(android.R.id.content);
|
||||
// Note: It'd be very weird if we get null by android.R.id.content.
|
||||
if (windowContentView == null) {
|
||||
Log.w(TAG, "Cannot find android.R.id.content view to add PreviewPlacerView");
|
||||
} else {
|
||||
windowContentView.addView(mPreviewPlacerView);
|
||||
}
|
||||
}
|
||||
|
||||
public void showGestureFloatingPreviewText(String gestureFloatingPreviewText) {
|
||||
locatePreviewPlacerView();
|
||||
|
@ -952,7 +992,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void showGestureTrail(PointerTracker tracker) {
|
||||
public void showGesturePreviewTrail(PointerTracker tracker) {
|
||||
locatePreviewPlacerView();
|
||||
mPreviewPlacerView.invalidatePointer(tracker);
|
||||
}
|
||||
|
@ -962,7 +1002,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
|||
public void showKeyPreview(PointerTracker tracker) {
|
||||
if (!mShowKeyPreviewPopup) return;
|
||||
|
||||
final TextView previewText = tracker.getKeyPreviewText();
|
||||
final TextView previewText = getKeyPreviewText(tracker.mPointerId);
|
||||
// If the key preview has no parent view yet, add it to the ViewGroup which can place
|
||||
// key preview absolutely in SoftInputWindow.
|
||||
if (previewText.getParent() == null) {
|
||||
|
@ -1052,7 +1092,6 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
|||
public void invalidateAllKeys() {
|
||||
mInvalidatedKeys.clear();
|
||||
mInvalidateAllKeys = true;
|
||||
mBufferNeedsUpdate = true;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
|
@ -1070,13 +1109,11 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
|
|||
mInvalidatedKeys.add(key);
|
||||
final int x = key.mX + getPaddingLeft();
|
||||
final int y = key.mY + getPaddingTop();
|
||||
mWorkingRect.set(x, y, x + key.mWidth, y + key.mHeight);
|
||||
mBufferNeedsUpdate = true;
|
||||
invalidate(mWorkingRect);
|
||||
invalidate(x, y, x + key.mWidth, y + key.mHeight);
|
||||
}
|
||||
|
||||
public void closing() {
|
||||
PointerTracker.dismissAllKeyPreviews();
|
||||
dismissAllKeyPreviews();
|
||||
cancelAllMessages();
|
||||
|
||||
mInvalidateAllKeys = true;
|
||||
|
|
|
@ -110,7 +110,6 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
|
|||
new WeakHashMap<Key, MoreKeysPanel>();
|
||||
private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint;
|
||||
|
||||
private final PointerTrackerParams mPointerTrackerParams;
|
||||
private final SuddenJumpingTouchEventHandler mTouchScreenRegulator;
|
||||
|
||||
protected KeyDetector mKeyDetector;
|
||||
|
@ -127,11 +126,26 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
|
|||
private static final int MSG_LONGPRESS_KEY = 2;
|
||||
private static final int MSG_DOUBLE_TAP = 3;
|
||||
|
||||
private final KeyTimerParams mParams;
|
||||
private final int mKeyRepeatStartTimeout;
|
||||
private final int mKeyRepeatInterval;
|
||||
private final int mLongPressKeyTimeout;
|
||||
private final int mLongPressShiftKeyTimeout;
|
||||
private final int mIgnoreAltCodeKeyTimeout;
|
||||
|
||||
public KeyTimerHandler(MainKeyboardView outerInstance, KeyTimerParams params) {
|
||||
public KeyTimerHandler(final MainKeyboardView outerInstance,
|
||||
final TypedArray mainKeyboardViewAttr) {
|
||||
super(outerInstance);
|
||||
mParams = params;
|
||||
|
||||
mKeyRepeatStartTimeout = mainKeyboardViewAttr.getInt(
|
||||
R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0);
|
||||
mKeyRepeatInterval = mainKeyboardViewAttr.getInt(
|
||||
R.styleable.MainKeyboardView_keyRepeatInterval, 0);
|
||||
mLongPressKeyTimeout = mainKeyboardViewAttr.getInt(
|
||||
R.styleable.MainKeyboardView_longPressKeyTimeout, 0);
|
||||
mLongPressShiftKeyTimeout = mainKeyboardViewAttr.getInt(
|
||||
R.styleable.MainKeyboardView_longPressShiftKeyTimeout, 0);
|
||||
mIgnoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt(
|
||||
R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -146,7 +160,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
|
|||
final Key currentKey = tracker.getKey();
|
||||
if (currentKey != null && currentKey.mCode == msg.arg1) {
|
||||
tracker.onRegisterKey(currentKey);
|
||||
startKeyRepeatTimer(tracker, mParams.mKeyRepeatInterval);
|
||||
startKeyRepeatTimer(tracker, mKeyRepeatInterval);
|
||||
}
|
||||
break;
|
||||
case MSG_LONGPRESS_KEY:
|
||||
|
@ -167,7 +181,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
|
|||
|
||||
@Override
|
||||
public void startKeyRepeatTimer(PointerTracker tracker) {
|
||||
startKeyRepeatTimer(tracker, mParams.mKeyRepeatStartTimeout);
|
||||
startKeyRepeatTimer(tracker, mKeyRepeatStartTimeout);
|
||||
}
|
||||
|
||||
public void cancelKeyRepeatTimer() {
|
||||
|
@ -185,7 +199,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
|
|||
final int delay;
|
||||
switch (code) {
|
||||
case Keyboard.CODE_SHIFT:
|
||||
delay = mParams.mLongPressShiftKeyTimeout;
|
||||
delay = mLongPressShiftKeyTimeout;
|
||||
break;
|
||||
default:
|
||||
delay = 0;
|
||||
|
@ -206,15 +220,15 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
|
|||
final int delay;
|
||||
switch (key.mCode) {
|
||||
case Keyboard.CODE_SHIFT:
|
||||
delay = mParams.mLongPressShiftKeyTimeout;
|
||||
delay = mLongPressShiftKeyTimeout;
|
||||
break;
|
||||
default:
|
||||
if (KeyboardSwitcher.getInstance().isInMomentarySwitchState()) {
|
||||
// We use longer timeout for sliding finger input started from the symbols
|
||||
// mode key.
|
||||
delay = mParams.mLongPressKeyTimeout * 3;
|
||||
delay = mLongPressKeyTimeout * 3;
|
||||
} else {
|
||||
delay = mParams.mLongPressKeyTimeout;
|
||||
delay = mLongPressKeyTimeout;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -268,7 +282,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
|
|||
}
|
||||
|
||||
sendMessageDelayed(
|
||||
obtainMessage(MSG_TYPING_STATE_EXPIRED), mParams.mIgnoreAltCodeKeyTimeout);
|
||||
obtainMessage(MSG_TYPING_STATE_EXPIRED), mIgnoreAltCodeKeyTimeout);
|
||||
if (isTyping) {
|
||||
return;
|
||||
}
|
||||
|
@ -307,50 +321,6 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
|
|||
}
|
||||
}
|
||||
|
||||
public static class PointerTrackerParams {
|
||||
public final boolean mSlidingKeyInputEnabled;
|
||||
public final int mTouchNoiseThresholdTime;
|
||||
public final float mTouchNoiseThresholdDistance;
|
||||
|
||||
public static final PointerTrackerParams DEFAULT = new PointerTrackerParams();
|
||||
|
||||
private PointerTrackerParams() {
|
||||
mSlidingKeyInputEnabled = false;
|
||||
mTouchNoiseThresholdTime =0;
|
||||
mTouchNoiseThresholdDistance = 0;
|
||||
}
|
||||
|
||||
public PointerTrackerParams(TypedArray mainKeyboardViewAttr) {
|
||||
mSlidingKeyInputEnabled = mainKeyboardViewAttr.getBoolean(
|
||||
R.styleable.MainKeyboardView_slidingKeyInputEnable, false);
|
||||
mTouchNoiseThresholdTime = mainKeyboardViewAttr.getInt(
|
||||
R.styleable.MainKeyboardView_touchNoiseThresholdTime, 0);
|
||||
mTouchNoiseThresholdDistance = mainKeyboardViewAttr.getDimension(
|
||||
R.styleable.MainKeyboardView_touchNoiseThresholdDistance, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static class KeyTimerParams {
|
||||
public final int mKeyRepeatStartTimeout;
|
||||
public final int mKeyRepeatInterval;
|
||||
public final int mLongPressKeyTimeout;
|
||||
public final int mLongPressShiftKeyTimeout;
|
||||
public final int mIgnoreAltCodeKeyTimeout;
|
||||
|
||||
public KeyTimerParams(TypedArray mainKeyboardViewAttr) {
|
||||
mKeyRepeatStartTimeout = mainKeyboardViewAttr.getInt(
|
||||
R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0);
|
||||
mKeyRepeatInterval = mainKeyboardViewAttr.getInt(
|
||||
R.styleable.MainKeyboardView_keyRepeatInterval, 0);
|
||||
mLongPressKeyTimeout = mainKeyboardViewAttr.getInt(
|
||||
R.styleable.MainKeyboardView_longPressKeyTimeout, 0);
|
||||
mLongPressShiftKeyTimeout = mainKeyboardViewAttr.getInt(
|
||||
R.styleable.MainKeyboardView_longPressShiftKeyTimeout, 0);
|
||||
mIgnoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt(
|
||||
R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public MainKeyboardView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, R.attr.mainKeyboardViewStyle);
|
||||
}
|
||||
|
@ -374,8 +344,8 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
|
|||
R.styleable.MainKeyboardView_autoCorrectionSpacebarLedEnabled, false);
|
||||
mAutoCorrectionSpacebarLedIcon = a.getDrawable(
|
||||
R.styleable.MainKeyboardView_autoCorrectionSpacebarLedIcon);
|
||||
mSpacebarTextRatio = a.getFraction(R.styleable.MainKeyboardView_spacebarTextRatio,
|
||||
1000, 1000, 1) / 1000.0f;
|
||||
mSpacebarTextRatio = a.getFraction(
|
||||
R.styleable.MainKeyboardView_spacebarTextRatio, 1, 1, 1.0f);
|
||||
mSpacebarTextColor = a.getColor(R.styleable.MainKeyboardView_spacebarTextColor, 0);
|
||||
mSpacebarTextShadowColor = a.getColor(
|
||||
R.styleable.MainKeyboardView_spacebarTextShadowColor, 0);
|
||||
|
@ -389,19 +359,15 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
|
|||
final int altCodeKeyWhileTypingFadeinAnimatorResId = a.getResourceId(
|
||||
R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0);
|
||||
|
||||
final KeyTimerParams keyTimerParams = new KeyTimerParams(a);
|
||||
mPointerTrackerParams = new PointerTrackerParams(a);
|
||||
|
||||
final float keyHysteresisDistance = a.getDimension(
|
||||
R.styleable.MainKeyboardView_keyHysteresisDistance, 0);
|
||||
mKeyDetector = new KeyDetector(keyHysteresisDistance);
|
||||
mKeyTimerHandler = new KeyTimerHandler(this, keyTimerParams);
|
||||
mKeyTimerHandler = new KeyTimerHandler(this, a);
|
||||
mConfigShowMoreKeysKeyboardAtTouchedPoint = a.getBoolean(
|
||||
R.styleable.MainKeyboardView_showMoreKeysKeyboardAtTouchedPoint, false);
|
||||
PointerTracker.setParameters(a);
|
||||
a.recycle();
|
||||
|
||||
PointerTracker.setParameters(mPointerTrackerParams);
|
||||
|
||||
mLanguageOnSpacebarFadeoutAnimator = loadObjectAnimator(
|
||||
languageOnSpacebarFadeoutAnimatorResId, this);
|
||||
mAltCodeKeyWhileTypingFadeoutAnimator = loadObjectAnimator(
|
||||
|
@ -482,7 +448,7 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
|
|||
super.setKeyboard(keyboard);
|
||||
mKeyDetector.setKeyboard(
|
||||
keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection);
|
||||
PointerTracker.setKeyDetector(mKeyDetector, mShouldHandleGesture);
|
||||
PointerTracker.setKeyDetector(mKeyDetector);
|
||||
mTouchScreenRegulator.setKeyboard(keyboard);
|
||||
mMoreKeysPanelCache.clear();
|
||||
|
||||
|
@ -500,12 +466,13 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
|
|||
AccessibleKeyboardViewProxy.getInstance().setKeyboard(keyboard);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGestureHandlingMode(final boolean shouldHandleGesture,
|
||||
boolean drawsGesturePreviewTrail, boolean drawsGestureFloatingPreviewText) {
|
||||
super.setGestureHandlingMode(shouldHandleGesture, drawsGesturePreviewTrail,
|
||||
drawsGestureFloatingPreviewText);
|
||||
PointerTracker.setKeyDetector(mKeyDetector, shouldHandleGesture);
|
||||
// Note that this method is called from a non-UI thread.
|
||||
public void setMainDictionaryAvailability(boolean mainDictionaryAvailable) {
|
||||
PointerTracker.setMainDictionaryAvailability(mainDictionaryAvailable);
|
||||
}
|
||||
|
||||
public void setGestureHandlingEnabledByUser(boolean gestureHandlingEnabledByUser) {
|
||||
PointerTracker.setGestureHandlingEnabledByUser(gestureHandlingEnabledByUser);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -527,7 +494,17 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
|
|||
// to properly show the splash screen, which requires that the window token of the
|
||||
// KeyboardView be non-null.
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.getInstance().mainKeyboardView_onAttachedToWindow();
|
||||
ResearchLogger.getInstance().mainKeyboardView_onAttachedToWindow(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
// Notify the research logger that the keyboard view has been detached. This is needed
|
||||
// to invalidate the reference of {@link MainKeyboardView} to null.
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.getInstance().mainKeyboardView_onDetachedFromWindow();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -607,9 +584,8 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
|
|||
}
|
||||
|
||||
private void invokeCodeInput(int primaryCode) {
|
||||
mKeyboardActionListener.onCodeInput(primaryCode,
|
||||
KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
|
||||
KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
|
||||
mKeyboardActionListener.onCodeInput(
|
||||
primaryCode, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
|
||||
}
|
||||
|
||||
private void invokeReleaseKey(int primaryCode) {
|
||||
|
@ -834,20 +810,6 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas c) {
|
||||
Utils.GCUtils.getInstance().reset();
|
||||
boolean tryGC = true;
|
||||
for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
|
||||
try {
|
||||
super.draw(c);
|
||||
tryGC = false;
|
||||
} catch (OutOfMemoryError e) {
|
||||
tryGC = Utils.GCUtils.getInstance().tryGCOrWait(TAG, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives hover events from the input framework.
|
||||
*
|
||||
|
|
|
@ -39,11 +39,7 @@ public class MoreKeysDetector extends KeyDetector {
|
|||
|
||||
Key nearestKey = null;
|
||||
int nearestDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare;
|
||||
final Keyboard keyboard = getKeyboard();
|
||||
if (keyboard == null) {
|
||||
throw new NullPointerException("Keyboard isn't set");
|
||||
}
|
||||
for (final Key key : keyboard.mKeys) {
|
||||
for (final Key key : getKeyboard().mKeys) {
|
||||
final int dist = key.squaredDistanceToEdge(touchX, touchY);
|
||||
if (dist < nearestDist) {
|
||||
nearestKey = key;
|
||||
|
|
|
@ -25,6 +25,7 @@ import android.widget.PopupWindow;
|
|||
|
||||
import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy;
|
||||
import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
|
||||
import com.android.inputmethod.latin.Constants;
|
||||
import com.android.inputmethod.latin.InputPointers;
|
||||
import com.android.inputmethod.latin.R;
|
||||
|
||||
|
@ -50,7 +51,8 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
|
|||
public void onCodeInput(int primaryCode, int x, int y) {
|
||||
// Because a more keys keyboard doesn't need proximity characters correction, we don't
|
||||
// send touch event coordinates.
|
||||
mListener.onCodeInput(primaryCode, NOT_A_TOUCH_COORDINATE, NOT_A_TOUCH_COORDINATE);
|
||||
mListener.onCodeInput(
|
||||
primaryCode, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -16,25 +16,25 @@
|
|||
|
||||
package com.android.inputmethod.keyboard;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.content.res.TypedArray;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.inputmethod.accessibility.AccessibilityUtils;
|
||||
import com.android.inputmethod.keyboard.internal.GestureStroke;
|
||||
import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewTrail;
|
||||
import com.android.inputmethod.keyboard.internal.PointerTrackerQueue;
|
||||
import com.android.inputmethod.latin.CollectionUtils;
|
||||
import com.android.inputmethod.latin.InputPointers;
|
||||
import com.android.inputmethod.latin.LatinImeLogger;
|
||||
import com.android.inputmethod.latin.R;
|
||||
import com.android.inputmethod.latin.define.ProductionFlag;
|
||||
import com.android.inputmethod.research.ResearchLogger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
||||
public class PointerTracker implements PointerTrackerQueue.Element {
|
||||
private static final String TAG = PointerTracker.class.getSimpleName();
|
||||
private static final boolean DEBUG_EVENT = false;
|
||||
private static final boolean DEBUG_MOVE_EVENT = false;
|
||||
|
@ -43,6 +43,9 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
|
||||
/** True if {@link PointerTracker}s should handle gesture events. */
|
||||
private static boolean sShouldHandleGesture = false;
|
||||
private static boolean sMainDictionaryAvailable = false;
|
||||
private static boolean sGestureHandlingEnabledByInputField = false;
|
||||
private static boolean sGestureHandlingEnabledByUser = false;
|
||||
|
||||
private static final int MIN_GESTURE_RECOGNITION_TIME = 100; // msec
|
||||
|
||||
|
@ -75,10 +78,9 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
|
||||
public interface DrawingProxy extends MoreKeysPanel.Controller {
|
||||
public void invalidateKey(Key key);
|
||||
public TextView inflateKeyPreviewText();
|
||||
public void showKeyPreview(PointerTracker tracker);
|
||||
public void dismissKeyPreview(PointerTracker tracker);
|
||||
public void showGestureTrail(PointerTracker tracker);
|
||||
public void showGesturePreviewTrail(PointerTracker tracker);
|
||||
}
|
||||
|
||||
public interface TimerProxy {
|
||||
|
@ -117,19 +119,40 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
}
|
||||
}
|
||||
|
||||
static class PointerTrackerParams {
|
||||
public final boolean mSlidingKeyInputEnabled;
|
||||
public final int mTouchNoiseThresholdTime;
|
||||
public final float mTouchNoiseThresholdDistance;
|
||||
public final int mTouchNoiseThresholdDistanceSquared;
|
||||
|
||||
public static final PointerTrackerParams DEFAULT = new PointerTrackerParams();
|
||||
|
||||
private PointerTrackerParams() {
|
||||
mSlidingKeyInputEnabled = false;
|
||||
mTouchNoiseThresholdTime = 0;
|
||||
mTouchNoiseThresholdDistance = 0.0f;
|
||||
mTouchNoiseThresholdDistanceSquared = 0;
|
||||
}
|
||||
|
||||
public PointerTrackerParams(TypedArray mainKeyboardViewAttr) {
|
||||
mSlidingKeyInputEnabled = mainKeyboardViewAttr.getBoolean(
|
||||
R.styleable.MainKeyboardView_slidingKeyInputEnable, false);
|
||||
mTouchNoiseThresholdTime = mainKeyboardViewAttr.getInt(
|
||||
R.styleable.MainKeyboardView_touchNoiseThresholdTime, 0);
|
||||
final float touchNouseThresholdDistance = mainKeyboardViewAttr.getDimension(
|
||||
R.styleable.MainKeyboardView_touchNoiseThresholdDistance, 0);
|
||||
mTouchNoiseThresholdDistance = touchNouseThresholdDistance;
|
||||
mTouchNoiseThresholdDistanceSquared =
|
||||
(int)(touchNouseThresholdDistance * touchNouseThresholdDistance);
|
||||
}
|
||||
}
|
||||
|
||||
// Parameters for pointer handling.
|
||||
private static MainKeyboardView.PointerTrackerParams sParams;
|
||||
private static int sTouchNoiseThresholdDistanceSquared;
|
||||
private static PointerTrackerParams sParams;
|
||||
private static boolean sNeedsPhantomSuddenMoveEventHack;
|
||||
|
||||
private static final ArrayList<PointerTracker> sTrackers = new ArrayList<PointerTracker>();
|
||||
private static final InputPointers sAggregratedPointers = new InputPointers(
|
||||
GestureStroke.DEFAULT_CAPACITY);
|
||||
private static final ArrayList<PointerTracker> sTrackers = CollectionUtils.newArrayList();
|
||||
private static PointerTrackerQueue sPointerTrackerQueue;
|
||||
// HACK: Change gesture detection criteria depending on this variable.
|
||||
// TODO: Find more comprehensive ways to detect a gesture start.
|
||||
// True when the previous user input was a gesture input, not a typing input.
|
||||
private static boolean sWasInGesture;
|
||||
|
||||
public final int mPointerId;
|
||||
|
||||
|
@ -140,15 +163,14 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
|
||||
private Keyboard mKeyboard;
|
||||
private int mKeyQuarterWidthSquared;
|
||||
private final TextView mKeyPreviewText;
|
||||
|
||||
private boolean mIsAlphabetKeyboard;
|
||||
private boolean mIsPossibleGesture = false;
|
||||
private boolean mInGesture = false;
|
||||
|
||||
// TODO: Remove these variables
|
||||
private int mLastRecognitionPointSize = 0;
|
||||
private long mLastRecognitionTime = 0;
|
||||
private boolean mIsDetectingGesture = false; // per PointerTracker.
|
||||
private static boolean sInGesture = false;
|
||||
private static long sGestureFirstDownTime;
|
||||
private static final InputPointers sAggregratedPointers = new InputPointers(
|
||||
GestureStroke.DEFAULT_CAPACITY);
|
||||
private static int sLastRecognitionPointSize = 0;
|
||||
private static long sLastRecognitionTime = 0;
|
||||
|
||||
// The position and time at which first down event occurred.
|
||||
private long mDownTime;
|
||||
|
@ -186,7 +208,7 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
private static final KeyboardActionListener EMPTY_LISTENER =
|
||||
new KeyboardActionListener.Adapter();
|
||||
|
||||
private final GestureStroke mGestureStroke;
|
||||
private final GestureStrokeWithPreviewTrail mGestureStrokeWithPreviewTrail;
|
||||
|
||||
public static void init(boolean hasDistinctMultitouch,
|
||||
boolean needsPhantomSuddenMoveEventHack) {
|
||||
|
@ -196,28 +218,32 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
sPointerTrackerQueue = null;
|
||||
}
|
||||
sNeedsPhantomSuddenMoveEventHack = needsPhantomSuddenMoveEventHack;
|
||||
|
||||
setParameters(MainKeyboardView.PointerTrackerParams.DEFAULT);
|
||||
updateGestureHandlingMode(null, false /* shouldHandleGesture */);
|
||||
sParams = PointerTrackerParams.DEFAULT;
|
||||
}
|
||||
|
||||
public static void setParameters(MainKeyboardView.PointerTrackerParams params) {
|
||||
sParams = params;
|
||||
sTouchNoiseThresholdDistanceSquared = (int)(
|
||||
params.mTouchNoiseThresholdDistance * params.mTouchNoiseThresholdDistance);
|
||||
public static void setParameters(final TypedArray mainKeyboardViewAttr) {
|
||||
sParams = new PointerTrackerParams(mainKeyboardViewAttr);
|
||||
}
|
||||
|
||||
private static void updateGestureHandlingMode(Keyboard keyboard, boolean shouldHandleGesture) {
|
||||
if (!shouldHandleGesture
|
||||
|| AccessibilityUtils.getInstance().isTouchExplorationEnabled()
|
||||
|| (keyboard != null && keyboard.mId.passwordInput())) {
|
||||
sShouldHandleGesture = false;
|
||||
} else {
|
||||
sShouldHandleGesture = true;
|
||||
}
|
||||
private static void updateGestureHandlingMode() {
|
||||
sShouldHandleGesture = sMainDictionaryAvailable
|
||||
&& sGestureHandlingEnabledByInputField
|
||||
&& sGestureHandlingEnabledByUser
|
||||
&& !AccessibilityUtils.getInstance().isTouchExplorationEnabled();
|
||||
}
|
||||
|
||||
public static PointerTracker getPointerTracker(final int id, KeyEventHandler handler) {
|
||||
// Note that this method is called from a non-UI thread.
|
||||
public static void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) {
|
||||
sMainDictionaryAvailable = mainDictionaryAvailable;
|
||||
updateGestureHandlingMode();
|
||||
}
|
||||
|
||||
public static void setGestureHandlingEnabledByUser(final boolean gestureHandlingEnabledByUser) {
|
||||
sGestureHandlingEnabledByUser = gestureHandlingEnabledByUser;
|
||||
updateGestureHandlingMode();
|
||||
}
|
||||
|
||||
public static PointerTracker getPointerTracker(final int id, final KeyEventHandler handler) {
|
||||
final ArrayList<PointerTracker> trackers = sTrackers;
|
||||
|
||||
// Create pointer trackers until we can get 'id+1'-th tracker, if needed.
|
||||
|
@ -233,7 +259,7 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
return sPointerTrackerQueue != null ? sPointerTrackerQueue.isAnyInSlidingKeyInput() : false;
|
||||
}
|
||||
|
||||
public static void setKeyboardActionListener(KeyboardActionListener listener) {
|
||||
public static void setKeyboardActionListener(final KeyboardActionListener listener) {
|
||||
final int trackersSize = sTrackers.size();
|
||||
for (int i = 0; i < trackersSize; ++i) {
|
||||
final PointerTracker tracker = sTrackers.get(i);
|
||||
|
@ -241,7 +267,7 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
}
|
||||
}
|
||||
|
||||
public static void setKeyDetector(KeyDetector keyDetector, boolean shouldHandleGesture) {
|
||||
public static void setKeyDetector(final KeyDetector keyDetector) {
|
||||
final int trackersSize = sTrackers.size();
|
||||
for (int i = 0; i < trackersSize; ++i) {
|
||||
final PointerTracker tracker = sTrackers.get(i);
|
||||
|
@ -250,70 +276,33 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
tracker.mKeyboardLayoutHasBeenChanged = true;
|
||||
}
|
||||
final Keyboard keyboard = keyDetector.getKeyboard();
|
||||
updateGestureHandlingMode(keyboard, shouldHandleGesture);
|
||||
sGestureHandlingEnabledByInputField = !keyboard.mId.passwordInput();
|
||||
updateGestureHandlingMode();
|
||||
}
|
||||
|
||||
public static void dismissAllKeyPreviews() {
|
||||
public static void setReleasedKeyGraphicsToAllKeys() {
|
||||
final int trackersSize = sTrackers.size();
|
||||
for (int i = 0; i < trackersSize; ++i) {
|
||||
final PointerTracker tracker = sTrackers.get(i);
|
||||
tracker.getKeyPreviewText().setVisibility(View.INVISIBLE);
|
||||
tracker.setReleasedKeyGraphics(tracker.mCurrentKey);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: To handle multi-touch gestures we may want to move this method to
|
||||
// {@link PointerTrackerQueue}.
|
||||
private static InputPointers getIncrementalBatchPoints() {
|
||||
final int trackersSize = sTrackers.size();
|
||||
for (int i = 0; i < trackersSize; ++i) {
|
||||
final PointerTracker tracker = sTrackers.get(i);
|
||||
tracker.mGestureStroke.appendIncrementalBatchPoints(sAggregratedPointers);
|
||||
}
|
||||
return sAggregratedPointers;
|
||||
}
|
||||
|
||||
// TODO: To handle multi-touch gestures we may want to move this method to
|
||||
// {@link PointerTrackerQueue}.
|
||||
private static InputPointers getAllBatchPoints() {
|
||||
final int trackersSize = sTrackers.size();
|
||||
for (int i = 0; i < trackersSize; ++i) {
|
||||
final PointerTracker tracker = sTrackers.get(i);
|
||||
tracker.mGestureStroke.appendAllBatchPoints(sAggregratedPointers);
|
||||
}
|
||||
return sAggregratedPointers;
|
||||
}
|
||||
|
||||
// TODO: To handle multi-touch gestures we may want to move this method to
|
||||
// {@link PointerTrackerQueue}.
|
||||
public static void clearBatchInputPointsOfAllPointerTrackers() {
|
||||
final int trackersSize = sTrackers.size();
|
||||
for (int i = 0; i < trackersSize; ++i) {
|
||||
final PointerTracker tracker = sTrackers.get(i);
|
||||
tracker.mGestureStroke.reset();
|
||||
}
|
||||
sAggregratedPointers.reset();
|
||||
}
|
||||
|
||||
private PointerTracker(int id, KeyEventHandler handler) {
|
||||
if (handler == null)
|
||||
private PointerTracker(final int id, final KeyEventHandler handler) {
|
||||
if (handler == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
mPointerId = id;
|
||||
mGestureStroke = new GestureStroke(id);
|
||||
mGestureStrokeWithPreviewTrail = new GestureStrokeWithPreviewTrail(id);
|
||||
setKeyDetectorInner(handler.getKeyDetector());
|
||||
mListener = handler.getKeyboardActionListener();
|
||||
mDrawingProxy = handler.getDrawingProxy();
|
||||
mTimerProxy = handler.getTimerProxy();
|
||||
mKeyPreviewText = mDrawingProxy.inflateKeyPreviewText();
|
||||
}
|
||||
|
||||
public TextView getKeyPreviewText() {
|
||||
return mKeyPreviewText;
|
||||
}
|
||||
|
||||
// Returns true if keyboard has been changed by this callback.
|
||||
private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key) {
|
||||
if (mInGesture) {
|
||||
private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key) {
|
||||
if (sInGesture) {
|
||||
return false;
|
||||
}
|
||||
final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
|
||||
|
@ -337,7 +326,8 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
|
||||
// Note that we need primaryCode argument because the keyboard may in shifted state and the
|
||||
// primaryCode is different from {@link Key#mCode}.
|
||||
private void callListenerOnCodeInput(Key key, int primaryCode, int x, int y) {
|
||||
private void callListenerOnCodeInput(final Key key, final int primaryCode, final int x,
|
||||
final int y) {
|
||||
final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
|
||||
final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState();
|
||||
final int code = altersCode ? key.mAltCode : primaryCode;
|
||||
|
@ -366,8 +356,9 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
|
||||
// Note that we need primaryCode argument because the keyboard may in shifted state and the
|
||||
// primaryCode is different from {@link Key#mCode}.
|
||||
private void callListenerOnRelease(Key key, int primaryCode, boolean withSliding) {
|
||||
if (mInGesture) {
|
||||
private void callListenerOnRelease(final Key key, final int primaryCode,
|
||||
final boolean withSliding) {
|
||||
if (sInGesture) {
|
||||
return;
|
||||
}
|
||||
final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
|
||||
|
@ -389,20 +380,19 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
}
|
||||
|
||||
private void callListenerOnCancelInput() {
|
||||
if (DEBUG_LISTENER)
|
||||
if (DEBUG_LISTENER) {
|
||||
Log.d(TAG, "onCancelInput");
|
||||
}
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.pointerTracker_callListenerOnCancelInput();
|
||||
}
|
||||
mListener.onCancelInput();
|
||||
}
|
||||
|
||||
private void setKeyDetectorInner(KeyDetector keyDetector) {
|
||||
private void setKeyDetectorInner(final KeyDetector keyDetector) {
|
||||
mKeyDetector = keyDetector;
|
||||
mKeyboard = keyDetector.getKeyboard();
|
||||
mIsAlphabetKeyboard = mKeyboard.mId.isAlphabetKeyboard();
|
||||
mGestureStroke.setGestureSampleLength(
|
||||
mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight);
|
||||
mGestureStrokeWithPreviewTrail.setGestureSampleLength(mKeyboard.mMostCommonKeyWidth);
|
||||
final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY);
|
||||
if (newKey != mCurrentKey) {
|
||||
if (mDrawingProxy != null) {
|
||||
|
@ -428,11 +418,11 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
return mCurrentKey != null && mCurrentKey.isModifier();
|
||||
}
|
||||
|
||||
public Key getKeyOn(int x, int y) {
|
||||
public Key getKeyOn(final int x, final int y) {
|
||||
return mKeyDetector.detectHitKey(x, y);
|
||||
}
|
||||
|
||||
private void setReleasedKeyGraphics(Key key) {
|
||||
private void setReleasedKeyGraphics(final Key key) {
|
||||
mDrawingProxy.dismissKeyPreview(this);
|
||||
if (key == null) {
|
||||
return;
|
||||
|
@ -463,7 +453,7 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
}
|
||||
}
|
||||
|
||||
private void setPressedKeyGraphics(Key key) {
|
||||
private void setPressedKeyGraphics(final Key key) {
|
||||
if (key == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -475,7 +465,7 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!key.noKeyPreview() && !mInGesture) {
|
||||
if (!key.noKeyPreview() && !sInGesture) {
|
||||
mDrawingProxy.showKeyPreview(this);
|
||||
}
|
||||
updatePressKeyGraphics(key);
|
||||
|
@ -502,20 +492,18 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
}
|
||||
}
|
||||
|
||||
private void updateReleaseKeyGraphics(Key key) {
|
||||
private void updateReleaseKeyGraphics(final Key key) {
|
||||
key.onReleased();
|
||||
mDrawingProxy.invalidateKey(key);
|
||||
}
|
||||
|
||||
private void updatePressKeyGraphics(Key key) {
|
||||
private void updatePressKeyGraphics(final Key key) {
|
||||
key.onPressed();
|
||||
mDrawingProxy.invalidateKey(key);
|
||||
}
|
||||
|
||||
public void drawGestureTrail(Canvas canvas, Paint paint) {
|
||||
if (mInGesture) {
|
||||
mGestureStroke.drawGestureTrail(canvas, paint, mLastX, mLastY);
|
||||
}
|
||||
public GestureStrokeWithPreviewTrail getGestureStrokeWithPreviewTrail() {
|
||||
return mGestureStrokeWithPreviewTrail;
|
||||
}
|
||||
|
||||
public int getLastX() {
|
||||
|
@ -530,77 +518,91 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
return mDownTime;
|
||||
}
|
||||
|
||||
private Key onDownKey(int x, int y, long eventTime) {
|
||||
private Key onDownKey(final int x, final int y, final long eventTime) {
|
||||
mDownTime = eventTime;
|
||||
return onMoveToNewKey(onMoveKeyInternal(x, y), x, y);
|
||||
}
|
||||
|
||||
private Key onMoveKeyInternal(int x, int y) {
|
||||
private Key onMoveKeyInternal(final int x, final int y) {
|
||||
mLastX = x;
|
||||
mLastY = y;
|
||||
return mKeyDetector.detectHitKey(x, y);
|
||||
}
|
||||
|
||||
private Key onMoveKey(int x, int y) {
|
||||
private Key onMoveKey(final int x, final int y) {
|
||||
return onMoveKeyInternal(x, y);
|
||||
}
|
||||
|
||||
private Key onMoveToNewKey(Key newKey, int x, int y) {
|
||||
private Key onMoveToNewKey(final Key newKey, final int x, final int y) {
|
||||
mCurrentKey = newKey;
|
||||
mKeyX = x;
|
||||
mKeyY = y;
|
||||
return newKey;
|
||||
}
|
||||
|
||||
private static int getActivePointerTrackerCount() {
|
||||
return (sPointerTrackerQueue == null) ? 1 : sPointerTrackerQueue.size();
|
||||
}
|
||||
|
||||
private void startBatchInput() {
|
||||
if (DEBUG_LISTENER) {
|
||||
Log.d(TAG, "onStartBatchInput");
|
||||
}
|
||||
mInGesture = true;
|
||||
sInGesture = true;
|
||||
mListener.onStartBatchInput();
|
||||
mDrawingProxy.showGesturePreviewTrail(this);
|
||||
}
|
||||
|
||||
private void updateBatchInput(InputPointers batchPoints) {
|
||||
private void updateBatchInput(final long eventTime) {
|
||||
synchronized (sAggregratedPointers) {
|
||||
mGestureStrokeWithPreviewTrail.appendIncrementalBatchPoints(sAggregratedPointers);
|
||||
final int size = sAggregratedPointers.getPointerSize();
|
||||
if (size > sLastRecognitionPointSize
|
||||
&& eventTime > sLastRecognitionTime + MIN_GESTURE_RECOGNITION_TIME) {
|
||||
sLastRecognitionPointSize = size;
|
||||
sLastRecognitionTime = eventTime;
|
||||
if (DEBUG_LISTENER) {
|
||||
Log.d(TAG, "onUpdateBatchInput: batchPoints=" + batchPoints.getPointerSize());
|
||||
Log.d(TAG, "onUpdateBatchInput: batchPoints=" + size);
|
||||
}
|
||||
mListener.onUpdateBatchInput(batchPoints);
|
||||
mListener.onUpdateBatchInput(sAggregratedPointers);
|
||||
}
|
||||
}
|
||||
mDrawingProxy.showGesturePreviewTrail(this);
|
||||
}
|
||||
|
||||
private void endBatchInput(InputPointers batchPoints) {
|
||||
private void endBatchInput() {
|
||||
synchronized (sAggregratedPointers) {
|
||||
mGestureStrokeWithPreviewTrail.appendAllBatchPoints(sAggregratedPointers);
|
||||
if (getActivePointerTrackerCount() == 1) {
|
||||
if (DEBUG_LISTENER) {
|
||||
Log.d(TAG, "onEndBatchInput: batchPoints=" + batchPoints.getPointerSize());
|
||||
Log.d(TAG, "onEndBatchInput: batchPoints="
|
||||
+ sAggregratedPointers.getPointerSize());
|
||||
}
|
||||
mListener.onEndBatchInput(batchPoints);
|
||||
clearBatchInputRecognitionStateOfThisPointerTracker();
|
||||
sInGesture = false;
|
||||
mListener.onEndBatchInput(sAggregratedPointers);
|
||||
clearBatchInputPointsOfAllPointerTrackers();
|
||||
sWasInGesture = true;
|
||||
}
|
||||
}
|
||||
mDrawingProxy.showGesturePreviewTrail(this);
|
||||
}
|
||||
|
||||
private void abortBatchInput() {
|
||||
clearBatchInputRecognitionStateOfThisPointerTracker();
|
||||
private static void abortBatchInput() {
|
||||
clearBatchInputPointsOfAllPointerTrackers();
|
||||
}
|
||||
|
||||
private void clearBatchInputRecognitionStateOfThisPointerTracker() {
|
||||
mIsPossibleGesture = false;
|
||||
mInGesture = false;
|
||||
mLastRecognitionPointSize = 0;
|
||||
mLastRecognitionTime = 0;
|
||||
private static void clearBatchInputPointsOfAllPointerTrackers() {
|
||||
final int trackersSize = sTrackers.size();
|
||||
for (int i = 0; i < trackersSize; ++i) {
|
||||
final PointerTracker tracker = sTrackers.get(i);
|
||||
tracker.mGestureStrokeWithPreviewTrail.reset();
|
||||
}
|
||||
sAggregratedPointers.reset();
|
||||
sLastRecognitionPointSize = 0;
|
||||
sLastRecognitionTime = 0;
|
||||
}
|
||||
|
||||
private boolean updateBatchInputRecognitionState(long eventTime, int size) {
|
||||
if (size > mLastRecognitionPointSize
|
||||
&& eventTime > mLastRecognitionTime + MIN_GESTURE_RECOGNITION_TIME) {
|
||||
mLastRecognitionPointSize = size;
|
||||
mLastRecognitionTime = eventTime;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void processMotionEvent(int action, int x, int y, long eventTime,
|
||||
KeyEventHandler handler) {
|
||||
public void processMotionEvent(final int action, final int x, final int y, final long eventTime,
|
||||
final KeyEventHandler handler) {
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
|
@ -619,9 +621,11 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
}
|
||||
}
|
||||
|
||||
public void onDownEvent(int x, int y, long eventTime, KeyEventHandler handler) {
|
||||
if (DEBUG_EVENT)
|
||||
public void onDownEvent(final int x, final int y, final long eventTime,
|
||||
final KeyEventHandler handler) {
|
||||
if (DEBUG_EVENT) {
|
||||
printTouchEvent("onDownEvent:", x, y, eventTime);
|
||||
}
|
||||
|
||||
mDrawingProxy = handler.getDrawingProxy();
|
||||
mTimerProxy = handler.getTimerProxy();
|
||||
|
@ -633,7 +637,7 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
final int dx = x - mLastX;
|
||||
final int dy = y - mLastY;
|
||||
final int distanceSquared = (dx * dx + dy * dy);
|
||||
if (distanceSquared < sTouchNoiseThresholdDistanceSquared) {
|
||||
if (distanceSquared < sParams.mTouchNoiseThresholdDistanceSquared) {
|
||||
if (DEBUG_MODE)
|
||||
Log.w(TAG, "onDownEvent: ignore potential noise: time=" + deltaT
|
||||
+ " distance=" + distanceSquared);
|
||||
|
@ -645,8 +649,8 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
}
|
||||
}
|
||||
|
||||
final PointerTrackerQueue queue = sPointerTrackerQueue;
|
||||
final Key key = getKeyOn(x, y);
|
||||
final PointerTrackerQueue queue = sPointerTrackerQueue;
|
||||
if (queue != null) {
|
||||
if (key != null && key.isModifier()) {
|
||||
// Before processing a down event of modifier key, all pointers already being
|
||||
|
@ -656,20 +660,30 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
queue.add(this);
|
||||
}
|
||||
onDownEventInternal(x, y, eventTime);
|
||||
if (queue != null && queue.size() == 1) {
|
||||
mIsPossibleGesture = false;
|
||||
// A gesture should start only from the letter key.
|
||||
if (sShouldHandleGesture && mIsAlphabetKeyboard && !mIsShowingMoreKeysPanel
|
||||
&& key != null && Keyboard.isLetterCode(key.mCode)) {
|
||||
mIsPossibleGesture = true;
|
||||
// TODO: pointer times should be relative to first down even in entire batch input
|
||||
// instead of resetting to 0 for each new down event.
|
||||
mGestureStroke.addPoint(x, y, 0, false);
|
||||
if (!sShouldHandleGesture) {
|
||||
return;
|
||||
}
|
||||
final int activePointerTrackerCount = getActivePointerTrackerCount();
|
||||
if (activePointerTrackerCount == 1) {
|
||||
mIsDetectingGesture = false;
|
||||
// A gesture should start only from the letter key.
|
||||
final boolean isAlphabetKeyboard = (mKeyboard != null)
|
||||
&& mKeyboard.mId.isAlphabetKeyboard();
|
||||
if (isAlphabetKeyboard && !mIsShowingMoreKeysPanel && key != null
|
||||
&& Keyboard.isLetterCode(key.mCode)) {
|
||||
mIsDetectingGesture = true;
|
||||
sGestureFirstDownTime = eventTime;
|
||||
mGestureStrokeWithPreviewTrail.addPoint(x, y, 0, false /* isHistorical */);
|
||||
}
|
||||
} else if (sInGesture && activePointerTrackerCount > 1) {
|
||||
mIsDetectingGesture = true;
|
||||
final int elapsedTimeFromFirstDown = (int)(eventTime - sGestureFirstDownTime);
|
||||
mGestureStrokeWithPreviewTrail.addPoint(x, y, elapsedTimeFromFirstDown,
|
||||
false /* isHistorical */);
|
||||
}
|
||||
}
|
||||
|
||||
private void onDownEventInternal(int x, int y, long eventTime) {
|
||||
private void onDownEventInternal(final int x, final int y, final long eventTime) {
|
||||
Key key = onDownKey(x, y, eventTime);
|
||||
// Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding
|
||||
// from modifier key, or 3) this pointer's KeyDetector always allows sliding input.
|
||||
|
@ -694,40 +708,38 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
}
|
||||
}
|
||||
|
||||
private void startSlidingKeyInput(Key key) {
|
||||
private void startSlidingKeyInput(final Key key) {
|
||||
if (!mIsInSlidingKeyInput) {
|
||||
mIgnoreModifierKey = key.isModifier();
|
||||
}
|
||||
mIsInSlidingKeyInput = true;
|
||||
}
|
||||
|
||||
private void onGestureMoveEvent(PointerTracker tracker, int x, int y, long eventTime,
|
||||
boolean isHistorical, Key key) {
|
||||
final int gestureTime = (int)(eventTime - tracker.getDownTime());
|
||||
if (sShouldHandleGesture && mIsPossibleGesture) {
|
||||
final GestureStroke stroke = mGestureStroke;
|
||||
private void onGestureMoveEvent(final int x, final int y, final long eventTime,
|
||||
final boolean isHistorical, final Key key) {
|
||||
final int gestureTime = (int)(eventTime - sGestureFirstDownTime);
|
||||
if (mIsDetectingGesture) {
|
||||
final GestureStroke stroke = mGestureStrokeWithPreviewTrail;
|
||||
stroke.addPoint(x, y, gestureTime, isHistorical);
|
||||
if (!mInGesture && stroke.isStartOfAGesture(gestureTime, sWasInGesture)) {
|
||||
if (!sInGesture && stroke.isStartOfAGesture()) {
|
||||
startBatchInput();
|
||||
}
|
||||
}
|
||||
|
||||
if (key != null && mInGesture) {
|
||||
final InputPointers batchPoints = getIncrementalBatchPoints();
|
||||
mDrawingProxy.showGestureTrail(this);
|
||||
if (updateBatchInputRecognitionState(eventTime, batchPoints.getPointerSize())) {
|
||||
updateBatchInput(batchPoints);
|
||||
if (sInGesture && key != null) {
|
||||
updateBatchInput(eventTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onMoveEvent(int x, int y, long eventTime, MotionEvent me) {
|
||||
if (DEBUG_MOVE_EVENT)
|
||||
public void onMoveEvent(final int x, final int y, final long eventTime, final MotionEvent me) {
|
||||
if (DEBUG_MOVE_EVENT) {
|
||||
printTouchEvent("onMoveEvent:", x, y, eventTime);
|
||||
if (mKeyAlreadyProcessed)
|
||||
}
|
||||
if (mKeyAlreadyProcessed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (me != null) {
|
||||
if (sShouldHandleGesture && me != null) {
|
||||
// Add historical points to gesture path.
|
||||
final int pointerIndex = me.findPointerIndex(mPointerId);
|
||||
final int historicalSize = me.getHistorySize();
|
||||
|
@ -735,24 +747,31 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
final int historicalX = (int)me.getHistoricalX(pointerIndex, h);
|
||||
final int historicalY = (int)me.getHistoricalY(pointerIndex, h);
|
||||
final long historicalTime = me.getHistoricalEventTime(h);
|
||||
onGestureMoveEvent(this, historicalX, historicalY, historicalTime,
|
||||
onGestureMoveEvent(historicalX, historicalY, historicalTime,
|
||||
true /* isHistorical */, null);
|
||||
}
|
||||
}
|
||||
|
||||
onMoveEventInternal(x, y, eventTime);
|
||||
}
|
||||
|
||||
private void onMoveEventInternal(final int x, final int y, final long eventTime) {
|
||||
final int lastX = mLastX;
|
||||
final int lastY = mLastY;
|
||||
final Key oldKey = mCurrentKey;
|
||||
Key key = onMoveKey(x, y);
|
||||
|
||||
if (sShouldHandleGesture) {
|
||||
// Register move event on gesture tracker.
|
||||
onGestureMoveEvent(this, x, y, eventTime, false /* isHistorical */, key);
|
||||
if (mInGesture) {
|
||||
onGestureMoveEvent(x, y, eventTime, false /* isHistorical */, key);
|
||||
if (sInGesture) {
|
||||
mIgnoreModifierKey = true;
|
||||
mTimerProxy.cancelLongPressTimer();
|
||||
mIsInSlidingKeyInput = true;
|
||||
mCurrentKey = null;
|
||||
setReleasedKeyGraphics(oldKey);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (key != null) {
|
||||
|
@ -797,7 +816,7 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
// TODO: Should find a way to balance gesture detection and this hack.
|
||||
if (sNeedsPhantomSuddenMoveEventHack
|
||||
&& lastMoveSquared >= mKeyQuarterWidthSquared
|
||||
&& !mIsPossibleGesture) {
|
||||
&& !mIsDetectingGesture) {
|
||||
if (DEBUG_MODE) {
|
||||
Log.w(TAG, String.format("onMoveEvent:"
|
||||
+ " phantom sudden move event is translated to "
|
||||
|
@ -815,11 +834,11 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
// touch panels when there are close multiple touches.
|
||||
// Caveat: When in chording input mode with a modifier key, we don't use
|
||||
// this hack.
|
||||
if (me != null && me.getPointerCount() > 1
|
||||
if (getActivePointerTrackerCount() > 1 && sPointerTrackerQueue != null
|
||||
&& !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) {
|
||||
onUpEventInternal();
|
||||
}
|
||||
if (!mIsPossibleGesture) {
|
||||
if (!mIsDetectingGesture) {
|
||||
mKeyAlreadyProcessed = true;
|
||||
}
|
||||
setReleasedKeyGraphics(oldKey);
|
||||
|
@ -837,7 +856,7 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
if (mIsAllowedSlidingKeyInput) {
|
||||
onMoveToNewKey(key, x, y);
|
||||
} else {
|
||||
if (!mIsPossibleGesture) {
|
||||
if (!mIsDetectingGesture) {
|
||||
mKeyAlreadyProcessed = true;
|
||||
}
|
||||
}
|
||||
|
@ -845,13 +864,14 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
}
|
||||
}
|
||||
|
||||
public void onUpEvent(int x, int y, long eventTime) {
|
||||
if (DEBUG_EVENT)
|
||||
public void onUpEvent(final int x, final int y, final long eventTime) {
|
||||
if (DEBUG_EVENT) {
|
||||
printTouchEvent("onUpEvent :", x, y, eventTime);
|
||||
}
|
||||
|
||||
final PointerTrackerQueue queue = sPointerTrackerQueue;
|
||||
if (queue != null) {
|
||||
if (!mInGesture) {
|
||||
if (!sInGesture) {
|
||||
if (mCurrentKey != null && mCurrentKey.isModifier()) {
|
||||
// Before processing an up event of modifier key, all pointers already being
|
||||
// tracked should be released.
|
||||
|
@ -860,18 +880,21 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
queue.releaseAllPointersOlderThan(this, eventTime);
|
||||
}
|
||||
}
|
||||
queue.remove(this);
|
||||
}
|
||||
onUpEventInternal();
|
||||
if (queue != null) {
|
||||
queue.remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
// Let this pointer tracker know that one of newer-than-this pointer trackers got an up event.
|
||||
// This pointer tracker needs to keep the key top graphics "pressed", but needs to get a
|
||||
// "virtual" up event.
|
||||
@Override
|
||||
public void onPhantomUpEvent(long eventTime) {
|
||||
if (DEBUG_EVENT)
|
||||
public void onPhantomUpEvent(final long eventTime) {
|
||||
if (DEBUG_EVENT) {
|
||||
printTouchEvent("onPhntEvent:", getLastX(), getLastY(), eventTime);
|
||||
}
|
||||
onUpEventInternal();
|
||||
mKeyAlreadyProcessed = true;
|
||||
}
|
||||
|
@ -879,37 +902,35 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
private void onUpEventInternal() {
|
||||
mTimerProxy.cancelKeyTimers();
|
||||
mIsInSlidingKeyInput = false;
|
||||
mIsPossibleGesture = false;
|
||||
mIsDetectingGesture = false;
|
||||
final Key currentKey = mCurrentKey;
|
||||
mCurrentKey = null;
|
||||
// Release the last pressed key.
|
||||
setReleasedKeyGraphics(mCurrentKey);
|
||||
setReleasedKeyGraphics(currentKey);
|
||||
if (mIsShowingMoreKeysPanel) {
|
||||
mDrawingProxy.dismissMoreKeysPanel();
|
||||
mIsShowingMoreKeysPanel = false;
|
||||
}
|
||||
|
||||
if (mInGesture) {
|
||||
// Register up event on gesture tracker.
|
||||
// TODO: Figure out how to deal with multiple fingers that are in gesture, sliding,
|
||||
// and/or tapping mode?
|
||||
endBatchInput(getAllBatchPoints());
|
||||
if (mCurrentKey != null) {
|
||||
callListenerOnRelease(mCurrentKey, mCurrentKey.mCode, true);
|
||||
mCurrentKey = null;
|
||||
if (sInGesture) {
|
||||
if (currentKey != null) {
|
||||
callListenerOnRelease(currentKey, currentKey.mCode, true);
|
||||
}
|
||||
mDrawingProxy.showGestureTrail(this);
|
||||
endBatchInput();
|
||||
return;
|
||||
}
|
||||
// This event will be recognized as a regular code input. Clear unused batch points so they
|
||||
// are not mistakenly included in the next batch event.
|
||||
// This event will be recognized as a regular code input. Clear unused possible batch points
|
||||
// so they are not mistakenly displayed as preview.
|
||||
clearBatchInputPointsOfAllPointerTrackers();
|
||||
if (mKeyAlreadyProcessed)
|
||||
if (mKeyAlreadyProcessed) {
|
||||
return;
|
||||
if (mCurrentKey != null && !mCurrentKey.isRepeatable()) {
|
||||
detectAndSendKey(mCurrentKey, mKeyX, mKeyY);
|
||||
}
|
||||
if (currentKey != null && !currentKey.isRepeatable()) {
|
||||
detectAndSendKey(currentKey, mKeyX, mKeyY);
|
||||
}
|
||||
}
|
||||
|
||||
public void onShowMoreKeysPanel(int x, int y, KeyEventHandler handler) {
|
||||
public void onShowMoreKeysPanel(final int x, final int y, final KeyEventHandler handler) {
|
||||
abortBatchInput();
|
||||
onLongPressed();
|
||||
mIsShowingMoreKeysPanel = true;
|
||||
|
@ -925,9 +946,10 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
}
|
||||
}
|
||||
|
||||
public void onCancelEvent(int x, int y, long eventTime) {
|
||||
if (DEBUG_EVENT)
|
||||
public void onCancelEvent(final int x, final int y, final long eventTime) {
|
||||
if (DEBUG_EVENT) {
|
||||
printTouchEvent("onCancelEvt:", x, y, eventTime);
|
||||
}
|
||||
|
||||
final PointerTrackerQueue queue = sPointerTrackerQueue;
|
||||
if (queue != null) {
|
||||
|
@ -947,24 +969,25 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
}
|
||||
}
|
||||
|
||||
private void startRepeatKey(Key key) {
|
||||
if (key != null && key.isRepeatable() && !mInGesture) {
|
||||
private void startRepeatKey(final Key key) {
|
||||
if (key != null && key.isRepeatable() && !sInGesture) {
|
||||
onRegisterKey(key);
|
||||
mTimerProxy.startKeyRepeatTimer(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void onRegisterKey(Key key) {
|
||||
public void onRegisterKey(final Key key) {
|
||||
if (key != null) {
|
||||
detectAndSendKey(key, key.mX, key.mY);
|
||||
mTimerProxy.startTypingStateTimer(key);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isMajorEnoughMoveToBeOnNewKey(int x, int y, Key newKey) {
|
||||
if (mKeyDetector == null)
|
||||
private boolean isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final Key newKey) {
|
||||
if (mKeyDetector == null) {
|
||||
throw new NullPointerException("keyboard and/or key detector not set");
|
||||
Key curKey = mCurrentKey;
|
||||
}
|
||||
final Key curKey = mCurrentKey;
|
||||
if (newKey == curKey) {
|
||||
return false;
|
||||
} else if (curKey != null) {
|
||||
|
@ -975,25 +998,25 @@ public class PointerTracker implements PointerTrackerQueue.ElementActions {
|
|||
}
|
||||
}
|
||||
|
||||
private void startLongPressTimer(Key key) {
|
||||
if (key != null && key.isLongPressEnabled() && !mInGesture) {
|
||||
private void startLongPressTimer(final Key key) {
|
||||
if (key != null && key.isLongPressEnabled() && !sInGesture) {
|
||||
mTimerProxy.startLongPressTimer(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void detectAndSendKey(Key key, int x, int y) {
|
||||
private void detectAndSendKey(final Key key, final int x, final int y) {
|
||||
if (key == null) {
|
||||
callListenerOnCancelInput();
|
||||
return;
|
||||
}
|
||||
|
||||
int code = key.mCode;
|
||||
final int code = key.mCode;
|
||||
callListenerOnCodeInput(key, code, x, y);
|
||||
callListenerOnRelease(key, code, false);
|
||||
sWasInGesture = false;
|
||||
}
|
||||
|
||||
private void printTouchEvent(String title, int x, int y, long eventTime) {
|
||||
private void printTouchEvent(final String title, final int x, final int y,
|
||||
final long eventTime) {
|
||||
final Key key = mKeyDetector.detectHitKey(x, y);
|
||||
final String code = KeyDetector.printableCode(key);
|
||||
Log.d(TAG, String.format("%s%s[%d] %4d %4d %5d %s", title,
|
||||
|
|
|
@ -18,9 +18,9 @@ package com.android.inputmethod.keyboard;
|
|||
|
||||
import android.graphics.Rect;
|
||||
import android.text.TextUtils;
|
||||
import android.util.FloatMath;
|
||||
|
||||
import com.android.inputmethod.keyboard.Keyboard.Params.TouchPositionCorrection;
|
||||
import com.android.inputmethod.latin.Constants;
|
||||
import com.android.inputmethod.latin.JniUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
@ -112,7 +112,7 @@ public class ProximityInfo {
|
|||
final Key[] keys = mKeys;
|
||||
final TouchPositionCorrection touchPositionCorrection = mTouchPositionCorrection;
|
||||
final int[] proximityCharsArray = new int[mGridSize * MAX_PROXIMITY_CHARS_SIZE];
|
||||
Arrays.fill(proximityCharsArray, KeyDetector.NOT_A_CODE);
|
||||
Arrays.fill(proximityCharsArray, Constants.NOT_A_CODE);
|
||||
for (int i = 0; i < mGridSize; ++i) {
|
||||
final int proximityCharsLength = gridNeighborKeys[i].length;
|
||||
for (int j = 0; j < proximityCharsLength; ++j) {
|
||||
|
@ -155,7 +155,9 @@ public class ProximityInfo {
|
|||
final float radius = touchPositionCorrection.mRadii[row];
|
||||
sweetSpotCenterXs[i] = hitBox.exactCenterX() + x * hitBoxWidth;
|
||||
sweetSpotCenterYs[i] = hitBox.exactCenterY() + y * hitBoxHeight;
|
||||
sweetSpotRadii[i] = radius * FloatMath.sqrt(
|
||||
// Note that, in recent versions of Android, FloatMath is actually slower than
|
||||
// java.lang.Math due to the way the JIT optimizes java.lang.Math.
|
||||
sweetSpotRadii[i] = radius * (float)Math.sqrt(
|
||||
hitBoxWidth * hitBoxWidth + hitBoxHeight * hitBoxHeight);
|
||||
}
|
||||
}
|
||||
|
@ -233,7 +235,7 @@ public class ProximityInfo {
|
|||
dest[index++] = code;
|
||||
}
|
||||
if (index < destLength) {
|
||||
dest[index] = KeyDetector.NOT_A_CODE;
|
||||
dest[index] = Constants.NOT_A_CODE;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.util.FloatMath;
|
||||
|
||||
import com.android.inputmethod.latin.Constants;
|
||||
import com.android.inputmethod.latin.InputPointers;
|
||||
import com.android.inputmethod.latin.ResizableIntArray;
|
||||
|
||||
|
@ -38,44 +33,30 @@ public class GestureStroke {
|
|||
private int mLastPointY;
|
||||
|
||||
private int mMinGestureLength;
|
||||
private int mMinGestureLengthWhileInGesture;
|
||||
private int mMinGestureSampleLength;
|
||||
|
||||
// TODO: Move some of these to resource.
|
||||
private static final float MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH = 1.0f;
|
||||
private static final float MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH_WHILE_IN_GESTURE = 0.5f;
|
||||
private static final int MIN_GESTURE_DURATION = 150; // msec
|
||||
private static final int MIN_GESTURE_DURATION_WHILE_IN_GESTURE = 75; // msec
|
||||
private static final float MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT = 1.0f / 6.0f;
|
||||
private static final float MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH = 0.75f;
|
||||
private static final int MIN_GESTURE_DURATION = 100; // msec
|
||||
private static final float MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH = 1.0f / 6.0f;
|
||||
private static final float GESTURE_RECOG_SPEED_THRESHOLD = 0.4f; // dip/msec
|
||||
private static final float GESTURE_RECOG_CURVATURE_THRESHOLD = (float)(Math.PI / 4.0f);
|
||||
|
||||
private static final float DOUBLE_PI = (float)(2 * Math.PI);
|
||||
private static final float DOUBLE_PI = (float)(2.0f * Math.PI);
|
||||
|
||||
// Fade based on number of gesture samples, see MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT
|
||||
private static final int DRAWING_GESTURE_FADE_START = 10;
|
||||
private static final int DRAWING_GESTURE_FADE_RATE = 6;
|
||||
|
||||
public GestureStroke(int pointerId) {
|
||||
public GestureStroke(final int pointerId) {
|
||||
mPointerId = pointerId;
|
||||
reset();
|
||||
}
|
||||
|
||||
public void setGestureSampleLength(final int keyWidth, final int keyHeight) {
|
||||
public void setGestureSampleLength(final int keyWidth) {
|
||||
// TODO: Find an appropriate base metric for these length. Maybe diagonal length of the key?
|
||||
mMinGestureLength = (int)(keyWidth * MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH);
|
||||
mMinGestureLengthWhileInGesture = (int)(
|
||||
keyWidth * MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH_WHILE_IN_GESTURE);
|
||||
mMinGestureSampleLength = (int)(keyHeight * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT);
|
||||
mMinGestureSampleLength = (int)(keyWidth * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH);
|
||||
}
|
||||
|
||||
public boolean isStartOfAGesture(final int downDuration, final boolean wasInGesture) {
|
||||
// The tolerance of the time duration and the stroke length to detect the start of a
|
||||
// gesture stroke should be eased when the previous input was a gesture input.
|
||||
if (wasInGesture) {
|
||||
return downDuration > MIN_GESTURE_DURATION_WHILE_IN_GESTURE
|
||||
&& mLength > mMinGestureLengthWhileInGesture;
|
||||
}
|
||||
public boolean isStartOfAGesture() {
|
||||
final int size = mEventTimes.getLength();
|
||||
final int downDuration = (size > 0) ? mEventTimes.get(size - 1) : 0;
|
||||
return downDuration > MIN_GESTURE_DURATION && mLength > mMinGestureLength;
|
||||
}
|
||||
|
||||
|
@ -149,23 +130,29 @@ public class GestureStroke {
|
|||
}
|
||||
|
||||
private void appendBatchPoints(final InputPointers out, final int size) {
|
||||
final int length = size - mLastIncrementalBatchSize;
|
||||
if (length <= 0) {
|
||||
return;
|
||||
}
|
||||
out.append(mPointerId, mEventTimes, mXCoordinates, mYCoordinates,
|
||||
mLastIncrementalBatchSize, size - mLastIncrementalBatchSize);
|
||||
mLastIncrementalBatchSize, length);
|
||||
mLastIncrementalBatchSize = size;
|
||||
}
|
||||
|
||||
private static float getDistance(final int p1x, final int p1y,
|
||||
final int p2x, final int p2y) {
|
||||
final float dx = p1x - p2x;
|
||||
final float dy = p1y - p2y;
|
||||
// TODO: Optimize out this {@link FloatMath#sqrt(float)} call.
|
||||
return FloatMath.sqrt(dx * dx + dy * dy);
|
||||
private static float getDistance(final int x1, final int y1, final int x2, final int y2) {
|
||||
final float dx = x1 - x2;
|
||||
final float dy = y1 - y2;
|
||||
// Note that, in recent versions of Android, FloatMath is actually slower than
|
||||
// java.lang.Math due to the way the JIT optimizes java.lang.Math.
|
||||
return (float)Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
private static float getAngle(final int p1x, final int p1y, final int p2x, final int p2y) {
|
||||
final int dx = p1x - p2x;
|
||||
final int dy = p1y - p2y;
|
||||
private static float getAngle(final int x1, final int y1, final int x2, final int y2) {
|
||||
final int dx = x1 - x2;
|
||||
final int dy = y1 - y2;
|
||||
if (dx == 0 && dy == 0) return 0;
|
||||
// Would it be faster to call atan2f() directly via JNI? Not sure about what the JIT
|
||||
// does with Math.atan2().
|
||||
return (float)Math.atan2(dy, dx);
|
||||
}
|
||||
|
||||
|
@ -176,23 +163,4 @@ public class GestureStroke {
|
|||
}
|
||||
return diff;
|
||||
}
|
||||
|
||||
public void drawGestureTrail(Canvas canvas, Paint paint, int lastX, int lastY) {
|
||||
// TODO: These paint parameter interpolation should be tunable, possibly introduce an object
|
||||
// that implements an interface such as Paint getPaint(int step, int strokePoints)
|
||||
final int size = mXCoordinates.getLength();
|
||||
int[] xCoords = mXCoordinates.getPrimitiveArray();
|
||||
int[] yCoords = mYCoordinates.getPrimitiveArray();
|
||||
int alpha = Constants.Color.ALPHA_OPAQUE;
|
||||
for (int i = size - 1; i > 0 && alpha > 0; i--) {
|
||||
paint.setAlpha(alpha);
|
||||
if (size - i > DRAWING_GESTURE_FADE_START) {
|
||||
alpha -= DRAWING_GESTURE_FADE_RATE;
|
||||
}
|
||||
canvas.drawLine(xCoords[i - 1], yCoords[i - 1], xCoords[i], yCoords[i], paint);
|
||||
if (i == size - 1) {
|
||||
canvas.drawLine(lastX, lastY, xCoords[i], yCoords[i], paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 com.android.inputmethod.keyboard.Keyboard;
|
||||
import com.android.inputmethod.latin.CollectionUtils;
|
||||
import com.android.inputmethod.latin.LatinImeLogger;
|
||||
import com.android.inputmethod.latin.StringUtils;
|
||||
|
||||
|
@ -258,7 +259,7 @@ public class KeySpecParser {
|
|||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
final ArrayList<T> list = new ArrayList<T>(end - start);
|
||||
final ArrayList<T> list = CollectionUtils.newArrayList(end - start);
|
||||
for (int i = start; i < end; i++) {
|
||||
list.add(array[i]);
|
||||
}
|
||||
|
@ -438,7 +439,7 @@ public class KeySpecParser {
|
|||
// Skip empty entry.
|
||||
if (pos - start > 0) {
|
||||
if (list == null) {
|
||||
list = new ArrayList<String>();
|
||||
list = CollectionUtils.newArrayList();
|
||||
}
|
||||
list.add(text.substring(start, pos));
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import android.util.Log;
|
|||
import android.util.SparseArray;
|
||||
|
||||
import com.android.inputmethod.keyboard.Keyboard;
|
||||
import com.android.inputmethod.latin.CollectionUtils;
|
||||
import com.android.inputmethod.latin.R;
|
||||
import com.android.inputmethod.latin.XmlParseUtils;
|
||||
|
||||
|
@ -33,7 +34,7 @@ public class KeyStyles {
|
|||
private static final String TAG = KeyStyles.class.getSimpleName();
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
final HashMap<String, KeyStyle> mStyles = new HashMap<String, KeyStyle>();
|
||||
final HashMap<String, KeyStyle> mStyles = CollectionUtils.newHashMap();
|
||||
|
||||
final KeyboardTextsSet mTextsSet;
|
||||
private final KeyStyle mEmptyKeyStyle;
|
||||
|
@ -90,7 +91,7 @@ public class KeyStyles {
|
|||
|
||||
private class DeclaredKeyStyle extends KeyStyle {
|
||||
private final String mParentStyleName;
|
||||
private final SparseArray<Object> mStyleAttributes = new SparseArray<Object>();
|
||||
private final SparseArray<Object> mStyleAttributes = CollectionUtils.newSparseArray();
|
||||
|
||||
public DeclaredKeyStyle(String parentStyleName) {
|
||||
mParentStyleName = parentStyleName;
|
||||
|
|
|
@ -17,13 +17,13 @@
|
|||
package com.android.inputmethod.keyboard.internal;
|
||||
|
||||
import com.android.inputmethod.keyboard.Keyboard;
|
||||
import com.android.inputmethod.latin.CollectionUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class KeyboardCodesSet {
|
||||
private static final HashMap<String, int[]> sLanguageToCodesMap =
|
||||
new HashMap<String, int[]>();
|
||||
private static final HashMap<String, Integer> sNameToIdMap = new HashMap<String, Integer>();
|
||||
private static final HashMap<String, int[]> sLanguageToCodesMap = CollectionUtils.newHashMap();
|
||||
private static final HashMap<String, Integer> sNameToIdMap = CollectionUtils.newHashMap();
|
||||
|
||||
private int[] mCodes = DEFAULT;
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import android.graphics.drawable.Drawable;
|
|||
import android.util.Log;
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
import com.android.inputmethod.latin.CollectionUtils;
|
||||
import com.android.inputmethod.latin.R;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
@ -35,7 +36,7 @@ public class KeyboardIconsSet {
|
|||
private static final SparseIntArray ATTR_ID_TO_ICON_ID = new SparseIntArray();
|
||||
|
||||
// Icon name to icon id map.
|
||||
private static final HashMap<String, Integer> sNameToIdsMap = new HashMap<String, Integer>();
|
||||
private static final HashMap<String, Integer> sNameToIdsMap = CollectionUtils.newHashMap();
|
||||
|
||||
private static final Object[] NAMES_AND_ATTR_IDS = {
|
||||
"undefined", ATTR_UNDEFINED,
|
||||
|
|
|
@ -19,6 +19,7 @@ package com.android.inputmethod.keyboard.internal;
|
|||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import com.android.inputmethod.latin.CollectionUtils;
|
||||
import com.android.inputmethod.latin.R;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
@ -45,14 +46,12 @@ import java.util.HashMap;
|
|||
*/
|
||||
public final class KeyboardTextsSet {
|
||||
// Language to texts map.
|
||||
private static final HashMap<String, String[]> sLocaleToTextsMap =
|
||||
new HashMap<String, String[]>();
|
||||
private static final HashMap<String, Integer> sNameToIdsMap =
|
||||
new HashMap<String, Integer>();
|
||||
private static final HashMap<String, String[]> sLocaleToTextsMap = CollectionUtils.newHashMap();
|
||||
private static final HashMap<String, Integer> sNameToIdsMap = CollectionUtils.newHashMap();
|
||||
|
||||
private String[] mTexts;
|
||||
// Resource name to text map.
|
||||
private HashMap<String, String> mResourceNameToTextsMap = new HashMap<String, String>();
|
||||
private HashMap<String, String> mResourceNameToTextsMap = CollectionUtils.newHashMap();
|
||||
|
||||
public void setLanguage(final String language) {
|
||||
mTexts = sLocaleToTextsMap.get(language);
|
||||
|
|
|
@ -18,85 +18,148 @@ package com.android.inputmethod.keyboard.internal;
|
|||
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import com.android.inputmethod.latin.CollectionUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class PointerTrackerQueue {
|
||||
private static final String TAG = PointerTrackerQueue.class.getSimpleName();
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
public interface ElementActions {
|
||||
public interface Element {
|
||||
public boolean isModifier();
|
||||
public boolean isInSlidingKeyInput();
|
||||
public void onPhantomUpEvent(long eventTime);
|
||||
}
|
||||
|
||||
// TODO: Use ring buffer instead of {@link LinkedList}.
|
||||
private final LinkedList<ElementActions> mQueue = new LinkedList<ElementActions>();
|
||||
private static final int INITIAL_CAPACITY = 10;
|
||||
private final ArrayList<Element> mExpandableArrayOfActivePointers =
|
||||
CollectionUtils.newArrayList(INITIAL_CAPACITY);
|
||||
private int mArraySize = 0;
|
||||
|
||||
public int size() {
|
||||
return mQueue.size();
|
||||
public synchronized int size() {
|
||||
return mArraySize;
|
||||
}
|
||||
|
||||
public synchronized void add(ElementActions tracker) {
|
||||
mQueue.add(tracker);
|
||||
public synchronized void add(final Element pointer) {
|
||||
final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
|
||||
final int arraySize = mArraySize;
|
||||
if (arraySize < expandableArray.size()) {
|
||||
expandableArray.set(arraySize, pointer);
|
||||
} else {
|
||||
expandableArray.add(pointer);
|
||||
}
|
||||
mArraySize = arraySize + 1;
|
||||
}
|
||||
|
||||
public synchronized void remove(ElementActions tracker) {
|
||||
mQueue.remove(tracker);
|
||||
public synchronized void remove(final Element pointer) {
|
||||
final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
|
||||
final int arraySize = mArraySize;
|
||||
int newSize = 0;
|
||||
for (int index = 0; index < arraySize; index++) {
|
||||
final Element element = expandableArray.get(index);
|
||||
if (element == pointer) {
|
||||
if (newSize != index) {
|
||||
Log.w(TAG, "Found duplicated element in remove: " + pointer);
|
||||
}
|
||||
continue; // Remove this element from the expandableArray.
|
||||
}
|
||||
if (newSize != index) {
|
||||
// Shift this element toward the beginning of the expandableArray.
|
||||
expandableArray.set(newSize, element);
|
||||
}
|
||||
newSize++;
|
||||
}
|
||||
mArraySize = newSize;
|
||||
}
|
||||
|
||||
public synchronized void releaseAllPointersOlderThan(ElementActions tracker,
|
||||
long eventTime) {
|
||||
public synchronized void releaseAllPointersOlderThan(final Element pointer,
|
||||
final long eventTime) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "releaseAllPoniterOlderThan: " + tracker + " " + this);
|
||||
Log.d(TAG, "releaseAllPoniterOlderThan: " + pointer + " " + this);
|
||||
}
|
||||
if (!mQueue.contains(tracker)) {
|
||||
return;
|
||||
final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
|
||||
final int arraySize = mArraySize;
|
||||
int newSize, index;
|
||||
for (newSize = index = 0; index < arraySize; index++) {
|
||||
final Element element = expandableArray.get(index);
|
||||
if (element == pointer) {
|
||||
break; // Stop releasing elements.
|
||||
}
|
||||
final Iterator<ElementActions> it = mQueue.iterator();
|
||||
while (it.hasNext()) {
|
||||
final ElementActions t = it.next();
|
||||
if (t == tracker) {
|
||||
break;
|
||||
if (!element.isModifier()) {
|
||||
element.onPhantomUpEvent(eventTime);
|
||||
continue; // Remove this element from the expandableArray.
|
||||
}
|
||||
if (!t.isModifier()) {
|
||||
t.onPhantomUpEvent(eventTime);
|
||||
it.remove();
|
||||
if (newSize != index) {
|
||||
// Shift this element toward the beginning of the expandableArray.
|
||||
expandableArray.set(newSize, element);
|
||||
}
|
||||
newSize++;
|
||||
}
|
||||
// Shift rest of the expandableArray.
|
||||
int count = 0;
|
||||
for (; index < arraySize; index++) {
|
||||
final Element element = expandableArray.get(index);
|
||||
if (element == pointer) {
|
||||
if (count > 0) {
|
||||
Log.w(TAG, "Found duplicated element in releaseAllPointersOlderThan: "
|
||||
+ pointer);
|
||||
}
|
||||
count++;
|
||||
}
|
||||
if (newSize != index) {
|
||||
expandableArray.set(newSize, expandableArray.get(index));
|
||||
newSize++;
|
||||
}
|
||||
}
|
||||
mArraySize = newSize;
|
||||
}
|
||||
|
||||
public void releaseAllPointers(long eventTime) {
|
||||
public void releaseAllPointers(final long eventTime) {
|
||||
releaseAllPointersExcept(null, eventTime);
|
||||
}
|
||||
|
||||
public synchronized void releaseAllPointersExcept(ElementActions tracker, long eventTime) {
|
||||
public synchronized void releaseAllPointersExcept(final Element pointer,
|
||||
final long eventTime) {
|
||||
if (DEBUG) {
|
||||
if (tracker == null) {
|
||||
if (pointer == null) {
|
||||
Log.d(TAG, "releaseAllPoniters: " + this);
|
||||
} else {
|
||||
Log.d(TAG, "releaseAllPoniterExcept: " + tracker + " " + this);
|
||||
Log.d(TAG, "releaseAllPoniterExcept: " + pointer + " " + this);
|
||||
}
|
||||
}
|
||||
final Iterator<ElementActions> it = mQueue.iterator();
|
||||
while (it.hasNext()) {
|
||||
final ElementActions t = it.next();
|
||||
if (t != tracker) {
|
||||
t.onPhantomUpEvent(eventTime);
|
||||
it.remove();
|
||||
final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
|
||||
final int arraySize = mArraySize;
|
||||
int newSize = 0, count = 0;
|
||||
for (int index = 0; index < arraySize; index++) {
|
||||
final Element element = expandableArray.get(index);
|
||||
if (element == pointer) {
|
||||
if (count > 0) {
|
||||
Log.w(TAG, "Found duplicated element in releaseAllPointersExcept: " + pointer);
|
||||
}
|
||||
count++;
|
||||
} else {
|
||||
element.onPhantomUpEvent(eventTime);
|
||||
continue; // Remove this element from the expandableArray.
|
||||
}
|
||||
if (newSize != index) {
|
||||
// Shift this element toward the beginning of the expandableArray.
|
||||
expandableArray.set(newSize, element);
|
||||
}
|
||||
newSize++;
|
||||
}
|
||||
mArraySize = newSize;
|
||||
}
|
||||
|
||||
public synchronized boolean hasModifierKeyOlderThan(ElementActions tracker) {
|
||||
final Iterator<ElementActions> it = mQueue.iterator();
|
||||
while (it.hasNext()) {
|
||||
final ElementActions t = it.next();
|
||||
if (t == tracker) {
|
||||
break;
|
||||
public synchronized boolean hasModifierKeyOlderThan(final Element pointer) {
|
||||
final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
|
||||
final int arraySize = mArraySize;
|
||||
for (int index = 0; index < arraySize; index++) {
|
||||
final Element element = expandableArray.get(index);
|
||||
if (element == pointer) {
|
||||
return false; // Stop searching modifier key.
|
||||
}
|
||||
if (t.isModifier()) {
|
||||
if (element.isModifier()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -104,8 +167,11 @@ public class PointerTrackerQueue {
|
|||
}
|
||||
|
||||
public synchronized boolean isAnyInSlidingKeyInput() {
|
||||
for (final ElementActions tracker : mQueue) {
|
||||
if (tracker.isInSlidingKeyInput()) {
|
||||
final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
|
||||
final int arraySize = mArraySize;
|
||||
for (int index = 0; index < arraySize; index++) {
|
||||
final Element element = expandableArray.get(index);
|
||||
if (element.isInSlidingKeyInput()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -113,12 +179,15 @@ public class PointerTrackerQueue {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
public synchronized String toString() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
for (final ElementActions tracker : mQueue) {
|
||||
final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
|
||||
final int arraySize = mArraySize;
|
||||
for (int index = 0; index < arraySize; index++) {
|
||||
final Element element = expandableArray.get(index);
|
||||
if (sb.length() > 0)
|
||||
sb.append(" ");
|
||||
sb.append(tracker.toString());
|
||||
sb.append(element.toString());
|
||||
}
|
||||
return "[" + sb.toString() + "]";
|
||||
}
|
||||
|
|
|
@ -23,10 +23,13 @@ import android.graphics.Paint;
|
|||
import android.graphics.Paint.Align;
|
||||
import android.os.Message;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.SparseArray;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
import com.android.inputmethod.keyboard.PointerTracker;
|
||||
import com.android.inputmethod.keyboard.internal.GesturePreviewTrail.GesturePreviewTrailParams;
|
||||
import com.android.inputmethod.latin.CollectionUtils;
|
||||
import com.android.inputmethod.latin.R;
|
||||
import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
|
||||
|
||||
|
@ -46,29 +49,42 @@ public class PreviewPlacerView extends RelativeLayout {
|
|||
private int mXOrigin;
|
||||
private int mYOrigin;
|
||||
|
||||
private final SparseArray<PointerTracker> mPointers = new SparseArray<PointerTracker>();
|
||||
private final SparseArray<GesturePreviewTrail> mGesturePreviewTrails =
|
||||
CollectionUtils.newSparseArray();
|
||||
private final GesturePreviewTrailParams mGesturePreviewTrailParams;
|
||||
|
||||
private String mGestureFloatingPreviewText;
|
||||
private int mLastPointerX;
|
||||
private int mLastPointerY;
|
||||
|
||||
private boolean mDrawsGesturePreviewTrail;
|
||||
private boolean mDrawsGestureFloatingPreviewText;
|
||||
|
||||
private final DrawingHandler mDrawingHandler = new DrawingHandler(this);
|
||||
private final DrawingHandler mDrawingHandler;
|
||||
|
||||
private static class DrawingHandler extends StaticInnerHandlerWrapper<PreviewPlacerView> {
|
||||
private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 0;
|
||||
private static final int MSG_UPDATE_GESTURE_PREVIEW_TRAIL = 1;
|
||||
|
||||
public DrawingHandler(PreviewPlacerView outerInstance) {
|
||||
private final GesturePreviewTrailParams mGesturePreviewTrailParams;
|
||||
|
||||
public DrawingHandler(final PreviewPlacerView outerInstance,
|
||||
final GesturePreviewTrailParams gesturePreviewTrailParams) {
|
||||
super(outerInstance);
|
||||
mGesturePreviewTrailParams = gesturePreviewTrailParams;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
public void handleMessage(final Message msg) {
|
||||
final PreviewPlacerView placerView = getOuterInstance();
|
||||
if (placerView == null) return;
|
||||
switch (msg.what) {
|
||||
case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT:
|
||||
placerView.setGestureFloatingPreviewText(null);
|
||||
break;
|
||||
case MSG_UPDATE_GESTURE_PREVIEW_TRAIL:
|
||||
placerView.invalidate();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,15 +100,32 @@ public class PreviewPlacerView extends RelativeLayout {
|
|||
placerView.mGestureFloatingPreviewTextLingerTimeout);
|
||||
}
|
||||
|
||||
private void cancelUpdateGestureTrailPreview() {
|
||||
removeMessages(MSG_UPDATE_GESTURE_PREVIEW_TRAIL);
|
||||
}
|
||||
|
||||
public void postUpdateGestureTrailPreview() {
|
||||
cancelUpdateGestureTrailPreview();
|
||||
sendMessageDelayed(obtainMessage(MSG_UPDATE_GESTURE_PREVIEW_TRAIL),
|
||||
mGesturePreviewTrailParams.mUpdateInterval);
|
||||
}
|
||||
|
||||
public void cancelAllMessages() {
|
||||
cancelDismissGestureFloatingPreviewText();
|
||||
cancelUpdateGestureTrailPreview();
|
||||
}
|
||||
}
|
||||
|
||||
public PreviewPlacerView(Context context, TypedArray keyboardViewAttr) {
|
||||
public PreviewPlacerView(final Context context, final AttributeSet attrs) {
|
||||
this(context, attrs, R.attr.keyboardViewStyle);
|
||||
}
|
||||
|
||||
public PreviewPlacerView(final Context context, final AttributeSet attrs, final int defStyle) {
|
||||
super(context);
|
||||
setWillNotDraw(false);
|
||||
|
||||
final TypedArray keyboardViewAttr = context.obtainStyledAttributes(
|
||||
attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
|
||||
final int gestureFloatingPreviewTextSize = keyboardViewAttr.getDimensionPixelSize(
|
||||
R.styleable.KeyboardView_gestureFloatingPreviewTextSize, 0);
|
||||
mGestureFloatingPreviewTextColor = keyboardViewAttr.getColor(
|
||||
|
@ -117,6 +150,10 @@ public class PreviewPlacerView extends RelativeLayout {
|
|||
R.styleable.KeyboardView_gesturePreviewTrailColor, 0);
|
||||
final int gesturePreviewTrailWidth = keyboardViewAttr.getDimensionPixelSize(
|
||||
R.styleable.KeyboardView_gesturePreviewTrailWidth, 0);
|
||||
mGesturePreviewTrailParams = new GesturePreviewTrailParams(keyboardViewAttr);
|
||||
keyboardViewAttr.recycle();
|
||||
|
||||
mDrawingHandler = new DrawingHandler(this, mGesturePreviewTrailParams);
|
||||
|
||||
mGesturePaint = new Paint();
|
||||
mGesturePaint.setAntiAlias(true);
|
||||
|
@ -132,48 +169,60 @@ public class PreviewPlacerView extends RelativeLayout {
|
|||
mTextPaint.setTextSize(gestureFloatingPreviewTextSize);
|
||||
}
|
||||
|
||||
public void setOrigin(int x, int y) {
|
||||
public void setOrigin(final int x, final int y) {
|
||||
mXOrigin = x;
|
||||
mYOrigin = y;
|
||||
}
|
||||
|
||||
public void setGesturePreviewMode(boolean drawsGesturePreviewTrail,
|
||||
boolean drawsGestureFloatingPreviewText) {
|
||||
public void setGesturePreviewMode(final boolean drawsGesturePreviewTrail,
|
||||
final boolean drawsGestureFloatingPreviewText) {
|
||||
mDrawsGesturePreviewTrail = drawsGesturePreviewTrail;
|
||||
mDrawsGestureFloatingPreviewText = drawsGestureFloatingPreviewText;
|
||||
}
|
||||
|
||||
public void invalidatePointer(PointerTracker tracker) {
|
||||
synchronized (mPointers) {
|
||||
mPointers.put(tracker.mPointerId, tracker);
|
||||
public void invalidatePointer(final PointerTracker tracker) {
|
||||
GesturePreviewTrail trail;
|
||||
synchronized (mGesturePreviewTrails) {
|
||||
trail = mGesturePreviewTrails.get(tracker.mPointerId);
|
||||
if (trail == null) {
|
||||
trail = new GesturePreviewTrail(mGesturePreviewTrailParams);
|
||||
mGesturePreviewTrails.put(tracker.mPointerId, trail);
|
||||
}
|
||||
}
|
||||
trail.addStroke(tracker.getGestureStrokeWithPreviewTrail(), tracker.getDownTime());
|
||||
|
||||
mLastPointerX = tracker.getLastX();
|
||||
mLastPointerY = tracker.getLastY();
|
||||
// TODO: Should narrow the invalidate region.
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(Canvas canvas) {
|
||||
public void onDraw(final Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
synchronized (mPointers) {
|
||||
canvas.translate(mXOrigin, mYOrigin);
|
||||
final int trackerCount = mPointers.size();
|
||||
boolean hasDrawnFloatingPreviewText = false;
|
||||
for (int index = 0; index < trackerCount; index++) {
|
||||
final PointerTracker tracker = mPointers.valueAt(index);
|
||||
if (mDrawsGesturePreviewTrail) {
|
||||
tracker.drawGestureTrail(canvas, mGesturePaint);
|
||||
boolean needsUpdatingGesturePreviewTrail = false;
|
||||
synchronized (mGesturePreviewTrails) {
|
||||
// Trails count == fingers count that have ever been active.
|
||||
final int trailsCount = mGesturePreviewTrails.size();
|
||||
for (int index = 0; index < trailsCount; index++) {
|
||||
final GesturePreviewTrail trail = mGesturePreviewTrails.valueAt(index);
|
||||
needsUpdatingGesturePreviewTrail |=
|
||||
trail.drawGestureTrail(canvas, mGesturePaint);
|
||||
}
|
||||
// TODO: Figure out more cleaner way to draw gesture preview text.
|
||||
if (mDrawsGestureFloatingPreviewText && !hasDrawnFloatingPreviewText) {
|
||||
drawGestureFloatingPreviewText(canvas, tracker, mGestureFloatingPreviewText);
|
||||
hasDrawnFloatingPreviewText = true;
|
||||
}
|
||||
if (needsUpdatingGesturePreviewTrail) {
|
||||
mDrawingHandler.postUpdateGestureTrailPreview();
|
||||
}
|
||||
}
|
||||
if (mDrawsGestureFloatingPreviewText) {
|
||||
drawGestureFloatingPreviewText(canvas, mGestureFloatingPreviewText);
|
||||
}
|
||||
canvas.translate(-mXOrigin, -mYOrigin);
|
||||
}
|
||||
}
|
||||
|
||||
public void setGestureFloatingPreviewText(String gestureFloatingPreviewText) {
|
||||
public void setGestureFloatingPreviewText(final String gestureFloatingPreviewText) {
|
||||
mGestureFloatingPreviewText = gestureFloatingPreviewText;
|
||||
invalidate();
|
||||
}
|
||||
|
@ -186,15 +235,17 @@ public class PreviewPlacerView extends RelativeLayout {
|
|||
mDrawingHandler.cancelAllMessages();
|
||||
}
|
||||
|
||||
private void drawGestureFloatingPreviewText(Canvas canvas, PointerTracker tracker,
|
||||
String gestureFloatingPreviewText) {
|
||||
private void drawGestureFloatingPreviewText(final Canvas canvas,
|
||||
final String gestureFloatingPreviewText) {
|
||||
if (TextUtils.isEmpty(gestureFloatingPreviewText)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Paint paint = mTextPaint;
|
||||
final int lastX = tracker.getLastX();
|
||||
final int lastY = tracker.getLastY();
|
||||
// TODO: Figure out how we should deal with the floating preview text with multiple moving
|
||||
// fingers.
|
||||
final int lastX = mLastPointerX;
|
||||
final int lastY = mLastPointerY;
|
||||
final int textSize = (int)paint.getTextSize();
|
||||
final int canvasWidth = canvas.getWidth();
|
||||
|
||||
|
|
|
@ -91,7 +91,7 @@ public class AdditionalSubtype {
|
|||
}
|
||||
final String[] prefSubtypeArray = prefSubtypes.split(PREF_SUBTYPE_SEPARATOR);
|
||||
final ArrayList<InputMethodSubtype> subtypesList =
|
||||
new ArrayList<InputMethodSubtype>(prefSubtypeArray.length);
|
||||
CollectionUtils.newArrayList(prefSubtypeArray.length);
|
||||
for (final String prefSubtype : prefSubtypeArray) {
|
||||
final InputMethodSubtype subtype = createAdditionalSubtype(prefSubtype);
|
||||
if (subtype.getNameResId() == SubtypeLocale.UNKNOWN_KEYBOARD_LAYOUT) {
|
||||
|
|
|
@ -89,7 +89,7 @@ public class AdditionalSubtypeSettings extends PreferenceFragment {
|
|||
super(context, android.R.layout.simple_spinner_item);
|
||||
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
|
||||
final TreeSet<SubtypeLocaleItem> items = new TreeSet<SubtypeLocaleItem>();
|
||||
final TreeSet<SubtypeLocaleItem> items = CollectionUtils.newTreeSet();
|
||||
final InputMethodInfo imi = ImfUtils.getInputMethodInfoOfThisIme(context);
|
||||
final int count = imi.getSubtypeCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
|
@ -533,7 +533,7 @@ public class AdditionalSubtypeSettings extends PreferenceFragment {
|
|||
|
||||
private InputMethodSubtype[] getSubtypes() {
|
||||
final PreferenceGroup group = getPreferenceScreen();
|
||||
final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
|
||||
final ArrayList<InputMethodSubtype> subtypes = CollectionUtils.newArrayList();
|
||||
final int count = group.getPreferenceCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final Preference pref = group.getPreference(i);
|
||||
|
|
|
@ -39,7 +39,6 @@ public class AutoCorrection {
|
|||
}
|
||||
final CharSequence lowerCasedWord = word.toString().toLowerCase();
|
||||
for (final String key : dictionaries.keySet()) {
|
||||
if (key.equals(Dictionary.TYPE_WHITELIST)) continue;
|
||||
final Dictionary dictionary = dictionaries.get(key);
|
||||
// It's unclear how realistically 'dictionary' can be null, but the monkey is somehow
|
||||
// managing to get null in here. Presumably the language is changing to a language with
|
||||
|
@ -64,7 +63,6 @@ public class AutoCorrection {
|
|||
}
|
||||
int maxFreq = -1;
|
||||
for (final String key : dictionaries.keySet()) {
|
||||
if (key.equals(Dictionary.TYPE_WHITELIST)) continue;
|
||||
final Dictionary dictionary = dictionaries.get(key);
|
||||
if (null == dictionary) continue;
|
||||
final int tempFreq = dictionary.getFrequency(word);
|
||||
|
@ -75,17 +73,10 @@ public class AutoCorrection {
|
|||
return maxFreq;
|
||||
}
|
||||
|
||||
// Returns true if this is a whitelist entry, or it isn't in any dictionary.
|
||||
public static boolean isWhitelistedOrNotAWord(
|
||||
// Returns true if this isn't in any dictionary.
|
||||
public static boolean isNotAWord(
|
||||
final ConcurrentHashMap<String, Dictionary> dictionaries,
|
||||
final CharSequence word, final boolean ignoreCase) {
|
||||
final WhitelistDictionary whitelistDictionary =
|
||||
(WhitelistDictionary)dictionaries.get(Dictionary.TYPE_WHITELIST);
|
||||
// If "word" is in the whitelist dictionary, it should not be auto corrected.
|
||||
if (whitelistDictionary != null
|
||||
&& whitelistDictionary.shouldForciblyAutoCorrectFrom(word)) {
|
||||
return true;
|
||||
}
|
||||
return !isValidWord(dictionaries, word, ignoreCase);
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ package com.android.inputmethod.latin;
|
|||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.inputmethod.keyboard.ProximityInfo;
|
||||
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
|
||||
|
@ -51,7 +52,9 @@ public class BinaryDictionary extends Dictionary {
|
|||
private static final int TYPED_LETTER_MULTIPLIER = 2;
|
||||
|
||||
private long mNativeDict;
|
||||
private final int[] mInputCodes = new int[MAX_WORD_LENGTH];
|
||||
private final Locale mLocale;
|
||||
private final int[] mInputCodePoints = new int[MAX_WORD_LENGTH];
|
||||
// TODO: The below should be int[] mOutputCodePoints
|
||||
private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_RESULTS];
|
||||
private final int[] mSpaceIndices = new int[MAX_SPACES];
|
||||
private final int[] mOutputScores = new int[MAX_RESULTS];
|
||||
|
@ -59,6 +62,25 @@ public class BinaryDictionary extends Dictionary {
|
|||
|
||||
private final boolean mUseFullEditDistance;
|
||||
|
||||
private final SparseArray<DicTraverseSession> mDicTraverseSessions =
|
||||
CollectionUtils.newSparseArray();
|
||||
|
||||
// TODO: There should be a way to remove used DicTraverseSession objects from
|
||||
// {@code mDicTraverseSessions}.
|
||||
private DicTraverseSession getTraverseSession(int traverseSessionId) {
|
||||
synchronized(mDicTraverseSessions) {
|
||||
DicTraverseSession traverseSession = mDicTraverseSessions.get(traverseSessionId);
|
||||
if (traverseSession == null) {
|
||||
traverseSession = mDicTraverseSessions.get(traverseSessionId);
|
||||
if (traverseSession == null) {
|
||||
traverseSession = new DicTraverseSession(mLocale, mNativeDict);
|
||||
mDicTraverseSessions.put(traverseSessionId, traverseSession);
|
||||
}
|
||||
}
|
||||
return traverseSession;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for the binary dictionary. This is supposed to be called from the
|
||||
* dictionary factory.
|
||||
|
@ -74,6 +96,7 @@ public class BinaryDictionary extends Dictionary {
|
|||
final String filename, final long offset, final long length,
|
||||
final boolean useFullEditDistance, final Locale locale, final String dictType) {
|
||||
super(dictType);
|
||||
mLocale = locale;
|
||||
mUseFullEditDistance = useFullEditDistance;
|
||||
loadDictionary(filename, offset, length);
|
||||
}
|
||||
|
@ -86,18 +109,17 @@ public class BinaryDictionary extends Dictionary {
|
|||
int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength, int maxWords,
|
||||
int maxPredictions);
|
||||
private native void closeNative(long dict);
|
||||
private native int getFrequencyNative(long dict, int[] word, int wordLength);
|
||||
private native int getFrequencyNative(long dict, int[] word);
|
||||
private native boolean isValidBigramNative(long dict, int[] word1, int[] word2);
|
||||
private native int getSuggestionsNative(long dict, long proximityInfo, int[] xCoordinates,
|
||||
int[] yCoordinates, int[] times, int[] pointerIds, int[] inputCodes, int codesSize,
|
||||
int commitPoint, boolean isGesture,
|
||||
private native int getSuggestionsNative(long dict, long proximityInfo, long traverseSession,
|
||||
int[] xCoordinates, int[] yCoordinates, int[] times, int[] pointerIds,
|
||||
int[] inputCodePoints, int codesSize, int commitPoint, boolean isGesture,
|
||||
int[] prevWordCodePointArray, boolean useFullEditDistance, char[] outputChars,
|
||||
int[] outputScores, int[] outputIndices, int[] outputTypes);
|
||||
private static native float calcNormalizedScoreNative(
|
||||
char[] before, int beforeLength, char[] after, int afterLength, int score);
|
||||
private static native int editDistanceNative(
|
||||
char[] before, int beforeLength, char[] after, int afterLength);
|
||||
private static native float calcNormalizedScoreNative(char[] before, char[] after, int score);
|
||||
private static native int editDistanceNative(char[] before, char[] after);
|
||||
|
||||
// TODO: Move native dict into session
|
||||
private final void loadDictionary(String path, long startOffset, long length) {
|
||||
mNativeDict = openNative(path, startOffset, length, TYPED_LETTER_MULTIPLIER,
|
||||
FULL_WORD_SCORE_MULTIPLIER, MAX_WORD_LENGTH, MAX_WORDS, MAX_PREDICTIONS);
|
||||
|
@ -106,10 +128,15 @@ public class BinaryDictionary extends Dictionary {
|
|||
@Override
|
||||
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
|
||||
final CharSequence prevWord, final ProximityInfo proximityInfo) {
|
||||
return getSuggestionsWithSessionId(composer, prevWord, proximityInfo, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
|
||||
final CharSequence prevWord, final ProximityInfo proximityInfo, int sessionId) {
|
||||
if (!isValidDictionary()) return null;
|
||||
Arrays.fill(mInputCodes, WordComposer.NOT_A_CODE);
|
||||
Arrays.fill(mOutputChars, (char) 0);
|
||||
Arrays.fill(mOutputScores, 0);
|
||||
|
||||
Arrays.fill(mInputCodePoints, Constants.NOT_A_CODE);
|
||||
// TODO: toLowerCase in the native code
|
||||
final int[] prevWordCodePointArray = (null == prevWord)
|
||||
? null : StringUtils.toCodePointArray(prevWord.toString());
|
||||
|
@ -119,7 +146,7 @@ public class BinaryDictionary extends Dictionary {
|
|||
if (composerSize <= 1 || !isGesture) {
|
||||
if (composerSize > MAX_WORD_LENGTH - 1) return null;
|
||||
for (int i = 0; i < composerSize; i++) {
|
||||
mInputCodes[i] = composer.getCodeAt(i);
|
||||
mInputCodePoints[i] = composer.getCodeAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,13 +154,13 @@ public class BinaryDictionary extends Dictionary {
|
|||
final int codesSize = isGesture ? ips.getPointerSize() : composerSize;
|
||||
// proximityInfo and/or prevWordForBigrams may not be null.
|
||||
final int tmpCount = getSuggestionsNative(mNativeDict,
|
||||
proximityInfo.getNativeProximityInfo(), ips.getXCoordinates(),
|
||||
ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(),
|
||||
mInputCodes, codesSize, 0 /* commitPoint */, isGesture, prevWordCodePointArray,
|
||||
proximityInfo.getNativeProximityInfo(), getTraverseSession(sessionId).getSession(),
|
||||
ips.getXCoordinates(), ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(),
|
||||
mInputCodePoints, codesSize, 0 /* commitPoint */, isGesture, prevWordCodePointArray,
|
||||
mUseFullEditDistance, mOutputChars, mOutputScores, mSpaceIndices, mOutputTypes);
|
||||
final int count = Math.min(tmpCount, MAX_PREDICTIONS);
|
||||
|
||||
final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>();
|
||||
final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
|
||||
for (int j = 0; j < count; ++j) {
|
||||
if (composerSize > 0 && mOutputScores[j] < 1) break;
|
||||
final int start = j * MAX_WORD_LENGTH;
|
||||
|
@ -142,9 +169,10 @@ public class BinaryDictionary extends Dictionary {
|
|||
++len;
|
||||
}
|
||||
if (len > 0) {
|
||||
final int score = SuggestedWordInfo.KIND_WHITELIST == mOutputTypes[j]
|
||||
? SuggestedWordInfo.MAX_SCORE : mOutputScores[j];
|
||||
suggestions.add(new SuggestedWordInfo(
|
||||
new String(mOutputChars, start, len),
|
||||
mOutputScores[j], SuggestedWordInfo.KIND_CORRECTION, mDictType));
|
||||
new String(mOutputChars, start, len), score, mOutputTypes[j], mDictType));
|
||||
}
|
||||
}
|
||||
return suggestions;
|
||||
|
@ -155,13 +183,11 @@ public class BinaryDictionary extends Dictionary {
|
|||
}
|
||||
|
||||
public static float calcNormalizedScore(String before, String after, int score) {
|
||||
return calcNormalizedScoreNative(before.toCharArray(), before.length(),
|
||||
after.toCharArray(), after.length(), score);
|
||||
return calcNormalizedScoreNative(before.toCharArray(), after.toCharArray(), score);
|
||||
}
|
||||
|
||||
public static int editDistance(String before, String after) {
|
||||
return editDistanceNative(
|
||||
before.toCharArray(), before.length(), after.toCharArray(), after.length());
|
||||
return editDistanceNative(before.toCharArray(), after.toCharArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -172,8 +198,8 @@ public class BinaryDictionary extends Dictionary {
|
|||
@Override
|
||||
public int getFrequency(CharSequence word) {
|
||||
if (word == null) return -1;
|
||||
int[] chars = StringUtils.toCodePointArray(word.toString());
|
||||
return getFrequencyNative(mNativeDict, chars, chars.length);
|
||||
int[] codePoints = StringUtils.toCodePointArray(word.toString());
|
||||
return getFrequencyNative(mNativeDict, codePoints);
|
||||
}
|
||||
|
||||
// TODO: Add a batch process version (isValidBigramMultiple?) to avoid excessive numbers of jni
|
||||
|
@ -186,11 +212,20 @@ public class BinaryDictionary extends Dictionary {
|
|||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
public void close() {
|
||||
synchronized (mDicTraverseSessions) {
|
||||
final int sessionsSize = mDicTraverseSessions.size();
|
||||
for (int index = 0; index < sessionsSize; ++index) {
|
||||
final DicTraverseSession traverseSession = mDicTraverseSessions.valueAt(index);
|
||||
if (traverseSession != null) {
|
||||
traverseSession.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
closeInternal();
|
||||
}
|
||||
|
||||
private void closeInternal() {
|
||||
private synchronized void closeInternal() {
|
||||
if (mNativeDict != 0) {
|
||||
closeNative(mNativeDict);
|
||||
mNativeDict = 0;
|
||||
|
|
|
@ -99,7 +99,7 @@ public class BinaryDictionaryFileDumper {
|
|||
}
|
||||
|
||||
try {
|
||||
final List<WordListInfo> list = new ArrayList<WordListInfo>();
|
||||
final List<WordListInfo> list = CollectionUtils.newArrayList();
|
||||
do {
|
||||
final String wordListId = c.getString(0);
|
||||
final String wordListLocale = c.getString(1);
|
||||
|
@ -267,7 +267,7 @@ public class BinaryDictionaryFileDumper {
|
|||
final ContentResolver resolver = context.getContentResolver();
|
||||
final List<WordListInfo> idList = getWordListWordListInfos(locale, context,
|
||||
hasDefaultWordList);
|
||||
final List<AssetFileAddress> fileAddressList = new ArrayList<AssetFileAddress>();
|
||||
final List<AssetFileAddress> fileAddressList = CollectionUtils.newArrayList();
|
||||
for (WordListInfo id : idList) {
|
||||
final AssetFileAddress afd = cacheWordList(id.mId, id.mLocale, resolver, context);
|
||||
if (null != afd) {
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
package com.android.inputmethod.latin;
|
||||
|
||||
import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
|
@ -23,6 +25,10 @@ import android.content.res.AssetFileDescriptor;
|
|||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
|
@ -51,6 +57,9 @@ class BinaryDictionaryGetter {
|
|||
private static final String MAIN_DICTIONARY_CATEGORY = "main";
|
||||
public static final String ID_CATEGORY_SEPARATOR = ":";
|
||||
|
||||
// The key considered to read the version attribute in a dictionary file.
|
||||
private static String VERSION_KEY = "version";
|
||||
|
||||
// Prevents this from being instantiated
|
||||
private BinaryDictionaryGetter() {}
|
||||
|
||||
|
@ -254,8 +263,7 @@ class BinaryDictionaryGetter {
|
|||
final Context context) {
|
||||
final File[] directoryList = getCachedDirectoryList(context);
|
||||
if (null == directoryList) return EMPTY_FILE_ARRAY;
|
||||
final HashMap<String, FileAndMatchLevel> cacheFiles =
|
||||
new HashMap<String, FileAndMatchLevel>();
|
||||
final HashMap<String, FileAndMatchLevel> cacheFiles = CollectionUtils.newHashMap();
|
||||
for (File directory : directoryList) {
|
||||
if (!directory.isDirectory()) continue;
|
||||
final String dirLocale = getWordListIdFromFileName(directory.getName());
|
||||
|
@ -336,6 +344,54 @@ class BinaryDictionaryGetter {
|
|||
return MAIN_DICTIONARY_CATEGORY.equals(idArray[0]);
|
||||
}
|
||||
|
||||
// ## HACK ## we prevent usage of a dictionary before version 18 for English only. The reason
|
||||
// for this is, since those do not include whitelist entries, the new code with an old version
|
||||
// of the dictionary would lose whitelist functionality.
|
||||
private static boolean hackCanUseDictionaryFile(final Locale locale, final File f) {
|
||||
// Only for English - other languages didn't have a whitelist, hence this
|
||||
// ad-hock ## HACK ##
|
||||
if (!Locale.ENGLISH.getLanguage().equals(locale.getLanguage())) return true;
|
||||
|
||||
FileInputStream inStream = null;
|
||||
try {
|
||||
// Read the version of the file
|
||||
inStream = new FileInputStream(f);
|
||||
final ByteBuffer buffer = inStream.getChannel().map(
|
||||
FileChannel.MapMode.READ_ONLY, 0, f.length());
|
||||
final int magic = buffer.getInt();
|
||||
if (magic != BinaryDictInputOutput.VERSION_2_MAGIC_NUMBER) {
|
||||
return false;
|
||||
}
|
||||
final int formatVersion = buffer.getInt();
|
||||
final int headerSize = buffer.getInt();
|
||||
final HashMap<String, String> options = CollectionUtils.newHashMap();
|
||||
BinaryDictInputOutput.populateOptions(buffer, headerSize, options);
|
||||
|
||||
final String version = options.get(VERSION_KEY);
|
||||
if (null == version) {
|
||||
// No version in the options : the format is unexpected
|
||||
return false;
|
||||
}
|
||||
// Version 18 is the first one to include the whitelist
|
||||
// Obviously this is a big ## HACK ##
|
||||
return Integer.parseInt(version) >= 18;
|
||||
} catch (java.io.FileNotFoundException e) {
|
||||
return false;
|
||||
} catch (java.io.IOException e) {
|
||||
return false;
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
} finally {
|
||||
if (inStream != null) {
|
||||
try {
|
||||
inStream.close();
|
||||
} catch (IOException e) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of file addresses for a given locale, trying relevant methods in order.
|
||||
*
|
||||
|
@ -362,18 +418,19 @@ class BinaryDictionaryGetter {
|
|||
final DictPackSettings dictPackSettings = new DictPackSettings(context);
|
||||
|
||||
boolean foundMainDict = false;
|
||||
final ArrayList<AssetFileAddress> fileList = new ArrayList<AssetFileAddress>();
|
||||
final ArrayList<AssetFileAddress> fileList = CollectionUtils.newArrayList();
|
||||
// cachedWordLists may not be null, see doc for getCachedDictionaryList
|
||||
for (final File f : cachedWordLists) {
|
||||
final String wordListId = getWordListIdFromFileName(f.getName());
|
||||
if (isMainWordListId(wordListId)) {
|
||||
final boolean canUse = f.canRead() && hackCanUseDictionaryFile(locale, f);
|
||||
if (canUse && isMainWordListId(wordListId)) {
|
||||
foundMainDict = true;
|
||||
}
|
||||
if (!dictPackSettings.isWordListActive(wordListId)) continue;
|
||||
if (f.canRead()) {
|
||||
if (canUse) {
|
||||
fileList.add(AssetFileAddress.makeFromFileName(f.getPath()));
|
||||
} else {
|
||||
Log.e(TAG, "Found a cached dictionary file but cannot read it");
|
||||
Log.e(TAG, "Found a cached dictionary file but cannot read or use it");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
// 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";
|
||||
// User history dictionary internal to LatinIME.
|
||||
public static final String TYPE_USER_HISTORY = "history";
|
||||
public static final String TYPE_WHITELIST ="whitelist";
|
||||
protected final String mDictType;
|
||||
|
||||
public Dictionary(final String dictType) {
|
||||
|
@ -62,6 +61,13 @@ public abstract class Dictionary {
|
|||
abstract public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
|
||||
final CharSequence prevWord, final ProximityInfo proximityInfo);
|
||||
|
||||
// The default implementation of this method ignores sessionId.
|
||||
// Subclasses that want to use sessionId need to override this method.
|
||||
public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
|
||||
final CharSequence prevWord, final ProximityInfo proximityInfo, int sessionId) {
|
||||
return getSuggestions(composer, prevWord, proximityInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given word occurs in the dictionary
|
||||
* @param word the word to search for. The search should be case-insensitive.
|
||||
|
|
|
@ -35,22 +35,22 @@ public class DictionaryCollection extends Dictionary {
|
|||
|
||||
public DictionaryCollection(final String dictType) {
|
||||
super(dictType);
|
||||
mDictionaries = new CopyOnWriteArrayList<Dictionary>();
|
||||
mDictionaries = CollectionUtils.newCopyOnWriteArrayList();
|
||||
}
|
||||
|
||||
public DictionaryCollection(final String dictType, Dictionary... dictionaries) {
|
||||
super(dictType);
|
||||
if (null == dictionaries) {
|
||||
mDictionaries = new CopyOnWriteArrayList<Dictionary>();
|
||||
mDictionaries = CollectionUtils.newCopyOnWriteArrayList();
|
||||
} else {
|
||||
mDictionaries = new CopyOnWriteArrayList<Dictionary>(dictionaries);
|
||||
mDictionaries = CollectionUtils.newCopyOnWriteArrayList(dictionaries);
|
||||
mDictionaries.removeAll(Collections.singleton(null));
|
||||
}
|
||||
}
|
||||
|
||||
public DictionaryCollection(final String dictType, Collection<Dictionary> dictionaries) {
|
||||
super(dictType);
|
||||
mDictionaries = new CopyOnWriteArrayList<Dictionary>(dictionaries);
|
||||
mDictionaries = CollectionUtils.newCopyOnWriteArrayList(dictionaries);
|
||||
mDictionaries.removeAll(Collections.singleton(null));
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@ public class DictionaryCollection extends Dictionary {
|
|||
// dictionary and add the rest to it if not null, hence the get(0)
|
||||
ArrayList<SuggestedWordInfo> suggestions = dictionaries.get(0).getSuggestions(composer,
|
||||
prevWord, proximityInfo);
|
||||
if (null == suggestions) suggestions = new ArrayList<SuggestedWordInfo>();
|
||||
if (null == suggestions) suggestions = CollectionUtils.newArrayList();
|
||||
final int length = dictionaries.size();
|
||||
for (int i = 1; i < length; ++ i) {
|
||||
final ArrayList<SuggestedWordInfo> sugg = dictionaries.get(i).getSuggestions(composer,
|
||||
|
|
|
@ -53,7 +53,7 @@ public class DictionaryFactory {
|
|||
createBinaryDictionary(context, locale));
|
||||
}
|
||||
|
||||
final LinkedList<Dictionary> dictList = new LinkedList<Dictionary>();
|
||||
final LinkedList<Dictionary> dictList = CollectionUtils.newLinkedList();
|
||||
final ArrayList<AssetFileAddress> assetFileList =
|
||||
BinaryDictionaryGetter.getDictionaryFiles(locale, context);
|
||||
if (null != assetFileList) {
|
||||
|
|
|
@ -62,7 +62,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
|
|||
* that filename.
|
||||
*/
|
||||
private static final HashMap<String, DictionaryController> sSharedDictionaryControllers =
|
||||
new HashMap<String, DictionaryController>();
|
||||
CollectionUtils.newHashMap();
|
||||
|
||||
/** The application context. */
|
||||
protected final Context mContext;
|
||||
|
@ -159,9 +159,9 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
|
|||
* the native side.
|
||||
*/
|
||||
public void clearFusionDictionary() {
|
||||
final HashMap<String, String> attributes = CollectionUtils.newHashMap();
|
||||
mFusionDictionary = new FusionDictionary(new Node(),
|
||||
new FusionDictionary.DictionaryOptions(new HashMap<String, String>(), false,
|
||||
false));
|
||||
new FusionDictionary.DictionaryOptions(attributes, false, false));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -175,7 +175,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
|
|||
mFusionDictionary.add(word, frequency, null);
|
||||
} else {
|
||||
// TODO: Do this in the subclass, with this class taking an arraylist.
|
||||
final ArrayList<WeightedString> shortcutTargets = new ArrayList<WeightedString>();
|
||||
final ArrayList<WeightedString> shortcutTargets = CollectionUtils.newArrayList();
|
||||
shortcutTargets.add(new WeightedString(shortcutTarget, frequency));
|
||||
mFusionDictionary.add(word, frequency, shortcutTargets);
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ package com.android.inputmethod.latin;
|
|||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.inputmethod.keyboard.KeyDetector;
|
||||
import com.android.inputmethod.keyboard.Keyboard;
|
||||
import com.android.inputmethod.keyboard.ProximityInfo;
|
||||
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
|
||||
|
@ -231,7 +230,7 @@ public class ExpandableDictionary extends Dictionary {
|
|||
childNode.mTerminal = true;
|
||||
if (isShortcutOnly) {
|
||||
if (null == childNode.mShortcutTargets) {
|
||||
childNode.mShortcutTargets = new ArrayList<char[]>();
|
||||
childNode.mShortcutTargets = CollectionUtils.newArrayList();
|
||||
}
|
||||
childNode.mShortcutTargets.add(shortcutTarget.toCharArray());
|
||||
} else {
|
||||
|
@ -251,7 +250,7 @@ public class ExpandableDictionary extends Dictionary {
|
|||
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
|
||||
final CharSequence prevWord, final ProximityInfo proximityInfo) {
|
||||
if (reloadDictionaryIfRequired()) return null;
|
||||
if (composer.size() <= 1) {
|
||||
if (composer.size() > 1) {
|
||||
if (composer.size() >= BinaryDictionary.MAX_WORD_LENGTH) {
|
||||
return null;
|
||||
}
|
||||
|
@ -260,7 +259,7 @@ public class ExpandableDictionary extends Dictionary {
|
|||
return suggestions;
|
||||
} else {
|
||||
if (TextUtils.isEmpty(prevWord)) return null;
|
||||
final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>();
|
||||
final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
|
||||
runBigramReverseLookUp(prevWord, suggestions);
|
||||
return suggestions;
|
||||
}
|
||||
|
@ -279,7 +278,7 @@ public class ExpandableDictionary extends Dictionary {
|
|||
|
||||
protected ArrayList<SuggestedWordInfo> getWordsInner(final WordComposer codes,
|
||||
final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) {
|
||||
final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>();
|
||||
final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
|
||||
mInputLength = codes.size();
|
||||
if (mCodes.length < mInputLength) mCodes = new int[mInputLength][];
|
||||
final InputPointers ips = codes.getInputPointers();
|
||||
|
@ -292,9 +291,9 @@ public class ExpandableDictionary extends Dictionary {
|
|||
mCodes[i] = new int[ProximityInfo.MAX_PROXIMITY_CHARS_SIZE];
|
||||
}
|
||||
final int x = xCoordinates != null && i < xCoordinates.length ?
|
||||
xCoordinates[i] : WordComposer.NOT_A_COORDINATE;
|
||||
xCoordinates[i] : Constants.NOT_A_COORDINATE;
|
||||
final int y = xCoordinates != null && i < yCoordinates.length ?
|
||||
yCoordinates[i] : WordComposer.NOT_A_COORDINATE;
|
||||
yCoordinates[i] : Constants.NOT_A_COORDINATE;
|
||||
proximityInfo.fillArrayWithNearestKeyCodes(x, y, codes.getCodeAt(i), mCodes[i]);
|
||||
}
|
||||
mMaxDepth = mInputLength * 3;
|
||||
|
@ -487,7 +486,7 @@ public class ExpandableDictionary extends Dictionary {
|
|||
for (int j = 0; j < alternativesSize; j++) {
|
||||
final int addedAttenuation = (j > 0 ? 1 : 2);
|
||||
final int currentChar = currentChars[j];
|
||||
if (currentChar == KeyDetector.NOT_A_CODE) {
|
||||
if (currentChar == Constants.NOT_A_CODE) {
|
||||
break;
|
||||
}
|
||||
if (currentChar == lowerC || currentChar == c) {
|
||||
|
@ -551,7 +550,7 @@ public class ExpandableDictionary extends Dictionary {
|
|||
Node secondWord = searchWord(mRoots, word2, 0, null);
|
||||
LinkedList<NextWord> bigrams = firstWord.mNGrams;
|
||||
if (bigrams == null || bigrams.size() == 0) {
|
||||
firstWord.mNGrams = new LinkedList<NextWord>();
|
||||
firstWord.mNGrams = CollectionUtils.newLinkedList();
|
||||
bigrams = firstWord.mNGrams;
|
||||
} else {
|
||||
for (NextWord nw : bigrams) {
|
||||
|
|
|
@ -29,10 +29,12 @@ public class InputAttributes {
|
|||
final public boolean mInputTypeNoAutoCorrect;
|
||||
final public boolean mIsSettingsSuggestionStripOn;
|
||||
final public boolean mApplicationSpecifiedCompletionOn;
|
||||
final private int mInputType;
|
||||
|
||||
public InputAttributes(final EditorInfo editorInfo, final boolean isFullscreenMode) {
|
||||
final int inputType = null != editorInfo ? editorInfo.inputType : 0;
|
||||
final int inputClass = inputType & InputType.TYPE_MASK_CLASS;
|
||||
mInputType = inputType;
|
||||
if (inputClass != InputType.TYPE_CLASS_TEXT) {
|
||||
// If we are not looking at a TYPE_CLASS_TEXT field, the following strange
|
||||
// cases may arise, so we do a couple sanity checks for them. If it's a
|
||||
|
@ -93,6 +95,10 @@ public class InputAttributes {
|
|||
}
|
||||
}
|
||||
|
||||
public boolean isSameInputType(final EditorInfo editorInfo) {
|
||||
return editorInfo.inputType == mInputType;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private void dumpFlags(final int inputType) {
|
||||
Log.i(TAG, "Input class:");
|
||||
|
|
|
@ -93,7 +93,7 @@ public class InputPointers {
|
|||
}
|
||||
mXCoordinates.append(xCoordinates, startPos, length);
|
||||
mYCoordinates.append(yCoordinates, startPos, length);
|
||||
mPointerIds.fill(pointerId, startPos, length);
|
||||
mPointerIds.fill(pointerId, mPointerIds.getLength(), length);
|
||||
mTimes.append(times, startPos, length);
|
||||
}
|
||||
|
||||
|
@ -124,4 +124,10 @@ public class InputPointers {
|
|||
public int[] getTimes() {
|
||||
return mTimes.getPrimitiveArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "size=" + getPointerSize() + " id=" + mPointerIds + " time=" + mTimes
|
||||
+ " x=" + mXCoordinates + " y=" + mYCoordinates;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -361,7 +361,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
mPrefs = prefs;
|
||||
LatinImeLogger.init(this, prefs);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.getInstance().init(this, prefs, mKeyboardSwitcher);
|
||||
ResearchLogger.getInstance().init(this, prefs);
|
||||
}
|
||||
InputMethodManagerCompatWrapper.init(this);
|
||||
SubtypeSwitcher.init(this);
|
||||
|
@ -381,18 +381,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
|
||||
ImfUtils.setAdditionalInputMethodSubtypes(this, mCurrentSettings.getAdditionalSubtypes());
|
||||
|
||||
Utils.GCUtils.getInstance().reset();
|
||||
boolean tryGC = true;
|
||||
// Shouldn't this be removed? I think that from Honeycomb on, the GC is now actually working
|
||||
// as expected and this code is useless.
|
||||
for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
|
||||
try {
|
||||
initSuggest();
|
||||
tryGC = false;
|
||||
} catch (OutOfMemoryError e) {
|
||||
tryGC = Utils.GCUtils.getInstance().tryGCOrWait("InitSuggest", e);
|
||||
}
|
||||
}
|
||||
|
||||
mDisplayOrientation = res.getConfiguration().orientation;
|
||||
|
||||
|
@ -416,7 +405,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
}
|
||||
|
||||
// Has to be package-visible for unit tests
|
||||
/* package */ void loadSettings() {
|
||||
/* package for test */
|
||||
void loadSettings() {
|
||||
// Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged()
|
||||
// is not guaranteed. It may even be called at the same time on a different thread.
|
||||
if (null == mPrefs) mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
|
@ -433,10 +423,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary());
|
||||
}
|
||||
|
||||
// Note that this method is called from a non-UI thread.
|
||||
@Override
|
||||
public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable) {
|
||||
mIsMainDictionaryAvailable = isMainDictionaryAvailable;
|
||||
updateKeyboardViewGestureHandlingModeByMainDictionaryAvailability();
|
||||
final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
|
||||
if (mainKeyboardView != null) {
|
||||
mainKeyboardView.setMainDictionaryAvailability(isMainDictionaryAvailable);
|
||||
}
|
||||
}
|
||||
|
||||
private void initSuggest() {
|
||||
|
@ -517,7 +511,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
|
||||
/* package private */ void resetSuggestMainDict() {
|
||||
final Locale subtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
|
||||
mSuggest.resetMainDict(this, subtypeLocale);
|
||||
mSuggest.resetMainDict(this, subtypeLocale, this /* SuggestInitializationListener */);
|
||||
mIsMainDictionaryAvailable = DictionaryFactory.isDictionaryAvailable(this, subtypeLocale);
|
||||
}
|
||||
|
||||
|
@ -536,7 +530,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration conf) {
|
||||
mSubtypeSwitcher.onConfigurationChanged(conf);
|
||||
// System locale has been changed. Needs to reload keyboard.
|
||||
if (mSubtypeSwitcher.onConfigurationChanged(conf, this)) {
|
||||
loadKeyboard();
|
||||
}
|
||||
// If orientation changed while predicting, commit the change
|
||||
if (mDisplayOrientation != conf.orientation) {
|
||||
mDisplayOrientation = conf.orientation;
|
||||
|
@ -603,6 +600,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
// Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged()
|
||||
// is not guaranteed. It may even be called at the same time on a different thread.
|
||||
mSubtypeSwitcher.updateSubtype(subtype);
|
||||
loadKeyboard();
|
||||
}
|
||||
|
||||
private void onStartInputInternal(EditorInfo editorInfo, boolean restarting) {
|
||||
|
@ -663,11 +661,23 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
// Forward this event to the accessibility utilities, if enabled.
|
||||
final AccessibilityUtils accessUtils = AccessibilityUtils.getInstance();
|
||||
if (accessUtils.isTouchExplorationEnabled()) {
|
||||
accessUtils.onStartInputViewInternal(editorInfo, restarting);
|
||||
accessUtils.onStartInputViewInternal(mainKeyboardView, editorInfo, restarting);
|
||||
}
|
||||
|
||||
if (!restarting) {
|
||||
mSubtypeSwitcher.updateParametersOnStartInputView();
|
||||
final boolean selectionChanged = mLastSelectionStart != editorInfo.initialSelStart
|
||||
|| mLastSelectionEnd != editorInfo.initialSelEnd;
|
||||
final boolean inputTypeChanged = !mCurrentSettings.isSameInputType(editorInfo);
|
||||
final boolean isDifferentTextField = !restarting || inputTypeChanged;
|
||||
if (isDifferentTextField) {
|
||||
final boolean currentSubtypeEnabled = mSubtypeSwitcher
|
||||
.updateParametersOnStartInputViewAndReturnIfCurrentSubtypeEnabled();
|
||||
if (!currentSubtypeEnabled) {
|
||||
// Current subtype is disabled. Needs to update subtype and keyboard.
|
||||
final InputMethodSubtype newSubtype = ImfUtils.getCurrentInputMethodSubtype(
|
||||
this, mSubtypeSwitcher.getNoLanguageSubtype());
|
||||
mSubtypeSwitcher.updateSubtype(newSubtype);
|
||||
loadKeyboard();
|
||||
}
|
||||
}
|
||||
|
||||
// The EditorInfo might have a flag that affects fullscreen mode.
|
||||
|
@ -675,9 +685,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
updateFullscreenMode();
|
||||
mApplicationSpecifiedCompletions = null;
|
||||
|
||||
final boolean selectionChanged = mLastSelectionStart != editorInfo.initialSelStart
|
||||
|| mLastSelectionEnd != editorInfo.initialSelEnd;
|
||||
if (!restarting || selectionChanged) {
|
||||
if (isDifferentTextField || selectionChanged) {
|
||||
// If the selection changed, we reset the input state. Essentially, we come here with
|
||||
// restarting == true when the app called setText() or similar. We should reset the
|
||||
// state if the app set the text to something else, but keep it if it set a suggestion
|
||||
|
@ -692,7 +700,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
}
|
||||
}
|
||||
|
||||
if (!restarting) {
|
||||
if (isDifferentTextField) {
|
||||
mainKeyboardView.closing();
|
||||
loadSettings();
|
||||
|
||||
|
@ -701,7 +709,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
}
|
||||
|
||||
switcher.loadKeyboard(editorInfo, mCurrentSettings);
|
||||
updateKeyboardViewGestureHandlingModeByMainDictionaryAvailability();
|
||||
}
|
||||
setSuggestionStripShownInternal(
|
||||
isSuggestionsStripVisible(), /* needsInputViewShown */ false);
|
||||
|
@ -719,8 +726,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
mHandler.cancelUpdateSuggestionStrip();
|
||||
mHandler.cancelDoubleSpacesTimer();
|
||||
|
||||
mainKeyboardView.setMainDictionaryAvailability(mIsMainDictionaryAvailable);
|
||||
mainKeyboardView.setKeyPreviewPopupEnabled(mCurrentSettings.mKeyPreviewPopupOn,
|
||||
mCurrentSettings.mKeyPreviewPopupDismissDelay);
|
||||
mainKeyboardView.setGestureHandlingEnabledByUser(mCurrentSettings.mGestureInputEnabled);
|
||||
mainKeyboardView.setGesturePreviewMode(mCurrentSettings.mGesturePreviewTrailEnabled,
|
||||
mCurrentSettings.mGestureFloatingPreviewTextEnabled);
|
||||
|
||||
if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
|
||||
}
|
||||
|
@ -898,13 +909,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
}
|
||||
}
|
||||
}
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_onDisplayCompletions(applicationSpecifiedCompletions);
|
||||
}
|
||||
if (!mCurrentSettings.isApplicationSpecifiedCompletionsOn()) return;
|
||||
mApplicationSpecifiedCompletions = applicationSpecifiedCompletions;
|
||||
if (applicationSpecifiedCompletions == null) {
|
||||
clearSuggestionStrip();
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_onDisplayCompletions(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -926,6 +937,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
// this case? This says to keep whatever the user typed.
|
||||
mWordComposer.setAutoCorrection(mWordComposer.getTypedWord());
|
||||
setSuggestionStripShown(true);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_onDisplayCompletions(applicationSpecifiedCompletions);
|
||||
}
|
||||
}
|
||||
|
||||
private void setSuggestionStripShownInternal(boolean shown, boolean needsInputViewShown) {
|
||||
|
@ -1046,9 +1060,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
final CharSequence typedWord = mWordComposer.getTypedWord();
|
||||
if (typedWord.length() > 0) {
|
||||
mConnection.commitText(typedWord, 1);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_commitText(typedWord);
|
||||
}
|
||||
final CharSequence prevWord = addToUserHistoryDictionary(typedWord);
|
||||
mLastComposedWord = mWordComposer.commitWord(
|
||||
LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, typedWord.toString(),
|
||||
|
@ -1084,18 +1095,27 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
return mConnection.getCursorCapsMode(inputType);
|
||||
}
|
||||
|
||||
// Factor in auto-caps and manual caps and compute the current caps mode.
|
||||
private int getActualCapsMode() {
|
||||
final int manual = mKeyboardSwitcher.getManualCapsMode();
|
||||
if (manual != WordComposer.CAPS_MODE_OFF) return manual;
|
||||
final int auto = getCurrentAutoCapsState();
|
||||
if (0 != (auto & TextUtils.CAP_MODE_CHARACTERS)) {
|
||||
return WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED;
|
||||
}
|
||||
if (0 != auto) return WordComposer.CAPS_MODE_AUTO_SHIFTED;
|
||||
return WordComposer.CAPS_MODE_OFF;
|
||||
}
|
||||
|
||||
private void swapSwapperAndSpace() {
|
||||
CharSequence lastTwo = mConnection.getTextBeforeCursor(2, 0);
|
||||
// It is guaranteed lastTwo.charAt(1) is a swapper - else this method is not called.
|
||||
if (lastTwo != null && lastTwo.length() == 2
|
||||
&& lastTwo.charAt(0) == Keyboard.CODE_SPACE) {
|
||||
mConnection.deleteSurroundingText(2, 0);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_deleteSurroundingText(2);
|
||||
}
|
||||
mConnection.commitText(lastTwo.charAt(1) + " ", 1);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_swapSwapperAndSpaceWhileInBatchEdit();
|
||||
ResearchLogger.latinIME_swapSwapperAndSpace();
|
||||
}
|
||||
mKeyboardSwitcher.updateShiftState();
|
||||
}
|
||||
|
@ -1112,9 +1132,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
mHandler.cancelDoubleSpacesTimer();
|
||||
mConnection.deleteSurroundingText(2, 0);
|
||||
mConnection.commitText(". ", 1);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_doubleSpaceAutoPeriod();
|
||||
}
|
||||
mKeyboardSwitcher.updateShiftState();
|
||||
return true;
|
||||
}
|
||||
|
@ -1178,9 +1195,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
|
||||
private void performEditorAction(int actionId) {
|
||||
mConnection.performEditorAction(actionId);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_performEditorAction(actionId);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleLanguageSwitchKey() {
|
||||
|
@ -1217,6 +1231,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
// For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}.
|
||||
if (code >= '0' && code <= '9') {
|
||||
super.sendKeyChar((char)code);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_sendKeyCodePoint(code);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1233,9 +1250,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
final String text = new String(new int[] { code }, 0, 1);
|
||||
mConnection.commitText(text, text.length());
|
||||
}
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_sendKeyCodePoint(code);
|
||||
}
|
||||
}
|
||||
|
||||
// Implementation of {@link KeyboardActionListener}.
|
||||
|
@ -1247,11 +1261,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
}
|
||||
mLastKeyTime = when;
|
||||
mConnection.beginBatchEdit();
|
||||
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_onCodeInput(primaryCode, x, y);
|
||||
}
|
||||
|
||||
final KeyboardSwitcher switcher = mKeyboardSwitcher;
|
||||
// The space state depends only on the last character pressed and its own previous
|
||||
// state. Here, we revert the space state to neutral if the key is actually modifying
|
||||
|
@ -1284,7 +1293,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
onSettingsKeyPressed();
|
||||
break;
|
||||
case Keyboard.CODE_SHORTCUT:
|
||||
mSubtypeSwitcher.switchToShortcutIME();
|
||||
mSubtypeSwitcher.switchToShortcutIME(this);
|
||||
break;
|
||||
case Keyboard.CODE_ACTION_ENTER:
|
||||
performEditorAction(getActionId(switcher.getKeyboard()));
|
||||
|
@ -1317,8 +1326,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
keyX = x;
|
||||
keyY = y;
|
||||
} else {
|
||||
keyX = NOT_A_TOUCH_COORDINATE;
|
||||
keyY = NOT_A_TOUCH_COORDINATE;
|
||||
keyX = Constants.NOT_A_COORDINATE;
|
||||
keyY = Constants.NOT_A_COORDINATE;
|
||||
}
|
||||
handleCharacter(primaryCode, keyX, keyY, spaceState);
|
||||
}
|
||||
|
@ -1333,22 +1342,24 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
mLastComposedWord.deactivate();
|
||||
mEnteredText = null;
|
||||
mConnection.endBatchEdit();
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_onCodeInput(primaryCode, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
// Called from PointerTracker through the KeyboardActionListener interface
|
||||
@Override
|
||||
public void onTextInput(CharSequence rawText) {
|
||||
mConnection.beginBatchEdit();
|
||||
commitTyped(LastComposedWord.NOT_A_SEPARATOR);
|
||||
if (mWordComposer.isComposingWord()) {
|
||||
commitCurrentAutoCorrection(LastComposedWord.NOT_A_SEPARATOR);
|
||||
}
|
||||
mHandler.postUpdateSuggestionStrip();
|
||||
final CharSequence text = specificTldProcessingOnTextInput(rawText);
|
||||
if (SPACE_STATE_PHANTOM == mSpaceState) {
|
||||
sendKeyCodePoint(Keyboard.CODE_SPACE);
|
||||
}
|
||||
mConnection.commitText(text, 1);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_commitText(text);
|
||||
}
|
||||
mConnection.endBatchEdit();
|
||||
mKeyboardSwitcher.updateShiftState();
|
||||
mKeyboardSwitcher.onCodeInput(Keyboard.CODE_OUTPUT_TEXT);
|
||||
|
@ -1361,15 +1372,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
public void onStartBatchInput() {
|
||||
mConnection.beginBatchEdit();
|
||||
if (mWordComposer.isComposingWord()) {
|
||||
commitTyped(LastComposedWord.NOT_A_SEPARATOR);
|
||||
commitCurrentAutoCorrection(LastComposedWord.NOT_A_SEPARATOR);
|
||||
mExpectingUpdateSelection = true;
|
||||
// TODO: Can we remove this?
|
||||
mSpaceState = SPACE_STATE_PHANTOM;
|
||||
}
|
||||
mConnection.endBatchEdit();
|
||||
// TODO: Should handle TextUtils.CAP_MODE_CHARACTER.
|
||||
mWordComposer.setAutoCapitalized(
|
||||
getCurrentAutoCapsState() != Constants.TextUtils.CAP_MODE_OFF);
|
||||
mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1447,9 +1457,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
// like the smiley key or the .com key.
|
||||
final int length = mEnteredText.length();
|
||||
mConnection.deleteSurroundingText(length, 0);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_deleteSurroundingText(length);
|
||||
}
|
||||
// If we have mEnteredText, then we know that mHasUncommittedTypedChars == false.
|
||||
// In addition we know that spaceState is false, and that we should not be
|
||||
// reverting any autocorrect at this point. So we can safely return.
|
||||
|
@ -1469,9 +1476,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
mHandler.postUpdateSuggestionStrip();
|
||||
} else {
|
||||
mConnection.deleteSurroundingText(1, 0);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_deleteSurroundingText(1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (mLastComposedWord.canRevertCommit()) {
|
||||
|
@ -1500,9 +1504,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
final int lengthToDelete = mLastSelectionEnd - mLastSelectionStart;
|
||||
mConnection.setSelection(mLastSelectionEnd, mLastSelectionEnd);
|
||||
mConnection.deleteSurroundingText(lengthToDelete, 0);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_deleteSurroundingText(lengthToDelete);
|
||||
}
|
||||
} else {
|
||||
// There is no selection, just delete one character.
|
||||
if (NOT_A_CURSOR_POSITION == mLastSelectionEnd) {
|
||||
|
@ -1521,14 +1522,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
} else {
|
||||
mConnection.deleteSurroundingText(1, 0);
|
||||
}
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_deleteSurroundingText(1);
|
||||
}
|
||||
if (mDeleteCount > DELETE_ACCELERATE_AT) {
|
||||
mConnection.deleteSurroundingText(1, 0);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_deleteSurroundingText(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mCurrentSettings.isSuggestionsRequested(mDisplayOrientation)) {
|
||||
|
@ -1604,13 +1599,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
mWordComposer.add(primaryCode, keyX, keyY);
|
||||
// If it's the first letter, make note of auto-caps state
|
||||
if (mWordComposer.size() == 1) {
|
||||
mWordComposer.setAutoCapitalized(
|
||||
getCurrentAutoCapsState() != Constants.TextUtils.CAP_MODE_OFF);
|
||||
mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode());
|
||||
}
|
||||
mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
|
||||
} else {
|
||||
final boolean swapWeakSpace = maybeStripSpace(primaryCode,
|
||||
spaceState, KeyboardActionListener.SUGGESTION_STRIP_COORDINATE == x);
|
||||
spaceState, Constants.SUGGESTION_STRIP_COORDINATE == x);
|
||||
|
||||
sendKeyCodePoint(primaryCode);
|
||||
|
||||
|
@ -1640,7 +1634,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
}
|
||||
|
||||
final boolean swapWeakSpace = maybeStripSpace(primaryCode, spaceState,
|
||||
KeyboardActionListener.SUGGESTION_STRIP_COORDINATE == x);
|
||||
Constants.SUGGESTION_STRIP_COORDINATE == x);
|
||||
|
||||
if (SPACE_STATE_PHANTOM == spaceState &&
|
||||
mCurrentSettings.isPhantomSpacePromotingSymbol(primaryCode)) {
|
||||
|
@ -1687,6 +1681,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
|
||||
Utils.Stats.onSeparator((char)primaryCode, x, y);
|
||||
|
||||
mHandler.postUpdateShiftState();
|
||||
return didAutoCorrect;
|
||||
}
|
||||
|
||||
|
@ -1707,7 +1702,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
|
||||
// TODO: make this private
|
||||
// Outside LatinIME, only used by the test suite.
|
||||
/* package for tests */ boolean isShowingPunctuationList() {
|
||||
/* package for tests */
|
||||
boolean isShowingPunctuationList() {
|
||||
if (mSuggestionStripView == null) return false;
|
||||
return mCurrentSettings.mSuggestPuncList == mSuggestionStripView.getSuggestions();
|
||||
}
|
||||
|
@ -1853,10 +1849,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
+ "is empty? Impossible! I must commit suicide.");
|
||||
}
|
||||
Utils.Stats.onAutoCorrection(typedWord, autoCorrection.toString(), separatorCodePoint);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_commitCurrentAutoCorrection(typedWord,
|
||||
autoCorrection.toString());
|
||||
}
|
||||
mExpectingUpdateSelection = true;
|
||||
commitChosenWord(autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD,
|
||||
separatorCodePoint);
|
||||
|
@ -1873,8 +1865,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
// Called from {@link SuggestionStripView} through the {@link SuggestionStripView#Listener}
|
||||
// interface
|
||||
@Override
|
||||
public void pickSuggestionManually(final int index, final CharSequence suggestion,
|
||||
final int x, final int y) {
|
||||
public void pickSuggestionManually(final int index, final CharSequence suggestion) {
|
||||
final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions();
|
||||
// If this is a punctuation picked from the suggestion strip, pass it to onCodeInput
|
||||
if (suggestion.length() == 1 && isShowingPunctuationList()) {
|
||||
|
@ -1882,13 +1873,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
// So, LatinImeLogger logs "" as a user's input.
|
||||
LatinImeLogger.logOnManualSuggestion("", suggestion.toString(), index, suggestedWords);
|
||||
// Rely on onCodeInput to do the complicated swapping/stripping logic consistently.
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_punctuationSuggestion(index, suggestion, x, y);
|
||||
}
|
||||
final int primaryCode = suggestion.charAt(0);
|
||||
onCodeInput(primaryCode,
|
||||
KeyboardActionListener.SUGGESTION_STRIP_COORDINATE,
|
||||
KeyboardActionListener.SUGGESTION_STRIP_COORDINATE);
|
||||
Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_punctuationSuggestion(index, suggestion);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1915,10 +1905,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
final CompletionInfo completionInfo = mApplicationSpecifiedCompletions[index];
|
||||
mConnection.commitCompletion(completionInfo);
|
||||
mConnection.endBatchEdit();
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_pickApplicationSpecifiedCompletion(index,
|
||||
completionInfo.getText(), x, y);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1927,12 +1913,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
final String replacedWord = mWordComposer.getTypedWord().toString();
|
||||
LatinImeLogger.logOnManualSuggestion(replacedWord,
|
||||
suggestion.toString(), index, suggestedWords);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion, x, y);
|
||||
}
|
||||
mExpectingUpdateSelection = true;
|
||||
commitChosenWord(suggestion, LastComposedWord.COMMIT_TYPE_MANUAL_PICK,
|
||||
LastComposedWord.NOT_A_SEPARATOR);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion);
|
||||
}
|
||||
mConnection.endBatchEdit();
|
||||
// Don't allow cancellation of manual pick
|
||||
mLastComposedWord.deactivate();
|
||||
|
@ -1948,8 +1934,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
// If the suggestion is not in the dictionary, the hint should be shown.
|
||||
&& !AutoCorrection.isValidWord(mSuggest.getUnigramDictionaries(), suggestion, true);
|
||||
|
||||
Utils.Stats.onSeparator((char)Keyboard.CODE_SPACE, WordComposer.NOT_A_COORDINATE,
|
||||
WordComposer.NOT_A_COORDINATE);
|
||||
Utils.Stats.onSeparator((char)Keyboard.CODE_SPACE,
|
||||
Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
|
||||
if (showingAddToDictionaryHint && mIsUserDictionaryAvailable) {
|
||||
mSuggestionStripView.showAddToDictionaryHint(
|
||||
suggestion, mCurrentSettings.mHintToSaveText);
|
||||
|
@ -1967,9 +1953,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions();
|
||||
mConnection.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan(
|
||||
this, chosenWord, suggestedWords, mIsMainDictionaryAvailable), 1);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_commitText(chosenWord);
|
||||
}
|
||||
// Add the word to the user history dictionary
|
||||
final CharSequence prevWord = addToUserHistoryDictionary(chosenWord);
|
||||
// TODO: figure out here if this is an auto-correct or if the best word is actually
|
||||
|
@ -1992,6 +1975,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
|
||||
private CharSequence addToUserHistoryDictionary(final CharSequence suggestion) {
|
||||
if (TextUtils.isEmpty(suggestion)) return null;
|
||||
if (mSuggest == null) return null;
|
||||
|
||||
// If correction is not enabled, we don't add words to the user history dictionary.
|
||||
// That's to avoid unintended additions in some sensitive fields, or fields that
|
||||
|
@ -2003,7 +1987,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
final CharSequence prevWord
|
||||
= mConnection.getNthPreviousWord(mCurrentSettings.mWordSeparators, 2);
|
||||
final String secondWord;
|
||||
if (mWordComposer.isAutoCapitalized() && !mWordComposer.isMostlyCaps()) {
|
||||
if (mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps()) {
|
||||
secondWord = suggestion.toString().toLowerCase(
|
||||
mSubtypeSwitcher.getCurrentSubtypeLocale());
|
||||
} else {
|
||||
|
@ -2036,9 +2020,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
mWordComposer.setComposingWord(word, mKeyboardSwitcher.getKeyboard());
|
||||
final int length = word.length();
|
||||
mConnection.deleteSurroundingText(length, 0);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_deleteSurroundingText(length);
|
||||
}
|
||||
mConnection.setComposingText(word, 1);
|
||||
mHandler.postUpdateSuggestionStrip();
|
||||
}
|
||||
|
@ -2066,9 +2047,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
}
|
||||
}
|
||||
mConnection.deleteSurroundingText(deleteLength, 0);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_deleteSurroundingText(deleteLength);
|
||||
}
|
||||
if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) {
|
||||
mUserHistoryDictionary.cancelAddingUserHistory(
|
||||
previousWord.toString(), committedWord.toString());
|
||||
|
@ -2076,8 +2054,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
mConnection.commitText(originallyTypedWord, 1);
|
||||
// Re-insert the separator
|
||||
sendKeyCodePoint(mLastComposedWord.mSeparatorCode);
|
||||
Utils.Stats.onSeparator(mLastComposedWord.mSeparatorCode, WordComposer.NOT_A_COORDINATE,
|
||||
WordComposer.NOT_A_COORDINATE);
|
||||
Utils.Stats.onSeparator(mLastComposedWord.mSeparatorCode,
|
||||
Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_revertCommit(originallyTypedWord);
|
||||
}
|
||||
|
@ -2093,9 +2071,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
return mCurrentSettings.isWordSeparator(code);
|
||||
}
|
||||
|
||||
// Notify that language or mode have been changed and toggleLanguage will update KeyboardID
|
||||
// according to new language or mode. Called from SubtypeSwitcher.
|
||||
public void onRefreshKeyboard() {
|
||||
// TODO: Make this private
|
||||
// Outside LatinIME, only used by the {@link InputTestsBase} test suite.
|
||||
/* package for test */
|
||||
void loadKeyboard() {
|
||||
// When the device locale is changed in SetupWizard etc., this method may get called via
|
||||
// onConfigurationChanged before SoftInputWindow is shown.
|
||||
initSuggest();
|
||||
|
@ -2103,7 +2082,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
if (mKeyboardSwitcher.getMainKeyboardView() != null) {
|
||||
// Reload keyboard because the current language has been changed.
|
||||
mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mCurrentSettings);
|
||||
updateKeyboardViewGestureHandlingModeByMainDictionaryAvailability();
|
||||
}
|
||||
// Since we just changed languages, we should re-evaluate suggestions with whatever word
|
||||
// we are currently composing. If we are not composing anything, we may want to display
|
||||
|
@ -2111,17 +2089,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
mHandler.postUpdateSuggestionStrip();
|
||||
}
|
||||
|
||||
private void updateKeyboardViewGestureHandlingModeByMainDictionaryAvailability() {
|
||||
final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
|
||||
if (mainKeyboardView != null) {
|
||||
final boolean shouldHandleGesture = mCurrentSettings.mGestureInputEnabled
|
||||
&& mIsMainDictionaryAvailable;
|
||||
mainKeyboardView.setGestureHandlingMode(shouldHandleGesture,
|
||||
mCurrentSettings.mGesturePreviewTrailEnabled,
|
||||
mCurrentSettings.mGestureFloatingPreviewTextEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Remove this method from {@link LatinIME} and move {@link FeedbackManager} to
|
||||
// {@link KeyboardSwitcher}. Called from KeyboardSwitcher
|
||||
public void hapticAndAudioFeedback(final int primaryCode) {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@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() {
|
||||
if (++mNestLevel == 1) {
|
||||
mIC = mParent.getCurrentInputConnection();
|
||||
if (null != mIC) mIC.beginBatchEdit();
|
||||
if (null != mIC) {
|
||||
mIC.beginBatchEdit();
|
||||
}
|
||||
} else {
|
||||
if (DBG) {
|
||||
throw new RuntimeException("Nest level too deep");
|
||||
|
@ -66,7 +68,9 @@ public class RichInputConnection {
|
|||
}
|
||||
public void endBatchEdit() {
|
||||
if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead
|
||||
if (--mNestLevel == 0 && null != mIC) mIC.endBatchEdit();
|
||||
if (--mNestLevel == 0 && null != mIC) {
|
||||
mIC.endBatchEdit();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkBatchEdit() {
|
||||
|
@ -79,12 +83,22 @@ public class RichInputConnection {
|
|||
|
||||
public void finishComposingText() {
|
||||
checkBatchEdit();
|
||||
if (null != mIC) mIC.finishComposingText();
|
||||
if (null != mIC) {
|
||||
mIC.finishComposingText();
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.richInputConnection_finishComposingText();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void commitText(final CharSequence text, final int i) {
|
||||
checkBatchEdit();
|
||||
if (null != mIC) mIC.commitText(text, i);
|
||||
if (null != mIC) {
|
||||
mIC.commitText(text, i);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.richInputConnection_commitText(text, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getCursorCapsMode(final int inputType) {
|
||||
|
@ -107,37 +121,72 @@ public class RichInputConnection {
|
|||
|
||||
public void deleteSurroundingText(final int i, final int j) {
|
||||
checkBatchEdit();
|
||||
if (null != mIC) mIC.deleteSurroundingText(i, j);
|
||||
if (null != mIC) {
|
||||
mIC.deleteSurroundingText(i, j);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.richInputConnection_deleteSurroundingText(i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void performEditorAction(final int actionId) {
|
||||
mIC = mParent.getCurrentInputConnection();
|
||||
if (null != mIC) mIC.performEditorAction(actionId);
|
||||
if (null != mIC) {
|
||||
mIC.performEditorAction(actionId);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.richInputConnection_performEditorAction(actionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void sendKeyEvent(final KeyEvent keyEvent) {
|
||||
checkBatchEdit();
|
||||
if (null != mIC) mIC.sendKeyEvent(keyEvent);
|
||||
if (null != mIC) {
|
||||
mIC.sendKeyEvent(keyEvent);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.richInputConnection_sendKeyEvent(keyEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setComposingText(final CharSequence text, final int i) {
|
||||
checkBatchEdit();
|
||||
if (null != mIC) mIC.setComposingText(text, i);
|
||||
if (null != mIC) {
|
||||
mIC.setComposingText(text, i);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.richInputConnection_setComposingText(text, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setSelection(final int from, final int to) {
|
||||
checkBatchEdit();
|
||||
if (null != mIC) mIC.setSelection(from, to);
|
||||
if (null != mIC) {
|
||||
mIC.setSelection(from, to);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.richInputConnection_setSelection(from, to);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void commitCorrection(final CorrectionInfo correctionInfo) {
|
||||
checkBatchEdit();
|
||||
if (null != mIC) mIC.commitCorrection(correctionInfo);
|
||||
if (null != mIC) {
|
||||
mIC.commitCorrection(correctionInfo);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.richInputConnection_commitCorrection(correctionInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void commitCompletion(final CompletionInfo completionInfo) {
|
||||
checkBatchEdit();
|
||||
if (null != mIC) mIC.commitCompletion(completionInfo);
|
||||
if (null != mIC) {
|
||||
mIC.commitCompletion(completionInfo);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.richInputConnection_commitCompletion(completionInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public CharSequence getNthPreviousWord(final String sentenceSeperators, final int n) {
|
||||
|
@ -315,9 +364,6 @@ public class RichInputConnection {
|
|||
if (lastOne != null && lastOne.length() == 1
|
||||
&& lastOne.charAt(0) == Keyboard.CODE_SPACE) {
|
||||
deleteSurroundingText(1, 0);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_deleteSurroundingText(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -382,13 +428,7 @@ public class RichInputConnection {
|
|||
return false;
|
||||
}
|
||||
deleteSurroundingText(2, 0);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_deleteSurroundingText(2);
|
||||
}
|
||||
commitText(" ", 1);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_revertDoubleSpaceWhileInBatchEdit();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -409,13 +449,7 @@ public class RichInputConnection {
|
|||
return false;
|
||||
}
|
||||
deleteSurroundingText(2, 0);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_deleteSurroundingText(2);
|
||||
}
|
||||
commitText(" " + textBeforeCursor.subSequence(0, 1), 1);
|
||||
if (ProductionFlag.IS_EXPERIMENTAL) {
|
||||
ResearchLogger.latinIME_revertSwapPunctuation();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -185,7 +185,7 @@ public class SettingsValues {
|
|||
|
||||
// Helper functions to create member values.
|
||||
private static SuggestedWords createSuggestPuncList(final String[] puncs) {
|
||||
final ArrayList<SuggestedWordInfo> puncList = new ArrayList<SuggestedWordInfo>();
|
||||
final ArrayList<SuggestedWordInfo> puncList = CollectionUtils.newArrayList();
|
||||
if (puncs != null) {
|
||||
for (final String puncSpec : puncs) {
|
||||
puncList.add(new SuggestedWordInfo(KeySpecParser.getLabel(puncSpec),
|
||||
|
@ -417,6 +417,10 @@ public class SettingsValues {
|
|||
prefs.edit().putString(Settings.PREF_LAST_USER_DICTIONARY_WRITE_TIME, newStr).apply();
|
||||
}
|
||||
|
||||
public boolean isSameInputType(final EditorInfo editorInfo) {
|
||||
return mInputAttributes.isSameInputType(editorInfo);
|
||||
}
|
||||
|
||||
// For debug.
|
||||
public String getInputAttributesDebugString() {
|
||||
return mInputAttributes.toString();
|
||||
|
|
|
@ -53,7 +53,7 @@ public class StringUtils {
|
|||
if (TextUtils.isEmpty(csv)) return "";
|
||||
final String[] elements = csv.split(",");
|
||||
if (!containsInArray(key, elements)) return csv;
|
||||
final ArrayList<String> result = new ArrayList<String>(elements.length - 1);
|
||||
final ArrayList<String> result = CollectionUtils.newArrayList(elements.length - 1);
|
||||
for (final String element : elements) {
|
||||
if (!key.equals(element)) result.add(element);
|
||||
}
|
||||
|
|
|
@ -45,13 +45,13 @@ public class SubtypeLocale {
|
|||
private static String[] sPredefinedKeyboardLayoutSet;
|
||||
// Keyboard layout to its display name map.
|
||||
private static final HashMap<String, String> sKeyboardLayoutToDisplayNameMap =
|
||||
new HashMap<String, String>();
|
||||
CollectionUtils.newHashMap();
|
||||
// Keyboard layout to subtype name resource id map.
|
||||
private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap =
|
||||
new HashMap<String, Integer>();
|
||||
CollectionUtils.newHashMap();
|
||||
// Exceptional locale to subtype name resource id map.
|
||||
private static final HashMap<String, Integer> sExceptionalLocaleToWithLayoutNameIdsMap =
|
||||
new HashMap<String, Integer>();
|
||||
CollectionUtils.newHashMap();
|
||||
private static final String SUBTYPE_NAME_RESOURCE_GENERIC_PREFIX =
|
||||
"string/subtype_generic_";
|
||||
private static final String SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX =
|
||||
|
@ -60,11 +60,11 @@ public class SubtypeLocale {
|
|||
"string/subtype_no_language_";
|
||||
// Exceptional locales to display name map.
|
||||
private static final HashMap<String, String> sExceptionalDisplayNamesMap =
|
||||
new HashMap<String, String>();
|
||||
CollectionUtils.newHashMap();
|
||||
// Keyboard layout set name for the subtypes that don't have a keyboardLayoutSet extra value.
|
||||
// This is for compatibility to keep the same subtype ids as pre-JellyBean.
|
||||
private static final HashMap<String,String> sLocaleAndExtraValueToKeyboardLayoutSetMap =
|
||||
new HashMap<String,String>();
|
||||
CollectionUtils.newHashMap();
|
||||
|
||||
private SubtypeLocale() {
|
||||
// Intentional empty constructor for utility class.
|
||||
|
|
|
@ -22,6 +22,7 @@ import android.content.Context;
|
|||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.inputmethodservice.InputMethodService;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.os.AsyncTask;
|
||||
|
@ -42,7 +43,6 @@ public class SubtypeSwitcher {
|
|||
private static final String TAG = SubtypeSwitcher.class.getSimpleName();
|
||||
|
||||
private static final SubtypeSwitcher sInstance = new SubtypeSwitcher();
|
||||
private /* final */ LatinIME mService;
|
||||
private /* final */ InputMethodManager mImm;
|
||||
private /* final */ Resources mResources;
|
||||
private /* final */ ConnectivityManager mConnectivityManager;
|
||||
|
@ -68,11 +68,11 @@ public class SubtypeSwitcher {
|
|||
return mEnabledSubtypeCount >= 2 || !mIsSystemLanguageSameAsInputLanguage;
|
||||
}
|
||||
|
||||
public void updateEnabledSubtypeCount(int count) {
|
||||
public void updateEnabledSubtypeCount(final int count) {
|
||||
mEnabledSubtypeCount = count;
|
||||
}
|
||||
|
||||
public void updateIsSystemLanguageSameAsInputLanguage(boolean isSame) {
|
||||
public void updateIsSystemLanguageSameAsInputLanguage(final boolean isSame) {
|
||||
mIsSystemLanguageSameAsInputLanguage = isSame;
|
||||
}
|
||||
}
|
||||
|
@ -81,18 +81,17 @@ public class SubtypeSwitcher {
|
|||
return sInstance;
|
||||
}
|
||||
|
||||
public static void init(LatinIME service) {
|
||||
SubtypeLocale.init(service);
|
||||
sInstance.initialize(service);
|
||||
sInstance.updateAllParameters();
|
||||
public static void init(final Context context) {
|
||||
SubtypeLocale.init(context);
|
||||
sInstance.initialize(context);
|
||||
sInstance.updateAllParameters(context);
|
||||
}
|
||||
|
||||
private SubtypeSwitcher() {
|
||||
// Intentional empty constructor for singleton.
|
||||
}
|
||||
|
||||
private void initialize(LatinIME service) {
|
||||
mService = service;
|
||||
private void initialize(final Context service) {
|
||||
mResources = service.getResources();
|
||||
mImm = ImfUtils.getInputMethodManager(service);
|
||||
mConnectivityManager = (ConnectivityManager) service.getSystemService(
|
||||
|
@ -111,39 +110,46 @@ public class SubtypeSwitcher {
|
|||
|
||||
// Update all parameters stored in SubtypeSwitcher.
|
||||
// Only configuration changed event is allowed to call this because this is heavy.
|
||||
private void updateAllParameters() {
|
||||
private void updateAllParameters(final Context context) {
|
||||
mCurrentSystemLocale = mResources.getConfiguration().locale;
|
||||
updateSubtype(ImfUtils.getCurrentInputMethodSubtype(mService, mNoLanguageSubtype));
|
||||
updateParametersOnStartInputView();
|
||||
updateSubtype(ImfUtils.getCurrentInputMethodSubtype(context, mNoLanguageSubtype));
|
||||
updateParametersOnStartInputViewAndReturnIfCurrentSubtypeEnabled();
|
||||
}
|
||||
|
||||
// Update parameters which are changed outside LatinIME. This parameters affect UI so they
|
||||
// should be updated every time onStartInputview.
|
||||
public void updateParametersOnStartInputView() {
|
||||
updateEnabledSubtypes();
|
||||
/**
|
||||
* Update parameters which are changed outside LatinIME. This parameters affect UI so they
|
||||
* should be updated every time onStartInputView.
|
||||
*
|
||||
* @return true if the current subtype is enabled.
|
||||
*/
|
||||
public boolean updateParametersOnStartInputViewAndReturnIfCurrentSubtypeEnabled() {
|
||||
final boolean currentSubtypeEnabled =
|
||||
updateEnabledSubtypesAndReturnIfEnabled(mCurrentSubtype);
|
||||
updateShortcutIME();
|
||||
return currentSubtypeEnabled;
|
||||
}
|
||||
|
||||
// Reload enabledSubtypes from the framework.
|
||||
private void updateEnabledSubtypes() {
|
||||
final InputMethodSubtype currentSubtype = mCurrentSubtype;
|
||||
boolean foundCurrentSubtypeBecameDisabled = true;
|
||||
/**
|
||||
* Update enabled subtypes from the framework.
|
||||
*
|
||||
* @param subtype the subtype to be checked
|
||||
* @return true if the {@code subtype} is enabled.
|
||||
*/
|
||||
private boolean updateEnabledSubtypesAndReturnIfEnabled(final InputMethodSubtype subtype) {
|
||||
final List<InputMethodSubtype> enabledSubtypesOfThisIme =
|
||||
mImm.getEnabledInputMethodSubtypeList(null, true);
|
||||
for (InputMethodSubtype ims : enabledSubtypesOfThisIme) {
|
||||
if (ims.equals(currentSubtype)) {
|
||||
foundCurrentSubtypeBecameDisabled = false;
|
||||
}
|
||||
}
|
||||
mNeedsToDisplayLanguage.updateEnabledSubtypeCount(enabledSubtypesOfThisIme.size());
|
||||
if (foundCurrentSubtypeBecameDisabled) {
|
||||
|
||||
for (final InputMethodSubtype ims : enabledSubtypesOfThisIme) {
|
||||
if (ims.equals(subtype)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (DBG) {
|
||||
Log.w(TAG, "Last subtype: "
|
||||
+ currentSubtype.getLocale() + "/" + currentSubtype.getExtraValue());
|
||||
Log.w(TAG, "Last subtype was disabled. Update to the current one.");
|
||||
}
|
||||
updateSubtype(ImfUtils.getCurrentInputMethodSubtype(mService, mNoLanguageSubtype));
|
||||
Log.w(TAG, "Subtype: " + subtype.getLocale() + "/" + subtype.getExtraValue()
|
||||
+ " was disabled");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void updateShortcutIME() {
|
||||
|
@ -159,8 +165,8 @@ public class SubtypeSwitcher {
|
|||
mImm.getShortcutInputMethodsAndSubtypes();
|
||||
mShortcutInputMethodInfo = null;
|
||||
mShortcutSubtype = null;
|
||||
for (InputMethodInfo imi : shortcuts.keySet()) {
|
||||
List<InputMethodSubtype> subtypes = shortcuts.get(imi);
|
||||
for (final InputMethodInfo imi : shortcuts.keySet()) {
|
||||
final List<InputMethodSubtype> subtypes = shortcuts.get(imi);
|
||||
// TODO: Returns the first found IMI for now. Should handle all shortcuts as
|
||||
// appropriate.
|
||||
mShortcutInputMethodInfo = imi;
|
||||
|
@ -194,24 +200,24 @@ public class SubtypeSwitcher {
|
|||
|
||||
mCurrentSubtype = newSubtype;
|
||||
updateShortcutIME();
|
||||
mService.onRefreshKeyboard();
|
||||
}
|
||||
|
||||
////////////////////////////
|
||||
// Shortcut IME functions //
|
||||
////////////////////////////
|
||||
|
||||
public void switchToShortcutIME() {
|
||||
public void switchToShortcutIME(final InputMethodService context) {
|
||||
if (mShortcutInputMethodInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String imiId = mShortcutInputMethodInfo.getId();
|
||||
switchToTargetIME(imiId, mShortcutSubtype);
|
||||
switchToTargetIME(imiId, mShortcutSubtype, context);
|
||||
}
|
||||
|
||||
private void switchToTargetIME(final String imiId, final InputMethodSubtype subtype) {
|
||||
final IBinder token = mService.getWindow().getWindow().getAttributes().token;
|
||||
private void switchToTargetIME(final String imiId, final InputMethodSubtype subtype,
|
||||
final InputMethodService context) {
|
||||
final IBinder token = context.getWindow().getWindow().getAttributes().token;
|
||||
if (token == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -253,7 +259,7 @@ public class SubtypeSwitcher {
|
|||
return true;
|
||||
}
|
||||
|
||||
public void onNetworkStateChanged(Intent intent) {
|
||||
public void onNetworkStateChanged(final Intent intent) {
|
||||
final boolean noConnection = intent.getBooleanExtra(
|
||||
ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
|
||||
mIsNetworkConnected = !noConnection;
|
||||
|
@ -265,7 +271,7 @@ public class SubtypeSwitcher {
|
|||
// Subtype Switching functions //
|
||||
//////////////////////////////////
|
||||
|
||||
public boolean needsToDisplayLanguage(Locale keyboardLocale) {
|
||||
public boolean needsToDisplayLanguage(final Locale keyboardLocale) {
|
||||
if (keyboardLocale.toString().equals(SubtypeLocale.NO_LANGUAGE)) {
|
||||
return true;
|
||||
}
|
||||
|
@ -279,12 +285,14 @@ public class SubtypeSwitcher {
|
|||
return SubtypeLocale.getSubtypeLocale(mCurrentSubtype);
|
||||
}
|
||||
|
||||
public void onConfigurationChanged(Configuration conf) {
|
||||
public boolean onConfigurationChanged(final Configuration conf, final Context context) {
|
||||
final Locale systemLocale = conf.locale;
|
||||
final boolean systemLocaleChanged = !systemLocale.equals(mCurrentSystemLocale);
|
||||
// If system configuration was changed, update all parameters.
|
||||
if (!systemLocale.equals(mCurrentSystemLocale)) {
|
||||
updateAllParameters();
|
||||
if (systemLocaleChanged) {
|
||||
updateAllParameters(context);
|
||||
}
|
||||
return systemLocaleChanged;
|
||||
}
|
||||
|
||||
public InputMethodSubtype getCurrentSubtype() {
|
||||
|
|
|
@ -50,9 +50,8 @@ public class Suggest {
|
|||
|
||||
private Dictionary mMainDictionary;
|
||||
private ContactsBinaryDictionary mContactsDict;
|
||||
private WhitelistDictionary mWhiteListDictionary;
|
||||
private final ConcurrentHashMap<String, Dictionary> mDictionaries =
|
||||
new ConcurrentHashMap<String, Dictionary>();
|
||||
CollectionUtils.newConcurrentHashMap();
|
||||
|
||||
public static final int MAX_SUGGESTIONS = 18;
|
||||
|
||||
|
@ -60,13 +59,11 @@ public class Suggest {
|
|||
|
||||
// Locale used for upper- and title-casing words
|
||||
private final Locale mLocale;
|
||||
private final SuggestInitializationListener mListener;
|
||||
|
||||
public Suggest(final Context context, final Locale locale,
|
||||
final SuggestInitializationListener listener) {
|
||||
initAsynchronously(context, locale);
|
||||
initAsynchronously(context, locale, listener);
|
||||
mLocale = locale;
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
/* package for test */ Suggest(final Context context, final File dictionary,
|
||||
|
@ -74,23 +71,13 @@ public class Suggest {
|
|||
final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(context, dictionary,
|
||||
startOffset, length /* useFullEditDistance */, false, locale);
|
||||
mLocale = locale;
|
||||
mListener = null;
|
||||
mMainDictionary = mainDict;
|
||||
addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, mainDict);
|
||||
initWhitelistAndAutocorrectAndPool(context, locale);
|
||||
}
|
||||
|
||||
private void initWhitelistAndAutocorrectAndPool(final Context context, final Locale locale) {
|
||||
mWhiteListDictionary = new WhitelistDictionary(context, locale);
|
||||
addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_WHITELIST, mWhiteListDictionary);
|
||||
}
|
||||
|
||||
private void initAsynchronously(final Context context, final Locale locale) {
|
||||
resetMainDict(context, locale);
|
||||
|
||||
// TODO: read the whitelist and init the pool asynchronously too.
|
||||
// initPool should be done asynchronously now that the pool is thread-safe.
|
||||
initWhitelistAndAutocorrectAndPool(context, locale);
|
||||
private void initAsynchronously(final Context context, final Locale locale,
|
||||
final SuggestInitializationListener listener) {
|
||||
resetMainDict(context, locale, listener);
|
||||
}
|
||||
|
||||
private static void addOrReplaceDictionary(
|
||||
|
@ -104,10 +91,11 @@ public class Suggest {
|
|||
}
|
||||
}
|
||||
|
||||
public void resetMainDict(final Context context, final Locale locale) {
|
||||
public void resetMainDict(final Context context, final Locale locale,
|
||||
final SuggestInitializationListener listener) {
|
||||
mMainDictionary = null;
|
||||
if (mListener != null) {
|
||||
mListener.onUpdateMainDictionaryAvailability(hasMainDictionary());
|
||||
if (listener != null) {
|
||||
listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
|
||||
}
|
||||
new Thread("InitializeBinaryDictionary") {
|
||||
@Override
|
||||
|
@ -116,8 +104,8 @@ public class Suggest {
|
|||
DictionaryFactory.createMainDictionaryFromManager(context, locale);
|
||||
addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, newMainDict);
|
||||
mMainDictionary = newMainDict;
|
||||
if (mListener != null) {
|
||||
mListener.onUpdateMainDictionaryAvailability(hasMainDictionary());
|
||||
if (listener != null) {
|
||||
listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
|
@ -170,9 +158,17 @@ public class Suggest {
|
|||
public SuggestedWords getSuggestedWords(
|
||||
final WordComposer wordComposer, CharSequence prevWordForBigram,
|
||||
final ProximityInfo proximityInfo, final boolean isCorrectionEnabled) {
|
||||
return getSuggestedWordsWithSessionId(
|
||||
wordComposer, prevWordForBigram, proximityInfo, isCorrectionEnabled, 0);
|
||||
}
|
||||
|
||||
public SuggestedWords getSuggestedWordsWithSessionId(
|
||||
final WordComposer wordComposer, CharSequence prevWordForBigram,
|
||||
final ProximityInfo proximityInfo, final boolean isCorrectionEnabled, int sessionId) {
|
||||
LatinImeLogger.onStartSuggestion(prevWordForBigram);
|
||||
if (wordComposer.isBatchMode()) {
|
||||
return getSuggestedWordsForBatchInput(wordComposer, prevWordForBigram, proximityInfo);
|
||||
return getSuggestedWordsForBatchInput(
|
||||
wordComposer, prevWordForBigram, proximityInfo, sessionId);
|
||||
} else {
|
||||
return getSuggestedWordsForTypingInput(wordComposer, prevWordForBigram, proximityInfo,
|
||||
isCorrectionEnabled);
|
||||
|
@ -209,23 +205,20 @@ public class Suggest {
|
|||
wordComposerForLookup, prevWordForBigram, proximityInfo));
|
||||
}
|
||||
|
||||
// TODO: Change this scheme - a boolean is not enough. A whitelisted word may be "valid"
|
||||
// but still autocorrected from - in the case the whitelist only capitalizes the word.
|
||||
// The whitelist should be case-insensitive, so it's not possible to be consistent with
|
||||
// a boolean flag. Right now this is handled with a slight hack in
|
||||
// WhitelistDictionary#shouldForciblyAutoCorrectFrom.
|
||||
final boolean allowsToBeAutoCorrected = AutoCorrection.isWhitelistedOrNotAWord(
|
||||
mDictionaries, consideredWord, wordComposer.isFirstCharCapitalized());
|
||||
|
||||
final CharSequence whitelistedWord =
|
||||
mWhiteListDictionary.getWhitelistedWord(consideredWord);
|
||||
if (whitelistedWord != null) {
|
||||
// MAX_SCORE ensures this will be considered strong enough to be auto-corrected
|
||||
suggestionsSet.add(new SuggestedWordInfo(whitelistedWord,
|
||||
SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_WHITELIST,
|
||||
Dictionary.TYPE_WHITELIST));
|
||||
final CharSequence whitelistedWord;
|
||||
if (suggestionsSet.isEmpty()) {
|
||||
whitelistedWord = null;
|
||||
} else if (SuggestedWordInfo.KIND_WHITELIST != suggestionsSet.first().mKind) {
|
||||
whitelistedWord = null;
|
||||
} else {
|
||||
whitelistedWord = suggestionsSet.first().mWord;
|
||||
}
|
||||
|
||||
final boolean allowsToBeAutoCorrected = (null != whitelistedWord
|
||||
&& !whitelistedWord.equals(consideredWord))
|
||||
|| AutoCorrection.isNotAWord(mDictionaries, consideredWord,
|
||||
wordComposer.isFirstCharCapitalized());
|
||||
|
||||
final boolean hasAutoCorrection;
|
||||
// TODO: using isCorrectionEnabled here is not very good. It's probably useless, because
|
||||
// any attempt to do auto-correction is already shielded with a test for this flag; at the
|
||||
|
@ -249,7 +242,7 @@ public class Suggest {
|
|||
}
|
||||
|
||||
final ArrayList<SuggestedWordInfo> suggestionsContainer =
|
||||
new ArrayList<SuggestedWordInfo>(suggestionsSet);
|
||||
CollectionUtils.newArrayList(suggestionsSet);
|
||||
final int suggestionsCount = suggestionsContainer.size();
|
||||
final boolean isFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
|
||||
final boolean isAllUpperCase = wordComposer.isAllUpperCase();
|
||||
|
@ -296,29 +289,28 @@ public class Suggest {
|
|||
// Retrieves suggestions for the batch input.
|
||||
private SuggestedWords getSuggestedWordsForBatchInput(
|
||||
final WordComposer wordComposer, CharSequence prevWordForBigram,
|
||||
final ProximityInfo proximityInfo) {
|
||||
final ProximityInfo proximityInfo, int sessionId) {
|
||||
final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator,
|
||||
MAX_SUGGESTIONS);
|
||||
|
||||
// At second character typed, search the unigrams (scores being affected by bigrams)
|
||||
for (final String key : mDictionaries.keySet()) {
|
||||
// Skip UserUnigramDictionary and WhitelistDictionary to lookup
|
||||
if (key.equals(Dictionary.TYPE_USER_HISTORY)
|
||||
|| key.equals(Dictionary.TYPE_WHITELIST)) {
|
||||
// Skip User history dictionary for lookup
|
||||
// TODO: The user history dictionary should just override getSuggestionsWithSessionId
|
||||
// to make sure it doesn't return anything and we should remove this test
|
||||
if (key.equals(Dictionary.TYPE_USER_HISTORY)) {
|
||||
continue;
|
||||
}
|
||||
final Dictionary dictionary = mDictionaries.get(key);
|
||||
suggestionsSet.addAll(dictionary.getSuggestions(
|
||||
wordComposer, prevWordForBigram, proximityInfo));
|
||||
suggestionsSet.addAll(dictionary.getSuggestionsWithSessionId(
|
||||
wordComposer, prevWordForBigram, proximityInfo, sessionId));
|
||||
}
|
||||
|
||||
final ArrayList<SuggestedWordInfo> suggestionsContainer =
|
||||
new ArrayList<SuggestedWordInfo>(suggestionsSet);
|
||||
CollectionUtils.newArrayList(suggestionsSet);
|
||||
final int suggestionsCount = suggestionsContainer.size();
|
||||
final boolean isFirstCharCapitalized = wordComposer.isAutoCapitalized();
|
||||
// TODO: Handle the manual temporary shifted mode.
|
||||
// TODO: Should handle TextUtils.CAP_MODE_CHARACTER.
|
||||
final boolean isAllUpperCase = false;
|
||||
final boolean isFirstCharCapitalized = wordComposer.wasShiftedNoLock();
|
||||
final boolean isAllUpperCase = wordComposer.isAllUpperCase();
|
||||
if (isFirstCharCapitalized || isAllUpperCase) {
|
||||
for (int i = 0; i < suggestionsCount; ++i) {
|
||||
final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
|
||||
|
@ -346,7 +338,7 @@ public class Suggest {
|
|||
typedWordInfo.setDebugString("+");
|
||||
final int suggestionsSize = suggestions.size();
|
||||
final ArrayList<SuggestedWordInfo> suggestionsList =
|
||||
new ArrayList<SuggestedWordInfo>(suggestionsSize);
|
||||
CollectionUtils.newArrayList(suggestionsSize);
|
||||
suggestionsList.add(typedWordInfo);
|
||||
// Note: i here is the index in mScores[], but the index in mSuggestions is one more
|
||||
// than i because we added the typed word to mSuggestions without touching mScores.
|
||||
|
@ -399,7 +391,7 @@ public class Suggest {
|
|||
}
|
||||
|
||||
public void close() {
|
||||
final HashSet<Dictionary> dictionaries = new HashSet<Dictionary>();
|
||||
final HashSet<Dictionary> dictionaries = CollectionUtils.newHashSet();
|
||||
dictionaries.addAll(mDictionaries.values());
|
||||
for (final Dictionary dictionary : dictionaries) {
|
||||
dictionary.close();
|
||||
|
|
|
@ -24,8 +24,10 @@ import java.util.Arrays;
|
|||
import java.util.HashSet;
|
||||
|
||||
public class SuggestedWords {
|
||||
private static final ArrayList<SuggestedWordInfo> EMPTY_WORD_INFO_LIST =
|
||||
CollectionUtils.newArrayList(0);
|
||||
public static final SuggestedWords EMPTY = new SuggestedWords(
|
||||
new ArrayList<SuggestedWordInfo>(0), false, false, false, false, false);
|
||||
EMPTY_WORD_INFO_LIST, false, false, false, false, false);
|
||||
|
||||
public final boolean mTypedWordValid;
|
||||
// Note: this INCLUDES cases where the word will auto-correct to itself. A good definition
|
||||
|
@ -83,7 +85,7 @@ public class SuggestedWords {
|
|||
|
||||
public static ArrayList<SuggestedWordInfo> getFromApplicationSpecifiedCompletions(
|
||||
final CompletionInfo[] infos) {
|
||||
final ArrayList<SuggestedWordInfo> result = new ArrayList<SuggestedWordInfo>();
|
||||
final ArrayList<SuggestedWordInfo> result = CollectionUtils.newArrayList();
|
||||
for (CompletionInfo info : infos) {
|
||||
if (null != info && info.getText() != null) {
|
||||
result.add(new SuggestedWordInfo(info.getText(), SuggestedWordInfo.MAX_SCORE,
|
||||
|
@ -97,8 +99,8 @@ public class SuggestedWords {
|
|||
// and replace it with what the user currently typed.
|
||||
public static ArrayList<SuggestedWordInfo> getTypedWordAndPreviousSuggestions(
|
||||
final CharSequence typedWord, final SuggestedWords previousSuggestions) {
|
||||
final ArrayList<SuggestedWordInfo> suggestionsList = new ArrayList<SuggestedWordInfo>();
|
||||
final HashSet<String> alreadySeen = new HashSet<String>();
|
||||
final ArrayList<SuggestedWordInfo> suggestionsList = CollectionUtils.newArrayList();
|
||||
final HashSet<String> alreadySeen = CollectionUtils.newHashSet();
|
||||
suggestionsList.add(new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE,
|
||||
SuggestedWordInfo.KIND_TYPED, Dictionary.TYPE_USER_TYPED));
|
||||
alreadySeen.add(typedWord.toString());
|
||||
|
|
|
@ -52,14 +52,14 @@ public class UserHistoryDictionary extends ExpandableDictionary {
|
|||
private static final int FREQUENCY_FOR_TYPED = 2;
|
||||
|
||||
/** Maximum number of pairs. Pruning will start when databases goes above this number. */
|
||||
private static int sMaxHistoryBigrams = 10000;
|
||||
public static final int sMaxHistoryBigrams = 10000;
|
||||
|
||||
/**
|
||||
* When it hits maximum bigram pair, it will delete until you are left with
|
||||
* only (sMaxHistoryBigrams - sDeleteHistoryBigrams) pairs.
|
||||
* Do not keep this number small to avoid deleting too often.
|
||||
*/
|
||||
private static int sDeleteHistoryBigrams = 1000;
|
||||
public static final int sDeleteHistoryBigrams = 1000;
|
||||
|
||||
/**
|
||||
* Database version should increase if the database structure changes
|
||||
|
@ -93,10 +93,10 @@ public class UserHistoryDictionary extends ExpandableDictionary {
|
|||
|
||||
private final static HashMap<String, String> sDictProjectionMap;
|
||||
private final static ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>
|
||||
sLangDictCache = new ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>();
|
||||
sLangDictCache = CollectionUtils.newConcurrentHashMap();
|
||||
|
||||
static {
|
||||
sDictProjectionMap = new HashMap<String, String>();
|
||||
sDictProjectionMap = CollectionUtils.newHashMap();
|
||||
sDictProjectionMap.put(MAIN_COLUMN_ID, MAIN_COLUMN_ID);
|
||||
sDictProjectionMap.put(MAIN_COLUMN_WORD1, MAIN_COLUMN_WORD1);
|
||||
sDictProjectionMap.put(MAIN_COLUMN_WORD2, MAIN_COLUMN_WORD2);
|
||||
|
@ -109,12 +109,8 @@ public class UserHistoryDictionary extends ExpandableDictionary {
|
|||
|
||||
private static DatabaseHelper sOpenHelper = null;
|
||||
|
||||
public void setDatabaseMax(int maxHistoryBigram) {
|
||||
sMaxHistoryBigrams = maxHistoryBigram;
|
||||
}
|
||||
|
||||
public void setDatabaseDelete(int deleteHistoryBigram) {
|
||||
sDeleteHistoryBigrams = deleteHistoryBigram;
|
||||
public String getLocale() {
|
||||
return mLocale;
|
||||
}
|
||||
|
||||
public synchronized static UserHistoryDictionary getInstance(
|
||||
|
@ -502,9 +498,11 @@ public class UserHistoryDictionary extends ExpandableDictionary {
|
|||
needsToSave(fc, isValid, addLevel0Bigram)) {
|
||||
freq = fc;
|
||||
} else {
|
||||
// Delete this entry
|
||||
freq = -1;
|
||||
}
|
||||
} else {
|
||||
// Delete this entry
|
||||
freq = -1;
|
||||
}
|
||||
}
|
||||
|
@ -541,6 +539,7 @@ public class UserHistoryDictionary extends ExpandableDictionary {
|
|||
getContentValues(word1, word2, mLocale));
|
||||
pairId = pairIdLong.intValue();
|
||||
}
|
||||
// Eliminate freq == 0 because that word is profanity.
|
||||
if (freq > 0) {
|
||||
if (PROFILE_SAVE_RESTORE) {
|
||||
++profInsert;
|
||||
|
|
|
@ -29,9 +29,8 @@ import java.util.Set;
|
|||
public class UserHistoryDictionaryBigramList {
|
||||
public static final byte FORGETTING_CURVE_INITIAL_VALUE = 0;
|
||||
private static final String TAG = UserHistoryDictionaryBigramList.class.getSimpleName();
|
||||
private static final HashMap<String, Byte> EMPTY_BIGRAM_MAP = new HashMap<String, Byte>();
|
||||
private final HashMap<String, HashMap<String, Byte>> mBigramMap =
|
||||
new HashMap<String, HashMap<String, Byte>>();
|
||||
private static final HashMap<String, Byte> EMPTY_BIGRAM_MAP = CollectionUtils.newHashMap();
|
||||
private final HashMap<String, HashMap<String, Byte>> mBigramMap = CollectionUtils.newHashMap();
|
||||
private int mSize = 0;
|
||||
|
||||
public void evictAll() {
|
||||
|
@ -57,7 +56,7 @@ public class UserHistoryDictionaryBigramList {
|
|||
if (mBigramMap.containsKey(word1)) {
|
||||
map = mBigramMap.get(word1);
|
||||
} else {
|
||||
map = new HashMap<String, Byte>();
|
||||
map = CollectionUtils.newHashMap();
|
||||
mBigramMap.put(word1, map);
|
||||
}
|
||||
if (!map.containsKey(word2)) {
|
||||
|
|
|
@ -212,7 +212,7 @@ public class UserHistoryForgettingCurveUtils {
|
|||
for (int j = 0; j < ELAPSED_TIME_MAX; ++j) {
|
||||
final float elapsedHours = j * ELAPSED_TIME_INTERVAL_HOURS;
|
||||
final float freq = initialFreq
|
||||
* NativeUtils.powf(initialFreq, elapsedHours / HALF_LIFE_HOURS);
|
||||
* (float)Math.pow(initialFreq, elapsedHours / HALF_LIFE_HOURS);
|
||||
final int intFreq = Math.min(FC_FREQ_MAX, Math.max(0, (int)freq));
|
||||
SCORE_TABLE[i][j] = intFreq;
|
||||
}
|
||||
|
|
|
@ -65,44 +65,6 @@ public class Utils {
|
|||
}
|
||||
}
|
||||
|
||||
public static class GCUtils {
|
||||
private static final String GC_TAG = GCUtils.class.getSimpleName();
|
||||
public static final int GC_TRY_COUNT = 2;
|
||||
// GC_TRY_LOOP_MAX is used for the hard limit of GC wait,
|
||||
// GC_TRY_LOOP_MAX should be greater than GC_TRY_COUNT.
|
||||
public static final int GC_TRY_LOOP_MAX = 5;
|
||||
private static final long GC_INTERVAL = DateUtils.SECOND_IN_MILLIS;
|
||||
private static GCUtils sInstance = new GCUtils();
|
||||
private int mGCTryCount = 0;
|
||||
|
||||
public static GCUtils getInstance() {
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
mGCTryCount = 0;
|
||||
}
|
||||
|
||||
public boolean tryGCOrWait(String metaData, Throwable t) {
|
||||
if (mGCTryCount == 0) {
|
||||
System.gc();
|
||||
}
|
||||
if (++mGCTryCount > GC_TRY_COUNT) {
|
||||
LatinImeLogger.logOnException(metaData, t);
|
||||
return false;
|
||||
} else {
|
||||
try {
|
||||
Thread.sleep(GC_INTERVAL);
|
||||
return true;
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(GC_TAG, "Sleep was interrupted.");
|
||||
LatinImeLogger.logOnException(metaData, t);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ static class RingCharBuffer {
|
||||
private static RingCharBuffer sRingCharBuffer = new RingCharBuffer();
|
||||
private static final char PLACEHOLDER_DELIMITER_CHAR = '\uFFFC';
|
||||
|
@ -477,7 +439,7 @@ public class Utils {
|
|||
|
||||
private static final String HARDWARE_PREFIX = Build.HARDWARE + ",";
|
||||
private static final HashMap<String, String> sDeviceOverrideValueMap =
|
||||
new HashMap<String, String>();
|
||||
CollectionUtils.newHashMap();
|
||||
|
||||
public static String getDeviceOverrideValue(Resources res, int overrideResId, String defValue) {
|
||||
final int orientation = res.getConfiguration().orientation;
|
||||
|
@ -495,7 +457,7 @@ public class Utils {
|
|||
return sDeviceOverrideValueMap.get(key);
|
||||
}
|
||||
|
||||
private static final HashMap<String, Long> EMPTY_LT_HASH_MAP = new HashMap<String, Long>();
|
||||
private static final HashMap<String, Long> EMPTY_LT_HASH_MAP = CollectionUtils.newHashMap();
|
||||
private static final String LOCALE_AND_TIME_STR_SEPARATER = ",";
|
||||
public static HashMap<String, Long> localeAndTimeStrToHashMap(String str) {
|
||||
if (TextUtils.isEmpty(str)) {
|
||||
|
@ -506,7 +468,7 @@ public class Utils {
|
|||
if (N < 2 || N % 2 != 0) {
|
||||
return EMPTY_LT_HASH_MAP;
|
||||
}
|
||||
final HashMap<String, Long> retval = new HashMap<String, Long>();
|
||||
final HashMap<String, Long> retval = CollectionUtils.newHashMap();
|
||||
for (int i = 0; i < N / 2; ++i) {
|
||||
final String localeStr = ss[i * 2];
|
||||
final long time = Long.valueOf(ss[i * 2 + 1]);
|
||||
|
|
|
@ -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;
|
||||
|
||||
import com.android.inputmethod.keyboard.Key;
|
||||
import com.android.inputmethod.keyboard.KeyDetector;
|
||||
import com.android.inputmethod.keyboard.Keyboard;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
@ -26,12 +25,16 @@ import java.util.Arrays;
|
|||
* A place to store the currently composing word with information such as adjacent key codes as well
|
||||
*/
|
||||
public class WordComposer {
|
||||
|
||||
public static final int NOT_A_CODE = KeyDetector.NOT_A_CODE;
|
||||
public static final int NOT_A_COORDINATE = -1;
|
||||
|
||||
private static final int N = BinaryDictionary.MAX_WORD_LENGTH;
|
||||
|
||||
public static final int CAPS_MODE_OFF = 0;
|
||||
// 1 is shift bit, 2 is caps bit, 4 is auto bit but this is just a convention as these bits
|
||||
// aren't used anywhere in the code
|
||||
public static final int CAPS_MODE_MANUAL_SHIFTED = 0x1;
|
||||
public static final int CAPS_MODE_MANUAL_SHIFT_LOCKED = 0x3;
|
||||
public static final int CAPS_MODE_AUTO_SHIFTED = 0x5;
|
||||
public static final int CAPS_MODE_AUTO_SHIFT_LOCKED = 0x7;
|
||||
|
||||
private int[] mPrimaryKeyCodes;
|
||||
private final InputPointers mInputPointers = new InputPointers(N);
|
||||
private final StringBuilder mTypedWord;
|
||||
|
@ -42,7 +45,7 @@ public class WordComposer {
|
|||
// Cache these values for performance
|
||||
private int mCapsCount;
|
||||
private int mDigitsCount;
|
||||
private boolean mAutoCapitalized;
|
||||
private int mCapitalizedMode;
|
||||
private int mTrailingSingleQuotesCount;
|
||||
private int mCodePointSize;
|
||||
|
||||
|
@ -68,7 +71,7 @@ public class WordComposer {
|
|||
mCapsCount = source.mCapsCount;
|
||||
mDigitsCount = source.mDigitsCount;
|
||||
mIsFirstCharCapitalized = source.mIsFirstCharCapitalized;
|
||||
mAutoCapitalized = source.mAutoCapitalized;
|
||||
mCapitalizedMode = source.mCapitalizedMode;
|
||||
mTrailingSingleQuotesCount = source.mTrailingSingleQuotesCount;
|
||||
mIsResumed = source.mIsResumed;
|
||||
mIsBatchMode = source.mIsBatchMode;
|
||||
|
@ -166,7 +169,7 @@ public class WordComposer {
|
|||
final int codePoint = Character.codePointAt(word, i);
|
||||
// We don't want to override the batch input points that are held in mInputPointers
|
||||
// (See {@link #add(int,int,int)}).
|
||||
add(codePoint, NOT_A_COORDINATE, NOT_A_COORDINATE);
|
||||
add(codePoint, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -181,7 +184,7 @@ public class WordComposer {
|
|||
add(codePoint, x, y);
|
||||
return;
|
||||
}
|
||||
add(codePoint, NOT_A_COORDINATE, NOT_A_COORDINATE);
|
||||
add(codePoint, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -262,7 +265,14 @@ public class WordComposer {
|
|||
* @return true if all user typed chars are upper case, false otherwise
|
||||
*/
|
||||
public boolean isAllUpperCase() {
|
||||
return (mCapsCount > 0) && (mCapsCount == size());
|
||||
return mCapitalizedMode == CAPS_MODE_AUTO_SHIFT_LOCKED
|
||||
|| mCapitalizedMode == CAPS_MODE_MANUAL_SHIFT_LOCKED
|
||||
|| (mCapsCount > 0) && (mCapsCount == size());
|
||||
}
|
||||
|
||||
public boolean wasShiftedNoLock() {
|
||||
return mCapitalizedMode == CAPS_MODE_AUTO_SHIFTED
|
||||
|| mCapitalizedMode == CAPS_MODE_MANUAL_SHIFTED;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -280,20 +290,27 @@ public class WordComposer {
|
|||
}
|
||||
|
||||
/**
|
||||
* Saves the reason why the word is capitalized - whether it was automatic or
|
||||
* due to the user hitting shift in the middle of a sentence.
|
||||
* @param auto whether it was an automatic capitalization due to start of sentence
|
||||
* Saves the caps mode at the start of composing.
|
||||
*
|
||||
* WordComposer needs to know about this for several reasons. The first is, we need to know
|
||||
* after the fact what the reason was, to register the correct form into the user history
|
||||
* dictionary: if the word was automatically capitalized, we should insert it in all-lower
|
||||
* case but if it's a manual pressing of shift, then it should be inserted as is.
|
||||
* Also, batch input needs to know about the current caps mode to display correctly
|
||||
* capitalized suggestions.
|
||||
* @param mode the mode at the time of start
|
||||
*/
|
||||
public void setAutoCapitalized(boolean auto) {
|
||||
mAutoCapitalized = auto;
|
||||
public void setCapitalizedModeAtStartComposingTime(final int mode) {
|
||||
mCapitalizedMode = mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the word was automatically capitalized.
|
||||
* @return whether the word was automatically capitalized
|
||||
*/
|
||||
public boolean isAutoCapitalized() {
|
||||
return mAutoCapitalized;
|
||||
public boolean wasAutoCapitalized() {
|
||||
return mCapitalizedMode == CAPS_MODE_AUTO_SHIFT_LOCKED
|
||||
|| mCapitalizedMode == CAPS_MODE_AUTO_SHIFTED;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -22,10 +22,13 @@ import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
|
|||
import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
|
@ -124,7 +127,7 @@ public class BinaryDictInputOutput {
|
|||
*/
|
||||
|
||||
private static final int VERSION_1_MAGIC_NUMBER = 0x78B1;
|
||||
private static final int VERSION_2_MAGIC_NUMBER = 0x9BC13AFE;
|
||||
public static final int VERSION_2_MAGIC_NUMBER = 0x9BC13AFE;
|
||||
private static final int MINIMUM_SUPPORTED_VERSION = 1;
|
||||
private static final int MAXIMUM_SUPPORTED_VERSION = 2;
|
||||
private static final int NOT_A_VERSION_NUMBER = -1;
|
||||
|
@ -307,33 +310,32 @@ public class BinaryDictInputOutput {
|
|||
}
|
||||
|
||||
/**
|
||||
* Reads a string from a RandomAccessFile. This is the converse of the above method.
|
||||
* Reads a string from a ByteBuffer. This is the converse of the above method.
|
||||
*/
|
||||
private static String readString(final RandomAccessFile source) throws IOException {
|
||||
private static String readString(final ByteBuffer buffer) {
|
||||
final StringBuilder s = new StringBuilder();
|
||||
int character = readChar(source);
|
||||
int character = readChar(buffer);
|
||||
while (character != INVALID_CHARACTER) {
|
||||
s.appendCodePoint(character);
|
||||
character = readChar(source);
|
||||
character = readChar(buffer);
|
||||
}
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a character from the file.
|
||||
* Reads a character from the ByteBuffer.
|
||||
*
|
||||
* This follows the character format documented earlier in this source file.
|
||||
*
|
||||
* @param source the file, positioned over an encoded character.
|
||||
* @param buffer the buffer, positioned over an encoded character.
|
||||
* @return the character code.
|
||||
*/
|
||||
private static int readChar(RandomAccessFile source) throws IOException {
|
||||
int character = source.readUnsignedByte();
|
||||
private static int readChar(final ByteBuffer buffer) {
|
||||
int character = readUnsignedByte(buffer);
|
||||
if (!fitsOnOneByte(character)) {
|
||||
if (GROUP_CHARACTERS_TERMINATOR == character)
|
||||
return INVALID_CHARACTER;
|
||||
if (GROUP_CHARACTERS_TERMINATOR == character) return INVALID_CHARACTER;
|
||||
character <<= 16;
|
||||
character += source.readUnsignedShort();
|
||||
character += readUnsignedShort(buffer);
|
||||
}
|
||||
return character;
|
||||
}
|
||||
|
@ -783,10 +785,10 @@ public class BinaryDictInputOutput {
|
|||
// their lower bound and exclude their higher bound so we need to have the first step
|
||||
// start at exactly 1 unit higher than floor(unigramFreq + half a step).
|
||||
// Note : to reconstruct the score, the dictionary reader will need to divide
|
||||
// MAX_TERMINAL_FREQUENCY - unigramFreq by 16.5 likewise, and add
|
||||
// (discretizedFrequency + 0.5) times this value to get the median value of the step,
|
||||
// which is the best approximation. This is how we get the most precise result with
|
||||
// only four bits.
|
||||
// MAX_TERMINAL_FREQUENCY - unigramFreq by 16.5 likewise to get the value of the step,
|
||||
// and add (discretizedFrequency + 0.5 + 0.5) times this value to get the best
|
||||
// approximation. (0.5 to get the first step start, and 0.5 to get the middle of the
|
||||
// step pointed by the discretized frequency.
|
||||
final float stepSize =
|
||||
(MAX_TERMINAL_FREQUENCY - unigramFrequency) / (1.5f + MAX_BIGRAM_FREQUENCY);
|
||||
final float firstStepStart = 1 + unigramFrequency + (stepSize / 2.0f);
|
||||
|
@ -1091,46 +1093,46 @@ public class BinaryDictInputOutput {
|
|||
// readDictionaryBinary is the public entry point for them.
|
||||
|
||||
static final int[] characterBuffer = new int[MAX_WORD_LENGTH];
|
||||
private static CharGroupInfo readCharGroup(RandomAccessFile source,
|
||||
final int originalGroupAddress) throws IOException {
|
||||
private static CharGroupInfo readCharGroup(final ByteBuffer buffer,
|
||||
final int originalGroupAddress) {
|
||||
int addressPointer = originalGroupAddress;
|
||||
final int flags = source.readUnsignedByte();
|
||||
final int flags = readUnsignedByte(buffer);
|
||||
++addressPointer;
|
||||
final int characters[];
|
||||
if (0 != (flags & FLAG_HAS_MULTIPLE_CHARS)) {
|
||||
int index = 0;
|
||||
int character = CharEncoding.readChar(source);
|
||||
int character = CharEncoding.readChar(buffer);
|
||||
addressPointer += CharEncoding.getCharSize(character);
|
||||
while (-1 != character) {
|
||||
characterBuffer[index++] = character;
|
||||
character = CharEncoding.readChar(source);
|
||||
character = CharEncoding.readChar(buffer);
|
||||
addressPointer += CharEncoding.getCharSize(character);
|
||||
}
|
||||
characters = Arrays.copyOfRange(characterBuffer, 0, index);
|
||||
} else {
|
||||
final int character = CharEncoding.readChar(source);
|
||||
final int character = CharEncoding.readChar(buffer);
|
||||
addressPointer += CharEncoding.getCharSize(character);
|
||||
characters = new int[] { character };
|
||||
}
|
||||
final int frequency;
|
||||
if (0 != (FLAG_IS_TERMINAL & flags)) {
|
||||
++addressPointer;
|
||||
frequency = source.readUnsignedByte();
|
||||
frequency = readUnsignedByte(buffer);
|
||||
} else {
|
||||
frequency = CharGroup.NOT_A_TERMINAL;
|
||||
}
|
||||
int childrenAddress = addressPointer;
|
||||
switch (flags & MASK_GROUP_ADDRESS_TYPE) {
|
||||
case FLAG_GROUP_ADDRESS_TYPE_ONEBYTE:
|
||||
childrenAddress += source.readUnsignedByte();
|
||||
childrenAddress += readUnsignedByte(buffer);
|
||||
addressPointer += 1;
|
||||
break;
|
||||
case FLAG_GROUP_ADDRESS_TYPE_TWOBYTES:
|
||||
childrenAddress += source.readUnsignedShort();
|
||||
childrenAddress += readUnsignedShort(buffer);
|
||||
addressPointer += 2;
|
||||
break;
|
||||
case FLAG_GROUP_ADDRESS_TYPE_THREEBYTES:
|
||||
childrenAddress += (source.readUnsignedByte() << 16) + source.readUnsignedShort();
|
||||
childrenAddress += readUnsignedInt24(buffer);
|
||||
addressPointer += 3;
|
||||
break;
|
||||
case FLAG_GROUP_ADDRESS_TYPE_NOADDRESS:
|
||||
|
@ -1140,38 +1142,38 @@ public class BinaryDictInputOutput {
|
|||
}
|
||||
ArrayList<WeightedString> shortcutTargets = null;
|
||||
if (0 != (flags & FLAG_HAS_SHORTCUT_TARGETS)) {
|
||||
final long pointerBefore = source.getFilePointer();
|
||||
final int pointerBefore = buffer.position();
|
||||
shortcutTargets = new ArrayList<WeightedString>();
|
||||
source.readUnsignedShort(); // Skip the size
|
||||
buffer.getShort(); // Skip the size
|
||||
while (true) {
|
||||
final int targetFlags = source.readUnsignedByte();
|
||||
final String word = CharEncoding.readString(source);
|
||||
final int targetFlags = readUnsignedByte(buffer);
|
||||
final String word = CharEncoding.readString(buffer);
|
||||
shortcutTargets.add(new WeightedString(word,
|
||||
targetFlags & FLAG_ATTRIBUTE_FREQUENCY));
|
||||
if (0 == (targetFlags & FLAG_ATTRIBUTE_HAS_NEXT)) break;
|
||||
}
|
||||
addressPointer += (source.getFilePointer() - pointerBefore);
|
||||
addressPointer += buffer.position() - pointerBefore;
|
||||
}
|
||||
ArrayList<PendingAttribute> bigrams = null;
|
||||
if (0 != (flags & FLAG_HAS_BIGRAMS)) {
|
||||
bigrams = new ArrayList<PendingAttribute>();
|
||||
while (true) {
|
||||
final int bigramFlags = source.readUnsignedByte();
|
||||
final int bigramFlags = readUnsignedByte(buffer);
|
||||
++addressPointer;
|
||||
final int sign = 0 == (bigramFlags & FLAG_ATTRIBUTE_OFFSET_NEGATIVE) ? 1 : -1;
|
||||
int bigramAddress = addressPointer;
|
||||
switch (bigramFlags & MASK_ATTRIBUTE_ADDRESS_TYPE) {
|
||||
case FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE:
|
||||
bigramAddress += sign * source.readUnsignedByte();
|
||||
bigramAddress += sign * readUnsignedByte(buffer);
|
||||
addressPointer += 1;
|
||||
break;
|
||||
case FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES:
|
||||
bigramAddress += sign * source.readUnsignedShort();
|
||||
bigramAddress += sign * readUnsignedShort(buffer);
|
||||
addressPointer += 2;
|
||||
break;
|
||||
case FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES:
|
||||
final int offset = ((source.readUnsignedByte() << 16)
|
||||
+ source.readUnsignedShort());
|
||||
final int offset = (readUnsignedByte(buffer) << 16)
|
||||
+ readUnsignedShort(buffer);
|
||||
bigramAddress += sign * offset;
|
||||
addressPointer += 3;
|
||||
break;
|
||||
|
@ -1188,15 +1190,15 @@ public class BinaryDictInputOutput {
|
|||
}
|
||||
|
||||
/**
|
||||
* Reads and returns the char group count out of a file and forwards the pointer.
|
||||
* Reads and returns the char group count out of a buffer and forwards the pointer.
|
||||
*/
|
||||
private static int readCharGroupCount(RandomAccessFile source) throws IOException {
|
||||
final int msb = source.readUnsignedByte();
|
||||
private static int readCharGroupCount(final ByteBuffer buffer) {
|
||||
final int msb = readUnsignedByte(buffer);
|
||||
if (MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT >= msb) {
|
||||
return msb;
|
||||
} else {
|
||||
return ((MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT & msb) << 8)
|
||||
+ source.readUnsignedByte();
|
||||
+ readUnsignedByte(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1204,31 +1206,29 @@ public class BinaryDictInputOutput {
|
|||
// of this method. Since it performs direct, unbuffered random access to the file and
|
||||
// may be called hundreds of thousands of times, the resulting performance is not
|
||||
// reasonable without some kind of cache. Thus:
|
||||
// TODO: perform buffered I/O here and in other places in the code.
|
||||
private static TreeMap<Integer, String> wordCache = new TreeMap<Integer, String>();
|
||||
/**
|
||||
* Finds, as a string, the word at the address passed as an argument.
|
||||
*
|
||||
* @param source the file to read from.
|
||||
* @param buffer the buffer to read from.
|
||||
* @param headerSize the size of the header.
|
||||
* @param address the address to seek.
|
||||
* @return the word, as a string.
|
||||
* @throws IOException if the file can't be read.
|
||||
*/
|
||||
private static String getWordAtAddress(final RandomAccessFile source, final long headerSize,
|
||||
int address) throws IOException {
|
||||
private static String getWordAtAddress(final ByteBuffer buffer, final int headerSize,
|
||||
final int address) {
|
||||
final String cachedString = wordCache.get(address);
|
||||
if (null != cachedString) return cachedString;
|
||||
final long originalPointer = source.getFilePointer();
|
||||
source.seek(headerSize);
|
||||
final int count = readCharGroupCount(source);
|
||||
final int originalPointer = buffer.position();
|
||||
buffer.position(headerSize);
|
||||
final int count = readCharGroupCount(buffer);
|
||||
int groupOffset = getGroupCountSize(count);
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
String result = null;
|
||||
|
||||
CharGroupInfo last = null;
|
||||
for (int i = count - 1; i >= 0; --i) {
|
||||
CharGroupInfo info = readCharGroup(source, groupOffset);
|
||||
CharGroupInfo info = readCharGroup(buffer, groupOffset);
|
||||
groupOffset = info.mEndAddress;
|
||||
if (info.mOriginalAddress == address) {
|
||||
builder.append(new String(info.mCharacters, 0, info.mCharacters.length));
|
||||
|
@ -1239,9 +1239,9 @@ public class BinaryDictInputOutput {
|
|||
if (info.mChildrenAddress > address) {
|
||||
if (null == last) continue;
|
||||
builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
|
||||
source.seek(last.mChildrenAddress + headerSize);
|
||||
buffer.position(last.mChildrenAddress + headerSize);
|
||||
groupOffset = last.mChildrenAddress + 1;
|
||||
i = source.readUnsignedByte();
|
||||
i = readUnsignedByte(buffer);
|
||||
last = null;
|
||||
continue;
|
||||
}
|
||||
|
@ -1249,14 +1249,14 @@ public class BinaryDictInputOutput {
|
|||
}
|
||||
if (0 == i && hasChildrenAddress(last.mChildrenAddress)) {
|
||||
builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
|
||||
source.seek(last.mChildrenAddress + headerSize);
|
||||
buffer.position(last.mChildrenAddress + headerSize);
|
||||
groupOffset = last.mChildrenAddress + 1;
|
||||
i = source.readUnsignedByte();
|
||||
i = readUnsignedByte(buffer);
|
||||
last = null;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
source.seek(originalPointer);
|
||||
buffer.position(originalPointer);
|
||||
wordCache.put(address, result);
|
||||
return result;
|
||||
}
|
||||
|
@ -1269,44 +1269,47 @@ public class BinaryDictInputOutput {
|
|||
* This will recursively read other nodes into the structure, populating the reverse
|
||||
* maps on the fly and using them to keep track of already read nodes.
|
||||
*
|
||||
* @param source the data file, correctly positioned at the start of a node.
|
||||
* @param buffer the buffer, correctly positioned at the start of a node.
|
||||
* @param headerSize the size, in bytes, of the file header.
|
||||
* @param reverseNodeMap a mapping from addresses to already read nodes.
|
||||
* @param reverseGroupMap a mapping from addresses to already read character groups.
|
||||
* @return the read node with all his children already read.
|
||||
*/
|
||||
private static Node readNode(RandomAccessFile source, long headerSize,
|
||||
Map<Integer, Node> reverseNodeMap, Map<Integer, CharGroup> reverseGroupMap)
|
||||
private static Node readNode(final ByteBuffer buffer, final int headerSize,
|
||||
final Map<Integer, Node> reverseNodeMap, final Map<Integer, CharGroup> reverseGroupMap)
|
||||
throws IOException {
|
||||
final int nodeOrigin = (int)(source.getFilePointer() - headerSize);
|
||||
final int count = readCharGroupCount(source);
|
||||
final int nodeOrigin = buffer.position() - headerSize;
|
||||
final int count = readCharGroupCount(buffer);
|
||||
final ArrayList<CharGroup> nodeContents = new ArrayList<CharGroup>();
|
||||
int groupOffset = nodeOrigin + getGroupCountSize(count);
|
||||
for (int i = count; i > 0; --i) {
|
||||
CharGroupInfo info = readCharGroup(source, groupOffset);
|
||||
CharGroupInfo info =readCharGroup(buffer, groupOffset);
|
||||
ArrayList<WeightedString> shortcutTargets = info.mShortcutTargets;
|
||||
ArrayList<WeightedString> bigrams = null;
|
||||
if (null != info.mBigrams) {
|
||||
bigrams = new ArrayList<WeightedString>();
|
||||
for (PendingAttribute bigram : info.mBigrams) {
|
||||
final String word = getWordAtAddress(source, headerSize, bigram.mAddress);
|
||||
final String word = getWordAtAddress(
|
||||
buffer, headerSize, bigram.mAddress);
|
||||
bigrams.add(new WeightedString(word, bigram.mFrequency));
|
||||
}
|
||||
}
|
||||
if (hasChildrenAddress(info.mChildrenAddress)) {
|
||||
Node children = reverseNodeMap.get(info.mChildrenAddress);
|
||||
if (null == children) {
|
||||
final long currentPosition = source.getFilePointer();
|
||||
source.seek(info.mChildrenAddress + headerSize);
|
||||
children = readNode(source, headerSize, reverseNodeMap, reverseGroupMap);
|
||||
source.seek(currentPosition);
|
||||
final int currentPosition = buffer.position();
|
||||
buffer.position(info.mChildrenAddress + headerSize);
|
||||
children = readNode(
|
||||
buffer, headerSize, reverseNodeMap, reverseGroupMap);
|
||||
buffer.position(currentPosition);
|
||||
}
|
||||
nodeContents.add(
|
||||
new CharGroup(info.mCharacters, shortcutTargets, bigrams, info.mFrequency,
|
||||
children));
|
||||
new CharGroup(info.mCharacters, shortcutTargets,
|
||||
bigrams, info.mFrequency, children));
|
||||
} else {
|
||||
nodeContents.add(
|
||||
new CharGroup(info.mCharacters, shortcutTargets, bigrams, info.mFrequency));
|
||||
new CharGroup(info.mCharacters, shortcutTargets,
|
||||
bigrams, info.mFrequency));
|
||||
}
|
||||
groupOffset = info.mEndAddress;
|
||||
}
|
||||
|
@ -1318,57 +1321,76 @@ public class BinaryDictInputOutput {
|
|||
|
||||
/**
|
||||
* Helper function to get the binary format version from the header.
|
||||
* @throws IOException
|
||||
*/
|
||||
private static int getFormatVersion(final RandomAccessFile source) throws IOException {
|
||||
final int magic_v1 = source.readUnsignedShort();
|
||||
if (VERSION_1_MAGIC_NUMBER == magic_v1) return source.readUnsignedByte();
|
||||
final int magic_v2 = (magic_v1 << 16) + source.readUnsignedShort();
|
||||
if (VERSION_2_MAGIC_NUMBER == magic_v2) return source.readUnsignedShort();
|
||||
private static int getFormatVersion(final ByteBuffer buffer) throws IOException {
|
||||
final int magic_v1 = readUnsignedShort(buffer);
|
||||
if (VERSION_1_MAGIC_NUMBER == magic_v1) return readUnsignedByte(buffer);
|
||||
final int magic_v2 = (magic_v1 << 16) + readUnsignedShort(buffer);
|
||||
if (VERSION_2_MAGIC_NUMBER == magic_v2) return readUnsignedShort(buffer);
|
||||
return NOT_A_VERSION_NUMBER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a random access file and returns the memory representation of the dictionary.
|
||||
* Reads options from a file and populate a map with their contents.
|
||||
*
|
||||
* The file is read at the current file pointer, so the caller must take care the pointer
|
||||
* is in the right place before calling this.
|
||||
*/
|
||||
public static void populateOptions(final ByteBuffer buffer, final int headerSize,
|
||||
final HashMap<String, String> options) {
|
||||
while (buffer.position() < headerSize) {
|
||||
final String key = CharEncoding.readString(buffer);
|
||||
final String value = CharEncoding.readString(buffer);
|
||||
options.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a byte buffer and returns the memory representation of the dictionary.
|
||||
*
|
||||
* This high-level method takes a binary file and reads its contents, populating a
|
||||
* FusionDictionary structure. The optional dict argument is an existing dictionary to
|
||||
* which words from the file should be added. If it is null, a new dictionary is created.
|
||||
*
|
||||
* @param source the file to read.
|
||||
* @param buffer the buffer to read.
|
||||
* @param dict an optional dictionary to add words to, or null.
|
||||
* @return the created (or merged) dictionary.
|
||||
*/
|
||||
public static FusionDictionary readDictionaryBinary(final RandomAccessFile source,
|
||||
public static FusionDictionary readDictionaryBinary(final ByteBuffer buffer,
|
||||
final FusionDictionary dict) throws IOException, UnsupportedFormatException {
|
||||
// Check file version
|
||||
final int version = getFormatVersion(source);
|
||||
if (version < MINIMUM_SUPPORTED_VERSION || version > MAXIMUM_SUPPORTED_VERSION ) {
|
||||
final int version = getFormatVersion(buffer);
|
||||
if (version < MINIMUM_SUPPORTED_VERSION || version > MAXIMUM_SUPPORTED_VERSION) {
|
||||
throw new UnsupportedFormatException("This file has version " + version
|
||||
+ ", but this implementation does not support versions above "
|
||||
+ MAXIMUM_SUPPORTED_VERSION);
|
||||
}
|
||||
|
||||
// Read options
|
||||
final int optionsFlags = source.readUnsignedShort();
|
||||
// clear cache
|
||||
wordCache.clear();
|
||||
|
||||
final long headerSize;
|
||||
// Read options
|
||||
final int optionsFlags = readUnsignedShort(buffer);
|
||||
|
||||
final int headerSize;
|
||||
final HashMap<String, String> options = new HashMap<String, String>();
|
||||
if (version < FIRST_VERSION_WITH_HEADER_SIZE) {
|
||||
headerSize = source.getFilePointer();
|
||||
headerSize = buffer.position();
|
||||
} else {
|
||||
headerSize = (source.readUnsignedByte() << 24) + (source.readUnsignedByte() << 16)
|
||||
+ (source.readUnsignedByte() << 8) + source.readUnsignedByte();
|
||||
while (source.getFilePointer() < headerSize) {
|
||||
final String key = CharEncoding.readString(source);
|
||||
final String value = CharEncoding.readString(source);
|
||||
options.put(key, value);
|
||||
headerSize = buffer.getInt();
|
||||
populateOptions(buffer, headerSize, options);
|
||||
buffer.position(headerSize);
|
||||
}
|
||||
source.seek(headerSize);
|
||||
|
||||
if (headerSize < 0) {
|
||||
throw new UnsupportedFormatException("header size can't be negative.");
|
||||
}
|
||||
|
||||
Map<Integer, Node> reverseNodeMapping = new TreeMap<Integer, Node>();
|
||||
Map<Integer, CharGroup> reverseGroupMapping = new TreeMap<Integer, CharGroup>();
|
||||
final Node root = readNode(source, headerSize, reverseNodeMapping, reverseGroupMapping);
|
||||
final Node root = readNode(
|
||||
buffer, headerSize, reverseNodeMapping, reverseGroupMapping);
|
||||
|
||||
FusionDictionary newDict = new FusionDictionary(root,
|
||||
new FusionDictionary.DictionaryOptions(options,
|
||||
|
@ -1391,6 +1413,28 @@ public class BinaryDictInputOutput {
|
|||
return newDict;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to read one byte from ByteBuffer.
|
||||
*/
|
||||
private static int readUnsignedByte(final ByteBuffer buffer) {
|
||||
return ((int)buffer.get()) & 0xFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to read two byte from ByteBuffer.
|
||||
*/
|
||||
private static int readUnsignedShort(final ByteBuffer buffer) {
|
||||
return ((int)buffer.getShort()) & 0xFFFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to read three byte from ByteBuffer.
|
||||
*/
|
||||
private static int readUnsignedInt24(final ByteBuffer buffer) {
|
||||
final int value = readUnsignedByte(buffer) << 16;
|
||||
return value + readUnsignedShort(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic test to find out whether the file is a binary dictionary or not.
|
||||
*
|
||||
|
@ -1400,14 +1444,44 @@ public class BinaryDictInputOutput {
|
|||
* @return true if it's a binary dictionary, false otherwise
|
||||
*/
|
||||
public static boolean isBinaryDictionary(final String filename) {
|
||||
FileInputStream inStream = null;
|
||||
try {
|
||||
RandomAccessFile f = new RandomAccessFile(filename, "r");
|
||||
final int version = getFormatVersion(f);
|
||||
final File file = new File(filename);
|
||||
inStream = new FileInputStream(file);
|
||||
final ByteBuffer buffer = inStream.getChannel().map(
|
||||
FileChannel.MapMode.READ_ONLY, 0, file.length());
|
||||
final int version = getFormatVersion(buffer);
|
||||
return (version >= MINIMUM_SUPPORTED_VERSION && version <= MAXIMUM_SUPPORTED_VERSION);
|
||||
} catch (FileNotFoundException e) {
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
} finally {
|
||||
if (inStream != null) {
|
||||
try {
|
||||
inStream.close();
|
||||
} catch (IOException e) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate bigram frequency from compressed value
|
||||
*
|
||||
* @see #makeBigramFlags
|
||||
*
|
||||
* @param unigramFrequency
|
||||
* @param bigramFrequency compressed frequency
|
||||
* @return approximate bigram frequency
|
||||
*/
|
||||
public static int reconstructBigramFrequency(final int unigramFrequency,
|
||||
final int bigramFrequency) {
|
||||
final float stepSize = (MAX_TERMINAL_FREQUENCY - unigramFrequency)
|
||||
/ (1.5f + MAX_BIGRAM_FREQUENCY);
|
||||
final float resultFreqFloat = (float)unigramFrequency
|
||||
+ stepSize * (bigramFrequency + 1.0f);
|
||||
return (int)resultFreqFloat;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -516,13 +516,23 @@ public class FusionDictionary implements Iterable<Word> {
|
|||
int indexOfGroup = findIndexOfChar(node, s.codePointAt(index));
|
||||
if (CHARACTER_NOT_FOUND == indexOfGroup) return null;
|
||||
currentGroup = node.mData.get(indexOfGroup);
|
||||
|
||||
if (s.length() - index < currentGroup.mChars.length) return null;
|
||||
int newIndex = index;
|
||||
while (newIndex < s.length() && newIndex - index < currentGroup.mChars.length) {
|
||||
if (currentGroup.mChars[newIndex - index] != s.codePointAt(newIndex)) return null;
|
||||
newIndex++;
|
||||
}
|
||||
index = newIndex;
|
||||
|
||||
if (DBG) checker.append(new String(currentGroup.mChars, 0, currentGroup.mChars.length));
|
||||
index += currentGroup.mChars.length;
|
||||
if (index < s.length()) {
|
||||
node = currentGroup.mChildren;
|
||||
}
|
||||
} while (null != node && index < s.length());
|
||||
|
||||
if (index < s.length()) return null;
|
||||
if (!currentGroup.isTerminal()) return null;
|
||||
if (DBG && !s.equals(checker.toString())) return null;
|
||||
return currentGroup;
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import android.view.textservice.SuggestionsInfo;
|
|||
|
||||
import com.android.inputmethod.keyboard.ProximityInfo;
|
||||
import com.android.inputmethod.latin.BinaryDictionary;
|
||||
import com.android.inputmethod.latin.CollectionUtils;
|
||||
import com.android.inputmethod.latin.ContactsBinaryDictionary;
|
||||
import com.android.inputmethod.latin.Dictionary;
|
||||
import com.android.inputmethod.latin.DictionaryCollection;
|
||||
|
@ -35,7 +36,6 @@ import com.android.inputmethod.latin.StringUtils;
|
|||
import com.android.inputmethod.latin.SynchronouslyLoadedContactsBinaryDictionary;
|
||||
import com.android.inputmethod.latin.SynchronouslyLoadedUserBinaryDictionary;
|
||||
import com.android.inputmethod.latin.UserBinaryDictionary;
|
||||
import com.android.inputmethod.latin.WhitelistDictionary;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
|
@ -63,12 +63,9 @@ public class AndroidSpellCheckerService extends SpellCheckerService
|
|||
public static final int CAPITALIZE_ALL = 2; // All caps
|
||||
|
||||
private final static String[] EMPTY_STRING_ARRAY = new String[0];
|
||||
private Map<String, DictionaryPool> mDictionaryPools =
|
||||
Collections.synchronizedMap(new TreeMap<String, DictionaryPool>());
|
||||
private Map<String, DictionaryPool> mDictionaryPools = CollectionUtils.newSynchronizedTreeMap();
|
||||
private Map<String, UserBinaryDictionary> mUserDictionaries =
|
||||
Collections.synchronizedMap(new TreeMap<String, UserBinaryDictionary>());
|
||||
private Map<String, Dictionary> mWhitelistDictionaries =
|
||||
Collections.synchronizedMap(new TreeMap<String, Dictionary>());
|
||||
CollectionUtils.newSynchronizedTreeMap();
|
||||
private ContactsBinaryDictionary mContactsDictionary;
|
||||
|
||||
// The threshold for a candidate to be offered as a suggestion.
|
||||
|
@ -80,7 +77,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService
|
|||
private final Object mUseContactsLock = new Object();
|
||||
|
||||
private final HashSet<WeakReference<DictionaryCollection>> mDictionaryCollectionsList =
|
||||
new HashSet<WeakReference<DictionaryCollection>>();
|
||||
CollectionUtils.newHashSet();
|
||||
|
||||
public static final int SCRIPT_LATIN = 0;
|
||||
public static final int SCRIPT_CYRILLIC = 1;
|
||||
|
@ -96,7 +93,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService
|
|||
// proximity to pass to the dictionary descent algorithm.
|
||||
// IMPORTANT: this only contains languages - do not write countries in there.
|
||||
// Only the language is searched from the map.
|
||||
mLanguageToScript = new TreeMap<String, Integer>();
|
||||
mLanguageToScript = CollectionUtils.newTreeMap();
|
||||
mLanguageToScript.put("en", SCRIPT_LATIN);
|
||||
mLanguageToScript.put("fr", SCRIPT_LATIN);
|
||||
mLanguageToScript.put("de", SCRIPT_LATIN);
|
||||
|
@ -234,7 +231,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService
|
|||
mSuggestionThreshold = suggestionThreshold;
|
||||
mRecommendedThreshold = recommendedThreshold;
|
||||
mMaxLength = maxLength;
|
||||
mSuggestions = new ArrayList<CharSequence>(maxLength + 1);
|
||||
mSuggestions = CollectionUtils.newArrayList(maxLength + 1);
|
||||
mScores = new int[mMaxLength];
|
||||
}
|
||||
|
||||
|
@ -362,12 +359,9 @@ public class AndroidSpellCheckerService extends SpellCheckerService
|
|||
|
||||
private void closeAllDictionaries() {
|
||||
final Map<String, DictionaryPool> oldPools = mDictionaryPools;
|
||||
mDictionaryPools = Collections.synchronizedMap(new TreeMap<String, DictionaryPool>());
|
||||
mDictionaryPools = CollectionUtils.newSynchronizedTreeMap();
|
||||
final Map<String, UserBinaryDictionary> oldUserDictionaries = mUserDictionaries;
|
||||
mUserDictionaries =
|
||||
Collections.synchronizedMap(new TreeMap<String, UserBinaryDictionary>());
|
||||
final Map<String, Dictionary> oldWhitelistDictionaries = mWhitelistDictionaries;
|
||||
mWhitelistDictionaries = Collections.synchronizedMap(new TreeMap<String, Dictionary>());
|
||||
mUserDictionaries = CollectionUtils.newSynchronizedTreeMap();
|
||||
new Thread("spellchecker_close_dicts") {
|
||||
@Override
|
||||
public void run() {
|
||||
|
@ -377,9 +371,6 @@ public class AndroidSpellCheckerService extends SpellCheckerService
|
|||
for (Dictionary dict : oldUserDictionaries.values()) {
|
||||
dict.close();
|
||||
}
|
||||
for (Dictionary dict : oldWhitelistDictionaries.values()) {
|
||||
dict.close();
|
||||
}
|
||||
synchronized (mUseContactsLock) {
|
||||
if (null != mContactsDictionary) {
|
||||
// The synchronously loaded contacts dictionary should have been in one
|
||||
|
@ -423,12 +414,6 @@ public class AndroidSpellCheckerService extends SpellCheckerService
|
|||
mUserDictionaries.put(localeStr, userDictionary);
|
||||
}
|
||||
dictionaryCollection.addDictionary(userDictionary);
|
||||
Dictionary whitelistDictionary = mWhitelistDictionaries.get(localeStr);
|
||||
if (null == whitelistDictionary) {
|
||||
whitelistDictionary = new WhitelistDictionary(this, locale);
|
||||
mWhitelistDictionaries.put(localeStr, whitelistDictionary);
|
||||
}
|
||||
dictionaryCollection.addDictionary(whitelistDictionary);
|
||||
synchronized (mUseContactsLock) {
|
||||
if (mUseContactsDictionary) {
|
||||
if (null == mContactsDictionary) {
|
||||
|
|
|
@ -22,6 +22,8 @@ import android.view.textservice.SentenceSuggestionsInfo;
|
|||
import android.view.textservice.SuggestionsInfo;
|
||||
import android.view.textservice.TextInfo;
|
||||
|
||||
import com.android.inputmethod.latin.CollectionUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheckerSession {
|
||||
|
@ -40,10 +42,10 @@ public class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheckerSess
|
|||
return null;
|
||||
}
|
||||
final int N = ssi.getSuggestionsCount();
|
||||
final ArrayList<Integer> additionalOffsets = new ArrayList<Integer>();
|
||||
final ArrayList<Integer> additionalLengths = new ArrayList<Integer>();
|
||||
final ArrayList<Integer> additionalOffsets = CollectionUtils.newArrayList();
|
||||
final ArrayList<Integer> additionalLengths = CollectionUtils.newArrayList();
|
||||
final ArrayList<SuggestionsInfo> additionalSuggestionsInfos =
|
||||
new ArrayList<SuggestionsInfo>();
|
||||
CollectionUtils.newArrayList();
|
||||
String currentWord = null;
|
||||
for (int i = 0; i < N; ++i) {
|
||||
final SuggestionsInfo si = ssi.getSuggestionsInfoAt(i);
|
||||
|
|
|
@ -24,6 +24,7 @@ import android.view.textservice.SuggestionsInfo;
|
|||
import android.view.textservice.TextInfo;
|
||||
|
||||
import com.android.inputmethod.compat.SuggestionsInfoCompatUtils;
|
||||
import com.android.inputmethod.latin.Constants;
|
||||
import com.android.inputmethod.latin.LocaleUtils;
|
||||
import com.android.inputmethod.latin.WordComposer;
|
||||
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
|
||||
|
@ -193,8 +194,8 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
|
|||
if (shouldFilterOut(inText, mScript)) {
|
||||
DictAndProximity dictInfo = null;
|
||||
try {
|
||||
dictInfo = mDictionaryPool.takeOrGetNull();
|
||||
if (null == dictInfo) {
|
||||
dictInfo = mDictionaryPool.pollWithDefaultTimeout();
|
||||
if (!DictionaryPool.isAValidDictionary(dictInfo)) {
|
||||
return AndroidSpellCheckerService.getNotInDictEmptySuggestions();
|
||||
}
|
||||
return dictInfo.mDictionary.isValidWord(inText)
|
||||
|
@ -225,8 +226,8 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
|
|||
final int xy = SpellCheckerProximityInfo.getXYForCodePointAndScript(
|
||||
codePoint, mScript);
|
||||
if (SpellCheckerProximityInfo.NOT_A_COORDINATE_PAIR == xy) {
|
||||
composer.add(codePoint, WordComposer.NOT_A_COORDINATE,
|
||||
WordComposer.NOT_A_COORDINATE);
|
||||
composer.add(codePoint,
|
||||
Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
|
||||
} else {
|
||||
composer.add(codePoint, xy & 0xFFFF, xy >> 16);
|
||||
}
|
||||
|
@ -236,8 +237,8 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
|
|||
boolean isInDict = true;
|
||||
DictAndProximity dictInfo = null;
|
||||
try {
|
||||
dictInfo = mDictionaryPool.takeOrGetNull();
|
||||
if (null == dictInfo) {
|
||||
dictInfo = mDictionaryPool.pollWithDefaultTimeout();
|
||||
if (!DictionaryPool.isAValidDictionary(dictInfo)) {
|
||||
return AndroidSpellCheckerService.getNotInDictEmptySuggestions();
|
||||
}
|
||||
final ArrayList<SuggestedWordInfo> suggestions =
|
||||
|
|
|
@ -16,19 +16,56 @@
|
|||
|
||||
package com.android.inputmethod.latin.spellcheck;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.inputmethod.keyboard.ProximityInfo;
|
||||
import com.android.inputmethod.latin.CollectionUtils;
|
||||
import com.android.inputmethod.latin.Dictionary;
|
||||
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
|
||||
import com.android.inputmethod.latin.WordComposer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* A blocking queue that creates dictionaries up to a certain limit as necessary.
|
||||
* As a deadlock-detecting device, if waiting for more than TIMEOUT = 3 seconds, we
|
||||
* will clear the queue and generate its contents again. This is transparent for
|
||||
* the client code, but may help with sloppy clients.
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> {
|
||||
private final static String TAG = DictionaryPool.class.getSimpleName();
|
||||
// How many seconds we wait for a dictionary to become available. Past this delay, we give up in
|
||||
// fear some bug caused a deadlock, and reset the whole pool.
|
||||
private final static int TIMEOUT = 3;
|
||||
private final AndroidSpellCheckerService mService;
|
||||
private final int mMaxSize;
|
||||
private final Locale mLocale;
|
||||
private int mSize;
|
||||
private volatile boolean mClosed;
|
||||
final static ArrayList<SuggestedWordInfo> noSuggestions = CollectionUtils.newArrayList();
|
||||
private final static DictAndProximity dummyDict = new DictAndProximity(
|
||||
new Dictionary(Dictionary.TYPE_MAIN) {
|
||||
@Override
|
||||
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
|
||||
final CharSequence prevWord, final ProximityInfo proximityInfo) {
|
||||
return noSuggestions;
|
||||
}
|
||||
@Override
|
||||
public boolean isValidWord(CharSequence word) {
|
||||
// This is never called. However if for some strange reason it ever gets
|
||||
// called, returning true is less destructive (it will not underline the
|
||||
// word in red).
|
||||
return true;
|
||||
}
|
||||
}, null);
|
||||
|
||||
static public boolean isAValidDictionary(final DictAndProximity dictInfo) {
|
||||
return null != dictInfo && dummyDict != dictInfo;
|
||||
}
|
||||
|
||||
public DictionaryPool(final int maxSize, final AndroidSpellCheckerService service,
|
||||
final Locale locale) {
|
||||
|
@ -41,13 +78,23 @@ public class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public DictAndProximity take() throws InterruptedException {
|
||||
public DictAndProximity poll(final long timeout, final TimeUnit unit)
|
||||
throws InterruptedException {
|
||||
final DictAndProximity dict = poll();
|
||||
if (null != dict) return dict;
|
||||
synchronized(this) {
|
||||
if (mSize >= mMaxSize) {
|
||||
// Our pool is already full. Wait until some dictionary is ready.
|
||||
return super.take();
|
||||
// Our pool is already full. Wait until some dictionary is ready, or TIMEOUT
|
||||
// expires to avoid a deadlock.
|
||||
final DictAndProximity result = super.poll(timeout, unit);
|
||||
if (null == result) {
|
||||
Log.e(TAG, "Deadlock detected ! Resetting dictionary pool");
|
||||
clear();
|
||||
mSize = 1;
|
||||
return mService.createDictAndProximity(mLocale);
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
++mSize;
|
||||
return mService.createDictAndProximity(mLocale);
|
||||
|
@ -56,9 +103,9 @@ public class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> {
|
|||
}
|
||||
|
||||
// Convenience method
|
||||
public DictAndProximity takeOrGetNull() {
|
||||
public DictAndProximity pollWithDefaultTimeout() {
|
||||
try {
|
||||
return take();
|
||||
return poll(TIMEOUT, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
return null;
|
||||
}
|
||||
|
@ -78,7 +125,7 @@ public class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> {
|
|||
public boolean offer(final DictAndProximity dict) {
|
||||
if (mClosed) {
|
||||
dict.mDictionary.close();
|
||||
return false;
|
||||
return super.offer(dummyDict);
|
||||
} else {
|
||||
return super.offer(dict);
|
||||
}
|
||||
|
|
|
@ -16,14 +16,15 @@
|
|||
|
||||
package com.android.inputmethod.latin.spellcheck;
|
||||
|
||||
import com.android.inputmethod.keyboard.KeyDetector;
|
||||
import com.android.inputmethod.keyboard.ProximityInfo;
|
||||
import com.android.inputmethod.latin.CollectionUtils;
|
||||
import com.android.inputmethod.latin.Constants;
|
||||
|
||||
import java.util.TreeMap;
|
||||
|
||||
public class SpellCheckerProximityInfo {
|
||||
/* public for test */
|
||||
final public static int NUL = KeyDetector.NOT_A_CODE;
|
||||
final public static int NUL = Constants.NOT_A_CODE;
|
||||
|
||||
// This must be the same as MAX_PROXIMITY_CHARS_SIZE else it will not work inside
|
||||
// native code - this value is passed at creation of the binary object and reused
|
||||
|
@ -59,7 +60,7 @@ public class SpellCheckerProximityInfo {
|
|||
// character.
|
||||
// Since we need to build such an array, we want to be able to search in our big proximity
|
||||
// data quickly by character, and a map is probably the best way to do this.
|
||||
final private static TreeMap<Integer, Integer> INDICES = new TreeMap<Integer, Integer>();
|
||||
final private static TreeMap<Integer, Integer> INDICES = CollectionUtils.newTreeMap();
|
||||
|
||||
// The proximity here is the union of
|
||||
// - the proximity for a QWERTY keyboard.
|
||||
|
@ -111,6 +112,7 @@ public class SpellCheckerProximityInfo {
|
|||
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
|
||||
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
|
||||
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
|
||||
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
|
||||
};
|
||||
static {
|
||||
buildProximityIndices(PROXIMITY, INDICES);
|
||||
|
@ -121,7 +123,7 @@ public class SpellCheckerProximityInfo {
|
|||
}
|
||||
|
||||
private static class Cyrillic {
|
||||
final private static TreeMap<Integer, Integer> INDICES = new TreeMap<Integer, Integer>();
|
||||
final private static TreeMap<Integer, Integer> INDICES = CollectionUtils.newTreeMap();
|
||||
// TODO: The following table is solely based on the keyboard layout. Consult with Russian
|
||||
// speakers on commonly misspelled words/letters.
|
||||
final static int[] PROXIMITY = {
|
||||
|
|
|
@ -58,6 +58,7 @@ import com.android.inputmethod.keyboard.MoreKeysPanel;
|
|||
import com.android.inputmethod.keyboard.PointerTracker;
|
||||
import com.android.inputmethod.keyboard.ViewLayoutUtils;
|
||||
import com.android.inputmethod.latin.AutoCorrection;
|
||||
import com.android.inputmethod.latin.CollectionUtils;
|
||||
import com.android.inputmethod.latin.LatinImeLogger;
|
||||
import com.android.inputmethod.latin.R;
|
||||
import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
|
||||
|
@ -72,7 +73,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
|
|||
OnLongClickListener {
|
||||
public interface Listener {
|
||||
public boolean addWordToUserDictionary(String word);
|
||||
public void pickSuggestionManually(int index, CharSequence word, int x, int y);
|
||||
public void pickSuggestionManually(int index, CharSequence word);
|
||||
}
|
||||
|
||||
// The maximum number of suggestions available. See {@link Suggest#mPrefMaxSuggestions}.
|
||||
|
@ -88,9 +89,9 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
|
|||
private final MoreSuggestions.Builder mMoreSuggestionsBuilder;
|
||||
private final PopupWindow mMoreSuggestionsWindow;
|
||||
|
||||
private final ArrayList<TextView> mWords = new ArrayList<TextView>();
|
||||
private final ArrayList<TextView> mInfos = new ArrayList<TextView>();
|
||||
private final ArrayList<View> mDividers = new ArrayList<View>();
|
||||
private final ArrayList<TextView> mWords = CollectionUtils.newArrayList();
|
||||
private final ArrayList<TextView> mInfos = CollectionUtils.newArrayList();
|
||||
private final ArrayList<View> mDividers = CollectionUtils.newArrayList();
|
||||
|
||||
private final PopupWindow mPreviewPopup;
|
||||
private final TextView mPreviewText;
|
||||
|
@ -131,7 +132,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
|
|||
|
||||
private static class SuggestionStripViewParams {
|
||||
private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3;
|
||||
private static final int DEFAULT_CENTER_SUGGESTION_PERCENTILE = 40;
|
||||
private static final float DEFAULT_CENTER_SUGGESTION_PERCENTILE = 0.40f;
|
||||
private static final int DEFAULT_MAX_MORE_SUGGESTIONS_ROW = 2;
|
||||
private static final int PUNCTUATIONS_IN_STRIP = 5;
|
||||
|
||||
|
@ -167,7 +168,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
|
|||
|
||||
private final int mSuggestionStripOption;
|
||||
|
||||
private final ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>();
|
||||
private final ArrayList<CharSequence> mTexts = CollectionUtils.newArrayList();
|
||||
|
||||
public boolean mMoreSuggestionsAvailable;
|
||||
|
||||
|
@ -195,16 +196,16 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
|
|||
R.styleable.SuggestionStripView, defStyle, R.style.SuggestionStripViewStyle);
|
||||
mSuggestionStripOption = a.getInt(
|
||||
R.styleable.SuggestionStripView_suggestionStripOption, 0);
|
||||
final float alphaValidTypedWord = getPercent(a,
|
||||
R.styleable.SuggestionStripView_alphaValidTypedWord, 100);
|
||||
final float alphaTypedWord = getPercent(a,
|
||||
R.styleable.SuggestionStripView_alphaTypedWord, 100);
|
||||
final float alphaAutoCorrect = getPercent(a,
|
||||
R.styleable.SuggestionStripView_alphaAutoCorrect, 100);
|
||||
final float alphaSuggested = getPercent(a,
|
||||
R.styleable.SuggestionStripView_alphaSuggested, 100);
|
||||
mAlphaObsoleted = getPercent(a,
|
||||
R.styleable.SuggestionStripView_alphaSuggested, 100);
|
||||
final float alphaValidTypedWord = getFraction(a,
|
||||
R.styleable.SuggestionStripView_alphaValidTypedWord, 1.0f);
|
||||
final float alphaTypedWord = getFraction(a,
|
||||
R.styleable.SuggestionStripView_alphaTypedWord, 1.0f);
|
||||
final float alphaAutoCorrect = getFraction(a,
|
||||
R.styleable.SuggestionStripView_alphaAutoCorrect, 1.0f);
|
||||
final float alphaSuggested = getFraction(a,
|
||||
R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
|
||||
mAlphaObsoleted = getFraction(a,
|
||||
R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
|
||||
mColorValidTypedWord = applyAlpha(a.getColor(
|
||||
R.styleable.SuggestionStripView_colorValidTypedWord, 0), alphaValidTypedWord);
|
||||
mColorTypedWord = applyAlpha(a.getColor(
|
||||
|
@ -216,14 +217,14 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
|
|||
mSuggestionsCountInStrip = a.getInt(
|
||||
R.styleable.SuggestionStripView_suggestionsCountInStrip,
|
||||
DEFAULT_SUGGESTIONS_COUNT_IN_STRIP);
|
||||
mCenterSuggestionWeight = getPercent(a,
|
||||
mCenterSuggestionWeight = getFraction(a,
|
||||
R.styleable.SuggestionStripView_centerSuggestionPercentile,
|
||||
DEFAULT_CENTER_SUGGESTION_PERCENTILE);
|
||||
mMaxMoreSuggestionsRow = a.getInt(
|
||||
R.styleable.SuggestionStripView_maxMoreSuggestionsRow,
|
||||
DEFAULT_MAX_MORE_SUGGESTIONS_ROW);
|
||||
mMinMoreSuggestionsWidth = getRatio(a,
|
||||
R.styleable.SuggestionStripView_minMoreSuggestionsWidth);
|
||||
mMinMoreSuggestionsWidth = getFraction(a,
|
||||
R.styleable.SuggestionStripView_minMoreSuggestionsWidth, 1.0f);
|
||||
a.recycle();
|
||||
|
||||
mMoreSuggestionsHint = getMoreSuggestionsHint(res,
|
||||
|
@ -277,14 +278,8 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
|
|||
return new BitmapDrawable(res, buffer);
|
||||
}
|
||||
|
||||
// Read integer value in TypedArray as percent.
|
||||
private static float getPercent(TypedArray a, int index, int defValue) {
|
||||
return a.getInt(index, defValue) / 100.0f;
|
||||
}
|
||||
|
||||
// Read fraction value in TypedArray as float.
|
||||
private static float getRatio(TypedArray a, int index) {
|
||||
return a.getFraction(index, 1000, 1000, 1) / 1000.0f;
|
||||
static float getFraction(final TypedArray a, final int index, final float defValue) {
|
||||
return a.getFraction(index, 1, 1, defValue);
|
||||
}
|
||||
|
||||
private CharSequence getStyledSuggestionWord(SuggestedWords suggestedWords, int pos) {
|
||||
|
@ -726,9 +721,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
|
|||
public boolean onCustomRequest(int requestCode) {
|
||||
final int index = requestCode;
|
||||
final CharSequence word = mSuggestedWords.getWord(index);
|
||||
// TODO: change caller path so coordinates are passed through here
|
||||
mListener.pickSuggestionManually(index, word, NOT_A_TOUCH_COORDINATE,
|
||||
NOT_A_TOUCH_COORDINATE);
|
||||
mListener.pickSuggestionManually(index, word);
|
||||
dismissMoreSuggestions();
|
||||
return true;
|
||||
}
|
||||
|
@ -874,7 +867,7 @@ public class SuggestionStripView extends RelativeLayout implements OnClickListen
|
|||
return;
|
||||
|
||||
final CharSequence word = mSuggestedWords.getWord(index);
|
||||
mListener.pickSuggestionManually(index, word, mLastX, mLastY);
|
||||
mListener.pickSuggestionManually(index, word);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.view.View;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
|
||||
import com.android.inputmethod.latin.R;
|
||||
|
||||
|
@ -31,6 +28,11 @@ public class FeedbackActivity extends Activity {
|
|||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.research_feedback_activity);
|
||||
final FeedbackLayout layout = (FeedbackLayout) findViewById(R.id.research_feedback_layout);
|
||||
final CheckBox checkbox = (CheckBox) findViewById(R.id.research_feedback_include_history);
|
||||
final CharSequence cs = checkbox.getText();
|
||||
final String actualString = String.format(cs.toString(),
|
||||
ResearchLogger.FEEDBACK_WORD_BUFFER_SIZE);
|
||||
checkbox.setText(actualString);
|
||||
layout.setActivity(this);
|
||||
}
|
||||
|
||||
|
|
|
@ -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.SuggestedWordInfo;
|
||||
import com.android.inputmethod.latin.define.ProductionFlag;
|
||||
import com.android.inputmethod.research.ResearchLogger.LogUnit;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
|
@ -37,6 +36,7 @@ import java.io.OutputStreamWriter;
|
|||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -51,21 +51,22 @@ import java.util.concurrent.TimeUnit;
|
|||
*/
|
||||
public class ResearchLog {
|
||||
private static final String TAG = ResearchLog.class.getSimpleName();
|
||||
private static final JsonWriter NULL_JSON_WRITER = new JsonWriter(
|
||||
new OutputStreamWriter(new NullOutputStream()));
|
||||
private static final boolean DEBUG = false;
|
||||
private static final long FLUSH_DELAY_IN_MS = 1000 * 5;
|
||||
private static final int ABORT_TIMEOUT_IN_MS = 1000 * 4;
|
||||
|
||||
final ScheduledExecutorService mExecutor;
|
||||
/* package */ final ScheduledExecutorService mExecutor;
|
||||
/* package */ final File mFile;
|
||||
private JsonWriter mJsonWriter = NULL_JSON_WRITER;
|
||||
// true if at least one byte of data has been written out to the log file. This must be
|
||||
// remembered because JsonWriter requires that calls matching calls to beginObject and
|
||||
// endObject, as well as beginArray and endArray, and the file is opened lazily, only when
|
||||
// it is certain that data will be written. Alternatively, the matching call exceptions
|
||||
// could be caught, but this might suppress other errors.
|
||||
private boolean mHasWrittenData = false;
|
||||
|
||||
private int mLoggingState;
|
||||
private static final int LOGGING_STATE_UNSTARTED = 0;
|
||||
private static final int LOGGING_STATE_READY = 1; // don't create file until necessary
|
||||
private static final int LOGGING_STATE_RUNNING = 2;
|
||||
private static final int LOGGING_STATE_STOPPING = 3;
|
||||
private static final int LOGGING_STATE_STOPPED = 4;
|
||||
private static final long FLUSH_DELAY_IN_MS = 1000 * 5;
|
||||
|
||||
private static final JsonWriter NULL_JSON_WRITER = new JsonWriter(
|
||||
new OutputStreamWriter(new NullOutputStream()));
|
||||
private static class NullOutputStream extends OutputStream {
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
|
@ -84,128 +85,81 @@ public class ResearchLog {
|
|||
}
|
||||
}
|
||||
|
||||
public ResearchLog(File outputFile) {
|
||||
mExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||
public ResearchLog(final File outputFile) {
|
||||
if (outputFile == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
mExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||
mFile = outputFile;
|
||||
mLoggingState = LOGGING_STATE_UNSTARTED;
|
||||
}
|
||||
|
||||
public synchronized void start() throws IOException {
|
||||
switch (mLoggingState) {
|
||||
case LOGGING_STATE_UNSTARTED:
|
||||
mLoggingState = LOGGING_STATE_READY;
|
||||
break;
|
||||
case LOGGING_STATE_READY:
|
||||
case LOGGING_STATE_RUNNING:
|
||||
case LOGGING_STATE_STOPPING:
|
||||
case LOGGING_STATE_STOPPED:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void stop() {
|
||||
switch (mLoggingState) {
|
||||
case LOGGING_STATE_UNSTARTED:
|
||||
mLoggingState = LOGGING_STATE_STOPPED;
|
||||
break;
|
||||
case LOGGING_STATE_READY:
|
||||
case LOGGING_STATE_RUNNING:
|
||||
public synchronized void close() {
|
||||
mExecutor.submit(new Callable<Object>() {
|
||||
@Override
|
||||
public Object call() throws Exception {
|
||||
try {
|
||||
if (mHasWrittenData) {
|
||||
mJsonWriter.endArray();
|
||||
mJsonWriter.flush();
|
||||
mJsonWriter.close();
|
||||
mHasWrittenData = false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.d(TAG, "error when closing ResearchLog:");
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
boolean success = mFile.setWritable(false, false);
|
||||
mLoggingState = LOGGING_STATE_STOPPED;
|
||||
if (mFile.exists()) {
|
||||
mFile.setWritable(false, false);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
removeAnyScheduledFlush();
|
||||
mExecutor.shutdown();
|
||||
mLoggingState = LOGGING_STATE_STOPPING;
|
||||
break;
|
||||
case LOGGING_STATE_STOPPING:
|
||||
case LOGGING_STATE_STOPPED:
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAlive() {
|
||||
switch (mLoggingState) {
|
||||
case LOGGING_STATE_UNSTARTED:
|
||||
case LOGGING_STATE_READY:
|
||||
case LOGGING_STATE_RUNNING:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void waitUntilStopped(final int timeoutInMs) throws InterruptedException {
|
||||
removeAnyScheduledFlush();
|
||||
mExecutor.shutdown();
|
||||
mExecutor.awaitTermination(timeoutInMs, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
private boolean mIsAbortSuccessful;
|
||||
|
||||
public synchronized void abort() {
|
||||
switch (mLoggingState) {
|
||||
case LOGGING_STATE_UNSTARTED:
|
||||
mLoggingState = LOGGING_STATE_STOPPED;
|
||||
isAbortSuccessful = true;
|
||||
break;
|
||||
case LOGGING_STATE_READY:
|
||||
case LOGGING_STATE_RUNNING:
|
||||
mExecutor.submit(new Callable<Object>() {
|
||||
@Override
|
||||
public Object call() throws Exception {
|
||||
try {
|
||||
if (mHasWrittenData) {
|
||||
mJsonWriter.endArray();
|
||||
mJsonWriter.close();
|
||||
mHasWrittenData = false;
|
||||
}
|
||||
} finally {
|
||||
isAbortSuccessful = mFile.delete();
|
||||
mIsAbortSuccessful = mFile.delete();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
removeAnyScheduledFlush();
|
||||
mExecutor.shutdown();
|
||||
mLoggingState = LOGGING_STATE_STOPPING;
|
||||
break;
|
||||
case LOGGING_STATE_STOPPING:
|
||||
case LOGGING_STATE_STOPPED:
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAbortSuccessful;
|
||||
public boolean isAbortSuccessful() {
|
||||
return isAbortSuccessful;
|
||||
public boolean blockingAbort() throws InterruptedException {
|
||||
abort();
|
||||
mExecutor.awaitTermination(ABORT_TIMEOUT_IN_MS, TimeUnit.MILLISECONDS);
|
||||
return mIsAbortSuccessful;
|
||||
}
|
||||
|
||||
public void awaitTermination(int delay, TimeUnit timeUnit) throws InterruptedException {
|
||||
mExecutor.awaitTermination(delay, timeUnit);
|
||||
}
|
||||
|
||||
/* package */ synchronized void flush() {
|
||||
switch (mLoggingState) {
|
||||
case LOGGING_STATE_UNSTARTED:
|
||||
break;
|
||||
case LOGGING_STATE_READY:
|
||||
case LOGGING_STATE_RUNNING:
|
||||
removeAnyScheduledFlush();
|
||||
mExecutor.submit(mFlushCallable);
|
||||
break;
|
||||
case LOGGING_STATE_STOPPING:
|
||||
case LOGGING_STATE_STOPPED:
|
||||
}
|
||||
}
|
||||
|
||||
private Callable<Object> mFlushCallable = new Callable<Object>() {
|
||||
private final Callable<Object> mFlushCallable = new Callable<Object>() {
|
||||
@Override
|
||||
public Object call() throws Exception {
|
||||
if (mLoggingState == LOGGING_STATE_RUNNING) {
|
||||
mJsonWriter.flush();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
@ -224,56 +178,40 @@ public class ResearchLog {
|
|||
mFlushFuture = mExecutor.schedule(mFlushCallable, FLUSH_DELAY_IN_MS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public synchronized void publishPublicEvents(final LogUnit logUnit) {
|
||||
switch (mLoggingState) {
|
||||
case LOGGING_STATE_UNSTARTED:
|
||||
break;
|
||||
case LOGGING_STATE_READY:
|
||||
case LOGGING_STATE_RUNNING:
|
||||
public synchronized void publish(final LogUnit logUnit, final boolean isIncludingPrivateData) {
|
||||
try {
|
||||
mExecutor.submit(new Callable<Object>() {
|
||||
@Override
|
||||
public Object call() throws Exception {
|
||||
logUnit.publishPublicEventsTo(ResearchLog.this);
|
||||
logUnit.publishTo(ResearchLog.this, isIncludingPrivateData);
|
||||
scheduleFlush();
|
||||
return null;
|
||||
}
|
||||
});
|
||||
break;
|
||||
case LOGGING_STATE_STOPPING:
|
||||
case LOGGING_STATE_STOPPED:
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void publishAllEvents(final LogUnit logUnit) {
|
||||
switch (mLoggingState) {
|
||||
case LOGGING_STATE_UNSTARTED:
|
||||
break;
|
||||
case LOGGING_STATE_READY:
|
||||
case LOGGING_STATE_RUNNING:
|
||||
mExecutor.submit(new Callable<Object>() {
|
||||
@Override
|
||||
public Object call() throws Exception {
|
||||
logUnit.publishAllEventsTo(ResearchLog.this);
|
||||
scheduleFlush();
|
||||
return null;
|
||||
}
|
||||
});
|
||||
break;
|
||||
case LOGGING_STATE_STOPPING:
|
||||
case LOGGING_STATE_STOPPED:
|
||||
} catch (RejectedExecutionException e) {
|
||||
// TODO: Add code to record loss of data, and report.
|
||||
}
|
||||
}
|
||||
|
||||
private static final String CURRENT_TIME_KEY = "_ct";
|
||||
private static final String UPTIME_KEY = "_ut";
|
||||
private static final String EVENT_TYPE_KEY = "_ty";
|
||||
|
||||
void outputEvent(final String[] keys, final Object[] values) {
|
||||
// not thread safe.
|
||||
// Not thread safe.
|
||||
if (keys.length == 0) {
|
||||
return;
|
||||
}
|
||||
if (DEBUG) {
|
||||
if (keys.length != values.length + 1) {
|
||||
Log.d(TAG, "Key and Value list sizes do not match. " + keys[0]);
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (mJsonWriter == NULL_JSON_WRITER) {
|
||||
mJsonWriter = new JsonWriter(new BufferedWriter(new FileWriter(mFile)));
|
||||
mJsonWriter.setLenient(true);
|
||||
mJsonWriter.beginArray();
|
||||
mHasWrittenData = true;
|
||||
}
|
||||
mJsonWriter.beginObject();
|
||||
mJsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis());
|
||||
|
@ -283,8 +221,8 @@ public class ResearchLog {
|
|||
for (int i = 0; i < length; i++) {
|
||||
mJsonWriter.name(keys[i + 1]);
|
||||
Object value = values[i];
|
||||
if (value instanceof String) {
|
||||
mJsonWriter.value((String) value);
|
||||
if (value instanceof CharSequence) {
|
||||
mJsonWriter.value(value.toString());
|
||||
} else if (value instanceof Number) {
|
||||
mJsonWriter.value((Number) value);
|
||||
} else if (value instanceof Boolean) {
|
||||
|
@ -331,14 +269,11 @@ public class ResearchLog {
|
|||
SuggestedWords words = (SuggestedWords) value;
|
||||
mJsonWriter.beginObject();
|
||||
mJsonWriter.name("typedWordValid").value(words.mTypedWordValid);
|
||||
mJsonWriter.name("willAutoCorrect")
|
||||
.value(words.mWillAutoCorrect);
|
||||
mJsonWriter.name("willAutoCorrect").value(words.mWillAutoCorrect);
|
||||
mJsonWriter.name("isPunctuationSuggestions")
|
||||
.value(words.mIsPunctuationSuggestions);
|
||||
mJsonWriter.name("isObsoleteSuggestions")
|
||||
.value(words.mIsObsoleteSuggestions);
|
||||
mJsonWriter.name("isPrediction")
|
||||
.value(words.mIsPrediction);
|
||||
mJsonWriter.name("isObsoleteSuggestions").value(words.mIsObsoleteSuggestions);
|
||||
mJsonWriter.name("isPrediction").value(words.mIsPrediction);
|
||||
mJsonWriter.name("words");
|
||||
mJsonWriter.beginArray();
|
||||
final int size = words.size();
|
||||
|
@ -363,8 +298,8 @@ public class ResearchLog {
|
|||
try {
|
||||
mJsonWriter.close();
|
||||
} catch (IllegalStateException e1) {
|
||||
// assume that this is just the json not being terminated properly.
|
||||
// ignore
|
||||
// Assume that this is just the json not being terminated properly.
|
||||
// Ignore
|
||||
} catch (IOException e1) {
|
||||
e1.printStackTrace();
|
||||
} finally {
|
||||
|
|
|
@ -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 := \
|
||||
com_android_inputmethod_keyboard_ProximityInfo.cpp \
|
||||
com_android_inputmethod_latin_BinaryDictionary.cpp \
|
||||
com_android_inputmethod_latin_NativeUtils.cpp \
|
||||
com_android_inputmethod_latin_DicTraverseSession.cpp \
|
||||
jni_common.cpp
|
||||
|
||||
LATIN_IME_CORE_SRC_FILES := \
|
||||
|
@ -46,6 +46,7 @@ LATIN_IME_CORE_SRC_FILES := \
|
|||
char_utils.cpp \
|
||||
correction.cpp \
|
||||
dictionary.cpp \
|
||||
dic_traverse_wrapper.cpp \
|
||||
proximity_info.cpp \
|
||||
proximity_info_state.cpp \
|
||||
unigram_dictionary.cpp \
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
|
||||
#define LOG_TAG "LatinIME: jni: ProximityInfo"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "com_android_inputmethod_keyboard_ProximityInfo.h"
|
||||
#include "jni.h"
|
||||
#include "jni_common.h"
|
||||
|
@ -26,53 +24,27 @@
|
|||
namespace latinime {
|
||||
|
||||
static jlong latinime_Keyboard_setProximityInfo(JNIEnv *env, jobject object,
|
||||
jstring localejStr, jint maxProximityCharsSize, jint displayWidth, jint displayHeight,
|
||||
jint gridWidth, jint gridHeight, jint mostCommonkeyWidth, jintArray proximityCharsArray,
|
||||
jint keyCount, jintArray keyXCoordinateArray, jintArray keyYCoordinateArray,
|
||||
jintArray keyWidthArray, jintArray keyHeightArray, jintArray keyCharCodeArray,
|
||||
jfloatArray sweetSpotCenterXArray, jfloatArray sweetSpotCenterYArray,
|
||||
jfloatArray sweetSpotRadiusArray) {
|
||||
const char *localeStrPtr = env->GetStringUTFChars(localejStr, 0);
|
||||
const std::string localeStr(localeStrPtr);
|
||||
jint *proximityChars = env->GetIntArrayElements(proximityCharsArray, 0);
|
||||
jint *keyXCoordinates = safeGetIntArrayElements(env, keyXCoordinateArray);
|
||||
jint *keyYCoordinates = safeGetIntArrayElements(env, keyYCoordinateArray);
|
||||
jint *keyWidths = safeGetIntArrayElements(env, keyWidthArray);
|
||||
jint *keyHeights = safeGetIntArrayElements(env, keyHeightArray);
|
||||
jint *keyCharCodes = safeGetIntArrayElements(env, keyCharCodeArray);
|
||||
jfloat *sweetSpotCenterXs = safeGetFloatArrayElements(env, sweetSpotCenterXArray);
|
||||
jfloat *sweetSpotCenterYs = safeGetFloatArrayElements(env, sweetSpotCenterYArray);
|
||||
jfloat *sweetSpotRadii = safeGetFloatArrayElements(env, sweetSpotRadiusArray);
|
||||
ProximityInfo *proximityInfo = new ProximityInfo(
|
||||
localeStr, maxProximityCharsSize, displayWidth, displayHeight, gridWidth, gridHeight,
|
||||
mostCommonkeyWidth, (const int32_t*)proximityChars, keyCount,
|
||||
(const int32_t*)keyXCoordinates, (const int32_t*)keyYCoordinates,
|
||||
(const int32_t*)keyWidths, (const int32_t*)keyHeights, (const int32_t*)keyCharCodes,
|
||||
(const float*)sweetSpotCenterXs, (const float*)sweetSpotCenterYs,
|
||||
(const float*)sweetSpotRadii);
|
||||
safeReleaseFloatArrayElements(env, sweetSpotRadiusArray, sweetSpotRadii);
|
||||
safeReleaseFloatArrayElements(env, sweetSpotCenterYArray, sweetSpotCenterYs);
|
||||
safeReleaseFloatArrayElements(env, sweetSpotCenterXArray, sweetSpotCenterXs);
|
||||
safeReleaseIntArrayElements(env, keyCharCodeArray, keyCharCodes);
|
||||
safeReleaseIntArrayElements(env, keyHeightArray, keyHeights);
|
||||
safeReleaseIntArrayElements(env, keyWidthArray, keyWidths);
|
||||
safeReleaseIntArrayElements(env, keyYCoordinateArray, keyYCoordinates);
|
||||
safeReleaseIntArrayElements(env, keyXCoordinateArray, keyXCoordinates);
|
||||
env->ReleaseIntArrayElements(proximityCharsArray, proximityChars, 0);
|
||||
env->ReleaseStringUTFChars(localejStr, localeStrPtr);
|
||||
return (jlong)proximityInfo;
|
||||
jstring localeJStr, jint maxProximityCharsSize, jint displayWidth, jint displayHeight,
|
||||
jint gridWidth, jint gridHeight, jint mostCommonkeyWidth, jintArray proximityChars,
|
||||
jint keyCount, jintArray keyXCoordinates, jintArray keyYCoordinates,
|
||||
jintArray keyWidths, jintArray keyHeights, jintArray keyCharCodes,
|
||||
jfloatArray sweetSpotCenterXs, jfloatArray sweetSpotCenterYs, jfloatArray sweetSpotRadii) {
|
||||
ProximityInfo *proximityInfo = new ProximityInfo(env, localeJStr, maxProximityCharsSize,
|
||||
displayWidth, displayHeight, gridWidth, gridHeight, mostCommonkeyWidth, proximityChars,
|
||||
keyCount, keyXCoordinates, keyYCoordinates, keyWidths, keyHeights, keyCharCodes,
|
||||
sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii);
|
||||
return reinterpret_cast<jlong>(proximityInfo);
|
||||
}
|
||||
|
||||
static void latinime_Keyboard_release(JNIEnv *env, jobject object, jlong proximityInfo) {
|
||||
ProximityInfo *pi = (ProximityInfo*)proximityInfo;
|
||||
if (!pi) return;
|
||||
ProximityInfo *pi = reinterpret_cast<ProximityInfo *>(proximityInfo);
|
||||
delete pi;
|
||||
}
|
||||
|
||||
static JNINativeMethod sKeyboardMethods[] = {
|
||||
{"setProximityInfoNative", "(Ljava/lang/String;IIIIII[II[I[I[I[I[I[F[F[F)J",
|
||||
(void*)latinime_Keyboard_setProximityInfo},
|
||||
{"releaseProximityInfoNative", "(J)V", (void*)latinime_Keyboard_release}
|
||||
reinterpret_cast<void *>(latinime_Keyboard_setProximityInfo)},
|
||||
{"releaseProximityInfoNative", "(J)V", reinterpret_cast<void *>(latinime_Keyboard_release)}
|
||||
};
|
||||
|
||||
int register_ProximityInfo(JNIEnv *env) {
|
||||
|
|
|
@ -14,15 +14,12 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
#include <cstring> // for memset()
|
||||
|
||||
#define LOG_TAG "LatinIME: jni: BinaryDictionary"
|
||||
|
||||
#include "binary_format.h"
|
||||
#include "com_android_inputmethod_latin_BinaryDictionary.h"
|
||||
#include "correction.h"
|
||||
#include "defines.h"
|
||||
#include "dictionary.h"
|
||||
#include "jni.h"
|
||||
#include "jni_common.h"
|
||||
#include "defines.h" // for macros below
|
||||
|
||||
#ifdef USE_MMAP_FOR_DICTIONARY
|
||||
#include <cerrno>
|
||||
|
@ -30,13 +27,21 @@
|
|||
#include <sys/mman.h>
|
||||
#else // USE_MMAP_FOR_DICTIONARY
|
||||
#include <cstdlib>
|
||||
#include <cstdio> // for fopen() etc.
|
||||
#endif // USE_MMAP_FOR_DICTIONARY
|
||||
|
||||
#include "binary_format.h"
|
||||
#include "com_android_inputmethod_latin_BinaryDictionary.h"
|
||||
#include "correction.h"
|
||||
#include "dictionary.h"
|
||||
#include "jni.h"
|
||||
#include "jni_common.h"
|
||||
|
||||
namespace latinime {
|
||||
|
||||
class ProximityInfo;
|
||||
|
||||
void releaseDictBuf(void *dictBuf, const size_t length, int fd);
|
||||
static void releaseDictBuf(const void *dictBuf, const size_t length, const int fd);
|
||||
|
||||
static jlong latinime_BinaryDictionary_open(JNIEnv *env, jobject object,
|
||||
jstring sourceDir, jlong dictOffset, jlong dictSize,
|
||||
|
@ -44,11 +49,14 @@ static jlong latinime_BinaryDictionary_open(JNIEnv *env, jobject object,
|
|||
jint maxPredictions) {
|
||||
PROF_OPEN;
|
||||
PROF_START(66);
|
||||
const char *sourceDirChars = env->GetStringUTFChars(sourceDir, 0);
|
||||
if (sourceDirChars == 0) {
|
||||
const jsize sourceDirUtf8Length = env->GetStringUTFLength(sourceDir);
|
||||
if (sourceDirUtf8Length <= 0) {
|
||||
AKLOGE("DICT: Can't get sourceDir string");
|
||||
return 0;
|
||||
}
|
||||
char sourceDirChars[sourceDirUtf8Length + 1];
|
||||
env->GetStringUTFRegion(sourceDir, 0, env->GetStringLength(sourceDir), sourceDirChars);
|
||||
sourceDirChars[sourceDirUtf8Length] = '\0';
|
||||
int fd = 0;
|
||||
void *dictBuf = 0;
|
||||
int adjust = 0;
|
||||
|
@ -68,7 +76,7 @@ static jlong latinime_BinaryDictionary_open(JNIEnv *env, jobject object,
|
|||
AKLOGE("DICT: Can't mmap dictionary. errno=%d", errno);
|
||||
return 0;
|
||||
}
|
||||
dictBuf = (void *)((char *)dictBuf + adjust);
|
||||
dictBuf = static_cast<char *>(dictBuf) + adjust;
|
||||
#else // USE_MMAP_FOR_DICTIONARY
|
||||
/* malloc version */
|
||||
FILE *file = 0;
|
||||
|
@ -98,17 +106,16 @@ static jlong latinime_BinaryDictionary_open(JNIEnv *env, jobject object,
|
|||
return 0;
|
||||
}
|
||||
#endif // USE_MMAP_FOR_DICTIONARY
|
||||
env->ReleaseStringUTFChars(sourceDir, sourceDirChars);
|
||||
|
||||
if (!dictBuf) {
|
||||
AKLOGE("DICT: dictBuf is null");
|
||||
return 0;
|
||||
}
|
||||
Dictionary *dictionary = 0;
|
||||
if (BinaryFormat::UNKNOWN_FORMAT == BinaryFormat::detectFormat((uint8_t*)dictBuf)) {
|
||||
if (BinaryFormat::UNKNOWN_FORMAT
|
||||
== BinaryFormat::detectFormat(static_cast<uint8_t *>(dictBuf))) {
|
||||
AKLOGE("DICT: dictionary format is unknown, bad magic number");
|
||||
#ifdef USE_MMAP_FOR_DICTIONARY
|
||||
releaseDictBuf(((char*)dictBuf) - adjust, adjDictSize, fd);
|
||||
releaseDictBuf(static_cast<const char *>(dictBuf) - adjust, adjDictSize, fd);
|
||||
#else // USE_MMAP_FOR_DICTIONARY
|
||||
releaseDictBuf(dictBuf, 0, 0);
|
||||
#endif // USE_MMAP_FOR_DICTIONARY
|
||||
|
@ -122,106 +129,131 @@ static jlong latinime_BinaryDictionary_open(JNIEnv *env, jobject object,
|
|||
}
|
||||
|
||||
static int latinime_BinaryDictionary_getSuggestions(JNIEnv *env, jobject object, jlong dict,
|
||||
jlong proximityInfo, jintArray xCoordinatesArray, jintArray yCoordinatesArray,
|
||||
jintArray timesArray, jintArray pointerIdArray, jintArray inputArray, jint arraySize,
|
||||
jint commitPoint, jboolean isGesture,
|
||||
jintArray prevWordForBigrams, jboolean useFullEditDistance, jcharArray outputArray,
|
||||
jintArray frequencyArray, jintArray spaceIndexArray, jintArray outputTypesArray) {
|
||||
Dictionary *dictionary = (Dictionary*) dict;
|
||||
jlong proximityInfo, jlong dicTraverseSession, jintArray xCoordinatesArray,
|
||||
jintArray yCoordinatesArray, jintArray timesArray, jintArray pointerIdsArray,
|
||||
jintArray inputCodePointsArray, jint arraySize, jint commitPoint, jboolean isGesture,
|
||||
jintArray prevWordCodePointsForBigrams, jboolean useFullEditDistance,
|
||||
jcharArray outputCharsArray, jintArray scoresArray, jintArray spaceIndicesArray,
|
||||
jintArray outputTypesArray) {
|
||||
Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
|
||||
if (!dictionary) return 0;
|
||||
ProximityInfo *pInfo = (ProximityInfo*)proximityInfo;
|
||||
int *xCoordinates = env->GetIntArrayElements(xCoordinatesArray, 0);
|
||||
int *yCoordinates = env->GetIntArrayElements(yCoordinatesArray, 0);
|
||||
int *times = env->GetIntArrayElements(timesArray, 0);
|
||||
int *pointerIds = env->GetIntArrayElements(pointerIdArray, 0);
|
||||
int *frequencies = env->GetIntArrayElements(frequencyArray, 0);
|
||||
int *inputCodes = env->GetIntArrayElements(inputArray, 0);
|
||||
jchar *outputChars = env->GetCharArrayElements(outputArray, 0);
|
||||
int *spaceIndices = env->GetIntArrayElements(spaceIndexArray, 0);
|
||||
int *outputTypes = env->GetIntArrayElements(outputTypesArray, 0);
|
||||
jint *prevWordChars = prevWordForBigrams
|
||||
? env->GetIntArrayElements(prevWordForBigrams, 0) : 0;
|
||||
jsize prevWordLength = prevWordChars ? env->GetArrayLength(prevWordForBigrams) : 0;
|
||||
ProximityInfo *pInfo = reinterpret_cast<ProximityInfo *>(proximityInfo);
|
||||
void *traverseSession = reinterpret_cast<void *>(dicTraverseSession);
|
||||
|
||||
// Input values
|
||||
int xCoordinates[arraySize];
|
||||
int yCoordinates[arraySize];
|
||||
int times[arraySize];
|
||||
int pointerIds[arraySize];
|
||||
const jsize inputCodePointsLength = env->GetArrayLength(inputCodePointsArray);
|
||||
int inputCodePoints[inputCodePointsLength];
|
||||
const jsize prevWordCodePointsLength =
|
||||
prevWordCodePointsForBigrams ? env->GetArrayLength(prevWordCodePointsForBigrams) : 0;
|
||||
int prevWordCodePointsInternal[prevWordCodePointsLength];
|
||||
int *prevWordCodePoints = 0;
|
||||
env->GetIntArrayRegion(xCoordinatesArray, 0, arraySize, xCoordinates);
|
||||
env->GetIntArrayRegion(yCoordinatesArray, 0, arraySize, yCoordinates);
|
||||
env->GetIntArrayRegion(timesArray, 0, arraySize, times);
|
||||
env->GetIntArrayRegion(pointerIdsArray, 0, arraySize, pointerIds);
|
||||
env->GetIntArrayRegion(inputCodePointsArray, 0, inputCodePointsLength, inputCodePoints);
|
||||
if (prevWordCodePointsForBigrams) {
|
||||
env->GetIntArrayRegion(prevWordCodePointsForBigrams, 0, prevWordCodePointsLength,
|
||||
prevWordCodePointsInternal);
|
||||
prevWordCodePoints = prevWordCodePointsInternal;
|
||||
}
|
||||
|
||||
// Output values
|
||||
// TODO: Should be "outputCodePointsLength" and "int outputCodePoints[]"
|
||||
const jsize outputCharsLength = env->GetArrayLength(outputCharsArray);
|
||||
unsigned short outputChars[outputCharsLength];
|
||||
const jsize scoresLength = env->GetArrayLength(scoresArray);
|
||||
int scores[scoresLength];
|
||||
const jsize spaceIndicesLength = env->GetArrayLength(spaceIndicesArray);
|
||||
int spaceIndices[spaceIndicesLength];
|
||||
const jsize outputTypesLength = env->GetArrayLength(outputTypesArray);
|
||||
int outputTypes[outputTypesLength];
|
||||
memset(outputChars, 0, sizeof(outputChars));
|
||||
memset(scores, 0, sizeof(scores));
|
||||
memset(spaceIndices, 0, sizeof(spaceIndices));
|
||||
memset(outputTypes, 0, sizeof(outputTypes));
|
||||
|
||||
int count;
|
||||
if (isGesture || arraySize > 1) {
|
||||
count = dictionary->getSuggestions(pInfo, xCoordinates, yCoordinates, times, pointerIds,
|
||||
inputCodes, arraySize, prevWordChars, prevWordLength, commitPoint, isGesture,
|
||||
useFullEditDistance, (unsigned short*) outputChars, frequencies, spaceIndices,
|
||||
outputTypes);
|
||||
if (isGesture || arraySize > 0) {
|
||||
count = dictionary->getSuggestions(pInfo, traverseSession, xCoordinates, yCoordinates,
|
||||
times, pointerIds, inputCodePoints, arraySize, prevWordCodePoints,
|
||||
prevWordCodePointsLength, commitPoint, isGesture, useFullEditDistance, outputChars,
|
||||
scores, spaceIndices, outputTypes);
|
||||
} else {
|
||||
count = dictionary->getBigrams(prevWordChars, prevWordLength, inputCodes,
|
||||
arraySize, (unsigned short*) outputChars, frequencies, outputTypes);
|
||||
count = dictionary->getBigrams(prevWordCodePoints, prevWordCodePointsLength,
|
||||
inputCodePoints, arraySize, outputChars, scores, outputTypes);
|
||||
}
|
||||
|
||||
if (prevWordChars) {
|
||||
env->ReleaseIntArrayElements(prevWordForBigrams, prevWordChars, JNI_ABORT);
|
||||
}
|
||||
env->ReleaseIntArrayElements(outputTypesArray, outputTypes, 0);
|
||||
env->ReleaseIntArrayElements(spaceIndexArray, spaceIndices, 0);
|
||||
env->ReleaseCharArrayElements(outputArray, outputChars, 0);
|
||||
env->ReleaseIntArrayElements(inputArray, inputCodes, JNI_ABORT);
|
||||
env->ReleaseIntArrayElements(frequencyArray, frequencies, 0);
|
||||
env->ReleaseIntArrayElements(pointerIdArray, pointerIds, 0);
|
||||
env->ReleaseIntArrayElements(timesArray, times, 0);
|
||||
env->ReleaseIntArrayElements(yCoordinatesArray, yCoordinates, 0);
|
||||
env->ReleaseIntArrayElements(xCoordinatesArray, xCoordinates, 0);
|
||||
// Copy back the output values
|
||||
// TODO: Should be SetIntArrayRegion()
|
||||
env->SetCharArrayRegion(outputCharsArray, 0, outputCharsLength, outputChars);
|
||||
env->SetIntArrayRegion(scoresArray, 0, scoresLength, scores);
|
||||
env->SetIntArrayRegion(spaceIndicesArray, 0, spaceIndicesLength, spaceIndices);
|
||||
env->SetIntArrayRegion(outputTypesArray, 0, outputTypesLength, outputTypes);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static jint latinime_BinaryDictionary_getFrequency(JNIEnv *env, jobject object, jlong dict,
|
||||
jintArray wordArray, jint wordLength) {
|
||||
Dictionary *dictionary = (Dictionary*)dict;
|
||||
if (!dictionary) return (jboolean) false;
|
||||
jint *word = env->GetIntArrayElements(wordArray, 0);
|
||||
jint result = dictionary->getFrequency(word, wordLength);
|
||||
env->ReleaseIntArrayElements(wordArray, word, JNI_ABORT);
|
||||
return result;
|
||||
jintArray wordArray) {
|
||||
Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
|
||||
if (!dictionary) return 0;
|
||||
const jsize codePointLength = env->GetArrayLength(wordArray);
|
||||
int codePoints[codePointLength];
|
||||
env->GetIntArrayRegion(wordArray, 0, codePointLength, codePoints);
|
||||
return dictionary->getFrequency(codePoints, codePointLength);
|
||||
}
|
||||
|
||||
static jboolean latinime_BinaryDictionary_isValidBigram(JNIEnv *env, jobject object, jlong dict,
|
||||
jintArray wordArray1, jintArray wordArray2) {
|
||||
Dictionary *dictionary = (Dictionary*)dict;
|
||||
Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
|
||||
if (!dictionary) return (jboolean) false;
|
||||
jint *word1 = env->GetIntArrayElements(wordArray1, 0);
|
||||
jint *word2 = env->GetIntArrayElements(wordArray2, 0);
|
||||
jsize length1 = word1 ? env->GetArrayLength(wordArray1) : 0;
|
||||
jsize length2 = word2 ? env->GetArrayLength(wordArray2) : 0;
|
||||
jboolean result = dictionary->isValidBigram(word1, length1, word2, length2);
|
||||
env->ReleaseIntArrayElements(wordArray2, word2, JNI_ABORT);
|
||||
env->ReleaseIntArrayElements(wordArray1, word1, JNI_ABORT);
|
||||
return result;
|
||||
const jsize codePointLength1 = env->GetArrayLength(wordArray1);
|
||||
const jsize codePointLength2 = env->GetArrayLength(wordArray2);
|
||||
int codePoints1[codePointLength1];
|
||||
int codePoints2[codePointLength2];
|
||||
env->GetIntArrayRegion(wordArray1, 0, codePointLength1, codePoints1);
|
||||
env->GetIntArrayRegion(wordArray2, 0, codePointLength2, codePoints2);
|
||||
return dictionary->isValidBigram(codePoints1, codePointLength1, codePoints2, codePointLength2);
|
||||
}
|
||||
|
||||
static jfloat latinime_BinaryDictionary_calcNormalizedScore(JNIEnv *env, jobject object,
|
||||
jcharArray before, jint beforeLength, jcharArray after, jint afterLength, jint score) {
|
||||
jchar *beforeChars = env->GetCharArrayElements(before, 0);
|
||||
jchar *afterChars = env->GetCharArrayElements(after, 0);
|
||||
jfloat result = Correction::RankingAlgorithm::calcNormalizedScore((unsigned short*)beforeChars,
|
||||
beforeLength, (unsigned short*)afterChars, afterLength, score);
|
||||
env->ReleaseCharArrayElements(after, afterChars, JNI_ABORT);
|
||||
env->ReleaseCharArrayElements(before, beforeChars, JNI_ABORT);
|
||||
return result;
|
||||
jcharArray before, jcharArray after, jint score) {
|
||||
jsize beforeLength = env->GetArrayLength(before);
|
||||
jsize afterLength = env->GetArrayLength(after);
|
||||
jchar beforeChars[beforeLength];
|
||||
jchar afterChars[afterLength];
|
||||
env->GetCharArrayRegion(before, 0, beforeLength, beforeChars);
|
||||
env->GetCharArrayRegion(after, 0, afterLength, afterChars);
|
||||
return Correction::RankingAlgorithm::calcNormalizedScore(
|
||||
static_cast<unsigned short *>(beforeChars), beforeLength,
|
||||
static_cast<unsigned short *>(afterChars), afterLength, score);
|
||||
}
|
||||
|
||||
static jint latinime_BinaryDictionary_editDistance(JNIEnv *env, jobject object,
|
||||
jcharArray before, jint beforeLength, jcharArray after, jint afterLength) {
|
||||
jchar *beforeChars = env->GetCharArrayElements(before, 0);
|
||||
jchar *afterChars = env->GetCharArrayElements(after, 0);
|
||||
jint result = Correction::RankingAlgorithm::editDistance(
|
||||
(unsigned short*)beforeChars, beforeLength, (unsigned short*)afterChars, afterLength);
|
||||
env->ReleaseCharArrayElements(after, afterChars, JNI_ABORT);
|
||||
env->ReleaseCharArrayElements(before, beforeChars, JNI_ABORT);
|
||||
return result;
|
||||
jcharArray before, jcharArray after) {
|
||||
jsize beforeLength = env->GetArrayLength(before);
|
||||
jsize afterLength = env->GetArrayLength(after);
|
||||
jchar beforeChars[beforeLength];
|
||||
jchar afterChars[afterLength];
|
||||
env->GetCharArrayRegion(before, 0, beforeLength, beforeChars);
|
||||
env->GetCharArrayRegion(after, 0, afterLength, afterChars);
|
||||
return Correction::RankingAlgorithm::editDistance(
|
||||
static_cast<unsigned short *>(beforeChars), beforeLength,
|
||||
static_cast<unsigned short *>(afterChars), afterLength);
|
||||
}
|
||||
|
||||
static void latinime_BinaryDictionary_close(JNIEnv *env, jobject object, jlong dict) {
|
||||
Dictionary *dictionary = (Dictionary*)dict;
|
||||
Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
|
||||
if (!dictionary) return;
|
||||
void *dictBuf = dictionary->getDict();
|
||||
const void *dictBuf = dictionary->getDict();
|
||||
if (!dictBuf) return;
|
||||
#ifdef USE_MMAP_FOR_DICTIONARY
|
||||
releaseDictBuf((void *)((char *)dictBuf - dictionary->getDictBufAdjust()),
|
||||
releaseDictBuf(static_cast<const char *>(dictBuf) - dictionary->getDictBufAdjust(),
|
||||
dictionary->getDictSize() + dictionary->getDictBufAdjust(), dictionary->getMmapFd());
|
||||
#else // USE_MMAP_FOR_DICTIONARY
|
||||
releaseDictBuf(dictBuf, 0, 0);
|
||||
|
@ -229,9 +261,9 @@ static void latinime_BinaryDictionary_close(JNIEnv *env, jobject object, jlong d
|
|||
delete dictionary;
|
||||
}
|
||||
|
||||
void releaseDictBuf(void *dictBuf, const size_t length, int fd) {
|
||||
static void releaseDictBuf(const void *dictBuf, const size_t length, const int fd) {
|
||||
#ifdef USE_MMAP_FOR_DICTIONARY
|
||||
int ret = munmap(dictBuf, length);
|
||||
int ret = munmap(const_cast<void *>(dictBuf), length);
|
||||
if (ret != 0) {
|
||||
AKLOGE("DICT: Failure in munmap. ret=%d errno=%d", ret, errno);
|
||||
}
|
||||
|
@ -240,20 +272,24 @@ void releaseDictBuf(void *dictBuf, const size_t length, int fd) {
|
|||
AKLOGE("DICT: Failure in close. ret=%d errno=%d", ret, errno);
|
||||
}
|
||||
#else // USE_MMAP_FOR_DICTIONARY
|
||||
free(dictBuf);
|
||||
free(const_cast<void *>(dictBuf));
|
||||
#endif // USE_MMAP_FOR_DICTIONARY
|
||||
}
|
||||
|
||||
static JNINativeMethod sMethods[] = {
|
||||
{"openNative", "(Ljava/lang/String;JJIIIII)J", (void*)latinime_BinaryDictionary_open},
|
||||
{"closeNative", "(J)V", (void*)latinime_BinaryDictionary_close},
|
||||
{"getSuggestionsNative", "(JJ[I[I[I[I[IIIZ[IZ[C[I[I[I)I",
|
||||
(void*) latinime_BinaryDictionary_getSuggestions},
|
||||
{"getFrequencyNative", "(J[II)I", (void*)latinime_BinaryDictionary_getFrequency},
|
||||
{"isValidBigramNative", "(J[I[I)Z", (void*)latinime_BinaryDictionary_isValidBigram},
|
||||
{"calcNormalizedScoreNative", "([CI[CII)F",
|
||||
(void*)latinime_BinaryDictionary_calcNormalizedScore},
|
||||
{"editDistanceNative", "([CI[CI)I", (void*)latinime_BinaryDictionary_editDistance}
|
||||
{"openNative", "(Ljava/lang/String;JJIIIII)J",
|
||||
reinterpret_cast<void *>(latinime_BinaryDictionary_open)},
|
||||
{"closeNative", "(J)V", reinterpret_cast<void *>(latinime_BinaryDictionary_close)},
|
||||
{"getSuggestionsNative", "(JJJ[I[I[I[I[IIIZ[IZ[C[I[I[I)I",
|
||||
reinterpret_cast<void *>(latinime_BinaryDictionary_getSuggestions)},
|
||||
{"getFrequencyNative", "(J[I)I",
|
||||
reinterpret_cast<void *>(latinime_BinaryDictionary_getFrequency)},
|
||||
{"isValidBigramNative", "(J[I[I)Z",
|
||||
reinterpret_cast<void *>(latinime_BinaryDictionary_isValidBigram)},
|
||||
{"calcNormalizedScoreNative", "([C[CI)F",
|
||||
reinterpret_cast<void *>(latinime_BinaryDictionary_calcNormalizedScore)},
|
||||
{"editDistanceNative", "([C[C)I",
|
||||
reinterpret_cast<void *>(latinime_BinaryDictionary_editDistance)}
|
||||
};
|
||||
|
||||
int register_BinaryDictionary(JNIEnv *env) {
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
||||
#ifndef _COM_ANDROID_INPUTMETHOD_LATIN_NATIVEUTILS_H
|
||||
#define _COM_ANDROID_INPUTMETHOD_LATIN_NATIVEUTILS_H
|
||||
#ifndef _COM_ANDROID_INPUTMETHOD_LATIN_DICTRAVERSESESSION_H
|
||||
#define _COM_ANDROID_INPUTMETHOD_LATIN_DICTRAVERSESESSION_H
|
||||
|
||||
#include "defines.h"
|
||||
#include "jni.h"
|
||||
|
||||
namespace latinime {
|
||||
|
||||
int register_NativeUtils(JNIEnv *env);
|
||||
|
||||
int register_DicTraverseSession(JNIEnv *env);
|
||||
} // namespace latinime
|
||||
#endif // _COM_ANDROID_INPUTMETHOD_LATIN_NATIVEUTILS_H
|
||||
#endif // _COM_ANDROID_INPUTMETHOD_LATIN_DICTRAVERSESESSION_H
|
|
@ -16,15 +16,15 @@
|
|||
|
||||
#define LOG_TAG "LatinIME: jni"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "com_android_inputmethod_keyboard_ProximityInfo.h"
|
||||
#include "com_android_inputmethod_latin_BinaryDictionary.h"
|
||||
#include "com_android_inputmethod_latin_NativeUtils.h"
|
||||
#include "com_android_inputmethod_latin_DicTraverseSession.h"
|
||||
#include "defines.h"
|
||||
#include "jni.h"
|
||||
#include "jni_common.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
using namespace latinime;
|
||||
|
||||
/*
|
||||
|
@ -34,7 +34,7 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
|
|||
JNIEnv *env = 0;
|
||||
jint result = -1;
|
||||
|
||||
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
|
||||
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
|
||||
AKLOGE("ERROR: GetEnv failed");
|
||||
goto bail;
|
||||
}
|
||||
|
@ -45,13 +45,13 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
|
|||
goto bail;
|
||||
}
|
||||
|
||||
if (!register_ProximityInfo(env)) {
|
||||
AKLOGE("ERROR: ProximityInfo native registration failed");
|
||||
if (!register_DicTraverseSession(env)) {
|
||||
AKLOGE("ERROR: DicTraverseSession native registration failed");
|
||||
goto bail;
|
||||
}
|
||||
|
||||
if (!register_NativeUtils(env)) {
|
||||
AKLOGE("ERROR: NativeUtils native registration failed");
|
||||
if (!register_ProximityInfo(env)) {
|
||||
AKLOGE("ERROR: ProximityInfo native registration failed");
|
||||
goto bail;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,32 +24,5 @@ namespace latinime {
|
|||
int registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods,
|
||||
int numMethods);
|
||||
|
||||
inline jint *safeGetIntArrayElements(JNIEnv *env, jintArray jArray) {
|
||||
if (jArray) {
|
||||
return env->GetIntArrayElements(jArray, 0);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
inline jfloat *safeGetFloatArrayElements(JNIEnv *env, jfloatArray jArray) {
|
||||
if (jArray) {
|
||||
return env->GetFloatArrayElements(jArray, 0);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
inline void safeReleaseIntArrayElements(JNIEnv *env, jintArray jArray, jint *cArray) {
|
||||
if (jArray) {
|
||||
env->ReleaseIntArrayElements(jArray, cArray, 0);
|
||||
}
|
||||
}
|
||||
|
||||
inline void safeReleaseFloatArrayElements(JNIEnv *env, jfloatArray jArray, jfloat *cArray) {
|
||||
if (jArray) {
|
||||
env->ReleaseFloatArrayElements(jArray, cArray, 0);
|
||||
}
|
||||
}
|
||||
} // namespace latinime
|
||||
#endif // LATINIME_JNI_COMMON_H
|
||||
|
|
|
@ -17,7 +17,9 @@
|
|||
#include "additional_proximity_chars.h"
|
||||
|
||||
namespace latinime {
|
||||
const std::string AdditionalProximityChars::LOCALE_EN_US("en");
|
||||
// TODO: Stop using hardcoded additional proximity characters.
|
||||
// TODO: Have proximity character informations in each language's binary dictionary.
|
||||
const char *AdditionalProximityChars::LOCALE_EN_US = "en";
|
||||
|
||||
const int32_t AdditionalProximityChars::EN_US_ADDITIONAL_A[EN_US_ADDITIONAL_A_SIZE] = {
|
||||
'e', 'i', 'o', 'u'
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
#ifndef LATINIME_ADDITIONAL_PROXIMITY_CHARS_H
|
||||
#define LATINIME_ADDITIONAL_PROXIMITY_CHARS_H
|
||||
|
||||
#include <cstring>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
|
||||
#include "defines.h"
|
||||
|
||||
|
@ -27,7 +27,7 @@ namespace latinime {
|
|||
class AdditionalProximityChars {
|
||||
private:
|
||||
DISALLOW_IMPLICIT_CONSTRUCTORS(AdditionalProximityChars);
|
||||
static const std::string LOCALE_EN_US;
|
||||
static const char *LOCALE_EN_US;
|
||||
static const int EN_US_ADDITIONAL_A_SIZE = 4;
|
||||
static const int32_t EN_US_ADDITIONAL_A[];
|
||||
static const int EN_US_ADDITIONAL_E_SIZE = 4;
|
||||
|
@ -39,14 +39,15 @@ class AdditionalProximityChars {
|
|||
static const int EN_US_ADDITIONAL_U_SIZE = 4;
|
||||
static const int32_t EN_US_ADDITIONAL_U[];
|
||||
|
||||
static bool isEnLocale(const std::string *locale_str) {
|
||||
return locale_str && locale_str->size() >= LOCALE_EN_US.size()
|
||||
&& LOCALE_EN_US.compare(0, LOCALE_EN_US.size(), *locale_str);
|
||||
static bool isEnLocale(const char *localeStr) {
|
||||
const size_t LOCALE_EN_US_SIZE = strlen(LOCALE_EN_US);
|
||||
return localeStr && strlen(localeStr) >= LOCALE_EN_US_SIZE
|
||||
&& strncmp(localeStr, LOCALE_EN_US, LOCALE_EN_US_SIZE) == 0;
|
||||
}
|
||||
|
||||
public:
|
||||
static int getAdditionalCharsSize(const std::string *locale_str, const int32_t c) {
|
||||
if (!isEnLocale(locale_str)) {
|
||||
static int getAdditionalCharsSize(const char *localeStr, const int32_t c) {
|
||||
if (!isEnLocale(localeStr)) {
|
||||
return 0;
|
||||
}
|
||||
switch(c) {
|
||||
|
@ -65,8 +66,8 @@ class AdditionalProximityChars {
|
|||
}
|
||||
}
|
||||
|
||||
static const int32_t *getAdditionalChars(const std::string *locale_str, const int32_t c) {
|
||||
if (!isEnLocale(locale_str)) {
|
||||
static const int32_t *getAdditionalChars(const char *localeStr, const int32_t c) {
|
||||
if (!isEnLocale(localeStr)) {
|
||||
return 0;
|
||||
}
|
||||
switch(c) {
|
||||
|
@ -84,10 +85,6 @@ class AdditionalProximityChars {
|
|||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static bool hasAdditionalChars(const std::string *locale_str, const int32_t c) {
|
||||
return getAdditionalCharsSize(locale_str, c) > 0;
|
||||
}
|
||||
};
|
||||
} // namespace latinime
|
||||
#endif // LATINIME_ADDITIONAL_PROXIMITY_CHARS_H
|
||||
|
|
|
@ -60,15 +60,15 @@ bool BigramDictionary::addWordBigram(unsigned short *word, int length, int frequ
|
|||
AKLOGI("Bigram: InsertAt -> %d MAX_PREDICTIONS: %d", insertAt, MAX_PREDICTIONS);
|
||||
}
|
||||
if (insertAt < MAX_PREDICTIONS) {
|
||||
memmove((char*) bigramFreq + (insertAt + 1) * sizeof(bigramFreq[0]),
|
||||
(char*) bigramFreq + insertAt * sizeof(bigramFreq[0]),
|
||||
memmove(bigramFreq + (insertAt + 1),
|
||||
bigramFreq + insertAt,
|
||||
(MAX_PREDICTIONS - insertAt - 1) * sizeof(bigramFreq[0]));
|
||||
bigramFreq[insertAt] = frequency;
|
||||
outputTypes[insertAt] = Dictionary::KIND_PREDICTION;
|
||||
memmove((char*) bigramChars + (insertAt + 1) * MAX_WORD_LENGTH * sizeof(short),
|
||||
(char*) bigramChars + (insertAt ) * MAX_WORD_LENGTH * sizeof(short),
|
||||
(MAX_PREDICTIONS - insertAt - 1) * sizeof(short) * MAX_WORD_LENGTH);
|
||||
unsigned short *dest = bigramChars + (insertAt ) * MAX_WORD_LENGTH;
|
||||
memmove(bigramChars + (insertAt + 1) * MAX_WORD_LENGTH,
|
||||
bigramChars + insertAt * MAX_WORD_LENGTH,
|
||||
(MAX_PREDICTIONS - insertAt - 1) * sizeof(bigramChars[0]) * MAX_WORD_LENGTH);
|
||||
unsigned short *dest = bigramChars + insertAt * MAX_WORD_LENGTH;
|
||||
while (length--) {
|
||||
*dest++ = *word++;
|
||||
}
|
||||
|
|
|
@ -29,8 +29,6 @@ class BigramDictionary {
|
|||
BigramDictionary(const unsigned char *dict, int maxWordLength, int maxPredictions);
|
||||
int getBigrams(const int32_t *word, int length, int *inputCodes, int codesSize,
|
||||
unsigned short *outWords, int *frequencies, int *outputTypes) const;
|
||||
int getBigramListPositionForWord(const int32_t *prevWord, const int prevWordLength,
|
||||
const bool forceLowerCaseSearch) const;
|
||||
void fillBigramAddressToFrequencyMapAndFilter(const int32_t *prevWord, const int prevWordLength,
|
||||
std::map<int, int> *map, uint8_t *filter) const;
|
||||
bool isValidBigram(const int32_t *word1, int length1, const int32_t *word2, int length2) const;
|
||||
|
@ -45,6 +43,8 @@ class BigramDictionary {
|
|||
bool getFirstBitOfByte(int *pos) { return (DICT[*pos] & 0x80) > 0; }
|
||||
bool getSecondBitOfByte(int *pos) { return (DICT[*pos] & 0x40) > 0; }
|
||||
bool checkFirstCharacter(unsigned short *word, int *inputCodes) const;
|
||||
int getBigramListPositionForWord(const int32_t *prevWord, const int prevWordLength,
|
||||
const bool forceLowerCaseSearch) const;
|
||||
|
||||
const unsigned char *DICT;
|
||||
const int MAX_WORD_LENGTH;
|
||||
|
|
|
@ -52,6 +52,8 @@ class BinaryFormat {
|
|||
|
||||
// Mask for attribute frequency, stored on 4 bits inside the flags byte.
|
||||
static const int MASK_ATTRIBUTE_FREQUENCY = 0x0F;
|
||||
// The numeric value of the shortcut frequency that means 'whitelist'.
|
||||
static const int WHITELIST_SHORTCUT_FREQUENCY = 15;
|
||||
|
||||
// Mask and flags for attribute address type selection.
|
||||
static const int MASK_ATTRIBUTE_ADDRESS_TYPE = 0x30;
|
||||
|
@ -59,13 +61,6 @@ class BinaryFormat {
|
|||
static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES = 0x20;
|
||||
static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES = 0x30;
|
||||
|
||||
private:
|
||||
DISALLOW_IMPLICIT_CONSTRUCTORS(BinaryFormat);
|
||||
const static int32_t MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
|
||||
const static int32_t CHARACTER_ARRAY_TERMINATOR = 0x1F;
|
||||
const static int MULTIPLE_BYTE_CHARACTER_ADDITIONAL_SIZE = 2;
|
||||
|
||||
public:
|
||||
const static int UNKNOWN_FORMAT = -1;
|
||||
// Originally, format version 1 had a 16-bit magic number, then the version number `01'
|
||||
// then options that must be 0. Hence the first 32-bits of the format are always as follow
|
||||
|
@ -92,13 +87,13 @@ class BinaryFormat {
|
|||
static int skipFrequency(const uint8_t flags, const int pos);
|
||||
static int skipShortcuts(const uint8_t *const dict, const uint8_t flags, const int pos);
|
||||
static int skipBigrams(const uint8_t *const dict, const uint8_t flags, const int pos);
|
||||
static int skipAllAttributes(const uint8_t *const dict, const uint8_t flags, const int pos);
|
||||
static int skipChildrenPosAndAttributes(const uint8_t *const dict, const uint8_t flags,
|
||||
const int pos);
|
||||
static int readChildrenPosition(const uint8_t *const dict, const uint8_t flags, const int pos);
|
||||
static bool hasChildrenInFlags(const uint8_t flags);
|
||||
static int getAttributeAddressAndForwardPointer(const uint8_t *const dict, const uint8_t flags,
|
||||
int *pos);
|
||||
static int getAttributeFrequencyFromFlags(const int flags);
|
||||
static int getTerminalPosition(const uint8_t *const root, const int32_t *const inWord,
|
||||
const int length, const bool forceLowerCaseSearch);
|
||||
static int getWordAtAddress(const uint8_t *const root, const int address, const int maxDepth,
|
||||
|
@ -115,6 +110,13 @@ class BinaryFormat {
|
|||
REQUIRES_FRENCH_LIGATURES_PROCESSING = 0x4
|
||||
};
|
||||
const static unsigned int NO_FLAGS = 0;
|
||||
|
||||
private:
|
||||
DISALLOW_IMPLICIT_CONSTRUCTORS(BinaryFormat);
|
||||
const static int32_t MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
|
||||
const static int32_t CHARACTER_ARRAY_TERMINATOR = 0x1F;
|
||||
const static int MULTIPLE_BYTE_CHARACTER_ADDITIONAL_SIZE = 2;
|
||||
static int skipAllAttributes(const uint8_t *const dict, const uint8_t flags, const int pos);
|
||||
};
|
||||
|
||||
inline int BinaryFormat::detectFormat(const uint8_t *const dict) {
|
||||
|
@ -340,6 +342,10 @@ inline int BinaryFormat::getAttributeAddressAndForwardPointer(const uint8_t *con
|
|||
}
|
||||
}
|
||||
|
||||
inline int BinaryFormat::getAttributeFrequencyFromFlags(const int flags) {
|
||||
return flags & MASK_ATTRIBUTE_FREQUENCY;
|
||||
}
|
||||
|
||||
// This function gets the byte position of the last chargroup of the exact matching word in the
|
||||
// dictionary. If no match is found, it returns NOT_VALID_WORD.
|
||||
inline int BinaryFormat::getTerminalPosition(const uint8_t *const root,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue