diff options
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 */); + } } |