• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.inputmethod.latin;
18 
19 import static com.android.inputmethod.latin.PersonalDictionaryLookup.ANY_LOCALE;
20 
21 import static org.mockito.Mockito.mock;
22 import static org.mockito.Mockito.times;
23 import static org.mockito.Mockito.verify;
24 import static org.mockito.Mockito.verifyNoMoreInteractions;
25 
26 import android.annotation.SuppressLint;
27 import android.content.ContentResolver;
28 import android.database.Cursor;
29 import android.net.Uri;
30 import android.provider.UserDictionary;
31 import android.test.AndroidTestCase;
32 import android.test.suitebuilder.annotation.SmallTest;
33 import android.util.Log;
34 
35 import com.android.inputmethod.latin.PersonalDictionaryLookup.PersonalDictionaryListener;
36 import com.android.inputmethod.latin.utils.ExecutorUtils;
37 
38 import java.util.HashSet;
39 import java.util.Locale;
40 import java.util.Set;
41 
42 /**
43  * Unit tests for {@link PersonalDictionaryLookup}.
44  *
45  * Note, this test doesn't mock out the ContentResolver, in order to make sure
46  * {@link PersonalDictionaryLookup} works in a real setting.
47  */
48 @SmallTest
49 public class PersonalDictionaryLookupTest extends AndroidTestCase {
50     private static final String TAG = PersonalDictionaryLookupTest.class.getSimpleName();
51 
52     private ContentResolver mContentResolver;
53     private HashSet<Uri> mAddedBackup;
54 
55     @Override
setUp()56     protected void setUp() throws Exception {
57         super.setUp();
58         mContentResolver = mContext.getContentResolver();
59         mAddedBackup = new HashSet<Uri>();
60     }
61 
62     @Override
tearDown()63     protected void tearDown() throws Exception {
64         // Remove all entries added during this test.
65         for (Uri row : mAddedBackup) {
66             mContentResolver.delete(row, null, null);
67         }
68         mAddedBackup.clear();
69 
70         super.tearDown();
71     }
72 
73     /**
74      * Adds the given word to the personal dictionary.
75      *
76      * @param word the word to add
77      * @param locale the locale of the word to add
78      * @param frequency the frequency of the word to add
79      * @return the Uri for the given word
80      */
81     @SuppressLint("NewApi")
addWord(final String word, final Locale locale, int frequency, String shortcut)82     private Uri addWord(final String word, final Locale locale, int frequency, String shortcut) {
83         // Add the given word for the given locale.
84         UserDictionary.Words.addWord(mContext, word, frequency, shortcut, locale);
85         // Obtain an Uri for the given word.
86         Cursor cursor = mContentResolver.query(UserDictionary.Words.CONTENT_URI, null,
87                 UserDictionary.Words.WORD + "='" + word + "'", null, null);
88         assertTrue(cursor.moveToFirst());
89         Uri uri = Uri.withAppendedPath(UserDictionary.Words.CONTENT_URI,
90                 cursor.getString(cursor.getColumnIndex(UserDictionary.Words._ID)));
91         // Add the row to the backup for later clearing.
92         mAddedBackup.add(uri);
93         return uri;
94     }
95 
96     /**
97      * Deletes the entry for the given word from UserDictionary.
98      *
99      * @param uri the Uri for the word as returned by addWord
100      */
deleteWord(Uri uri)101     private void deleteWord(Uri uri) {
102         // Remove the word from the backup so that it's not cleared again later.
103         mAddedBackup.remove(uri);
104         // Remove the word from the personal dictionary.
105         mContentResolver.delete(uri, null, null);
106     }
107 
setUpWord(final Locale locale)108     private PersonalDictionaryLookup setUpWord(final Locale locale) {
109         // Insert "foo" in the personal dictionary for the given locale.
110         addWord("foo", locale, 17, null);
111 
112         // Create the PersonalDictionaryLookup and wait until it's loaded.
113         PersonalDictionaryLookup lookup =
114                 new PersonalDictionaryLookup(mContext, ExecutorUtils.SPELLING);
115         lookup.open();
116         return lookup;
117     }
118 
setUpShortcut(final Locale locale)119     private PersonalDictionaryLookup setUpShortcut(final Locale locale) {
120         // Insert "shortcut" => "Expansion" in the personal dictionary for the given locale.
121         addWord("Expansion", locale, 17, "shortcut");
122 
123         // Create the PersonalDictionaryLookup and wait until it's loaded.
124         PersonalDictionaryLookup lookup =
125                 new PersonalDictionaryLookup(mContext, ExecutorUtils.SPELLING);
126         lookup.open();
127         return lookup;
128     }
129 
verifyWordExists(final Set<String> set, final String word)130     private void verifyWordExists(final Set<String> set, final String word) {
131         assertTrue(set.contains(word));
132     }
133 
verifyWordDoesNotExist(final Set<String> set, final String word)134     private void verifyWordDoesNotExist(final Set<String> set, final String word) {
135         assertFalse(set.contains(word));
136     }
137 
testShortcutKeyMatching()138     public void testShortcutKeyMatching() {
139         Log.d(TAG, "testShortcutKeyMatching");
140         PersonalDictionaryLookup lookup = setUpShortcut(Locale.US);
141 
142         assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.US));
143         assertNull(lookup.expandShortcut("Shortcut", Locale.US));
144         assertNull(lookup.expandShortcut("SHORTCUT", Locale.US));
145         assertNull(lookup.expandShortcut("shortcu", Locale.US));
146         assertNull(lookup.expandShortcut("shortcutt", Locale.US));
147 
148         lookup.close();
149     }
150 
testShortcutMatchesInputCountry()151     public void testShortcutMatchesInputCountry() {
152         Log.d(TAG, "testShortcutMatchesInputCountry");
153         PersonalDictionaryLookup lookup = setUpShortcut(Locale.US);
154 
155         verifyWordExists(lookup.getShortcutsForLocale(Locale.US), "shortcut");
156         assertTrue(lookup.getShortcutsForLocale(Locale.UK).isEmpty());
157         assertTrue(lookup.getShortcutsForLocale(Locale.ENGLISH).isEmpty());
158         assertTrue(lookup.getShortcutsForLocale(Locale.FRENCH).isEmpty());
159         assertTrue(lookup.getShortcutsForLocale(ANY_LOCALE).isEmpty());
160 
161         assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.US));
162         assertNull(lookup.expandShortcut("shortcut", Locale.UK));
163         assertNull(lookup.expandShortcut("shortcut", Locale.ENGLISH));
164         assertNull(lookup.expandShortcut("shortcut", Locale.FRENCH));
165         assertNull(lookup.expandShortcut("shortcut", ANY_LOCALE));
166 
167         lookup.close();
168     }
169 
testShortcutMatchesInputLanguage()170     public void testShortcutMatchesInputLanguage() {
171         Log.d(TAG, "testShortcutMatchesInputLanguage");
172         PersonalDictionaryLookup lookup = setUpShortcut(Locale.ENGLISH);
173 
174         verifyWordExists(lookup.getShortcutsForLocale(Locale.US), "shortcut");
175         verifyWordExists(lookup.getShortcutsForLocale(Locale.UK), "shortcut");
176         verifyWordExists(lookup.getShortcutsForLocale(Locale.ENGLISH), "shortcut");
177         assertTrue(lookup.getShortcutsForLocale(Locale.FRENCH).isEmpty());
178         assertTrue(lookup.getShortcutsForLocale(ANY_LOCALE).isEmpty());
179 
180         assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.US));
181         assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.UK));
182         assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.ENGLISH));
183         assertNull(lookup.expandShortcut("shortcut", Locale.FRENCH));
184         assertNull(lookup.expandShortcut("shortcut", ANY_LOCALE));
185 
186         lookup.close();
187     }
188 
testShortcutMatchesAnyLocale()189     public void testShortcutMatchesAnyLocale() {
190         PersonalDictionaryLookup lookup = setUpShortcut(PersonalDictionaryLookup.ANY_LOCALE);
191 
192         verifyWordExists(lookup.getShortcutsForLocale(Locale.US), "shortcut");
193         verifyWordExists(lookup.getShortcutsForLocale(Locale.UK), "shortcut");
194         verifyWordExists(lookup.getShortcutsForLocale(Locale.ENGLISH), "shortcut");
195         verifyWordExists(lookup.getShortcutsForLocale(Locale.FRENCH), "shortcut");
196         verifyWordExists(lookup.getShortcutsForLocale(ANY_LOCALE), "shortcut");
197 
198         assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.US));
199         assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.UK));
200         assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.ENGLISH));
201         assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.FRENCH));
202         assertEquals("Expansion", lookup.expandShortcut("shortcut", ANY_LOCALE));
203 
204         lookup.close();
205     }
206 
testExactLocaleMatch()207     public void testExactLocaleMatch() {
208         Log.d(TAG, "testExactLocaleMatch");
209         PersonalDictionaryLookup lookup = setUpWord(Locale.US);
210 
211         verifyWordExists(lookup.getWordsForLocale(Locale.US), "foo");
212         verifyWordDoesNotExist(lookup.getWordsForLocale(Locale.UK), "foo");
213         verifyWordDoesNotExist(lookup.getWordsForLocale(Locale.ENGLISH), "foo");
214         verifyWordDoesNotExist(lookup.getWordsForLocale(Locale.FRENCH), "foo");
215         verifyWordDoesNotExist(lookup.getWordsForLocale(ANY_LOCALE), "foo");
216 
217         // Any capitalization variation should match.
218         assertTrue(lookup.isValidWord("foo", Locale.US));
219         assertTrue(lookup.isValidWord("Foo", Locale.US));
220         assertTrue(lookup.isValidWord("FOO", Locale.US));
221         // But similar looking words don't match.
222         assertFalse(lookup.isValidWord("fo", Locale.US));
223         assertFalse(lookup.isValidWord("fop", Locale.US));
224         assertFalse(lookup.isValidWord("fooo", Locale.US));
225         // Other locales, including more general locales won't match.
226         assertFalse(lookup.isValidWord("foo", Locale.ENGLISH));
227         assertFalse(lookup.isValidWord("foo", Locale.UK));
228         assertFalse(lookup.isValidWord("foo", Locale.FRENCH));
229         assertFalse(lookup.isValidWord("foo", ANY_LOCALE));
230 
231         lookup.close();
232     }
233 
testSubLocaleMatch()234     public void testSubLocaleMatch() {
235         Log.d(TAG, "testSubLocaleMatch");
236         PersonalDictionaryLookup lookup = setUpWord(Locale.ENGLISH);
237 
238         verifyWordExists(lookup.getWordsForLocale(Locale.US), "foo");
239         verifyWordExists(lookup.getWordsForLocale(Locale.UK), "foo");
240         verifyWordExists(lookup.getWordsForLocale(Locale.ENGLISH), "foo");
241         verifyWordDoesNotExist(lookup.getWordsForLocale(Locale.FRENCH), "foo");
242         verifyWordDoesNotExist(lookup.getWordsForLocale(ANY_LOCALE), "foo");
243 
244         // Any capitalization variation should match for both en and en_US.
245         assertTrue(lookup.isValidWord("foo", Locale.ENGLISH));
246         assertTrue(lookup.isValidWord("foo", Locale.US));
247         assertTrue(lookup.isValidWord("Foo", Locale.US));
248         assertTrue(lookup.isValidWord("FOO", Locale.US));
249         // But similar looking words don't match.
250         assertFalse(lookup.isValidWord("fo", Locale.US));
251         assertFalse(lookup.isValidWord("fop", Locale.US));
252         assertFalse(lookup.isValidWord("fooo", Locale.US));
253 
254         lookup.close();
255     }
256 
testAllLocalesMatch()257     public void testAllLocalesMatch() {
258         Log.d(TAG, "testAllLocalesMatch");
259         PersonalDictionaryLookup lookup = setUpWord(null);
260 
261         verifyWordExists(lookup.getWordsForLocale(Locale.US), "foo");
262         verifyWordExists(lookup.getWordsForLocale(Locale.UK), "foo");
263         verifyWordExists(lookup.getWordsForLocale(Locale.ENGLISH), "foo");
264         verifyWordExists(lookup.getWordsForLocale(Locale.FRENCH), "foo");
265         verifyWordExists(lookup.getWordsForLocale(ANY_LOCALE), "foo");
266 
267         // Any capitalization variation should match for fr, en and en_US.
268         assertTrue(lookup.isValidWord("foo", ANY_LOCALE));
269         assertTrue(lookup.isValidWord("foo", Locale.FRENCH));
270         assertTrue(lookup.isValidWord("foo", Locale.ENGLISH));
271         assertTrue(lookup.isValidWord("foo", Locale.US));
272         assertTrue(lookup.isValidWord("Foo", Locale.US));
273         assertTrue(lookup.isValidWord("FOO", Locale.US));
274         // But similar looking words don't match.
275         assertFalse(lookup.isValidWord("fo", Locale.US));
276         assertFalse(lookup.isValidWord("fop", Locale.US));
277         assertFalse(lookup.isValidWord("fooo", Locale.US));
278 
279         lookup.close();
280     }
281 
testMultipleLocalesMatch()282     public void testMultipleLocalesMatch() {
283         Log.d(TAG, "testMultipleLocalesMatch");
284 
285         // Insert "Foo" as capitalized in the personal dictionary under the en_US and en_CA and fr
286         // locales.
287         addWord("Foo", Locale.US, 17, null);
288         addWord("foO", Locale.CANADA, 17, null);
289         addWord("fOo", Locale.FRENCH, 17, null);
290 
291         // Create the PersonalDictionaryLookup and wait until it's loaded.
292         PersonalDictionaryLookup lookup = new PersonalDictionaryLookup(mContext,
293                 ExecutorUtils.SPELLING);
294         lookup.open();
295 
296         // Both en_CA and en_US match.
297         assertTrue(lookup.isValidWord("foo", Locale.CANADA));
298         assertTrue(lookup.isValidWord("foo", Locale.US));
299         assertTrue(lookup.isValidWord("foo", Locale.FRENCH));
300         // Other locales, including more general locales won't match.
301         assertFalse(lookup.isValidWord("foo", Locale.ENGLISH));
302         assertFalse(lookup.isValidWord("foo", Locale.UK));
303         assertFalse(lookup.isValidWord("foo", ANY_LOCALE));
304 
305         lookup.close();
306     }
307 
308 
testCaseMatchingForWordsAndShortcuts()309     public void testCaseMatchingForWordsAndShortcuts() {
310         Log.d(TAG, "testCaseMatchingForWordsAndShortcuts");
311         addWord("Foo", Locale.US, 17, "f");
312         addWord("bokabu", Locale.US, 17, "Bu");
313 
314         // Create the PersonalDictionaryLookup and wait until it's loaded.
315         PersonalDictionaryLookup lookup = new PersonalDictionaryLookup(mContext,
316                 ExecutorUtils.SPELLING);
317         lookup.open();
318 
319         // Valid, inspite of capitalization in US but not in other
320         // locales.
321         assertTrue(lookup.isValidWord("Foo", Locale.US));
322         assertTrue(lookup.isValidWord("foo", Locale.US));
323         assertFalse(lookup.isValidWord("Foo", Locale.UK));
324         assertFalse(lookup.isValidWord("foo", Locale.UK));
325 
326         // Valid in all forms in US.
327         assertTrue(lookup.isValidWord("bokabu", Locale.US));
328         assertTrue(lookup.isValidWord("BOKABU", Locale.US));
329         assertTrue(lookup.isValidWord("BokaBU", Locale.US));
330 
331         // Correct capitalization; sensitive to shortcut casing & locale.
332         assertEquals("Foo", lookup.expandShortcut("f", Locale.US));
333         assertNull(lookup.expandShortcut("f", Locale.UK));
334 
335         // Correct capitalization; sensitive to shortcut casing & locale.
336         assertEquals("bokabu", lookup.expandShortcut("Bu", Locale.US));
337         assertNull(lookup.expandShortcut("Bu", Locale.UK));
338         assertNull(lookup.expandShortcut("bu", Locale.US));
339 
340         // Verify that raw strings are retained for #getWordsForLocale.
341         verifyWordExists(lookup.getWordsForLocale(Locale.US), "Foo");
342         verifyWordDoesNotExist(lookup.getWordsForLocale(Locale.US), "foo");
343     }
344 
testManageListeners()345     public void testManageListeners() {
346         Log.d(TAG, "testManageListeners");
347 
348         PersonalDictionaryLookup lookup =
349                 new PersonalDictionaryLookup(mContext, ExecutorUtils.SPELLING);
350 
351         PersonalDictionaryListener listener = mock(PersonalDictionaryListener.class);
352         // Add the same listener a bunch of times. It doesn't make a difference.
353         lookup.addListener(listener);
354         lookup.addListener(listener);
355         lookup.addListener(listener);
356         lookup.notifyListeners();
357 
358         verify(listener, times(1)).onUpdate();
359 
360         // Remove the same listener a bunch of times. It doesn't make a difference.
361         lookup.removeListener(listener);
362         lookup.removeListener(listener);
363         lookup.removeListener(listener);
364         lookup.notifyListeners();
365 
366         verifyNoMoreInteractions(listener);
367     }
368 
testReload()369     public void testReload() {
370         Log.d(TAG, "testReload");
371 
372         // Insert "foo".
373         Uri uri = addWord("foo", Locale.US, 17, null);
374 
375         // Create the PersonalDictionaryLookup and wait until it's loaded.
376         PersonalDictionaryLookup lookup =
377                 new PersonalDictionaryLookup(mContext, ExecutorUtils.SPELLING);
378         lookup.open();
379 
380         // "foo" should match.
381         assertTrue(lookup.isValidWord("foo", Locale.US));
382 
383         // "bar" shouldn't match.
384         assertFalse(lookup.isValidWord("bar", Locale.US));
385 
386         // Now delete "foo" and add "bar".
387         deleteWord(uri);
388         addWord("bar", Locale.US, 18, null);
389 
390         // Wait a little bit before expecting a change. The time we wait should be greater than
391         // PersonalDictionaryLookup.RELOAD_DELAY_MS.
392         try {
393             Thread.sleep(PersonalDictionaryLookup.RELOAD_DELAY_MS + 1000);
394         } catch (InterruptedException e) {
395         }
396 
397         // Perform lookups again. Reload should have occured.
398         //
399         // "foo" should not match.
400         assertFalse(lookup.isValidWord("foo", Locale.US));
401 
402         // "bar" should match.
403         assertTrue(lookup.isValidWord("bar", Locale.US));
404 
405         lookup.close();
406     }
407 
testDictionaryStats()408     public void testDictionaryStats() {
409         Log.d(TAG, "testDictionaryStats");
410 
411         // Insert "foo" and "bar". Only "foo" has a shortcut.
412         Uri uri = addWord("foo", Locale.GERMANY, 17, "f");
413         addWord("bar", Locale.GERMANY, 17, null);
414 
415         // Create the PersonalDictionaryLookup and wait until it's loaded.
416         PersonalDictionaryLookup lookup =
417                 new PersonalDictionaryLookup(mContext, ExecutorUtils.SPELLING);
418         lookup.open();
419 
420         // "foo" should match.
421         assertTrue(lookup.isValidWord("foo", Locale.GERMANY));
422 
423         // "bar" should match.
424         assertTrue(lookup.isValidWord("bar", Locale.GERMANY));
425 
426         // "foo" should have a shortcut.
427         assertEquals("foo", lookup.expandShortcut("f", Locale.GERMANY));
428 
429         // Now delete "foo".
430         deleteWord(uri);
431 
432         // Wait a little bit before expecting a change. The time we wait should be greater than
433         // PersonalDictionaryLookup.RELOAD_DELAY_MS.
434         try {
435             Thread.sleep(PersonalDictionaryLookup.RELOAD_DELAY_MS + 1000);
436         } catch (InterruptedException e) {
437         }
438 
439         // Perform lookups again. Reload should have occured.
440         //
441         // "foo" should not match.
442         assertFalse(lookup.isValidWord("foo", Locale.GERMANY));
443 
444         // "foo" should not have a shortcut.
445         assertNull(lookup.expandShortcut("f", Locale.GERMANY));
446 
447         // "bar" should still match.
448         assertTrue(lookup.isValidWord("bar", Locale.GERMANY));
449 
450         lookup.close();
451     }
452 
testClose()453     public void testClose() {
454         Log.d(TAG, "testClose");
455 
456         // Insert "foo".
457         Uri uri = addWord("foo", Locale.US, 17, null);
458 
459         // Create the PersonalDictionaryLookup and wait until it's loaded.
460         PersonalDictionaryLookup lookup =
461                 new PersonalDictionaryLookup(mContext, ExecutorUtils.SPELLING);
462         lookup.open();
463 
464         // "foo" should match.
465         assertTrue(lookup.isValidWord("foo", Locale.US));
466 
467         // "bar" shouldn't match.
468         assertFalse(lookup.isValidWord("bar", Locale.US));
469 
470         // Now close (prevents further reloads).
471         lookup.close();
472 
473         // Now delete "foo" and add "bar".
474         deleteWord(uri);
475         addWord("bar", Locale.US, 18, null);
476 
477         // Wait a little bit before expecting a change. The time we wait should be greater than
478         // PersonalDictionaryLookup.RELOAD_DELAY_MS.
479         try {
480             Thread.sleep(PersonalDictionaryLookup.RELOAD_DELAY_MS + 1000);
481         } catch (InterruptedException e) {
482         }
483 
484         // Perform lookups again. Reload should not have occurred.
485         //
486         // "foo" should stil match.
487         assertTrue(lookup.isValidWord("foo", Locale.US));
488 
489         // "bar" should still not match.
490         assertFalse(lookup.isValidWord("bar", Locale.US));
491     }
492 }
493