Thin out audio and haptic feedback while key repeat

Bug: 6522943
Change-Id: Id60f256ab0f8741578eda276116817fa48917325
main
Tadashi G. Takaoka 2013-08-13 12:10:26 +09:00
parent ae59ce0262
commit ab16237e69
6 changed files with 55 additions and 36 deletions

View File

@ -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, * @param primaryCode the unicode of the key being pressed. If the touch is not on a valid key,
* the value will be zero. * 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. * @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. * 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 { public static class Adapter implements KeyboardActionListener {
@Override @Override
public void onPressKey(int primaryCode, boolean isRepeatKey, boolean isSinglePointer) {} public void onPressKey(int primaryCode, int repeatCount, boolean isSinglePointer) {}
@Override @Override
public void onReleaseKey(int primaryCode, boolean withSliding) {} public void onReleaseKey(int primaryCode, boolean withSliding) {}
@Override @Override

View File

@ -217,7 +217,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
startWhileTypingFadeinAnimation(keyboardView); startWhileTypingFadeinAnimation(keyboardView);
break; break;
case MSG_REPEAT_KEY: case MSG_REPEAT_KEY:
tracker.onKeyRepeat(msg.arg1); tracker.onKeyRepeat(msg.arg1 /* code */, msg.arg2 /* repeatCount */);
break; break;
case MSG_LONGPRESS_KEY: case MSG_LONGPRESS_KEY:
keyboardView.onLongPress(tracker); keyboardView.onLongPress(tracker);
@ -230,12 +230,14 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
} }
@Override @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(); final Key key = tracker.getKey();
if (key == null || delay == 0) { if (key == null || delay == 0) {
return; return;
} }
sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, key.mCode, 0, tracker), delay); sendMessageDelayed(
obtainMessage(MSG_REPEAT_KEY, key.mCode, repeatCount, tracker), delay);
} }
public void cancelKeyRepeatTimer() { public void cancelKeyRepeatTimer() {
@ -938,7 +940,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
if (key.hasNoPanelAutoMoreKey()) { if (key.hasNoPanelAutoMoreKey()) {
final int moreKeyCode = key.mMoreKeys[0].mCode; final int moreKeyCode = key.mMoreKeys[0].mCode;
tracker.onLongPressed(); tracker.onLongPressed();
listener.onPressKey(moreKeyCode, false /* isRepeatKey */, true /* isSinglePointer */); listener.onPressKey(moreKeyCode, 0 /* repeatCount */, true /* isSinglePointer */);
listener.onCodeInput(moreKeyCode, listener.onCodeInput(moreKeyCode,
Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
listener.onReleaseKey(moreKeyCode, false /* withSliding */); listener.onReleaseKey(moreKeyCode, false /* withSliding */);

View File

@ -64,7 +64,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
/** /**
* Get KeyboardActionListener object that is used to register key code and so on. * 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(); public KeyboardActionListener getKeyboardActionListener();
@ -94,7 +94,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
public interface TimerProxy { public interface TimerProxy {
public void startTypingStateTimer(Key typedKey); public void startTypingStateTimer(Key typedKey);
public boolean isTypingState(); 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 startLongPressTimer(PointerTracker tracker, int delay);
public void cancelLongPressTimer(); public void cancelLongPressTimer();
public void startDoubleTapShiftKeyTimer(); public void startDoubleTapShiftKeyTimer();
@ -111,7 +111,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
@Override @Override
public boolean isTypingState() { return false; } public boolean isTypingState() { return false; }
@Override @Override
public void startKeyRepeatTimer(PointerTracker tracker, int delay) {} public void startKeyRepeatTimer(PointerTracker tracker, int repeatCount, int delay) {}
@Override @Override
public void startLongPressTimer(PointerTracker tracker, int delay) {} public void startLongPressTimer(PointerTracker tracker, int delay) {}
@Override @Override
@ -490,7 +490,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
// Returns true if keyboard has been changed by this callback. // Returns true if keyboard has been changed by this callback.
private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key, 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 // While gesture input is going on, this method should be a no-operation. But when gesture
// input has been canceled, <code>sInGesture</code> and <code>mIsDetectingGesture</code> // input has been canceled, <code>sInGesture</code> and <code>mIsDetectingGesture</code>
// are set to false. To keep this method is a no-operation, // 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), KeyDetector.printableCode(key),
ignoreModifierKey ? " ignoreModifier" : "", ignoreModifierKey ? " ignoreModifier" : "",
key.isEnabled() ? "" : " disabled", key.isEnabled() ? "" : " disabled",
isRepeatKey ? " repeat" : "")); repeatCount > 0 ? " repeatCount=" + repeatCount : ""));
} }
if (ignoreModifierKey) { if (ignoreModifierKey) {
return false; return false;
} }
if (key.isEnabled()) { if (key.isEnabled()) {
mListener.onPressKey(key.mCode, isRepeatKey, getActivePointerTrackerCount() == 1); mListener.onPressKey(key.mCode, repeatCount, getActivePointerTrackerCount() == 1);
final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged; final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged;
mKeyboardLayoutHasBeenChanged = false; mKeyboardLayoutHasBeenChanged = false;
mTimerProxy.startTypingStateTimer(key); 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 // 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 // {@link #setKeyboard}. In those cases, we should update key according to the new
// keyboard layout. // keyboard layout.
if (callListenerOnPressAndCheckKeyboardLayoutChange(key, false /* isRepeatKey */)) { if (callListenerOnPressAndCheckKeyboardLayoutChange(key, 0 /* repeatCount */)) {
key = onDownKey(x, y, eventTime); 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 // at {@link #setKeyboard}. In those cases, we should update key according
// to the new keyboard layout. // to the new keyboard layout.
Key key = newKey; Key key = newKey;
if (callListenerOnPressAndCheckKeyboardLayoutChange(key, false /* isRepeatKey */)) { if (callListenerOnPressAndCheckKeyboardLayoutChange(key, 0 /* repeatCount */)) {
key = onMoveKey(x, y); key = onMoveKey(x, y);
} }
onMoveToNewKey(key, 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. // Don't start key repeat when we are in sliding input mode.
if (mIsInSlidingKeyInput) return; if (mIsInSlidingKeyInput) return;
detectAndSendKey(key, key.mX, key.mY, SystemClock.uptimeMillis()); 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(); final Key key = getKey();
if (key == null || key.mCode != code) { if (key == null || key.mCode != code) {
return; return;
} }
mTimerProxy.startKeyRepeatTimer(this, sParams.mKeyRepeatInterval); final int nextRepeatCount = repeatCount + 1;
callListenerOnPressAndCheckKeyboardLayoutChange(key, true /* isRepeatKey */); mTimerProxy.startKeyRepeatTimer(this, nextRepeatCount, sParams.mKeyRepeatInterval);
callListenerOnPressAndCheckKeyboardLayoutChange(key, repeatCount);
callListenerOnCodeInput(key, code, mKeyX, mKeyY, SystemClock.uptimeMillis()); callListenerOnCodeInput(key, code, mKeyX, mKeyY, SystemClock.uptimeMillis());
} }

View File

@ -57,10 +57,10 @@ public final class AudioAndHapticFeedbackManager {
mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
} }
public void hapticAndAudioFeedback(final int primaryCode, public void performHapticAndAudioFeedback(final int code,
final View viewToPerformHapticFeedbackOn) { final View viewToPerformHapticFeedbackOn) {
vibrateInternal(viewToPerformHapticFeedbackOn); performHapticFeedback(viewToPerformHapticFeedbackOn);
playKeyClick(primaryCode); performAudioFeedback(code);
} }
public boolean hasVibrator() { public boolean hasVibrator() {
@ -81,14 +81,14 @@ public final class AudioAndHapticFeedbackManager {
return mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_NORMAL; 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 is null, we can't play a sound anyway, so return
if (mAudioManager == null) { if (mAudioManager == null) {
return; return;
} }
if (mSoundOn) { if (mSoundOn) {
final int sound; final int sound;
switch (primaryCode) { switch (code) {
case Constants.CODE_DELETE: case Constants.CODE_DELETE:
sound = AudioManager.FX_KEYPRESS_DELETE; sound = AudioManager.FX_KEYPRESS_DELETE;
break; break;
@ -106,7 +106,7 @@ public final class AudioAndHapticFeedbackManager {
} }
} }
private void vibrateInternal(final View viewToPerformHapticFeedbackOn) { public void performHapticFeedback(final View viewToPerformHapticFeedbackOn) {
if (!mSettingsValues.mVibrateOn) { if (!mSettingsValues.mVibrateOn) {
return; return;
} }

View File

@ -122,6 +122,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private static final int PENDING_IMS_CALLBACK_DURATION = 800; 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, * The name of the scheme used by the Package Manager to warn of a new package installation,
* replacement or removal. * replacement or removal.
@ -1466,7 +1468,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
break; break;
case Constants.CODE_SHIFT: case Constants.CODE_SHIFT:
// Note: Calling back to the keyboard on Shift key is handled in // 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(); final Keyboard currentKeyboard = switcher.getKeyboard();
if (null != currentKeyboard && currentKeyboard.mId.isAlphabetKeyboard()) { if (null != currentKeyboard && currentKeyboard.mId.isAlphabetKeyboard()) {
// TODO: Instead of checking for alphabetic keyboard here, separate keycodes for // TODO: Instead of checking for alphabetic keyboard here, separate keycodes for
@ -1480,7 +1482,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
break; break;
case Constants.CODE_SWITCH_ALPHA_SYMBOL: case Constants.CODE_SWITCH_ALPHA_SYMBOL:
// Note: Calling back to the keyboard on symbol key is handled in // 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; break;
case Constants.CODE_SETTINGS: case Constants.CODE_SETTINGS:
onSettingsKeyPressed(); onSettingsKeyPressed();
@ -2697,30 +2699,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(); final MainKeyboardView keyboardView = mKeyboardSwitcher.getMainKeyboardView();
if (keyboardView != null && keyboardView.isInSlidingKeyInput()) { if (keyboardView != null && keyboardView.isInSlidingKeyInput()) {
// No need to feedback while sliding input. // No need to feedback while sliding input.
return; return;
} }
if (isRepeatKey) { if (repeatCount > 0) {
// No need to feedback when repeating key. if (code == Constants.CODE_DELETE && !mConnection.canDeleteCharacters()) {
return; // 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; // Callback of the {@link KeyboardActionListener}. This is called when a key is depressed;
// release matching call is {@link #onReleaseKey(int,boolean)} below. // release matching call is {@link #onReleaseKey(int,boolean)} below.
@Override @Override
public void onPressKey(final int primaryCode, final boolean isRepeatKey, public void onPressKey(final int primaryCode, final int repeatCount,
final boolean isSinglePointer) { final boolean isSinglePointer) {
mKeyboardSwitcher.onPressKey(primaryCode, isSinglePointer); mKeyboardSwitcher.onPressKey(primaryCode, isSinglePointer);
hapticAndAudioFeedback(primaryCode, isRepeatKey); hapticAndAudioFeedback(primaryCode, repeatCount);
} }
// Callback of the {@link KeyboardActionListener}. This is called when a key is released; // 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 @Override
public void onReleaseKey(final int primaryCode, final boolean withSliding) { public void onReleaseKey(final int primaryCode, final boolean withSliding) {
mKeyboardSwitcher.onReleaseKey(primaryCode, withSliding); mKeyboardSwitcher.onReleaseKey(primaryCode, withSliding);

View File

@ -198,7 +198,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
@Override @Override
public boolean onLongClick(final View view) { public boolean onLongClick(final View view) {
AudioAndHapticFeedbackManager.getInstance().hapticAndAudioFeedback( AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(
Constants.NOT_A_CODE, this); Constants.NOT_A_CODE, this);
return showMoreSuggestions(); return showMoreSuggestions();
} }