Add an auto complete's threshold option.

Change-Id: I3a6821ced8642ab8f954e79a25e31766e4a18eb8
main
Mitsuhiro Shimoda 2010-09-28 12:23:26 +09:00
parent 6614ac9f7b
commit b1abda8d62
8 changed files with 295 additions and 17 deletions

View File

@ -25,4 +25,14 @@
<integer name="config_preview_fadeout_anim_time">90</integer>
<integer name="config_mini_keyboard_fadein_anim_time">0</integer>
<integer name="config_mini_keyboard_fadeout_anim_time">100</integer>
<string-array name="auto_complete_threshold_values">
<!-- Off, When auto completing setting is Off, this value is not used. -->
<item></item>
<!-- Modest : Suggestion whose normalized score is greater than this value
will be subject to auto-completion. -->
<item>0.22</item>
<!-- Aggressive : Suggestion whose normalized score is greater than this value
will be subject to auto-completion. -->
<item>0</item>
</string-array>
</resources>

View File

@ -86,11 +86,6 @@
<!-- Description for show suggestions -->
<string name="show_suggestions_summary">Display suggested words while typing</string>
<!-- Option to enable auto completion -->
<string name="auto_complete">Auto-complete</string>
<!-- Description for auto completion -->
<string name="auto_complete_summary">Spacebar and punctuation automatically insert highlighted word</string>
<!-- Option to show/hide the settings key -->
<string name="prefs_settings_key">Show settings key</string>
<!-- Array of the settings key mode values -->
@ -112,6 +107,31 @@
<item>@string/settings_key_mode_always_hide_name</item>
</string-array>
<!-- Option to decide the auto completion threshold score -->
<!-- Option to enable auto completion -->
<string name="auto_complete">Auto-complete</string>
<!-- Description for auto completion -->
<string name="auto_complete_summary">Spacebar and punctuation automatically insert highlighted word</string>
<string name="auto_completion_threshold_mode_value_off" translatable="false">0</string>
<string name="auto_completion_threshold_mode_value_modest" translatable="false">1</string>
<string name="auto_completion_threshold_mode_value_aggeressive" translatable="false">2</string>
<string-array name="auto_completion_threshold_mode_values" translatable="false">
<item>@string/auto_completion_threshold_mode_value_off</item>
<item>@string/auto_completion_threshold_mode_value_modest</item>
<item>@string/auto_completion_threshold_mode_value_aggeressive</item>
</string-array>
<!-- Option to disable auto completion. -->
<string name="auto_completion_threshold_mode_off">Off</string>
<!-- Option to use modest auto completion. -->
<string name="auto_completion_threshold_mode_modest">Modest</string>
<!-- Option to use aggressive auto completion. -->
<string name="auto_completion_threshold_mode_aggeressive">Aggressive</string>
<string-array name="auto_completion_threshold_modes">
<item>@string/auto_completion_threshold_mode_off</item>
<item>@string/auto_completion_threshold_mode_modest</item>
<item>@string/auto_completion_threshold_mode_aggeressive</item>
</string-array>
<!-- Option to enable bigram completion -->
<string name="bigram_suggestion">Bigram Suggestions</string>
<!-- Description for auto completion -->

View File

@ -97,13 +97,14 @@
android:defaultValue="true"
/>
<CheckBoxPreference
android:key="auto_complete"
<ListPreference
android:key="auto_completion_threshold"
android:title="@string/auto_complete"
android:summary="@string/auto_complete_summary"
android:persistent="true"
android:defaultValue="@bool/enable_autocorrect"
android:dependency="show_suggestions"
android:entryValues="@array/auto_completion_threshold_mode_values"
android:entries="@array/auto_completion_threshold_modes"
android:defaultValue="@string/auto_completion_threshold_mode_value_modest"
/>
<CheckBoxPreference
@ -112,7 +113,6 @@
android:summary="@string/bigram_suggestion_summary"
android:persistent="true"
android:defaultValue="true"
android:dependency="auto_complete"
/>
</PreferenceCategory>

View File

