/* * Copyright (C) 2015 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 static com.android.inputmethod.latin.PersonalDictionaryLookup.ANY_LOCALE; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import android.annotation.SuppressLint; import android.content.ContentResolver; import android.database.Cursor; import android.net.Uri; import android.provider.UserDictionary; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; import com.android.inputmethod.latin.PersonalDictionaryLookup.PersonalDictionaryListener; import com.android.inputmethod.latin.utils.ExecutorUtils; import java.util.HashSet; import java.util.Locale; import java.util.Set; /** * Unit tests for {@link PersonalDictionaryLookup}. * * Note, this test doesn't mock out the ContentResolver, in order to make sure * {@link PersonalDictionaryLookup} works in a real setting. */ @SmallTest public class PersonalDictionaryLookupTest extends AndroidTestCase { private static final String TAG = PersonalDictionaryLookupTest.class.getSimpleName(); private ContentResolver mContentResolver; private HashSet mAddedBackup; @Override protected void setUp() throws Exception { super.setUp(); mContentResolver = mContext.getContentResolver(); mAddedBackup = new HashSet(); } @Override protected void tearDown() throws Exception { // Remove all entries added during this test. for (Uri row : mAddedBackup) { mContentResolver.delete(row, null, null); } mAddedBackup.clear(); super.tearDown(); } /** * Adds the given word to the personal dictionary. * * @param word the word to add * @param locale the locale of the word to add * @param frequency the frequency of the word to add * @return the Uri for the given word */ @SuppressLint("NewApi") private Uri addWord(final String word, final Locale locale, int frequency, String shortcut) { // Add the given word for the given locale. UserDictionary.Words.addWord(mContext, word, frequency, shortcut, locale); // Obtain an Uri for the given word. Cursor cursor = mContentResolver.query(UserDictionary.Words.CONTENT_URI, null, UserDictionary.Words.WORD + "='" + word + "'", null, null); assertTrue(cursor.moveToFirst()); Uri uri = Uri.withAppendedPath(UserDictionary.Words.CONTENT_URI, cursor.getString(cursor.getColumnIndex(UserDictionary.Words._ID))); // Add the row to the backup for later clearing. mAddedBackup.add(uri); return uri; } /** * Deletes the entry for the given word from UserDictionary. * * @param uri the Uri for the word as returned by addWord */ private void deleteWord(Uri uri) { // Remove the word from the backup so that it's not cleared again later. mAddedBackup.remove(uri); // Remove the word from the personal dictionary. mContentResolver.delete(uri, null, null); } private PersonalDictionaryLookup setUpWord(final Locale locale) { // Insert "foo" in the personal dictionary for the given locale. addWord("foo", locale, 17, null); // Create the PersonalDictionaryLookup and wait until it's loaded. PersonalDictionaryLookup lookup = new PersonalDictionaryLookup(mContext, ExecutorUtils.SPELLING); lookup.open(); return lookup; } private PersonalDictionaryLookup setUpShortcut(final Locale locale) { // Insert "shortcut" => "Expansion" in the personal dictionary for the given locale. addWord("Expansion", locale, 17, "shortcut"); // Create the PersonalDictionaryLookup and wait until it's loaded. PersonalDictionaryLookup lookup = new PersonalDictionaryLookup(mContext, ExecutorUtils.SPELLING); lookup.open(); return lookup; } private void verifyWordExists(final Set set, final String word) { assertTrue(set.contains(word)); } private void verifyWordDoesNotExist(final Set set, final String word) { assertFalse(set.contains(word)); } public void testShortcutKeyMatching() { Log.d(TAG, "testShortcutKeyMatching"); PersonalDictionaryLookup lookup = setUpShortcut(Locale.US); assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.US)); assertNull(lookup.expandShortcut("Shortcut", Locale.US)); assertNull(lookup.expandShortcut("SHORTCUT", Locale.US)); assertNull(lookup.expandShortcut("shortcu", Locale.US)); assertNull(lookup.expandShortcut("shortcutt", Locale.US)); lookup.close(); } public void testShortcutMatchesInputCountry() { Log.d(TAG, "testShortcutMatchesInputCountry"); PersonalDictionaryLookup lookup = setUpShortcut(Locale.US); verifyWordExists(lookup.getShortcutsForLocale(Locale.US), "shortcut"); assertTrue(lookup.getShortcutsForLocale(Locale.UK).isEmpty()); assertTrue(lookup.getShortcutsForLocale(Locale.ENGLISH).isEmpty()); assertTrue(lookup.getShortcutsForLocale(Locale.FRENCH).isEmpty()); assertTrue(lookup.getShortcutsForLocale(ANY_LOCALE).isEmpty()); assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.US)); assertNull(lookup.expandShortcut("shortcut", Locale.UK)); assertNull(lookup.expandShortcut("shortcut", Locale.ENGLISH)); assertNull(lookup.expandShortcut("shortcut", Locale.FRENCH)); assertNull(lookup.expandShortcut("shortcut", ANY_LOCALE)); lookup.close(); } public void testShortcutMatchesInputLanguage() { Log.d(TAG, "testShortcutMatchesInputLanguage"); PersonalDictionaryLookup lookup = setUpShortcut(Locale.ENGLISH); verifyWordExists(lookup.getShortcutsForLocale(Locale.US), "shortcut"); verifyWordExists(lookup.getShortcutsForLocale(Locale.UK), "shortcut"); verifyWordExists(lookup.getShortcutsForLocale(Locale.ENGLISH), "shortcut"); assertTrue(lookup.getShortcutsForLocale(Locale.FRENCH).isEmpty()); assertTrue(lookup.getShortcutsForLocale(ANY_LOCALE).isEmpty()); assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.US)); assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.UK)); assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.ENGLISH)); assertNull(lookup.expandShortcut("shortcut", Locale.FRENCH)); assertNull(lookup.expandShortcut("shortcut", ANY_LOCALE)); lookup.close(); } public void testShortcutMatchesAnyLocale() { PersonalDictionaryLookup lookup = setUpShortcut(PersonalDictionaryLookup.ANY_LOCALE); verifyWordExists(lookup.getShortcutsForLocale(Locale.US), "shortcut"); verifyWordExists(lookup.getShortcutsForLocale(Locale.UK), "shortcut"); verifyWordExists(lookup.getShortcutsForLocale(Locale.ENGLISH), "shortcut"); verifyWordExists(lookup.getShortcutsForLocale(Locale.FRENCH), "shortcut"); verifyWordExists(lookup.getShortcutsForLocale(ANY_LOCALE), "shortcut"); assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.US)); assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.UK)); assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.ENGLISH)); assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.FRENCH)); assertEquals("Expansion", lookup.expandShortcut("shortcut", ANY_LOCALE)); lookup.close(); } public void testExactLocaleMatch() { Log.d(TAG, "testExactLocaleMatch"); PersonalDictionaryLookup lookup = setUpWord(Locale.US); verifyWordExists(lookup.getWordsForLocale(Locale.US), "foo"); verifyWordDoesNotExist(lookup.getWordsForLocale(Locale.UK), "foo"); verifyWordDoesNotExist(lookup.getWordsForLocale(Locale.ENGLISH), "foo"); verifyWordDoesNotExist(lookup.getWordsForLocale(Locale.FRENCH), "foo"); verifyWordDoesNotExist(lookup.getWordsForLocale(ANY_LOCALE), "foo"); // Any capitalization variation should match. assertTrue(lookup.isValidWord("foo", Locale.US)); assertTrue(lookup.isValidWord("Foo", Locale.US)); assertTrue(lookup.isValidWord("FOO", Locale.US)); // But similar looking words don't match. assertFalse(lookup.isValidWord("fo", Locale.US)); assertFalse(lookup.isValidWord("fop", Locale.US)); assertFalse(lookup.isValidWord("fooo", Locale.US)); // Other locales, including more general locales won't match. assertFalse(lookup.isValidWord("foo", Locale.ENGLISH)); assertFalse(lookup.isValidWord("foo", Locale.UK)); assertFalse(lookup.isValidWord("foo", Locale.FRENCH)); assertFalse(lookup.isValidWord("foo", ANY_LOCALE)); lookup.close(); } public void testSubLocaleMatch() { Log.d(TAG, "testSubLocaleMatch"); PersonalDictionaryLookup lookup = setUpWord(Locale.ENGLISH); verifyWordExists(lookup.getWordsForLocale(Locale.US), "foo"); verifyWordExists(lookup.getWordsForLocale(Locale.UK), "foo"); verifyWordExists(lookup.getWordsForLocale(Locale.ENGLISH), "foo"); verifyWordDoesNotExist(lookup.getWordsForLocale(Locale.FRENCH), "foo"); verifyWordDoesNotExist(lookup.getWordsForLocale(ANY_LOCALE), "foo"); // Any capitalization variation should match for both en and en_US. assertTrue(lookup.isValidWord("foo", Locale.ENGLISH)); assertTrue(lookup.isValidWord("foo", Locale.US)); assertTrue(lookup.isValidWord("Foo", Locale.US)); assertTrue(lookup.isValidWord("FOO", Locale.US)); // But similar looking words don't match. assertFalse(lookup.isValidWord("fo", Locale.US)); assertFalse(lookup.isValidWord("fop", Locale.US)); assertFalse(lookup.isValidWord("fooo", Locale.US)); lookup.close(); } public void testAllLocalesMatch() { Log.d(TAG, "testAllLocalesMatch"); PersonalDictionaryLookup lookup = setUpWord(null); verifyWordExists(lookup.getWordsForLocale(Locale.US), "foo"); verifyWordExists(lookup.getWordsForLocale(Locale.UK), "foo"); verifyWordExists(lookup.getWordsForLocale(Locale.ENGLISH), "foo"); verifyWordExists(lookup.getWordsForLocale(Locale.FRENCH), "foo"); verifyWordExists(lookup.getWordsForLocale(ANY_LOCALE), "foo"); // Any capitalization variation should match for fr, en and en_US. assertTrue(lookup.isValidWord("foo", ANY_LOCALE)); assertTrue(lookup.isValidWord("foo", Locale.FRENCH)); assertTrue(lookup.isValidWord("foo", Locale.ENGLISH)); assertTrue(lookup.isValidWord("foo", Locale.US)); assertTrue(lookup.isValidWord("Foo", Locale.US)); assertTrue(lookup.isValidWord("FOO", Locale.US)); // But similar looking words don't match. assertFalse(lookup.isValidWord("fo", Locale.US)); assertFalse(lookup.isValidWord("fop", Locale.US)); assertFalse(lookup.isValidWord("fooo", Locale.US)); lookup.close(); } public void testMultipleLocalesMatch() { Log.d(TAG, "testMultipleLocalesMatch"); // Insert "Foo" as capitalized in the personal dictionary under the en_US and en_CA and fr // locales. addWord("Foo", Locale.US, 17, null); addWord("foO", Locale.CANADA, 17, null); addWord("fOo", Locale.FRENCH, 17, null); // Create the PersonalDictionaryLookup and wait until it's loaded. PersonalDictionaryLookup lookup = new PersonalDictionaryLookup(mContext, ExecutorUtils.SPELLING); lookup.open(); // Both en_CA and en_US match. assertTrue(lookup.isValidWord("foo", Locale.CANADA)); assertTrue(lookup.isValidWord("foo", Locale.US)); assertTrue(lookup.isValidWord("foo", Locale.FRENCH)); // Other locales, including more general locales won't match. assertFalse(lookup.isValidWord("foo", Locale.ENGLISH)); assertFalse(lookup.isValidWord("foo", Locale.UK)); assertFalse(lookup.isValidWord("foo", ANY_LOCALE)); lookup.close(); } public void testCaseMatchingForWordsAndShortcuts() { Log.d(TAG, "testCaseMatchingForWordsAndShortcuts"); addWord("Foo", Locale.US, 17, "f"); addWord("bokabu", Locale.US, 17, "Bu"); // Create the PersonalDictionaryLookup and wait until it's loaded. PersonalDictionaryLookup lookup = new PersonalDictionaryLookup(mContext, ExecutorUtils.SPELLING); lookup.open(); // Valid, inspite of capitalization in US but not in other // locales. assertTrue(lookup.isValidWord("Foo", Locale.US)); assertTrue(lookup.isValidWord("foo", Locale.US)); assertFalse(lookup.isValidWord("Foo", Locale.UK)); assertFalse(lookup.isValidWord("foo", Locale.UK)); // Valid in all forms in US. assertTrue(lookup.isValidWord("bokabu", Locale.US)); assertTrue(lookup.isValidWord("BOKABU", Locale.US)); assertTrue(lookup.isValidWord("BokaBU", Locale.US)); // Correct capitalization; sensitive to shortcut casing & locale. assertEquals("Foo", lookup.expandShortcut("f", Locale.US)); assertNull(lookup.expandShortcut("f", Locale.UK)); // Correct capitalization; sensitive to shortcut casing & locale. assertEquals("bokabu", lookup.expandShortcut("Bu", Locale.US)); assertNull(lookup.expandShortcut("Bu", Locale.UK)); assertNull(lookup.expandShortcut("bu", Locale.US)); // Verify that raw strings are retained for #getWordsForLocale. verifyWordExists(lookup.getWordsForLocale(Locale.US), "Foo"); verifyWordDoesNotExist(lookup.getWordsForLocale(Locale.US), "foo"); } public void testManageListeners() { Log.d(TAG, "testManageListeners"); PersonalDictionaryLookup lookup = new PersonalDictionaryLookup(mContext, ExecutorUtils.SPELLING); PersonalDictionaryListener listener = mock(PersonalDictionaryListener.class); // Add the same listener a bunch of times. It doesn't make a difference. lookup.addListener(listener); lookup.addListener(listener); lookup.addListener(listener); lookup.notifyListeners(); verify(listener, times(1)).onUpdate(); // Remove the same listener a bunch of times. It doesn't make a difference. lookup.removeListener(listener); lookup.removeListener(listener); lookup.removeListener(listener); lookup.notifyListeners(); verifyNoMoreInteractions(listener); } public void testReload() { Log.d(TAG, "testReload"); // Insert "foo". Uri uri = addWord("foo", Locale.US, 17, null); // Create the PersonalDictionaryLookup and wait until it's loaded. PersonalDictionaryLookup lookup = new PersonalDictionaryLookup(mContext, ExecutorUtils.SPELLING); lookup.open(); // "foo" should match. assertTrue(lookup.isValidWord("foo", Locale.US)); // "bar" shouldn't match. assertFalse(lookup.isValidWord("bar", Locale.US)); // Now delete "foo" and add "bar". deleteWord(uri); addWord("bar", Locale.US, 18, null); // Wait a little bit before expecting a change. The time we wait should be greater than // PersonalDictionaryLookup.RELOAD_DELAY_MS. try { Thread.sleep(PersonalDictionaryLookup.RELOAD_DELAY_MS + 1000); } catch (InterruptedException e) { } // Perform lookups again. Reload should have occured. // // "foo" should not match. assertFalse(lookup.isValidWord("foo", Locale.US)); // "bar" should match. assertTrue(lookup.isValidWord("bar", Locale.US)); lookup.close(); } public void testDictionaryStats() { Log.d(TAG, "testDictionaryStats"); // Insert "foo" and "bar". Only "foo" has a shortcut. Uri uri = addWord("foo", Locale.GERMANY, 17, "f"); addWord("bar", Locale.GERMANY, 17, null); // Create the PersonalDictionaryLookup and wait until it's loaded. PersonalDictionaryLookup lookup = new PersonalDictionaryLookup(mContext, ExecutorUtils.SPELLING); lookup.open(); // "foo" should match. assertTrue(lookup.isValidWord("foo", Locale.GERMANY)); // "bar" should match. assertTrue(lookup.isValidWord("bar", Locale.GERMANY)); // "foo" should have a shortcut. assertEquals("foo", lookup.expandShortcut("f", Locale.GERMANY)); // Now delete "foo". deleteWord(uri); // Wait a little bit before expecting a change. The time we wait should be greater than // PersonalDictionaryLookup.RELOAD_DELAY_MS. try { Thread.sleep(PersonalDictionaryLookup.RELOAD_DELAY_MS + 1000); } catch (InterruptedException e) { } // Perform lookups again. Reload should have occured. // // "foo" should not match. assertFalse(lookup.isValidWord("foo", Locale.GERMANY)); // "foo" should not have a shortcut. assertNull(lookup.expandShortcut("f", Locale.GERMANY)); // "bar" should still match. assertTrue(lookup.isValidWord("bar", Locale.GERMANY)); lookup.close(); } public void testClose() { Log.d(TAG, "testClose"); // Insert "foo". Uri uri = addWord("foo", Locale.US, 17, null); // Create the PersonalDictionaryLookup and wait until it's loaded. PersonalDictionaryLookup lookup = new PersonalDictionaryLookup(mContext, ExecutorUtils.SPELLING); lookup.open(); // "foo" should match. assertTrue(lookup.isValidWord("foo", Locale.US)); // "bar" shouldn't match. assertFalse(lookup.isValidWord("bar", Locale.US)); // Now close (prevents further reloads). lookup.close(); // Now delete "foo" and add "bar". deleteWord(uri); addWord("bar", Locale.US, 18, null); // Wait a little bit before expecting a change. The time we wait should be greater than // PersonalDictionaryLookup.RELOAD_DELAY_MS. try { Thread.sleep(PersonalDictionaryLookup.RELOAD_DELAY_MS + 1000); } catch (InterruptedException e) { } // Perform lookups again. Reload should not have occurred. // // "foo" should stil match. assertTrue(lookup.isValidWord("foo", Locale.US)); // "bar" should still not match. assertFalse(lookup.isValidWord("bar", Locale.US)); } }