diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java index b26698665..dc760e685 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java @@ -26,10 +26,10 @@ public interface KeyboardActionListener { * * @param primaryCode the unicode of the key being pressed. If the touch is not on a valid key, * the value will be zero. - * @param isRepeatKey true if pressing has occurred while key repeat input. + * @param repeatCount how many times the key was repeated. Zero if it is the first press. * @param isSinglePointer true if pressing has occurred while no other key is being pressed. */ - public void onPressKey(int primaryCode, boolean isRepeatKey, boolean isSinglePointer); + public void onPressKey(int primaryCode, int repeatCount, boolean isSinglePointer); /** * Called when the user releases a key. This is sent after the {@link #onCodeInput} is called. @@ -103,7 +103,7 @@ public interface KeyboardActionListener { public static class Adapter implements KeyboardActionListener { @Override - public void onPressKey(int primaryCode, boolean isRepeatKey, boolean isSinglePointer) {} + public void onPressKey(int primaryCode, int repeatCount, boolean isSinglePointer) {} @Override public void onReleaseKey(int primaryCode, boolean withSliding) {} @Override diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java index 526c2f1ec..f3d0eadc8 100644 --- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java @@ -217,7 +217,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack startWhileTypingFadeinAnimation(keyboardView); break; case MSG_REPEAT_KEY: - tracker.onKeyRepeat(msg.arg1); + tracker.onKeyRepeat(msg.arg1 /* code */, msg.arg2 /* repeatCount */); break; case MSG_LONGPRESS_KEY: keyboardView.onLongPress(tracker); @@ -230,12 +230,14 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack } @Override - public void startKeyRepeatTimer(final PointerTracker tracker, final int delay) { + public void startKeyRepeatTimer(final PointerTracker tracker, final int repeatCount, + final int delay) { final Key key = tracker.getKey(); if (key == null || delay == 0) { return; } - sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, key.mCode, 0, tracker), delay); + sendMessageDelayed( + obtainMessage(MSG_REPEAT_KEY, key.mCode, repeatCount, tracker), delay); } public void cancelKeyRepeatTimer() { @@ -938,7 +940,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack if (key.hasNoPanelAutoMoreKey()) { final int moreKeyCode = key.mMoreKeys[0].mCode; tracker.onLongPressed(); - listener.onPressKey(moreKeyCode, false /* isRepeatKey */, true /* isSinglePointer */); + listener.onPressKey(moreKeyCode, 0 /* repeatCount */, true /* isSinglePointer */); listener.onCodeInput(moreKeyCode, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); listener.onReleaseKey(moreKeyCode, false /* withSliding */); diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index b66ee2a65..5387ddb6d 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -64,7 +64,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { /** * Get KeyboardActionListener object that is used to register key code and so on. - * @return the KeyboardActionListner for this PointerTracker + * @return the KeyboardActionListner for this PointerTracke */ public KeyboardActionListener getKeyboardActionListener(); @@ -94,7 +94,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { public interface TimerProxy { public void startTypingStateTimer(Key typedKey); public boolean isTypingState(); - public void startKeyRepeatTimer(PointerTracker tracker, int delay); + public void startKeyRepeatTimer(PointerTracker tracker, int repeatCount, int delay); public void startLongPressTimer(PointerTracker tracker, int delay); public void cancelLongPressTimer(); public void startDoubleTapShiftKeyTimer(); @@ -111,7 +111,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { @Override public boolean isTypingState() { return false; } @Override - public void startKeyRepeatTimer(PointerTracker tracker, int delay) {} + public void startKeyRepeatTimer(PointerTracker tracker, int repeatCount, int delay) {} @Override public void startLongPressTimer(PointerTracker tracker, int delay) {} @Override @@ -490,7 +490,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { // Returns true if keyboard has been changed by this callback. private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key, - final boolean isRepeatKey) { + final int repeatCount) { // While gesture input is going on, this method should be a no-operation. But when gesture // input has been canceled, sInGesture and mIsDetectingGesture // are set to false. To keep this method is a no-operation, @@ -504,13 +504,13 @@ public final class PointerTracker implements PointerTrackerQueue.Element { KeyDetector.printableCode(key), ignoreModifierKey ? " ignoreModifier" : "", key.isEnabled() ? "" : " disabled", - isRepeatKey ? " repeat" : "")); + repeatCount > 0 ? " repeatCount=" + repeatCount : "")); } if (ignoreModifierKey) { return false; } if (key.isEnabled()) { - mListener.onPressKey(key.mCode, isRepeatKey, getActivePointerTrackerCount() == 1); + mListener.onPressKey(key.mCode, repeatCount, getActivePointerTrackerCount() == 1); final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged; mKeyboardLayoutHasBeenChanged = false; mTimerProxy.startTypingStateTimer(key); @@ -967,7 +967,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { // This onPress call may have changed keyboard layout. Those cases are detected at // {@link #setKeyboard}. In those cases, we should update key according to the new // keyboard layout. - if (callListenerOnPressAndCheckKeyboardLayoutChange(key, false /* isRepeatKey */)) { + if (callListenerOnPressAndCheckKeyboardLayoutChange(key, 0 /* repeatCount */)) { key = onDownKey(x, y, eventTime); } @@ -1057,7 +1057,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { // at {@link #setKeyboard}. In those cases, we should update key according // to the new keyboard layout. Key key = newKey; - if (callListenerOnPressAndCheckKeyboardLayoutChange(key, false /* isRepeatKey */)) { + if (callListenerOnPressAndCheckKeyboardLayoutChange(key, 0 /* repeatCount */)) { key = onMoveKey(x, y); } onMoveToNewKey(key, x, y); @@ -1413,16 +1413,18 @@ public final class PointerTracker implements PointerTrackerQueue.Element { // Don't start key repeat when we are in sliding input mode. if (mIsInSlidingKeyInput) return; detectAndSendKey(key, key.mX, key.mY, SystemClock.uptimeMillis()); - mTimerProxy.startKeyRepeatTimer(this, sParams.mKeyRepeatStartTimeout); + final int startRepeatCount = 1; + mTimerProxy.startKeyRepeatTimer(this, startRepeatCount, sParams.mKeyRepeatStartTimeout); } - public void onKeyRepeat(final int code) { + public void onKeyRepeat(final int code, final int repeatCount) { final Key key = getKey(); if (key == null || key.mCode != code) { return; } - mTimerProxy.startKeyRepeatTimer(this, sParams.mKeyRepeatInterval); - callListenerOnPressAndCheckKeyboardLayoutChange(key, true /* isRepeatKey */); + final int nextRepeatCount = repeatCount + 1; + mTimerProxy.startKeyRepeatTimer(this, nextRepeatCount, sParams.mKeyRepeatInterval); + callListenerOnPressAndCheckKeyboardLayoutChange(key, repeatCount); callListenerOnCodeInput(key, code, mKeyX, mKeyY, SystemClock.uptimeMillis()); } diff --git a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java index 42c57946d..54bc29559 100644 --- a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java +++ b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java @@ -57,10 +57,10 @@ public final class AudioAndHapticFeedbackManager { mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); } - public void hapticAndAudioFeedback(final int primaryCode, + public void performHapticAndAudioFeedback(final int code, final View viewToPerformHapticFeedbackOn) { - vibrateInternal(viewToPerformHapticFeedbackOn); - playKeyClick(primaryCode); + performHapticFeedback(viewToPerformHapticFeedbackOn); + performAudioFeedback(code); } public boolean hasVibrator() { @@ -81,14 +81,14 @@ public final class AudioAndHapticFeedbackManager { return mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_NORMAL; } - private void playKeyClick(final int primaryCode) { + public void performAudioFeedback(final int code) { // if mAudioManager is null, we can't play a sound anyway, so return if (mAudioManager == null) { return; } if (mSoundOn) { final int sound; - switch (primaryCode) { + switch (code) { case Constants.CODE_DELETE: sound = AudioManager.FX_KEYPRESS_DELETE; break; @@ -106,7 +106,7 @@ public final class AudioAndHapticFeedbackManager { } } - private void vibrateInternal(final View viewToPerformHapticFeedbackOn) { + public void performHapticFeedback(final View viewToPerformHapticFeedbackOn) { if (!mSettingsValues.mVibrateOn) { return; } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index fa9f4660a..ffe317161 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -122,6 +122,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private static final int PENDING_IMS_CALLBACK_DURATION = 800; + private static final int PERIOD_FOR_AUDIO_AND_HAPTIC_FEEDBACK_IN_KEY_REPEAT = 2; + /** * The name of the scheme used by the Package Manager to warn of a new package installation, * replacement or removal. @@ -1462,7 +1464,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen break; case Constants.CODE_SHIFT: // Note: Calling back to the keyboard on Shift key is handled in - // {@link #onPressKey(int,boolean)} and {@link #onReleaseKey(int,boolean)}. + // {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}. final Keyboard currentKeyboard = switcher.getKeyboard(); if (null != currentKeyboard && currentKeyboard.mId.isAlphabetKeyboard()) { // TODO: Instead of checking for alphabetic keyboard here, separate keycodes for @@ -1476,7 +1478,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen break; case Constants.CODE_SWITCH_ALPHA_SYMBOL: // Note: Calling back to the keyboard on symbol key is handled in - // {@link #onPressKey(int,boolean)} and {@link #onReleaseKey(int,boolean)}. + // {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}. break; case Constants.CODE_SETTINGS: onSettingsKeyPressed(); @@ -2698,30 +2700,43 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } - private void hapticAndAudioFeedback(final int code, final boolean isRepeatKey) { + private void hapticAndAudioFeedback(final int code, final int repeatCount) { final MainKeyboardView keyboardView = mKeyboardSwitcher.getMainKeyboardView(); if (keyboardView != null && keyboardView.isInSlidingKeyInput()) { // No need to feedback while sliding input. return; } - if (isRepeatKey) { - // No need to feedback when repeating key. - return; + if (repeatCount > 0) { + if (code == Constants.CODE_DELETE && !mConnection.canDeleteCharacters()) { + // No need to feedback when repeat delete key will have no effect. + return; + } + // TODO: Use event time that the last feedback has been generated instead of relying on + // a repeat count to thin out feedback. + if (repeatCount % PERIOD_FOR_AUDIO_AND_HAPTIC_FEEDBACK_IN_KEY_REPEAT == 0) { + return; + } } - AudioAndHapticFeedbackManager.getInstance().hapticAndAudioFeedback(code, keyboardView); + final AudioAndHapticFeedbackManager feedbackManager = + AudioAndHapticFeedbackManager.getInstance(); + if (repeatCount == 0) { + // TODO: Reconsider how to perform haptic feedback when repeating key. + feedbackManager.performHapticFeedback(keyboardView); + } + feedbackManager.performAudioFeedback(code); } // Callback of the {@link KeyboardActionListener}. This is called when a key is depressed; // release matching call is {@link #onReleaseKey(int,boolean)} below. @Override - public void onPressKey(final int primaryCode, final boolean isRepeatKey, + public void onPressKey(final int primaryCode, final int repeatCount, final boolean isSinglePointer) { mKeyboardSwitcher.onPressKey(primaryCode, isSinglePointer); - hapticAndAudioFeedback(primaryCode, isRepeatKey); + hapticAndAudioFeedback(primaryCode, repeatCount); } // Callback of the {@link KeyboardActionListener}. This is called when a key is released; - // press matching call is {@link #onPressKey(int,boolean,boolean)} above. + // press matching call is {@link #onPressKey(int,int,boolean)} above. @Override public void onReleaseKey(final int primaryCode, final boolean withSliding) { mKeyboardSwitcher.onReleaseKey(primaryCode, withSliding); diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java index 2644f3c9c..badc942b9 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java @@ -198,7 +198,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick @Override public boolean onLongClick(final View view) { - AudioAndHapticFeedbackManager.getInstance().hapticAndAudioFeedback( + AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback( Constants.NOT_A_CODE, this); return showMoreSuggestions(); }