InputLogicTests.java revision 845b24d9d31072b98958c557366617ad1c34f1b7
1/* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17package com.android.inputmethod.latin; 18 19import android.content.Context; 20import android.content.Intent; 21import android.content.SharedPreferences; 22import android.os.Looper; 23import android.os.MessageQueue; 24import android.preference.PreferenceManager; 25import android.test.ServiceTestCase; 26import android.text.InputType; 27import android.text.SpannableStringBuilder; 28import android.text.style.SuggestionSpan; 29import android.util.Log; 30import android.view.LayoutInflater; 31import android.view.ViewGroup; 32import android.view.View; 33import android.view.inputmethod.BaseInputConnection; 34import android.view.inputmethod.EditorInfo; 35import android.view.inputmethod.InputConnection; 36import android.widget.FrameLayout; 37import android.widget.TextView; 38 39import com.android.inputmethod.keyboard.Keyboard; 40import com.android.inputmethod.keyboard.KeyboardActionListener; 41import com.android.inputmethod.latin.spellcheck.AndroidSpellCheckerService; // for proximity info 42import com.android.inputmethod.latin.spellcheck.SpellCheckerProximityInfo; 43 44import java.util.Arrays; 45import java.util.HashMap; 46 47public class InputLogicTests extends ServiceTestCase<LatinIME> { 48 49 private static final String PREF_DEBUG_MODE = "debug_mode"; 50 51 private LatinIME mLatinIME; 52 private TextView mTextView; 53 private InputConnection mInputConnection; 54 55 public InputLogicTests() { 56 super(LatinIME.class); 57 } 58 59 // returns the previous setting value 60 private boolean setDebugMode(final boolean mode) { 61 final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mLatinIME); 62 final boolean previousDebugSetting = prefs.getBoolean(PREF_DEBUG_MODE, false); 63 final SharedPreferences.Editor editor = prefs.edit(); 64 editor.putBoolean(PREF_DEBUG_MODE, true); 65 editor.commit(); 66 return previousDebugSetting; 67 } 68 69 @Override 70 protected void setUp() { 71 try { 72 super.setUp(); 73 } catch (Exception e) { 74 e.printStackTrace(); 75 } 76 mTextView = new TextView(getContext()); 77 mTextView.setInputType(InputType.TYPE_CLASS_TEXT); 78 mTextView.setEnabled(true); 79 setupService(); 80 mLatinIME = getService(); 81 final boolean previousDebugSetting = setDebugMode(true); 82 mLatinIME.onCreate(); 83 setDebugMode(previousDebugSetting); 84 final EditorInfo ei = new EditorInfo(); 85 ei.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT; 86 final InputConnection ic = mTextView.onCreateInputConnection(ei); 87 ei.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT; 88 final LayoutInflater inflater = 89 (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); 90 final ViewGroup vg = new FrameLayout(getContext()); 91 final View inputView = inflater.inflate(R.layout.input_view, vg); 92 mLatinIME.setInputView(inputView); 93 mLatinIME.onBindInput(); 94 mLatinIME.onCreateInputView(); 95 mLatinIME.onStartInputView(ei, false); 96 mLatinIME.onCreateInputMethodInterface().startInput(ic, ei); 97 mInputConnection = ic; 98 // Wait for the main dictionary to be loaded (we need it for auto-correction tests) 99 int remainingAttempts = 10; 100 while (remainingAttempts > 0 && !mLatinIME.mSuggest.hasMainDictionary()) { 101 try { 102 Thread.sleep(100); 103 } catch (InterruptedException e) { 104 // Don't do much 105 } finally { 106 --remainingAttempts; 107 } 108 } 109 if (!mLatinIME.mSuggest.hasMainDictionary()) { 110 throw new RuntimeException("Can't initialize the main dictionary"); 111 } 112 } 113 114 // We need to run the messages added to the handler from LatinIME. The only way to do 115 // that is to call Looper#loop() on the right looper, so we're going to get the looper 116 // object and call #loop() here. The messages in the handler actually run on the UI 117 // thread of the keyboard by design of the handler, so we want to call it synchronously 118 // on the same thread that the tests are running on to mimic the actual environment as 119 // closely as possible. 120 // Now, Looper#loop() never exits in normal operation unless the Looper#quit() method 121 // is called, so we need to do that at the right time so that #loop() returns at some 122 // point and we don't end up in an infinite loop. 123 // After we quit, the looper is still technically ready to process more messages but 124 // the handler will refuse to enqueue any because #quit() has been called and it 125 // explicitly tests for it on message enqueuing, so we'll have to reset it so that 126 // it lets us continue normal operation. 127 private void runMessages() { 128 // Here begins deep magic. 129 final Looper looper = mLatinIME.mHandler.getLooper(); 130 mLatinIME.mHandler.post(new Runnable() { 131 @Override 132 public void run() { 133 looper.quit(); 134 } 135 }); 136 // The only way to get out of Looper#loop() is to call #quit() on it (or on its queue). 137 // Once #quit() is called remaining messages are not processed, which is why we post 138 // a message that calls it instead of calling it directly. 139 looper.loop(); 140 141 // Once #quit() has been called, the message queue has an "mQuiting" field that prevents 142 // any subsequent post in this queue. However the queue itself is still fully functional! 143 // If we have a way of resetting "queue.mQuiting" then we can continue using it as normal, 144 // coming back to this method to run the messages. 145 MessageQueue queue = looper.getQueue(); 146 try { 147 // However there is no way of doing it externally, and mQuiting is private. 148 // So... get out the big guns. 149 java.lang.reflect.Field f = MessageQueue.class.getDeclaredField("mQuiting"); 150 f.setAccessible(true); // What do you mean "private"? 151 f.setBoolean(queue, false); 152 } catch (NoSuchFieldException e) { 153 throw new RuntimeException(e); 154 } catch (IllegalAccessException e) { 155 throw new RuntimeException(e); 156 } 157 } 158 159 // type(int) and type(String): helper methods to send a code point resp. a string to LatinIME. 160 private void type(final int codePoint) { 161 // onPressKey and onReleaseKey are explicitly deactivated here, but they do happen in the 162 // code (although multitouch/slide input and other factors make the sequencing complicated). 163 // They are supposed to be entirely deconnected from the input logic from LatinIME point of 164 // view and only delegates to the parts of the code that care. So we don't include them here 165 // to keep these tests as pinpoint as possible and avoid bringing it too many dependencies, 166 // but keep them in mind if something breaks. Commenting them out as is should work. 167 //mLatinIME.onPressKey(codePoint); 168 mLatinIME.onCodeInput(codePoint, 169 KeyboardActionListener.SPELL_CHECKER_COORDINATE, 170 KeyboardActionListener.SPELL_CHECKER_COORDINATE); 171 //mLatinIME.onReleaseKey(codePoint, false); 172 } 173 174 private void type(final String stringToType) { 175 for (int i = 0; i < stringToType.length(); i = stringToType.offsetByCodePoints(i, 1)) { 176 type(stringToType.codePointAt(i)); 177 } 178 } 179 180 // Helper to avoid writing the try{}catch block each time 181 private static void sleep(final int milliseconds) { 182 try { 183 Thread.sleep(milliseconds); 184 } catch (InterruptedException e) {} 185 } 186 187 public void testTypeWord() { 188 final String WORD_TO_TYPE = "abcd"; 189 type(WORD_TO_TYPE); 190 assertEquals("type word", WORD_TO_TYPE, mTextView.getText().toString()); 191 } 192 193 public void testPickSuggestionThenBackspace() { 194 final String WORD_TO_TYPE = "this"; 195 final String EXPECTED_RESULT = "this"; 196 type(WORD_TO_TYPE); 197 mLatinIME.pickSuggestionManually(0, WORD_TO_TYPE); 198 mLatinIME.onUpdateSelection(0, 0, WORD_TO_TYPE.length(), WORD_TO_TYPE.length(), -1, -1); 199 type(Keyboard.CODE_DELETE); 200 assertEquals("press suggestion then backspace", EXPECTED_RESULT, 201 mTextView.getText().toString()); 202 } 203 204 public void testPickAutoCorrectionThenBackspace() { 205 final String WORD_TO_TYPE = "tgis"; 206 final String WORD_TO_PICK = "this"; 207 final String EXPECTED_RESULT = "tgis"; 208 type(WORD_TO_TYPE); 209 // Choose the auto-correction, which is always in position 0. For "tgis", the 210 // auto-correction should be "this". 211 mLatinIME.pickSuggestionManually(0, WORD_TO_PICK); 212 mLatinIME.onUpdateSelection(0, 0, WORD_TO_TYPE.length(), WORD_TO_TYPE.length(), -1, -1); 213 assertEquals("pick typed word over auto-correction then backspace", WORD_TO_PICK, 214 mTextView.getText().toString()); 215 type(Keyboard.CODE_DELETE); 216 assertEquals("pick typed word over auto-correction then backspace", EXPECTED_RESULT, 217 mTextView.getText().toString()); 218 } 219 220 public void testPickTypedWordOverAutoCorrectionThenBackspace() { 221 final String WORD_TO_TYPE = "tgis"; 222 final String EXPECTED_RESULT = "tgis"; 223 type(WORD_TO_TYPE); 224 // Choose the typed word, which should be in position 1 (because position 0 should 225 // be occupied by the "this" auto-correction, as checked by testAutoCorrect()) 226 mLatinIME.pickSuggestionManually(1, WORD_TO_TYPE); 227 mLatinIME.onUpdateSelection(0, 0, WORD_TO_TYPE.length(), WORD_TO_TYPE.length(), -1, -1); 228 assertEquals("pick typed word over auto-correction then backspace", WORD_TO_TYPE, 229 mTextView.getText().toString()); 230 type(Keyboard.CODE_DELETE); 231 assertEquals("pick typed word over auto-correction then backspace", EXPECTED_RESULT, 232 mTextView.getText().toString()); 233 } 234 235 public void testPickDifferentSuggestionThenBackspace() { 236 final String WORD_TO_TYPE = "tgis"; 237 final String WORD_TO_PICK = "thus"; 238 final String EXPECTED_RESULT = "tgis"; 239 type(WORD_TO_TYPE); 240 // Choose the second suggestion, which should be in position 2 and should be "thus" 241 // when "tgis is typed. 242 mLatinIME.pickSuggestionManually(2, WORD_TO_PICK); 243 mLatinIME.onUpdateSelection(0, 0, WORD_TO_TYPE.length(), WORD_TO_TYPE.length(), -1, -1); 244 assertEquals("pick different suggestion then backspace", WORD_TO_PICK, 245 mTextView.getText().toString()); 246 type(Keyboard.CODE_DELETE); 247 assertEquals("pick different suggestion then backspace", EXPECTED_RESULT, 248 mTextView.getText().toString()); 249 } 250 251 public void testDeleteSelection() { 252 final String STRING_TO_TYPE = "some text delete me some text"; 253 final int SELECTION_START = 10; 254 final int SELECTION_END = 19; 255 final String EXPECTED_RESULT = "some text some text"; 256 type(STRING_TO_TYPE); 257 // There is no IMF to call onUpdateSelection for us so we must do it by hand. 258 // Send once to simulate the cursor actually responding to the move caused by typing. 259 // This is necessary because LatinIME is bookkeeping to avoid confusing a real cursor 260 // move with a move triggered by LatinIME inputting stuff. 261 mLatinIME.onUpdateSelection(0, 0, STRING_TO_TYPE.length(), STRING_TO_TYPE.length(), -1, -1); 262 mInputConnection.setSelection(SELECTION_START, SELECTION_END); 263 // And now we simulate the user actually selecting some text. 264 mLatinIME.onUpdateSelection(0, 0, SELECTION_START, SELECTION_END, -1, -1); 265 type(Keyboard.CODE_DELETE); 266 assertEquals("delete selection", EXPECTED_RESULT, mTextView.getText().toString()); 267 } 268 269 public void testAutoCorrect() { 270 final String STRING_TO_TYPE = "tgis "; 271 final String EXPECTED_RESULT = "this "; 272 type(STRING_TO_TYPE); 273 assertEquals("simple auto-correct", EXPECTED_RESULT, mTextView.getText().toString()); 274 } 275 276 public void testAutoCorrectWithPeriod() { 277 final String STRING_TO_TYPE = "tgis."; 278 final String EXPECTED_RESULT = "this."; 279 type(STRING_TO_TYPE); 280 assertEquals("auto-correct with period", EXPECTED_RESULT, mTextView.getText().toString()); 281 } 282 283 public void testAutoCorrectWithPeriodThenRevert() { 284 final String STRING_TO_TYPE = "tgis."; 285 final String EXPECTED_RESULT = "tgis."; 286 type(STRING_TO_TYPE); 287 mLatinIME.onUpdateSelection(0, 0, STRING_TO_TYPE.length(), STRING_TO_TYPE.length(), -1, -1); 288 type(Keyboard.CODE_DELETE); 289 assertEquals("auto-correct with period then revert", EXPECTED_RESULT, 290 mTextView.getText().toString()); 291 } 292 293 public void testDoubleSpace() { 294 final String STRING_TO_TYPE = "this "; 295 final String EXPECTED_RESULT = "this. "; 296 type(STRING_TO_TYPE); 297 assertEquals("double space make a period", EXPECTED_RESULT, mTextView.getText().toString()); 298 } 299 300 public void testCancelDoubleSpace() { 301 final String STRING_TO_TYPE = "this "; 302 final String EXPECTED_RESULT = "this "; 303 type(STRING_TO_TYPE); 304 type(Keyboard.CODE_DELETE); 305 assertEquals("double space make a period", EXPECTED_RESULT, mTextView.getText().toString()); 306 } 307 308 public void testBackspaceAtStartAfterAutocorrect() { 309 final String STRING_TO_TYPE = "tgis "; 310 final String EXPECTED_RESULT = "this "; 311 final int NEW_CURSOR_POSITION = 0; 312 type(STRING_TO_TYPE); 313 mLatinIME.onUpdateSelection(0, 0, STRING_TO_TYPE.length(), STRING_TO_TYPE.length(), -1, -1); 314 mInputConnection.setSelection(NEW_CURSOR_POSITION, NEW_CURSOR_POSITION); 315 mLatinIME.onUpdateSelection(0, 0, NEW_CURSOR_POSITION, NEW_CURSOR_POSITION, -1, -1); 316 type(Keyboard.CODE_DELETE); 317 assertEquals("auto correct then move cursor to start of line then backspace", 318 EXPECTED_RESULT, mTextView.getText().toString()); 319 } 320 321 public void testAutoCorrectThenMoveCursorThenBackspace() { 322 final String STRING_TO_TYPE = "and tgis "; 323 final String EXPECTED_RESULT = "andthis "; 324 final int NEW_CURSOR_POSITION = STRING_TO_TYPE.indexOf('t'); 325 type(STRING_TO_TYPE); 326 mLatinIME.onUpdateSelection(0, 0, STRING_TO_TYPE.length(), STRING_TO_TYPE.length(), -1, -1); 327 mInputConnection.setSelection(NEW_CURSOR_POSITION, NEW_CURSOR_POSITION); 328 mLatinIME.onUpdateSelection(0, 0, NEW_CURSOR_POSITION, NEW_CURSOR_POSITION, -1, -1); 329 type(Keyboard.CODE_DELETE); 330 assertEquals("auto correct then move cursor then backspace", 331 EXPECTED_RESULT, mTextView.getText().toString()); 332 } 333 334 public void testNoSpaceAfterManualPick() { 335 final String WORD_TO_TYPE = "this"; 336 final String EXPECTED_RESULT = WORD_TO_TYPE; 337 type(WORD_TO_TYPE); 338 mLatinIME.pickSuggestionManually(0, WORD_TO_TYPE); 339 assertEquals("no space after manual pick", EXPECTED_RESULT, 340 mTextView.getText().toString()); 341 } 342 343 public void testManualPickThenType() { 344 final String WORD1_TO_TYPE = "this"; 345 final String WORD2_TO_TYPE = "is"; 346 final String EXPECTED_RESULT = "this is"; 347 type(WORD1_TO_TYPE); 348 mLatinIME.pickSuggestionManually(0, WORD1_TO_TYPE); 349 type(WORD2_TO_TYPE); 350 assertEquals("manual pick then type", EXPECTED_RESULT, mTextView.getText().toString()); 351 } 352 353 public void testManualPickThenSeparator() { 354 final String WORD1_TO_TYPE = "this"; 355 final String WORD2_TO_TYPE = "!"; 356 final String EXPECTED_RESULT = "this!"; 357 type(WORD1_TO_TYPE); 358 mLatinIME.pickSuggestionManually(0, WORD1_TO_TYPE); 359 type(WORD2_TO_TYPE); 360 assertEquals("manual pick then separator", EXPECTED_RESULT, mTextView.getText().toString()); 361 } 362 363 public void testWordThenSpaceThenPunctuationFromStripTwice() { 364 final String WORD_TO_TYPE = "this "; 365 final String PUNCTUATION_FROM_STRIP = "!"; 366 final String EXPECTED_RESULT = "this!! "; 367 type(WORD_TO_TYPE); 368 sleep(DELAY_TO_WAIT_FOR_UNDERLINE); 369 runMessages(); 370 assertTrue("type word then type space should display punctuation strip", 371 mLatinIME.isShowingPunctuationList()); 372 mLatinIME.pickSuggestionManually(0, PUNCTUATION_FROM_STRIP); 373 mLatinIME.pickSuggestionManually(0, PUNCTUATION_FROM_STRIP); 374 assertEquals("type word then type space then punctuation from strip twice", EXPECTED_RESULT, 375 mTextView.getText().toString()); 376 } 377 378 public void testWordThenSpaceThenPunctuationFromKeyboardTwice() { 379 final String WORD_TO_TYPE = "this !!"; 380 final String EXPECTED_RESULT = "this !!"; 381 type(WORD_TO_TYPE); 382 assertEquals("manual pick then space then punctuation from keyboard twice", EXPECTED_RESULT, 383 mTextView.getText().toString()); 384 } 385 386 public void testManualPickThenPunctuationFromStripTwiceThenType() { 387 final String WORD1_TO_TYPE = "this"; 388 final String WORD2_TO_TYPE = "is"; 389 final String PUNCTUATION_FROM_STRIP = "!"; 390 final String EXPECTED_RESULT = "this!! is"; 391 type(WORD1_TO_TYPE); 392 mLatinIME.pickSuggestionManually(0, WORD1_TO_TYPE); 393 mLatinIME.pickSuggestionManually(0, PUNCTUATION_FROM_STRIP); 394 mLatinIME.pickSuggestionManually(0, PUNCTUATION_FROM_STRIP); 395 type(WORD2_TO_TYPE); 396 assertEquals("pick word then pick punctuation twice then type", EXPECTED_RESULT, 397 mTextView.getText().toString()); 398 } 399 400 public void testManualPickThenSpaceThenType() { 401 final String WORD1_TO_TYPE = "this"; 402 final String WORD2_TO_TYPE = " is"; 403 final String EXPECTED_RESULT = "this is"; 404 type(WORD1_TO_TYPE); 405 mLatinIME.pickSuggestionManually(0, WORD1_TO_TYPE); 406 type(WORD2_TO_TYPE); 407 assertEquals("manual pick then space then type", EXPECTED_RESULT, 408 mTextView.getText().toString()); 409 } 410 411 public void testManualPickThenManualPick() { 412 final String WORD1_TO_TYPE = "this"; 413 final String WORD2_TO_PICK = "is"; 414 final String EXPECTED_RESULT = "this is"; 415 type(WORD1_TO_TYPE); 416 mLatinIME.pickSuggestionManually(0, WORD1_TO_TYPE); 417 // Here we fake picking a word through bigram prediction. This test is taking 418 // advantage of the fact that Latin IME blindly trusts the caller of #pickSuggestionManually 419 // to actually pass the right string. 420 mLatinIME.pickSuggestionManually(1, WORD2_TO_PICK); 421 assertEquals("manual pick then manual pick", EXPECTED_RESULT, 422 mTextView.getText().toString()); 423 } 424 425 public void testManualPickThenManualPickWithPunctAtStart() { 426 final String WORD1_TO_TYPE = "this"; 427 final String WORD2_TO_PICK = "!is"; 428 final String EXPECTED_RESULT = "this!is"; 429 type(WORD1_TO_TYPE); 430 mLatinIME.pickSuggestionManually(0, WORD1_TO_TYPE); 431 mLatinIME.pickSuggestionManually(1, WORD2_TO_PICK); 432 assertEquals("manual pick then manual pick a word with punct at start", EXPECTED_RESULT, 433 mTextView.getText().toString()); 434 } 435 436 public void testDeleteWholeComposingWord() { 437 final String WORD_TO_TYPE = "this"; 438 type(WORD_TO_TYPE); 439 for (int i = 0; i < WORD_TO_TYPE.length(); ++i) { 440 type(Keyboard.CODE_DELETE); 441 } 442 assertEquals("delete whole composing word", "", mTextView.getText().toString()); 443 } 444 445 public void testManuallyPickedWordThenColon() { 446 final String WORD_TO_TYPE = "this"; 447 final String PUNCTUATION = ":"; 448 final String EXPECTED_RESULT = "this:"; 449 type(WORD_TO_TYPE); 450 mLatinIME.pickSuggestionManually(0, WORD_TO_TYPE); 451 type(PUNCTUATION); 452 assertEquals("manually pick word then colon", 453 EXPECTED_RESULT, mTextView.getText().toString()); 454 } 455 456 public void testManuallyPickedWordThenOpenParen() { 457 final String WORD_TO_TYPE = "this"; 458 final String PUNCTUATION = "("; 459 final String EXPECTED_RESULT = "this ("; 460 type(WORD_TO_TYPE); 461 mLatinIME.pickSuggestionManually(0, WORD_TO_TYPE); 462 type(PUNCTUATION); 463 assertEquals("manually pick word then open paren", 464 EXPECTED_RESULT, mTextView.getText().toString()); 465 } 466 467 public void testManuallyPickedWordThenCloseParen() { 468 final String WORD_TO_TYPE = "this"; 469 final String PUNCTUATION = ")"; 470 final String EXPECTED_RESULT = "this)"; 471 type(WORD_TO_TYPE); 472 mLatinIME.pickSuggestionManually(0, WORD_TO_TYPE); 473 type(PUNCTUATION); 474 assertEquals("manually pick word then close paren", 475 EXPECTED_RESULT, mTextView.getText().toString()); 476 } 477 478 public void testManuallyPickedWordThenSmiley() { 479 final String WORD_TO_TYPE = "this"; 480 final String SPECIAL_KEY = ":-)"; 481 final String EXPECTED_RESULT = "this :-)"; 482 type(WORD_TO_TYPE); 483 mLatinIME.pickSuggestionManually(0, WORD_TO_TYPE); 484 mLatinIME.onTextInput(SPECIAL_KEY); 485 assertEquals("manually pick word then press the smiley key", 486 EXPECTED_RESULT, mTextView.getText().toString()); 487 } 488 489 public void testManuallyPickedWordThenDotCom() { 490 final String WORD_TO_TYPE = "this"; 491 final String SPECIAL_KEY = ".com"; 492 final String EXPECTED_RESULT = "this.com"; 493 type(WORD_TO_TYPE); 494 mLatinIME.pickSuggestionManually(0, WORD_TO_TYPE); 495 mLatinIME.onTextInput(SPECIAL_KEY); 496 assertEquals("manually pick word then press the .com key", 497 EXPECTED_RESULT, mTextView.getText().toString()); 498 } 499 500 public void testTypeWordTypeDotThenPressDotCom() { 501 final String WORD_TO_TYPE = "this."; 502 final String SPECIAL_KEY = ".com"; 503 final String EXPECTED_RESULT = "this.com"; 504 type(WORD_TO_TYPE); 505 mLatinIME.onTextInput(SPECIAL_KEY); 506 assertEquals("type word type dot then press the .com key", 507 EXPECTED_RESULT, mTextView.getText().toString()); 508 } 509 510 // A helper class to ease span tests 511 private static class Span { 512 final SpannableStringBuilder mInputText; 513 final SuggestionSpan mSpan; 514 final int mStart; 515 final int mEnd; 516 // The supplied CharSequence should be an instance of SpannableStringBuilder, 517 // and it should contain exactly zero or one SuggestionSpan. Otherwise, an exception 518 // is thrown. 519 public Span(final CharSequence inputText) { 520 mInputText = (SpannableStringBuilder)inputText; 521 final SuggestionSpan[] spans = 522 mInputText.getSpans(0, mInputText.length(), SuggestionSpan.class); 523 if (0 == spans.length) { 524 mSpan = null; 525 mStart = -1; 526 mEnd = -1; 527 } else if (1 == spans.length) { 528 mSpan = spans[0]; 529 mStart = mInputText.getSpanStart(mSpan); 530 mEnd = mInputText.getSpanEnd(mSpan); 531 } else { 532 throw new RuntimeException("Expected one SuggestionSpan, found " + spans.length); 533 } 534 } 535 public boolean isAutoCorrectionIndicator() { 536 return 0 != (SuggestionSpan.FLAG_AUTO_CORRECTION & mSpan.getFlags()); 537 } 538 } 539 540 static final int DELAY_TO_WAIT_FOR_UNDERLINE = 200; // The message is posted with a 100 ms delay 541 public void testBlueUnderline() { 542 final String STRING_TO_TYPE = "tgis"; 543 final int EXPECTED_SPAN_START = 0; 544 final int EXPECTED_SPAN_END = 4; 545 type(STRING_TO_TYPE); 546 sleep(DELAY_TO_WAIT_FOR_UNDERLINE); 547 runMessages(); 548 final Span span = new Span(mTextView.getText()); 549 assertEquals("show blue underline, span start", EXPECTED_SPAN_START, span.mStart); 550 assertEquals("show blue underline, span end", EXPECTED_SPAN_END, span.mEnd); 551 assertEquals("show blue underline, span color", true, span.isAutoCorrectionIndicator()); 552 } 553 554 public void testBlueUnderlineDisappears() { 555 final String STRING_1_TO_TYPE = "tgis"; 556 final String STRING_2_TO_TYPE = "q"; 557 final int EXPECTED_SPAN_START = 0; 558 final int EXPECTED_SPAN_END = 5; 559 type(STRING_1_TO_TYPE); 560 sleep(DELAY_TO_WAIT_FOR_UNDERLINE); 561 runMessages(); 562 type(STRING_2_TO_TYPE); 563 // We haven't have time to look into the dictionary yet, so the line should still be 564 // blue to avoid any flicker. 565 final Span spanBefore = new Span(mTextView.getText()); 566 assertEquals("extend blue underline, span start", EXPECTED_SPAN_START, spanBefore.mStart); 567 assertEquals("extend blue underline, span end", EXPECTED_SPAN_END, spanBefore.mEnd); 568 assertEquals("extend blue underline, span color", true, 569 spanBefore.isAutoCorrectionIndicator()); 570 sleep(DELAY_TO_WAIT_FOR_UNDERLINE); 571 runMessages(); 572 // Now we have been able to re-evaluate the word, there shouldn't be an auto-correction span 573 final Span spanAfter = new Span(mTextView.getText()); 574 assertNull("hide blue underline", spanAfter.mSpan); 575 } 576 577 public void testBlueUnderlineOnBackspace() { 578 final String STRING_TO_TYPE = "tgis"; 579 final int EXPECTED_SPAN_START = 0; 580 final int EXPECTED_SPAN_END = 4; 581 type(STRING_TO_TYPE); 582 sleep(DELAY_TO_WAIT_FOR_UNDERLINE); 583 runMessages(); 584 type(Keyboard.CODE_SPACE); 585 sleep(DELAY_TO_WAIT_FOR_UNDERLINE); 586 runMessages(); 587 type(Keyboard.CODE_DELETE); 588 sleep(DELAY_TO_WAIT_FOR_UNDERLINE); 589 runMessages(); 590 type(Keyboard.CODE_DELETE); 591 sleep(DELAY_TO_WAIT_FOR_UNDERLINE); 592 runMessages(); 593 final Span span = new Span(mTextView.getText()); 594 assertEquals("show blue underline after backspace, span start", 595 EXPECTED_SPAN_START, span.mStart); 596 assertEquals("show blue underline after backspace, span end", 597 EXPECTED_SPAN_END, span.mEnd); 598 assertEquals("show blue underline after backspace, span color", true, 599 span.isAutoCorrectionIndicator()); 600 } 601 602 public void testBlueUnderlineDisappearsWhenCursorMoved() { 603 final String STRING_TO_TYPE = "tgis"; 604 final int NEW_CURSOR_POSITION = 0; 605 type(STRING_TO_TYPE); 606 sleep(DELAY_TO_WAIT_FOR_UNDERLINE); 607 // Simulate the onUpdateSelection() event 608 mLatinIME.onUpdateSelection(0, 0, STRING_TO_TYPE.length(), STRING_TO_TYPE.length(), -1, -1); 609 runMessages(); 610 // Here the blue underline has been set. testBlueUnderline() is testing for this already, 611 // so let's not test it here again. 612 // Now simulate the user moving the cursor. 613 mInputConnection.setSelection(NEW_CURSOR_POSITION, NEW_CURSOR_POSITION); 614 mLatinIME.onUpdateSelection(0, 0, NEW_CURSOR_POSITION, NEW_CURSOR_POSITION, -1, -1); 615 sleep(DELAY_TO_WAIT_FOR_UNDERLINE); 616 runMessages(); 617 final Span span = new Span(mTextView.getText()); 618 assertNull("blue underline removed when cursor is moved", span.mSpan); 619 } 620 // TODO: Add some tests for non-BMP characters 621} 622