1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.text.method.cts; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertFalse; 21 import static org.junit.Assert.assertTrue; 22 23 import android.os.SystemClock; 24 import android.text.Editable; 25 import android.text.InputType; 26 import android.text.Layout; 27 import android.text.Selection; 28 import android.text.Spannable; 29 import android.text.SpannableStringBuilder; 30 import android.text.StaticLayout; 31 import android.text.method.BaseKeyListener; 32 import android.view.KeyCharacterMap; 33 import android.view.KeyEvent; 34 import android.widget.TextView; 35 import android.widget.TextView.BufferType; 36 37 import androidx.test.ext.junit.runners.AndroidJUnit4; 38 import androidx.test.filters.MediumTest; 39 40 import org.junit.Test; 41 import org.junit.runner.RunWith; 42 43 /** 44 * Test {@link android.text.method.BaseKeyListener}. 45 */ 46 @MediumTest 47 @RunWith(AndroidJUnit4.class) 48 public class BaseKeyListenerTest extends KeyListenerTestCase { 49 private static final CharSequence TEST_STRING = "123456"; 50 51 @Test testBackspace()52 public void testBackspace() throws Throwable { 53 verifyBackspace(0); 54 } 55 verifyBackspace(int modifiers)56 private void verifyBackspace(int modifiers) throws Throwable { 57 final BaseKeyListener mockBaseKeyListener = new MockBaseKeyListener(); 58 final KeyEvent event = getKey(KeyEvent.KEYCODE_DEL, modifiers); 59 Editable content = Editable.Factory.getInstance().newEditable(TEST_STRING); 60 61 // Nothing to delete when the cursor is at the beginning. 62 prepTextViewSync(content, mockBaseKeyListener, false, 0, 0); 63 mockBaseKeyListener.backspace(mTextView, content, event.getKeyCode(), event); 64 assertEquals("123456", content.toString()); 65 66 // Delete the first three letters using a selection. 67 prepTextViewSync(content, mockBaseKeyListener, false, 0, 3); 68 mockBaseKeyListener.backspace(mTextView, content, event.getKeyCode(), event); 69 assertEquals("456", content.toString()); 70 71 // Delete the character prior to the cursor when there's no selection 72 prepTextViewSync(content, mockBaseKeyListener, false, 2, 2); 73 mockBaseKeyListener.backspace(mTextView, content, event.getKeyCode(), event); 74 assertEquals("46", content.toString()); 75 mockBaseKeyListener.backspace(mTextView, content, event.getKeyCode(), event); 76 assertEquals("6", content.toString()); 77 78 // The deletion works on a Logical direction basis in RTL text.. 79 String testText = "\u05E9\u05DC\u05D5\u05DD\u002E"; 80 content = Editable.Factory.getInstance().newEditable(testText); 81 82 prepTextViewSync(content, mockBaseKeyListener, false, 0, 0); 83 mockBaseKeyListener.backspace(mTextView, content, event.getKeyCode(), event); 84 assertEquals(testText, content.toString()); 85 86 int end = testText.length(); 87 prepTextViewSync(content, mockBaseKeyListener, false, end, end); 88 mockBaseKeyListener.backspace(mTextView, content, event.getKeyCode(), event); 89 assertEquals("\u05E9\u05DC\u05D5\u05DD", content.toString()); 90 91 int middle = testText.length() / 2; 92 prepTextViewSync(content, mockBaseKeyListener, false, middle, middle); 93 mockBaseKeyListener.backspace(mTextView, content, event.getKeyCode(), event); 94 assertEquals("\u05E9\u05D5\u05DD", content.toString()); 95 96 // And in BiDi text 97 testText = "\u05D6\u05D4\u0020Android\u0020\u05E2\u05D5\u05D1\u05D3"; 98 content = Editable.Factory.getInstance().newEditable(testText); 99 100 prepTextViewSync(content, mockBaseKeyListener, false, 0, 0); 101 mockBaseKeyListener.backspace(mTextView, content, event.getKeyCode(), event); 102 assertEquals(content.toString(), content.toString()); 103 104 end = testText.length(); 105 prepTextViewSync(content, mockBaseKeyListener, false, end, end); 106 mockBaseKeyListener.backspace(mTextView, content, event.getKeyCode(), event); 107 assertEquals("\u05D6\u05D4\u0020Android\u0020\u05E2\u05D5\u05D1", content.toString()); 108 109 prepTextViewSync(content, mockBaseKeyListener, false, 6, 6); 110 mockBaseKeyListener.backspace(mTextView, content, event.getKeyCode(), event); 111 assertEquals("\u05D6\u05D4\u0020Anroid\u0020\u05E2\u05D5\u05D1", content.toString()); 112 } 113 114 @Test testBackspace_withShift()115 public void testBackspace_withShift() throws Throwable { 116 verifyBackspace(KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_LEFT_ON); 117 } 118 119 private static final String LONG_TEXT_FOR_ALT_BACKSPACE = "Hello, world. This is Android. Alt" 120 + " Backspace should work as removing text from head of the text until cursor position." 121 + " Alt ForwardDelete should work as removing text from cursor position until end of" 122 + " the text."; 123 124 @Test testBackspace_withAlt()125 public void testBackspace_withAlt() throws Throwable { 126 final BaseKeyListener mockBaseKeyListener = new MockBaseKeyListener(); 127 128 129 Editable content = Editable.Factory.getInstance().newEditable(LONG_TEXT_FOR_ALT_BACKSPACE); 130 131 int middleIndex = LONG_TEXT_FOR_ALT_BACKSPACE.length() / 2; 132 prepTextViewWithLinesSync(content, mockBaseKeyListener, false, middleIndex, middleIndex, 3); 133 134 Layout layout = mTextView.getLayout(); 135 int lineIndex = layout.getLineForOffset(middleIndex); 136 int lineStartOffset = layout.getLineStart(lineIndex); 137 138 executeAltBackspace(content, mockBaseKeyListener); 139 140 String expectedText = LONG_TEXT_FOR_ALT_BACKSPACE.substring(0, lineStartOffset) 141 + LONG_TEXT_FOR_ALT_BACKSPACE.substring(middleIndex); 142 143 assertEquals(expectedText, content.toString()); 144 } 145 146 @Test testForwardDelete_withAlt()147 public void testForwardDelete_withAlt() throws Throwable { 148 final BaseKeyListener mockBaseKeyListener = new MockBaseKeyListener(); 149 150 Editable content = Editable.Factory.getInstance().newEditable(LONG_TEXT_FOR_ALT_BACKSPACE); 151 152 int middleIndex = LONG_TEXT_FOR_ALT_BACKSPACE.length() / 2; 153 prepTextViewWithLinesSync(content, mockBaseKeyListener, false, middleIndex, middleIndex, 3); 154 155 Layout layout = mTextView.getLayout(); 156 int lineIndex = layout.getLineForOffset(middleIndex); 157 int lineEndOffset = layout.getLineEnd(lineIndex); 158 159 executeAltForwardDelete(content, mockBaseKeyListener); 160 161 String expectedText = LONG_TEXT_FOR_ALT_BACKSPACE.substring(0, middleIndex) 162 + LONG_TEXT_FOR_ALT_BACKSPACE.substring(lineEndOffset); 163 164 assertEquals(expectedText, content.toString()); 165 } 166 167 @Test testBackspace_withSendKeys()168 public void testBackspace_withSendKeys() throws Throwable { 169 final BaseKeyListener mockBaseKeyListener = new MockBaseKeyListener(); 170 171 // Delete the first character '1' 172 prepTextViewSync(TEST_STRING, mockBaseKeyListener, true, 1, 1); 173 sendKeys(mTextView, KeyEvent.KEYCODE_DEL); 174 assertEquals("23456", mTextView.getText().toString()); 175 176 // Delete character '2' and '3' 177 prepTextViewSync(TEST_STRING, mockBaseKeyListener, true, 1, 3); 178 sendKeys(mTextView, KeyEvent.KEYCODE_DEL); 179 assertEquals("1456", mTextView.getText().toString()); 180 181 // Alt+DEL deletes everything preceding from the cursor. 182 prepTextViewSync(TEST_STRING, mockBaseKeyListener, true, 3, 3); 183 sendKeyWhileHoldingModifier(mTextView, KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_ALT_LEFT); 184 assertEquals("456", mTextView.getText().toString()); 185 186 // ALT+DEL deletes the selection only. 187 prepTextViewSync(TEST_STRING, mockBaseKeyListener, true, 2, 4); 188 sendKeyWhileHoldingModifier(mTextView, KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_ALT_LEFT); 189 assertEquals("1256", mTextView.getText().toString()); 190 191 // DEL key does not take effect when TextView does not have BaseKeyListener. 192 prepTextViewSync(TEST_STRING, null, true, 1, 1); 193 sendKeys(mTextView, KeyEvent.KEYCODE_DEL); 194 assertEquals(TEST_STRING, mTextView.getText().toString()); 195 } 196 verifyCursorPosition(Editable content, int offset)197 private void verifyCursorPosition(Editable content, int offset) { 198 assertEquals(offset, Selection.getSelectionStart(content)); 199 assertEquals(offset, Selection.getSelectionEnd(content)); 200 } 201 202 @Test testBackspace_withCtrl()203 public void testBackspace_withCtrl() throws Throwable { 204 final BaseKeyListener mockBaseKeyListener = new MockBaseKeyListener(); 205 206 // If the contents only having symbolic characters, delete all characters. 207 String testText = "!#$%&'()`{*}_?+"; 208 Editable content = Editable.Factory.getInstance().newEditable(testText); 209 prepTextViewSync(content, mockBaseKeyListener, false, testText.length(), testText.length()); 210 executeCtrlBackspace(content, mockBaseKeyListener); 211 assertEquals("", content.toString()); 212 verifyCursorPosition(content, 0); 213 214 // Latin ASCII text 215 testText = "Hello, World. This is Android."; 216 content = Editable.Factory.getInstance().newEditable(testText); 217 218 // If the cursor is head of the text, should do nothing. 219 prepTextViewSync(content, mockBaseKeyListener, false, 0, 0); 220 executeCtrlBackspace(content, mockBaseKeyListener); 221 assertEquals("Hello, World. This is Android.", content.toString()); 222 verifyCursorPosition(content, 0); 223 224 prepTextViewSync(content, mockBaseKeyListener, false, testText.length(), testText.length()); 225 executeCtrlBackspace(content, mockBaseKeyListener); 226 assertEquals("Hello, World. This is ", content.toString()); 227 verifyCursorPosition(content, content.toString().length()); 228 229 executeCtrlBackspace(content, mockBaseKeyListener); 230 assertEquals("Hello, World. This ", content.toString()); 231 verifyCursorPosition(content, content.toString().length()); 232 233 executeCtrlBackspace(content, mockBaseKeyListener); 234 assertEquals("Hello, World. ", content.toString()); 235 verifyCursorPosition(content, content.toString().length()); 236 237 executeCtrlBackspace(content, mockBaseKeyListener); 238 assertEquals("Hello, ", content.toString()); 239 verifyCursorPosition(content, content.toString().length()); 240 241 executeCtrlBackspace(content, mockBaseKeyListener); 242 assertEquals("", content.toString()); 243 verifyCursorPosition(content, 0); 244 245 executeCtrlBackspace(content, mockBaseKeyListener); 246 assertEquals("", content.toString()); 247 verifyCursorPosition(content, 0); 248 249 // Latin ASCII, cursor is middle of the text. 250 testText = "Hello, World. This is Android."; 251 content = Editable.Factory.getInstance().newEditable(testText); 252 int charsFromTail = 12; // Cursor location is 12 chars from the tail.(before "is"). 253 prepTextViewSync(content, mockBaseKeyListener, false, 254 testText.length() - charsFromTail, testText.length() - charsFromTail); 255 256 executeCtrlBackspace(content, mockBaseKeyListener); 257 assertEquals("Hello, World. is Android.", content.toString()); 258 verifyCursorPosition(content, content.toString().length() - charsFromTail); 259 260 executeCtrlBackspace(content, mockBaseKeyListener); 261 assertEquals("Hello, is Android.", content.toString()); 262 verifyCursorPosition(content, content.toString().length() - charsFromTail); 263 264 executeCtrlBackspace(content, mockBaseKeyListener); 265 assertEquals(" is Android.", content.toString()); 266 verifyCursorPosition(content, 0); 267 268 executeCtrlBackspace(content, mockBaseKeyListener); 269 assertEquals(" is Android.", content.toString()); 270 verifyCursorPosition(content, 0); 271 272 // Latin ASCII, cursor is inside word. 273 testText = "Hello, World. This is Android."; 274 content = Editable.Factory.getInstance().newEditable(testText); 275 charsFromTail = 14; // Cursor location is 12 chars from the tail. (inside "This") 276 prepTextViewSync(content, mockBaseKeyListener, false, 277 testText.length() - charsFromTail, testText.length() - charsFromTail); 278 279 280 executeCtrlBackspace(content, mockBaseKeyListener); 281 assertEquals("Hello, World. is is Android.", content.toString()); 282 verifyCursorPosition(content, content.toString().length() - charsFromTail); 283 284 executeCtrlBackspace(content, mockBaseKeyListener); 285 assertEquals("Hello, is is Android.", content.toString()); 286 verifyCursorPosition(content, content.toString().length() - charsFromTail); 287 288 executeCtrlBackspace(content, mockBaseKeyListener); 289 assertEquals("is is Android.", content.toString()); 290 verifyCursorPosition(content, 0); 291 292 executeCtrlBackspace(content, mockBaseKeyListener); 293 assertEquals("is is Android.", content.toString()); 294 verifyCursorPosition(content, 0); 295 296 // Hebrew Text 297 // The deletion works on a Logical direction basis. 298 testText = "\u05E9\u05DC\u05D5\u05DD\u0020\u05D4\u05E2\u05D5\u05DC\u05DD\u002E\u0020" + 299 "\u05D6\u05D4\u0020\u05D0\u05E0\u05D3\u05E8\u05D5\u05D0\u05D9\u05D3\u002E"; 300 content = Editable.Factory.getInstance().newEditable(testText); 301 prepTextViewSync(content, mockBaseKeyListener, false, testText.length(), testText.length()); 302 303 executeCtrlBackspace(content, mockBaseKeyListener); 304 assertEquals("\u05E9\u05DC\u05D5\u05DD\u0020\u05D4\u05E2\u05D5\u05DC\u05DD\u002E\u0020" + 305 "\u05D6\u05D4\u0020", content.toString()); 306 verifyCursorPosition(content, content.toString().length()); 307 308 executeCtrlBackspace(content, mockBaseKeyListener); 309 assertEquals("\u05E9\u05DC\u05D5\u05DD\u0020\u05D4\u05E2\u05D5\u05DC\u05DD\u002E\u0020", 310 content.toString()); 311 verifyCursorPosition(content, content.toString().length()); 312 313 executeCtrlBackspace(content, mockBaseKeyListener); 314 assertEquals("\u05E9\u05DC\u05D5\u05DD\u0020", content.toString()); 315 verifyCursorPosition(content, content.toString().length()); 316 317 executeCtrlBackspace(content, mockBaseKeyListener); 318 assertEquals("", content.toString()); 319 verifyCursorPosition(content, 0); 320 321 executeCtrlBackspace(content, mockBaseKeyListener); 322 assertEquals("", content.toString()); 323 verifyCursorPosition(content, 0); 324 325 // BiDi Text 326 // The deletion works on a Logical direction basis. 327 testText = "\u05D6\u05D4\u0020\u05DC\u002D\u0020\u0041Android\u0020\u05E2\u05D5\u05D1" + 328 "\u05D3\u0020\u05D4\u05D9\u05D8\u05D1\u002E"; 329 content = Editable.Factory.getInstance().newEditable(testText); 330 prepTextViewSync(content, mockBaseKeyListener, false, testText.length(), testText.length()); 331 332 executeCtrlBackspace(content, mockBaseKeyListener); 333 assertEquals("\u05D6\u05D4\u0020\u05DC\u002D\u0020\u0041Android\u0020\u05E2\u05D5\u05D1" + 334 "\u05D3\u0020", content.toString()); 335 verifyCursorPosition(content, content.toString().length()); 336 337 executeCtrlBackspace(content, mockBaseKeyListener); 338 assertEquals("\u05D6\u05D4\u0020\u05DC\u002D\u0020\u0041Android\u0020", content.toString()); 339 verifyCursorPosition(content, content.toString().length()); 340 341 executeCtrlBackspace(content, mockBaseKeyListener); 342 assertEquals("\u05D6\u05D4\u0020\u05DC\u002D\u0020", content.toString()); 343 verifyCursorPosition(content, content.toString().length()); 344 345 executeCtrlBackspace(content, mockBaseKeyListener); 346 assertEquals("\u05D6\u05D4\u0020", content.toString()); 347 verifyCursorPosition(content, content.toString().length()); 348 349 executeCtrlBackspace(content, mockBaseKeyListener); 350 assertEquals("", content.toString()); 351 verifyCursorPosition(content, 0); 352 353 executeCtrlBackspace(content, mockBaseKeyListener); 354 assertEquals("", content.toString()); 355 verifyCursorPosition(content, 0); 356 } 357 358 @Test testForwardDelete_withCtrl()359 public void testForwardDelete_withCtrl() throws Throwable { 360 final BaseKeyListener mockBaseKeyListener = new MockBaseKeyListener(); 361 362 // If the contents only having symbolic characters, delete all characters. 363 String testText = "!#$%&'()`{*}_?+"; 364 Editable content = Editable.Factory.getInstance().newEditable(testText); 365 prepTextViewSync(content, mockBaseKeyListener, false, 0, 0); 366 executeCtrlForwardDelete(content, mockBaseKeyListener); 367 assertEquals("", content.toString()); 368 verifyCursorPosition(content, 0); 369 370 // Latin ASCII text 371 testText = "Hello, World. This is Android."; 372 content = Editable.Factory.getInstance().newEditable(testText); 373 374 // If the cursor is tail of the text, should do nothing. 375 prepTextViewSync(content, mockBaseKeyListener, false, testText.length(), testText.length()); 376 executeCtrlForwardDelete(content, mockBaseKeyListener); 377 assertEquals("Hello, World. This is Android.", content.toString()); 378 verifyCursorPosition(content, testText.length()); 379 380 prepTextViewSync(content, mockBaseKeyListener, false, 0, 0); 381 executeCtrlForwardDelete(content, mockBaseKeyListener); 382 assertEquals(", World. This is Android.", content.toString()); 383 verifyCursorPosition(content, 0); 384 385 executeCtrlForwardDelete(content, mockBaseKeyListener); 386 assertEquals(". This is Android.", content.toString()); 387 verifyCursorPosition(content, 0); 388 389 executeCtrlForwardDelete(content, mockBaseKeyListener); 390 assertEquals(" is Android.", content.toString()); 391 verifyCursorPosition(content, 0); 392 393 executeCtrlForwardDelete(content, mockBaseKeyListener); 394 assertEquals(" Android.", content.toString()); 395 verifyCursorPosition(content, 0); 396 397 executeCtrlForwardDelete(content, mockBaseKeyListener); 398 assertEquals(".", content.toString()); 399 verifyCursorPosition(content, 0); 400 401 executeCtrlForwardDelete(content, mockBaseKeyListener); 402 assertEquals("", content.toString()); 403 verifyCursorPosition(content, 0); 404 405 executeCtrlForwardDelete(content, mockBaseKeyListener); 406 assertEquals("", content.toString()); 407 verifyCursorPosition(content, 0); 408 409 // Latin ASCII, cursor is middle of the text. 410 testText = "Hello, World. This is Android."; 411 content = Editable.Factory.getInstance().newEditable(testText); 412 int charsFromHead = 14; // Cursor location is 14 chars from the head.(before "This"). 413 prepTextViewSync(content, mockBaseKeyListener, false, charsFromHead, charsFromHead); 414 415 executeCtrlForwardDelete(content, mockBaseKeyListener); 416 assertEquals("Hello, World. is Android.", content.toString()); 417 verifyCursorPosition(content, charsFromHead); 418 419 executeCtrlForwardDelete(content, mockBaseKeyListener); 420 assertEquals("Hello, World. Android.", content.toString()); 421 verifyCursorPosition(content, charsFromHead); 422 423 executeCtrlForwardDelete(content, mockBaseKeyListener); 424 assertEquals("Hello, World. .", content.toString()); 425 verifyCursorPosition(content, charsFromHead); 426 427 executeCtrlForwardDelete(content, mockBaseKeyListener); 428 assertEquals("Hello, World. ", content.toString()); 429 verifyCursorPosition(content, charsFromHead); 430 431 executeCtrlForwardDelete(content, mockBaseKeyListener); 432 assertEquals("Hello, World. ", content.toString()); 433 verifyCursorPosition(content, charsFromHead); 434 435 // Latin ASCII, cursor is inside word. 436 testText = "Hello, World. This is Android."; 437 content = Editable.Factory.getInstance().newEditable(testText); 438 charsFromHead = 16; // Cursor location is 16 chars from the head. (inside "This") 439 prepTextViewSync(content, mockBaseKeyListener, false, charsFromHead, charsFromHead); 440 441 executeCtrlForwardDelete(content, mockBaseKeyListener); 442 assertEquals("Hello, World. Th is Android.", content.toString()); 443 verifyCursorPosition(content, charsFromHead); 444 445 executeCtrlForwardDelete(content, mockBaseKeyListener); 446 assertEquals("Hello, World. Th Android.", content.toString()); 447 verifyCursorPosition(content, charsFromHead); 448 449 executeCtrlForwardDelete(content, mockBaseKeyListener); 450 assertEquals("Hello, World. Th.", content.toString()); 451 verifyCursorPosition(content, charsFromHead); 452 453 executeCtrlForwardDelete(content, mockBaseKeyListener); 454 assertEquals("Hello, World. Th", content.toString()); 455 verifyCursorPosition(content, charsFromHead); 456 457 executeCtrlForwardDelete(content, mockBaseKeyListener); 458 assertEquals("Hello, World. Th", content.toString()); 459 verifyCursorPosition(content, charsFromHead); 460 461 // Hebrew Text 462 // The deletion works on a Logical direction basis. 463 testText = "\u05E9\u05DC\u05D5\u05DD\u0020\u05D4\u05E2\u05D5\u05DC\u05DD\u002E\u0020" + 464 "\u05D6\u05D4\u0020\u05D0\u05E0\u05D3\u05E8\u05D5\u05D0\u05D9\u05D3\u002E"; 465 content = Editable.Factory.getInstance().newEditable(testText); 466 prepTextViewSync(content, mockBaseKeyListener, false, 0, 0); 467 468 executeCtrlForwardDelete(content, mockBaseKeyListener); 469 assertEquals("\u0020\u05D4\u05E2\u05D5\u05DC\u05DD\u002E\u0020\u05D6\u05D4\u0020\u05D0" + 470 "\u05E0\u05D3\u05E8\u05D5\u05D0\u05D9\u05D3\u002E", content.toString()); 471 verifyCursorPosition(content, 0); 472 473 executeCtrlForwardDelete(content, mockBaseKeyListener); 474 assertEquals("\u002E\u0020\u05D6\u05D4\u0020\u05D0\u05E0\u05D3\u05E8\u05D5\u05D0\u05D9" + 475 "\u05D3\u002E", content.toString()); 476 verifyCursorPosition(content, 0); 477 478 executeCtrlForwardDelete(content, mockBaseKeyListener); 479 assertEquals("\u0020\u05D0\u05E0\u05D3\u05E8\u05D5\u05D0\u05D9\u05D3\u002E", 480 content.toString()); 481 verifyCursorPosition(content, 0); 482 483 executeCtrlForwardDelete(content, mockBaseKeyListener); 484 assertEquals("\u002E", content.toString()); 485 verifyCursorPosition(content, 0); 486 487 executeCtrlForwardDelete(content, mockBaseKeyListener); 488 assertEquals("", content.toString()); 489 verifyCursorPosition(content, 0); 490 491 executeCtrlForwardDelete(content, mockBaseKeyListener); 492 assertEquals("", content.toString()); 493 verifyCursorPosition(content, 0); 494 495 // BiDi Text 496 // The deletion works on a Logical direction basis. 497 testText = "\u05D6\u05D4\u0020\u05DC\u002D\u0020\u0041Android\u0020\u05E2\u05D5\u05D1" + 498 "\u05D3\u0020\u05D4\u05D9\u05D8\u05D1\u002E"; 499 content = Editable.Factory.getInstance().newEditable(testText); 500 prepTextViewSync(content, mockBaseKeyListener, false, 0, 0); 501 502 executeCtrlForwardDelete(content, mockBaseKeyListener); 503 assertEquals("\u0020\u05DC\u002D\u0020\u0041Android\u0020\u05E2\u05D5\u05D1\u05D3\u0020" + 504 "\u05D4\u05D9\u05D8\u05D1\u002E", content.toString()); 505 verifyCursorPosition(content, 0); 506 507 executeCtrlForwardDelete(content, mockBaseKeyListener); 508 assertEquals("\u002D\u0020\u0041Android\u0020\u05E2\u05D5\u05D1\u05D3\u0020\u05D4\u05D9" + 509 "\u05D8\u05D1\u002E", content.toString()); 510 verifyCursorPosition(content, 0); 511 512 executeCtrlForwardDelete(content, mockBaseKeyListener); 513 assertEquals("\u0020\u05E2\u05D5\u05D1\u05D3\u0020\u05D4\u05D9\u05D8\u05D1\u002E", 514 content.toString()); 515 verifyCursorPosition(content, 0); 516 517 executeCtrlForwardDelete(content, mockBaseKeyListener); 518 assertEquals("\u0020\u05D4\u05D9\u05D8\u05D1\u002E", content.toString()); 519 verifyCursorPosition(content, 0); 520 521 executeCtrlForwardDelete(content, mockBaseKeyListener); 522 assertEquals("\u002E", content.toString()); 523 verifyCursorPosition(content, 0); 524 525 executeCtrlForwardDelete(content, mockBaseKeyListener); 526 assertEquals("", content.toString()); 527 verifyCursorPosition(content, 0); 528 529 executeCtrlForwardDelete(content, mockBaseKeyListener); 530 assertEquals("", content.toString()); 531 verifyCursorPosition(content, 0); 532 } 533 534 /* 535 * Check point: 536 * 1. Press 0 key, the content of TextView does not changed. 537 * 2. Set a selection and press DEL key, the selection is deleted. 538 * 3. ACTION_MULTIPLE KEYCODE_UNKNOWN by inserting the event's text into the content. 539 */ 540 @Test testPressKey()541 public void testPressKey() throws Throwable { 542 final BaseKeyListener mockBaseKeyListener = new MockBaseKeyListener(); 543 544 // press '0' key. 545 prepTextViewSync(TEST_STRING, mockBaseKeyListener, true, 0, 0); 546 sendKeys(mTextView, KeyEvent.KEYCODE_0); 547 assertEquals("123456", mTextView.getText().toString()); 548 549 // delete character '2' 550 prepTextViewSync(mTextView.getText(), mockBaseKeyListener, true, 1, 2); 551 sendKeys(mTextView, KeyEvent.KEYCODE_DEL); 552 assertEquals("13456", mTextView.getText().toString()); 553 554 // test ACTION_MULTIPLE KEYCODE_UNKNOWN key event. 555 KeyEvent event = new KeyEvent(SystemClock.uptimeMillis(), "abcd", 556 KeyCharacterMap.BUILT_IN_KEYBOARD, 0); 557 prepTextViewSync(mTextView.getText(), mockBaseKeyListener, true, 2, 2); 558 sendKey(mTextView, event); 559 mInstrumentation.waitForIdleSync(); 560 // the text of TextView is never changed, onKeyOther never works. 561 // assertEquals("13abcd456", mTextView.getText().toString()); 562 } 563 564 @Test testOnKeyOther()565 public void testOnKeyOther() { 566 final BaseKeyListener mockBaseKeyListener = new MockBaseKeyListener(); 567 final String string = "abc"; 568 final SpannableStringBuilder content = new SpannableStringBuilder(string); 569 570 KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_UNKNOWN); 571 assertFalse(mockBaseKeyListener.onKeyOther(mTextView, content, event)); 572 assertEquals(string, content.toString()); 573 574 event = new KeyEvent(KeyEvent.ACTION_MULTIPLE, KeyEvent.KEYCODE_0); 575 assertFalse(mockBaseKeyListener.onKeyOther(mTextView, content, event)); 576 assertEquals(string, content.toString()); 577 578 Selection.setSelection(content, 1, 0); 579 event = new KeyEvent(KeyEvent.ACTION_MULTIPLE, KeyEvent.KEYCODE_UNKNOWN); 580 assertFalse(mockBaseKeyListener.onKeyOther(mTextView, content, event)); 581 assertEquals(string, content.toString()); 582 583 event = new KeyEvent(SystemClock.uptimeMillis(), "b", 0, 0); 584 assertTrue(mockBaseKeyListener.onKeyOther(mTextView, content, event)); 585 assertEquals("bbc", content.toString()); 586 } 587 executeAltBackspace(Editable content, BaseKeyListener listener)588 private void executeAltBackspace(Editable content, BaseKeyListener listener) { 589 final KeyEvent delKeyEvent = getKey(KeyEvent.KEYCODE_DEL, 590 KeyEvent.META_ALT_ON | KeyEvent.META_ALT_LEFT_ON); 591 listener.backspace(mTextView, content, KeyEvent.KEYCODE_DEL, delKeyEvent); 592 } 593 executeAltForwardDelete(Editable content, BaseKeyListener listener)594 private void executeAltForwardDelete(Editable content, BaseKeyListener listener) { 595 final KeyEvent delKeyEvent = getKey(KeyEvent.KEYCODE_FORWARD_DEL, 596 KeyEvent.META_ALT_ON | KeyEvent.META_ALT_LEFT_ON); 597 listener.forwardDelete(mTextView, content, KeyEvent.KEYCODE_FORWARD_DEL, delKeyEvent); 598 } 599 executeCtrlBackspace(Editable content, BaseKeyListener listener)600 private void executeCtrlBackspace(Editable content, BaseKeyListener listener) { 601 final KeyEvent delKeyEvent = getKey(KeyEvent.KEYCODE_DEL, 602 KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_LEFT_ON); 603 listener.backspace(mTextView, content, KeyEvent.KEYCODE_DEL, delKeyEvent); 604 } 605 executeCtrlForwardDelete(Editable content, BaseKeyListener listener)606 private void executeCtrlForwardDelete(Editable content, BaseKeyListener listener) { 607 final KeyEvent delKeyEvent = getKey(KeyEvent.KEYCODE_FORWARD_DEL, 608 KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_LEFT_ON); 609 listener.forwardDelete(mTextView, content, KeyEvent.KEYCODE_FORWARD_DEL, delKeyEvent); 610 } 611 612 /** 613 * Prepares mTextView state for tests by synchronously setting the content and key listener, on 614 * the UI thread. 615 */ prepTextViewSync(final CharSequence content, final BaseKeyListener keyListener, final boolean selectInTextView, final int selectionStart, final int selectionEnd)616 private void prepTextViewSync(final CharSequence content, final BaseKeyListener keyListener, 617 final boolean selectInTextView, final int selectionStart, final int selectionEnd) 618 throws Throwable { 619 mActivityRule.runOnUiThread(() -> { 620 mTextView.setText(content, BufferType.EDITABLE); 621 mTextView.setKeyListener(keyListener); 622 Selection.setSelection( 623 selectInTextView ? mTextView.getText() : (Spannable) content, 624 selectionStart, selectionEnd); 625 }); 626 mInstrumentation.waitForIdleSync(); 627 assertTrue(mTextView.hasWindowFocus()); 628 } 629 getLineCount(CharSequence content, TextView tv)630 private static int getLineCount(CharSequence content, TextView tv) { 631 return StaticLayout.Builder.obtain(content, 0, content.length(), tv.getPaint(), 632 tv.getWidth()) 633 .setBreakStrategy(tv.getBreakStrategy()) 634 .setHyphenationFrequency(tv.getHyphenationFrequency()) 635 .build().getLineCount(); 636 } 637 prepTextViewWithLinesSync(final CharSequence content, final BaseKeyListener keyListener, final boolean selectInTextView, final int selectionStart, final int selectionEnd, int lineCount)638 private void prepTextViewWithLinesSync(final CharSequence content, 639 final BaseKeyListener keyListener, final boolean selectInTextView, 640 final int selectionStart, final int selectionEnd, int lineCount) 641 throws Throwable { 642 mActivityRule.runOnUiThread(() -> { 643 644 if (getLineCount(content, mTextView) < lineCount) { 645 for (float textSize = mTextView.getTextSize(); textSize < 1024f; textSize += 5f) { 646 mTextView.setTextSize(textSize); 647 if (getLineCount(content, mTextView) >= lineCount) { 648 break; 649 } 650 } 651 } 652 653 mTextView.setText(content, BufferType.EDITABLE); 654 mTextView.setKeyListener(keyListener); 655 Selection.setSelection( 656 selectInTextView ? mTextView.getText() : (Spannable) content, 657 selectionStart, selectionEnd); 658 }); 659 mInstrumentation.waitForIdleSync(); 660 assertTrue(mTextView.hasWindowFocus()); 661 } 662 663 /** 664 * A mocked {@link android.text.method.BaseKeyListener} for testing purposes. 665 */ 666 private class MockBaseKeyListener extends BaseKeyListener { getInputType()667 public int getInputType() { 668 return InputType.TYPE_CLASS_DATETIME 669 | InputType.TYPE_DATETIME_VARIATION_DATE; 670 } 671 } 672 } 673