1/*
2 * Copyright (C) 2010 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
17package android.webkit;
18
19import android.accessibilityservice.AccessibilityService;
20import android.accessibilityservice.AccessibilityServiceInfo;
21import android.content.ComponentName;
22import android.content.ContentResolver;
23import android.content.Intent;
24import android.os.Handler;
25import android.os.Looper;
26import android.os.SystemClock;
27import android.provider.Settings;
28import android.test.ActivityInstrumentationTestCase2;
29import android.test.suitebuilder.annotation.LargeTest;
30import android.view.KeyEvent;
31import android.view.accessibility.AccessibilityEvent;
32import android.view.accessibility.AccessibilityManager;
33
34/**
35 * This is a test for the behavior of the {@link AccessibilityInjector}
36 * which is used by {@link WebView} to provide basic accessibility support
37 * in case JavaScript is disabled.
38 * </p>
39 * Note: This test works against the generated {@link AccessibilityEvent}s
40 *       to so it also checks if the test for announcing navigation axis and
41 *       status messages as appropriate.
42 */
43public class AccessibilityInjectorTest
44    extends ActivityInstrumentationTestCase2<AccessibilityInjectorTestActivity> {
45
46    /** The timeout to wait for the expected selection. */
47    private static final long TIMEOUT_WAIT_FOR_SELECTION_STRING = 1000;
48
49    /** The timeout to wait for accessibility and the mock service to be enabled. */
50    private static final long TIMEOUT_ENABLE_ACCESSIBILITY_AND_MOCK_SERVICE = 1000;
51
52    /** The count of tests to detect when to shut down the service. */
53    private static final int TEST_CASE_COUNT = 19;
54
55    /** The meta state for pressed left ALT. */
56    private static final int META_STATE_ALT_LEFT_ON = KeyEvent.META_ALT_ON
57            | KeyEvent.META_ALT_LEFT_ON;
58
59    /** Prefix for the CSS style span appended by WebKit. */
60    private static final String APPLE_SPAN_PREFIX = "<span class=\"Apple-style-span\"";
61
62    /** Suffix for the CSS style span appended by WebKit. */
63    private static final String APPLE_SPAN_SUFFIX = "</span>";
64
65    /** The value for not specified selection string since null is a valid value. */
66    private static final String SELECTION_STRING_UNKNOWN = "Unknown";
67
68    /** Lock for locking the test. */
69    private static final Object sTestLock = new Object();
70
71    /** Key bindings used for testing. */
72    private static final String TEST_KEY_DINDINGS =
73        "0x13=0x01000100;" +
74        "0x14=0x01010100;" +
75        "0x15=0x04000000;" +
76        "0x16=0x04000000;" +
77        "0x200000013=0x03020701:0x03010201:0x03000101:0x03030001:0x03040001:0x03050001:0x03060001;" +
78        "0x200000014=0x03010001:0x03020101:0x03070201:0x03030701:0x03040701:0x03050701:0x03060701;" +
79        "0x200000015=0x03040301:0x03050401:0x03060501:0x03000601:0x03010601:0x03020601:0x03070601;" +
80        "0x200000016=0x03050601:0x03040501:0x03030401:0x03020301:0x03070301:0x03010301:0x03000301;";
81
82    /** Handle to the test for use by the mock service. */
83    private static AccessibilityInjectorTest sInstance;
84
85    /** Flag indicating if the accessibility service is ready to receive events. */
86    private static boolean sIsAccessibilityServiceReady;
87
88    /** The count of executed tests to detect when to toggle accessibility and the service. */
89    private static int sExecutedTestCount;
90
91    /** Worker thread with a handler to perform non test thread processing. */
92    private Worker mWorker;
93
94    /** Handle to the {@link WebView} to load data in. */
95    private WebView mWebView;
96
97    /** Used for caching the default bindings so they can be restored. */
98    private static String sDefaultKeyBindings;
99
100    /** The received selection string for assertion checking. */
101    private static String sReceivedSelectionString = SELECTION_STRING_UNKNOWN;
102
103    public AccessibilityInjectorTest() {
104        super(AccessibilityInjectorTestActivity.class);
105    }
106
107    @Override
108    protected void setUp() throws Exception {
109        super.setUp();
110        mWorker = new Worker();
111        sInstance = this;
112        if (sExecutedTestCount == 0) {
113            // until JUnit4 comes to play with @BeforeTest
114            disableAccessibilityAndMockAccessibilityService();
115            enableAccessibilityAndMockAccessibilityService();
116            injectTestWebContentKeyBindings();
117        }
118    }
119
120    @Override
121    protected void tearDown() throws Exception {
122        if (mWorker != null) {
123            mWorker.stop();
124        }
125        if (sExecutedTestCount == TEST_CASE_COUNT) {
126            // until JUnit4 comes to play with @AfterTest
127            disableAccessibilityAndMockAccessibilityService();
128            restoreDefaultWebContentKeyBindings();
129        }
130        super.tearDown();
131    }
132
133    /**
134     * Tests navigation by character.
135     */
136    @LargeTest
137    public void testNavigationByCharacter() throws Exception {
138        // a bit ugly but helps detect beginning and end of all tests so accessibility
139        // and the mock service are not toggled on every test (expensive)
140        sExecutedTestCount++;
141
142        String html =
143            "<html>" +
144               "<head>" +
145               "</head>" +
146               "<body>" +
147                   "<p>" +
148                      "a<b>b</b>c" +
149                   "</p>" +
150                   "<p>" +
151                     "d" +
152                   "<p/>" +
153                   "e" +
154               "</body>" +
155             "</html>";
156
157        WebView webView = loadHTML(html);
158
159        // change navigation axis to word
160        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, META_STATE_ALT_LEFT_ON);
161        assertSelectionString("1"); // expect the word navigation axis
162
163        // change navigation axis to character
164        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, META_STATE_ALT_LEFT_ON);
165        assertSelectionString("0"); // expect the character navigation axis
166
167        // go to the first character
168        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
169        assertSelectionString("a");
170
171        // go to the second character
172        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
173        assertSelectionString("<b>b</b>");
174
175        // go to the third character
176        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
177        assertSelectionString("c");
178
179        // go to the fourth character
180        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
181        assertSelectionString("d");
182
183        // go to the fifth character
184        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
185        assertSelectionString("e");
186
187        // try to go past the last character
188        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
189        assertSelectionString(null);
190
191        // go to the fifth character (reverse)
192        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
193        assertSelectionString("e");
194
195        // go to the fourth character (reverse)
196        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
197        assertSelectionString("d");
198
199        // go to the third character
200        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
201        assertSelectionString("c");
202
203        // go to the second character
204        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
205        assertSelectionString("<b>b</b>");
206
207        // go to the first character
208        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
209        assertSelectionString("a");
210
211        // try to go before the first character
212        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
213        assertSelectionString(null);
214
215        // go to the first character
216        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
217        assertSelectionString("a");
218
219        // go to the second character (reverse again)
220        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
221        assertSelectionString("<b>b</b>");
222    }
223
224    /**
225     * Tests navigation by word.
226     */
227    @LargeTest
228    public void testNavigationByWord() throws Exception {
229        // a bit ugly but helps detect beginning and end of all tests so accessibility
230        // and the mock service are not toggled on every test (expensive)
231        sExecutedTestCount++;
232
233        String html =
234            "<html>" +
235               "<head>" +
236               "</head>" +
237               "<body>" +
238                   "<p>" +
239                      "This is <b>a</b> sentence" +
240                   "</p>" +
241                   "<p>" +
242                     " scattered " +
243                     "<p/>" +
244                     " all over " +
245                   "</p>" +
246                   "<div>" +
247                     "<p>the place.</p>" +
248                   "</div>" +
249               "</body>" +
250             "</html>";
251
252        WebView webView = loadHTML(html);
253
254        // change navigation axis to word
255        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, META_STATE_ALT_LEFT_ON);
256        assertSelectionString("1"); // expect the word navigation axis
257
258        // go to the first word
259        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
260        assertSelectionString("This");
261
262        // go to the second word
263        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
264        assertSelectionString("is");
265
266        // go to the third word
267        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
268        assertSelectionString("<b>a</b>");
269
270        // go to the fourth word
271        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
272        assertSelectionString("sentence");
273
274        // go to the fifth word
275        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
276        assertSelectionString("scattered");
277
278        // go to the sixth word
279        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
280        assertSelectionString("all");
281
282        // go to the seventh word
283        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
284        assertSelectionString("over");
285
286        // go to the eight word
287        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
288        assertSelectionString("the");
289
290        // go to the ninth word
291        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
292        assertSelectionString("place");
293
294        // NOTE: WebKit selection returns the dot as a word
295        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
296        assertSelectionString(".");
297
298        // try to go past the last word
299        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
300        assertSelectionString(null);
301
302        // go to the last word (reverse)
303        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
304        assertSelectionString("place.");
305
306        // go to the eight word
307        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
308        assertSelectionString("the");
309
310        // go to the seventh word
311        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
312        assertSelectionString("over");
313
314        // go to the sixth word
315        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
316        assertSelectionString("all");
317
318        // go to the fifth word
319        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
320        assertSelectionString("scattered");
321
322        // go to the fourth word
323        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
324        assertSelectionString("sentence");
325
326        // go to the third word
327        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
328        assertSelectionString("<b>a</b>");
329
330        // go to the second word
331        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
332        assertSelectionString("is");
333
334        // go to the first word
335        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
336        assertSelectionString("This");
337
338        // try to go before the first word
339        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
340        assertSelectionString(null);
341
342        // go to the first word
343        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
344        assertSelectionString("This");
345
346        // go to the second word (reverse again)
347        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
348        assertSelectionString("is");
349    }
350
351    /**
352     * Tests navigation by sentence.
353     */
354    @LargeTest
355    public void testNavigationBySentence() throws Exception {
356        // a bit ugly but helps detect beginning and end of all tests so accessibility
357        // and the mock service are not toggled on every test (expensive)
358        sExecutedTestCount++;
359
360        String html =
361            "<html>" +
362              "<head>" +
363              "</head>" +
364              "<body>" +
365                "<div>" +
366                  "<p>" +
367                    "This is the first sentence of the first paragraph and has an <b>inline bold tag</b>." +
368                    "This is the second sentence of the first paragraph." +
369                  "</p>" +
370                  "<h1>This is a heading</h1>" +
371                  "<p>" +
372                    "This is the first sentence of the second paragraph." +
373                    "This is the second sentence of the second paragraph." +
374                  "</p>" +
375                "</div>" +
376              "</body>" +
377            "</html>";
378
379        WebView webView = loadHTML(html);
380
381        // Sentence axis is the default
382
383        // go to the first sentence
384        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
385        assertSelectionString("This is the first sentence of the first paragraph and has an "
386                + "<b>inline bold tag</b>.");
387
388        // go to the second sentence
389        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
390        assertSelectionString("This is the second sentence of the first paragraph.");
391
392        // go to the third sentence
393        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
394        assertSelectionString("This is a heading");
395
396        // go to the fourth sentence
397        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
398        assertSelectionString("This is the first sentence of the second paragraph.");
399
400        // go to the fifth sentence
401        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
402        assertSelectionString("This is the second sentence of the second paragraph.");
403
404        // try to go past the last sentence
405        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
406        assertSelectionString(null);
407
408        // go to the fifth sentence
409        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
410        assertSelectionString("This is the second sentence of the second paragraph.");
411
412        // go to the fourth sentence (reverse)
413        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
414        assertSelectionString("This is the first sentence of the second paragraph.");
415
416        // go to the third sentence
417        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
418        assertSelectionString("This is a heading");
419
420        // go to the second sentence
421        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
422        assertSelectionString("This is the second sentence of the first paragraph.");
423
424        // go to the first sentence
425        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
426        assertSelectionString("This is the first sentence of the first paragraph and has an "
427                + "<b>inline bold tag</b>.");
428
429        // try to go before the first sentence
430        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
431        assertSelectionString(null);
432
433        // go to the first sentence
434        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
435        assertSelectionString("This is the first sentence of the first paragraph and has an "
436                + "<b>inline bold tag</b>.");
437
438        // go to the second sentence (reverse again)
439        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
440        assertSelectionString("This is the second sentence of the first paragraph.");
441    }
442
443    /**
444     * Tests navigation by heading.
445     */
446    @LargeTest
447    public void testNavigationByHeading() throws Exception {
448        // a bit ugly but helps detect beginning and end of all tests so accessibility
449        // and the mock service are not toggled on every test (expensive)
450        sExecutedTestCount++;
451
452        String html =
453            "<!DOCTYPE html>" +
454            "<html>" +
455              "<head>" +
456              "</head>" +
457              "<body>" +
458                "<h1>Heading one</h1>" +
459                "<p>" +
460                  "This is some text" +
461                "</p>" +
462                "<h2>Heading two</h2>" +
463                "<p>" +
464                  "This is some text" +
465                "</p>" +
466                "<h3>Heading three</h3>" +
467                "<p>" +
468                  "This is some text" +
469                "</p>" +
470                "<h4>Heading four</h4>" +
471                "<p>" +
472                  "This is some text" +
473                "</p>" +
474                "<h5>Heading five</h5>" +
475                "<p>" +
476                  "This is some text" +
477                "</p>" +
478                "<h6>Heading six</h6>" +
479              "</body>" +
480            "</html>";
481
482        WebView webView = loadHTML(html);
483
484        // change navigation axis to heading
485        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_RIGHT, META_STATE_ALT_LEFT_ON);
486        assertSelectionString("3"); // expect the heading navigation axis
487
488        // go to the first heading
489        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
490        assertSelectionString("<h1>Heading one</h1>");
491
492        // go to the second heading
493        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
494        assertSelectionString("<h2>Heading two</h2>");
495
496        // go to the third heading
497        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
498        assertSelectionString("<h3>Heading three</h3>");
499
500        // go to the fourth heading
501        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
502        assertSelectionString("<h4>Heading four</h4>");
503
504        // go to the fifth heading
505        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
506        assertSelectionString("<h5>Heading five</h5>");
507
508        // go to the sixth heading
509        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
510        assertSelectionString("<h6>Heading six</h6>");
511
512        // try to go past the last heading
513        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
514        assertSelectionString(null);
515
516        // go to the fifth heading (reverse)
517        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
518        assertSelectionString("<h5>Heading five</h5>");
519
520        // go to the fourth heading
521        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
522        assertSelectionString("<h4>Heading four</h4>");
523
524        // go to the third heading
525        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
526        assertSelectionString("<h3>Heading three</h3>");
527
528        // go to the second heading
529        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
530        assertSelectionString("<h2>Heading two</h2>");
531
532        // go to the first heading
533        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
534        assertSelectionString("<h1>Heading one</h1>");
535
536        // try to go before the first heading
537        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
538        assertSelectionString(null);
539
540        // go to the second heading (reverse again)
541        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
542        assertSelectionString("<h2>Heading two</h2>");
543    }
544
545    /**
546     * Tests navigation by sibling.
547     */
548    @LargeTest
549    public void testNavigationBySibing() throws Exception {
550        // a bit ugly but helps detect beginning and end of all tests so accessibility
551        // and the mock service are not toggled on every test (expensive)
552        sExecutedTestCount++;
553
554        String html =
555            "<!DOCTYPE html>" +
556            "<html>" +
557              "<head>" +
558              "</head>" +
559              "<body>" +
560                "<h1>Heading one</h1>" +
561                "<p>" +
562                  "This is some text" +
563                "</p>" +
564                "<div>" +
565                  "<button>Input</button>" +
566                "</div>" +
567              "</body>" +
568            "</html>";
569
570        WebView webView = loadHTML(html);
571
572        // change navigation axis to heading
573        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_RIGHT, META_STATE_ALT_LEFT_ON);
574        assertSelectionString("3"); // expect the heading navigation axis
575
576        // change navigation axis to sibling
577        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_RIGHT, META_STATE_ALT_LEFT_ON);
578        assertSelectionString("4"); // expect the sibling navigation axis
579
580        // change navigation axis to parent/first child
581        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_RIGHT, META_STATE_ALT_LEFT_ON);
582        assertSelectionString("5"); // expect the parent/first child navigation axis
583
584        // go to the first child of the body
585        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
586        assertSelectionString("<h1>Heading one</h1>");
587
588        // change navigation axis to sibling
589        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_LEFT, META_STATE_ALT_LEFT_ON);
590        assertSelectionString("4"); // expect the sibling navigation axis
591
592        // go to the next sibling
593        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
594        assertSelectionString("<p>This is some text</p>");
595
596        // go to the next sibling
597        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
598        assertSelectionString("<div><button>Input</button></div>");
599
600        // try to go past the last sibling
601        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
602        assertSelectionString(null);
603
604        // go to the previous sibling (reverse)
605        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
606        assertSelectionString("<p>This is some text</p>");
607
608        // go to the previous sibling
609        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
610        assertSelectionString("<h1>Heading one</h1>");
611
612        // try to go before the previous sibling
613        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
614        assertSelectionString(null);
615
616        // go to the next sibling (reverse again)
617        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
618        assertSelectionString("<p>This is some text</p>");
619    }
620
621    /**
622     * Tests navigation by parent/first child.
623     */
624    @LargeTest
625    public void testNavigationByParentFirstChild() throws Exception {
626        // a bit ugly but helps detect beginning and end of all tests so accessibility
627        // and the mock service are not toggled on every test (expensive)
628        sExecutedTestCount++;
629
630        String html =
631            "<!DOCTYPE html>" +
632            "<html>" +
633              "<head>" +
634              "</head>" +
635              "<body>" +
636                "<div>" +
637                  "<button>Input</button>" +
638                "</div>" +
639              "</body>" +
640            "</html>";
641
642        WebView webView = loadHTML(html);
643
644        // change navigation axis to document
645        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_LEFT, META_STATE_ALT_LEFT_ON);
646        assertSelectionString("6"); // expect the document navigation axis
647
648        // change navigation axis to parent/first child
649        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_LEFT, META_STATE_ALT_LEFT_ON);
650        assertSelectionString("5"); // expect the parent/first child navigation axis
651
652        // go to the first child
653        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
654        assertSelectionString("<div><button>Input</button></div>");
655
656        // go to the first child
657        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
658        assertSelectionString("<button>Input</button>");
659
660        // try to go to the first child of a leaf element
661        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
662        assertSelectionString(null);
663
664        // go to the parent (reverse)
665        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
666        assertSelectionString("<div><button>Input</button></div>");
667
668        // go to the parent
669        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
670        assertSelectionString("<body><div><button>Input</button></div></body>");
671
672        // try to go to the body parent
673        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
674        assertSelectionString(null);
675
676        // go to the first child (reverse again)
677        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
678        assertSelectionString("<div><button>Input</button></div>");
679    }
680
681    /**
682     * Tests navigation by document.
683     */
684    @LargeTest
685    public void testNavigationByDocument() throws Exception {
686        // a bit ugly but helps detect beginning and end of all tests so accessibility
687        // and the mock service are not toggled on every test (expensive)
688        sExecutedTestCount++;
689
690        String html =
691            "<!DOCTYPE html>" +
692            "<html>" +
693              "<head>" +
694              "</head>" +
695              "<body>" +
696                "<button>Click</button>" +
697              "</body>" +
698            "</html>";
699
700        WebView webView = loadHTML(html);
701
702        // change navigation axis to document
703        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_LEFT, META_STATE_ALT_LEFT_ON);
704        assertSelectionString("6"); // expect the document navigation axis
705
706        // go to the bottom of the document
707        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
708        assertSelectionString("Click");
709
710        // go to the top of the document (reverse)
711        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
712        assertSelectionString("<body><button>Click</button></body>");
713
714        // go to the bottom of the document (reverse again)
715        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
716        assertSelectionString("Click");
717    }
718
719    /**
720     * Tests the sync between the text navigation and navigation by DOM elements.
721     */
722    @LargeTest
723    public void testSyncBetweenTextAndDomNodeNavigation() throws Exception {
724        // a bit ugly but helps detect beginning and end of all tests so accessibility
725        // and the mock service are not toggled on every test (expensive)
726        sExecutedTestCount++;
727
728        String html =
729            "<!DOCTYPE html>" +
730            "<html>" +
731              "<head>" +
732              "</head>" +
733              "<body>" +
734                "<p>" +
735                  "First" +
736                "</p>" +
737                "<button>Second</button>" +
738                "<p>" +
739                  "Third" +
740                "</p>" +
741              "</body>" +
742            "</html>";
743
744        WebView webView = loadHTML(html);
745
746        // change navigation axis to word
747        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, META_STATE_ALT_LEFT_ON);
748        assertSelectionString("1"); // expect the word navigation axis
749
750        // go to the first word
751        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
752        assertSelectionString("First");
753
754        // change navigation axis to heading
755        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_RIGHT, META_STATE_ALT_LEFT_ON);
756        assertSelectionString("3"); // expect the heading navigation axis
757
758        // change navigation axis to sibling
759        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_RIGHT, META_STATE_ALT_LEFT_ON);
760        assertSelectionString("4"); // expect the sibling navigation axis
761
762        // go to the next sibling
763        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
764        assertSelectionString("<button>Second</button>");
765
766        // change navigation axis to character
767        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, META_STATE_ALT_LEFT_ON);
768        assertSelectionString("0"); // expect the character navigation axis
769
770        // change navigation axis to word
771        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, META_STATE_ALT_LEFT_ON);
772        assertSelectionString("1"); // expect the word navigation axis
773
774        // go to the next word
775        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
776        assertSelectionString("Third");
777    }
778
779    /**
780     * Tests that the selection does not cross anchor boundaries. This is a
781     * workaround for the asymmetric and inconsistent handling of text with
782     * links by WebKit while traversing by sentence.
783     */
784    @LargeTest
785    public void testEnforceSelectionDoesNotCrossAnchorBoundary1() throws Exception {
786        // a bit ugly but helps detect beginning and end of all tests so accessibility
787        // and the mock service are not toggled on every test (expensive)
788        sExecutedTestCount++;
789
790        String html =
791            "<!DOCTYPE html>" +
792            "<html>" +
793              "<head>" +
794              "</head>" +
795              "<body>" +
796                "<div>First</div>" +
797                "<p>" +
798                  "<a href=\"\">Second</a> Third" +
799                "</p>" +
800              "</body>" +
801            "</html>";
802
803        WebView webView = loadHTML(html);
804
805        // go to the first sentence
806        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
807        assertSelectionString("<div>First</div>");
808
809        // go to the second sentence
810        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
811        assertSelectionString("<a href=\"\">Second</a>");
812
813        // go to the third sentence
814        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
815        assertSelectionString("Third");
816
817        // go to past the last sentence
818        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
819        assertSelectionString(null);
820
821        // go to the third sentence
822        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
823        assertSelectionString("Third");
824
825        // go to the second sentence
826        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
827        assertSelectionString("<a href=\"\">Second</a>");
828
829        // go to the first sentence
830        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
831        assertSelectionString("First");
832
833        // go to before the first sentence
834        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
835        assertSelectionString(null);
836
837        // go to the first sentence
838        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
839        assertSelectionString("<div>First</div>");
840    }
841
842    /**
843     * Tests that the selection does not cross anchor boundaries. This is a
844     * workaround for the asymmetric and inconsistent handling of text with
845     * links by WebKit while traversing by sentence.
846     */
847    @LargeTest
848    public void testEnforceSelectionDoesNotCrossAnchorBoundary2() throws Exception {
849        // a bit ugly but helps detect beginning and end of all tests so accessibility
850        // and the mock service are not toggled on every test (expensive)
851        sExecutedTestCount++;
852
853        String html =
854            "<!DOCTYPE html>" +
855            "<html>" +
856              "<head>" +
857              "</head>" +
858              "<body>" +
859                "<div>First</div>" +
860                "<a href=\"#\">Second</a>" +
861                "&nbsp;" +
862                "<a href=\"#\">Third</a>" +
863              "</body>" +
864            "</html>";
865
866        WebView webView = loadHTML(html);
867
868        // go to the first sentence
869        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
870        assertSelectionString("First");
871
872        // go to the second sentence
873        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
874        assertSelectionString("<a href=\"#\">Second</a>");
875
876        // go to the third sentence
877        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
878        assertSelectionString("&nbsp;");
879
880        // go to the fourth sentence
881        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
882        assertSelectionString("<a href=\"#\">Third</a>");
883
884        // go to past the last sentence
885        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
886        assertSelectionString(null);
887
888        // go to the fourth sentence
889        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
890        assertSelectionString("<a href=\"#\">Third</a>");
891
892        // go to the third sentence
893        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
894        assertSelectionString("&nbsp;");
895
896        // go to the second sentence
897        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
898        assertSelectionString("<a href=\"#\">Second</a>");
899
900        // go to the first sentence
901        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
902        assertSelectionString("First");
903
904        // go to before the first sentence
905        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
906        assertSelectionString(null);
907
908        // go to the first sentence
909        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
910        assertSelectionString("First");
911    }
912
913    /**
914     * Tests that the selection does not cross anchor boundaries. This is a
915     * workaround for the asymmetric and inconsistent handling of text with
916     * links by WebKit while traversing by sentence.
917     */
918    @LargeTest
919    public void testEnforceSelectionDoesNotCrossAnchorBoundary3() throws Exception {
920        // a bit ugly but helps detect beginning and end of all tests so accessibility
921        // and the mock service are not toggled on every test (expensive)
922        sExecutedTestCount++;
923
924        String html =
925            "<!DOCTYPE html>" +
926            "<html>" +
927              "<head>" +
928              "</head>" +
929              "<body>" +
930                "<div>" +
931                  "First" +
932                "<div>" +
933                "<div>" +
934                  "<a href=\"#\">Second</a>" +
935                "</div>" +
936                "<div>" +
937                  "Third" +
938                "</div>" +
939              "</body>" +
940            "</html>";
941
942        WebView webView = loadHTML(html);
943
944        // go to the first sentence
945        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
946        assertSelectionString("First");
947
948        // go to the second sentence
949        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
950        assertSelectionString("<a href=\"#\">Second</a>");
951
952        // go to the third sentence
953        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
954        assertSelectionString("Third");
955
956        // go to past the last sentence
957        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
958        assertSelectionString(null);
959
960        // go to the third sentence
961        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
962        assertSelectionString("Third");
963
964        // go to the second sentence
965        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
966        assertSelectionString("<a href=\"#\">Second</a>");
967
968        // go to the first sentence
969        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
970        assertSelectionString("First");
971
972        // go to before the first sentence
973        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
974        assertSelectionString(null);
975
976        // go to the first sentence
977        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
978        assertSelectionString("First");
979    }
980
981    /**
982     * Tests skipping of content with hidden visibility.
983     */
984    @LargeTest
985    public void testSkipVisibilityHidden() throws Exception {
986        // a bit ugly but helps detect beginning and end of all tests so accessibility
987        // and the mock service are not toggled on every test (expensive)
988        sExecutedTestCount++;
989
990        String html =
991            "<!DOCTYPE html>" +
992            "<html>" +
993              "<head>" +
994              "</head>" +
995              "<body>" +
996                "<div>First </div>" +
997                "<div style=\"visibility:hidden;\">Second</div>" +
998                "<div> Third</div>" +
999              "</body>" +
1000            "</html>";
1001
1002        WebView webView = loadHTML(html);
1003
1004        // change navigation axis to word
1005        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, META_STATE_ALT_LEFT_ON);
1006        assertSelectionString("1"); // expect the word navigation axis
1007
1008        // go to the first word
1009        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1010        assertSelectionString("First");
1011
1012        // go to the third word (the second is invisible)
1013        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1014        assertSelectionString("Third");
1015
1016        // go to past the last sentence
1017        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1018        assertSelectionString(null);
1019
1020        // go to the third word (the second is invisible)
1021        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1022        assertSelectionString("Third");
1023
1024        // go to the first word
1025        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1026        assertSelectionString("First");
1027
1028        // go to before the first word
1029        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1030        assertSelectionString(null);
1031
1032        // go to the first word
1033        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1034        assertSelectionString("First");
1035    }
1036
1037    /**
1038     * Tests skipping of content with display none.
1039     */
1040    @LargeTest
1041    public void testSkipDisplayNone() throws Exception {
1042        // a bit ugly but helps detect beginning and end of all tests so accessibility
1043        // and the mock service are not toggled on every test (expensive)
1044        sExecutedTestCount++;
1045
1046        String html =
1047            "<!DOCTYPE html>" +
1048            "<html>" +
1049              "<head>" +
1050              "</head>" +
1051              "<body>" +
1052                "<div>First</div>" +
1053                "<div style=\"display: none;\">Second</div>" +
1054                "<div>Third</div>" +
1055              "</body>" +
1056            "</html>";
1057
1058        WebView webView = loadHTML(html);
1059
1060        // change navigation axis to word
1061        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, META_STATE_ALT_LEFT_ON);
1062        assertSelectionString("1"); // expect the word navigation axis
1063
1064        // go to the first word
1065        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1066        assertSelectionString("First");
1067
1068        // go to the third word (the second is invisible)
1069        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1070        assertSelectionString("Third");
1071
1072        // go to past the last sentence
1073        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1074        assertSelectionString(null);
1075
1076        // go to the third word (the second is invisible)
1077        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1078        assertSelectionString("Third");
1079
1080        // go to the first word
1081        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1082        assertSelectionString("First");
1083
1084        // go to before the first word
1085        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1086        assertSelectionString(null);
1087
1088        // go to the first word
1089        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1090        assertSelectionString("First");
1091    }
1092
1093    /**
1094     * Tests for the selection not getting stuck.
1095     *
1096     * Note: The selection always proceeds but if it can
1097     * be selecting the same content i.e. between the start
1098     * and end are contained the same text nodes.
1099     */
1100    @LargeTest
1101    public void testSelectionTextProceed() throws Exception {
1102        // a bit ugly but helps detect beginning and end of all tests so accessibility
1103        // and the mock service are not toggled on every test (expensive)
1104        sExecutedTestCount++;
1105
1106        String html =
1107            "<!DOCTYPE html>" +
1108            "<html>" +
1109              "<head>" +
1110              "</head>" +
1111              "<body>" +
1112                "<a href=\"#\">First</a>" +
1113                "<span><a href=\"#\"><span>Second</span>&nbsp;<small>a</small></a>" +
1114                "</span>&nbsp;<a href=\"#\">Third</a>" +
1115              "</body>" +
1116            "</html>";
1117
1118        WebView webView = loadHTML(html);
1119
1120        // go to the first sentence
1121        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1122        assertSelectionString("<a href=\"#\">First</a>");
1123
1124        // go to the second sentence
1125        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1126        assertSelectionString("<a href=\"#\"><span>Second&nbsp;<small>a</small></a>");
1127
1128        // go to the third sentence
1129        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1130        assertSelectionString("&nbsp;");
1131
1132        // go to the fourth sentence
1133        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1134        assertSelectionString("<a href=\"#\">Third</a>");
1135
1136        // go to past the last sentence
1137        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1138        assertSelectionString(null);
1139
1140        // go to the third sentence
1141        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1142        assertSelectionString("<a href=\"#\">Third</a>");
1143
1144        // NOTE: Here we are a bit asymmetric around whitespace but we can live with it
1145        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1146        assertSelectionString("&nbsp;");
1147
1148        // go to the second sentence
1149        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1150        assertSelectionString("<a href=\"#\"><span>Second&nbsp;<small>a</small></a>");
1151
1152        // go to the first sentence
1153        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1154        assertSelectionString("<a href=\"#\">First</a>");
1155
1156        // go to before the first sentence
1157        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1158        assertSelectionString(null);
1159
1160        // go to the first sentence
1161        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1162        assertSelectionString("<a href=\"#\">First</a>");
1163    }
1164
1165    /**
1166     * Tests if input elements are selected rather skipped.
1167     */
1168    @LargeTest
1169    public void testSelectionOfInputElements() throws Exception {
1170        // a bit ugly but helps detect beginning and end of all tests so accessibility
1171        // and the mock service are not toggled on every test (expensive)
1172        sExecutedTestCount++;
1173
1174        String html =
1175            "<!DOCTYPE html>" +
1176            "<html>" +
1177              "<head>" +
1178              "</head>" +
1179              "<body>" +
1180                "<p>" +
1181                  "First" +
1182                "</p>" +
1183                "<input type=\"text\"/>" +
1184                "<p>" +
1185                  "Second" +
1186                "</p>" +
1187              "</body>" +
1188            "</html>";
1189
1190        WebView webView = loadHTML(html);
1191
1192        // go to the first sentence
1193        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1194        assertSelectionString("First");
1195
1196        // go to the second sentence
1197        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1198        assertSelectionString("<input type=\"text\">");
1199
1200        // go to the third sentence
1201        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1202        assertSelectionString("Second");
1203
1204        // go to past the last sentence
1205        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1206        assertSelectionString(null);
1207
1208        // go to the third sentence
1209        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1210        assertSelectionString("Second");
1211
1212        // go to the second sentence
1213        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1214        assertSelectionString("<input type=\"text\">");
1215
1216        // go to the first sentence
1217        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1218        assertSelectionString("First");
1219
1220        // go to before the first sentence
1221        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1222        assertSelectionString(null);
1223
1224        // go to the first sentence
1225        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1226        assertSelectionString("First");
1227    }
1228
1229    /**
1230     * Tests traversing of input controls.
1231     */
1232    @LargeTest
1233    public void testSelectionOfInputElements2() throws Exception {
1234        // a bit ugly but helps detect beginning and end of all tests so accessibility
1235        // and the mock service are not toggled on every test (expensive)
1236        sExecutedTestCount++;
1237
1238        String html =
1239            "<!DOCTYPE html>" +
1240            "<html>" +
1241              "<head>" +
1242              "</head>" +
1243              "<body>" +
1244                "<div>" +
1245                  "First" +
1246                  "<input type=\"text\"/>" +
1247                  "<span>" +
1248                    "<input type=\"text\"/>" +
1249                  "</span>" +
1250                  "<button type=\"button\">Click Me!</button>" +
1251                  "<div>" +
1252                    "<input type=\"submit\"/>" +
1253                  "</div>" +
1254                  "<p>" +
1255                    "Second" +
1256                  "</p>" +
1257                "</div>" +
1258              "</body>" +
1259            "</html>";
1260
1261        WebView webView = loadHTML(html);
1262
1263        // go to the first sentence
1264        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1265        assertSelectionString("First");
1266
1267        // go to the second sentence
1268        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1269        assertSelectionString("<input type=\"text\">");
1270
1271        // go to the third sentence
1272        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1273        assertSelectionString("<input type=\"text\">");
1274
1275        // go to the fourth sentence
1276        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1277        assertSelectionString("<button type=\"button\">Click Me!</button>");
1278
1279        // go to the fifth sentence
1280        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1281        assertSelectionString("<input type=\"submit\">");
1282
1283        // go to the sixth sentence
1284        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1285        assertSelectionString("Second");
1286
1287        // go to past the last sentence
1288        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1289        assertSelectionString(null);
1290
1291        // go to the sixth sentence
1292        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1293        assertSelectionString("Second");
1294
1295        // go to the fifth sentence
1296        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1297        assertSelectionString("<input type=\"submit\">");
1298
1299        // go to the fourth sentence
1300        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1301        assertSelectionString("<button type=\"button\">Click Me!</button>");
1302
1303        // go to the third sentence
1304        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1305        assertSelectionString("<input type=\"text\">");
1306
1307        // go to the second sentence
1308        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1309        assertSelectionString("<input type=\"text\">");
1310
1311        // go to the first sentence
1312        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1313        assertSelectionString("First");
1314    }
1315
1316    /**
1317     * Tests traversing of input controls.
1318     */
1319    @LargeTest
1320    public void testSelectionOfInputElements3() throws Exception {
1321        // a bit ugly but helps detect beginning and end of all tests so accessibility
1322        // and the mock service are not toggled on every test (expensive)
1323        sExecutedTestCount++;
1324
1325        String html =
1326            "<!DOCTYPE html>" +
1327            "<html>" +
1328              "<head>" +
1329              "</head>" +
1330              "<body>" +
1331                "<input type=\"text\"/>" +
1332                "<button type=\"button\">Click Me!</button>" +
1333                "<select>" +
1334                  "<option value=\"volvo\">Volvo</option>" +
1335                  "<option value=\"saab\">Saab</option>" +
1336                "</select>" +
1337              "</body>" +
1338            "</html>";
1339
1340        WebView webView = loadHTML(html);
1341
1342        // go to the first sentence
1343        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1344        assertSelectionString("<input type=\"text\">");
1345
1346        // go to the second sentence
1347        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1348        assertSelectionString("<button type=\"button\">Click Me!</button>");
1349
1350        // go to the third sentence
1351        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1352        assertSelectionString("<select><option value=\"volvo\">Volvo</option>" +
1353                "<option value=\"saab\">Saab</option></select>");
1354
1355        // go to past the last sentence
1356        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1357        assertSelectionString(null);
1358
1359        // go to the third sentence
1360        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1361        assertSelectionString("<select><option value=\"volvo\">Volvo</option>" +
1362                "<option value=\"saab\">Saab</option></select>");
1363
1364        // go to the second sentence
1365        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1366        assertSelectionString("<button type=\"button\">Click Me!</button>");
1367
1368        // go to the first sentence
1369        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1370        assertSelectionString("<input type=\"text\">");
1371
1372        // go to before the first sentence
1373        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1374        assertSelectionString(null);
1375
1376        // go to the first sentence
1377        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1378        assertSelectionString("<input type=\"text\">");
1379    }
1380
1381    /**
1382     * Tests traversing of input controls.
1383     */
1384    @LargeTest
1385    public void testSelectionOfInputElements4() throws Exception {
1386        // a bit ugly but helps detect beginning and end of all tests so accessibility
1387        // and the mock service are not toggled on every test (expensive)
1388        sExecutedTestCount++;
1389
1390        String html =
1391            "<!DOCTYPE html>" +
1392            "<html>" +
1393              "<head>" +
1394              "</head>" +
1395              "<body>" +
1396                "Start" +
1397                "<span>" +
1398                  "<span>" +
1399                    "<input type=\"submit\">" +
1400                  "</span>" +
1401                "</span>" +
1402                "<input type=\"text\" size=\"30\">" +
1403                "<span>" +
1404                  "<span>" +
1405                    "<input type=\"submit\" size=\"30\">" +
1406                  "</span>" +
1407                "</span>" +
1408                "End" +
1409              "</body>" +
1410            "</html>";
1411
1412        WebView webView = loadHTML(html);
1413
1414        // go to the first sentence
1415        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1416        assertSelectionString("Start");
1417
1418        // go to the second sentence
1419        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1420        assertSelectionString("<input type=\"submit\">");
1421
1422        // go to the third sentence
1423        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1424        assertSelectionString("<input type=\"text\" size=\"30\">");
1425
1426        // go to the fourth sentence
1427        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1428        assertSelectionString("<input type=\"submit\" size=\"30\">");
1429
1430        // go to the fifth sentence
1431        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1432        assertSelectionString("End");
1433
1434        // go to past the last sentence
1435        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1436        assertSelectionString(null);
1437
1438        // go to the fifth sentence
1439        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1440        assertSelectionString("End");
1441
1442        // go to the fourth sentence
1443        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1444        assertSelectionString("<input type=\"submit\" size=\"30\">");
1445
1446        // go to the third sentence
1447        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1448        assertSelectionString("<input type=\"text\" size=\"30\">");
1449
1450        // go to the second sentence
1451        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1452        assertSelectionString("<input type=\"submit\">");
1453
1454        // go to the first sentence
1455        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1456        assertSelectionString("Start");
1457
1458        // go to before the first sentence
1459        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1460        assertSelectionString(null);
1461
1462        // go to the first sentence
1463        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1464        assertSelectionString("Start");
1465    }
1466
1467    /**
1468     * Tests traversing of input controls.
1469     */
1470    @LargeTest
1471    public void testSelectionOfInputElements5() throws Exception {
1472        // a bit ugly but helps detect beginning and end of all tests so accessibility
1473        // and the mock service are not toggled on every test (expensive)
1474        sExecutedTestCount++;
1475
1476        String html =
1477            "<!DOCTYPE html>" +
1478            "<html>" +
1479              "<head>" +
1480              "</head>" +
1481              "<body>" +
1482                "<div>" +
1483                  "First" +
1484                  "<input type=\"hidden\">" +
1485                  "<input type=\"hidden\">" +
1486                  "<input type=\"hidden\">" +
1487                  "<input type=\"hidden\">" +
1488                  "<input type=\"text\">" +
1489                  "<span>" +
1490                    "<span>" +
1491                      "<input type=\"submit\">" +
1492                    "</span>" +
1493                  "</span>" +
1494                "</div>" +
1495              "</body>" +
1496              "Second" +
1497            "</html>";
1498
1499        WebView webView = loadHTML(html);
1500
1501        // go to the first sentence
1502        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1503        assertSelectionString("First");
1504
1505        // go to the second sentence
1506        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1507        assertSelectionString("<input type=\"text\">");
1508
1509        // go to the third sentence
1510        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1511        assertSelectionString("<input type=\"submit\">");
1512
1513        // go to the fourth sentence
1514        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1515        assertSelectionString("Second");
1516
1517        // go to past the last sentence
1518        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1519        assertSelectionString(null);
1520
1521        // go to the fourth sentence
1522        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1523        assertSelectionString("Second");
1524
1525        // go to the third sentence
1526        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1527        assertSelectionString("<input type=\"submit\">");
1528
1529        // go to the second sentence
1530        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1531        assertSelectionString("<input type=\"text\">");
1532
1533        // go to the first sentence
1534        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1535        assertSelectionString("First");
1536
1537        // go to before the first sentence
1538        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
1539        assertSelectionString(null);
1540
1541        // go to the first sentence
1542        sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
1543        assertSelectionString("First");
1544    }
1545
1546    /**
1547     * Enable accessibility and the mock accessibility service.
1548     */
1549    private void enableAccessibilityAndMockAccessibilityService() {
1550        // make sure the manager is instantiated so the system initializes it
1551        AccessibilityManager.getInstance(getActivity());
1552
1553        // enable accessibility and the mock accessibility service
1554        Settings.Secure.putInt(getActivity().getContentResolver(),
1555                Settings.Secure.ACCESSIBILITY_ENABLED, 1);
1556        String enabledServices = new ComponentName(getActivity().getPackageName(),
1557                MockAccessibilityService.class.getName()).flattenToShortString();
1558        Settings.Secure.putString(getActivity().getContentResolver(),
1559                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, enabledServices);
1560
1561        // poll within a timeout and let be interrupted in case of success
1562        long incrementStep = TIMEOUT_ENABLE_ACCESSIBILITY_AND_MOCK_SERVICE / 5;
1563        long start = SystemClock.uptimeMillis();
1564        while (SystemClock.uptimeMillis() - start < TIMEOUT_ENABLE_ACCESSIBILITY_AND_MOCK_SERVICE &&
1565                !sIsAccessibilityServiceReady) {
1566            synchronized (sTestLock) {
1567                try {
1568                    sTestLock.wait(incrementStep);
1569                } catch (InterruptedException ie) {
1570                    /* ignore */
1571                }
1572            }
1573        }
1574
1575        if (!sIsAccessibilityServiceReady) {
1576            throw new IllegalStateException("MockAccessibilityService not ready. Did you add " +
1577                    "tests and forgot to update AccessibilityInjectorTest#TEST_CASE_COUNT?");
1578        }
1579    }
1580
1581    @Override
1582    protected void scrubClass(Class<?> testCaseClass) {
1583        /* do nothing - avoid superclass behavior */
1584    }
1585
1586    /**
1587     * Strips the apple span appended by WebKit while generating
1588     * the selection markup.
1589     *
1590     * @param markup The markup.
1591     * @return Stripped from apple spans markup.
1592     */
1593    private static String stripAppleSpanFromMarkup(String markup) {
1594        StringBuilder stripped = new StringBuilder(markup);
1595        int prefixBegIdx = stripped.indexOf(APPLE_SPAN_PREFIX);
1596        while (prefixBegIdx >= 0) {
1597            int prefixEndIdx = stripped.indexOf(">", prefixBegIdx) + 1;
1598            stripped.replace(prefixBegIdx, prefixEndIdx, "");
1599            int suffixBegIdx = stripped.lastIndexOf(APPLE_SPAN_SUFFIX);
1600            int suffixEndIdx = suffixBegIdx + APPLE_SPAN_SUFFIX.length();
1601            stripped.replace(suffixBegIdx, suffixEndIdx, "");
1602            prefixBegIdx = stripped.indexOf(APPLE_SPAN_PREFIX);
1603        }
1604        return stripped.toString();
1605    }
1606
1607    /**
1608     * Disables accessibility and the mock accessibility service.
1609     */
1610    private void disableAccessibilityAndMockAccessibilityService() {
1611        // disable accessibility and the mock accessibility service
1612        Settings.Secure.putInt(getActivity().getContentResolver(),
1613                Settings.Secure.ACCESSIBILITY_ENABLED, 0);
1614        Settings.Secure.putString(getActivity().getContentResolver(),
1615                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "");
1616    }
1617
1618    /**
1619     * Asserts the next <code>expectedSelectionString</code> to be received.
1620     */
1621    private void assertSelectionString(String expectedSelectionString) {
1622        assertTrue("MockAccessibilityService not ready", sIsAccessibilityServiceReady);
1623
1624        long incrementStep = TIMEOUT_WAIT_FOR_SELECTION_STRING / 5;
1625        long start = SystemClock.uptimeMillis();
1626        while (SystemClock.uptimeMillis() - start < TIMEOUT_WAIT_FOR_SELECTION_STRING &&
1627                sReceivedSelectionString == SELECTION_STRING_UNKNOWN) {
1628            synchronized (sTestLock) {
1629                try {
1630                    sTestLock.wait(incrementStep);
1631                } catch (InterruptedException ie) {
1632                    /* ignore */
1633                }
1634            }
1635        }
1636        try {
1637            if (sReceivedSelectionString == SELECTION_STRING_UNKNOWN) {
1638                fail("No selection string received. Expected: " + expectedSelectionString);
1639            }
1640            assertEquals(expectedSelectionString, sReceivedSelectionString);
1641        } finally {
1642            sReceivedSelectionString = SELECTION_STRING_UNKNOWN;
1643        }
1644    }
1645
1646    /**
1647     * Sends a {@link KeyEvent} (up and down) to the {@link WebView}.
1648     *
1649     * @param keyCode The event key code.
1650     */
1651    private void sendKeyEvent(WebView webView, int keyCode, int metaState) {
1652        webView.onKeyDown(keyCode, new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, keyCode, 1, metaState));
1653        webView.onKeyUp(keyCode, new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode, 1, metaState));
1654    }
1655
1656    /**
1657     * Loads HTML content in a {@link WebView}.
1658     *
1659     * @param html The HTML content;
1660     * @return The {@link WebView} view.
1661     */
1662    private WebView loadHTML(final String html) {
1663        mWorker.getHandler().post(new Runnable() {
1664            public void run() {
1665                if (mWebView == null) {
1666                    mWebView = getActivity().getWebView();
1667                    mWebView.setWebViewClient(new WebViewClient() {
1668                        @Override
1669                        public void onPageFinished(WebView view, String url) {
1670                            mWorker.getHandler().post(new Runnable() {
1671                                public void run() {
1672                                    synchronized (sTestLock) {
1673                                        sTestLock.notifyAll();
1674                                    }
1675                                }
1676                            });
1677                        }
1678                    });
1679                }
1680                mWebView.loadData(html, "text/html", null);
1681            }
1682        });
1683        synchronized (sTestLock) {
1684            try {
1685                sTestLock.wait();
1686            } catch (InterruptedException ie) {
1687                /* ignore */
1688            }
1689        }
1690        return mWebView;
1691    }
1692
1693    /**
1694     * Injects web content key bindings used for testing. This is required
1695     * to ensure that this test will be agnostic to changes of the bindings.
1696     */
1697    private void injectTestWebContentKeyBindings() {
1698        ContentResolver contentResolver = getActivity().getContentResolver();
1699        sDefaultKeyBindings = Settings.Secure.getString(contentResolver,
1700                Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS);
1701        Settings.Secure.putString(contentResolver,
1702                Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS, TEST_KEY_DINDINGS);
1703    }
1704
1705    /**
1706     * Restores the default web content key bindings.
1707     */
1708    private void restoreDefaultWebContentKeyBindings() {
1709        Settings.Secure.putString(getActivity().getContentResolver(),
1710                Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS,
1711                sDefaultKeyBindings);
1712    }
1713
1714    /**
1715     * This is a worker thread responsible for creating the {@link WebView}.
1716     */
1717    private class Worker implements Runnable {
1718        private final Object mWorkerLock = new Object();
1719        private Handler mHandler;
1720
1721       public Worker() {
1722            new Thread(this).start();
1723            synchronized (mWorkerLock) {
1724                while (mHandler == null) {
1725                    try {
1726                        mWorkerLock.wait();
1727                    } catch (InterruptedException ex) {
1728                        /* ignore */
1729                    }
1730                }
1731            }
1732        }
1733
1734        public void run() {
1735            synchronized (mWorkerLock) {
1736                Looper.prepare();
1737                mHandler = new Handler();
1738                mWorkerLock.notifyAll();
1739            }
1740            Looper.loop();
1741        }
1742
1743        public Handler getHandler() {
1744            return mHandler;
1745        }
1746
1747        public void stop() {
1748            mHandler.getLooper().quit();
1749        }
1750    }
1751
1752    /**
1753     * Mock accessibility service to receive the accessibility events
1754     * with the current {@link WebView} selection.
1755     */
1756    public static class MockAccessibilityService extends AccessibilityService {
1757        private boolean mIsServiceInfoSet;
1758
1759        @Override
1760        protected void onServiceConnected() {
1761            if (mIsServiceInfoSet) {
1762                return;
1763            }
1764            AccessibilityServiceInfo info = new AccessibilityServiceInfo();
1765            info.eventTypes = AccessibilityEvent.TYPE_VIEW_SELECTED;
1766            info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
1767            setServiceInfo(info);
1768            mIsServiceInfoSet = true;
1769
1770            sIsAccessibilityServiceReady = true;
1771
1772            if (sInstance == null) {
1773                return;
1774            }
1775            synchronized (sTestLock) {
1776                sTestLock.notifyAll();
1777            }
1778        }
1779
1780        @Override
1781        public void onAccessibilityEvent(AccessibilityEvent event) {
1782            if (sInstance == null) {
1783                return;
1784            }
1785            if (!event.getText().isEmpty()) {
1786                CharSequence text = event.getText().get(0);
1787                if (text != null) {
1788                    sReceivedSelectionString = stripAppleSpanFromMarkup(text.toString());
1789                } else {
1790                    sReceivedSelectionString = null;
1791                }
1792            }
1793            synchronized (sTestLock) {
1794                sTestLock.notifyAll();
1795            }
1796        }
1797
1798        @Override
1799        public void onInterrupt() {
1800            /* do nothing */
1801        }
1802
1803        @Override
1804        public boolean onUnbind(Intent intent) {
1805            sIsAccessibilityServiceReady = false;
1806            return false;
1807        }
1808    }
1809}
1810