@ -68,6 +68,7 @@ import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@ -94,7 +95,7 @@ public class LatinIME extends InputMethodService
private static final String PREF_AUTO_CAP = "auto_cap";
private static final String PREF_QUICK_FIXES = "quick_fixes";
private static final String PREF_SHOW_SUGGESTIONS = "show_suggestions";
private static final String PREF_AUTO_COMPLETE = "auto_complete";
private static final String PREF_AUTO_COMPLETION_THRESHOLD = "auto_completion_threshold";
private static final String PREF_BIGRAM_SUGGESTIONS = "bigram_suggestion";
private static final String PREF_VOICE_MODE = "voice_mode";
@ -445,6 +446,7 @@ public class LatinIME extends InputMethodService
int[] dictionaries = getDictionary(orig);
mSuggest = new Suggest(this, dictionaries);
loadAndSetAutoCompletionThreshold(sp);
updateAutoTextEnabled(saveLocale);
if (mUserDictionary != null) mUserDictionary.close();
mUserDictionary = new UserDictionary(this, mInputLocale);
@ -2469,6 +2471,9 @@ public class LatinIME extends InputMethodService
mLocaleSupportedForVoiceInput = voiceInputSupportedLocales.contains(mInputLocale);
mShowSuggestions = sp.getBoolean(PREF_SHOW_SUGGESTIONS, true);
mAutoCorrectEnabled = mShowSuggestions && isAutoCorrectEnabled(sp);
mBigramSuggestionEnabled = mAutoCorrectEnabled && isBigramSuggestionEnabled(sp);
loadAndSetAutoCompletionThreshold(sp);
if (VOICE_INSTALLED) {
final String voiceMode = sp.getString(PREF_VOICE_MODE,
@ -2483,14 +2488,61 @@ public class LatinIME extends InputMethodService
mEnableVoice = enableVoice;
mVoiceOnPrimary = voiceOnPrimary;
}
mAutoCorrectEnabled = sp.getBoolean(PREF_AUTO_COMPLETE,
mResources.getBoolean(R.bool.enable_autocorrect)) & mShowSuggestions;
mBigramSuggestionEnabled = sp.getBoolean(PREF_BIGRAM_SUGGESTIONS, true) & mShowSuggestions;
updateCorrectionMode();
updateAutoTextEnabled(mResources.getConfiguration().locale);
mLanguageSwitcher.loadLocales(sp);
}
/**
* load Auto completion threshold from SharedPreferences,
* and modify mSuggest's threshold.
*/
private void loadAndSetAutoCompletionThreshold(SharedPreferences sp) {
// When mSuggest is not initialized, cannnot modify mSuggest's threshold.
if (mSuggest == null) return;
// When auto completion setting is turned off, the threshold is ignored.
if (!isAutoCorrectEnabled(sp)) return;
final String currentAutoCompletionSetting = sp.getString(PREF_AUTO_COMPLETION_THRESHOLD,
mResources.getString(R.string.auto_completion_threshold_mode_value_modest));
final String[] autoCompletionThresholdValues = mResources.getStringArray(
R.array.auto_complete_threshold_values);
// When autoCompletionThreshold is greater than 1.0,
// auto completion is virtually turned off.
double autoCompletionThreshold = Double.MAX_VALUE;
try {
final int arrayIndex = Integer.valueOf(currentAutoCompletionSetting);
if (arrayIndex >= 0 && arrayIndex < autoCompletionThresholdValues.length) {
autoCompletionThreshold = Double.parseDouble(
autoCompletionThresholdValues[arrayIndex]);
}
} catch (NumberFormatException e) {
// Whenever the threshold settings are correct,
// never come here.
autoCompletionThreshold = Double.MAX_VALUE;
Log.w(TAG, "Cannot load auto completion threshold setting."
+ " currentAutoCompletionSetting: " + currentAutoCompletionSetting
+ ", autoCompletionThresholdValues: "
+ Arrays.toString(autoCompletionThresholdValues));
}
// TODO: This should be refactored :
// setAutoCompleteThreshold should be called outside of this method.
mSuggest.setAutoCompleteThreshold(autoCompletionThreshold);
}
private boolean isAutoCorrectEnabled(SharedPreferences sp) {
final String currentAutoCompletionSetting = sp.getString(PREF_AUTO_COMPLETION_THRESHOLD,
mResources.getString(R.string.auto_completion_threshold_mode_value_modest));
final String autoCompletionOff = mResources.getString(
R.string.auto_completion_threshold_mode_value_off);
return !currentAutoCompletionSetting.equals(autoCompletionOff);
}
private boolean isBigramSuggestionEnabled(SharedPreferences sp) {
// TODO: Define default value instead of 'true'.
return sp.getBoolean(PREF_BIGRAM_SUGGESTIONS, true);
}
private void initSuggestPuncList() {
mSuggestPuncList = new ArrayList<CharSequence>();
mSuggestPuncs = mResources.getString(R.string.suggested_punctuations);

View File

@ -43,6 +43,9 @@ public class LatinIMESettings extends PreferenceActivity
private static final String QUICK_FIXES_KEY = "quick_fixes";
private static final String PREDICTION_SETTINGS_KEY = "prediction_settings";
private static final String VOICE_SETTINGS_KEY = "voice_mode";
private static final String PREF_SHOW_SUGGESTIONS = "show_suggestions";
private static final String PREF_AUTO_COMPLETION_THRESHOLD = "auto_completion_threshold";
private static final String PREF_BIGRAM_SUGGESTIONS = "bigram_suggestion";
/* package */ static final String PREF_SETTINGS_KEY = "settings_key";
private static final String TAG = "LatinIMESettings";
@ -53,6 +56,9 @@ public class LatinIMESettings extends PreferenceActivity
private CheckBoxPreference mQuickFixes;
private ListPreference mVoicePreference;
private ListPreference mSettingsKeyPreference;
private CheckBoxPreference mShowSuggestions;
private ListPreference mAutoCompletionThreshold;
private CheckBoxPreference mBigramSuggestion;
private boolean mVoiceOn;
private VoiceInputLogger mLogger;
@ -60,6 +66,18 @@ public class LatinIMESettings extends PreferenceActivity
private boolean mOkClicked = false;
private String mVoiceModeOff;
private void ensureConsistencyOfAutoCompletionSettings() {
if (mShowSuggestions.isChecked()) {
mAutoCompletionThreshold.setEnabled(true);
final String autoCompletionOff = getResources().getString(
R.string.auto_completion_threshold_mode_value_off);
final String currentSetting = mAutoCompletionThreshold.getValue();
mBigramSuggestion.setEnabled(!currentSetting.equals(autoCompletionOff));
} else {
mAutoCompletionThreshold.setEnabled(false);
mBigramSuggestion.setEnabled(false);
}
}
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
@ -73,6 +91,11 @@ public class LatinIMESettings extends PreferenceActivity
mVoiceModeOff = getString(R.string.voice_mode_off);
mVoiceOn = !(prefs.getString(VOICE_SETTINGS_KEY, mVoiceModeOff).equals(mVoiceModeOff));
mLogger = VoiceInputLogger.getLogger(this);
mShowSuggestions = (CheckBoxPreference) findPreference(PREF_SHOW_SUGGESTIONS);
mAutoCompletionThreshold = (ListPreference) findPreference(PREF_AUTO_COMPLETION_THRESHOLD);
mBigramSuggestion = (CheckBoxPreference) findPreference(PREF_BIGRAM_SUGGESTIONS);
ensureConsistencyOfAutoCompletionSettings();
}
@Override
@ -108,6 +131,7 @@ public class LatinIMESettings extends PreferenceActivity
showVoiceConfirmation();
}
}
ensureConsistencyOfAutoCompletionSettings();
mVoiceOn = !(prefs.getString(VOICE_SETTINGS_KEY, mVoiceModeOff).equals(mVoiceModeOff));
updateVoiceModeSummary();
updateSettingsKeySummary();

