Fix updating the shift state upon backspace

Bug: 13514349
Change-Id: If4c9db12b0ab5be676f7a2f72715f469066ee537
main
Jean Chalard 2014-03-31 19:43:12 +09:00
parent 30d5ed67d6
commit 2282e8520a
12 changed files with 99 additions and 45 deletions

View File

@ -52,12 +52,16 @@ public class DeadKeyCombiner implements Combiner {
// how dead keys work). // how dead keys work).
// If the event is a space, we should commit the dead char alone, but if it's // If the event is a space, we should commit the dead char alone, but if it's
// not, we need to commit both. // not, we need to commit both.
// TODO: this is not necessarily triggered by hardware key events, so it's not
// a good idea to masquerade as one. This should be typed as a software
// composite event or something.
return Event.createHardwareKeypressEvent(deadCodePoint, event.mKeyCode, return Event.createHardwareKeypressEvent(deadCodePoint, event.mKeyCode,
Constants.CODE_SPACE == event.mCodePoint ? null : event /* next */); Constants.CODE_SPACE == event.mCodePoint ? null : event /* next */,
false /* isKeyRepeat */);
} else { } else {
// We could combine the characters. // We could combine the characters.
return Event.createHardwareKeypressEvent(resultingCodePoint, event.mKeyCode, return Event.createHardwareKeypressEvent(resultingCodePoint, event.mKeyCode,
null /* next */); null /* next */, false /* isKeyRepeat */);
} }
} }
} }

View File

