diff options
43 files changed, 1177 insertions, 412 deletions
diff --git a/java/res/layout/setup_welcome_video.xml b/java/res/layout/setup_welcome_video.xml index 751773211..01c25ea8e 100644 --- a/java/res/layout/setup_welcome_video.xml +++ b/java/res/layout/setup_welcome_video.xml @@ -36,13 +36,15 @@ android:id="@+id/setup_welcome_video" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@color/setup_background" /> + android:background="@color/setup_background" + android:contentDescription="@string/setup_welcome_additional_description"/> <ImageView android:id="@+id/setup_welcome_image" android:layout_width="match_parent" android:layout_height="wrap_content" android:adjustViewBounds="true" - android:visibility="gone" /> + android:visibility="gone" + android:contentDescription="@string/setup_welcome_additional_description"/> </LinearLayout> <View android:layout_weight="@integer/setup_welcome_video_end_padding_weight_in_screen" diff --git a/java/res/values/keypress-vibration-durations.xml b/java/res/values/keypress-vibration-durations.xml index 9ce5051d2..ad6beadb6 100644 --- a/java/res/values/keypress-vibration-durations.xml +++ b/java/res/values/keypress-vibration-durations.xml @@ -37,7 +37,7 @@ <item>MODEL=(SAMSUNG-)?GT-I(930[05][NT]?|9308):MANUFACTURER=samsung,8</item> <item>MODEL=(SAMSUNG-)?SGH-(T999[V]?|I747[M]?|N064|N035):MANUFACTURER=samsung,8</item> <item>MODEL=(SAMSUNG-)?SCH-(J021|R530|I535|I939):MANUFACTURER=samsung,8</item> - <item>MODEL=(SAMSUNG-)?(SCL21|SC-06D|SC-03E]):MANUFACTURER=samsung,8</item> + <item>MODEL=(SAMSUNG-)?(SCL21|SC-06D|SC-03E):MANUFACTURER=samsung,8</item> <item>MODEL=(SAMSUNG-)?(SHV-210[KLS]?|SPH-L710):MANUFACTURER=samsung,8</item> <!-- LG Optimus G --> <item>MODEL=LG-E97[013]|LS970|L-01E:MANUFACTURER=LGE,15</item> diff --git a/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java b/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java index d0e8446f5..77f67b8a3 100644 --- a/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java +++ b/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java @@ -144,7 +144,7 @@ public final class LocaleUtils { public static String getMatchLevelSortedString(final 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/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java index 1550e77e3..ae72b4a6b 100644 --- a/java/src/com/android/inputmethod/keyboard/Key.java +++ b/java/src/com/android/inputmethod/keyboard/Key.java @@ -453,7 +453,7 @@ public class Key implements Comparable<Key> { } else { label = "/" + mLabel; } - return String.format("%s%s %d,%d %dx%d %s/%s/%s", + return String.format(Locale.ROOT, "%s%s %d,%d %dx%d %s/%s/%s", Constants.printableCode(mCode), label, mX, mY, mWidth, mHeight, mHintLabel, KeyboardIconsSet.getIconName(mIconId), backgroundName(mBackgroundType)); } diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java index aa27067bc..4c5dd25c4 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java @@ -187,7 +187,7 @@ public final class KeyboardId { public String toString() { final String orientation = (mOrientation == Configuration.ORIENTATION_PORTRAIT) ? "port" : "land"; - return String.format("[%s %s:%s %s:%dx%d %s %s %s%s%s%s%s%s%s%s%s]", + return String.format(Locale.ROOT, "[%s %s:%s %s:%dx%d %s %s %s%s%s%s%s%s%s%s%s]", elementIdToName(mElementId), mLocale, mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET), diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index ad08d6477..83f109014 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -32,6 +32,7 @@ import com.android.inputmethod.keyboard.KeyboardLayoutSet.KeyboardLayoutSetExcep import com.android.inputmethod.keyboard.PointerTracker.TimerProxy; import com.android.inputmethod.keyboard.internal.KeyboardState; import com.android.inputmethod.latin.AudioAndHapticFeedbackManager; +import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.InputView; import com.android.inputmethod.latin.LatinIME; import com.android.inputmethod.latin.LatinImeLogger; @@ -68,8 +69,6 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { new KeyboardTheme(5, R.style.KeyboardTheme_IceCreamSandwich), }; - private final AudioAndHapticFeedbackManager mFeedbackManager = - AudioAndHapticFeedbackManager.getInstance(); private SubtypeSwitcher mSubtypeSwitcher; private SharedPreferences mPrefs; @@ -151,7 +150,6 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { mKeyboardLayoutSet = builder.build(); try { mState.onLoadKeyboard(); - mFeedbackManager.onSettingsChanged(settingsValues); } catch (KeyboardLayoutSetException e) { Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause()); LatinImeLogger.logOnException(e.mKeyboardId.toString(), e.getCause()); @@ -159,10 +157,6 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { } } - public void onRingerModeChanged() { - mFeedbackManager.onRingerModeChanged(); - } - public void saveKeyboardState() { if (getKeyboard() != null) { mState.onSaveKeyboardState(); @@ -217,9 +211,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { } public void onPressKey(final int code, final boolean isSinglePointer) { - if (isVibrateAndSoundFeedbackRequired()) { - mFeedbackManager.hapticAndAudioFeedback(code, mKeyboardView); - } + hapticAndAudioFeedback(code); mState.onPressKey(code, isSinglePointer, mLatinIME.getCurrentAutoCapsState()); } @@ -328,22 +320,19 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { } } - // Implements {@link KeyboardState.SwitchActions}. - @Override - public void hapticAndAudioFeedback(final int code) { - mFeedbackManager.hapticAndAudioFeedback(code, mKeyboardView); + private void hapticAndAudioFeedback(final int code) { + if (mKeyboardView == null || mKeyboardView.isInSlidingKeyInput()) { + return; + } + AudioAndHapticFeedbackManager.getInstance().hapticAndAudioFeedback(code, mKeyboardView); } public void onLongPressTimeout(final int code) { mState.onLongPressTimeout(code); - } - - public boolean isInMomentarySwitchState() { - return mState.isInMomentarySwitchState(); - } - - private boolean isVibrateAndSoundFeedbackRequired() { - return mKeyboardView != null && !mKeyboardView.isInSlidingKeyInput(); + final Keyboard keyboard = getKeyboard(); + if (keyboard != null && keyboard.mId.isAlphabetKeyboard() && code == Constants.CODE_SHIFT) { + hapticAndAudioFeedback(code); + } } /** diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java index 6c6fc6157..7493df874 100644 --- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java @@ -57,6 +57,7 @@ import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams; import com.android.inputmethod.keyboard.internal.PreviewPlacerView; import com.android.inputmethod.keyboard.internal.SlidingKeyInputPreview; import com.android.inputmethod.keyboard.internal.TouchScreenRegulator; +import com.android.inputmethod.latin.AudioAndHapticFeedbackManager; import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.CoordinateUtils; @@ -240,7 +241,9 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack case MSG_REPEAT_KEY: final Key currentKey = tracker.getKey(); if (currentKey != null && currentKey.mCode == msg.arg1) { - tracker.onRegisterKey(currentKey); + tracker.onRepeatKey(currentKey); + AudioAndHapticFeedbackManager.getInstance().hapticAndAudioFeedback( + currentKey.mCode, keyboardView); startKeyRepeatTimer(tracker, mKeyRepeatInterval); } break; @@ -312,9 +315,8 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack default: final int longpressTimeout = Settings.getInstance().getCurrent().mKeyLongpressTimeout; - if (KeyboardSwitcher.getInstance().isInMomentarySwitchState()) { - // We use longer timeout for sliding finger input started from the symbols - // mode key. + if (tracker.isInSlidingKeyInputFromModifier()) { + // We use longer timeout for sliding finger input started from the modifier key. delay = longpressTimeout * 3; } else { delay = longpressTimeout; @@ -987,16 +989,14 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack /** * Called when a key is long pressed. * @param tracker the pointer tracker which pressed the parent key - * @return true if the long press is handled, false otherwise. Subclasses should call the - * method on the base class if the subclass doesn't wish to handle the call. */ - private boolean onLongPress(final PointerTracker tracker) { + private void onLongPress(final PointerTracker tracker) { if (isShowingMoreKeysPanel()) { - return false; + return; } final Key key = tracker.getKey(); if (key == null) { - return false; + return; } if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.mainKeyboardView_onLongPress(); @@ -1007,18 +1007,18 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack tracker.onLongPressed(); invokeCodeInput(embeddedCode); invokeReleaseKey(code); - KeyboardSwitcher.getInstance().hapticAndAudioFeedback(code); - return true; + AudioAndHapticFeedbackManager.getInstance().hapticAndAudioFeedback(code, this); + return; } if (code == Constants.CODE_SPACE || code == Constants.CODE_LANGUAGE_SWITCH) { // Long pressing the space key invokes IME switcher dialog. if (invokeCustomRequest(LatinIME.CODE_SHOW_INPUT_METHOD_PICKER)) { tracker.onLongPressed(); invokeReleaseKey(code); - return true; + return; } } - return openMoreKeysPanel(key, tracker); + openMoreKeysPanel(key, tracker); } private boolean invokeCustomRequest(final int requestCode) { @@ -1034,10 +1034,10 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack mKeyboardActionListener.onReleaseKey(code, false); } - private boolean openMoreKeysPanel(final Key key, final PointerTracker tracker) { + private void openMoreKeysPanel(final Key key, final PointerTracker tracker) { final MoreKeysPanel moreKeysPanel = onCreateMoreKeysPanel(key, getContext()); if (moreKeysPanel == null) { - return false; + return; } final int[] lastCoords = CoordinateUtils.newInstance(); @@ -1059,7 +1059,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack final int translatedX = moreKeysPanel.translateX(CoordinateUtils.x(lastCoords)); final int translatedY = moreKeysPanel.translateY(CoordinateUtils.y(lastCoords)); tracker.onShowMoreKeysPanel(translatedX, translatedY, moreKeysPanel); - return true; } public boolean isInSlidingKeyInput() { diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index 174239325..5df7011cb 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -1266,15 +1266,13 @@ public final class PointerTracker implements PointerTrackerQueue.Element { if (!key.isRepeatable()) return; // Don't start key repeat when we are in sliding input mode. if (mIsInSlidingKeyInput) return; - onRegisterKey(key); + onRepeatKey(key); mTimerProxy.startKeyRepeatTimer(this); } - public void onRegisterKey(final Key key) { - if (key != null) { - detectAndSendKey(key, key.mX, key.mY, SystemClock.uptimeMillis()); - mTimerProxy.startTypingStateTimer(key); - } + public void onRepeatKey(final Key key) { + detectAndSendKey(key, key.mX, key.mY, SystemClock.uptimeMillis()); + mTimerProxy.startTypingStateTimer(key); } private boolean isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final long eventTime, diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java index b1813a141..ba449eeb3 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java @@ -53,7 +53,9 @@ public final class KeySpecParser { private static final int MAX_STRING_REFERENCE_INDIRECTION = 10; // Constants for parsing. - private static final char LABEL_END = '|'; + private static final char COMMA = ','; + private static final char BACKSLASH = '\\'; + private static final char VERTICAL_BAR = '|'; private static final String PREFIX_TEXT = "!text/"; static final String PREFIX_ICON = "!icon/"; private static final String PREFIX_CODE = "!code/"; @@ -64,6 +66,59 @@ public final class KeySpecParser { // Intentional empty constructor for utility class. } + /** + * Split the text containing multiple key specifications separated by commas into an array of + * key specifications. + * A key specification can contain a character escaped by the backslash character, including a + * comma character. + * Note that an empty key specification will be eliminated from the result array. + * + * @param text the text containing multiple key specifications. + * @return an array of key specification text. Null if the specified <code>text</code> is empty + * or has no key specifications. + */ + public static String[] splitKeySpecs(final String text) { + final int size = text.length(); + if (size == 0) { + return null; + } + // Optimization for one-letter key specification. + if (size == 1) { + return text.charAt(0) == COMMA ? null : new String[] { text }; + } + + ArrayList<String> list = null; + int start = 0; + // The characters in question in this loop are COMMA and BACKSLASH. These characters never + // match any high or low surrogate character. So it is OK to iterate through with char + // index. + for (int pos = 0; pos < size; pos++) { + final char c = text.charAt(pos); + if (c == COMMA) { + // Skip empty entry. + if (pos - start > 0) { + if (list == null) { + list = CollectionUtils.newArrayList(); + } + list.add(text.substring(start, pos)); + } + // Skip comma + start = pos + 1; + } else if (c == BACKSLASH) { + // Skip escape character and escaped character. + pos++; + } + } + final String remain = (size - start > 0) ? text.substring(start) : null; + if (list == null) { + return remain != null ? new String[] { remain } : null; + } + if (remain != null) { + list.add(remain); + } + return list.toArray(new String[list.size()]); + } + private static boolean hasIcon(final String moreKeySpec) { return moreKeySpec.startsWith(PREFIX_ICON); } @@ -78,14 +133,14 @@ public final class KeySpecParser { } private static String parseEscape(final String text) { - if (text.indexOf(Constants.CSV_ESCAPE) < 0) { + if (text.indexOf(BACKSLASH) < 0) { return text; } final int length = text.length(); final StringBuilder sb = new StringBuilder(); for (int pos = 0; pos < length; pos++) { final char c = text.charAt(pos); - if (c == Constants.CSV_ESCAPE && pos + 1 < length) { + if (c == BACKSLASH && pos + 1 < length) { // Skip escape char pos++; sb.append(text.charAt(pos)); @@ -97,20 +152,20 @@ public final class KeySpecParser { } private static int indexOfLabelEnd(final String moreKeySpec, final int start) { - if (moreKeySpec.indexOf(Constants.CSV_ESCAPE, start) < 0) { - final int end = moreKeySpec.indexOf(LABEL_END, start); + if (moreKeySpec.indexOf(BACKSLASH, start) < 0) { + final int end = moreKeySpec.indexOf(VERTICAL_BAR, start); if (end == 0) { - throw new KeySpecParserError(LABEL_END + " at " + start + ": " + moreKeySpec); + throw new KeySpecParserError(VERTICAL_BAR + " at " + start + ": " + moreKeySpec); } return end; } final int length = moreKeySpec.length(); for (int pos = start; pos < length; pos++) { final char c = moreKeySpec.charAt(pos); - if (c == Constants.CSV_ESCAPE && pos + 1 < length) { + if (c == BACKSLASH && pos + 1 < length) { // Skip escape char pos++; - } else if (c == LABEL_END) { + } else if (c == VERTICAL_BAR) { return pos; } } @@ -136,9 +191,9 @@ public final class KeySpecParser { return null; } if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) { - throw new KeySpecParserError("Multiple " + LABEL_END + ": " + moreKeySpec); + throw new KeySpecParserError("Multiple " + VERTICAL_BAR + ": " + moreKeySpec); } - return parseEscape(moreKeySpec.substring(end + /* LABEL_END */1)); + return parseEscape(moreKeySpec.substring(end + /* VERTICAL_BAR */1)); } static String getOutputText(final String moreKeySpec) { @@ -169,7 +224,7 @@ public final class KeySpecParser { if (hasCode(moreKeySpec)) { final int end = indexOfLabelEnd(moreKeySpec, 0); if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) { - throw new KeySpecParserError("Multiple " + LABEL_END + ": " + moreKeySpec); + throw new KeySpecParserError("Multiple " + VERTICAL_BAR + ": " + moreKeySpec); } return parseCode(moreKeySpec.substring(end + 1), codesSet, CODE_UNSPECIFIED); } @@ -204,7 +259,7 @@ public final class KeySpecParser { public static int getIconId(final String moreKeySpec) { if (moreKeySpec != null && hasIcon(moreKeySpec)) { - final int end = moreKeySpec.indexOf(LABEL_END, PREFIX_ICON.length()); + final int end = moreKeySpec.indexOf(VERTICAL_BAR, PREFIX_ICON.length()); final String name = (end < 0) ? moreKeySpec.substring(PREFIX_ICON.length()) : moreKeySpec.substring(PREFIX_ICON.length(), end); return KeyboardIconsSet.getIconId(name); @@ -351,7 +406,7 @@ public final class KeySpecParser { final String name = text.substring(pos + prefixLen, end); sb.append(textsSet.getText(name)); pos = end - 1; - } else if (c == Constants.CSV_ESCAPE) { + } else if (c == BACKSLASH) { if (sb != null) { // Append both escape character and escaped character. sb.append(text.substring(pos, Math.min(pos + 2, size))); @@ -366,7 +421,6 @@ public final class KeySpecParser { text = sb.toString(); } } while (sb != null); - return text; } diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java index 5db3ebbd1..f65056948 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java @@ -18,8 +18,6 @@ package com.android.inputmethod.keyboard.internal; import android.content.res.TypedArray; -import com.android.inputmethod.latin.StringUtils; - public abstract class KeyStyle { private final KeyboardTextsSet mTextsSet; @@ -42,7 +40,7 @@ public abstract class KeyStyle { protected String[] parseStringArray(final TypedArray a, final int index) { if (a.hasValue(index)) { final String text = KeySpecParser.resolveTextReference(a.getString(index), mTextsSet); - return StringUtils.parseCsvString(text); + return KeySpecParser.splitKeySpecs(text); } return null; } diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java index 6af1bd75f..9f6374bf7 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java @@ -58,7 +58,6 @@ public final class KeyboardState { public void cancelDoubleTapTimer(); public void startLongPressTimer(int code); public void cancelLongPressTimer(); - public void hapticAndAudioFeedback(int code); } private final SwitchActions mSwitchActions; @@ -387,7 +386,6 @@ public final class KeyboardState { } if (mIsAlphabetMode && code == Constants.CODE_SHIFT) { mLongPressShiftLockFired = true; - mSwitchActions.hapticAndAudioFeedback(code); } } @@ -576,11 +574,6 @@ public final class KeyboardState { } } - public boolean isInMomentarySwitchState() { - return mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL - || mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE; - } - private static boolean isSpaceCharacter(final int c) { return c == Constants.CODE_SPACE || c == Constants.CODE_ENTER; } diff --git a/java/src/com/android/inputmethod/keyboard/internal/MatrixUtils.java b/java/src/com/android/inputmethod/keyboard/internal/MatrixUtils.java new file mode 100644 index 000000000..4916a15b5 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/internal/MatrixUtils.java @@ -0,0 +1,166 @@ +/* + * 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.keyboard.internal; + +import com.android.inputmethod.annotations.UsedForTesting; + +import android.util.Log; + +import java.util.Arrays; + +/** + * Utilities for matrix operations. Don't instantiate objects inside this class to prevent + * unexpected performance regressions. + */ +@UsedForTesting +public class MatrixUtils { + private static final String TAG = MatrixUtils.class.getSimpleName(); + public static class MatrixOperationFailedException extends Exception { + private static final String TAG = MatrixOperationFailedException.class.getSimpleName(); + private static final long serialVersionUID = 4384485606788583829L; + + public MatrixOperationFailedException(String msg) { + super(msg); + Log.d(TAG, msg); + } + } + + /** + * A utility function to inverse matrix. + * Find a pivot and swap the row of squareMatrix0 and squareMatrix1 + */ + private static void findPivotAndSwapRow(final int row, final float[][] squareMatrix0, + final float[][] squareMatrix1, final int size) { + int ip = row; + float pivot = Math.abs(squareMatrix0[row][row]); + for (int i = row + 1; i < size; ++i) { + if (pivot < Math.abs(squareMatrix0[i][row])) { + ip = i; + pivot = Math.abs(squareMatrix0[i][row]); + } + } + if (ip != row) { + for (int j = 0; j < size; ++j) { + final float temp0 = squareMatrix0[ip][j]; + squareMatrix0[ip][j] = squareMatrix0[row][j]; + squareMatrix0[row][j] = temp0; + final float temp1 = squareMatrix1[ip][j]; + squareMatrix1[ip][j] = squareMatrix1[row][j]; + squareMatrix1[row][j] = temp1; + } + } + } + + /** + * A utility function to inverse matrix. This function calculates answer for each row by + * sweeping method of Gauss Jordan elimination + */ + private static void sweep(final int row, final float[][] squareMatrix0, + final float[][] squareMatrix1, final int size) throws MatrixOperationFailedException { + final float pivot = squareMatrix0[row][row]; + if (pivot == 0) { + throw new MatrixOperationFailedException("Inverse failed. Invalid pivot"); + } + for (int j = 0; j < size; ++j) { + squareMatrix0[row][j] /= pivot; + squareMatrix1[row][j] /= pivot; + } + for (int i = 0; i < size; i++) { + final float sweepTargetValue = squareMatrix0[i][row]; + if (i != row) { + for (int j = row; j < size; ++j) { + squareMatrix0[i][j] -= sweepTargetValue * squareMatrix0[row][j]; + } + for (int j = 0; j < size; ++j) { + squareMatrix1[i][j] -= sweepTargetValue * squareMatrix1[row][j]; + } + } + } + } + + /** + * A function to inverse matrix. + * The inverse matrix of squareMatrix will be output to inverseMatrix. Please notice that + * the value of squareMatrix is modified in this function and can't be resuable. + */ + @UsedForTesting + public static void inverse(final float[][] squareMatrix, + final float[][] inverseMatrix) throws MatrixOperationFailedException { + final int size = squareMatrix.length; + if (squareMatrix[0].length != size || inverseMatrix.length != size + || inverseMatrix[0].length != size) { + throw new MatrixOperationFailedException( + "--- invalid length. column should be 2 times larger than row."); + } + for (int i = 0; i < size; ++i) { + Arrays.fill(inverseMatrix[i], 0.0f); + inverseMatrix[i][i] = 1.0f; + } + for (int i = 0; i < size; ++i) { + findPivotAndSwapRow(i, squareMatrix, inverseMatrix, size); + sweep(i, squareMatrix, inverseMatrix, size); + } + } + + /** + * A matrix operation to multiply m0 and m1. + */ + @UsedForTesting + public static void multiply(final float[][] m0, final float[][] m1, + final float[][] retval) throws MatrixOperationFailedException { + if (m0[0].length != m1.length) { + throw new MatrixOperationFailedException( + "--- invalid length for multiply " + m0[0].length + ", " + m1.length); + } + final int m0h = m0.length; + final int m0w = m0[0].length; + final int m1w = m1[0].length; + if (retval.length != m0h || retval[0].length != m1w) { + throw new MatrixOperationFailedException( + "--- invalid length of retval " + retval.length + ", " + retval[0].length); + } + + for (int i = 0; i < m0h; i++) { + Arrays.fill(retval[i], 0); + for (int j = 0; j < m1w; j++) { + for (int k = 0; k < m0w; k++) { + retval[i][j] += m0[i][k] * m1[k][j]; + } + } + } + } + + /** + * A utility function to dump the specified matrix in a readable way + */ + @UsedForTesting + public static void dump(final String title, final float[][] a) { + final int column = a[0].length; + final int row = a.length; + Log.d(TAG, "Dump matrix: " + title); + Log.d(TAG, "/*---------------------"); + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < row; ++i) { + sb.setLength(0); + for (int j = 0; j < column; ++j) { + sb.append(String.format("%4f", a[i][j])).append(' '); + } + Log.d(TAG, sb.toString()); + } + Log.d(TAG, "---------------------*/"); + } +} diff --git a/java/src/com/android/inputmethod/keyboard/internal/SmoothingUtils.java b/java/src/com/android/inputmethod/keyboard/internal/SmoothingUtils.java new file mode 100644 index 000000000..e5665bcdd --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/internal/SmoothingUtils.java @@ -0,0 +1,102 @@ +/* + * 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.keyboard.internal; + +import com.android.inputmethod.annotations.UsedForTesting; +import com.android.inputmethod.keyboard.internal.MatrixUtils.MatrixOperationFailedException; + +import android.util.Log; + +import java.util.Arrays; + +/** + * Utilities to smooth coordinates. Currently, we calculate 3d least squares formula by using + * Lagrangian smoothing + */ +@UsedForTesting +public class SmoothingUtils { + private static final String TAG = SmoothingUtils.class.getSimpleName(); + private static final boolean DEBUG = false; + + private SmoothingUtils() { + // not allowed to instantiate publicly + } + + /** + * Find a most likely 3d least squares formula for specified coordinates. + * "retval" should be a 1x4 size matrix. + */ + @UsedForTesting + public static void get3DParameters(final float[] xs, final float[] ys, + final float[][] retval) throws MatrixOperationFailedException { + final int COEFF_COUNT = 4; // Coefficient count for 3d smoothing + if (retval.length != COEFF_COUNT || retval[0].length != 1) { + Log.d(TAG, "--- invalid length of 3d retval " + retval.length + ", " + + retval[0].length); + return; + } + final int N = xs.length; + // TODO: Never isntantiate the matrix + final float[][] m0 = new float[COEFF_COUNT][COEFF_COUNT]; + final float[][] m0Inv = new float[COEFF_COUNT][COEFF_COUNT]; + final float[][] m1 = new float[COEFF_COUNT][N]; + final float[][] m2 = new float[N][1]; + + // m0 + for (int i = 0; i < COEFF_COUNT; ++i) { + Arrays.fill(m0[i], 0); + for (int j = 0; j < COEFF_COUNT; ++j) { + final int pow = i + j; + for (int k = 0; k < N; ++k) { + m0[i][j] += (float) Math.pow((double) xs[k], pow); + } + } + } + // m0Inv + MatrixUtils.inverse(m0, m0Inv); + if (DEBUG) { + MatrixUtils.dump("m0-1", m0Inv); + } + + // m1 + for (int i = 0; i < COEFF_COUNT; ++i) { + for (int j = 0; j < N; ++j) { + m1[i][j] = (i == 0) ? 1.0f : m1[i - 1][j] * xs[j]; + } + } + + // m2 + for (int i = 0; i < N; ++i) { + m2[i][0] = ys[i]; + } + + final float[][] m0Invxm1 = new float[COEFF_COUNT][N]; + if (DEBUG) { + MatrixUtils.dump("a0", m0Inv); + MatrixUtils.dump("a1", m1); + } + MatrixUtils.multiply(m0Inv, m1, m0Invxm1); + if (DEBUG) { + MatrixUtils.dump("a2", m0Invxm1); + MatrixUtils.dump("a3", m2); + } + MatrixUtils.multiply(m0Invxm1, m2, retval); + if (DEBUG) { + MatrixUtils.dump("result", retval); + } + } +} 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/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 f85c16be3..cebc93c18 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()); } @@ -2368,9 +2369,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, @@ -2701,7 +2704,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/personalization/AccountUtils.java b/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java index 93687e193..a446672cb 100644 --- a/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java +++ b/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java @@ -23,6 +23,7 @@ import android.util.Patterns; import java.util.ArrayList; import java.util.List; +import java.util.Locale; public class AccountUtils { private AccountUtils() { @@ -44,4 +45,22 @@ public class AccountUtils { } return retval; } + + /** + * Get all device accounts having specified domain name. + * @param context application context + * @param domain domain name used for filtering + * @return List of account names that contain the specified domain name + */ + public static List<String> getDeviceAccountsWithDomain( + final Context context, final String domain) { + final ArrayList<String> retval = new ArrayList<String>(); + final String atDomain = "@" + domain.toLowerCase(Locale.ROOT); + for (final Account account : getAccounts(context)) { + if (account.name.toLowerCase(Locale.ROOT).endsWith(atDomain)) { + retval.add(account.name); + } + } + return retval; + } } diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java index ad350a02f..9764610b3 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java @@ -54,6 +54,7 @@ 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.AudioAndHapticFeedbackManager; import com.android.inputmethod.latin.AutoCorrection; import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.Constants; @@ -249,10 +250,12 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick } private CharSequence getStyledSuggestionWord(final SuggestedWords suggestedWords, - final int pos) { - final String word = suggestedWords.getWord(pos); - final boolean isAutoCorrect = pos == 1 && suggestedWords.willAutoCorrect(); - final boolean isTypedWordValid = pos == 0 && suggestedWords.mTypedWordValid; + final int indexInSuggestedWords) { + final String word = suggestedWords.getWord(indexInSuggestedWords); + final boolean isAutoCorrect = indexInSuggestedWords == 1 + && suggestedWords.willAutoCorrect(); + final boolean isTypedWordValid = indexInSuggestedWords == 0 + && suggestedWords.mTypedWordValid; if (!isAutoCorrect && !isTypedWordValid) return word; @@ -269,28 +272,31 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick return spannedWord; } - private int getWordPosition(final int index, final SuggestedWords suggestedWords) { + private int getIndexInSuggestedWords(final int indexInStrip, + final SuggestedWords suggestedWords) { // TODO: This works for 3 suggestions. Revisit this algorithm when there are 5 or more // suggestions. - final int centerPos = suggestedWords.willAutoCorrect() ? 1 : 0; - if (index == mCenterSuggestionIndex) { - return centerPos; - } else if (index == centerPos) { + final int mostImportantIndexInSuggestedWords = suggestedWords.willAutoCorrect() ? 1 : 0; + if (indexInStrip == mCenterSuggestionIndex) { + return mostImportantIndexInSuggestedWords; + } else if (indexInStrip == mostImportantIndexInSuggestedWords) { return mCenterSuggestionIndex; } else { - return index; + return indexInStrip; } } - private int getSuggestionTextColor(final int index, final SuggestedWords suggestedWords, - final int pos) { + private int getSuggestionTextColor(final int indexInStrip, + final SuggestedWords suggestedWords) { + final int indexInSuggestedWords = getIndexInSuggestedWords( + indexInStrip, suggestedWords); // TODO: Need to revisit this logic with bigram suggestions - final boolean isSuggested = (pos != 0); + final boolean isSuggested = (indexInSuggestedWords != 0); final int color; - if (index == mCenterSuggestionIndex && suggestedWords.willAutoCorrect()) { + if (indexInStrip == mCenterSuggestionIndex && suggestedWords.willAutoCorrect()) { color = mColorAutoCorrect; - } else if (index == mCenterSuggestionIndex && suggestedWords.mTypedWordValid) { + } else if (indexInStrip == mCenterSuggestionIndex && suggestedWords.mTypedWordValid) { color = mColorValidTypedWord; } else if (isSuggested) { color = mColorSuggested; @@ -300,7 +306,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick if (LatinImeLogger.sDBG && suggestedWords.size() > 1) { // If we auto-correct, then the autocorrection is in slot 0 and the typed word // is in slot 1. - if (index == mCenterSuggestionIndex + if (indexInStrip == mCenterSuggestionIndex && AutoCorrection.shouldBlockAutoCorrectionBySafetyNet( suggestedWords.getWord(1), suggestedWords.getWord(0))) { return 0xFFFF0000; @@ -337,67 +343,101 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick setupTexts(suggestedWords, countInStrip); mMoreSuggestionsAvailable = (suggestedWords.size() > countInStrip); int x = 0; - for (int index = 0; index < countInStrip; index++) { - final int pos = getWordPosition(index, suggestedWords); - - if (index != 0) { - final View divider = mDividers.get(pos); + for (int indexInStrip = 0; indexInStrip < countInStrip; indexInStrip++) { + if (indexInStrip != 0) { + final View divider = mDividers.get(indexInStrip); // Add divider if this isn't the left most suggestion in suggestions strip. addDivider(stripView, divider); x += divider.getMeasuredWidth(); } - final CharSequence styled = mTexts.get(pos); - final TextView word = mWords.get(pos); - if (index == mCenterSuggestionIndex && mMoreSuggestionsAvailable) { - // TODO: This "more suggestions hint" should have nicely designed icon. - word.setCompoundDrawablesWithIntrinsicBounds( - null, null, null, mMoreSuggestionsHint); - // HACK: To align with other TextView that has no compound drawables. - word.setCompoundDrawablePadding(-mMoreSuggestionsHint.getIntrinsicHeight()); - } else { - word.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null); - } - - // Disable this suggestion if the suggestion is null or empty. - word.setEnabled(!TextUtils.isEmpty(styled)); - word.setTextColor(getSuggestionTextColor(index, suggestedWords, pos)); - final int width = getSuggestionWidth(index, stripWidth); - final CharSequence text = getEllipsizedText(styled, width, word.getPaint()); - final float scaleX = word.getTextScaleX(); - word.setText(text); // TextView.setText() resets text scale x to 1.0. - word.setTextScaleX(scaleX); + final int width = getSuggestionWidth(indexInStrip, stripWidth); + final TextView word = layoutWord(suggestedWords, indexInStrip, width); stripView.addView(word); - setLayoutWeight( - word, getSuggestionWeight(index), ViewGroup.LayoutParams.MATCH_PARENT); + setLayoutWeight(word, getSuggestionWeight(indexInStrip), + ViewGroup.LayoutParams.MATCH_PARENT); x += word.getMeasuredWidth(); - if (DBG && pos < suggestedWords.size()) { - final String debugInfo = Utils.getDebugInfo(suggestedWords, pos); - if (debugInfo != null) { - final TextView info = mInfos.get(pos); - info.setText(debugInfo); - placer.addView(info); - info.measure(ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT); - final int infoWidth = info.getMeasuredWidth(); - final int y = info.getMeasuredHeight(); - ViewLayoutUtils.placeViewAt( - info, x - infoWidth, y, infoWidth, info.getMeasuredHeight()); - } + if (DBG) { + layoutDebugInfo(suggestedWords, indexInStrip, placer, x); } } } - private int getSuggestionWidth(final int index, final int maxWidth) { + /** + * Format appropriately the suggested word indirectly specified by + * <code>indexInStrip</code> as text in a corresponding {@link TextView}. When the + * suggested word doesn't exist, the corresponding {@link TextView} will be disabled + * and never respond to user interaction. The suggested word may be shrunk or ellipsized to + * fit in the specified width. + * + * The <code>indexInStrip</code> argument is the index in the suggestion strip. The indices + * increase towards the right for LTR scripts and the left for RTL scripts, starting with 0. + * The index of the most important suggestion is in {@link #mCenterSuggestionIndex}. This + * usually doesn't match the index in <code>suggedtedWords</code> -- see + * {@link #getIndexInSuggestedWords(int,SuggestedWords)}. + * + * @param suggestedWords the list of suggestions. + * @param indexInStrip the in the suggestion strip. + * @param width the maximum width for layout in pixels. + * @return the {@link TextView} containing the suggested word appropriately formatted. + */ + private TextView layoutWord(final SuggestedWords suggestedWords, final int indexInStrip, + final int width) { + final int indexInSuggestedWords = getIndexInSuggestedWords( + indexInStrip, suggestedWords); + final CharSequence styled = mTexts.get(indexInSuggestedWords); + final TextView word = mWords.get(indexInSuggestedWords); + if (indexInStrip == mCenterSuggestionIndex && mMoreSuggestionsAvailable) { + // TODO: This "more suggestions hint" should have a nicely designed icon. + word.setCompoundDrawablesWithIntrinsicBounds( + null, null, null, mMoreSuggestionsHint); + // HACK: Align with other TextViews that have no compound drawables. + word.setCompoundDrawablePadding(-mMoreSuggestionsHint.getIntrinsicHeight()); + } else { + word.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null); + } + + // Disable this suggestion if the suggestion is null or empty. + word.setEnabled(!TextUtils.isEmpty(styled)); + word.setTextColor(getSuggestionTextColor(indexInStrip, suggestedWords)); + final CharSequence text = getEllipsizedText(styled, width, word.getPaint()); + final float scaleX = word.getTextScaleX(); + word.setText(text); // TextView.setText() resets text scale x to 1.0. + word.setTextScaleX(scaleX); + return word; + } + + private void layoutDebugInfo(final SuggestedWords suggestedWords, final int indexInStrip, + final ViewGroup placer, final int x) { + final int indexInSuggestedWords = getIndexInSuggestedWords( + indexInStrip, suggestedWords); + if (indexInSuggestedWords >= suggestedWords.size()) { + return; + } + final String debugInfo = Utils.getDebugInfo(suggestedWords, indexInSuggestedWords); + if (debugInfo == null) { + return; + } + final TextView info = mInfos.get(indexInSuggestedWords); + info.setText(debugInfo); + placer.addView(info); + info.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + final int infoWidth = info.getMeasuredWidth(); + final int y = info.getMeasuredHeight(); + ViewLayoutUtils.placeViewAt( + info, x - infoWidth, y, infoWidth, info.getMeasuredHeight()); + } + + private int getSuggestionWidth(final int indexInStrip, final int maxWidth) { final int paddings = mPadding * mSuggestionsCountInStrip; final int dividers = mDividerWidth * (mSuggestionsCountInStrip - 1); final int availableWidth = maxWidth - paddings - dividers; - return (int)(availableWidth * getSuggestionWeight(index)); + return (int)(availableWidth * getSuggestionWeight(indexInStrip)); } - private float getSuggestionWeight(final int index) { - if (index == mCenterSuggestionIndex) { + private float getSuggestionWeight(final int indexInStrip) { + if (indexInStrip == mCenterSuggestionIndex) { return mCenterSuggestionWeight; } else { // TODO: Revisit this for cases of 5 or more suggestions @@ -421,16 +461,16 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick private void layoutPunctuationSuggestions(final SuggestedWords suggestedWords, final ViewGroup stripView) { final int countInStrip = Math.min(suggestedWords.size(), PUNCTUATIONS_IN_STRIP); - for (int index = 0; index < countInStrip; index++) { - if (index != 0) { + for (int indexInStrip = 0; indexInStrip < countInStrip; indexInStrip++) { + if (indexInStrip != 0) { // Add divider if this isn't the left most suggestion in suggestions strip. - addDivider(stripView, mDividers.get(index)); + addDivider(stripView, mDividers.get(indexInStrip)); } - final TextView word = mWords.get(index); + final TextView word = mWords.get(indexInStrip); word.setEnabled(true); word.setTextColor(mColorAutoCorrect); - final String text = suggestedWords.getWord(index); + final String text = suggestedWords.getWord(indexInStrip); word.setText(text); word.setTextScaleX(1.0f); word.setCompoundDrawables(null, null, null, null); @@ -689,7 +729,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(); } diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp index 1dd68ea8b..0e9c29261 100644 --- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp +++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp @@ -35,6 +35,7 @@ #include "dictionary.h" #include "jni.h" #include "jni_common.h" +#include "suggest_options.h" namespace latinime { @@ -128,10 +129,9 @@ static jlong latinime_BinaryDictionary_open(JNIEnv *env, jclass clazz, jstring s static int latinime_BinaryDictionary_getSuggestions(JNIEnv *env, jclass clazz, jlong dict, jlong proximityInfo, jlong dicTraverseSession, jintArray xCoordinatesArray, jintArray yCoordinatesArray, jintArray timesArray, jintArray pointerIdsArray, - jintArray inputCodePointsArray, jint inputSize, jint commitPoint, jboolean isGesture, - jintArray prevWordCodePointsForBigrams, jboolean useFullEditDistance, - jintArray outputCodePointsArray, jintArray scoresArray, jintArray spaceIndicesArray, - jintArray outputTypesArray) { + jintArray inputCodePointsArray, jint inputSize, jint commitPoint, jintArray suggestOptions, + jintArray prevWordCodePointsForBigrams, jintArray outputCodePointsArray, + jintArray scoresArray, jintArray spaceIndicesArray, jintArray outputTypesArray) { Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict); if (!dictionary) return 0; ProximityInfo *pInfo = reinterpret_cast<ProximityInfo *>(proximityInfo); @@ -159,6 +159,11 @@ static int latinime_BinaryDictionary_getSuggestions(JNIEnv *env, jclass clazz, j prevWordCodePoints = prevWordCodePointsInternal; } + const jsize numberOfOptions = env->GetArrayLength(suggestOptions); + int options[numberOfOptions]; + env->GetIntArrayRegion(suggestOptions, 0, numberOfOptions, options); + SuggestOptions givenOptions(options, numberOfOptions); + // Output values /* By the way, let's check the output array length here to make sure */ const jsize outputCodePointsLength = env->GetArrayLength(outputCodePointsArray); @@ -185,11 +190,12 @@ static int latinime_BinaryDictionary_getSuggestions(JNIEnv *env, jclass clazz, j memset(outputTypes, 0, sizeof(outputTypes)); int count; - if (isGesture || inputSize > 0) { + if (givenOptions.isGesture() || inputSize > 0) { count = dictionary->getSuggestions(pInfo, traverseSession, xCoordinates, yCoordinates, times, pointerIds, inputCodePoints, inputSize, prevWordCodePoints, - prevWordCodePointsLength, commitPoint, isGesture, useFullEditDistance, - outputCodePoints, scores, spaceIndices, outputTypes); + prevWordCodePointsLength, commitPoint, givenOptions.isGesture(), + givenOptions.useFullEditDistance(), outputCodePoints, scores, + spaceIndices, outputTypes); } else { count = dictionary->getBigrams(prevWordCodePoints, prevWordCodePointsLength, inputCodePoints, inputSize, outputCodePoints, scores, outputTypes); @@ -288,7 +294,7 @@ static JNINativeMethod sMethods[] = { const_cast<char *>("(J)V"), reinterpret_cast<void *>(latinime_BinaryDictionary_close)}, {const_cast<char *>("getSuggestionsNative"), - const_cast<char *>("(JJJ[I[I[I[I[IIIZ[IZ[I[I[I[I)I"), + const_cast<char *>("(JJJ[I[I[I[I[III[I[I[I[I[I[I)I"), reinterpret_cast<void *>(latinime_BinaryDictionary_getSuggestions)}, {const_cast<char *>("getProbabilityNative"), const_cast<char *>("(J[I)I"), diff --git a/native/jni/src/dictionary.h b/native/jni/src/dictionary.h index 2ad5b6c0b..edec83fbf 100644 --- a/native/jni/src/dictionary.h +++ b/native/jni/src/dictionary.h @@ -41,6 +41,10 @@ class Dictionary { static const int KIND_APP_DEFINED = 6; // Suggested by the application static const int KIND_SHORTCUT = 7; // A shortcut static const int KIND_PREDICTION = 8; // A prediction (== a suggestion with no input) + // KIND_RESUMED: A resumed suggestion (comes from a span, currently this type is used only + // in java for re-correction) + static const int KIND_RESUMED = 9; + static const int KIND_OOV_CORRECTION = 10; // Most probable string correction static const int KIND_MASK_FLAGS = 0xFFFFFF00; // Mask to get the flags static const int KIND_FLAG_POSSIBLY_OFFENSIVE = 0x80000000; diff --git a/native/jni/src/suggest_options.h b/native/jni/src/suggest_options.h new file mode 100644 index 000000000..1bed47c93 --- /dev/null +++ b/native/jni/src/suggest_options.h @@ -0,0 +1,65 @@ +/* + * 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. + */ + +#ifndef LATINIME_SUGGEST_OPTIONS_H +#define LATINIME_SUGGEST_OPTIONS_H + +#include "defines.h" + +namespace latinime { + +class SuggestOptions{ + public: + AK_FORCE_INLINE bool isGesture() const { + return getBoolOption(IS_GESTURE); + } + + AK_FORCE_INLINE bool useFullEditDistance() const { + return getBoolOption(USE_FULL_EDIT_DISTANCE); + } + + SuggestOptions(const int *const options, const int length) + : mOptions(options), mLength(length) {} + + private: + // Need to update com.android.inputmethod.latin.NativeSuggestOptions when you add, remove or + // reorder options. + static const int IS_GESTURE = 0; + static const int USE_FULL_EDIT_DISTANCE = 1; + + const int *const mOptions; + const int mLength; + + AK_FORCE_INLINE bool isValidKey(const int key) const { + return 0 <= key && key < mLength; + } + + AK_FORCE_INLINE bool getBoolOption(const int key) const { + if (isValidKey(key)) { + return mOptions[key] != 0; + } + return false; + } + + AK_FORCE_INLINE int getIntOption(const int key) const { + if (isValidKey(key)) { + return mOptions[key]; + } + return 0; + } +}; +} // namespace latinime +#endif // LATINIME_SUGGEST_OPTIONS_H diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserSplitTests.java index 9014e7cc8..eea1efc49 100644 --- a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java +++ b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserSplitTests.java @@ -17,11 +17,13 @@ package com.android.inputmethod.keyboard.internal; import android.app.Instrumentation; +import android.content.Context; +import android.content.res.Resources; import android.test.InstrumentationTestCase; import android.test.suitebuilder.annotation.MediumTest; import com.android.inputmethod.latin.CollectionUtils; -import com.android.inputmethod.latin.StringUtils; +import com.android.inputmethod.latin.LocaleUtils.RunInLocale; import java.lang.reflect.Field; import java.util.ArrayList; @@ -29,16 +31,24 @@ import java.util.Arrays; import java.util.Locale; @MediumTest -public class KeySpecParserCsvTests extends InstrumentationTestCase { - private final KeyboardTextsSet mTextsSet = new KeyboardTextsSet(); +public class KeySpecParserSplitTests extends InstrumentationTestCase { + private static final Locale TEST_LOCALE = Locale.ENGLISH; + final KeyboardTextsSet mTextsSet = new KeyboardTextsSet(); @Override protected void setUp() throws Exception { super.setUp(); final Instrumentation instrumentation = getInstrumentation(); - mTextsSet.setLanguage(Locale.ENGLISH.getLanguage()); - mTextsSet.loadStringResources(instrumentation.getTargetContext()); + final Context targetContext = instrumentation.getTargetContext(); + mTextsSet.setLanguage(TEST_LOCALE.getLanguage()); + new RunInLocale<Void>() { + @Override + protected Void job(final Resources res) { + mTextsSet.loadStringResources(targetContext); + return null; + } + }.runInLocale(targetContext.getResources(), TEST_LOCALE); final String[] testResourceNames = getAllResourceIdNames( com.android.inputmethod.latin.tests.R.string.class); mTextsSet.loadStringResourcesInternal(instrumentation.getContext(), @@ -56,8 +66,8 @@ public class KeySpecParserCsvTests extends InstrumentationTestCase { return names.toArray(new String[names.size()]); } - private static void assertArrayEquals(final String message, final Object[] expected, - final Object[] actual) { + private static <T> void assertArrayEquals(final String message, final T[] expected, + final T[] actual) { if (expected == actual) { return; } @@ -70,15 +80,19 @@ public class KeySpecParserCsvTests extends InstrumentationTestCase { return; } for (int i = 0; i < expected.length; i++) { - assertEquals(message + " [" + i + "]", - Arrays.toString(expected), Arrays.toString(actual)); + final T e = expected[i]; + final T a = actual[i]; + if (e == a) { + continue; + } + assertEquals(message + " [" + i + "]", e, a); } } private void assertTextArray(final String message, final String value, final String ... expectedArray) { final String resolvedActual = KeySpecParser.resolveTextReference(value, mTextsSet); - final String[] actual = StringUtils.parseCsvString(resolvedActual); + final String[] actual = KeySpecParser.splitKeySpecs(resolvedActual); final String[] expected = (expectedArray.length == 0) ? null : expectedArray; assertArrayEquals(message, expected, actual); } @@ -101,7 +115,7 @@ public class KeySpecParserCsvTests extends InstrumentationTestCase { private static final String SURROGATE1 = PAIR1 + PAIR2; private static final String SURROGATE2 = PAIR1 + PAIR2 + PAIR3; - public void testParseCsvTextZero() { + public void testSplitZero() { assertTextArray("Empty string", ""); assertTextArray("Empty entry", ","); assertTextArray("Empty entry at beginning", ",a", "a"); @@ -110,7 +124,7 @@ public class KeySpecParserCsvTests extends InstrumentationTestCase { assertTextArray("Empty entries with escape", ",a,b\\,c,,d,", "a", "b\\,c", "d"); } - public void testParseCsvTextSingle() { + public void testSplitSingle() { assertTextArray("Single char", "a", "a"); assertTextArray("Surrogate pair", PAIR1, PAIR1); assertTextArray("Single escape", "\\", "\\"); @@ -139,7 +153,7 @@ public class KeySpecParserCsvTests extends InstrumentationTestCase { assertTextArray("Incomplete resource reference 4", "!" + SURROGATE2, "!" + SURROGATE2); } - public void testParseCsvTextSingleEscaped() { + public void testSplitSingleEscaped() { assertTextArray("Escaped char", "\\a", "\\a"); assertTextArray("Escaped surrogate pair", "\\" + PAIR1, "\\" + PAIR1); assertTextArray("Escaped comma", "\\,", "\\,"); @@ -174,7 +188,7 @@ public class KeySpecParserCsvTests extends InstrumentationTestCase { assertTextArray("Escaped !TEXT/NAME", "\\!TEXT/EMPTY_STRING", "\\!TEXT/EMPTY_STRING"); } - public void testParseCsvTextMulti() { + public void testSplitMulti() { assertTextArray("Multiple chars", "a,b,c", "a", "b", "c"); assertTextArray("Multiple chars", "a,b,\\c", "a", "b", "\\c"); assertTextArray("Multiple chars and escape at beginning and end", @@ -189,7 +203,7 @@ public class KeySpecParserCsvTests extends InstrumentationTestCase { " abc ", " def ", " ghi "); } - public void testParseCsvTextMultiEscaped() { + public void testSplitMultiEscaped() { assertTextArray("Multiple chars with comma", "a,\\,,c", "a", "\\,", "c"); assertTextArray("Multiple chars with comma surrounded by spaces", " a , \\, , c ", " a ", " \\, ", " c "); @@ -208,17 +222,17 @@ public class KeySpecParserCsvTests extends InstrumentationTestCase { "\\!", "\\!TEXT/EMPTY_STRING"); } - public void testParseCsvResourceError() { + public void testSplitResourceError() { assertError("Incomplete resource name", "!text/", "!text/"); assertError("Non existing resource", "!text/non_existing"); } - public void testParseCsvResourceZero() { + public void testSplitResourceZero() { assertTextArray("Empty string", "!text/empty_string"); } - public void testParseCsvResourceSingle() { + public void testSplitResourceSingle() { assertTextArray("Single char", "!text/single_char", "a"); assertTextArray("Space", @@ -240,7 +254,7 @@ public class KeySpecParserCsvTests extends InstrumentationTestCase { "\\\\!text/single_char", "\\\\a"); } - public void testParseCsvResourceSingleEscaped() { + public void testSplitResourceSingleEscaped() { assertTextArray("Escaped char", "!text/escaped_char", "\\a"); assertTextArray("Escaped comma", @@ -267,7 +281,7 @@ public class KeySpecParserCsvTests extends InstrumentationTestCase { "!text/escaped_label_with_escape", "a\\\\c"); } - public void testParseCsvResourceMulti() { + public void testSplitResourceMulti() { assertTextArray("Multiple chars", "!text/multiple_chars", "a", "b", "c"); assertTextArray("Multiple chars surrounded by spaces", @@ -279,7 +293,7 @@ public class KeySpecParserCsvTests extends InstrumentationTestCase { "!text/multiple_labels_surrounded_by_spaces", " abc ", " def ", " ghi "); } - public void testParseCsvResourcetMultiEscaped() { + public void testSplitResourcetMultiEscaped() { assertTextArray("Multiple chars with comma", "!text/multiple_chars_with_comma", "a", "\\,", "c"); @@ -300,7 +314,7 @@ public class KeySpecParserCsvTests extends InstrumentationTestCase { " ab\\\\ ", " d\\\\\\, ", " g\\,i "); } - public void testParseMultipleResources() { + public void testSplitMultipleResources() { assertTextArray("Literals and resources", "1,!text/multiple_chars,z", "1", "a", "b", "c", "z"); assertTextArray("Literals and resources and escape at end", @@ -322,7 +336,7 @@ public class KeySpecParserCsvTests extends InstrumentationTestCase { "abcabc", "def", "ghi"); } - public void testParseIndirectReference() { + public void testSplitIndirectReference() { assertTextArray("Indirect", "!text/indirect_string", "a", "b", "c"); assertTextArray("Indirect with literal", @@ -331,7 +345,7 @@ public class KeySpecParserCsvTests extends InstrumentationTestCase { "!text/indirect2_string", "a", "b", "c"); } - public void testParseInfiniteIndirectReference() { + public void testSplitInfiniteIndirectReference() { assertError("Infinite indirection", "1,!text/infinite_indirection,2", "1", "infinite", "<infinite>", "loop", "2"); } diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java index b1ae6f5b7..b55158d3e 100644 --- a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java +++ b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java @@ -20,23 +20,27 @@ import static com.android.inputmethod.keyboard.internal.KeyboardIconsSet.ICON_UN import static com.android.inputmethod.latin.Constants.CODE_OUTPUT_TEXT; import static com.android.inputmethod.latin.Constants.CODE_UNSPECIFIED; +import android.content.Context; +import android.content.res.Resources; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.LocaleUtils.RunInLocale; import java.util.Arrays; import java.util.Locale; @SmallTest public class KeySpecParserTests extends AndroidTestCase { - private final KeyboardCodesSet mCodesSet = new KeyboardCodesSet(); - private final KeyboardTextsSet mTextsSet = new KeyboardTextsSet(); + private final static Locale TEST_LOCALE = Locale.ENGLISH; + final KeyboardCodesSet mCodesSet = new KeyboardCodesSet(); + final KeyboardTextsSet mTextsSet = new KeyboardTextsSet(); private static final String CODE_SETTINGS = "!code/key_settings"; private static final String ICON_SETTINGS = "!icon/settings_key"; - private static final String CODE_SETTINGS_UPPERCASE = CODE_SETTINGS.toUpperCase(); - private static final String ICON_SETTINGS_UPPERCASE = ICON_SETTINGS.toUpperCase(); + private static final String CODE_SETTINGS_UPPERCASE = CODE_SETTINGS.toUpperCase(Locale.ROOT); + private static final String ICON_SETTINGS_UPPERCASE = ICON_SETTINGS.toUpperCase(Locale.ROOT); private static final String CODE_NON_EXISTING = "!code/non_existing"; private static final String ICON_NON_EXISTING = "!icon/non_existing"; @@ -48,10 +52,17 @@ public class KeySpecParserTests extends AndroidTestCase { protected void setUp() throws Exception { super.setUp(); - final String language = Locale.ENGLISH.getLanguage(); + final String language = TEST_LOCALE.getLanguage(); mCodesSet.setLanguage(language); mTextsSet.setLanguage(language); - mTextsSet.loadStringResources(getContext()); + final Context context = getContext(); + new RunInLocale<Void>() { + @Override + protected Void job(final Resources res) { + mTextsSet.loadStringResources(context); + return null; + } + }.runInLocale(context.getResources(), TEST_LOCALE); mCodeSettings = KeySpecParser.parseCode( CODE_SETTINGS, mCodesSet, CODE_UNSPECIFIED); @@ -587,7 +598,7 @@ public class KeySpecParserTests extends AndroidTestCase { new String[] { null, "a", "b", "c" }, true); // Upper case specification will not work. assertGetBooleanValue("HAS LABEL", HAS_LABEL, - new String[] { HAS_LABEL.toUpperCase(), "a", "b", "c" }, + new String[] { HAS_LABEL.toUpperCase(Locale.ROOT), "a", "b", "c" }, new String[] { "!HASLABEL!", "a", "b", "c" }, false); assertGetBooleanValue("No has label", HAS_LABEL, @@ -600,13 +611,13 @@ public class KeySpecParserTests extends AndroidTestCase { // Upper case specification will not work. assertGetBooleanValue("Multiple has label", HAS_LABEL, new String[] { - "a", HAS_LABEL.toUpperCase(), "b", "c", HAS_LABEL, "d" }, + "a", HAS_LABEL.toUpperCase(Locale.ROOT), "b", "c", HAS_LABEL, "d" }, new String[] { "a", "!HASLABEL!", "b", "c", null, "d" }, true); // Upper case specification will not work. assertGetBooleanValue("Multiple has label with needs dividers", HAS_LABEL, new String[] { - "a", HAS_LABEL, "b", NEEDS_DIVIDER, HAS_LABEL.toUpperCase(), "d" }, + "a", HAS_LABEL, "b", NEEDS_DIVIDER, HAS_LABEL.toUpperCase(Locale.ROOT), "d" }, new String[] { "a", null, "b", NEEDS_DIVIDER, "!HASLABEL!", "d" }, true); } @@ -625,7 +636,7 @@ public class KeySpecParserTests extends AndroidTestCase { new String[] { null, "a", "b", "c" }, 3); // Upper case specification will not work. assertGetIntValue("FIXED COLUMN ORDER 3", FIXED_COLUMN_ORDER, -1, - new String[] { FIXED_COLUMN_ORDER.toUpperCase() + "3", "a", "b", "c" }, + new String[] { FIXED_COLUMN_ORDER.toUpperCase(Locale.ROOT) + "3", "a", "b", "c" }, new String[] { "!FIXEDCOLUMNORDER!3", "a", "b", "c" }, -1); assertGetIntValue("No fixed column order", FIXED_COLUMN_ORDER, -1, @@ -641,7 +652,7 @@ public class KeySpecParserTests extends AndroidTestCase { // Upper case specification will not work. assertGetIntValue("Multiple fixed column order 5,3 with has label", FIXED_COLUMN_ORDER, -1, new String[] { - FIXED_COLUMN_ORDER.toUpperCase() + "5", HAS_LABEL, "a", + FIXED_COLUMN_ORDER.toUpperCase(Locale.ROOT) + "5", HAS_LABEL, "a", FIXED_COLUMN_ORDER + "3", "b" }, new String[] { "!FIXEDCOLUMNORDER!5", HAS_LABEL, "a", null, "b" }, 3); } diff --git a/tests/src/com/android/inputmethod/keyboard/internal/MatrixUtilsTests.java b/tests/src/com/android/inputmethod/keyboard/internal/MatrixUtilsTests.java new file mode 100644 index 000000000..e2a11ab99 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/internal/MatrixUtilsTests.java @@ -0,0 +1,84 @@ +/* + * 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.keyboard.internal; + +import com.android.inputmethod.keyboard.internal.MatrixUtils.MatrixOperationFailedException; + +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +@SmallTest +public class MatrixUtilsTests extends AndroidTestCase { + // "run tests" -c com.android.inputmethod.keyboard.internal.MatrixUtilsTests + private static final boolean DEBUG = false; + private static final float EPSILON = 0.00001f; + + private static void assertEqualsFloat(float f0, float f1) { + assertEqualsFloat(f0, f1, EPSILON); + } + + /* package */ static void assertEqualsFloat(float f0, float f1, float error) { + assertTrue(Math.abs(f0 - f1) < error); + } + + public void testMulti() { + final float[][] matrixA = {{1, 2}, {3, 4}}; + final float[][] matrixB = {{5, 6}, {7, 8}}; + final float[][] retval = new float[2][2]; + try { + MatrixUtils.multiply(matrixA, matrixB, retval); + } catch (MatrixOperationFailedException e) { + assertTrue(false); + } + if (DEBUG) { + MatrixUtils.dump("multi", retval); + } + assertEqualsFloat(retval[0][0], 19); + assertEqualsFloat(retval[0][1], 22); + assertEqualsFloat(retval[1][0], 43); + assertEqualsFloat(retval[1][1], 50); + } + + public void testInverse() { + final int N = 4; + final float[][] matrix = + {{1, 2, 3, 4}, {4, 0, 5, 6}, {6, 4, 2, 0}, {6, 4, 2, 1}}; + final float[][] inverse = new float[N][N]; + final float[][] tempMatrix = new float[N][N]; + for (int i = 0; i < N; ++i) { + for (int j = 0; j < N; ++j) { + tempMatrix[i][j] = matrix[i][j]; + } + } + final float[][] retval = new float[N][N]; + try { + MatrixUtils.inverse(tempMatrix, inverse); + } catch (MatrixOperationFailedException e) { + assertTrue(false); + } + try { + MatrixUtils.multiply(matrix, inverse, retval); + } catch (MatrixOperationFailedException e) { + assertTrue(false); + } + for (int i = 0; i < N; ++i) { + for (int j = 0; j < N; ++j) { + assertEqualsFloat(((i == j) ? 1.0f : 0.0f), retval[i][j]); + } + } + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java b/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java index 2544b6c52..2cb964805 100644 --- a/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java +++ b/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java @@ -149,11 +149,6 @@ public class MockKeyboardSwitcher implements KeyboardState.SwitchActions { mLongPressTimeoutCode = 0; } - @Override - public void hapticAndAudioFeedback(final int code) { - // Nothing to do. - } - public void onLongPressTimeout(final int code) { // TODO: Handle simultaneous long presses. if (mLongPressTimeoutCode == code) { diff --git a/tests/src/com/android/inputmethod/keyboard/internal/SmoothingUtilsTests.java b/tests/src/com/android/inputmethod/keyboard/internal/SmoothingUtilsTests.java new file mode 100644 index 000000000..293741af2 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/internal/SmoothingUtilsTests.java @@ -0,0 +1,45 @@ +/* + * 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.keyboard.internal; + +import com.android.inputmethod.keyboard.internal.MatrixUtils.MatrixOperationFailedException; + +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +@SmallTest +public class SmoothingUtilsTests extends AndroidTestCase { + // "run tests" -c com.android.inputmethod.keyboard.internal.SmoothingUtilsTests + private static final boolean DEBUG = false; + + public void testGet3DParamaters() { + final float[] xs = new float[] {0, 1, 2, 3, 4}; + final float[] ys = new float[] {1, 4, 15, 40, 85}; // y = x^3 + x^2 + x + 1 + final float[][] retval = new float[4][1]; + try { + SmoothingUtils.get3DParameters(xs, ys, retval); + if (DEBUG) { + MatrixUtils.dump("3d", retval); + } + for (int i = 0; i < 4; ++i) { + MatrixUtilsTests.assertEqualsFloat(retval[i][0], 1.0f, 0.001f); + } + } catch (MatrixOperationFailedException e) { + assertTrue(false); + } + } +} diff --git a/tests/src/com/android/inputmethod/latin/InputPointersTests.java b/tests/src/com/android/inputmethod/latin/InputPointersTests.java index e1149b3fd..301582979 100644 --- a/tests/src/com/android/inputmethod/latin/InputPointersTests.java +++ b/tests/src/com/android/inputmethod/latin/InputPointersTests.java @@ -108,13 +108,13 @@ public class InputPointersTests extends AndroidTestCase { assertNotSame("pointerIds after copy", dst.getPointerIds(), src.getPointerIds()); assertNotSame("times after copy", dst.getTimes(), src.getTimes()); final int size = dst.getPointerSize(); - assertArrayEquals("xCoordinates values after copy", + assertIntArrayEquals("xCoordinates values after copy", dst.getXCoordinates(), 0, src.getXCoordinates(), 0, size); - assertArrayEquals("yCoordinates values after copy", + assertIntArrayEquals("yCoordinates values after copy", dst.getYCoordinates(), 0, src.getYCoordinates(), 0, size); - assertArrayEquals("pointerIds values after copy", + assertIntArrayEquals("pointerIds values after copy", dst.getPointerIds(), 0, src.getPointerIds(), 0, size); - assertArrayEquals("times values after copy", + assertIntArrayEquals("times values after copy", dst.getTimes(), 0, src.getTimes(), 0, size); } @@ -135,34 +135,34 @@ public class InputPointersTests extends AndroidTestCase { dst.append(src, 0, 0); assertEquals("size after append zero", dstLen, dst.getPointerSize()); - assertArrayEquals("xCoordinates after append zero", + assertIntArrayEquals("xCoordinates after append zero", dstCopy.getXCoordinates(), 0, dst.getXCoordinates(), 0, dstLen); - assertArrayEquals("yCoordinates after append zero", + assertIntArrayEquals("yCoordinates after append zero", dstCopy.getYCoordinates(), 0, dst.getYCoordinates(), 0, dstLen); - assertArrayEquals("pointerIds after append zero", + assertIntArrayEquals("pointerIds after append zero", dstCopy.getPointerIds(), 0, dst.getPointerIds(), 0, dstLen); - assertArrayEquals("times after append zero", + assertIntArrayEquals("times after append zero", dstCopy.getTimes(), 0, dst.getTimes(), 0, dstLen); dst.append(src, 0, srcLen); assertEquals("size after append", dstLen + srcLen, dst.getPointerSize()); assertTrue("primitive length after append", dst.getPointerIds().length >= dstLen + srcLen); - assertArrayEquals("original xCoordinates values after append", + assertIntArrayEquals("original xCoordinates values after append", dstCopy.getXCoordinates(), 0, dst.getXCoordinates(), 0, dstLen); - assertArrayEquals("original yCoordinates values after append", + assertIntArrayEquals("original yCoordinates values after append", dstCopy.getYCoordinates(), 0, dst.getYCoordinates(), 0, dstLen); - assertArrayEquals("original pointerIds values after append", + assertIntArrayEquals("original pointerIds values after append", dstCopy.getPointerIds(), 0, dst.getPointerIds(), 0, dstLen); - assertArrayEquals("original times values after append", + assertIntArrayEquals("original times values after append", dstCopy.getTimes(), 0, dst.getTimes(), 0, dstLen); - assertArrayEquals("appended xCoordinates values after append", + assertIntArrayEquals("appended xCoordinates values after append", src.getXCoordinates(), 0, dst.getXCoordinates(), dstLen, srcLen); - assertArrayEquals("appended yCoordinates values after append", + assertIntArrayEquals("appended yCoordinates values after append", src.getYCoordinates(), 0, dst.getYCoordinates(), dstLen, srcLen); - assertArrayEquals("appended pointerIds values after append", + assertIntArrayEquals("appended pointerIds values after append", src.getPointerIds(), 0, dst.getPointerIds(), dstLen, srcLen); - assertArrayEquals("appended times values after append", + assertIntArrayEquals("appended times values after append", src.getTimes(), 0, dst.getTimes(), dstLen, srcLen); } @@ -190,47 +190,55 @@ public class InputPointersTests extends AndroidTestCase { dst.append(srcPointerId, srcTimes, srcXCoords, srcYCoords, 0, 0); assertEquals("size after append zero", dstLen, dst.getPointerSize()); - assertArrayEquals("xCoordinates after append zero", + assertIntArrayEquals("xCoordinates after append zero", dstCopy.getXCoordinates(), 0, dst.getXCoordinates(), 0, dstLen); - assertArrayEquals("yCoordinates after append zero", + assertIntArrayEquals("yCoordinates after append zero", dstCopy.getYCoordinates(), 0, dst.getYCoordinates(), 0, dstLen); - assertArrayEquals("pointerIds after append zero", + assertIntArrayEquals("pointerIds after append zero", dstCopy.getPointerIds(), 0, dst.getPointerIds(), 0, dstLen); - assertArrayEquals("times after append zero", + assertIntArrayEquals("times after append zero", dstCopy.getTimes(), 0, dst.getTimes(), 0, dstLen); dst.append(srcPointerId, srcTimes, srcXCoords, srcYCoords, 0, srcLen); assertEquals("size after append", dstLen + srcLen, dst.getPointerSize()); assertTrue("primitive length after append", dst.getPointerIds().length >= dstLen + srcLen); - assertArrayEquals("original xCoordinates values after append", + assertIntArrayEquals("original xCoordinates values after append", dstCopy.getXCoordinates(), 0, dst.getXCoordinates(), 0, dstLen); - assertArrayEquals("original yCoordinates values after append", + assertIntArrayEquals("original yCoordinates values after append", dstCopy.getYCoordinates(), 0, dst.getYCoordinates(), 0, dstLen); - assertArrayEquals("original pointerIds values after append", + assertIntArrayEquals("original pointerIds values after append", dstCopy.getPointerIds(), 0, dst.getPointerIds(), 0, dstLen); - assertArrayEquals("original times values after append", + assertIntArrayEquals("original times values after append", dstCopy.getTimes(), 0, dst.getTimes(), 0, dstLen); - assertArrayEquals("appended xCoordinates values after append", + assertIntArrayEquals("appended xCoordinates values after append", srcXCoords.getPrimitiveArray(), 0, dst.getXCoordinates(), dstLen, srcLen); - assertArrayEquals("appended yCoordinates values after append", + assertIntArrayEquals("appended yCoordinates values after append", srcYCoords.getPrimitiveArray(), 0, dst.getYCoordinates(), dstLen, srcLen); - assertArrayEquals("appended pointerIds values after append", + assertIntArrayEquals("appended pointerIds values after append", srcPointerIds, 0, dst.getPointerIds(), dstLen, srcLen); - assertArrayEquals("appended times values after append", + assertIntArrayEquals("appended times values after append", srcTimes.getPrimitiveArray(), 0, dst.getTimes(), dstLen, srcLen); } - private static void assertArrayEquals(String message, int[] expecteds, int expectedPos, - int[] actuals, int actualPos, int length) { - if (expecteds == null && actuals == null) { + // TODO: Consolidate this method with + // {@link ResizableIntArrayTests#assertIntArrayEquals(String,int[],int,int[],int,int)}. + private static void assertIntArrayEquals(final String message, final int[] expecteds, + final int expectedPos, final int[] actuals, final int actualPos, final int length) { + if (expecteds == actuals) { return; } if (expecteds == null || actuals == null) { - fail(message + ": expecteds=" + expecteds + " actuals=" + actuals); + assertEquals(message, Arrays.toString(expecteds), Arrays.toString(actuals)); + return; + } + if (expecteds.length < expectedPos + length || actuals.length < actualPos + length) { + fail(message + ": insufficient length: expecteds=" + Arrays.toString(expecteds) + + " actuals=" + Arrays.toString(actuals)); + return; } for (int i = 0; i < length; i++) { - assertEquals(message + ": element at " + i, + assertEquals(message + " [" + i + "]", expecteds[i + expectedPos], actuals[i + actualPos]); } } diff --git a/tests/src/com/android/inputmethod/latin/ResizableIntArrayTests.java b/tests/src/com/android/inputmethod/latin/ResizableIntArrayTests.java index 2d1b836c2..b9fee950e 100644 --- a/tests/src/com/android/inputmethod/latin/ResizableIntArrayTests.java +++ b/tests/src/com/android/inputmethod/latin/ResizableIntArrayTests.java @@ -19,6 +19,8 @@ package com.android.inputmethod.latin; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; +import java.util.Arrays; + @SmallTest public class ResizableIntArrayTests extends AndroidTestCase { private static final int DEFAULT_CAPACITY = 48; @@ -186,7 +188,7 @@ public class ResizableIntArrayTests extends AndroidTestCase { assertEquals("length after copy", dst.getLength(), src.getLength()); assertSame("array after copy", array, dst.getPrimitiveArray()); assertNotSame("array after copy", dst.getPrimitiveArray(), src.getPrimitiveArray()); - assertArrayEquals("values after copy", + assertIntArrayEquals("values after copy", dst.getPrimitiveArray(), 0, src.getPrimitiveArray(), 0, dst.getLength()); final int smallerLength = DEFAULT_CAPACITY / 2; @@ -197,7 +199,7 @@ public class ResizableIntArrayTests extends AndroidTestCase { assertEquals("length after copy to smaller", dst.getLength(), src.getLength()); assertNotSame("array after copy to smaller", array2, array3); assertNotSame("array after copy to smaller", array3, src.getPrimitiveArray()); - assertArrayEquals("values after copy to smaller", + assertIntArrayEquals("values after copy to smaller", dst.getPrimitiveArray(), 0, src.getPrimitiveArray(), 0, dst.getLength()); } @@ -220,7 +222,7 @@ public class ResizableIntArrayTests extends AndroidTestCase { dst.append(src, 0, 0); assertEquals("length after append zero", dstLen, dst.getLength()); assertSame("array after append zero", array, dst.getPrimitiveArray()); - assertArrayEquals("values after append zero", + assertIntArrayEquals("values after append zero", dstCopy.getPrimitiveArray(), 0, dst.getPrimitiveArray(), 0, dstLen); dst.append(src, 0, srcLen); @@ -228,9 +230,9 @@ public class ResizableIntArrayTests extends AndroidTestCase { assertSame("array after append", array, dst.getPrimitiveArray()); assertTrue("primitive length after append", dst.getPrimitiveArray().length >= dstLen + srcLen); - assertArrayEquals("original values after append", + assertIntArrayEquals("original values after append", dstCopy.getPrimitiveArray(), 0, dst.getPrimitiveArray(), 0, dstLen); - assertArrayEquals("appended values after append", + assertIntArrayEquals("appended values after append", src.getPrimitiveArray(), 0, dst.getPrimitiveArray(), dstLen, srcLen); dst.append(src, 0, srcLen); @@ -238,11 +240,11 @@ public class ResizableIntArrayTests extends AndroidTestCase { assertNotSame("array after 2nd append", array, dst.getPrimitiveArray()); assertTrue("primitive length after 2nd append", dst.getPrimitiveArray().length >= dstLen + srcLen * 2); - assertArrayEquals("original values after 2nd append", + assertIntArrayEquals("original values after 2nd append", dstCopy.getPrimitiveArray(), 0, dst.getPrimitiveArray(), 0, dstLen); - assertArrayEquals("appended values after 2nd append", + assertIntArrayEquals("appended values after 2nd append", src.getPrimitiveArray(), 0, dst.getPrimitiveArray(), dstLen, srcLen); - assertArrayEquals("appended values after 2nd append", + assertIntArrayEquals("appended values after 2nd append", src.getPrimitiveArray(), 0, dst.getPrimitiveArray(), dstLen + srcLen, srcLen); } @@ -319,16 +321,22 @@ public class ResizableIntArrayTests extends AndroidTestCase { } } - private static void assertArrayEquals(String message, int[] expecteds, int expectedPos, - int[] actuals, int actualPos, int length) { - if (expecteds == null && actuals == null) { + private static void assertIntArrayEquals(final String message, final int[] expecteds, + final int expectedPos, final int[] actuals, final int actualPos, final int length) { + if (expecteds == actuals) { return; } if (expecteds == null || actuals == null) { - fail(message + ": expecteds=" + expecteds + " actuals=" + actuals); + assertEquals(message, Arrays.toString(expecteds), Arrays.toString(actuals)); + return; + } + if (expecteds.length < expectedPos + length || actuals.length < actualPos + length) { + fail(message + ": insufficient length: expecteds=" + Arrays.toString(expecteds) + + " actuals=" + Arrays.toString(actuals)); + return; } for (int i = 0; i < length; i++) { - assertEquals(message + ": element at " + i, + assertEquals(message + " [" + i + "]", expecteds[i + expectedPos], actuals[i + actualPos]); } } diff --git a/tests/src/com/android/inputmethod/latin/ResourceUtilsTests.java b/tests/src/com/android/inputmethod/latin/ResourceUtilsTests.java index ed16846b9..c915522ee 100644 --- a/tests/src/com/android/inputmethod/latin/ResourceUtilsTests.java +++ b/tests/src/com/android/inputmethod/latin/ResourceUtilsTests.java @@ -19,25 +19,41 @@ package com.android.inputmethod.latin; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; +import com.android.inputmethod.latin.ResourceUtils.DeviceOverridePatternSyntaxError; + import java.util.HashMap; @SmallTest public class ResourceUtilsTests extends AndroidTestCase { public void testFindDefaultConstant() { final String[] nullArray = null; - assertNull(ResourceUtils.findDefaultConstant(nullArray)); - final String[] emptyArray = {}; - assertNull(ResourceUtils.findDefaultConstant(emptyArray)); - final String[] array = { + "HARDWARE=grouper,0.3", + "HARDWARE=mako,0.4", + ",defaultValue1", + "HARDWARE=manta,0.2", + ",defaultValue2", + }; + + try { + assertNull(ResourceUtils.findDefaultConstant(nullArray)); + assertNull(ResourceUtils.findDefaultConstant(emptyArray)); + assertEquals(ResourceUtils.findDefaultConstant(array), "defaultValue1"); + } catch (final DeviceOverridePatternSyntaxError e) { + fail(e.getMessage()); + } + + final String[] errorArray = { "HARDWARE=grouper,0.3", - "HARDWARE=mako,0.4", - ",defaultValue1", - "HARDWARE=manta,0.2", - ",defaultValue2", + "no_comma" }; - assertEquals(ResourceUtils.findDefaultConstant(array), "defaultValue1"); + try { + final String defaultValue = ResourceUtils.findDefaultConstant(errorArray); + fail("exception should be thrown: defaultValue=" + defaultValue); + } catch (final DeviceOverridePatternSyntaxError e) { + assertEquals("Array element has no comma: no_comma", e.getMessage()); + } } public void testFindConstantForKeyValuePairsSimple() { @@ -67,33 +83,23 @@ public class ResourceUtilsTests extends AndroidTestCase { final HashMap<String,String> keyValues = CollectionUtils.newHashMap(); keyValues.put(HARDWARE_KEY, "grouper"); - assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.3"); + assertEquals("0.3", ResourceUtils.findConstantForKeyValuePairs(keyValues, array)); keyValues.put(HARDWARE_KEY, "mako"); - assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.4"); + assertEquals("0.4", ResourceUtils.findConstantForKeyValuePairs(keyValues, array)); keyValues.put(HARDWARE_KEY, "manta"); - assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.2"); + assertEquals("0.2", ResourceUtils.findConstantForKeyValuePairs(keyValues, array)); + + keyValues.clear(); + keyValues.put("hardware", "grouper"); + assertNull(ResourceUtils.findConstantForKeyValuePairs(keyValues, array)); - try { - keyValues.clear(); - keyValues.put("hardware", "grouper"); - final String constant = ResourceUtils.findConstantForKeyValuePairs(keyValues, array); - fail("condition without HARDWARE must fail: constant=" + constant); - } catch (final RuntimeException e) { - assertEquals(e.getMessage(), "Found unknown key: HARDWARE=grouper"); - } keyValues.clear(); keyValues.put(HARDWARE_KEY, "MAKO"); assertNull(ResourceUtils.findConstantForKeyValuePairs(keyValues, array)); keyValues.put(HARDWARE_KEY, "mantaray"); assertNull(ResourceUtils.findConstantForKeyValuePairs(keyValues, array)); - try { - final String constant = ResourceUtils.findConstantForKeyValuePairs( - emptyKeyValue, array); - fail("emptyCondition shouldn't match: constant=" + constant); - } catch (final RuntimeException e) { - assertEquals(e.getMessage(), "Found unknown key: HARDWARE=grouper"); - } + assertNull(ResourceUtils.findConstantForKeyValuePairs(emptyKeyValue, array)); } public void testFindConstantForKeyValuePairsCombined() { @@ -102,6 +108,8 @@ public class ResourceUtilsTests extends AndroidTestCase { final String MANUFACTURER_KEY = "MANUFACTURER"; final String[] array = { ",defaultValue", + "no_comma", + "error_pattern,0.1", "HARDWARE=grouper:MANUFACTURER=asus,0.3", "HARDWARE=mako:MODEL=Nexus 4,0.4", "HARDWARE=manta:MODEL=Nexus 10:MANUFACTURER=samsung,0.2" @@ -117,25 +125,25 @@ public class ResourceUtilsTests extends AndroidTestCase { keyValues.put(HARDWARE_KEY, "grouper"); keyValues.put(MODEL_KEY, "Nexus 7"); keyValues.put(MANUFACTURER_KEY, "asus"); - assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.3"); + assertEquals("0.3", ResourceUtils.findConstantForKeyValuePairs(keyValues, array)); assertNull(ResourceUtils.findConstantForKeyValuePairs(keyValues, failArray)); keyValues.clear(); keyValues.put(HARDWARE_KEY, "mako"); keyValues.put(MODEL_KEY, "Nexus 4"); keyValues.put(MANUFACTURER_KEY, "LGE"); - assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.4"); + assertEquals("0.4", ResourceUtils.findConstantForKeyValuePairs(keyValues, array)); assertNull(ResourceUtils.findConstantForKeyValuePairs(keyValues, failArray)); keyValues.clear(); keyValues.put(HARDWARE_KEY, "manta"); keyValues.put(MODEL_KEY, "Nexus 10"); keyValues.put(MANUFACTURER_KEY, "samsung"); - assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.2"); + assertEquals("0.2", ResourceUtils.findConstantForKeyValuePairs(keyValues, array)); assertNull(ResourceUtils.findConstantForKeyValuePairs(keyValues, failArray)); keyValues.put(HARDWARE_KEY, "mantaray"); assertNull(ResourceUtils.findConstantForKeyValuePairs(keyValues, array)); - assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, failArray), "0.2"); + assertEquals("0.2", ResourceUtils.findConstantForKeyValuePairs(keyValues, failArray)); } public void testFindConstantForKeyValuePairsRegexp() { @@ -144,6 +152,8 @@ public class ResourceUtilsTests extends AndroidTestCase { final String MANUFACTURER_KEY = "MANUFACTURER"; final String[] array = { ",defaultValue", + "no_comma", + "HARDWARE=error_regexp:MANUFACTURER=error[regexp,0.1", "HARDWARE=grouper|tilapia:MANUFACTURER=asus,0.3", "HARDWARE=[mM][aA][kK][oO]:MODEL=Nexus 4,0.4", "HARDWARE=manta.*:MODEL=Nexus 10:MANUFACTURER=samsung,0.2" @@ -153,24 +163,24 @@ public class ResourceUtilsTests extends AndroidTestCase { keyValues.put(HARDWARE_KEY, "grouper"); keyValues.put(MODEL_KEY, "Nexus 7"); keyValues.put(MANUFACTURER_KEY, "asus"); - assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.3"); + assertEquals("0.3", ResourceUtils.findConstantForKeyValuePairs(keyValues, array)); keyValues.put(HARDWARE_KEY, "tilapia"); - assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.3"); + assertEquals("0.3", ResourceUtils.findConstantForKeyValuePairs(keyValues, array)); keyValues.clear(); keyValues.put(HARDWARE_KEY, "mako"); keyValues.put(MODEL_KEY, "Nexus 4"); keyValues.put(MANUFACTURER_KEY, "LGE"); - assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.4"); + assertEquals("0.4", ResourceUtils.findConstantForKeyValuePairs(keyValues, array)); keyValues.put(HARDWARE_KEY, "MAKO"); - assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.4"); + assertEquals("0.4", ResourceUtils.findConstantForKeyValuePairs(keyValues, array)); keyValues.clear(); keyValues.put(HARDWARE_KEY, "manta"); keyValues.put(MODEL_KEY, "Nexus 10"); keyValues.put(MANUFACTURER_KEY, "samsung"); - assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.2"); + assertEquals("0.2", ResourceUtils.findConstantForKeyValuePairs(keyValues, array)); keyValues.put(HARDWARE_KEY, "mantaray"); - assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.2"); + assertEquals("0.2", ResourceUtils.findConstantForKeyValuePairs(keyValues, array)); } } diff --git a/tests/src/com/android/inputmethod/latin/StringUtilsTests.java b/tests/src/com/android/inputmethod/latin/StringUtilsTests.java index 29e790a7d..4f260987b 100644 --- a/tests/src/com/android/inputmethod/latin/StringUtilsTests.java +++ b/tests/src/com/android/inputmethod/latin/StringUtilsTests.java @@ -40,57 +40,62 @@ public class StringUtilsTests extends AndroidTestCase { })); } - public void testContainsInCsv() { - assertFalse("null", StringUtils.containsInCsv("key", null)); - assertFalse("empty", StringUtils.containsInCsv("key", "")); - assertFalse("not in 1 element", StringUtils.containsInCsv("key", "key1")); - assertFalse("not in 2 elements", StringUtils.containsInCsv("key", "key1,key2")); - - assertTrue("in 1 element", StringUtils.containsInCsv("key", "key")); - assertTrue("in 2 elements", StringUtils.containsInCsv("key", "key1,key")); + public void testContainsInExtraValues() { + assertFalse("null", StringUtils.containsInCommaSplittableText("key", null)); + assertFalse("empty", StringUtils.containsInCommaSplittableText("key", "")); + assertFalse("not in 1 element", + StringUtils.containsInCommaSplittableText("key", "key1")); + assertFalse("not in 2 elements", + StringUtils.containsInCommaSplittableText("key", "key1,key2")); + + assertTrue("in 1 element", StringUtils.containsInCommaSplittableText("key", "key")); + assertTrue("in 2 elements", StringUtils.containsInCommaSplittableText("key", "key1,key")); } - public void testAppendToCsvIfNotExists() { - assertEquals("null", "key", StringUtils.appendToCsvIfNotExists("key", null)); - assertEquals("empty", "key", StringUtils.appendToCsvIfNotExists("key", "")); + public void testAppendToExtraValuesIfNotExists() { + assertEquals("null", "key", + StringUtils.appendToCommaSplittableTextIfNotExists("key", null)); + assertEquals("empty", "key", + StringUtils.appendToCommaSplittableTextIfNotExists("key", "")); assertEquals("not in 1 element", "key1,key", - StringUtils.appendToCsvIfNotExists("key", "key1")); + StringUtils.appendToCommaSplittableTextIfNotExists("key", "key1")); assertEquals("not in 2 elements", "key1,key2,key", - StringUtils.appendToCsvIfNotExists("key", "key1,key2")); + StringUtils.appendToCommaSplittableTextIfNotExists("key", "key1,key2")); assertEquals("in 1 element", "key", - StringUtils.appendToCsvIfNotExists("key", "key")); + StringUtils.appendToCommaSplittableTextIfNotExists("key", "key")); assertEquals("in 2 elements at position 1", "key,key2", - StringUtils.appendToCsvIfNotExists("key", "key,key2")); + StringUtils.appendToCommaSplittableTextIfNotExists("key", "key,key2")); assertEquals("in 2 elements at position 2", "key1,key", - StringUtils.appendToCsvIfNotExists("key", "key1,key")); + StringUtils.appendToCommaSplittableTextIfNotExists("key", "key1,key")); assertEquals("in 3 elements at position 2", "key1,key,key3", - StringUtils.appendToCsvIfNotExists("key", "key1,key,key3")); + StringUtils.appendToCommaSplittableTextIfNotExists("key", "key1,key,key3")); } - public void testRemoveFromCsvIfExists() { - assertEquals("null", "", StringUtils.removeFromCsvIfExists("key", null)); - assertEquals("empty", "", StringUtils.removeFromCsvIfExists("key", "")); + public void testRemoveFromExtraValuesIfExists() { + assertEquals("null", "", StringUtils.removeFromCommaSplittableTextIfExists("key", null)); + assertEquals("empty", "", StringUtils.removeFromCommaSplittableTextIfExists("key", "")); assertEquals("not in 1 element", "key1", - StringUtils.removeFromCsvIfExists("key", "key1")); + StringUtils.removeFromCommaSplittableTextIfExists("key", "key1")); assertEquals("not in 2 elements", "key1,key2", - StringUtils.removeFromCsvIfExists("key", "key1,key2")); + StringUtils.removeFromCommaSplittableTextIfExists("key", "key1,key2")); assertEquals("in 1 element", "", - StringUtils.removeFromCsvIfExists("key", "key")); + StringUtils.removeFromCommaSplittableTextIfExists("key", "key")); assertEquals("in 2 elements at position 1", "key2", - StringUtils.removeFromCsvIfExists("key", "key,key2")); + StringUtils.removeFromCommaSplittableTextIfExists("key", "key,key2")); assertEquals("in 2 elements at position 2", "key1", - StringUtils.removeFromCsvIfExists("key", "key1,key")); + StringUtils.removeFromCommaSplittableTextIfExists("key", "key1,key")); assertEquals("in 3 elements at position 2", "key1,key3", - StringUtils.removeFromCsvIfExists("key", "key1,key,key3")); + StringUtils.removeFromCommaSplittableTextIfExists("key", "key1,key,key3")); assertEquals("in 3 elements at position 1,2,3", "", - StringUtils.removeFromCsvIfExists("key", "key,key,key")); + StringUtils.removeFromCommaSplittableTextIfExists("key", "key,key,key")); assertEquals("in 5 elements at position 2,4", "key1,key3,key5", - StringUtils.removeFromCsvIfExists("key", "key1,key,key3,key,key5")); + StringUtils.removeFromCommaSplittableTextIfExists( + "key", "key1,key,key3,key,key5")); } |