diff options
21 files changed, 442 insertions, 208 deletions
diff --git a/java/res/drawable-hdpi/more_suggestions_hint.png b/java/res/drawable-hdpi/more_suggestions_hint.png Binary files differindex 4515f4434..93604752b 100644 --- a/java/res/drawable-hdpi/more_suggestions_hint.png +++ b/java/res/drawable-hdpi/more_suggestions_hint.png diff --git a/java/res/drawable-mdpi/more_suggestions_hint.png b/java/res/drawable-mdpi/more_suggestions_hint.png Binary files differindex 6168de353..7352810d3 100644 --- a/java/res/drawable-mdpi/more_suggestions_hint.png +++ b/java/res/drawable-mdpi/more_suggestions_hint.png diff --git a/java/res/drawable-xhdpi/more_suggestions_hint.png b/java/res/drawable-xhdpi/more_suggestions_hint.png Binary files differindex f577a3617..35fb42087 100644 --- a/java/res/drawable-xhdpi/more_suggestions_hint.png +++ b/java/res/drawable-xhdpi/more_suggestions_hint.png diff --git a/java/res/values-sw768dp/config.xml b/java/res/values-sw768dp/config.xml index 0f8f106b7..d88020952 100644 --- a/java/res/values-sw768dp/config.xml +++ b/java/res/values-sw768dp/config.xml @@ -32,7 +32,6 @@ <bool name="config_auto_correction_spacebar_led_enabled">false</bool> <!-- Showing mini keyboard, just above the touched point if true, aligned to the key if false --> <bool name="config_show_mini_keyboard_at_touched_point">true</bool> - <integer name="config_delay_update_suggestions">180</integer> <!-- Long pressing space will invoke IME switcher if > 0, never invoke IME switcher if == 0 --> <integer name="config_long_press_space_key_timeout">0</integer> <!-- This configuration is the index of the array {@link KeyboardSwitcher.KEYBOARD_THEMES}. --> diff --git a/java/res/xml/method.xml b/java/res/xml/method.xml index 0bf560d5a..452294e04 100644 --- a/java/res/xml/method.xml +++ b/java/res/xml/method.xml @@ -31,13 +31,13 @@ android:label="@string/subtype_en_US" android:imeSubtypeLocale="en_US" android:imeSubtypeMode="keyboard" - android:imeSubtypeExtraValue="TrySuppressingImeSwitcher" + android:imeSubtypeExtraValue="TrySuppressingImeSwitcher,AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_en_GB" android:imeSubtypeLocale="en_GB" android:imeSubtypeMode="keyboard" - android:imeSubtypeExtraValue="TrySuppressingImeSwitcher" + android:imeSubtypeExtraValue="TrySuppressingImeSwitcher,AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" @@ -48,61 +48,73 @@ android:label="@string/subtype_generic" android:imeSubtypeLocale="cs" android:imeSubtypeMode="keyboard" + android:imeSubtypeExtraValue="AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" android:imeSubtypeLocale="da" android:imeSubtypeMode="keyboard" + android:imeSubtypeExtraValue="AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" android:imeSubtypeLocale="de" android:imeSubtypeMode="keyboard" + android:imeSubtypeExtraValue="AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_de_qwerty" android:imeSubtypeLocale="de_ZZ" android:imeSubtypeMode="keyboard" + android:imeSubtypeExtraValue="AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" android:imeSubtypeLocale="es" android:imeSubtypeMode="keyboard" + android:imeSubtypeExtraValue="AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" android:imeSubtypeLocale="fi" android:imeSubtypeMode="keyboard" + android:imeSubtypeExtraValue="AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" android:imeSubtypeLocale="fr" android:imeSubtypeMode="keyboard" + android:imeSubtypeExtraValue="AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" android:imeSubtypeLocale="fr_CA" android:imeSubtypeMode="keyboard" + android:imeSubtypeExtraValue="AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" android:imeSubtypeLocale="fr_CH" android:imeSubtypeMode="keyboard" + android:imeSubtypeExtraValue="AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" android:imeSubtypeLocale="hr" android:imeSubtypeMode="keyboard" + android:imeSubtypeExtraValue="AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" android:imeSubtypeLocale="hu" android:imeSubtypeMode="keyboard" + android:imeSubtypeExtraValue="AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" android:imeSubtypeLocale="it" android:imeSubtypeMode="keyboard" + android:imeSubtypeExtraValue="AsciiCapable" /> <!-- Java uses the deprecated "iw" code instead of the standard "he" code for Hebrew. --> <subtype android:icon="@drawable/ic_subtype_keyboard" @@ -114,21 +126,25 @@ android:label="@string/subtype_generic" android:imeSubtypeLocale="nb" android:imeSubtypeMode="keyboard" + android:imeSubtypeExtraValue="AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" android:imeSubtypeLocale="nl" android:imeSubtypeMode="keyboard" + android:imeSubtypeExtraValue="AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" android:imeSubtypeLocale="pl" android:imeSubtypeMode="keyboard" + android:imeSubtypeExtraValue="AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" android:imeSubtypeLocale="pt" android:imeSubtypeMode="keyboard" + android:imeSubtypeExtraValue="AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" @@ -144,10 +160,12 @@ android:label="@string/subtype_generic" android:imeSubtypeLocale="sv" android:imeSubtypeMode="keyboard" + android:imeSubtypeExtraValue="AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" android:imeSubtypeLocale="tr" android:imeSubtypeMode="keyboard" + android:imeSubtypeExtraValue="AsciiCapable" /> </input-method> diff --git a/java/src/com/android/inputmethod/deprecated/languageswitcher/InputLanguageSelection.java b/java/src/com/android/inputmethod/deprecated/languageswitcher/InputLanguageSelection.java index e75559e62..7eb5acda8 100644 --- a/java/src/com/android/inputmethod/deprecated/languageswitcher/InputLanguageSelection.java +++ b/java/src/com/android/inputmethod/deprecated/languageswitcher/InputLanguageSelection.java @@ -21,7 +21,6 @@ import com.android.inputmethod.latin.DictionaryFactory; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.Settings; import com.android.inputmethod.latin.SharedPreferencesCompat; -import com.android.inputmethod.latin.SubtypeSwitcher; import com.android.inputmethod.latin.Utils; import org.xmlpull.v1.XmlPullParserException; @@ -237,12 +236,12 @@ public class InputLanguageSelection extends PreferenceActivity { if (finalSize == 0) { preprocess[finalSize++] = - new LocaleEntry(SubtypeSwitcher.getFullDisplayName(l, false), l); + new LocaleEntry(Utils.getFullDisplayName(l, false), l); } else { if (s.equals("zz_ZZ")) { // ignore this locale } else { - final String displayName = SubtypeSwitcher.getFullDisplayName(l, false); + final String displayName = Utils.getFullDisplayName(l, false); preprocess[finalSize++] = new LocaleEntry(displayName, l); } } diff --git a/java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java b/java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java index dcb826e8f..71d15dc3d 100644 --- a/java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java +++ b/java/src/com/android/inputmethod/deprecated/voice/RecognitionView.java @@ -18,6 +18,7 @@ package com.android.inputmethod.deprecated.voice; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.SubtypeSwitcher; +import com.android.inputmethod.latin.Utils; import android.content.Context; import android.content.res.Resources; @@ -221,7 +222,7 @@ public class RecognitionView { Locale locale = SubtypeSwitcher.getInstance().getInputLocale(); mLanguage.setVisibility(View.VISIBLE); - mLanguage.setText(SubtypeSwitcher.getFullDisplayName(locale, true)); + mLanguage.setText(Utils.getFullDisplayName(locale, true)); mPopupLayout.setBackgroundDrawable(mListeningBorder); break; diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index 8bf82807a..b1212f424 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -258,8 +258,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha final SoftReference<LatinKeyboard> ref = mKeyboardCache.get(id); LatinKeyboard keyboard = (ref == null) ? null : ref.get(); if (keyboard == null) { - final Locale savedLocale = Utils.setSystemLocale( - mResources, mSubtypeSwitcher.getInputLocale()); + final Locale savedLocale = Utils.setSystemLocale(mResources, id.mLocale); try { keyboard = new LatinKeyboard.Builder(mThemeContext).load(id).build(); } finally { @@ -319,13 +318,19 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha final boolean hasSettingsKey = settingsKeyEnabled && !noSettingsKey; final int f2KeyMode = getF2KeyMode(settingsKeyEnabled, noSettingsKey); final boolean hasShortcutKey = voiceKeyEnabled && (isSymbols != voiceKeyOnMain); + final boolean forceAscii = Utils.inPrivateImeOptions( + mPackageName, LatinIME.IME_OPTION_FORCE_ASCII, editorInfo); + final boolean asciiCapable = mSubtypeSwitcher.currentSubtypeContainsExtraValueKey( + LatinIME.SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE); + final Locale locale = (forceAscii && !asciiCapable) + ? Locale.US : mSubtypeSwitcher.getInputLocale(); final Configuration conf = mResources.getConfiguration(); final DisplayMetrics dm = mResources.getDisplayMetrics(); return new KeyboardId( - mResources.getResourceEntryName(xmlId), xmlId, mSubtypeSwitcher.getInputLocale(), - conf.orientation, dm.widthPixels, mode, editorInfo, - hasSettingsKey, f2KeyMode, noSettingsKey, voiceKeyEnabled, hasShortcutKey); + mResources.getResourceEntryName(xmlId), xmlId, locale, conf.orientation, + dm.widthPixels, mode, editorInfo, hasSettingsKey, f2KeyMode, noSettingsKey, + voiceKeyEnabled, hasShortcutKey); } public int getKeyboardMode() { diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java index 1b6f57b92..345272044 100644 --- a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java +++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java @@ -141,7 +141,7 @@ public class LatinKeyboard extends Keyboard { } } - public void setSpacebarTextFadeFactor(float fadeFactor, LatinKeyboardView view) { + public void setSpacebarTextFadeFactor(float fadeFactor, KeyboardView view) { mSpacebarTextFadeFactor = fadeFactor; updateSpacebarForLocale(false); if (view != null) @@ -154,7 +154,7 @@ public class LatinKeyboard extends Keyboard { return newColor; } - public void updateShortcutKey(boolean available, LatinKeyboardView view) { + public void updateShortcutKey(boolean available, KeyboardView view) { if (mShortcutKey == null) return; mShortcutKey.setEnabled(available); @@ -193,9 +193,8 @@ public class LatinKeyboard extends Keyboard { && Utils.hasMultipleEnabledIMEsOrSubtypes(imm, true /* include aux subtypes */); mSpaceKey.setNeedsSpecialPopupHint(shouldShowInputMethodPicker); // If application locales are explicitly selected. - if (mSubtypeSwitcher.needsToDisplayLanguage()) { - mSpaceKey.setIcon(getSpaceDrawable( - mSubtypeSwitcher.getInputLocale(), isAutoCorrection)); + if (mSubtypeSwitcher.needsToDisplayLanguage(mId.mLocale)) { + mSpaceKey.setIcon(getSpaceDrawable(mId.mLocale, isAutoCorrection)); } else if (isAutoCorrection) { mSpaceKey.setIcon(getSpaceDrawable(null, true)); } else { @@ -216,7 +215,7 @@ public class LatinKeyboard extends Keyboard { final Rect bounds = new Rect(); // Estimate appropriate language name text size to fit in maxTextWidth. - String language = SubtypeSwitcher.getFullDisplayName(locale, true); + String language = Utils.getFullDisplayName(locale, true); int textWidth = getTextWidth(paint, language, origTextSize, bounds); // Assuming text width and text size are proportional to each other. float textSize = origTextSize * Math.min(width / textWidth, 1.0f); @@ -228,7 +227,7 @@ public class LatinKeyboard extends Keyboard { final boolean useShortName; if (useMiddleName) { - language = SubtypeSwitcher.getMiddleDisplayLanguage(locale); + language = Utils.getMiddleDisplayLanguage(locale); textWidth = getTextWidth(paint, language, origTextSize, bounds); textSize = origTextSize * Math.min(width / textWidth, 1.0f); useShortName = (textSize / origTextSize < MINIMUM_SCALE_OF_LANGUAGE_NAME) @@ -238,7 +237,7 @@ public class LatinKeyboard extends Keyboard { } if (useShortName) { - language = SubtypeSwitcher.getShortDisplayLanguage(locale); + language = Utils.getShortDisplayLanguage(locale); textWidth = getTextWidth(paint, language, origTextSize, bounds); textSize = origTextSize * Math.min(width / textWidth, 1.0f); } diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index ed5f83b3b..24bb7b78a 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -63,10 +63,10 @@ public class BinaryDictionaryFileDumper { } /** - * Queries a content provider for the list of dictionaries for a specific locale + * Queries a content provider for the list of word lists for a specific locale * available to copy into Latin IME. */ - private static List<String> getDictIdList(final Locale locale, final Context context) { + private static List<String> getWordListIds(final Locale locale, final Context context) { final ContentResolver resolver = context.getContentResolver(); final Uri dictionaryPackUri = getProviderUri(locale.toString()); @@ -87,77 +87,150 @@ public class BinaryDictionaryFileDumper { return list; } + /** - * Queries a content provider for dictionary data for some locale and cache the returned files - * - * This will query a content provider for dictionary data for a given locale, and copy the - * files locally so that they can be mmap'ed. This may overwrite previously cached dictionaries - * with newer versions if a newer version is made available by the content provider. - * @returns the addresses of the files, or null if no data could be obtained. - * @throw FileNotFoundException if the provider returns non-existent data. - * @throw IOException if the provider-returned data could not be read. + * Helper method to encapsulate exception handling. */ - public static List<AssetFileAddress> cacheDictionariesFromContentProvider(final Locale locale, - final Context context) { - final ContentResolver resolver = context.getContentResolver(); - final List<String> idList = getDictIdList(locale, context); - final List<AssetFileAddress> fileAddressList = new ArrayList<AssetFileAddress>(); - for (String id : idList) { - final Uri wordListUri = getProviderUri(id); + private static AssetFileDescriptor openAssetFileDescriptor(final ContentResolver resolver, + final Uri uri) { + try { + return resolver.openAssetFileDescriptor(uri, "r"); + } catch (FileNotFoundException e) { + // I don't want to log the word list URI here for security concerns + Log.e(TAG, "Could not find a word list from the dictionary provider."); + return null; + } + } + + /** + * Caches a word list the id of which is passed as an argument. This will write the file + * to the cache file name designated by its id and locale, overwriting it if already present + * and creating it (and its containing directory) if necessary. + */ + private static AssetFileAddress cacheWordList(final String id, final Locale locale, + final ContentResolver resolver, final Context context) { + + final int COMPRESSED_CRYPTED_COMPRESSED = 0; + final int CRYPTED_COMPRESSED = 1; + final int COMPRESSED_CRYPTED = 2; + final int COMPRESSED_ONLY = 3; + final int CRYPTED_ONLY = 4; + final int NONE = 5; + final int MODE_MIN = COMPRESSED_CRYPTED_COMPRESSED; + final int MODE_MAX = NONE; + + final Uri wordListUri = getProviderUri(id); + final String outputFileName = BinaryDictionaryGetter.getCacheFileName(id, locale, context); + + for (int mode = MODE_MIN; mode <= MODE_MAX; ++mode) { + InputStream originalSourceStream = null; + InputStream inputStream = null; + FileOutputStream outputStream = null; AssetFileDescriptor afd = null; try { - afd = resolver.openAssetFileDescriptor(wordListUri, "r"); - } catch (FileNotFoundException e) { - // leave null inside afd and continue - } - if (null == afd) continue; - try { - final String fileName = copyFileTo(afd.createInputStream(), - BinaryDictionaryGetter.getCacheFileName(id, locale, context)); - afd.close(); + // Open input. + afd = openAssetFileDescriptor(resolver, wordListUri); + // If we can't open it at all, don't even try a number of times. + if (null == afd) return null; + originalSourceStream = afd.createInputStream(); + // Open output. + outputStream = new FileOutputStream(outputFileName); + // Get the appropriate decryption method for this try + switch (mode) { + case COMPRESSED_CRYPTED_COMPRESSED: + inputStream = FileTransforms.getUncompressedStream( + FileTransforms.getDecryptedStream( + FileTransforms.getUncompressedStream( + originalSourceStream))); + break; + case CRYPTED_COMPRESSED: + inputStream = FileTransforms.getUncompressedStream( + FileTransforms.getDecryptedStream(originalSourceStream)); + break; + case COMPRESSED_CRYPTED: + inputStream = FileTransforms.getDecryptedStream( + FileTransforms.getUncompressedStream(originalSourceStream)); + break; + case COMPRESSED_ONLY: + inputStream = FileTransforms.getUncompressedStream(originalSourceStream); + break; + case CRYPTED_ONLY: + inputStream = FileTransforms.getDecryptedStream(originalSourceStream); + break; + case NONE: + inputStream = originalSourceStream; + break; + } + copyFileTo(inputStream, outputStream); if (0 >= resolver.delete(wordListUri, null, null)) { - // I'd rather not print the word list ID to the log out of security concerns Log.e(TAG, "Could not have the dictionary pack delete a word list"); } - fileAddressList.add(AssetFileAddress.makeFromFileName(fileName)); - } catch (IOException e) { - // Can't read the file for some reason. Continue onto the next file. - Log.e(TAG, "Cannot read a word list from the dictionary pack : " + e); + // Success! Close files (through the finally{} clause) and return. + return AssetFileAddress.makeFromFileName(outputFileName); + } catch (Exception e) { + Log.e(TAG, "Can't open word list in mode " + mode + " : " + e); + // Try the next method. + } finally { + // Ignore exceptions while closing files. + try { + // afd.close() will close inputStream, we should not call inputStream.close(). + if (null != afd) afd.close(); + } catch (Exception e) { + Log.e(TAG, "Exception while closing a cross-process file descriptor : " + e); + } + try { + if (null != outputStream) outputStream.close(); + } catch (Exception e) { + Log.e(TAG, "Exception while closing a file : " + e); + } } } - return fileAddressList; + + // We could not copy the file at all. This is very unexpected. + // I'd rather not print the word list ID to the log out of security concerns + Log.e(TAG, "Could not copy a word list. Will not be able to use it."); + // If we can't copy it we should probably delete it to avoid trying to copy it over + // and over each time we open LatinIME. + if (0 >= resolver.delete(wordListUri, null, null)) { + Log.e(TAG, "In addition, we were unable to delete it."); + } + return null; } /** - * Accepts a resource number as dictionary data for some locale and returns the name of a file. + * Queries a content provider for word list data for some locale and cache the returned files * - * This will make the resource the cached dictionary for this locale, overwriting any previous - * cached data. + * This will query a content provider for word list data for a given locale, and copy the + * files locally so that they can be mmap'ed. This may overwrite previously cached word lists + * with newer versions if a newer version is made available by the content provider. + * @returns the addresses of the word list files, or null if no data could be obtained. + * @throw FileNotFoundException if the provider returns non-existent data. + * @throw IOException if the provider-returned data could not be read. */ - public static String getDictionaryFileFromResource(int resource, Locale locale, - Context context) throws FileNotFoundException, IOException { - final Resources res = context.getResources(); - final Locale savedLocale = Utils.setSystemLocale(res, locale); - final InputStream stream = res.openRawResource(resource); - Utils.setSystemLocale(res, savedLocale); - return copyFileTo(stream, - BinaryDictionaryGetter.getCacheFileName(Integer.toString(resource), - locale, context)); + public static List<AssetFileAddress> cacheWordListsFromContentProvider(final Locale locale, + final Context context) { + final ContentResolver resolver = context.getContentResolver(); + final List<String> idList = getWordListIds(locale, context); + final List<AssetFileAddress> fileAddressList = new ArrayList<AssetFileAddress>(); + for (String id : idList) { + final AssetFileAddress afd = cacheWordList(id, locale, resolver, context); + if (null != afd) { + fileAddressList.add(afd); + } + } + return fileAddressList; } /** - * Copies the data in an input stream to a target file, creating the file if necessary and - * overwriting it if it already exists. + * Copies the data in an input stream to a target file. * @param input the stream to be copied. - * @param outputFileName the name of a file to copy the data to. It is created if necessary. + * @param outputFile an outputstream to copy the data to. */ - private static String copyFileTo(final InputStream input, final String outputFileName) + private static void copyFileTo(final InputStream input, final FileOutputStream output) throws FileNotFoundException, IOException { final byte[] buffer = new byte[FILE_READ_BUFFER_SIZE]; - final FileOutputStream output = new FileOutputStream(outputFileName); for (int readBytes = input.read(buffer); readBytes >= 0; readBytes = input.read(buffer)) output.write(buffer, 0, readBytes); input.close(); - return outputFileName; } } diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java index 5d2dab0a9..38344300c 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java @@ -205,7 +205,7 @@ class BinaryDictionaryGetter { * @param context the context on which to open the files upon. * @return an array of binary dictionary files, which may be empty but may not be null. */ - private static File[] getCachedDictionaryList(final Locale locale, + private static File[] getCachedWordLists(final Locale locale, final Context context) { final String directoryName = getCacheDirectoryForLocale(locale, context); final File[] cacheFiles = new File(directoryName).listFiles(); @@ -235,11 +235,11 @@ class BinaryDictionaryGetter { public static List<AssetFileAddress> getDictionaryFiles(final Locale locale, final Context context, final int fallbackResId) { - // cacheDictionariesFromContentProvider returns the list of files it copied to local + // cacheWordListsFromContentProvider returns the list of files it copied to local // storage, but we don't really care about what was copied NOW: what we want is the // list of everything we ever cached, so we ignore the return value. - BinaryDictionaryFileDumper.cacheDictionariesFromContentProvider(locale, context); - final File[] cachedDictionaryList = getCachedDictionaryList(locale, context); + BinaryDictionaryFileDumper.cacheWordListsFromContentProvider(locale, context); + final File[] cachedWordLists = getCachedWordLists(locale, context); final String mainDictId = getMainDictId(locale); @@ -247,8 +247,8 @@ class BinaryDictionaryGetter { boolean foundMainDict = false; final ArrayList<AssetFileAddress> fileList = new ArrayList<AssetFileAddress>(); - // cachedDictionaryList may not be null, see doc for getCachedDictionaryList - for (final File f : cachedDictionaryList) { + // cachedWordLists may not be null, see doc for getCachedDictionaryList + for (final File f : cachedWordLists) { final String wordListId = getWordListIdFromFileName(f.getName()); if (wordListId.equals(mainDictId)) { foundMainDict = true; diff --git a/java/src/com/android/inputmethod/latin/CandidateView.java b/java/src/com/android/inputmethod/latin/CandidateView.java index d46b4b5b5..5f20c70b4 100644 --- a/java/src/com/android/inputmethod/latin/CandidateView.java +++ b/java/src/com/android/inputmethod/latin/CandidateView.java @@ -413,8 +413,10 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo // TODO: This "more suggestions hint" should have nicely designed icon. word.setCompoundDrawablesWithIntrinsicBounds( null, null, null, mMoreCandidateHint); + // HACK: To align with other TextView that has no compound drawables. + word.setCompoundDrawablePadding(-mMoreCandidateHint.getIntrinsicHeight()); } else { - word.setCompoundDrawables(null, null, null, null); + word.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null); } // Disable this candidate if the suggestion is null or empty. @@ -426,7 +428,7 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo word.setText(text); // TextView.setText() resets text scale x to 1.0. word.setTextScaleX(scaleX); stripView.addView(word); - setLayoutWeight(word, getCandidateWeight(index), mCandidateStripHeight); + setLayoutWeight(word, getCandidateWeight(index), MATCH_PARENT); if (DBG) { final CharSequence debugInfo = getDebugInfo(suggestions, pos); @@ -526,8 +528,7 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo mPreviewPopup = new PopupWindow(context); mPreviewText = (TextView) inflater.inflate(R.layout.candidate_preview, null); - mPreviewPopup.setWindowLayoutMode(ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT); + mPreviewPopup.setWindowLayoutMode(WRAP_CONTENT, WRAP_CONTENT); mPreviewPopup.setContentView(mPreviewText); mPreviewPopup.setBackgroundDrawable(null); diff --git a/java/src/com/android/inputmethod/latin/FileTransforms.java b/java/src/com/android/inputmethod/latin/FileTransforms.java new file mode 100644 index 000000000..d0374e01e --- /dev/null +++ b/java/src/com/android/inputmethod/latin/FileTransforms.java @@ -0,0 +1,40 @@ +/* + * 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.util.Log; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.zip.GZIPInputStream; + +public class FileTransforms { + public static OutputStream getCryptedStream(OutputStream out) { + // Crypt the stream. + return out; + } + + public static InputStream getDecryptedStream(InputStream in) { + // Decrypt the stream. + return in; + } + + public static InputStream getUncompressedStream(InputStream in) throws IOException { + return new GZIPInputStream(in); + } +} diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 394414d03..552517bc8 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -106,6 +106,18 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar */ public static final String IME_OPTION_NO_SETTINGS_KEY = "noSettingsKey"; + /** + * The private IME option used to indicate that the given text field needs + * ASCII code points input. + */ + public static final String IME_OPTION_FORCE_ASCII = "forceAscii"; + + /** + * The subtype extra value used to indicate that the subtype keyboard layout is capable for + * typing ASCII characters. + */ + public static final String SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE = "AsciiCapable"; + private static final int EXTENDED_TOUCHABLE_REGION_HEIGHT = 100; // How many continuous deletes at which to start deleting at a higher speed. diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java index 0a391a77e..d969e39eb 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java +++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java @@ -415,7 +415,10 @@ public class SubtypeSwitcher { return mEnabledKeyboardSubtypesOfCurrentInputMethod.size(); } - public boolean needsToDisplayLanguage() { + public boolean needsToDisplayLanguage(Locale keyboardLocale) { + if (!keyboardLocale.equals(mInputLocale)) { + return false; + } return mNeedsToDisplayLanguage; } @@ -492,36 +495,8 @@ public class SubtypeSwitcher { KeyboardSwitcher.getInstance().getKeyboardView().getWindowToken()); } - public static String getFullDisplayName(Locale locale, boolean returnsNameInThisLocale) { - if (returnsNameInThisLocale) { - return toTitleCase(SubtypeLocale.getFullDisplayName(locale), locale); - } else { - return toTitleCase(locale.getDisplayName(), locale); - } - } - - public static String getDisplayLanguage(Locale locale) { - return toTitleCase(SubtypeLocale.getFullDisplayName(locale), locale); - } - - public static String getMiddleDisplayLanguage(Locale locale) { - return toTitleCase((Utils.constructLocaleFromString( - locale.getLanguage()).getDisplayLanguage(locale)), locale); - } - - public static String getShortDisplayLanguage(Locale locale) { - return toTitleCase(locale.getLanguage(), locale); - } - - private static String toTitleCase(String s, Locale locale) { - if (s.length() == 0) { - return s; - } - return s.toUpperCase(locale).charAt(0) + s.substring(1); - } - public String getInputLanguageName() { - return getDisplayLanguage(getInputLocale()); + return Utils.getDisplayLanguage(getInputLocale()); } ///////////////////////////// diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java index 36fbfd951..ff051dcbb 100644 --- a/java/src/com/android/inputmethod/latin/Utils.java +++ b/java/src/com/android/inputmethod/latin/Utils.java @@ -769,4 +769,32 @@ public class Utils { StringBuilderPool.recycle((StringBuilder)garbage); } } + + public static String getFullDisplayName(Locale locale, boolean returnsNameInThisLocale) { + if (returnsNameInThisLocale) { + return toTitleCase(SubtypeLocale.getFullDisplayName(locale), locale); + } else { + return toTitleCase(locale.getDisplayName(), locale); + } + } + + public static String getDisplayLanguage(Locale locale) { + return toTitleCase(SubtypeLocale.getFullDisplayName(locale), locale); + } + + public static String getMiddleDisplayLanguage(Locale locale) { + return toTitleCase((constructLocaleFromString( + locale.getLanguage()).getDisplayLanguage(locale)), locale); + } + + public static String getShortDisplayLanguage(Locale locale) { + return toTitleCase(locale.getLanguage(), locale); + } + + private static String toTitleCase(String s, Locale locale) { + if (s.length() <= 1) { + return s; + } + return s.toUpperCase(locale).charAt(0) + s.substring(1); + } } diff --git a/native/src/correction.cpp b/native/src/correction.cpp index fb160149d..fcb8bea5c 100644 --- a/native/src/correction.cpp +++ b/native/src/correction.cpp @@ -190,15 +190,15 @@ void Correction::startToTraverseAllNodes() { } bool Correction::needsToPrune() const { - return (mOutputIndex - 1 >= (mTransposedPos >= 0 ? mInputLength - 1 : mMaxDepth) - || mProximityCount > mMaxEditDistance); + return mOutputIndex - 1 >= mMaxDepth || mProximityCount > mMaxEditDistance; } +// TODO: inline? Correction::CorrectionType Correction::processSkipChar( - const int32_t c, const bool isTerminal) { + const int32_t c, const bool isTerminal, const bool inputIndexIncremented) { mWord[mOutputIndex] = c; if (needsToTraverseAllNodes() && isTerminal) { - mTerminalInputIndex = mInputIndex; + mTerminalInputIndex = mInputIndex - (inputIndexIncremented ? 1 : 0); mTerminalOutputIndex = mOutputIndex; incrementOutputIndex(); return TRAVERSE_ALL_ON_TERMINAL; @@ -210,15 +210,28 @@ Correction::CorrectionType Correction::processSkipChar( Correction::CorrectionType Correction::processCharAndCalcState( const int32_t c, const bool isTerminal) { + const int correctionCount = (mSkippedCount + mExcessiveCount + mTransposedCount); + // TODO: Change the limit if we'll allow two or more corrections + const bool noCorrectionsHappenedSoFar = correctionCount == 0; + const bool canTryCorrection = noCorrectionsHappenedSoFar; if (mNeedsToTraverseAllNodes || isQuote(c)) { - if (mLastCharExceeded > 0 && mInputIndex == mInputLength - 1 - && mProximityInfo->getMatchedProximityId(mInputIndex, c, false) - == ProximityInfo::SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR) { - mLastCharExceeded = false; - --mExcessiveCount; + bool incremented = false; + if (mLastCharExceeded && mInputIndex == mInputLength - 1) { + // TODO: Do not check the proximity if EditDistance exceeds the threshold + const int matchId = mProximityInfo->getMatchedProximityId(mInputIndex, c, true); + if (matchId == ProximityInfo::SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR) { + mLastCharExceeded = false; + --mExcessiveCount; + } else if (matchId == ProximityInfo::NEAR_PROXIMITY_CHAR) { + mLastCharExceeded = false; + --mExcessiveCount; + ++mProximityCount; + } + incrementInputIndex(); + incremented = true; } - return processSkipChar(c, isTerminal); + return processSkipChar(c, isTerminal, incremented); } if (mExcessivePos >= 0) { @@ -226,7 +239,7 @@ Correction::CorrectionType Correction::processCharAndCalcState( mExcessivePos = mOutputIndex; } if (mExcessivePos < mInputLength - 1) { - mExceeding = mExcessivePos == mInputIndex; + mExceeding = mExcessivePos == mInputIndex && canTryCorrection; } } @@ -237,7 +250,7 @@ Correction::CorrectionType Correction::processCharAndCalcState( } mSkipPos = mOutputIndex; } - mSkipping = mSkipPos == mOutputIndex; + mSkipping = mSkipPos == mOutputIndex && canTryCorrection; } if (mTransposedPos >= 0) { @@ -245,7 +258,7 @@ Correction::CorrectionType Correction::processCharAndCalcState( mTransposedPos = mOutputIndex; } if (mTransposedPos < mInputLength - 1) { - mTransposing = mInputIndex == mTransposedPos; + mTransposing = mInputIndex == mTransposedPos && canTryCorrection; } } @@ -258,46 +271,95 @@ Correction::CorrectionType Correction::processCharAndCalcState( } else if (mCorrectionStates[mOutputIndex].mExceeding) { --mTransposedCount; ++mExcessiveCount; + --mExcessivePos; incrementInputIndex(); } else { --mTransposedCount; + if (DEBUG_CORRECTION) { + DUMP_WORD(mWord, mOutputIndex); + LOGI("UNRELATED(0): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount, + mTransposedCount, mExcessiveCount, c); + } return UNRELATED; } } - // TODO: sum counters - const bool checkProximityChars = - !(mSkippedCount > 0 || mExcessivePos >= 0 || mTransposedPos >= 0); + // TODO: Change the limit if we'll allow two or more proximity chars with corrections + const bool checkProximityChars = noCorrectionsHappenedSoFar || mProximityCount == 0; const int matchedProximityCharId = secondTransposing ? ProximityInfo::SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR : mProximityInfo->getMatchedProximityId(mInputIndex, c, checkProximityChars); if (ProximityInfo::UNRELATED_CHAR == matchedProximityCharId) { - if (mInputIndex - 1 < mInputLength && (mExceeding || mTransposing) + // TODO: Optimize + // As the current char turned out to be an unrelated char, + // we will try other correction-types. Please note that mCorrectionStates[mOutputIndex] + // here refers to the previous state. + if (canTryCorrection && mCorrectionStates[mOutputIndex].mProximityMatching + && mCorrectionStates[mOutputIndex].mExceeding + && mProximityInfo->getMatchedProximityId(mInputIndex, mWord[mOutputIndex], false) + == ProximityInfo::SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR) { + // Conversion p->e + ++mExcessiveCount; + --mProximityCount; + } else if (mInputIndex < mInputLength - 1 && mOutputIndex > 0 && mTransposedCount > 0 + && !mCorrectionStates[mOutputIndex].mTransposing + && mCorrectionStates[mOutputIndex - 1].mTransposing + && mProximityInfo->getMatchedProximityId( + mInputIndex, mWord[mOutputIndex - 1], false) + == ProximityInfo::SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR && mProximityInfo->getMatchedProximityId(mInputIndex + 1, c, false) == ProximityInfo::SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR) { - if (mTransposing) { - ++mTransposedCount; - } else { - ++mExcessiveCount; - incrementInputIndex(); - } - } else if (mSkipping && mProximityCount == 0) { - // Skip this letter and continue deeper + // Conversion t->e + // Example: + // occaisional -> occa sional + // mmmmttx -> mmmm(E)mmmmmm + mTransposedCount -= 2; + ++mExcessiveCount; + ++mInputIndex; + } else if (mOutputIndex > 0 && mInputIndex > 0 && mTransposedCount > 0 + && !mCorrectionStates[mOutputIndex].mTransposing + && mCorrectionStates[mOutputIndex - 1].mTransposing + && mProximityInfo->getMatchedProximityId(mInputIndex - 1, c, false) + == ProximityInfo::SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR) { + // Conversion t->s + // Example: + // chcolate -> chocolate + // mmttx -> mmsmmmmmm + mTransposedCount -= 2; ++mSkippedCount; - return processSkipChar(c, isTerminal); - } else if (checkProximityChars - && mInputIndex > 0 + --mInputIndex; + } else if (canTryCorrection && mInputIndex > 0 && mCorrectionStates[mOutputIndex].mProximityMatching && mCorrectionStates[mOutputIndex].mSkipping && mProximityInfo->getMatchedProximityId(mInputIndex - 1, c, false) == ProximityInfo::SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR) { + // Conversion p->s // Note: This logic tries saving cases like contrst --> contrast -- "a" is one of // proximity chars of "s", but it should rather be handled as a skipped char. ++mSkippedCount; --mProximityCount; - return processSkipChar(c, isTerminal); + return processSkipChar(c, isTerminal, false); + } else if ((mExceeding || mTransposing) && mInputIndex - 1 < mInputLength + && mProximityInfo->getMatchedProximityId(mInputIndex + 1, c, false) + == ProximityInfo::SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR) { + // 1.2. Excessive or transpose correction + if (mTransposing) { + ++mTransposedCount; + } else { + ++mExcessiveCount; + incrementInputIndex(); + } + } else if (mSkipping) { + // 3. Skip correction + ++mSkippedCount; + return processSkipChar(c, isTerminal, false); } else { + if (DEBUG_CORRECTION) { + DUMP_WORD(mWord, mOutputIndex); + LOGI("UNRELATED(1): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount, + mTransposedCount, mExcessiveCount, c); + } return UNRELATED; } } else if (secondTransposing @@ -312,10 +374,9 @@ Correction::CorrectionType Correction::processCharAndCalcState( mWord[mOutputIndex] = c; - mLastCharExceeded = mExcessiveCount == 0 && mSkippedCount == 0 - && mProximityCount == 0 && mTransposedCount == 0 - // TODO: remove this line once excessive correction is conmibned to others. - && mExcessivePos >= 0 && (mInputIndex == mInputLength - 2); + // 4. Last char excessive correction + mLastCharExceeded = mExcessiveCount == 0 && mSkippedCount == 0 && mTransposedCount == 0 + && mProximityCount == 0 && (mInputIndex == mInputLength - 2); const bool isSameAsUserTypedLength = (mInputLength == mInputIndex + 1) || mLastCharExceeded; if (mLastCharExceeded) { ++mExcessiveCount; @@ -326,6 +387,9 @@ Correction::CorrectionType Correction::processCharAndCalcState( startToTraverseAllNodes(); } + const bool needsToTryOnTerminalForTheLastPossibleExcessiveChar = + mExceeding && mInputIndex == mInputLength - 2; + // Finally, we are ready to go to the next character, the next "virtual node". // We should advance the input index. // We do this in this branch of the 'if traverseAllNodes' because we are still matching @@ -335,7 +399,8 @@ Correction::CorrectionType Correction::processCharAndCalcState( // Also, the next char is one "virtual node" depth more than this char. incrementOutputIndex(); - if (isSameAsUserTypedLength && isTerminal) { + if ((needsToTryOnTerminalForTheLastPossibleExcessiveChar + || isSameAsUserTypedLength) && isTerminal) { mTerminalInputIndex = mInputIndex - 1; mTerminalOutputIndex = mOutputIndex - 1; return ON_TERMINAL; @@ -453,35 +518,25 @@ inline static int editDistance( int Correction::RankingAlgorithm::calculateFinalFreq(const int inputIndex, const int outputIndex, const int freq, int* editDistanceTable, const Correction* correction) { const int excessivePos = correction->getExcessivePos(); - const int transposedPos = correction->getTransposedPos(); const int inputLength = correction->mInputLength; const int typedLetterMultiplier = correction->TYPED_LETTER_MULTIPLIER; const int fullWordMultiplier = correction->FULL_WORD_MULTIPLIER; const ProximityInfo *proximityInfo = correction->mProximityInfo; const int skippedCount = correction->mSkippedCount; - const int transposedCount = correction->mTransposedCount; - const int excessiveCount = correction->mExcessiveCount; + const int transposedCount = correction->mTransposedCount / 2; + const int excessiveCount = correction->mExcessiveCount + correction->mTransposedCount % 2; const int proximityMatchedCount = correction->mProximityCount; const bool lastCharExceeded = correction->mLastCharExceeded; if (skippedCount >= inputLength || inputLength == 0) { return -1; } - // TODO: remove - if (transposedPos >= 0 && transposedCount == 0) { - return -1; - } - - // TODO: remove - if (excessivePos >= 0 && excessiveCount == 0) { - return -1; - } - - const bool sameLength = lastCharExceeded ? (inputLength == inputIndex + 2) + // TODO: find more robust way + bool sameLength = lastCharExceeded ? (inputLength == inputIndex + 2) : (inputLength == inputIndex + 1); // TODO: use mExcessiveCount - int matchCount = inputLength - correction->mProximityCount - (excessivePos >= 0 ? 1 : 0); + const int matchCount = inputLength - correction->mProximityCount - excessiveCount; const unsigned short* word = correction->mWord; const bool skipped = skippedCount > 0; @@ -490,29 +545,51 @@ int Correction::RankingAlgorithm::calculateFinalFreq(const int inputIndex, const - getQuoteCount(proximityInfo->getPrimaryInputWord(), inputLength)); // TODO: Calculate edit distance for transposed and excessive - int matchWeight; int ed = 0; - int adJustedProximityMatchedCount = proximityMatchedCount; + int adjustedProximityMatchedCount = proximityMatchedCount; + + int finalFreq = freq; // TODO: Optimize this. - if (excessivePos < 0 && transposedPos < 0 && (proximityMatchedCount > 0 || skipped)) { + // TODO: Ignoring edit distance for transposed char, for now + if (transposedCount == 0 && (proximityMatchedCount > 0 || skipped || excessiveCount > 0)) { const unsigned short* primaryInputWord = proximityInfo->getPrimaryInputWord(); ed = editDistance(editDistanceTable, primaryInputWord, inputLength, word, outputIndex + 1); - matchWeight = powerIntCapped(typedLetterMultiplier, outputIndex + 1 - ed); - if (ed == 1 && inputLength == outputIndex) { - // Promote a word with just one skipped char - multiplyRate(WORDS_WITH_JUST_ONE_CORRECTION_PROMOTION_RATE, &matchWeight); + const int matchWeight = powerIntCapped(typedLetterMultiplier, + max(inputLength, outputIndex + 1) - ed); + multiplyIntCapped(matchWeight, &finalFreq); + + // TODO: Demote further if there are two or more excessive chars with longer user input? + if (inputLength > outputIndex + 1) { + multiplyRate(INPUT_EXCEEDS_OUTPUT_DEMOTION_RATE, &finalFreq); } + ed = max(0, ed - quoteDiffCount); - adJustedProximityMatchedCount = min(max(0, ed - (outputIndex + 1 - inputLength)), + + if (ed == 1 && (inputLength == outputIndex || inputLength == outputIndex + 2)) { + // Promote a word with just one skipped or excessive char + if (sameLength) { + multiplyRate(WORDS_WITH_JUST_ONE_CORRECTION_PROMOTION_RATE, &finalFreq); + } else { + multiplyIntCapped(typedLetterMultiplier, &finalFreq); + } + } else if (ed == 0) { + multiplyIntCapped(typedLetterMultiplier, &finalFreq); + sameLength = true; + } + adjustedProximityMatchedCount = min(max(0, ed - (outputIndex + 1 - inputLength)), proximityMatchedCount); } else { - matchWeight = powerIntCapped(typedLetterMultiplier, matchCount); + // TODO: Calculate the edit distance for transposed char + const int matchWeight = powerIntCapped(typedLetterMultiplier, matchCount); + multiplyIntCapped(matchWeight, &finalFreq); } - // TODO: Demote by edit distance - int finalFreq = freq * matchWeight; + if (proximityInfo->getMatchedProximityId(0, word[0], true) + == ProximityInfo::UNRELATED_CHAR) { + multiplyRate(FIRST_CHAR_DIFFERENT_DEMOTION_RATE, &finalFreq); + } /////////////////////////////////////////////// // Promotion and Demotion for each correction @@ -530,13 +607,16 @@ int Correction::RankingAlgorithm::calculateFinalFreq(const int inputIndex, const } // Demotion for a word with transposed character - if (transposedPos >= 0) multiplyRate( + if (transposedCount > 0) multiplyRate( WORDS_WITH_TRANSPOSED_CHARACTERS_DEMOTION_RATE, &finalFreq); // Demotion for a word with excessive character - if (excessivePos >= 0) { + if (excessiveCount > 0) { multiplyRate(WORDS_WITH_EXCESSIVE_CHARACTER_DEMOTION_RATE, &finalFreq); - if (!proximityInfo->existsAdjacentProximityChars(inputIndex)) { + if (!lastCharExceeded && !proximityInfo->existsAdjacentProximityChars(excessivePos)) { + if (DEBUG_CORRECTION_FREQ) { + LOGI("Double excessive demotion"); + } // If an excessive character is not adjacent to the left char or the right char, // we will demote this word. multiplyRate(WORDS_WITH_EXCESSIVE_CHARACTER_OUT_OF_PROXIMITY_DEMOTION_RATE, &finalFreq); @@ -544,7 +624,7 @@ int Correction::RankingAlgorithm::calculateFinalFreq(const int inputIndex, const } // Promotion for a word with proximity characters - for (int i = 0; i < adJustedProximityMatchedCount; ++i) { + for (int i = 0; i < adjustedProximityMatchedCount; ++i) { // A word with proximity corrections if (DEBUG_DICT_FULL) { LOGI("Found a proximity correction."); @@ -553,20 +633,22 @@ int Correction::RankingAlgorithm::calculateFinalFreq(const int inputIndex, const multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &finalFreq); } - const int errorCount = proximityMatchedCount + skippedCount; + const int errorCount = adjustedProximityMatchedCount > 0 + ? adjustedProximityMatchedCount + : (proximityMatchedCount + transposedCount); multiplyRate( 100 - CORRECTION_COUNT_RATE_DEMOTION_RATE_BASE * errorCount / inputLength, &finalFreq); // Promotion for an exactly matched word - if (matchCount == outputIndex + 1) { + if (ed == 0) { // Full exact match - if (sameLength && transposedPos < 0 && !skipped && excessivePos < 0) { + if (sameLength && transposedCount == 0 && !skipped && excessiveCount == 0) { finalFreq = capped255MultForFullMatchAccentsOrCapitalizationDifference(finalFreq); } } // Promote a word with no correction - if (proximityMatchedCount == 0 && transposedPos < 0 && !skipped && excessivePos < 0) { + if (proximityMatchedCount == 0 && transposedCount == 0 && !skipped && excessiveCount == 0) { multiplyRate(FULL_MATCHED_WORDS_PROMOTION_RATE, &finalFreq); } @@ -584,12 +666,16 @@ int Correction::RankingAlgorithm::calculateFinalFreq(const int inputIndex, const m ... matching s ... skipping a ... traversing all + t ... transposing + e ... exceeding + p ... proximity matching */ if (matchCount == inputLength && matchCount >= 2 && !skipped && word[matchCount] == word[matchCount - 1]) { multiplyRate(WORDS_WITH_MATCH_SKIP_PROMOTION_RATE, &finalFreq); } + // TODO: Do not use sameLength? if (sameLength) { multiplyIntCapped(fullWordMultiplier, &finalFreq); } @@ -598,6 +684,13 @@ int Correction::RankingAlgorithm::calculateFinalFreq(const int inputIndex, const LOGI("calc: %d, %d", outputIndex, sameLength); } + if (DEBUG_CORRECTION_FREQ) { + DUMP_WORD(correction->mWord, outputIndex + 1); + LOGI("FinalFreq: [P%d, S%d, T%d, E%d] %d, %d, %d, %d, %d", proximityMatchedCount, + skippedCount, transposedCount, excessiveCount, lastCharExceeded, sameLength, + quoteDiffCount, ed, finalFreq); + } + return finalFreq; } diff --git a/native/src/correction.h b/native/src/correction.h index 3cd600cf0..f3194b788 100644 --- a/native/src/correction.h +++ b/native/src/correction.h @@ -99,7 +99,8 @@ private: inline bool needsToTraverseAllNodes(); inline void startToTraverseAllNodes(); inline bool isQuote(const unsigned short c); - inline CorrectionType processSkipChar(const int32_t c, const bool isTerminal); + inline CorrectionType processSkipChar( + const int32_t c, const bool isTerminal, const bool inputIndexIncremented); // TODO: remove inline void incrementProximityCount() { diff --git a/native/src/defines.h b/native/src/defines.h index a29fb7e5b..009d0ad3d 100644 --- a/native/src/defines.h +++ b/native/src/defines.h @@ -95,10 +95,12 @@ static void prof_out(void) { #define DEBUG_DICT true #define DEBUG_DICT_FULL false #define DEBUG_EDIT_DISTANCE false -#define DEBUG_SHOW_FOUND_WORD DEBUG_DICT_FULL +#define DEBUG_SHOW_FOUND_WORD false #define DEBUG_NODE DEBUG_DICT_FULL #define DEBUG_TRACE DEBUG_DICT_FULL #define DEBUG_PROXIMITY_INFO true +#define DEBUG_CORRECTION false +#define DEBUG_CORRECTION_FREQ true #define DUMP_WORD(word, length) do { dumpWord(word, length); } while(0) @@ -121,6 +123,8 @@ static void dumpWord(const unsigned short* word, const int length) { #define DEBUG_NODE false #define DEBUG_TRACE false #define DEBUG_PROXIMITY_INFO false +#define DEBUG_CORRECTION false +#define DEBUG_CORRECTION_FREQ false #define DUMP_WORD(word, length) @@ -178,7 +182,9 @@ static void dumpWord(const unsigned short* word, const int length) { #define WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE 90 #define WORDS_WITH_MATCH_SKIP_PROMOTION_RATE 105 #define WORDS_WITH_JUST_ONE_CORRECTION_PROMOTION_RATE 160 -#define CORRECTION_COUNT_RATE_DEMOTION_RATE_BASE 42 +#define CORRECTION_COUNT_RATE_DEMOTION_RATE_BASE 45 +#define INPUT_EXCEEDS_OUTPUT_DEMOTION_RATE 70 +#define FIRST_CHAR_DIFFERENT_DEMOTION_RATE 96 // This should be greater than or equal to MAX_WORD_LENGTH defined in BinaryDictionary.java // This is only used for the size of array. Not to be used in c functions. diff --git a/native/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp index 805e1cbb7..4e671a1c4 100644 --- a/native/src/unigram_dictionary.cpp +++ b/native/src/unigram_dictionary.cpp @@ -189,32 +189,19 @@ void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo, // TODO: remove PROF_START(1); - // Note: This line is intentionally left blank + getSuggestionCandidates(); PROF_END(1); PROF_START(2); - // Suggestion with missing character - if (DEBUG_DICT) { - LOGI("--- Suggest missing characters"); - } - getSuggestionCandidates(0, -1, -1); + // Note: This line is intentionally left blank PROF_END(2); PROF_START(3); - // Suggestion with excessive character - if (DEBUG_DICT) { - LOGI("--- Suggest excessive characters"); - } - getSuggestionCandidates(-1, 0, -1); + // Note: This line is intentionally left blank PROF_END(3); PROF_START(4); - // Suggestion with transposed characters - // Only suggest words that length is mInputLength - if (DEBUG_DICT) { - LOGI("--- Suggest transposed characters"); - } - getSuggestionCandidates(-1, -1, 0); + // Note: This line is intentionally left blank PROF_END(4); PROF_START(5); @@ -328,14 +315,9 @@ bool UnigramDictionary::addWord(unsigned short *word, int length, int frequency) static const char QUOTE = '\''; static const char SPACE = ' '; -void UnigramDictionary::getSuggestionCandidates(const int skipPos, - const int excessivePos, const int transposedPos) { - if (DEBUG_DICT) { - assert(transposedPos + 1 < mInputLength); - assert(excessivePos < mInputLength); - assert(missingPos < mInputLength); - } - mCorrection->setCorrectionParams(skipPos, excessivePos, transposedPos, +void UnigramDictionary::getSuggestionCandidates() { + // TODO: Remove setCorrectionParams + mCorrection->setCorrectionParams(0, 0, 0, -1 /* spaceProximityPos */, -1 /* missingSpacePos */); int rootPosition = ROOT_POS; // Get the number of children of root, then increment the position @@ -727,6 +709,9 @@ inline bool UnigramDictionary::processCurrentNode(const int initialPos, pos = BinaryFormat::skipFrequency(flags, pos); *nextSiblingPosition = BinaryFormat::skipChildrenPosAndAttributes(DICT_ROOT, flags, pos); + if (DEBUG_DICT_FULL) { + LOGI("Traversing was pruned."); + } return false; } } diff --git a/native/src/unigram_dictionary.h b/native/src/unigram_dictionary.h index cfe63ff79..65746db8d 100644 --- a/native/src/unigram_dictionary.h +++ b/native/src/unigram_dictionary.h @@ -87,8 +87,7 @@ private: void initSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates, const int *codes, const int codesSize, unsigned short *outWords, int *frequencies); - void getSuggestionCandidates(const int skipPos, const int excessivePos, - const int transposedPos); + void getSuggestionCandidates(); bool addWord(unsigned short *word, int length, int frequency); void getSplitTwoWordsSuggestion(const int inputLength, Correction *correction); void getMissingSpaceWords( |