@ -65,6 +65,8 @@ public class Event {
// This event is a dead character, usually input by a dead key. Examples include dead-acute // This event is a dead character, usually input by a dead key. Examples include dead-acute
// or dead-abovering. // or dead-abovering.
final private static int FLAG_DEAD = 0x1; final private static int FLAG_DEAD = 0x1;
// This event is coming from a key repeat, software or hardware.
final private static int FLAG_REPEAT = 0x2;
final private int mEventType; // The type of event - one of the constants above final private int mEventType; // The type of event - one of the constants above
// The code point associated with the event, if relevant. This is a unicode code point, and // The code point associated with the event, if relevant. This is a unicode code point, and
@ -130,16 +132,16 @@ public class Event {
} }
public static Event createSoftwareKeypressEvent(final int codePoint, final int keyCode, public static Event createSoftwareKeypressEvent(final int codePoint, final int keyCode,
final int x, final int y) { final int x, final int y, final boolean isKeyRepeat) {
return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, keyCode, x, y, return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, keyCode, x, y,
null /* suggestedWordInfo */, FLAG_NONE, null /* next */); null /* suggestedWordInfo */, isKeyRepeat ? FLAG_REPEAT : FLAG_NONE, null);
} }
public static Event createHardwareKeypressEvent(final int codePoint, final int keyCode, public static Event createHardwareKeypressEvent(final int codePoint, final int keyCode,
final Event next) { final Event next, final boolean isKeyRepeat) {
return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, keyCode, return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, keyCode,
Constants.EXTERNAL_KEYBOARD_COORDINATE, Constants.EXTERNAL_KEYBOARD_COORDINATE, Constants.EXTERNAL_KEYBOARD_COORDINATE, Constants.EXTERNAL_KEYBOARD_COORDINATE,
null /* suggestedWordInfo */, FLAG_NONE, next); null /* suggestedWordInfo */, isKeyRepeat ? FLAG_REPEAT : FLAG_NONE, next);
} }
// This creates an input event for a dead character. @see {@link #FLAG_DEAD} // This creates an input event for a dead character. @see {@link #FLAG_DEAD}
@ -228,6 +230,10 @@ public class Event {
return 0 != (FLAG_DEAD & mFlags); return 0 != (FLAG_DEAD & mFlags);
} }
public boolean isKeyRepeat() {
return 0 != (FLAG_REPEAT & mFlags);
}
// Returns whether this is a fake key press from the suggestion strip. This happens with // Returns whether this is a fake key press from the suggestion strip. This happens with
// punctuation signs selected from the suggestion strip. // punctuation signs selected from the suggestion strip.
public boolean isSuggestionStripPress() { public boolean isSuggestionStripPress() {

View File

@ -46,9 +46,10 @@ public class HardwareKeyboardEventDecoder implements HardwareEventDecoder {
// do not necessarily map to a unicode character. This represents a physical key, like // do not necessarily map to a unicode character. This represents a physical key, like
// the key for 'A' or Space, but also Backspace or Ctrl or Caps Lock. // the key for 'A' or Space, but also Backspace or Ctrl or Caps Lock.
final int keyCode = keyEvent.getKeyCode(); final int keyCode = keyEvent.getKeyCode();
final boolean isKeyRepeat = (0 != keyEvent.getRepeatCount());
if (KeyEvent.KEYCODE_DEL == keyCode) { if (KeyEvent.KEYCODE_DEL == keyCode) {
return Event.createHardwareKeypressEvent(Event.NOT_A_CODE_POINT, Constants.CODE_DELETE, return Event.createHardwareKeypressEvent(Event.NOT_A_CODE_POINT, Constants.CODE_DELETE,
null /* next */); null /* next */, isKeyRepeat);
} }
if (keyEvent.isPrintingKey() || KeyEvent.KEYCODE_SPACE == keyCode if (keyEvent.isPrintingKey() || KeyEvent.KEYCODE_SPACE == keyCode
|| KeyEvent.KEYCODE_ENTER == keyCode) { || KeyEvent.KEYCODE_ENTER == keyCode) {
@ -65,15 +66,16 @@ public class HardwareKeyboardEventDecoder implements HardwareEventDecoder {
// Latin IME decide what to do with it. // Latin IME decide what to do with it.
if (keyEvent.isShiftPressed()) { if (keyEvent.isShiftPressed()) {
return Event.createHardwareKeypressEvent(Event.NOT_A_CODE_POINT, return Event.createHardwareKeypressEvent(Event.NOT_A_CODE_POINT,
Constants.CODE_SHIFT_ENTER, null /* next */); Constants.CODE_SHIFT_ENTER, null /* next */, isKeyRepeat);
} else { } else {
return Event.createHardwareKeypressEvent(Constants.CODE_ENTER, keyCode, return Event.createHardwareKeypressEvent(Constants.CODE_ENTER, keyCode,
null /* next */); null /* next */, isKeyRepeat);
} }
} }
// If not Enter, then this is just a regular keypress event for a normal character // If not Enter, then this is just a regular keypress event for a normal character
// that can be committed right away, taking into account the current state. // that can be committed right away, taking into account the current state.
return Event.createHardwareKeypressEvent(keyCode, codePointAndFlags, null /* next */); return Event.createHardwareKeypressEvent(keyCode, codePointAndFlags, null /* next */,
isKeyRepeat);
} }
return Event.createNotHandledEvent(); return Event.createNotHandledEvent();
} }

View File

