diff options
| author | Menny Even Danan <menny@evendanan.net> | 2016-03-07 05:03:04 +0000 |
|---|---|---|
| committer | Menny Even Danan <menny@evendanan.net> | 2016-03-07 05:03:04 +0000 |
| commit | 857c17e1f3315fb686878d8c1abe7edaba875cde (patch) | |
| tree | 653370e9ee72cc571a472c238af789f4ac6a7576 /src | |
| parent | 40bdb20b07dc46c41ad67e2ae922300366552efc (diff) | |
| download | AnySoftKeyboard-857c17e1f3315fb686878d8c1abe7edaba875cde.tar.gz AnySoftKeyboard-857c17e1f3315fb686878d8c1abe7edaba875cde.tar.bz2 | |
fix for #585. undo-commit was extended to auto-added-word state
Diffstat (limited to 'src')
5 files changed, 351 insertions, 227 deletions
diff --git a/src/main/java/com/anysoftkeyboard/AnySoftKeyboard.java b/src/main/java/com/anysoftkeyboard/AnySoftKeyboard.java index 5cce118e7..8b8aa7034 100644 --- a/src/main/java/com/anysoftkeyboard/AnySoftKeyboard.java +++ b/src/main/java/com/anysoftkeyboard/AnySoftKeyboard.java @@ -66,7 +66,6 @@ import com.anysoftkeyboard.base.dictionaries.EditableDictionary; import com.anysoftkeyboard.dictionaries.ExternalDictionaryFactory; import com.anysoftkeyboard.dictionaries.Suggest; import com.anysoftkeyboard.dictionaries.TextEntryState; -import com.anysoftkeyboard.dictionaries.TextEntryState.State; import com.anysoftkeyboard.dictionaries.sqlite.AutoDictionary; import com.anysoftkeyboard.keyboards.AnyKeyboard; import com.anysoftkeyboard.keyboards.AnyKeyboard.HardKeyboardTranslator; @@ -102,9 +101,7 @@ import com.menny.android.anysoftkeyboard.BuildConfig; import com.menny.android.anysoftkeyboard.R; import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.Set; /** * Input method implementation for QWERTY-ish keyboard. @@ -646,9 +643,9 @@ public abstract class AnySoftKeyboard extends InputMethodService implements } } else { Log.d(TAG, "onUpdateSelection: not predicting at this moment, maybe the cursor is now at a new word?"); - if (TextEntryState.getState() == State.ACCEPTED_DEFAULT) { + if (TextEntryState.willUndoCommitOnBackspace()){ if (mUndoCommitCursorPosition == oldSelStart && mUndoCommitCursorPosition != newSelStart) { - Log.d(TAG, "onUpdateSelection: I am in ACCEPTED_DEFAULT state, but the user moved the cursor, so it is not possible to undo_commit now."); + Log.d(TAG, "onUpdateSelection: I am in a state that is position sensitive but the user moved the cursor, so it is not possible to undo_commit now."); abortCorrection(true, false); } else if (mUndoCommitCursorPosition == -2) { Log.d(TAG, "onUpdateSelection: I am in ACCEPTED_DEFAULT state, time to store the position - I can only undo-commit from here."); @@ -1973,8 +1970,7 @@ public abstract class AnySoftKeyboard extends InputMethodService implements if (TextEntryState.getState() == TextEntryState.State.UNDO_COMMIT) { revertLastWord(deleteChar); } else if (deleteChar) { - if (mCandidateView != null - && mCandidateView.dismissAddToDictionaryHint()) { + if (mCandidateView != null && mCandidateView.dismissAddToDictionaryHint()) { // Go back to the suggestion mode if the user canceled the // "Touch again to save". // NOTE: we don't revert the word when backspacing @@ -2049,7 +2045,6 @@ public abstract class AnySoftKeyboard extends InputMethodService implements if (BuildConfig.DEBUG) Log.d(TAG, "handleCharacter: %d, isPredictionOn: %s, mPredicting: %s", primaryCode, isPredictionOn(), mPredicting); mExpectingSelectionUpdateBy = SystemClock.uptimeMillis() + MAX_TIME_TO_EXPECT_SELECTION_UPDATE; - if (!mPredicting && isPredictionOn() && isAlphabet(primaryCode) && !isCursorTouchingWord()) { mPredicting = true; mUndoCommitCursorPosition = -2;// so it will be marked the next time @@ -2103,17 +2098,15 @@ public abstract class AnySoftKeyboard extends InputMethodService implements final int cursorPosition; if (mWord.cursorPosition() != mWord.length()) { //Cursor is not at the end of the word. I'll need to reposition - cursorPosition = getCursorPosition(ic); + cursorPosition = mGlobalCursorPosition + 1/*adding the new character*/; + ic.beginBatchEdit(); } else { cursorPosition = -1; } - if (cursorPosition >= 0) - ic.beginBatchEdit(); - ic.setComposingText(mWord.getTypedWord(), 1); - if (cursorPosition >= 0) { - ic.setSelection(cursorPosition + 1, cursorPosition + 1); + if (cursorPosition > 0) { + ic.setSelection(cursorPosition, cursorPosition); ic.endBatchEdit(); } } @@ -2359,13 +2352,15 @@ public abstract class AnySoftKeyboard extends InputMethodService implements mJustAutoAddedWord = addToDictionaries(mWord, AutoDictionary.AdditionType.Picked); } - final boolean showingAddToDictionaryHint = !mJustAutoAddedWord + final boolean showingAddToDictionaryHint = + (!mJustAutoAddedWord) && index == 0 && (mQuickFixes || mShowSuggestions) - && !mSuggest.isValidWord(suggestion)// this is for the case that the word was auto-added upon picking - && !mSuggest.isValidWord(suggestion.toString().toLowerCase(getCurrentKeyboard().getLocale())); + && (!mSuggest.isValidWord(suggestion))// this is for the case that the word was auto-added upon picking + && (!mSuggest.isValidWord(suggestion.toString().toLowerCase(getCurrentKeyboard().getLocale()))); if (showingAddToDictionaryHint) { + TextEntryState.acceptedSuggestionAddedToDictionary(); if (mCandidateView != null) mCandidateView.showAddToDictionaryHint(suggestion); } else if (!TextUtils.isEmpty(mCommittedWord) && !mJustAutoAddedWord) { //showing next-words if: diff --git a/src/main/java/com/anysoftkeyboard/dictionaries/TextEntryState.java b/src/main/java/com/anysoftkeyboard/dictionaries/TextEntryState.java index 2422acb74..9ef459daa 100644 --- a/src/main/java/com/anysoftkeyboard/dictionaries/TextEntryState.java +++ b/src/main/java/com/anysoftkeyboard/dictionaries/TextEntryState.java @@ -49,6 +49,23 @@ public class TextEntryState { private static int sActualChars; + public static boolean willUndoCommitOnBackspace() { + switch (sState) { + case ACCEPTED_DEFAULT: + case PICKED_TYPED_ADDED_TO_DICTIONARY: + return true; + default: + return false; + } + } + + public static void acceptedSuggestionAddedToDictionary() { + if (BuildConfig.TESTING_BUILD) { + if (sState != State.PICKED_SUGGESTION) Log.wtf(TAG, "acceptedSuggestionAddedToDictionary should only be called in a PICKED_SUGGESTION state!"); + } + sState = State.PICKED_TYPED_ADDED_TO_DICTIONARY; + } + public enum State { UNKNOWN, START, @@ -61,7 +78,8 @@ public class TextEntryState { SPACE_AFTER_PICKED, UNDO_COMMIT, CORRECTING, - PICKED_CORRECTION; + PICKED_CORRECTION, + PICKED_TYPED_ADDED_TO_DICTIONARY, } private static State sState = State.UNKNOWN; @@ -129,21 +147,6 @@ public class TextEntryState { displayState(); } - // State.ACCEPTED_DEFAULT will be changed to other sub-states - // (see "case ACCEPTED_DEFAULT" in typedCharacter() below), - // and should be restored back to State.ACCEPTED_DEFAULT after processing for each sub-state. - public static void backToAcceptedDefault(CharSequence typedWord) { - if (typedWord == null) return; - switch (sState) { - case SPACE_AFTER_ACCEPTED: - case PUNCTUATION_AFTER_ACCEPTED: - case IN_WORD: - sState = State.ACCEPTED_DEFAULT; - break; - } - displayState(); - } - public static void acceptedTyped(CharSequence typedWord) { sWordNotInDictionaryCount++; sState = State.PICKED_SUGGESTION; @@ -164,35 +167,25 @@ public class TextEntryState { displayState(); } - public static void selectedForCorrection() { - sState = State.CORRECTING; - displayState(); - } - public static void typedCharacter(char c, boolean isSeparator) { boolean isSpace = c == ' '; switch (sState) { case IN_WORD: if (isSpace || isSeparator) { sState = State.START; - } else { - // State hasn't changed. - } + }/* else State hasn't changed.*/ break; case ACCEPTED_DEFAULT: case SPACE_AFTER_PICKED: - if (isSpace) { - sState = State.SPACE_AFTER_ACCEPTED; - } else if (isSeparator) { - sState = State.PUNCTUATION_AFTER_ACCEPTED; - } else { - sState = State.IN_WORD; - } - break; case PICKED_SUGGESTION: case PICKED_CORRECTION: + case PICKED_TYPED_ADDED_TO_DICTIONARY: if (isSpace) { - sState = State.SPACE_AFTER_PICKED; + if (sState == State.ACCEPTED_DEFAULT || sState == State.SPACE_AFTER_PICKED) { + sState = State.SPACE_AFTER_ACCEPTED; + } else { + sState = State.SPACE_AFTER_PICKED; + } } else if (isSeparator) { // Swap sState = State.PUNCTUATION_AFTER_ACCEPTED; @@ -229,7 +222,8 @@ public class TextEntryState { if (sState == State.ACCEPTED_DEFAULT) { sState = State.UNDO_COMMIT; sAutoSuggestUndoneCount++; - //LatinImeLogger.logOnAutoSuggestionCanceled(); + } else if (sState == State.PICKED_TYPED_ADDED_TO_DICTIONARY) { + sState = State.UNDO_COMMIT; } else if (sState == State.UNDO_COMMIT) { sState = State.IN_WORD; } diff --git a/src/test/java/com/anysoftkeyboard/AnySoftKeyboardDictionaryGetWordsTest.java b/src/test/java/com/anysoftkeyboard/AnySoftKeyboardDictionaryGetWordsTest.java index ef20357be..6d761120c 100644 --- a/src/test/java/com/anysoftkeyboard/AnySoftKeyboardDictionaryGetWordsTest.java +++ b/src/test/java/com/anysoftkeyboard/AnySoftKeyboardDictionaryGetWordsTest.java @@ -2,6 +2,7 @@ package com.anysoftkeyboard; import android.view.inputmethod.EditorInfo; +import com.anysoftkeyboard.api.KeyCodes; import com.anysoftkeyboard.keyboards.views.CandidateView; import com.menny.android.anysoftkeyboard.AskGradleTestRunner; @@ -82,6 +83,8 @@ public class AnySoftKeyboardDictionaryGetWordsTest { @Test public void testAskForSuggestionsWithDelayedInputConnectionUpdates() { + TestInputConnection inputConnection = (TestInputConnection) mAnySoftKeyboardUnderTest.getCurrentInputConnection(); + inputConnection.setSendUpdates(false); verifyNoSuggestionsInteractions(mSpiedCandidateView); mAnySoftKeyboardUnderTest.simulateKeyPress('h'); verifySuggestions(mSpiedCandidateView, true, "h"); @@ -90,7 +93,7 @@ public class AnySoftKeyboardDictionaryGetWordsTest { //sending a delayed event from the input-connection. //this can happen when the user is clicking fast (in ASK thread), but the other side (the app thread) //is too slow, or busy with something to send out events. - mAnySoftKeyboardUnderTest.updateInputConnection('h'); + inputConnection.sendUpdateNow(); mAnySoftKeyboardUnderTest.simulateKeyPress('l'); verifySuggestions(mSpiedCandidateView, true, "hel", "hell", "hello"); @@ -111,7 +114,7 @@ public class AnySoftKeyboardDictionaryGetWordsTest { @Test public void testAutoPickWordWhenCursorAtTheEndOfTheWord() { - TestableAnySoftKeyboard.TestInputConnection inputConnection = (TestableAnySoftKeyboard.TestInputConnection) mAnySoftKeyboardUnderTest.getCurrentInputConnection(); + TestInputConnection inputConnection = (TestInputConnection) mAnySoftKeyboardUnderTest.getCurrentInputConnection(); verifyNoSuggestionsInteractions(mSpiedCandidateView); mAnySoftKeyboardUnderTest.simulateTextTyping("h"); verifySuggestions(mSpiedCandidateView, true, "h"); @@ -120,14 +123,16 @@ public class AnySoftKeyboardDictionaryGetWordsTest { mAnySoftKeyboardUnderTest.simulateTextTyping("l"); verifySuggestions(mSpiedCandidateView, true, "hel", "hell", "hello"); - Assert.assertEquals("", inputConnection.getLastCommitText()); + Assert.assertEquals("", inputConnection.getLastCommitCorrection()); mAnySoftKeyboardUnderTest.simulateKeyPress(' '); - Assert.assertEquals("hell", inputConnection.getLastCommitText()); + Assert.assertEquals("hell", inputConnection.getLastCommitCorrection()); + //we should also see the space + Assert.assertEquals("hell ", inputConnection.getCurrentTextInInputConnection()); } @Test public void testDoesNotAutoPickWordWhenCursorNotAtTheEndOfTheWord() { - TestableAnySoftKeyboard.TestInputConnection inputConnection = (TestableAnySoftKeyboard.TestInputConnection) mAnySoftKeyboardUnderTest.getCurrentInputConnection(); + TestInputConnection inputConnection = (TestInputConnection) mAnySoftKeyboardUnderTest.getCurrentInputConnection(); verifyNoSuggestionsInteractions(mSpiedCandidateView); mAnySoftKeyboardUnderTest.simulateTextTyping("h"); verifySuggestions(mSpiedCandidateView, true, "h"); @@ -140,24 +145,48 @@ public class AnySoftKeyboardDictionaryGetWordsTest { verifySuggestions(mSpiedCandidateView, true, "hel", "hell", "hello"); Mockito.reset(inputConnection);//clearing any previous interactions with finishComposingText - Assert.assertEquals("", inputConnection.getLastCommitText()); + Assert.assertEquals("", inputConnection.getLastCommitCorrection()); mAnySoftKeyboardUnderTest.simulateKeyPress(' '); //this time, it will not auto-pick since the cursor is inside the word (and not at the end) - Assert.assertEquals("", inputConnection.getLastCommitText()); + Assert.assertEquals("", inputConnection.getLastCommitCorrection()); //will stop composing in the input-connection Mockito.verify(inputConnection).finishComposingText(); //also, it will abort suggestions verifySuggestions(mSpiedCandidateView, true); } + @Test + public void testBackSpaceCorrectlyWhenEditingManuallyPickedWord() { + //related to https://github.com/AnySoftKeyboard/AnySoftKeyboard/issues/585 + TestInputConnection inputConnection = (TestInputConnection) mAnySoftKeyboardUnderTest.getCurrentInputConnection(); + + verifyNoSuggestionsInteractions(mSpiedCandidateView); + mAnySoftKeyboardUnderTest.simulateTextTyping("hel"); + verifySuggestions(mSpiedCandidateView, true, "hel", "hell", "hello"); + + Assert.assertEquals("", inputConnection.getLastCommitCorrection()); + mAnySoftKeyboardUnderTest.pickSuggestionManually(0, "hel"); + //at this point, the candidates view will show a hint + Mockito.verify(mAnySoftKeyboardUnderTest.getMockCandidateView()).showAddToDictionaryHint("hel"); + Assert.assertEquals("hel ", inputConnection.getCurrentTextInInputConnection()); + //now, navigating to to the 'e' + inputConnection.setSelection(2, 2); + Assert.assertEquals("hel ", inputConnection.getCurrentTextInInputConnection()); + Assert.assertEquals(2, inputConnection.getCurrentStartPosition()); + mAnySoftKeyboardUnderTest.simulateKeyPress(KeyCodes.DELETE, true); + Assert.assertEquals("hl ", inputConnection.getCurrentTextInInputConnection()); + Assert.assertEquals(1, inputConnection.getCurrentStartPosition()); + } + private void verifyNoSuggestionsInteractions(CandidateView candidateView) { Mockito.verify(candidateView, Mockito.never()).setSuggestions(Mockito.anyList(), Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.anyBoolean()); } private void verifySuggestions(CandidateView candidateView, boolean resetCandidateView, CharSequence... expectedSuggestions) { ArgumentCaptor<List> suggestionsCaptor = ArgumentCaptor.forClass(List.class); - Mockito.verify(candidateView).setSuggestions(suggestionsCaptor.capture(), Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.anyBoolean()); - List actualSuggestions = suggestionsCaptor.getValue(); + Mockito.verify(candidateView, Mockito.atLeastOnce()).setSuggestions(suggestionsCaptor.capture(), Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.anyBoolean()); + List<List> allValues = suggestionsCaptor.getAllValues(); + List actualSuggestions = allValues.get(allValues.size()-1); if (expectedSuggestions.length == 0) { Assert.assertTrue(actualSuggestions == null || actualSuggestions.size() == 0); } else { @@ -168,6 +197,6 @@ public class AnySoftKeyboardDictionaryGetWordsTest { } } - if (resetCandidateView) Mockito.reset(candidateView); + if (resetCandidateView) mAnySoftKeyboardUnderTest.resetMockCandidateView(); } }
\ No newline at end of file diff --git a/src/test/java/com/anysoftkeyboard/TestInputConnection.java b/src/test/java/com/anysoftkeyboard/TestInputConnection.java new file mode 100644 index 000000000..15e913ce1 --- /dev/null +++ b/src/test/java/com/anysoftkeyboard/TestInputConnection.java @@ -0,0 +1,249 @@ +package com.anysoftkeyboard; + +import android.annotation.TargetApi; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.style.UnderlineSpan; +import android.view.KeyEvent; +import android.view.inputmethod.CompletionInfo; +import android.view.inputmethod.CorrectionInfo; +import android.view.inputmethod.ExtractedText; +import android.view.inputmethod.ExtractedTextRequest; +import android.view.inputmethod.InputConnection; + +public class TestInputConnection implements InputConnection { + + @NonNull + private UnderlineSpan mCurrentComposingSpan = new UnderlineSpan(); + private boolean mSendUpdates = true; + private boolean mInEditMode = false; + private boolean mChangesWhileInEdit = false; + + private int mCursorPosition = 0; + private SpannableStringBuilder mInputText = new SpannableStringBuilder(); + @NonNull + private final AnySoftKeyboard mIme; + + private String mLastCommitCorrection = ""; + + public TestInputConnection(@NonNull AnySoftKeyboard ime) { + mIme = ime; + } + + @Override + public CharSequence getTextBeforeCursor(int n, int flags) { + String unspanned = mInputText.toString(); + int start = Math.max(0, mCursorPosition - n); + int end = Math.min(mInputText.length(), mCursorPosition); + return unspanned.substring(start, end); + } + + @Override + public CharSequence getTextAfterCursor(int n, int flags) { + String unspanned = mInputText.toString(); + int start = Math.max(0, mCursorPosition); + int end = Math.min(mInputText.length(), mCursorPosition + n); + return unspanned.substring(start, end); + } + + @Override + public CharSequence getSelectedText(int flags) { + return ""; + } + + @Override + public int getCursorCapsMode(int reqModes) { + return 0; + } + + @Override + public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { + ExtractedText extracted = new ExtractedText(); + extracted.startOffset = 0; + extracted.selectionStart = mCursorPosition; + extracted.selectionEnd = mCursorPosition; + + return extracted; + } + + @Override + public boolean deleteSurroundingText(int beforeLength, int afterLength) { + if (beforeLength == 0 && afterLength == 0) return true; + + final int deleteStart = Math.max(mCursorPosition - beforeLength, 0); + final int deleteEnd = Math.min(mCursorPosition + afterLength, mInputText.length()); + mInputText.delete(deleteStart, deleteEnd); + final int cursorDelta = mCursorPosition - deleteStart; + notifyTextChange(-cursorDelta); + return true; + } + + private void notifyTextChange(int cursorDelta) { + final int oldPosition = mCursorPosition; + mCursorPosition += cursorDelta; + if (mInEditMode) { + mChangesWhileInEdit = true; + } else { + int[] composedTextRange = findComposedText(); + if (mSendUpdates) mIme.onUpdateSelection(oldPosition, oldPosition, mCursorPosition, mCursorPosition, composedTextRange[0], composedTextRange[1]); + } + } + + public void setSendUpdates(boolean sendUpdates) { + mSendUpdates = sendUpdates; + } + + public void sendUpdateNow() { + final boolean originalSendState = mSendUpdates; + mSendUpdates = true; + notifyTextChange(0); + mSendUpdates = originalSendState; + } + + @Override + public boolean setComposingText(CharSequence text, int newCursorPosition) { + commitTextAs(text, true); + return true; + } + + private void commitTextAs(CharSequence text, boolean asComposing) { + int[] composedTextRange = findComposedText(); + mInputText.delete(composedTextRange[0], composedTextRange[1]); + final int textRemoved = (composedTextRange[1] - composedTextRange[0]); + mInputText.append(asComposing? asComposeText(text) : text); + notifyTextChange(text.length() - textRemoved); + } + + private int[] findComposedText() { + int start = mInputText.getSpanStart(mCurrentComposingSpan); + int end = mInputText.getSpanEnd(mCurrentComposingSpan); + if (start == -1) return new int[] {mCursorPosition, mCursorPosition}; + else return new int[] {start, end}; + } + + private CharSequence asComposeText(CharSequence text) { + mCurrentComposingSpan = new UnderlineSpan(); + SpannableString composed = new SpannableString(text); + composed.setSpan(mCurrentComposingSpan, 0, text.length(), 0); + return composed; + } + + @Override + public boolean setComposingRegion(int start, int end) { + return false; + } + + @Override + public boolean finishComposingText() { + mInputText.clearSpans(); + return true; + } + + @Override + public boolean commitText(CharSequence text, int newCursorPosition) { + commitTextAs(text, false); + return true; + } + + @Override + public boolean commitCompletion(CompletionInfo text) { + return false; + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + @Override + public boolean commitCorrection(CorrectionInfo correctionInfo) { + mLastCommitCorrection = correctionInfo.getNewText().toString(); + return true; + } + + public String getLastCommitCorrection() { + return mLastCommitCorrection; + } + + @Override + public boolean setSelection(int start, int end) { + if (start == end && start == mCursorPosition) return true; + + notifyTextChange(start - mCursorPosition); + + return true; + } + + @Override + public boolean performEditorAction(int editorAction) { + return false; + } + + @Override + public boolean performContextMenuAction(int id) { + return false; + } + + @Override + public boolean beginBatchEdit() { + mInEditMode = true; + return true; + } + + @Override + public boolean endBatchEdit() { + mInEditMode = false; + if (mChangesWhileInEdit) sendUpdateNow(); + mChangesWhileInEdit = false; + return true; + } + + @Override + public boolean sendKeyEvent(KeyEvent event) { + /* + ic.sendKeyEvent(new KeyEvent(eventTime, eventTime, + KeyEvent.ACTION_DOWN, keyEventCode, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, + KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE)); + ic.sendKeyEvent(new KeyEvent(eventTime, SystemClock.uptimeMillis(), + KeyEvent.ACTION_UP, keyEventCode, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, + KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE)); + */ + if (event.getAction() == KeyEvent.ACTION_UP) { + //only handling UP events + if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) { + deleteSurroundingText(1, 0); + } else if (event.getKeyCode() == KeyEvent.KEYCODE_SPACE) { + commitText(" ", 1); + } + } + return true; + } + + @Override + public boolean clearMetaKeyStates(int states) { + return true; + } + + @Override + public boolean reportFullscreenMode(boolean enabled) { + return false; + } + + @Override + public boolean performPrivateCommand(String action, Bundle data) { + return false; + } + + @Override + public boolean requestCursorUpdates(int cursorUpdateMode) { + return false; + } + + @NonNull + public String getCurrentTextInInputConnection() { + return mInputText.toString(); + } + + public int getCurrentStartPosition() { + return mCursorPosition; + } +} diff --git a/src/test/java/com/anysoftkeyboard/TestableAnySoftKeyboard.java b/src/test/java/com/anysoftkeyboard/TestableAnySoftKeyboard.java index 852f7e9a4..0abdc7e63 100644 --- a/src/test/java/com/anysoftkeyboard/TestableAnySoftKeyboard.java +++ b/src/test/java/com/anysoftkeyboard/TestableAnySoftKeyboard.java @@ -1,17 +1,9 @@ package com.anysoftkeyboard; -import android.annotation.TargetApi; import android.content.Context; -import android.os.Build; -import android.os.Bundle; import android.support.annotation.NonNull; -import android.view.KeyEvent; import android.view.View; -import android.view.inputmethod.CompletionInfo; -import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.ExtractedText; -import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import com.anysoftkeyboard.addons.AddOn; @@ -31,6 +23,8 @@ import com.menny.android.anysoftkeyboard.SoftKeyboard; import org.junit.Assert; import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import org.robolectric.Robolectric; import org.robolectric.shadows.ShadowSystemClock; @@ -50,6 +44,7 @@ public class TestableAnySoftKeyboard extends SoftKeyboard { private CandidateView mMockCandidateView; private UserDictionary mSpiedUserDictionary; private boolean mHidden = true; + private boolean mCandidateShowsHint = false; public Suggest getSpiedSuggest() { return mSpiedSuggest; @@ -81,10 +76,30 @@ public class TestableAnySoftKeyboard extends SoftKeyboard { public View onCreateCandidatesView() { View spiedRootView = Mockito.spy(super.onCreateCandidatesView()); mMockCandidateView = Mockito.mock(CandidateView.class); + resetMockCandidateView(); Mockito.doReturn(mMockCandidateView).when(spiedRootView).findViewById(R.id.candidates); return spiedRootView; } + public void resetMockCandidateView() { + Mockito.reset(mMockCandidateView); + Mockito.doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + boolean previousState = mCandidateShowsHint; + mCandidateShowsHint = false; + return previousState; + } + }).when(mMockCandidateView).dismissAddToDictionaryHint(); + Mockito.doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + mCandidateShowsHint = true; + return null; + } + }).when(mMockCandidateView).showAddToDictionaryHint(Mockito.any(CharSequence.class)); + } + @Override public EditorInfo getCurrentInputEditorInfo() { return mEditorInfo; @@ -147,7 +162,6 @@ public class TestableAnySoftKeyboard extends SoftKeyboard { if (asDiscreteKeys) { for (char key : text.toCharArray()) { simulateKeyPress(key, advanceTime); - updateInputConnection(key); } } else { onText(null, text); @@ -156,10 +170,6 @@ public class TestableAnySoftKeyboard extends SoftKeyboard { } } - public void updateInputConnection(char key) { - mInputConnection.appendToInput(key); - } - public void simulateKeyPress(final int keyCode, final boolean advanceTime) { onPress(keyCode); Robolectric.flushForegroundThreadScheduler(); @@ -253,157 +263,4 @@ public class TestableAnySoftKeyboard extends SoftKeyboard { } } - public static class TestInputConnection implements InputConnection { - - private int mCursorPosition = 0; - private StringBuilder mInputText = new StringBuilder(); - @NonNull - private final AnySoftKeyboard mIme; - - private String mLastCommitText = ""; - - public TestInputConnection(@NonNull AnySoftKeyboard ime) { - mIme = ime; - } - - @Override - public CharSequence getTextBeforeCursor(int n, int flags) { - return mInputText.substring(Math.max(0, mCursorPosition - n), Math.min(mInputText.length(), mCursorPosition)); - } - - @Override - public CharSequence getTextAfterCursor(int n, int flags) { - return mInputText.substring(Math.max(0, mCursorPosition), Math.min(mInputText.length(), mCursorPosition + n)); - } - - @Override - public CharSequence getSelectedText(int flags) { - return ""; - } - - @Override - public int getCursorCapsMode(int reqModes) { - return 0; - } - - @Override - public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { - ExtractedText extracted = new ExtractedText(); - extracted.startOffset = 0; - extracted.selectionStart = mCursorPosition; - extracted.selectionEnd = mCursorPosition; - - return extracted; - } - - @Override - public boolean deleteSurroundingText(int beforeLength, int afterLength) { - //String beforeText = mInputText.substring(0, Math.min(mInputText.length(), mCursorPosition-beforeLength)); - //String afterText = mInputText.substring(mCursorPosition+afterLength); - mInputText.delete(mCursorPosition-beforeLength, mCursorPosition+afterLength);// = beforeText+afterText; - notifyTextChange(-beforeLength); - return true; - } - - private void notifyTextChange(int cursorDelta) { - final int oldPosition = mCursorPosition; - mCursorPosition += cursorDelta; - mIme.onUpdateSelection(oldPosition, oldPosition, mCursorPosition, mCursorPosition, 0, mInputText.length()); - } - - @Override - public boolean setComposingText(CharSequence text, int newCursorPosition) { - return false; - } - - @Override - public boolean setComposingRegion(int start, int end) { - return false; - } - - @Override - public boolean finishComposingText() { - return false; - } - - @Override - public boolean commitText(CharSequence text, int newCursorPosition) { - return true; - } - - @Override - public boolean commitCompletion(CompletionInfo text) { - return false; - } - - @TargetApi(Build.VERSION_CODES.HONEYCOMB) - @Override - public boolean commitCorrection(CorrectionInfo correctionInfo) { - mLastCommitText = correctionInfo.getNewText().toString(); - return true; - } - - public String getLastCommitText() { - return mLastCommitText; - } - - @Override - public boolean setSelection(int start, int end) { - if (start != mCursorPosition) { - notifyTextChange(start-mCursorPosition); - } - - return true; - } - - @Override - public boolean performEditorAction(int editorAction) { - return false; - } - - @Override - public boolean performContextMenuAction(int id) { - return false; - } - - @Override - public boolean beginBatchEdit() { - return true; - } - - @Override - public boolean endBatchEdit() { - return true; - } - - @Override - public boolean sendKeyEvent(KeyEvent event) { - return true; - } - - @Override - public boolean clearMetaKeyStates(int states) { - return true; - } - - @Override - public boolean reportFullscreenMode(boolean enabled) { - return false; - } - - @Override - public boolean performPrivateCommand(String action, Bundle data) { - return false; - } - - @Override - public boolean requestCursorUpdates(int cursorUpdateMode) { - return false; - } - - public void appendToInput(char key) { - mInputText.insert(mCursorPosition, key); - notifyTextChange(1); - } - } } |
