diff options
13 files changed, 420 insertions, 153 deletions
diff --git a/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java index 4ea7fb888..ee9125a07 100644 --- a/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java +++ b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java @@ -19,6 +19,7 @@ package com.android.inputmethod.compat; import android.os.Build; import android.view.inputmethod.InputMethodSubtype; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.Constants; import java.lang.reflect.Constructor; @@ -64,7 +65,12 @@ public final class InputMethodSubtypeCompatUtils { } public static boolean isAsciiCapable(final InputMethodSubtype subtype) { - return (Boolean)CompatUtils.invoke(subtype, false, METHOD_isAsciiCapable) + return isAsciiCapableWithAPI(subtype) || subtype.containsExtraValueKey(Constants.Subtype.ExtraValue.ASCII_CAPABLE); } + + @UsedForTesting + public static boolean isAsciiCapableWithAPI(final InputMethodSubtype subtype) { + return (Boolean)CompatUtils.invoke(subtype, false, METHOD_isAsciiCapable); + } } diff --git a/java/src/com/android/inputmethod/compat/LocaleSpanCompatUtils.java b/java/src/com/android/inputmethod/compat/LocaleSpanCompatUtils.java new file mode 100644 index 000000000..967645878 --- /dev/null +++ b/java/src/com/android/inputmethod/compat/LocaleSpanCompatUtils.java @@ -0,0 +1,59 @@ +/* + * 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.compat; + +import com.android.inputmethod.annotations.UsedForTesting; +import com.android.inputmethod.compat.CompatUtils; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.Locale; + +@UsedForTesting +public final class LocaleSpanCompatUtils { + // Note that LocaleSpan(Locale locale) has been introduced in API level 17 + // (Build.VERSION_CODE.JELLY_BEAN_MR1). + private static Class<?> getLocalSpanClass() { + try { + return Class.forName("android.text.style.LocaleSpan"); + } catch (ClassNotFoundException e) { + return null; + } + } + private static final Constructor<?> LOCALE_SPAN_CONSTRUCTOR; + private static final Method LOCALE_SPAN_GET_LOCALE; + static { + final Class<?> localeSpanClass = getLocalSpanClass(); + LOCALE_SPAN_CONSTRUCTOR = CompatUtils.getConstructor(localeSpanClass, Locale.class); + LOCALE_SPAN_GET_LOCALE = CompatUtils.getMethod(localeSpanClass, "getLocale"); + } + + @UsedForTesting + public static boolean isLocaleSpanAvailable() { + return (LOCALE_SPAN_CONSTRUCTOR != null && LOCALE_SPAN_GET_LOCALE != null); + } + + @UsedForTesting + public static Object newLocaleSpan(final Locale locale) { + return CompatUtils.newInstance(LOCALE_SPAN_CONSTRUCTOR, locale); + } + + @UsedForTesting + public static Locale getLocaleFromLocaleSpan(final Object localeSpan) { + return (Locale) CompatUtils.invoke(localeSpan, null, LOCALE_SPAN_GET_LOCALE); + } +} diff --git a/java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java b/java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java index 31fa86774..ad411f9ee 100644 --- a/java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java +++ b/java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java @@ -150,8 +150,9 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment { // TODO: Should filter out already existing combinations of locale and layout. for (final String layout : SubtypeLocaleUtils.getPredefinedKeyboardLayoutSet()) { // This is a dummy subtype with NO_LANGUAGE, only for display. - final InputMethodSubtype subtype = AdditionalSubtypeUtils.createAdditionalSubtype( - SubtypeLocaleUtils.NO_LANGUAGE, layout, null); + final InputMethodSubtype subtype = + AdditionalSubtypeUtils.createDummyAdditionalSubtype( + SubtypeLocaleUtils.NO_LANGUAGE, layout); add(new KeyboardLayoutSetItem(subtype)); } } @@ -286,8 +287,9 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment { (SubtypeLocaleItem) mSubtypeLocaleSpinner.getSelectedItem(); final KeyboardLayoutSetItem layout = (KeyboardLayoutSetItem) mKeyboardLayoutSetSpinner.getSelectedItem(); - final InputMethodSubtype subtype = AdditionalSubtypeUtils.createAdditionalSubtype( - locale.first, layout.first, Constants.Subtype.ExtraValue.ASCII_CAPABLE); + final InputMethodSubtype subtype = + AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + locale.first, layout.first); setSubtype(subtype); notifyChanged(); if (isEditing) { diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java index b57eab31b..90c8f618f 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java @@ -374,8 +374,8 @@ public final class AndroidSpellCheckerService extends SpellCheckerService public DictAndKeyboard createDictAndKeyboard(final Locale locale) { final int script = ScriptUtils.getScriptFromSpellCheckerLocale(locale); final String keyboardLayoutName = getKeyboardLayoutNameForScript(script); - final InputMethodSubtype subtype = AdditionalSubtypeUtils.createAdditionalSubtype( - locale.toString(), keyboardLayoutName, null); + final InputMethodSubtype subtype = AdditionalSubtypeUtils.createDummyAdditionalSubtype( + locale.toString(), keyboardLayoutName); final KeyboardLayoutSet keyboardLayoutSet = createKeyboardSetForSpellChecker(subtype); final DictionaryCollection dictionaryCollection = diff --git a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java index 3ca7c7e1c..db7f2a56c 100644 --- a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java @@ -17,6 +17,7 @@ package com.android.inputmethod.latin.utils; import static com.android.inputmethod.latin.Constants.Subtype.KEYBOARD_MODE; +import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.ASCII_CAPABLE; import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.EMOJI_CAPABLE; import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.IS_ADDITIONAL_SUBTYPE; import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET; @@ -56,25 +57,33 @@ public final class AdditionalSubtypeUtils { private static final int LENGTH_WITH_EXTRA_VALUE = (INDEX_OF_EXTRA_VALUE + 1); private static final String PREF_SUBTYPE_SEPARATOR = ";"; - public static InputMethodSubtype createAdditionalSubtype(final String localeString, - final String keyboardLayoutSetName, final String extraValue) { - final String layoutExtraValue = KEYBOARD_LAYOUT_SET + "=" + keyboardLayoutSetName; - final String layoutDisplayNameExtraValue; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN - && SubtypeLocaleUtils.isExceptionalLocale(localeString)) { - final String layoutDisplayName = SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName( - keyboardLayoutSetName); - layoutDisplayNameExtraValue = StringUtils.appendToCommaSplittableTextIfNotExists( - UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME + "=" + layoutDisplayName, extraValue); - } else { - layoutDisplayNameExtraValue = extraValue; - } - final String additionalSubtypeExtraValue = - StringUtils.appendToCommaSplittableTextIfNotExists( - IS_ADDITIONAL_SUBTYPE, layoutDisplayNameExtraValue); + private static InputMethodSubtype createAdditionalSubtypeInternal( + final String localeString, final String keyboardLayoutSetName, + final boolean isAsciiCapable, final boolean isEmojiCapable) { final int nameId = SubtypeLocaleUtils.getSubtypeNameId(localeString, keyboardLayoutSetName); - return buildInputMethodSubtype( - nameId, localeString, layoutExtraValue, additionalSubtypeExtraValue); + final String platformVersionDependentExtraValues = getPlatformVersionDependentExtraValue( + localeString, keyboardLayoutSetName, isAsciiCapable, isEmojiCapable); + final int platformVersionIndependentSubtypeId = + getPlatformVersionIndependentSubtypeId(localeString, keyboardLayoutSetName); + // NOTE: In KitKat and later, InputMethodSubtypeBuilder#setIsAsciiCapable is also available. + // TODO: Use InputMethodSubtypeBuilder#setIsAsciiCapable when appropriate. + return InputMethodSubtypeCompatUtils.newInputMethodSubtype(nameId, + R.drawable.ic_ime_switcher_dark, localeString, KEYBOARD_MODE, + platformVersionDependentExtraValues, + false /* isAuxiliary */, false /* overrideImplicitlyEnabledSubtype */, + platformVersionIndependentSubtypeId); + } + + public static InputMethodSubtype createDummyAdditionalSubtype( + final String localeString, final String keyboardLayoutSetName) { + return createAdditionalSubtypeInternal(localeString, keyboardLayoutSetName, + false /* isAsciiCapable */, false /* isEmojiCapable */); + } + + public static InputMethodSubtype createAsciiEmojiCapableAdditionalSubtype( + final String localeString, final String keyboardLayoutSetName) { + return createAdditionalSubtypeInternal(localeString, keyboardLayoutSetName, + true /* isAsciiCapable */, true /* isEmojiCapable */); } public static String getPrefSubtype(final InputMethodSubtype subtype) { @@ -106,10 +115,10 @@ public final class AdditionalSubtypeUtils { } final String localeString = elems[INDEX_OF_LOCALE]; final String keyboardLayoutSetName = elems[INDEX_OF_KEYBOARD_LAYOUT]; - final String extraValue = (elems.length == LENGTH_WITH_EXTRA_VALUE) - ? elems[INDEX_OF_EXTRA_VALUE] : null; - final InputMethodSubtype subtype = createAdditionalSubtype( - localeString, keyboardLayoutSetName, extraValue); + // Here we assume that all the additional subtypes have AsciiCapable and EmojiCapable. + // This is actually what the setting dialog for additional subtype is doing. + final InputMethodSubtype subtype = createAsciiEmojiCapableAdditionalSubtype( + localeString, keyboardLayoutSetName); if (subtype.getNameResId() == SubtypeLocaleUtils.UNKNOWN_KEYBOARD_LAYOUT) { // Skip unknown keyboard layout subtype. This may happen when predefined keyboard // layout has been removed. @@ -148,35 +157,80 @@ public final class AdditionalSubtypeUtils { return sb.toString(); } - private static InputMethodSubtype buildInputMethodSubtype(final int nameId, - final String localeString, final String layoutExtraValue, - final String additionalSubtypeExtraValue) { - // To preserve additional subtype settings and user's selection across OS updates, subtype - // id shouldn't be changed. New attributes, such as emojiCapable, are carefully excluded - // from the calculation of subtype id. - final String compatibleExtraValue = StringUtils.joinCommaSplittableText( - layoutExtraValue, additionalSubtypeExtraValue); - final int compatibleSubtypeId = getInputMethodSubtypeId(localeString, compatibleExtraValue); - final String extraValue; - // Color Emoji is supported from KitKat. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - extraValue = StringUtils.appendToCommaSplittableTextIfNotExists( - EMOJI_CAPABLE, compatibleExtraValue); - } else { - extraValue = compatibleExtraValue; + /** + * Returns the extra value that is optimized for the running OS. + * <p> + * Historically the extra value has been used as the last resort to annotate various kinds of + * attributes. Some of these attributes are valid only on some platform versions. Thus we cannot + * assume that the extra values stored in a persistent storage are always valid. We need to + * regenerate the extra value on the fly instead. + * </p> + * @param localeString the locale string (e.g., "en_US"). + * @param keyboardLayoutSetName the keyboard layout set name (e.g., "dvorak"). + * @param isAsciiCapable true when ASCII characters are supported with this layout. + * @param isEmojiCapable true when Unicode Emoji characters are supported with this layout. + * @return extra value that is optimized for the running OS. + * @see #getPlatformVersionIndependentSubtypeId(String, String) + */ + private static String getPlatformVersionDependentExtraValue(final String localeString, + final String keyboardLayoutSetName, final boolean isAsciiCapable, + final boolean isEmojiCapable) { + final ArrayList<String> extraValueItems = new ArrayList<>(); + extraValueItems.add(KEYBOARD_LAYOUT_SET + "=" + keyboardLayoutSetName); + if (isAsciiCapable) { + extraValueItems.add(ASCII_CAPABLE); } - return InputMethodSubtypeCompatUtils.newInputMethodSubtype(nameId, - R.drawable.ic_ime_switcher_dark, localeString, KEYBOARD_MODE, extraValue, - false, false, compatibleSubtypeId); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && + SubtypeLocaleUtils.isExceptionalLocale(localeString)) { + extraValueItems.add(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME + "=" + + SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(keyboardLayoutSetName)); + } + if (isEmojiCapable && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + extraValueItems.add(EMOJI_CAPABLE); + } + extraValueItems.add(IS_ADDITIONAL_SUBTYPE); + return TextUtils.join(",", extraValueItems); } - private static int getInputMethodSubtypeId(final String localeString, final String extraValue) { - // From the compatibility point of view, the calculation of subtype id has been copied from - // {@link InputMethodSubtype} of JellyBean MR2. + /** + * Returns the subtype ID that is supposed to be compatible between different version of OSes. + * <p> + * From the compatibility point of view, it is important to keep subtype id predictable and + * stable between different OSes. For this purpose, the calculation code in this method is + * carefully chosen and then fixed. Treat the following code as no more or less than a + * hash function. Each component to be hashed can be different from the corresponding value + * that is used to instantiate {@link InputMethodSubtype} actually. + * For example, you don't need to update <code>compatibilityExtraValueItems</code> in this + * method even when we need to add some new extra values for the actual instance of + * {@link InputMethodSubtype}. + * </p> + * @param localeString the locale string (e.g., "en_US"). + * @param keyboardLayoutSetName the keyboard layout set name (e.g., "dvorak"). + * @return a platform-version independent subtype ID. + * @see #getPlatformVersionDependentExtraValue(String, String, boolean, boolean) + */ + private static int getPlatformVersionIndependentSubtypeId(final String localeString, + final String keyboardLayoutSetName) { + // For compatibility reasons, we concatenate the extra values in the following order. + // - KeyboardLayoutSet + // - AsciiCapable + // - UntranslatableReplacementStringInSubtypeName + // - EmojiCapable + // - isAdditionalSubtype + final ArrayList<String> compatibilityExtraValueItems = new ArrayList<>(); + compatibilityExtraValueItems.add(KEYBOARD_LAYOUT_SET + "=" + keyboardLayoutSetName); + compatibilityExtraValueItems.add(ASCII_CAPABLE); + if (SubtypeLocaleUtils.isExceptionalLocale(localeString)) { + compatibilityExtraValueItems.add(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME + "=" + + SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(keyboardLayoutSetName)); + } + compatibilityExtraValueItems.add(EMOJI_CAPABLE); + compatibilityExtraValueItems.add(IS_ADDITIONAL_SUBTYPE); + final String compatibilityExtraValues = TextUtils.join(",", compatibilityExtraValueItems); return Arrays.hashCode(new Object[] { localeString, KEYBOARD_MODE, - extraValue, + compatibilityExtraValues, false /* isAuxiliary */, false /* overrideImplicitlyEnabledSubtype */ }); } diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java index e4237a7f2..ceb038371 100644 --- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/StringUtils.java @@ -75,31 +75,6 @@ public final class StringUtils { return containsInArray(text, extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT)); } - public static String joinCommaSplittableText(final String head, final String tail) { - if (TextUtils.isEmpty(head) && TextUtils.isEmpty(tail)) { - return EMPTY_STRING; - } - // Here either head or tail is not null. - if (TextUtils.isEmpty(head)) { - return tail; - } - if (TextUtils.isEmpty(tail)) { - return head; - } - return head + SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT + tail; - } - - public static String appendToCommaSplittableTextIfNotExists(final String text, - final String extraValues) { - if (TextUtils.isEmpty(extraValues)) { - return text; - } - if (containsInCommaSplittableText(text, extraValues)) { - return extraValues; - } - return extraValues + SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT + text; - } - public static String removeFromCommaSplittableTextIfExists(final String text, final String extraValues) { if (TextUtils.isEmpty(extraValues)) { diff --git a/tests/src/com/android/inputmethod/compat/LocaleSpanCompatUtilsTests.java b/tests/src/com/android/inputmethod/compat/LocaleSpanCompatUtilsTests.java new file mode 100644 index 000000000..a920373d4 --- /dev/null +++ b/tests/src/com/android/inputmethod/compat/LocaleSpanCompatUtilsTests.java @@ -0,0 +1,38 @@ +/* + * 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.compat; + +import android.os.Build; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import java.util.Locale; + +@SmallTest +public class LocaleSpanCompatUtilsTests extends AndroidTestCase { + public void testInstantiatable() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { + // LocaleSpan isn't yet available. + return; + } + assertTrue(LocaleSpanCompatUtils.isLocaleSpanAvailable()); + final Object japaneseLocaleSpan = LocaleSpanCompatUtils.newLocaleSpan(Locale.JAPANESE); + assertNotNull(japaneseLocaleSpan); + assertEquals(Locale.JAPANESE, + LocaleSpanCompatUtils.getLocaleFromLocaleSpan(japaneseLocaleSpan)); + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java index ab7d1b28d..cf884bfea 100644 --- a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java +++ b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java @@ -104,8 +104,8 @@ public abstract class KeyboardLayoutSetTestsBase extends AndroidTestCase { final Locale subtypeLocale = SubtypeLocaleUtils.getSubtypeLocale(subtype); if (locale.equals(subtypeLocale)) { // Create additional subtype. - return AdditionalSubtypeUtils.createAdditionalSubtype( - locale.toString(), keyboardLayout, null /* extraValue */); + return AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + locale.toString(), keyboardLayout); } } throw new RuntimeException( diff --git a/tests/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelperTests.java b/tests/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelperTests.java index 0be1e374c..6ea27588e 100644 --- a/tests/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelperTests.java +++ b/tests/src/com/android/inputmethod/keyboard/internal/LanguageOnSpacebarHelperTests.java @@ -67,10 +67,10 @@ public class LanguageOnSpacebarHelperTests extends AndroidTestCase { Locale.CANADA_FRENCH.toString(), "qwerty"); FR_CH_SWISS = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( "fr_CH", "swiss"); - FR_CH_QWERTZ = AdditionalSubtypeUtils.createAdditionalSubtype( - "fr_CH", "qwertz", null); - FR_CH_QWERTY = AdditionalSubtypeUtils.createAdditionalSubtype( - "fr_CH", "qwerty", null); + FR_CH_QWERTZ = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + "fr_CH", "qwertz"); + FR_CH_QWERTY = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + "fr_CH", "qwerty"); ZZ_QWERTY = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( SubtypeLocaleUtils.NO_LANGUAGE, "qwerty"); } diff --git a/tests/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtilsTests.java new file mode 100644 index 000000000..91c9c3775 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtilsTests.java @@ -0,0 +1,175 @@ +/* + * 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.utils; + +import android.content.Context; +import android.os.Build; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; +import android.view.inputmethod.InputMethodSubtype; + +import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils; + +import java.util.Locale; + +import static com.android.inputmethod.latin.Constants.Subtype.KEYBOARD_MODE; +import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.ASCII_CAPABLE; +import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.EMOJI_CAPABLE; +import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.IS_ADDITIONAL_SUBTYPE; +import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET; +import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue + .UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME; + +@SmallTest +public class AdditionalSubtypeUtilsTests extends AndroidTestCase { + + /** + * Predictable subtype ID for en_US dvorak layout. This is actually a hash code calculated as + * follows. + * <code> + * final boolean isAuxiliary = false; + * final boolean overrideImplicitlyEnabledSubtype = false; + * final int SUBTYPE_ID_EN_US_DVORAK = Arrays.hashCode(new Object[] { + * "en_US", + * "keyboard", + * "KeyboardLayoutSet=dvorak" + * + ",AsciiCapable" + * + ",UntranslatableReplacementStringInSubtypeName=Dvorak" + * + ",EmojiCapable" + * + ",isAdditionalSubtype", + * isAuxiliary, + * overrideImplicitlyEnabledSubtype }); + * </code> + */ + private static int SUBTYPE_ID_EN_US_DVORAK = 0xb3c0cc56; + private static String EXTRA_VALUE_EN_US_DVORAK_ICS = + "KeyboardLayoutSet=dvorak" + + ",AsciiCapable" + + ",isAdditionalSubtype"; + private static String EXTRA_VALUE_EN_US_DVORAK_JELLY_BEAN = + "KeyboardLayoutSet=dvorak" + + ",AsciiCapable" + + ",UntranslatableReplacementStringInSubtypeName=Dvorak" + + ",isAdditionalSubtype"; + private static String EXTRA_VALUE_EN_US_DVORAK_KITKAT = + "KeyboardLayoutSet=dvorak" + + ",AsciiCapable" + + ",UntranslatableReplacementStringInSubtypeName=Dvorak" + + ",EmojiCapable" + + ",isAdditionalSubtype"; + + /** + * Predictable subtype ID for azerty layout. This is actually a hash code calculated as follows. + * <code> + * final boolean isAuxiliary = false; + * final boolean overrideImplicitlyEnabledSubtype = false; + * final int SUBTYPE_ID_ZZ_AZERTY = Arrays.hashCode(new Object[] { + * "zz", + * "keyboard", + * "KeyboardLayoutSet=azerty" + * + ",AsciiCapable" + * + ",EmojiCapable" + * + ",isAdditionalSubtype", + * isAuxiliary, + * overrideImplicitlyEnabledSubtype }); + * </code> + */ + private static int SUBTYPE_ID_ZZ_AZERTY = 0x5b6be697; + private static String EXTRA_VALUE_ZZ_AZERTY_ICS = + "KeyboardLayoutSet=azerty" + + ",AsciiCapable" + + ",isAdditionalSubtype"; + private static String EXTRA_VALUE_ZZ_AZERTY_KITKAT = + "KeyboardLayoutSet=azerty" + + ",AsciiCapable" + + ",EmojiCapable" + + ",isAdditionalSubtype"; + + @Override + protected void setUp() throws Exception { + super.setUp(); + final Context context = getContext(); + SubtypeLocaleUtils.init(context); + } + + private static void assertEnUsDvorak(InputMethodSubtype subtype) { + assertEquals("en_US", subtype.getLocale()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + assertEquals(EXTRA_VALUE_EN_US_DVORAK_KITKAT, subtype.getExtraValue()); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + assertEquals(EXTRA_VALUE_EN_US_DVORAK_JELLY_BEAN, subtype.getExtraValue()); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + assertEquals(EXTRA_VALUE_EN_US_DVORAK_ICS, subtype.getExtraValue()); + } + assertTrue(subtype.containsExtraValueKey(ASCII_CAPABLE)); + assertTrue(InputMethodSubtypeCompatUtils.isAsciiCapable(subtype)); + // TODO: Enable following test + // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + // assertTrue(InputMethodSubtypeCompatUtils.isAsciiCapableWithAPI(subtype)); + // } + assertTrue(subtype.containsExtraValueKey(EMOJI_CAPABLE)); + assertTrue(subtype.containsExtraValueKey(IS_ADDITIONAL_SUBTYPE)); + assertEquals("dvorak", subtype.getExtraValueOf(KEYBOARD_LAYOUT_SET)); + assertEquals("Dvorak", subtype.getExtraValueOf(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)); + assertEquals(KEYBOARD_MODE, subtype.getMode()); + assertEquals(SUBTYPE_ID_EN_US_DVORAK, subtype.hashCode()); + } + + private static void assertAzerty(InputMethodSubtype subtype) { + assertEquals("zz", subtype.getLocale()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + assertEquals(EXTRA_VALUE_ZZ_AZERTY_KITKAT, subtype.getExtraValue()); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + assertEquals(EXTRA_VALUE_ZZ_AZERTY_ICS, subtype.getExtraValue()); + } + assertTrue(subtype.containsExtraValueKey(ASCII_CAPABLE)); + assertTrue(InputMethodSubtypeCompatUtils.isAsciiCapable(subtype)); + // TODO: Enable following test + // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + // assertTrue(InputMethodSubtypeCompatUtils.isAsciiCapableWithAPI(subtype)); + // } + assertTrue(subtype.containsExtraValueKey(EMOJI_CAPABLE)); + assertTrue(subtype.containsExtraValueKey(IS_ADDITIONAL_SUBTYPE)); + assertEquals("azerty", subtype.getExtraValueOf(KEYBOARD_LAYOUT_SET)); + assertFalse(subtype.containsExtraValueKey(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)); + assertEquals(KEYBOARD_MODE, subtype.getMode()); + assertEquals(SUBTYPE_ID_ZZ_AZERTY, subtype.hashCode()); + } + + public void testRestorable() { + final InputMethodSubtype EN_UK_DVORAK = + AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + Locale.US.toString(), "dvorak"); + final InputMethodSubtype ZZ_AZERTY = + AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + SubtypeLocaleUtils.NO_LANGUAGE, "azerty"); + assertEnUsDvorak(EN_UK_DVORAK); + assertAzerty(ZZ_AZERTY); + + // Make sure the subtype can be stored and restored in a deterministic manner. + final InputMethodSubtype[] subtypes = { EN_UK_DVORAK, ZZ_AZERTY }; + final String prefSubtype = AdditionalSubtypeUtils.createPrefSubtypes(subtypes); + final InputMethodSubtype[] restoredSubtypes = + AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefSubtype); + assertEquals(2, restoredSubtypes.length); + final InputMethodSubtype restored_EN_UK_DVORAK = restoredSubtypes[0]; + final InputMethodSubtype restored_ZZ_AZERTY = restoredSubtypes[1]; + + assertEnUsDvorak(restored_EN_UK_DVORAK); + assertAzerty(restored_ZZ_AZERTY); + } +} diff --git a/tests/src/com/android/inputmethod/latin/utils/SpacebarLanguagetUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/SpacebarLanguagetUtilsTests.java index 4156de78b..fdde34251 100644 --- a/tests/src/com/android/inputmethod/latin/utils/SpacebarLanguagetUtilsTests.java +++ b/tests/src/com/android/inputmethod/latin/utils/SpacebarLanguagetUtilsTests.java @@ -87,20 +87,20 @@ public class SpacebarLanguagetUtilsTests extends AndroidTestCase { "de_CH", "swiss"); ZZ = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( SubtypeLocaleUtils.NO_LANGUAGE, "qwerty"); - DE_QWERTY = AdditionalSubtypeUtils.createAdditionalSubtype( - Locale.GERMAN.toString(), "qwerty", null); - FR_QWERTZ = AdditionalSubtypeUtils.createAdditionalSubtype( - Locale.FRENCH.toString(), "qwertz", null); - EN_US_AZERTY = AdditionalSubtypeUtils.createAdditionalSubtype( - Locale.US.toString(), "azerty", null); - EN_UK_DVORAK = AdditionalSubtypeUtils.createAdditionalSubtype( - Locale.UK.toString(), "dvorak", null); - ES_US_COLEMAK = AdditionalSubtypeUtils.createAdditionalSubtype( - "es_US", "colemak", null); - ZZ_AZERTY = AdditionalSubtypeUtils.createAdditionalSubtype( - SubtypeLocaleUtils.NO_LANGUAGE, "azerty", null); - ZZ_PC = AdditionalSubtypeUtils.createAdditionalSubtype( - SubtypeLocaleUtils.NO_LANGUAGE, "pcqwerty", null); + DE_QWERTY = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + Locale.GERMAN.toString(), "qwerty"); + FR_QWERTZ = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + Locale.FRENCH.toString(), "qwertz"); + EN_US_AZERTY = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + Locale.US.toString(), "azerty"); + EN_UK_DVORAK = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + Locale.UK.toString(), "dvorak"); + ES_US_COLEMAK = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + "es_US", "colemak"); + ZZ_AZERTY = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + SubtypeLocaleUtils.NO_LANGUAGE, "azerty"); + ZZ_PC = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + SubtypeLocaleUtils.NO_LANGUAGE, "pcqwerty"); } public void testAllFullDisplayNameForSpacebar() { diff --git a/tests/src/com/android/inputmethod/latin/utils/StringAndJsonUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/StringAndJsonUtilsTests.java index cd9a98356..4448a6baf 100644 --- a/tests/src/com/android/inputmethod/latin/utils/StringAndJsonUtilsTests.java +++ b/tests/src/com/android/inputmethod/latin/utils/StringAndJsonUtilsTests.java @@ -56,48 +56,6 @@ public class StringAndJsonUtilsTests extends AndroidTestCase { assertTrue("in 2 elements", StringUtils.containsInCommaSplittableText("key", "key1,key")); } - public void testJoinCommaSplittableText() { - assertEquals("2 nulls", "", - StringUtils.joinCommaSplittableText(null, null)); - assertEquals("null and empty", "", - StringUtils.joinCommaSplittableText(null, "")); - assertEquals("empty and null", "", - StringUtils.joinCommaSplittableText("", null)); - assertEquals("2 empties", "", - StringUtils.joinCommaSplittableText("", "")); - assertEquals("text and null", "text", - StringUtils.joinCommaSplittableText("text", null)); - assertEquals("text and empty", "text", - StringUtils.joinCommaSplittableText("text", "")); - assertEquals("null and text", "text", - StringUtils.joinCommaSplittableText(null, "text")); - assertEquals("empty and text", "text", - StringUtils.joinCommaSplittableText("", "text")); - assertEquals("2 texts", "text1,text2", - StringUtils.joinCommaSplittableText("text1", "text2")); - } - - public void testAppendToCommaSplittableTextIfNotExists() { - assertEquals("null", "key", - StringUtils.appendToCommaSplittableTextIfNotExists("key", null)); - assertEquals("empty", "key", - StringUtils.appendToCommaSplittableTextIfNotExists("key", "")); - - assertEquals("not in 1 element", "key1,key", - StringUtils.appendToCommaSplittableTextIfNotExists("key", "key1")); - assertEquals("not in 2 elements", "key1,key2,key", - StringUtils.appendToCommaSplittableTextIfNotExists("key", "key1,key2")); - - assertEquals("in 1 element", "key", - StringUtils.appendToCommaSplittableTextIfNotExists("key", "key")); - assertEquals("in 2 elements at position 1", "key,key2", - StringUtils.appendToCommaSplittableTextIfNotExists("key", "key,key2")); - assertEquals("in 2 elements at position 2", "key1,key", - StringUtils.appendToCommaSplittableTextIfNotExists("key", "key1,key")); - assertEquals("in 3 elements at position 2", "key1,key,key3", - StringUtils.appendToCommaSplittableTextIfNotExists("key", "key1,key,key3")); - } - public void testRemoveFromCommaSplittableTextIfExists() { assertEquals("null", "", StringUtils.removeFromCommaSplittableTextIfExists("key", null)); assertEquals("empty", "", StringUtils.removeFromCommaSplittableTextIfExists("key", "")); diff --git a/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java index 8e409ab99..ce3df7dd6 100644 --- a/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java +++ b/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java @@ -87,20 +87,20 @@ public class SubtypeLocaleUtilsTests extends AndroidTestCase { "de_CH", "swiss"); ZZ = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( SubtypeLocaleUtils.NO_LANGUAGE, "qwerty"); - DE_QWERTY = AdditionalSubtypeUtils.createAdditionalSubtype( - Locale.GERMAN.toString(), "qwerty", null); - FR_QWERTZ = AdditionalSubtypeUtils.createAdditionalSubtype( - Locale.FRENCH.toString(), "qwertz", null); - EN_US_AZERTY = AdditionalSubtypeUtils.createAdditionalSubtype( - Locale.US.toString(), "azerty", null); - EN_UK_DVORAK = AdditionalSubtypeUtils.createAdditionalSubtype( - Locale.UK.toString(), "dvorak", null); - ES_US_COLEMAK = AdditionalSubtypeUtils.createAdditionalSubtype( - "es_US", "colemak", null); - ZZ_AZERTY = AdditionalSubtypeUtils.createAdditionalSubtype( - SubtypeLocaleUtils.NO_LANGUAGE, "azerty", null); - ZZ_PC = AdditionalSubtypeUtils.createAdditionalSubtype( - SubtypeLocaleUtils.NO_LANGUAGE, "pcqwerty", null); + DE_QWERTY = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + Locale.GERMAN.toString(), "qwerty"); + FR_QWERTZ = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + Locale.FRENCH.toString(), "qwertz"); + EN_US_AZERTY = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + Locale.US.toString(), "azerty"); + EN_UK_DVORAK = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + Locale.UK.toString(), "dvorak"); + ES_US_COLEMAK = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + "es_US", "colemak"); + ZZ_AZERTY = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + SubtypeLocaleUtils.NO_LANGUAGE, "azerty"); + ZZ_PC = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( + SubtypeLocaleUtils.NO_LANGUAGE, "pcqwerty"); } public void testAllFullDisplayName() { |