@ -606,7 +606,8 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
return; return;
} }
final int code = (Integer) tag; final int code = (Integer) tag;
mKeyboardActionListener.onCodeInput(code, NOT_A_COORDINATE, NOT_A_COORDINATE); mKeyboardActionListener.onCodeInput(code, NOT_A_COORDINATE, NOT_A_COORDINATE,
false /* isKeyRepeat */);
mKeyboardActionListener.onReleaseKey(code, false /* withSliding */); mKeyboardActionListener.onReleaseKey(code, false /* withSliding */);
} }
@ -634,7 +635,8 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
if (code == Constants.CODE_OUTPUT_TEXT) { if (code == Constants.CODE_OUTPUT_TEXT) {
mKeyboardActionListener.onTextInput(key.getOutputText()); mKeyboardActionListener.onTextInput(key.getOutputText());
} else { } else {
mKeyboardActionListener.onCodeInput(code, NOT_A_COORDINATE, NOT_A_COORDINATE); mKeyboardActionListener.onCodeInput(code, NOT_A_COORDINATE, NOT_A_COORDINATE,
false /* isKeyRepeat */);
} }
mKeyboardActionListener.onReleaseKey(code, false /* withSliding */); mKeyboardActionListener.onReleaseKey(code, false /* withSliding */);
} }
@ -901,8 +903,8 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
} }
private void handleKeyUp() { private void handleKeyUp() {
mKeyboardActionListener.onCodeInput( mKeyboardActionListener.onCodeInput(Constants.CODE_DELETE,
Constants.CODE_DELETE, NOT_A_COORDINATE, NOT_A_COORDINATE); NOT_A_COORDINATE, NOT_A_COORDINATE, false /* isKeyRepeat */);
mKeyboardActionListener.onReleaseKey( mKeyboardActionListener.onReleaseKey(
Constants.CODE_DELETE, false /* withSliding */); Constants.CODE_DELETE, false /* withSliding */);
++mRepeatCount; ++mRepeatCount;

View File

@ -53,8 +53,10 @@ public interface KeyboardActionListener {
* {@link PointerTracker} or so, the value should be * {@link PointerTracker} or so, the value should be
* {@link Constants#NOT_A_COORDINATE}.If it's called on insertion from the * {@link Constants#NOT_A_COORDINATE}.If it's called on insertion from the
* suggestion strip, it should be {@link Constants#SUGGESTION_STRIP_COORDINATE}. * suggestion strip, it should be {@link Constants#SUGGESTION_STRIP_COORDINATE}.
* @param isKeyRepeat true if this is a key repeat, false otherwise
*/ */
public void onCodeInput(int primaryCode, int x, int y); // TODO: change this to send an Event object instead
public void onCodeInput(int primaryCode, int x, int y, boolean isKeyRepeat);
/** /**
* Sends a string of characters to the listener. * Sends a string of characters to the listener.
@ -107,7 +109,7 @@ public interface KeyboardActionListener {
@Override @Override
public void onReleaseKey(int primaryCode, boolean withSliding) {} public void onReleaseKey(int primaryCode, boolean withSliding) {}
@Override @Override
public void onCodeInput(int primaryCode, int x, int y) {} public void onCodeInput(int primaryCode, int x, int y, boolean isKeyRepeat) {}
@Override @Override
public void onTextInput(String text) {} public void onTextInput(String text) {}
@Override @Override

View File

@ -626,8 +626,8 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
final int moreKeyCode = key.getMoreKeys()[0].mCode; final int moreKeyCode = key.getMoreKeys()[0].mCode;
tracker.onLongPressed(); tracker.onLongPressed();
listener.onPressKey(moreKeyCode, 0 /* repeatCount */, 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, false /* isKeyRepeat */);
listener.onReleaseKey(moreKeyCode, false /* withSliding */); listener.onReleaseKey(moreKeyCode, false /* withSliding */);
return; return;
} }

View File

@ -143,9 +143,10 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
mListener.onTextInput(mCurrentKey.getOutputText()); mListener.onTextInput(mCurrentKey.getOutputText());
} else if (code != Constants.CODE_UNSPECIFIED) { } else if (code != Constants.CODE_UNSPECIFIED) {
if (getKeyboard().hasProximityCharsCorrection(code)) { if (getKeyboard().hasProximityCharsCorrection(code)) {
mListener.onCodeInput(code, x, y); mListener.onCodeInput(code, x, y, false /* isKeyRepeat */);
} else { } else {
mListener.onCodeInput(code, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); mListener.onCodeInput(code, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
false /* isKeyRepeat */);
} }
} }
} }

View File