View File

@ -168,4 +168,58 @@ public class LatinIMEUtil {
mLength = 0;
}
}
public static int editDistance(CharSequence s, CharSequence t) {
if (s == null || t == null) {
throw new IllegalArgumentException("editDistance: Arguments should not be null.");
}
final int sl = s.length();
final int tl = t.length();
int[][] dp = new int [sl + 1][tl + 1];
for (int i = 0; i <= sl; i++) {
dp[i][0] = i;
}
for (int j = 0; j <= tl; j++) {
dp[0][j] = j;
}
for (int i = 0; i < sl; ++i) {
for (int j = 0; j < tl; ++j) {
if (s.charAt(i) == t.charAt(j)) {
dp[i + 1][j + 1] = dp[i][j];
} else {
dp[i + 1][j + 1] = 1 + Math.min(dp[i][j],
Math.min(dp[i + 1][j], dp[i][j + 1]));
}
}
}
return dp[sl][tl];
}
// In dictionary.cpp, getSuggestion() method,
// suggestion scores are computed using the below formula.
// original score (called 'frequency')
// := pow(mTypedLetterMultiplier (this is defined 2),
// (the number of matched characters between typed word and suggested word))
// * (individual word's score which defined in the unigram dictionary,
// and this score is defined in range [0, 255].)
// * (when before.length() == after.length(),
// mFullWordMultiplier (this is defined 2))
// So, maximum original score is pow(2, before.length()) * 255 * 2
// So, we can normalize original score by dividing this value.
private static final int MAX_INITIAL_SCORE = 255;
private static final int TYPED_LETTER_MULTIPLIER = 2;
private static final int FULL_WORD_MULTIPLYER = 2;
public static double calcNormalizedScore(CharSequence before, CharSequence after, int score) {
final int beforeLength = before.length();
final int afterLength = after.length();
final int distance = editDistance(before, after);
final double maximumScore = MAX_INITIAL_SCORE
* Math.pow(TYPED_LETTER_MULTIPLIER, beforeLength)
* FULL_WORD_MULTIPLYER;
// add a weight based on edit distance.
// distance <= max(afterLength, beforeLength) == afterLength,
// so, 0 <= distance / afterLength <= 1
final double weight = 1.0 - (double) distance / afterLength;
return (score / maximumScore) * weight;
}
}

