Home | History | Annotate | Download | only in latin
      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
     56     protected void setUp() throws Exception {
     57         super.setUp();
     58         mContentResolver = mContext.getContentResolver();
     59         mAddedBackup = new HashSet<Uri>();
     60     }
     61 
     62     @Override
     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")
     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      */
    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 
    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 
    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 
    130     private void verifyWordExists(final Set<String> set, final String word) {
    131         assertTrue(set.contains(word));
    132     }
    133 
    134     private void verifyWordDoesNotExist(final Set<String> set, final String word) {
    135         assertFalse(set.contains(word));
    136     }
    137 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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