@ -325,7 +325,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
// Note that we need primaryCode argument because the keyboard may in shifted state and the // Note that we need primaryCode argument because the keyboard may in shifted state and the
// primaryCode is different from {@link Key#mKeyCode}. // primaryCode is different from {@link Key#mKeyCode}.
private void callListenerOnCodeInput(final Key key, final int primaryCode, final int x, private void callListenerOnCodeInput(final Key key, final int primaryCode, final int x,
final int y, final long eventTime) { final int y, final long eventTime, final boolean isKeyRepeat) {
final boolean ignoreModifierKey = mIsInDraggingFinger && key.isModifier(); final boolean ignoreModifierKey = mIsInDraggingFinger && key.isModifier();
final boolean altersCode = key.altCodeWhileTyping() && sTimerProxy.isTypingState(); final boolean altersCode = key.altCodeWhileTyping() && sTimerProxy.isTypingState();
final int code = altersCode ? key.getAltCode() : primaryCode; final int code = altersCode ? key.getAltCode() : primaryCode;
@ -350,10 +350,10 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
sListener.onTextInput(key.getOutputText()); sListener.onTextInput(key.getOutputText());
} else if (code != Constants.CODE_UNSPECIFIED) { } else if (code != Constants.CODE_UNSPECIFIED) {
if (mKeyboard.hasProximityCharsCorrection(code)) { if (mKeyboard.hasProximityCharsCorrection(code)) {
sListener.onCodeInput(code, x, y); sListener.onCodeInput(code, x, y, isKeyRepeat);
} else { } else {
sListener.onCodeInput(code, sListener.onCodeInput(code,
Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, isKeyRepeat);
} }
} }
} }
@ -1204,7 +1204,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
} }
final int code = key.getCode(); final int code = key.getCode();
callListenerOnCodeInput(key, code, x, y, eventTime); callListenerOnCodeInput(key, code, x, y, eventTime, false /* isKeyRepeat */);
callListenerOnRelease(key, code, false /* withSliding */); callListenerOnRelease(key, code, false /* withSliding */);
} }
@ -1229,7 +1229,8 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
final int nextRepeatCount = repeatCount + 1; final int nextRepeatCount = repeatCount + 1;
startKeyRepeatTimer(nextRepeatCount); startKeyRepeatTimer(nextRepeatCount);
callListenerOnPressAndCheckKeyboardLayoutChange(key, repeatCount); callListenerOnPressAndCheckKeyboardLayoutChange(key, repeatCount);
callListenerOnCodeInput(key, code, mKeyX, mKeyY, SystemClock.uptimeMillis()); callListenerOnCodeInput(key, code, mKeyX, mKeyY, SystemClock.uptimeMillis(),
true /* isKeyRepeat */);
} }
private void startKeyRepeatTimer(final int repeatCount) { private void startKeyRepeatTimer(final int repeatCount) {

View File

@ -1224,7 +1224,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Implementation of {@link KeyboardActionListener}. // Implementation of {@link KeyboardActionListener}.
@Override @Override
public void onCodeInput(final int codePoint, final int x, final int y) { public void onCodeInput(final int codePoint, final int x, final int y,
final boolean isKeyRepeat) {
final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
// x and y include some padding, but everything down the line (especially native // x and y include some padding, but everything down the line (especially native
// code) needs the coordinates in the keyboard frame. // code) needs the coordinates in the keyboard frame.
@ -1250,7 +1251,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mSubtypeSwitcher.switchToShortcutIME(this); mSubtypeSwitcher.switchToShortcutIME(this);
// Still call the *#onCodeInput methods for readability. // Still call the *#onCodeInput methods for readability.
} }
final Event event = createSoftwareKeypressEvent(codeToSend, keyX, keyY); final Event event = createSoftwareKeypressEvent(codeToSend, keyX, keyY, isKeyRepeat);
final InputTransaction completeInputTransaction = final InputTransaction completeInputTransaction =
mInputLogic.onCodeInput(mSettings.getCurrent(), event, mInputLogic.onCodeInput(mSettings.getCurrent(), event,
mKeyboardSwitcher.getKeyboardShiftMode(), mHandler); mKeyboardSwitcher.getKeyboardShiftMode(), mHandler);
@ -1261,7 +1262,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// A helper method to split the code point and the key code. Ultimately, they should not be // A helper method to split the code point and the key code. Ultimately, they should not be
// squashed into the same variable, and this method should be removed. // squashed into the same variable, and this method should be removed.
private static Event createSoftwareKeypressEvent(final int keyCodeOrCodePoint, final int keyX, private static Event createSoftwareKeypressEvent(final int keyCodeOrCodePoint, final int keyX,
final int keyY) { final int keyY, final boolean isKeyRepeat) {
final int keyCode; final int keyCode;
final int codePoint; final int codePoint;
if (keyCodeOrCodePoint <= 0) { if (keyCodeOrCodePoint <= 0) {
@ -1271,7 +1272,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
keyCode = Event.NOT_A_KEY_CODE; keyCode = Event.NOT_A_KEY_CODE;
codePoint = keyCodeOrCodePoint; codePoint = keyCodeOrCodePoint;
} }
return Event.createSoftwareKeypressEvent(codePoint, keyCode, keyX, keyY); return Event.createSoftwareKeypressEvent(codePoint, keyCode, keyX, keyY, isKeyRepeat);
} }
// Called from PointerTracker through the KeyboardActionListener interface // Called from PointerTracker through the KeyboardActionListener interface

View File

@ -884,10 +884,17 @@ public final class InputLogic {
mSpaceState = SpaceState.NONE; mSpaceState = SpaceState.NONE;
mDeleteCount++; mDeleteCount++;
// In many cases, we may have to put the keyboard in auto-shift state again. However // In many cases after backspace, we need to update the shift state. Normally we need
// we want to wait a few milliseconds before doing it to avoid the keyboard flashing // to do this right away to avoid the shift state being out of date in case the user types
// during key repeat. // backspace then some other character very fast. However, in the case of backspace key
inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_LATER); // repeat, this can lead to flashiness when the cursor flies over positions where the
// shift state should be updated, so if this is a key repeat, we update after a small delay.
// Then again, even in the case of a key repeat, if the cursor is at start of text, it
// can't go any further back, so we can update right away even if it's a key repeat.
final int shiftUpdateKind =
inputTransaction.mEvent.isKeyRepeat() && mConnection.getExpectedSelectionStart() > 0
? InputTransaction.SHIFT_UPDATE_LATER : InputTransaction.SHIFT_UPDATE_NOW;
inputTransaction.requireShiftUpdate(shiftUpdateKind);
if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) { if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
// If we are in the middle of a recorrection, we need to commit the recorrection // If we are in the middle of a recorrection, we need to commit the recorrection
@ -910,11 +917,6 @@ public final class InputLogic {
} }
mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1); mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
inputTransaction.setRequiresUpdateSuggestions(); inputTransaction.setRequiresUpdateSuggestions();
if (!mWordComposer.isComposingWord()) {
// If we just removed the last character, auto-caps mode may have changed so we
// need to re-evaluate.
inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
}
} else { } else {
if (mLastComposedWord.canRevertCommit()) { if (mLastComposedWord.canRevertCommit()) {
if (inputTransaction.mSettingsValues.mIsInternal) { if (inputTransaction.mSettingsValues.mIsInternal) {
@ -1025,8 +1027,6 @@ public final class InputLogic {
restartSuggestionsOnWordTouchedByCursor(inputTransaction.mSettingsValues, restartSuggestionsOnWordTouchedByCursor(inputTransaction.mSettingsValues,
true /* includeResumedWordInSuggestions */); true /* includeResumedWordInSuggestions */);
} }
// We just removed at least one character. We need to update the auto-caps state.
inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
} }
} }