View File

@ -81,6 +81,7 @@ public class Suggest implements Dictionary.WordCallback {
private boolean mAutoTextEnabled;
private double mAutoCompleteThreshold;
private int[] mPriorities = new int[mPrefMaxSuggestions];
private int[] mBigramPriorities = new int[PREF_MAX_BIGRAMS];
@ -163,6 +164,10 @@ public class Suggest implements Dictionary.WordCallback {
mUserBigramDictionary = userBigramDictionary;
}
public void setAutoCompleteThreshold(double threshold) {
mAutoCompleteThreshold = threshold;
}
/**
* Number of suggestions to generate from the input key sequence. This has
* to be a number between 1 and 100 (inclusive).
@ -301,10 +306,16 @@ public class Suggest implements Dictionary.WordCallback {
}
mMainDict.getWords(wordComposer, this, mNextLettersFrequencies);
if ((mCorrectionMode == CORRECTION_FULL || mCorrectionMode == CORRECTION_FULL_BIGRAM)
&& mSuggestions.size() > 0) {
&& mSuggestions.size() > 0 && mPriorities.length > 0) {
// TODO: when the normalized score of the first suggestion is nearly equals to
// the normalized score of the second suggestion, behave less aggressive.
final double normalizedScore = LatinIMEUtil.calcNormalizedScore(
mOriginalWord, mSuggestions.get(0), mPriorities[0]);
if (normalizedScore >= mAutoCompleteThreshold) {
mHaveCorrection = true;
}
}
}
if (mOriginalWord != null) {
mSuggestions.add(0, mOriginalWord.toString());
}

View File

@ -0,0 +1,107 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.inputmethod.latin;
import android.test.AndroidTestCase;
public class EditDistanceTests extends AndroidTestCase {
@Override
protected void setUp() throws Exception {
super.setUp();
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
}
/*
* dist(kitten, sitting) == 3
*
* kitten-
* .|||.|
* sitting
*/
public void testExample1() {
final int dist = LatinIMEUtil.editDistance("kitten", "sitting");
assertEquals("edit distance between 'kitten' and 'sitting' is 3",
3, dist);
}
/*
* dist(Sunday, Saturday) == 3
*
* Saturday
* | |.|||
* S--unday
*/
public void testExample2() {
final int dist = LatinIMEUtil.editDistance("Saturday", "Sunday");
assertEquals("edit distance between 'Saturday' and 'Sunday' is 3",
3, dist);
}
public void testBothEmpty() {
final int dist = LatinIMEUtil.editDistance("", "");
assertEquals("when both string are empty, no edits are needed",
0, dist);
}
public void testFirstArgIsEmpty() {
final int dist = LatinIMEUtil.editDistance("", "aaaa");
assertEquals("when only one string of the arguments is empty,"
+ " the edit distance is the length of the other.",
4, dist);
}
public void testSecoondArgIsEmpty() {
final int dist = LatinIMEUtil.editDistance("aaaa", "");
assertEquals("when only one string of the arguments is empty,"
+ " the edit distance is the length of the other.",
4, dist);
}
public void testSameStrings() {
final String arg1 = "The quick brown fox jumps over the lazy dog.";
final String arg2 = "The quick brown fox jumps over the lazy dog.";
final int dist = LatinIMEUtil.editDistance(arg1, arg2);
assertEquals("when same strings are passed, distance equals 0.",
0, dist);
}
public void testSameReference() {
final String arg = "The quick brown fox jumps over the lazy dog.";
final int dist = LatinIMEUtil.editDistance(arg, arg);
assertEquals("when same string references are passed, the distance equals 0.",
0, dist);
}
public void testNullArg() {
try {
LatinIMEUtil.editDistance(null, "aaa");
fail("IllegalArgumentException should be thrown.");
} catch (Exception e) {
assertTrue(e instanceof IllegalArgumentException);
}
try {
LatinIMEUtil.editDistance("aaa", null);
fail("IllegalArgumentException should be thrown.");
} catch (Exception e) {
assertTrue(e instanceof IllegalArgumentException);
}
}
}