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