aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod/latin
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/com/android/inputmethod/latin')
-rw-r--r--java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java20
-rw-r--r--java/src/com/android/inputmethod/latin/AssetFileAddress.java10
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java140
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java26
-rw-r--r--java/src/com/android/inputmethod/latin/Constants.java29
-rw-r--r--java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java20
-rw-r--r--java/src/com/android/inputmethod/latin/Dictionary.java7
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFactory.java50
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryWriter.java26
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java481
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableDictionary.java894
-rw-r--r--java/src/com/android/inputmethod/latin/InputAttributes.java6
-rw-r--r--java/src/com/android/inputmethod/latin/InputView.java228
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java377
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputConnection.java213
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeSwitcher.java63
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java142
-rw-r--r--java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java5
-rw-r--r--java/src/com/android/inputmethod/latin/UserBinaryDictionary.java18
-rw-r--r--java/src/com/android/inputmethod/latin/WordComposer.java47
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/AbstractDictDecoder.java48
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java113
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java62
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java229
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/DictDecoder.java11
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/DictUpdater.java54
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java492
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/FormatSpec.java158
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java147
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/SparseTableContentReader.java126
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/SparseTableContentWriter.java93
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Ver2DictDecoder.java (renamed from java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java)20
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Ver2DictEncoder.java (renamed from java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java)12
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Ver3DictUpdater.java82
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java341
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java457
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Ver4DictUpdater.java59
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java169
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java190
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java50
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegister.java8
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java84
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java70
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java37
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java23
-rw-r--r--java/src/com/android/inputmethod/latin/settings/DebugSettings.java3
-rw-r--r--java/src/com/android/inputmethod/latin/settings/Settings.java31
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsFragment.java119
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsValues.java38
-rw-r--r--java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java10
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java2
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java1
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java5
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java2
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java11
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java20
-rw-r--r--java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java79
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java18
-rw-r--r--java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java12
-rw-r--r--java/src/com/android/inputmethod/latin/utils/FileUtils.java33
-rw-r--r--java/src/com/android/inputmethod/latin/utils/JsonUtils.java103
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java (renamed from java/src/com/android/inputmethod/latin/utils/StaticInnerHandlerWrapper.java)20
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LocaleUtils.java47
-rw-r--r--java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java1
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ResourceUtils.java8
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java9
-rw-r--r--java/src/com/android/inputmethod/latin/utils/StringUtils.java118
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java4
-rw-r--r--java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java36
-rw-r--r--java/src/com/android/inputmethod/latin/utils/UnigramProperty.java82
-rw-r--r--java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java12
-rw-r--r--java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java233
73 files changed, 2661 insertions, 4335 deletions
diff --git a/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java b/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
index 463d09344..1aee22baf 100644
--- a/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
+++ b/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
@@ -21,21 +21,19 @@ import android.util.Log;
import com.android.inputmethod.latin.makedict.DictEncoder;
import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
-import com.android.inputmethod.latin.makedict.Ver3DictEncoder;
+import com.android.inputmethod.latin.makedict.Ver2DictEncoder;
import java.io.File;
import java.io.IOException;
import java.util.Map;
-// TODO: Quit extending Dictionary after implementing dynamic binary dictionary.
-abstract public class AbstractDictionaryWriter extends Dictionary {
+abstract public class AbstractDictionaryWriter {
/** Used for Log actions from this class */
private static final String TAG = AbstractDictionaryWriter.class.getSimpleName();
private final Context mContext;
- public AbstractDictionaryWriter(final Context context, final String dictType) {
- super(dictType);
+ public AbstractDictionaryWriter(final Context context) {
mContext = context;
}
@@ -55,20 +53,18 @@ abstract public class AbstractDictionaryWriter extends Dictionary {
// TODO: Remove lastModifiedTime after making binary dictionary support forgetting curve.
abstract public void addBigramWords(final String word0, final String word1,
- final int frequency, final boolean isValid,
- final long lastModifiedTime);
+ final int frequency, final boolean isValid, final long lastModifiedTime);
abstract public void removeBigramWords(final String word0, final String word1);
abstract protected void writeDictionary(final DictEncoder dictEncoder,
final Map<String, String> attributeMap) throws IOException, UnsupportedFormatException;
- public void write(final String fileName, final Map<String, String> attributeMap) {
- final String tempFileName = fileName + ".temp";
- final File file = new File(mContext.getFilesDir(), fileName);
- final File tempFile = new File(mContext.getFilesDir(), tempFileName);
+ public void write(final File file, final Map<String, String> attributeMap) {
+ final String tempFilePath = file.getAbsolutePath() + ".temp";
+ final File tempFile = new File(tempFilePath);
try {
- final DictEncoder dictEncoder = new Ver3DictEncoder(tempFile);
+ final DictEncoder dictEncoder = new Ver2DictEncoder(tempFile);
writeDictionary(dictEncoder, attributeMap);
tempFile.renameTo(file);
} catch (IOException e) {
diff --git a/java/src/com/android/inputmethod/latin/AssetFileAddress.java b/java/src/com/android/inputmethod/latin/AssetFileAddress.java
index 875192554..fd6c24dfe 100644
--- a/java/src/com/android/inputmethod/latin/AssetFileAddress.java
+++ b/java/src/com/android/inputmethod/latin/AssetFileAddress.java
@@ -16,6 +16,8 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.latin.utils.FileUtils;
+
import java.io.File;
/**
@@ -52,4 +54,12 @@ public final class AssetFileAddress {
if (!f.isFile()) return null;
return new AssetFileAddress(filename, offset, length);
}
+
+ public boolean pointsToPhysicalFile() {
+ return 0 == mOffset;
+ }
+
+ public void deleteUnderlyingFile() {
+ FileUtils.deleteRecursively(new File(mFilename));
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index fd296988e..95ac3e203 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -26,6 +26,7 @@ import com.android.inputmethod.latin.settings.NativeSuggestOptions;
import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.JniUtils;
import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.utils.UnigramProperty;
import java.io.File;
import java.util.ArrayList;
@@ -57,6 +58,21 @@ public final class BinaryDictionary extends Dictionary {
@UsedForTesting
public static final String MAX_BIGRAM_COUNT_QUERY = "MAX_BIGRAM_COUNT";
+ public static final int NOT_A_VALID_TIMESTAMP = -1;
+
+ // Format to get unigram flags from native side via getUnigramPropertyNative().
+ private static final int FORMAT_UNIGRAM_PROPERTY_OUTPUT_FLAG_COUNT = 4;
+ private static final int FORMAT_UNIGRAM_PROPERTY_IS_NOT_A_WORD_INDEX = 0;
+ private static final int FORMAT_UNIGRAM_PROPERTY_IS_BLACKLISTED_INDEX = 1;
+ private static final int FORMAT_UNIGRAM_PROPERTY_HAS_BIGRAMS_INDEX = 2;
+ private static final int FORMAT_UNIGRAM_PROPERTY_HAS_SHORTCUTS_INDEX = 3;
+
+ // Format to get unigram historical info from native side via getUnigramPropertyNative().
+ private static final int FORMAT_UNIGRAM_PROPERTY_OUTPUT_HISTORICAL_INFO_COUNT = 3;
+ private static final int FORMAT_UNIGRAM_PROPERTY_TIMESTAMP_INDEX = 0;
+ private static final int FORMAT_UNIGRAM_PROPERTY_LEVEL_INDEX = 1;
+ private static final int FORMAT_UNIGRAM_PROPERTY_COUNT_INDEX = 2;
+
private long mNativeDict;
private final Locale mLocale;
private final long mDictSize;
@@ -123,8 +139,13 @@ public final class BinaryDictionary extends Dictionary {
private static native boolean needsToRunGCNative(long dict, boolean mindsBlockByGC);
private static native void flushWithGCNative(long dict, String filePath);
private static native void closeNative(long dict);
+ private static native int getFormatVersionNative(long dict);
private static native int getProbabilityNative(long dict, int[] word);
private static native int getBigramProbabilityNative(long dict, int[] word0, int[] word1);
+ private static native void getUnigramPropertyNative(long dict, int[] word,
+ int[] outCodePoints, boolean[] outFlags, int[] outProbability,
+ int[] outHistoricalInfo, ArrayList<int[]> outShortcutTargets,
+ ArrayList<Integer> outShortcutProbabilities);
private static native int getSuggestionsNative(long dict, long proximityInfo,
long traverseSession, int[] xCoordinates, int[] yCoordinates, int[] times,
int[] pointerIds, int[] inputCodePoints, int inputSize, int commitPoint,
@@ -133,10 +154,14 @@ public final class BinaryDictionary extends Dictionary {
int[] outputAutoCommitFirstWordConfidence);
private static native float calcNormalizedScoreNative(int[] before, int[] after, int score);
private static native int editDistanceNative(int[] before, int[] after);
- private static native void addUnigramWordNative(long dict, int[] word, int probability);
+ private static native void addUnigramWordNative(long dict, int[] word, int probability,
+ int[] shortcutTarget, int shortcutProbability, boolean isNotAWord,
+ boolean isBlacklisted, int timestamp);
private static native void addBigramWordsNative(long dict, int[] word0, int[] word1,
- int probability);
+ int probability, int timestamp);
private static native void removeBigramWordsNative(long dict, int[] word0, int[] word1);
+ private static native int addMultipleDictionaryEntriesNative(long dict,
+ LanguageModelParam[] languageModelParams, int startIndex);
private static native int calculateProbabilityNative(long dict, int unigramProbability,
int bigramProbability);
private static native String getPropertyNative(long dict, String query);
@@ -235,6 +260,10 @@ public final class BinaryDictionary extends Dictionary {
return mNativeDict != 0;
}
+ public int getFormatVersion() {
+ return getFormatVersionNative(mNativeDict);
+ }
+
public static float calcNormalizedScore(final String before, final String after,
final int score) {
return calcNormalizedScoreNative(StringUtils.toCodePointArray(before),
@@ -274,23 +303,55 @@ public final class BinaryDictionary extends Dictionary {
return getBigramProbabilityNative(mNativeDict, codePoints0, codePoints1);
}
- // Add a unigram entry to binary dictionary in native code.
- public void addUnigramWord(final String word, final int probability) {
+ @UsedForTesting
+ public UnigramProperty getUnigramProperty(final String word) {
+ if (TextUtils.isEmpty(word)) {
+ return null;
+ }
+ final int[] codePoints = StringUtils.toCodePointArray(word);
+ final int[] outCodePoints = new int[MAX_WORD_LENGTH];
+ final boolean[] outFlags = new boolean[FORMAT_UNIGRAM_PROPERTY_OUTPUT_FLAG_COUNT];
+ final int[] outProbability = new int[1];
+ final int[] outHistoricalInfo =
+ new int[FORMAT_UNIGRAM_PROPERTY_OUTPUT_HISTORICAL_INFO_COUNT];
+ final ArrayList<int[]> outShortcutTargets = CollectionUtils.newArrayList();
+ final ArrayList<Integer> outShortcutProbabilities = CollectionUtils.newArrayList();
+ getUnigramPropertyNative(mNativeDict, codePoints, outCodePoints, outFlags, outProbability,
+ outHistoricalInfo, outShortcutTargets, outShortcutProbabilities);
+ return new UnigramProperty(codePoints,
+ outFlags[FORMAT_UNIGRAM_PROPERTY_IS_NOT_A_WORD_INDEX],
+ outFlags[FORMAT_UNIGRAM_PROPERTY_IS_BLACKLISTED_INDEX],
+ outFlags[FORMAT_UNIGRAM_PROPERTY_HAS_BIGRAMS_INDEX],
+ outFlags[FORMAT_UNIGRAM_PROPERTY_HAS_SHORTCUTS_INDEX], outProbability[0],
+ outHistoricalInfo[FORMAT_UNIGRAM_PROPERTY_TIMESTAMP_INDEX],
+ outHistoricalInfo[FORMAT_UNIGRAM_PROPERTY_LEVEL_INDEX],
+ outHistoricalInfo[FORMAT_UNIGRAM_PROPERTY_COUNT_INDEX],
+ outShortcutTargets, outShortcutProbabilities);
+ }
+
+ // Add a unigram entry to binary dictionary with unigram attributes in native code.
+ public void addUnigramWord(final String word, final int probability,
+ final String shortcutTarget, final int shortcutProbability, final boolean isNotAWord,
+ final boolean isBlacklisted, final int timestamp) {
if (TextUtils.isEmpty(word)) {
return;
}
final int[] codePoints = StringUtils.toCodePointArray(word);
- addUnigramWordNative(mNativeDict, codePoints, probability);
+ final int[] shortcutTargetCodePoints = (shortcutTarget != null) ?
+ StringUtils.toCodePointArray(shortcutTarget) : null;
+ addUnigramWordNative(mNativeDict, codePoints, probability, shortcutTargetCodePoints,
+ shortcutProbability, isNotAWord, isBlacklisted, timestamp);
}
- // Add a bigram entry to binary dictionary in native code.
- public void addBigramWords(final String word0, final String word1, final int probability) {
+ // Add a bigram entry to binary dictionary with timestamp in native code.
+ public void addBigramWords(final String word0, final String word1, final int probability,
+ final int timestamp) {
if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) {
return;
}
final int[] codePoints0 = StringUtils.toCodePointArray(word0);
final int[] codePoints1 = StringUtils.toCodePointArray(word1);
- addBigramWordsNative(mNativeDict, codePoints0, codePoints1, probability);
+ addBigramWordsNative(mNativeDict, codePoints0, codePoints1, probability, timestamp);
}
// Remove a bigram entry form binary dictionary in native code.
@@ -303,10 +364,71 @@ public final class BinaryDictionary extends Dictionary {
removeBigramWordsNative(mNativeDict, codePoints0, codePoints1);
}
+ public static class LanguageModelParam {
+ public final int[] mWord0;
+ public final int[] mWord1;
+ // TODO: this needs to be a list of shortcuts
+ public final int[] mShortcutTarget;
+ public final int mUnigramProbability;
+ public final int mBigramProbability;
+ public final int mShortcutProbability;
+ public final boolean mIsNotAWord;
+ public final boolean mIsBlacklisted;
+ public final int mTimestamp;
+
+ // Constructor for unigram. TODO: support shortcuts
+ public LanguageModelParam(final String word, final int unigramProbability,
+ final int timestamp) {
+ mWord0 = null;
+ mWord1 = StringUtils.toCodePointArray(word);
+ mShortcutTarget = null;
+ mUnigramProbability = unigramProbability;
+ mBigramProbability = NOT_A_PROBABILITY;
+ mShortcutProbability = NOT_A_PROBABILITY;
+ mIsNotAWord = false;
+ mIsBlacklisted = false;
+ mTimestamp = timestamp;
+ }
+
+ // Constructor for unigram and bigram.
+ public LanguageModelParam(final String word0, final String word1,
+ final int unigramProbability, final int bigramProbability,
+ final int timestamp) {
+ mWord0 = StringUtils.toCodePointArray(word0);
+ mWord1 = StringUtils.toCodePointArray(word1);
+ mShortcutTarget = null;
+ mUnigramProbability = unigramProbability;
+ mBigramProbability = bigramProbability;
+ mShortcutProbability = NOT_A_PROBABILITY;
+ mIsNotAWord = false;
+ mIsBlacklisted = false;
+ mTimestamp = timestamp;
+ }
+ }
+
+ public void addMultipleDictionaryEntries(final LanguageModelParam[] languageModelParams) {
+ if (!isValidDictionary()) return;
+ int processedParamCount = 0;
+ while (processedParamCount < languageModelParams.length) {
+ if (needsToRunGC(true /* mindsBlockByGC */)) {
+ flushWithGC();
+ }
+ processedParamCount = addMultipleDictionaryEntriesNative(mNativeDict,
+ languageModelParams, processedParamCount);
+ if (processedParamCount <= 0) {
+ return;
+ }
+ }
+
+ }
+
private void reopen() {
close();
final File dictFile = new File(mDictFilePath);
- mNativeDict = openNative(dictFile.getAbsolutePath(), 0 /* startOffset */,
+ // WARNING: Because we pass 0 as the offset and file.length() as the length, this can
+ // only be called for actual files. Right now it's only called by the flush() family of
+ // functions, which require an updatable dictionary, so it's okay. But beware.
+ loadDictionary(dictFile.getAbsolutePath(), 0 /* startOffset */,
dictFile.length(), true /* isUpdatable */);
}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index 722a82961..b4382bc2c 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -98,7 +98,7 @@ public final class BinaryDictionaryFileDumper {
* This creates a URI builder able to build a URI pointing to the dictionary
* pack content provider for a specific dictionary id.
*/
- private static Uri.Builder getProviderUriBuilder(final String path) {
+ public static Uri.Builder getProviderUriBuilder(final String path) {
return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
.authority(DictionaryPackConstants.AUTHORITY).appendPath(path);
}
@@ -339,15 +339,25 @@ public final class BinaryDictionaryFileDumper {
Log.e(TAG, "Could not copy a word list. Will not be able to use it.");
// If we can't copy it we should warn the dictionary provider so that it can mark it
// as invalid.
- wordListUriBuilder.appendQueryParameter(QUERY_PARAMETER_DELETE_RESULT,
- QUERY_PARAMETER_FAILURE);
+ reportBrokenFileToDictionaryProvider(providerClient, clientId, wordlistId);
+ }
+
+ public static boolean reportBrokenFileToDictionaryProvider(
+ final ContentProviderClient providerClient, final String clientId,
+ final String wordlistId) {
try {
+ final Uri.Builder wordListUriBuilder = getContentUriBuilderForType(clientId,
+ providerClient, QUERY_PATH_DATAFILE, wordlistId /* extraPath */);
+ wordListUriBuilder.appendQueryParameter(QUERY_PARAMETER_DELETE_RESULT,
+ QUERY_PARAMETER_FAILURE);
if (0 >= providerClient.delete(wordListUriBuilder.build(), null, null)) {
- Log.e(TAG, "In addition, we were unable to delete it.");
+ Log.e(TAG, "Unable to delete a word list.");
}
} catch (RemoteException e) {
- Log.e(TAG, "In addition, communication with the dictionary provider was cut", e);
+ Log.e(TAG, "Communication with the dictionary provider was cut", e);
+ return false;
}
+ return true;
}
// Ideally the two following methods should be merged, but AssetFileDescriptor does not
@@ -432,8 +442,9 @@ public final class BinaryDictionaryFileDumper {
// Actually copy the file
final byte[] buffer = new byte[FILE_READ_BUFFER_SIZE];
- for (int readBytes = input.read(buffer); readBytes >= 0; readBytes = input.read(buffer))
+ for (int readBytes = input.read(buffer); readBytes >= 0; readBytes = input.read(buffer)) {
output.write(buffer, 0, readBytes);
+ }
input.close();
}
@@ -478,8 +489,7 @@ public final class BinaryDictionaryFileDumper {
* @param context the context for resources and providers.
* @param clientId the client ID to use.
*/
- public static void initializeClientRecordHelper(final Context context,
- final String clientId) {
+ public static void initializeClientRecordHelper(final Context context, final String clientId) {
try {
final ContentProviderClient client = context.getContentResolver().
acquireContentProviderClient(getProviderUriBuilder("").build());
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index 9a9653094..c260434d5 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -70,38 +70,47 @@ public final class Constants {
public static final class ExtraValue {
/**
- * The subtype extra value used to indicate that the subtype keyboard layout is capable
- * for typing ASCII characters.
+ * The subtype extra value used to indicate that this subtype is capable of
+ * entering ASCII characters.
*/
public static final String ASCII_CAPABLE = "AsciiCapable";
/**
- * The subtype extra value used to indicate that the subtype keyboard layout is capable
- * for typing EMOJI characters.
+ * The subtype extra value used to indicate that this subtype is enabled
+ * when the default subtype is not marked as ascii capable.
+ */
+ public static final String ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE =
+ "EnabledWhenDefaultIsNotAsciiCapable";
+
+ /**
+ * The subtype extra value used to indicate that this subtype is capable of
+ * entering emoji characters.
*/
public static final String EMOJI_CAPABLE = "EmojiCapable";
+
/**
- * The subtype extra value used to indicate that the subtype require network connection
- * to work.
+ * The subtype extra value used to indicate that this subtype requires a network
+ * connection to work.
*/
public static final String REQ_NETWORK_CONNECTIVITY = "requireNetworkConnectivity";
/**
- * The subtype extra value used to indicate that the subtype display name contains "%s"
- * for replacement mark and it should be replaced by this extra value.
+ * The subtype extra value used to indicate that the display name of this subtype
+ * contains a "%s" for printf-like replacement and it should be replaced by
+ * this extra value.
* This extra value is supported on JellyBean and later.
*/
public static final String UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME =
"UntranslatableReplacementStringInSubtypeName";
/**
- * The subtype extra value used to indicate that the subtype keyboard layout set name.
+ * The subtype extra value used to indicate this subtype keyboard layout set name.
* This extra value is private to LatinIME.
*/
public static final String KEYBOARD_LAYOUT_SET = "KeyboardLayoutSet";
/**
- * The subtype extra value used to indicate that the subtype is additional subtype
+ * The subtype extra value used to indicate that this subtype is an additional subtype
* that the user defined. This extra value is private to LatinIME.
*/
public static final String IS_ADDITIONAL_SUBTYPE = "isAdditionalSubtype";
diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index 47891c6b7..ae41e74e3 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -33,6 +33,7 @@ import android.util.Log;
import com.android.inputmethod.latin.utils.StringUtils;
+import java.io.File;
import java.util.List;
import java.util.Locale;
@@ -44,7 +45,8 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
private static final String TAG = ContactsBinaryDictionary.class.getSimpleName();
private static final String NAME = "contacts";
- private static boolean DEBUG = false;
+ private static final boolean DEBUG = false;
+ private static final boolean DEBUG_DUMP = false;
/**
* Frequency for contacts information into the dictionary
@@ -71,8 +73,8 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
private final boolean mUseFirstLastBigrams;
public ContactsBinaryDictionary(final Context context, final Locale locale) {
- super(context, getFilenameWithLocale(NAME, locale.toString()), Dictionary.TYPE_CONTACTS,
- false /* isUpdatable */);
+ super(context, getDictNameWithLocale(NAME, locale), locale,
+ Dictionary.TYPE_CONTACTS, false /* isUpdatable */);
mLocale = locale;
mUseFirstLastBigrams = useFirstLastBigramsForLocale(locale);
registerObserver(context);
@@ -82,6 +84,11 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
loadDictionary();
}
+ // Dummy constructor for tests.
+ public ContactsBinaryDictionary(final Context context, final Locale locale, final File file) {
+ this(context, locale);
+ }
+
private synchronized void registerObserver(final Context context) {
// Perform a managed query. The Activity will handle closing and requerying the cursor
// when needed.
@@ -168,6 +175,10 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
if (isValidName(name)) {
addName(name);
++count;
+ } else {
+ if (DEBUG_DUMP) {
+ Log.d(TAG, "Invalid name: " + name);
+ }
}
cursor.moveToNext();
}
@@ -204,6 +215,9 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
if (Character.isLetter(name.codePointAt(i))) {
int end = getWordEndPosition(name, len, i);
String word = name.substring(i, end);
+ if (DEBUG_DUMP) {
+ Log.d(TAG, "addName word = " + word);
+ }
i = end - 1;
// Don't add single letter words, possibly confuses
// capitalization of i.
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index fa79f5af7..e04524843 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -52,13 +52,10 @@ public abstract class Dictionary {
public static final String TYPE_CONTACTS = "contacts";
// User dictionary, the system-managed one.
public static final String TYPE_USER = "user";
- // User history dictionary internal to LatinIME. This assumes bigram prediction for now.
+ // User history dictionary internal to LatinIME.
public static final String TYPE_USER_HISTORY = "history";
- // Personalization binary dictionary internal to LatinIME.
+ // Personalization dictionary.
public static final String TYPE_PERSONALIZATION = "personalization";
- // Personalization prediction dictionary internal to LatinIME's Java code.
- public static final String TYPE_PERSONALIZATION_PREDICTION_IN_JAVA =
- "personalization_prediction_in_java";
public final String mDictType;
public Dictionary(final String dictType) {
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index 828e54f14..e09c309ea 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -16,6 +16,7 @@
package com.android.inputmethod.latin;
+import android.content.ContentProviderClient;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
@@ -64,6 +65,10 @@ public final class DictionaryFactory {
useFullEditDistance, locale, Dictionary.TYPE_MAIN);
if (readOnlyBinaryDictionary.isValidDictionary()) {
dictList.add(readOnlyBinaryDictionary);
+ } else {
+ readOnlyBinaryDictionary.close();
+ // Prevent this dictionary to do any further harm.
+ killDictionary(context, f);
}
}
}
@@ -75,6 +80,51 @@ public final class DictionaryFactory {
}
/**
+ * Kills a dictionary so that it is never used again, if possible.
+ * @param context The context to contact the dictionary provider, if possible.
+ * @param f A file address to the dictionary to kill.
+ */
+ private static void killDictionary(final Context context, final AssetFileAddress f) {
+ if (f.pointsToPhysicalFile()) {
+ f.deleteUnderlyingFile();
+ // Warn the dictionary provider if the dictionary came from there.
+ final ContentProviderClient providerClient;
+ try {
+ providerClient = context.getContentResolver().acquireContentProviderClient(
+ BinaryDictionaryFileDumper.getProviderUriBuilder("").build());
+ } catch (final SecurityException e) {
+ Log.e(TAG, "No permission to communicate with the dictionary provider", e);
+ return;
+ }
+ if (null == providerClient) {
+ Log.e(TAG, "Can't establish communication with the dictionary provider");
+ return;
+ }
+ final String wordlistId =
+ DictionaryInfoUtils.getWordListIdFromFileName(new File(f.mFilename).getName());
+ if (null != wordlistId) {
+ // TODO: this is a reasonable last resort, but it is suboptimal.
+ // The following will remove the entry for this dictionary with the dictionary
+ // provider. When the metadata is downloaded again, we will try downloading it
+ // again.
+ // However, in the practice that will mean the user will find themselves without
+ // the new dictionary. That's fine for languages where it's included in the APK,
+ // but for other languages it will leave the user without a dictionary at all until
+ // the next update, which may be a few days away.
+ // Ideally, we would trigger a new download right away, and use increasing retry
+ // delays for this particular id/version combination.
+ // Then again, this is expected to only ever happen in case of human mistake. If
+ // the wrong file is on the server, the following is still doing the right thing.
+ // If it's a file left over from the last version however, it's not great.
+ BinaryDictionaryFileDumper.reportBrokenFileToDictionaryProvider(
+ providerClient,
+ context.getString(R.string.dictionary_pack_client_id),
+ wordlistId);
+ }
+ }
+ }
+
+ /**
* Initializes a main dictionary collection from a dictionary pack, with default flags.
*
* This searches for a content provider providing a dictionary pack for the specified
diff --git a/java/src/com/android/inputmethod/latin/DictionaryWriter.java b/java/src/com/android/inputmethod/latin/DictionaryWriter.java
index 3df2a2b63..89ef96d7f 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryWriter.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryWriter.java
@@ -18,8 +18,6 @@ package com.android.inputmethod.latin;
import android.content.Context;
-import com.android.inputmethod.keyboard.ProximityInfo;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.makedict.DictEncoder;
import com.android.inputmethod.latin.makedict.FormatSpec;
import com.android.inputmethod.latin.makedict.FusionDictionary;
@@ -37,14 +35,14 @@ import java.util.Map;
* An in memory dictionary for memorizing entries and writing a binary dictionary.
*/
public class DictionaryWriter extends AbstractDictionaryWriter {
- private static final int BINARY_DICT_VERSION = 3;
+ private static final int BINARY_DICT_VERSION = 2;
private static final FormatSpec.FormatOptions FORMAT_OPTIONS =
- new FormatSpec.FormatOptions(BINARY_DICT_VERSION, true /* supportsDynamicUpdate */);
+ new FormatSpec.FormatOptions(BINARY_DICT_VERSION, false /* supportsDynamicUpdate */);
private FusionDictionary mFusionDictionary;
- public DictionaryWriter(final Context context, final String dictType) {
- super(context, dictType);
+ public DictionaryWriter(final Context context) {
+ super(context);
clear();
}
@@ -52,7 +50,7 @@ public class DictionaryWriter extends AbstractDictionaryWriter {
public void clear() {
final HashMap<String, String> attributes = CollectionUtils.newHashMap();
mFusionDictionary = new FusionDictionary(new PtNodeArray(),
- new FusionDictionary.DictionaryOptions(attributes, false, false));
+ new FusionDictionary.DictionaryOptions(attributes));
}
/**
@@ -92,18 +90,4 @@ public class DictionaryWriter extends AbstractDictionaryWriter {
}
dictEncoder.writeDictionary(mFusionDictionary, FORMAT_OPTIONS);
}
-
- @Override
- public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final String prevWord, final ProximityInfo proximityInfo,
- boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
- // This class doesn't support suggestion.
- return null;
- }
-
- @Override
- public boolean isValidWord(String word) {
- // This class doesn't support dictionary retrieval.
- return false;
- }
}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index eb8650e6f..f785835b8 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -17,23 +17,26 @@
package com.android.inputmethod.latin;
import android.content.Context;
-import android.os.SystemClock;
import android.util.Log;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.BinaryDictionary.LanguageModelParam;
import com.android.inputmethod.latin.makedict.FormatSpec;
-import com.android.inputmethod.latin.personalization.DynamicPersonalizationDictionaryWriter;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.utils.AsyncResultHolder;
import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.FileUtils;
import com.android.inputmethod.latin.utils.PrioritizedSerialExecutor;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
@@ -52,10 +55,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
/** Whether to print debug output to log */
private static boolean DEBUG = false;
-
- // TODO: Remove.
- /** Whether to call binary dictionary dynamically updating methods. */
- public static boolean ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE = true;
+ private static final boolean DBG_STRESS_TEST = false;
private static final int TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS = 100;
@@ -64,22 +64,19 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
protected static final int MAX_WORD_LENGTH = Constants.DICTIONARY_MAX_WORD_LENGTH;
- private static final int DICTIONARY_FORMAT_VERSION = 3;
-
- private static final String SUPPORTS_DYNAMIC_UPDATE =
- FormatSpec.FileHeader.ATTRIBUTE_VALUE_TRUE;
+ private static final int DICTIONARY_FORMAT_VERSION = FormatSpec.VERSION4;
/**
* A static map of update controllers, each of which records the time of accesses to a single
* binary dictionary file and tracks whether the file is regenerating. The key for this map is
- * the filename and the value is the shared dictionary time recorder associated with that
- * filename.
+ * the dictionary name and the value is the shared dictionary time recorder associated with
+ * that dictionary name.
*/
private static final ConcurrentHashMap<String, DictionaryUpdateController>
- sFilenameDictionaryUpdateControllerMap = CollectionUtils.newConcurrentHashMap();
+ sDictNameDictionaryUpdateControllerMap = CollectionUtils.newConcurrentHashMap();
private static final ConcurrentHashMap<String, PrioritizedSerialExecutor>
- sFilenameExecutorMap = CollectionUtils.newConcurrentHashMap();
+ sDictNameExecutorMap = CollectionUtils.newConcurrentHashMap();
/** The application context. */
protected final Context mContext;
@@ -95,18 +92,24 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
protected AbstractDictionaryWriter mDictionaryWriter;
/**
- * The name of this dictionary, used as the filename for storing the binary dictionary. Multiple
- * dictionary instances with the same filename is supported, with access controlled by
- * DictionaryTimeRecorder.
+ * The name of this dictionary, used as a part of the filename for storing the binary
+ * dictionary. Multiple dictionary instances with the same name is supported, with access
+ * controlled by DictionaryUpdateController.
*/
- private final String mFilename;
+ private final String mDictName;
+
+ /** Dictionary locale */
+ private final Locale mLocale;
/** Whether to support dynamically updating the dictionary */
private final boolean mIsUpdatable;
+ /** Dictionary file */
+ private final File mDictFile;
+
// TODO: remove, once dynamic operations is serialized
/** Controls updating the shared binary dictionary file across multiple instances. */
- private final DictionaryUpdateController mFilenameDictionaryUpdateController;
+ private final DictionaryUpdateController mDictNameDictionaryUpdateController;
// TODO: remove, once dynamic operations is serialized
/** Controls updating the local binary dictionary for this instance. */
@@ -132,45 +135,70 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
protected abstract boolean hasContentChanged();
+ protected boolean matchesExpectedBinaryDictFormatVersionForThisType(final int formatVersion) {
+ // This class is using format 2 because it's used by the User and Contacts dictionary
+ // only, which right now use format 2 (dicts using format 4 use Decaying*, which overrides
+ // this method).
+ // TODO: Migrate these dicts to ver4 format, and remove this function.
+ return formatVersion == FormatSpec.VERSION2;
+ }
+
+ public boolean isValidDictionary() {
+ return mBinaryDictionary.isValidDictionary();
+ }
+
+ private File getDictFile() {
+ return mDictFile;
+ }
+
/**
- * Gets the dictionary update controller for the given filename.
+ * Gets the dictionary update controller for the given dictionary name.
*/
private static DictionaryUpdateController getDictionaryUpdateController(
- String filename) {
- DictionaryUpdateController recorder = sFilenameDictionaryUpdateControllerMap.get(filename);
+ final String dictName) {
+ DictionaryUpdateController recorder = sDictNameDictionaryUpdateControllerMap.get(dictName);
if (recorder == null) {
- synchronized(sFilenameDictionaryUpdateControllerMap) {
+ synchronized(sDictNameDictionaryUpdateControllerMap) {
recorder = new DictionaryUpdateController();
- sFilenameDictionaryUpdateControllerMap.put(filename, recorder);
+ sDictNameDictionaryUpdateControllerMap.put(dictName, recorder);
}
}
return recorder;
}
/**
- * Gets the executor for the given filename.
+ * Gets the executor for the given dictionary name.
*/
- private static PrioritizedSerialExecutor getExecutor(final String filename) {
- PrioritizedSerialExecutor executor = sFilenameExecutorMap.get(filename);
+ private static PrioritizedSerialExecutor getExecutor(final String dictName) {
+ PrioritizedSerialExecutor executor = sDictNameExecutorMap.get(dictName);
if (executor == null) {
- synchronized(sFilenameExecutorMap) {
+ synchronized(sDictNameExecutorMap) {
executor = new PrioritizedSerialExecutor();
- sFilenameExecutorMap.put(filename, executor);
+ sDictNameExecutorMap.put(dictName, executor);
}
}
return executor;
}
+ /**
+ * Shutdowns all executors and removes all executors from the executor map for testing.
+ */
+ @UsedForTesting
+ public static void shutdownAllExecutors() {
+ synchronized(sDictNameExecutorMap) {
+ for (final PrioritizedSerialExecutor executor : sDictNameExecutorMap.values()) {
+ executor.shutdown();
+ sDictNameExecutorMap.remove(executor);
+ }
+ }
+ }
+
private static AbstractDictionaryWriter getDictionaryWriter(final Context context,
- final String dictType, final boolean isDynamicPersonalizationDictionary) {
+ final boolean isDynamicPersonalizationDictionary) {
if (isDynamicPersonalizationDictionary) {
- if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
- return null;
- } else {
- return new DynamicPersonalizationDictionaryWriter(context, dictType);
- }
+ return null;
} else {
- return new DictionaryWriter(context, dictType);
+ return new DictionaryWriter(context);
}
}
@@ -178,26 +206,37 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
* Creates a new expandable binary dictionary.
*
* @param context The application context of the parent.
- * @param filename The filename for this binary dictionary. Multiple dictionaries with the same
- * filename is supported.
+ * @param dictName The name of the dictionary. Multiple instances with the same
+ * name is supported.
+ * @param locale the dictionary locale.
* @param dictType the dictionary type, as a human-readable string
* @param isUpdatable whether to support dynamically updating the dictionary. Please note that
* dynamic dictionary has negative effects on memory space and computation time.
*/
- public ExpandableBinaryDictionary(final Context context, final String filename,
- final String dictType, final boolean isUpdatable) {
+ public ExpandableBinaryDictionary(final Context context, final String dictName,
+ final Locale locale, final String dictType, final boolean isUpdatable) {
+ this(context, dictName, locale, dictType, isUpdatable,
+ new File(context.getFilesDir(), dictName + DICT_FILE_EXTENSION));
+ }
+
+ // Creates an instance that uses a given dictionary file.
+ public ExpandableBinaryDictionary(final Context context, final String dictName,
+ final Locale locale, final String dictType, final boolean isUpdatable,
+ final File dictFile) {
super(dictType);
- mFilename = filename;
+ mDictName = dictName;
mContext = context;
+ mLocale = locale;
mIsUpdatable = isUpdatable;
+ mDictFile = dictFile;
mBinaryDictionary = null;
- mFilenameDictionaryUpdateController = getDictionaryUpdateController(filename);
+ mDictNameDictionaryUpdateController = getDictionaryUpdateController(dictName);
// Currently, only dynamic personalization dictionary is updatable.
- mDictionaryWriter = getDictionaryWriter(context, dictType, isUpdatable);
+ mDictionaryWriter = getDictionaryWriter(context, isUpdatable);
}
- protected static String getFilenameWithLocale(final String name, final String localeStr) {
- return name + "." + localeStr + DICT_FILE_EXTENSION;
+ protected static String getDictNameWithLocale(final String name, final Locale locale) {
+ return name + "." + locale.toString();
}
/**
@@ -205,23 +244,20 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
@Override
public void close() {
- getExecutor(mFilename).execute(new Runnable() {
+ getExecutor(mDictName).execute(new Runnable() {
@Override
public void run() {
if (mBinaryDictionary!= null) {
mBinaryDictionary.close();
mBinaryDictionary = null;
}
- if (mDictionaryWriter != null) {
- mDictionaryWriter.close();
- }
}
});
}
protected void closeBinaryDictionary() {
// Ensure that no other threads are accessing the local binary dictionary.
- getExecutor(mFilename).execute(new Runnable() {
+ getExecutor(mDictName).execute(new Runnable() {
@Override
public void run() {
if (mBinaryDictionary != null) {
@@ -234,19 +270,23 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
protected Map<String, String> getHeaderAttributeMap() {
HashMap<String, String> attributeMap = new HashMap<String, String>();
- attributeMap.put(FormatSpec.FileHeader.SUPPORTS_DYNAMIC_UPDATE_ATTRIBUTE,
- SUPPORTS_DYNAMIC_UPDATE);
- attributeMap.put(FormatSpec.FileHeader.DICTIONARY_ID_ATTRIBUTE, mFilename);
+ attributeMap.put(FormatSpec.FileHeader.DICTIONARY_ID_ATTRIBUTE, mDictName);
+ attributeMap.put(FormatSpec.FileHeader.DICTIONARY_LOCALE_ATTRIBUTE, mLocale.toString());
+ attributeMap.put(FormatSpec.FileHeader.DICTIONARY_VERSION_ATTRIBUTE,
+ String.valueOf(TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())));
return attributeMap;
}
protected void clear() {
- getExecutor(mFilename).execute(new Runnable() {
+ getExecutor(mDictName).execute(new Runnable() {
@Override
public void run() {
- if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE && mDictionaryWriter == null) {
+ if (mDictionaryWriter == null) {
mBinaryDictionary.close();
- final File file = new File(mContext.getFilesDir(), mFilename);
+ final File file = getDictFile();
+ if (file.exists() && !FileUtils.deleteRecursively(file)) {
+ Log.e(TAG, "Can't remove a file: " + file.getName());
+ }
BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(),
DICTIONARY_FORMAT_VERSION, getHeaderAttributeMap());
mBinaryDictionary = new BinaryDictionary(
@@ -286,8 +326,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
* Check whether GC is needed and run GC if required.
*/
protected void runGCIfRequired(final boolean mindsBlockByGC) {
- if (!ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) return;
- getExecutor(mFilename).execute(new Runnable() {
+ getExecutor(mDictName).execute(new Runnable() {
@Override
public void run() {
runGCIfRequiredInternalLocked(mindsBlockByGC);
@@ -296,18 +335,17 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
private void runGCIfRequiredInternalLocked(final boolean mindsBlockByGC) {
- if (!ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) return;
// Calls to needsToRunGC() need to be serialized.
if (mBinaryDictionary.needsToRunGC(mindsBlockByGC)) {
- if (setIsRegeneratingIfNotRegenerating()) {
+ if (setProcessingLargeTaskIfNot()) {
// Run GC after currently existing time sensitive operations.
- getExecutor(mFilename).executePrioritized(new Runnable() {
+ getExecutor(mDictName).executePrioritized(new Runnable() {
@Override
public void run() {
try {
mBinaryDictionary.flushWithGC();
} finally {
- mFilenameDictionaryUpdateController.mIsRegenerating.set(false);
+ mDictNameDictionaryUpdateController.mProcessingLargeTask.set(false);
}
}
});
@@ -318,23 +356,19 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
/**
* Dynamically adds a word unigram to the dictionary. May overwrite an existing entry.
*/
- protected void addWordDynamically(final String word, final String shortcutTarget,
- final int frequency, final int shortcutFreq, final boolean isNotAWord) {
+ protected void addWordDynamically(final String word, final int frequency,
+ final String shortcutTarget, final int shortcutFreq, final boolean isNotAWord,
+ final boolean isBlacklisted, final int timestamp) {
if (!mIsUpdatable) {
- Log.w(TAG, "addWordDynamically is called for non-updatable dictionary: " + mFilename);
+ Log.w(TAG, "addWordDynamically is called for non-updatable dictionary: " + mDictName);
return;
}
- getExecutor(mFilename).execute(new Runnable() {
+ getExecutor(mDictName).execute(new Runnable() {
@Override
public void run() {
- if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
- runGCIfRequiredInternalLocked(true /* mindsBlockByGC */);
- mBinaryDictionary.addUnigramWord(word, frequency);
- } else {
- // TODO: Remove.
- mDictionaryWriter.addUnigramWord(word, shortcutTarget, frequency, shortcutFreq,
- isNotAWord);
- }
+ runGCIfRequiredInternalLocked(true /* mindsBlockByGC */);
+ mBinaryDictionary.addUnigramWord(word, frequency, shortcutTarget, shortcutFreq,
+ isNotAWord, isBlacklisted, timestamp);
}
});
}
@@ -343,23 +377,17 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
* Dynamically adds a word bigram in the dictionary. May overwrite an existing entry.
*/
protected void addBigramDynamically(final String word0, final String word1,
- final int frequency, final boolean isValid) {
+ final int frequency, final int timestamp) {
if (!mIsUpdatable) {
Log.w(TAG, "addBigramDynamically is called for non-updatable dictionary: "
- + mFilename);
+ + mDictName);
return;
}
- getExecutor(mFilename).execute(new Runnable() {
+ getExecutor(mDictName).execute(new Runnable() {
@Override
public void run() {
- if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
- runGCIfRequiredInternalLocked(true /* mindsBlockByGC */);
- mBinaryDictionary.addBigramWords(word0, word1, frequency);
- } else {
- // TODO: Remove.
- mDictionaryWriter.addBigramWords(word0, word1, frequency, isValid,
- 0 /* lastTouchedTime */);
- }
+ runGCIfRequiredInternalLocked(true /* mindsBlockByGC */);
+ mBinaryDictionary.addBigramWords(word0, word1, frequency, timestamp);
}
});
}
@@ -370,18 +398,48 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
protected void removeBigramDynamically(final String word0, final String word1) {
if (!mIsUpdatable) {
Log.w(TAG, "removeBigramDynamically is called for non-updatable dictionary: "
- + mFilename);
+ + mDictName);
return;
}
- getExecutor(mFilename).execute(new Runnable() {
+ getExecutor(mDictName).execute(new Runnable() {
@Override
public void run() {
- if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
- runGCIfRequiredInternalLocked(true /* mindsBlockByGC */);
- mBinaryDictionary.removeBigramWords(word0, word1);
- } else {
- // TODO: Remove.
- mDictionaryWriter.removeBigramWords(word0, word1);
+ runGCIfRequiredInternalLocked(true /* mindsBlockByGC */);
+ mBinaryDictionary.removeBigramWords(word0, word1);
+ }
+ });
+ }
+
+ public interface AddMultipleDictionaryEntriesCallback {
+ public void onFinished();
+ }
+
+ /**
+ * Dynamically add multiple entries to the dictionary.
+ */
+ protected void addMultipleDictionaryEntriesDynamically(
+ final ArrayList<LanguageModelParam> languageModelParams,
+ final AddMultipleDictionaryEntriesCallback callback) {
+ if (!mIsUpdatable) {
+ Log.w(TAG, "addMultipleDictionaryEntriesDynamically is called for non-updatable " +
+ "dictionary: " + mDictName);
+ return;
+ }
+ getExecutor(mDictName).execute(new Runnable() {
+ @Override
+ public void run() {
+ final boolean locked = setProcessingLargeTaskIfNot();
+ try {
+ mBinaryDictionary.addMultipleDictionaryEntries(
+ languageModelParams.toArray(
+ new LanguageModelParam[languageModelParams.size()]));
+ } finally {
+ if (callback != null) {
+ callback.onFinished();
+ }
+ if (locked) {
+ mDictNameDictionaryUpdateController.mProcessingLargeTask.set(false);
+ }
}
}
});
@@ -393,49 +451,23 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
final int sessionId) {
reloadDictionaryIfRequired();
- if (isRegenerating()) {
+ if (processingLargeTask()) {
return null;
}
- final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
final AsyncResultHolder<ArrayList<SuggestedWordInfo>> holder =
new AsyncResultHolder<ArrayList<SuggestedWordInfo>>();
- getExecutor(mFilename).executePrioritized(new Runnable() {
+ getExecutor(mDictName).executePrioritized(new Runnable() {
@Override
public void run() {
- if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
- if (mBinaryDictionary == null) {
- holder.set(null);
- return;
- }
- final ArrayList<SuggestedWordInfo> binarySuggestion =
- mBinaryDictionary.getSuggestionsWithSessionId(composer, prevWord,
- proximityInfo, blockOffensiveWords, additionalFeaturesOptions,
- sessionId);
- holder.set(binarySuggestion);
- } else {
- final ArrayList<SuggestedWordInfo> inMemDictSuggestion =
- composer.isBatchMode() ? null :
- mDictionaryWriter.getSuggestionsWithSessionId(composer,
- prevWord, proximityInfo, blockOffensiveWords,
- additionalFeaturesOptions, sessionId);
- // TODO: Remove checking mIsUpdatable and use native suggestion.
- if (mBinaryDictionary != null && !mIsUpdatable) {
- final ArrayList<SuggestedWordInfo> binarySuggestion =
- mBinaryDictionary.getSuggestionsWithSessionId(composer, prevWord,
- proximityInfo, blockOffensiveWords,
- additionalFeaturesOptions, sessionId);
- if (inMemDictSuggestion == null) {
- holder.set(binarySuggestion);
- } else if (binarySuggestion == null) {
- holder.set(inMemDictSuggestion);
- } else {
- binarySuggestion.addAll(inMemDictSuggestion);
- holder.set(binarySuggestion);
- }
- } else {
- holder.set(inMemDictSuggestion);
- }
+ if (mBinaryDictionary == null) {
+ holder.set(null);
+ return;
}
+ final ArrayList<SuggestedWordInfo> binarySuggestion =
+ mBinaryDictionary.getSuggestionsWithSessionId(composer, prevWord,
+ proximityInfo, blockOffensiveWords, additionalFeaturesOptions,
+ sessionId);
+ holder.set(binarySuggestion);
}
});
return holder.get(null, TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS);
@@ -456,11 +488,11 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
protected boolean isValidWordInner(final String word) {
- if (isRegenerating()) {
+ if (processingLargeTask()) {
return false;
}
final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>();
- getExecutor(mFilename).executePrioritized(new Runnable() {
+ getExecutor(mDictName).executePrioritized(new Runnable() {
@Override
public void run() {
holder.set(isValidWordLocked(word));
@@ -484,7 +516,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
* dictionary exists, this method will generate one.
*/
protected void loadDictionary() {
- mPerInstanceDictionaryUpdateController.mLastUpdateRequestTime = SystemClock.uptimeMillis();
+ mPerInstanceDictionaryUpdateController.mLastUpdateRequestTime = System.currentTimeMillis();
reloadDictionaryIfRequired();
}
@@ -494,12 +526,22 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
private void loadBinaryDictionary() {
if (DEBUG) {
- Log.d(TAG, "Loading binary dictionary: " + mFilename + " request="
- + mFilenameDictionaryUpdateController.mLastUpdateRequestTime + " update="
- + mFilenameDictionaryUpdateController.mLastUpdateTime);
+ Log.d(TAG, "Loading binary dictionary: " + mDictName + " request="
+ + mDictNameDictionaryUpdateController.mLastUpdateRequestTime + " update="
+ + mDictNameDictionaryUpdateController.mLastUpdateTime);
+ }
+ if (DBG_STRESS_TEST) {
+ // Test if this class does not cause problems when it takes long time to load binary
+ // dictionary.
+ try {
+ Log.w(TAG, "Start stress in loading: " + mDictName);
+ Thread.sleep(15000);
+ Log.w(TAG, "End stress in loading");
+ } catch (InterruptedException e) {
+ }
}
- final File file = new File(mContext.getFilesDir(), mFilename);
+ final File file = getDictFile();
final String filename = file.getAbsolutePath();
final long length = file.length();
@@ -511,7 +553,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
// swapping in the new one.
// TODO: Ensure multi-thread assignment of mBinaryDictionary.
final BinaryDictionary oldBinaryDictionary = mBinaryDictionary;
- getExecutor(mFilename).executePrioritized(new Runnable() {
+ getExecutor(mDictName).executePrioritized(new Runnable() {
@Override
public void run() {
mBinaryDictionary = newBinaryDictionary;
@@ -533,29 +575,31 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
private void writeBinaryDictionary() {
if (DEBUG) {
- Log.d(TAG, "Generating binary dictionary: " + mFilename + " request="
- + mFilenameDictionaryUpdateController.mLastUpdateRequestTime + " update="
- + mFilenameDictionaryUpdateController.mLastUpdateTime);
+ Log.d(TAG, "Generating binary dictionary: " + mDictName + " request="
+ + mDictNameDictionaryUpdateController.mLastUpdateRequestTime + " update="
+ + mDictNameDictionaryUpdateController.mLastUpdateTime);
}
if (needsToReloadBeforeWriting()) {
mDictionaryWriter.clear();
loadDictionaryAsync();
- mDictionaryWriter.write(mFilename, getHeaderAttributeMap());
+ mDictionaryWriter.write(getDictFile(), getHeaderAttributeMap());
} else {
- if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
- if (mBinaryDictionary == null || !mBinaryDictionary.isValidDictionary()) {
- final File file = new File(mContext.getFilesDir(), mFilename);
- BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(),
- DICTIONARY_FORMAT_VERSION, getHeaderAttributeMap());
- } else {
- if (mBinaryDictionary.needsToRunGC(false /* mindsBlockByGC */)) {
- mBinaryDictionary.flushWithGC();
- } else {
- mBinaryDictionary.flush();
- }
+ if (mBinaryDictionary == null || !isValidDictionary()
+ // TODO: remove the check below
+ || !matchesExpectedBinaryDictFormatVersionForThisType(
+ mBinaryDictionary.getFormatVersion())) {
+ final File file = getDictFile();
+ if (file.exists() && !FileUtils.deleteRecursively(file)) {
+ Log.e(TAG, "Can't remove a file: " + file.getName());
}
+ BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(),
+ DICTIONARY_FORMAT_VERSION, getHeaderAttributeMap());
} else {
- mDictionaryWriter.write(mFilename, getHeaderAttributeMap());
+ if (mBinaryDictionary.needsToRunGC(false /* mindsBlockByGC */)) {
+ mBinaryDictionary.flushWithGC();
+ } else {
+ mBinaryDictionary.flush();
+ }
}
}
}
@@ -568,12 +612,12 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
* the current binary dictionary from file.
*/
protected void setRequiresReload(final boolean requiresRebuild) {
- final long time = SystemClock.uptimeMillis();
+ final long time = System.currentTimeMillis();
mPerInstanceDictionaryUpdateController.mLastUpdateRequestTime = time;
- mFilenameDictionaryUpdateController.mLastUpdateRequestTime = time;
+ mDictNameDictionaryUpdateController.mLastUpdateRequestTime = time;
if (DEBUG) {
- Log.d(TAG, "Reload request: " + mFilename + ": request=" + time + " update="
- + mFilenameDictionaryUpdateController.mLastUpdateTime);
+ Log.d(TAG, "Reload request: " + mDictName + ": request=" + time + " update="
+ + mDictNameDictionaryUpdateController.mLastUpdateTime);
}
}
@@ -582,7 +626,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
public final void reloadDictionaryIfRequired() {
if (!isReloadRequired()) return;
- if (setIsRegeneratingIfNotRegenerating()) {
+ if (setProcessingLargeTaskIfNot()) {
reloadDictionary();
}
}
@@ -594,13 +638,14 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
return mBinaryDictionary == null || mPerInstanceDictionaryUpdateController.isOutOfDate();
}
- private boolean isRegenerating() {
- return mFilenameDictionaryUpdateController.mIsRegenerating.get();
+ private boolean processingLargeTask() {
+ return mDictNameDictionaryUpdateController.mProcessingLargeTask.get();
}
- // Returns whether the dictionary can be regenerated.
- private boolean setIsRegeneratingIfNotRegenerating() {
- return mFilenameDictionaryUpdateController.mIsRegenerating.compareAndSet(
+ // Returns whether the dictionary is being used for a large task. If true, we should not use
+ // this dictionary for latency sensitive operations.
+ private boolean setProcessingLargeTaskIfNot() {
+ return mDictNameDictionaryUpdateController.mProcessingLargeTask.compareAndSet(
false /* expect */ , true /* update */);
}
@@ -611,13 +656,13 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
private final void reloadDictionary() {
// Ensure that only one thread attempts to read or write to the shared binary dictionary
// file at the same time.
- getExecutor(mFilename).execute(new Runnable() {
+ getExecutor(mDictName).execute(new Runnable() {
@Override
public void run() {
try {
- final long time = SystemClock.uptimeMillis();
+ final long time = System.currentTimeMillis();
final boolean dictionaryFileExists = dictionaryFileExists();
- if (mFilenameDictionaryUpdateController.isOutOfDate()
+ if (mDictNameDictionaryUpdateController.isOutOfDate()
|| !dictionaryFileExists) {
// If the shared dictionary file does not exist or is out of date, the
// first instance that acquires the lock will generate a new one.
@@ -626,31 +671,44 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
// rebuild the binary dictionary. Empty dictionaries are supported (in
// the case where loadDictionaryAsync() adds nothing) in order to
// provide a uniform framework.
- mFilenameDictionaryUpdateController.mLastUpdateTime = time;
+ mDictNameDictionaryUpdateController.mLastUpdateTime = time;
writeBinaryDictionary();
loadBinaryDictionary();
} else {
// If not, the reload request was unnecessary so revert
// LastUpdateRequestTime to LastUpdateTime.
- mFilenameDictionaryUpdateController.mLastUpdateRequestTime =
- mFilenameDictionaryUpdateController.mLastUpdateTime;
+ mDictNameDictionaryUpdateController.mLastUpdateRequestTime =
+ mDictNameDictionaryUpdateController.mLastUpdateTime;
}
} else if (mBinaryDictionary == null ||
mPerInstanceDictionaryUpdateController.mLastUpdateTime
- < mFilenameDictionaryUpdateController.mLastUpdateTime) {
+ < mDictNameDictionaryUpdateController.mLastUpdateTime) {
// Otherwise, if the local dictionary is older than the shared dictionary,
// load the shared dictionary.
loadBinaryDictionary();
}
- if (mBinaryDictionary != null && !mBinaryDictionary.isValidDictionary()) {
- // Binary dictionary is not valid. Regenerate the dictionary file.
- mFilenameDictionaryUpdateController.mLastUpdateTime = time;
- writeBinaryDictionary();
- loadBinaryDictionary();
- }
- mPerInstanceDictionaryUpdateController.mLastUpdateTime = time;
+ // If we just loaded the binary dictionary, then mBinaryDictionary is not
+ // up-to-date yet so it's useless to test it right away. Schedule the check
+ // for right after it's loaded instead.
+ getExecutor(mDictName).executePrioritized(new Runnable() {
+ @Override
+ public void run() {
+ if (mBinaryDictionary != null && !(isValidDictionary()
+ // TODO: remove the check below
+ && matchesExpectedBinaryDictFormatVersionForThisType(
+ mBinaryDictionary.getFormatVersion()))) {
+ // Binary dictionary or its format version is not valid. Regenerate
+ // the dictionary file. writeBinaryDictionary will remove the
+ // existing files if appropriate.
+ mDictNameDictionaryUpdateController.mLastUpdateTime = time;
+ writeBinaryDictionary();
+ loadBinaryDictionary();
+ }
+ mPerInstanceDictionaryUpdateController.mLastUpdateTime = time;
+ }
+ });
} finally {
- mFilenameDictionaryUpdateController.mIsRegenerating.set(false);
+ mDictNameDictionaryUpdateController.mProcessingLargeTask.set(false);
}
}
});
@@ -658,28 +716,13 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
// TODO: cache the file's existence so that we avoid doing a disk access each time.
private boolean dictionaryFileExists() {
- final File file = new File(mContext.getFilesDir(), mFilename);
- return file.exists();
- }
-
- /**
- * Load the dictionary to memory.
- */
- protected void asyncLoadDictionaryToMemory() {
- getExecutor(mFilename).executePrioritized(new Runnable() {
- @Override
- public void run() {
- if (!ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
- loadDictionaryAsync();
- }
- }
- });
+ return getDictFile().exists();
}
/**
* Generate binary dictionary using DictionaryWriter.
*/
- protected void asyncFlashAllBinaryDictionary() {
+ protected void asyncFlushBinaryDictionary() {
final Runnable newTask = new Runnable() {
@Override
public void run() {
@@ -687,37 +730,32 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
};
final Runnable oldTask = mUnfinishedFlushingTask.getAndSet(newTask);
- getExecutor(mFilename).replaceAndExecute(oldTask, newTask);
+ getExecutor(mDictName).replaceAndExecute(oldTask, newTask);
}
/**
- * For tracking whether the dictionary is out of date and the dictionary is regenerating.
- * Can be shared across multiple dictionary instances that access the same filename.
+ * For tracking whether the dictionary is out of date and the dictionary is used in a large
+ * task. Can be shared across multiple dictionary instances that access the same filename.
*/
private static class DictionaryUpdateController {
public volatile long mLastUpdateTime = 0;
public volatile long mLastUpdateRequestTime = 0;
- public volatile AtomicBoolean mIsRegenerating = new AtomicBoolean();
+ public volatile AtomicBoolean mProcessingLargeTask = new AtomicBoolean();
public boolean isOutOfDate() {
return (mLastUpdateRequestTime > mLastUpdateTime);
}
}
- // TODO: Implement native binary methods once the dynamic dictionary implementation is done.
+ // TODO: Implement BinaryDictionary.isInDictionary().
@UsedForTesting
public boolean isInDictionaryForTests(final String word) {
final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>();
- getExecutor(mFilename).executePrioritized(new Runnable() {
+ getExecutor(mDictName).executePrioritized(new Runnable() {
@Override
public void run() {
if (mDictType == Dictionary.TYPE_USER_HISTORY) {
- if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
- holder.set(mBinaryDictionary.isValidWord(word));
- } else {
- holder.set(((DynamicPersonalizationDictionaryWriter) mDictionaryWriter)
- .isInBigramListForTests(word));
- }
+ holder.set(mBinaryDictionary.isValidWord(word));
}
}
});
@@ -725,12 +763,33 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
@UsedForTesting
- public void shutdownExecutorForTests() {
- getExecutor(mFilename).shutdown();
+ public void waitAllTasksForTests() {
+ final CountDownLatch countDownLatch = new CountDownLatch(1);
+ getExecutor(mDictName).execute(new Runnable() {
+ @Override
+ public void run() {
+ countDownLatch.countDown();
+ }
+ });
+ try {
+ countDownLatch.await();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Interrupted while waiting for finishing dictionary operations.", e);
+ }
}
@UsedForTesting
- public boolean isTerminatedForTests() {
- return getExecutor(mFilename).isTerminated();
+ protected void runAfterGcForDebug(final Runnable r) {
+ getExecutor(mDictName).executePrioritized(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mBinaryDictionary.flushWithGC();
+ r.run();
+ } finally {
+ mDictNameDictionaryUpdateController.mProcessingLargeTask.set(false);
+ }
+ }
+ });
}
}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
deleted file mode 100644
index 95c9bcab9..000000000
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ /dev/null
@@ -1,894 +0,0 @@
-/*
- * Copyright (C) 2009 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.text.TextUtils;
-import android.util.Log;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.keyboard.ProximityInfo;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.utils.CollectionUtils;
-import com.android.inputmethod.latin.utils.UserHistoryForgettingCurveUtils.ForgettingCurveParams;
-
-import java.util.ArrayList;
-import java.util.LinkedList;
-
-/**
- * Class for an in-memory dictionary that can grow dynamically and can
- * be searched for suggestions and valid words.
- */
-// TODO: Remove after binary dictionary supports dynamic update.
-public class ExpandableDictionary extends Dictionary {
- private static final String TAG = ExpandableDictionary.class.getSimpleName();
- /**
- * The weight to give to a word if it's length is the same as the number of typed characters.
- */
- private static final int FULL_WORD_SCORE_MULTIPLIER = 2;
-
- private char[] mWordBuilder = new char[Constants.DICTIONARY_MAX_WORD_LENGTH];
- private int mMaxDepth;
- private int mInputLength;
-
- private static final class Node {
- char mCode;
- int mFrequency;
- boolean mTerminal;
- Node mParent;
- NodeArray mChildren;
- ArrayList<char[]> mShortcutTargets;
- boolean mShortcutOnly;
- LinkedList<NextWord> mNGrams; // Supports ngram
- }
-
- private static final class NodeArray {
- Node[] mData;
- int mLength = 0;
- private static final int INCREMENT = 2;
-
- NodeArray() {
- mData = new Node[INCREMENT];
- }
-
- void add(final Node n) {
- if (mLength + 1 > mData.length) {
- Node[] tempData = new Node[mLength + INCREMENT];
- if (mLength > 0) {
- System.arraycopy(mData, 0, tempData, 0, mLength);
- }
- mData = tempData;
- }
- mData[mLength++] = n;
- }
- }
-
- public interface NextWord {
- public Node getWordNode();
- public int getFrequency();
- public ForgettingCurveParams getFcParams();
- public int notifyTypedAgainAndGetFrequency();
- }
-
- private static final class NextStaticWord implements NextWord {
- public final Node mWord;
- private final int mFrequency;
- public NextStaticWord(Node word, int frequency) {
- mWord = word;
- mFrequency = frequency;
- }
-
- @Override
- public Node getWordNode() {
- return mWord;
- }
-
- @Override
- public int getFrequency() {
- return mFrequency;
- }
-
- @Override
- public ForgettingCurveParams getFcParams() {
- return null;
- }
-
- @Override
- public int notifyTypedAgainAndGetFrequency() {
- return mFrequency;
- }
- }
-
- private static final class NextHistoryWord implements NextWord {
- public final Node mWord;
- public final ForgettingCurveParams mFcp;
-
- public NextHistoryWord(Node word, ForgettingCurveParams fcp) {
- mWord = word;
- mFcp = fcp;
- }
-
- @Override
- public Node getWordNode() {
- return mWord;
- }
-
- @Override
- public int getFrequency() {
- return mFcp.getFrequency();
- }
-
- @Override
- public ForgettingCurveParams getFcParams() {
- return mFcp;
- }
-
- @Override
- public int notifyTypedAgainAndGetFrequency() {
- return mFcp.notifyTypedAgainAndGetFrequency();
- }
- }
-
- private NodeArray mRoots;
-
- private int[][] mCodes;
-
- public ExpandableDictionary(final String dictType) {
- super(dictType);
- clearDictionary();
- mCodes = new int[Constants.DICTIONARY_MAX_WORD_LENGTH][];
- }
-
- public int getMaxWordLength() {
- return Constants.DICTIONARY_MAX_WORD_LENGTH;
- }
-
- /**
- * Add a word with an optional shortcut to the dictionary.
- * @param word The word to add.
- * @param shortcutTarget A shortcut target for this word, or null if none.
- * @param frequency The frequency for this unigram.
- * @param shortcutFreq The frequency of the shortcut (0~15, with 15 = whitelist). Ignored
- * if shortcutTarget is null.
- */
- public void addWord(final String word, final String shortcutTarget, final int frequency,
- final int shortcutFreq) {
- if (word.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH) {
- return;
- }
- addWordRec(mRoots, word, 0, shortcutTarget, frequency, shortcutFreq, null);
- }
-
- /**
- * Add a word, recursively searching for its correct place in the trie tree.
- * @param children The node to recursively search for addition. Initially, the root of the tree.
- * @param word The word to add.
- * @param depth The current depth in the tree.
- * @param shortcutTarget A shortcut target for this word, or null if none.
- * @param frequency The frequency for this unigram.
- * @param shortcutFreq The frequency of the shortcut (0~15, with 15 = whitelist). Ignored
- * if shortcutTarget is null.
- * @param parentNode The parent node, for up linking. Initially null, as the root has no parent.
- */
- private void addWordRec(final NodeArray children, final String word, final int depth,
- final String shortcutTarget, final int frequency, final int shortcutFreq,
- final Node parentNode) {
- final int wordLength = word.length();
- if (wordLength <= depth) return;
- final char c = word.charAt(depth);
- // Does children have the current character?
- final int childrenLength = children.mLength;
- Node childNode = null;
- for (int i = 0; i < childrenLength; i++) {
- final Node node = children.mData[i];
- if (node.mCode == c) {
- childNode = node;
- break;
- }
- }
- final boolean isShortcutOnly = (null != shortcutTarget);
- if (childNode == null) {
- childNode = new Node();
- childNode.mCode = c;
- childNode.mParent = parentNode;
- childNode.mShortcutOnly = isShortcutOnly;
- children.add(childNode);
- }
- if (wordLength == depth + 1) {
- // Terminate this word
- childNode.mTerminal = true;
- if (isShortcutOnly) {
- if (null == childNode.mShortcutTargets) {
- childNode.mShortcutTargets = CollectionUtils.newArrayList();
- }
- childNode.mShortcutTargets.add(shortcutTarget.toCharArray());
- } else {
- childNode.mShortcutOnly = false;
- }
- childNode.mFrequency = Math.max(frequency, childNode.mFrequency);
- if (childNode.mFrequency > 255) childNode.mFrequency = 255;
- return;
- }
- if (childNode.mChildren == null) {
- childNode.mChildren = new NodeArray();
- }
- addWordRec(childNode.mChildren, word, depth + 1, shortcutTarget, frequency, shortcutFreq,
- childNode);
- }
-
- @Override
- public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final String prevWord, final ProximityInfo proximityInfo,
- final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
- if (composer.size() > 1) {
- if (composer.size() >= Constants.DICTIONARY_MAX_WORD_LENGTH) {
- return null;
- }
- final ArrayList<SuggestedWordInfo> suggestions =
- getWordsInner(composer, prevWord, proximityInfo);
- return suggestions;
- } else {
- if (TextUtils.isEmpty(prevWord)) return null;
- final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
- runBigramReverseLookUp(prevWord, suggestions);
- return suggestions;
- }
- }
-
- private ArrayList<SuggestedWordInfo> getWordsInner(final WordComposer codes,
- final String prevWordForBigrams, final ProximityInfo proximityInfo) {
- final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
- mInputLength = codes.size();
- if (mCodes.length < mInputLength) mCodes = new int[mInputLength][];
- final InputPointers ips = codes.getInputPointers();
- final int[] xCoordinates = ips.getXCoordinates();
- final int[] yCoordinates = ips.getYCoordinates();
- // Cache the codes so that we don't have to lookup an array list
- for (int i = 0; i < mInputLength; i++) {
- // TODO: Calculate proximity info here.
- if (mCodes[i] == null || mCodes[i].length < 1) {
- mCodes[i] = new int[ProximityInfo.MAX_PROXIMITY_CHARS_SIZE];
- }
- final int x = xCoordinates != null && i < xCoordinates.length ?
- xCoordinates[i] : Constants.NOT_A_COORDINATE;
- final int y = xCoordinates != null && i < yCoordinates.length ?
- yCoordinates[i] : Constants.NOT_A_COORDINATE;
- proximityInfo.fillArrayWithNearestKeyCodes(x, y, codes.getCodeAt(i), mCodes[i]);
- }
- mMaxDepth = mInputLength * 3;
- getWordsRec(mRoots, codes, mWordBuilder, 0, false, 1, 0, -1, suggestions);
- for (int i = 0; i < mInputLength; i++) {
- getWordsRec(mRoots, codes, mWordBuilder, 0, false, 1, 0, i, suggestions);
- }
- return suggestions;
- }
-
- @Override
- public synchronized boolean isValidWord(final String word) {
- final Node node = searchNode(mRoots, word, 0, word.length());
- // If node is null, we didn't find the word, so it's not valid.
- // If node.mShortcutOnly is true, then it exists as a shortcut but not as a word,
- // so that means it's not a valid word.
- // If node.mShortcutOnly is false, then it exists as a word (it may also exist as
- // a shortcut, but this does not matter), so it's a valid word.
- return (node == null) ? false : !node.mShortcutOnly;
- }
-
- public boolean removeBigram(final String word0, final String word1) {
- // Refer to addOrSetBigram() about word1.toLowerCase()
- final Node firstWord = searchWord(mRoots, word0.toLowerCase(), 0, null);
- final Node secondWord = searchWord(mRoots, word1, 0, null);
- LinkedList<NextWord> bigrams = firstWord.mNGrams;
- NextWord bigramNode = null;
- if (bigrams == null || bigrams.size() == 0) {
- return false;
- } else {
- for (NextWord nw : bigrams) {
- if (nw.getWordNode() == secondWord) {
- bigramNode = nw;
- break;
- }
- }
- }
- if (bigramNode == null) {
- return false;
- }
- return bigrams.remove(bigramNode);
- }
-
- /**
- * Returns the word's frequency or -1 if not found
- */
- @UsedForTesting
- public int getWordFrequency(final String word) {
- // Case-sensitive search
- final Node node = searchNode(mRoots, word, 0, word.length());
- return (node == null) ? -1 : node.mFrequency;
- }
-
- public NextWord getBigramWord(final String word0, final String word1) {
- // Refer to addOrSetBigram() about word0.toLowerCase()
- final Node firstWord = searchWord(mRoots, word0.toLowerCase(), 0, null);
- final Node secondWord = searchWord(mRoots, word1, 0, null);
- LinkedList<NextWord> bigrams = firstWord.mNGrams;
- if (bigrams == null || bigrams.size() == 0) {
- return null;
- } else {
- for (NextWord nw : bigrams) {
- if (nw.getWordNode() == secondWord) {
- return nw;
- }
- }
- }
- return null;
- }
-
- private static int computeSkippedWordFinalFreq(final int freq, final int snr,
- final int inputLength) {
- // The computation itself makes sense for >= 2, but the == 2 case returns 0
- // anyway so we may as well test against 3 instead and return the constant
- if (inputLength >= 3) {
- return (freq * snr * (inputLength - 2)) / (inputLength - 1);
- } else {
- return 0;
- }
- }
-
- /**
- * Helper method to add a word and its shortcuts.
- *
- * @param node the terminal node
- * @param word the word to insert, as an array of code points
- * @param depth the depth of the node in the tree
- * @param finalFreq the frequency for this word
- * @param suggestions the suggestion collection to add the suggestions to
- * @return whether there is still space for more words.
- */
- private boolean addWordAndShortcutsFromNode(final Node node, final char[] word, final int depth,
- final int finalFreq, final ArrayList<SuggestedWordInfo> suggestions) {
- if (finalFreq > 0 && !node.mShortcutOnly) {
- // Use KIND_CORRECTION always. This dictionary does not really have a notion of
- // COMPLETION against CORRECTION; we could artificially add one by looking at
- // the respective size of the typed word and the suggestion if it matters sometime
- // in the future.
- suggestions.add(new SuggestedWordInfo(new String(word, 0, depth + 1), finalFreq,
- SuggestedWordInfo.KIND_CORRECTION, this /* sourceDict */,
- SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
- SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
- if (suggestions.size() >= Suggest.MAX_SUGGESTIONS) return false;
- }
- if (null != node.mShortcutTargets) {
- final int length = node.mShortcutTargets.size();
- for (int shortcutIndex = 0; shortcutIndex < length; ++shortcutIndex) {
- final char[] shortcut = node.mShortcutTargets.get(shortcutIndex);
- suggestions.add(new SuggestedWordInfo(new String(shortcut, 0, shortcut.length),
- finalFreq, SuggestedWordInfo.KIND_SHORTCUT, this /* sourceDict */,
- SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
- SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
- if (suggestions.size() > Suggest.MAX_SUGGESTIONS) return false;
- }
- }
- return true;
- }
-
- /**
- * Recursively traverse the tree for words that match the input. Input consists of
- * a list of arrays. Each item in the list is one input character position. An input
- * character is actually an array of multiple possible candidates. This function is not
- * optimized for speed, assuming that the user dictionary will only be a few hundred words in
- * size.
- * @param roots node whose children have to be search for matches
- * @param codes the input character codes
- * @param word the word being composed as a possible match
- * @param depth the depth of traversal - the length of the word being composed thus far
- * @param completion whether the traversal is now in completion mode - meaning that we've
- * exhausted the input and we're looking for all possible suffixes.
- * @param snr current weight of the word being formed
- * @param inputIndex position in the input characters. This can be off from the depth in
- * case we skip over some punctuations such as apostrophe in the traversal. That is, if you type
- * "wouldve", it could be matching "would've", so the depth will be one more than the
- * inputIndex
- * @param suggestions the list in which to add suggestions
- */
- // TODO: Share this routine with the native code for BinaryDictionary
- private void getWordsRec(final NodeArray roots, final WordComposer codes, final char[] word,
- final int depth, final boolean completion, final int snr, final int inputIndex,
- final int skipPos, final ArrayList<SuggestedWordInfo> suggestions) {
- final int count = roots.mLength;
- final int codeSize = mInputLength;
- // Optimization: Prune out words that are too long compared to how much was typed.
- if (depth > mMaxDepth) {
- return;
- }
- final int[] currentChars;
- if (codeSize <= inputIndex) {
- currentChars = null;
- } else {
- currentChars = mCodes[inputIndex];
- }
-
- for (int i = 0; i < count; i++) {
- final Node node = roots.mData[i];
- final char c = node.mCode;
- final char lowerC = toLowerCase(c);
- final boolean terminal = node.mTerminal;
- final NodeArray children = node.mChildren;
- final int freq = node.mFrequency;
- if (completion || currentChars == null) {
- word[depth] = c;
- if (terminal) {
- final int finalFreq;
- if (skipPos < 0) {
- finalFreq = freq * snr;
- } else {
- finalFreq = computeSkippedWordFinalFreq(freq, snr, mInputLength);
- }
- if (!addWordAndShortcutsFromNode(node, word, depth, finalFreq, suggestions)) {
- // No space left in the queue, bail out
- return;
- }
- }
- if (children != null) {
- getWordsRec(children, codes, word, depth + 1, true, snr, inputIndex,
- skipPos, suggestions);
- }
- } else if ((c == Constants.CODE_SINGLE_QUOTE
- && currentChars[0] != Constants.CODE_SINGLE_QUOTE) || depth == skipPos) {
- // Skip the ' and continue deeper
- word[depth] = c;
- if (children != null) {
- getWordsRec(children, codes, word, depth + 1, completion, snr, inputIndex,
- skipPos, suggestions);
- }
- } else {
- // Don't use alternatives if we're looking for missing characters
- final int alternativesSize = skipPos >= 0 ? 1 : currentChars.length;
- for (int j = 0; j < alternativesSize; j++) {
- final int addedAttenuation = (j > 0 ? 1 : 2);
- final int currentChar = currentChars[j];
- if (currentChar == Constants.NOT_A_CODE) {
- break;
- }
- if (currentChar == lowerC || currentChar == c) {
- word[depth] = c;
-
- if (codeSize == inputIndex + 1) {
- if (terminal) {
- final int finalFreq;
- if (skipPos < 0) {
- finalFreq = freq * snr * addedAttenuation
- * FULL_WORD_SCORE_MULTIPLIER;
- } else {
- finalFreq = computeSkippedWordFinalFreq(freq,
- snr * addedAttenuation, mInputLength);
- }
- if (!addWordAndShortcutsFromNode(node, word, depth, finalFreq,
- suggestions)) {
- // No space left in the queue, bail out
- return;
- }
- }
- if (children != null) {
- getWordsRec(children, codes, word, depth + 1,
- true, snr * addedAttenuation, inputIndex + 1,
- skipPos, suggestions);
- }
- } else if (children != null) {
- getWordsRec(children, codes, word, depth + 1,
- false, snr * addedAttenuation, inputIndex + 1,
- skipPos, suggestions);
- }
- }
- }
- }
- }
- }
-
- public int setBigramAndGetFrequency(final String word0, final String word1,
- final int frequency) {
- return setBigramAndGetFrequency(word0, word1, frequency, null /* unused */);
- }
-
- public int setBigramAndGetFrequency(final String word0, final String word1,
- final ForgettingCurveParams fcp) {
- return setBigramAndGetFrequency(word0, word1, 0 /* unused */, fcp);
- }
-
- /**
- * Adds bigrams to the in-memory trie structure that is being used to retrieve any word
- * @param word0 the first word of this bigram
- * @param word1 the second word of this bigram
- * @param frequency frequency for this bigram
- * @param fcp an instance of ForgettingCurveParams to use for decay policy
- * @return returns the final bigram frequency
- */
- private int setBigramAndGetFrequency(final String word0, final String word1,
- final int frequency, final ForgettingCurveParams fcp) {
- if (TextUtils.isEmpty(word0)) {
- Log.e(TAG, "Invalid bigram previous word: " + word0);
- return frequency;
- }
- // We don't want results to be different according to case of the looked up left hand side
- // word. We do want however to return the correct case for the right hand side.
- // So we want to squash the case of the left hand side, and preserve that of the right
- // hand side word.
- final String word0Lower = word0.toLowerCase();
- if (TextUtils.isEmpty(word0Lower) || TextUtils.isEmpty(word1)) {
- Log.e(TAG, "Invalid bigram pair: " + word0 + ", " + word0Lower + ", " + word1);
- return frequency;
- }
- final Node firstWord = searchWord(mRoots, word0Lower, 0, null);
- final Node secondWord = searchWord(mRoots, word1, 0, null);
- LinkedList<NextWord> bigrams = firstWord.mNGrams;
- if (bigrams == null || bigrams.size() == 0) {
- firstWord.mNGrams = CollectionUtils.newLinkedList();
- bigrams = firstWord.mNGrams;
- } else {
- for (NextWord nw : bigrams) {
- if (nw.getWordNode() == secondWord) {
- return nw.notifyTypedAgainAndGetFrequency();
- }
- }
- }
- if (fcp != null) {
- // history
- firstWord.mNGrams.add(new NextHistoryWord(secondWord, fcp));
- } else {
- firstWord.mNGrams.add(new NextStaticWord(secondWord, frequency));
- }
- return frequency;
- }
-
- /**
- * Searches for the word and add the word if it does not exist.
- * @return Returns the terminal node of the word we are searching for.
- */
- private Node searchWord(final NodeArray children, final String word, final int depth,
- final Node parentNode) {
- final int wordLength = word.length();
- final char c = word.charAt(depth);
- // Does children have the current character?
- final int childrenLength = children.mLength;
- Node childNode = null;
- for (int i = 0; i < childrenLength; i++) {
- final Node node = children.mData[i];
- if (node.mCode == c) {
- childNode = node;
- break;
- }
- }
- if (childNode == null) {
- childNode = new Node();
- childNode.mCode = c;
- childNode.mParent = parentNode;
- children.add(childNode);
- }
- if (wordLength == depth + 1) {
- // Terminate this word
- childNode.mTerminal = true;
- return childNode;
- }
- if (childNode.mChildren == null) {
- childNode.mChildren = new NodeArray();
- }
- return searchWord(childNode.mChildren, word, depth + 1, childNode);
- }
-
- private void runBigramReverseLookUp(final String previousWord,
- final ArrayList<SuggestedWordInfo> suggestions) {
- // Search for the lowercase version of the word only, because that's where bigrams
- // store their sons.
- final Node prevWord = searchNode(mRoots, previousWord.toLowerCase(), 0,
- previousWord.length());
- if (prevWord != null && prevWord.mNGrams != null) {
- reverseLookUp(prevWord.mNGrams, suggestions);
- }
- }
-
- // Local to reverseLookUp, but do not allocate each time.
- private final char[] mLookedUpString = new char[Constants.DICTIONARY_MAX_WORD_LENGTH];
-
- /**
- * reverseLookUp retrieves the full word given a list of terminal nodes and adds those words
- * to the suggestions list passed as an argument.
- * @param terminalNodes list of terminal nodes we want to add
- * @param suggestions the suggestion collection to add the word to
- */
- private void reverseLookUp(final LinkedList<NextWord> terminalNodes,
- final ArrayList<SuggestedWordInfo> suggestions) {
- Node node;
- int freq;
- for (NextWord nextWord : terminalNodes) {
- node = nextWord.getWordNode();
- freq = nextWord.getFrequency();
- int index = Constants.DICTIONARY_MAX_WORD_LENGTH;
- do {
- --index;
- mLookedUpString[index] = node.mCode;
- node = node.mParent;
- } while (node != null && index > 0);
-
- // If node is null, we have a word longer than MAX_WORD_LENGTH in the dictionary.
- // It's a little unclear how this can happen, but just in case it does it's safer
- // to ignore the word in this case.
- if (freq >= 0 && node == null) {
- suggestions.add(new SuggestedWordInfo(new String(mLookedUpString, index,
- Constants.DICTIONARY_MAX_WORD_LENGTH - index),
- freq, SuggestedWordInfo.KIND_CORRECTION, this /* sourceDict */,
- SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
- SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
- }
- }
- }
-
- /**
- * Recursively search for the terminal node of the word.
- *
- * One iteration takes the full word to search for and the current index of the recursion.
- *
- * @param children the node of the trie to search under.
- * @param word the word to search for. Only read [offset..length] so there may be trailing chars
- * @param offset the index in {@code word} this recursion should operate on.
- * @param length the length of the input word.
- * @return Returns the terminal node of the word if the word exists
- */
- private Node searchNode(final NodeArray children, final CharSequence word, final int offset,
- final int length) {
- final int count = children.mLength;
- final char currentChar = word.charAt(offset);
- for (int j = 0; j < count; j++) {
- final Node node = children.mData[j];
- if (node.mCode == currentChar) {
- if (offset == length - 1) {
- if (node.mTerminal) {
- return node;
- }
- } else {
- if (node.mChildren != null) {
- Node returnNode = searchNode(node.mChildren, word, offset + 1, length);
- if (returnNode != null) return returnNode;
- }
- }
- }
- }
- return null;
- }
-
- public void clearDictionary() {
- mRoots = new NodeArray();
- }
-
- private static char toLowerCase(final char c) {
- char baseChar = c;
- if (c < BASE_CHARS.length) {
- baseChar = BASE_CHARS[c];
- }
- if (baseChar >= 'A' && baseChar <= 'Z') {
- return (char)(baseChar | 32);
- } else if (baseChar > 127) {
- return Character.toLowerCase(baseChar);
- }
- return baseChar;
- }
-
- /**
- * Table mapping most combined Latin, Greek, and Cyrillic characters
- * to their base characters. If c is in range, BASE_CHARS[c] == c
- * if c is not a combined character, or the base character if it
- * is combined.
- *
- * cf. native/jni/src/utils/char_utils.cpp
- */
- private static final char BASE_CHARS[] = {
- /* U+0000 */ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
- /* U+0008 */ 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
- /* U+0010 */ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
- /* U+0018 */ 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
- /* U+0020 */ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
- /* U+0028 */ 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
- /* U+0030 */ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
- /* U+0038 */ 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
- /* U+0040 */ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
- /* U+0048 */ 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
- /* U+0050 */ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
- /* U+0058 */ 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
- /* U+0060 */ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
- /* U+0068 */ 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
- /* U+0070 */ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
- /* U+0078 */ 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
- /* U+0080 */ 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
- /* U+0088 */ 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
- /* U+0090 */ 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
- /* U+0098 */ 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
- /* U+00A0 */ 0x0020, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
- /* U+00A8 */ 0x0020, 0x00A9, 0x0061, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x0020,
- /* U+00B0 */ 0x00B0, 0x00B1, 0x0032, 0x0033, 0x0020, 0x03BC, 0x00B6, 0x00B7,
- /* U+00B8 */ 0x0020, 0x0031, 0x006F, 0x00BB, 0x0031, 0x0031, 0x0033, 0x00BF,
- /* U+00C0 */ 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x00C6, 0x0043,
- /* U+00C8 */ 0x0045, 0x0045, 0x0045, 0x0045, 0x0049, 0x0049, 0x0049, 0x0049,
- /* U+00D0 */ 0x00D0, 0x004E, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x00D7,
- /* U+00D8 */ 0x004F, 0x0055, 0x0055, 0x0055, 0x0055, 0x0059, 0x00DE, 0x0073,
- // U+00D8: Manually changed from 00D8 to 004F
- // TODO: Check if it's really acceptable to consider Ø a diacritical variant of O
- // U+00DF: Manually changed from 00DF to 0073
- /* U+00E0 */ 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x00E6, 0x0063,
- /* U+00E8 */ 0x0065, 0x0065, 0x0065, 0x0065, 0x0069, 0x0069, 0x0069, 0x0069,
- /* U+00F0 */ 0x00F0, 0x006E, 0x006F, 0x006F, 0x006F, 0x006F, 0x006F, 0x00F7,
- /* U+00F8 */ 0x006F, 0x0075, 0x0075, 0x0075, 0x0075, 0x0079, 0x00FE, 0x0079,
- // U+00F8: Manually changed from 00F8 to 006F
- // TODO: Check if it's really acceptable to consider ø a diacritical variant of o
- /* U+0100 */ 0x0041, 0x0061, 0x0041, 0x0061, 0x0041, 0x0061, 0x0043, 0x0063,
- /* U+0108 */ 0x0043, 0x0063, 0x0043, 0x0063, 0x0043, 0x0063, 0x0044, 0x0064,
- /* U+0110 */ 0x0110, 0x0111, 0x0045, 0x0065, 0x0045, 0x0065, 0x0045, 0x0065,
- /* U+0118 */ 0x0045, 0x0065, 0x0045, 0x0065, 0x0047, 0x0067, 0x0047, 0x0067,
- /* U+0120 */ 0x0047, 0x0067, 0x0047, 0x0067, 0x0048, 0x0068, 0x0126, 0x0127,
- /* U+0128 */ 0x0049, 0x0069, 0x0049, 0x0069, 0x0049, 0x0069, 0x0049, 0x0069,
- /* U+0130 */ 0x0049, 0x0131, 0x0049, 0x0069, 0x004A, 0x006A, 0x004B, 0x006B,
- /* U+0138 */ 0x0138, 0x004C, 0x006C, 0x004C, 0x006C, 0x004C, 0x006C, 0x004C,
- /* U+0140 */ 0x006C, 0x004C, 0x006C, 0x004E, 0x006E, 0x004E, 0x006E, 0x004E,
- // U+0141: Manually changed from 0141 to 004C
- // U+0142: Manually changed from 0142 to 006C
- /* U+0148 */ 0x006E, 0x02BC, 0x014A, 0x014B, 0x004F, 0x006F, 0x004F, 0x006F,
- /* U+0150 */ 0x004F, 0x006F, 0x0152, 0x0153, 0x0052, 0x0072, 0x0052, 0x0072,
- /* U+0158 */ 0x0052, 0x0072, 0x0053, 0x0073, 0x0053, 0x0073, 0x0053, 0x0073,
- /* U+0160 */ 0x0053, 0x0073, 0x0054, 0x0074, 0x0054, 0x0074, 0x0166, 0x0167,
- /* U+0168 */ 0x0055, 0x0075, 0x0055, 0x0075, 0x0055, 0x0075, 0x0055, 0x0075,
- /* U+0170 */ 0x0055, 0x0075, 0x0055, 0x0075, 0x0057, 0x0077, 0x0059, 0x0079,
- /* U+0178 */ 0x0059, 0x005A, 0x007A, 0x005A, 0x007A, 0x005A, 0x007A, 0x0073,
- /* U+0180 */ 0x0180, 0x0181, 0x0182, 0x0183, 0x0184, 0x0185, 0x0186, 0x0187,
- /* U+0188 */ 0x0188, 0x0189, 0x018A, 0x018B, 0x018C, 0x018D, 0x018E, 0x018F,
- /* U+0190 */ 0x0190, 0x0191, 0x0192, 0x0193, 0x0194, 0x0195, 0x0196, 0x0197,
- /* U+0198 */ 0x0198, 0x0199, 0x019A, 0x019B, 0x019C, 0x019D, 0x019E, 0x019F,
- /* U+01A0 */ 0x004F, 0x006F, 0x01A2, 0x01A3, 0x01A4, 0x01A5, 0x01A6, 0x01A7,
- /* U+01A8 */ 0x01A8, 0x01A9, 0x01AA, 0x01AB, 0x01AC, 0x01AD, 0x01AE, 0x0055,
- /* U+01B0 */ 0x0075, 0x01B1, 0x01B2, 0x01B3, 0x01B4, 0x01B5, 0x01B6, 0x01B7,
- /* U+01B8 */ 0x01B8, 0x01B9, 0x01BA, 0x01BB, 0x01BC, 0x01BD, 0x01BE, 0x01BF,
- /* U+01C0 */ 0x01C0, 0x01C1, 0x01C2, 0x01C3, 0x0044, 0x0044, 0x0064, 0x004C,
- /* U+01C8 */ 0x004C, 0x006C, 0x004E, 0x004E, 0x006E, 0x0041, 0x0061, 0x0049,
- /* U+01D0 */ 0x0069, 0x004F, 0x006F, 0x0055, 0x0075, 0x0055, 0x0075, 0x0055,
- // U+01D5: Manually changed from 00DC to 0055
- // U+01D6: Manually changed from 00FC to 0075
- // U+01D7: Manually changed from 00DC to 0055
- /* U+01D8 */ 0x0075, 0x0055, 0x0075, 0x0055, 0x0075, 0x01DD, 0x0041, 0x0061,
- // U+01D8: Manually changed from 00FC to 0075
- // U+01D9: Manually changed from 00DC to 0055
- // U+01DA: Manually changed from 00FC to 0075
- // U+01DB: Manually changed from 00DC to 0055
- // U+01DC: Manually changed from 00FC to 0075
- // U+01DE: Manually changed from 00C4 to 0041
- // U+01DF: Manually changed from 00E4 to 0061
- /* U+01E0 */ 0x0041, 0x0061, 0x00C6, 0x00E6, 0x01E4, 0x01E5, 0x0047, 0x0067,
- // U+01E0: Manually changed from 0226 to 0041
- // U+01E1: Manually changed from 0227 to 0061
- /* U+01E8 */ 0x004B, 0x006B, 0x004F, 0x006F, 0x004F, 0x006F, 0x01B7, 0x0292,
- // U+01EC: Manually changed from 01EA to 004F
- // U+01ED: Manually changed from 01EB to 006F
- /* U+01F0 */ 0x006A, 0x0044, 0x0044, 0x0064, 0x0047, 0x0067, 0x01F6, 0x01F7,
- /* U+01F8 */ 0x004E, 0x006E, 0x0041, 0x0061, 0x00C6, 0x00E6, 0x004F, 0x006F,
- // U+01FA: Manually changed from 00C5 to 0041
- // U+01FB: Manually changed from 00E5 to 0061
- // U+01FE: Manually changed from 00D8 to 004F
- // TODO: Check if it's really acceptable to consider Ø a diacritical variant of O
- // U+01FF: Manually changed from 00F8 to 006F
- // TODO: Check if it's really acceptable to consider ø a diacritical variant of o
- /* U+0200 */ 0x0041, 0x0061, 0x0041, 0x0061, 0x0045, 0x0065, 0x0045, 0x0065,
- /* U+0208 */ 0x0049, 0x0069, 0x0049, 0x0069, 0x004F, 0x006F, 0x004F, 0x006F,
- /* U+0210 */ 0x0052, 0x0072, 0x0052, 0x0072, 0x0055, 0x0075, 0x0055, 0x0075,
- /* U+0218 */ 0x0053, 0x0073, 0x0054, 0x0074, 0x021C, 0x021D, 0x0048, 0x0068,
- /* U+0220 */ 0x0220, 0x0221, 0x0222, 0x0223, 0x0224, 0x0225, 0x0041, 0x0061,
- /* U+0228 */ 0x0045, 0x0065, 0x004F, 0x006F, 0x004F, 0x006F, 0x004F, 0x006F,
- // U+022A: Manually changed from 00D6 to 004F
- // U+022B: Manually changed from 00F6 to 006F
- // U+022C: Manually changed from 00D5 to 004F
- // U+022D: Manually changed from 00F5 to 006F
- /* U+0230 */ 0x004F, 0x006F, 0x0059, 0x0079, 0x0234, 0x0235, 0x0236, 0x0237,
- // U+0230: Manually changed from 022E to 004F
- // U+0231: Manually changed from 022F to 006F
- /* U+0238 */ 0x0238, 0x0239, 0x023A, 0x023B, 0x023C, 0x023D, 0x023E, 0x023F,
- /* U+0240 */ 0x0240, 0x0241, 0x0242, 0x0243, 0x0244, 0x0245, 0x0246, 0x0247,
- /* U+0248 */ 0x0248, 0x0249, 0x024A, 0x024B, 0x024C, 0x024D, 0x024E, 0x024F,
- /* U+0250 */ 0x0250, 0x0251, 0x0252, 0x0253, 0x0254, 0x0255, 0x0256, 0x0257,
- /* U+0258 */ 0x0258, 0x0259, 0x025A, 0x025B, 0x025C, 0x025D, 0x025E, 0x025F,
- /* U+0260 */ 0x0260, 0x0261, 0x0262, 0x0263, 0x0264, 0x0265, 0x0266, 0x0267,
- /* U+0268 */ 0x0268, 0x0269, 0x026A, 0x026B, 0x026C, 0x026D, 0x026E, 0x026F,
- /* U+0270 */ 0x0270, 0x0271, 0x0272, 0x0273, 0x0274, 0x0275, 0x0276, 0x0277,
- /* U+0278 */ 0x0278, 0x0279, 0x027A, 0x027B, 0x027C, 0x027D, 0x027E, 0x027F,
- /* U+0280 */ 0x0280, 0x0281, 0x0282, 0x0283, 0x0284, 0x0285, 0x0286, 0x0287,
- /* U+0288 */ 0x0288, 0x0289, 0x028A, 0x028B, 0x028C, 0x028D, 0x028E, 0x028F,
- /* U+0290 */ 0x0290, 0x0291, 0x0292, 0x0293, 0x0294, 0x0295, 0x0296, 0x0297,
- /* U+0298 */ 0x0298, 0x0299, 0x029A, 0x029B, 0x029C, 0x029D, 0x029E, 0x029F,
- /* U+02A0 */ 0x02A0, 0x02A1, 0x02A2, 0x02A3, 0x02A4, 0x02A5, 0x02A6, 0x02A7,
- /* U+02A8 */ 0x02A8, 0x02A9, 0x02AA, 0x02AB, 0x02AC, 0x02AD, 0x02AE, 0x02AF,
- /* U+02B0 */ 0x0068, 0x0266, 0x006A, 0x0072, 0x0279, 0x027B, 0x0281, 0x0077,
- /* U+02B8 */ 0x0079, 0x02B9, 0x02BA, 0x02BB, 0x02BC, 0x02BD, 0x02BE, 0x02BF,
- /* U+02C0 */ 0x02C0, 0x02C1, 0x02C2, 0x02C3, 0x02C4, 0x02C5, 0x02C6, 0x02C7,
- /* U+02C8 */ 0x02C8, 0x02C9, 0x02CA, 0x02CB, 0x02CC, 0x02CD, 0x02CE, 0x02CF,
- /* U+02D0 */ 0x02D0, 0x02D1, 0x02D2, 0x02D3, 0x02D4, 0x02D5, 0x02D6, 0x02D7,
- /* U+02D8 */ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x02DE, 0x02DF,
- /* U+02E0 */ 0x0263, 0x006C, 0x0073, 0x0078, 0x0295, 0x02E5, 0x02E6, 0x02E7,
- /* U+02E8 */ 0x02E8, 0x02E9, 0x02EA, 0x02EB, 0x02EC, 0x02ED, 0x02EE, 0x02EF,
- /* U+02F0 */ 0x02F0, 0x02F1, 0x02F2, 0x02F3, 0x02F4, 0x02F5, 0x02F6, 0x02F7,
- /* U+02F8 */ 0x02F8, 0x02F9, 0x02FA, 0x02FB, 0x02FC, 0x02FD, 0x02FE, 0x02FF,
- /* U+0300 */ 0x0300, 0x0301, 0x0302, 0x0303, 0x0304, 0x0305, 0x0306, 0x0307,
- /* U+0308 */ 0x0308, 0x0309, 0x030A, 0x030B, 0x030C, 0x030D, 0x030E, 0x030F,
- /* U+0310 */ 0x0310, 0x0311, 0x0312, 0x0313, 0x0314, 0x0315, 0x0316, 0x0317,
- /* U+0318 */ 0x0318, 0x0319, 0x031A, 0x031B, 0x031C, 0x031D, 0x031E, 0x031F,
- /* U+0320 */ 0x0320, 0x0321, 0x0322, 0x0323, 0x0324, 0x0325, 0x0326, 0x0327,
- /* U+0328 */ 0x0328, 0x0329, 0x032A, 0x032B, 0x032C, 0x032D, 0x032E, 0x032F,
- /* U+0330 */ 0x0330, 0x0331, 0x0332, 0x0333, 0x0334, 0x0335, 0x0336, 0x0337,
- /* U+0338 */ 0x0338, 0x0339, 0x033A, 0x033B, 0x033C, 0x033D, 0x033E, 0x033F,
- /* U+0340 */ 0x0300, 0x0301, 0x0342, 0x0313, 0x0308, 0x0345, 0x0346, 0x0347,
- /* U+0348 */ 0x0348, 0x0349, 0x034A, 0x034B, 0x034C, 0x034D, 0x034E, 0x034F,
- /* U+0350 */ 0x0350, 0x0351, 0x0352, 0x0353, 0x0354, 0x0355, 0x0356, 0x0357,
- /* U+0358 */ 0x0358, 0x0359, 0x035A, 0x035B, 0x035C, 0x035D, 0x035E, 0x035F,
- /* U+0360 */ 0x0360, 0x0361, 0x0362, 0x0363, 0x0364, 0x0365, 0x0366, 0x0367,
- /* U+0368 */ 0x0368, 0x0369, 0x036A, 0x036B, 0x036C, 0x036D, 0x036E, 0x036F,
- /* U+0370 */ 0x0370, 0x0371, 0x0372, 0x0373, 0x02B9, 0x0375, 0x0376, 0x0377,
- /* U+0378 */ 0x0378, 0x0379, 0x0020, 0x037B, 0x037C, 0x037D, 0x003B, 0x037F,
- /* U+0380 */ 0x0380, 0x0381, 0x0382, 0x0383, 0x0020, 0x00A8, 0x0391, 0x00B7,
- /* U+0388 */ 0x0395, 0x0397, 0x0399, 0x038B, 0x039F, 0x038D, 0x03A5, 0x03A9,
- /* U+0390 */ 0x03CA, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397,
- /* U+0398 */ 0x0398, 0x0399, 0x039A, 0x039B, 0x039C, 0x039D, 0x039E, 0x039F,
- /* U+03A0 */ 0x03A0, 0x03A1, 0x03A2, 0x03A3, 0x03A4, 0x03A5, 0x03A6, 0x03A7,
- /* U+03A8 */ 0x03A8, 0x03A9, 0x0399, 0x03A5, 0x03B1, 0x03B5, 0x03B7, 0x03B9,
- /* U+03B0 */ 0x03CB, 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7,
- /* U+03B8 */ 0x03B8, 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF,
- /* U+03C0 */ 0x03C0, 0x03C1, 0x03C2, 0x03C3, 0x03C4, 0x03C5, 0x03C6, 0x03C7,
- /* U+03C8 */ 0x03C8, 0x03C9, 0x03B9, 0x03C5, 0x03BF, 0x03C5, 0x03C9, 0x03CF,
- /* U+03D0 */ 0x03B2, 0x03B8, 0x03A5, 0x03D2, 0x03D2, 0x03C6, 0x03C0, 0x03D7,
- /* U+03D8 */ 0x03D8, 0x03D9, 0x03DA, 0x03DB, 0x03DC, 0x03DD, 0x03DE, 0x03DF,
- /* U+03E0 */ 0x03E0, 0x03E1, 0x03E2, 0x03E3, 0x03E4, 0x03E5, 0x03E6, 0x03E7,
- /* U+03E8 */ 0x03E8, 0x03E9, 0x03EA, 0x03EB, 0x03EC, 0x03ED, 0x03EE, 0x03EF,
- /* U+03F0 */ 0x03BA, 0x03C1, 0x03C2, 0x03F3, 0x0398, 0x03B5, 0x03F6, 0x03F7,
- /* U+03F8 */ 0x03F8, 0x03A3, 0x03FA, 0x03FB, 0x03FC, 0x03FD, 0x03FE, 0x03FF,
- /* U+0400 */ 0x0415, 0x0415, 0x0402, 0x0413, 0x0404, 0x0405, 0x0406, 0x0406,
- /* U+0408 */ 0x0408, 0x0409, 0x040A, 0x040B, 0x041A, 0x0418, 0x0423, 0x040F,
- /* U+0410 */ 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417,
- /* U+0418 */ 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F,
- // U+0419: Manually changed from 0418 to 0419
- /* U+0420 */ 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427,
- /* U+0428 */ 0x0428, 0x0429, 0x042C, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F,
- // U+042A: Manually changed from 042A to 042C
- /* U+0430 */ 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437,
- /* U+0438 */ 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F,
- // U+0439: Manually changed from 0438 to 0439
- /* U+0440 */ 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447,
- /* U+0448 */ 0x0448, 0x0449, 0x044C, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F,
- // U+044A: Manually changed from 044A to 044C
- /* U+0450 */ 0x0435, 0x0435, 0x0452, 0x0433, 0x0454, 0x0455, 0x0456, 0x0456,
- /* U+0458 */ 0x0458, 0x0459, 0x045A, 0x045B, 0x043A, 0x0438, 0x0443, 0x045F,
- /* U+0460 */ 0x0460, 0x0461, 0x0462, 0x0463, 0x0464, 0x0465, 0x0466, 0x0467,
- /* U+0468 */ 0x0468, 0x0469, 0x046A, 0x046B, 0x046C, 0x046D, 0x046E, 0x046F,
- /* U+0470 */ 0x0470, 0x0471, 0x0472, 0x0473, 0x0474, 0x0475, 0x0474, 0x0475,
- /* U+0478 */ 0x0478, 0x0479, 0x047A, 0x047B, 0x047C, 0x047D, 0x047E, 0x047F,
- /* U+0480 */ 0x0480, 0x0481, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487,
- /* U+0488 */ 0x0488, 0x0489, 0x048A, 0x048B, 0x048C, 0x048D, 0x048E, 0x048F,
- /* U+0490 */ 0x0490, 0x0491, 0x0492, 0x0493, 0x0494, 0x0495, 0x0496, 0x0497,
- /* U+0498 */ 0x0498, 0x0499, 0x049A, 0x049B, 0x049C, 0x049D, 0x049E, 0x049F,
- /* U+04A0 */ 0x04A0, 0x04A1, 0x04A2, 0x04A3, 0x04A4, 0x04A5, 0x04A6, 0x04A7,
- /* U+04A8 */ 0x04A8, 0x04A9, 0x04AA, 0x04AB, 0x04AC, 0x04AD, 0x04AE, 0x04AF,
- /* U+04B0 */ 0x04B0, 0x04B1, 0x04B2, 0x04B3, 0x04B4, 0x04B5, 0x04B6, 0x04B7,
- /* U+04B8 */ 0x04B8, 0x04B9, 0x04BA, 0x04BB, 0x04BC, 0x04BD, 0x04BE, 0x04BF,
- /* U+04C0 */ 0x04C0, 0x0416, 0x0436, 0x04C3, 0x04C4, 0x04C5, 0x04C6, 0x04C7,
- /* U+04C8 */ 0x04C8, 0x04C9, 0x04CA, 0x04CB, 0x04CC, 0x04CD, 0x04CE, 0x04CF,
- /* U+04D0 */ 0x0410, 0x0430, 0x0410, 0x0430, 0x04D4, 0x04D5, 0x0415, 0x0435,
- /* U+04D8 */ 0x04D8, 0x04D9, 0x04D8, 0x04D9, 0x0416, 0x0436, 0x0417, 0x0437,
- /* U+04E0 */ 0x04E0, 0x04E1, 0x0418, 0x0438, 0x0418, 0x0438, 0x041E, 0x043E,
- /* U+04E8 */ 0x04E8, 0x04E9, 0x04E8, 0x04E9, 0x042D, 0x044D, 0x0423, 0x0443,
- /* U+04F0 */ 0x0423, 0x0443, 0x0423, 0x0443, 0x0427, 0x0447, 0x04F6, 0x04F7,
- /* U+04F8 */ 0x042B, 0x044B, 0x04FA, 0x04FB, 0x04FC, 0x04FD, 0x04FE, 0x04FF,
- };
-}
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
index 8caf6f17f..fcf043031 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -52,8 +52,7 @@ public final class InputAttributes {
} else if (inputClass == 0) {
// TODO: is this check still necessary?
Log.w(TAG, String.format("Unexpected input class: inputType=0x%08x"
- + " imeOptions=0x%08x",
- inputType, editorInfo.imeOptions));
+ + " imeOptions=0x%08x", inputType, editorInfo.imeOptions));
}
mIsSettingsSuggestionStripOn = false;
mInputTypeNoAutoCorrect = false;
@@ -204,8 +203,7 @@ public final class InputAttributes {
public static boolean inPrivateImeOptions(String packageName, String key,
EditorInfo editorInfo) {
if (editorInfo == null) return false;
- final String findingKey = (packageName != null) ? packageName + "." + key
- : key;
+ final String findingKey = (packageName != null) ? packageName + "." + key : key;
return StringUtils.containsInCommaSplittableText(findingKey, editorInfo.privateImeOptions);
}
}
diff --git a/java/src/com/android/inputmethod/latin/InputView.java b/java/src/com/android/inputmethod/latin/InputView.java
index 81ccf83d8..76b0912f6 100644
--- a/java/src/com/android/inputmethod/latin/InputView.java
+++ b/java/src/com/android/inputmethod/latin/InputView.java
@@ -23,87 +23,203 @@ import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
-public final class InputView extends LinearLayout {
- private View mSuggestionStripView;
- private View mKeyboardView;
- private int mKeyboardTopPadding;
+import com.android.inputmethod.keyboard.MainKeyboardView;
+import com.android.inputmethod.latin.suggestions.MoreSuggestionsView;
+import com.android.inputmethod.latin.suggestions.SuggestionStripView;
- private boolean mIsForwardingEvent;
+public final class InputView extends LinearLayout {
private final Rect mInputViewRect = new Rect();
- private final Rect mEventForwardingRect = new Rect();
- private final Rect mEventReceivingRect = new Rect();
+ private KeyboardTopPaddingForwarder mKeyboardTopPaddingForwarder;
+ private MoreSuggestionsViewCanceler mMoreSuggestionsViewCanceler;
public InputView(final Context context, final AttributeSet attrs) {
super(context, attrs, 0);
}
- public void setKeyboardGeometry(final int keyboardTopPadding) {
- mKeyboardTopPadding = keyboardTopPadding;
- }
-
@Override
protected void onFinishInflate() {
- mSuggestionStripView = findViewById(R.id.suggestion_strip_view);
- mKeyboardView = findViewById(R.id.keyboard_view);
+ final SuggestionStripView suggestionStripView =
+ (SuggestionStripView)findViewById(R.id.suggestion_strip_view);
+ final MainKeyboardView mainKeyboardView =
+ (MainKeyboardView)findViewById(R.id.keyboard_view);
+ mKeyboardTopPaddingForwarder = new KeyboardTopPaddingForwarder(
+ mainKeyboardView, suggestionStripView);
+ mMoreSuggestionsViewCanceler = new MoreSuggestionsViewCanceler(
+ mainKeyboardView, suggestionStripView);
+ }
+
+ public void setKeyboardTopPadding(final int keyboardTopPadding) {
+ mKeyboardTopPaddingForwarder.setKeyboardTopPadding(keyboardTopPadding);
}
@Override
public boolean dispatchTouchEvent(final MotionEvent me) {
- if (mSuggestionStripView.getVisibility() != VISIBLE
- || mKeyboardView.getVisibility() != VISIBLE) {
- return super.dispatchTouchEvent(me);
- }
-
- // The touch events that hit the top padding of keyboard should be forwarded to
- // {@link SuggestionStripView}.
final Rect rect = mInputViewRect;
- this.getGlobalVisibleRect(rect);
+ getGlobalVisibleRect(rect);
final int x = (int)me.getX() + rect.left;
final int y = (int)me.getY() + rect.top;
- final Rect forwardingRect = mEventForwardingRect;
- mKeyboardView.getGlobalVisibleRect(forwardingRect);
- if (!mIsForwardingEvent && !forwardingRect.contains(x, y)) {
- return super.dispatchTouchEvent(me);
+ // The touch events that hit the top padding of keyboard should be
+ // forwarded to {@link SuggestionStripView}.
+ if (mKeyboardTopPaddingForwarder.dispatchTouchEvent(x, y, me)) {
+ return true;
+ }
+ // To cancel {@link MoreSuggestionsView}, we should intercept a touch event to
+ // {@link MainKeyboardView} and dismiss the {@link MoreSuggestionsView}.
+ if (mMoreSuggestionsViewCanceler.dispatchTouchEvent(x, y, me)) {
+ return true;
+ }
+ return super.dispatchTouchEvent(me);
+ }
+
+ /**
+ * This class forwards series of {@link MotionEvent}s from <code>Forwarder</code> view to
+ * <code>Receiver</code> view.
+ *
+ * @param <Sender> a {@link View} that may send a {@link MotionEvent} to <Receiver>.
+ * @param <Receiver> a {@link View} that receives forwarded {@link MotionEvent} from
+ * <Forwarder>.
+ */
+ private static abstract class MotionEventForwarder<Sender extends View, Receiver extends View> {
+ protected final Sender mSenderView;
+ protected final Receiver mReceiverView;
+
+ private boolean mIsForwardingEvent;
+ protected final Rect mEventSendingRect = new Rect();
+ protected final Rect mEventReceivingRect = new Rect();
+
+ public MotionEventForwarder(final Sender senderView, final Receiver receiverView) {
+ mSenderView = senderView;
+ mReceiverView = receiverView;
}
- final int forwardingLimitY = forwardingRect.top + mKeyboardTopPadding;
- boolean sendToTarget = false;
+ // Return true if a touch event of global coordinate x, y needs to be forwarded.
+ protected abstract boolean needsToForward(final int x, final int y);
- switch (me.getAction()) {
- case MotionEvent.ACTION_DOWN:
- if (y < forwardingLimitY) {
- // This down event and further move and up events should be forwarded to the target.
- mIsForwardingEvent = true;
- sendToTarget = true;
+ // Translate global x-coordinate to <code>Receiver</code> local coordinate.
+ protected int translateX(final int x) {
+ return x - mEventReceivingRect.left;
+ }
+
+ // Translate global y-coordinate to <code>Receiver</code> local coordinate.
+ protected int translateY(final int y) {
+ return y - mEventReceivingRect.top;
+ }
+
+ // Callback when a {@link MotionEvent} is forwarded.
+ protected void onForwardingEvent(final MotionEvent me) {}
+
+ // Dispatches a {@link MotioneEvent} to <code>Receiver</code> if needed and returns true.
+ // Otherwise returns false.
+ public boolean dispatchTouchEvent(final int x, final int y, final MotionEvent me) {
+ // Forwards a {link MotionEvent} only if both <code>Sender</code> and
+ // <code>Receiver</code> are visible.
+ if (mSenderView.getVisibility() != View.VISIBLE ||
+ mReceiverView.getVisibility() != View.VISIBLE) {
+ return false;
+ }
+ final Rect sendingRect = mEventSendingRect;
+ mSenderView.getGlobalVisibleRect(sendingRect);
+ if (!mIsForwardingEvent && !sendingRect.contains(x, y)) {
+ return false;
}
- break;
- case MotionEvent.ACTION_MOVE:
- sendToTarget = mIsForwardingEvent;
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- sendToTarget = mIsForwardingEvent;
- mIsForwardingEvent = false;
- break;
+
+ boolean shouldForwardToReceiver = false;
+
+ switch (me.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ // If the down event happens in the forwarding area, successive {@link MotionEvent}s
+ // should be forwarded.
+ if (needsToForward(x, y)) {
+ mIsForwardingEvent = true;
+ shouldForwardToReceiver = true;
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ shouldForwardToReceiver = mIsForwardingEvent;
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ shouldForwardToReceiver = mIsForwardingEvent;
+ mIsForwardingEvent = false;
+ break;
+ }
+
+ if (!shouldForwardToReceiver) {
+ return false;
+ }
+
+ final Rect receivingRect = mEventReceivingRect;
+ mReceiverView.getGlobalVisibleRect(receivingRect);
+ // Translate global coordinates to <code>Receiver</code> local coordinates.
+ me.setLocation(translateX(x), translateY(y));
+ mReceiverView.dispatchTouchEvent(me);
+ onForwardingEvent(me);
+ return true;
}
+ }
+
+ /**
+ * This class forwards {@link MotionEvent}s happened in the top padding of
+ * {@link MainKeyboardView} to {@link SuggestionStripView}.
+ */
+ private static class KeyboardTopPaddingForwarder
+ extends MotionEventForwarder<MainKeyboardView, SuggestionStripView> {
+ private int mKeyboardTopPadding;
- if (!sendToTarget) {
- return super.dispatchTouchEvent(me);
+ public KeyboardTopPaddingForwarder(final MainKeyboardView mainKeyboardView,
+ final SuggestionStripView suggestionStripView) {
+ super(mainKeyboardView, suggestionStripView);
}
- final Rect receivingRect = mEventReceivingRect;
- mSuggestionStripView.getGlobalVisibleRect(receivingRect);
- final int translatedX = x - receivingRect.left;
- final int translatedY;
- if (y < forwardingLimitY) {
- // The forwarded event should have coordinates that are inside of the target.
- translatedY = Math.min(y - receivingRect.top, receivingRect.height() - 1);
- } else {
- translatedY = y - receivingRect.top;
+ public void setKeyboardTopPadding(final int keyboardTopPadding) {
+ mKeyboardTopPadding = keyboardTopPadding;
+ }
+
+ private boolean isInKeyboardTopPadding(final int y) {
+ return y < mEventSendingRect.top + mKeyboardTopPadding;
+ }
+
+ @Override
+ protected boolean needsToForward(final int x, final int y) {
+ return isInKeyboardTopPadding(y);
+ }
+
+ @Override
+ protected int translateY(final int y) {
+ final int translatedY = super.translateY(y);
+ if (isInKeyboardTopPadding(y)) {
+ // The forwarded event should have coordinates that are inside of
+ // the target.
+ return Math.min(translatedY, mEventReceivingRect.height() - 1);
+ }
+ return translatedY;
+ }
+ }
+
+ /**
+ * This class forwards {@link MotionEvent}s happened in the {@link MainKeyboardView} to
+ * {@link SuggestionStripView} when the {@link MoreSuggestionsView} is showing.
+ * {@link SuggestionStripView} dismisses {@link MoreSuggestionsView} when it receives those
+ * events.
+ */
+ private static class MoreSuggestionsViewCanceler
+ extends MotionEventForwarder<MainKeyboardView, SuggestionStripView> {
+ public MoreSuggestionsViewCanceler(final MainKeyboardView mainKeyboardView,
+ final SuggestionStripView suggestionStripView) {
+ super(mainKeyboardView, suggestionStripView);
+ }
+
+ @Override
+ protected boolean needsToForward(final int x, final int y) {
+ return mReceiverView.isShowingMoreSuggestionPanel() && mEventSendingRect.contains(x, y);
+ }
+
+ @Override
+ protected void onForwardingEvent(final MotionEvent me) {
+ if (me.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mReceiverView.dismissMoreSuggestionsPanel();
+ }
}
- me.setLocation(translatedX, translatedY);
- mSuggestionStripView.dispatchTouchEvent(me);
- return true;
}
}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 77d07019f..67b570277 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -68,7 +68,6 @@ import com.android.inputmethod.compat.InputMethodServiceCompatUtils;
import com.android.inputmethod.compat.SuggestionSpanUtils;
import com.android.inputmethod.dictionarypack.DictionaryPackConstants;
import com.android.inputmethod.event.EventInterpreter;
-import com.android.inputmethod.keyboard.KeyDetector;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardActionListener;
import com.android.inputmethod.keyboard.KeyboardId;
@@ -78,10 +77,7 @@ import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.personalization.DictionaryDecayBroadcastReciever;
-import com.android.inputmethod.latin.personalization.PersonalizationDictionary;
import com.android.inputmethod.latin.personalization.PersonalizationDictionarySessionRegister;
-import com.android.inputmethod.latin.personalization.PersonalizationHelper;
-import com.android.inputmethod.latin.personalization.PersonalizationPredictionDictionary;
import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.settings.SettingsActivity;
@@ -97,12 +93,11 @@ import com.android.inputmethod.latin.utils.InputTypeUtils;
import com.android.inputmethod.latin.utils.IntentUtils;
import com.android.inputmethod.latin.utils.JniUtils;
import com.android.inputmethod.latin.utils.LatinImeLoggerUtils;
+import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
import com.android.inputmethod.latin.utils.RecapitalizeStatus;
-import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper;
import com.android.inputmethod.latin.utils.StringUtils;
import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask;
import com.android.inputmethod.latin.utils.TextRange;
-import com.android.inputmethod.latin.utils.UserHistoryForgettingCurveUtils;
import com.android.inputmethod.research.ResearchLogger;
import java.io.FileDescriptor;
@@ -110,6 +105,7 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Locale;
import java.util.TreeSet;
+import java.util.concurrent.TimeUnit;
/**
* Input method implementation for Qwerty'ish keyboard.
@@ -181,9 +177,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private boolean mIsMainDictionaryAvailable;
private UserBinaryDictionary mUserDictionary;
- private UserHistoryDictionary mUserHistoryDictionary;
- private PersonalizationPredictionDictionary mPersonalizationPredictionDictionary;
- private PersonalizationDictionary mPersonalizationDictionary;
private boolean mIsUserDictionaryAvailable;
private LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
@@ -196,9 +189,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private int mLastSelectionStart = NOT_A_CURSOR_POSITION;
private int mLastSelectionEnd = NOT_A_CURSOR_POSITION;
- // Whether we are expecting an onUpdateSelection event to fire. If it does when we don't
- // "expect" it, it means the user actually moved the cursor.
- private boolean mExpectingUpdateSelection;
private int mDeleteCount;
private long mLastKeyTime;
private final TreeSet<Long> mCurrentlyPressedHardwareKeys = CollectionUtils.newTreeSet();
@@ -227,7 +217,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public final UIHandler mHandler = new UIHandler(this);
private InputUpdater mInputUpdater;
- public static final class UIHandler extends StaticInnerHandlerWrapper<LatinIME> {
+ public static final class UIHandler extends LeakGuardHandlerWrapper<LatinIME> {
private static final int MSG_UPDATE_SHIFT_STATE = 0;
private static final int MSG_PENDING_IMS_CALLBACK = 1;
private static final int MSG_UPDATE_SUGGESTION_STRIP = 2;
@@ -236,6 +226,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private static final int MSG_REOPEN_DICTIONARIES = 5;
private static final int MSG_ON_END_BATCH_INPUT = 6;
private static final int MSG_RESET_CACHES = 7;
+ // Update this when adding new messages
+ private static final int MSG_LAST = MSG_RESET_CACHES;
private static final int ARG1_NOT_GESTURE_INPUT = 0;
private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
@@ -248,12 +240,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private long mDoubleSpacePeriodTimeout;
private long mDoubleSpacePeriodTimerStart;
- public UIHandler(final LatinIME outerInstance) {
- super(outerInstance);
+ public UIHandler(final LatinIME ownerInstance) {
+ super(ownerInstance);
}
public void onCreate() {
- final Resources res = getOuterInstance().getResources();
+ final Resources res = getOwnerInstance().getResources();
mDelayUpdateSuggestions =
res.getInteger(R.integer.config_delay_update_suggestions);
mDelayUpdateShiftState =
@@ -264,7 +256,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public void handleMessage(final Message msg) {
- final LatinIME latinIme = getOuterInstance();
+ final LatinIME latinIme = getOwnerInstance();
final KeyboardSwitcher switcher = latinIme.mKeyboardSwitcher;
switch (msg.what) {
case MSG_UPDATE_SUGGESTION_STRIP:
@@ -347,6 +339,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
removeMessages(MSG_UPDATE_SHIFT_STATE);
}
+ @UsedForTesting
+ public void removeAllMessages() {
+ for (int i = 0; i <= MSG_LAST; ++i) {
+ removeMessages(i);
+ }
+ }
+
public void showGesturePreviewAndSuggestionStrip(final SuggestedWords suggestedWords,
final boolean dismissGestureFloatingPreviewText) {
removeMessages(MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP);
@@ -401,7 +400,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
removeMessages(MSG_PENDING_IMS_CALLBACK);
resetPendingImsCallback();
mIsOrientationChanging = true;
- final LatinIME latinIme = getOuterInstance();
+ final LatinIME latinIme = getOwnerInstance();
if (latinIme.isInputViewShown()) {
latinIme.mKeyboardSwitcher.saveKeyboardState();
}
@@ -434,7 +433,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mIsOrientationChanging = false;
mPendingSuccessiveImsCallback = true;
}
- final LatinIME latinIme = getOuterInstance();
+ final LatinIME latinIme = getOwnerInstance();
executePendingImsCallback(latinIme, editorInfo, restarting);
latinIme.onStartInputInternal(editorInfo, restarting);
}
@@ -453,7 +452,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
sendMessageDelayed(obtainMessage(MSG_PENDING_IMS_CALLBACK),
PENDING_IMS_CALLBACK_DURATION);
}
- final LatinIME latinIme = getOuterInstance();
+ final LatinIME latinIme = getOwnerInstance();
executePendingImsCallback(latinIme, editorInfo, restarting);
latinIme.onStartInputViewInternal(editorInfo, restarting);
mAppliedEditorInfo = editorInfo;
@@ -465,7 +464,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Typically this is the first onFinishInputView after orientation changed.
mHasPendingFinishInputView = true;
} else {
- final LatinIME latinIme = getOuterInstance();
+ final LatinIME latinIme = getOwnerInstance();
latinIme.onFinishInputViewInternal(finishingInput);
mAppliedEditorInfo = null;
}
@@ -476,7 +475,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Typically this is the first onFinishInput after orientation changed.
mHasPendingFinishInput = true;
} else {
- final LatinIME latinIme = getOuterInstance();
+ final LatinIME latinIme = getOwnerInstance();
executePendingImsCallback(latinIme, null, false);
latinIme.onFinishInputInternal();
}
@@ -589,9 +588,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// the layout; at this time, we need to skip resetting the contacts dictionary. It will
// be done later inside {@see #initSuggest()} when the reopenDictionaries message is
// processed.
- if (!mHandler.hasPendingReopenDictionaries()) {
- // May need to reset the contacts dictionary depending on the user settings.
- resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary());
+ if (!mHandler.hasPendingReopenDictionaries() && mSuggest != null) {
+ // May need to reset dictionaries depending on the user settings.
+ mSuggest.setAdditionalDictionaries(mSuggest /* oldSuggest */, mSettings.getCurrent());
}
}
@@ -609,7 +608,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final Locale switcherSubtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
final String switcherLocaleStr = switcherSubtypeLocale.toString();
final Locale subtypeLocale;
- final String localeStr;
if (TextUtils.isEmpty(switcherLocaleStr)) {
// This happens in very rare corner cases - for example, immediately after a switch
// to LatinIME has been requested, about a frame later another switch happens. In this
@@ -619,10 +617,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// of knowing anyway.
Log.e(TAG, "System is reporting no current subtype.");
subtypeLocale = getResources().getConfiguration().locale;
- localeStr = subtypeLocale.toString();
} else {
subtypeLocale = switcherSubtypeLocale;
- localeStr = switcherLocaleStr;
}
final Suggest newSuggest = new Suggest(this /* Context */, subtypeLocale,
@@ -637,71 +633,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
ResearchLogger.getInstance().initSuggest(newSuggest);
}
- mUserDictionary = new UserBinaryDictionary(this, localeStr);
+ mUserDictionary = new UserBinaryDictionary(this, subtypeLocale);
mIsUserDictionaryAvailable = mUserDictionary.isEnabled();
newSuggest.setUserDictionary(mUserDictionary);
-
- final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
-
- mUserHistoryDictionary = PersonalizationHelper.getUserHistoryDictionary(
- this, localeStr, prefs);
- newSuggest.setUserHistoryDictionary(mUserHistoryDictionary);
- mPersonalizationDictionary = PersonalizationHelper
- .getPersonalizationDictionary(this, localeStr, prefs);
- newSuggest.setPersonalizationDictionary(mPersonalizationDictionary);
- mPersonalizationPredictionDictionary = PersonalizationHelper
- .getPersonalizationPredictionDictionary(this, localeStr, prefs);
- newSuggest.setPersonalizationPredictionDictionary(mPersonalizationPredictionDictionary);
-
+ newSuggest.setAdditionalDictionaries(mSuggest /* oldSuggest */, mSettings.getCurrent());
final Suggest oldSuggest = mSuggest;
- resetContactsDictionary(null != oldSuggest ? oldSuggest.getContactsDictionary() : null);
mSuggest = newSuggest;
if (oldSuggest != null) oldSuggest.close();
}
- /**
- * Resets the contacts dictionary in mSuggest according to the user settings.
- *
- * This method takes an optional contacts dictionary to use when the locale hasn't changed
- * since the contacts dictionary can be opened or closed as necessary depending on the settings.
- *
- * @param oldContactsDictionary an optional dictionary to use, or null
- */
- private void resetContactsDictionary(final ContactsBinaryDictionary oldContactsDictionary) {
- final Suggest suggest = mSuggest;
- final boolean shouldSetDictionary =
- (null != suggest && mSettings.getCurrent().mUseContactsDict);
-
- final ContactsBinaryDictionary dictionaryToUse;
- if (!shouldSetDictionary) {
- // Make sure the dictionary is closed. If it is already closed, this is a no-op,
- // so it's safe to call it anyways.
- if (null != oldContactsDictionary) oldContactsDictionary.close();
- dictionaryToUse = null;
- } else {
- final Locale locale = mSubtypeSwitcher.getCurrentSubtypeLocale();
- if (null != oldContactsDictionary) {
- if (!oldContactsDictionary.mLocale.equals(locale)) {
- // If the locale has changed then recreate the contacts dictionary. This
- // allows locale dependent rules for handling bigram name predictions.
- oldContactsDictionary.close();
- dictionaryToUse = new ContactsBinaryDictionary(this, locale);
- } else {
- // Make sure the old contacts dictionary is opened. If it is already open,
- // this is a no-op, so it's safe to call it anyways.
- oldContactsDictionary.reopen(this);
- dictionaryToUse = oldContactsDictionary;
- }
- } else {
- dictionaryToUse = new ContactsBinaryDictionary(this, locale);
- }
- }
-
- if (null != suggest) {
- suggest.setContactsDictionary(dictionaryToUse);
- }
- }
-
/* package private */ void resetSuggestMainDict() {
final Locale subtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
mSuggest.resetMainDict(this, subtypeLocale, this /* SuggestInitializationListener */);
@@ -760,8 +700,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
.findViewById(android.R.id.extractArea);
mKeyPreviewBackingView = view.findViewById(R.id.key_preview_backing);
mSuggestionStripView = (SuggestionStripView)view.findViewById(R.id.suggestion_strip_view);
- if (mSuggestionStripView != null)
+ if (mSuggestionStripView != null) {
mSuggestionStripView.setListener(this, view);
+ }
if (LatinImeLogger.sVISUALDEBUG) {
mKeyPreviewBackingView.setBackgroundColor(0x10FF0000);
}
@@ -907,7 +848,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// refresh later.
final boolean canReachInputConnection;
if (!mConnection.resetCachesUponCursorMoveAndReturnSuccess(editorInfo.initialSelStart,
- false /* shouldFinishComposition */)) {
+ editorInfo.initialSelEnd, false /* shouldFinishComposition */)) {
// We try resetting the caches up to 5 times before giving up.
mHandler.postResetCaches(isDifferentTextField, 5 /* remainingTries */);
// mLastSelection{Start,End} are reset later in this method, don't need to do it here
@@ -1022,11 +963,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Only for debug
mBoostPersonalizationDictionaryForDebug =
currentSettingsValues.mBoostPersonalizationDictionaryForDebug;
- if (mBoostPersonalizationDictionaryForDebug) {
- UserHistoryForgettingCurveUtils.boostMaxFreqForDebug();
- } else {
- UserHistoryForgettingCurveUtils.resetMaxFreqForDebug();
- }
}
}
@@ -1088,16 +1024,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
+ ", ce=" + composingSpanEnd);
}
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- final boolean expectingUpdateSelectionFromLogger =
- ResearchLogger.getAndClearLatinIMEExpectingUpdateSelection();
ResearchLogger.latinIME_onUpdateSelection(mLastSelectionStart, mLastSelectionEnd,
oldSelStart, oldSelEnd, newSelStart, newSelEnd, composingSpanStart,
- composingSpanEnd, mExpectingUpdateSelection,
- expectingUpdateSelectionFromLogger, mConnection);
- if (expectingUpdateSelectionFromLogger) {
- // TODO: Investigate. Quitting now sounds wrong - we won't do the resetting work
- return;
- }
+ composingSpanEnd, mConnection);
}
final boolean selectionChanged = mLastSelectionStart != newSelStart
@@ -1116,14 +1045,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// TODO: revisit this when LatinIME supports hardware keyboards.
// NOTE: the test harness subclasses LatinIME and overrides isInputViewShown().
// TODO: find a better way to simulate actual execution.
- if (isInputViewShown() && !mExpectingUpdateSelection
- && !mConnection.isBelatedExpectedUpdate(oldSelStart, newSelStart)) {
- // TAKE CARE: there is a race condition when we enter this test even when the user
- // did not explicitly move the cursor. This happens when typing fast, where two keys
- // turn this flag on in succession and both onUpdateSelection() calls arrive after
- // the second one - the first call successfully avoids this test, but the second one
- // enters. For the moment we rely on noComposingSpan to further reduce the impact.
-
+ if (isInputViewShown() && !mConnection.isBelatedExpectedUpdate(oldSelStart, newSelStart,
+ oldSelEnd, newSelEnd)) {
// TODO: the following is probably better done in resetEntireInputState().
// it should only happen when the cursor moved, and the very purpose of the
// test below is to narrow down whether this happened or not. Likewise with
@@ -1149,13 +1072,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Another option would be to send suggestions each time we set the composing
// text, but that is probably too expensive to do, so we decided to leave things
// as is.
- resetEntireInputState(newSelStart);
+ resetEntireInputState(newSelStart, newSelEnd);
} else {
- // resetEntireInputState calls resetCachesUponCursorMove, but with the second
- // argument as true. But in all cases where we don't reset the entire input state,
- // we still want to tell the rich input connection about the new cursor position so
- // that it can update its caches.
- mConnection.resetCachesUponCursorMoveAndReturnSuccess(newSelStart,
+ // resetEntireInputState calls resetCachesUponCursorMove, but forcing the
+ // composition to end. But in all cases where we don't reset the entire input
+ // state, we still want to tell the rich input connection about the new cursor
+ // position so that it can update its caches.
+ mConnection.resetCachesUponCursorMoveAndReturnSuccess(newSelStart, newSelEnd,
false /* shouldFinishComposition */);
}
@@ -1168,7 +1091,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mRecapitalizeStatus.deactivate();
mKeyboardSwitcher.updateShiftState();
}
- mExpectingUpdateSelection = false;
// Make a note of the cursor position
mLastSelectionStart = newSelStart;
@@ -1354,8 +1276,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public boolean onEvaluateFullscreenMode() {
// Reread resource value here, because this method is called by framework anytime as needed.
- final boolean isFullscreenModeAllowed =
- Settings.readUseFullscreenMode(getResources());
+ final boolean isFullscreenModeAllowed = Settings.readUseFullscreenMode(getResources());
if (super.onEvaluateFullscreenMode() && isFullscreenModeAllowed) {
// TODO: Remove this hack. Actually we should not really assume NO_EXTRACT_UI
// implies NO_FULLSCREEN. However, the framework mistakenly does. i.e. NO_EXTRACT_UI
@@ -1380,7 +1301,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// This will reset the whole input state to the starting state. It will clear
// the composing word, reset the last composed word, tell the inputconnection about it.
- private void resetEntireInputState(final int newCursorPosition) {
+ private void resetEntireInputState(final int newSelStart, final int newSelEnd) {
final boolean shouldFinishComposition = mWordComposer.isComposingWord();
resetComposingState(true /* alsoResetLastComposedWord */);
final SettingsValues settingsValues = mSettings.getCurrent();
@@ -1389,14 +1310,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} else {
setSuggestedWords(settingsValues.mSuggestPuncList, false);
}
- mConnection.resetCachesUponCursorMoveAndReturnSuccess(newCursorPosition,
+ mConnection.resetCachesUponCursorMoveAndReturnSuccess(newSelStart, newSelEnd,
shouldFinishComposition);
}
private void resetComposingState(final boolean alsoResetLastComposedWord) {
mWordComposer.reset();
- if (alsoResetLastComposedWord)
+ if (alsoResetLastComposedWord) {
mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
+ }
}
private void commitTyped(final String separatorString) {
@@ -1443,15 +1365,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (0 != (auto & TextUtils.CAP_MODE_CHARACTERS)) {
return WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED;
}
- if (0 != auto) return WordComposer.CAPS_MODE_AUTO_SHIFTED;
+ if (0 != auto) {
+ return WordComposer.CAPS_MODE_AUTO_SHIFTED;
+ }
return WordComposer.CAPS_MODE_OFF;
}
private void swapSwapperAndSpace() {
final CharSequence lastTwo = mConnection.getTextBeforeCursor(2, 0);
// It is guaranteed lastTwo.charAt(1) is a swapper - else this method is not called.
- if (lastTwo != null && lastTwo.length() == 2
- && lastTwo.charAt(0) == Constants.CODE_SPACE) {
+ if (lastTwo != null && lastTwo.length() == 2 && lastTwo.charAt(0) == Constants.CODE_SPACE) {
mConnection.deleteSurroundingText(2, 0);
final String text = lastTwo.charAt(1) + " ";
mConnection.commitText(text, 1);
@@ -1491,6 +1414,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
ResearchLogger.latinIME_maybeDoubleSpacePeriod(textToInsert,
false /* isBatchMode */);
}
+ mWordComposer.discardPreviousWordForSuggestion();
mKeyboardSwitcher.updateShiftState();
return true;
}
@@ -1730,7 +1654,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
// If we are in the middle of a recorrection, we need to commit the recorrection
// first so that we can insert the character at the current cursor position.
- resetEntireInputState(mLastSelectionStart);
+ resetEntireInputState(mLastSelectionStart, mLastSelectionEnd);
} else {
commitTyped(LastComposedWord.NOT_A_SEPARATOR);
}
@@ -1746,7 +1670,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
handleCharacter(primaryCode, keyX, keyY, spaceState);
}
- mExpectingUpdateSelection = true;
return didAutoCorrect;
}
@@ -1786,9 +1709,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mInputUpdater.onStartBatchInput();
mHandler.cancelUpdateSuggestionStrip();
mConnection.beginBatchEdit();
- final SettingsValues settingsValues = mSettings.getCurrent();
+ final SettingsValues currentSettingsValues = mSettings.getCurrent();
if (mWordComposer.isComposingWord()) {
- if (settingsValues.mIsInternal) {
+ if (currentSettingsValues.mIsInternal) {
if (mWordComposer.isBatchMode()) {
LatinImeLoggerUtils.onAutoCorrection(
"", mWordComposer.getTypedWord(), " ", mWordComposer);
@@ -1799,7 +1722,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
// If we are in the middle of a recorrection, we need to commit the recorrection
// first so that we can insert the batch input at the current cursor position.
- resetEntireInputState(mLastSelectionStart);
+ resetEntireInputState(mLastSelectionStart, mLastSelectionEnd);
} else if (wordComposerSize <= 1) {
// We auto-correct the previous (typed, not gestured) string iff it's one character
// long. The reason for this is, even in the middle of gesture typing, you'll still
@@ -1812,15 +1735,26 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} else {
commitTyped(LastComposedWord.NOT_A_SEPARATOR);
}
- mExpectingUpdateSelection = true;
}
final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
if (Character.isLetterOrDigit(codePointBeforeCursor)
- || settingsValues.isUsuallyFollowedBySpace(codePointBeforeCursor)) {
+ || currentSettingsValues.isUsuallyFollowedBySpace(codePointBeforeCursor)) {
+ final boolean autoShiftHasBeenOverriden = mKeyboardSwitcher.getKeyboardShiftMode() !=
+ getCurrentAutoCapsState();
mSpaceState = SPACE_STATE_PHANTOM;
+ if (!autoShiftHasBeenOverriden) {
+ // When we change the space state, we need to update the shift state of the
+ // keyboard unless it has been overridden manually. This is happening for example
+ // after typing some letters and a period, then gesturing; the keyboard is not in
+ // caps mode yet, but since a gesture is starting, it should go in caps mode,
+ // unless the user explictly said it should not.
+ mKeyboardSwitcher.updateShiftState();
+ }
}
mConnection.endBatchEdit();
- mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode());
+ mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(getActualCapsMode(),
+ // Prev word is 1st word before cursor
+ getNthPreviousWordForSuggestion(currentSettingsValues, 1 /* nthPreviousWord */));
}
static final class InputUpdater implements Handler.Callback {
@@ -1993,7 +1927,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mConnection.commitText(commitParts[0], 0);
mSpaceState = SPACE_STATE_PHANTOM;
mKeyboardSwitcher.updateShiftState();
- mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode());
+ mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
+ getActualCapsMode(), commitParts[0]);
++mAutoCommitSequenceNumber;
}
}
@@ -2003,8 +1938,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// This method must run in UI Thread.
public void onEndBatchInputAsyncInternal(final SuggestedWords suggestedWords) {
- final String batchInputText = suggestedWords.isEmpty()
- ? null : suggestedWords.getWord(0);
+ final String batchInputText = suggestedWords.isEmpty() ? null : suggestedWords.getWord(0);
if (TextUtils.isEmpty(batchInputText)) {
return;
}
@@ -2026,7 +1960,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mWordComposer.setBatchInputWord(batchInputText);
mConnection.setComposingText(batchInputText, 1);
}
- mExpectingUpdateSelection = true;
mConnection.endBatchEdit();
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_onEndBatchInput(batchInputText, 0, suggestedWords);
@@ -2080,9 +2013,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
private void handleBackspace(final int spaceState) {
- // We revert these in this method if the deletion doesn't happen.
mDeleteCount++;
- mExpectingUpdateSelection = true;
// In many cases, we may have to put the keyboard in auto-shift state again. However
// we want to wait a few milliseconds before doing it to avoid the keyboard flashing
@@ -2092,7 +2023,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
// If we are in the middle of a recorrection, we need to commit the recorrection
// first so that we can remove the character at the current cursor position.
- resetEntireInputState(mLastSelectionStart);
+ resetEntireInputState(mLastSelectionStart, mLastSelectionEnd);
// When we exit this if-clause, mWordComposer.isComposingWord() will return false.
}
if (mWordComposer.isComposingWord()) {
@@ -2163,6 +2094,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// later (typically, in a subsequent press on backspace).
mLastSelectionEnd = mLastSelectionStart;
mConnection.deleteSurroundingText(numCharsDeleted, 0);
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_handleBackspace(numCharsDeleted,
+ false /* shouldUncommitLogUnit */);
+ }
} else {
// There is no selection, just delete one character.
if (NOT_A_CURSOR_POSITION == mLastSelectionEnd) {
@@ -2185,22 +2120,27 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} else {
final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
if (codePointBeforeCursor == Constants.NOT_A_CODE) {
- // Nothing to delete before the cursor. We have to revert the deletion
- // states that were updated at the beginning of this method.
- mDeleteCount--;
- mExpectingUpdateSelection = false;
+ // Nothing to delete before the cursor.
return;
}
final int lengthToDelete =
Character.isSupplementaryCodePoint(codePointBeforeCursor) ? 2 : 1;
mConnection.deleteSurroundingText(lengthToDelete, 0);
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_handleBackspace(lengthToDelete,
+ true /* shouldUncommitLogUnit */);
+ }
if (mDeleteCount > DELETE_ACCELERATE_AT) {
final int codePointBeforeCursorToDeleteAgain =
mConnection.getCodePointBeforeCursor();
if (codePointBeforeCursorToDeleteAgain != Constants.NOT_A_CODE) {
final int lengthToDeleteAgain = Character.isSupplementaryCodePoint(
- codePointBeforeCursorToDeleteAgain) ? 2 : 1;
+ codePointBeforeCursorToDeleteAgain) ? 2 : 1;
mConnection.deleteSurroundingText(lengthToDeleteAgain, 0);
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_handleBackspace(lengthToDeleteAgain,
+ true /* shouldUncommitLogUnit */);
+ }
}
}
}
@@ -2217,8 +2157,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
/*
* Strip a trailing space if necessary and returns whether it's a swap weak space situation.
*/
- private boolean maybeStripSpace(final int code,
- final int spaceState, final boolean isFromSuggestionStrip) {
+ private boolean maybeStripSpace(final int code, final int spaceState,
+ final boolean isFromSuggestionStrip) {
if (Constants.CODE_ENTER == code && SPACE_STATE_SWAP_PUNCTUATION == spaceState) {
mConnection.removeTrailingSpace();
return false;
@@ -2233,8 +2173,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return false;
}
- private void handleCharacter(final int primaryCode, final int x,
- final int y, final int spaceState) {
+ private void handleCharacter(final int primaryCode, final int x, final int y,
+ final int spaceState) {
// TODO: refactor this method to stop flipping isComposingWord around all the time, and
// make it shorter (possibly cut into several pieces). Also factor handleNonSpecialCharacter
// which has the same name as other handle* methods but is not the same.
@@ -2254,7 +2194,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
// If we are in the middle of a recorrection, we need to commit the recorrection
// first so that we can insert the character at the current cursor position.
- resetEntireInputState(mLastSelectionStart);
+ resetEntireInputState(mLastSelectionStart, mLastSelectionEnd);
isComposingWord = false;
}
// We want to find out whether to start composing a new word with this character. If so,
@@ -2284,25 +2224,24 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
resetComposingState(false /* alsoResetLastComposedWord */);
}
if (isComposingWord) {
- final int keyX, keyY;
- if (Constants.isValidCoordinate(x) && Constants.isValidCoordinate(y)) {
- final KeyDetector keyDetector =
- mKeyboardSwitcher.getMainKeyboardView().getKeyDetector();
- keyX = keyDetector.getTouchX(x);
- keyY = keyDetector.getTouchY(y);
- } else {
- keyX = x;
- keyY = y;
- }
+ final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
+ // TODO: We should reconsider which coordinate system should be used to represent
+ // keyboard event.
+ final int keyX = mainKeyboardView.getKeyX(x);
+ final int keyY = mainKeyboardView.getKeyY(y);
mWordComposer.add(primaryCode, keyX, keyY);
// If it's the first letter, make note of auto-caps state
if (mWordComposer.size() == 1) {
- mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode());
+ // We pass 1 to getPreviousWordForSuggestion because we were not composing a word
+ // yet, so the word we want is the 1st word before the cursor.
+ mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
+ getActualCapsMode(),
+ getNthPreviousWordForSuggestion(currentSettings, 1 /* nthPreviousWord */));
}
mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
} else {
- final boolean swapWeakSpace = maybeStripSpace(primaryCode,
- spaceState, Constants.SUGGESTION_STRIP_COORDINATE == x);
+ final boolean swapWeakSpace = maybeStripSpace(primaryCode, spaceState,
+ Constants.SUGGESTION_STRIP_COORDINATE == x);
sendKeyCodePoint(primaryCode);
@@ -2366,7 +2305,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
// If we are in the middle of a recorrection, we need to commit the recorrection
// first so that we can insert the separator at the current cursor position.
- resetEntireInputState(mLastSelectionStart);
+ resetEntireInputState(mLastSelectionStart, mLastSelectionEnd);
}
if (mWordComposer.isComposingWord()) { // May have changed since we stored wasComposing
if (currentSettings.mCorrectionEnabled) {
@@ -2539,6 +2478,24 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
+ /**
+ * Get the nth previous word before the cursor as context for the suggestion process.
+ * @param currentSettings the current settings values.
+ * @param nthPreviousWord reverse index of the word to get (1-indexed)
+ * @return the nth previous word before the cursor.
+ */
+ private String getNthPreviousWordForSuggestion(final SettingsValues currentSettings,
+ final int nthPreviousWord) {
+ if (currentSettings.mCurrentLanguageHasSpaces) {
+ // If we are typing in a language with spaces we can just look up the previous
+ // word from textview.
+ return mConnection.getNthPreviousWord(currentSettings, nthPreviousWord);
+ } else {
+ return LastComposedWord.NOT_A_COMPOSED_WORD == mLastComposedWord ? null
+ : mLastComposedWord.mCommittedWord;
+ }
+ }
+
private void getSuggestedWords(final int sessionId, final int sequenceNumber,
final OnGetSuggestedWordsCallback callback) {
final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
@@ -2552,17 +2509,25 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// should just skip whitespace if any, so 1.
final SettingsValues currentSettings = mSettings.getCurrent();
final int[] additionalFeaturesOptions = currentSettings.mAdditionalFeaturesSettingValues;
- final String prevWord;
- if (currentSettings.mCurrentLanguageHasSpaces) {
- // If we are typing in a language with spaces we can just look up the previous
- // word from textview.
- prevWord = mConnection.getNthPreviousWord(currentSettings.mWordSeparators,
- mWordComposer.isComposingWord() ? 2 : 1);
- } else {
- prevWord = LastComposedWord.NOT_A_COMPOSED_WORD == mLastComposedWord ? null
- : mLastComposedWord.mCommittedWord;
+
+ if (DEBUG) {
+ if (mWordComposer.isComposingWord() || mWordComposer.isBatchMode()) {
+ final String previousWord = mWordComposer.getPreviousWordForSuggestion();
+ // TODO: this is for checking consistency with older versions. Remove this when
+ // we are confident this is stable.
+ // We're checking the previous word in the text field against the memorized previous
+ // word. If we are composing a word we should have the second word before the cursor
+ // memorized, otherwise we should have the first.
+ final String rereadPrevWord = getNthPreviousWordForSuggestion(currentSettings,
+ mWordComposer.isComposingWord() ? 2 : 1);
+ if (!TextUtils.equals(previousWord, rereadPrevWord)) {
+ throw new RuntimeException("Unexpected previous word: "
+ + previousWord + " <> " + rereadPrevWord);
+ }
+ }
}
- suggest.getSuggestedWords(mWordComposer, prevWord, keyboard.getProximityInfo(),
+ suggest.getSuggestedWords(mWordComposer, mWordComposer.getPreviousWordForSuggestion(),
+ keyboard.getProximityInfo(),
currentSettings.mBlockPotentiallyOffensive, currentSettings.mCorrectionEnabled,
additionalFeaturesOptions, sessionId, sequenceNumber, callback);
}
@@ -2679,7 +2644,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
ResearchLogger.latinIme_commitCurrentAutoCorrection(typedWord, autoCorrection,
separator, mWordComposer.isBatchMode(), suggestedWords);
}
- mExpectingUpdateSelection = true;
commitChosenWord(autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD,
separator);
if (!typedWord.equals(autoCorrection)) {
@@ -2750,7 +2714,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// typed word.
final String replacedWord = mWordComposer.getTypedWord();
LatinImeLogger.logOnManualSuggestion(replacedWord, suggestion, index, suggestedWords);
- mExpectingUpdateSelection = true;
commitChosenWord(suggestion, LastComposedWord.COMMIT_TYPE_MANUAL_PICK,
LastComposedWord.NOT_A_SEPARATOR);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
@@ -2806,6 +2769,19 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// strings.
mLastComposedWord = mWordComposer.commitWord(commitType, chosenWord, separatorString,
prevWord);
+ final boolean shouldDiscardPreviousWordForSuggestion;
+ if (0 == StringUtils.codePointCount(separatorString)) {
+ // Separator is 0-length. Discard the word only if the current language has spaces.
+ shouldDiscardPreviousWordForSuggestion =
+ mSettings.getCurrent().mCurrentLanguageHasSpaces;
+ } else {
+ // Otherwise, we discard if the separator contains any non-whitespace.
+ shouldDiscardPreviousWordForSuggestion =
+ !StringUtils.containsOnlyWhitespace(separatorString);
+ }
+ if (shouldDiscardPreviousWordForSuggestion) {
+ mWordComposer.discardPreviousWordForSuggestion();
+ }
}
private void setPunctuationSuggestions() {
@@ -2830,10 +2806,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final SettingsValues currentSettings = mSettings.getCurrent();
if (!currentSettings.mCorrectionEnabled) return null;
- final UserHistoryDictionary userHistoryDictionary = mUserHistoryDictionary;
+ final UserHistoryDictionary userHistoryDictionary = suggest.getUserHistoryDictionary();
if (userHistoryDictionary == null) return null;
- final String prevWord = mConnection.getNthPreviousWord(currentSettings.mWordSeparators, 2);
+ final String prevWord = mConnection.getNthPreviousWord(currentSettings, 2);
final String secondWord;
if (mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps()) {
secondWord = suggestion.toLowerCase(mSubtypeSwitcher.getCurrentSubtypeLocale());
@@ -2845,7 +2821,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final int maxFreq = AutoCorrectionUtils.getMaxFrequency(
suggest.getUnigramDictionaries(), suggestion);
if (maxFreq == 0) return null;
- userHistoryDictionary.addToDictionary(prevWord, secondWord, maxFreq > 0);
+ userHistoryDictionary.addToDictionary(prevWord, secondWord, maxFreq > 0,
+ (int)TimeUnit.MILLISECONDS.toSeconds((System.currentTimeMillis())));
return prevWord;
}
@@ -2901,7 +2878,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
}
- mWordComposer.setComposingWord(typedWord, mKeyboardSwitcher.getKeyboard());
+ mWordComposer.setComposingWord(typedWord,
+ getNthPreviousWordForSuggestion(currentSettings,
+ // We want the previous word for suggestion. If we have chars in the word
+ // before the cursor, then we want the word before that, hence 2; otherwise,
+ // we want the word immediately before the cursor, hence 1.
+ 0 == numberOfCharsInWordBeforeCursor ? 1 : 2),
+ mKeyboardSwitcher.getKeyboard());
mWordComposer.setCursorPositionWithinWord(
typedWord.codePointCount(0, numberOfCharsInWordBeforeCursor));
mConnection.setComposingRegion(
@@ -2979,7 +2962,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
private void restartSuggestionsOnWordBeforeCursor(final String word) {
- mWordComposer.setComposingWord(word, mKeyboardSwitcher.getKeyboard());
+ mWordComposer.setComposingWord(word,
+ // Previous word is the 2nd word before cursor because we are restarting on the
+ // 1st word before cursor.
+ getNthPreviousWordForSuggestion(mSettings.getCurrent(), 2 /* nthPreviousWord */),
+ mKeyboardSwitcher.getKeyboard());
final int length = word.length();
mConnection.deleteSurroundingText(length, 0);
mConnection.setComposingText(word, 1);
@@ -2997,7 +2984,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
* @param remainingTries How many times we may try again before giving up.
*/
private void retryResetCaches(final boolean tryResumeSuggestions, final int remainingTries) {
- if (!mConnection.resetCachesUponCursorMoveAndReturnSuccess(mLastSelectionStart, false)) {
+ if (!mConnection.resetCachesUponCursorMoveAndReturnSuccess(mLastSelectionStart,
+ mLastSelectionEnd, false)) {
if (0 < remainingTries) {
mHandler.postResetCaches(tryResumeSuggestions, remainingTries - 1);
return;
@@ -3024,8 +3012,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
throw new RuntimeException("revertCommit, but we are composing a word");
}
final CharSequence wordBeforeCursor =
- mConnection.getTextBeforeCursor(deleteLength, 0)
- .subSequence(0, cancelLength);
+ mConnection.getTextBeforeCursor(deleteLength, 0).subSequence(0, cancelLength);
if (!TextUtils.equals(committedWord, wordBeforeCursor)) {
throw new RuntimeException("revertCommit check failed: we thought we were "
+ "reverting \"" + committedWord
@@ -3034,7 +3021,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
mConnection.deleteSurroundingText(deleteLength, 0);
if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) {
- mUserHistoryDictionary.cancelAddingUserHistory(previousWord, committedWord);
+ if (mSuggest != null) {
+ mSuggest.cancelAddingUserHistory(previousWord, committedWord);
+ }
}
final String stringToCommit = originallyTypedWord + mLastComposedWord.mSeparatorString;
if (mSettings.getCurrent().mCurrentLanguageHasSpaces) {
@@ -3045,7 +3034,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} else {
// For languages without spaces, we revert the typed string but the cursor is flush
// with the typed word, so we need to resume suggestions right away.
- mWordComposer.setComposingWord(stringToCommit, mKeyboardSwitcher.getKeyboard());
+ mWordComposer.setComposingWord(stringToCommit, previousWord,
+ mKeyboardSwitcher.getKeyboard());
mConnection.setComposingText(stringToCommit, 1);
}
if (mSettings.isInternal()) {
@@ -3095,8 +3085,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private void hapticAndAudioFeedback(final int code, final int repeatCount) {
final MainKeyboardView keyboardView = mKeyboardSwitcher.getMainKeyboardView();
- if (keyboardView != null && keyboardView.isInSlidingKeyInput()) {
- // No need to feedback while sliding input.
+ if (keyboardView != null && keyboardView.isInDraggingFinger()) {
+ // No need to feedback while finger is dragging.
return;
}
if (repeatCount > 0) {
@@ -3226,8 +3216,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final Intent intent = IntentUtils.getInputLanguageSelectionIntent(
mRichImm.getInputMethodIdOfThisIme(),
Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
- | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
break;
case 1:
@@ -3236,9 +3226,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
};
- final AlertDialog.Builder builder = new AlertDialog.Builder(this)
- .setItems(items, listener)
- .setTitle(title);
+ final AlertDialog.Builder builder =
+ new AlertDialog.Builder(this).setItems(items, listener).setTitle(title);
showOptionDialog(builder.create());
}
@@ -3299,11 +3288,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final Printer p = new PrintWriterPrinter(fout);
p.println("LatinIME state :");
+ p.println(" VersionCode = " + ApplicationUtils.getVersionCode(this));
+ p.println(" VersionName = " + ApplicationUtils.getVersionName(this));
final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
final int keyboardMode = keyboard != null ? keyboard.mId.mMode : -1;
p.println(" Keyboard mode = " + keyboardMode);
final SettingsValues settingsValues = mSettings.getCurrent();
- p.println(" mIsSuggestionsSuggestionsRequested = "
+ p.println(" mIsSuggestionsRequested = "
+ settingsValues.isSuggestionsRequested(mDisplayOrientation));
p.println(" mCorrectionEnabled=" + settingsValues.mCorrectionEnabled);
p.println(" isComposingWord=" + mWordComposer.isComposingWord());
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 673d1b4c2..37311acf2 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -57,14 +57,19 @@ public final class RichInputConnection {
private static final int INVALID_CURSOR_POSITION = -1;
/**
- * This variable contains an expected value for the cursor position. This is where the
- * cursor may end up after all the keyboard-triggered updates have passed. We keep this to
- * compare it to the actual cursor position to guess whether the move was caused by a
- * keyboard command or not.
- * It's not really the cursor position: the cursor may not be there yet, and it's also expected
- * there be cases where it never actually comes to be there.
+ * This variable contains an expected value for the selection start position. This is where the
+ * cursor or selection start may end up after all the keyboard-triggered updates have passed. We
+ * keep this to compare it to the actual selection start to guess whether the move was caused by
+ * a keyboard command or not.
+ * It's not really the selection start position: the selection start may not be there yet, and
+ * in some cases, it may never arrive there.
*/
- private int mExpectedCursorPosition = INVALID_CURSOR_POSITION; // in chars, not code points
+ private int mExpectedSelStart = INVALID_CURSOR_POSITION; // in chars, not code points
+ /**
+ * The expected selection end. Only differs from mExpectedSelStart if a non-empty selection is
+ * expected. The same caveats as mExpectedSelStart apply.
+ */
+ private int mExpectedSelEnd = INVALID_CURSOR_POSITION; // in chars, not code points
/**
* This contains the committed text immediately preceding the cursor and the composing
* text if any. It is refreshed when the cursor moves by calling upon the TextView.
@@ -103,16 +108,16 @@ public final class RichInputConnection {
final String reference = (beforeCursor.length() <= actualLength) ? beforeCursor.toString()
: beforeCursor.subSequence(beforeCursor.length() - actualLength,
beforeCursor.length()).toString();
- if (et.selectionStart != mExpectedCursorPosition
+ if (et.selectionStart != mExpectedSelStart
|| !(reference.equals(internal.toString()))) {
- final String context = "Expected cursor position = " + mExpectedCursorPosition
- + "\nActual cursor position = " + et.selectionStart
+ final String context = "Expected selection start = " + mExpectedSelStart
+ + "\nActual selection start = " + et.selectionStart
+ "\nExpected text = " + internal.length() + " " + internal
+ "\nActual text = " + reference.length() + " " + reference;
((LatinIME)mParent).debugDumpStateAndCrashWithException(context);
} else {
Log.e(TAG, DebugLogUtils.getStackTrace(2));
- Log.e(TAG, "Exp <> Actual : " + mExpectedCursorPosition + " <> " + et.selectionStart);
+ Log.e(TAG, "Exp <> Actual : " + mExpectedSelStart + " <> " + et.selectionStart);
}
}
@@ -150,16 +155,50 @@ public final class RichInputConnection {
* data, so we empty the cache and note that we don't know the new cursor position, and we
* return false so that the caller knows about this and can retry later.
*
- * @param newCursorPosition The new position of the cursor, as received from the system.
- * @param shouldFinishComposition Whether we should finish the composition in progress.
+ * @param newSelStart the new position of the selection start, as received from the system.
+ * @param newSelEnd the new position of the selection end, as received from the system.
+ * @param shouldFinishComposition whether we should finish the composition in progress.
* @return true if we were able to connect to the editor successfully, false otherwise. When
* this method returns false, the caches could not be correctly refreshed so they were only
* reset: the caller should try again later to return to normal operation.
*/
- public boolean resetCachesUponCursorMoveAndReturnSuccess(final int newCursorPosition,
- final boolean shouldFinishComposition) {
- mExpectedCursorPosition = newCursorPosition;
+ public boolean resetCachesUponCursorMoveAndReturnSuccess(final int newSelStart,
+ final int newSelEnd, final boolean shouldFinishComposition) {
+ mExpectedSelStart = newSelStart;
+ mExpectedSelEnd = newSelEnd;
mComposingText.setLength(0);
+ final boolean didReloadTextSuccessfully = reloadTextCache();
+ if (!didReloadTextSuccessfully) {
+ Log.d(TAG, "Will try to retrieve text later.");
+ return false;
+ }
+ final int lengthOfTextBeforeCursor = mCommittedTextBeforeComposingText.length();
+ if (lengthOfTextBeforeCursor > newSelStart
+ || (lengthOfTextBeforeCursor < Constants.EDITOR_CONTENTS_CACHE_SIZE
+ && newSelStart < Constants.EDITOR_CONTENTS_CACHE_SIZE)) {
+ // newSelStart and newSelEnd may be lying -- when rotating the device (probably a
+ // framework bug). If we have less chars than we asked for, then we know how many chars
+ // we have, and if we got more than newSelStart says, then we know it was lying. In both
+ // cases the length is more reliable. Note that we only have to check newSelStart (not
+ // newSelEnd) since if newSelEnd is wrong, the newSelStart will be wrong as well.
+ mExpectedSelStart = lengthOfTextBeforeCursor;
+ mExpectedSelEnd = lengthOfTextBeforeCursor;
+ }
+ if (null != mIC && shouldFinishComposition) {
+ mIC.finishComposingText();
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.richInputConnection_finishComposingText();
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Reload the cached text from the InputConnection.
+ *
+ * @return true if successful
+ */
+ private boolean reloadTextCache() {
mCommittedTextBeforeComposingText.setLength(0);
mIC = mParent.getCurrentInputConnection();
// Call upon the inputconnection directly since our own method is using the cache, and
@@ -169,27 +208,12 @@ public final class RichInputConnection {
if (null == textBeforeCursor) {
// For some reason the app thinks we are not connected to it. This looks like a
// framework bug... Fall back to ground state and return false.
- mExpectedCursorPosition = INVALID_CURSOR_POSITION;
- Log.e(TAG, "Unable to connect to the editor to retrieve text... will retry later");
+ mExpectedSelStart = INVALID_CURSOR_POSITION;
+ mExpectedSelEnd = INVALID_CURSOR_POSITION;
+ Log.e(TAG, "Unable to connect to the editor to retrieve text.");
return false;
}
mCommittedTextBeforeComposingText.append(textBeforeCursor);
- final int lengthOfTextBeforeCursor = textBeforeCursor.length();
- if (lengthOfTextBeforeCursor > newCursorPosition
- || (lengthOfTextBeforeCursor < Constants.EDITOR_CONTENTS_CACHE_SIZE
- && newCursorPosition < Constants.EDITOR_CONTENTS_CACHE_SIZE)) {
- // newCursorPosition may be lying -- when rotating the device (probably a framework
- // bug). If we have less chars than we asked for, then we know how many chars we have,
- // and if we got more than newCursorPosition says, then we know it was lying. In both
- // cases the length is more reliable
- mExpectedCursorPosition = lengthOfTextBeforeCursor;
- }
- if (null != mIC && shouldFinishComposition) {
- mIC.finishComposingText();
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_finishComposingText();
- }
- }
return true;
}
@@ -218,7 +242,8 @@ public final class RichInputConnection {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
mCommittedTextBeforeComposingText.append(text);
- mExpectedCursorPosition += text.length() - mComposingText.length();
+ mExpectedSelStart += text.length() - mComposingText.length();
+ mExpectedSelEnd = mExpectedSelStart;
mComposingText.setLength(0);
if (null != mIC) {
mIC.commitText(text, i);
@@ -231,7 +256,7 @@ public final class RichInputConnection {
}
public boolean canDeleteCharacters() {
- return mExpectedCursorPosition > 0;
+ return mExpectedSelStart > 0;
}
/**
@@ -268,11 +293,10 @@ public final class RichInputConnection {
// heavy pressing of delete, for example DEFAULT_TEXT_CACHE_SIZE - 5 times or so.
// getCapsMode should be updated to be able to return a "not enough info" result so that
// we can get more context only when needed.
- if (TextUtils.isEmpty(mCommittedTextBeforeComposingText) && 0 != mExpectedCursorPosition) {
- final CharSequence textBeforeCursor = getTextBeforeCursor(
- Constants.EDITOR_CONTENTS_CACHE_SIZE, 0);
- if (!TextUtils.isEmpty(textBeforeCursor)) {
- mCommittedTextBeforeComposingText.append(textBeforeCursor);
+ if (TextUtils.isEmpty(mCommittedTextBeforeComposingText) && 0 != mExpectedSelStart) {
+ if (!reloadTextCache()) {
+ Log.w(TAG, "Unable to connect to the editor. "
+ + "Setting caps mode without knowing text.");
}
}
// This never calls InputConnection#getCapsMode - in fact, it's a static method that
@@ -295,8 +319,8 @@ public final class RichInputConnection {
// However, if we don't have an expected cursor position, then we should always
// go fetch the cache again (as it happens, INVALID_CURSOR_POSITION < 0, so we need to
// test for this explicitly)
- if (INVALID_CURSOR_POSITION != mExpectedCursorPosition
- && (cachedLength >= n || cachedLength >= mExpectedCursorPosition)) {
+ if (INVALID_CURSOR_POSITION != mExpectedSelStart
+ && (cachedLength >= n || cachedLength >= mExpectedSelStart)) {
final StringBuilder s = new StringBuilder(mCommittedTextBeforeComposingText);
// We call #toString() here to create a temporary object.
// In some situations, this method is called on a worker thread, and it's possible
@@ -336,10 +360,14 @@ public final class RichInputConnection {
+ remainingChars, 0);
mCommittedTextBeforeComposingText.setLength(len);
}
- if (mExpectedCursorPosition > beforeLength) {
- mExpectedCursorPosition -= beforeLength;
+ if (mExpectedSelStart > beforeLength) {
+ mExpectedSelStart -= beforeLength;
+ mExpectedSelEnd -= beforeLength;
} else {
- mExpectedCursorPosition = 0;
+ // There are fewer characters before the cursor in the buffer than we are being asked to
+ // delete. Only delete what is there.
+ mExpectedSelStart = 0;
+ mExpectedSelEnd -= mExpectedSelStart;
}
if (null != mIC) {
mIC.deleteSurroundingText(beforeLength, afterLength);
@@ -373,7 +401,8 @@ public final class RichInputConnection {
switch (keyEvent.getKeyCode()) {
case KeyEvent.KEYCODE_ENTER:
mCommittedTextBeforeComposingText.append("\n");
- mExpectedCursorPosition += 1;
+ mExpectedSelStart += 1;
+ mExpectedSelEnd = mExpectedSelStart;
break;
case KeyEvent.KEYCODE_DEL:
if (0 == mComposingText.length()) {
@@ -385,18 +414,24 @@ public final class RichInputConnection {
} else {
mComposingText.delete(mComposingText.length() - 1, mComposingText.length());
}
- if (mExpectedCursorPosition > 0) mExpectedCursorPosition -= 1;
+ if (mExpectedSelStart > 0 && mExpectedSelStart == mExpectedSelEnd) {
+ // TODO: Handle surrogate pairs.
+ mExpectedSelStart -= 1;
+ }
+ mExpectedSelEnd = mExpectedSelStart;
break;
case KeyEvent.KEYCODE_UNKNOWN:
if (null != keyEvent.getCharacters()) {
mCommittedTextBeforeComposingText.append(keyEvent.getCharacters());
- mExpectedCursorPosition += keyEvent.getCharacters().length();
+ mExpectedSelStart += keyEvent.getCharacters().length();
+ mExpectedSelEnd = mExpectedSelStart;
}
break;
default:
- final String text = new String(new int[] { keyEvent.getUnicodeChar() }, 0, 1);
+ final String text = StringUtils.newSingleCodePointString(keyEvent.getUnicodeChar());
mCommittedTextBeforeComposingText.append(text);
- mExpectedCursorPosition += text.length();
+ mExpectedSelStart += text.length();
+ mExpectedSelEnd = mExpectedSelStart;
break;
}
}
@@ -430,10 +465,12 @@ public final class RichInputConnection {
public void setComposingText(final CharSequence text, final int newCursorPosition) {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
- mExpectedCursorPosition += text.length() - mComposingText.length();
+ mExpectedSelStart += text.length() - mComposingText.length();
+ mExpectedSelEnd = mExpectedSelStart;
mComposingText.setLength(0);
mComposingText.append(text);
- // TODO: support values of i != 1. At this time, this is never called with i != 1.
+ // TODO: support values of newCursorPosition != 1. At this time, this is never called with
+ // newCursorPosition != 1.
if (null != mIC) {
mIC.setComposingText(text, newCursorPosition);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
@@ -443,19 +480,31 @@ public final class RichInputConnection {
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
}
- public void setSelection(final int start, final int end) {
+ /**
+ * Set the selection of the text editor.
+ *
+ * Calls through to {@link InputConnection#setSelection(int, int)}.
+ *
+ * @param start the character index where the selection should start.
+ * @param end the character index where the selection should end.
+ * @return Returns true on success, false if the input connection is no longer valid either when
+ * setting the selection or when retrieving the text cache at that point.
+ */
+ public boolean setSelection(final int start, final int end) {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
+ mExpectedSelStart = start;
+ mExpectedSelEnd = end;
if (null != mIC) {
- mIC.setSelection(start, end);
+ final boolean isIcValid = mIC.setSelection(start, end);
+ if (!isIcValid) {
+ return false;
+ }
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.richInputConnection_setSelection(start, end);
}
}
- mExpectedCursorPosition = start;
- mCommittedTextBeforeComposingText.setLength(0);
- mCommittedTextBeforeComposingText.append(
- getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE, 0));
+ return reloadTextCache();
}
public void commitCorrection(final CorrectionInfo correctionInfo) {
@@ -476,7 +525,8 @@ public final class RichInputConnection {
// text should never be null, but just in case, it's better to insert nothing than to crash
if (null == text) text = "";
mCommittedTextBeforeComposingText.append(text);
- mExpectedCursorPosition += text.length() - mComposingText.length();
+ mExpectedSelStart += text.length() - mComposingText.length();
+ mExpectedSelEnd = mExpectedSelStart;
mComposingText.setLength(0);
if (null != mIC) {
mIC.commitCompletion(completionInfo);
@@ -488,7 +538,7 @@ public final class RichInputConnection {
}
@SuppressWarnings("unused")
- public String getNthPreviousWord(final String sentenceSeperators, final int n) {
+ public String getNthPreviousWord(final SettingsValues currentSettingsValues, final int n) {
mIC = mParent.getCurrentInputConnection();
if (null == mIC) return null;
final CharSequence prev = getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0);
@@ -507,7 +557,7 @@ public final class RichInputConnection {
}
}
}
- return getNthPreviousWord(prev, sentenceSeperators, n);
+ return getNthPreviousWord(prev, currentSettingsValues, n);
}
private static boolean isSeparator(int code, String sep) {
@@ -531,7 +581,7 @@ public final class RichInputConnection {
// (n = 2) "abc |" -> null
// (n = 2) "abc. def|" -> null
public static String getNthPreviousWord(final CharSequence prev,
- final String sentenceSeperators, final int n) {
+ final SettingsValues currentSettingsValues, final int n) {
if (prev == null) return null;
final String[] w = spaceRegex.split(prev);
@@ -543,7 +593,8 @@ public final class RichInputConnection {
// If ends in a separator, return null
final char lastChar = nthPrevWord.charAt(length - 1);
- if (sentenceSeperators.contains(String.valueOf(lastChar))) return null;
+ if (currentSettingsValues.isWordSeparator(lastChar)
+ || currentSettingsValues.isWordConnector(lastChar)) return null;
return nthPrevWord;
}
@@ -758,20 +809,30 @@ public final class RichInputConnection {
* this update and not the ones in-between. This is almost impossible to achieve even trying
* very very hard.
*
- * @param oldSelStart The value of the old cursor position in the update.
- * @param newSelStart The value of the new cursor position in the update.
+ * @param oldSelStart The value of the old selection in the update.
+ * @param newSelStart The value of the new selection in the update.
+ * @param oldSelEnd The value of the old selection end in the update.
+ * @param newSelEnd The value of the new selection end in the update.
* @return whether this is a belated expected update or not.
*/
- public boolean isBelatedExpectedUpdate(final int oldSelStart, final int newSelStart) {
- // If this is an update that arrives at our expected position, it's a belated update.
- if (newSelStart == mExpectedCursorPosition) return true;
- // If this is an update that moves the cursor from our expected position, it must be
- // an explicit move.
- if (oldSelStart == mExpectedCursorPosition) return false;
- // The following returns true if newSelStart is between oldSelStart and
- // mCurrentCursorPosition. We assume that if the updated position is between the old
- // position and the expected position, then it must be a belated update.
- return (newSelStart - oldSelStart) * (mExpectedCursorPosition - newSelStart) >= 0;
+ public boolean isBelatedExpectedUpdate(final int oldSelStart, final int newSelStart,
+ final int oldSelEnd, final int newSelEnd) {
+ // This update is "belated" if we are expecting it. That is, mExpectedSelStart and
+ // mExpectedSelEnd match the new values that the TextView is updating TO.
+ if (mExpectedSelStart == newSelStart && mExpectedSelEnd == newSelEnd) return true;
+ // This update is not belated if mExpectedSelStart and mExpeectedSelend match the old
+ // values, and one of newSelStart or newSelEnd is updated to a different value. In this
+ // case, there is likely something other than the IME that has moved the selection endpoint
+ // to the new value.
+ if (mExpectedSelStart == oldSelStart && mExpectedSelEnd == oldSelEnd
+ && (oldSelStart != newSelStart || oldSelEnd != newSelEnd)) return false;
+ // If nether of the above two cases holds, then the system may be having trouble keeping up
+ // with updates. If 1) the selection is a cursor, 2) newSelStart is between oldSelStart
+ // and mExpectedSelStart, and 3) newSelEnd is between oldSelEnd and mExpectedSelEnd, then
+ // assume a belated update.
+ return (newSelStart == newSelEnd)
+ && (newSelStart - oldSelStart) * (mExpectedSelStart - newSelStart) >= 0
+ && (newSelEnd - oldSelEnd) * (mExpectedSelEnd - newSelEnd) >= 0;
}
/**
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index cd9c89f04..379eaaa3d 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -32,7 +32,9 @@ import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
+import com.android.inputmethod.latin.utils.LocaleUtils;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
import java.util.List;
@@ -56,23 +58,34 @@ public final class SubtypeSwitcher {
private InputMethodSubtype mEmojiSubtype;
private boolean mIsNetworkConnected;
+ private static final String KEYBOARD_MODE = "keyboard";
// Dummy no language QWERTY subtype. See {@link R.xml.method}.
- private static final InputMethodSubtype DUMMY_NO_LANGUAGE_SUBTYPE = new InputMethodSubtype(
- R.string.subtype_no_language_qwerty, R.drawable.ic_ime_switcher_dark,
- SubtypeLocaleUtils.NO_LANGUAGE, "keyboard", "KeyboardLayoutSet="
- + SubtypeLocaleUtils.QWERTY
- + "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE
- + ",EnabledWhenDefaultIsNotAsciiCapable,"
- + Constants.Subtype.ExtraValue.EMOJI_CAPABLE,
- false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */);
+ private static final int SUBTYPE_ID_OF_DUMMY_NO_LANGUAGE_SUBTYPE = 0xdde0bfd3;
+ private static final String EXTRA_VALUE_OF_DUMMY_NO_LANGUAGE_SUBTYPE =
+ "KeyboardLayoutSet=" + SubtypeLocaleUtils.QWERTY
+ + "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE
+ + "," + Constants.Subtype.ExtraValue.ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE
+ + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
+ private static final InputMethodSubtype DUMMY_NO_LANGUAGE_SUBTYPE =
+ InputMethodSubtypeCompatUtils.newInputMethodSubtype(
+ R.string.subtype_no_language_qwerty, R.drawable.ic_ime_switcher_dark,
+ SubtypeLocaleUtils.NO_LANGUAGE, KEYBOARD_MODE,
+ EXTRA_VALUE_OF_DUMMY_NO_LANGUAGE_SUBTYPE,
+ false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */,
+ SUBTYPE_ID_OF_DUMMY_NO_LANGUAGE_SUBTYPE);
// Caveat: We probably should remove this when we add an Emoji subtype in {@link R.xml.method}.
// Dummy Emoji subtype. See {@link R.xml.method}.
- private static final InputMethodSubtype DUMMY_EMOJI_SUBTYPE = new InputMethodSubtype(
- R.string.subtype_emoji, R.drawable.ic_ime_switcher_dark,
- SubtypeLocaleUtils.NO_LANGUAGE, "keyboard", "KeyboardLayoutSet="
- + SubtypeLocaleUtils.EMOJI + ","
- + Constants.Subtype.ExtraValue.EMOJI_CAPABLE,
- false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */);
+ private static final int SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE = 0xd78b2ed0;
+ private static final String EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE =
+ "KeyboardLayoutSet=" + SubtypeLocaleUtils.EMOJI
+ + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
+ private static final InputMethodSubtype DUMMY_EMOJI_SUBTYPE =
+ InputMethodSubtypeCompatUtils.newInputMethodSubtype(
+ R.string.subtype_emoji, R.drawable.ic_ime_switcher_dark,
+ SubtypeLocaleUtils.NO_LANGUAGE, KEYBOARD_MODE,
+ EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE,
+ false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */,
+ SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE);
static final class NeedsToDisplayLanguage {
private int mEnabledSubtypeCount;
@@ -256,18 +269,23 @@ public final class SubtypeSwitcher {
return mNeedsToDisplayLanguage.getValue();
}
- private static Locale sForcedLocaleForTesting = null;
+ private static InputMethodSubtype sForcedSubtypeForTesting = null;
@UsedForTesting
- void forceLocale(final Locale locale) {
- sForcedLocaleForTesting = locale;
+ void forceSubtype(final InputMethodSubtype subtype) {
+ sForcedSubtypeForTesting = subtype;
}
public Locale getCurrentSubtypeLocale() {
- if (null != sForcedLocaleForTesting) return sForcedLocaleForTesting;
+ if (null != sForcedSubtypeForTesting) {
+ return LocaleUtils.constructLocaleFromString(sForcedSubtypeForTesting.getLocale());
+ }
return SubtypeLocaleUtils.getSubtypeLocale(getCurrentSubtype());
}
public InputMethodSubtype getCurrentSubtype() {
+ if (null != sForcedSubtypeForTesting) {
+ return sForcedSubtypeForTesting;
+ }
return mRichImm.getCurrentInputMethodSubtype(getNoLanguageSubtype());
}
@@ -279,8 +297,8 @@ public final class SubtypeSwitcher {
if (mNoLanguageSubtype != null) {
return mNoLanguageSubtype;
}
- Log.w(TAG, "Can't find no lanugage with QWERTY subtype");
- Log.w(TAG, "No input method subtype found; return dummy subtype: "
+ Log.w(TAG, "Can't find any language with QWERTY subtype");
+ Log.w(TAG, "No input method subtype found; returning dummy subtype: "
+ DUMMY_NO_LANGUAGE_SUBTYPE);
return DUMMY_NO_LANGUAGE_SUBTYPE;
}
@@ -293,8 +311,9 @@ public final class SubtypeSwitcher {
if (mEmojiSubtype != null) {
return mEmojiSubtype;
}
- Log.w(TAG, "Can't find Emoji subtype");
- Log.w(TAG, "No input method subtype found; return dummy subtype: " + DUMMY_EMOJI_SUBTYPE);
+ Log.w(TAG, "Can't find emoji subtype");
+ Log.w(TAG, "No input method subtype found; returning dummy subtype: "
+ + DUMMY_EMOJI_SUBTYPE);
return DUMMY_EMOJI_SUBTYPE;
}
}
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 0a4c7a55d..32ab1f3df 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -25,9 +25,10 @@ import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.personalization.PersonalizationDictionary;
-import com.android.inputmethod.latin.personalization.PersonalizationPredictionDictionary;
+import com.android.inputmethod.latin.personalization.PersonalizationHelper;
import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
import com.android.inputmethod.latin.settings.Settings;
+import com.android.inputmethod.latin.settings.SettingsValues;
import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
import com.android.inputmethod.latin.utils.BoundedTreeSet;
import com.android.inputmethod.latin.utils.CollectionUtils;
@@ -72,7 +73,9 @@ public final class Suggest {
CollectionUtils.newConcurrentHashMap();
private HashSet<String> mOnlyDictionarySetForDebug = null;
private Dictionary mMainDictionary;
- private ContactsBinaryDictionary mContactsDict;
+ private ContactsBinaryDictionary mContactsDictionary;
+ private UserHistoryDictionary mUserHistoryDictionary;
+ private PersonalizationDictionary mPersonalizationDictionary;
@UsedForTesting
private boolean mIsCurrentlyWaitingForMainDictionary = false;
@@ -81,24 +84,28 @@ public final class Suggest {
// Locale used for upper- and title-casing words
public final Locale mLocale;
+ private final Context mContext;
+
public Suggest(final Context context, final Locale locale,
final SuggestInitializationListener listener) {
initAsynchronously(context, locale, listener);
mLocale = locale;
+ mContext = context;
+ // TODO: Use SettingsValues instead of Settings.
// initialize a debug flag for the personalization
if (Settings.readUseOnlyPersonalizationDictionaryForDebug(
PreferenceManager.getDefaultSharedPreferences(context))) {
mOnlyDictionarySetForDebug = new HashSet<String>();
mOnlyDictionarySetForDebug.add(Dictionary.TYPE_PERSONALIZATION);
- mOnlyDictionarySetForDebug.add(Dictionary.TYPE_PERSONALIZATION_PREDICTION_IN_JAVA);
}
}
@UsedForTesting
- Suggest(final AssetFileAddress[] dictionaryList, final Locale locale) {
+ Suggest(final Context context, final AssetFileAddress[] dictionaryList, final Locale locale) {
final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(dictionaryList,
false /* useFullEditDistance */, locale);
mLocale = locale;
+ mContext = context;
mMainDictionary = mainDict;
addOrReplaceDictionaryInternal(Dictionary.TYPE_MAIN, mainDict);
}
@@ -165,7 +172,15 @@ public final class Suggest {
}
public ContactsBinaryDictionary getContactsDictionary() {
- return mContactsDict;
+ return mContactsDictionary;
+ }
+
+ public UserHistoryDictionary getUserHistoryDictionary() {
+ return mUserHistoryDictionary;
+ }
+
+ public PersonalizationDictionary getPersonalizationDictionary() {
+ return mPersonalizationDictionary;
}
public ConcurrentHashMap<String, Dictionary> getUnigramDictionaries() {
@@ -186,24 +201,120 @@ public final class Suggest {
* won't be used.
*/
public void setContactsDictionary(final ContactsBinaryDictionary contactsDictionary) {
- mContactsDict = contactsDictionary;
+ mContactsDictionary = contactsDictionary;
addOrReplaceDictionaryInternal(Dictionary.TYPE_CONTACTS, contactsDictionary);
}
public void setUserHistoryDictionary(final UserHistoryDictionary userHistoryDictionary) {
+ mUserHistoryDictionary = userHistoryDictionary;
addOrReplaceDictionaryInternal(Dictionary.TYPE_USER_HISTORY, userHistoryDictionary);
}
- public void setPersonalizationPredictionDictionary(
- final PersonalizationPredictionDictionary personalizationPredictionDictionary) {
- addOrReplaceDictionaryInternal(Dictionary.TYPE_PERSONALIZATION_PREDICTION_IN_JAVA,
- personalizationPredictionDictionary);
- }
-
public void setPersonalizationDictionary(
final PersonalizationDictionary personalizationDictionary) {
- addOrReplaceDictionaryInternal(Dictionary.TYPE_PERSONALIZATION,
- personalizationDictionary);
+ mPersonalizationDictionary = personalizationDictionary;
+ addOrReplaceDictionaryInternal(Dictionary.TYPE_PERSONALIZATION, personalizationDictionary);
+ }
+
+ /**
+ * Set dictionaries that can be turned off according to the user settings.
+ *
+ * @param oldSuggest the instance having old dictionaries
+ * @param settingsValues current SettingsValues
+ */
+ public void setAdditionalDictionaries(final Suggest oldSuggest,
+ final SettingsValues settingsValues) {
+ // Contacts dictionary
+ resetContactsDictionary(null != oldSuggest ? oldSuggest.getContactsDictionary() : null,
+ settingsValues);
+ // User history dictionary & Personalization dictionary
+ resetPersonalizedDictionaries(oldSuggest, settingsValues);
+ }
+
+ /**
+ * Set the user history dictionary and personalization dictionary according to the user
+ * settings.
+ *
+ * @param oldSuggest the instance that has been used
+ * @param settingsValues current settingsValues
+ */
+ // TODO: Consolidate resetPersonalizedDictionaries() and resetContactsDictionary(). Call up the
+ // new method for each dictionary.
+ private void resetPersonalizedDictionaries(final Suggest oldSuggest,
+ final SettingsValues settingsValues) {
+ final boolean shouldSetDictionaries = settingsValues.mUsePersonalizedDicts;
+
+ final UserHistoryDictionary oldUserHistoryDictionary = (null == oldSuggest) ? null :
+ oldSuggest.getUserHistoryDictionary();
+ final PersonalizationDictionary oldPersonalizationDictionary = (null == oldSuggest) ? null :
+ oldSuggest.getPersonalizationDictionary();
+ final UserHistoryDictionary userHistoryDictionaryToUse;
+ final PersonalizationDictionary personalizationDictionaryToUse;
+ if (!shouldSetDictionaries) {
+ userHistoryDictionaryToUse = null;
+ personalizationDictionaryToUse = null;
+ } else {
+ if (null != oldUserHistoryDictionary
+ && oldUserHistoryDictionary.mLocale.equals(mLocale)) {
+ userHistoryDictionaryToUse = oldUserHistoryDictionary;
+ } else {
+ userHistoryDictionaryToUse =
+ PersonalizationHelper.getUserHistoryDictionary(mContext, mLocale);
+ }
+ if (null != oldPersonalizationDictionary
+ && oldPersonalizationDictionary.mLocale.equals(mLocale)) {
+ personalizationDictionaryToUse = oldPersonalizationDictionary;
+ } else {
+ personalizationDictionaryToUse =
+ PersonalizationHelper.getPersonalizationDictionary(mContext, mLocale);
+ }
+ }
+ setUserHistoryDictionary(userHistoryDictionaryToUse);
+ setPersonalizationDictionary(personalizationDictionaryToUse);
+ }
+
+ /**
+ * Set the contacts dictionary according to the user settings.
+ *
+ * This method takes an optional contacts dictionary to use when the locale hasn't changed
+ * since the contacts dictionary can be opened or closed as necessary depending on the settings.
+ *
+ * @param oldContactsDictionary an optional dictionary to use, or null
+ * @param settingsValues current settingsValues
+ */
+ private void resetContactsDictionary(final ContactsBinaryDictionary oldContactsDictionary,
+ final SettingsValues settingsValues) {
+ final boolean shouldSetDictionary = settingsValues.mUseContactsDict;
+ final ContactsBinaryDictionary dictionaryToUse;
+ if (!shouldSetDictionary) {
+ // Make sure the dictionary is closed. If it is already closed, this is a no-op,
+ // so it's safe to call it anyways.
+ if (null != oldContactsDictionary) oldContactsDictionary.close();
+ dictionaryToUse = null;
+ } else {
+ if (null != oldContactsDictionary) {
+ if (!oldContactsDictionary.mLocale.equals(mLocale)) {
+ // If the locale has changed then recreate the contacts dictionary. This
+ // allows locale dependent rules for handling bigram name predictions.
+ oldContactsDictionary.close();
+ dictionaryToUse = new ContactsBinaryDictionary(mContext, mLocale);
+ } else {
+ // Make sure the old contacts dictionary is opened. If it is already open,
+ // this is a no-op, so it's safe to call it anyways.
+ oldContactsDictionary.reopen(mContext);
+ dictionaryToUse = oldContactsDictionary;
+ }
+ } else {
+ dictionaryToUse = new ContactsBinaryDictionary(mContext, mLocale);
+ }
+ }
+ setContactsDictionary(dictionaryToUse);
+ }
+
+ public void cancelAddingUserHistory(final String previousWord, final String committedWord) {
+ if (mUserHistoryDictionary != null) {
+ mUserHistoryDictionary.cancelAddingUserHistory(previousWord, committedWord);
+ }
}
public void setAutoCorrectionThreshold(float threshold) {
@@ -432,7 +543,8 @@ public final class Suggest {
final String scoreInfoString;
if (normalizedScore > 0) {
scoreInfoString = String.format(
- Locale.ROOT, "%d (%4.2f)", cur.mScore, normalizedScore);
+ Locale.ROOT, "%d (%4.2f), %s", cur.mScore, normalizedScore,
+ cur.mSourceDict.mDictType);
} else {
scoreInfoString = Integer.toString(cur.mScore);
}
diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
index 6405b5e46..9cb2f5bc4 100644
--- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
@@ -22,14 +22,15 @@ import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import java.util.ArrayList;
+import java.util.Locale;
public final class SynchronouslyLoadedUserBinaryDictionary extends UserBinaryDictionary {
- public SynchronouslyLoadedUserBinaryDictionary(final Context context, final String locale) {
+ public SynchronouslyLoadedUserBinaryDictionary(final Context context, final Locale locale) {
this(context, locale, false);
}
- public SynchronouslyLoadedUserBinaryDictionary(final Context context, final String locale,
+ public SynchronouslyLoadedUserBinaryDictionary(final Context context, final Locale locale,
final boolean alsoUseMoreRestrictiveLocales) {
super(context, locale, alsoUseMoreRestrictiveLocales);
}
diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index 15b3d8d02..6fc98220f 100644
--- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
@@ -18,7 +18,6 @@ package com.android.inputmethod.latin;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
-import android.content.ContentUris;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
@@ -33,6 +32,7 @@ import com.android.inputmethod.compat.UserDictionaryCompatUtils;
import com.android.inputmethod.latin.utils.LocaleUtils;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
+import java.io.File;
import java.util.Arrays;
import java.util.Locale;
@@ -75,20 +75,26 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
final private String mLocale;
final private boolean mAlsoUseMoreRestrictiveLocales;
- public UserBinaryDictionary(final Context context, final String locale) {
+ public UserBinaryDictionary(final Context context, final Locale locale) {
this(context, locale, false);
}
- public UserBinaryDictionary(final Context context, final String locale,
+ // Dummy constructor for tests.
+ public UserBinaryDictionary(final Context context, final Locale locale, final File file) {
+ this(context, locale);
+ }
+
+ public UserBinaryDictionary(final Context context, final Locale locale,
final boolean alsoUseMoreRestrictiveLocales) {
- super(context, getFilenameWithLocale(NAME, locale), Dictionary.TYPE_USER,
+ super(context, getDictNameWithLocale(NAME, locale), locale, Dictionary.TYPE_USER,
false /* isUpdatable */);
if (null == locale) throw new NullPointerException(); // Catch the error earlier
- if (SubtypeLocaleUtils.NO_LANGUAGE.equals(locale)) {
+ final String localeStr = locale.toString();
+ if (SubtypeLocaleUtils.NO_LANGUAGE.equals(localeStr)) {
// If we don't have a locale, insert into the "all locales" user dictionary.
mLocale = USER_DICTIONARY_ALL_LANGUAGES;
} else {
- mLocale = locale;
+ mLocale = localeStr;
}
mAlsoUseMoreRestrictiveLocales = alsoUseMoreRestrictiveLocales;
// Perform a managed query. The Activity will handle closing and re-querying the cursor
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 039dadc66..7da97e57a 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -48,6 +48,11 @@ public final class WordComposer {
// at any given time. However this is not limited in size, while mPrimaryKeyCodes is limited
// to MAX_WORD_LENGTH code points.
private final StringBuilder mTypedWord;
+ // The previous word (before the composing word). Used as context for suggestions. May be null
+ // after resetting and before starting a new composing word, or when there is no context like
+ // at the start of text for example. It can also be set to null externally when the user
+ // enters a separator that does not let bigrams across, like a period or a comma.
+ private String mPreviousWordForSuggestion;
private String mAutoCorrection;
private boolean mIsResumed;
private boolean mIsBatchMode;
@@ -85,6 +90,7 @@ public final class WordComposer {
mIsBatchMode = false;
mCursorPositionWithinWord = 0;
mRejectedBatchModeSuggestion = null;
+ mPreviousWordForSuggestion = null;
refreshSize();
}
@@ -101,6 +107,7 @@ public final class WordComposer {
mIsBatchMode = source.mIsBatchMode;
mCursorPositionWithinWord = source.mCursorPositionWithinWord;
mRejectedBatchModeSuggestion = source.mRejectedBatchModeSuggestion;
+ mPreviousWordForSuggestion = source.mPreviousWordForSuggestion;
refreshSize();
}
@@ -118,6 +125,7 @@ public final class WordComposer {
mIsBatchMode = false;
mCursorPositionWithinWord = 0;
mRejectedBatchModeSuggestion = null;
+ mPreviousWordForSuggestion = null;
refreshSize();
}
@@ -284,8 +292,13 @@ public final class WordComposer {
/**
* Set the currently composing word to the one passed as an argument.
* This will register NOT_A_COORDINATE for X and Ys, and use the passed keyboard for proximity.
+ * @param word the char sequence to set as the composing word.
+ * @param previousWord the previous word, to use as context for suggestions. Can be null if
+ * the context is nil (typically, at start of text).
+ * @param keyboard the keyboard this is typed on, for coordinate info/proximity.
*/
- public void setComposingWord(final CharSequence word, final Keyboard keyboard) {
+ public void setComposingWord(final CharSequence word, final String previousWord,
+ final Keyboard keyboard) {
reset();
final int length = word.length();
for (int i = 0; i < length; i = Character.offsetByCodePoints(word, i, 1)) {
@@ -293,6 +306,7 @@ public final class WordComposer {
addKeyInfo(codePoint, keyboard);
}
mIsResumed = true;
+ mPreviousWordForSuggestion = previousWord;
}
/**
@@ -343,6 +357,10 @@ public final class WordComposer {
return mTypedWord.toString();
}
+ public String getPreviousWordForSuggestion() {
+ return mPreviousWordForSuggestion;
+ }
+
/**
* Whether or not the user typed a capital letter as the first letter in the word
* @return capitalization preference
@@ -388,18 +406,21 @@ public final class WordComposer {
}
/**
- * Saves the caps mode at the start of composing.
+ * Saves the caps mode and the previous word at the start of composing.
*
- * WordComposer needs to know about this for several reasons. The first is, we need to know
- * after the fact what the reason was, to register the correct form into the user history
- * dictionary: if the word was automatically capitalized, we should insert it in all-lower
- * case but if it's a manual pressing of shift, then it should be inserted as is.
+ * WordComposer needs to know about the caps mode for several reasons. The first is, we need
+ * to know after the fact what the reason was, to register the correct form into the user
+ * history dictionary: if the word was automatically capitalized, we should insert it in
+ * all-lower case but if it's a manual pressing of shift, then it should be inserted as is.
* Also, batch input needs to know about the current caps mode to display correctly
* capitalized suggestions.
* @param mode the mode at the time of start
+ * @param previousWord the previous word as context for suggestions. May be null if none.
*/
- public void setCapitalizedModeAtStartComposingTime(final int mode) {
+ public void setCapitalizedModeAndPreviousWordAtStartComposingTime(final int mode,
+ final String previousWord) {
mCapitalizedMode = mode;
+ mPreviousWordForSuggestion = previousWord;
}
/**
@@ -451,6 +472,7 @@ public final class WordComposer {
mCapsCount = 0;
mDigitsCount = 0;
mIsBatchMode = false;
+ mPreviousWordForSuggestion = mTypedWord.toString();
mTypedWord.setLength(0);
mCodePointSize = 0;
mTrailingSingleQuotesCount = 0;
@@ -464,7 +486,15 @@ public final class WordComposer {
return lastComposedWord;
}
- public void resumeSuggestionOnLastComposedWord(final LastComposedWord lastComposedWord) {
+ // Call this when the recorded previous word should be discarded. This is typically called
+ // when the user inputs a separator that's not whitespace (including the case of the
+ // double-space-to-period feature).
+ public void discardPreviousWordForSuggestion() {
+ mPreviousWordForSuggestion = null;
+ }
+
+ public void resumeSuggestionOnLastComposedWord(final LastComposedWord lastComposedWord,
+ final String previousWord) {
mPrimaryKeyCodes = lastComposedWord.mPrimaryKeyCodes;
mInputPointers.set(lastComposedWord.mInputPointers);
mTypedWord.setLength(0);
@@ -475,6 +505,7 @@ public final class WordComposer {
mCursorPositionWithinWord = mCodePointSize;
mRejectedBatchModeSuggestion = null;
mIsResumed = true;
+ mPreviousWordForSuggestion = previousWord;
}
public boolean isBatchMode() {
diff --git a/java/src/com/android/inputmethod/latin/makedict/AbstractDictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/AbstractDictDecoder.java
index fda97dafc..f8fa68f45 100644
--- a/java/src/com/android/inputmethod/latin/makedict/AbstractDictDecoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/AbstractDictDecoder.java
@@ -32,36 +32,35 @@ import java.util.TreeMap;
* A base class of the binary dictionary decoder.
*/
public abstract class AbstractDictDecoder implements DictDecoder {
- protected FileHeader readHeader(final DictBuffer dictBuffer)
+ private static final int SUCCESS = 0;
+ private static final int ERROR_CANNOT_READ = 1;
+ private static final int ERROR_WRONG_FORMAT = 2;
+
+ protected FileHeader readHeader(final DictBuffer headerBuffer)
throws IOException, UnsupportedFormatException {
- if (dictBuffer == null) {
+ if (headerBuffer == null) {
openDictBuffer();
}
- final int version = HeaderReader.readVersion(dictBuffer);
+ final int version = HeaderReader.readVersion(headerBuffer);
if (version < FormatSpec.MINIMUM_SUPPORTED_VERSION
|| version > FormatSpec.MAXIMUM_SUPPORTED_VERSION) {
throw new UnsupportedFormatException("Unsupported version : " + version);
}
// TODO: Remove this field.
- final int optionsFlags = HeaderReader.readOptionFlags(dictBuffer);
-
- final int headerSize = HeaderReader.readHeaderSize(dictBuffer);
-
+ final int optionsFlags = HeaderReader.readOptionFlags(headerBuffer);
+ final int headerSize = HeaderReader.readHeaderSize(headerBuffer);
if (headerSize < 0) {
throw new UnsupportedFormatException("header size can't be negative.");
}
- final HashMap<String, String> attributes = HeaderReader.readAttributes(dictBuffer,
+ final HashMap<String, String> attributes = HeaderReader.readAttributes(headerBuffer,
headerSize);
final FileHeader header = new FileHeader(headerSize,
- new FusionDictionary.DictionaryOptions(attributes,
- 0 != (optionsFlags & FormatSpec.GERMAN_UMLAUT_PROCESSING_FLAG),
- 0 != (optionsFlags & FormatSpec.FRENCH_LIGATURE_PROCESSING_FLAG)),
- new FormatOptions(version,
- 0 != (optionsFlags & FormatSpec.SUPPORTS_DYNAMIC_UPDATE),
- 0 != (optionsFlags & FormatSpec.CONTAINS_TIMESTAMP_FLAG)));
+ new FusionDictionary.DictionaryOptions(attributes),
+ new FormatOptions(version,
+ 0 != (optionsFlags & FormatSpec.CONTAINS_TIMESTAMP_FLAG)));
return header;
}
@@ -204,4 +203,25 @@ public abstract class AbstractDictDecoder implements DictDecoder {
return readLength;
}
}
+
+ /**
+ * Check whether the header contains the expected information. This is a no-error method,
+ * that will return an error code and never throw a checked exception.
+ * @return an error code, either ERROR_* or SUCCESS.
+ */
+ private int checkHeader() {
+ try {
+ readHeader();
+ } catch (IOException e) {
+ return ERROR_CANNOT_READ;
+ } catch (UnsupportedFormatException e) {
+ return ERROR_WRONG_FORMAT;
+ }
+ return SUCCESS;
+ }
+
+ @Override
+ public boolean hasValidRawBinaryDictionary() {
+ return checkHeader() == SUCCESS;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
index 216492b4d..2dbb5eb93 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
@@ -24,12 +24,9 @@ import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Map;
import java.util.TreeMap;
@@ -61,6 +58,7 @@ public final class BinaryDictDecoderUtils {
public int readInt();
public int position();
public void position(int newPosition);
+ @UsedForTesting
public void put(final byte b);
public int limit();
@UsedForTesting
@@ -169,6 +167,15 @@ public final class BinaryDictDecoderUtils {
return size;
}
+ @UsedForTesting
+ static int getCharArraySize(final int[] chars, final int start, final int end) {
+ int size = 0;
+ for (int i = start; i < end; ++i) {
+ size += getCharSize(chars[i]);
+ }
+ return size;
+ }
+
/**
* Writes a char array to a byte buffer.
*
@@ -200,8 +207,7 @@ public final class BinaryDictDecoderUtils {
* @param word the string to write.
* @return the size written, in bytes.
*/
- static int writeString(final byte[] buffer, final int origin,
- final String word) {
+ static int writeString(final byte[] buffer, final int origin, final String word) {
final int length = word.length();
int index = origin;
for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
@@ -223,22 +229,63 @@ public final class BinaryDictDecoderUtils {
*
* This will also write the terminator byte.
*
- * @param buffer the OutputStream to write to.
+ * @param stream the OutputStream to write to.
* @param word the string to write.
+ * @return the size written, in bytes.
*/
- static void writeString(final OutputStream buffer, final String word) throws IOException {
+ static int writeString(final OutputStream stream, final String word) throws IOException {
final int length = word.length();
+ int written = 0;
for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
final int codePoint = word.codePointAt(i);
- if (1 == getCharSize(codePoint)) {
- buffer.write((byte) codePoint);
+ final int charSize = getCharSize(codePoint);
+ if (1 == charSize) {
+ stream.write((byte) codePoint);
+ } else {
+ stream.write((byte) (0xFF & (codePoint >> 16)));
+ stream.write((byte) (0xFF & (codePoint >> 8)));
+ stream.write((byte) (0xFF & codePoint));
+ }
+ written += charSize;
+ }
+ stream.write(FormatSpec.PTNODE_CHARACTERS_TERMINATOR);
+ written += FormatSpec.PTNODE_TERMINATOR_SIZE;
+ return written;
+ }
+
+ /**
+ * Writes an array of code points with our character format to an OutputStream.
+ *
+ * This will also write the terminator byte.
+ *
+ * @param stream the OutputStream to write to.
+ * @param codePoints the array of code points
+ * @return the size written, in bytes.
+ */
+ // TODO: Merge this method with writeCharArray and rename the various write* methods to
+ // make the difference clear.
+ @UsedForTesting
+ static int writeCodePoints(final OutputStream stream, final int[] codePoints,
+ final int startIndex, final int endIndex)
+ throws IOException {
+ int written = 0;
+ for (int i = startIndex; i < endIndex; ++i) {
+ final int codePoint = codePoints[i];
+ final int charSize = getCharSize(codePoint);
+ if (1 == charSize) {
+ stream.write((byte) codePoint);
} else {
- buffer.write((byte) (0xFF & (codePoint >> 16)));
- buffer.write((byte) (0xFF & (codePoint >> 8)));
- buffer.write((byte) (0xFF & codePoint));
+ stream.write((byte) (0xFF & (codePoint >> 16)));
+ stream.write((byte) (0xFF & (codePoint >> 8)));
+ stream.write((byte) (0xFF & codePoint));
}
+ written += charSize;
+ }
+ if (endIndex - startIndex > 1) {
+ stream.write(FormatSpec.PTNODE_CHARACTERS_TERMINATOR);
+ written += FormatSpec.PTNODE_TERMINATOR_SIZE;
}
- buffer.write(FormatSpec.PTNODE_CHARACTERS_TERMINATOR);
+ return written;
}
/**
@@ -286,7 +333,7 @@ public final class BinaryDictDecoderUtils {
static int readChildrenAddress(final DictBuffer dictBuffer,
final int optionFlags, final FormatOptions options) {
- if (options.mSupportsDynamicUpdate) {
+ if (options.supportsDynamicUpdate()) {
final int address = dictBuffer.readUnsignedInt24();
if (address == 0) return FormatSpec.NO_CHILDREN_ADDRESS;
if ((address & FormatSpec.MSB24) != 0) {
@@ -392,7 +439,7 @@ public final class BinaryDictDecoderUtils {
final FormatOptions options) {
dictDecoder.setPosition(headerSize);
final int count = dictDecoder.readPtNodeCount();
- int groupPos = headerSize + BinaryDictIOUtils.getPtNodeCountSize(count);
+ int groupPos = dictDecoder.getPosition();
final StringBuilder builder = new StringBuilder();
WeightedString result = null;
@@ -454,9 +501,9 @@ public final class BinaryDictDecoderUtils {
do { // Scan the linked-list node.
final int nodeArrayHeadPos = dictDecoder.getPosition();
final int count = dictDecoder.readPtNodeCount();
- int groupOffsetPos = nodeArrayHeadPos + BinaryDictIOUtils.getPtNodeCountSize(count);
+ int groupPos = dictDecoder.getPosition();
for (int i = count; i > 0; --i) { // Scan the array of PtNode.
- PtNodeInfo info = dictDecoder.readPtNode(groupOffsetPos, options);
+ PtNodeInfo info = dictDecoder.readPtNode(groupPos, options);
if (BinaryDictIOUtils.isMovedPtNode(info.mFlags, options)) continue;
ArrayList<WeightedString> shortcutTargets = info.mShortcutTargets;
ArrayList<WeightedString> bigrams = null;
@@ -492,15 +539,15 @@ public final class BinaryDictDecoderUtils {
0 != (info.mFlags & FormatSpec.FLAG_IS_NOT_A_WORD),
0 != (info.mFlags & FormatSpec.FLAG_IS_BLACKLISTED)));
}
- groupOffsetPos = info.mEndAddress;
+ groupPos = info.mEndAddress;
}
// reach the end of the array.
- if (options.mSupportsDynamicUpdate) {
+ if (options.supportsDynamicUpdate()) {
final boolean hasValidForwardLink = dictDecoder.readAndFollowForwardLink();
if (!hasValidForwardLink) break;
}
- } while (options.mSupportsDynamicUpdate && dictDecoder.hasNextPtNodeArray());
+ } while (options.supportsDynamicUpdate() && dictDecoder.hasNextPtNodeArray());
final PtNodeArray nodeArray = new PtNodeArray(nodeArrayContents);
nodeArray.mCachedAddressBeforeUpdate = nodeArrayOriginPos;
@@ -556,7 +603,7 @@ public final class BinaryDictDecoderUtils {
Map<Integer, PtNodeArray> reverseNodeArrayMapping = new TreeMap<Integer, PtNodeArray>();
Map<Integer, PtNode> reversePtNodeMapping = new TreeMap<Integer, PtNode>();
- final PtNodeArray root = readNodeArray(dictDecoder, fileHeader.mHeaderSize,
+ final PtNodeArray root = readNodeArray(dictDecoder, fileHeader.mBodyOffset,
reverseNodeArrayMapping, reversePtNodeMapping, fileHeader.mFormatOptions);
FusionDictionary newDict = new FusionDictionary(root, fileHeader.mDictionaryOptions);
@@ -592,32 +639,10 @@ public final class BinaryDictDecoderUtils {
/**
* Basic test to find out whether the file is a binary dictionary or not.
*
- * Concretely this only tests the magic number.
- *
* @param file The file to test.
* @return true if it's a binary dictionary, false otherwise
*/
public static boolean isBinaryDictionary(final File file) {
- FileInputStream inStream = null;
- try {
- inStream = new FileInputStream(file);
- final ByteBuffer buffer = inStream.getChannel().map(
- FileChannel.MapMode.READ_ONLY, 0, file.length());
- final int version = getFormatVersion(new ByteBufferDictBuffer(buffer));
- return (version >= FormatSpec.MINIMUM_SUPPORTED_VERSION
- && version <= FormatSpec.MAXIMUM_SUPPORTED_VERSION);
- } catch (FileNotFoundException e) {
- return false;
- } catch (IOException e) {
- return false;
- } finally {
- if (inStream != null) {
- try {
- inStream.close();
- } catch (IOException e) {
- // do nothing
- }
- }
- }
+ return FormatSpec.getDictDecoder(file).hasValidRawBinaryDictionary();
}
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
index f761829de..bb40e0dd5 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
@@ -16,10 +16,11 @@
package com.android.inputmethod.latin.makedict;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding;
+import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer;
import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
-import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions;
import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
@@ -160,7 +161,7 @@ public class BinaryDictEncoderUtils {
node.mCachedSize = nodeSize;
size += nodeSize;
}
- if (options.mSupportsDynamicUpdate) {
+ if (options.supportsDynamicUpdate()) {
size += FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
}
ptNodeArray.mCachedSize = size;
@@ -245,6 +246,27 @@ public class BinaryDictEncoderUtils {
}
}
+ @UsedForTesting
+ static void writeUIntToDictBuffer(final DictBuffer dictBuffer, final int value,
+ final int size) {
+ switch(size) {
+ case 4:
+ dictBuffer.put((byte) ((value >> 24) & 0xFF));
+ /* fall through */
+ case 3:
+ dictBuffer.put((byte) ((value >> 16) & 0xFF));
+ /* fall through */
+ case 2:
+ dictBuffer.put((byte) ((value >> 8) & 0xFF));
+ /* fall through */
+ case 1:
+ dictBuffer.put((byte) (value & 0xFF));
+ break;
+ default:
+ /* nop */
+ }
+ }
+
// End utility methods
// This method is responsible for finding a nice ordering of the nodes that favors run-time
@@ -377,7 +399,7 @@ public class BinaryDictEncoderUtils {
nodeSize += FormatSpec.PTNODE_FREQUENCY_SIZE;
}
}
- if (formatOptions.mSupportsDynamicUpdate) {
+ if (formatOptions.supportsDynamicUpdate()) {
nodeSize += FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE;
} else if (null != ptNode.mChildren) {
nodeSize += getByteSize(getOffsetToTargetNodeArrayDuringUpdate(ptNodeArray,
@@ -397,7 +419,7 @@ public class BinaryDictEncoderUtils {
ptNode.mCachedSize = nodeSize;
size += nodeSize;
}
- if (formatOptions.mSupportsDynamicUpdate) {
+ if (formatOptions.supportsDynamicUpdate()) {
size += FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
}
if (ptNodeArray.mCachedSize != size) {
@@ -513,7 +535,7 @@ public class BinaryDictEncoderUtils {
if (passes > MAX_PASSES) throw new RuntimeException("Too many passes - probably a bug");
} while (changesDone);
- if (formatOptions.mSupportsDynamicUpdate) {
+ if (formatOptions.supportsDynamicUpdate()) {
computeParentAddresses(flatNodes);
}
final PtNodeArray lastPtNodeArray = flatNodes.get(flatNodes.size() - 1);
@@ -622,7 +644,7 @@ public class BinaryDictEncoderUtils {
byte flags = 0;
if (hasMultipleChars) flags |= FormatSpec.FLAG_HAS_MULTIPLE_CHARS;
if (isTerminal) flags |= FormatSpec.FLAG_IS_TERMINAL;
- if (formatOptions.mSupportsDynamicUpdate) {
+ if (formatOptions.supportsDynamicUpdate()) {
flags |= FormatSpec.FLAG_IS_NOT_MOVED;
} else if (true) {
switch (childrenAddressSize) {
@@ -690,6 +712,13 @@ public class BinaryDictEncoderUtils {
+ word + " is " + unigramFrequency);
bigramFrequency = unigramFrequency;
}
+ bigramFlags += getBigramFrequencyDiff(unigramFrequency, bigramFrequency)
+ & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY;
+ return bigramFlags;
+ }
+
+ public static int getBigramFrequencyDiff(final int unigramFrequency,
+ final int bigramFrequency) {
// We compute the difference between 255 (which means probability = 1) and the
// unigram score. We split this into a number of discrete steps.
// Now, the steps are numbered 0~15; 0 represents an increase of 1 step while 15
@@ -723,22 +752,15 @@ public class BinaryDictEncoderUtils {
// include this bigram in the dictionary. For now, register as 0, and live with the
// small over-estimation that we get in this case. TODO: actually remove this bigram
// if discretizedFrequency < 0.
- final int finalBigramFrequency = discretizedFrequency > 0 ? discretizedFrequency : 0;
- bigramFlags += finalBigramFrequency & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY;
- return bigramFlags;
+ return discretizedFrequency > 0 ? discretizedFrequency : 0;
}
/**
- * Makes the 2-byte value for options flags.
+ * Makes the 2-byte value for options flags. Unused at the moment, and always 0.
*/
- private static final int makeOptionsValue(final FusionDictionary dictionary,
- final FormatOptions formatOptions) {
- final DictionaryOptions options = dictionary.mOptions;
- final boolean hasBigrams = dictionary.hasBigrams();
- return (options.mFrenchLigatureProcessing ? FormatSpec.FRENCH_LIGATURE_PROCESSING_FLAG : 0)
- + (options.mGermanUmlautProcessing ? FormatSpec.GERMAN_UMLAUT_PROCESSING_FLAG : 0)
- + (hasBigrams ? FormatSpec.CONTAINS_BIGRAMS_FLAG : 0)
- + (formatOptions.mSupportsDynamicUpdate ? FormatSpec.SUPPORTS_DYNAMIC_UPDATE : 0);
+ private static final int makeOptionsValue(final FormatOptions formatOptions) {
+ // TODO: why doesn't this handle CONTAINS_TIMESTAMP_FLAG?
+ return 0;
}
/**
@@ -826,7 +848,7 @@ public class BinaryDictEncoderUtils {
}
dictEncoder.writePtNode(ptNode, parentPosition, formatOptions, dict);
}
- if (formatOptions.mSupportsDynamicUpdate) {
+ if (formatOptions.supportsDynamicUpdate()) {
dictEncoder.writeForwardLinkAddress(FormatSpec.NO_FORWARD_LINK_ADDRESS);
}
if (dictEncoder.getPosition() != ptNodeArray.mCachedAddressAfterUpdate
@@ -927,7 +949,7 @@ public class BinaryDictEncoderUtils {
headerBuffer.write((byte) (0xFF & version));
// Options flags
- final int options = makeOptionsValue(dict, formatOptions);
+ final int options = makeOptionsValue(formatOptions);
headerBuffer.write((byte) (0xFF & (options >> 8)));
headerBuffer.write((byte) (0xFF & options));
final int headerSizeOffset = headerBuffer.size();
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
index d5516ef46..07ba777c7 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
@@ -23,7 +23,6 @@ import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer;
import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
-import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
import com.android.inputmethod.latin.utils.ByteArrayDictBuffer;
import java.io.File;
@@ -32,7 +31,6 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
-import java.util.Iterator;
import java.util.Map;
import java.util.Stack;
@@ -62,7 +60,7 @@ public final class BinaryDictIOUtils {
* Retrieves all node arrays without recursive call.
*/
private static void readUnigramsAndBigramsBinaryInner(final DictDecoder dictDecoder,
- final int headerSize, final Map<Integer, String> words,
+ final int bodyOffset, final Map<Integer, String> words,
final Map<Integer, Integer> frequencies,
final Map<Integer, ArrayList<PendingAttribute>> bigrams,
final FormatOptions formatOptions) {
@@ -71,7 +69,7 @@ public final class BinaryDictIOUtils {
Stack<Position> stack = new Stack<Position>();
int index = 0;
- Position initPos = new Position(headerSize, 0);
+ Position initPos = new Position(bodyOffset, 0);
stack.push(initPos);
while (!stack.empty()) {
@@ -87,7 +85,7 @@ public final class BinaryDictIOUtils {
if (p.mNumOfPtNode == Position.NOT_READ_PTNODE_COUNT) {
p.mNumOfPtNode = dictDecoder.readPtNodeCount();
- p.mAddress += getPtNodeCountSize(p.mNumOfPtNode);
+ p.mAddress = dictDecoder.getPosition();
p.mPosition = 0;
}
if (p.mNumOfPtNode == 0) {
@@ -112,7 +110,7 @@ public final class BinaryDictIOUtils {
}
if (p.mPosition == p.mNumOfPtNode) {
- if (formatOptions.mSupportsDynamicUpdate) {
+ if (formatOptions.supportsDynamicUpdate()) {
final boolean hasValidForwardLinkAddress =
dictDecoder.readAndFollowForwardLink();
if (hasValidForwardLinkAddress && dictDecoder.hasNextPtNodeArray()) {
@@ -154,7 +152,7 @@ public final class BinaryDictIOUtils {
UnsupportedFormatException {
// Read header
final FileHeader header = dictDecoder.readHeader();
- readUnigramsAndBigramsBinaryInner(dictDecoder, header.mHeaderSize, words,
+ readUnigramsAndBigramsBinaryInner(dictDecoder, header.mBodyOffset, words,
frequencies, bigrams, header.mFormatOptions);
}
@@ -228,7 +226,7 @@ public final class BinaryDictIOUtils {
// a forward link address that we need to consult and possibly resume
// search on the next node array in the linked list.
if (foundNextPtNode) break;
- if (!header.mFormatOptions.mSupportsDynamicUpdate) {
+ if (!header.mFormatOptions.supportsDynamicUpdate()) {
return FormatSpec.NOT_VALID_WORD;
}
@@ -245,8 +243,8 @@ public final class BinaryDictIOUtils {
/**
* @return the size written, in bytes. Always 3 bytes.
*/
- static int writeSInt24ToBuffer(final DictBuffer dictBuffer,
- final int value) {
+ @UsedForTesting
+ static int writeSInt24ToBuffer(final DictBuffer dictBuffer, final int value) {
final int absValue = Math.abs(value);
dictBuffer.put((byte)(((value < 0 ? 0x80 : 0) | (absValue >> 16)) & 0xFF));
dictBuffer.put((byte)((absValue >> 8) & 0xFF));
@@ -257,6 +255,7 @@ public final class BinaryDictIOUtils {
/**
* @return the size written, in bytes. Always 3 bytes.
*/
+ @UsedForTesting
static int writeSInt24ToStream(final OutputStream destination, final int value)
throws IOException {
final int absValue = Math.abs(value);
@@ -266,28 +265,7 @@ public final class BinaryDictIOUtils {
return 3;
}
- /**
- * @return the size written, in bytes. 1, 2, or 3 bytes.
- */
- private static int writeVariableAddress(final OutputStream destination, final int value)
- throws IOException {
- switch (BinaryDictEncoderUtils.getByteSize(value)) {
- case 1:
- destination.write((byte)value);
- break;
- case 2:
- destination.write((byte)(0xFF & (value >> 8)));
- destination.write((byte)(0xFF & value));
- break;
- case 3:
- destination.write((byte)(0xFF & (value >> 16)));
- destination.write((byte)(0xFF & (value >> 8)));
- destination.write((byte)(0xFF & value));
- break;
- }
- return BinaryDictEncoderUtils.getByteSize(value);
- }
-
+ @UsedForTesting
static void skipString(final DictBuffer dictBuffer,
final boolean hasMultipleChars) {
if (hasMultipleChars) {
@@ -301,176 +279,25 @@ public final class BinaryDictIOUtils {
}
/**
- * Write a string to a stream.
- *
- * @param destination the stream to write.
- * @param word the string to be written.
- * @return the size written, in bytes.
- * @throws IOException
- */
- private static int writeString(final OutputStream destination, final String word)
- throws IOException {
- int size = 0;
- final int length = word.length();
- for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
- final int codePoint = word.codePointAt(i);
- if (CharEncoding.getCharSize(codePoint) == 1) {
- destination.write((byte)codePoint);
- size++;
- } else {
- destination.write((byte)(0xFF & (codePoint >> 16)));
- destination.write((byte)(0xFF & (codePoint >> 8)));
- destination.write((byte)(0xFF & codePoint));
- size += 3;
- }
- }
- destination.write((byte)FormatSpec.PTNODE_CHARACTERS_TERMINATOR);
- size += FormatSpec.PTNODE_TERMINATOR_SIZE;
- return size;
- }
-
- /**
- * Write a PtNode to an output stream from a PtNodeInfo.
- * A PtNode is an in-memory representation of a node in the patricia trie.
- * A PtNode info is a container for low-level information about how the
- * PtNode is stored in the binary format.
+ * Writes a PtNodeCount to the stream.
*
* @param destination the stream to write.
- * @param info the PtNode info to be written.
- * @return the size written, in bytes.
+ * @param ptNodeCount the count.
+ * @return the size written in bytes.
*/
- private static int writePtNode(final OutputStream destination, final PtNodeInfo info)
- throws IOException {
- int size = FormatSpec.PTNODE_FLAGS_SIZE;
- destination.write((byte)info.mFlags);
- final int parentOffset = info.mParentAddress == FormatSpec.NO_PARENT_ADDRESS ?
- FormatSpec.NO_PARENT_ADDRESS : info.mParentAddress - info.mOriginalAddress;
- size += writeSInt24ToStream(destination, parentOffset);
-
- for (int i = 0; i < info.mCharacters.length; ++i) {
- if (CharEncoding.getCharSize(info.mCharacters[i]) == 1) {
- destination.write((byte)info.mCharacters[i]);
- size++;
- } else {
- size += writeSInt24ToStream(destination, info.mCharacters[i]);
- }
- }
- if (info.mCharacters.length > 1) {
- destination.write((byte)FormatSpec.PTNODE_CHARACTERS_TERMINATOR);
- size++;
- }
-
- if ((info.mFlags & FormatSpec.FLAG_IS_TERMINAL) != 0) {
- destination.write((byte)info.mFrequency);
- size++;
- }
-
- if (DBG) {
- MakedictLog.d("writePtNode origin=" + info.mOriginalAddress + ", size=" + size
- + ", child=" + info.mChildrenAddress + ", characters ="
- + new String(info.mCharacters, 0, info.mCharacters.length));
- }
- final int childrenOffset = info.mChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS ?
- 0 : info.mChildrenAddress - (info.mOriginalAddress + size);
- writeSInt24ToStream(destination, childrenOffset);
- size += FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE;
-
- if (info.mShortcutTargets != null && info.mShortcutTargets.size() > 0) {
- final int shortcutListSize =
- BinaryDictEncoderUtils.getShortcutListSize(info.mShortcutTargets);
- destination.write((byte)(shortcutListSize >> 8));
- destination.write((byte)(shortcutListSize & 0xFF));
- size += 2;
- final Iterator<WeightedString> shortcutIterator = info.mShortcutTargets.iterator();
- while (shortcutIterator.hasNext()) {
- final WeightedString target = shortcutIterator.next();
- destination.write((byte)BinaryDictEncoderUtils.makeShortcutFlags(
- shortcutIterator.hasNext(), target.mFrequency));
- size++;
- size += writeString(destination, target.mWord);
- }
- }
-
- if (info.mBigrams != null) {
- // TODO: Consolidate this code with the code that computes the size of the bigram list
- // in BinaryDictEncoderUtils#computeActualNodeArraySize
- for (int i = 0; i < info.mBigrams.size(); ++i) {
-
- final int bigramFrequency = info.mBigrams.get(i).mFrequency;
- int bigramFlags = (i < info.mBigrams.size() - 1)
- ? FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT : 0;
- size++;
- final int bigramOffset = info.mBigrams.get(i).mAddress - (info.mOriginalAddress
- + size);
- bigramFlags |= (bigramOffset < 0) ? FormatSpec.FLAG_BIGRAM_ATTR_OFFSET_NEGATIVE : 0;
- switch (BinaryDictEncoderUtils.getByteSize(bigramOffset)) {
- case 1:
- bigramFlags |= FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_ONEBYTE;
- break;
- case 2:
- bigramFlags |= FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_TWOBYTES;
- break;
- case 3:
- bigramFlags |= FormatSpec.FLAG_BIGRAM_ATTR_ADDRESS_TYPE_THREEBYTES;
- break;
- }
- bigramFlags |= bigramFrequency & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY;
- destination.write((byte)bigramFlags);
- size += writeVariableAddress(destination, Math.abs(bigramOffset));
- }
- }
- return size;
- }
-
- /**
- * Compute the size of the PtNode.
- */
- static int computePtNodeSize(final PtNodeInfo info, final FormatOptions formatOptions) {
- int size = FormatSpec.PTNODE_FLAGS_SIZE + FormatSpec.PARENT_ADDRESS_SIZE
- + BinaryDictEncoderUtils.getPtNodeCharactersSize(info.mCharacters)
- + getChildrenAddressSize(info.mFlags, formatOptions);
- if ((info.mFlags & FormatSpec.FLAG_IS_TERMINAL) != 0) {
- size += FormatSpec.PTNODE_FREQUENCY_SIZE;
- }
- if (info.mShortcutTargets != null && !info.mShortcutTargets.isEmpty()) {
- size += BinaryDictEncoderUtils.getShortcutListSize(info.mShortcutTargets);
- }
- if (info.mBigrams != null) {
- for (final PendingAttribute attr : info.mBigrams) {
- size += FormatSpec.PTNODE_FLAGS_SIZE;
- size += BinaryDictEncoderUtils.getByteSize(attr.mAddress);
- }
- }
- return size;
- }
-
- /**
- * Write a node array to the stream.
- *
- * @param destination the stream to write.
- * @param infos an array of PtNodeInfo to be written.
- * @return the size written, in bytes.
- * @throws IOException
- */
- static int writeNodes(final OutputStream destination, final PtNodeInfo[] infos)
+ @UsedForTesting
+ static int writePtNodeCount(final OutputStream destination, final int ptNodeCount)
throws IOException {
- int size = getPtNodeCountSize(infos.length);
- switch (getPtNodeCountSize(infos.length)) {
- case 1:
- destination.write((byte)infos.length);
- break;
- case 2:
- final int encodedPtNodeCount =
- infos.length | FormatSpec.LARGE_PTNODE_ARRAY_SIZE_FIELD_SIZE_FLAG;
- destination.write((byte)(encodedPtNodeCount >> 8));
- destination.write((byte)(encodedPtNodeCount & 0xFF));
- break;
- default:
- throw new RuntimeException("Invalid node count size.");
+ final int countSize = BinaryDictIOUtils.getPtNodeCountSize(ptNodeCount);
+ // the count must fit on one byte or two bytes.
+ // Please see comments in FormatSpec.
+ if (countSize != 1 && countSize != 2) {
+ throw new RuntimeException("Strange size from getPtNodeCountSize : " + countSize);
}
- for (final PtNodeInfo info : infos) size += writePtNode(destination, info);
- writeSInt24ToStream(destination, FormatSpec.NO_FORWARD_LINK_ADDRESS);
- return size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
+ final int encodedPtNodeCount = (countSize == 2) ?
+ (ptNodeCount | FormatSpec.LARGE_PTNODE_ARRAY_SIZE_FIELD_SIZE_FLAG) : ptNodeCount;
+ BinaryDictEncoderUtils.writeUIntToStream(destination, encodedPtNodeCount, countSize);
+ return countSize;
}
private static final int HEADER_READING_BUFFER_SIZE = 16384;
@@ -529,7 +356,7 @@ public final class BinaryDictIOUtils {
* Helper method to check whether the node is moved.
*/
public static boolean isMovedPtNode(final int flags, final FormatOptions options) {
- return options.mSupportsDynamicUpdate
+ return options.supportsDynamicUpdate()
&& ((flags & FormatSpec.MASK_CHILDREN_ADDRESS_TYPE) == FormatSpec.FLAG_IS_MOVED);
}
@@ -538,14 +365,14 @@ public final class BinaryDictIOUtils {
*/
public static boolean supportsDynamicUpdate(final FormatOptions options) {
return options.mVersion >= FormatSpec.FIRST_VERSION_WITH_DYNAMIC_UPDATE
- && options.mSupportsDynamicUpdate;
+ && options.supportsDynamicUpdate();
}
/**
* Helper method to check whether the node is deleted.
*/
public static boolean isDeletedPtNode(final int flags, final FormatOptions formatOptions) {
- return formatOptions.mSupportsDynamicUpdate
+ return formatOptions.supportsDynamicUpdate()
&& ((flags & FormatSpec.MASK_CHILDREN_ADDRESS_TYPE) == FormatSpec.FLAG_IS_DELETED);
}
@@ -568,7 +395,7 @@ public final class BinaryDictIOUtils {
static int getChildrenAddressSize(final int optionFlags,
final FormatOptions formatOptions) {
- if (formatOptions.mSupportsDynamicUpdate) return FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE;
+ if (formatOptions.supportsDynamicUpdate()) return FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE;
switch (optionFlags & FormatSpec.MASK_CHILDREN_ADDRESS_TYPE) {
case FormatSpec.FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE:
return 1;
diff --git a/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java
index 3dbeee099..b4838f00f 100644
--- a/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java
@@ -35,6 +35,7 @@ import java.util.TreeMap;
/**
* An interface of binary dictionary decoders.
*/
+// TODO: Straighten out responsibility for the buffer's file pointer.
public interface DictDecoder {
/**
@@ -43,7 +44,7 @@ public interface DictDecoder {
public FileHeader readHeader() throws IOException, UnsupportedFormatException;
/**
- * Reads PtNode from nodeAddress.
+ * Reads PtNode from ptNodePos.
* @param ptNodePos the position of PtNode.
* @param formatOptions the format options.
* @return PtNodeInfo.
@@ -127,7 +128,8 @@ public interface DictDecoder {
* Opens the dictionary file and makes DictBuffer.
*/
@UsedForTesting
- public void openDictBuffer() throws FileNotFoundException, IOException;
+ public void openDictBuffer() throws FileNotFoundException, IOException,
+ UnsupportedFormatException;
@UsedForTesting
public boolean isDictBufferOpen();
@@ -228,4 +230,9 @@ public interface DictDecoder {
}
public void skipPtNode(final FormatOptions formatOptions);
+
+ /**
+ * @return whether this decoder has a valid binary dictionary that it can decode.
+ */
+ public boolean hasValidRawBinaryDictionary();
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/DictUpdater.java b/java/src/com/android/inputmethod/latin/makedict/DictUpdater.java
deleted file mode 100644
index c4f7ec91f..000000000
--- a/java/src/com/android/inputmethod/latin/makedict/DictUpdater.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2013 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.makedict;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
-
-import java.io.IOException;
-import java.util.ArrayList;
-
-/**
- * An interface of a binary dictionary updater.
- */
-@UsedForTesting
-public interface DictUpdater extends DictDecoder {
-
- /**
- * Deletes the word from the binary dictionary.
- *
- * @param word the word to be deleted.
- */
- @UsedForTesting
- public void deleteWord(final String word) throws IOException, UnsupportedFormatException;
-
- /**
- * Inserts a word into a binary dictionary.
- *
- * @param word the word to be inserted.
- * @param frequency the frequency of the new word.
- * @param bigramStrings bigram list, or null if none.
- * @param shortcuts shortcut list, or null if none.
- * @param isBlackListEntry whether this should be a blacklist entry.
- */
- // TODO: Support batch insertion.
- @UsedForTesting
- public void insertWord(final String word, final int frequency,
- final ArrayList<WeightedString> bigramStrings,
- final ArrayList<WeightedString> shortcuts, final boolean isNotAWord,
- final boolean isBlackListEntry) throws IOException, UnsupportedFormatException;
-}
diff --git a/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java
deleted file mode 100644
index 28da9ffdd..000000000
--- a/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java
+++ /dev/null
@@ -1,492 +0,0 @@
-/*
- * Copyright (C) 2013 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.makedict;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer;
-import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
-import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
-import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
-import com.android.inputmethod.latin.utils.CollectionUtils;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-
-/**
- * The utility class to help dynamic updates on the binary dictionary.
- *
- * All the methods in this class are static.
- */
-@UsedForTesting
-public final class DynamicBinaryDictIOUtils {
- private static final boolean DBG = false;
- private static final int MAX_JUMPS = 10000;
-
- private DynamicBinaryDictIOUtils() {
- // This utility class is not publicly instantiable.
- }
-
- /* package */ static int markAsDeleted(final int flags) {
- return (flags & (~FormatSpec.MASK_CHILDREN_ADDRESS_TYPE)) | FormatSpec.FLAG_IS_DELETED;
- }
-
- /**
- * Update a parent address in a PtNode that is referred to by ptNodeOriginAddress.
- *
- * @param dictUpdater the DictUpdater to write.
- * @param ptNodeOriginAddress the address of the PtNode.
- * @param newParentAddress the absolute address of the parent.
- * @param formatOptions file format options.
- */
- private static void updateParentAddress(final Ver3DictUpdater dictUpdater,
- final int ptNodeOriginAddress, final int newParentAddress,
- final FormatOptions formatOptions) {
- final DictBuffer dictBuffer = dictUpdater.getDictBuffer();
- final int originalPosition = dictBuffer.position();
- dictBuffer.position(ptNodeOriginAddress);
- if (!formatOptions.mSupportsDynamicUpdate) {
- throw new RuntimeException("this file format does not support parent addresses");
- }
- final int flags = dictBuffer.readUnsignedByte();
- if (BinaryDictIOUtils.isMovedPtNode(flags, formatOptions)) {
- // If the node is moved, the parent address is stored in the destination node.
- // We are guaranteed to process the destination node later, so there is no need to
- // update anything here.
- dictBuffer.position(originalPosition);
- return;
- }
- if (DBG) {
- MakedictLog.d("update parent address flags=" + flags + ", " + ptNodeOriginAddress);
- }
- final int parentOffset = newParentAddress - ptNodeOriginAddress;
- BinaryDictIOUtils.writeSInt24ToBuffer(dictBuffer, parentOffset);
- dictBuffer.position(originalPosition);
- }
-
- /**
- * Update parent addresses in a node array stored at ptNodeOriginAddress.
- *
- * @param dictUpdater the DictUpdater to be modified.
- * @param ptNodeOriginAddress the address of the node array to update.
- * @param newParentAddress the address to be written.
- * @param formatOptions file format options.
- */
- private static void updateParentAddresses(final Ver3DictUpdater dictUpdater,
- final int ptNodeOriginAddress, final int newParentAddress,
- final FormatOptions formatOptions) {
- final int originalPosition = dictUpdater.getPosition();
- dictUpdater.setPosition(ptNodeOriginAddress);
- do {
- final int count = dictUpdater.readPtNodeCount();
- for (int i = 0; i < count; ++i) {
- updateParentAddress(dictUpdater, dictUpdater.getPosition(), newParentAddress,
- formatOptions);
- dictUpdater.skipPtNode(formatOptions);
- }
- if (!dictUpdater.readAndFollowForwardLink()) break;
- if (dictUpdater.getPosition() == FormatSpec.NO_FORWARD_LINK_ADDRESS) break;
- } while (formatOptions.mSupportsDynamicUpdate);
- dictUpdater.setPosition(originalPosition);
- }
-
- /**
- * Update a children address in a PtNode that is addressed by ptNodeOriginAddress.
- *
- * @param dictUpdater the DictUpdater to write.
- * @param ptNodeOriginAddress the address of the PtNode.
- * @param newChildrenAddress the absolute address of the child.
- * @param formatOptions file format options.
- */
- private static void updateChildrenAddress(final Ver3DictUpdater dictUpdater,
- final int ptNodeOriginAddress, final int newChildrenAddress,
- final FormatOptions formatOptions) {
- final DictBuffer dictBuffer = dictUpdater.getDictBuffer();
- final int originalPosition = dictBuffer.position();
- dictBuffer.position(ptNodeOriginAddress);
- final int flags = dictBuffer.readUnsignedByte();
- BinaryDictDecoderUtils.readParentAddress(dictBuffer, formatOptions);
- BinaryDictIOUtils.skipString(dictBuffer, (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS) != 0);
- if ((flags & FormatSpec.FLAG_IS_TERMINAL) != 0) dictBuffer.readUnsignedByte();
- final int childrenOffset = newChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS
- ? FormatSpec.NO_CHILDREN_ADDRESS : newChildrenAddress - dictBuffer.position();
- BinaryDictIOUtils.writeSInt24ToBuffer(dictBuffer, childrenOffset);
- dictBuffer.position(originalPosition);
- }
-
- /**
- * Helper method to move a PtNode to the tail of the file.
- */
- private static int movePtNode(final OutputStream destination,
- final Ver3DictUpdater dictUpdater, final PtNodeInfo info,
- final int nodeArrayOriginAddress, final int oldNodeAddress,
- final FormatOptions formatOptions) throws IOException {
- final DictBuffer dictBuffer = dictUpdater.getDictBuffer();
- updateParentAddress(dictUpdater, oldNodeAddress, dictBuffer.limit() + 1, formatOptions);
- dictBuffer.position(oldNodeAddress);
- final int currentFlags = dictBuffer.readUnsignedByte();
- dictBuffer.position(oldNodeAddress);
- dictBuffer.put((byte)(FormatSpec.FLAG_IS_MOVED | (currentFlags
- & (~FormatSpec.MASK_MOVE_AND_DELETE_FLAG))));
- int size = FormatSpec.PTNODE_FLAGS_SIZE;
- updateForwardLink(dictUpdater, nodeArrayOriginAddress, dictBuffer.limit(), formatOptions);
- size += BinaryDictIOUtils.writeNodes(destination, new PtNodeInfo[] { info });
- return size;
- }
-
- @SuppressWarnings("unused")
- private static void updateForwardLink(final Ver3DictUpdater dictUpdater,
- final int nodeArrayOriginAddress, final int newNodeArrayAddress,
- final FormatOptions formatOptions) {
- final DictBuffer dictBuffer = dictUpdater.getDictBuffer();
- dictUpdater.setPosition(nodeArrayOriginAddress);
- int jumpCount = 0;
- while (jumpCount++ < MAX_JUMPS) {
- final int count = dictUpdater.readPtNodeCount();
- for (int i = 0; i < count; ++i) {
- dictUpdater.readPtNode(dictUpdater.getPosition(), formatOptions);
- }
- final int forwardLinkAddress = dictBuffer.readUnsignedInt24();
- if (forwardLinkAddress == FormatSpec.NO_FORWARD_LINK_ADDRESS) {
- dictBuffer.position(dictBuffer.position() - FormatSpec.FORWARD_LINK_ADDRESS_SIZE);
- BinaryDictIOUtils.writeSInt24ToBuffer(dictBuffer, newNodeArrayAddress);
- return;
- }
- dictBuffer.position(forwardLinkAddress);
- }
- if (DBG && jumpCount >= MAX_JUMPS) {
- throw new RuntimeException("too many jumps, probably a bug.");
- }
- }
-
- /**
- * Move a PtNode that is referred to by oldPtNodeOrigin to the tail of the file, and set the
- * children address to the byte after the PtNode.
- *
- * @param fileEndAddress the address of the tail of the file.
- * @param codePoints the characters to put inside the PtNode.
- * @param length how many code points to read from codePoints.
- * @param flags the flags for this PtNode.
- * @param frequency the frequency of this terminal.
- * @param parentAddress the address of the parent PtNode of this PtNode.
- * @param shortcutTargets the shortcut targets for this PtNode.
- * @param bigrams the bigrams for this PtNode.
- * @param destination the stream representing the tail of the file.
- * @param dictUpdater the DictUpdater.
- * @param oldPtNodeArrayOrigin the origin of the old PtNode array this PtNode was a part of.
- * @param oldPtNodeOrigin the old origin where this PtNode used to be stored.
- * @param formatOptions format options for this dictionary.
- * @return the size written, in bytes.
- * @throws IOException if the file can't be accessed
- */
- private static int movePtNode(final int fileEndAddress, final int[] codePoints,
- final int length, final int flags, final int frequency, final int parentAddress,
- final ArrayList<WeightedString> shortcutTargets,
- final ArrayList<PendingAttribute> bigrams, final OutputStream destination,
- final Ver3DictUpdater dictUpdater, final int oldPtNodeArrayOrigin,
- final int oldPtNodeOrigin, final FormatOptions formatOptions) throws IOException {
- int size = 0;
- final int newPtNodeOrigin = fileEndAddress + 1;
- final int[] writtenCharacters = Arrays.copyOfRange(codePoints, 0, length);
- final PtNodeInfo tmpInfo = new PtNodeInfo(newPtNodeOrigin, -1 /* endAddress */,
- flags, writtenCharacters, frequency, parentAddress, FormatSpec.NO_CHILDREN_ADDRESS,
- shortcutTargets, bigrams);
- size = BinaryDictIOUtils.computePtNodeSize(tmpInfo, formatOptions);
- final PtNodeInfo newInfo = new PtNodeInfo(newPtNodeOrigin, newPtNodeOrigin + size,
- flags, writtenCharacters, frequency, parentAddress,
- fileEndAddress + 1 + size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE, shortcutTargets,
- bigrams);
- movePtNode(destination, dictUpdater, newInfo, oldPtNodeArrayOrigin, oldPtNodeOrigin,
- formatOptions);
- return 1 + size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
- }
-
- /**
- * Converts a list of WeightedString to a list of PendingAttribute.
- */
- public static ArrayList<PendingAttribute> resolveBigramPositions(final DictUpdater dictUpdater,
- final ArrayList<WeightedString> bigramStrings)
- throws IOException, UnsupportedFormatException {
- if (bigramStrings == null) return CollectionUtils.newArrayList();
- final ArrayList<PendingAttribute> bigrams = CollectionUtils.newArrayList();
- for (final WeightedString bigram : bigramStrings) {
- final int pos = dictUpdater.getTerminalPosition(bigram.mWord);
- if (pos == FormatSpec.NOT_VALID_WORD) {
- // TODO: figure out what is the correct thing to do here.
- } else {
- bigrams.add(new PendingAttribute(bigram.mFrequency, pos));
- }
- }
- return bigrams;
- }
-
- /**
- * Insert a word into a binary dictionary.
- *
- * @param dictUpdater the dict updater.
- * @param destination a stream to the underlying file, with the pointer at the end of the file.
- * @param word the word to insert.
- * @param frequency the frequency of the new word.
- * @param bigramStrings bigram list, or null if none.
- * @param shortcuts shortcut list, or null if none.
- * @param isBlackListEntry whether this should be a blacklist entry.
- * @throws IOException if the file can't be accessed.
- * @throws UnsupportedFormatException if the existing dictionary is in an unexpected format.
- */
- // TODO: Support batch insertion.
- // TODO: Remove @UsedForTesting once UserHistoryDictionary is implemented by BinaryDictionary.
- @UsedForTesting
- public static void insertWord(final Ver3DictUpdater dictUpdater,
- final OutputStream destination, final String word, final int frequency,
- final ArrayList<WeightedString> bigramStrings,
- final ArrayList<WeightedString> shortcuts, final boolean isNotAWord,
- final boolean isBlackListEntry)
- throws IOException, UnsupportedFormatException {
- final ArrayList<PendingAttribute> bigrams = resolveBigramPositions(dictUpdater,
- bigramStrings);
- final DictBuffer dictBuffer = dictUpdater.getDictBuffer();
-
- final boolean isTerminal = true;
- final boolean hasBigrams = !bigrams.isEmpty();
- final boolean hasShortcuts = shortcuts != null && !shortcuts.isEmpty();
-
- // find the insert position of the word.
- if (dictBuffer.position() != 0) dictBuffer.position(0);
- final FileHeader fileHeader = dictUpdater.readHeader();
-
- int wordPos = 0, address = dictBuffer.position(), nodeOriginAddress = dictBuffer.position();
- final int[] codePoints = FusionDictionary.getCodePoints(word);
- final int wordLen = codePoints.length;
-
- for (int depth = 0; depth < Constants.DICTIONARY_MAX_WORD_LENGTH; ++depth) {
- if (wordPos >= wordLen) break;
- nodeOriginAddress = dictBuffer.position();
- int nodeParentAddress = -1;
- final int ptNodeCount = BinaryDictDecoderUtils.readPtNodeCount(dictBuffer);
- boolean foundNextNode = false;
-
- for (int i = 0; i < ptNodeCount; ++i) {
- address = dictBuffer.position();
- final PtNodeInfo currentInfo = dictUpdater.readPtNode(address,
- fileHeader.mFormatOptions);
- final boolean isMovedNode = BinaryDictIOUtils.isMovedPtNode(currentInfo.mFlags,
- fileHeader.mFormatOptions);
- if (isMovedNode) continue;
- nodeParentAddress = (currentInfo.mParentAddress == FormatSpec.NO_PARENT_ADDRESS)
- ? FormatSpec.NO_PARENT_ADDRESS : currentInfo.mParentAddress + address;
- boolean matched = true;
- for (int p = 0; p < currentInfo.mCharacters.length; ++p) {
- if (wordPos + p >= wordLen) {
- /*
- * splitting
- * before
- * abcd - ef
- *
- * insert "abc"
- *
- * after
- * abc - d - ef
- */
- final int newNodeAddress = dictBuffer.limit();
- final int flags = BinaryDictEncoderUtils.makePtNodeFlags(p > 1,
- isTerminal, 0, hasShortcuts, hasBigrams, false /* isNotAWord */,
- false /* isBlackListEntry */, fileHeader.mFormatOptions);
- int written = movePtNode(newNodeAddress, currentInfo.mCharacters, p, flags,
- frequency, nodeParentAddress, shortcuts, bigrams, destination,
- dictUpdater, nodeOriginAddress, address, fileHeader.mFormatOptions);
-
- final int[] characters2 = Arrays.copyOfRange(currentInfo.mCharacters, p,
- currentInfo.mCharacters.length);
- if (currentInfo.mChildrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) {
- updateParentAddresses(dictUpdater, currentInfo.mChildrenAddress,
- newNodeAddress + written + 1, fileHeader.mFormatOptions);
- }
- final PtNodeInfo newInfo2 = new PtNodeInfo(
- newNodeAddress + written + 1, -1 /* endAddress */,
- currentInfo.mFlags, characters2, currentInfo.mFrequency,
- newNodeAddress + 1, currentInfo.mChildrenAddress,
- currentInfo.mShortcutTargets, currentInfo.mBigrams);
- BinaryDictIOUtils.writeNodes(destination, new PtNodeInfo[] { newInfo2 });
- return;
- } else if (codePoints[wordPos + p] != currentInfo.mCharacters[p]) {
- if (p > 0) {
- /*
- * splitting
- * before
- * ab - cd
- *
- * insert "ac"
- *
- * after
- * a - b - cd
- * |
- * - c
- */
-
- final int newNodeAddress = dictBuffer.limit();
- final int childrenAddress = currentInfo.mChildrenAddress;
-
- // move prefix
- final int prefixFlags = BinaryDictEncoderUtils.makePtNodeFlags(p > 1,
- false /* isTerminal */, 0 /* childrenAddressSize*/,
- false /* hasShortcut */, false /* hasBigrams */,
- false /* isNotAWord */, false /* isBlackListEntry */,
- fileHeader.mFormatOptions);
- int written = movePtNode(newNodeAddress, currentInfo.mCharacters, p,
- prefixFlags, -1 /* frequency */, nodeParentAddress, null, null,
- destination, dictUpdater, nodeOriginAddress, address,
- fileHeader.mFormatOptions);
-
- final int[] suffixCharacters = Arrays.copyOfRange(
- currentInfo.mCharacters, p, currentInfo.mCharacters.length);
- if (currentInfo.mChildrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) {
- updateParentAddresses(dictUpdater, currentInfo.mChildrenAddress,
- newNodeAddress + written + 1, fileHeader.mFormatOptions);
- }
- final int suffixFlags = BinaryDictEncoderUtils.makePtNodeFlags(
- suffixCharacters.length > 1,
- (currentInfo.mFlags & FormatSpec.FLAG_IS_TERMINAL) != 0,
- 0 /* childrenAddressSize */,
- (currentInfo.mFlags & FormatSpec.FLAG_HAS_SHORTCUT_TARGETS)
- != 0,
- (currentInfo.mFlags & FormatSpec.FLAG_HAS_BIGRAMS) != 0,
- isNotAWord, isBlackListEntry, fileHeader.mFormatOptions);
- final PtNodeInfo suffixInfo = new PtNodeInfo(
- newNodeAddress + written + 1, -1 /* endAddress */, suffixFlags,
- suffixCharacters, currentInfo.mFrequency, newNodeAddress + 1,
- currentInfo.mChildrenAddress, currentInfo.mShortcutTargets,
- currentInfo.mBigrams);
- written += BinaryDictIOUtils.computePtNodeSize(suffixInfo,
- fileHeader.mFormatOptions) + 1;
-
- final int[] newCharacters = Arrays.copyOfRange(codePoints, wordPos + p,
- codePoints.length);
- final int flags = BinaryDictEncoderUtils.makePtNodeFlags(
- newCharacters.length > 1, isTerminal,
- 0 /* childrenAddressSize */, hasShortcuts, hasBigrams,
- isNotAWord, isBlackListEntry, fileHeader.mFormatOptions);
- final PtNodeInfo newInfo = new PtNodeInfo(
- newNodeAddress + written, -1 /* endAddress */, flags,
- newCharacters, frequency, newNodeAddress + 1,
- FormatSpec.NO_CHILDREN_ADDRESS, shortcuts, bigrams);
- BinaryDictIOUtils.writeNodes(destination,
- new PtNodeInfo[] { suffixInfo, newInfo });
- return;
- }
- matched = false;
- break;
- }
- }
-
- if (matched) {
- if (wordPos + currentInfo.mCharacters.length == wordLen) {
- // the word exists in the dictionary.
- // only update the PtNode.
- final int newNodeAddress = dictBuffer.limit();
- final boolean hasMultipleChars = currentInfo.mCharacters.length > 1;
- final int flags = BinaryDictEncoderUtils.makePtNodeFlags(hasMultipleChars,
- isTerminal, 0 /* childrenAddressSize */, hasShortcuts, hasBigrams,
- isNotAWord, isBlackListEntry, fileHeader.mFormatOptions);
- final PtNodeInfo newInfo = new PtNodeInfo(newNodeAddress + 1,
- -1 /* endAddress */, flags, currentInfo.mCharacters, frequency,
- nodeParentAddress, currentInfo.mChildrenAddress, shortcuts,
- bigrams);
- movePtNode(destination, dictUpdater, newInfo, nodeOriginAddress, address,
- fileHeader.mFormatOptions);
- return;
- }
- wordPos += currentInfo.mCharacters.length;
- if (currentInfo.mChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS) {
- /*
- * found the prefix of the word.
- * make new PtNode and link to the PtNode from this PtNode.
- *
- * before
- * ab - cd
- *
- * insert "abcde"
- *
- * after
- * ab - cd - e
- */
- final int newNodeArrayAddress = dictBuffer.limit();
- updateChildrenAddress(dictUpdater, address, newNodeArrayAddress,
- fileHeader.mFormatOptions);
- final int newNodeAddress = newNodeArrayAddress + 1;
- final boolean hasMultipleChars = (wordLen - wordPos) > 1;
- final int flags = BinaryDictEncoderUtils.makePtNodeFlags(hasMultipleChars,
- isTerminal, 0 /* childrenAddressSize */, hasShortcuts, hasBigrams,
- isNotAWord, isBlackListEntry, fileHeader.mFormatOptions);
- final int[] characters = Arrays.copyOfRange(codePoints, wordPos, wordLen);
- final PtNodeInfo newInfo = new PtNodeInfo(newNodeAddress, -1, flags,
- characters, frequency, address, FormatSpec.NO_CHILDREN_ADDRESS,
- shortcuts, bigrams);
- BinaryDictIOUtils.writeNodes(destination, new PtNodeInfo[] { newInfo });
- return;
- }
- dictBuffer.position(currentInfo.mChildrenAddress);
- foundNextNode = true;
- break;
- }
- }
-
- if (foundNextNode) continue;
-
- // reached the end of the array.
- final int linkAddressPosition = dictBuffer.position();
- int nextLink = dictBuffer.readUnsignedInt24();
- if ((nextLink & FormatSpec.MSB24) != 0) {
- nextLink = -(nextLink & FormatSpec.SINT24_MAX);
- }
- if (nextLink == FormatSpec.NO_FORWARD_LINK_ADDRESS) {
- /*
- * expand this node.
- *
- * before
- * ab - cd
- *
- * insert "abef"
- *
- * after
- * ab - cd
- * |
- * - ef
- */
-
- // change the forward link address.
- final int newNodeAddress = dictBuffer.limit();
- dictBuffer.position(linkAddressPosition);
- BinaryDictIOUtils.writeSInt24ToBuffer(dictBuffer, newNodeAddress);
-
- final int[] characters = Arrays.copyOfRange(codePoints, wordPos, wordLen);
- final int flags = BinaryDictEncoderUtils.makePtNodeFlags(characters.length > 1,
- isTerminal, 0 /* childrenAddressSize */, hasShortcuts, hasBigrams,
- isNotAWord, isBlackListEntry, fileHeader.mFormatOptions);
- final PtNodeInfo newInfo = new PtNodeInfo(newNodeAddress + 1,
- -1 /* endAddress */, flags, characters, frequency, nodeParentAddress,
- FormatSpec.NO_CHILDREN_ADDRESS, shortcuts, bigrams);
- BinaryDictIOUtils.writeNodes(destination, new PtNodeInfo[]{ newInfo });
- return;
- } else {
- depth--;
- dictBuffer.position(nextLink);
- }
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index b56234f6d..437fa942b 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -40,12 +40,8 @@ public final class FormatSpec {
* p | not used 3 bits
* t | each unigram and bigram entry has a time stamp?
* i | 1 bit, 1 = yes, 0 = no : CONTAINS_TIMESTAMP_FLAG
- * o | has bigrams ? 1 bit, 1 = yes, 0 = no : CONTAINS_BIGRAMS_FLAG
- * n | FRENCH_LIGATURE_PROCESSING_FLAG
- * f | supports dynamic updates ? 1 bit, 1 = yes, 0 = no : SUPPORTS_DYNAMIC_UPDATE
- * l | GERMAN_UMLAUT_PROCESSING_FLAG
- * a |
- * gs
+ * o |
+ * nflags
*
* h |
* e | size of the file header, 4bytes
@@ -82,45 +78,36 @@ public final class FormatSpec {
* s
*
* f |
- * o | IF SUPPORTS_DYNAMIC_UPDATE (defined in the file header)
- * r | forward link address, 3byte
- * w | 1 byte = bbbbbbbb match
- * a | case 1xxxxxxx => -((xxxxxxx << 16) + (next byte << 8) + next byte)
- * r | otherwise => (xxxxxxx << 16) + (next byte << 8) + next byte
- * d |
- * linkaddress
+ * o | forward link address, 3byte
+ * r | 1 byte = bbbbbbbb match
+ * w | case 1xxxxxxx => -((xxxxxxx << 16) + (next byte << 8) + next byte)
+ * a | otherwise => (xxxxxxx << 16) + (next byte << 8) + next byte
+ * r |
+ * dlinkaddress
*/
/* Node (FusionDictionary.PtNode) layout is as follows:
- * | IF !SUPPORTS_DYNAMIC_UPDATE
- * | addressType xx : mask with MASK_CHILDREN_ADDRESS_TYPE
- * | 2 bits, 00 = no children : FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS
- * f | 01 = 1 byte : FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE
- * l | 10 = 2 bytes : FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES
- * a | 11 = 3 bytes : FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES
- * g | ELSE
- * s | is moved ? 2 bits, 11 = no : FLAG_IS_NOT_MOVED
- * | This must be the same as FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES
- * | 01 = yes : FLAG_IS_MOVED
- * | the new address is stored in the same place as the parent address
- * | is deleted? 10 = yes : FLAG_IS_DELETED
- * | has several chars ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_MULTIPLE_CHARS
- * | has a terminal ? 1 bit, 1 = yes, 0 = no : FLAG_IS_TERMINAL
- * | has shortcut targets ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_SHORTCUT_TARGETS
+ * | is moved ? 2 bits, 11 = no : FLAG_IS_NOT_MOVED
+ * | This must be the same as FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES
+ * | 01 = yes : FLAG_IS_MOVED
+ * f | the new address is stored in the same place as the parent address
+ * l | is deleted? 10 = yes : FLAG_IS_DELETED
+ * a | has several chars ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_MULTIPLE_CHARS
+ * g | has a terminal ? 1 bit, 1 = yes, 0 = no : FLAG_IS_TERMINAL
+ * s | has shortcut targets ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_SHORTCUT_TARGETS
* | has bigrams ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_BIGRAMS
* | is not a word ? 1 bit, 1 = yes, 0 = no : FLAG_IS_NOT_A_WORD
* | is blacklisted ? 1 bit, 1 = yes, 0 = no : FLAG_IS_BLACKLISTED
*
* p |
- * a | IF SUPPORTS_DYNAMIC_UPDATE (defined in the file header)
- * r | parent address, 3byte
- * e | 1 byte = bbbbbbbb match
- * n | case 1xxxxxxx => -((0xxxxxxx << 16) + (next byte << 8) + next byte)
- * t | otherwise => (bbbbbbbb << 16) + (next byte << 8) + next byte
- * a | This address is relative to the head of the PtNode.
- * d | If the node doesn't have a parent, this field is set to 0.
+ * a | parent address, 3byte
+ * r | 1 byte = bbbbbbbb match
+ * e | case 1xxxxxxx => -((0xxxxxxx << 16) + (next byte << 8) + next byte)
+ * n | otherwise => (bbbbbbbb << 16) + (next byte << 8) + next byte
+ * t | This address is relative to the head of the PtNode.
+ * a | If the node doesn't have a parent, this field is set to 0.
* d |
- * ress
+ * dress
*
* c | IF FLAG_HAS_MULTIPLE_CHARS
* h | char, char, char, char n * (1 or 3 bytes) : use PtNodeInfo for i/o helpers
@@ -134,23 +121,16 @@ public final class FormatSpec {
* e | frequency 1 byte
* q |
*
- * c | IF SUPPORTS_DYNAMIC_UPDATE
- * h | children address, 3 bytes
- * i | 1 byte = bbbbbbbb match
- * l | case 1xxxxxxx => -((0xxxxxxx << 16) + (next byte << 8) + next byte)
- * d | otherwise => (bbbbbbbb<<16) + (next byte << 8) + next byte
- * r | if this node doesn't have children, this field is set to 0.
- * e | (see BinaryDictEncoderUtils#writeVariableSignedAddress)
- * n | ELSIF 00 = FLAG_CHILDREN_ADDRESS_TYPE_NOADDRESS == addressType
- * a | // nothing
- * d | ELSIF 01 = FLAG_CHILDREN_ADDRESS_TYPE_ONEBYTE == addressType
- * d | children address, 1 byte
- * r | ELSIF 10 = FLAG_CHILDREN_ADDRESS_TYPE_TWOBYTES == addressType
- * e | children address, 2 bytes
- * s | ELSE // 11 = FLAG_CHILDREN_ADDRESS_TYPE_THREEBYTES = addressType
- * s | children address, 3 bytes
- * | END
- * | This address is relative to the position of this field.
+ * c |
+ * h | children address, 3 bytes
+ * i | 1 byte = bbbbbbbb match
+ * l | case 1xxxxxxx => -((0xxxxxxx << 16) + (next byte << 8) + next byte)
+ * d | otherwise => (bbbbbbbb<<16) + (next byte << 8) + next byte
+ * r | if this node doesn't have children, this field is set to 0.
+ * e | (see BinaryDictEncoderUtils#writeVariableSignedAddress)
+ * n | This address is relative to the position of this field.
+ * a |
+ * ddress
*
* | IF FLAG_IS_TERMINAL && FLAG_HAS_SHORTCUT_TARGETS
* | shortcut string list
@@ -199,20 +179,21 @@ public final class FormatSpec {
*/
public static final int MAGIC_NUMBER = 0x9BC13AFE;
- static final int MINIMUM_SUPPORTED_VERSION = 2;
- static final int MAXIMUM_SUPPORTED_VERSION = 4;
static final int NOT_A_VERSION_NUMBER = -1;
static final int FIRST_VERSION_WITH_DYNAMIC_UPDATE = 3;
static final int FIRST_VERSION_WITH_TERMINAL_ID = 4;
- static final int VERSION3 = 3;
- static final int VERSION4 = 4;
+
+ // These MUST have the same values as the relevant constants in format_utils.h.
+ // From version 4 on, we use version * 100 + revision as a version number. That allows
+ // us to change the format during development while having testing devices remove
+ // older files with each upgrade, while still having a readable versioning scheme.
+ public static final int VERSION2 = 2;
+ public static final int VERSION4 = 401;
+ static final int MINIMUM_SUPPORTED_VERSION = VERSION2;
+ static final int MAXIMUM_SUPPORTED_VERSION = VERSION4;
// These options need to be the same numeric values as the one in the native reading code.
- static final int GERMAN_UMLAUT_PROCESSING_FLAG = 0x1;
// TODO: Make the native reading code read this variable.
- static final int SUPPORTS_DYNAMIC_UPDATE = 0x2;
- static final int FRENCH_LIGATURE_PROCESSING_FLAG = 0x4;
- static final int CONTAINS_BIGRAMS_FLAG = 0x8;
static final int CONTAINS_TIMESTAMP_FLAG = 0x10;
// TODO: Make this value adaptative to content data, store it in the header, and
@@ -263,8 +244,10 @@ public final class FormatSpec {
static final int PTNODE_ATTRIBUTE_MAX_ADDRESS_SIZE = 3;
static final int PTNODE_SHORTCUT_LIST_SIZE_SIZE = 2;
- // These values are used only by version 4 or later.
+ // These values are used only by version 4 or later. They MUST match the definitions in
+ // ver4_dict_constants.cpp.
static final String TRIE_FILE_EXTENSION = ".trie";
+ public static final String HEADER_FILE_EXTENSION = ".header";
static final String FREQ_FILE_EXTENSION = ".freq";
static final String UNIGRAM_TIMESTAMP_FILE_EXTENSION = ".timestamp";
// tat = Terminal Address Table
@@ -278,9 +261,9 @@ public final class FormatSpec {
static final int UNIGRAM_TIMESTAMP_SIZE = 4;
// With the English main dictionary as of October 2013, the size of bigram address table is
- // is 584KB with the block size being 4.
- // This is 91% of that of full address table.
- static final int BIGRAM_ADDRESS_TABLE_BLOCK_SIZE = 4;
+ // is 345KB with the block size being 16.
+ // This is 54% of that of full address table.
+ static final int BIGRAM_ADDRESS_TABLE_BLOCK_SIZE = 16;
static final int BIGRAM_CONTENT_COUNT = 2;
static final int BIGRAM_FREQ_CONTENT_INDEX = 0;
static final int BIGRAM_TIMESTAMP_CONTENT_INDEX = 1;
@@ -293,7 +276,7 @@ public final class FormatSpec {
static final int SHORTCUT_CONTENT_COUNT = 1;
static final int SHORTCUT_CONTENT_INDEX = 0;
// With the English main dictionary as of October 2013, the size of shortcut address table is
- // 29KB with the block size being 64.
+ // 26KB with the block size being 64.
// This is only 4.4% of that of full address table.
static final int SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE = 64;
static final String SHORTCUT_CONTENT_ID = "_shortcut";
@@ -331,43 +314,36 @@ public final class FormatSpec {
*/
public static final class FormatOptions {
public final int mVersion;
- public final boolean mSupportsDynamicUpdate;
public final boolean mHasTerminalId;
public final boolean mHasTimestamp;
- @UsedForTesting
- public FormatOptions(final int version) {
- this(version, false);
- }
@UsedForTesting
- public FormatOptions(final int version, final boolean supportsDynamicUpdate) {
- this(version, supportsDynamicUpdate, false /* hasTimestamp */);
+ public FormatOptions(final int version) {
+ this(version, false /* hasTimestamp */);
}
- public FormatOptions(final int version, final boolean supportsDynamicUpdate,
- final boolean hasTimestamp) {
+ public FormatOptions(final int version, final boolean hasTimestamp) {
mVersion = version;
- if (version < FIRST_VERSION_WITH_DYNAMIC_UPDATE && supportsDynamicUpdate) {
- throw new RuntimeException("Dynamic updates are only supported with versions "
- + FIRST_VERSION_WITH_DYNAMIC_UPDATE + " and ulterior.");
- }
- mSupportsDynamicUpdate = supportsDynamicUpdate;
mHasTerminalId = (version >= FIRST_VERSION_WITH_TERMINAL_ID);
mHasTimestamp = hasTimestamp;
}
+
+ public boolean supportsDynamicUpdate() {
+ return mVersion >= FIRST_VERSION_WITH_DYNAMIC_UPDATE;
+ }
}
/**
* Class representing file header.
*/
public static final class FileHeader {
- public final int mHeaderSize;
+ public final int mBodyOffset;
public final DictionaryOptions mDictionaryOptions;
public final FormatOptions mFormatOptions;
// Note that these are corresponding definitions in native code in latinime::HeaderPolicy
// and latinime::HeaderReadWriteUtils.
- public static final String SUPPORTS_DYNAMIC_UPDATE_ATTRIBUTE = "SUPPORTS_DYNAMIC_UPDATE";
public static final String USES_FORGETTING_CURVE_ATTRIBUTE = "USES_FORGETTING_CURVE";
+ public static final String HAS_HISTORICAL_INFO_ATTRIBUTE = "HAS_HISTORICAL_INFO";
public static final String ATTRIBUTE_VALUE_TRUE = "1";
public static final String DICTIONARY_VERSION_ATTRIBUTE = "version";
@@ -375,10 +351,20 @@ public final class FormatSpec {
public static final String DICTIONARY_ID_ATTRIBUTE = "dictionary";
private static final String DICTIONARY_DESCRIPTION_ATTRIBUTE = "description";
public FileHeader(final int headerSize, final DictionaryOptions dictionaryOptions,
- final FormatOptions formatOptions) {
- mHeaderSize = headerSize;
+ final FormatOptions formatOptions) throws UnsupportedFormatException {
mDictionaryOptions = dictionaryOptions;
mFormatOptions = formatOptions;
+ mBodyOffset = formatOptions.mVersion < VERSION4 ? headerSize : 0;
+ if (null == getLocaleString()) {
+ throw new UnsupportedFormatException("Cannot create a FileHeader without a locale");
+ }
+ if (null == getVersion()) {
+ throw new UnsupportedFormatException(
+ "Cannot create a FileHeader without a version");
+ }
+ if (null == getId()) {
+ throw new UnsupportedFormatException("Cannot create a FileHeader without an ID");
+ }
}
// Helper method to get the locale as a String
@@ -415,7 +401,7 @@ public final class FormatSpec {
if (dictFile.isDirectory()) {
return new Ver4DictDecoder(dictFile, bufferType);
} else if (dictFile.isFile()) {
- return new Ver3DictDecoder(dictFile, bufferType);
+ return new Ver2DictDecoder(dictFile, bufferType);
}
return null;
}
@@ -425,7 +411,7 @@ public final class FormatSpec {
if (dictFile.isDirectory()) {
return new Ver4DictDecoder(dictFile, factory);
} else if (dictFile.isFile()) {
- return new Ver3DictDecoder(dictFile, factory);
+ return new Ver2DictDecoder(dictFile, factory);
}
return null;
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
index 3bb218bea..fdf2ae7b5 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
@@ -303,14 +303,9 @@ public final class FusionDictionary implements Iterable<Word> {
* Options global to the dictionary.
*/
public static final class DictionaryOptions {
- public final boolean mGermanUmlautProcessing;
- public final boolean mFrenchLigatureProcessing;
public final HashMap<String, String> mAttributes;
- public DictionaryOptions(final HashMap<String, String> attributes,
- final boolean germanUmlautProcessing, final boolean frenchLigatureProcessing) {
+ public DictionaryOptions(final HashMap<String, String> attributes) {
mAttributes = attributes;
- mGermanUmlautProcessing = germanUmlautProcessing;
- mFrenchLigatureProcessing = frenchLigatureProcessing;
}
@Override
public String toString() { // Convenience method
@@ -339,14 +334,6 @@ public final class FusionDictionary implements Iterable<Word> {
}
s.append("\n");
}
- if (mGermanUmlautProcessing) {
- s.append(indent);
- s.append("Needs German umlaut processing\n");
- }
- if (mFrenchLigatureProcessing) {
- s.append(indent);
- s.append("Needs French ligature processing\n");
- }
return s.toString();
}
}
@@ -701,138 +688,6 @@ public final class FusionDictionary implements Iterable<Word> {
}
/**
- * Recursively count the number of nodes in a given branch of the trie.
- *
- * @param nodeArray the node array to count.
- * @return the number of nodes in this branch.
- */
- public static int countNodeArrays(final PtNodeArray nodeArray) {
- int size = 1;
- for (int i = nodeArray.mData.size() - 1; i >= 0; --i) {
- PtNode ptNode = nodeArray.mData.get(i);
- if (null != ptNode.mChildren)
- size += countNodeArrays(ptNode.mChildren);
- }
- return size;
- }
-
- // Recursively find out whether there are any bigrams.
- // This can be pretty expensive especially if there aren't any (we return as soon
- // as we find one, so it's much cheaper if there are bigrams)
- private static boolean hasBigramsInternal(final PtNodeArray nodeArray) {
- if (null == nodeArray) return false;
- for (int i = nodeArray.mData.size() - 1; i >= 0; --i) {
- PtNode ptNode = nodeArray.mData.get(i);
- if (null != ptNode.mBigrams) return true;
- if (hasBigramsInternal(ptNode.mChildren)) return true;
- }
- return false;
- }
-
- /**
- * Finds out whether there are any bigrams in this dictionary.
- *
- * @return true if there is any bigram, false otherwise.
- */
- // TODO: this is expensive especially for large dictionaries without any bigram.
- // The up side is, this is always accurate and correct and uses no memory. We should
- // find a more efficient way of doing this, without compromising too much on memory
- // and ease of use.
- public boolean hasBigrams() {
- return hasBigramsInternal(mRootNodeArray);
- }
-
- // Historically, the tails of the words were going to be merged to save space.
- // However, that would prevent the code to search for a specific address in log(n)
- // time so this was abandoned.
- // The code is still of interest as it does add some compression to any dictionary
- // that has no need for attributes. Implementations that does not read attributes should be
- // able to read a dictionary with merged tails.
- // Also, the following code does support frequencies, as in, it will only merges
- // tails that share the same frequency. Though it would result in the above loss of
- // performance while searching by address, it is still technically possible to merge
- // tails that contain attributes, but this code does not take that into account - it does
- // not compare attributes and will merge terminals with different attributes regardless.
- public void mergeTails() {
- MakedictLog.i("Do not merge tails");
- return;
-
-// MakedictLog.i("Merging PtNodes. Number of PtNodes : " + countPtNodes(root));
-// MakedictLog.i("Number of PtNodes : " + countPtNodes(root));
-//
-// final HashMap<String, ArrayList<PtNodeArray>> repository =
-// new HashMap<String, ArrayList<PtNodeArray>>();
-// mergeTailsInner(repository, root);
-//
-// MakedictLog.i("Number of different pseudohashes : " + repository.size());
-// int size = 0;
-// for (ArrayList<PtNodeArray> a : repository.values()) {
-// size += a.size();
-// }
-// MakedictLog.i("Number of nodes after merge : " + (1 + size));
-// MakedictLog.i("Recursively seen nodes : " + countNodes(root));
- }
-
- // The following methods are used by the deactivated mergeTails()
-// private static boolean isEqual(PtNodeArray a, PtNodeArray b) {
-// if (null == a && null == b) return true;
-// if (null == a || null == b) return false;
-// if (a.data.size() != b.data.size()) return false;
-// final int size = a.data.size();
-// for (int i = size - 1; i >= 0; --i) {
-// PtNode aPtNode = a.data.get(i);
-// PtNode bPtNode = b.data.get(i);
-// if (aPtNode.frequency != bPtNode.frequency) return false;
-// if (aPtNode.alternates == null && bPtNode.alternates != null) return false;
-// if (aPtNode.alternates != null && !aPtNode.equals(bPtNode.alternates)) return false;
-// if (!Arrays.equals(aPtNode.chars, bPtNode.chars)) return false;
-// if (!isEqual(aPtNode.children, bPtNode.children)) return false;
-// }
-// return true;
-// }
-
-// static private HashMap<String, ArrayList<PtNodeArray>> mergeTailsInner(
-// final HashMap<String, ArrayList<PtNodeArray>> map, final PtNodeArray nodeArray) {
-// final ArrayList<PtNode> branches = nodeArray.data;
-// final int nodeSize = branches.size();
-// for (int i = 0; i < nodeSize; ++i) {
-// PtNode ptNode = branches.get(i);
-// if (null != ptNode.children) {
-// String pseudoHash = getPseudoHash(ptNode.children);
-// ArrayList<PtNodeArray> similarList = map.get(pseudoHash);
-// if (null == similarList) {
-// similarList = new ArrayList<PtNodeArray>();
-// map.put(pseudoHash, similarList);
-// }
-// boolean merged = false;
-// for (PtNodeArray similar : similarList) {
-// if (isEqual(ptNode.children, similar)) {
-// ptNode.children = similar;
-// merged = true;
-// break;
-// }
-// }
-// if (!merged) {
-// similarList.add(ptNode.children);
-// }
-// mergeTailsInner(map, ptNode.children);
-// }
-// }
-// return map;
-// }
-
-// private static String getPseudoHash(final PtNodeArray nodeArray) {
-// StringBuilder s = new StringBuilder();
-// for (PtNode ptNode : nodeArray.data) {
-// s.append(ptNode.frequency);
-// for (int ch : ptNode.chars) {
-// s.append(Character.toChars(ch));
-// }
-// }
-// return s.toString();
-// }
-
- /**
* Iterator to walk through a dictionary.
*
* This is purely for convenience.
diff --git a/java/src/com/android/inputmethod/latin/makedict/SparseTableContentReader.java b/java/src/com/android/inputmethod/latin/makedict/SparseTableContentReader.java
new file mode 100644
index 000000000..63e1f56f5
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/SparseTableContentReader.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2013 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.makedict;
+
+import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer;
+import com.android.inputmethod.latin.makedict.DictDecoder.DictionaryBufferFactory;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * An auxiliary class for reading SparseTable and data written by SparseTableContentWriter.
+ */
+public class SparseTableContentReader {
+
+ /**
+ * An interface of a function which is passed to SparseTableContentReader.read.
+ */
+ public interface SparseTableContentReaderInterface {
+ /**
+ * Reads data.
+ *
+ * @param buffer the DictBuffer. The position of the buffer is set to the head of data.
+ */
+ public void read(final DictBuffer buffer);
+ }
+
+ protected final int mContentCount;
+ protected final int mBlockSize;
+ protected final File mBaseDir;
+ protected final File mLookupTableFile;
+ protected final File[] mAddressTableFiles;
+ protected final File[] mContentFiles;
+ protected DictBuffer mLookupTableBuffer;
+ protected final DictBuffer[] mAddressTableBuffers;
+ private final DictBuffer[] mContentBuffers;
+ protected final DictionaryBufferFactory mFactory;
+
+ /**
+ * Sole constructor of SparseTableContentReader.
+ *
+ * @param name the name of SparseTable.
+ * @param blockSize the block size of the content table.
+ * @param baseDir the directory which contains the files of the content table.
+ * @param contentFilenames the file names of content files.
+ * @param contentSuffixes the ids of contents. These ids are used for a suffix of a name of
+ * address files and content files.
+ * @param factory the DictionaryBufferFactory which is used for opening the files.
+ */
+ public SparseTableContentReader(final String name, final int blockSize, final File baseDir,
+ final String[] contentFilenames, final String[] contentSuffixes,
+ final DictionaryBufferFactory factory) {
+ if (contentFilenames.length != contentSuffixes.length) {
+ throw new RuntimeException("The length of contentFilenames and the length of"
+ + " contentSuffixes are different " + contentFilenames.length + ", "
+ + contentSuffixes.length);
+ }
+ mBlockSize = blockSize;
+ mBaseDir = baseDir;
+ mFactory = factory;
+ mContentCount = contentFilenames.length;
+ mLookupTableFile = new File(baseDir, name + FormatSpec.LOOKUP_TABLE_FILE_SUFFIX);
+ mAddressTableFiles = new File[mContentCount];
+ mContentFiles = new File[mContentCount];
+ for (int i = 0; i < mContentCount; ++i) {
+ mAddressTableFiles[i] = new File(mBaseDir,
+ name + FormatSpec.CONTENT_TABLE_FILE_SUFFIX + contentSuffixes[i]);
+ mContentFiles[i] = new File(mBaseDir, contentFilenames[i] + contentSuffixes[i]);
+ }
+ mAddressTableBuffers = new DictBuffer[mContentCount];
+ mContentBuffers = new DictBuffer[mContentCount];
+ }
+
+ public void openBuffers() throws FileNotFoundException, IOException {
+ mLookupTableBuffer = mFactory.getDictionaryBuffer(mLookupTableFile);
+ for (int i = 0; i < mContentCount; ++i) {
+ mAddressTableBuffers[i] = mFactory.getDictionaryBuffer(mAddressTableFiles[i]);
+ mContentBuffers[i] = mFactory.getDictionaryBuffer(mContentFiles[i]);
+ }
+ }
+
+ /**
+ * Calls the read() callback of the reader with the appropriate buffer appropriately positioned.
+ * @param contentNumber the index in the original contentFilenames[] array.
+ * @param terminalId the terminal ID to read.
+ * @param reader the reader on which to call the callback.
+ */
+ protected void read(final int contentNumber, final int terminalId,
+ final SparseTableContentReaderInterface reader) {
+ if (terminalId < 0 || (terminalId / mBlockSize) * SparseTable.SIZE_OF_INT_IN_BYTES
+ >= mLookupTableBuffer.limit()) {
+ return;
+ }
+
+ mLookupTableBuffer.position((terminalId / mBlockSize) * SparseTable.SIZE_OF_INT_IN_BYTES);
+ final int indexInAddressTable = mLookupTableBuffer.readInt();
+ if (indexInAddressTable == SparseTable.NOT_EXIST) {
+ return;
+ }
+
+ mAddressTableBuffers[contentNumber].position(SparseTable.SIZE_OF_INT_IN_BYTES
+ * ((indexInAddressTable * mBlockSize) + (terminalId % mBlockSize)));
+ final int address = mAddressTableBuffers[contentNumber].readInt();
+ if (address == SparseTable.NOT_EXIST) {
+ return;
+ }
+
+ mContentBuffers[contentNumber].position(address);
+ reader.read(mContentBuffers[contentNumber]);
+ }
+} \ No newline at end of file
diff --git a/java/src/com/android/inputmethod/latin/makedict/SparseTableContentWriter.java b/java/src/com/android/inputmethod/latin/makedict/SparseTableContentWriter.java
new file mode 100644
index 000000000..49f0fd624
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/SparseTableContentWriter.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2013 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.makedict;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * An auxiliary class for writing data associated with SparseTable to files.
+ */
+public class SparseTableContentWriter {
+ public interface SparseTableContentWriterInterface {
+ public void write(final OutputStream outStream) throws IOException;
+ }
+
+ private final int mContentCount;
+ private final SparseTable mSparseTable;
+ private final File mLookupTableFile;
+ protected final File mBaseDir;
+ private final File[] mAddressTableFiles;
+ private final File[] mContentFiles;
+ protected final OutputStream[] mContentOutStreams;
+
+ /**
+ * Sole constructor of SparseTableContentWriter.
+ *
+ * @param name the name of SparseTable.
+ * @param initialCapacity the initial capacity of SparseTable.
+ * @param blockSize the block size of the content table.
+ * @param baseDir the directory which contains the files of the content table.
+ * @param contentFilenames the file names of content files.
+ * @param contentIds the ids of contents. These ids are used for a suffix of a name of address
+ * files and content files.
+ */
+ public SparseTableContentWriter(final String name, final int initialCapacity,
+ final int blockSize, final File baseDir, final String[] contentFilenames,
+ final String[] contentIds) {
+ if (contentFilenames.length != contentIds.length) {
+ throw new RuntimeException("The length of contentFilenames and the length of"
+ + " contentIds are different " + contentFilenames.length + ", "
+ + contentIds.length);
+ }
+ mContentCount = contentFilenames.length;
+ mSparseTable = new SparseTable(initialCapacity, blockSize, mContentCount);
+ mLookupTableFile = new File(baseDir, name + FormatSpec.LOOKUP_TABLE_FILE_SUFFIX);
+ mAddressTableFiles = new File[mContentCount];
+ mContentFiles = new File[mContentCount];
+ mBaseDir = baseDir;
+ for (int i = 0; i < mContentCount; ++i) {
+ mAddressTableFiles[i] = new File(mBaseDir,
+ name + FormatSpec.CONTENT_TABLE_FILE_SUFFIX + contentIds[i]);
+ mContentFiles[i] = new File(mBaseDir, contentFilenames[i] + contentIds[i]);
+ }
+ mContentOutStreams = new OutputStream[mContentCount];
+ }
+
+ public void openStreams() throws FileNotFoundException {
+ for (int i = 0; i < mContentCount; ++i) {
+ mContentOutStreams[i] = new FileOutputStream(mContentFiles[i]);
+ }
+ }
+
+ protected void write(final int contentIndex, final int index,
+ final SparseTableContentWriterInterface writer) throws IOException {
+ mSparseTable.set(contentIndex, index, (int) mContentFiles[contentIndex].length());
+ writer.write(mContentOutStreams[contentIndex]);
+ mContentOutStreams[contentIndex].flush();
+ }
+
+ public void closeStreams() throws IOException {
+ mSparseTable.writeToFiles(mLookupTableFile, mAddressTableFiles);
+ for (int i = 0; i < mContentCount; ++i) {
+ mContentOutStreams[i].close();
+ }
+ }
+} \ No newline at end of file
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver2DictDecoder.java
index acab4f8a5..ea0a2c6c2 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver3DictDecoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver2DictDecoder.java
@@ -23,7 +23,6 @@ import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
-import com.android.inputmethod.latin.utils.JniUtils;
import android.util.Log;
@@ -34,18 +33,11 @@ import java.util.ArrayList;
import java.util.Arrays;
/**
- * An implementation of DictDecoder for version 3 binary dictionary.
+ * An implementation of DictDecoder for version 2 binary dictionary.
*/
@UsedForTesting
-public class Ver3DictDecoder extends AbstractDictDecoder {
- private static final String TAG = Ver3DictDecoder.class.getSimpleName();
-
- static {
- JniUtils.loadNativeLibrary();
- }
-
- // TODO: implement something sensical instead of just a phony method
- private static native int doNothing();
+public class Ver2DictDecoder extends AbstractDictDecoder {
+ private static final String TAG = Ver2DictDecoder.class.getSimpleName();
protected static class PtNodeReader extends AbstractDictDecoder.PtNodeReader {
private static int readFrequency(final DictBuffer dictBuffer) {
@@ -57,7 +49,7 @@ public class Ver3DictDecoder extends AbstractDictDecoder {
private final DictionaryBufferFactory mBufferFactory;
protected DictBuffer mDictBuffer;
- /* package */ Ver3DictDecoder(final File file, final int factoryFlag) {
+ /* package */ Ver2DictDecoder(final File file, final int factoryFlag) {
mDictionaryBinaryFile = file;
mDictBuffer = null;
@@ -72,7 +64,7 @@ public class Ver3DictDecoder extends AbstractDictDecoder {
}
}
- /* package */ Ver3DictDecoder(final File file, final DictionaryBufferFactory factory) {
+ /* package */ Ver2DictDecoder(final File file, final DictionaryBufferFactory factory) {
mDictionaryBinaryFile = file;
mBufferFactory = factory;
}
@@ -166,7 +158,7 @@ public class Ver3DictDecoder extends AbstractDictDecoder {
final ArrayList<PendingAttribute> bigrams;
if (0 != (flags & FormatSpec.FLAG_HAS_BIGRAMS)) {
bigrams = new ArrayList<PendingAttribute>();
- addressPointer += PtNodeReader.readBigramAddresses(mDictBuffer, bigrams,
+ addressPointer += PtNodeReader.readBigramAddresses(mDictBuffer, bigrams,
addressPointer);
if (bigrams.size() >= FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
throw new RuntimeException("Too many bigrams in a PtNode (" + bigrams.size()
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver2DictEncoder.java
index 5da34534e..e5430423d 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver3DictEncoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver2DictEncoder.java
@@ -31,16 +31,16 @@ import java.util.ArrayList;
import java.util.Iterator;
/**
- * An implementation of DictEncoder for version 3 binary dictionary.
+ * An implementation of DictEncoder for version 2 binary dictionary.
*/
-public class Ver3DictEncoder implements DictEncoder {
+public class Ver2DictEncoder implements DictEncoder {
private final File mDictFile;
private OutputStream mOutStream;
private byte[] mBuffer;
private int mPosition;
- public Ver3DictEncoder(final File dictFile) {
+ public Ver2DictEncoder(final File dictFile) {
mDictFile = dictFile;
mOutStream = null;
mBuffer = null;
@@ -49,7 +49,7 @@ public class Ver3DictEncoder implements DictEncoder {
// This constructor is used only by BinaryDictOffdeviceUtilsTests.
// If you want to use this in the production code, you should consider keeping consistency of
// the interface of Ver3DictDecoder by using factory.
- public Ver3DictEncoder(final OutputStream outStream) {
+ public Ver2DictEncoder(final OutputStream outStream) {
mDictFile = null;
mOutStream = outStream;
}
@@ -68,7 +68,7 @@ public class Ver3DictEncoder implements DictEncoder {
@Override
public void writeDictionary(final FusionDictionary dict, final FormatOptions formatOptions)
throws IOException, UnsupportedFormatException {
- if (formatOptions.mVersion > FormatSpec.VERSION3) {
+ if (formatOptions.mVersion > FormatSpec.VERSION2) {
throw new UnsupportedFormatException(
"The given format options has wrong version number : "
+ formatOptions.mVersion);
@@ -169,7 +169,7 @@ public class Ver3DictEncoder implements DictEncoder {
private void writeChildrenPosition(final PtNode ptNode, final FormatOptions formatOptions) {
final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode, formatOptions);
- if (formatOptions.mSupportsDynamicUpdate) {
+ if (formatOptions.supportsDynamicUpdate()) {
mPosition += BinaryDictEncoderUtils.writeSignedChildrenPosition(mBuffer, mPosition,
childrenPos);
} else {
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver3DictUpdater.java b/java/src/com/android/inputmethod/latin/makedict/Ver3DictUpdater.java
deleted file mode 100644
index 07adda625..000000000
--- a/java/src/com/android/inputmethod/latin/makedict/Ver3DictUpdater.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2013 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.makedict;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.ArrayList;
-
-/**
- * An implementation of DictUpdater for version 3 binary dictionary.
- */
-@UsedForTesting
-public class Ver3DictUpdater extends Ver3DictDecoder implements DictUpdater {
- private OutputStream mOutStream;
-
- @UsedForTesting
- public Ver3DictUpdater(final File dictFile, final int factoryType) {
- // DictUpdater must have an updatable DictBuffer.
- super(dictFile, ((factoryType & MASK_DICTBUFFER) == USE_BYTEARRAY)
- ? USE_BYTEARRAY : USE_WRITABLE_BYTEBUFFER);
- mOutStream = null;
- }
-
- private void openStreamAndBuffer() throws FileNotFoundException, IOException {
- super.openDictBuffer();
- mOutStream = new FileOutputStream(mDictionaryBinaryFile, true /* append */);
- }
-
- private void close() throws IOException {
- if (mOutStream != null) {
- mOutStream.close();
- mOutStream = null;
- }
- }
-
- @Override @UsedForTesting
- public void deleteWord(final String word) throws IOException, UnsupportedFormatException {
- if (mOutStream == null) openStreamAndBuffer();
- mDictBuffer.position(0);
- readHeader();
- final int wordPos = getTerminalPosition(word);
- if (wordPos != FormatSpec.NOT_VALID_WORD) {
- mDictBuffer.position(wordPos);
- final int flags = mDictBuffer.readUnsignedByte();
- mDictBuffer.position(wordPos);
- mDictBuffer.put((byte) DynamicBinaryDictIOUtils.markAsDeleted(flags));
- }
- close();
- }
-
- @Override @UsedForTesting
- public void insertWord(final String word, final int frequency,
- final ArrayList<WeightedString> bigramStrings,
- final ArrayList<WeightedString> shortcuts,
- final boolean isNotAWord, final boolean isBlackListEntry)
- throws IOException, UnsupportedFormatException {
- if (mOutStream == null) openStreamAndBuffer();
- DynamicBinaryDictIOUtils.insertWord(this, mOutStream, word, frequency, bigramStrings,
- shortcuts, isNotAWord, isBlackListEntry);
- close();
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java
index 734223ec2..7071893d2 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java
@@ -40,26 +40,52 @@ import java.util.Arrays;
public class Ver4DictDecoder extends AbstractDictDecoder {
private static final String TAG = Ver4DictDecoder.class.getSimpleName();
- private static final int FILETYPE_TRIE = 1;
- private static final int FILETYPE_FREQUENCY = 2;
- private static final int FILETYPE_TERMINAL_ADDRESS_TABLE = 3;
- private static final int FILETYPE_BIGRAM_FREQ = 4;
- private static final int FILETYPE_SHORTCUT = 5;
-
- private final File mDictDirectory;
- private final DictionaryBufferFactory mBufferFactory;
+ protected static final int FILETYPE_TRIE = 1;
+ protected static final int FILETYPE_FREQUENCY = 2;
+ protected static final int FILETYPE_TERMINAL_ADDRESS_TABLE = 3;
+ protected static final int FILETYPE_BIGRAM_FREQ = 4;
+ protected static final int FILETYPE_SHORTCUT = 5;
+ protected static final int FILETYPE_HEADER = 6;
+
+ protected final File mDictDirectory;
+ protected final DictionaryBufferFactory mBufferFactory;
protected DictBuffer mDictBuffer;
- private DictBuffer mFrequencyBuffer;
- private DictBuffer mTerminalAddressTableBuffer;
- private DictBuffer mBigramBuffer;
- private DictBuffer mShortcutBuffer;
- private SparseTable mBigramAddressTable;
- private SparseTable mShortcutAddressTable;
+ protected DictBuffer mHeaderBuffer;
+ protected DictBuffer mFrequencyBuffer;
+ protected DictBuffer mTerminalAddressTableBuffer;
+ private BigramContentReader mBigramReader;
+ private ShortcutContentReader mShortcutReader;
+
+ /**
+ * Raw PtNode info straight out of a trie file in version 4 dictionary.
+ */
+ protected static final class Ver4PtNodeInfo {
+ public final int mFlags;
+ public final int[] mCharacters;
+ public final int mTerminalId;
+ public final int mChildrenPos;
+ public final int mParentPos;
+ public final int mNodeSize;
+ public int mStartIndexOfCharacters;
+ public int mEndIndexOfCharacters; // exclusive
+
+ public Ver4PtNodeInfo(final int flags, final int[] characters, final int terminalId,
+ final int childrenPos, final int parentPos, final int nodeSize) {
+ mFlags = flags;
+ mCharacters = characters;
+ mTerminalId = terminalId;
+ mChildrenPos = childrenPos;
+ mParentPos = parentPos;
+ mNodeSize = nodeSize;
+ mStartIndexOfCharacters = 0;
+ mEndIndexOfCharacters = characters.length;
+ }
+ }
@UsedForTesting
/* package */ Ver4DictDecoder(final File dictDirectory, final int factoryFlag) {
mDictDirectory = dictDirectory;
- mDictBuffer = mFrequencyBuffer = null;
+ mDictBuffer = mHeaderBuffer = mFrequencyBuffer = null;
if ((factoryFlag & MASK_DICTBUFFER) == USE_READONLY_BYTEBUFFER) {
mBufferFactory = new DictionaryBufferFromReadOnlyByteBufferFactory();
@@ -76,13 +102,16 @@ public class Ver4DictDecoder extends AbstractDictDecoder {
/* package */ Ver4DictDecoder(final File dictDirectory, final DictionaryBufferFactory factory) {
mDictDirectory = dictDirectory;
mBufferFactory = factory;
- mDictBuffer = mFrequencyBuffer = null;
+ mDictBuffer = mHeaderBuffer = mFrequencyBuffer = null;
}
- private File getFile(final int fileType) {
+ protected File getFile(final int fileType) throws UnsupportedFormatException {
if (fileType == FILETYPE_TRIE) {
return new File(mDictDirectory,
mDictDirectory.getName() + FormatSpec.TRIE_FILE_EXTENSION);
+ } else if (fileType == FILETYPE_HEADER) {
+ return new File(mDictDirectory,
+ mDictDirectory.getName() + FormatSpec.HEADER_FILE_EXTENSION);
} else if (fileType == FILETYPE_FREQUENCY) {
return new File(mDictDirectory,
mDictDirectory.getName() + FormatSpec.FREQ_FILE_EXTENSION);
@@ -98,20 +127,27 @@ public class Ver4DictDecoder extends AbstractDictDecoder {
mDictDirectory.getName() + FormatSpec.SHORTCUT_FILE_EXTENSION
+ FormatSpec.SHORTCUT_CONTENT_ID);
} else {
- throw new RuntimeException("Unsupported kind of file : " + fileType);
+ throw new UnsupportedFormatException("Unsupported kind of file : " + fileType);
}
}
@Override
- public void openDictBuffer() throws FileNotFoundException, IOException {
+ public void openDictBuffer() throws FileNotFoundException, IOException,
+ UnsupportedFormatException {
+ if (!mDictDirectory.isDirectory()) {
+ throw new UnsupportedFormatException("Format 4 dictionary needs a directory");
+ }
+ mHeaderBuffer = mBufferFactory.getDictionaryBuffer(getFile(FILETYPE_HEADER));
mDictBuffer = mBufferFactory.getDictionaryBuffer(getFile(FILETYPE_TRIE));
mFrequencyBuffer = mBufferFactory.getDictionaryBuffer(getFile(FILETYPE_FREQUENCY));
mTerminalAddressTableBuffer = mBufferFactory.getDictionaryBuffer(
getFile(FILETYPE_TERMINAL_ADDRESS_TABLE));
- mBigramBuffer = mBufferFactory.getDictionaryBuffer(getFile(FILETYPE_BIGRAM_FREQ));
- loadBigramAddressSparseTable();
- mShortcutBuffer = mBufferFactory.getDictionaryBuffer(getFile(FILETYPE_SHORTCUT));
- loadShortcutAddressSparseTable();
+ mBigramReader = new BigramContentReader(mDictDirectory.getName(),
+ mDictDirectory, mBufferFactory, false);
+ mBigramReader.openBuffers();
+ mShortcutReader = new ShortcutContentReader(mDictDirectory.getName(), mDictDirectory,
+ mBufferFactory);
+ mShortcutReader.openBuffers();
}
@Override
@@ -119,46 +155,134 @@ public class Ver4DictDecoder extends AbstractDictDecoder {
return mDictBuffer != null;
}
+ @UsedForTesting
+ /* package */ DictBuffer getHeaderBuffer() {
+ return mHeaderBuffer;
+ }
+
+ @UsedForTesting
/* package */ DictBuffer getDictBuffer() {
return mDictBuffer;
}
@Override
public FileHeader readHeader() throws IOException, UnsupportedFormatException {
- if (mDictBuffer == null) {
+ if (mHeaderBuffer == null) {
openDictBuffer();
}
- final FileHeader header = super.readHeader(mDictBuffer);
+ mHeaderBuffer.position(0);
+ final FileHeader header = super.readHeader(mHeaderBuffer);
final int version = header.mFormatOptions.mVersion;
- if (version != 4) {
+ if (version != FormatSpec.VERSION4) {
throw new UnsupportedFormatException("File header has a wrong version : " + version);
}
return header;
}
- private void loadBigramAddressSparseTable() throws IOException {
- final File lookupIndexFile = new File(mDictDirectory, mDictDirectory.getName()
- + FormatSpec.BIGRAM_FILE_EXTENSION + FormatSpec.LOOKUP_TABLE_FILE_SUFFIX);
- final File freqsFile = new File(mDictDirectory, mDictDirectory.getName()
- + FormatSpec.BIGRAM_FILE_EXTENSION + FormatSpec.CONTENT_TABLE_FILE_SUFFIX
- + FormatSpec.BIGRAM_FREQ_CONTENT_ID);
- mBigramAddressTable = SparseTable.readFromFiles(lookupIndexFile, new File[] { freqsFile },
- FormatSpec.BIGRAM_ADDRESS_TABLE_BLOCK_SIZE);
+ /**
+ * An auxiliary class for reading bigrams.
+ */
+ protected static class BigramContentReader extends SparseTableContentReader {
+ public BigramContentReader(final String name, final File baseDir,
+ final DictionaryBufferFactory factory, final boolean hasTimestamp) {
+ super(name + FormatSpec.BIGRAM_FILE_EXTENSION,
+ FormatSpec.BIGRAM_ADDRESS_TABLE_BLOCK_SIZE, baseDir,
+ getContentFilenames(name, hasTimestamp), getContentIds(hasTimestamp), factory);
+ }
+
+ // TODO: Consolidate this method and BigramContentWriter.getContentFilenames.
+ protected static String[] getContentFilenames(final String name,
+ final boolean hasTimestamp) {
+ final String[] contentFilenames;
+ if (hasTimestamp) {
+ contentFilenames = new String[] { name + FormatSpec.BIGRAM_FILE_EXTENSION,
+ name + FormatSpec.BIGRAM_FILE_EXTENSION };
+ } else {
+ contentFilenames = new String[] { name + FormatSpec.BIGRAM_FILE_EXTENSION };
+ }
+ return contentFilenames;
+ }
+
+ // TODO: Consolidate this method and BigramContentWriter.getContentIds.
+ protected static String[] getContentIds(final boolean hasTimestamp) {
+ final String[] contentIds;
+ if (hasTimestamp) {
+ contentIds = new String[] { FormatSpec.BIGRAM_FREQ_CONTENT_ID,
+ FormatSpec.BIGRAM_TIMESTAMP_CONTENT_ID };
+ } else {
+ contentIds = new String[] { FormatSpec.BIGRAM_FREQ_CONTENT_ID };
+ }
+ return contentIds;
+ }
+
+ public ArrayList<PendingAttribute> readTargetsAndFrequencies(final int terminalId,
+ final DictBuffer terminalAddressTableBuffer) {
+ final ArrayList<PendingAttribute> bigrams = CollectionUtils.newArrayList();
+ read(FormatSpec.BIGRAM_FREQ_CONTENT_INDEX, terminalId,
+ new SparseTableContentReaderInterface() {
+ @Override
+ public void read(final DictBuffer buffer) {
+ while (bigrams.size() < FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
+ // If bigrams.size() reaches FormatSpec.MAX_BIGRAMS_IN_A_PTNODE,
+ // remaining bigram entries are ignored.
+ final int bigramFlags = buffer.readUnsignedByte();
+ final int targetTerminalId = buffer.readUnsignedInt24();
+ terminalAddressTableBuffer.position(targetTerminalId
+ * FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE);
+ final int targetAddress =
+ terminalAddressTableBuffer.readUnsignedInt24();
+ bigrams.add(new PendingAttribute(bigramFlags
+ & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY,
+ targetAddress));
+ if (0 == (bigramFlags
+ & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) {
+ break;
+ }
+ }
+ if (bigrams.size() >= FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
+ throw new RuntimeException("Too many bigrams in a PtNode ("
+ + bigrams.size() + " but max is "
+ + FormatSpec.MAX_BIGRAMS_IN_A_PTNODE + ")");
+ }
+ }
+ });
+ if (bigrams.isEmpty()) return null;
+ return bigrams;
+ }
}
- // TODO: Let's have something like SparseTableContentsReader in this class.
- private void loadShortcutAddressSparseTable() throws IOException {
- final File lookupIndexFile = new File(mDictDirectory, mDictDirectory.getName()
- + FormatSpec.SHORTCUT_FILE_EXTENSION + FormatSpec.LOOKUP_TABLE_FILE_SUFFIX);
- final File contentFile = new File(mDictDirectory, mDictDirectory.getName()
- + FormatSpec.SHORTCUT_FILE_EXTENSION + FormatSpec.CONTENT_TABLE_FILE_SUFFIX
- + FormatSpec.SHORTCUT_CONTENT_ID);
- final File timestampsFile = new File(mDictDirectory, mDictDirectory.getName()
- + FormatSpec.SHORTCUT_FILE_EXTENSION + FormatSpec.CONTENT_TABLE_FILE_SUFFIX
- + FormatSpec.SHORTCUT_CONTENT_ID);
- mShortcutAddressTable = SparseTable.readFromFiles(lookupIndexFile,
- new File[] { contentFile, timestampsFile },
- FormatSpec.SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE);
+ /**
+ * An auxiliary class for reading shortcuts.
+ */
+ protected static class ShortcutContentReader extends SparseTableContentReader {
+ public ShortcutContentReader(final String name, final File baseDir,
+ final DictionaryBufferFactory factory) {
+ super(name + FormatSpec.SHORTCUT_FILE_EXTENSION,
+ FormatSpec.SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE, baseDir,
+ new String[] { name + FormatSpec.SHORTCUT_FILE_EXTENSION },
+ new String[] { FormatSpec.SHORTCUT_CONTENT_ID }, factory);
+ }
+
+ public ArrayList<WeightedString> readShortcuts(final int terminalId) {
+ final ArrayList<WeightedString> shortcuts = CollectionUtils.newArrayList();
+ read(FormatSpec.SHORTCUT_CONTENT_INDEX, terminalId,
+ new SparseTableContentReaderInterface() {
+ @Override
+ public void read(final DictBuffer buffer) {
+ while (true) {
+ final int flags = buffer.readUnsignedByte();
+ final String word = CharEncoding.readString(buffer);
+ shortcuts.add(new WeightedString(word,
+ flags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY));
+ if (0 == (flags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) {
+ break;
+ }
+ }
+ }
+ });
+ if (shortcuts.isEmpty()) return null;
+ return shortcuts;
+ }
}
protected static class PtNodeReader extends AbstractDictDecoder.PtNodeReader {
@@ -172,102 +296,82 @@ public class Ver4DictDecoder extends AbstractDictDecoder {
}
}
- private ArrayList<WeightedString> readShortcuts(final int terminalId) {
- if (mShortcutAddressTable.get(0, terminalId) == SparseTable.NOT_EXIST) return null;
-
- final ArrayList<WeightedString> ret = CollectionUtils.newArrayList();
- final int posOfShortcuts = mShortcutAddressTable.get(FormatSpec.SHORTCUT_CONTENT_INDEX,
- terminalId);
- mShortcutBuffer.position(posOfShortcuts);
- while (true) {
- final int flags = mShortcutBuffer.readUnsignedByte();
- final String word = CharEncoding.readString(mShortcutBuffer);
- ret.add(new WeightedString(word,
- flags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY));
- if (0 == (flags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) break;
- }
- return ret;
- }
+ private final int[] mCharacterBufferForReadingVer4PtNodeInfo
+ = new int[FormatSpec.MAX_WORD_LENGTH];
+ /**
+ * Reads PtNode from ptNodePos in the trie file and returns Ver4PtNodeInfo.
+ *
+ * @param ptNodePos the position of PtNode.
+ * @param options the format options.
+ * @return Ver4PtNodeInfo.
+ */
// TODO: Make this buffer thread safe.
// TODO: Support words longer than FormatSpec.MAX_WORD_LENGTH.
- private final int[] mCharacterBuffer = new int[FormatSpec.MAX_WORD_LENGTH];
- @Override
- public PtNodeInfo readPtNode(int ptNodePos, FormatOptions options) {
- int addressPointer = ptNodePos;
+ protected Ver4PtNodeInfo readVer4PtNodeInfo(final int ptNodePos, final FormatOptions options) {
+ int readingPos = ptNodePos;
final int flags = PtNodeReader.readPtNodeOptionFlags(mDictBuffer);
- addressPointer += FormatSpec.PTNODE_FLAGS_SIZE;
+ readingPos += FormatSpec.PTNODE_FLAGS_SIZE;
- final int parentAddress = PtNodeReader.readParentAddress(mDictBuffer, options);
+ final int parentPos = PtNodeReader.readParentAddress(mDictBuffer, options);
if (BinaryDictIOUtils.supportsDynamicUpdate(options)) {
- addressPointer += FormatSpec.PARENT_ADDRESS_SIZE;
+ readingPos += FormatSpec.PARENT_ADDRESS_SIZE;
}
final int characters[];
if (0 != (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS)) {
int index = 0;
int character = CharEncoding.readChar(mDictBuffer);
- addressPointer += CharEncoding.getCharSize(character);
+ readingPos += CharEncoding.getCharSize(character);
while (FormatSpec.INVALID_CHARACTER != character
&& index < FormatSpec.MAX_WORD_LENGTH) {
- mCharacterBuffer[index++] = character;
+ mCharacterBufferForReadingVer4PtNodeInfo[index++] = character;
character = CharEncoding.readChar(mDictBuffer);
- addressPointer += CharEncoding.getCharSize(character);
+ readingPos += CharEncoding.getCharSize(character);
}
- characters = Arrays.copyOfRange(mCharacterBuffer, 0, index);
+ characters = Arrays.copyOfRange(mCharacterBufferForReadingVer4PtNodeInfo, 0, index);
} else {
final int character = CharEncoding.readChar(mDictBuffer);
- addressPointer += CharEncoding.getCharSize(character);
+ readingPos += CharEncoding.getCharSize(character);
characters = new int[] { character };
}
final int terminalId;
if (0 != (FormatSpec.FLAG_IS_TERMINAL & flags)) {
terminalId = PtNodeReader.readTerminalId(mDictBuffer);
- addressPointer += FormatSpec.PTNODE_TERMINAL_ID_SIZE;
+ readingPos += FormatSpec.PTNODE_TERMINAL_ID_SIZE;
} else {
terminalId = PtNode.NOT_A_TERMINAL;
}
+ int childrenPos = PtNodeReader.readChildrenAddress(mDictBuffer, flags, options);
+ if (childrenPos != FormatSpec.NO_CHILDREN_ADDRESS) {
+ childrenPos += readingPos;
+ }
+ readingPos += BinaryDictIOUtils.getChildrenAddressSize(flags, options);
+
+ return new Ver4PtNodeInfo(flags, characters, terminalId, childrenPos, parentPos,
+ readingPos - ptNodePos);
+ }
+
+ @Override
+ public PtNodeInfo readPtNode(int ptNodePos, FormatOptions options) {
+ final Ver4PtNodeInfo nodeInfo = readVer4PtNodeInfo(ptNodePos, options);
+
final int frequency;
- if (0 != (FormatSpec.FLAG_IS_TERMINAL & flags)) {
- frequency = PtNodeReader.readFrequency(mFrequencyBuffer, terminalId);
+ if (0 != (FormatSpec.FLAG_IS_TERMINAL & nodeInfo.mFlags)) {
+ frequency = PtNodeReader.readFrequency(mFrequencyBuffer, nodeInfo.mTerminalId);
} else {
frequency = PtNode.NOT_A_TERMINAL;
}
- int childrenAddress = PtNodeReader.readChildrenAddress(mDictBuffer, flags, options);
- if (childrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) {
- childrenAddress += addressPointer;
- }
- addressPointer += BinaryDictIOUtils.getChildrenAddressSize(flags, options);
- final ArrayList<WeightedString> shortcutTargets = readShortcuts(terminalId);
-
- final ArrayList<PendingAttribute> bigrams;
- if (0 != (flags & FormatSpec.FLAG_HAS_BIGRAMS)) {
- bigrams = new ArrayList<PendingAttribute>();
- final int posOfBigrams = mBigramAddressTable.get(0 /* contentTableIndex */, terminalId);
- mBigramBuffer.position(posOfBigrams);
- while (bigrams.size() < FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
- // If bigrams.size() reaches FormatSpec.MAX_BIGRAMS_IN_A_PTNODE,
- // remaining bigram entries are ignored.
- final int bigramFlags = mBigramBuffer.readUnsignedByte();
- final int targetTerminalId = mBigramBuffer.readUnsignedInt24();
- mTerminalAddressTableBuffer.position(
- targetTerminalId * FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE);
- final int targetAddress = mTerminalAddressTableBuffer.readUnsignedInt24();
- bigrams.add(new PendingAttribute(
- bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY,
- targetAddress));
- if (0 == (bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) break;
- }
- if (bigrams.size() >= FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
- throw new RuntimeException("Too many bigrams in a PtNode (" + bigrams.size()
- + " but max is " + FormatSpec.MAX_BIGRAMS_IN_A_PTNODE + ")");
- }
- } else {
- bigrams = null;
- }
- return new PtNodeInfo(ptNodePos, addressPointer, flags, characters, frequency,
- parentAddress, childrenAddress, shortcutTargets, bigrams);
+
+ final ArrayList<WeightedString> shortcutTargets = mShortcutReader.readShortcuts(
+ nodeInfo.mTerminalId);
+ final ArrayList<PendingAttribute> bigrams = mBigramReader.readTargetsAndFrequencies(
+ nodeInfo.mTerminalId, mTerminalAddressTableBuffer);
+
+ return new PtNodeInfo(ptNodePos, ptNodePos + nodeInfo.mNodeSize, nodeInfo.mFlags,
+ nodeInfo.mCharacters, frequency, nodeInfo.mParentPos, nodeInfo.mChildrenPos,
+ shortcutTargets, bigrams);
}
private void deleteDictFiles() {
@@ -318,10 +422,14 @@ public class Ver4DictDecoder extends AbstractDictDecoder {
@Override
public boolean readAndFollowForwardLink() {
- final int nextAddress = mDictBuffer.readUnsignedInt24();
- if (nextAddress >= 0 && nextAddress < mDictBuffer.limit()) {
- mDictBuffer.position(nextAddress);
- return true;
+ final int forwardLinkPos = mDictBuffer.position();
+ int nextRelativePos = BinaryDictDecoderUtils.readSInt24(mDictBuffer);
+ if (nextRelativePos != FormatSpec.NO_FORWARD_LINK_ADDRESS) {
+ final int nextPos = forwardLinkPos + nextRelativePos;
+ if (nextPos >= 0 && nextPos < mDictBuffer.limit()) {
+ mDictBuffer.position(nextPos);
+ return true;
+ }
}
return false;
}
@@ -332,6 +440,7 @@ public class Ver4DictDecoder extends AbstractDictDecoder {
}
@Override
+ @UsedForTesting
public void skipPtNode(final FormatOptions formatOptions) {
final int flags = PtNodeReader.readPtNodeOptionFlags(mDictBuffer);
PtNodeReader.readParentAddress(mDictBuffer, formatOptions);
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
index 8d5b48a9b..d34aa171e 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
@@ -1,5 +1,4 @@
/*
-/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,21 +17,15 @@
package com.android.inputmethod.latin.makedict;
import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding;
-import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
+import com.android.inputmethod.latin.BinaryDictionary;
+import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
-import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions;
import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
-import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+import com.android.inputmethod.latin.utils.LocaleUtils;
import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Iterator;
/**
* An implementation of DictEncoder for version 4 binary dictionary.
@@ -40,244 +33,19 @@ import java.util.Iterator;
@UsedForTesting
public class Ver4DictEncoder implements DictEncoder {
private final File mDictPlacedDir;
- private byte[] mTrieBuf;
- private int mTriePos;
- private int mHeaderSize;
- private OutputStream mTrieOutStream;
- private OutputStream mFreqOutStream;
- private OutputStream mUnigramTimestampOutStream;
- private OutputStream mTerminalAddressTableOutStream;
- private File mDictDir;
- private String mBaseFilename;
- private BigramContentWriter mBigramWriter;
- private ShortcutContentWriter mShortcutWriter;
@UsedForTesting
public Ver4DictEncoder(final File dictPlacedDir) {
mDictPlacedDir = dictPlacedDir;
}
- private interface SparseTableContentWriterInterface {
- public void write(final OutputStream outStream) throws IOException;
- }
-
- private static class SparseTableContentWriter {
- private final int mContentCount;
- private final SparseTable mSparseTable;
- private final File mLookupTableFile;
- protected final File mBaseDir;
- private final File[] mAddressTableFiles;
- private final File[] mContentFiles;
- protected final OutputStream[] mContentOutStreams;
-
- public SparseTableContentWriter(final String name, final int initialCapacity,
- final int blockSize, final File baseDir, final String[] contentFilenames,
- final String[] contentIds) {
- if (contentFilenames.length != contentIds.length) {
- throw new RuntimeException("The length of contentFilenames and the length of"
- + " contentIds are different " + contentFilenames.length + ", "
- + contentIds.length);
- }
- mContentCount = contentFilenames.length;
- mSparseTable = new SparseTable(initialCapacity, blockSize, mContentCount);
- mLookupTableFile = new File(baseDir, name + FormatSpec.LOOKUP_TABLE_FILE_SUFFIX);
- mAddressTableFiles = new File[mContentCount];
- mContentFiles = new File[mContentCount];
- mBaseDir = baseDir;
- for (int i = 0; i < mContentCount; ++i) {
- mAddressTableFiles[i] = new File(mBaseDir,
- name + FormatSpec.CONTENT_TABLE_FILE_SUFFIX + contentIds[i]);
- mContentFiles[i] = new File(mBaseDir, contentFilenames[i] + contentIds[i]);
- }
- mContentOutStreams = new OutputStream[mContentCount];
- }
-
- public void openStreams() throws FileNotFoundException {
- for (int i = 0; i < mContentCount; ++i) {
- mContentOutStreams[i] = new FileOutputStream(mContentFiles[i]);
- }
- }
-
- protected void write(final int contentIndex, final int index,
- final SparseTableContentWriterInterface writer) throws IOException {
- mSparseTable.set(contentIndex, index, (int) mContentFiles[contentIndex].length());
- writer.write(mContentOutStreams[contentIndex]);
- mContentOutStreams[contentIndex].flush();
- }
-
- public void closeStreams() throws IOException {
- mSparseTable.writeToFiles(mLookupTableFile, mAddressTableFiles);
- for (int i = 0; i < mContentCount; ++i) {
- mContentOutStreams[i].close();
- }
- }
- }
-
- private static class BigramContentWriter extends SparseTableContentWriter {
- private final boolean mWriteTimestamp;
-
- public BigramContentWriter(final String name, final int initialCapacity,
- final File baseDir, final boolean writeTimestamp) {
- super(name + FormatSpec.BIGRAM_FILE_EXTENSION, initialCapacity,
- FormatSpec.BIGRAM_ADDRESS_TABLE_BLOCK_SIZE, baseDir,
- getContentFilenames(name, writeTimestamp), getContentIds(writeTimestamp));
- mWriteTimestamp = writeTimestamp;
- }
-
- private static String[] getContentFilenames(final String name,
- final boolean writeTimestamp) {
- final String[] contentFilenames;
- if (writeTimestamp) {
- contentFilenames = new String[] { name + FormatSpec.BIGRAM_FILE_EXTENSION,
- name + FormatSpec.BIGRAM_FILE_EXTENSION };
- } else {
- contentFilenames = new String[] { name + FormatSpec.BIGRAM_FILE_EXTENSION };
- }
- return contentFilenames;
- }
-
- private static String[] getContentIds(final boolean writeTimestamp) {
- final String[] contentIds;
- if (writeTimestamp) {
- contentIds = new String[] { FormatSpec.BIGRAM_FREQ_CONTENT_ID,
- FormatSpec.BIGRAM_TIMESTAMP_CONTENT_ID };
- } else {
- contentIds = new String[] { FormatSpec.BIGRAM_FREQ_CONTENT_ID };
- }
- return contentIds;
- }
-
- public void writeBigramsForOneWord(final int terminalId, final int bigramCount,
- final Iterator<WeightedString> bigramIterator, final FusionDictionary dict)
- throws IOException {
- write(FormatSpec.BIGRAM_FREQ_CONTENT_INDEX, terminalId,
- new SparseTableContentWriterInterface() {
- @Override
- public void write(final OutputStream outStream) throws IOException {
- writeBigramsForOneWordInternal(outStream, bigramIterator, dict);
- }});
- if (mWriteTimestamp) {
- write(FormatSpec.BIGRAM_TIMESTAMP_CONTENT_INDEX, terminalId,
- new SparseTableContentWriterInterface() {
- @Override
- public void write(final OutputStream outStream) throws IOException {
- initBigramTimestampsCountersAndLevelsForOneWordInternal(outStream,
- bigramCount);
- }});
- }
- }
-
- private void writeBigramsForOneWordInternal(final OutputStream outStream,
- final Iterator<WeightedString> bigramIterator, final FusionDictionary dict)
- throws IOException {
- while (bigramIterator.hasNext()) {
- final WeightedString bigram = bigramIterator.next();
- final PtNode target =
- FusionDictionary.findWordInTree(dict.mRootNodeArray, bigram.mWord);
- final int unigramFrequencyForThisWord = target.mFrequency;
- final int bigramFlags = BinaryDictEncoderUtils.makeBigramFlags(
- bigramIterator.hasNext(), 0, bigram.mFrequency,
- unigramFrequencyForThisWord, bigram.mWord);
- BinaryDictEncoderUtils.writeUIntToStream(outStream, bigramFlags,
- FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE);
- BinaryDictEncoderUtils.writeUIntToStream(outStream, target.mTerminalId,
- FormatSpec.PTNODE_ATTRIBUTE_MAX_ADDRESS_SIZE);
- }
- }
-
- private void initBigramTimestampsCountersAndLevelsForOneWordInternal(
- final OutputStream outStream, final int bigramCount) throws IOException {
- for (int i = 0; i < bigramCount; ++i) {
- // TODO: Figure out what initial values should be.
- BinaryDictEncoderUtils.writeUIntToStream(outStream, 0 /* value */,
- FormatSpec.BIGRAM_TIMESTAMP_SIZE);
- BinaryDictEncoderUtils.writeUIntToStream(outStream, 0 /* value */,
- FormatSpec.BIGRAM_COUNTER_SIZE);
- BinaryDictEncoderUtils.writeUIntToStream(outStream, 0 /* value */,
- FormatSpec.BIGRAM_LEVEL_SIZE);
- }
- }
- }
-
- private static class ShortcutContentWriter extends SparseTableContentWriter {
- public ShortcutContentWriter(final String name, final int initialCapacity,
- final File baseDir) {
- super(name + FormatSpec.SHORTCUT_FILE_EXTENSION, initialCapacity,
- FormatSpec.SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE, baseDir,
- new String[] { name + FormatSpec.SHORTCUT_FILE_EXTENSION },
- new String[] { FormatSpec.SHORTCUT_CONTENT_ID });
- }
-
- public void writeShortcutForOneWord(final int terminalId,
- final Iterator<WeightedString> shortcutIterator) throws IOException {
- write(FormatSpec.SHORTCUT_CONTENT_INDEX, terminalId,
- new SparseTableContentWriterInterface() {
- @Override
- public void write(final OutputStream outStream) throws IOException {
- writeShortcutForOneWordInternal(outStream, shortcutIterator);
- }
- });
- }
-
- private void writeShortcutForOneWordInternal(final OutputStream outStream,
- final Iterator<WeightedString> shortcutIterator) throws IOException {
- while (shortcutIterator.hasNext()) {
- final WeightedString target = shortcutIterator.next();
- final int shortcutFlags = BinaryDictEncoderUtils.makeShortcutFlags(
- shortcutIterator.hasNext(), target.mFrequency);
- BinaryDictEncoderUtils.writeUIntToStream(outStream, shortcutFlags,
- FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE);
- CharEncoding.writeString(outStream, target.mWord);
- }
- }
- }
-
- private void openStreams(final FormatOptions formatOptions, final DictionaryOptions dictOptions)
- throws FileNotFoundException, IOException {
- final FileHeader header = new FileHeader(0, dictOptions, formatOptions);
- mBaseFilename = header.getId() + "." + header.getVersion();
- mDictDir = new File(mDictPlacedDir, mBaseFilename);
- final File trieFile = new File(mDictDir, mBaseFilename + FormatSpec.TRIE_FILE_EXTENSION);
- final File freqFile = new File(mDictDir, mBaseFilename + FormatSpec.FREQ_FILE_EXTENSION);
- final File timestampFile = new File(mDictDir,
- mBaseFilename + FormatSpec.UNIGRAM_TIMESTAMP_FILE_EXTENSION);
- final File terminalAddressTableFile = new File(mDictDir,
- mBaseFilename + FormatSpec.TERMINAL_ADDRESS_TABLE_FILE_EXTENSION);
- if (!mDictDir.isDirectory()) {
- if (mDictDir.exists()) mDictDir.delete();
- mDictDir.mkdirs();
- }
- mTrieOutStream = new FileOutputStream(trieFile);
- mFreqOutStream = new FileOutputStream(freqFile);
- mTerminalAddressTableOutStream = new FileOutputStream(terminalAddressTableFile);
- if (formatOptions.mHasTimestamp) {
- mUnigramTimestampOutStream = new FileOutputStream(timestampFile);
- }
- }
-
- private void close() throws IOException {
- try {
- if (mTrieOutStream != null) {
- mTrieOutStream.close();
- }
- if (mFreqOutStream != null) {
- mFreqOutStream.close();
- }
- if (mTerminalAddressTableOutStream != null) {
- mTerminalAddressTableOutStream.close();
- }
- if (mUnigramTimestampOutStream != null) {
- mUnigramTimestampOutStream.close();
- }
- } finally {
- mTrieOutStream = null;
- mFreqOutStream = null;
- mTerminalAddressTableOutStream = null;
- }
- }
-
+ // TODO: This builds a FusionDictionary first and iterates it to add words to the binary
+ // dictionary. However, it is possible to just add words directly to the binary dictionary
+ // instead.
+ // In the long run, when we stop supporting version 2, FusionDictionary will become deprecated
+ // and we can remove it. Then we'll be able to just call BinaryDictionary directly.
@Override
- public void writeDictionary(final FusionDictionary dict, final FormatOptions formatOptions)
+ public void writeDictionary(FusionDictionary dict, FormatOptions formatOptions)
throws IOException, UnsupportedFormatException {
if (formatOptions.mVersion != FormatSpec.VERSION4) {
throw new UnsupportedFormatException("File header has a wrong version number : "
@@ -286,190 +54,71 @@ public class Ver4DictEncoder implements DictEncoder {
if (!mDictPlacedDir.isDirectory()) {
throw new UnsupportedFormatException("Given path is not a directory.");
}
-
- if (mTrieOutStream == null) {
- openStreams(formatOptions, dict.mOptions);
- }
-
- mHeaderSize = BinaryDictEncoderUtils.writeDictionaryHeader(mTrieOutStream, dict,
- formatOptions);
-
- MakedictLog.i("Flattening the tree...");
- ArrayList<PtNodeArray> flatNodes = BinaryDictEncoderUtils.flattenTree(dict.mRootNodeArray);
- int terminalCount = 0;
- for (final PtNodeArray array : flatNodes) {
- for (final PtNode node : array.mData) {
- if (node.isTerminal()) node.mTerminalId = terminalCount++;
+ if (!BinaryDictionary.createEmptyDictFile(mDictPlacedDir.getAbsolutePath(),
+ FormatSpec.VERSION4, dict.mOptions.mAttributes)) {
+ throw new IOException("Cannot create dictionary file : "
+ + mDictPlacedDir.getAbsolutePath());
+ }
+ final BinaryDictionary binaryDict = new BinaryDictionary(mDictPlacedDir.getAbsolutePath(),
+ 0l, mDictPlacedDir.length(), true /* useFullEditDistance */,
+ LocaleUtils.constructLocaleFromString(dict.mOptions.mAttributes.get(
+ FormatSpec.FileHeader.DICTIONARY_LOCALE_ATTRIBUTE)),
+ Dictionary.TYPE_USER /* Dictionary type. Does not matter for us */,
+ true /* isUpdatable */);
+ if (!binaryDict.isValidDictionary()) {
+ // Somehow createEmptyDictFile returned true, but the file was not created correctly
+ throw new IOException("Cannot create dictionary file");
+ }
+ for (final Word word : dict) {
+ // TODO: switch to addMultipleDictionaryEntries when they support shortcuts
+ if (null == word.mShortcutTargets || word.mShortcutTargets.isEmpty()) {
+ binaryDict.addUnigramWord(word.mWord, word.mFrequency,
+ null /* shortcutTarget */, 0 /* shortcutProbability */,
+ word.mIsNotAWord, word.mIsBlacklistEntry, 0 /* timestamp */);
+ } else {
+ for (final WeightedString shortcutTarget : word.mShortcutTargets) {
+ binaryDict.addUnigramWord(word.mWord, word.mFrequency,
+ shortcutTarget.mWord, shortcutTarget.mFrequency,
+ word.mIsNotAWord, word.mIsBlacklistEntry, 0 /* timestamp */);
+ }
+ }
+ if (binaryDict.needsToRunGC(true /* mindsBlockByGC */)) {
+ binaryDict.flushWithGC();
}
}
-
- MakedictLog.i("Computing addresses...");
- BinaryDictEncoderUtils.computeAddresses(dict, flatNodes, formatOptions);
- if (MakedictLog.DBG) BinaryDictEncoderUtils.checkFlatPtNodeArrayList(flatNodes);
-
- writeTerminalData(flatNodes, terminalCount);
- if (formatOptions.mHasTimestamp) {
- initUnigramTimestamps(terminalCount);
- }
- mBigramWriter = new BigramContentWriter(mBaseFilename, terminalCount, mDictDir,
- formatOptions.mHasTimestamp);
- writeBigrams(flatNodes, dict);
- mShortcutWriter = new ShortcutContentWriter(mBaseFilename, terminalCount, mDictDir);
- writeShortcuts(flatNodes);
-
- final PtNodeArray lastNodeArray = flatNodes.get(flatNodes.size() - 1);
- final int bufferSize = lastNodeArray.mCachedAddressAfterUpdate + lastNodeArray.mCachedSize;
- mTrieBuf = new byte[bufferSize];
-
- MakedictLog.i("Writing file...");
- for (PtNodeArray nodeArray : flatNodes) {
- BinaryDictEncoderUtils.writePlacedPtNodeArray(dict, this, nodeArray, formatOptions);
- }
- if (MakedictLog.DBG) {
- BinaryDictEncoderUtils.showStatistics(flatNodes);
- MakedictLog.i("has " + terminalCount + " terminals.");
+ for (final Word word0 : dict) {
+ if (null == word0.mBigrams) continue;
+ for (final WeightedString word1 : word0.mBigrams) {
+ binaryDict.addBigramWords(word0.mWord, word1.mWord, word1.mFrequency,
+ 0 /* timestamp */);
+ if (binaryDict.needsToRunGC(true /* mindsBlockByGC */)) {
+ binaryDict.flushWithGC();
+ }
+ }
}
- mTrieOutStream.write(mTrieBuf);
-
- MakedictLog.i("Done");
- close();
+ binaryDict.flushWithGC();
+ binaryDict.close();
}
@Override
public void setPosition(int position) {
- if (mTrieBuf == null || position < 0 || position >- mTrieBuf.length) return;
- mTriePos = position;
}
@Override
public int getPosition() {
- return mTriePos;
+ return 0;
}
@Override
public void writePtNodeCount(int ptNodeCount) {
- final int countSize = BinaryDictIOUtils.getPtNodeCountSize(ptNodeCount);
- // ptNodeCount must fit on one byte or two bytes.
- // Please see comments in FormatSpec
- if (countSize != 1 && countSize != 2) {
- throw new RuntimeException("Strange size from getPtNodeCountSize : " + countSize);
- }
- final int encodedPtNodeCount = (countSize == 2) ?
- (ptNodeCount | FormatSpec.LARGE_PTNODE_ARRAY_SIZE_FIELD_SIZE_FLAG) : ptNodeCount;
- mTriePos = BinaryDictEncoderUtils.writeUIntToBuffer(mTrieBuf, mTriePos, encodedPtNodeCount,
- countSize);
- }
-
- private void writePtNodeFlags(final PtNode ptNode, final FormatOptions formatOptions) {
- final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode, formatOptions);
- mTriePos = BinaryDictEncoderUtils.writeUIntToBuffer(mTrieBuf, mTriePos,
- BinaryDictEncoderUtils.makePtNodeFlags(ptNode, childrenPos, formatOptions),
- FormatSpec.PTNODE_FLAGS_SIZE);
- }
-
- private void writeParentPosition(int parentPos, final PtNode ptNode,
- final FormatOptions formatOptions) {
- if (parentPos != FormatSpec.NO_PARENT_ADDRESS) {
- parentPos -= ptNode.mCachedAddressAfterUpdate;
- }
- mTriePos = BinaryDictEncoderUtils.writeParentAddress(mTrieBuf, mTriePos, parentPos,
- formatOptions);
- }
-
- private void writeCharacters(final int[] characters, final boolean hasSeveralChars) {
- mTriePos = CharEncoding.writeCharArray(characters, mTrieBuf, mTriePos);
- if (hasSeveralChars) {
- mTrieBuf[mTriePos++] = FormatSpec.PTNODE_CHARACTERS_TERMINATOR;
- }
- }
-
- private void writeTerminalId(final int terminalId) {
- mTriePos = BinaryDictEncoderUtils.writeUIntToBuffer(mTrieBuf, mTriePos, terminalId,
- FormatSpec.PTNODE_TERMINAL_ID_SIZE);
- }
-
- private void writeChildrenPosition(PtNode ptNode, FormatOptions formatOptions) {
- final int childrenPos = BinaryDictEncoderUtils.getChildrenPosition(ptNode, formatOptions);
- if (formatOptions.mSupportsDynamicUpdate) {
- mTriePos += BinaryDictEncoderUtils.writeSignedChildrenPosition(mTrieBuf,
- mTriePos, childrenPos);
- } else {
- mTriePos += BinaryDictEncoderUtils.writeChildrenPosition(mTrieBuf,
- mTriePos, childrenPos);
- }
- }
-
- private void writeBigrams(final ArrayList<PtNodeArray> flatNodes, final FusionDictionary dict)
- throws IOException {
- mBigramWriter.openStreams();
- for (final PtNodeArray nodeArray : flatNodes) {
- for (final PtNode ptNode : nodeArray.mData) {
- if (ptNode.mBigrams != null) {
- mBigramWriter.writeBigramsForOneWord(ptNode.mTerminalId, ptNode.mBigrams.size(),
- ptNode.mBigrams.iterator(), dict);
- }
- }
- }
- mBigramWriter.closeStreams();
- }
-
- private void writeShortcuts(final ArrayList<PtNodeArray> flatNodes) throws IOException {
- mShortcutWriter.openStreams();
- for (final PtNodeArray nodeArray : flatNodes) {
- for (final PtNode ptNode : nodeArray.mData) {
- if (ptNode.mShortcutTargets != null && !ptNode.mShortcutTargets.isEmpty()) {
- mShortcutWriter.writeShortcutForOneWord(ptNode.mTerminalId,
- ptNode.mShortcutTargets.iterator());
- }
- }
- }
- mShortcutWriter.closeStreams();
}
@Override
public void writeForwardLinkAddress(int forwardLinkAddress) {
- mTriePos = BinaryDictEncoderUtils.writeUIntToBuffer(mTrieBuf, mTriePos,
- forwardLinkAddress, FormatSpec.FORWARD_LINK_ADDRESS_SIZE);
}
@Override
- public void writePtNode(final PtNode ptNode, final int parentPosition,
- final FormatOptions formatOptions, final FusionDictionary dict) {
- writePtNodeFlags(ptNode, formatOptions);
- writeParentPosition(parentPosition, ptNode, formatOptions);
- writeCharacters(ptNode.mChars, ptNode.hasSeveralChars());
- if (ptNode.isTerminal()) {
- writeTerminalId(ptNode.mTerminalId);
- }
- writeChildrenPosition(ptNode, formatOptions);
- }
-
- private void writeTerminalData(final ArrayList<PtNodeArray> flatNodes,
- final int terminalCount) throws IOException {
- final byte[] freqBuf = new byte[terminalCount * FormatSpec.FREQUENCY_AND_FLAGS_SIZE];
- final byte[] terminalAddressTableBuf =
- new byte[terminalCount * FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE];
- for (final PtNodeArray nodeArray : flatNodes) {
- for (final PtNode ptNode : nodeArray.mData) {
- if (ptNode.isTerminal()) {
- BinaryDictEncoderUtils.writeUIntToBuffer(freqBuf,
- ptNode.mTerminalId * FormatSpec.FREQUENCY_AND_FLAGS_SIZE,
- ptNode.mFrequency, FormatSpec.FREQUENCY_AND_FLAGS_SIZE);
- BinaryDictEncoderUtils.writeUIntToBuffer(terminalAddressTableBuf,
- ptNode.mTerminalId * FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE,
- ptNode.mCachedAddressAfterUpdate + mHeaderSize,
- FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE);
- }
- }
- }
- mFreqOutStream.write(freqBuf);
- mTerminalAddressTableOutStream.write(terminalAddressTableBuf);
- }
-
- private void initUnigramTimestamps(final int terminalCount) throws IOException {
- // Initial value of time stamps for each word is 0.
- final byte[] unigramTimestampBuf =
- new byte[terminalCount * FormatSpec.UNIGRAM_TIMESTAMP_SIZE];
- mUnigramTimestampOutStream.write(unigramTimestampBuf);
+ public void writePtNode(
+ PtNode ptNode, int parentPosition, FormatOptions formatOptions, FusionDictionary dict) {
}
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictUpdater.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictUpdater.java
deleted file mode 100644
index 3d8f186ba..000000000
--- a/java/src/com/android/inputmethod/latin/makedict/Ver4DictUpdater.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2013 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.makedict;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-
-/**
- * An implementation of DictUpdater for version 4 binary dictionary.
- */
-@UsedForTesting
-public class Ver4DictUpdater extends Ver4DictDecoder implements DictUpdater {
-
- @UsedForTesting
- public Ver4DictUpdater(final File dictDirectory, final int factoryType) {
- // DictUpdater must have an updatable DictBuffer.
- super(dictDirectory, ((factoryType & MASK_DICTBUFFER) == USE_BYTEARRAY)
- ? USE_BYTEARRAY : USE_WRITABLE_BYTEBUFFER);
- }
-
- @Override
- public void deleteWord(final String word) throws IOException, UnsupportedFormatException {
- if (mDictBuffer == null) openDictBuffer();
- readHeader();
- final int wordPos = getTerminalPosition(word);
- if (wordPos != FormatSpec.NOT_VALID_WORD) {
- mDictBuffer.position(wordPos);
- final int flags = PtNodeReader.readPtNodeOptionFlags(mDictBuffer);
- mDictBuffer.position(wordPos);
- mDictBuffer.put((byte) DynamicBinaryDictIOUtils.markAsDeleted(flags));
- }
- }
-
- @Override
- public void insertWord(final String word, final int frequency,
- final ArrayList<WeightedString> bigramStrings, final ArrayList<WeightedString> shortcuts,
- final boolean isNotAWord, final boolean isBlackListEntry)
- throws IOException, UnsupportedFormatException {
- // TODO: Implement this method.
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
index 1de15a333..701c29023 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
@@ -17,18 +17,16 @@
package com.android.inputmethod.latin.personalization;
import android.content.Context;
-import android.content.SharedPreferences;
import android.util.Log;
import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.BinaryDictionary.LanguageModelParam;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.ExpandableBinaryDictionary;
-import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.makedict.DictDecoder;
import com.android.inputmethod.latin.makedict.FormatSpec;
-import com.android.inputmethod.latin.settings.Settings;
-import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils;
import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils.OnAddWordListener;
@@ -36,7 +34,9 @@ import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Locale;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
/**
* This class is a base class of a dictionary that supports decaying for the personalized language
@@ -45,8 +45,7 @@ import java.util.Map;
public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableBinaryDictionary {
private static final String TAG = DecayingExpandableBinaryDictionaryBase.class.getSimpleName();
public static final boolean DBG_SAVE_RESTORE = false;
- private static final boolean DBG_STRESS_TEST = false;
- private static final boolean PROFILE_SAVE_RESTORE = LatinImeLogger.sDBG;
+ private static final boolean DBG_DUMP_ON_CLOSE = false;
/** Any pair being typed or picked */
public static final int FREQUENCY_FOR_TYPED = 2;
@@ -54,52 +53,56 @@ public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableB
public static final int FREQUENCY_FOR_WORDS_IN_DICTS = FREQUENCY_FOR_TYPED;
public static final int FREQUENCY_FOR_WORDS_NOT_IN_DICTS = Dictionary.NOT_A_PROBABILITY;
- /** Locale for which this user history dictionary is storing words */
- private final String mLocale;
+ public static final int REQUIRED_BINARY_DICTIONARY_VERSION = FormatSpec.VERSION4;
- private final String mFileName;
+ /** The locale for this dictionary. */
+ public final Locale mLocale;
- private final SharedPreferences mPrefs;
+ private final String mDictName;
- private final ArrayList<PersonalizationDictionaryUpdateSession> mSessions =
- CollectionUtils.newArrayList();
-
- // Should always be false except when we use this class for test
- @UsedForTesting boolean mIsTest = false;
+ /* package */ DecayingExpandableBinaryDictionaryBase(final Context context,
+ final Locale locale, final String dictionaryType, final String dictName) {
+ super(context, dictName, locale, dictionaryType, true);
+ mLocale = locale;
+ mDictName = dictName;
+ if (mLocale != null && mLocale.toString().length() > 1) {
+ reloadDictionaryIfRequired();
+ }
+ }
+ // Creates an instance that uses a given dictionary file for testing.
+ @UsedForTesting
/* package */ DecayingExpandableBinaryDictionaryBase(final Context context,
- final String locale, final SharedPreferences sp, final String dictionaryType,
- final String fileName) {
- super(context, fileName, dictionaryType, true);
+ final Locale locale, final String dictionaryType, final String dictName,
+ final File dictFile) {
+ super(context, dictName, locale, dictionaryType, true, dictFile);
mLocale = locale;
- mFileName = fileName;
- mPrefs = sp;
- if (mLocale != null && mLocale.length() > 1) {
- asyncLoadDictionaryToMemory();
+ mDictName = dictName;
+ if (mLocale != null && mLocale.toString().length() > 1) {
reloadDictionaryIfRequired();
}
}
@Override
public void close() {
- if (!ExpandableBinaryDictionary.ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
- closeBinaryDictionary();
+ if (DBG_DUMP_ON_CLOSE) {
+ dumpAllWordsForDebug();
}
// Flush pending writes.
- // TODO: Remove after this class become to use a dynamic binary dictionary.
- asyncFlashAllBinaryDictionary();
- Settings.writeLastUserHistoryWriteTime(mPrefs, mLocale);
+ asyncFlushBinaryDictionary();
}
@Override
protected Map<String, String> getHeaderAttributeMap() {
HashMap<String, String> attributeMap = new HashMap<String, String>();
- attributeMap.put(FormatSpec.FileHeader.SUPPORTS_DYNAMIC_UPDATE_ATTRIBUTE,
- FormatSpec.FileHeader.ATTRIBUTE_VALUE_TRUE);
attributeMap.put(FormatSpec.FileHeader.USES_FORGETTING_CURVE_ATTRIBUTE,
FormatSpec.FileHeader.ATTRIBUTE_VALUE_TRUE);
- attributeMap.put(FormatSpec.FileHeader.DICTIONARY_ID_ATTRIBUTE, mFileName);
- attributeMap.put(FormatSpec.FileHeader.DICTIONARY_LOCALE_ATTRIBUTE, mLocale);
+ attributeMap.put(FormatSpec.FileHeader.HAS_HISTORICAL_INFO_ATTRIBUTE,
+ FormatSpec.FileHeader.ATTRIBUTE_VALUE_TRUE);
+ attributeMap.put(FormatSpec.FileHeader.DICTIONARY_ID_ATTRIBUTE, mDictName);
+ attributeMap.put(FormatSpec.FileHeader.DICTIONARY_LOCALE_ATTRIBUTE, mLocale.toString());
+ attributeMap.put(FormatSpec.FileHeader.DICTIONARY_VERSION_ATTRIBUTE,
+ String.valueOf(TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())));
return attributeMap;
}
@@ -113,6 +116,25 @@ public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableB
return false;
}
+ @Override
+ protected boolean matchesExpectedBinaryDictFormatVersionForThisType(final int formatVersion) {
+ // This class is using format 4 because it's used by all version 4 dictionaries.
+ // TODO: remove this when all dynamically generated dicts use version 4.
+ return formatVersion == REQUIRED_BINARY_DICTIONARY_VERSION;
+ }
+
+ public void addMultipleDictionaryEntriesToDictionary(
+ final ArrayList<LanguageModelParam> languageModelParams,
+ final ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback callback) {
+ if (languageModelParams == null || languageModelParams.isEmpty()) {
+ if (callback != null) {
+ callback.onFinished();
+ }
+ return;
+ }
+ addMultipleDictionaryEntriesDynamically(languageModelParams, callback);
+ }
+
/**
* Pair will be added to the decaying dictionary.
*
@@ -121,70 +143,63 @@ public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableB
* context, as in beginning of a sentence for example.
* The second word may not be null (a NullPointerException would be thrown).
*/
- public void addToDictionary(final String word0, final String word1, final boolean isValid) {
+ public void addToDictionary(final String word0, final String word1, final boolean isValid,
+ final int timestamp) {
if (word1.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH ||
(word0 != null && word0.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH)) {
return;
}
- final int frequency = ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE ?
- (isValid ? FREQUENCY_FOR_WORDS_IN_DICTS : FREQUENCY_FOR_WORDS_NOT_IN_DICTS) :
- FREQUENCY_FOR_TYPED;
- addWordDynamically(word1, null /* shortcutTarget */, frequency, 0 /* shortcutFreq */,
- false /* isNotAWord */);
+ final int frequency = isValid ?
+ FREQUENCY_FOR_WORDS_IN_DICTS : FREQUENCY_FOR_WORDS_NOT_IN_DICTS;
+ addWordDynamically(word1, frequency, null /* shortcutTarget */, 0 /* shortcutFreq */,
+ false /* isNotAWord */, false /* isBlacklisted */, timestamp);
// Do not insert a word as a bigram of itself
if (word1.equals(word0)) {
return;
}
if (null != word0) {
- addBigramDynamically(word0, word1, frequency, isValid);
+ addBigramDynamically(word0, word1, frequency, timestamp);
}
}
- public void cancelAddingUserHistory(final String word0, final String word1) {
- removeBigramDynamically(word0, word1);
- }
-
@Override
protected void loadDictionaryAsync() {
- final int[] profTotalCount = { 0 };
- final String locale = getLocale();
- if (DBG_STRESS_TEST) {
- try {
- Log.w(TAG, "Start stress in loading: " + locale);
- Thread.sleep(15000);
- Log.w(TAG, "End stress in loading");
- } catch (InterruptedException e) {
+ // Never loaded to memory in Java side.
+ }
+
+ @UsedForTesting
+ public void dumpAllWordsForDebug() {
+ runAfterGcForDebug(new Runnable() {
+ @Override
+ public void run() {
+ dumpAllWordsForDebugLocked();
}
- }
- final long last = Settings.readLastUserHistoryWriteTime(mPrefs, locale);
- final long now = System.currentTimeMillis();
- final ExpandableBinaryDictionary dictionary = this;
+ });
+ }
+
+ private void dumpAllWordsForDebugLocked() {
+ Log.d(TAG, "dumpAllWordsForDebug started.");
final OnAddWordListener listener = new OnAddWordListener() {
@Override
public void setUnigram(final String word, final String shortcutTarget,
final int frequency, final int shortcutFreq) {
- if (DBG_SAVE_RESTORE) {
- Log.d(TAG, "load unigram: " + word + "," + frequency);
- }
- addWord(word, shortcutTarget, frequency, shortcutFreq, false /* isNotAWord */);
- ++profTotalCount[0];
+ Log.d(TAG, "load unigram: " + word + "," + frequency);
}
@Override
public void setBigram(final String word0, final String word1, final int frequency) {
if (word0.length() < Constants.DICTIONARY_MAX_WORD_LENGTH
&& word1.length() < Constants.DICTIONARY_MAX_WORD_LENGTH) {
- if (DBG_SAVE_RESTORE) {
- Log.d(TAG, "load bigram: " + word0 + "," + word1 + "," + frequency);
- }
- ++profTotalCount[0];
- addBigram(word0, word1, frequency, last);
+ Log.d(TAG, "load bigram: " + word0 + "," + word1 + "," + frequency);
+ } else {
+ Log.d(TAG, "Skip inserting a too long bigram: " + word0 + "," + word1 + ","
+ + frequency);
}
}
};
// Load the dictionary from binary file
- final File dictFile = new File(mContext.getFilesDir(), mFileName);
+ final File dictFile = new File(mContext.getFilesDir(), mDictName);
final DictDecoder dictDecoder = FormatSpec.getDictDecoder(dictFile,
DictDecoder.USE_BYTEARRAY);
if (dictDecoder == null) {
@@ -198,35 +213,17 @@ public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableB
UserHistoryDictIOUtils.readDictionaryBinary(dictDecoder, listener);
} catch (IOException e) {
Log.d(TAG, "IOException on opening a bytebuffer", e);
- } finally {
- if (PROFILE_SAVE_RESTORE) {
- final long diff = System.currentTimeMillis() - now;
- Log.d(TAG, "PROF: Load UserHistoryDictionary: "
- + locale + ", " + diff + "ms. load " + profTotalCount[0] + "entries.");
- }
+ } catch (UnsupportedFormatException e) {
+ Log.d(TAG, "Unsupported format, can't read the dictionary", e);
}
}
- protected String getLocale() {
- return mLocale;
- }
-
- public void registerUpdateSession(PersonalizationDictionaryUpdateSession session) {
- session.setPredictionDictionary(this);
- mSessions.add(session);
- session.onDictionaryReady();
- }
-
- public void unRegisterUpdateSession(PersonalizationDictionaryUpdateSession session) {
- mSessions.remove(session);
- }
-
@UsedForTesting
public void clearAndFlushDictionary() {
// Clear the node structure on memory
clear();
// Then flush the cleared state of the dictionary on disk.
- asyncFlashAllBinaryDictionary();
+ asyncFlushBinaryDictionary();
}
/* package */ void decayIfNeeded() {
diff --git a/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java b/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java
deleted file mode 100644
index 6f152bb91..000000000
--- a/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright (C) 2013 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.personalization;
-
-import android.content.Context;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.compat.ActivityManagerCompatUtils;
-import com.android.inputmethod.keyboard.ProximityInfo;
-import com.android.inputmethod.latin.AbstractDictionaryWriter;
-import com.android.inputmethod.latin.ExpandableDictionary;
-import com.android.inputmethod.latin.WordComposer;
-import com.android.inputmethod.latin.ExpandableDictionary.NextWord;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.makedict.DictEncoder;
-import com.android.inputmethod.latin.makedict.FormatSpec;
-import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
-import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils;
-import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils.BigramDictionaryInterface;
-import com.android.inputmethod.latin.utils.UserHistoryForgettingCurveUtils;
-import com.android.inputmethod.latin.utils.UserHistoryForgettingCurveUtils.ForgettingCurveParams;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Map;
-
-// Currently this class is used to implement dynamic prodiction dictionary.
-// TODO: Move to native code.
-public class DynamicPersonalizationDictionaryWriter extends AbstractDictionaryWriter {
- private static final String TAG = DynamicPersonalizationDictionaryWriter.class.getSimpleName();
- /** Maximum number of pairs. Pruning will start when databases goes above this number. */
- public static final int DEFAULT_MAX_HISTORY_BIGRAMS = 10000;
- public static final int LOW_MEMORY_MAX_HISTORY_BIGRAMS = 2000;
-
- /** Any pair being typed or picked */
- private static final int FREQUENCY_FOR_TYPED = 2;
-
- private static final int BINARY_DICT_VERSION = 3;
- private static final FormatSpec.FormatOptions FORMAT_OPTIONS =
- new FormatSpec.FormatOptions(BINARY_DICT_VERSION, true /* supportsDynamicUpdate */);
-
- private final UserHistoryDictionaryBigramList mBigramList =
- new UserHistoryDictionaryBigramList();
- private final ExpandableDictionary mExpandableDictionary;
- private final int mMaxHistoryBigrams;
-
- public DynamicPersonalizationDictionaryWriter(final Context context, final String dictType) {
- super(context, dictType);
- mExpandableDictionary = new ExpandableDictionary(dictType);
- final boolean isLowRamDevice = ActivityManagerCompatUtils.isLowRamDevice(context);
- mMaxHistoryBigrams = isLowRamDevice ?
- LOW_MEMORY_MAX_HISTORY_BIGRAMS : DEFAULT_MAX_HISTORY_BIGRAMS;
- }
-
- @Override
- public void clear() {
- mBigramList.evictAll();
- mExpandableDictionary.clearDictionary();
- }
-
- /**
- * Adds a word unigram to the fusion dictionary. Call updateBinaryDictionary when all changes
- * are done to update the binary dictionary.
- * @param word The word to add.
- * @param shortcutTarget A shortcut target for this word, or null if none.
- * @param frequency The frequency for this unigram.
- * @param shortcutFreq The frequency of the shortcut (0~15, with 15 = whitelist). Ignored
- * if shortcutTarget is null.
- * @param isNotAWord true if this is not a word, i.e. shortcut only.
- */
- @Override
- public void addUnigramWord(final String word, final String shortcutTarget, final int frequency,
- final int shortcutFreq, final boolean isNotAWord) {
- if (mBigramList.size() > mMaxHistoryBigrams * 2) {
- // Too many entries: just stop adding new vocabulary and wait next refresh.
- return;
- }
- mExpandableDictionary.addWord(word, shortcutTarget, frequency, shortcutFreq);
- mBigramList.addBigram(null, word, (byte)frequency);
- }
-
- @Override
- public void addBigramWords(final String word0, final String word1, final int frequency,
- final boolean isValid, final long lastModifiedTime) {
- if (mBigramList.size() > mMaxHistoryBigrams * 2) {
- // Too many entries: just stop adding new vocabulary and wait next refresh.
- return;
- }
- if (lastModifiedTime > 0) {
- mExpandableDictionary.setBigramAndGetFrequency(word0, word1,
- new ForgettingCurveParams(frequency, System.currentTimeMillis(),
- lastModifiedTime));
- mBigramList.addBigram(word0, word1, (byte)frequency);
- } else {
- mExpandableDictionary.setBigramAndGetFrequency(word0, word1,
- new ForgettingCurveParams(isValid));
- mBigramList.addBigram(word0, word1, (byte)frequency);
- }
- }
-
- @Override
- public void removeBigramWords(final String word0, final String word1) {
- if (mBigramList.removeBigram(word0, word1)) {
- mExpandableDictionary.removeBigram(word0, word1);
- }
- }
-
- @Override
- protected void writeDictionary(final DictEncoder dictEncoder,
- final Map<String, String> attributeMap) throws IOException, UnsupportedFormatException {
- UserHistoryDictIOUtils.writeDictionary(dictEncoder,
- new FrequencyProvider(mBigramList, mExpandableDictionary, mMaxHistoryBigrams),
- mBigramList, FORMAT_OPTIONS);
- }
-
- private static class FrequencyProvider implements BigramDictionaryInterface {
- private final UserHistoryDictionaryBigramList mBigramList;
- private final ExpandableDictionary mExpandableDictionary;
- private final int mMaxHistoryBigrams;
-
- public FrequencyProvider(final UserHistoryDictionaryBigramList bigramList,
- final ExpandableDictionary expandableDictionary, final int maxHistoryBigrams) {
- mBigramList = bigramList;
- mExpandableDictionary = expandableDictionary;
- mMaxHistoryBigrams = maxHistoryBigrams;
- }
-
- @Override
- public int getFrequency(final String word0, final String word1) {
- final int freq;
- if (word0 == null) { // unigram
- freq = FREQUENCY_FOR_TYPED;
- } else { // bigram
- final NextWord nw = mExpandableDictionary.getBigramWord(word0, word1);
- if (nw != null) {
- final ForgettingCurveParams forgettingCurveParams = nw.getFcParams();
- final byte prevFc = mBigramList.getBigrams(word0).get(word1);
- final byte fc = forgettingCurveParams.getFc();
- final boolean isValid = forgettingCurveParams.isValid();
- if (prevFc > 0 && prevFc == fc) {
- freq = fc & 0xFF;
- } else if (UserHistoryForgettingCurveUtils.
- needsToSave(fc, isValid, mBigramList.size() <= mMaxHistoryBigrams)) {
- freq = fc & 0xFF;
- } else {
- // Delete this entry
- freq = -1;
- }
- } else {
- // Delete this entry
- freq = -1;
- }
- }
- return freq;
- }
- }
-
- @Override
- public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final String prevWord, final ProximityInfo proximityInfo,
- boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
- return mExpandableDictionary.getSuggestions(composer, prevWord, proximityInfo,
- blockOffensiveWords, additionalFeaturesOptions);
- }
-
- @Override
- public boolean isValidWord(final String word) {
- return mExpandableDictionary.isValidWord(word);
- }
-
- @UsedForTesting
- public boolean isInBigramListForTests(final String word) {
- // TODO: Use native method to determine whether the word is in dictionary or not
- return mBigramList.containsKey(word) || mBigramList.getBigrams(null).containsKey(word);
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
index f257165cb..596562f1d 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
@@ -16,53 +16,37 @@
package com.android.inputmethod.latin.personalization;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.ExpandableBinaryDictionary;
import com.android.inputmethod.latin.utils.CollectionUtils;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Locale;
+
import android.content.Context;
-import android.content.SharedPreferences;
-import java.util.ArrayList;
+public class PersonalizationDictionary extends DecayingExpandableBinaryDictionaryBase {
+ private static final String NAME = PersonalizationDictionary.class.getSimpleName();
-/**
- * This class is a dictionary for the personalized language model that uses binary dictionary.
- */
-public class PersonalizationDictionary extends ExpandableBinaryDictionary {
- private static final String NAME = "personalization";
private final ArrayList<PersonalizationDictionaryUpdateSession> mSessions =
CollectionUtils.newArrayList();
- /** Locale for which this user history dictionary is storing words */
- private final String mLocale;
-
- public PersonalizationDictionary(final Context context, final String locale,
- final SharedPreferences prefs) {
- // TODO: Make isUpdatable true.
- super(context, getFilenameWithLocale(NAME, locale), Dictionary.TYPE_PERSONALIZATION,
- false /* isUpdatable */);
- mLocale = locale;
- // TODO: Restore last updated time
- loadDictionary();
- }
-
- @Override
- protected void loadDictionaryAsync() {
- // TODO: Implement
- }
-
- @Override
- protected boolean hasContentChanged() {
- return false;
+ /* package */ PersonalizationDictionary(final Context context, final Locale locale) {
+ super(context, locale, Dictionary.TYPE_PERSONALIZATION,
+ getDictNameWithLocale(NAME, locale));
}
- @Override
- protected boolean needsToReloadBeforeWriting() {
- return false;
+ // Creates an instance that uses a given dictionary file for testing.
+ @UsedForTesting
+ public PersonalizationDictionary(final Context context, final Locale locale,
+ final File dictFile) {
+ super(context, locale, Dictionary.TYPE_PERSONALIZATION, getDictNameWithLocale(NAME, locale),
+ dictFile);
}
public void registerUpdateSession(PersonalizationDictionaryUpdateSession session) {
- session.setDictionary(this);
+ session.setPredictionDictionary(this);
mSessions.add(session);
session.onDictionaryReady();
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegister.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegister.java
index c1833ff14..542bda621 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegister.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegister.java
@@ -20,18 +20,18 @@ import android.content.Context;
import android.content.res.Configuration;
public class PersonalizationDictionarySessionRegister {
- public static void init(Context context) {
+ public static void init(final Context context) {
}
public static void onConfigurationChanged(final Context context, final Configuration conf) {
}
- public static void onUpdateData(Context context, String type) {
+ public static void onUpdateData(final Context context, final String type) {
}
- public static void onRemoveData(Context context, String type) {
+ public static void onRemoveData(final Context context, final String type) {
}
- public static void onDestroy(Context context) {
+ public static void onDestroy(final Context context) {
}
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java
index a86f6e584..61354762b 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java
@@ -18,61 +18,37 @@ package com.android.inputmethod.latin.personalization;
import android.content.Context;
+import com.android.inputmethod.latin.BinaryDictionary.LanguageModelParam;
+import com.android.inputmethod.latin.ExpandableBinaryDictionary;
+
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Locale;
/**
* This class is a session where a data provider can communicate with a personalization
* dictionary.
*/
public abstract class PersonalizationDictionaryUpdateSession {
- /**
- * This class is a parameter for a new unigram or bigram word which will be added
- * to the personalization dictionary.
- */
- public static class PersonalizationLanguageModelParam {
- public final String mWord0;
- public final String mWord1;
- public final boolean mIsValid;
- public final int mFrequency;
- public PersonalizationLanguageModelParam(String word0, String word1, boolean isValid,
- int frequency) {
- mWord0 = word0;
- mWord1 = word1;
- mIsValid = isValid;
- mFrequency = frequency;
- }
- }
-
- // TODO: Use a dynamic binary dictionary instead
public WeakReference<PersonalizationDictionary> mDictionary;
- public WeakReference<DecayingExpandableBinaryDictionaryBase> mPredictionDictionary;
- public final String mSystemLocale;
- public PersonalizationDictionaryUpdateSession(String locale) {
+ public final Locale mSystemLocale;
+
+ public PersonalizationDictionaryUpdateSession(final Locale locale) {
mSystemLocale = locale;
}
public abstract void onDictionaryReady();
- public abstract void onDictionaryClosed(Context context);
+ public abstract void onDictionaryClosed(final Context context);
- public void setDictionary(PersonalizationDictionary dictionary) {
+ public void setPredictionDictionary(final PersonalizationDictionary dictionary) {
mDictionary = new WeakReference<PersonalizationDictionary>(dictionary);
}
- public void setPredictionDictionary(DecayingExpandableBinaryDictionaryBase dictionary) {
- mPredictionDictionary =
- new WeakReference<DecayingExpandableBinaryDictionaryBase>(dictionary);
- }
-
protected PersonalizationDictionary getDictionary() {
return mDictionary == null ? null : mDictionary.get();
}
- protected DecayingExpandableBinaryDictionaryBase getPredictionDictionary() {
- return mPredictionDictionary == null ? null : mPredictionDictionary.get();
- }
-
private void unsetDictionary() {
final PersonalizationDictionary dictionary = getDictionary();
if (dictionary == null) {
@@ -81,48 +57,30 @@ public abstract class PersonalizationDictionaryUpdateSession {
dictionary.unRegisterUpdateSession(this);
}
- private void unsetPredictionDictionary() {
- final DecayingExpandableBinaryDictionaryBase dictionary = getPredictionDictionary();
- if (dictionary == null) {
- return;
- }
- dictionary.unRegisterUpdateSession(this);
- }
-
- public void clearAndFlushPredictionDictionary(Context context) {
- final DecayingExpandableBinaryDictionaryBase dictionary = getPredictionDictionary();
+ public void clearAndFlushDictionary(final Context context) {
+ final PersonalizationDictionary dictionary = getDictionary();
if (dictionary == null) {
return;
}
dictionary.clearAndFlushDictionary();
}
- public void closeSession(Context context) {
+ public void closeSession(final Context context) {
unsetDictionary();
- unsetPredictionDictionary();
onDictionaryClosed(context);
}
- // TODO: Support multi locale to add bigram
- public void addBigramToPersonalizationDictionary(String word0, String word1, boolean isValid,
- int frequency) {
- final DecayingExpandableBinaryDictionaryBase dictionary = getPredictionDictionary();
- if (dictionary == null) {
- return;
- }
- dictionary.addToDictionary(word0, word1, isValid);
- }
-
- // Bulk import
- // TODO: Support multi locale to add bigram
- public void addBigramsToPersonalizationDictionary(
- final ArrayList<PersonalizationLanguageModelParam> lmParams) {
- final DecayingExpandableBinaryDictionaryBase dictionary = getPredictionDictionary();
+ // TODO: Support multi locale.
+ public void addMultipleDictionaryEntriesToDictionary(
+ final ArrayList<LanguageModelParam> languageModelParams,
+ final ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback callback) {
+ final PersonalizationDictionary dictionary = getDictionary();
if (dictionary == null) {
+ if (callback != null) {
+ callback.onFinished();
+ }
return;
}
- for (final PersonalizationLanguageModelParam lmParam : lmParams) {
- dictionary.addToDictionary(lmParam.mWord0, lmParam.mWord1, lmParam.mIsValid);
- }
+ dictionary.addMultipleDictionaryEntriesToDictionary(languageModelParams, callback);
}
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
index 221ddeeba..d55cae132 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
@@ -19,11 +19,10 @@ package com.android.inputmethod.latin.personalization;
import com.android.inputmethod.latin.utils.CollectionUtils;
import android.content.Context;
-import android.content.SharedPreferences;
-import android.preference.PreferenceManager;
import android.util.Log;
import java.lang.ref.SoftReference;
+import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
public class PersonalizationHelper {
@@ -31,21 +30,16 @@ public class PersonalizationHelper {
private static final boolean DEBUG = false;
private static final ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>
sLangUserHistoryDictCache = CollectionUtils.newConcurrentHashMap();
-
private static final ConcurrentHashMap<String, SoftReference<PersonalizationDictionary>>
sLangPersonalizationDictCache = CollectionUtils.newConcurrentHashMap();
- private static final ConcurrentHashMap<String,
- SoftReference<PersonalizationPredictionDictionary>>
- sLangPersonalizationPredictionDictCache =
- CollectionUtils.newConcurrentHashMap();
-
public static UserHistoryDictionary getUserHistoryDictionary(
- final Context context, final String locale, final SharedPreferences sp) {
+ final Context context, final Locale locale) {
+ final String localeStr = locale.toString();
synchronized (sLangUserHistoryDictCache) {
- if (sLangUserHistoryDictCache.containsKey(locale)) {
+ if (sLangUserHistoryDictCache.containsKey(localeStr)) {
final SoftReference<UserHistoryDictionary> ref =
- sLangUserHistoryDictCache.get(locale);
+ sLangUserHistoryDictCache.get(localeStr);
final UserHistoryDictionary dict = ref == null ? null : ref.get();
if (dict != null) {
if (DEBUG) {
@@ -55,8 +49,9 @@ public class PersonalizationHelper {
return dict;
}
}
- final UserHistoryDictionary dict = new UserHistoryDictionary(context, locale, sp);
- sLangUserHistoryDictCache.put(locale, new SoftReference<UserHistoryDictionary>(dict));
+ final UserHistoryDictionary dict = new UserHistoryDictionary(context, locale);
+ sLangUserHistoryDictCache.put(localeStr,
+ new SoftReference<UserHistoryDictionary>(dict));
return dict;
}
}
@@ -74,57 +69,30 @@ public class PersonalizationHelper {
}
public static void registerPersonalizationDictionaryUpdateSession(final Context context,
- final PersonalizationDictionaryUpdateSession session, String locale) {
- final PersonalizationPredictionDictionary predictionDictionary =
- getPersonalizationPredictionDictionary(context, locale,
- PreferenceManager.getDefaultSharedPreferences(context));
- predictionDictionary.registerUpdateSession(session);
- final PersonalizationDictionary dictionary =
- getPersonalizationDictionary(context, locale,
- PreferenceManager.getDefaultSharedPreferences(context));
- dictionary.registerUpdateSession(session);
+ final PersonalizationDictionaryUpdateSession session, final Locale locale) {
+ final PersonalizationDictionary personalizationDictionary =
+ getPersonalizationDictionary(context, locale);
+ personalizationDictionary.registerUpdateSession(session);
}
public static PersonalizationDictionary getPersonalizationDictionary(
- final Context context, final String locale, final SharedPreferences sp) {
+ final Context context, final Locale locale) {
+ final String localeStr = locale.toString();
synchronized (sLangPersonalizationDictCache) {
- if (sLangPersonalizationDictCache.containsKey(locale)) {
+ if (sLangPersonalizationDictCache.containsKey(localeStr)) {
final SoftReference<PersonalizationDictionary> ref =
- sLangPersonalizationDictCache.get(locale);
+ sLangPersonalizationDictCache.get(localeStr);
final PersonalizationDictionary dict = ref == null ? null : ref.get();
if (dict != null) {
if (DEBUG) {
- Log.w(TAG, "Use cached PersonalizationDictCache for " + locale);
+ Log.w(TAG, "Use cached PersonalizationDictionary for " + locale);
}
return dict;
}
}
- final PersonalizationDictionary dict =
- new PersonalizationDictionary(context, locale, sp);
+ final PersonalizationDictionary dict = new PersonalizationDictionary(context, locale);
sLangPersonalizationDictCache.put(
- locale, new SoftReference<PersonalizationDictionary>(dict));
- return dict;
- }
- }
-
- public static PersonalizationPredictionDictionary getPersonalizationPredictionDictionary(
- final Context context, final String locale, final SharedPreferences sp) {
- synchronized (sLangPersonalizationPredictionDictCache) {
- if (sLangPersonalizationPredictionDictCache.containsKey(locale)) {
- final SoftReference<PersonalizationPredictionDictionary> ref =
- sLangPersonalizationPredictionDictCache.get(locale);
- final PersonalizationPredictionDictionary dict = ref == null ? null : ref.get();
- if (dict != null) {
- if (DEBUG) {
- Log.w(TAG, "Use cached PersonalizationPredictionDictionary for " + locale);
- }
- return dict;
- }
- }
- final PersonalizationPredictionDictionary dict =
- new PersonalizationPredictionDictionary(context, locale, sp);
- sLangPersonalizationPredictionDictCache.put(
- locale, new SoftReference<PersonalizationPredictionDictionary>(dict));
+ localeStr, new SoftReference<PersonalizationDictionary>(dict));
return dict;
}
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java
deleted file mode 100644
index 432954453..000000000
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2013 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.personalization;
-
-import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.ExpandableBinaryDictionary;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-
-public class PersonalizationPredictionDictionary extends DecayingExpandableBinaryDictionaryBase {
- private static final String NAME = PersonalizationPredictionDictionary.class.getSimpleName();
-
- /* package */ PersonalizationPredictionDictionary(final Context context, final String locale,
- final SharedPreferences sp) {
- super(context, locale, sp, Dictionary.TYPE_PERSONALIZATION_PREDICTION_IN_JAVA,
- getDictionaryFileName(locale));
- }
-
- private static String getDictionaryFileName(final String locale) {
- return NAME + "." + locale + ExpandableBinaryDictionary.DICT_FILE_EXTENSION;
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
index a60226d7e..868f21cbc 100644
--- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
@@ -16,11 +16,13 @@
package com.android.inputmethod.latin.personalization;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.ExpandableBinaryDictionary;
+
+import java.io.File;
+import java.util.Locale;
import android.content.Context;
-import android.content.SharedPreferences;
/**
* Locally gathers stats about the words user types and various other signals like auto-correction
@@ -29,12 +31,19 @@ import android.content.SharedPreferences;
public class UserHistoryDictionary extends DecayingExpandableBinaryDictionaryBase {
/* package for tests */ static final String NAME =
UserHistoryDictionary.class.getSimpleName();
- /* package */ UserHistoryDictionary(final Context context, final String locale,
- final SharedPreferences sp) {
- super(context, locale, sp, Dictionary.TYPE_USER_HISTORY, getDictionaryFileName(locale));
+ /* package */ UserHistoryDictionary(final Context context, final Locale locale) {
+ super(context, locale, Dictionary.TYPE_USER_HISTORY, getDictNameWithLocale(NAME, locale));
+ }
+
+ // Creates an instance that uses a given dictionary file for testing.
+ @UsedForTesting
+ public UserHistoryDictionary(final Context context, final Locale locale,
+ final File dictFile) {
+ super(context, locale, Dictionary.TYPE_USER_HISTORY, getDictNameWithLocale(NAME, locale),
+ dictFile);
}
- private static String getDictionaryFileName(final String locale) {
- return NAME + "." + locale + ExpandableBinaryDictionary.DICT_FILE_EXTENSION;
+ public void cancelAddingUserHistory(final String word0, final String word1) {
+ removeBigramDynamically(word0, word1);
}
}
diff --git a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
index da1fb73fe..d060485bd 100644
--- a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
@@ -112,8 +112,7 @@ public final class DebugSettings extends PreferenceFragment
updateDebugMode();
mServiceNeedsRestart = true;
}
- } else if (key.equals(PREF_FORCE_NON_DISTINCT_MULTITOUCH)
- || key.equals(PREF_USE_ONLY_PERSONALIZATION_DICTIONARY_FOR_DEBUG)) {
+ } else if (key.equals(PREF_FORCE_NON_DISTINCT_MULTITOUCH)) {
mServiceNeedsRestart = true;
}
}
diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java
index df2c6907f..94f145813 100644
--- a/java/src/com/android/inputmethod/latin/settings/Settings.java
+++ b/java/src/com/android/inputmethod/latin/settings/Settings.java
@@ -27,12 +27,10 @@ import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
import com.android.inputmethod.latin.InputAttributes;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
-import com.android.inputmethod.latin.utils.LocaleUtils;
import com.android.inputmethod.latin.utils.ResourceUtils;
import com.android.inputmethod.latin.utils.RunInLocale;
import com.android.inputmethod.latin.utils.StringUtils;
-import java.util.HashMap;
import java.util.Locale;
import java.util.concurrent.locks.ReentrantLock;
@@ -53,10 +51,9 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public static final String PREF_AUTO_CORRECTION_THRESHOLD = "auto_correction_threshold";
public static final String PREF_SHOW_SUGGESTIONS_SETTING = "show_suggestions_setting";
public static final String PREF_MISC_SETTINGS = "misc_settings";
- public static final String PREF_LAST_USER_DICTIONARY_WRITE_TIME =
- "last_user_dictionary_write_time";
public static final String PREF_ADVANCED_SETTINGS = "pref_advanced_settings";
public static final String PREF_KEY_USE_CONTACTS_DICT = "pref_key_use_contacts_dict";
+ public static final String PREF_KEY_USE_PERSONALIZED_DICTS = "pref_key_use_personalized_dicts";
public static final String PREF_KEY_USE_DOUBLE_SPACE_PERIOD =
"pref_key_use_double_space_period";
public static final String PREF_BLOCK_POTENTIALLY_OFFENSIVE =
@@ -229,16 +226,15 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
res.getBoolean(R.bool.config_default_phrase_gesture_enabled));
}
- public static boolean readFromBuildConfigIfToShowKeyPreviewPopupSettingsOption(
- final Resources res) {
- return res.getBoolean(R.bool.config_enable_show_option_of_key_preview_popup);
+ public static boolean readFromBuildConfigIfToShowKeyPreviewPopupOption(final Resources res) {
+ return res.getBoolean(R.bool.config_enable_show_key_preview_popup_option);
}
public static boolean readKeyPreviewPopupEnabled(final SharedPreferences prefs,
final Resources res) {
final boolean defaultKeyPreviewPopup = res.getBoolean(
R.bool.config_default_key_preview_popup);
- if (!readFromBuildConfigIfToShowKeyPreviewPopupSettingsOption(res)) {
+ if (!readFromBuildConfigIfToShowKeyPreviewPopupOption(res)) {
return defaultKeyPreviewPopup;
}
return prefs.getBoolean(PREF_POPUP_ON, defaultKeyPreviewPopup);
@@ -333,25 +329,6 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
return prefs.getBoolean(DebugSettings.PREF_USABILITY_STUDY_MODE, true);
}
- public static long readLastUserHistoryWriteTime(final SharedPreferences prefs,
- final String locale) {
- final String str = prefs.getString(PREF_LAST_USER_DICTIONARY_WRITE_TIME, "");
- final HashMap<String, Long> map = LocaleUtils.localeAndTimeStrToHashMap(str);
- if (map.containsKey(locale)) {
- return map.get(locale);
- }
- return 0;
- }
-
- public static void writeLastUserHistoryWriteTime(final SharedPreferences prefs,
- final String locale) {
- final String oldStr = prefs.getString(PREF_LAST_USER_DICTIONARY_WRITE_TIME, "");
- final HashMap<String, Long> map = LocaleUtils.localeAndTimeStrToHashMap(oldStr);
- map.put(locale, System.currentTimeMillis());
- final String newStr = LocaleUtils.localeAndTimeHashMapToStr(map);
- prefs.edit().putString(PREF_LAST_USER_DICTIONARY_WRITE_TIME, newStr).apply();
- }
-
public static boolean readUseFullscreenMode(final Resources res) {
return res.getBoolean(R.bool.config_use_fullscreen_mode);
}
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
index 5c60a7350..d7a3e95b3 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
@@ -48,7 +48,6 @@ import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
import com.android.inputmethod.latin.utils.ApplicationUtils;
import com.android.inputmethod.latin.utils.FeedbackUtils;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
-import com.android.inputmethod.research.ResearchLogger;
import com.android.inputmethodcommon.InputMethodSettingsFragment;
import java.util.TreeSet;
@@ -61,13 +60,6 @@ public final class SettingsFragment extends InputMethodSettingsFragment
DBG_USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS
|| Build.VERSION.SDK_INT <= 18 /* Build.VERSION.JELLY_BEAN_MR2 */;
- private CheckBoxPreference mVoiceInputKeyPreference;
- private ListPreference mShowCorrectionSuggestionsPreference;
- private ListPreference mAutoCorrectionThresholdPreference;
- private ListPreference mKeyPreviewPopupDismissDelay;
- // Use bigrams to predict the next word when there is no input for it yet
- private CheckBoxPreference mBigramPrediction;
-
private void setPreferenceEnabled(final String preferenceKey, final boolean enabled) {
final Preference preference = findPreference(preferenceKey);
if (preference != null) {
@@ -75,6 +67,18 @@ public final class SettingsFragment extends InputMethodSettingsFragment
}
}
+ private void updateListPreferenceSummaryToCurrentValue(final String prefKey) {
+ // Because the "%s" summary trick of {@link ListPreference} doesn't work properly before
+ // KitKat, we need to update the summary programmatically.
+ final ListPreference listPreference = (ListPreference)findPreference(prefKey);
+ if (listPreference == null) {
+ return;
+ }
+ final CharSequence entries[] = listPreference.getEntries();
+ final int entryIndex = listPreference.findIndexOfValue(listPreference.getValue());
+ listPreference.setSummary(entryIndex < 0 ? null : entries[entryIndex]);
+ }
+
private static void removePreference(final String preferenceKey, final PreferenceGroup parent) {
if (parent == null) {
return;
@@ -107,16 +111,9 @@ public final class SettingsFragment extends InputMethodSettingsFragment
SubtypeLocaleUtils.init(context);
AudioAndHapticFeedbackManager.init(context);
- mVoiceInputKeyPreference =
- (CheckBoxPreference) findPreference(Settings.PREF_VOICE_INPUT_KEY);
- mShowCorrectionSuggestionsPreference =
- (ListPreference) findPreference(Settings.PREF_SHOW_SUGGESTIONS_SETTING);
final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
prefs.registerOnSharedPreferenceChangeListener(this);
- mAutoCorrectionThresholdPreference =
- (ListPreference) findPreference(Settings.PREF_AUTO_CORRECTION_THRESHOLD);
- mBigramPrediction = (CheckBoxPreference) findPreference(Settings.PREF_BIGRAM_PREDICTIONS);
ensureConsistencyOfAutoCorrectionSettings();
final PreferenceGroup generalSettings =
@@ -143,12 +140,7 @@ public final class SettingsFragment extends InputMethodSettingsFragment
feedbackSettings.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(final Preference pref) {
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- // Use development-only feedback mechanism
- ResearchLogger.getInstance().presentFeedbackDialogFromSettings();
- } else {
- FeedbackUtils.showFeedbackForm(getActivity());
- }
+ FeedbackUtils.showFeedbackForm(getActivity());
return true;
}
});
@@ -167,7 +159,7 @@ public final class SettingsFragment extends InputMethodSettingsFragment
final boolean showVoiceKeyOption = res.getBoolean(
R.bool.config_enable_show_voice_key_option);
if (!showVoiceKeyOption) {
- generalSettings.removePreference(mVoiceInputKeyPreference);
+ removePreference(Settings.PREF_VOICE_INPUT_KEY, generalSettings);
}
final PreferenceGroup advancedSettings =
@@ -177,26 +169,27 @@ public final class SettingsFragment extends InputMethodSettingsFragment
removePreference(Settings.PREF_VIBRATION_DURATION_SETTINGS, advancedSettings);
}
- mKeyPreviewPopupDismissDelay =
- (ListPreference) findPreference(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
- if (!Settings.readFromBuildConfigIfToShowKeyPreviewPopupSettingsOption(res)) {
+ if (!Settings.readFromBuildConfigIfToShowKeyPreviewPopupOption(res)) {
removePreference(Settings.PREF_POPUP_ON, generalSettings);
removePreference(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY, advancedSettings);
} else {
+ // TODO: Cleanup this setup.
+ final ListPreference keyPreviewPopupDismissDelay =
+ (ListPreference) findPreference(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
final String popupDismissDelayDefaultValue = Integer.toString(res.getInteger(
R.integer.config_key_preview_linger_timeout));
- mKeyPreviewPopupDismissDelay.setEntries(new String[] {
+ keyPreviewPopupDismissDelay.setEntries(new String[] {
res.getString(R.string.key_preview_popup_dismiss_no_delay),
res.getString(R.string.key_preview_popup_dismiss_default_delay),
});
- mKeyPreviewPopupDismissDelay.setEntryValues(new String[] {
+ keyPreviewPopupDismissDelay.setEntryValues(new String[] {
"0",
popupDismissDelayDefaultValue
});
- if (null == mKeyPreviewPopupDismissDelay.getValue()) {
- mKeyPreviewPopupDismissDelay.setValue(popupDismissDelayDefaultValue);
+ if (null == keyPreviewPopupDismissDelay.getValue()) {
+ keyPreviewPopupDismissDelay.setValue(popupDismissDelayDefaultValue);
}
- mKeyPreviewPopupDismissDelay.setEnabled(
+ keyPreviewPopupDismissDelay.setEnabled(
Settings.readKeyPreviewPopupEnabled(prefs, res));
}
@@ -243,20 +236,19 @@ public final class SettingsFragment extends InputMethodSettingsFragment
@Override
public void onResume() {
super.onResume();
- final boolean isShortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled();
- if (!isShortcutImeEnabled) {
- getPreferenceScreen().removePreference(mVoiceInputKeyPreference);
- }
final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
+ final Resources res = getResources();
+ final boolean isShortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled();
+ setPreferenceEnabled(Settings.PREF_VOICE_INPUT_KEY, isShortcutImeEnabled);
final CheckBoxPreference showSetupWizardIcon =
(CheckBoxPreference)findPreference(Settings.PREF_SHOW_SETUP_WIZARD_ICON);
if (showSetupWizardIcon != null) {
showSetupWizardIcon.setChecked(Settings.readShowSetupWizardIcon(prefs, getActivity()));
}
- updateShowCorrectionSuggestionsSummary();
- updateKeyPreviewPopupDelaySummary();
- updateColorSchemeSummary(prefs, getResources());
- updateCustomInputStylesSummary();
+ updateListPreferenceSummaryToCurrentValue(Settings.PREF_SHOW_SUGGESTIONS_SETTING);
+ updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
+ updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEYBOARD_LAYOUT);
+ updateCustomInputStylesSummary(prefs, res);
}
@Override
@@ -287,50 +279,26 @@ public final class SettingsFragment extends InputMethodSettingsFragment
LauncherIconVisibilityManager.updateSetupWizardIconVisibility(getActivity());
}
ensureConsistencyOfAutoCorrectionSettings();
- updateShowCorrectionSuggestionsSummary();
- updateKeyPreviewPopupDelaySummary();
- updateColorSchemeSummary(prefs, res);
+ updateListPreferenceSummaryToCurrentValue(Settings.PREF_SHOW_SUGGESTIONS_SETTING);
+ updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
+ updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEYBOARD_LAYOUT);
refreshEnablingsOfKeypressSoundAndVibrationSettings(prefs, getResources());
}
private void ensureConsistencyOfAutoCorrectionSettings() {
final String autoCorrectionOff = getResources().getString(
R.string.auto_correction_threshold_mode_index_off);
- final String currentSetting = mAutoCorrectionThresholdPreference.getValue();
- mBigramPrediction.setEnabled(!currentSetting.equals(autoCorrectionOff));
- }
-
- private void updateShowCorrectionSuggestionsSummary() {
- mShowCorrectionSuggestionsPreference.setSummary(
- getResources().getStringArray(R.array.prefs_suggestion_visibilities)
- [mShowCorrectionSuggestionsPreference.findIndexOfValue(
- mShowCorrectionSuggestionsPreference.getValue())]);
+ final ListPreference autoCorrectionThresholdPref = (ListPreference)findPreference(
+ Settings.PREF_AUTO_CORRECTION_THRESHOLD);
+ final String currentSetting = autoCorrectionThresholdPref.getValue();
+ setPreferenceEnabled(
+ Settings.PREF_BIGRAM_PREDICTIONS, !currentSetting.equals(autoCorrectionOff));
}
- private void updateColorSchemeSummary(final SharedPreferences prefs, final Resources res) {
- // Because the "%s" summary trick of {@link ListPreference} doesn't work properly before
- // KitKat, we need to update the summary by code.
- final Preference preference = findPreference(Settings.PREF_KEYBOARD_LAYOUT);
- if (!(preference instanceof ListPreference)) {
- Log.w(TAG, "Can't find Keyboard Color Scheme preference");
- return;
- }
- final ListPreference colorSchemePreference = (ListPreference)preference;
- final int themeIndex = Settings.readKeyboardThemeIndex(prefs, res);
- int entryIndex = colorSchemePreference.findIndexOfValue(Integer.toString(themeIndex));
- if (entryIndex < 0) {
- final int defaultThemeIndex = Settings.resetAndGetDefaultKeyboardThemeIndex(prefs, res);
- entryIndex = colorSchemePreference.findIndexOfValue(
- Integer.toString(defaultThemeIndex));
- }
- colorSchemePreference.setSummary(colorSchemePreference.getEntries()[entryIndex]);
- }
-
- private void updateCustomInputStylesSummary() {
+ private void updateCustomInputStylesSummary(final SharedPreferences prefs,
+ final Resources res) {
final PreferenceScreen customInputStyles =
(PreferenceScreen)findPreference(Settings.PREF_CUSTOM_INPUT_STYLES);
- final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
- final Resources res = getResources();
final String prefSubtype = Settings.readPrefAdditionalSubtypes(prefs, res);
final InputMethodSubtype[] subtypes =
AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefSubtype);
@@ -342,13 +310,6 @@ public final class SettingsFragment extends InputMethodSettingsFragment
customInputStyles.setSummary(styles);
}
- private void updateKeyPreviewPopupDelaySummary() {
- final ListPreference lp = mKeyPreviewPopupDismissDelay;
- final CharSequence[] entries = lp.getEntries();
- if (entries == null || entries.length <= 0) return;
- lp.setSummary(entries[lp.findIndexOfValue(lp.getValue())]);
- }
-
private void refreshEnablingsOfKeypressSoundAndVibrationSettings(
final SharedPreferences sp, final Resources res) {
setPreferenceEnabled(Settings.PREF_VIBRATION_DURATION_SETTINGS,
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index f331c78e5..d98e547fc 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -29,11 +29,9 @@ import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.InputAttributes;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.RichInputMethodManager;
-import com.android.inputmethod.latin.SubtypeSwitcher;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.utils.CollectionUtils;
-import com.android.inputmethod.latin.utils.InputTypeUtils;
import com.android.inputmethod.latin.utils.StringUtils;
import java.util.ArrayList;
@@ -67,10 +65,11 @@ public final class SettingsValues {
public final boolean mVibrateOn;
public final boolean mSoundOn;
public final boolean mKeyPreviewPopupOn;
- private final boolean mShowsVoiceInputKey;
+ public final boolean mShowsVoiceInputKey;
public final boolean mIncludesOtherImesInLanguageSwitchList;
public final boolean mShowsLanguageSwitchKey;
public final boolean mUseContactsDict;
+ public final boolean mUsePersonalizedDicts;
public final boolean mUseDoubleSpacePeriod;
public final boolean mBlockPotentiallyOffensive;
// Use bigrams to predict the next word when there is no input for it yet
@@ -148,6 +147,7 @@ public final class SettingsValues {
Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, false);
mShowsLanguageSwitchKey = Settings.readShowsLanguageSwitchKey(prefs);
mUseContactsDict = prefs.getBoolean(Settings.PREF_KEY_USE_CONTACTS_DICT, true);
+ mUsePersonalizedDicts = prefs.getBoolean(Settings.PREF_KEY_USE_PERSONALIZED_DICTS, true);
mUseDoubleSpacePeriod = prefs.getBoolean(Settings.PREF_KEY_USE_DOUBLE_SPACE_PERIOD, true);
mBlockPotentiallyOffensive = Settings.readBlockPotentiallyOffensive(prefs, res);
mAutoCorrectEnabled = Settings.readAutoCorrectEnabled(autoCorrectionThresholdRawValue, res);
@@ -206,6 +206,7 @@ public final class SettingsValues {
mIncludesOtherImesInLanguageSwitchList = false;
mShowsLanguageSwitchKey = true;
mUseContactsDict = true;
+ mUsePersonalizedDicts = true;
mUseDoubleSpacePeriod = true;
mBlockPotentiallyOffensive = true;
mAutoCorrectEnabled = true;
@@ -271,13 +272,6 @@ public final class SettingsValues {
return mInputAttributes.mShouldInsertSpacesAutomatically;
}
- public boolean isVoiceKeyEnabled(final EditorInfo editorInfo) {
- final boolean shortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled();
- final int inputType = (editorInfo != null) ? editorInfo.inputType : 0;
- return shortcutImeEnabled && mShowsVoiceInputKey
- && !InputTypeUtils.isPasswordInputType(inputType);
- }
-
public boolean isLanguageSwitchKeyEnabled() {
if (!mShowsLanguageSwitchKey) {
return false;
@@ -374,16 +368,20 @@ public final class SettingsValues {
return autoCorrectionThreshold;
}
- private static boolean needsToShowVoiceInputKey(SharedPreferences prefs, Resources res) {
- final String voiceModeMain = res.getString(R.string.voice_mode_main);
- final String voiceMode = prefs.getString(Settings.PREF_VOICE_MODE_OBSOLETE, voiceModeMain);
- final boolean showsVoiceInputKey = voiceMode == null || voiceMode.equals(voiceModeMain);
- if (!showsVoiceInputKey) {
- // Migrate settings from PREF_VOICE_MODE_OBSOLETE to PREF_VOICE_INPUT_KEY
- // Set voiceModeMain as a value of obsolete voice mode settings.
- prefs.edit().putString(Settings.PREF_VOICE_MODE_OBSOLETE, voiceModeMain).apply();
- // Disable voice input key.
- prefs.edit().putBoolean(Settings.PREF_VOICE_INPUT_KEY, false).apply();
+ private static boolean needsToShowVoiceInputKey(final SharedPreferences prefs,
+ final Resources res) {
+ if (!prefs.contains(Settings.PREF_VOICE_INPUT_KEY)) {
+ // Migrate preference from {@link Settings#PREF_VOICE_MODE_OBSOLETE} to
+ // {@link Settings#PREF_VOICE_INPUT_KEY}.
+ final String voiceModeMain = res.getString(R.string.voice_mode_main);
+ final String voiceMode = prefs.getString(
+ Settings.PREF_VOICE_MODE_OBSOLETE, voiceModeMain);
+ final boolean shouldShowVoiceInputKey = voiceModeMain.equals(voiceMode);
+ prefs.edit().putBoolean(Settings.PREF_VOICE_INPUT_KEY, shouldShowVoiceInputKey).apply();
+ }
+ // Remove the obsolete preference if exists.
+ if (prefs.contains(Settings.PREF_VOICE_MODE_OBSOLETE)) {
+ prefs.edit().remove(Settings.PREF_VOICE_MODE_OBSOLETE).apply();
}
return prefs.getBoolean(Settings.PREF_VOICE_INPUT_KEY, true);
}
diff --git a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java
index c4a813c24..5072fabd6 100644
--- a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java
+++ b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java
@@ -38,7 +38,7 @@ import com.android.inputmethod.compat.ViewCompatUtils;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.settings.SettingsActivity;
import com.android.inputmethod.latin.utils.CollectionUtils;
-import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper;
+import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
import java.util.ArrayList;
@@ -74,21 +74,21 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL
private SettingsPoolingHandler mHandler;
private static final class SettingsPoolingHandler
- extends StaticInnerHandlerWrapper<SetupWizardActivity> {
+ extends LeakGuardHandlerWrapper<SetupWizardActivity> {
private static final int MSG_POLLING_IME_SETTINGS = 0;
private static final long IME_SETTINGS_POLLING_INTERVAL = 200;
private final InputMethodManager mImmInHandler;
- public SettingsPoolingHandler(final SetupWizardActivity outerInstance,
+ public SettingsPoolingHandler(final SetupWizardActivity ownerInstance,
final InputMethodManager imm) {
- super(outerInstance);
+ super(ownerInstance);
mImmInHandler = imm;
}
@Override
public void handleMessage(final Message msg) {
- final SetupWizardActivity setupWizardActivity = getOuterInstance();
+ final SetupWizardActivity setupWizardActivity = getOwnerInstance();
if (setupWizardActivity == null) {
return;
}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 503b18b1b..c108b20ed 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -428,7 +428,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
final String localeStr = locale.toString();
UserBinaryDictionary userDictionary = mUserDictionaries.get(localeStr);
if (null == userDictionary) {
- userDictionary = new SynchronouslyLoadedUserBinaryDictionary(this, localeStr, true);
+ userDictionary = new SynchronouslyLoadedUserBinaryDictionary(this, locale, true);
mUserDictionaries.put(localeStr, userDictionary);
}
dictionaryCollection.addDictionary(userDictionary);
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
index a0aed2829..b7a5a4026 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
@@ -49,6 +49,7 @@ public final class DictionaryPool extends LinkedBlockingQueue<DictAndKeyboard> {
final static ArrayList<SuggestedWordInfo> noSuggestions = CollectionUtils.newArrayList();
private final static DictAndKeyboard dummyDict = new DictAndKeyboard(
new Dictionary(Dictionary.TYPE_MAIN) {
+ // TODO: this dummy dictionary should be a singleton in the Dictionary class.
@Override
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
final String prevWord, final ProximityInfo proximityInfo,
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index acd47450b..52012c846 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -66,7 +66,8 @@ public final class MoreSuggestions extends Keyboard {
clearKeys();
mDivider = res.getDrawable(R.drawable.more_suggestions_divider);
mDividerWidth = mDivider.getIntrinsicWidth();
- final float padding = res.getDimension(R.dimen.more_suggestions_key_horizontal_padding);
+ final float padding = res.getDimension(
+ R.dimen.config_more_suggestions_key_horizontal_padding);
int row = 0;
int index = fromIndex;
@@ -75,7 +76,7 @@ public final class MoreSuggestions extends Keyboard {
while (index < size) {
final String word = suggestedWords.getWord(index);
// TODO: Should take care of text x-scaling.
- mWidths[index] = (int)(TypefaceUtils.getLabelWidth(word, paint) + padding);
+ mWidths[index] = (int)(TypefaceUtils.getStringWidth(word, paint) + padding);
final int numColumn = index - rowStartIndex + 1;
final int columnWidth =
(maxWidth - mDividerWidth * (numColumn - 1)) / numColumn;
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
index 0ebe37782..549ff0d9d 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
@@ -54,7 +54,7 @@ public final class MoreSuggestionsView extends MoreKeysKeyboardView {
public void adjustVerticalCorrectionForModalMode() {
// Set vertical correction to zero (Reset more keys keyboard sliding allowance
- // {@link R#dimen.more_keys_keyboard_slide_allowance}).
+ // {@link R#dimen.config_more_keys_keyboard_slide_allowance}).
mKeyDetector.setKeyboard(getKeyboard(), -getPaddingLeft(), -getPaddingTop());
}
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
index faa5560e4..72281e62c 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
@@ -119,7 +119,8 @@ final class SuggestionStripLayoutHelper {
mDividerWidth = dividerView.getMeasuredWidth();
final Resources res = wordView.getResources();
- mSuggestionsStripHeight = res.getDimensionPixelSize(R.dimen.suggestions_strip_height);
+ mSuggestionsStripHeight = res.getDimensionPixelSize(
+ R.dimen.config_suggestions_strip_height);
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.SuggestionStripView, defStyle, R.style.SuggestionStripView);
@@ -145,15 +146,17 @@ final class SuggestionStripLayoutHelper {
a.recycle();
mMoreSuggestionsHint = getMoreSuggestionsHint(res,
- res.getDimension(R.dimen.more_suggestions_hint_text_size), mColorAutoCorrect);
+ res.getDimension(R.dimen.config_more_suggestions_hint_text_size),
+ mColorAutoCorrect);
mCenterPositionInStrip = mSuggestionsCountInStrip / 2;
// Assuming there are at least three suggestions. Also, note that the suggestions are
// laid out according to script direction, so this is left of the center for LTR scripts
// and right of the center for RTL scripts.
mTypedWordPositionWhenAutocorrect = mCenterPositionInStrip - 1;
mMoreSuggestionsBottomGap = res.getDimensionPixelOffset(
- R.dimen.more_suggestions_bottom_gap);
- mMoreSuggestionsRowHeight = res.getDimensionPixelSize(R.dimen.more_suggestions_row_height);
+ R.dimen.config_more_suggestions_bottom_gap);
+ mMoreSuggestionsRowHeight = res.getDimensionPixelSize(
+ R.dimen.config_more_suggestions_row_height);
final LayoutInflater inflater = LayoutInflater.from(context);
mWordToSaveView = (TextView)inflater.inflate(R.layout.suggestion_word, null);
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index 75f17c559..073148a50 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -112,7 +112,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
final Resources res = context.getResources();
mMoreSuggestionsModalTolerance = res.getDimensionPixelOffset(
- R.dimen.more_suggestions_modal_tolerance);
+ R.dimen.config_more_suggestions_modal_tolerance);
mMoreSuggestionsSlidingDetector = new GestureDetector(
context, mMoreSuggestionsSlidingListener);
}
@@ -162,19 +162,19 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
mSuggestionsStrip.removeAllViews();
removeAllViews();
addView(mSuggestionsStrip);
- mMoreSuggestionsView.dismissMoreKeysPanel();
+ dismissMoreSuggestionsPanel();
}
private final MoreSuggestionsListener mMoreSuggestionsListener = new MoreSuggestionsListener() {
@Override
public void onSuggestionSelected(final int index, final SuggestedWordInfo wordInfo) {
mListener.pickSuggestionManually(index, wordInfo);
- mMoreSuggestionsView.dismissMoreKeysPanel();
+ dismissMoreSuggestionsPanel();
}
@Override
public void onCancelInput() {
- mMoreSuggestionsView.dismissMoreKeysPanel();
+ dismissMoreSuggestionsPanel();
}
};
@@ -192,10 +192,18 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
@Override
public void onCancelMoreKeysPanel(final MoreKeysPanel panel) {
- mMoreSuggestionsView.dismissMoreKeysPanel();
+ dismissMoreSuggestionsPanel();
}
};
+ public boolean isShowingMoreSuggestionPanel() {
+ return mMoreSuggestionsView.isShowingInParent();
+ }
+
+ public void dismissMoreSuggestionsPanel() {
+ mMoreSuggestionsView.dismissMoreKeysPanel();
+ }
+
@Override
public boolean onLongClick(final View view) {
AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(
@@ -322,6 +330,6 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- mMoreSuggestionsView.dismissMoreKeysPanel();
+ dismissMoreSuggestionsPanel();
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
index d87f6f3c4..ef1d0f42c 100644
--- a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
@@ -17,21 +17,25 @@
package com.android.inputmethod.latin.utils;
import static com.android.inputmethod.latin.Constants.Subtype.KEYBOARD_MODE;
+import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.IS_ADDITIONAL_SUBTYPE;
import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET;
import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME;
import android.os.Build;
import android.text.TextUtils;
+import android.util.Log;
import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
-import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
import java.util.ArrayList;
+import java.util.Arrays;
public final class AdditionalSubtypeUtils {
+ private static final String TAG = AdditionalSubtypeUtils.class.getSimpleName();
+
private static final InputMethodSubtype[] EMPTY_SUBTYPE_ARRAY = new InputMethodSubtype[0];
private AdditionalSubtypeUtils() {
@@ -43,6 +47,11 @@ public final class AdditionalSubtypeUtils {
}
private static final String LOCALE_AND_LAYOUT_SEPARATOR = ":";
+ private static final int INDEX_OF_LOCALE = 0;
+ private static final int INDEX_OF_KEYBOARD_LAYOUT = 1;
+ private static final int INDEX_OF_EXTRA_VALUE = 2;
+ private static final int LENGTH_WITHOUT_EXTRA_VALUE = (INDEX_OF_KEYBOARD_LAYOUT + 1);
+ private static final int LENGTH_WITH_EXTRA_VALUE = (INDEX_OF_EXTRA_VALUE + 1);
private static final String PREF_SUBTYPE_SEPARATOR = ";";
public static InputMethodSubtype createAdditionalSubtype(final String localeString,
@@ -79,17 +88,6 @@ public final class AdditionalSubtypeUtils {
: basePrefSubtype + LOCALE_AND_LAYOUT_SEPARATOR + extraValue;
}
- public static InputMethodSubtype createAdditionalSubtype(final String prefSubtype) {
- final String elems[] = prefSubtype.split(LOCALE_AND_LAYOUT_SEPARATOR);
- if (elems.length < 2 || elems.length > 3) {
- throw new RuntimeException("Unknown additional subtype specified: " + prefSubtype);
- }
- final String localeString = elems[0];
- final String keyboardLayoutSetName = elems[1];
- final String extraValue = (elems.length == 3) ? elems[2] : null;
- return createAdditionalSubtype(localeString, keyboardLayoutSetName, extraValue);
- }
-
public static InputMethodSubtype[] createAdditionalSubtypesArray(final String prefSubtypes) {
if (TextUtils.isEmpty(prefSubtypes)) {
return EMPTY_SUBTYPE_ARRAY;
@@ -98,7 +96,19 @@ public final class AdditionalSubtypeUtils {
final ArrayList<InputMethodSubtype> subtypesList =
CollectionUtils.newArrayList(prefSubtypeArray.length);
for (final String prefSubtype : prefSubtypeArray) {
- final InputMethodSubtype subtype = createAdditionalSubtype(prefSubtype);
+ final String elems[] = prefSubtype.split(LOCALE_AND_LAYOUT_SEPARATOR);
+ if (elems.length != LENGTH_WITHOUT_EXTRA_VALUE
+ && elems.length != LENGTH_WITH_EXTRA_VALUE) {
+ Log.w(TAG, "Unknown additional subtype specified: " + prefSubtype + " in "
+ + prefSubtypes);
+ continue;
+ }
+ final String localeString = elems[INDEX_OF_LOCALE];
+ final String keyboardLayoutSetName = elems[INDEX_OF_KEYBOARD_LAYOUT];
+ final String extraValue = (elems.length == LENGTH_WITH_EXTRA_VALUE)
+ ? elems[INDEX_OF_EXTRA_VALUE] : null;
+ final InputMethodSubtype subtype = createAdditionalSubtype(
+ localeString, keyboardLayoutSetName, extraValue);
if (subtype.getNameResId() == SubtypeLocaleUtils.UNKNOWN_KEYBOARD_LAYOUT) {
// Skip unknown keyboard layout subtype. This may happen when predefined keyboard
// layout has been removed.
@@ -137,31 +147,36 @@ public final class AdditionalSubtypeUtils {
return sb.toString();
}
- private static InputMethodSubtype buildInputMethodSubtype(int nameId, String localeString,
- String layoutExtraValue, String additionalSubtypeExtraValue) {
- // CAVEAT! If you want to change subtypeId after changing the extra values,
- // you must change "getInputMethodSubtypeId". But it will remove the additional keyboard
- // from the current users. So, you should be really careful to change it.
- final int subtypeId = getInputMethodSubtypeId(nameId, localeString, layoutExtraValue,
- additionalSubtypeExtraValue);
+ private static InputMethodSubtype buildInputMethodSubtype(final int nameId,
+ final String localeString, final String layoutExtraValue,
+ final String additionalSubtypeExtraValue) {
+ // To preserve additional subtype settings and user's selection across OS updates, subtype
+ // id shouldn't be changed. New attributes, such as emojiCapable, are carefully excluded
+ // from the calculation of subtype id.
+ final String compatibleExtraValue = StringUtils.joinCommaSplittableText(
+ layoutExtraValue, additionalSubtypeExtraValue);
+ final int compatibleSubtypeId = getInputMethodSubtypeId(localeString, compatibleExtraValue);
final String extraValue;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
- extraValue = layoutExtraValue + "," + additionalSubtypeExtraValue
- + "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE
- + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
+ // Color Emoji is supported from KitKat.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ extraValue = StringUtils.appendToCommaSplittableTextIfNotExists(
+ EMOJI_CAPABLE, compatibleExtraValue);
} else {
- extraValue = layoutExtraValue + "," + additionalSubtypeExtraValue;
+ extraValue = compatibleExtraValue;
}
return InputMethodSubtypeCompatUtils.newInputMethodSubtype(nameId,
R.drawable.ic_ime_switcher_dark, localeString, KEYBOARD_MODE, extraValue,
- false, false, subtypeId);
+ false, false, compatibleSubtypeId);
}
- private static int getInputMethodSubtypeId(int nameId, String localeString,
- String layoutExtraValue, String additionalSubtypeExtraValue) {
- // TODO: Use InputMethodSubtypeBuilder once we use SDK version 19.
- return (new InputMethodSubtype(nameId, R.drawable.ic_ime_switcher_dark,
- localeString, KEYBOARD_MODE, layoutExtraValue + "," + additionalSubtypeExtraValue,
- false, false)).hashCode();
+ private static int getInputMethodSubtypeId(final String localeString, final String extraValue) {
+ // From the compatibility point of view, the calculation of subtype id has been copied from
+ // {@link InputMethodSubtype} of JellyBean MR2.
+ return Arrays.hashCode(new Object[] {
+ localeString,
+ KEYBOARD_MODE,
+ extraValue,
+ false /* isAuxiliary */,
+ false /* overrideImplicitlyEnabledSubtype */ });
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java b/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java
index 08a2a8c5a..e521ec807 100644
--- a/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java
@@ -62,4 +62,22 @@ public final class ApplicationUtils {
}
return "";
}
+
+ /**
+ * A utility method to get the application's PackageInfo.versionCode
+ * @return the application's PackageInfo.versionCode
+ */
+ public static int getVersionCode(final Context context) {
+ try {
+ if (context == null) {
+ return 0;
+ }
+ final String packageName = context.getPackageName();
+ final PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
+ return info.versionCode;
+ } catch (final NameNotFoundException e) {
+ Log.e(TAG, "Could not find version info.", e);
+ }
+ return 0;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java b/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java
index c2e97a36f..d12aad639 100644
--- a/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java
+++ b/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java
@@ -20,7 +20,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
- * This class is a holder of a result of asynchronous computation.
+ * This class is a holder of the result of an asynchronous computation.
*
* @param <E> the type of the result.
*/
@@ -36,9 +36,9 @@ public class AsyncResultHolder<E> {
}
/**
- * Sets the result value to this holder.
+ * Sets the result value of this holder.
*
- * @param result the value which is set.
+ * @param result the value to set.
*/
public void set(final E result) {
synchronized(mLock) {
@@ -54,12 +54,12 @@ public class AsyncResultHolder<E> {
* Causes the current thread to wait unless the value is set or the specified time is elapsed.
*
* @param defaultValue the default value.
- * @param timeOut the time to wait.
- * @return if the result is set until the time limit then the result, otherwise defaultValue.
+ * @param timeOut the maximum time to wait.
+ * @return if the result is set before the time limit then the result, otherwise defaultValue.
*/
public E get(final E defaultValue, final long timeOut) {
try {
- if(mLatch.await(timeOut, TimeUnit.MILLISECONDS)) {
+ if (mLatch.await(timeOut, TimeUnit.MILLISECONDS)) {
return mResult;
} else {
return defaultValue;
diff --git a/java/src/com/android/inputmethod/latin/utils/FileUtils.java b/java/src/com/android/inputmethod/latin/utils/FileUtils.java
new file mode 100644
index 000000000..83c1e7c4d
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/FileUtils.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2013 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.utils;
+
+import java.io.File;
+
+/**
+ * A simple class to help with removing directories recursively.
+ */
+public class FileUtils {
+ public static boolean deleteRecursively(final File path) {
+ if (path.isDirectory()) {
+ for (final File child : path.listFiles()) {
+ deleteRecursively(child);
+ }
+ }
+ return path.delete();
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/JsonUtils.java b/java/src/com/android/inputmethod/latin/utils/JsonUtils.java
new file mode 100644
index 000000000..764ef72ce
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/JsonUtils.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2013 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.utils;
+
+import android.util.JsonReader;
+import android.util.JsonWriter;
+import android.util.Log;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public final class JsonUtils {
+ private static final String TAG = JsonUtils.class.getSimpleName();
+
+ private static final String INTEGER_CLASS_NAME = Integer.class.getSimpleName();
+ private static final String STRING_CLASS_NAME = String.class.getSimpleName();
+
+ private static final String EMPTY_STRING = "";
+
+ public static List<Object> jsonStrToList(final String s) {
+ final ArrayList<Object> list = CollectionUtils.newArrayList();
+ final JsonReader reader = new JsonReader(new StringReader(s));
+ try {
+ reader.beginArray();
+ while (reader.hasNext()) {
+ reader.beginObject();
+ while (reader.hasNext()) {
+ final String name = reader.nextName();
+ if (name.equals(INTEGER_CLASS_NAME)) {
+ list.add(reader.nextInt());
+ } else if (name.equals(STRING_CLASS_NAME)) {
+ list.add(reader.nextString());
+ } else {
+ Log.w(TAG, "Invalid name: " + name);
+ reader.skipValue();
+ }
+ }
+ reader.endObject();
+ }
+ reader.endArray();
+ return list;
+ } catch (final IOException e) {
+ } finally {
+ close(reader);
+ }
+ return Collections.<Object>emptyList();
+ }
+
+ public static String listToJsonStr(final List<Object> list) {
+ if (list == null || list.isEmpty()) {
+ return EMPTY_STRING;
+ }
+ final StringWriter sw = new StringWriter();
+ final JsonWriter writer = new JsonWriter(sw);
+ try {
+ writer.beginArray();
+ for (final Object o : list) {
+ writer.beginObject();
+ if (o instanceof Integer) {
+ writer.name(INTEGER_CLASS_NAME).value((Integer)o);
+ } else if (o instanceof String) {
+ writer.name(STRING_CLASS_NAME).value((String)o);
+ }
+ writer.endObject();
+ }
+ writer.endArray();
+ return sw.toString();
+ } catch (final IOException e) {
+ } finally {
+ close(writer);
+ }
+ return EMPTY_STRING;
+ }
+
+ private static void close(final Closeable closeable) {
+ try {
+ if (closeable != null) {
+ closeable.close();
+ }
+ } catch (final IOException e) {
+ // Ignore
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java b/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java
index e958a7e71..d14ba508b 100644
--- a/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java
@@ -35,7 +35,7 @@ public final class LatinImeLoggerUtils {
public static void onSeparator(final int code, final int x, final int y) {
// Helper method to log a single code point separator
// TODO: cache this mapping of a code point to a string in a sparse array in StringUtils
- onSeparator(new String(new int[]{code}, 0, 1), x, y);
+ onSeparator(StringUtils.newSingleCodePointString(code), x, y);
}
public static void onSeparator(final String separator, final int x, final int y) {
diff --git a/java/src/com/android/inputmethod/latin/utils/StaticInnerHandlerWrapper.java b/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java
index 44e5d17b4..8469c87b0 100644
--- a/java/src/com/android/inputmethod/latin/utils/StaticInnerHandlerWrapper.java
+++ b/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java
@@ -21,22 +21,22 @@ import android.os.Looper;
import java.lang.ref.WeakReference;
-public class StaticInnerHandlerWrapper<T> extends Handler {
- private final WeakReference<T> mOuterInstanceRef;
+public class LeakGuardHandlerWrapper<T> extends Handler {
+ private final WeakReference<T> mOwnerInstanceRef;
- public StaticInnerHandlerWrapper(final T outerInstance) {
- this(outerInstance, Looper.myLooper());
+ public LeakGuardHandlerWrapper(final T ownerInstance) {
+ this(ownerInstance, Looper.myLooper());
}
- public StaticInnerHandlerWrapper(final T outerInstance, final Looper looper) {
+ public LeakGuardHandlerWrapper(final T ownerInstance, final Looper looper) {
super(looper);
- if (outerInstance == null) {
- throw new NullPointerException("outerInstance is null");
+ if (ownerInstance == null) {
+ throw new NullPointerException("ownerInstance is null");
}
- mOuterInstanceRef = new WeakReference<T>(outerInstance);
+ mOwnerInstanceRef = new WeakReference<T>(ownerInstance);
}
- public T getOuterInstance() {
- return mOuterInstanceRef.get();
+ public T getOwnerInstance() {
+ return mOwnerInstanceRef.get();
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java
index 22045aa38..0c55484b4 100644
--- a/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java
@@ -30,9 +30,6 @@ import java.util.Locale;
* dictionary pack.
*/
public final class LocaleUtils {
- private static final HashMap<String, Long> EMPTY_LT_HASH_MAP = CollectionUtils.newHashMap();
- private static final String LOCALE_AND_TIME_STR_SEPARATER = ",";
-
private LocaleUtils() {
// Intentional empty constructor for utility class.
}
@@ -168,12 +165,14 @@ public final class LocaleUtils {
* Creates a locale from a string specification.
*/
public static Locale constructLocaleFromString(final String localeStr) {
- if (localeStr == null)
+ if (localeStr == null) {
return null;
+ }
synchronized (sLocaleCache) {
- if (sLocaleCache.containsKey(localeStr))
- return sLocaleCache.get(localeStr);
- Locale retval = null;
+ Locale retval = sLocaleCache.get(localeStr);
+ if (retval != null) {
+ return retval;
+ }
String[] localeParams = localeStr.split("_", 3);
if (localeParams.length == 1) {
retval = new Locale(localeParams[0]);
@@ -188,38 +187,4 @@ public final class LocaleUtils {
return retval;
}
}
-
- public static HashMap<String, Long> localeAndTimeStrToHashMap(String str) {
- if (TextUtils.isEmpty(str)) {
- return EMPTY_LT_HASH_MAP;
- }
- final String[] ss = str.split(LOCALE_AND_TIME_STR_SEPARATER);
- final int N = ss.length;
- if (N < 2 || N % 2 != 0) {
- return EMPTY_LT_HASH_MAP;
- }
- final HashMap<String, Long> retval = CollectionUtils.newHashMap();
- for (int i = 0; i < N / 2; ++i) {
- final String localeStr = ss[i * 2];
- final long time = Long.valueOf(ss[i * 2 + 1]);
- retval.put(localeStr, time);
- }
- return retval;
- }
-
- public static String localeAndTimeHashMapToStr(HashMap<String, Long> map) {
- if (map == null || map.isEmpty()) {
- return "";
- }
- final StringBuilder builder = new StringBuilder();
- for (String localeStr : map.keySet()) {
- if (builder.length() > 0) {
- builder.append(LOCALE_AND_TIME_STR_SEPARATER);
- }
- final Long time = map.get(localeStr);
- builder.append(localeStr).append(LOCALE_AND_TIME_STR_SEPARATER);
- builder.append(String.valueOf(time));
- }
- return builder.toString();
- }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java b/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java
index 201a70d42..b10d08af3 100644
--- a/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java
+++ b/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java
@@ -137,6 +137,7 @@ public class PrioritizedSerialExecutor {
public void shutdown() {
synchronized(mLock) {
mIsShutdown = true;
+ mThreadPoolExecutor.shutdown();
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java b/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
index 22c92446a..deb28a08d 100644
--- a/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
@@ -227,19 +227,19 @@ public final class ResourceUtils {
final String keyboardHeightString = getDeviceOverrideValue(res, R.array.keyboard_heights);
final float keyboardHeight;
if (TextUtils.isEmpty(keyboardHeightString)) {
- keyboardHeight = res.getDimension(R.dimen.keyboardHeight);
+ keyboardHeight = res.getDimension(R.dimen.config_default_keyboard_height);
} else {
keyboardHeight = Float.parseFloat(keyboardHeightString) * dm.density;
}
final float maxKeyboardHeight = res.getFraction(
- R.fraction.maxKeyboardHeight, dm.heightPixels, dm.heightPixels);
+ R.fraction.config_max_keyboard_height, dm.heightPixels, dm.heightPixels);
float minKeyboardHeight = res.getFraction(
- R.fraction.minKeyboardHeight, dm.heightPixels, dm.heightPixels);
+ R.fraction.config_min_keyboard_height, dm.heightPixels, dm.heightPixels);
if (minKeyboardHeight < 0.0f) {
// Specified fraction was negative, so it should be calculated against display
// width.
minKeyboardHeight = -res.getFraction(
- R.fraction.minKeyboardHeight, dm.widthPixels, dm.widthPixels);
+ R.fraction.config_min_keyboard_height, dm.widthPixels, dm.widthPixels);
}
// Keyboard height will not exceed maxKeyboardHeight and will not be less than
// minKeyboardHeight.
diff --git a/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java b/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java
index b51fd9377..be0955456 100644
--- a/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java
@@ -40,12 +40,17 @@ public final class SpannableStringUtils {
* are out of range in <code>dest</code>.
*/
public static void copyNonParagraphSuggestionSpansFrom(Spanned source, int start, int end,
- Spannable dest, int destoff) {
+ Spannable dest, int destoff) {
Object[] spans = source.getSpans(start, end, SuggestionSpan.class);
for (int i = 0; i < spans.length; i++) {
int fl = source.getSpanFlags(spans[i]);
- if (0 != (fl & Spannable.SPAN_PARAGRAPH)) continue;
+ // We don't care about the PARAGRAPH flag in LatinIME code. However, if this flag
+ // is set, Spannable#setSpan will throw an exception unless the span is on the edge
+ // of a word. But the spans have been split into two by the getText{Before,After}Cursor
+ // methods, so after concatenation they may end in the middle of a word.
+ // Since we don't use them, we can just remove them and avoid crashing.
+ fl &= ~Spannable.SPAN_PARAGRAPH;
int st = source.getSpanStart(spans[i]);
int en = source.getSpanEnd(spans[i]);
diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
index a36548392..85f44541e 100644
--- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
@@ -16,20 +16,15 @@
package com.android.inputmethod.latin.utils;
+import android.text.TextUtils;
+import android.util.Log;
+
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.settings.SettingsValues;
-import android.text.TextUtils;
-import android.util.JsonReader;
-import android.util.JsonWriter;
-import android.util.Log;
-
import java.io.IOException;
-import java.io.StringReader;
-import java.io.StringWriter;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Locale;
@@ -39,6 +34,8 @@ public final class StringUtils {
public static final int CAPITALIZE_FIRST = 1; // First only
public static final int CAPITALIZE_ALL = 2; // All caps
+ private static final String EMPTY_STRING = "";
+
private StringUtils() {
// This utility class is not publicly instantiable.
}
@@ -80,6 +77,20 @@ public final class StringUtils {
return containsInArray(text, extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT));
}
+ public static String joinCommaSplittableText(final String head, final String tail) {
+ if (TextUtils.isEmpty(head) && TextUtils.isEmpty(tail)) {
+ return EMPTY_STRING;
+ }
+ // Here either head or tail is not null.
+ if (TextUtils.isEmpty(head)) {
+ return tail;
+ }
+ if (TextUtils.isEmpty(tail)) {
+ return head;
+ }
+ return head + SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT + tail;
+ }
+
public static String appendToCommaSplittableTextIfNotExists(final String text,
final String extraValues) {
if (TextUtils.isEmpty(extraValues)) {
@@ -94,7 +105,7 @@ public final class StringUtils {
public static String removeFromCommaSplittableTextIfExists(final String text,
final String extraValues) {
if (TextUtils.isEmpty(extraValues)) {
- return "";
+ return EMPTY_STRING;
}
final String[] elements = extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT);
if (!containsInArray(text, elements)) {
@@ -239,6 +250,24 @@ public final class StringUtils {
return true;
}
+ /**
+ * Returns true if all code points in text are whitespace, false otherwise. Empty is true.
+ */
+ // Interestingly enough, U+00A0 NO-BREAK SPACE and U+200B ZERO-WIDTH SPACE are not considered
+ // whitespace, while EN SPACE, EM SPACE and IDEOGRAPHIC SPACES are.
+ public static boolean containsOnlyWhitespace(final String text) {
+ final int length = text.length();
+ int i = 0;
+ while (i < length) {
+ final int codePoint = text.codePointAt(i);
+ if (!Character.isWhitespace(codePoint)) {
+ return false;
+ }
+ i += Character.charCount(codePoint);
+ }
+ return true;
+ }
+
@UsedForTesting
public static boolean looksValidForDictionaryInsertion(final CharSequence text,
final SettingsValues settings) {
@@ -367,7 +396,7 @@ public final class StringUtils {
return false;
}
- public static boolean isEmptyStringOrWhiteSpaces(String s) {
+ public static boolean isEmptyStringOrWhiteSpaces(final String s) {
final int N = codePointCount(s);
for (int i = 0; i < N; ++i) {
if (!Character.isWhitespace(s.codePointAt(i))) {
@@ -378,9 +407,9 @@ public final class StringUtils {
}
@UsedForTesting
- public static String byteArrayToHexString(byte[] bytes) {
+ public static String byteArrayToHexString(final byte[] bytes) {
if (bytes == null || bytes.length == 0) {
- return "";
+ return EMPTY_STRING;
}
final StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
@@ -393,7 +422,7 @@ public final class StringUtils {
* Convert hex string to byte array. The string length must be an even number.
*/
@UsedForTesting
- public static byte[] hexStringToByteArray(String hexString) {
+ public static byte[] hexStringToByteArray(final String hexString) {
if (TextUtils.isEmpty(hexString)) {
return null;
}
@@ -409,67 +438,4 @@ public final class StringUtils {
}
return bytes;
}
-
- public static List<Object> jsonStrToList(String s) {
- final ArrayList<Object> retval = CollectionUtils.newArrayList();
- final JsonReader reader = new JsonReader(new StringReader(s));
- try {
- reader.beginArray();
- while(reader.hasNext()) {
- reader.beginObject();
- while (reader.hasNext()) {
- final String name = reader.nextName();
- if (name.equals(Integer.class.getSimpleName())) {
- retval.add(reader.nextInt());
- } else if (name.equals(String.class.getSimpleName())) {
- retval.add(reader.nextString());
- } else {
- Log.w(TAG, "Invalid name: " + name);
- reader.skipValue();
- }
- }
- reader.endObject();
- }
- reader.endArray();
- return retval;
- } catch (IOException e) {
- } finally {
- try {
- reader.close();
- } catch (IOException e) {
- }
- }
- return Collections.<Object>emptyList();
- }
-
- public static String listToJsonStr(List<Object> list) {
- if (list == null || list.isEmpty()) {
- return "";
- }
- final StringWriter sw = new StringWriter();
- final JsonWriter writer = new JsonWriter(sw);
- try {
- writer.beginArray();
- for (final Object o : list) {
- writer.beginObject();
- if (o instanceof Integer) {
- writer.name(Integer.class.getSimpleName()).value((Integer)o);
- } else if (o instanceof String) {
- writer.name(String.class.getSimpleName()).value((String)o);
- }
- writer.endObject();
- }
- writer.endArray();
- return sw.toString();
- } catch (IOException e) {
- } finally {
- try {
- if (writer != null) {
- writer.close();
- }
- } catch (IOException e) {
- }
- }
- return "";
- }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
index 102a41b4e..fdbe81ab6 100644
--- a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
@@ -197,7 +197,9 @@ public final class SubtypeLocaleUtils {
// es_US spanish F Español (EE.UU.) exception
// fr azerty F Français
// fr_CA qwerty F Français (Canada)
+ // fr_CH swiss F Français (Suisse)
// de qwertz F Deutsch
+ // de_CH swiss T Deutsch (Schweiz)
// zz qwerty F No language (QWERTY) in system locale
// fr qwertz T Français (QWERTZ)
// de qwerty T Deutsch (QWERTY)
@@ -298,7 +300,9 @@ public final class SubtypeLocaleUtils {
// es_US spanish F Es Español Español (EE.UU.) exception
// fr azerty F Fr Français Français
// fr_CA qwerty F Fr Français Français (Canada)
+ // fr_CH swiss F Fr Français Français (Suisse)
// de qwertz F De Deutsch Deutsch
+ // de_CH swiss T De Deutsch Deutsch (Schweiz)
// zz qwerty F QWERTY QWERTY
// fr qwertz T Fr Français Français
// de qwerty T De Deutsch Deutsch
diff --git a/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java b/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
index 47ea1ea75..087a7f255 100644
--- a/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
@@ -22,6 +22,9 @@ import android.graphics.Typeface;
import android.util.SparseArray;
public final class TypefaceUtils {
+ private static final char[] KEY_LABEL_REFERENCE_CHAR = { 'M' };
+ private static final char[] KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR = { '8' };
+
private TypefaceUtils() {
// This utility class is not publicly instantiable.
}
@@ -31,7 +34,7 @@ public final class TypefaceUtils {
// Working variable for the following method.
private static final Rect sTextHeightBounds = new Rect();
- public static float getCharHeight(final char[] referenceChar, final Paint paint) {
+ private static float getCharHeight(final char[] referenceChar, final Paint paint) {
final int key = getCharGeometryCacheKey(referenceChar[0], paint);
synchronized (sTextHeightCache) {
final Float cachedValue = sTextHeightCache.get(key);
@@ -51,7 +54,7 @@ public final class TypefaceUtils {
// Working variable for the following method.
private static final Rect sTextWidthBounds = new Rect();
- public static float getCharWidth(final char[] referenceChar, final Paint paint) {
+ private static float getCharWidth(final char[] referenceChar, final Paint paint) {
final int key = getCharGeometryCacheKey(referenceChar[0], paint);
synchronized (sTextWidthCache) {
final Float cachedValue = sTextWidthCache.get(key);
@@ -66,11 +69,6 @@ public final class TypefaceUtils {
}
}
- public static float getStringWidth(final String string, final Paint paint) {
- paint.getTextBounds(string, 0, string.length(), sTextWidthBounds);
- return sTextWidthBounds.width();
- }
-
private static int getCharGeometryCacheKey(final char referenceChar, final Paint paint) {
final int labelSize = (int)paint.getTextSize();
final Typeface face = paint.getTypeface();
@@ -86,9 +84,25 @@ public final class TypefaceUtils {
}
}
- public static float getLabelWidth(final String label, final Paint paint) {
- final Rect textBounds = new Rect();
- paint.getTextBounds(label, 0, label.length(), textBounds);
- return textBounds.width();
+ public static float getReferenceCharHeight(final Paint paint) {
+ return getCharHeight(KEY_LABEL_REFERENCE_CHAR, paint);
+ }
+
+ public static float getReferenceCharWidth(final Paint paint) {
+ return getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint);
+ }
+
+ public static float getReferenceDigitWidth(final Paint paint) {
+ return getCharWidth(KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR, paint);
+ }
+
+ // Working variable for the following method.
+ private static final Rect sStringWidthBounds = new Rect();
+
+ public static float getStringWidth(final String string, final Paint paint) {
+ synchronized (sStringWidthBounds) {
+ paint.getTextBounds(string, 0, string.length(), sStringWidthBounds);
+ return sStringWidthBounds.width();
+ }
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/UnigramProperty.java b/java/src/com/android/inputmethod/latin/utils/UnigramProperty.java
new file mode 100644
index 000000000..4feee4393
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/UnigramProperty.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2013 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.utils;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.BinaryDictionary;
+import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+
+import java.util.ArrayList;
+
+// This has information that belong to a unigram. This class has some detailed attributes such as
+// historical information but they have to be checked only for testing purpose.
+@UsedForTesting
+public class UnigramProperty {
+ public final String mCodePoints;
+ public final boolean mIsNotAWord;
+ public final boolean mIsBlacklisted;
+ public final boolean mHasBigrams;
+ public final boolean mHasShortcuts;
+ public final int mProbability;
+ // mTimestamp, mLevel and mCount are historical info. These values are depend on the
+ // implementation in native code; thus, we must not use them and have any assumptions about
+ // them except for tests.
+ public final int mTimestamp;
+ public final int mLevel;
+ public final int mCount;
+ public final ArrayList<WeightedString> mShortcutTargets = CollectionUtils.newArrayList();
+
+ private static int getCodePointCount(final int[] codePoints) {
+ for (int i = 0; i < codePoints.length; i++) {
+ if (codePoints[i] == 0) {
+ return i;
+ }
+ }
+ return codePoints.length;
+ }
+
+ // This represents invalid unigram when the probability is BinaryDictionary.NOT_A_PROBABILITY.
+ public UnigramProperty(final int[] codePoints, final boolean isNotAWord,
+ final boolean isBlacklisted, final boolean hasBigram,
+ final boolean hasShortcuts, final int probability, final int timestamp,
+ final int level, final int count, final ArrayList<int[]> shortcutTargets,
+ final ArrayList<Integer> shortcutProbabilities) {
+ mCodePoints = new String(codePoints, 0 /* offset */, getCodePointCount(codePoints));
+ mIsNotAWord = isNotAWord;
+ mIsBlacklisted = isBlacklisted;
+ mHasBigrams = hasBigram;
+ mHasShortcuts = hasShortcuts;
+ mProbability = probability;
+ mTimestamp = timestamp;
+ mLevel = level;
+ mCount = count;
+ final int shortcutTargetCount = shortcutTargets.size();
+ for (int i = 0; i < shortcutTargetCount; i++) {
+ final int[] shortcutTargetCodePointArray = shortcutTargets.get(i);
+ final String shortcutTargetString = new String(shortcutTargetCodePointArray,
+ 0 /* offset */, getCodePointCount(shortcutTargetCodePointArray));
+ mShortcutTargets.add(
+ new WeightedString(shortcutTargetString, shortcutProbabilities.get(i)));
+ }
+ }
+
+ @UsedForTesting
+ public boolean isValid() {
+ return mProbability != BinaryDictionary.NOT_A_PROBABILITY;
+ }
+} \ No newline at end of file
diff --git a/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
index 635afe7cc..db628fe18 100644
--- a/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
@@ -70,10 +70,11 @@ public final class UserHistoryDictIOUtils {
/**
* Writes dictionary to file.
*/
+ @UsedForTesting
public static void writeDictionary(final DictEncoder dictEncoder,
final BigramDictionaryInterface dict, final UserHistoryDictionaryBigramList bigrams,
- final FormatOptions formatOptions) {
- final FusionDictionary fusionDict = constructFusionDictionary(dict, bigrams);
+ final FormatOptions formatOptions, final HashMap<String, String> options) {
+ final FusionDictionary fusionDict = constructFusionDictionary(dict, bigrams, options);
fusionDict.addOptionAttribute(USES_FORGETTING_CURVE_KEY, USES_FORGETTING_CURVE_VALUE);
fusionDict.addOptionAttribute(LAST_UPDATED_TIME_KEY,
String.valueOf(TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())));
@@ -91,11 +92,10 @@ public final class UserHistoryDictIOUtils {
* Constructs a new FusionDictionary from BigramDictionaryInterface.
*/
@UsedForTesting
- static FusionDictionary constructFusionDictionary(
- final BigramDictionaryInterface dict, final UserHistoryDictionaryBigramList bigrams) {
+ static FusionDictionary constructFusionDictionary(final BigramDictionaryInterface dict,
+ final UserHistoryDictionaryBigramList bigrams, final HashMap<String, String> options) {
final FusionDictionary fusionDict = new FusionDictionary(new PtNodeArray(),
- new FusionDictionary.DictionaryOptions(new HashMap<String, String>(), false,
- false));
+ new FusionDictionary.DictionaryOptions(options));
int profTotal = 0;
for (final String word1 : bigrams.keySet()) {
final HashMap<String, Byte> word1Bigrams = bigrams.getBigrams(word1);
diff --git a/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java b/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java
deleted file mode 100644
index 1992b2f5d..000000000
--- a/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Copyright (C) 2012 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.utils;
-
-import android.util.Log;
-
-import java.util.concurrent.TimeUnit;
-
-public final class UserHistoryForgettingCurveUtils {
- private static final String TAG = UserHistoryForgettingCurveUtils.class.getSimpleName();
- private static final boolean DEBUG = false;
- private static final int DEFAULT_FC_FREQ = 127;
- private static final int BOOSTED_FC_FREQ = 200;
- private static int FC_FREQ_MAX = DEFAULT_FC_FREQ;
- /* package */ static final int COUNT_MAX = 3;
- private static final int FC_LEVEL_MAX = 3;
- /* package */ static final int ELAPSED_TIME_MAX = 15;
- private static final int ELAPSED_TIME_INTERVAL_HOURS = 6;
- private static final long ELAPSED_TIME_INTERVAL_MILLIS =
- TimeUnit.HOURS.toMillis(ELAPSED_TIME_INTERVAL_HOURS);
- private static final int HALF_LIFE_HOURS = 48;
- private static final int MAX_PUSH_ELAPSED = (FC_LEVEL_MAX + 1) * (ELAPSED_TIME_MAX + 1);
-
- public static void boostMaxFreqForDebug() {
- FC_FREQ_MAX = BOOSTED_FC_FREQ;
- }
-
- public static void resetMaxFreqForDebug() {
- FC_FREQ_MAX = DEFAULT_FC_FREQ;
- }
-
- private UserHistoryForgettingCurveUtils() {
- // This utility class is not publicly instantiable.
- }
-
- public static final class ForgettingCurveParams {
- private byte mFc;
- long mLastTouchedTime = 0;
- private final boolean mIsValid;
-
- private void updateLastTouchedTime() {
- mLastTouchedTime = System.currentTimeMillis();
- }
-
- public ForgettingCurveParams(boolean isValid) {
- this(System.currentTimeMillis(), isValid);
- }
-
- private ForgettingCurveParams(long now, boolean isValid) {
- this(pushCount((byte)0, isValid), now, now, isValid);
- }
-
- /** This constructor is called when the user history bigram dictionary is being restored. */
- public ForgettingCurveParams(int fc, long now, long last) {
- // All words with level >= 1 had been saved.
- // Invalid words with level == 0 had been saved.
- // Valid words words with level == 0 had *not* been saved.
- this(fc, now, last, fcToLevel((byte)fc) > 0);
- }
-
- private ForgettingCurveParams(int fc, long now, long last, boolean isValid) {
- mIsValid = isValid;
- mFc = (byte)fc;
- mLastTouchedTime = last;
- updateElapsedTime(now);
- }
-
- public boolean isValid() {
- return mIsValid;
- }
-
- public byte getFc() {
- updateElapsedTime(System.currentTimeMillis());
- return mFc;
- }
-
- public int getFrequency() {
- updateElapsedTime(System.currentTimeMillis());
- return UserHistoryForgettingCurveUtils.fcToFreq(mFc);
- }
-
- public int notifyTypedAgainAndGetFrequency() {
- updateLastTouchedTime();
- // TODO: Check whether this word is valid or not
- mFc = pushCount(mFc, false);
- return UserHistoryForgettingCurveUtils.fcToFreq(mFc);
- }
-
- private void updateElapsedTime(long now) {
- final int elapsedTimeCount =
- (int)((now - mLastTouchedTime) / ELAPSED_TIME_INTERVAL_MILLIS);
- if (elapsedTimeCount <= 0) {
- return;
- }
- if (elapsedTimeCount >= MAX_PUSH_ELAPSED) {
- mLastTouchedTime = now;
- mFc = 0;
- return;
- }
- for (int i = 0; i < elapsedTimeCount; ++i) {
- mLastTouchedTime += ELAPSED_TIME_INTERVAL_MILLIS;
- mFc = pushElapsedTime(mFc);
- }
- }
- }
-
- /* package */ static int fcToElapsedTime(byte fc) {
- return fc & 0x0F;
- }
-
- /* package */ static int fcToCount(byte fc) {
- return (fc >> 4) & 0x03;
- }
-
- /* package */ static int fcToLevel(byte fc) {
- return (fc >> 6) & 0x03;
- }
-
- private static int calcFreq(int elapsedTime, int count, int level) {
- if (level <= 0) {
- // Reserved words, just return -1
- return -1;
- }
- if (count == COUNT_MAX) {
- // Temporary promote because it's frequently typed recently
- ++level;
- }
- final int et = Math.min(FC_FREQ_MAX, Math.max(0, elapsedTime));
- final int l = Math.min(FC_LEVEL_MAX, Math.max(0, level));
- return MathUtils.SCORE_TABLE[l - 1][et];
- }
-
- /* pakcage */ static byte calcFc(int elapsedTime, int count, int level) {
- final int et = Math.min(FC_FREQ_MAX, Math.max(0, elapsedTime));
- final int c = Math.min(COUNT_MAX, Math.max(0, count));
- final int l = Math.min(FC_LEVEL_MAX, Math.max(0, level));
- return (byte)(et | (c << 4) | (l << 6));
- }
-
- public static int fcToFreq(byte fc) {
- final int elapsedTime = fcToElapsedTime(fc);
- final int count = fcToCount(fc);
- final int level = fcToLevel(fc);
- return calcFreq(elapsedTime, count, level);
- }
-
- public static byte pushElapsedTime(byte fc) {
- int elapsedTime = fcToElapsedTime(fc);
- int count = fcToCount(fc);
- int level = fcToLevel(fc);
- if (elapsedTime >= ELAPSED_TIME_MAX) {
- // Downgrade level
- elapsedTime = 0;
- count = COUNT_MAX;
- --level;
- } else {
- ++elapsedTime;
- }
- return calcFc(elapsedTime, count, level);
- }
-
- public static byte pushCount(byte fc, boolean isValid) {
- final int elapsedTime = fcToElapsedTime(fc);
- int count = fcToCount(fc);
- int level = fcToLevel(fc);
- if ((elapsedTime == 0 && count >= COUNT_MAX) || (isValid && level == 0)) {
- // Upgrade level
- ++level;
- count = 0;
- if (DEBUG) {
- Log.d(TAG, "Upgrade level.");
- }
- } else {
- ++count;
- }
- return calcFc(0, count, level);
- }
-
- // TODO: isValid should be false for a word whose frequency is 0,
- // or that is not in the dictionary.
- /**
- * Check wheather we should save the bigram to the SQL DB or not
- */
- public static boolean needsToSave(byte fc, boolean isValid, boolean addLevel0Bigram) {
- int level = fcToLevel(fc);
- if (level == 0) {
- if (isValid || !addLevel0Bigram) {
- return false;
- }
- }
- final int elapsedTime = fcToElapsedTime(fc);
- return (elapsedTime < ELAPSED_TIME_MAX - 1 || level > 0);
- }
-
- private static final class MathUtils {
- public static final int[][] SCORE_TABLE = new int[FC_LEVEL_MAX][ELAPSED_TIME_MAX + 1];
- static {
- for (int i = 0; i < FC_LEVEL_MAX; ++i) {
- final float initialFreq;
- if (i >= 2) {
- initialFreq = FC_FREQ_MAX;
- } else if (i == 1) {
- initialFreq = FC_FREQ_MAX / 2;
- } else if (i == 0) {
- initialFreq = FC_FREQ_MAX / 4;
- } else {
- continue;
- }
- for (int j = 0; j < ELAPSED_TIME_MAX; ++j) {
- final float elapsedHours = j * ELAPSED_TIME_INTERVAL_HOURS;
- final float freq = initialFreq
- * (float)Math.pow(initialFreq, elapsedHours / HALF_LIFE_HOURS);
- final int intFreq = Math.min(FC_FREQ_MAX, Math.max(0, (int)freq));
- SCORE_TABLE[i][j] = intFreq;
- }
- }
- }
- }
-}