View File

@ -254,7 +254,7 @@ public class InputTestsBase extends ServiceTestCase<LatinIMEForTests> {
} }
// type(int) and type(String): helper methods to send a code point resp. a string to LatinIME. // type(int) and type(String): helper methods to send a code point resp. a string to LatinIME.
protected void type(final int codePoint) { protected void typeInternal(final int codePoint, final boolean isKeyRepeat) {
// onPressKey and onReleaseKey are explicitly deactivated here, but they do happen in the // onPressKey and onReleaseKey are explicitly deactivated here, but they do happen in the
// code (although multitouch/slide input and other factors make the sequencing complicated). // code (although multitouch/slide input and other factors make the sequencing complicated).
// They are supposed to be entirely deconnected from the input logic from LatinIME point of // They are supposed to be entirely deconnected from the input logic from LatinIME point of
@ -263,16 +263,26 @@ public class InputTestsBase extends ServiceTestCase<LatinIMEForTests> {
// but keep them in mind if something breaks. Commenting them out as is should work. // but keep them in mind if something breaks. Commenting them out as is should work.
//mLatinIME.onPressKey(codePoint, 0 /* repeatCount */, true /* isSinglePointer */); //mLatinIME.onPressKey(codePoint, 0 /* repeatCount */, true /* isSinglePointer */);
final Key key = mKeyboard.getKey(codePoint); final Key key = mKeyboard.getKey(codePoint);
if (key != null) { if (key == null) {
mLatinIME.onCodeInput(codePoint, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
isKeyRepeat);
} else {
final int x = key.getX() + key.getWidth() / 2; final int x = key.getX() + key.getWidth() / 2;
final int y = key.getY() + key.getHeight() / 2; final int y = key.getY() + key.getHeight() / 2;
mLatinIME.onCodeInput(codePoint, x, y); mLatinIME.onCodeInput(codePoint, x, y, isKeyRepeat);
return;
} }
mLatinIME.onCodeInput(codePoint, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); // Also see the comment at the top of this function about onReleaseKey
//mLatinIME.onReleaseKey(codePoint, false /* withSliding */); //mLatinIME.onReleaseKey(codePoint, false /* withSliding */);
} }
protected void type(final int codePoint) {
typeInternal(codePoint, false /* isKeyRepeat */);
}
protected void repeatKey(final int codePoint) {
typeInternal(codePoint, true /* isKeyRepeat */);
}
protected void type(final String stringToType) { protected void type(final String stringToType) {
for (int i = 0; i < stringToType.length(); i = stringToType.offsetByCodePoints(i, 1)) { for (int i = 0; i < stringToType.length(); i = stringToType.offsetByCodePoints(i, 1)) {
type(stringToType.codePointAt(i)); type(stringToType.codePointAt(i));

View File

@ -53,4 +53,29 @@ public class ShiftModeTests extends InputTestsBase {
type(" "); type(" ");
assertTrue("Caps after period space", isCapsModeAutoShifted()); assertTrue("Caps after period space", isCapsModeAutoShifted());
} }
public void testBackspace() {
assertTrue("Initial auto caps state", isCapsModeAutoShifted());
type("A");
assertFalse("Caps state after one letter", isCapsModeAutoShifted());
type(Constants.CODE_DELETE);
assertTrue("Auto caps state at start after delete", isCapsModeAutoShifted());
}
public void testRepeatingBackspace() {
final String SENTENCE_TO_TYPE = "Test sentence. Another.";
final int BACKSPACE_COUNT =
SENTENCE_TO_TYPE.length() - SENTENCE_TO_TYPE.lastIndexOf(' ') - 1;
type(SENTENCE_TO_TYPE);
assertFalse("Caps after typing \"" + SENTENCE_TO_TYPE + "\"", isCapsModeAutoShifted());
type(Constants.CODE_DELETE);
for (int i = 1; i < BACKSPACE_COUNT; ++i) {
repeatKey(Constants.CODE_DELETE);
}
assertFalse("Caps immediately after repeating Backspace a lot", isCapsModeAutoShifted());
sleep(DELAY_TO_WAIT_FOR_PREDICTIONS);
runMessages();
assertTrue("Caps after a while after repeating Backspace a lot", isCapsModeAutoShifted());
}
} }