diff --git a/java/res/values/predefined-subtypes.xml b/java/res/values/predefined-subtypes.xml index e67fee53f..602f53eac 100644 --- a/java/res/values/predefined-subtypes.xml +++ b/java/res/values/predefined-subtypes.xml @@ -18,6 +18,6 @@ */ --> - - de:qwerty,fr:qwertz + + de:qwerty:AsciiCapable;fr:qwertz:AsciiCapable diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java index 35209e0ad..07f71ee17 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java @@ -29,11 +29,11 @@ import android.view.inputmethod.InputMethodSubtype; import com.android.inputmethod.compat.EditorInfoCompatUtils; import com.android.inputmethod.keyboard.KeyboardLayoutSet.Params.ElementParams; +import com.android.inputmethod.latin.InputAttributes; import com.android.inputmethod.latin.InputTypeUtils; import com.android.inputmethod.latin.LatinIME; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.StringUtils; import com.android.inputmethod.latin.SubtypeLocale; import com.android.inputmethod.latin.SubtypeSwitcher; import com.android.inputmethod.latin.XmlParseUtils; @@ -229,7 +229,7 @@ public class KeyboardLayoutSet { params.mMode = getKeyboardMode(editorInfo); params.mEditorInfo = (editorInfo != null) ? editorInfo : EMPTY_EDITOR_INFO; - params.mNoSettingsKey = StringUtils.inPrivateImeOptions( + params.mNoSettingsKey = InputAttributes.inPrivateImeOptions( mPackageName, LatinIME.IME_OPTION_NO_SETTINGS_KEY, mEditorInfo); } @@ -242,7 +242,7 @@ public class KeyboardLayoutSet { public Builder setSubtype(InputMethodSubtype subtype) { final boolean asciiCapable = subtype.containsExtraValueKey( LatinIME.SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE); - final boolean deprecatedForceAscii = StringUtils.inPrivateImeOptions( + final boolean deprecatedForceAscii = InputAttributes.inPrivateImeOptions( mPackageName, LatinIME.IME_OPTION_FORCE_ASCII, mEditorInfo); final boolean forceAscii = EditorInfoCompatUtils.hasFlagForceAscii( mParams.mEditorInfo.imeOptions) @@ -259,9 +259,9 @@ public class KeyboardLayoutSet { public Builder setOptions(boolean voiceKeyEnabled, boolean voiceKeyOnMain, boolean languageSwitchKeyEnabled) { @SuppressWarnings("deprecation") - final boolean deprecatedNoMicrophone = StringUtils.inPrivateImeOptions( + final boolean deprecatedNoMicrophone = InputAttributes.inPrivateImeOptions( null, LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, mEditorInfo); - final boolean noMicrophone = StringUtils.inPrivateImeOptions( + final boolean noMicrophone = InputAttributes.inPrivateImeOptions( mPackageName, LatinIME.IME_OPTION_NO_MICROPHONE, mEditorInfo) || deprecatedNoMicrophone; mParams.mVoiceKeyEnabled = voiceKeyEnabled && !noMicrophone; diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java index b24f06472..323f74ab9 100644 --- a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java +++ b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java @@ -43,28 +43,35 @@ public class AdditionalSubtype { } public static InputMethodSubtype createAdditionalSubtype( - String localeString, String keyboardLayoutSet) { - final String extraValue = String.format( - "%s=%s,%s", LatinIME.SUBTYPE_EXTRA_VALUE_KEYBOARD_LAYOUT_SET, keyboardLayoutSet, - SUBTYPE_EXTRA_VALUE_IS_ADDITIONAL_SUBTYPE); - Integer nameId = sKeyboardLayoutToNameIdsMap.get(keyboardLayoutSet); + String localeString, String keyboardLayoutSetName, String extraValue) { + final String layoutExtraValue = LatinIME.SUBTYPE_EXTRA_VALUE_KEYBOARD_LAYOUT_SET + "=" + + keyboardLayoutSetName; + final String filteredExtraValue = StringUtils.appendToCsvIfNotExists( + SUBTYPE_EXTRA_VALUE_IS_ADDITIONAL_SUBTYPE, + StringUtils.removeFromCsvIfExists(layoutExtraValue, extraValue)); + Integer nameId = sKeyboardLayoutToNameIdsMap.get(keyboardLayoutSetName); if (nameId == null) nameId = R.string.subtype_generic; return new InputMethodSubtype(nameId, R.drawable.ic_subtype_keyboard, - localeString, SUBTYPE_MODE_KEYBOARD, extraValue, false, false); + localeString, SUBTYPE_MODE_KEYBOARD, filteredExtraValue, false, false); } private static final String LOCALE_AND_LAYOUT_SEPARATOR = ":"; - private static final String SUBTYPE_SEPARATOR = ","; + private static final String PREF_SUBTYPE_SEPARATOR = ";"; - public static InputMethodSubtype[] createAdditionalSubtypesArray(String csvSubtypes) { - final String[] subtypeSpecs = csvSubtypes.split(SUBTYPE_SEPARATOR); - final InputMethodSubtype[] subtypesArray = new InputMethodSubtype[subtypeSpecs.length]; - for (int i = 0; i < subtypeSpecs.length; i++) { - final String elems[] = subtypeSpecs[i].split(LOCALE_AND_LAYOUT_SEPARATOR); + public static InputMethodSubtype[] createAdditionalSubtypesArray(String prefSubtypes) { + final String[] prefSubtypeArray = prefSubtypes.split(PREF_SUBTYPE_SEPARATOR); + final InputMethodSubtype[] subtypesArray = new InputMethodSubtype[prefSubtypeArray.length]; + for (int i = 0; i < prefSubtypeArray.length; i++) { + final String prefSubtype = prefSubtypeArray[i]; + final String elems[] = prefSubtype.split(LOCALE_AND_LAYOUT_SEPARATOR); + if (elems.length < 2 || elems.length > 3) { + throw new RuntimeException("Unknown subtype found in preference: " + prefSubtype); + } final String localeString = elems[0]; final String keyboardLayoutSetName = elems[1]; + final String extraValue = (elems.length == 3) ? elems[2] : null; subtypesArray[i] = AdditionalSubtype.createAdditionalSubtype( - localeString, keyboardLayoutSetName); + localeString, keyboardLayoutSetName, extraValue); } return subtypesArray; } diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java index a6ce04069..9c32f947c 100644 --- a/java/src/com/android/inputmethod/latin/InputAttributes.java +++ b/java/src/com/android/inputmethod/latin/InputAttributes.java @@ -162,4 +162,12 @@ public class InputAttributes { + "\n mIsSettingsSuggestionStripOn = " + mIsSettingsSuggestionStripOn + "\n mApplicationSpecifiedCompletionOn = " + mApplicationSpecifiedCompletionOn; } + + public static boolean inPrivateImeOptions(String packageName, String key, + EditorInfo editorInfo) { + if (editorInfo == null) return false; + final String findingKey = (packageName != null) ? packageName + "." + key + : key; + return StringUtils.containsInCsv(findingKey, editorInfo.privateImeOptions); + } } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 682901cbc..9cfde8b77 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -669,12 +669,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (ProductionFlag.IS_EXPERIMENTAL) { ResearchLogger.latinIME_onStartInputViewInternal(editorInfo); } - if (StringUtils.inPrivateImeOptions(null, IME_OPTION_NO_MICROPHONE_COMPAT, editorInfo)) { + if (InputAttributes.inPrivateImeOptions( + null, IME_OPTION_NO_MICROPHONE_COMPAT, editorInfo)) { Log.w(TAG, "Deprecated private IME option specified: " + editorInfo.privateImeOptions); Log.w(TAG, "Use " + getPackageName() + "." + IME_OPTION_NO_MICROPHONE + " instead"); } - if (StringUtils.inPrivateImeOptions(getPackageName(), IME_OPTION_FORCE_ASCII, editorInfo)) { + if (InputAttributes.inPrivateImeOptions( + getPackageName(), IME_OPTION_FORCE_ASCII, editorInfo)) { Log.w(TAG, "Deprecated private IME option specified: " + editorInfo.privateImeOptions); Log.w(TAG, "Use EditorInfo.IME_FLAG_FORCE_ASCII flag instead"); @@ -1077,7 +1079,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (ic == null) return false; final CharSequence lastThree = ic.getTextBeforeCursor(3, 0); if (lastThree != null && lastThree.length() == 3 - && StringUtils.canBeFollowedByPeriod(lastThree.charAt(0)) + && canBeFollowedByPeriod(lastThree.charAt(0)) && lastThree.charAt(1) == Keyboard.CODE_SPACE && lastThree.charAt(2) == Keyboard.CODE_SPACE && mHandler.isAcceptingDoubleSpaces()) { @@ -1093,6 +1095,18 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return false; } + private static boolean canBeFollowedByPeriod(final int codePoint) { + // TODO: Check again whether there really ain't a better way to check this. + // TODO: This should probably be language-dependant... + return Character.isLetterOrDigit(codePoint) + || codePoint == Keyboard.CODE_SINGLE_QUOTE + || codePoint == Keyboard.CODE_DOUBLE_QUOTE + || codePoint == Keyboard.CODE_CLOSING_PARENTHESIS + || codePoint == Keyboard.CODE_CLOSING_SQUARE_BRACKET + || codePoint == Keyboard.CODE_CLOSING_CURLY_BRACKET + || codePoint == Keyboard.CODE_CLOSING_ANGLE_BRACKET; + } + // "ic" may be null private static void removeTrailingSpaceWhileInBatchEdit(final InputConnection ic) { if (ic == null) return; diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java index c160555f0..b83cec462 100644 --- a/java/src/com/android/inputmethod/latin/SettingsValues.java +++ b/java/src/com/android/inputmethod/latin/SettingsValues.java @@ -150,7 +150,7 @@ public class SettingsValues { mVoiceKeyOnMain = mVoiceMode != null && mVoiceMode.equals(voiceModeMain); mAdditionalSubtypes = AdditionalSubtype.createAdditionalSubtypesArray( - getCsvAdditionalSubtypes(prefs, res)); + getPrefAdditionalSubtypes(prefs, res)); } // Helper functions to create member values. @@ -315,10 +315,10 @@ public class SettingsValues { return mAdditionalSubtypes; } - public static String getCsvAdditionalSubtypes(final SharedPreferences prefs, + public static String getPrefAdditionalSubtypes(final SharedPreferences prefs, final Resources res) { - final String csvPredefinedSubtypes = res.getString(R.string.predefined_subtypes, ""); - return prefs.getString(Settings.PREF_CUSTOM_INPUT_STYLES, csvPredefinedSubtypes); + final String prefSubtypes = res.getString(R.string.predefined_subtypes, ""); + return prefs.getString(Settings.PREF_CUSTOM_INPUT_STYLES, prefSubtypes); } // Accessed from the settings interface, hence public diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java index a599933d8..160581cbe 100644 --- a/java/src/com/android/inputmethod/latin/StringUtils.java +++ b/java/src/com/android/inputmethod/latin/StringUtils.java @@ -17,9 +17,6 @@ package com.android.inputmethod.latin; import android.text.TextUtils; -import android.view.inputmethod.EditorInfo; - -import com.android.inputmethod.keyboard.Keyboard; import java.util.ArrayList; import java.util.Locale; @@ -29,39 +26,38 @@ public class StringUtils { // This utility class is not publicly instantiable. } - public static boolean canBeFollowedByPeriod(final int codePoint) { - // TODO: Check again whether there really ain't a better way to check this. - // TODO: This should probably be language-dependant... - return Character.isLetterOrDigit(codePoint) - || codePoint == Keyboard.CODE_SINGLE_QUOTE - || codePoint == Keyboard.CODE_DOUBLE_QUOTE - || codePoint == Keyboard.CODE_CLOSING_PARENTHESIS - || codePoint == Keyboard.CODE_CLOSING_SQUARE_BRACKET - || codePoint == Keyboard.CODE_CLOSING_CURLY_BRACKET - || codePoint == Keyboard.CODE_CLOSING_ANGLE_BRACKET; - } - public static int codePointCount(String text) { if (TextUtils.isEmpty(text)) return 0; return text.codePointCount(0, text.length()); } - private static boolean containsInCsv(String key, String csv) { - if (csv == null) - return false; - for (String option : csv.split(",")) { - if (option.equals(key)) - return true; + public static boolean containsInArray(String key, String[] array) { + for (final String element : array) { + if (key.equals(element)) return true; } return false; } - public static boolean inPrivateImeOptions(String packageName, String key, - EditorInfo editorInfo) { - if (editorInfo == null) - return false; - return containsInCsv(packageName != null ? packageName + "." + key : key, - editorInfo.privateImeOptions); + public static boolean containsInCsv(String key, String csv) { + if (TextUtils.isEmpty(csv)) return false; + return containsInArray(key, csv.split(",")); + } + + public static String appendToCsvIfNotExists(String key, String csv) { + if (TextUtils.isEmpty(csv)) return key; + if (containsInCsv(key, csv)) return csv; + return csv + "," + key; + } + + public static String removeFromCsvIfExists(String key, String csv) { + if (TextUtils.isEmpty(csv)) return ""; + final String[] elements = csv.split(","); + if (!containsInArray(key, elements)) return csv; + final ArrayList result = new ArrayList(elements.length - 1); + for (final String element : elements) { + if (!key.equals(element)) result.add(element); + } + return TextUtils.join(",", result); } /** diff --git a/tests/src/com/android/inputmethod/latin/StringUtilsTests.java b/tests/src/com/android/inputmethod/latin/StringUtilsTests.java new file mode 100644 index 000000000..8a5a82246 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/StringUtilsTests.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.latin; + +import android.test.AndroidTestCase; + +public class StringUtilsTests extends AndroidTestCase { + public void testContainsInArray() { + assertFalse("empty array", StringUtils.containsInArray("key", new String[0])); + assertFalse("not in 1 element", StringUtils.containsInArray("key", new String[] { + "key1" + })); + assertFalse("not in 2 elements", StringUtils.containsInArray("key", new String[] { + "key1", "key2" + })); + + assertTrue("in 1 element", StringUtils.containsInArray("key", new String[] { + "key" + })); + assertTrue("in 2 elements", StringUtils.containsInArray("key", new String[] { + "key1", "key" + })); + } + + public void testContainsInCsv() { + assertFalse("null", StringUtils.containsInCsv("key", null)); + assertFalse("empty", StringUtils.containsInCsv("key", "")); + assertFalse("not in 1 element", StringUtils.containsInCsv("key", "key1")); + assertFalse("not in 2 elements", StringUtils.containsInCsv("key", "key1,key2")); + + assertTrue("in 1 element", StringUtils.containsInCsv("key", "key")); + assertTrue("in 2 elements", StringUtils.containsInCsv("key", "key1,key")); + } + + public void testAppendToCsvIfNotExists() { + assertEquals("null", "key", StringUtils.appendToCsvIfNotExists("key", null)); + assertEquals("empty", "key", StringUtils.appendToCsvIfNotExists("key", "")); + + assertEquals("not in 1 element", "key1,key", + StringUtils.appendToCsvIfNotExists("key", "key1")); + assertEquals("not in 2 elements", "key1,key2,key", + StringUtils.appendToCsvIfNotExists("key", "key1,key2")); + + assertEquals("in 1 element", "key", + StringUtils.appendToCsvIfNotExists("key", "key")); + assertEquals("in 2 elements at position 1", "key,key2", + StringUtils.appendToCsvIfNotExists("key", "key,key2")); + assertEquals("in 2 elements at position 2", "key1,key", + StringUtils.appendToCsvIfNotExists("key", "key1,key")); + assertEquals("in 3 elements at position 2", "key1,key,key3", + StringUtils.appendToCsvIfNotExists("key", "key1,key,key3")); + } + + public void testRemoveFromCsvIfExists() { + assertEquals("null", "", StringUtils.removeFromCsvIfExists("key", null)); + assertEquals("empty", "", StringUtils.removeFromCsvIfExists("key", "")); + + assertEquals("not in 1 element", "key1", + StringUtils.removeFromCsvIfExists("key", "key1")); + assertEquals("not in 2 elements", "key1,key2", + StringUtils.removeFromCsvIfExists("key", "key1,key2")); + + assertEquals("in 1 element", "", + StringUtils.removeFromCsvIfExists("key", "key")); + assertEquals("in 2 elements at position 1", "key2", + StringUtils.removeFromCsvIfExists("key", "key,key2")); + assertEquals("in 2 elements at position 2", "key1", + StringUtils.removeFromCsvIfExists("key", "key1,key")); + assertEquals("in 3 elements at position 2", "key1,key3", + StringUtils.removeFromCsvIfExists("key", "key1,key,key3")); + + assertEquals("in 3 elements at position 1,2,3", "", + StringUtils.removeFromCsvIfExists("key", "key,key,key")); + assertEquals("in 5 elements at position 2,4", "key1,key3,key5", + StringUtils.removeFromCsvIfExists("key", "key1,key,key3,key,key5")); + } +} diff --git a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java index 12711c137..971fbc21b 100644 --- a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java +++ b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java @@ -165,13 +165,13 @@ public class SubtypeLocaleTests extends AndroidTestCase { public void testAdditionalSubtype() { final InputMethodSubtype DE_QWERTY = AdditionalSubtype.createAdditionalSubtype( - Locale.GERMAN.toString(), AdditionalSubtype.QWERTY); + Locale.GERMAN.toString(), AdditionalSubtype.QWERTY, null); final InputMethodSubtype FR_QWERTZ = AdditionalSubtype.createAdditionalSubtype( - Locale.FRENCH.toString(), AdditionalSubtype.QWERTZ); + Locale.FRENCH.toString(), AdditionalSubtype.QWERTZ, null); final InputMethodSubtype EN_AZERTY = AdditionalSubtype.createAdditionalSubtype( - Locale.ENGLISH.toString(), AdditionalSubtype.AZERTY); + Locale.ENGLISH.toString(), AdditionalSubtype.AZERTY, null); final InputMethodSubtype ZZ_AZERTY = AdditionalSubtype.createAdditionalSubtype( - SubtypeLocale.NO_LANGUAGE, AdditionalSubtype.AZERTY); + SubtypeLocale.NO_LANGUAGE, AdditionalSubtype.AZERTY, null); assertTrue(AdditionalSubtype.isAdditionalSubtype(FR_QWERTZ)); assertTrue(AdditionalSubtype.isAdditionalSubtype(DE_QWERTY));