aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--java/AndroidManifest.xml1
-rw-r--r--java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java3
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java3
-rw-r--r--java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java4
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java4
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputMethodManager.java30
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputMethodSubtype.java98
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeSwitcher.java100
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LocaleUtils.java9
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java49
-rw-r--r--tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java16
-rw-r--r--tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java42
-rw-r--r--tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java75
-rw-r--r--tools/dicttool/src/com/android/inputmethod/latin/dicttool/CommandList.java1
-rw-r--r--tools/dicttool/src/com/android/inputmethod/latin/dicttool/Header.java69
-rw-r--r--tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java93
16 files changed, 457 insertions, 140 deletions
diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml
index ee1cef6b5..88e867f09 100644
--- a/java/AndroidManifest.xml
+++ b/java/AndroidManifest.xml
@@ -37,7 +37,6 @@
<application android:label="@string/english_ime_name"
android:icon="@drawable/ic_launcher_keyboard"
- android:killAfterRestore="false"
android:supportsRtl="true"
android:allowBackup="true">
diff --git a/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java
index 3a27c5739..58ad4bd4c 100644
--- a/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java
@@ -26,6 +26,8 @@ import com.android.inputmethod.latin.common.Constants;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
+import javax.annotation.Nonnull;
+
public final class InputMethodSubtypeCompatUtils {
private static final String TAG = InputMethodSubtypeCompatUtils.class.getSimpleName();
// Note that InputMethodSubtype(int nameId, int iconId, String locale, String mode,
@@ -53,6 +55,7 @@ public final class InputMethodSubtypeCompatUtils {
}
@SuppressWarnings("deprecation")
+ @Nonnull
public static InputMethodSubtype newInputMethodSubtype(int nameId, int iconId, String locale,
String mode, String extraValue, boolean isAuxiliary,
boolean overridesImplicitlyEnabledSubtype, int id) {
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
index 7eb91b588..1637ed9f6 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
@@ -38,7 +38,6 @@ import com.android.inputmethod.keyboard.internal.KeysCache;
import com.android.inputmethod.latin.InputAttributes;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.RichInputMethodSubtype;
-import com.android.inputmethod.latin.SubtypeSwitcher;
import com.android.inputmethod.latin.define.DebugFlags;
import com.android.inputmethod.latin.utils.InputTypeUtils;
import com.android.inputmethod.latin.utils.ScriptUtils;
@@ -276,7 +275,7 @@ public final class KeyboardLayoutSet {
mParams.mEditorInfo.imeOptions)
|| deprecatedForceAscii;
final RichInputMethodSubtype keyboardSubtype = (forceAscii && !asciiCapable)
- ? SubtypeSwitcher.getInstance().getNoLanguageSubtype()
+ ? RichInputMethodSubtype.getNoLanguageSubtype()
: subtype;
mParams.mSubtype = keyboardSubtype;
mParams.mKeyboardLayoutSetName = KEYBOARD_LAYOUT_SET_RESOURCE_PREFIX
diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java
index 06184f8d2..cf4dd3db3 100644
--- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java
+++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java
@@ -48,7 +48,7 @@ import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;
import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.SubtypeSwitcher;
+import com.android.inputmethod.latin.RichInputMethodSubtype;
import com.android.inputmethod.latin.common.Constants;
import com.android.inputmethod.latin.utils.ResourceUtils;
@@ -113,7 +113,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
context, null /* editorInfo */);
final Resources res = context.getResources();
mEmojiLayoutParams = new EmojiLayoutParams(res);
- builder.setSubtype(SubtypeSwitcher.getInstance().getEmojiSubtype());
+ builder.setSubtype(RichInputMethodSubtype.getEmojiSubtype());
builder.setKeyboardGeometry(ResourceUtils.getDefaultKeyboardWidth(res),
mEmojiLayoutParams.mEmojiKeyboardHeight);
final KeyboardLayoutSet layoutSet = builder.build();
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index dc665471d..eb0037e8c 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -626,7 +626,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private void refreshPersonalizationDictionarySession(
final SettingsValues currentSettingsValues) {
mDictionaryFacilitator.setIsMonolingualUser(
- mSubtypeSwitcher.isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes());
+ mRichImm.isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes());
mPersonalizationDictionaryUpdater.onLoadSettings(
currentSettingsValues.mUsePersonalizedDicts);
mContextualDictionaryUpdater.onLoadSettings(currentSettingsValues.mUsePersonalizedDicts);
@@ -863,7 +863,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// also wouldn't be consuming gesture data.
mGestureConsumer = GestureConsumer.NULL_GESTURE_CONSUMER;
mRichImm.clearSubtypeCaches();
- mSubtypeSwitcher.refreshSubtypeInfo();
+ mSubtypeSwitcher.onSubtypeChanged(mRichImm.getCurrentRawSubtype());
final KeyboardSwitcher switcher = mKeyboardSwitcher;
switcher.updateKeyboardTheme();
final MainKeyboardView mainKeyboardView = switcher.getMainKeyboardView();
diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
index 8d8e7ac38..a53837a59 100644
--- a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
+++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
@@ -29,6 +29,8 @@ import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
+import com.android.inputmethod.latin.common.Constants;
import com.android.inputmethod.latin.settings.AdditionalFeaturesSettingUtils;
import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
@@ -36,7 +38,10 @@ import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
+import java.util.Set;
import javax.annotation.Nonnull;
@@ -432,4 +437,29 @@ public class RichInputMethodManager {
}
return mImmWrapper.shouldOfferSwitchingToNextInputMethod(binder);
}
+
+ public boolean isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes() {
+ final Locale systemLocale = mContext.getResources().getConfiguration().locale;
+ final Set<InputMethodSubtype> enabledSubtypesOfEnabledImes = new HashSet<>();
+ final InputMethodManager inputMethodManager = getInputMethodManager();
+ final List<InputMethodInfo> enabledInputMethodInfoList =
+ inputMethodManager.getEnabledInputMethodList();
+ for (final InputMethodInfo info : enabledInputMethodInfoList) {
+ final List<InputMethodSubtype> enabledSubtypes =
+ inputMethodManager.getEnabledInputMethodSubtypeList(
+ info, true /* allowsImplicitlySelectedSubtypes */);
+ if (enabledSubtypes.isEmpty()) {
+ // An IME with no subtypes is found.
+ return false;
+ }
+ enabledSubtypesOfEnabledImes.addAll(enabledSubtypes);
+ }
+ for (final InputMethodSubtype subtype : enabledSubtypesOfEnabledImes) {
+ if (!subtype.isAuxiliary() && !subtype.getLocale().isEmpty()
+ && !systemLocale.equals(SubtypeLocaleUtils.getSubtypeLocale(subtype))) {
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodSubtype.java b/java/src/com/android/inputmethod/latin/RichInputMethodSubtype.java
index 8d055531c..1a6c6ba06 100644
--- a/java/src/com/android/inputmethod/latin/RichInputMethodSubtype.java
+++ b/java/src/com/android/inputmethod/latin/RichInputMethodSubtype.java
@@ -16,33 +16,45 @@
package com.android.inputmethod.latin;
+import static com.android.inputmethod.latin.common.Constants.Subtype.KEYBOARD_MODE;
+
+import android.util.Log;
import android.view.inputmethod.InputMethodSubtype;
+import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
+import com.android.inputmethod.latin.common.Constants;
import com.android.inputmethod.latin.utils.LocaleUtils;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
import java.util.Arrays;
import java.util.Locale;
+import javax.annotation.Nonnull;
+
/**
* Enrichment class for InputMethodSubtype to enable concurrent multi-lingual input.
*
* Right now, this returns the extra value of its primary subtype.
*/
public final class RichInputMethodSubtype {
+ private static final String TAG = RichInputMethodSubtype.class.getSimpleName();
+
+ @Nonnull
private final InputMethodSubtype mSubtype;
+ @Nonnull
private final Locale[] mLocales;
- public RichInputMethodSubtype(final InputMethodSubtype subtype, final Locale... locales) {
+ public RichInputMethodSubtype(@Nonnull final InputMethodSubtype subtype,
+ @Nonnull final Locale... locales) {
mSubtype = subtype;
- mLocales = new Locale[locales.length+1];
+ mLocales = new Locale[locales.length + 1];
mLocales[0] = LocaleUtils.constructLocaleFromString(mSubtype.getLocale());
System.arraycopy(locales, 0, mLocales, 1, locales.length);
}
// Extra values are determined by the primary subtype. This is probably right, but
// we may have to revisit this later.
- public String getExtraValueOf(final String key) {
+ public String getExtraValueOf(@Nonnull final String key) {
return mSubtype.getExtraValueOf(key);
}
@@ -80,6 +92,7 @@ public final class RichInputMethodSubtype {
// en_US azerty T English English (US)
// zz azerty T AZERTY AZERTY
// Get the RichInputMethodSubtype's full display name in its locale.
+ @Nonnull
public String getFullDisplayName() {
if (isNoLanguage()) {
return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(mSubtype);
@@ -88,6 +101,7 @@ public final class RichInputMethodSubtype {
}
// Get the RichInputMethodSubtype's middle display name in its locale.
+ @Nonnull
public String getMiddleDisplayName() {
if (isNoLanguage()) {
return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(mSubtype);
@@ -96,7 +110,7 @@ public final class RichInputMethodSubtype {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(final Object o) {
if (!(o instanceof RichInputMethodSubtype)) {
return false;
}
@@ -114,6 +128,7 @@ public final class RichInputMethodSubtype {
return "Multi-lingual subtype: " + mSubtype.toString() + ", " + Arrays.toString(mLocales);
}
+ @Nonnull
public Locale[] getLocales() {
return mLocales;
}
@@ -124,5 +139,80 @@ public final class RichInputMethodSubtype {
}
// TODO: remove this method
+ @Nonnull
public InputMethodSubtype getRawSubtype() { return mSubtype; }
+
+ // Dummy no language QWERTY subtype. See {@link R.xml.method}.
+ 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;
+ @Nonnull
+ private static final RichInputMethodSubtype DUMMY_NO_LANGUAGE_SUBTYPE =
+ new RichInputMethodSubtype(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 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;
+ @Nonnull
+ private static final RichInputMethodSubtype DUMMY_EMOJI_SUBTYPE = new RichInputMethodSubtype(
+ 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));
+ private static RichInputMethodSubtype sNoLanguageSubtype;
+ private static RichInputMethodSubtype sEmojiSubtype;
+
+ @Nonnull
+ public static RichInputMethodSubtype getNoLanguageSubtype() {
+ RichInputMethodSubtype noLanguageSubtype = sNoLanguageSubtype;
+ if (noLanguageSubtype == null) {
+ final InputMethodSubtype rawNoLanguageSubtype = RichInputMethodManager.getInstance()
+ .findSubtypeByLocaleAndKeyboardLayoutSet(
+ SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.QWERTY);
+ if (rawNoLanguageSubtype != null) {
+ noLanguageSubtype = new RichInputMethodSubtype(rawNoLanguageSubtype);
+ }
+ }
+ if (noLanguageSubtype != null) {
+ sNoLanguageSubtype = noLanguageSubtype;
+ return noLanguageSubtype;
+ }
+ 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;
+ }
+
+ @Nonnull
+ public static RichInputMethodSubtype getEmojiSubtype() {
+ RichInputMethodSubtype emojiSubtype = sEmojiSubtype;
+ if (emojiSubtype == null) {
+ final InputMethodSubtype rawEmojiSubtype = RichInputMethodManager.getInstance()
+ .findSubtypeByLocaleAndKeyboardLayoutSet(
+ SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.EMOJI);
+ if (rawEmojiSubtype != null) {
+ emojiSubtype = new RichInputMethodSubtype(rawEmojiSubtype);
+ }
+ }
+ if (emojiSubtype != null) {
+ sEmojiSubtype = emojiSubtype;
+ return emojiSubtype;
+ }
+ 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/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index 98bce95bd..306ccf0d0 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -17,6 +17,7 @@
package com.android.inputmethod.latin;
import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.REQ_NETWORK_CONNECTIVITY;
+
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
@@ -31,18 +32,14 @@ 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.keyboard.internal.LanguageOnSpacebarHelper;
-import com.android.inputmethod.latin.common.Constants;
import com.android.inputmethod.latin.define.DebugFlags;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
-import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
-import java.util.Set;
import javax.annotation.Nonnull;
@@ -60,39 +57,8 @@ public final class SubtypeSwitcher {
private InputMethodInfo mShortcutInputMethodInfo;
private InputMethodSubtype mShortcutSubtype;
private RichInputMethodSubtype mCurrentRichInputMethodSubtype;
- private RichInputMethodSubtype mNoLanguageSubtype;
- private RichInputMethodSubtype mEmojiSubtype;
private boolean mIsNetworkConnected;
- private static final String KEYBOARD_MODE = "keyboard";
- // Dummy no language QWERTY subtype. See {@link R.xml.method}.
- 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 RichInputMethodSubtype DUMMY_NO_LANGUAGE_SUBTYPE =
- new RichInputMethodSubtype(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 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 RichInputMethodSubtype DUMMY_EMOJI_SUBTYPE = new RichInputMethodSubtype(
- 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));
-
public static SubtypeSwitcher getInstance() {
return sInstance;
}
@@ -119,12 +85,8 @@ public final class SubtypeSwitcher {
final NetworkInfo info = connectivityManager.getActiveNetworkInfo();
mIsNetworkConnected = (info != null && info.isConnected());
- refreshSubtypeInfo();
- updateParametersOnStartInputView();
- }
-
- public void refreshSubtypeInfo() {
onSubtypeChanged(mRichImm.getCurrentRawSubtype());
+ updateParametersOnStartInputView();
}
/**
@@ -267,31 +229,6 @@ public final class SubtypeSwitcher {
return mLanguageOnSpacebarHelper.getLanguageOnSpacebarFormatType(subtype);
}
- public boolean isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes() {
- final Locale systemLocale = mResources.getConfiguration().locale;
- final Set<InputMethodSubtype> enabledSubtypesOfEnabledImes = new HashSet<>();
- final InputMethodManager inputMethodManager = mRichImm.getInputMethodManager();
- final List<InputMethodInfo> enabledInputMethodInfoList =
- inputMethodManager.getEnabledInputMethodList();
- for (final InputMethodInfo info : enabledInputMethodInfoList) {
- final List<InputMethodSubtype> enabledSubtypes =
- inputMethodManager.getEnabledInputMethodSubtypeList(
- info, true /* allowsImplicitlySelectedSubtypes */);
- if (enabledSubtypes.isEmpty()) {
- // An IME with no subtypes is found.
- return false;
- }
- enabledSubtypesOfEnabledImes.addAll(enabledSubtypes);
- }
- for (final InputMethodSubtype subtype : enabledSubtypesOfEnabledImes) {
- if (!subtype.isAuxiliary() && !subtype.getLocale().isEmpty()
- && !systemLocale.equals(SubtypeLocaleUtils.getSubtypeLocale(subtype))) {
- return false;
- }
- }
- return true;
- }
-
private static RichInputMethodSubtype sForcedSubtypeForTesting = null;
@UsedForTesting
@@ -313,39 +250,6 @@ public final class SubtypeSwitcher {
return mCurrentRichInputMethodSubtype;
}
- public RichInputMethodSubtype getNoLanguageSubtype() {
- if (mNoLanguageSubtype == null) {
- mNoLanguageSubtype = new RichInputMethodSubtype(
- mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
- SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.QWERTY));
- }
- if (mNoLanguageSubtype != null) {
- return mNoLanguageSubtype;
- }
- 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;
- }
-
- public RichInputMethodSubtype getEmojiSubtype() {
- if (mEmojiSubtype == null) {
- final InputMethodSubtype rawEmojiSubtype =
- mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
- SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.EMOJI);
- if (null != rawEmojiSubtype) {
- mEmojiSubtype = new RichInputMethodSubtype(rawEmojiSubtype);
- }
- }
- if (mEmojiSubtype != null) {
- return mEmojiSubtype;
- }
- 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;
- }
-
public String getCombiningRulesExtraValueOfCurrentSubtype() {
return SubtypeLocaleUtils.getCombiningRulesExtraValue(getCurrentSubtype().getRawSubtype());
}
diff --git a/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java
index c519a0de6..4bab497ba 100644
--- a/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java
@@ -21,6 +21,8 @@ import android.text.TextUtils;
import java.util.HashMap;
import java.util.Locale;
+import javax.annotation.Nullable;
+
/**
* A class to help with handling Locales in string form.
*
@@ -164,8 +166,10 @@ public final class LocaleUtils {
/**
* Creates a locale from a string specification.
*/
- public static Locale constructLocaleFromString(final String localeStr) {
+ @Nullable
+ public static Locale constructLocaleFromString(@Nullable final String localeStr) {
if (localeStr == null) {
+ // TODO: Should this be Locale.ROOT?
return null;
}
synchronized (sLocaleCache) {
@@ -173,7 +177,7 @@ public final class LocaleUtils {
if (retval != null) {
return retval;
}
- String[] localeParams = localeStr.split("_", 3);
+ final String[] localeParams = localeStr.split("_", 3);
if (localeParams.length == 1) {
retval = new Locale(localeParams[0]);
} else if (localeParams.length == 2) {
@@ -184,6 +188,7 @@ public final class LocaleUtils {
if (retval != null) {
sLocaleCache.put(localeStr, retval);
}
+ // TODO: Should return Locale.ROOT instead of null?
return retval;
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
index 55c1dc9e5..e4a8ee2a6 100644
--- a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
@@ -34,6 +34,8 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
+import javax.annotation.Nonnull;
+
/**
* A helper class to deal with subtype locales.
*/
@@ -173,7 +175,8 @@ public final class SubtypeLocaleUtils {
return nameId == null ? UNKNOWN_KEYBOARD_LAYOUT : nameId;
}
- public static Locale getDisplayLocaleOfSubtypeLocale(final String localeString) {
+ @Nonnull
+ public static Locale getDisplayLocaleOfSubtypeLocale(@Nonnull final String localeString) {
if (NO_LANGUAGE.equals(localeString)) {
return sResources.getConfiguration().locale;
}
@@ -183,17 +186,20 @@ public final class SubtypeLocaleUtils {
return LocaleUtils.constructLocaleFromString(localeString);
}
- public static String getSubtypeLocaleDisplayNameInSystemLocale(final String localeString) {
+ public static String getSubtypeLocaleDisplayNameInSystemLocale(
+ @Nonnull final String localeString) {
final Locale displayLocale = sResources.getConfiguration().locale;
return getSubtypeLocaleDisplayNameInternal(localeString, displayLocale);
}
- public static String getSubtypeLocaleDisplayName(final String localeString) {
+ @Nonnull
+ public static String getSubtypeLocaleDisplayName(@Nonnull final String localeString) {
final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(localeString);
return getSubtypeLocaleDisplayNameInternal(localeString, displayLocale);
}
- public static String getSubtypeLanguageDisplayName(final String localeString) {
+ @Nonnull
+ public static String getSubtypeLanguageDisplayName(@Nonnull final String localeString) {
final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(localeString);
final String languageString;
if (sExceptionalLocaleDisplayedInRootLocale.containsKey(localeString)) {
@@ -205,8 +211,9 @@ public final class SubtypeLocaleUtils {
return getSubtypeLocaleDisplayNameInternal(languageString, displayLocale);
}
- private static String getSubtypeLocaleDisplayNameInternal(final String localeString,
- final Locale displayLocale) {
+ @Nonnull
+ private static String getSubtypeLocaleDisplayNameInternal(@Nonnull final String localeString,
+ @Nonnull final Locale displayLocale) {
if (NO_LANGUAGE.equals(localeString)) {
// No language subtype should be displayed in system locale.
return sResources.getString(R.string.subtype_no_language);
@@ -255,8 +262,9 @@ public final class SubtypeLocaleUtils {
// en_US azerty T English (US) (AZERTY) exception
// zz azerty T Alphabet (AZERTY) in system locale
- private static String getReplacementString(final InputMethodSubtype subtype,
- final Locale displayLocale) {
+ @Nonnull
+ private static String getReplacementString(@Nonnull final InputMethodSubtype subtype,
+ @Nonnull final Locale displayLocale) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
&& subtype.containsExtraValueKey(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)) {
return subtype.getExtraValueOf(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME);
@@ -264,20 +272,24 @@ public final class SubtypeLocaleUtils {
return getSubtypeLocaleDisplayNameInternal(subtype.getLocale(), displayLocale);
}
- public static String getSubtypeDisplayNameInSystemLocale(final InputMethodSubtype subtype) {
+ @Nonnull
+ public static String getSubtypeDisplayNameInSystemLocale(
+ @Nonnull final InputMethodSubtype subtype) {
final Locale displayLocale = sResources.getConfiguration().locale;
return getSubtypeDisplayNameInternal(subtype, displayLocale);
}
- public static String getSubtypeNameForLogging(final InputMethodSubtype subtype) {
+ @Nonnull
+ public static String getSubtypeNameForLogging(@Nonnull final InputMethodSubtype subtype) {
if (subtype == null) {
return "<null subtype>";
}
return getSubtypeLocale(subtype) + "/" + getKeyboardLayoutSetName(subtype);
}
- private static String getSubtypeDisplayNameInternal(final InputMethodSubtype subtype,
- final Locale displayLocale) {
+ @Nonnull
+ private static String getSubtypeDisplayNameInternal(@Nonnull final InputMethodSubtype subtype,
+ @Nonnull final Locale displayLocale) {
final String replacementString = getReplacementString(subtype, displayLocale);
// TODO: rework this for multi-lingual subtypes
final int nameResId = subtype.getNameResId();
@@ -302,21 +314,26 @@ public final class SubtypeLocaleUtils {
getSubtypeName.runInLocale(sResources, displayLocale), displayLocale);
}
- public static Locale getSubtypeLocale(final InputMethodSubtype subtype) {
+ @Nonnull
+ public static Locale getSubtypeLocale(@Nonnull final InputMethodSubtype subtype) {
final String localeString = subtype.getLocale();
return LocaleUtils.constructLocaleFromString(localeString);
}
- public static String getKeyboardLayoutSetDisplayName(final InputMethodSubtype subtype) {
+ @Nonnull
+ public static String getKeyboardLayoutSetDisplayName(
+ @Nonnull final InputMethodSubtype subtype) {
final String layoutName = getKeyboardLayoutSetName(subtype);
return getKeyboardLayoutSetDisplayName(layoutName);
}
- public static String getKeyboardLayoutSetDisplayName(final String layoutName) {
+ @Nonnull
+ public static String getKeyboardLayoutSetDisplayName(@Nonnull final String layoutName) {
return sKeyboardLayoutToDisplayNameMap.get(layoutName);
}
- public static String getKeyboardLayoutSetName(final RichInputMethodSubtype subtype) {
+ @Nonnull
+ public static String getKeyboardLayoutSetName(@Nonnull final RichInputMethodSubtype subtype) {
return getKeyboardLayoutSetName(subtype.getRawSubtype());
}
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java
index d239f8dac..6c60fdc0c 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java
@@ -26,6 +26,7 @@ import com.android.inputmethod.latin.BinaryDictionary;
import com.android.inputmethod.latin.common.CodePointUtils;
import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding;
import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer;
+import com.android.inputmethod.latin.makedict.FormatSpec.DictionaryOptions;
import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
@@ -67,6 +68,8 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase {
private static final SparseArray<List<Integer>> sChainBigrams = new SparseArray<>();
private static final HashMap<String, List<String>> sShortcuts = new HashMap<>();
+ final Random mRandom;
+
public BinaryDictDecoderEncoderTests() {
this(System.currentTimeMillis(), DEFAULT_MAX_UNIGRAMS);
}
@@ -75,10 +78,10 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase {
super();
BinaryDictionaryUtils.setCurrentTimeForTest(0);
Log.e(TAG, "Testing dictionary: seed is " + seed);
- final Random random = new Random(seed);
+ mRandom = new Random(seed);
sWords.clear();
sWordsWithVariousCodePoints.clear();
- generateWords(maxUnigrams, random);
+ generateWords(maxUnigrams, mRandom);
for (int i = 0; i < sWords.size(); ++i) {
sChainBigrams.put(i, new ArrayList<Integer>());
@@ -96,10 +99,10 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase {
sShortcuts.clear();
for (int i = 0; i < NUM_OF_NODES_HAVING_SHORTCUTS; ++i) {
- final int from = Math.abs(random.nextInt()) % sWords.size();
+ final int from = Math.abs(mRandom.nextInt()) % sWords.size();
sShortcuts.put(sWords.get(from), new ArrayList<String>());
for (int j = 0; j < NUM_OF_SHORTCUTS; ++j) {
- final int to = Math.abs(random.nextInt()) % sWords.size();
+ final int to = Math.abs(mRandom.nextInt()) % sWords.size();
sShortcuts.get(sWords.get(from)).add(sWords.get(to));
}
}
@@ -604,11 +607,10 @@ public class BinaryDictDecoderEncoderTests extends AndroidTestCase {
+ " : " + outputOptions(bufferType, formatOptions));
// Test a word that isn't contained within the dictionary.
- final Random random = new Random((int)System.currentTimeMillis());
final int[] codePointSet = CodePointUtils.generateCodePointSet(DEFAULT_CODE_POINT_SET_SIZE,
- random);
+ mRandom);
for (int i = 0; i < 1000; ++i) {
- final String word = CodePointUtils.generateWord(random, codePointSet);
+ final String word = CodePointUtils.generateWord(mRandom, codePointSet);
if (sWords.indexOf(word) != -1) continue;
checkGetTerminalPosition(dictDecoder, word, false);
}
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
index 120b96bc6..be75565bb 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
@@ -17,11 +17,16 @@
package com.android.inputmethod.latin.makedict;
import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.HashMap;
+import java.util.LinkedList;
+
+import javax.annotation.Nonnull;
/**
* Decodes binary files for a FusionDictionary.
@@ -361,6 +366,43 @@ public final class BinaryDictDecoderUtils {
}
/**
+ * Helper method that brutally decodes a header from a byte array.
+ *
+ * @param headerBuffer a buffer containing the bytes of the header.
+ * @return a hashmap of the attributes stored in the header
+ */
+ @Nonnull
+ public static HashMap<String, String> decodeHeaderAttributes(@Nonnull final byte[] headerBuffer)
+ throws UnsupportedFormatException {
+ final StringBuilder sb = new StringBuilder();
+ final LinkedList<String> keyValues = new LinkedList<>();
+ int index = 0;
+ while (index < headerBuffer.length) {
+ if (headerBuffer[index] == FormatSpec.PTNODE_CHARACTERS_TERMINATOR) {
+ keyValues.add(sb.toString());
+ sb.setLength(0);
+ } else if (CharEncoding.fitsOnOneByte(headerBuffer[index] & 0xFF,
+ null /* codePointTable */)) {
+ sb.appendCodePoint(headerBuffer[index] & 0xFF);
+ } else {
+ sb.appendCodePoint(((headerBuffer[index] & 0xFF) << 16)
+ + ((headerBuffer[index + 1] & 0xFF) << 8)
+ + (headerBuffer[index + 2] & 0xFF));
+ index += 2;
+ }
+ index += 1;
+ }
+ if ((keyValues.size() & 1) != 0) {
+ throw new UnsupportedFormatException("Odd number of attributes");
+ }
+ final HashMap<String, String> attributes = new HashMap<>();
+ for (int i = 0; i < keyValues.size(); i += 2) {
+ attributes.put(keyValues.get(i), keyValues.get(i + 1));
+ }
+ return attributes;
+ }
+
+ /**
* Helper method to pass a file name instead of a File object to isBinaryDictionary.
*/
public static boolean isBinaryDictionary(final String filename) {
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java
index 49a6e8e14..3ec28f313 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java
@@ -19,6 +19,10 @@ package com.android.inputmethod.latin.dicttool;
import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils;
import com.android.inputmethod.latin.makedict.BinaryDictIOUtils;
import com.android.inputmethod.latin.makedict.DictDecoder;
+import com.android.inputmethod.latin.makedict.DictionaryHeader;
+import com.android.inputmethod.latin.makedict.FormatSpec;
+import com.android.inputmethod.latin.makedict.FormatSpec.DictionaryOptions;
+import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
import com.android.inputmethod.latin.makedict.FusionDictionary;
import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
@@ -34,6 +38,8 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.HashMap;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -59,7 +65,7 @@ public final class BinaryDictOffdeviceUtils {
};
private final int mDecoderSpecIndex;
- T mResult;
+ public T mResult;
public DecoderChainSpec() {
mDecoderSpecIndex = 0;
@@ -142,6 +148,54 @@ public final class BinaryDictOffdeviceUtils {
}
}
+ public static class HeaderReaderProcessor implements InputProcessor<DictionaryHeader> {
+ // Arbitrarily limit the header length to 32k. Sounds like it would never be larger
+ // than this. Revisit this if needed later.
+ private final int MAX_HEADER_LENGTH = 32 * 1024;
+ @Override @Nonnull
+ public DictionaryHeader process(final InputStream input) throws IOException,
+ UnsupportedFormatException {
+ // Do everything as curtly and ad-hoc as possible for performance.
+ final byte[] tmpBuffer = new byte[12];
+ if (tmpBuffer.length != input.read(tmpBuffer)) {
+ throw new UnsupportedFormatException("File too short, not a dictionary");
+ }
+ // Ad-hoc check for the magic number. See FormatSpec.java as well as
+ // byte_array_utils.h and BinaryDictEncoderUtils#writeDictionaryHeader().
+ final int MAGIC_NUMBER_START_OFFSET = 0;
+ final int VERSION_START_OFFSET = 4;
+ final int HEADER_SIZE_OFFSET = 8;
+ final int magicNumber = ((tmpBuffer[MAGIC_NUMBER_START_OFFSET] & 0xFF) << 24)
+ + ((tmpBuffer[MAGIC_NUMBER_START_OFFSET + 1] & 0xFF) << 16)
+ + ((tmpBuffer[MAGIC_NUMBER_START_OFFSET + 2] & 0xFF) << 8)
+ + (tmpBuffer[MAGIC_NUMBER_START_OFFSET + 3] & 0xFF);
+ if (magicNumber != FormatSpec.MAGIC_NUMBER) {
+ throw new UnsupportedFormatException("Wrong magic number");
+ }
+ final int version = ((tmpBuffer[VERSION_START_OFFSET] & 0xFF) << 8)
+ + (tmpBuffer[VERSION_START_OFFSET + 1] & 0xFF);
+ if (version != FormatSpec.VERSION2 && version != FormatSpec.VERSION201
+ && version != FormatSpec.VERSION202) {
+ throw new UnsupportedFormatException("Only versions 2, 201, 202 are supported");
+ }
+ final int totalHeaderSize = ((tmpBuffer[HEADER_SIZE_OFFSET] & 0xFF) << 24)
+ + ((tmpBuffer[HEADER_SIZE_OFFSET + 1] & 0xFF) << 16)
+ + ((tmpBuffer[HEADER_SIZE_OFFSET + 2] & 0xFF) << 8)
+ + (tmpBuffer[HEADER_SIZE_OFFSET + 3] & 0xFF);
+ if (totalHeaderSize > MAX_HEADER_LENGTH) {
+ throw new UnsupportedFormatException("Header too large");
+ }
+ final byte[] headerBuffer = new byte[totalHeaderSize - tmpBuffer.length];
+ if (headerBuffer.length != input.read(headerBuffer)) {
+ throw new UnsupportedFormatException("File shorter than specified in the header");
+ }
+ final HashMap<String, String> attributes =
+ BinaryDictDecoderUtils.decodeHeaderAttributes(headerBuffer);
+ return new DictionaryHeader(totalHeaderSize, new DictionaryOptions(attributes),
+ new FormatOptions(version, false /* hasTimestamp */));
+ }
+ }
+
public static void copy(final InputStream input, final OutputStream output) throws IOException {
final byte[] buffer = new byte[COPY_BUFFER_SIZE];
for (int readBytes = input.read(buffer); readBytes >= 0; readBytes = input.read(buffer)) {
@@ -162,11 +216,22 @@ public final class BinaryDictOffdeviceUtils {
@Nonnull DecoderChainSpec spec = new DecoderChainSpec();
while (null != spec) {
try {
- try (final InputStream input = spec.getStream(src)) {
- spec.mResult = processor.process(input);
- return spec;
+ final InputStream input = spec.getStream(src);
+ spec.mResult = processor.process(input);
+ try {
+ input.close();
+ } catch (IOException e) {
+ // CipherInputStream doesn't like being closed without having read the
+ // entire stream, for some reason. But we don't want to because it's a waste
+ // of resources. We really, really don't care about this.
+ // However on close() CipherInputStream does throw this exception, wrapped
+ // in an IOException so we need to catch it.
+ if (!(e.getCause() instanceof javax.crypto.BadPaddingException)) {
+ throw e;
+ }
}
- } catch (IOException | UnsupportedFormatException e) {
+ return spec;
+ } catch (IOException | UnsupportedFormatException | ArrayIndexOutOfBoundsException e) {
// If the format is not the right one for this file, the processor will throw one
// of these exceptions. In our case, that means we should try the next spec,
// since it may still be at another format we haven't tried yet.
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CommandList.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CommandList.java
index 07450ca51..8fdf7633f 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CommandList.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CommandList.java
@@ -20,6 +20,7 @@ public class CommandList {
public static void populate() {
// TODO: Move some commands to native code.
Dicttool.addCommand("info", Info.class);
+ Dicttool.addCommand("header", Header.class);
Dicttool.addCommand("diff", Diff.class);
Dicttool.addCommand("compress", Compress.Compressor.class);
Dicttool.addCommand("uncompress", Compress.Uncompressor.class);
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Header.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Header.java
new file mode 100644
index 000000000..51efdec33
--- /dev/null
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Header.java
@@ -0,0 +1,69 @@
+/**
+ * Copyright (C) 2014 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.dicttool;
+
+import com.android.inputmethod.latin.BinaryDictionary;
+import com.android.inputmethod.latin.dicttool.BinaryDictOffdeviceUtils.DecoderChainSpec;
+import com.android.inputmethod.latin.makedict.DictionaryHeader;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Locale;
+
+public class Header extends Dicttool.Command {
+ public static final String COMMAND = "header";
+
+ public Header() {
+ }
+
+ @Override
+ public String getHelp() {
+ return COMMAND + " <filename>: prints the header contents of a dictionary file";
+ }
+
+ @Override
+ public void run() throws UnsupportedFormatException {
+ final boolean plumbing;
+ if (mArgs.length > 0 && "-p".equals(mArgs[0])) {
+ plumbing = true;
+ mArgs = Arrays.copyOfRange(mArgs, 1, mArgs.length);
+ } else {
+ plumbing = false;
+ }
+ if (mArgs.length < 1) {
+ throw new RuntimeException("Not enough arguments for command " + COMMAND);
+ }
+ final String filename = mArgs[0];
+ final File dictFile = new File(filename);
+ final DecoderChainSpec<DictionaryHeader> spec =
+ BinaryDictOffdeviceUtils.decodeDictionaryForProcess(dictFile,
+ new BinaryDictOffdeviceUtils.HeaderReaderProcessor());
+ if (null == spec) {
+ throw new UnsupportedFormatException(filename
+ + " doesn't seem to be a valid version 2 dictionary file");
+ }
+
+ final DictionaryHeader header = spec.mResult;
+ System.out.println("Dictionary : " + dictFile.getAbsolutePath());
+ System.out.println("Size : " + dictFile.length() + " bytes");
+ System.out.println("Format : Binary dictionary format");
+ System.out.println("Packaging : " + spec.describeChain());
+ System.out.println("Header attributes :");
+ System.out.print(header.mDictionaryOptions.toString(2 /* indentCount */, plumbing));
+ }
+}
diff --git a/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java b/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java
index 0fb4cf59c..ea9d4cc19 100644
--- a/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java
+++ b/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java
@@ -16,10 +16,17 @@
package com.android.inputmethod.latin.dicttool;
+import com.android.inputmethod.latin.common.CodePointUtils;
+import com.android.inputmethod.latin.dicttool.BinaryDictOffdeviceUtils;
+import com.android.inputmethod.latin.dicttool.Compress;
+import com.android.inputmethod.latin.dicttool.Crypt;
+import com.android.inputmethod.latin.dicttool.BinaryDictOffdeviceUtils.DecoderChainSpec;
import com.android.inputmethod.latin.makedict.BinaryDictIOUtils;
+import com.android.inputmethod.latin.makedict.BinaryDictUtils;
import com.android.inputmethod.latin.makedict.DictDecoder;
import com.android.inputmethod.latin.makedict.DictEncoder;
import com.android.inputmethod.latin.makedict.DictionaryHeader;
+import com.android.inputmethod.latin.makedict.FormatSpec;
import com.android.inputmethod.latin.makedict.FormatSpec.DictionaryOptions;
import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
import com.android.inputmethod.latin.makedict.FusionDictionary;
@@ -35,13 +42,37 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Random;
+import java.util.Set;
/**
* Unit tests for BinaryDictOffdeviceUtils
*/
public class BinaryDictOffdeviceUtilsTests extends TestCase {
private static final int TEST_FREQ = 37; // Some arbitrary value unlikely to happen by chance
+ private static final int CODE_POINT_SET_SIZE = 300;
+ final Random mRandom;
+ private static final ArrayList<String> sWords = new ArrayList<>();
+
+ public BinaryDictOffdeviceUtilsTests(final long seed, final int maxUnigrams) {
+ super();
+ mRandom = new Random(seed);
+ sWords.clear();
+ generateWords(maxUnigrams, mRandom);
+ }
+
+ private static void generateWords(final int maxUnigrams, final Random random) {
+ final int[] codePointSet = CodePointUtils.generateCodePointSet(
+ CODE_POINT_SET_SIZE, random);
+ final Set<String> wordSet = new HashSet<>();
+ while (wordSet.size() < maxUnigrams) {
+ wordSet.add(CodePointUtils.generateWord(random, codePointSet));
+ }
+ sWords.addAll(wordSet);
+ }
public void testGetRawDictWorks() throws IOException, UnsupportedFormatException {
final String VERSION = "1";
@@ -70,7 +101,7 @@ public class BinaryDictOffdeviceUtilsTests extends TestCase {
try (final OutputStream out = Compress.getCompressedStream(
new BufferedOutputStream(new FileOutputStream(dst)))) {
final DictEncoder dictEncoder = new Ver2DictEncoder(out);
- dictEncoder.writeDictionary(dict, new FormatOptions(2, false));
+ dictEncoder.writeDictionary(dict, new FormatOptions(FormatSpec.VERSION202, false));
}
// Test for an actually compressed dictionary and its contents
@@ -119,4 +150,64 @@ public class BinaryDictOffdeviceUtilsTests extends TestCase {
assertNull("Wrongly identified data file",
BinaryDictOffdeviceUtils.getRawDictionaryOrNull(gzDst));
}
+
+ public void runTestHeaderReaderProcessorWithOneSpec(final boolean compress, final boolean crypt)
+ throws IOException, UnsupportedFormatException {
+ final String dictName = "testHeaderReaderProcessor";
+ final String dictVersion = Long.toString(System.currentTimeMillis());
+ final FormatOptions formatOptions = BinaryDictUtils.STATIC_OPTIONS;
+ final int MAX_NUMBER_OF_OPTIONS_TO_ADD = 5;
+ final HashMap<String, String> options = new HashMap<>();
+ // Required attributes
+ options.put("dictionary", "main:en_US");
+ options.put("locale", "en_US");
+ options.put("version", Integer.toString(mRandom.nextInt()));
+ // Add some random options for test
+ final int numberOfOptionsToAdd = mRandom.nextInt() % (MAX_NUMBER_OF_OPTIONS_TO_ADD + 1);
+ for (int i = 0; i < numberOfOptionsToAdd; ++i) {
+ options.put(sWords.get(2 * i), sWords.get(2 * 1 + 1));
+ }
+ final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
+ new DictionaryOptions(options));
+
+ for (int i = 0; i < sWords.size(); ++i) {
+ final String word = sWords.get(i);
+ dict.add(word, new ProbabilityInfo(TEST_FREQ), null /* shortcuts */,
+ false /* isNotAWord */, false /* isPossiblyOffensive */);
+ }
+
+ File file = File.createTempFile(dictName, ".tmp");
+ final DictEncoder dictEncoder = BinaryDictUtils.getDictEncoder(file, formatOptions);
+ dictEncoder.writeDictionary(dict, formatOptions);
+
+ if (compress) {
+ final File rawFile = file;
+ file = File.createTempFile(dictName + ".compress", ".tmp");
+ final Compress.Compressor compressCommand = new Compress.Compressor();
+ compressCommand.setArgs(new String[] { rawFile.getPath(), file.getPath() });
+ compressCommand.run();
+ }
+ if (crypt) {
+ final File rawFile = file;
+ file = File.createTempFile(dictName + ".crypt", ".tmp");
+ final Crypt.Encrypter cryptCommand = new Crypt.Encrypter();
+ cryptCommand.setArgs(new String[] { rawFile.getPath(), file.getPath() });
+ cryptCommand.run();
+ }
+
+ final DecoderChainSpec<DictionaryHeader> spec =
+ BinaryDictOffdeviceUtils.decodeDictionaryForProcess(file,
+ new BinaryDictOffdeviceUtils.HeaderReaderProcessor());
+ assertNotNull("Can't decode a dictionary we just wrote : " + file, spec);
+ final DictionaryHeader header = spec.mResult;
+ assertEquals("raw" + (crypt ? " > encryption" : "") + (compress ? " > compression" : ""),
+ spec.describeChain());
+ assertEquals(header.mDictionaryOptions.mAttributes, options);
+ }
+
+ public void testHeaderReaderProcessor() throws IOException, UnsupportedFormatException {
+ runTestHeaderReaderProcessorWithOneSpec(false /* compress */, false /* crypt */);
+ runTestHeaderReaderProcessorWithOneSpec(true /* compress */, false /* crypt */);
+ runTestHeaderReaderProcessorWithOneSpec(true /* compress */, true /* crypt */);
+ }
}