diff options
Diffstat (limited to 'java/src/com/android/inputmethod/latin')
22 files changed, 981 insertions, 701 deletions
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 99b95ea98..85b14d849 100644 --- a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java +++ b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java @@ -49,13 +49,14 @@ public final class AdditionalSubtype { && SubtypeLocale.isExceptionalLocale(localeString)) { final String layoutDisplayName = SubtypeLocale.getKeyboardLayoutSetDisplayName( keyboardLayoutSetName); - layoutDisplayNameExtraValue = StringUtils.appendToCsvIfNotExists( + layoutDisplayNameExtraValue = StringUtils.appendToCommaSplittableTextIfNotExists( UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME + "=" + layoutDisplayName, extraValue); } else { layoutDisplayNameExtraValue = extraValue; } - final String additionalSubtypeExtraValue = StringUtils.appendToCsvIfNotExists( - IS_ADDITIONAL_SUBTYPE, layoutDisplayNameExtraValue); + final String additionalSubtypeExtraValue = + StringUtils.appendToCommaSplittableTextIfNotExists( + IS_ADDITIONAL_SUBTYPE, layoutDisplayNameExtraValue); final int nameId = SubtypeLocale.getSubtypeNameId(localeString, keyboardLayoutSetName); return new InputMethodSubtype(nameId, R.drawable.ic_subtype_keyboard, localeString, KEYBOARD_MODE, @@ -66,8 +67,9 @@ 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.removeFromCsvIfExists(layoutExtraValue, - StringUtils.removeFromCsvIfExists(IS_ADDITIONAL_SUBTYPE, subtype.getExtraValue())); + final String extraValue = StringUtils.removeFromCommaSplittableTextIfExists( + layoutExtraValue, StringUtils.removeFromCommaSplittableTextIfExists( + IS_ADDITIONAL_SUBTYPE, subtype.getExtraValue())); final String basePrefSubtype = localeString + LOCALE_AND_LAYOUT_SEPARATOR + keyboardLayoutSetName; return extraValue.isEmpty() ? basePrefSubtype diff --git a/java/src/com/android/inputmethod/latin/AutoCorrection.java b/java/src/com/android/inputmethod/latin/AutoCorrection.java index fa35922b0..86be4295a 100644 --- a/java/src/com/android/inputmethod/latin/AutoCorrection.java +++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java @@ -32,12 +32,13 @@ public final class AutoCorrection { // Purely static class: can't instantiate. } - public static boolean isValidWord(final ConcurrentHashMap<String, Dictionary> dictionaries, - final String word, final boolean ignoreCase) { + public static boolean isValidWord(final Suggest suggest, final String word, + final boolean ignoreCase) { if (TextUtils.isEmpty(word)) { return false; } - final String lowerCasedWord = word.toLowerCase(); + final ConcurrentHashMap<String, Dictionary> dictionaries = suggest.getUnigramDictionaries(); + final String lowerCasedWord = word.toLowerCase(suggest.mLocale); for (final String key : dictionaries.keySet()) { final Dictionary dictionary = dictionaries.get(key); // It's unclear how realistically 'dictionary' can be null, but the monkey is somehow @@ -73,13 +74,6 @@ public final class AutoCorrection { return maxFreq; } - // Returns true if this is in any of the dictionaries. - public static boolean isInTheDictionary( - final ConcurrentHashMap<String, Dictionary> dictionaries, - final String word, final boolean ignoreCase) { - return isValidWord(dictionaries, word, ignoreCase); - } - public static boolean suggestionExceedsAutoCorrectionThreshold( final SuggestedWordInfo suggestion, final String consideredWord, final float autoCorrectionThreshold) { diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index 4fc1919dc..aad129d76 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -45,7 +45,7 @@ public final class BinaryDictionary extends Dictionary { private final int[] mOutputScores = new int[MAX_RESULTS]; private final int[] mOutputTypes = new int[MAX_RESULTS]; - private final boolean mUseFullEditDistance; + private final NativeSuggestOptions mNativeSuggestOptions = new NativeSuggestOptions(); private final SparseArray<DicTraverseSession> mDicTraverseSessions = CollectionUtils.newSparseArray(); @@ -79,7 +79,7 @@ public final class BinaryDictionary extends Dictionary { final boolean useFullEditDistance, final Locale locale, final String dictType) { super(dictType); mLocale = locale; - mUseFullEditDistance = useFullEditDistance; + mNativeSuggestOptions.setUseFullEditDistance(useFullEditDistance); loadDictionary(filename, offset, length); } @@ -94,7 +94,7 @@ public final class BinaryDictionary extends Dictionary { private static native int getSuggestionsNative(long dict, long proximityInfo, long traverseSession, int[] xCoordinates, int[] yCoordinates, int[] times, int[] pointerIds, int[] inputCodePoints, int inputSize, int commitPoint, - boolean isGesture, int[] prevWordCodePointArray, boolean useFullEditDistance, + int[] suggestOptions, int[] prevWordCodePointArray, int[] outputCodePoints, int[] outputScores, int[] outputIndices, int[] outputTypes); private static native float calcNormalizedScoreNative(int[] before, int[] after, int score); private static native int editDistanceNative(int[] before, int[] after); @@ -135,12 +135,15 @@ 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(), ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(), mInputCodePoints, - inputSize, 0 /* commitPoint */, isGesture, prevWordCodePointArray, - mUseFullEditDistance, mOutputCodePoints, mOutputScores, mSpaceIndices, + inputSize, 0 /* commitPoint */, mNativeSuggestOptions.getOptions(), + prevWordCodePointArray, mOutputCodePoints, mOutputScores, mSpaceIndices, mOutputTypes); final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList(); for (int j = 0; j < count; ++j) { diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index a9b58de44..c038db87c 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -32,6 +32,7 @@ import com.android.inputmethod.latin.DictionaryInfoUtils.DictionaryInfo; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; +import java.io.Closeable; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; @@ -319,20 +320,12 @@ public final class BinaryDictionaryFileDumper { // Try the next method. } finally { // Ignore exceptions while closing files. - try { - if (null != afd) afd.close(); - if (null != inputStream) inputStream.close(); - if (null != uncompressedStream) uncompressedStream.close(); - if (null != decryptedStream) decryptedStream.close(); - if (null != bufferedInputStream) bufferedInputStream.close(); - } catch (Exception e) { - Log.e(TAG, "Exception while closing a file descriptor", e); - } - try { - if (null != bufferedOutputStream) bufferedOutputStream.close(); - } catch (Exception e) { - Log.e(TAG, "Exception while closing a file", e); - } + closeAssetFileDescriptorAndReportAnyException(afd); + closeCloseableAndReportAnyException(inputStream); + closeCloseableAndReportAnyException(uncompressedStream); + closeCloseableAndReportAnyException(decryptedStream); + closeCloseableAndReportAnyException(bufferedInputStream); + closeCloseableAndReportAnyException(bufferedOutputStream); } } @@ -352,6 +345,26 @@ public final class BinaryDictionaryFileDumper { } } + // Ideally the two following methods should be merged, but AssetFileDescriptor does not + // implement Closeable although it does implement #close(), and Java does not have + // structural typing. + private static void closeAssetFileDescriptorAndReportAnyException( + final AssetFileDescriptor file) { + try { + if (null != file) file.close(); + } catch (Exception e) { + Log.e(TAG, "Exception while closing a file", e); + } + } + + private static void closeCloseableAndReportAnyException(final Closeable file) { + try { + if (null != file) file.close(); + } catch (Exception e) { + Log.e(TAG, "Exception while closing a file", e); + } + } + /** * Queries a content provider for word list data for some locale and cache the returned files * @@ -363,8 +376,14 @@ public final class BinaryDictionaryFileDumper { */ public static void cacheWordListsFromContentProvider(final Locale locale, final Context context, final boolean hasDefaultWordList) { - final ContentProviderClient providerClient = context.getContentResolver(). + final ContentProviderClient providerClient; + try { + providerClient = context.getContentResolver(). acquireContentProviderClient(getProviderUriBuilder("").build()); + } catch (final SecurityException e) { + Log.e(TAG, "No permission to communicate with the dictionary provider", e); + return; + } if (null == providerClient) { Log.e(TAG, "Can't establish communication with the dictionary provider"); return; 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 dd58db575..1f673e9b0 100644 --- a/java/src/com/android/inputmethod/latin/InputAttributes.java +++ b/java/src/com/android/inputmethod/latin/InputAttributes.java @@ -199,6 +199,6 @@ public final class InputAttributes { if (editorInfo == null) return false; final String findingKey = (packageName != null) ? packageName + "." + key : key; - return StringUtils.containsInCsv(findingKey, editorInfo.privateImeOptions); + return StringUtils.containsInCommaSplittableText(findingKey, editorInfo.privateImeOptions); } } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index c464a7067..c9a42a3a4 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -480,6 +480,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final InputAttributes inputAttributes = new InputAttributes(getCurrentInputEditorInfo(), isFullscreenMode()); mSettings.loadSettings(locale, inputAttributes); + AudioAndHapticFeedbackManager.getInstance().onSettingsChanged(mSettings.getCurrent()); // May need to reset the contacts dictionary depending on the user settings. resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary()); } @@ -854,6 +855,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } // Remove pending messages related to update suggestions mHandler.cancelUpdateSuggestionStrip(); + if (mWordComposer.isComposingWord()) mConnection.finishComposingText(); resetComposingState(true /* alsoResetLastComposedWord */); // Notify ResearchLogger if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { @@ -2020,9 +2022,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Returns true if we did an autocorrection, false otherwise. private boolean handleSeparator(final int primaryCode, final int x, final int y, final int spaceState) { - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_handleSeparator(primaryCode, mWordComposer.isComposingWord()); - } boolean didAutoCorrect = false; if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) { // If we are in the middle of a recorrection, we need to commit the recorrection @@ -2046,6 +2045,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mSettings.getCurrent().isUsuallyPrecededBySpace(primaryCode)) { promotePhantomSpace(); } + if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { + ResearchLogger.latinIME_handleSeparator(primaryCode, mWordComposer.isComposingWord()); + } sendKeyCodePoint(primaryCode); if (Constants.CODE_SPACE == primaryCode) { @@ -2368,9 +2370,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Please note that if mSuggest is null, it means that everything is off: suggestion // and correction, so we shouldn't try to show the hint final boolean showingAddToDictionaryHint = - SuggestedWordInfo.KIND_TYPED == suggestionInfo.mKind && mSuggest != null - // If the suggestion is not in the dictionary, the hint should be shown. - && !AutoCorrection.isValidWord(mSuggest.getUnigramDictionaries(), suggestion, true); + (SuggestedWordInfo.KIND_TYPED == suggestionInfo.mKind + || SuggestedWordInfo.KIND_OOV_CORRECTION == suggestionInfo.mKind) + && mSuggest != null + // If the suggestion is not in the dictionary, the hint should be shown. + && !AutoCorrection.isValidWord(mSuggest, suggestion, true); if (mSettings.isInternal()) { Stats.onSeparator((char)Constants.CODE_SPACE, @@ -2585,8 +2589,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.latinIME_revertCommit(committedWord, originallyTypedWord, mWordComposer.isBatchMode(), mLastComposedWord.mSeparatorString); - ResearchLogger.getInstance().uncommitCurrentLogUnit(committedWord, - true /* dumpCurrentLogUnit */); } // Don't restart suggestion yet. We'll restart if the user deletes the // separator. @@ -2599,10 +2601,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen public void promotePhantomSpace() { if (mSettings.getCurrent().shouldInsertSpacesAutomatically() && !mConnection.textBeforeCursorLooksLikeURL()) { - sendKeyCodePoint(Constants.CODE_SPACE); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.latinIME_promotePhantomSpace(); } + sendKeyCodePoint(Constants.CODE_SPACE); } } @@ -2703,7 +2705,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { mSubtypeSwitcher.onNetworkStateChanged(intent); } else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) { - mKeyboardSwitcher.onRingerModeChanged(); + AudioAndHapticFeedbackManager.getInstance().onRingerModeChanged(); } } }; diff --git a/java/src/com/android/inputmethod/latin/LocaleUtils.java b/java/src/com/android/inputmethod/latin/LocaleUtils.java index 5fde8158a..a1e40502e 100644 --- a/java/src/com/android/inputmethod/latin/LocaleUtils.java +++ b/java/src/com/android/inputmethod/latin/LocaleUtils.java @@ -148,7 +148,7 @@ public final class LocaleUtils { public static String getMatchLevelSortedString(int matchLevel) { // This works because the match levels are 0~99 (actually 0~30) // Ideally this should use a number of digits equals to the 1og10 of the greater matchLevel - return String.format("%02d", MATCH_LEVEL_MAX - matchLevel); + return String.format(Locale.ROOT, "%02d", MATCH_LEVEL_MAX - matchLevel); } /** diff --git a/java/src/com/android/inputmethod/latin/NativeSuggestOptions.java b/java/src/com/android/inputmethod/latin/NativeSuggestOptions.java new file mode 100644 index 000000000..291551301 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/NativeSuggestOptions.java @@ -0,0 +1,53 @@ +/* + * 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; + +public class NativeSuggestOptions { + // Need to update suggest_options.h when you add, remove or reorder options. + private static final int IS_GESTURE = 0; + private static final int USE_FULL_EDIT_DISTANCE = 1; + private static final int OPTIONS_SIZE = 2; + + private final int[] mOptions = new int[OPTIONS_SIZE + + AdditionalFeaturesSettingUtils.ADDITIONAL_FEATURES_SETTINGS_SIZE]; + + public void setIsGesture(final boolean value) { + setBooleanOption(IS_GESTURE, value); + } + + public void setUseFullEditDistance(final boolean value) { + 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; + } + + 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 ab050d7a3..c2fd4fb32 100644 --- a/java/src/com/android/inputmethod/latin/StringUtils.java +++ b/java/src/com/android/inputmethod/latin/StringUtils.java @@ -35,33 +35,55 @@ public final class StringUtils { return text.codePointCount(0, text.length()); } - public static boolean containsInArray(final String key, final String[] array) { + public static boolean containsInArray(final String text, final String[] array) { for (final String element : array) { - if (key.equals(element)) return true; + if (text.equals(element)) return true; } return false; } - public static boolean containsInCsv(final String key, final String csv) { - if (TextUtils.isEmpty(csv)) return false; - return containsInArray(key, csv.split(",")); + /** + * 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 containsInCommaSplittableText(final String text, + final String extraValues) { + if (TextUtils.isEmpty(extraValues)) { + return false; + } + return containsInArray(text, extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT)); } - public static String appendToCsvIfNotExists(final String key, final String csv) { - if (TextUtils.isEmpty(csv)) return key; - if (containsInCsv(key, csv)) return csv; - return csv + "," + key; + 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 removeFromCsvIfExists(final String key, final String csv) { - if (TextUtils.isEmpty(csv)) return ""; - final String[] elements = csv.split(","); - if (!containsInArray(key, elements)) return csv; + public static String removeFromCommaSplittableTextIfExists(final String text, + final String extraValues) { + if (TextUtils.isEmpty(extraValues)) { + return ""; + } + final String[] elements = extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT); + if (!containsInArray(text, elements)) { + return extraValues; + } final ArrayList<String> result = CollectionUtils.newArrayList(elements.length - 1); for (final String element : elements) { - if (!key.equals(element)) result.add(element); + if (!text.equals(element)) { + result.add(element); + } } - return TextUtils.join(",", result); + return TextUtils.join(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT, result); } /** @@ -131,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/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index dc9bef22a..5d580f29b 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -229,7 +229,7 @@ public final class Suggest { // or if it's a 2+ characters non-word (i.e. it's not in the dictionary). final boolean allowsToBeAutoCorrected = (null != whitelistedWord && !whitelistedWord.equals(consideredWord)) - || (consideredWord.length() > 1 && !AutoCorrection.isInTheDictionary(mDictionaries, + || (consideredWord.length() > 1 && !AutoCorrection.isValidWord(this, consideredWord, wordComposer.isFirstCharCapitalized())); final boolean hasAutoCorrection; @@ -379,7 +379,8 @@ public final class Suggest { typedWord, cur.toString(), cur.mScore); final String scoreInfoString; if (normalizedScore > 0) { - scoreInfoString = String.format("%d (%4.2f)", cur.mScore, normalizedScore); + scoreInfoString = String.format( + Locale.ROOT, "%d (%4.2f)", cur.mScore, normalizedScore); } else { scoreInfoString = Integer.toString(cur.mScore); } diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java index dfddb0ffe..1f453273b 100644 --- a/java/src/com/android/inputmethod/latin/SuggestedWords.java +++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java @@ -132,7 +132,10 @@ public final class SuggestedWords { public static final int KIND_APP_DEFINED = 6; // Suggested by the application public static final int KIND_SHORTCUT = 7; // A shortcut public static final int KIND_PREDICTION = 8; // A prediction (== a suggestion with no input) - public static final int KIND_RESUMED = 9; // A resumed suggestion (comes from a span) + // KIND_RESUMED: A resumed suggestion (comes from a span, currently this type is used only + // in java for re-correction) + public static final int KIND_RESUMED = 9; + public static final int KIND_OOV_CORRECTION = 10; // Most probable string correction public static final int KIND_MASK_FLAGS = 0xFFFFFF00; // Mask to get the flags public static final int KIND_FLAG_POSSIBLY_OFFENSIVE = 0x80000000; diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java index 0f96c54dc..949720fda 100644 --- a/java/src/com/android/inputmethod/latin/Utils.java +++ b/java/src/com/android/inputmethod/latin/Utils.java @@ -64,7 +64,8 @@ public final class Utils { * task should be interrupted; otherwise, in-progress tasks are allowed * to complete. */ - public static void cancelTask(AsyncTask<?, ?, ?> task, boolean mayInterruptIfRunning) { + public static void cancelTask(final AsyncTask<?, ?, ?> task, + final boolean mayInterruptIfRunning) { if (task != null && task.getStatus() != AsyncTask.Status.FINISHED) { task.cancel(mayInterruptIfRunning); } @@ -86,26 +87,34 @@ public final class Utils { private RingCharBuffer() { // Intentional empty constructor for singleton. } + @UsedForTesting public static RingCharBuffer getInstance() { return sRingCharBuffer; } - public static RingCharBuffer init(InputMethodService context, boolean enabled, - boolean usabilityStudy) { - if (!(enabled || usabilityStudy)) return null; + + public static RingCharBuffer init(final InputMethodService context, final boolean enabled, + final boolean usabilityStudy) { + if (!(enabled || usabilityStudy)) { + return null; + } sRingCharBuffer.mContext = context; sRingCharBuffer.mEnabled = true; UsabilityStudyLogUtils.getInstance().init(context); return sRingCharBuffer; } - private static int normalize(int in) { + + private static int normalize(final int in) { int ret = in % BUFSIZE; return ret < 0 ? ret + BUFSIZE : ret; } + // TODO: accept code points @UsedForTesting - public void push(char c, int x, int y) { - if (!mEnabled) return; + public void push(final char c, final int x, final int y) { + if (!mEnabled) { + return; + } mCharBuf[mEnd] = c; mXBuf[mEnd] = x; mYBuf[mEnd] = y; @@ -114,52 +123,54 @@ public final class Utils { ++mLength; } } + public char pop() { if (mLength < 1) { return PLACEHOLDER_DELIMITER_CHAR; - } else { - mEnd = normalize(mEnd - 1); - --mLength; - return mCharBuf[mEnd]; } + mEnd = normalize(mEnd - 1); + --mLength; + return mCharBuf[mEnd]; } - public char getBackwardNthChar(int n) { + + public char getBackwardNthChar(final int n) { if (mLength <= n || n < 0) { return PLACEHOLDER_DELIMITER_CHAR; - } else { - return mCharBuf[normalize(mEnd - n - 1)]; } + return mCharBuf[normalize(mEnd - n - 1)]; } - public int getPreviousX(char c, int back) { - int index = normalize(mEnd - 2 - back); + + public int getPreviousX(final char c, final int back) { + final int index = normalize(mEnd - 2 - back); if (mLength <= back || Character.toLowerCase(c) != Character.toLowerCase(mCharBuf[index])) { return INVALID_COORDINATE; - } else { - return mXBuf[index]; } + return mXBuf[index]; } - public int getPreviousY(char c, int back) { + + public int getPreviousY(final char c, final int back) { int index = normalize(mEnd - 2 - back); if (mLength <= back || Character.toLowerCase(c) != Character.toLowerCase(mCharBuf[index])) { return INVALID_COORDINATE; - } else { - return mYBuf[index]; } + return mYBuf[index]; } - public String getLastWord(int ignoreCharCount) { - StringBuilder sb = new StringBuilder(); + + public String getLastWord(final int ignoreCharCount) { + final StringBuilder sb = new StringBuilder(); + final LatinIME latinIme = (LatinIME)mContext; int i = ignoreCharCount; for (; i < mLength; ++i) { - char c = mCharBuf[normalize(mEnd - 1 - i)]; - if (!((LatinIME)mContext).isWordSeparator(c)) { + final char c = mCharBuf[normalize(mEnd - 1 - i)]; + if (!latinIme.isWordSeparator(c)) { break; } } for (; i < mLength; ++i) { char c = mCharBuf[normalize(mEnd - 1 - i)]; - if (!((LatinIME)mContext).isWordSeparator(c)) { + if (!latinIme.isWordSeparator(c)) { sb.append(c); } else { break; @@ -167,6 +178,7 @@ public final class Utils { } return sb.reverse().toString(); } + public void reset() { mLength = 0; } @@ -174,11 +186,11 @@ public final class Utils { // Get the current stack trace public static String getStackTrace(final int limit) { - StringBuilder sb = new StringBuilder(); + final StringBuilder sb = new StringBuilder(); try { throw new RuntimeException(); - } catch (RuntimeException e) { - StackTraceElement[] frames = e.getStackTrace(); + } catch (final RuntimeException e) { + final StackTraceElement[] frames = e.getStackTrace(); // Start at 1 because the first frame is here and we don't care about it for (int j = 1; j < frames.length && j < limit + 1; ++j) { sb.append(frames[j].toString() + "\n"); @@ -222,7 +234,7 @@ public final class Utils { return OnDemandInitializationHolder.sInstance; } - public void init(InputMethodService ims) { + public void init(final InputMethodService ims) { mIms = ims; mDirectory = ims.getFilesDir(); } @@ -232,17 +244,17 @@ public final class Utils { && (mDirectory != null && mDirectory.exists())) { try { mWriter = getPrintWriter(mDirectory, FILENAME, false); - } catch (IOException e) { + } catch (final IOException e) { Log.e(USABILITY_TAG, "Can't create log file."); } } } - public static void writeBackSpace(int x, int y) { + public static void writeBackSpace(final int x, final int y) { UsabilityStudyLogUtils.getInstance().write("<backspace>\t" + x + "\t" + y); } - public void writeChar(char c, int x, int y) { + public static void writeChar(final char c, final int x, final int y) { String inputChar = String.valueOf(c); switch (c) { case '\n': @@ -279,15 +291,15 @@ public final class Utils { private synchronized String getBufferedLogs() { mWriter.flush(); - StringBuilder sb = new StringBuilder(); - BufferedReader br = getBufferedReader(); + final StringBuilder sb = new StringBuilder(); + final BufferedReader br = getBufferedReader(); String line; try { while ((line = br.readLine()) != null) { sb.append('\n'); sb.append(line); } - } catch (IOException e) { + } catch (final IOException e) { Log.e(USABILITY_TAG, "Can't read log file."); } finally { if (LatinImeLogger.sDBG) { @@ -295,7 +307,7 @@ public final class Utils { } try { br.close(); - } catch (IOException e) { + } catch (final IOException e) { // ignore. } } @@ -334,10 +346,10 @@ public final class Utils { srcStream.close(); dest.close(); destStream.close(); - } catch (FileNotFoundException e1) { + } catch (final FileNotFoundException e1) { Log.w(USABILITY_TAG, e1); return; - } catch (IOException e2) { + } catch (final IOException e2) { Log.w(USABILITY_TAG, e2); return; } @@ -387,13 +399,13 @@ public final class Utils { createLogFileIfNotExist(); try { return new BufferedReader(new FileReader(mFile)); - } catch (FileNotFoundException e) { + } catch (final FileNotFoundException e) { return null; } } - private PrintWriter getPrintWriter( - File dir, String filename, boolean renew) throws IOException { + private PrintWriter getPrintWriter(final File dir, final String filename, + final boolean renew) throws IOException { mFile = new File(dir, filename); if (mFile.exists()) { if (renew) { @@ -405,8 +417,7 @@ public final class Utils { } public static final class Stats { - public static void onNonSeparator(final char code, final int x, - final int y) { + public static void onNonSeparator(final char code, final int x, final int y) { RingCharBuffer.getInstance().push(code, x, y); LatinImeLogger.logOnInputChar(); } @@ -430,7 +441,9 @@ public final class Utils { public static void onAutoCorrection(final String typedWord, final String correctedWord, final String separatorString, final WordComposer wordComposer) { final boolean isBatchMode = wordComposer.isBatchMode(); - if (!isBatchMode && TextUtils.isEmpty(typedWord)) return; + if (!isBatchMode && TextUtils.isEmpty(typedWord)) { + return; + } // TODO: this fails when the separator is more than 1 code point long, but // the backend can't handle it yet. The only case when this happens is with // smileys and other multi-character keys. @@ -454,36 +467,43 @@ public final class Utils { } public static String getDebugInfo(final SuggestedWords suggestions, final int pos) { - if (!LatinImeLogger.sDBG) return null; + if (!LatinImeLogger.sDBG) { + return null; + } final SuggestedWordInfo wordInfo = suggestions.getInfo(pos); - if (wordInfo == null) return null; + if (wordInfo == null) { + return null; + } final String info = wordInfo.getDebugString(); - if (TextUtils.isEmpty(info)) return null; + if (TextUtils.isEmpty(info)) { + return null; + } return info; } - public static int getAcitivityTitleResId(Context context, Class<? extends Activity> cls) { + public static int getAcitivityTitleResId(final Context context, + final Class<? extends Activity> cls) { final ComponentName cn = new ComponentName(context, cls); try { final ActivityInfo ai = context.getPackageManager().getActivityInfo(cn, 0); if (ai != null) { return ai.labelRes; } - } catch (NameNotFoundException e) { + } catch (final NameNotFoundException e) { Log.e(TAG, "Failed to get settings activity title res id.", e); } return 0; } - public static String getVersionName(Context context) { + public static String getVersionName(final Context context) { try { if (context == null) { return ""; } final String packageName = context.getPackageName(); - PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0); + final PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0); return info.versionName; - } catch (NameNotFoundException e) { + } catch (final NameNotFoundException e) { Log.e(TAG, "Could not find version info.", e); } return ""; 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/setup/LauncherIconVisibilityManager.java b/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java index 6a7cd9b6f..604ebeeb6 100644 --- a/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java +++ b/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java @@ -91,7 +91,7 @@ public final class LauncherIconVisibilityManager extends BroadcastReceiver { } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { Log.i(TAG, "Boot has been completed"); return true; - } else if (IntentCompatUtils.has_ACTION_USER_INITIALIZE(intent)) { + } else if (IntentCompatUtils.is_ACTION_USER_INITIALIZE(action)) { Log.i(TAG, "User initialize"); return true; } diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java new file mode 100644 index 000000000..d9bb3eaf4 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java @@ -0,0 +1,556 @@ +/* + * 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.suggestions; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.graphics.Paint.Align; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.Spanned; +import android.text.TextPaint; +import android.text.TextUtils; +import android.text.style.CharacterStyle; +import android.text.style.StyleSpan; +import android.text.style.UnderlineSpan; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.View.OnClickListener; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.inputmethod.keyboard.ViewLayoutUtils; +import com.android.inputmethod.latin.AutoCorrection; +import com.android.inputmethod.latin.CollectionUtils; +import com.android.inputmethod.latin.LatinImeLogger; +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.ResourceUtils; +import com.android.inputmethod.latin.SuggestedWords; +import com.android.inputmethod.latin.Utils; + +import java.util.ArrayList; + +final class SuggestionStripLayoutHelper { + private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3; + private static final float DEFAULT_CENTER_SUGGESTION_PERCENTILE = 0.40f; + private static final int DEFAULT_MAX_MORE_SUGGESTIONS_ROW = 2; + private static final int PUNCTUATIONS_IN_STRIP = 5; + private static final float MIN_TEXT_XSCALE = 0.70f; + + public final int mPadding; + public final int mDividerWidth; + public final int mSuggestionsStripHeight; + public final int mSuggestionsCountInStrip; + public final int mMoreSuggestionsRowHeight; + private int mMaxMoreSuggestionsRow; + public final float mMinMoreSuggestionsWidth; + public final int mMoreSuggestionsBottomGap; + + private final ArrayList<TextView> mWordViews; + private final ArrayList<View> mDividerViews; + private final ArrayList<TextView> mDebugInfoViews; + + private final int mColorValidTypedWord; + private final int mColorTypedWord; + private final int mColorAutoCorrect; + private final int mColorSuggested; + private final float mAlphaObsoleted; + private final float mCenterSuggestionWeight; + private final int mCenterPositionInStrip; + private final Drawable mMoreSuggestionsHint; + private static final String MORE_SUGGESTIONS_HINT = "\u2026"; + private static final String LEFTWARDS_ARROW = "\u2190"; + + private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD); + private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan(); + private static final int AUTO_CORRECT_BOLD = 0x01; + private static final int AUTO_CORRECT_UNDERLINE = 0x02; + private static final int VALID_TYPED_WORD_BOLD = 0x04; + + private final int mSuggestionStripOption; + + private final ArrayList<CharSequence> mWords = CollectionUtils.newArrayList(); + + public boolean mMoreSuggestionsAvailable; + + private final TextView mWordToSaveView; + private final TextView mLeftwardsArrowView; + private final TextView mHintToSaveView; + + public SuggestionStripLayoutHelper(final Context context, final AttributeSet attrs, + final int defStyle, final ArrayList<TextView> wordViews, + final ArrayList<View> dividerViews, final ArrayList<TextView> debugInfoViews) { + mWordViews = wordViews; + mDividerViews = dividerViews; + mDebugInfoViews = debugInfoViews; + + final TextView wordView = wordViews.get(0); + final View dividerView = dividerViews.get(0); + mPadding = wordView.getCompoundPaddingLeft() + wordView.getCompoundPaddingRight(); + dividerView.measure( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + mDividerWidth = dividerView.getMeasuredWidth(); + + final Resources res = wordView.getResources(); + mSuggestionsStripHeight = res.getDimensionPixelSize(R.dimen.suggestions_strip_height); + + final TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.SuggestionStripView, defStyle, R.style.SuggestionStripViewStyle); + mSuggestionStripOption = a.getInt( + R.styleable.SuggestionStripView_suggestionStripOption, 0); + final float alphaValidTypedWord = ResourceUtils.getFraction(a, + R.styleable.SuggestionStripView_alphaValidTypedWord, 1.0f); + final float alphaTypedWord = ResourceUtils.getFraction(a, + R.styleable.SuggestionStripView_alphaTypedWord, 1.0f); + final float alphaAutoCorrect = ResourceUtils.getFraction(a, + R.styleable.SuggestionStripView_alphaAutoCorrect, 1.0f); + final float alphaSuggested = ResourceUtils.getFraction(a, + R.styleable.SuggestionStripView_alphaSuggested, 1.0f); + mAlphaObsoleted = ResourceUtils.getFraction(a, + R.styleable.SuggestionStripView_alphaSuggested, 1.0f); + mColorValidTypedWord = applyAlpha(a.getColor( + R.styleable.SuggestionStripView_colorValidTypedWord, 0), alphaValidTypedWord); + mColorTypedWord = applyAlpha(a.getColor( + R.styleable.SuggestionStripView_colorTypedWord, 0), alphaTypedWord); + mColorAutoCorrect = applyAlpha(a.getColor( + R.styleable.SuggestionStripView_colorAutoCorrect, 0), alphaAutoCorrect); + mColorSuggested = applyAlpha(a.getColor( + R.styleable.SuggestionStripView_colorSuggested, 0), alphaSuggested); + mSuggestionsCountInStrip = a.getInt( + R.styleable.SuggestionStripView_suggestionsCountInStrip, + DEFAULT_SUGGESTIONS_COUNT_IN_STRIP); + mCenterSuggestionWeight = ResourceUtils.getFraction(a, + R.styleable.SuggestionStripView_centerSuggestionPercentile, + DEFAULT_CENTER_SUGGESTION_PERCENTILE); + mMaxMoreSuggestionsRow = a.getInt( + R.styleable.SuggestionStripView_maxMoreSuggestionsRow, + DEFAULT_MAX_MORE_SUGGESTIONS_ROW); + mMinMoreSuggestionsWidth = ResourceUtils.getFraction(a, + R.styleable.SuggestionStripView_minMoreSuggestionsWidth, 1.0f); + a.recycle(); + + mMoreSuggestionsHint = getMoreSuggestionsHint(res, + res.getDimension(R.dimen.more_suggestions_hint_text_size), mColorAutoCorrect); + mCenterPositionInStrip = mSuggestionsCountInStrip / 2; + mMoreSuggestionsBottomGap = res.getDimensionPixelOffset( + R.dimen.more_suggestions_bottom_gap); + mMoreSuggestionsRowHeight = res.getDimensionPixelSize(R.dimen.more_suggestions_row_height); + + final LayoutInflater inflater = LayoutInflater.from(context); + mWordToSaveView = (TextView)inflater.inflate(R.layout.suggestion_word, null); + mLeftwardsArrowView = (TextView)inflater.inflate(R.layout.hint_add_to_dictionary, null); + mHintToSaveView = (TextView)inflater.inflate(R.layout.hint_add_to_dictionary, null); + } + + public int getMaxMoreSuggestionsRow() { + return mMaxMoreSuggestionsRow; + } + + private int getMoreSuggestionsHeight() { + return mMaxMoreSuggestionsRow * mMoreSuggestionsRowHeight + mMoreSuggestionsBottomGap; + } + + public int setMoreSuggestionsHeight(final int remainingHeight) { + final int currentHeight = getMoreSuggestionsHeight(); + if (currentHeight <= remainingHeight) { + return currentHeight; + } + + mMaxMoreSuggestionsRow = (remainingHeight - mMoreSuggestionsBottomGap) + / mMoreSuggestionsRowHeight; + final int newHeight = getMoreSuggestionsHeight(); + return newHeight; + } + + private static Drawable getMoreSuggestionsHint(final Resources res, final float textSize, + final int color) { + final Paint paint = new Paint(); + paint.setAntiAlias(true); + paint.setTextAlign(Align.CENTER); + paint.setTextSize(textSize); + paint.setColor(color); + final Rect bounds = new Rect(); + paint.getTextBounds(MORE_SUGGESTIONS_HINT, 0, MORE_SUGGESTIONS_HINT.length(), bounds); + final int width = Math.round(bounds.width() + 0.5f); + final int height = Math.round(bounds.height() + 0.5f); + final Bitmap buffer = Bitmap.createBitmap(width, (height * 3 / 2), Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(buffer); + canvas.drawText(MORE_SUGGESTIONS_HINT, width / 2, height, paint); + return new BitmapDrawable(res, buffer); + } + + private CharSequence getStyledSuggestionWord(final SuggestedWords suggestedWords, + 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; + } + + final int len = word.length(); + final Spannable spannedWord = new SpannableString(word); + final int option = mSuggestionStripOption; + if ((isAutoCorrect && (option & AUTO_CORRECT_BOLD) != 0) + || (isTypedWordValid && (option & VALID_TYPED_WORD_BOLD) != 0)) { + spannedWord.setSpan(BOLD_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + } + if (isAutoCorrect && (option & AUTO_CORRECT_UNDERLINE) != 0) { + spannedWord.setSpan(UNDERLINE_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + } + return spannedWord; + } + + private int getIndexInSuggestedWords(final int positionInStrip, + final SuggestedWords suggestedWords) { + // TODO: This works for 3 suggestions. Revisit this algorithm when there are 5 or more + // suggestions. + final int mostImportantIndexInSuggestedWords = suggestedWords.willAutoCorrect() ? 1 : 0; + if (positionInStrip == mCenterPositionInStrip) { + return mostImportantIndexInSuggestedWords; + } + if (positionInStrip == mostImportantIndexInSuggestedWords) { + return mCenterPositionInStrip; + } + return positionInStrip; + } + + private int getSuggestionTextColor(final int positionInStrip, + final SuggestedWords suggestedWords) { + final int indexInSuggestedWords = getIndexInSuggestedWords( + positionInStrip, suggestedWords); + // TODO: Need to revisit this logic with bigram suggestions + final boolean isSuggested = (indexInSuggestedWords != 0); + + final int color; + if (positionInStrip == mCenterPositionInStrip && suggestedWords.willAutoCorrect()) { + color = mColorAutoCorrect; + } else if (positionInStrip == mCenterPositionInStrip && suggestedWords.mTypedWordValid) { + color = mColorValidTypedWord; + } else if (isSuggested) { + color = mColorSuggested; + } else { + color = mColorTypedWord; + } + 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 (positionInStrip == mCenterPositionInStrip + && AutoCorrection.shouldBlockAutoCorrectionBySafetyNet( + suggestedWords.getWord(1), suggestedWords.getWord(0))) { + return 0xFFFF0000; + } + } + + if (suggestedWords.mIsObsoleteSuggestions && isSuggested) { + return applyAlpha(color, mAlphaObsoleted); + } + return color; + } + + private static int applyAlpha(final int color, final float alpha) { + final int newAlpha = (int)(Color.alpha(color) * alpha); + return Color.argb(newAlpha, Color.red(color), Color.green(color), Color.blue(color)); + } + + private static void addDivider(final ViewGroup stripView, final View dividerView) { + stripView.addView(dividerView); + final LinearLayout.LayoutParams params = + (LinearLayout.LayoutParams)dividerView.getLayoutParams(); + params.gravity = Gravity.CENTER; + } + + public void layout(final SuggestedWords suggestedWords, final ViewGroup stripView, + final ViewGroup placerView) { + if (suggestedWords.mIsPunctuationSuggestions) { + layoutPunctuationSuggestions(suggestedWords, stripView); + return; + } + + final int countInStrip = mSuggestionsCountInStrip; + setupWords(suggestedWords, countInStrip); + mMoreSuggestionsAvailable = (suggestedWords.size() > countInStrip); + int x = 0; + for (int positionInStrip = 0; positionInStrip < countInStrip; positionInStrip++) { + if (positionInStrip != 0) { + final View divider = mDividerViews.get(positionInStrip); + // Add divider if this isn't the left most suggestion in suggestions strip. + addDivider(stripView, divider); + x += divider.getMeasuredWidth(); + } + + final int width = getSuggestionWidth(positionInStrip, placerView.getWidth()); + final TextView wordView = layoutWord(suggestedWords, positionInStrip, width); + stripView.addView(wordView); + setLayoutWeight(wordView, getSuggestionWeight(positionInStrip), + ViewGroup.LayoutParams.MATCH_PARENT); + x += wordView.getMeasuredWidth(); + + if (SuggestionStripView.DBG) { + layoutDebugInfo(suggestedWords, positionInStrip, placerView, x); + } + } + } + + /** + * Format appropriately the suggested word indirectly specified by + * <code>positionInStrip</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>positionInStrip</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 #mCenterPositionInStrip}. This + * usually doesn't match the index in <code>suggedtedWords</code> -- see + * {@link #getIndexInSuggestedWords(int,SuggestedWords)}. + * + * @param suggestedWords the list of suggestions. + * @param positionInStrip 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 positionInStrip, + final int width) { + final int indexInSuggestedWords = getIndexInSuggestedWords(positionInStrip, suggestedWords); + final CharSequence word = mWords.get(indexInSuggestedWords); + final TextView wordView = mWordViews.get(indexInSuggestedWords); + if (positionInStrip == mCenterPositionInStrip && mMoreSuggestionsAvailable) { + // TODO: This "more suggestions hint" should have a nicely designed icon. + wordView.setCompoundDrawablesWithIntrinsicBounds( + null, null, null, mMoreSuggestionsHint); + // HACK: Align with other TextViews that have no compound drawables. + wordView.setCompoundDrawablePadding(-mMoreSuggestionsHint.getIntrinsicHeight()); + } else { + wordView.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null); + } + + // Disable this suggestion if the suggestion is null or empty. + wordView.setEnabled(!TextUtils.isEmpty(word)); + wordView.setTextColor(getSuggestionTextColor(positionInStrip, suggestedWords)); + final CharSequence text = getEllipsizedText(word, width, wordView.getPaint()); + final float scaleX = wordView.getTextScaleX(); + wordView.setText(text); // TextView.setText() resets text scale x to 1.0. + wordView.setTextScaleX(scaleX); + return wordView; + } + + private void layoutDebugInfo(final SuggestedWords suggestedWords, final int positionInStrip, + final ViewGroup placerView, final int x) { + final int indexInSuggestedWords = getIndexInSuggestedWords(positionInStrip, suggestedWords); + if (indexInSuggestedWords >= suggestedWords.size()) { + return; + } + final String debugInfo = Utils.getDebugInfo(suggestedWords, indexInSuggestedWords); + if (debugInfo == null) { + return; + } + final TextView debugInfoView = mDebugInfoViews.get(indexInSuggestedWords); + debugInfoView.setText(debugInfo); + placerView.addView(debugInfoView); + debugInfoView.measure( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + final int infoWidth = debugInfoView.getMeasuredWidth(); + final int y = debugInfoView.getMeasuredHeight(); + ViewLayoutUtils.placeViewAt( + debugInfoView, x - infoWidth, y, infoWidth, debugInfoView.getMeasuredHeight()); + } + + private int getSuggestionWidth(final int positionInStrip, final int maxWidth) { + final int paddings = mPadding * mSuggestionsCountInStrip; + final int dividers = mDividerWidth * (mSuggestionsCountInStrip - 1); + final int availableWidth = maxWidth - paddings - dividers; + return (int)(availableWidth * getSuggestionWeight(positionInStrip)); + } + + private float getSuggestionWeight(final int positionInStrip) { + if (positionInStrip == mCenterPositionInStrip) { + return mCenterSuggestionWeight; + } + // TODO: Revisit this for cases of 5 or more suggestions + return (1.0f - mCenterSuggestionWeight) / (mSuggestionsCountInStrip - 1); + } + + private void setupWords(final SuggestedWords suggestedWords, final int countInStrip) { + mWords.clear(); + final int count = Math.min(suggestedWords.size(), countInStrip); + for (int pos = 0; pos < count; pos++) { + final CharSequence styled = getStyledSuggestionWord(suggestedWords, pos); + mWords.add(styled); + } + for (int pos = count; pos < countInStrip; pos++) { + // Make this inactive for touches in layout(). + mWords.add(null); + } + } + + private void layoutPunctuationSuggestions(final SuggestedWords suggestedWords, + final ViewGroup stripView) { + final int countInStrip = Math.min(suggestedWords.size(), PUNCTUATIONS_IN_STRIP); + 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, mDividerViews.get(indexInStrip)); + } + + final TextView word = mWordViews.get(indexInStrip); + word.setEnabled(true); + word.setTextColor(mColorAutoCorrect); + final String text = suggestedWords.getWord(indexInStrip); + word.setText(text); + word.setTextScaleX(1.0f); + word.setCompoundDrawables(null, null, null, null); + stripView.addView(word); + setLayoutWeight(word, 1.0f, mSuggestionsStripHeight); + } + mMoreSuggestionsAvailable = false; + } + + public void layoutAddToDictionaryHint(final String word, final ViewGroup stripView, + final int stripWidth, final CharSequence hintText, final OnClickListener listener) { + final int width = stripWidth - mDividerWidth - mPadding * 2; + + final TextView wordView = mWordToSaveView; + wordView.setTextColor(mColorTypedWord); + final int wordWidth = (int)(width * mCenterSuggestionWeight); + final CharSequence text = getEllipsizedText(word, wordWidth, wordView.getPaint()); + final float wordScaleX = wordView.getTextScaleX(); + wordView.setTag(word); + wordView.setText(text); + wordView.setTextScaleX(wordScaleX); + stripView.addView(wordView); + setLayoutWeight(wordView, mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT); + + stripView.addView(mDividerViews.get(0)); + + final TextView leftArrowView = mLeftwardsArrowView; + leftArrowView.setTextColor(mColorAutoCorrect); + leftArrowView.setText(LEFTWARDS_ARROW); + stripView.addView(leftArrowView); + + final TextView hintView = mHintToSaveView; + hintView.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL); + hintView.setTextColor(mColorAutoCorrect); + final int hintWidth = width - wordWidth - leftArrowView.getWidth(); + final float hintScaleX = getTextScaleX(hintText, hintWidth, hintView.getPaint()); + hintView.setText(hintText); + hintView.setTextScaleX(hintScaleX); + stripView.addView(hintView); + setLayoutWeight( + hintView, 1.0f - mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT); + + wordView.setOnClickListener(listener); + leftArrowView.setOnClickListener(listener); + hintView.setOnClickListener(listener); + } + + public CharSequence getAddToDictionaryWord() { + return (CharSequence)mWordToSaveView.getTag(); + } + + public boolean isAddToDictionaryShowing(final View v) { + return v == mWordToSaveView || v == mHintToSaveView || v == mLeftwardsArrowView; + } + + private static void setLayoutWeight(final View v, final float weight, final int height) { + final ViewGroup.LayoutParams lp = v.getLayoutParams(); + if (lp instanceof LinearLayout.LayoutParams) { + final LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams)lp; + llp.weight = weight; + llp.width = 0; + llp.height = height; + } + } + + private static float getTextScaleX(final CharSequence text, final int maxWidth, + final TextPaint paint) { + paint.setTextScaleX(1.0f); + final int width = getTextWidth(text, paint); + if (width <= maxWidth) { + return 1.0f; + } + return maxWidth / (float)width; + } + + private static CharSequence getEllipsizedText(final CharSequence text, final int maxWidth, + final TextPaint paint) { + if (text == null) { + return null; + } + final float scaleX = getTextScaleX(text, maxWidth, paint); + if (scaleX >= MIN_TEXT_XSCALE) { + paint.setTextScaleX(scaleX); + return text; + } + + // Note that TextUtils.ellipsize() use text-x-scale as 1.0 if ellipsize is needed. To + // get squeezed and ellipsized text, passes enlarged width (maxWidth / MIN_TEXT_XSCALE). + final CharSequence ellipsized = TextUtils.ellipsize( + text, paint, maxWidth / MIN_TEXT_XSCALE, TextUtils.TruncateAt.MIDDLE); + paint.setTextScaleX(MIN_TEXT_XSCALE); + return ellipsized; + } + + private static int getTextWidth(final CharSequence text, final TextPaint paint) { + if (TextUtils.isEmpty(text)) { + return 0; + } + final Typeface savedTypeface = paint.getTypeface(); + paint.setTypeface(getTextTypeface(text)); + final int len = text.length(); + final float[] widths = new float[len]; + final int count = paint.getTextWidths(text, 0, len, widths); + int width = 0; + for (int i = 0; i < count; i++) { + width += Math.round(widths[i] + 0.5f); + } + paint.setTypeface(savedTypeface); + return width; + } + + private static Typeface getTextTypeface(final CharSequence text) { + if (!(text instanceof SpannableString)) { + return Typeface.DEFAULT; + } + + final SpannableString ss = (SpannableString)text; + final StyleSpan[] styles = ss.getSpans(0, text.length(), StyleSpan.class); + if (styles.length == 0) { + return Typeface.DEFAULT; + } + + if (styles[0].getStyle() == Typeface.BOLD) { + return Typeface.DEFAULT_BOLD; + } + // TODO: BOLD_ITALIC, ITALIC case? + return Typeface.DEFAULT; + } +} diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java index ad350a02f..226bf873e 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java @@ -18,34 +18,14 @@ package com.android.inputmethod.latin.suggestions; import android.content.Context; import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Paint.Align; -import android.graphics.Rect; -import android.graphics.Typeface; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.Spanned; -import android.text.TextPaint; -import android.text.TextUtils; -import android.text.style.CharacterStyle; -import android.text.style.StyleSpan; -import android.text.style.UnderlineSpan; import android.util.AttributeSet; import android.view.GestureDetector; -import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; import android.view.ViewGroup; -import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; @@ -53,16 +33,13 @@ import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.MainKeyboardView; import com.android.inputmethod.keyboard.MoreKeysPanel; -import com.android.inputmethod.keyboard.ViewLayoutUtils; -import com.android.inputmethod.latin.AutoCorrection; +import com.android.inputmethod.latin.AudioAndHapticFeedbackManager; import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.ResourceUtils; import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.Utils; import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.suggestions.MoreSuggestions.MoreSuggestionsListener; import com.android.inputmethod.research.ResearchLogger; @@ -88,477 +65,14 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick private final MoreSuggestionsView mMoreSuggestionsView; private final MoreSuggestions.Builder mMoreSuggestionsBuilder; - private final ArrayList<TextView> mWords = CollectionUtils.newArrayList(); - private final ArrayList<TextView> mInfos = CollectionUtils.newArrayList(); - private final ArrayList<View> mDividers = CollectionUtils.newArrayList(); + private final ArrayList<TextView> mWordViews = CollectionUtils.newArrayList(); + private final ArrayList<TextView> mDebugInfoViews = CollectionUtils.newArrayList(); + private final ArrayList<View> mDividerViews = CollectionUtils.newArrayList(); Listener mListener; private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY; - private final SuggestionStripViewParams mParams; - private static final float MIN_TEXT_XSCALE = 0.70f; - - private static final class SuggestionStripViewParams { - private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3; - private static final float DEFAULT_CENTER_SUGGESTION_PERCENTILE = 0.40f; - private static final int DEFAULT_MAX_MORE_SUGGESTIONS_ROW = 2; - private static final int PUNCTUATIONS_IN_STRIP = 5; - - public final int mPadding; - public final int mDividerWidth; - public final int mSuggestionsStripHeight; - public final int mSuggestionsCountInStrip; - public final int mMoreSuggestionsRowHeight; - private int mMaxMoreSuggestionsRow; - public final float mMinMoreSuggestionsWidth; - public final int mMoreSuggestionsBottomGap; - - private final ArrayList<TextView> mWords; - private final ArrayList<View> mDividers; - private final ArrayList<TextView> mInfos; - - private final int mColorValidTypedWord; - private final int mColorTypedWord; - private final int mColorAutoCorrect; - private final int mColorSuggested; - private final float mAlphaObsoleted; - private final float mCenterSuggestionWeight; - private final int mCenterSuggestionIndex; - private final Drawable mMoreSuggestionsHint; - private static final String MORE_SUGGESTIONS_HINT = "\u2026"; - private static final String LEFTWARDS_ARROW = "\u2190"; - - private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD); - private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan(); - private static final int AUTO_CORRECT_BOLD = 0x01; - private static final int AUTO_CORRECT_UNDERLINE = 0x02; - private static final int VALID_TYPED_WORD_BOLD = 0x04; - - private final int mSuggestionStripOption; - - private final ArrayList<CharSequence> mTexts = CollectionUtils.newArrayList(); - - public boolean mMoreSuggestionsAvailable; - - private final TextView mWordToSaveView; - private final TextView mLeftwardsArrowView; - private final TextView mHintToSaveView; - - public SuggestionStripViewParams(final Context context, final AttributeSet attrs, - final int defStyle, final ArrayList<TextView> words, final ArrayList<View> dividers, - final ArrayList<TextView> infos) { - mWords = words; - mDividers = dividers; - mInfos = infos; - - final TextView word = words.get(0); - final View divider = dividers.get(0); - mPadding = word.getCompoundPaddingLeft() + word.getCompoundPaddingRight(); - divider.measure( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); - mDividerWidth = divider.getMeasuredWidth(); - - final Resources res = word.getResources(); - mSuggestionsStripHeight = res.getDimensionPixelSize(R.dimen.suggestions_strip_height); - - final TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.SuggestionStripView, defStyle, R.style.SuggestionStripViewStyle); - mSuggestionStripOption = a.getInt( - R.styleable.SuggestionStripView_suggestionStripOption, 0); - final float alphaValidTypedWord = ResourceUtils.getFraction(a, - R.styleable.SuggestionStripView_alphaValidTypedWord, 1.0f); - final float alphaTypedWord = ResourceUtils.getFraction(a, - R.styleable.SuggestionStripView_alphaTypedWord, 1.0f); - final float alphaAutoCorrect = ResourceUtils.getFraction(a, - R.styleable.SuggestionStripView_alphaAutoCorrect, 1.0f); - final float alphaSuggested = ResourceUtils.getFraction(a, - R.styleable.SuggestionStripView_alphaSuggested, 1.0f); - mAlphaObsoleted = ResourceUtils.getFraction(a, - R.styleable.SuggestionStripView_alphaSuggested, 1.0f); - mColorValidTypedWord = applyAlpha(a.getColor( - R.styleable.SuggestionStripView_colorValidTypedWord, 0), alphaValidTypedWord); - mColorTypedWord = applyAlpha(a.getColor( - R.styleable.SuggestionStripView_colorTypedWord, 0), alphaTypedWord); - mColorAutoCorrect = applyAlpha(a.getColor( - R.styleable.SuggestionStripView_colorAutoCorrect, 0), alphaAutoCorrect); - mColorSuggested = applyAlpha(a.getColor( - R.styleable.SuggestionStripView_colorSuggested, 0), alphaSuggested); - mSuggestionsCountInStrip = a.getInt( - R.styleable.SuggestionStripView_suggestionsCountInStrip, - DEFAULT_SUGGESTIONS_COUNT_IN_STRIP); - mCenterSuggestionWeight = ResourceUtils.getFraction(a, - R.styleable.SuggestionStripView_centerSuggestionPercentile, - DEFAULT_CENTER_SUGGESTION_PERCENTILE); - mMaxMoreSuggestionsRow = a.getInt( - R.styleable.SuggestionStripView_maxMoreSuggestionsRow, - DEFAULT_MAX_MORE_SUGGESTIONS_ROW); - mMinMoreSuggestionsWidth = ResourceUtils.getFraction(a, - R.styleable.SuggestionStripView_minMoreSuggestionsWidth, 1.0f); - a.recycle(); - - mMoreSuggestionsHint = getMoreSuggestionsHint(res, - res.getDimension(R.dimen.more_suggestions_hint_text_size), mColorAutoCorrect); - mCenterSuggestionIndex = mSuggestionsCountInStrip / 2; - mMoreSuggestionsBottomGap = res.getDimensionPixelOffset( - R.dimen.more_suggestions_bottom_gap); - mMoreSuggestionsRowHeight = res.getDimensionPixelSize( - R.dimen.more_suggestions_row_height); - - final LayoutInflater inflater = LayoutInflater.from(context); - mWordToSaveView = (TextView)inflater.inflate(R.layout.suggestion_word, null); - mLeftwardsArrowView = (TextView)inflater.inflate(R.layout.hint_add_to_dictionary, null); - mHintToSaveView = (TextView)inflater.inflate(R.layout.hint_add_to_dictionary, null); - } - - public int getMaxMoreSuggestionsRow() { - return mMaxMoreSuggestionsRow; - } - - private int getMoreSuggestionsHeight() { - return mMaxMoreSuggestionsRow * mMoreSuggestionsRowHeight + mMoreSuggestionsBottomGap; - } - - public int setMoreSuggestionsHeight(final int remainingHeight) { - final int currentHeight = getMoreSuggestionsHeight(); - if (currentHeight <= remainingHeight) { - return currentHeight; - } - - mMaxMoreSuggestionsRow = (remainingHeight - mMoreSuggestionsBottomGap) - / mMoreSuggestionsRowHeight; - final int newHeight = getMoreSuggestionsHeight(); - return newHeight; - } - - private static Drawable getMoreSuggestionsHint(final Resources res, final float textSize, - final int color) { - final Paint paint = new Paint(); - paint.setAntiAlias(true); - paint.setTextAlign(Align.CENTER); - paint.setTextSize(textSize); - paint.setColor(color); - final Rect bounds = new Rect(); - paint.getTextBounds(MORE_SUGGESTIONS_HINT, 0, MORE_SUGGESTIONS_HINT.length(), bounds); - final int width = Math.round(bounds.width() + 0.5f); - final int height = Math.round(bounds.height() + 0.5f); - final Bitmap buffer = Bitmap.createBitmap( - width, (height * 3 / 2), Bitmap.Config.ARGB_8888); - final Canvas canvas = new Canvas(buffer); - canvas.drawText(MORE_SUGGESTIONS_HINT, width / 2, height, paint); - return new BitmapDrawable(res, buffer); - } - - 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; - if (!isAutoCorrect && !isTypedWordValid) - return word; - - final int len = word.length(); - final Spannable spannedWord = new SpannableString(word); - final int option = mSuggestionStripOption; - if ((isAutoCorrect && (option & AUTO_CORRECT_BOLD) != 0) - || (isTypedWordValid && (option & VALID_TYPED_WORD_BOLD) != 0)) { - spannedWord.setSpan(BOLD_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); - } - if (isAutoCorrect && (option & AUTO_CORRECT_UNDERLINE) != 0) { - spannedWord.setSpan(UNDERLINE_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); - } - return spannedWord; - } - - private int getWordPosition(final int index, 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) { - return mCenterSuggestionIndex; - } else { - return index; - } - } - - private int getSuggestionTextColor(final int index, final SuggestedWords suggestedWords, - final int pos) { - // TODO: Need to revisit this logic with bigram suggestions - final boolean isSuggested = (pos != 0); - - final int color; - if (index == mCenterSuggestionIndex && suggestedWords.willAutoCorrect()) { - color = mColorAutoCorrect; - } else if (index == mCenterSuggestionIndex && suggestedWords.mTypedWordValid) { - color = mColorValidTypedWord; - } else if (isSuggested) { - color = mColorSuggested; - } else { - color = mColorTypedWord; - } - 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 - && AutoCorrection.shouldBlockAutoCorrectionBySafetyNet( - suggestedWords.getWord(1), suggestedWords.getWord(0))) { - return 0xFFFF0000; - } - } - - if (suggestedWords.mIsObsoleteSuggestions && isSuggested) { - return applyAlpha(color, mAlphaObsoleted); - } else { - return color; - } - } - - private static int applyAlpha(final int color, final float alpha) { - final int newAlpha = (int)(Color.alpha(color) * alpha); - return Color.argb(newAlpha, Color.red(color), Color.green(color), Color.blue(color)); - } - - private static void addDivider(final ViewGroup stripView, final View divider) { - stripView.addView(divider); - final LinearLayout.LayoutParams params = - (LinearLayout.LayoutParams)divider.getLayoutParams(); - params.gravity = Gravity.CENTER; - } - - public void layout(final SuggestedWords suggestedWords, final ViewGroup stripView, - final ViewGroup placer, final int stripWidth) { - if (suggestedWords.mIsPunctuationSuggestions) { - layoutPunctuationSuggestions(suggestedWords, stripView); - return; - } - - final int countInStrip = mSuggestionsCountInStrip; - 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); - // 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); - stripView.addView(word); - setLayoutWeight( - word, getSuggestionWeight(index), 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()); - } - } - } - } - - private int getSuggestionWidth(final int index, 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)); - } - - private float getSuggestionWeight(final int index) { - if (index == mCenterSuggestionIndex) { - return mCenterSuggestionWeight; - } else { - // TODO: Revisit this for cases of 5 or more suggestions - return (1.0f - mCenterSuggestionWeight) / (mSuggestionsCountInStrip - 1); - } - } - - private void setupTexts(final SuggestedWords suggestedWords, final int countInStrip) { - mTexts.clear(); - final int count = Math.min(suggestedWords.size(), countInStrip); - for (int pos = 0; pos < count; pos++) { - final CharSequence styled = getStyledSuggestionWord(suggestedWords, pos); - mTexts.add(styled); - } - for (int pos = count; pos < countInStrip; pos++) { - // Make this inactive for touches in layout(). - mTexts.add(null); - } - } - - 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) { - // Add divider if this isn't the left most suggestion in suggestions strip. - addDivider(stripView, mDividers.get(index)); - } - - final TextView word = mWords.get(index); - word.setEnabled(true); - word.setTextColor(mColorAutoCorrect); - final String text = suggestedWords.getWord(index); - word.setText(text); - word.setTextScaleX(1.0f); - word.setCompoundDrawables(null, null, null, null); - stripView.addView(word); - setLayoutWeight(word, 1.0f, mSuggestionsStripHeight); - } - mMoreSuggestionsAvailable = false; - } - - public void layoutAddToDictionaryHint(final String word, final ViewGroup stripView, - final int stripWidth, final CharSequence hintText, final OnClickListener listener) { - final int width = stripWidth - mDividerWidth - mPadding * 2; - - final TextView wordView = mWordToSaveView; - wordView.setTextColor(mColorTypedWord); - final int wordWidth = (int)(width * mCenterSuggestionWeight); - final CharSequence text = getEllipsizedText(word, wordWidth, wordView.getPaint()); - final float wordScaleX = wordView.getTextScaleX(); - wordView.setTag(word); - wordView.setText(text); - wordView.setTextScaleX(wordScaleX); - stripView.addView(wordView); - setLayoutWeight(wordView, mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT); - - stripView.addView(mDividers.get(0)); - - final TextView leftArrowView = mLeftwardsArrowView; - leftArrowView.setTextColor(mColorAutoCorrect); - leftArrowView.setText(LEFTWARDS_ARROW); - stripView.addView(leftArrowView); - - final TextView hintView = mHintToSaveView; - hintView.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL); - hintView.setTextColor(mColorAutoCorrect); - final int hintWidth = width - wordWidth - leftArrowView.getWidth(); - final float hintScaleX = getTextScaleX(hintText, hintWidth, hintView.getPaint()); - hintView.setText(hintText); - hintView.setTextScaleX(hintScaleX); - stripView.addView(hintView); - setLayoutWeight( - hintView, 1.0f - mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT); - - wordView.setOnClickListener(listener); - leftArrowView.setOnClickListener(listener); - hintView.setOnClickListener(listener); - } - - public CharSequence getAddToDictionaryWord() { - return (CharSequence)mWordToSaveView.getTag(); - } - - public boolean isAddToDictionaryShowing(final View v) { - return v == mWordToSaveView || v == mHintToSaveView || v == mLeftwardsArrowView; - } - - private static void setLayoutWeight(final View v, final float weight, final int height) { - final ViewGroup.LayoutParams lp = v.getLayoutParams(); - if (lp instanceof LinearLayout.LayoutParams) { - final LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams)lp; - llp.weight = weight; - llp.width = 0; - llp.height = height; - } - } - - private static float getTextScaleX(final CharSequence text, final int maxWidth, - final TextPaint paint) { - paint.setTextScaleX(1.0f); - final int width = getTextWidth(text, paint); - if (width <= maxWidth) { - return 1.0f; - } - return maxWidth / (float)width; - } - - private static CharSequence getEllipsizedText(final CharSequence text, final int maxWidth, - final TextPaint paint) { - if (text == null) return null; - paint.setTextScaleX(1.0f); - final int width = getTextWidth(text, paint); - if (width <= maxWidth) { - return text; - } - final float scaleX = maxWidth / (float)width; - if (scaleX >= MIN_TEXT_XSCALE) { - paint.setTextScaleX(scaleX); - return text; - } - - // Note that TextUtils.ellipsize() use text-x-scale as 1.0 if ellipsize is needed. To - // get squeezed and ellipsized text, passes enlarged width (maxWidth / MIN_TEXT_XSCALE). - final CharSequence ellipsized = TextUtils.ellipsize( - text, paint, maxWidth / MIN_TEXT_XSCALE, TextUtils.TruncateAt.MIDDLE); - paint.setTextScaleX(MIN_TEXT_XSCALE); - return ellipsized; - } - - private static int getTextWidth(final CharSequence text, final TextPaint paint) { - if (TextUtils.isEmpty(text)) return 0; - final Typeface savedTypeface = paint.getTypeface(); - paint.setTypeface(getTextTypeface(text)); - final int len = text.length(); - final float[] widths = new float[len]; - final int count = paint.getTextWidths(text, 0, len, widths); - int width = 0; - for (int i = 0; i < count; i++) { - width += Math.round(widths[i] + 0.5f); - } - paint.setTypeface(savedTypeface); - return width; - } - - private static Typeface getTextTypeface(final CharSequence text) { - if (!(text instanceof SpannableString)) - return Typeface.DEFAULT; - - final SpannableString ss = (SpannableString)text; - final StyleSpan[] styles = ss.getSpans(0, text.length(), StyleSpan.class); - if (styles.length == 0) - return Typeface.DEFAULT; - - switch (styles[0].getStyle()) { - case Typeface.BOLD: return Typeface.DEFAULT_BOLD; - // TODO: BOLD_ITALIC, ITALIC case? - default: return Typeface.DEFAULT; - } - } - } + private final SuggestionStripLayoutHelper mLayoutHelper; /** * Construct a {@link SuggestionStripView} for showing suggestions to be picked by the user. @@ -582,16 +96,16 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick word.setTag(pos); word.setOnClickListener(this); word.setOnLongClickListener(this); - mWords.add(word); + mWordViews.add(word); final View divider = inflater.inflate(R.layout.suggestion_divider, null); divider.setTag(pos); divider.setOnClickListener(this); - mDividers.add(divider); - mInfos.add((TextView)inflater.inflate(R.layout.suggestion_info, null)); + mDividerViews.add(divider); + mDebugInfoViews.add((TextView)inflater.inflate(R.layout.suggestion_info, null)); } - mParams = new SuggestionStripViewParams( - context, attrs, defStyle, mWords, mDividers, mInfos); + mLayoutHelper = new SuggestionStripLayoutHelper( + context, attrs, defStyle, mWordViews, mDividerViews, mDebugInfoViews); mMoreSuggestionsContainer = inflater.inflate(R.layout.more_suggestions, null); mMoreSuggestionsView = (MoreSuggestionsView)mMoreSuggestionsContainer @@ -617,24 +131,25 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick public void setSuggestions(final SuggestedWords suggestedWords) { clear(); mSuggestedWords = suggestedWords; - mParams.layout(mSuggestedWords, mSuggestionsStrip, this, getWidth()); + mLayoutHelper.layout(mSuggestedWords, mSuggestionsStrip, this); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.suggestionStripView_setSuggestions(mSuggestedWords); } } public int setMoreSuggestionsHeight(final int remainingHeight) { - return mParams.setMoreSuggestionsHeight(remainingHeight); + return mLayoutHelper.setMoreSuggestionsHeight(remainingHeight); } public boolean isShowingAddToDictionaryHint() { return mSuggestionsStrip.getChildCount() > 0 - && mParams.isAddToDictionaryShowing(mSuggestionsStrip.getChildAt(0)); + && mLayoutHelper.isAddToDictionaryShowing(mSuggestionsStrip.getChildAt(0)); } public void showAddToDictionaryHint(final String word, final CharSequence hintText) { clear(); - mParams.layoutAddToDictionaryHint(word, mSuggestionsStrip, getWidth(), hintText, this); + mLayoutHelper.layoutAddToDictionaryHint( + word, mSuggestionsStrip, getWidth(), hintText, this); } public boolean dismissAddToDictionaryHint() { @@ -689,7 +204,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick @Override public boolean onLongClick(final View view) { - KeyboardSwitcher.getInstance().hapticAndAudioFeedback(Constants.NOT_A_CODE); + AudioAndHapticFeedbackManager.getInstance().hapticAndAudioFeedback( + Constants.NOT_A_CODE, this); return showMoreSuggestions(); } @@ -698,30 +214,30 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick if (parentKeyboard == null) { return false; } - final SuggestionStripViewParams params = mParams; - if (!params.mMoreSuggestionsAvailable) { + final SuggestionStripLayoutHelper layoutHelper = mLayoutHelper; + if (!layoutHelper.mMoreSuggestionsAvailable) { return false; } final int stripWidth = getWidth(); final View container = mMoreSuggestionsContainer; final int maxWidth = stripWidth - container.getPaddingLeft() - container.getPaddingRight(); final MoreSuggestions.Builder builder = mMoreSuggestionsBuilder; - builder.layout(mSuggestedWords, params.mSuggestionsCountInStrip, maxWidth, - (int)(maxWidth * params.mMinMoreSuggestionsWidth), - params.getMaxMoreSuggestionsRow(), parentKeyboard); + builder.layout(mSuggestedWords, layoutHelper.mSuggestionsCountInStrip, maxWidth, + (int)(maxWidth * layoutHelper.mMinMoreSuggestionsWidth), + layoutHelper.getMaxMoreSuggestionsRow(), parentKeyboard); mMoreSuggestionsView.setKeyboard(builder.build()); container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); final MoreKeysPanel moreKeysPanel = mMoreSuggestionsView; final int pointX = stripWidth / 2; - final int pointY = -params.mMoreSuggestionsBottomGap; + final int pointY = -layoutHelper.mMoreSuggestionsBottomGap; moreKeysPanel.showMoreKeysPanel(this, mMoreSuggestionsController, pointX, pointY, mMoreSuggestionsListener); mMoreSuggestionsMode = MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING; mOriginX = mLastX; mOriginY = mLastY; - for (int i = 0; i < params.mSuggestionsCountInStrip; i++) { - mWords.get(i).setPressed(false); + for (int i = 0; i < layoutHelper.mSuggestionsCountInStrip; i++) { + mWordViews.get(i).setPressed(false); } return true; } @@ -791,18 +307,20 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick @Override public void onClick(final View view) { - if (mParams.isAddToDictionaryShowing(view)) { - mListener.addWordToUserDictionary(mParams.getAddToDictionaryWord().toString()); + if (mLayoutHelper.isAddToDictionaryShowing(view)) { + mListener.addWordToUserDictionary(mLayoutHelper.getAddToDictionaryWord().toString()); clear(); return; } final Object tag = view.getTag(); - if (!(tag instanceof Integer)) + if (!(tag instanceof Integer)) { return; + } final int index = (Integer) tag; - if (index >= mSuggestedWords.size()) + if (index >= mSuggestedWords.size()) { return; + } final SuggestedWordInfo wordInfo = mSuggestedWords.getInfo(index); mListener.pickSuggestionManually(index, wordInfo); |