aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--java/proguard.flags5
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java20
-rwxr-xr-xjava/src/com/android/inputmethod/latin/Suggest.java21
-rw-r--r--java/src/com/android/inputmethod/latin/WordComposer.java2
-rw-r--r--tests/Android.mk17
-rw-r--r--tests/AndroidManifest.xml33
-rw-r--r--tests/data/wordlist.xml243
-rw-r--r--tests/res/raw/test.dictbin0 -> 2562 bytes
-rw-r--r--tests/src/com/android/inputmethod/latin/tests/SuggestTests.java248
9 files changed, 581 insertions, 8 deletions
diff --git a/java/proguard.flags b/java/proguard.flags
index 0a5d2dda9..829a096c0 100644
--- a/java/proguard.flags
+++ b/java/proguard.flags
@@ -1,3 +1,8 @@
-keep class com.android.inputmethod.latin.BinaryDictionary {
int mDictLength;
+ <init>(...);
+}
+
+-keep class com.android.inputmethod.latin.Suggest {
+ <init>(...);
}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 4901b210b..6473f4558 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -68,6 +68,26 @@ public class BinaryDictionary extends Dictionary {
}
}
+ /**
+ * Create a dictionary from a byte buffer. This is used for testing.
+ * @param context application context for reading resources
+ * @param resId the resource containing the raw binary dictionary
+ */
+ public BinaryDictionary(Context context, ByteBuffer byteBuffer) {
+ if (byteBuffer != null) {
+ if (byteBuffer.isDirect()) {
+ mNativeDictDirectBuffer = byteBuffer;
+ } else {
+ mNativeDictDirectBuffer = ByteBuffer.allocateDirect(byteBuffer.capacity());
+ byteBuffer.rewind();
+ mNativeDictDirectBuffer.put(byteBuffer);
+ }
+ mDictLength = byteBuffer.capacity();
+ mNativeDict = openNative(mNativeDictDirectBuffer,
+ TYPED_LETTER_MULTIPLIER, FULL_WORD_FREQ_MULTIPLIER);
+ }
+ }
+
private native int openNative(ByteBuffer bb, int typedLetterMultiplier, int fullWordMultiplier);
private native void closeNative(int dict);
private native boolean isValidWordNative(int nativeData, char[] word, int wordLength);
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index a70bea003..010913d6d 100755
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -16,18 +16,17 @@
package com.android.inputmethod.latin;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
import android.content.Context;
import android.text.AutoText;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import com.android.inputmethod.latin.WordComposer;
-
/**
* This class loads a dictionary and provides a list of suggestions for a given sequence of
* characters. This includes corrections and completions.
@@ -69,9 +68,17 @@ public class Suggest implements Dictionary.WordCallback {
private int mCorrectionMode = CORRECTION_BASIC;
-
public Suggest(Context context, int dictionaryResId) {
mMainDict = new BinaryDictionary(context, dictionaryResId);
+ initPool();
+ }
+
+ public Suggest(Context context, ByteBuffer byteBuffer) {
+ mMainDict = new BinaryDictionary(context, byteBuffer);
+ initPool();
+ }
+
+ private void initPool() {
for (int i = 0; i < mPrefMaxSuggestions; i++) {
StringBuilder sb = new StringBuilder(32);
mStringPool.add(sb);
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 19f714ae7..2547aa133 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -44,7 +44,7 @@ public class WordComposer {
*/
private boolean mIsCapitalized;
- WordComposer() {
+ public WordComposer() {
mCodes = new ArrayList<int[]>(12);
mTypedWord = new StringBuilder(20);
}
diff --git a/tests/Android.mk b/tests/Android.mk
new file mode 100644
index 000000000..fba7a8d74
--- /dev/null
+++ b/tests/Android.mk
@@ -0,0 +1,17 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+LOCAL_CERTIFICATE := shared
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+# Include all test java files.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := LatinIMETests
+
+LOCAL_INSTRUMENTATION_FOR := LatinIME
+
+include $(BUILD_PACKAGE)
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
new file mode 100644
index 000000000..210e81489
--- /dev/null
+++ b/tests/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.inputmethod.latin.tests">
+
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <!-- meta-data android:name="com.android.contacts.iconset" android:resource="@xml/iconset" /-->
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
+
+ </application>
+
+ <instrumentation android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.inputmethod.latin"
+ android:label="LatinIME tests">
+ </instrumentation>
+</manifest>
diff --git a/tests/data/wordlist.xml b/tests/data/wordlist.xml
new file mode 100644
index 000000000..22d0caa38
--- /dev/null
+++ b/tests/data/wordlist.xml
@@ -0,0 +1,243 @@
+<wordlist>
+ <w f="255">the</w>
+ <w f="246">and</w>
+ <w f="245">of</w>
+ <w f="242">to</w>
+ <w f="231">in</w>
+ <w f="230">that</w>
+ <w f="229">for</w>
+ <w f="224">with</w>
+ <w f="224">on</w>
+ <w f="224">it</w>
+ <w f="223">this</w>
+ <w f="222">you</w>
+ <w f="219">is</w>
+ <w f="219">was</w>
+ <w f="219">by</w>
+ <w f="219">or</w>
+ <w f="218">from</w>
+ <w f="217">but</w>
+ <w f="216">be</w>
+ <w f="216">Sunday</w>
+ <w f="215">are</w>
+ <w f="215">he</w>
+ <w f="214">so</w>
+ <w f="214">not</w>
+ <w f="213">have</w>
+ <w f="213">as</w>
+ <w f="211">all</w>
+ <w f="211">his</w>
+ <w f="210">my</w>
+ <w f="210">if</w>
+ <w f="210">which</w>
+ <w f="210">they</w>
+ <w f="209">at</w>
+ <w f="207">it's</w>
+ <w f="207">an</w>
+ <w f="207">your</w>
+ <w f="206">will</w>
+ <w f="206">about</w>
+ <w f="206">I'm</w>
+ <w f="205">there</w>
+ <w f="205">had</w>
+ <w f="205">has</w>
+ <w f="204">when</w>
+ <w f="203">no</w>
+ <w f="203">were</w>
+ <w f="203">what</w>
+ <w f="203">more</w>
+ <w f="203">out</w>
+ <w f="203">just</w>
+ <w f="202">their</w>
+ <w f="202">up</w>
+ <w f="202">would</w>
+ <w f="202">here</w>
+ <w f="202">can</w>
+ <w f="201">who</w>
+ <w f="200">her</w>
+ <w f="200">me</w>
+ <w f="200">now</w>
+ <w f="200">our</w>
+ <w f="200">do</w>
+ <w f="200">some</w>
+ <w f="199">been</w>
+ <w f="199">two</w>
+ <w f="199">like</w>
+ <w f="199">them</w>
+ <w f="199">new</w>
+ <w f="198">time</w>
+ <w f="198">we</w>
+ <w f="198">she</w>
+ <w f="197">one</w>
+ <w f="197">over</w>
+ <w f="197">may</w>
+ <w f="197">any</w>
+ <w f="197">him</w>
+ <w f="197">calling</w>
+ <w f="196">other</w>
+ <w f="196">how</w>
+ <w f="196">see</w>
+ <w f="195">because</w>
+ <w f="195">then</w>
+ <w f="195">right</w>
+ <w f="195">into</w>
+ <w f="195">well</w>
+ <w f="195">very</w>
+ <w f="195">said</w>
+ <w f="195">people</w>
+ <w f="194">these</w>
+ <w f="194">than</w>
+ <w f="193">only</w>
+ <w f="193">back</w>
+ <w f="193">first</w>
+ <w f="193">dot</w>
+ <w f="193">after</w>
+ <w f="193">where</w>
+ <w f="192">please</w>
+ <w f="192">could</w>
+ <w f="192">its</w>
+ <w f="192">before</w>
+ <w f="192">us</w>
+ <w f="192">again</w>
+ <w f="192">home</w>
+ <w f="191">also</w>
+ <w f="191">that's</w>
+ <w f="191">think</w>
+ <w f="191">three</w>
+ <w f="191">good</w>
+ <w f="191">get</w>
+ <w f="190">know</w>
+ <w f="190">thank</w>
+ <w f="190">should</w>
+ <w f="190">going</w>
+ <w f="190">down</w>
+ <w f="189">last</w>
+ <w f="189">today</w>
+ <w f="189">those</w>
+ <w f="189">go</w>
+ <w f="189">through</w>
+ <w f="189">such</w>
+ <w f="189">don't</w>
+ <w f="189">did</w>
+ <w f="188">most</w>
+ <w f="188">day</w>
+ <w f="188">man</w>
+ <w f="188">number</w>
+ <w f="188">work</w>
+ <w f="187">too</w>
+ <w f="187">show</w>
+ <w f="187">made</w>
+ <w f="187">even</w>
+ <w f="187">being</w>
+ <w f="187">make</w>
+ <w f="187">give</w>
+ <w f="186">off</w>
+ <w f="186">com</w>
+ <w f="186">much</w>
+ <w f="186">great</w>
+ <w f="186">take</w>
+ <w f="186">call</w>
+ <w f="186">way</w>
+ <w f="186">four</w>
+ <w f="186">say</w>
+ <w f="185">information</w>
+ <w f="185">under</w>
+ <w f="185">page</w>
+ <w f="185">many</w>
+ <w f="185">little</w>
+ <w f="185">thanks</w>
+ <w f="185">okay</w>
+ <w f="185">five</w>
+ <w f="185">we're</w>
+ <w f="185">between</w>
+ <w f="184">use</w>
+ <w f="184">come</w>
+ <w f="184">years</w>
+ <w f="184">office</w>
+ <w f="184">house</w>
+ <w f="184">search</w>
+ <w f="184">free</w>
+ <w f="183">next</w>
+ <w f="183">without</w>
+ <w f="183">still</w>
+ <w f="183">around</w>
+ <w f="183">I've</w>
+ <w f="183">business</w>
+ <w f="183">part</w>
+ <w f="183">every</w>
+ <w f="183">bye</w>
+ <w f="183">upon</w>
+ <w f="183">you're</w>
+ <w f="183">state</w>
+ <w f="183">life</w>
+ <w f="183">year</w>
+ <w f="182">thing</w>
+ <w f="182">since</w>
+ <w f="182">things</w>
+ <w f="182">something</w>
+ <w f="182">long</w>
+ <w f="182">got</w>
+ <w f="182">while</w>
+ <w f="182">I'll</w>
+ <w f="182">help</w>
+ <w f="182">service</w>
+ <w f="182">really</w>
+ <w f="182">must</w>
+ <w f="182">does</w>
+ <w f="182">name</w>
+ <w f="181">both</w>
+ <w f="181">six</w>
+ <w f="181">want</w>
+ <w f="181">same</w>
+ <w f="181">each</w>
+ <w f="181">yet</w>
+ <w f="181">let</w>
+ <w f="181">view</w>
+ <w f="181">place</w>
+ <w f="181">another</w>
+ <w f="181">company</w>
+ <w f="181">talk</w>
+ <w f="181">might</w>
+ <w f="181">am</w>
+ <w f="181">though</w>
+ <w f="181">find</w>
+ <w f="180">details</w>
+ <w f="180">look</w>
+ <w f="180">world</w>
+ <w f="180">old</w>
+ <w f="180">called</w>
+ <w f="180">case</w>
+ <w f="180">system</w>
+ <w f="180">news</w>
+ <w f="179">used</w>
+ <w f="179">contact</w>
+ <w f="179">never</w>
+ <w f="179">seven</w>
+ <w f="179">city</w>
+ <w f="179">until</w>
+ <w f="179">during</w>
+ <w f="179">set</w>
+ <w f="179">why</w>
+ <w f="179">point</w>
+ <w f="179">twenty</w>
+ <w f="179">high</w>
+ <w f="179">love</w>
+ <w f="179">services</w>
+ <w f="170">niño</w>
+ <w f="170">María</w>
+ <w f="0">hmmm</w>
+ <w f="0">hon</w>
+ <w f="0">tty</w>
+ <w f="0">ttyl</w>
+ <w f="0">txt</w>
+ <w f="0">ur</w>
+ <w f="0">wah</w>
+ <w f="0">whatcha</w>
+ <w f="0">woah</w>
+ <w f="0">ya</w>
+ <w f="0">yea</w>
+ <w f="0">yeh</w>
+ <w f="0">yessir</w>
+ <w f="0">yikes</w>
+ <w f="0">yrs</w>
+</wordlist>
diff --git a/tests/res/raw/test.dict b/tests/res/raw/test.dict
new file mode 100644
index 000000000..e789aaa9a
--- /dev/null
+++ b/tests/res/raw/test.dict
Binary files differ
diff --git a/tests/src/com/android/inputmethod/latin/tests/SuggestTests.java b/tests/src/com/android/inputmethod/latin/tests/SuggestTests.java
new file mode 100644
index 000000000..9401d926a
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/tests/SuggestTests.java
@@ -0,0 +1,248 @@
+package com.android.inputmethod.latin.tests;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.Channels;
+import java.util.List;
+
+import android.content.Context;
+import android.test.AndroidTestCase;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.inputmethod.latin.Suggest;
+import com.android.inputmethod.latin.WordComposer;
+
+public class SuggestTests extends AndroidTestCase {
+ private static final String TAG = "SuggestTests";
+
+ private Suggest mSuggest;
+
+ int[][] adjacents = {
+ {'a','s','w','q',-1},
+ {'b','h','v','n','g','j',-1},
+ {'c','v','f','x','g',},
+ {'d','f','r','e','s','x',-1},
+ {'e','w','r','s','d',-1},
+ {'f','g','d','c','t','r',-1},
+ {'g','h','f','y','t','v',-1},
+ {'h','j','u','g','b','y',-1},
+ {'i','o','u','k',-1},
+ {'j','k','i','h','u','n',-1},
+ {'k','l','o','j','i','m',-1},
+ {'l','k','o','p',-1},
+ {'m','k','n','l',-1},
+ {'n','m','j','k','b',-1},
+ {'o','p','i','l',-1},
+ {'p','o',-1},
+ {'q','w',-1},
+ {'r','t','e','f',-1},
+ {'s','d','e','w','a','z',-1},
+ {'t','y','r',-1},
+ {'u','y','i','h','j',-1},
+ {'v','b','g','c','h',-1},
+ {'w','e','q',-1},
+ {'x','c','d','z','f',-1},
+ {'y','u','t','h','g',-1},
+ {'z','s','x','a','d',-1},
+ };
+
+ @Override
+ protected void setUp() {
+ final Context context = getTestContext();
+ InputStream is = context.getResources().openRawResource(R.raw.test);
+ Log.i(TAG, "Stream type is " + is);
+ try {
+ int avail = is.available();
+ if (avail > 0) {
+ ByteBuffer byteBuffer =
+ ByteBuffer.allocateDirect(avail).order(ByteOrder.nativeOrder());
+ int got = Channels.newChannel(is).read(byteBuffer);
+ if (got != avail) {
+ Log.e(TAG, "Read " + got + " bytes, expected " + avail);
+ } else {
+ mSuggest = new Suggest(context, byteBuffer);
+ Log.i(TAG, "Created mSuggest " + avail + " bytes");
+ }
+ }
+ } catch (IOException ioe) {
+ Log.w(TAG, "No available size for binary dictionary");
+ }
+ mSuggest.setAutoTextEnabled(false);
+ mSuggest.setCorrectionMode(Suggest.CORRECTION_FULL);
+ }
+
+ /************************** Helper functions ************************/
+
+ private WordComposer createWordComposer(CharSequence s) {
+ WordComposer word = new WordComposer();
+ for (int i = 0; i < s.length(); i++) {
+ final char c = s.charAt(i);
+ int[] codes;
+ // If it's not a lowercase letter, don't find adjacent letters
+ if (c < 'a' || c > 'z') {
+ codes = new int[] { c };
+ } else {
+ codes = adjacents[c - 'a'];
+ }
+ word.add(c, codes);
+ }
+ return word;
+ }
+
+ private void showList(String title, List<CharSequence> suggestions) {
+ Log.i(TAG, title);
+ for (int i = 0; i < suggestions.size(); i++) {
+ Log.i(title, suggestions.get(i) + ", ");
+ }
+ }
+
+ private boolean isDefaultSuggestion(List<CharSequence> suggestions, CharSequence word) {
+ // Check if either the word is what you typed or the first alternative
+ return suggestions.size() > 0 &&
+ (/*TextUtils.equals(suggestions.get(0), word) || */
+ (suggestions.size() > 1 && TextUtils.equals(suggestions.get(1), word)));
+ }
+
+ private boolean isDefaultSuggestion(CharSequence typed, CharSequence expected) {
+ WordComposer word = createWordComposer(typed);
+ List<CharSequence> suggestions = mSuggest.getSuggestions(null, word, false);
+ return isDefaultSuggestion(suggestions, expected);
+ }
+
+ private boolean isDefaultCorrection(CharSequence typed, CharSequence expected) {
+ WordComposer word = createWordComposer(typed);
+ List<CharSequence> suggestions = mSuggest.getSuggestions(null, word, false);
+ return isDefaultSuggestion(suggestions, expected) && mSuggest.hasMinimalCorrection();
+ }
+
+ private boolean isASuggestion(CharSequence typed, CharSequence expected) {
+ WordComposer word = createWordComposer(typed);
+ List<CharSequence> suggestions = mSuggest.getSuggestions(null, word, false);
+ for (int i = 1; i < suggestions.size(); i++) {
+ if (TextUtils.equals(suggestions.get(i), expected)) return true;
+ }
+ return false;
+ }
+
+ private boolean isValid(CharSequence typed) {
+ return mSuggest.isValidWord(typed);
+ }
+
+ /************************** Tests ************************/
+
+ /**
+ * Tests for simple completions of one character.
+ */
+ public void testCompletion1char() {
+ assertTrue(isDefaultSuggestion("peopl", "people"));
+ assertTrue(isDefaultSuggestion("abou", "about"));
+ assertTrue(isDefaultSuggestion("thei", "their"));
+ }
+
+ /**
+ * Tests for simple completions of two characters.
+ */
+ public void testCompletion2char() {
+ assertTrue(isDefaultSuggestion("peop", "people"));
+ assertTrue(isDefaultSuggestion("calli", "calling"));
+ assertTrue(isDefaultSuggestion("busine", "business"));
+ }
+
+ /**
+ * Tests for proximity errors.
+ */
+ public void testProximityPositive() {
+ assertTrue(isDefaultSuggestion("peiple", "people"));
+ assertTrue(isDefaultSuggestion("peoole", "people"));
+ assertTrue(isDefaultSuggestion("pwpple", "people"));
+ }
+
+ /**
+ * Tests for proximity errors - negative, when the error key is not near.
+ */
+ public void testProximityNegative() {
+ assertFalse(isDefaultSuggestion("arout", "about"));
+ assertFalse(isDefaultSuggestion("ire", "are"));
+ }
+
+ /**
+ * Tests for checking if apostrophes are added automatically.
+ */
+ public void testApostropheInsertion() {
+ assertTrue(isDefaultSuggestion("im", "I'm"));
+ assertTrue(isDefaultSuggestion("dont", "don't"));
+ }
+
+ /**
+ * Test to make sure apostrophed word is not suggested for an apostrophed word.
+ */
+ public void testApostrophe() {
+ assertFalse(isDefaultSuggestion("don't", "don't"));
+ }
+
+ /**
+ * Tests for suggestion of capitalized version of a word.
+ */
+ public void testCapitalization() {
+ assertTrue(isDefaultSuggestion("i'm", "I'm"));
+ assertTrue(isDefaultSuggestion("sunday", "Sunday"));
+ assertTrue(isDefaultSuggestion("sundat", "Sunday"));
+ }
+
+ /**
+ * Tests to see if more than one completion is provided for certain prefixes.
+ */
+ public void testMultipleCompletions() {
+ assertTrue(isASuggestion("com", "come"));
+ assertTrue(isASuggestion("com", "company"));
+ assertTrue(isASuggestion("th", "the"));
+ assertTrue(isASuggestion("th", "that"));
+ assertTrue(isASuggestion("th", "this"));
+ assertTrue(isASuggestion("th", "they"));
+ }
+
+ /**
+ * Does the suggestion engine recognize zero frequency words as valid words.
+ */
+ public void testZeroFrequencyAccepted() {
+ assertTrue(isValid("yikes"));
+ assertFalse(isValid("yike"));
+ }
+
+ /**
+ * Tests to make sure that zero frequency words are not suggested as completions.
+ */
+ public void testZeroFrequencySuggestionsNegative() {
+ assertFalse(isASuggestion("yike", "yikes"));
+ assertFalse(isASuggestion("what", "whatcha"));
+ }
+
+ /**
+ * Tests to ensure that words with large edit distances are not suggested, in some cases
+ * and not considered corrections, in some cases.
+ */
+ public void testTooLargeEditDistance() {
+ assertFalse(isASuggestion("sniyr", "about"));
+ assertFalse(isDefaultCorrection("rjw", "the"));
+ }
+
+ /**
+ * Make sure isValid is case-sensitive.
+ */
+ public void testValidityCaseSensitivity() {
+ assertTrue(isValid("Sunday"));
+ assertFalse(isValid("sunday"));
+ }
+
+ /**
+ * Are accented forms of words suggested as corrections?
+ */
+ public void testAccents() {
+ assertTrue(isDefaultCorrection("nino", "ni\u00F1o")); // ni–o
+ assertTrue(isDefaultCorrection("nimo", "ni\u00F1o")); // ni–o
+ assertTrue(isDefaultCorrection("maria", "Mar\u00EDa")); // Mar’a
+ }
+}