diff options
Diffstat (limited to 'java/src/com/android/inputmethod')
17 files changed, 376 insertions, 181 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java index b1813a141..ba449eeb3 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java @@ -53,7 +53,9 @@ public final class KeySpecParser { private static final int MAX_STRING_REFERENCE_INDIRECTION = 10; // Constants for parsing. - private static final char LABEL_END = '|'; + private static final char COMMA = ','; + private static final char BACKSLASH = '\\'; + private static final char VERTICAL_BAR = '|'; private static final String PREFIX_TEXT = "!text/"; static final String PREFIX_ICON = "!icon/"; private static final String PREFIX_CODE = "!code/"; @@ -64,6 +66,59 @@ public final class KeySpecParser { // Intentional empty constructor for utility class. } + /** + * Split the text containing multiple key specifications separated by commas into an array of + * key specifications. + * A key specification can contain a character escaped by the backslash character, including a + * comma character. + * Note that an empty key specification will be eliminated from the result array. + * + * @param text the text containing multiple key specifications. + * @return an array of key specification text. Null if the specified <code>text</code> is empty + * or has no key specifications. + */ + public static String[] splitKeySpecs(final String text) { + final int size = text.length(); + if (size == 0) { + return null; + } + // Optimization for one-letter key specification. + if (size == 1) { + return text.charAt(0) == COMMA ? null : new String[] { text }; + } + + ArrayList<String> list = null; + int start = 0; + // The characters in question in this loop are COMMA and BACKSLASH. These characters never + // match any high or low surrogate character. So it is OK to iterate through with char + // index. + for (int pos = 0; pos < size; pos++) { + final char c = text.charAt(pos); + if (c == COMMA) { + // Skip empty entry. + if (pos - start > 0) { + if (list == null) { + list = CollectionUtils.newArrayList(); + } + list.add(text.substring(start, pos)); + } + // Skip comma + start = pos + 1; + } else if (c == BACKSLASH) { + // Skip escape character and escaped character. + pos++; + } + } + final String remain = (size - start > 0) ? text.substring(start) : null; + if (list == null) { + return remain != null ? new String[] { remain } : null; + } + if (remain != null) { + list.add(remain); + } + return list.toArray(new String[list.size()]); + } + private static boolean hasIcon(final String moreKeySpec) { return moreKeySpec.startsWith(PREFIX_ICON); } @@ -78,14 +133,14 @@ public final class KeySpecParser { } private static String parseEscape(final String text) { - if (text.indexOf(Constants.CSV_ESCAPE) < 0) { + if (text.indexOf(BACKSLASH) < 0) { return text; } final int length = text.length(); final StringBuilder sb = new StringBuilder(); for (int pos = 0; pos < length; pos++) { final char c = text.charAt(pos); - if (c == Constants.CSV_ESCAPE && pos + 1 < length) { + if (c == BACKSLASH && pos + 1 < length) { // Skip escape char pos++; sb.append(text.charAt(pos)); @@ -97,20 +152,20 @@ public final class KeySpecParser { } private static int indexOfLabelEnd(final String moreKeySpec, final int start) { - if (moreKeySpec.indexOf(Constants.CSV_ESCAPE, start) < 0) { - final int end = moreKeySpec.indexOf(LABEL_END, start); + if (moreKeySpec.indexOf(BACKSLASH, start) < 0) { + final int end = moreKeySpec.indexOf(VERTICAL_BAR, start); if (end == 0) { - throw new KeySpecParserError(LABEL_END + " at " + start + ": " + moreKeySpec); + throw new KeySpecParserError(VERTICAL_BAR + " at " + start + ": " + moreKeySpec); } return end; } final int length = moreKeySpec.length(); for (int pos = start; pos < length; pos++) { final char c = moreKeySpec.charAt(pos); - if (c == Constants.CSV_ESCAPE && pos + 1 < length) { + if (c == BACKSLASH && pos + 1 < length) { // Skip escape char pos++; - } else if (c == LABEL_END) { + } else if (c == VERTICAL_BAR) { return pos; } } @@ -136,9 +191,9 @@ public final class KeySpecParser { return null; } if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) { - throw new KeySpecParserError("Multiple " + LABEL_END + ": " + moreKeySpec); + throw new KeySpecParserError("Multiple " + VERTICAL_BAR + ": " + moreKeySpec); } - return parseEscape(moreKeySpec.substring(end + /* LABEL_END */1)); + return parseEscape(moreKeySpec.substring(end + /* VERTICAL_BAR */1)); } static String getOutputText(final String moreKeySpec) { @@ -169,7 +224,7 @@ public final class KeySpecParser { if (hasCode(moreKeySpec)) { final int end = indexOfLabelEnd(moreKeySpec, 0); if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) { - throw new KeySpecParserError("Multiple " + LABEL_END + ": " + moreKeySpec); + throw new KeySpecParserError("Multiple " + VERTICAL_BAR + ": " + moreKeySpec); } return parseCode(moreKeySpec.substring(end + 1), codesSet, CODE_UNSPECIFIED); } @@ -204,7 +259,7 @@ public final class KeySpecParser { public static int getIconId(final String moreKeySpec) { if (moreKeySpec != null && hasIcon(moreKeySpec)) { - final int end = moreKeySpec.indexOf(LABEL_END, PREFIX_ICON.length()); + final int end = moreKeySpec.indexOf(VERTICAL_BAR, PREFIX_ICON.length()); final String name = (end < 0) ? moreKeySpec.substring(PREFIX_ICON.length()) : moreKeySpec.substring(PREFIX_ICON.length(), end); return KeyboardIconsSet.getIconId(name); @@ -351,7 +406,7 @@ public final class KeySpecParser { final String name = text.substring(pos + prefixLen, end); sb.append(textsSet.getText(name)); pos = end - 1; - } else if (c == Constants.CSV_ESCAPE) { + } else if (c == BACKSLASH) { if (sb != null) { // Append both escape character and escaped character. sb.append(text.substring(pos, Math.min(pos + 2, size))); @@ -366,7 +421,6 @@ public final class KeySpecParser { text = sb.toString(); } } while (sb != null); - return text; } diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java index 5db3ebbd1..f65056948 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java @@ -18,8 +18,6 @@ package com.android.inputmethod.keyboard.internal; import android.content.res.TypedArray; -import com.android.inputmethod.latin.StringUtils; - public abstract class KeyStyle { private final KeyboardTextsSet mTextsSet; @@ -42,7 +40,7 @@ public abstract class KeyStyle { protected String[] parseStringArray(final TypedArray a, final int index) { if (a.hasValue(index)) { final String text = KeySpecParser.resolveTextReference(a.getString(index), mTextsSet); - return StringUtils.parseCsvString(text); + return KeySpecParser.splitKeySpecs(text); } return null; } diff --git a/java/src/com/android/inputmethod/latin/AdditionalFeaturesSettingUtils.java b/java/src/com/android/inputmethod/latin/AdditionalFeaturesSettingUtils.java new file mode 100644 index 000000000..0fdaea50c --- /dev/null +++ b/java/src/com/android/inputmethod/latin/AdditionalFeaturesSettingUtils.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin; + +import com.android.inputmethodcommon.InputMethodSettingsFragment; + +import android.content.Context; +import android.content.SharedPreferences; + +/** + * Utility class for managing additional features settings. + */ +public class AdditionalFeaturesSettingUtils { + public static final int ADDITIONAL_FEATURES_SETTINGS_SIZE = 0; + + private AdditionalFeaturesSettingUtils() { + // This utility class is not publicly instantiable. + } + + public static void addAdditionalFeaturesPreferences( + final Context context, final InputMethodSettingsFragment settingsFragment) { + // do nothing. + } + + public static void readAdditionalFeaturesPreferencesIntoArray( + final SharedPreferences prefs, final int[] additionalFeaturesPreferences) { + // do nothing. + } + + public static int[] getAdditionalNativeSuggestOptions() { + return Settings.getInstance().getCurrent().mAdditionalFeaturesSettingValues; + } +} diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java index 2c700e55b..85b14d849 100644 --- a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java +++ b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java @@ -49,13 +49,13 @@ public final class AdditionalSubtype { && SubtypeLocale.isExceptionalLocale(localeString)) { final String layoutDisplayName = SubtypeLocale.getKeyboardLayoutSetDisplayName( keyboardLayoutSetName); - layoutDisplayNameExtraValue = StringUtils.appendToCommaConcatenatedTextIfNotExists( + layoutDisplayNameExtraValue = StringUtils.appendToCommaSplittableTextIfNotExists( UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME + "=" + layoutDisplayName, extraValue); } else { layoutDisplayNameExtraValue = extraValue; } final String additionalSubtypeExtraValue = - StringUtils.appendToCommaConcatenatedTextIfNotExists( + StringUtils.appendToCommaSplittableTextIfNotExists( IS_ADDITIONAL_SUBTYPE, layoutDisplayNameExtraValue); final int nameId = SubtypeLocale.getSubtypeNameId(localeString, keyboardLayoutSetName); return new InputMethodSubtype(nameId, R.drawable.ic_subtype_keyboard, @@ -67,8 +67,8 @@ public final class AdditionalSubtype { final String localeString = subtype.getLocale(); final String keyboardLayoutSetName = SubtypeLocale.getKeyboardLayoutSetName(subtype); final String layoutExtraValue = KEYBOARD_LAYOUT_SET + "=" + keyboardLayoutSetName; - final String extraValue = StringUtils.removeFromCommaConcatenatedTextIfExists( - layoutExtraValue, StringUtils.removeFromCommaConcatenatedTextIfExists( + final String extraValue = StringUtils.removeFromCommaSplittableTextIfExists( + layoutExtraValue, StringUtils.removeFromCommaSplittableTextIfExists( IS_ADDITIONAL_SUBTYPE, subtype.getExtraValue())); final String basePrefSubtype = localeString + LOCALE_AND_LAYOUT_SEPARATOR + keyboardLayoutSetName; diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index c644a7722..aad129d76 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -136,6 +136,8 @@ public final class BinaryDictionary extends Dictionary { final InputPointers ips = composer.getInputPointers(); final int inputSize = isGesture ? ips.getPointerSize() : composerSize; mNativeSuggestOptions.setIsGesture(isGesture); + mNativeSuggestOptions.setAdditionalFeaturesOptions( + AdditionalFeaturesSettingUtils.getAdditionalNativeSuggestOptions()); // proximityInfo and/or prevWordForBigrams may not be null. final int count = getSuggestionsNative(mNativeDict, proximityInfo.getNativeProximityInfo(), getTraverseSession(sessionId).getSession(), ips.getXCoordinates(), diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java index 86bb25562..64c14d32f 100644 --- a/java/src/com/android/inputmethod/latin/Constants.java +++ b/java/src/com/android/inputmethod/latin/Constants.java @@ -215,10 +215,6 @@ public final class Constants { } } - // Constants for CSV parsing. - public static final char CSV_SEPARATOR = ','; - public static final char CSV_ESCAPE = '\\'; - private Constants() { // This utility class is not publicly instantiable. } diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java index 8f98e3a42..1f673e9b0 100644 --- a/java/src/com/android/inputmethod/latin/InputAttributes.java +++ b/java/src/com/android/inputmethod/latin/InputAttributes.java @@ -199,7 +199,6 @@ public final class InputAttributes { if (editorInfo == null) return false; final String findingKey = (packageName != null) ? packageName + "." + key : key; - return StringUtils.containsInCommaConcatenatedText( - findingKey, editorInfo.privateImeOptions); + return StringUtils.containsInCommaSplittableText(findingKey, editorInfo.privateImeOptions); } } diff --git a/java/src/com/android/inputmethod/latin/NativeSuggestOptions.java b/java/src/com/android/inputmethod/latin/NativeSuggestOptions.java index 4425f07b7..291551301 100644 --- a/java/src/com/android/inputmethod/latin/NativeSuggestOptions.java +++ b/java/src/com/android/inputmethod/latin/NativeSuggestOptions.java @@ -22,7 +22,8 @@ public class NativeSuggestOptions { private static final int USE_FULL_EDIT_DISTANCE = 1; private static final int OPTIONS_SIZE = 2; - private final int[] mOptions = new int[OPTIONS_SIZE]; + private final int[] mOptions = new int[OPTIONS_SIZE + + AdditionalFeaturesSettingUtils.ADDITIONAL_FEATURES_SETTINGS_SIZE]; public void setIsGesture(final boolean value) { setBooleanOption(IS_GESTURE, value); @@ -32,6 +33,12 @@ public class NativeSuggestOptions { setBooleanOption(USE_FULL_EDIT_DISTANCE, value); } + public void setAdditionalFeaturesOptions(final int[] additionalOptions) { + for (int i = 0; i < additionalOptions.length; i++) { + setIntegerOption(OPTIONS_SIZE + i, additionalOptions[i]); + } + } + public int[] getOptions() { return mOptions; } @@ -39,4 +46,8 @@ public class NativeSuggestOptions { private void setBooleanOption(final int key, final boolean value) { mOptions[key] = value ? 1 : 0; } + + private void setIntegerOption(final int key, final int value) { + mOptions[key] = value; + } } diff --git a/java/src/com/android/inputmethod/latin/ResourceUtils.java b/java/src/com/android/inputmethod/latin/ResourceUtils.java index a9fba5348..0eb8b4f09 100644 --- a/java/src/com/android/inputmethod/latin/ResourceUtils.java +++ b/java/src/com/android/inputmethod/latin/ResourceUtils.java @@ -27,6 +27,7 @@ import com.android.inputmethod.annotations.UsedForTesting; import java.util.ArrayList; import java.util.HashMap; +import java.util.regex.PatternSyntaxException; public final class ResourceUtils { private static final String TAG = ResourceUtils.class.getSimpleName(); @@ -83,22 +84,39 @@ public final class ResourceUtils { return overrideValue; } - final String defaultValue = findDefaultConstant(overrideArray); - // The defaultValue might be an empty string. - if (defaultValue == null) { - Log.w(TAG, "Couldn't find override value nor default value:" - + " resource="+ res.getResourceEntryName(overrideResId) - + " build=" + sBuildKeyValuesDebugString); - } else { - Log.i(TAG, "Found default value:" - + " resource="+ res.getResourceEntryName(overrideResId) - + " build=" + sBuildKeyValuesDebugString - + " default=" + defaultValue); + String defaultValue = null; + try { + defaultValue = findDefaultConstant(overrideArray); + // The defaultValue might be an empty string. + if (defaultValue == null) { + Log.w(TAG, "Couldn't find override value nor default value:" + + " resource="+ res.getResourceEntryName(overrideResId) + + " build=" + sBuildKeyValuesDebugString); + } else { + Log.i(TAG, "Found default value:" + + " resource="+ res.getResourceEntryName(overrideResId) + + " build=" + sBuildKeyValuesDebugString + + " default=" + defaultValue); + } + } catch (final DeviceOverridePatternSyntaxError e) { + Log.w(TAG, "Syntax error, ignored", e); } sDeviceOverrideValueMap.put(key, defaultValue); return defaultValue; } + @SuppressWarnings("serial") + static class DeviceOverridePatternSyntaxError extends Exception { + public DeviceOverridePatternSyntaxError(final String message, final String expression) { + this(message, expression, null); + } + + public DeviceOverridePatternSyntaxError(final String message, final String expression, + final Throwable throwable) { + super(message + ": " + expression, throwable); + } + } + /** * Find the condition that fulfills specified key value pairs from an array of * "condition,constant", and return the corresponding string constant. A condition is @@ -123,10 +141,12 @@ public final class ResourceUtils { if (conditionConstantArray == null || keyValuePairs == null) { return null; } + String foundValue = null; for (final String conditionConstant : conditionConstantArray) { final int posComma = conditionConstant.indexOf(','); if (posComma < 0) { - throw new RuntimeException("Array element has no comma: " + conditionConstant); + Log.w(TAG, "Array element has no comma: " + conditionConstant); + continue; } final String condition = conditionConstant.substring(0, posComma); if (condition.isEmpty()) { @@ -134,44 +154,59 @@ public final class ResourceUtils { // {@link #findConstantForDefault(String[])}. continue; } - if (fulfillsCondition(keyValuePairs, condition)) { - return conditionConstant.substring(posComma + 1); + try { + if (fulfillsCondition(keyValuePairs, condition)) { + // Take first match + if (foundValue == null) { + foundValue = conditionConstant.substring(posComma + 1); + } + // And continue walking through all conditions. + } + } catch (final DeviceOverridePatternSyntaxError e) { + Log.w(TAG, "Syntax error, ignored", e); } } - return null; + return foundValue; } private static boolean fulfillsCondition(final HashMap<String,String> keyValuePairs, - final String condition) { + final String condition) throws DeviceOverridePatternSyntaxError { final String[] patterns = condition.split(":"); // Check all patterns in a condition are true + boolean matchedAll = true; for (final String pattern : patterns) { final int posEqual = pattern.indexOf('='); if (posEqual < 0) { - throw new RuntimeException("Pattern has no '=': " + condition); + throw new DeviceOverridePatternSyntaxError("Pattern has no '='", condition); } final String key = pattern.substring(0, posEqual); final String value = keyValuePairs.get(key); if (value == null) { - throw new RuntimeException("Found unknown key: " + condition); + throw new DeviceOverridePatternSyntaxError("Unknown key", condition); } final String patternRegexpValue = pattern.substring(posEqual + 1); - if (!value.matches(patternRegexpValue)) { - return false; + try { + if (!value.matches(patternRegexpValue)) { + matchedAll = false; + // And continue walking through all patterns. + } + } catch (final PatternSyntaxException e) { + throw new DeviceOverridePatternSyntaxError("Syntax error", condition, e); } } - return true; + return matchedAll; } @UsedForTesting - static String findDefaultConstant(final String[] conditionConstantArray) { + static String findDefaultConstant(final String[] conditionConstantArray) + throws DeviceOverridePatternSyntaxError { if (conditionConstantArray == null) { return null; } for (final String condition : conditionConstantArray) { final int posComma = condition.indexOf(','); if (posComma < 0) { - throw new RuntimeException("Array element has no comma: " + condition); + throw new DeviceOverridePatternSyntaxError("Array element has no comma", condition); } if (posComma == 0) { // condition is empty. return condition.substring(posComma + 1); diff --git a/java/src/com/android/inputmethod/latin/SeekBarDialogPreference.java b/java/src/com/android/inputmethod/latin/SeekBarDialogPreference.java index 7c4156c48..3ea9fedd7 100644 --- a/java/src/com/android/inputmethod/latin/SeekBarDialogPreference.java +++ b/java/src/com/android/inputmethod/latin/SeekBarDialogPreference.java @@ -32,6 +32,7 @@ public final class SeekBarDialogPreference extends DialogPreference public int readValue(final String key); public int readDefaultValue(final String key); public void writeValue(final int value, final String key); + public void writeDefaultValue(final String key); public void feedbackValue(final int value); } @@ -122,12 +123,16 @@ public final class SeekBarDialogPreference extends DialogPreference @Override public void onClick(final DialogInterface dialog, final int which) { super.onClick(dialog, which); + final String key = getKey(); if (which == DialogInterface.BUTTON_NEUTRAL) { - setValue(clipValue(mValueProxy.readDefaultValue(getKey())), false /* fromUser */); + setValue(clipValue(mValueProxy.readDefaultValue(key)), false /* fromUser */); + mValueProxy.writeDefaultValue(key); + return; } - if (which != DialogInterface.BUTTON_NEGATIVE) { + if (which == DialogInterface.BUTTON_POSITIVE) { setSummary(mValueView.getText()); - mValueProxy.writeValue(getClippedValueFromProgress(mSeekBar.getProgress()), getKey()); + mValueProxy.writeValue(getClippedValueFromProgress(mSeekBar.getProgress()), key); + return; } } diff --git a/java/src/com/android/inputmethod/latin/SettingsFragment.java b/java/src/com/android/inputmethod/latin/SettingsFragment.java index 835ef7b46..7225cd6bf 100644 --- a/java/src/com/android/inputmethod/latin/SettingsFragment.java +++ b/java/src/com/android/inputmethod/latin/SettingsFragment.java @@ -207,6 +207,8 @@ public final class SettingsFragment extends InputMethodSettingsFragment if (!Settings.readFromBuildConfigIfGestureInputEnabled(res)) { removePreference(Settings.PREF_GESTURE_SETTINGS, getPreferenceScreen()); + } else { + AdditionalFeaturesSettingUtils.addAdditionalFeaturesPreferences(context, this); } setupKeyLongpressTimeoutSettings(prefs, res); @@ -327,6 +329,11 @@ public final class SettingsFragment extends InputMethodSettingsFragment } @Override + public void writeDefaultValue(final String key) { + sp.edit().remove(key).apply(); + } + + @Override public int readValue(final String key) { return Settings.readKeypressVibrationDuration(sp, res); } @@ -357,6 +364,11 @@ public final class SettingsFragment extends InputMethodSettingsFragment } @Override + public void writeDefaultValue(final String key) { + sp.edit().remove(key).apply(); + } + + @Override public int readValue(final String key) { return Settings.readKeyLongpressTimeout(sp, res); } @@ -395,6 +407,11 @@ public final class SettingsFragment extends InputMethodSettingsFragment } @Override + public void writeDefaultValue(final String key) { + sp.edit().remove(key).apply(); + } + + @Override public int readValue(final String key) { return getPercentageFromValue(Settings.readKeypressSoundVolume(sp, res)); } diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java index 615b2dfab..09102447f 100644 --- a/java/src/com/android/inputmethod/latin/SettingsValues.java +++ b/java/src/com/android/inputmethod/latin/SettingsValues.java @@ -80,6 +80,10 @@ public final class SettingsValues { private final boolean mVoiceKeyEnabled; private final boolean mVoiceKeyOnMain; + // Setting values for additional features + public final int[] mAdditionalFeaturesSettingValues = + new int[AdditionalFeaturesSettingUtils.ADDITIONAL_FEATURES_SETTINGS_SIZE]; + // Debug settings public final boolean mIsInternal; @@ -96,7 +100,7 @@ public final class SettingsValues { mWordConnectors = StringUtils.toCodePointArray(res.getString(R.string.symbols_word_connectors)); Arrays.sort(mWordConnectors); - final String[] suggestPuncsSpec = StringUtils.parseCsvString(res.getString( + final String[] suggestPuncsSpec = KeySpecParser.splitKeySpecs(res.getString( R.string.suggested_punctuations)); mSuggestPuncList = createSuggestPuncList(suggestPuncsSpec); mWordSeparators = res.getString(R.string.symbols_word_separators); @@ -149,6 +153,8 @@ public final class SettingsValues { Settings.PREF_SHOW_SUGGESTIONS_SETTING, res.getString(R.string.prefs_suggestion_visibility_default_value)); mSuggestionVisibility = createSuggestionVisibility(res, showSuggestionsSetting); + AdditionalFeaturesSettingUtils.readAdditionalFeaturesPreferencesIntoArray( + prefs, mAdditionalFeaturesSettingValues); mIsInternal = Settings.isInternal(prefs); } diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java index fa90ba252..c2fd4fb32 100644 --- a/java/src/com/android/inputmethod/latin/StringUtils.java +++ b/java/src/com/android/inputmethod/latin/StringUtils.java @@ -42,33 +42,38 @@ public final class StringUtils { return false; } - private static final String SEPARATOR_FOR_COMMA_CONCATENATED_TEXT = ","; + /** + * Comma-Splittable Text is similar to Comma-Separated Values (CSV) but has much simpler syntax. + * Unlike CSV, Comma-Splittable Text has no escaping mechanism, so that the text can't contain + * a comma character in it. + */ + private static final String SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT = ","; - public static boolean containsInCommaConcatenatedText(final String text, + public static boolean containsInCommaSplittableText(final String text, final String extraValues) { if (TextUtils.isEmpty(extraValues)) { return false; } - return containsInArray(text, extraValues.split(SEPARATOR_FOR_COMMA_CONCATENATED_TEXT)); + return containsInArray(text, extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT)); } - public static String appendToCommaConcatenatedTextIfNotExists(final String text, + public static String appendToCommaSplittableTextIfNotExists(final String text, final String extraValues) { if (TextUtils.isEmpty(extraValues)) { return text; } - if (containsInCommaConcatenatedText(text, extraValues)) { + if (containsInCommaSplittableText(text, extraValues)) { return extraValues; } - return extraValues + SEPARATOR_FOR_COMMA_CONCATENATED_TEXT + text; + return extraValues + SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT + text; } - public static String removeFromCommaConcatenatedTextIfExists(final String text, + public static String removeFromCommaSplittableTextIfExists(final String text, final String extraValues) { if (TextUtils.isEmpty(extraValues)) { return ""; } - final String[] elements = extraValues.split(SEPARATOR_FOR_COMMA_CONCATENATED_TEXT); + final String[] elements = extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT); if (!containsInArray(text, elements)) { return extraValues; } @@ -78,7 +83,7 @@ public final class StringUtils { result.add(element); } } - return TextUtils.join(SEPARATOR_FOR_COMMA_CONCATENATED_TEXT, result); + return TextUtils.join(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT, result); } /** @@ -148,44 +153,6 @@ public final class StringUtils { return codePoints; } - public static String[] parseCsvString(final String text) { - final int size = text.length(); - if (size == 0) { - return null; - } - if (codePointCount(text) == 1) { - return text.codePointAt(0) == Constants.CSV_SEPARATOR ? null : new String[] { text }; - } - - ArrayList<String> list = null; - int start = 0; - for (int pos = 0; pos < size; pos++) { - final char c = text.charAt(pos); - if (c == Constants.CSV_SEPARATOR) { - // Skip empty entry. - if (pos - start > 0) { - if (list == null) { - list = CollectionUtils.newArrayList(); - } - list.add(text.substring(start, pos)); - } - // Skip comma - start = pos + 1; - } else if (c == Constants.CSV_ESCAPE) { - // Skip escape character and escaped character. - pos++; - } - } - final String remain = (size - start > 0) ? text.substring(start) : null; - if (list == null) { - return remain != null ? new String[] { remain } : null; - } - if (remain != null) { - list.add(remain); - } - return list.toArray(new String[list.size()]); - } - // This method assumes the text is not null. For the empty string, it returns CAPITALIZE_NONE. public static int getCapitalizationType(final String text) { // If the first char is not uppercase, then the word is either all lower case or diff --git a/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java b/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java index 93687e193..a446672cb 100644 --- a/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java +++ b/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java @@ -23,6 +23,7 @@ import android.util.Patterns; import java.util.ArrayList; import java.util.List; +import java.util.Locale; public class AccountUtils { private AccountUtils() { @@ -44,4 +45,22 @@ public class AccountUtils { } return retval; } + + /** + * Get all device accounts having specified domain name. + * @param context application context + * @param domain domain name used for filtering + * @return List of account names that contain the specified domain name + */ + public static List<String> getDeviceAccountsWithDomain( + final Context context, final String domain) { + final ArrayList<String> retval = new ArrayList<String>(); + final String atDomain = "@" + domain.toLowerCase(Locale.ROOT); + for (final Account account : getAccounts(context)) { + if (account.name.toLowerCase(Locale.ROOT).endsWith(atDomain)) { + retval.add(account.name); + } + } + return retval; + } } diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java index 1113939d1..9764610b3 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java @@ -250,10 +250,12 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick } private CharSequence getStyledSuggestionWord(final SuggestedWords suggestedWords, - final int pos) { - final String word = suggestedWords.getWord(pos); - final boolean isAutoCorrect = pos == 1 && suggestedWords.willAutoCorrect(); - final boolean isTypedWordValid = pos == 0 && suggestedWords.mTypedWordValid; + final int indexInSuggestedWords) { + final String word = suggestedWords.getWord(indexInSuggestedWords); + final boolean isAutoCorrect = indexInSuggestedWords == 1 + && suggestedWords.willAutoCorrect(); + final boolean isTypedWordValid = indexInSuggestedWords == 0 + && suggestedWords.mTypedWordValid; if (!isAutoCorrect && !isTypedWordValid) return word; @@ -270,28 +272,31 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick return spannedWord; } - private int getWordPosition(final int index, final SuggestedWords suggestedWords) { + private int getIndexInSuggestedWords(final int indexInStrip, + final SuggestedWords suggestedWords) { // TODO: This works for 3 suggestions. Revisit this algorithm when there are 5 or more // suggestions. - final int centerPos = suggestedWords.willAutoCorrect() ? 1 : 0; - if (index == mCenterSuggestionIndex) { - return centerPos; - } else if (index == centerPos) { + final int mostImportantIndexInSuggestedWords = suggestedWords.willAutoCorrect() ? 1 : 0; + if (indexInStrip == mCenterSuggestionIndex) { + return mostImportantIndexInSuggestedWords; + } else if (indexInStrip == mostImportantIndexInSuggestedWords) { return mCenterSuggestionIndex; } else { - return index; + return indexInStrip; } } - private int getSuggestionTextColor(final int index, final SuggestedWords suggestedWords, - final int pos) { + private int getSuggestionTextColor(final int indexInStrip, + final SuggestedWords suggestedWords) { + final int indexInSuggestedWords = getIndexInSuggestedWords( + indexInStrip, suggestedWords); // TODO: Need to revisit this logic with bigram suggestions - final boolean isSuggested = (pos != 0); + final boolean isSuggested = (indexInSuggestedWords != 0); final int color; - if (index == mCenterSuggestionIndex && suggestedWords.willAutoCorrect()) { + if (indexInStrip == mCenterSuggestionIndex && suggestedWords.willAutoCorrect()) { color = mColorAutoCorrect; - } else if (index == mCenterSuggestionIndex && suggestedWords.mTypedWordValid) { + } else if (indexInStrip == mCenterSuggestionIndex && suggestedWords.mTypedWordValid) { color = mColorValidTypedWord; } else if (isSuggested) { color = mColorSuggested; @@ -301,7 +306,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick if (LatinImeLogger.sDBG && suggestedWords.size() > 1) { // If we auto-correct, then the autocorrection is in slot 0 and the typed word // is in slot 1. - if (index == mCenterSuggestionIndex + if (indexInStrip == mCenterSuggestionIndex && AutoCorrection.shouldBlockAutoCorrectionBySafetyNet( suggestedWords.getWord(1), suggestedWords.getWord(0))) { return 0xFFFF0000; @@ -338,67 +343,101 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick setupTexts(suggestedWords, countInStrip); mMoreSuggestionsAvailable = (suggestedWords.size() > countInStrip); int x = 0; - for (int index = 0; index < countInStrip; index++) { - final int pos = getWordPosition(index, suggestedWords); - - if (index != 0) { - final View divider = mDividers.get(pos); + for (int indexInStrip = 0; indexInStrip < countInStrip; indexInStrip++) { + if (indexInStrip != 0) { + final View divider = mDividers.get(indexInStrip); // Add divider if this isn't the left most suggestion in suggestions strip. addDivider(stripView, divider); x += divider.getMeasuredWidth(); } - final CharSequence styled = mTexts.get(pos); - final TextView word = mWords.get(pos); - if (index == mCenterSuggestionIndex && mMoreSuggestionsAvailable) { - // TODO: This "more suggestions hint" should have nicely designed icon. - word.setCompoundDrawablesWithIntrinsicBounds( - null, null, null, mMoreSuggestionsHint); - // HACK: To align with other TextView that has no compound drawables. - word.setCompoundDrawablePadding(-mMoreSuggestionsHint.getIntrinsicHeight()); - } else { - word.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null); - } - - // Disable this suggestion if the suggestion is null or empty. - word.setEnabled(!TextUtils.isEmpty(styled)); - word.setTextColor(getSuggestionTextColor(index, suggestedWords, pos)); - final int width = getSuggestionWidth(index, stripWidth); - final CharSequence text = getEllipsizedText(styled, width, word.getPaint()); - final float scaleX = word.getTextScaleX(); - word.setText(text); // TextView.setText() resets text scale x to 1.0. - word.setTextScaleX(scaleX); + final int width = getSuggestionWidth(indexInStrip, stripWidth); + final TextView word = layoutWord(suggestedWords, indexInStrip, width); stripView.addView(word); - setLayoutWeight( - word, getSuggestionWeight(index), ViewGroup.LayoutParams.MATCH_PARENT); + setLayoutWeight(word, getSuggestionWeight(indexInStrip), + ViewGroup.LayoutParams.MATCH_PARENT); x += word.getMeasuredWidth(); - if (DBG && pos < suggestedWords.size()) { - final String debugInfo = Utils.getDebugInfo(suggestedWords, pos); - if (debugInfo != null) { - final TextView info = mInfos.get(pos); - info.setText(debugInfo); - placer.addView(info); - info.measure(ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT); - final int infoWidth = info.getMeasuredWidth(); - final int y = info.getMeasuredHeight(); - ViewLayoutUtils.placeViewAt( - info, x - infoWidth, y, infoWidth, info.getMeasuredHeight()); - } + if (DBG) { + layoutDebugInfo(suggestedWords, indexInStrip, placer, x); } } } - private int getSuggestionWidth(final int index, final int maxWidth) { + /** + * Format appropriately the suggested word indirectly specified by + * <code>indexInStrip</code> as text in a corresponding {@link TextView}. When the + * suggested word doesn't exist, the corresponding {@link TextView} will be disabled + * and never respond to user interaction. The suggested word may be shrunk or ellipsized to + * fit in the specified width. + * + * The <code>indexInStrip</code> argument is the index in the suggestion strip. The indices + * increase towards the right for LTR scripts and the left for RTL scripts, starting with 0. + * The index of the most important suggestion is in {@link #mCenterSuggestionIndex}. This + * usually doesn't match the index in <code>suggedtedWords</code> -- see + * {@link #getIndexInSuggestedWords(int,SuggestedWords)}. + * + * @param suggestedWords the list of suggestions. + * @param indexInStrip the in the suggestion strip. + * @param width the maximum width for layout in pixels. + * @return the {@link TextView} containing the suggested word appropriately formatted. + */ + private TextView layoutWord(final SuggestedWords suggestedWords, final int indexInStrip, + final int width) { + final int indexInSuggestedWords = getIndexInSuggestedWords( + indexInStrip, suggestedWords); + final CharSequence styled = mTexts.get(indexInSuggestedWords); + final TextView word = mWords.get(indexInSuggestedWords); + if (indexInStrip == mCenterSuggestionIndex && mMoreSuggestionsAvailable) { + // TODO: This "more suggestions hint" should have a nicely designed icon. + word.setCompoundDrawablesWithIntrinsicBounds( + null, null, null, mMoreSuggestionsHint); + // HACK: Align with other TextViews that have no compound drawables. + word.setCompoundDrawablePadding(-mMoreSuggestionsHint.getIntrinsicHeight()); + } else { + word.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null); + } + + // Disable this suggestion if the suggestion is null or empty. + word.setEnabled(!TextUtils.isEmpty(styled)); + word.setTextColor(getSuggestionTextColor(indexInStrip, suggestedWords)); + final CharSequence text = getEllipsizedText(styled, width, word.getPaint()); + final float scaleX = word.getTextScaleX(); + word.setText(text); // TextView.setText() resets text scale x to 1.0. + word.setTextScaleX(scaleX); + return word; + } + + private void layoutDebugInfo(final SuggestedWords suggestedWords, final int indexInStrip, + final ViewGroup placer, final int x) { + final int indexInSuggestedWords = getIndexInSuggestedWords( + indexInStrip, suggestedWords); + if (indexInSuggestedWords >= suggestedWords.size()) { + return; + } + final String debugInfo = Utils.getDebugInfo(suggestedWords, indexInSuggestedWords); + if (debugInfo == null) { + return; + } + final TextView info = mInfos.get(indexInSuggestedWords); + info.setText(debugInfo); + placer.addView(info); + info.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + final int infoWidth = info.getMeasuredWidth(); + final int y = info.getMeasuredHeight(); + ViewLayoutUtils.placeViewAt( + info, x - infoWidth, y, infoWidth, info.getMeasuredHeight()); + } + + private int getSuggestionWidth(final int indexInStrip, final int maxWidth) { final int paddings = mPadding * mSuggestionsCountInStrip; final int dividers = mDividerWidth * (mSuggestionsCountInStrip - 1); final int availableWidth = maxWidth - paddings - dividers; - return (int)(availableWidth * getSuggestionWeight(index)); + return (int)(availableWidth * getSuggestionWeight(indexInStrip)); } - private float getSuggestionWeight(final int index) { - if (index == mCenterSuggestionIndex) { + private float getSuggestionWeight(final int indexInStrip) { + if (indexInStrip == mCenterSuggestionIndex) { return mCenterSuggestionWeight; } else { // TODO: Revisit this for cases of 5 or more suggestions @@ -422,16 +461,16 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick private void layoutPunctuationSuggestions(final SuggestedWords suggestedWords, final ViewGroup stripView) { final int countInStrip = Math.min(suggestedWords.size(), PUNCTUATIONS_IN_STRIP); - for (int index = 0; index < countInStrip; index++) { - if (index != 0) { + for (int indexInStrip = 0; indexInStrip < countInStrip; indexInStrip++) { + if (indexInStrip != 0) { // Add divider if this isn't the left most suggestion in suggestions strip. - addDivider(stripView, mDividers.get(index)); + addDivider(stripView, mDividers.get(indexInStrip)); } - final TextView word = mWords.get(index); + final TextView word = mWords.get(indexInStrip); word.setEnabled(true); word.setTextColor(mColorAutoCorrect); - final String text = suggestedWords.getWord(index); + final String text = suggestedWords.getWord(indexInStrip); word.setText(text); word.setTextScaleX(1.0f); word.setCompoundDrawables(null, null, null, null); diff --git a/java/src/com/android/inputmethod/research/LogUnit.java b/java/src/com/android/inputmethod/research/LogUnit.java index cf1388f46..164c7e8cc 100644 --- a/java/src/com/android/inputmethod/research/LogUnit.java +++ b/java/src/com/android/inputmethod/research/LogUnit.java @@ -67,7 +67,7 @@ public class LogUnit { private String[] mWordArray = EMPTY_STRING_ARRAY; private boolean mMayContainDigit; private boolean mIsPartOfMegaword; - private boolean mContainsCorrection; + private boolean mContainsUserDeletions; // mCorrectionType indicates whether the word was corrected at all, and if so, the nature of the // correction. @@ -277,13 +277,13 @@ public class LogUnit { } // TODO: Refactor to eliminate getter/setters - public void setContainsCorrection() { - mContainsCorrection = true; + public void setContainsUserDeletions() { + mContainsUserDeletions = true; } // TODO: Refactor to eliminate getter/setters - public boolean containsCorrection() { - return mContainsCorrection; + public boolean containsUserDeletions() { + return mContainsUserDeletions; } // TODO: Refactor to eliminate getter/setters @@ -323,7 +323,7 @@ public class LogUnit { true /* isPartOfMegaword */); newLogUnit.mWords = null; newLogUnit.mMayContainDigit = mMayContainDigit; - newLogUnit.mContainsCorrection = mContainsCorrection; + newLogUnit.mContainsUserDeletions = mContainsUserDeletions; // Purge the logStatements and associated data from this LogUnit. laterLogStatements.clear(); @@ -346,7 +346,7 @@ public class LogUnit { setWords(logUnit.mWords); } mMayContainDigit = mMayContainDigit || logUnit.mMayContainDigit; - mContainsCorrection = mContainsCorrection || logUnit.mContainsCorrection; + mContainsUserDeletions = mContainsUserDeletions || logUnit.mContainsUserDeletions; mIsPartOfMegaword = false; } diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java index cbdde0dc6..56ab90cb4 100644 --- a/java/src/com/android/inputmethod/research/ResearchLogger.java +++ b/java/src/com/android/inputmethod/research/ResearchLogger.java @@ -260,14 +260,14 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang if (DEBUG) { final String wordsString = logUnit.getWordsAsString(); Log.d(TAG, "onPublish: '" + wordsString - + "', hc: " + logUnit.containsCorrection() + + "', hc: " + logUnit.containsUserDeletions() + ", cipd: " + canIncludePrivateData); } for (final String word : logUnit.getWordsAsStringArray()) { final Dictionary dictionary = getDictionary(); mStatistics.recordWordEntered( dictionary != null && dictionary.isValidWord(word), - logUnit.containsCorrection()); + logUnit.containsUserDeletions()); } } publishLogUnits(logUnits, mMainResearchLog, canIncludePrivateData); @@ -819,8 +819,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang mCurrentLogUnit.setMayContainDigit(); } - private void setCurrentLogUnitContainsCorrection() { - mCurrentLogUnit.setContainsCorrection(); + private void setCurrentLogUnitContainsUserDeletions() { + mCurrentLogUnit.setContainsUserDeletions(); } private void setCurrentLogUnitCorrectionType(final int correctionType) { @@ -920,7 +920,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang if (DEBUG) { Log.d(TAG, "publishLogBuffer: " + (logUnit.hasOneOrMoreWords() ? logUnit.getWordsAsString() : "<wordless>") - + ", correction?: " + logUnit.containsCorrection()); + + ", correction?: " + logUnit.containsUserDeletions()); } researchLog.publish(logUnit, canIncludePrivateData); } @@ -1286,7 +1286,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final ResearchLogger researchLogger = getInstance(); if (!replacedWord.equals(suggestion.toString())) { // The user chose something other than what was already there. - researchLogger.setCurrentLogUnitContainsCorrection(); + researchLogger.setCurrentLogUnitContainsUserDeletions(); researchLogger.setCurrentLogUnitCorrectionType(LogUnit.CORRECTIONTYPE_TYPO); } final String scrubbedWord = scrubDigitsFromString(suggestion); @@ -1463,7 +1463,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang LOGSTATEMENT_LATINIME_REVERTCOMMIT, committedWord, originallyTypedWord, separatorString); if (logUnit != null) { - logUnit.setContainsCorrection(); + logUnit.setContainsUserDeletions(); } researchLogger.mStatistics.recordRevertCommit(SystemClock.uptimeMillis()); } |