diff options
Diffstat (limited to 'java/src')
18 files changed, 229 insertions, 105 deletions
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/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java index 1fe23a330..d4051f74b 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java @@ -36,6 +36,7 @@ import android.util.Xml; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodSubtype; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.compat.EditorInfoCompatUtils; import com.android.inputmethod.keyboard.internal.KeyboardBuilder; import com.android.inputmethod.keyboard.internal.KeyboardParams; @@ -424,6 +425,7 @@ public final class KeyboardLayoutSet { SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT, false); } + @UsedForTesting public static KeyboardLayoutSet createKeyboardSetForTest(final Context context, final InputMethodSubtype subtype, final int orientation, final boolean testCasesHaveTouchCoordinates) { diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index ad08d6477..c5bd62431 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,24 +320,25 @@ 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); + final Keyboard keyboard = getKeyboard(); + if (keyboard != null && keyboard.mId.isAlphabetKeyboard() && code == Constants.CODE_SHIFT) { + hapticAndAudioFeedback(code); + } } public boolean isInMomentarySwitchState() { return mState.isInMomentarySwitchState(); } - private boolean isVibrateAndSoundFeedbackRequired() { - return mKeyboardView != null && !mKeyboardView.isInSlidingKeyInput(); - } - /** * Updates state machine to figure out when to automatically switch back to the previous mode. */ diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java index 6c6fc6157..34464f690 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; @@ -987,16 +990,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 +1008,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 +1035,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 +1060,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/GestureStrokeWithPreviewPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java index b31f00b62..8deadbf96 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java @@ -58,7 +58,7 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { } private static double degreeToRadian(final int degree) { - return (double)degree / 180.0d * Math.PI; + return degree / 180.0d * Math.PI; } public GestureStrokePreviewParams(final TypedArray mainKeyboardViewAttr) { @@ -125,8 +125,18 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { } + /** + * Append sampled preview points. + * + * @param eventTimes the event time array of gesture trail to be drawn. + * @param xCoords the x-coordinates array of gesture trail to be drawn. + * @param yCoords the y-coordinates array of gesture trail to be drawn. + * @param types the point types array of gesture trail. This is valid only when + * {@link GestureTrail#DEBUG_SHOW_POINTS} is true. + */ public void appendPreviewStroke(final ResizableIntArray eventTimes, - final ResizableIntArray xCoords, final ResizableIntArray yCoords) { + final ResizableIntArray xCoords, final ResizableIntArray yCoords, + final ResizableIntArray types) { final int length = mPreviewEventTimes.getLength() - mLastPreviewSize; if (length <= 0) { return; @@ -134,6 +144,9 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { eventTimes.append(mPreviewEventTimes, mLastPreviewSize, length); xCoords.append(mPreviewXCoordinates, mLastPreviewSize, length); yCoords.append(mPreviewYCoordinates, mLastPreviewSize, length); + if (GestureTrail.DEBUG_SHOW_POINTS) { + types.fill(GestureTrail.POINT_TYPE_SAMPLED, types.getLength(), length); + } mLastPreviewSize = mPreviewEventTimes.getLength(); } @@ -148,6 +161,8 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { * @param eventTimes the event time array of gesture trail to be drawn. * @param xCoords the x-coordinates array of gesture trail to be drawn. * @param yCoords the y-coordinates array of gesture trail to be drawn. + * @param types the point types array of gesture trail. This is valid only when + * {@link GestureTrail#DEBUG_SHOW_POINTS} is true. * @return the start index of the last interpolated segment of input arrays. */ public int interpolateStrokeAndReturnStartIndexOfLastSegment(final int lastInterpolatedIndex, @@ -189,7 +204,7 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { eventTimes.add(d1, (int)(dt * t) + t1); xCoords.add(d1, (int)mInterpolator.mInterpolatedX); yCoords.add(d1, (int)mInterpolator.mInterpolatedY); - if (GestureTrail.DBG_SHOW_POINTS) { + if (GestureTrail.DEBUG_SHOW_POINTS) { types.add(d1, GestureTrail.POINT_TYPE_INTERPOLATED); } d1++; @@ -197,7 +212,7 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { eventTimes.add(d1, pt[p2]); xCoords.add(d1, px[p2]); yCoords.add(d1, py[p2]); - if (GestureTrail.DBG_SHOW_POINTS) { + if (GestureTrail.DEBUG_SHOW_POINTS) { types.add(d1, GestureTrail.POINT_TYPE_SAMPLED); } } diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java index 03dd1c372..0f3cd7887 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java @@ -36,10 +36,11 @@ import com.android.inputmethod.latin.ResizableIntArray; * @attr ref R.styleable#MainKeyboardView_gestureTrailWidth */ final class GestureTrail { - public static final boolean DBG_SHOW_POINTS = false; - public static final int POINT_TYPE_SAMPLED = 0; - public static final int POINT_TYPE_INTERPOLATED = 1; - public static final int POINT_TYPE_COMPROMISED = 2; + public static final boolean DEBUG_SHOW_POINTS = false; + public static final int POINT_TYPE_SAMPLED = 1; + public static final int POINT_TYPE_INTERPOLATED = 2; + private static final int FADEOUT_START_DELAY_FOR_DEBUG = 2000; // millisecond + private static final int FADEOUT_DURATION_FOR_DEBUG = 200; // millisecond private static final int DEFAULT_CAPACITY = GestureStrokeWithPreviewPoints.PREVIEW_CAPACITY; @@ -48,7 +49,7 @@ final class GestureTrail { private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY); private final ResizableIntArray mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY); private final ResizableIntArray mPointTypes = new ResizableIntArray( - DBG_SHOW_POINTS ? DEFAULT_CAPACITY : 0); + DEBUG_SHOW_POINTS ? DEFAULT_CAPACITY : 0); private int mCurrentStrokeId = -1; // The wall time of the zero value in {@link #mEventTimes} private long mCurrentTimeBase; @@ -83,10 +84,12 @@ final class GestureTrail { R.styleable.MainKeyboardView_gestureTrailShadowRatio, 0); mTrailShadowEnabled = (trailShadowRatioInt > 0); mTrailShadowRatio = (float)trailShadowRatioInt / (float)PERCENTAGE_INT; - mFadeoutStartDelay = DBG_SHOW_POINTS ? 2000 : mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gestureTrailFadeoutStartDelay, 0); - mFadeoutDuration = DBG_SHOW_POINTS ? 200 : mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gestureTrailFadeoutDuration, 0); + mFadeoutStartDelay = DEBUG_SHOW_POINTS ? FADEOUT_START_DELAY_FOR_DEBUG + : mainKeyboardViewAttr.getInt( + R.styleable.MainKeyboardView_gestureTrailFadeoutStartDelay, 0); + mFadeoutDuration = DEBUG_SHOW_POINTS ? FADEOUT_DURATION_FOR_DEBUG + : mainKeyboardViewAttr.getInt( + R.styleable.MainKeyboardView_gestureTrailFadeoutDuration, 0); mTrailLingerDuration = mFadeoutStartDelay + mFadeoutDuration; mUpdateInterval = mainKeyboardViewAttr.getInt( R.styleable.MainKeyboardView_gestureTrailUpdateInterval, 0); @@ -117,7 +120,7 @@ final class GestureTrail { private void addStrokeLocked(final GestureStrokeWithPreviewPoints stroke, final long downTime) { final int trailSize = mEventTimes.getLength(); - stroke.appendPreviewStroke(mEventTimes, mXCoordinates, mYCoordinates); + stroke.appendPreviewStroke(mEventTimes, mXCoordinates, mYCoordinates, mPointTypes); if (mEventTimes.getLength() == trailSize) { return; } @@ -255,23 +258,15 @@ final class GestureTrail { final int alpha = getAlpha(elapsedTime, params); paint.setAlpha(alpha); canvas.drawPath(path, paint); - if (DBG_SHOW_POINTS) { - if (pointTypes[i] == POINT_TYPE_INTERPOLATED) { - paint.setColor(Color.RED); - } else if (pointTypes[i] == POINT_TYPE_SAMPLED) { - paint.setColor(0xFFA000FF); - } else { - paint.setColor(Color.GREEN); - } - canvas.drawCircle(p1x - 1, p1y - 1, 2, paint); - paint.setColor(params.mTrailColor); - } } } p1x = p2x; p1y = p2y; r1 = r2; } + if (DEBUG_SHOW_POINTS) { + debugDrawPoints(canvas, startIndex, trailSize, paint); + } } final int newSize = trailSize - startIndex; @@ -281,11 +276,14 @@ final class GestureTrail { System.arraycopy(eventTimes, startIndex, eventTimes, 0, newSize); System.arraycopy(xCoords, startIndex, xCoords, 0, newSize); System.arraycopy(yCoords, startIndex, yCoords, 0, newSize); + if (DEBUG_SHOW_POINTS) { + System.arraycopy(pointTypes, startIndex, pointTypes, 0, newSize); + } } mEventTimes.setLength(newSize); mXCoordinates.setLength(newSize); mYCoordinates.setLength(newSize); - if (DBG_SHOW_POINTS) { + if (DEBUG_SHOW_POINTS) { mPointTypes.setLength(newSize); } // The start index of the last segment of the stroke @@ -295,4 +293,26 @@ final class GestureTrail { } return newSize > 0; } + + private void debugDrawPoints(final Canvas canvas, final int startIndex, final int endIndex, + final Paint paint) { + final int[] xCoords = mXCoordinates.getPrimitiveArray(); + final int[] yCoords = mYCoordinates.getPrimitiveArray(); + final int[] pointTypes = mPointTypes.getPrimitiveArray(); + // {@link Paint} that is zero width stroke and anti alias off draws exactly 1 pixel. + paint.setAntiAlias(false); + paint.setStrokeWidth(0); + for (int i = startIndex; i < endIndex; i++) { + final int pointType = pointTypes[i]; + if (pointType == POINT_TYPE_INTERPOLATED) { + paint.setColor(Color.RED); + } else if (pointType == POINT_TYPE_SAMPLED) { + paint.setColor(0xFFA000FF); + } else { + paint.setColor(Color.GREEN); + } + canvas.drawPoint(getXCoordValue(xCoords[i]), yCoords[i], paint); + } + paint.setAntiAlias(true); + } } diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java index 6af1bd75f..f18d5edff 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); } } 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/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index f85c16be3..592db35dd 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()); } @@ -2370,7 +2371,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen 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); + && !AutoCorrection.isValidWord(mSuggest, suggestion, true); if (mSettings.isInternal()) { Stats.onSeparator((char)Constants.CODE_SPACE, @@ -2701,7 +2702,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/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/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java index ad350a02f..1113939d1 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; @@ -689,7 +690,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/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java index 7e8f16697..3482153b4 100644 --- a/java/src/com/android/inputmethod/research/MainLogBuffer.java +++ b/java/src/com/android/inputmethod/research/MainLogBuffer.java @@ -63,6 +63,15 @@ public abstract class MainLogBuffer extends FixedLogBuffer { private static final boolean DEBUG = false && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG; + // Keep consistent with switch statement in Statistics.recordPublishabilityResultCode() + public static final int PUBLISHABILITY_PUBLISHABLE = 0; + public static final int PUBLISHABILITY_UNPUBLISHABLE_STOPPING = 1; + public static final int PUBLISHABILITY_UNPUBLISHABLE_INCORRECT_WORD_COUNT = 2; + public static final int PUBLISHABILITY_UNPUBLISHABLE_SAMPLED_TOO_RECENTLY = 3; + public static final int PUBLISHABILITY_UNPUBLISHABLE_DICTIONARY_UNAVAILABLE = 4; + public static final int PUBLISHABILITY_UNPUBLISHABLE_MAY_CONTAIN_DIGIT = 5; + public static final int PUBLISHABILITY_UNPUBLISHABLE_NOT_IN_DICTIONARY = 6; + // The size of the n-grams logged. E.g. N_GRAM_SIZE = 2 means to sample bigrams. public static final int N_GRAM_SIZE = 2; @@ -105,21 +114,24 @@ public abstract class MainLogBuffer extends FixedLogBuffer { } /** - * Determines whether uploading the n words at the front the MainLogBuffer will not violate - * user privacy. + * Determines whether the string determined by a series of LogUnits will not violate user + * privacy if published. + * + * @param logUnits a LogUnit list to check for publishability + * @param nGramSize the smallest n-gram acceptable to be published. if + * {@link ResearchLogger.IS_LOGGING_EVERYTHING} is true, then publish if there are more than + * {@code minNGramSize} words in the logUnits, otherwise wait. if {@link + * ResearchLogger.IS_LOGGING_EVERYTHING} is false, then ensure that there are exactly nGramSize + * words in the LogUnits. * - * The size of the MainLogBuffer is just enough to hold one n-gram, its corrections, and any - * non-character data that is typed between words. The decision about privacy is made based on - * the buffer's entire content. If it is decided that the privacy risks are too great to upload - * the contents of this buffer, a censored version of the LogItems may still be uploaded. E.g., - * the screen orientation and other characteristics about the device can be uploaded without - * revealing much about the user. + * @return one of the {@code PUBLISHABILITY_*} result codes defined in this class. */ - private boolean isSafeNGram(final ArrayList<LogUnit> logUnits, final int minNGramSize) { + private int getPublishabilityResultCode(final ArrayList<LogUnit> logUnits, + final int nGramSize) { // Bypass privacy checks when debugging. if (ResearchLogger.IS_LOGGING_EVERYTHING) { if (mIsStopping) { - return true; + return PUBLISHABILITY_UNPUBLISHABLE_STOPPING; } // Only check that it is the right length. If not, wait for later words to make // complete n-grams. @@ -129,13 +141,17 @@ public abstract class MainLogBuffer extends FixedLogBuffer { final LogUnit logUnit = logUnits.get(i); numWordsInLogUnitList += logUnit.getNumWords(); } - return numWordsInLogUnitList >= minNGramSize; + if (numWordsInLogUnitList >= nGramSize) { + return PUBLISHABILITY_PUBLISHABLE; + } else { + return PUBLISHABILITY_UNPUBLISHABLE_INCORRECT_WORD_COUNT; + } } // Check that we are not sampling too frequently. Having sampled recently might disclose // too much of the user's intended meaning. if (mNumWordsUntilSafeToSample > 0) { - return false; + return PUBLISHABILITY_UNPUBLISHABLE_SAMPLED_TOO_RECENTLY; } // Reload the dictionary in case it has changed (e.g., because the user has changed // languages). @@ -144,7 +160,7 @@ public abstract class MainLogBuffer extends FixedLogBuffer { // Main dictionary is unavailable. Since we cannot check it, we cannot tell if a // word is out-of-vocabulary or not. Therefore, we must judge the entire buffer // contents to potentially pose a privacy risk. - return false; + return PUBLISHABILITY_UNPUBLISHABLE_DICTIONARY_UNAVAILABLE; } // Check each word in the buffer. If any word poses a privacy threat, we cannot upload @@ -155,7 +171,7 @@ public abstract class MainLogBuffer extends FixedLogBuffer { if (!logUnit.hasOneOrMoreWords()) { // Digits outside words are a privacy threat. if (logUnit.mayContainDigit()) { - return false; + return PUBLISHABILITY_UNPUBLISHABLE_MAY_CONTAIN_DIGIT; } } else { numWordsInLogUnitList += logUnit.getNumWords(); @@ -168,14 +184,18 @@ public abstract class MainLogBuffer extends FixedLogBuffer { + ResearchLogger.hasLetters(word) + ", isValid: " + (dictionary.isValidWord(word))); } - return false; + return PUBLISHABILITY_UNPUBLISHABLE_NOT_IN_DICTIONARY; } } } } // Finally, only return true if the ngram is the right size. - return numWordsInLogUnitList == minNGramSize; + if (numWordsInLogUnitList == nGramSize) { + return PUBLISHABILITY_PUBLISHABLE; + } else { + return PUBLISHABILITY_UNPUBLISHABLE_INCORRECT_WORD_COUNT; + } } public void shiftAndPublishAll() throws IOException { @@ -216,7 +236,9 @@ public abstract class MainLogBuffer extends FixedLogBuffer { // TODO: Refactor this method to require fewer passes through the LogUnits. Should really // require only one pass. ArrayList<LogUnit> logUnits = peekAtFirstNWords(N_GRAM_SIZE); - if (isSafeNGram(logUnits, N_GRAM_SIZE)) { + final int publishabilityResultCode = getPublishabilityResultCode(logUnits, N_GRAM_SIZE); + ResearchLogger.recordPublishabilityResultCode(publishabilityResultCode); + if (publishabilityResultCode == MainLogBuffer.PUBLISHABILITY_PUBLISHABLE) { // Good n-gram at the front of the buffer. Publish it, disclosing details. publish(logUnits, true /* canIncludePrivateData */); shiftOutWords(N_GRAM_SIZE); diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java index 0220e20bd..64f0349fc 100644 --- a/java/src/com/android/inputmethod/research/ResearchLogger.java +++ b/java/src/com/android/inputmethod/research/ResearchLogger.java @@ -1660,12 +1660,24 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } /** - * Shared event for logging committed text. + * Shared events for logging committed text. + * + * The "CommitTextEventHappened" LogStatement is written to the log even if privacy rules + * indicate that the word contents should not be logged. It has no contents, and only serves to + * record the event and thereby make it easier to calculate word-level statistics even when the + * word contents are unknown. */ private static final LogStatement LOGSTATEMENT_COMMITTEXT = - new LogStatement("CommitText", true, false, "committedText", "isBatchMode"); + new LogStatement("CommitText", true /* isPotentiallyPrivate */, + false /* isPotentiallyRevealing */, "committedText", "isBatchMode"); + private static final LogStatement LOGSTATEMENT_COMMITTEXT_EVENT_HAPPENED = + new LogStatement("CommitTextEventHappened", false /* isPotentiallyPrivate */, + false /* isPotentiallyRevealing */); private void enqueueCommitText(final String word, final boolean isBatchMode) { + // Event containing the word; will be published only if privacy checks pass enqueueEvent(LOGSTATEMENT_COMMITTEXT, word, isBatchMode); + // Event not containing the word; will always be published + enqueueEvent(LOGSTATEMENT_COMMITTEXT_EVENT_HAPPENED); } /** @@ -1884,6 +1896,20 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } /** + * Call this method when the logging system has attempted publication of an n-gram. + * + * Statistics are gathered about the success or failure. + * + * @param publishabilityResultCode a result code as defined by + * {@code MainLogBuffer.PUBLISHABILITY_*} + */ + static void recordPublishabilityResultCode(final int publishabilityResultCode) { + final ResearchLogger researchLogger = getInstance(); + final Statistics statistics = researchLogger.mStatistics; + statistics.recordPublishabilityResultCode(publishabilityResultCode); + } + + /** * Log statistics. * * ContextualData, recorded at the end of a session. @@ -1895,7 +1921,11 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang "averageTimeDuringRepeatedDelete", "averageTimeAfterDelete", "dictionaryWordCount", "splitWordsCount", "gestureInputCount", "gestureCharsCount", "gesturesDeletedCount", "manualSuggestionsCount", - "revertCommitsCount", "correctedWordsCount", "autoCorrectionsCount"); + "revertCommitsCount", "correctedWordsCount", "autoCorrectionsCount", + "publishableCount", "unpublishableStoppingCount", + "unpublishableIncorrectWordCount", "unpublishableSampledTooRecentlyCount", + "unpublishableDictionaryUnavailableCount", "unpublishableMayContainDigitCount", + "unpublishableNotInDictionaryCount"); private static void logStatistics() { final ResearchLogger researchLogger = getInstance(); final Statistics statistics = researchLogger.mStatistics; @@ -1910,6 +1940,10 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang statistics.mGesturesInputCount, statistics.mGesturesCharsCount, statistics.mGesturesDeletedCount, statistics.mManualSuggestionsCount, statistics.mRevertCommitsCount, statistics.mCorrectedWordsCount, - statistics.mAutoCorrectionsCount); + statistics.mAutoCorrectionsCount, statistics.mPublishableCount, + statistics.mUnpublishableStoppingCount, statistics.mUnpublishableIncorrectWordCount, + statistics.mUnpublishableSampledTooRecently, + statistics.mUnpublishableDictionaryUnavailable, + statistics.mUnpublishableMayContainDigit, statistics.mUnpublishableNotInDictionary); } } diff --git a/java/src/com/android/inputmethod/research/Statistics.java b/java/src/com/android/inputmethod/research/Statistics.java index 7f6c851bb..e573ca012 100644 --- a/java/src/com/android/inputmethod/research/Statistics.java +++ b/java/src/com/android/inputmethod/research/Statistics.java @@ -61,6 +61,16 @@ public class Statistics { boolean mIsEmptyUponStarting; boolean mIsEmptinessStateKnown; + // Counts of how often an n-gram is collected or not, and the reasons for the decision. + // Keep consistent with publishability result code list in MainLogBuffer + int mPublishableCount; + int mUnpublishableStoppingCount; + int mUnpublishableIncorrectWordCount; + int mUnpublishableSampledTooRecently; + int mUnpublishableDictionaryUnavailable; + int mUnpublishableMayContainDigit; + int mUnpublishableNotInDictionary; + // Timers to count average time to enter a key, first press a delete key, // between delete keys, and then to return typing after a delete key. final AverageTimeCounter mKeyCounter = new AverageTimeCounter(); @@ -133,6 +143,13 @@ public class Statistics { mAfterDeleteKeyCounter.reset(); mGesturesCharsCount = 0; mGesturesDeletedCount = 0; + mPublishableCount = 0; + mUnpublishableStoppingCount = 0; + mUnpublishableIncorrectWordCount = 0; + mUnpublishableSampledTooRecently = 0; + mUnpublishableDictionaryUnavailable = 0; + mUnpublishableMayContainDigit = 0; + mUnpublishableNotInDictionary = 0; mLastTapTime = 0; mIsLastKeyDeleteKey = false; @@ -230,4 +247,31 @@ public class Statistics { mIsLastKeyDeleteKey = isDeletion; mLastTapTime = time; } + + public void recordPublishabilityResultCode(final int publishabilityResultCode) { + // Keep consistent with publishability result code list in MainLogBuffer + switch (publishabilityResultCode) { + case MainLogBuffer.PUBLISHABILITY_PUBLISHABLE: + mPublishableCount++; + break; + case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_STOPPING: + mUnpublishableStoppingCount++; + break; + case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_INCORRECT_WORD_COUNT: + mUnpublishableIncorrectWordCount++; + break; + case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_SAMPLED_TOO_RECENTLY: + mUnpublishableSampledTooRecently++; + break; + case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_DICTIONARY_UNAVAILABLE: + mUnpublishableDictionaryUnavailable++; + break; + case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_MAY_CONTAIN_DIGIT: + mUnpublishableMayContainDigit++; + break; + case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_NOT_IN_DICTIONARY: + mUnpublishableNotInDictionary++; + break; + } + } } |