ImeTest.java revision 03b57e008b61dfcb1fbad3aea950ae0e001748b0
1// Copyright 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5package org.chromium.content.browser.input; 6 7import android.app.Activity; 8import android.content.ClipData; 9import android.content.ClipboardManager; 10import android.content.Context; 11import android.test.suitebuilder.annotation.MediumTest; 12import android.test.suitebuilder.annotation.SmallTest; 13import android.text.Editable; 14import android.text.Selection; 15import android.text.TextUtils; 16import android.view.KeyEvent; 17import android.view.View; 18import android.view.inputmethod.EditorInfo; 19 20import org.chromium.base.ThreadUtils; 21import org.chromium.base.test.util.Feature; 22import org.chromium.base.test.util.UrlUtils; 23import org.chromium.content.browser.ContentViewCore; 24import org.chromium.content.browser.test.util.Criteria; 25import org.chromium.content.browser.test.util.CriteriaHelper; 26import org.chromium.content.browser.test.util.DOMUtils; 27import org.chromium.content.browser.test.util.TestCallbackHelperContainer; 28import org.chromium.content.browser.test.util.TestInputMethodManagerWrapper; 29import org.chromium.content_shell_apk.ContentShellTestBase; 30 31import java.util.ArrayList; 32import java.util.concurrent.Callable; 33 34/** 35 * Integration tests for text input using cases based on fixed regressions. 36 */ 37public class ImeTest extends ContentShellTestBase { 38 39 private static final String DATA_URL = UrlUtils.encodeHtmlDataUri( 40 "<html><head><meta name=\"viewport\"" + 41 "content=\"width=device-width, initial-scale=2.0, maximum-scale=2.0\" /></head>" + 42 "<body><form action=\"about:blank\">" + 43 "<input id=\"input_text\" type=\"text\" /><br/>" + 44 "<input id=\"input_radio\" type=\"radio\" style=\"width:50px;height:50px\" />" + 45 "<br/><textarea id=\"textarea\" rows=\"4\" cols=\"20\"></textarea>" + 46 "</form></body></html>"); 47 48 private TestAdapterInputConnection mConnection; 49 private ImeAdapter mImeAdapter; 50 51 private ContentViewCore mContentViewCore; 52 private TestCallbackHelperContainer mCallbackContainer; 53 private TestInputMethodManagerWrapper mInputMethodManagerWrapper; 54 55 @Override 56 public void setUp() throws Exception { 57 super.setUp(); 58 59 launchContentShellWithUrl(DATA_URL); 60 assertTrue("Page failed to load", waitForActiveShellToBeDoneLoading()); 61 mContentViewCore = getContentViewCore(); 62 63 mInputMethodManagerWrapper = new TestInputMethodManagerWrapper(mContentViewCore); 64 getImeAdapter().setInputMethodManagerWrapper(mInputMethodManagerWrapper); 65 assertEquals(0, mInputMethodManagerWrapper.getShowSoftInputCounter()); 66 mContentViewCore.setAdapterInputConnectionFactory( 67 new TestAdapterInputConnectionFactory()); 68 69 mCallbackContainer = new TestCallbackHelperContainer(mContentViewCore); 70 // TODO(aurimas) remove this wait once crbug.com/179511 is fixed. 71 assertWaitForPageScaleFactorMatch(2); 72 assertTrue(DOMUtils.waitForNonZeroNodeBounds( 73 mContentViewCore, "input_text")); 74 DOMUtils.clickNode(this, mContentViewCore, "input_text"); 75 assertWaitForKeyboardStatus(true); 76 77 mConnection = (TestAdapterInputConnection) getAdapterInputConnection(); 78 mImeAdapter = getImeAdapter(); 79 80 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 0, "", 0, 0, -1, -1); 81 assertEquals(1, mInputMethodManagerWrapper.getShowSoftInputCounter()); 82 assertEquals(0, mInputMethodManagerWrapper.getEditorInfo().initialSelStart); 83 assertEquals(0, mInputMethodManagerWrapper.getEditorInfo().initialSelEnd); 84 } 85 86 @MediumTest 87 @Feature({"TextInput", "Main"}) 88 public void testKeyboardDismissedAfterClickingGo() throws Throwable { 89 setComposingText(mConnection, "hello", 1); 90 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "hello", 5, 5, 0, 5); 91 92 performGo(getAdapterInputConnection(), mCallbackContainer); 93 94 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 2, "", 0, 0, -1, -1); 95 assertWaitForKeyboardStatus(false); 96 } 97 98 @SmallTest 99 @Feature({"TextInput", "Main"}) 100 @RerunWithUpdatedContainerView 101 public void testGetTextUpdatesAfterEnteringText() throws Throwable { 102 setComposingText(mConnection, "h", 1); 103 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "h", 1, 1, 0, 1); 104 assertEquals(1, mInputMethodManagerWrapper.getShowSoftInputCounter()); 105 106 setComposingText(mConnection, "he", 1); 107 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 2, "he", 2, 2, 0, 2); 108 assertEquals(1, mInputMethodManagerWrapper.getShowSoftInputCounter()); 109 110 setComposingText(mConnection, "hel", 1); 111 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 3, "hel", 3, 3, 0, 3); 112 assertEquals(1, mInputMethodManagerWrapper.getShowSoftInputCounter()); 113 114 commitText(mConnection, "hel", 1); 115 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 4, "hel", 3, 3, -1, -1); 116 assertEquals(1, mInputMethodManagerWrapper.getShowSoftInputCounter()); 117 } 118 119 @SmallTest 120 @Feature({"TextInput"}) 121 @RerunWithUpdatedContainerView 122 public void testImeCopy() throws Exception { 123 commitText(mConnection, "hello", 1); 124 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "hello", 5, 5, -1, -1); 125 126 setSelection(mConnection, 2, 5); 127 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 2, "hello", 2, 5, -1, -1); 128 129 copy(mImeAdapter); 130 assertClipboardContents(getActivity(), "llo"); 131 } 132 133 @SmallTest 134 @Feature({"TextInput"}) 135 public void testEnterTextAndRefocus() throws Exception { 136 commitText(mConnection, "hello", 1); 137 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "hello", 5, 5, -1, -1); 138 139 DOMUtils.clickNode(this, mContentViewCore, "input_radio"); 140 assertWaitForKeyboardStatus(false); 141 142 DOMUtils.clickNode(this, mContentViewCore, "input_text"); 143 assertWaitForKeyboardStatus(true); 144 assertEquals(5, mInputMethodManagerWrapper.getEditorInfo().initialSelStart); 145 assertEquals(5, mInputMethodManagerWrapper.getEditorInfo().initialSelEnd); 146 } 147 148 @SmallTest 149 @Feature({"TextInput"}) 150 public void testKeyboardNotDismissedAfterCopySelection() throws Exception { 151 commitText(mConnection, "Sample Text", 1); 152 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, 153 "Sample Text", 11, 11, -1, -1); 154 DOMUtils.clickNode(this, mContentViewCore, "input_text"); 155 assertWaitForKeyboardStatus(true); 156 DOMUtils.longPressNode(this, mContentViewCore, "input_text"); 157 selectAll(mImeAdapter); 158 copy(mImeAdapter); 159 assertWaitForKeyboardStatus(true); 160 assertEquals(11, Selection.getSelectionEnd(mContentViewCore.getEditableForTest())); 161 assertEquals(11, Selection.getSelectionEnd(mContentViewCore.getEditableForTest())); 162 } 163 164 @SmallTest 165 @Feature({"TextInput"}) 166 public void testImeNotShownOnLongPressingEmptyInput() throws Exception { 167 DOMUtils.focusNode(mContentViewCore, "input_radio"); 168 DOMUtils.longPressNode(this, mContentViewCore, "input_text"); 169 assertWaitForKeyboardStatus(false); 170 commitText(mConnection, "Sample Text", 1); 171 DOMUtils.longPressNode(this, mContentViewCore, "input_text"); 172 assertWaitForKeyboardStatus(true); 173 } 174 175 @SmallTest 176 @Feature({"TextInput"}) 177 public void testImeCut() throws Exception { 178 commitText(mConnection, "snarful", 1); 179 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "snarful", 7, 7, -1, -1); 180 181 setSelection(mConnection, 1, 5); 182 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 2, "snarful", 1, 5, -1, -1); 183 184 cut(mImeAdapter); 185 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 3, "sul", 1, 1, -1, -1); 186 187 assertClipboardContents(getActivity(), "narf"); 188 } 189 190 @SmallTest 191 @Feature({"TextInput"}) 192 public void testImePaste() throws Exception { 193 ThreadUtils.runOnUiThreadBlocking(new Runnable() { 194 @Override 195 public void run() { 196 ClipboardManager clipboardManager = 197 (ClipboardManager) getActivity().getSystemService( 198 Context.CLIPBOARD_SERVICE); 199 clipboardManager.setPrimaryClip(ClipData.newPlainText("blarg", "blarg")); 200 } 201 }); 202 203 paste(mImeAdapter); 204 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "blarg", 5, 5, -1, -1); 205 206 setSelection(mConnection, 3, 5); 207 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 2, "blarg", 3, 5, -1, -1); 208 209 paste(mImeAdapter); 210 // Paste is a two step process when there is a non-zero selection. 211 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 3, "bla", 3, 3, -1, -1); 212 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 4, "blablarg", 8, 8, -1, -1); 213 214 paste(mImeAdapter); 215 waitAndVerifyEditableCallback( 216 mConnection.mImeUpdateQueue, 5, "blablargblarg", 13, 13, -1, -1); 217 } 218 219 @SmallTest 220 @Feature({"TextInput"}) 221 public void testImeSelectAndUnSelectAll() throws Exception { 222 commitText(mConnection, "hello", 1); 223 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "hello", 5, 5, -1, -1); 224 225 selectAll(mImeAdapter); 226 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 2, "hello", 0, 5, -1, -1); 227 228 unselect(mImeAdapter); 229 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 3, "", 0, 0, -1, -1); 230 231 assertWaitForKeyboardStatus(false); 232 } 233 234 @SmallTest 235 @Feature({"TextInput", "Main"}) 236 public void testShowImeIfNeeded() throws Throwable { 237 // showImeIfNeeded() is now implicitly called by the updated focus 238 // heuristic so no need to call explicitly. http://crbug.com/371927 239 DOMUtils.focusNode(mContentViewCore, "input_radio"); 240 assertWaitForKeyboardStatus(false); 241 242 DOMUtils.focusNode(mContentViewCore, "input_text"); 243 assertWaitForKeyboardStatus(true); 244 } 245 246 @SmallTest 247 @Feature({"TextInput", "Main"}) 248 public void testFinishComposingText() throws Throwable { 249 DOMUtils.focusNode(mContentViewCore, "input_radio"); 250 assertWaitForKeyboardStatus(false); 251 DOMUtils.focusNode(mContentViewCore, "textarea"); 252 assertWaitForKeyboardStatus(true); 253 254 mConnection = (TestAdapterInputConnection) getAdapterInputConnection(); 255 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 0, "", 0, 0, -1, -1); 256 257 commitText(mConnection, "hllo", 1); 258 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "hllo", 4, 4, -1, -1); 259 260 commitText(mConnection, " ", 1); 261 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 2, "hllo ", 5, 5, -1, -1); 262 263 setSelection(mConnection, 1, 1); 264 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 3, "hllo ", 1, 1, -1, -1); 265 266 setComposingRegion(mConnection, 0, 4); 267 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 4, "hllo ", 1, 1, 0, 4); 268 269 finishComposingText(mConnection); 270 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 5, "hllo ", 1, 1, -1, -1); 271 272 commitText(mConnection, "\n", 1); 273 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 6, "h\nllo ", 2, 2, -1, -1); 274 } 275 276 private int getTypedKeycodeGuess(String before, String after) { 277 KeyEvent ev = ImeAdapter.getTypedKeyEventGuess(before, after); 278 if (ev == null) return -1; 279 return ev.getKeyCode(); 280 } 281 282 @SmallTest 283 @Feature({"TextInput", "Main"}) 284 public void testGuessedKeyCodeFromTyping() throws Throwable { 285 assertEquals(-1, getTypedKeycodeGuess(null, "")); 286 assertEquals(KeyEvent.KEYCODE_X, getTypedKeycodeGuess(null, "x")); 287 assertEquals(-1, getTypedKeycodeGuess(null, "xyz")); 288 289 assertEquals(-1, getTypedKeycodeGuess("abc", "abc")); 290 assertEquals(KeyEvent.KEYCODE_DEL, getTypedKeycodeGuess("abc", "")); 291 292 assertEquals(KeyEvent.KEYCODE_H, getTypedKeycodeGuess("", "h")); 293 assertEquals(KeyEvent.KEYCODE_DEL, getTypedKeycodeGuess("h", "")); 294 assertEquals(KeyEvent.KEYCODE_E, getTypedKeycodeGuess("h", "he")); 295 assertEquals(KeyEvent.KEYCODE_L, getTypedKeycodeGuess("he", "hel")); 296 assertEquals(KeyEvent.KEYCODE_O, getTypedKeycodeGuess("hel", "helo")); 297 assertEquals(KeyEvent.KEYCODE_DEL, getTypedKeycodeGuess("helo", "hel")); 298 assertEquals(KeyEvent.KEYCODE_L, getTypedKeycodeGuess("hel", "hell")); 299 assertEquals(KeyEvent.KEYCODE_L, getTypedKeycodeGuess("hell", "helll")); 300 assertEquals(KeyEvent.KEYCODE_DEL, getTypedKeycodeGuess("helll", "hell")); 301 assertEquals(KeyEvent.KEYCODE_O, getTypedKeycodeGuess("hell", "hello")); 302 303 assertEquals(KeyEvent.KEYCODE_X, getTypedKeycodeGuess("xxx", "xxxx")); 304 assertEquals(KeyEvent.KEYCODE_X, getTypedKeycodeGuess("xxx", "xxxxx")); 305 assertEquals(KeyEvent.KEYCODE_DEL, getTypedKeycodeGuess("xxx", "xx")); 306 assertEquals(KeyEvent.KEYCODE_DEL, getTypedKeycodeGuess("xxx", "x")); 307 308 assertEquals(KeyEvent.KEYCODE_Y, getTypedKeycodeGuess("xxx", "xxxy")); 309 assertEquals(KeyEvent.KEYCODE_Y, getTypedKeycodeGuess("xxx", "xxxxy")); 310 assertEquals(-1, getTypedKeycodeGuess("xxx", "xy")); 311 assertEquals(-1, getTypedKeycodeGuess("xxx", "y")); 312 313 assertEquals(-1, getTypedKeycodeGuess("foo", "bar")); 314 assertEquals(-1, getTypedKeycodeGuess("foo", "bars")); 315 assertEquals(-1, getTypedKeycodeGuess("foo", "ba")); 316 317 // Some characters also require modifiers so we have to check the full event. 318 KeyEvent ev = ImeAdapter.getTypedKeyEventGuess(null, "!"); 319 assertEquals(KeyEvent.KEYCODE_1, ev.getKeyCode()); 320 assertTrue(ev.isShiftPressed()); 321 } 322 323 @SmallTest 324 @Feature({"TextInput", "Main"}) 325 public void testKeyCodesWhileComposingText() throws Throwable { 326 DOMUtils.focusNode(mContentViewCore, "textarea"); 327 assertWaitForKeyboardStatus(true); 328 329 // The calls below are a reflection of what the stock Google Keyboard (Android 4.4) sends 330 // when the noted key is touched on screen. Exercise care when altering to make sure 331 // that the test reflects reality. If this test breaks, it's possible that code has 332 // changed and different calls need to be made instead. 333 mConnection = (TestAdapterInputConnection) getAdapterInputConnection(); 334 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 0, "", 0, 0, -1, -1); 335 336 // H 337 expectUpdateStateCall(mConnection); 338 setComposingText(mConnection, "h", 1); 339 assertEquals(KeyEvent.KEYCODE_H, mImeAdapter.mLastSyntheticKeyCode); 340 assertUpdateStateCall(mConnection, 1000); 341 assertEquals("h", mConnection.getTextBeforeCursor(9, 0)); 342 343 // O 344 expectUpdateStateCall(mConnection); 345 setComposingText(mConnection, "ho", 1); 346 assertEquals(KeyEvent.KEYCODE_O, mImeAdapter.mLastSyntheticKeyCode); 347 assertUpdateStateCall(mConnection, 1000); 348 assertEquals("ho", mConnection.getTextBeforeCursor(9, 0)); 349 350 // DEL 351 expectUpdateStateCall(mConnection); 352 setComposingText(mConnection, "h", 1); 353 assertEquals(KeyEvent.KEYCODE_DEL, mImeAdapter.mLastSyntheticKeyCode); 354 assertUpdateStateCall(mConnection, 1000); 355 setComposingRegion(mConnection, 0, 1); // DEL calls cancelComposition() then restarts 356 setComposingText(mConnection, "h", 1); 357 assertEquals("h", mConnection.getTextBeforeCursor(9, 0)); 358 359 // I 360 setComposingText(mConnection, "hi", 1); 361 assertEquals(KeyEvent.KEYCODE_I, mImeAdapter.mLastSyntheticKeyCode); 362 assertEquals("hi", mConnection.getTextBeforeCursor(9, 0)); 363 364 // SPACE 365 commitText(mConnection, "hi", 1); 366 assertEquals(-1, mImeAdapter.mLastSyntheticKeyCode); 367 commitText(mConnection, " ", 1); 368 assertEquals(KeyEvent.KEYCODE_SPACE, mImeAdapter.mLastSyntheticKeyCode); 369 assertEquals("hi ", mConnection.getTextBeforeCursor(9, 0)); 370 371 // DEL 372 deleteSurroundingText(mConnection, 1, 0); 373 assertEquals(KeyEvent.KEYCODE_DEL, mImeAdapter.mLastSyntheticKeyCode); 374 setComposingRegion(mConnection, 0, 2); 375 assertEquals("hi", mConnection.getTextBeforeCursor(9, 0)); 376 377 // DEL 378 setComposingText(mConnection, "h", 1); 379 assertEquals(KeyEvent.KEYCODE_DEL, mImeAdapter.mLastSyntheticKeyCode); 380 assertEquals("h", mConnection.getTextBeforeCursor(9, 0)); 381 382 // DEL 383 commitText(mConnection, "", 1); 384 assertEquals(KeyEvent.KEYCODE_DEL, mImeAdapter.mLastSyntheticKeyCode); 385 assertEquals("", mConnection.getTextBeforeCursor(9, 0)); 386 387 // DEL (on empty input) 388 deleteSurroundingText(mConnection, 1, 0); // DEL on empty still sends 1,0 389 assertEquals(KeyEvent.KEYCODE_DEL, mImeAdapter.mLastSyntheticKeyCode); 390 assertEquals("", mConnection.getTextBeforeCursor(9, 0)); 391 } 392 393 @SmallTest 394 @Feature({"TextInput", "Main"}) 395 public void testKeyCodesWhileTypingText() throws Throwable { 396 DOMUtils.focusNode(mContentViewCore, "textarea"); 397 assertWaitForKeyboardStatus(true); 398 399 // The calls below are a reflection of what the Hacker's Keyboard sends when the noted 400 // key is touched on screen. Exercise care when altering to make sure that the test 401 // reflects reality. 402 mConnection = (TestAdapterInputConnection) getAdapterInputConnection(); 403 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 0, "", 0, 0, -1, -1); 404 405 // H 406 expectUpdateStateCall(mConnection); 407 commitText(mConnection, "h", 1); 408 assertEquals(KeyEvent.KEYCODE_H, mImeAdapter.mLastSyntheticKeyCode); 409 assertEquals("h", mConnection.getTextBeforeCursor(9, 0)); 410 assertUpdateStateCall(mConnection, 1000); 411 assertEquals("h", mConnection.getTextBeforeCursor(9, 0)); 412 413 // O 414 expectUpdateStateCall(mConnection); 415 commitText(mConnection, "o", 1); 416 assertEquals(KeyEvent.KEYCODE_O, mImeAdapter.mLastSyntheticKeyCode); 417 assertEquals("ho", mConnection.getTextBeforeCursor(9, 0)); 418 assertUpdateStateCall(mConnection, 1000); 419 assertEquals("ho", mConnection.getTextBeforeCursor(9, 0)); 420 421 // DEL 422 expectUpdateStateCall(mConnection); 423 deleteSurroundingText(mConnection, 1, 0); 424 assertEquals(KeyEvent.KEYCODE_DEL, mImeAdapter.mLastSyntheticKeyCode); 425 assertEquals("h", mConnection.getTextBeforeCursor(9, 0)); 426 assertUpdateStateCall(mConnection, 1000); 427 assertEquals("h", mConnection.getTextBeforeCursor(9, 0)); 428 429 // I 430 expectUpdateStateCall(mConnection); 431 commitText(mConnection, "i", 1); 432 assertEquals(KeyEvent.KEYCODE_I, mImeAdapter.mLastSyntheticKeyCode); 433 assertEquals("hi", mConnection.getTextBeforeCursor(9, 0)); 434 assertUpdateStateCall(mConnection, 1000); 435 assertEquals("hi", mConnection.getTextBeforeCursor(9, 0)); 436 437 // SPACE 438 expectUpdateStateCall(mConnection); 439 commitText(mConnection, " ", 1); 440 assertEquals(KeyEvent.KEYCODE_SPACE, mImeAdapter.mLastSyntheticKeyCode); 441 assertEquals("hi ", mConnection.getTextBeforeCursor(9, 0)); 442 assertUpdateStateCall(mConnection, 1000); 443 assertEquals("hi ", mConnection.getTextBeforeCursor(9, 0)); 444 445 // DEL 446 expectUpdateStateCall(mConnection); 447 deleteSurroundingText(mConnection, 1, 0); 448 assertEquals(KeyEvent.KEYCODE_DEL, mImeAdapter.mLastSyntheticKeyCode); 449 assertEquals("hi", mConnection.getTextBeforeCursor(9, 0)); 450 assertUpdateStateCall(mConnection, 1000); 451 assertEquals("hi", mConnection.getTextBeforeCursor(9, 0)); 452 453 // DEL 454 expectUpdateStateCall(mConnection); 455 deleteSurroundingText(mConnection, 1, 0); 456 assertEquals(KeyEvent.KEYCODE_DEL, mImeAdapter.mLastSyntheticKeyCode); 457 assertEquals("h", mConnection.getTextBeforeCursor(9, 0)); 458 assertUpdateStateCall(mConnection, 1000); 459 assertEquals("h", mConnection.getTextBeforeCursor(9, 0)); 460 461 // DEL 462 expectUpdateStateCall(mConnection); 463 deleteSurroundingText(mConnection, 1, 0); 464 assertEquals(KeyEvent.KEYCODE_DEL, mImeAdapter.mLastSyntheticKeyCode); 465 assertEquals("", mConnection.getTextBeforeCursor(9, 0)); 466 assertUpdateStateCall(mConnection, 1000); 467 assertEquals("", mConnection.getTextBeforeCursor(9, 0)); 468 469 // DEL (on empty input) 470 deleteSurroundingText(mConnection, 1, 0); // DEL on empty still sends 1,0 471 assertEquals(KeyEvent.KEYCODE_DEL, mImeAdapter.mLastSyntheticKeyCode); 472 assertEquals("", mConnection.getTextBeforeCursor(9, 0)); 473 } 474 475 @SmallTest 476 @Feature({"TextInput", "Main"}) 477 public void testSetComposingRegionOutOfBounds() throws Throwable { 478 DOMUtils.focusNode(mContentViewCore, "textarea"); 479 assertWaitForKeyboardStatus(true); 480 481 mConnection = (TestAdapterInputConnection) getAdapterInputConnection(); 482 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 0, "", 0, 0, -1, -1); 483 setComposingText(mConnection, "hello", 1); 484 485 setComposingRegion(mConnection, 0, 0); 486 setComposingRegion(mConnection, 0, 9); 487 setComposingRegion(mConnection, 9, 0); 488 } 489 490 @SmallTest 491 @Feature({"TextInput", "Main"}) 492 public void testEnterKeyEventWhileComposingText() throws Throwable { 493 DOMUtils.focusNode(mContentViewCore, "input_radio"); 494 assertWaitForKeyboardStatus(false); 495 DOMUtils.focusNode(mContentViewCore, "textarea"); 496 assertWaitForKeyboardStatus(true); 497 498 mConnection = (TestAdapterInputConnection) getAdapterInputConnection(); 499 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 0, "", 0, 0, -1, -1); 500 501 setComposingText(mConnection, "hello", 1); 502 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "hello", 5, 5, 0, 5); 503 504 getInstrumentation().runOnMainSync(new Runnable() { 505 @Override 506 public void run() { 507 mConnection.sendKeyEvent( 508 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER)); 509 mConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER)); 510 } 511 }); 512 513 // TODO(aurimas): remove this workaround when crbug.com/278584 is fixed. 514 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 2, "hello", 5, 5, -1, -1); 515 // The second new line is not a user visible/editable one, it is a side-effect of Blink 516 // using <br> internally. 517 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 3, "hello\n\n", 6, 6, -1, -1); 518 } 519 520 @SmallTest 521 @Feature({"TextInput", "Main"}) 522 public void testTransitionsWhileComposingText() throws Throwable { 523 DOMUtils.focusNode(mContentViewCore, "textarea"); 524 assertWaitForKeyboardStatus(true); 525 526 mConnection = (TestAdapterInputConnection) getAdapterInputConnection(); 527 waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 0, "", 0, 0, -1, -1); 528 529 // H 530 expectUpdateStateCall(mConnection); 531 setComposingText(mConnection, "h", 1); 532 assertEquals(KeyEvent.KEYCODE_H, mImeAdapter.mLastSyntheticKeyCode); 533 534 // Simulate switch of input fields. 535 finishComposingText(mConnection); 536 537 // H 538 expectUpdateStateCall(mConnection); 539 setComposingText(mConnection, "h", 1); 540 assertEquals(KeyEvent.KEYCODE_H, mImeAdapter.mLastSyntheticKeyCode); 541 } 542 543 private void performGo(final AdapterInputConnection inputConnection, 544 TestCallbackHelperContainer testCallbackHelperContainer) throws Throwable { 545 handleBlockingCallbackAction( 546 testCallbackHelperContainer.getOnPageFinishedHelper(), 547 new Runnable() { 548 @Override 549 public void run() { 550 inputConnection.performEditorAction(EditorInfo.IME_ACTION_GO); 551 } 552 }); 553 } 554 555 private void assertWaitForKeyboardStatus(final boolean show) throws InterruptedException { 556 assertTrue(CriteriaHelper.pollForCriteria(new Criteria() { 557 @Override 558 public boolean isSatisfied() { 559 return show == getImeAdapter().mIsShowWithoutHideOutstanding && 560 (!show || getAdapterInputConnection() != null); 561 } 562 })); 563 } 564 565 private void waitAndVerifyEditableCallback(final ArrayList<TestImeState> states, 566 final int index, String text, int selectionStart, int selectionEnd, 567 int compositionStart, int compositionEnd) throws InterruptedException { 568 assertTrue(CriteriaHelper.pollForCriteria(new Criteria() { 569 @Override 570 public boolean isSatisfied() { 571 return states.size() > index; 572 } 573 })); 574 states.get(index).assertEqualState( 575 text, selectionStart, selectionEnd, compositionStart, compositionEnd); 576 } 577 578 private void expectUpdateStateCall(final TestAdapterInputConnection connection) { 579 connection.mImeUpdateQueue.clear(); 580 } 581 582 private void assertUpdateStateCall(final TestAdapterInputConnection connection, int maxms) 583 throws Exception { 584 while (connection.mImeUpdateQueue.size() == 0 && maxms > 0) { 585 try { 586 Thread.sleep(50); 587 } catch (Exception e) { 588 // Not really a problem since we're just going to sleep again. 589 } 590 maxms -= 50; 591 } 592 assertTrue(connection.mImeUpdateQueue.size() > 0); 593 } 594 595 private void assertClipboardContents(final Activity activity, final String expectedContents) 596 throws InterruptedException { 597 assertTrue(CriteriaHelper.pollForCriteria(new Criteria() { 598 @Override 599 public boolean isSatisfied() { 600 return ThreadUtils.runOnUiThreadBlockingNoException(new Callable<Boolean>() { 601 @Override 602 public Boolean call() throws Exception { 603 ClipboardManager clipboardManager = 604 (ClipboardManager) activity.getSystemService( 605 Context.CLIPBOARD_SERVICE); 606 ClipData clip = clipboardManager.getPrimaryClip(); 607 return clip != null && clip.getItemCount() == 1 608 && TextUtils.equals(clip.getItemAt(0).getText(), expectedContents); 609 } 610 }); 611 } 612 })); 613 } 614 615 private ImeAdapter getImeAdapter() { 616 return mContentViewCore.getImeAdapterForTest(); 617 } 618 619 private AdapterInputConnection getAdapterInputConnection() { 620 return mContentViewCore.getInputConnectionForTest(); 621 } 622 623 private void copy(final ImeAdapter adapter) { 624 ThreadUtils.runOnUiThreadBlocking(new Runnable() { 625 @Override 626 public void run() { 627 adapter.copy(); 628 } 629 }); 630 } 631 632 private void cut(final ImeAdapter adapter) { 633 ThreadUtils.runOnUiThreadBlocking(new Runnable() { 634 @Override 635 public void run() { 636 adapter.cut(); 637 } 638 }); 639 } 640 641 private void paste(final ImeAdapter adapter) { 642 ThreadUtils.runOnUiThreadBlocking(new Runnable() { 643 @Override 644 public void run() { 645 adapter.paste(); 646 } 647 }); 648 } 649 650 private void selectAll(final ImeAdapter adapter) { 651 ThreadUtils.runOnUiThreadBlocking(new Runnable() { 652 @Override 653 public void run() { 654 adapter.selectAll(); 655 } 656 }); 657 } 658 659 private void unselect(final ImeAdapter adapter) { 660 ThreadUtils.runOnUiThreadBlocking(new Runnable() { 661 @Override 662 public void run() { 663 adapter.unselect(); 664 } 665 }); 666 } 667 668 private void commitText(final AdapterInputConnection connection, final CharSequence text, 669 final int newCursorPosition) { 670 ThreadUtils.runOnUiThreadBlocking(new Runnable() { 671 @Override 672 public void run() { 673 connection.commitText(text, newCursorPosition); 674 } 675 }); 676 } 677 678 private void setSelection(final AdapterInputConnection connection, final int start, 679 final int end) { 680 ThreadUtils.runOnUiThreadBlocking(new Runnable() { 681 @Override 682 public void run() { 683 connection.setSelection(start, end); 684 } 685 }); 686 } 687 688 private void setComposingRegion(final AdapterInputConnection connection, final int start, 689 final int end) { 690 ThreadUtils.runOnUiThreadBlocking(new Runnable() { 691 @Override 692 public void run() { 693 connection.setComposingRegion(start, end); 694 } 695 }); 696 } 697 698 private void setComposingText(final AdapterInputConnection connection, final CharSequence text, 699 final int newCursorPosition) { 700 ThreadUtils.runOnUiThreadBlocking(new Runnable() { 701 @Override 702 public void run() { 703 connection.setComposingText(text, newCursorPosition); 704 } 705 }); 706 } 707 708 private void finishComposingText(final AdapterInputConnection connection) { 709 ThreadUtils.runOnUiThreadBlocking(new Runnable() { 710 @Override 711 public void run() { 712 connection.finishComposingText(); 713 } 714 }); 715 } 716 717 private void deleteSurroundingText(final AdapterInputConnection connection, final int before, 718 final int after) { 719 ThreadUtils.runOnUiThreadBlocking(new Runnable() { 720 @Override 721 public void run() { 722 connection.deleteSurroundingText(before, after); 723 } 724 }); 725 } 726 727 private static class TestAdapterInputConnectionFactory extends 728 ImeAdapter.AdapterInputConnectionFactory { 729 @Override 730 public AdapterInputConnection get(View view, ImeAdapter imeAdapter, 731 Editable editable, EditorInfo outAttrs) { 732 return new TestAdapterInputConnection(view, imeAdapter, editable, outAttrs); 733 } 734 } 735 736 private static class TestAdapterInputConnection extends AdapterInputConnection { 737 private final ArrayList<TestImeState> mImeUpdateQueue = new ArrayList<TestImeState>(); 738 739 public TestAdapterInputConnection(View view, ImeAdapter imeAdapter, 740 Editable editable, EditorInfo outAttrs) { 741 super(view, imeAdapter, editable, outAttrs); 742 } 743 744 @Override 745 public void updateState(String text, int selectionStart, int selectionEnd, 746 int compositionStart, int compositionEnd, boolean requiredAck) { 747 mImeUpdateQueue.add(new TestImeState(text, selectionStart, selectionEnd, 748 compositionStart, compositionEnd)); 749 super.updateState(text, selectionStart, selectionEnd, compositionStart, 750 compositionEnd, requiredAck); 751 } 752 } 753 754 private static class TestImeState { 755 private final String mText; 756 private final int mSelectionStart; 757 private final int mSelectionEnd; 758 private final int mCompositionStart; 759 private final int mCompositionEnd; 760 761 public TestImeState(String text, int selectionStart, int selectionEnd, 762 int compositionStart, int compositionEnd) { 763 mText = text; 764 mSelectionStart = selectionStart; 765 mSelectionEnd = selectionEnd; 766 mCompositionStart = compositionStart; 767 mCompositionEnd = compositionEnd; 768 } 769 770 public void assertEqualState(String text, int selectionStart, int selectionEnd, 771 int compositionStart, int compositionEnd) { 772 assertEquals("Text did not match", text, mText); 773 assertEquals("Selection start did not match", selectionStart, mSelectionStart); 774 assertEquals("Selection end did not match", selectionEnd, mSelectionEnd); 775 assertEquals("Composition start did not match", compositionStart, mCompositionStart); 776 assertEquals("Composition end did not match", compositionEnd, mCompositionEnd); 777 } 778 } 779} 780