<lambda>null1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
2  * Any copyright is dedicated to the Public Domain.
3    http://creativecommons.org/publicdomain/zero/1.0/ */
4 
5 package org.mozilla.geckoview.test
6 
7 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
8 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
9 
10 import android.graphics.Rect
11 
12 import android.os.Build
13 import android.os.Bundle
14 import android.os.SystemClock
15 
16 import androidx.test.filters.MediumTest
17 import androidx.test.platform.app.InstrumentationRegistry
18 import androidx.test.ext.junit.runners.AndroidJUnit4
19 
20 import android.text.InputType
21 import android.util.SparseLongArray
22 
23 import android.view.accessibility.AccessibilityNodeInfo
24 import android.view.accessibility.AccessibilityNodeProvider
25 import android.view.accessibility.AccessibilityEvent
26 import android.view.accessibility.AccessibilityRecord
27 import android.view.View
28 import android.view.ViewGroup
29 import android.widget.EditText
30 
31 import android.widget.FrameLayout
32 
33 import org.hamcrest.Matchers.*
34 import org.junit.Assume.assumeThat
35 import org.junit.Test
36 import org.junit.Before
37 import org.junit.After
38 import org.junit.Ignore
39 import org.junit.runner.RunWith
40 import org.mozilla.geckoview.AllowOrDeny
41 import org.mozilla.geckoview.GeckoResult
42 import org.mozilla.geckoview.GeckoSession
43 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.Setting
44 
45 const val DISPLAY_WIDTH = 480
46 const val DISPLAY_HEIGHT = 640
47 
48 @RunWith(AndroidJUnit4::class)
49 @MediumTest
50 @WithDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
51 class AccessibilityTest : BaseSessionTest() {
52     lateinit var view: View
53     val screenRect = Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT)
54     val provider: AccessibilityNodeProvider get() = view.accessibilityNodeProvider
55     private val nodeInfos = mutableListOf<AccessibilityNodeInfo>()
56 
57     // Given a child ID, return the virtual descendent ID.
58     private fun getVirtualDescendantId(childId: Long): Int {
59         try {
60             val getVirtualDescendantIdMethod =
61                 AccessibilityNodeInfo::class.java.getMethod("getVirtualDescendantId", Long::class.java)
62             val virtualDescendantId = getVirtualDescendantIdMethod.invoke(null, childId) as Int
63             return if (virtualDescendantId == Int.MAX_VALUE) -1 else virtualDescendantId
64         } catch (ex: Exception) {
65             return 0
66         }
67     }
68 
69     // Retrieve the virtual descendent ID of the event's source.
70     private fun getSourceId(event: AccessibilityEvent): Int {
71         try {
72             val getSourceIdMethod =
73                 AccessibilityRecord::class.java.getMethod("getSourceNodeId")
74             return getVirtualDescendantId(getSourceIdMethod.invoke(event) as Long)
75         } catch (ex: Exception) {
76             return 0
77         }
78     }
79 
80     private fun createNodeInfo(id: Int): AccessibilityNodeInfo {
81         val node = provider.createAccessibilityNodeInfo(id);
82         nodeInfos.add(node)
83         return node;
84     }
85 
86     // Get a child ID by index.
87     private fun AccessibilityNodeInfo.getChildId(index: Int): Int =
88             getVirtualDescendantId(
89                     if (Build.VERSION.SDK_INT >= 21)
90                         AccessibilityNodeInfo::class.java.getMethod(
91                                 "getChildId", Int::class.java).invoke(this, index) as Long
92                     else
93                         (AccessibilityNodeInfo::class.java.getMethod("getChildNodeIds")
94                                 .invoke(this) as SparseLongArray).get(index))
95 
96     private interface EventDelegate {
97         fun onAccessibilityFocused(event: AccessibilityEvent) { }
98         fun onAccessibilityFocusCleared(event: AccessibilityEvent) { }
99         fun onClicked(event: AccessibilityEvent) { }
100         fun onFocused(event: AccessibilityEvent) { }
101         fun onSelected(event: AccessibilityEvent) { }
102         fun onScrolled(event: AccessibilityEvent) { }
103         fun onTextSelectionChanged(event: AccessibilityEvent) { }
104         fun onTextChanged(event: AccessibilityEvent) { }
105         fun onTextTraversal(event: AccessibilityEvent) { }
106         fun onWinContentChanged(event: AccessibilityEvent) { }
107         fun onWinStateChanged(event: AccessibilityEvent) { }
108         fun onAnnouncement(event: AccessibilityEvent) { }
109     }
110 
111     @Before fun setup() {
112         // We initialize a view with a parent and grandparent so that the
113         // accessibility events propagate up at least to the parent.
114         val context = InstrumentationRegistry.getInstrumentation().targetContext
115         view = FrameLayout(context)
116         FrameLayout(context).addView(view)
117         FrameLayout(context).addView(view.parent as View)
118 
119         // Force on accessibility and assign the session's accessibility
120         // object a view.
121         sessionRule.runtime.settings.forceEnableAccessibility = true;
122         mainSession.accessibility.view = view
123 
124         // Set up an external delegate that will intercept accessibility events.
125         sessionRule.addExternalDelegateUntilTestEnd(
126             EventDelegate::class,
127         { newDelegate -> (view.parent as View).setAccessibilityDelegate(object : View.AccessibilityDelegate() {
128             override fun onRequestSendAccessibilityEvent(host: ViewGroup, child: View, event: AccessibilityEvent): Boolean {
129                 when (event.eventType) {
130                     AccessibilityEvent.TYPE_VIEW_FOCUSED -> newDelegate.onFocused(event)
131                     AccessibilityEvent.TYPE_VIEW_CLICKED -> newDelegate.onClicked(event)
132                     AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED -> newDelegate.onAccessibilityFocused(event)
133                     AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED -> newDelegate.onAccessibilityFocusCleared(event)
134                     AccessibilityEvent.TYPE_VIEW_SELECTED -> newDelegate.onSelected(event)
135                     AccessibilityEvent.TYPE_VIEW_SCROLLED -> newDelegate.onScrolled(event)
136                     AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED -> newDelegate.onTextSelectionChanged(event)
137                     AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED -> newDelegate.onTextChanged(event)
138                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY -> newDelegate.onTextTraversal(event)
139                     AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED -> newDelegate.onWinContentChanged(event)
140                     AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED -> newDelegate.onWinStateChanged(event)
141                     AccessibilityEvent.TYPE_ANNOUNCEMENT -> newDelegate.onAnnouncement(event)
142                     else -> {}
143                 }
144                 return false
145             }
146         }) },
147         { (view.parent as View).setAccessibilityDelegate(null) },
148         object : EventDelegate { })
149     }
150 
151     @After fun teardown() {
152         sessionRule.runtime.settings.forceEnableAccessibility = false
153         sessionRule.session.accessibility.view = null
154         nodeInfos.forEach { node -> node.recycle() }
155     }
156 
157     private fun waitForInitialFocus(moveToFirstChild: Boolean = false) {
158         sessionRule.waitUntilCalled(object: GeckoSession.NavigationDelegate {
159             override fun onLoadRequest(session: GeckoSession,
160                                        request: GeckoSession.NavigationDelegate.LoadRequest)
161                     : GeckoResult<AllowOrDeny>? {
162                 return GeckoResult.allow()
163             }
164         })
165         // XXX: Sometimes we get the window state change of the initial
166         // about:blank page loading. Need to figure out how to ignore that.
167         sessionRule.waitUntilCalled(object : EventDelegate {
168             @AssertCalled(count = 1)
169             override fun onFocused(event: AccessibilityEvent) { }
170 
171             @AssertCalled
172             override fun onWinStateChanged(event: AccessibilityEvent) { }
173 
174             @AssertCalled
175             override fun onWinContentChanged(event: AccessibilityEvent) { }
176         })
177 
178         if (moveToFirstChild) {
179             provider.performAction(View.NO_ID,
180                 AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, null)
181         }
182     }
183 
184     @Test fun testRootNode() {
185         assertThat("provider is not null", provider, notNullValue())
186         val node = createNodeInfo(AccessibilityNodeProvider.HOST_VIEW_ID)
187         assertThat("Root node should have WebView class name",
188             node.className.toString(), equalTo("android.webkit.WebView"))
189     }
190 
191     @Test fun testPageLoad() {
192         sessionRule.session.loadTestPath(INPUTS_PATH)
193 
194         sessionRule.waitUntilCalled(object : EventDelegate {
195             @AssertCalled(count = 1)
196             override fun onFocused(event: AccessibilityEvent) { }
197         })
198     }
199 
200     @Test fun testAccessibilityFocus() {
201         var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID
202         sessionRule.session.loadTestPath(INPUTS_PATH)
203         waitForInitialFocus(true)
204 
205         sessionRule.waitUntilCalled(object : EventDelegate {
206             @AssertCalled(count = 1)
207             override fun onAccessibilityFocused(event: AccessibilityEvent) {
208                 nodeId = getSourceId(event)
209                 val node = createNodeInfo(nodeId)
210                 assertThat("Label accessibility focused", node.className.toString(),
211                         equalTo("android.view.View"))
212                 assertThat("Text node should not be focusable", node.isFocusable, equalTo(false))
213                 assertThat("Text node should be a11y focused", node.isAccessibilityFocused, equalTo(true))
214                 assertThat("Text node should not be clickable", node.isClickable, equalTo(false))
215             }
216         })
217 
218         provider.performAction(nodeId,
219             AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, null)
220 
221         sessionRule.waitUntilCalled(object : EventDelegate {
222             @AssertCalled(count = 1)
223             override fun onAccessibilityFocused(event: AccessibilityEvent) {
224                 nodeId = getSourceId(event)
225                 val node = createNodeInfo(nodeId)
226                 assertThat("Editbox accessibility focused", node.className.toString(),
227                         equalTo("android.widget.EditText"))
228                 assertThat("Entry node should be focusable", node.isFocusable, equalTo(true))
229                 assertThat("Entry node should be a11y focused", node.isAccessibilityFocused, equalTo(true))
230                 assertThat("Entry node should be clickable", node.isClickable, equalTo(true))
231             }
232         })
233 
234         provider.performAction(nodeId,
235                 AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null)
236 
237         sessionRule.waitUntilCalled(object : EventDelegate {
238             @AssertCalled(count = 1)
239             override fun onAccessibilityFocusCleared(event: AccessibilityEvent) {
240                 assertThat("Accessibility focused node is now cleared", getSourceId(event), equalTo(nodeId))
241                 val node = createNodeInfo(nodeId)
242                 assertThat("Entry node should node be a11y focused", node.isAccessibilityFocused, equalTo(false))
243             }
244         })
245     }
246 
247     fun loadTestPage(page: String) {
248         sessionRule.session.loadTestPath("/assets/www/accessibility/$page.html")
249     }
250 
251     @Test fun testTextEntryNode() {
252         loadTestPage("test-text-entry-node")
253         waitForInitialFocus()
254 
255         mainSession.evaluateJS("document.querySelector('input[aria-label=Name]').focus()")
256 
257         sessionRule.waitUntilCalled(object : EventDelegate {
258             @AssertCalled(count = 1)
259             override fun onFocused(event: AccessibilityEvent) {
260                 val nodeId = getSourceId(event)
261                 val node = createNodeInfo(nodeId)
262                 assertThat("Focused EditBox", node.className.toString(),
263                         equalTo("android.widget.EditText"))
264                 if (Build.VERSION.SDK_INT >= 19) {
265                     assertThat("Hint has field name",
266                             node.extras.getString("AccessibilityNodeInfo.hint"),
267                             equalTo("Name description"))
268                 }
269             }
270         })
271 
272         mainSession.evaluateJS("document.querySelector('input[aria-label=Last]').focus()")
273 
274         sessionRule.waitUntilCalled(object : EventDelegate {
275             @AssertCalled(count = 1)
276             override fun onFocused(event: AccessibilityEvent) {
277                 val nodeId = getSourceId(event)
278                 val node = createNodeInfo(nodeId)
279                 assertThat("Focused EditBox", node.className.toString(),
280                         equalTo("android.widget.EditText"))
281                 if (Build.VERSION.SDK_INT >= 19) {
282                     assertThat("Hint has field name",
283                             node.extras.getString("AccessibilityNodeInfo.hint"),
284                             equalTo("Last, required"))
285                 }
286             }
287         })
288     }
289 
290     @Test fun testMoveCaretAccessibilityFocus() {
291         loadTestPage("test-move-caret-accessibility-focus")
292         waitForInitialFocus(false)
293 
294         mainSession.evaluateJS("""
295             this.select = function select(node, start, end) {
296                 let r = new Range();
297                 r.setStart(node, start);
298                 r.setEnd(node, end);
299                 let s = getSelection();
300                 s.removeAllRanges();
301                 s.addRange(r);
302             };
303             this.select(document.querySelector('p').childNodes[2], 2, 6);
304         """.trimIndent())
305 
306         sessionRule.waitUntilCalled(object : EventDelegate {
307             @AssertCalled(count = 1)
308             override fun onAccessibilityFocused(event: AccessibilityEvent) {
309                 val node = createNodeInfo(getSourceId(event))
310                 assertThat("Text node should match text", node.text as String, equalTo(", sweet "))
311             }
312         })
313 
314         mainSession.evaluateJS("""
315             this.select(document.querySelector('p').lastElementChild.firstChild, 1, 2);
316         """.trimIndent())
317 
318         sessionRule.waitUntilCalled(object : EventDelegate {
319             @AssertCalled(count = 1)
320             override fun onAccessibilityFocused(event: AccessibilityEvent) {
321                 val node = createNodeInfo(getSourceId(event))
322                 assertThat("Text node should match text", node.text as String, equalTo("world"))
323             }
324         })
325 
326         mainSession.finder.find("sweet", 0)
327         sessionRule.waitUntilCalled(object : EventDelegate {
328             @AssertCalled(count = 1)
329             override fun onAccessibilityFocused(event: AccessibilityEvent) {
330                 val node = createNodeInfo(getSourceId(event))
331                 assertThat("Text node should match text", node.contentDescription as String, equalTo("sweet"))
332             }
333         })
334 
335         // reset caret position
336         mainSession.evaluateJS("""
337             this.select(document.body, 0, 0);
338         """.trimIndent())
339         sessionRule.waitUntilCalled(object : EventDelegate {
340             @AssertCalled(count = 1)
341             override fun onFocused(event: AccessibilityEvent) {}
342         })
343 
344         mainSession.finder.find("Hell", 0)
345         sessionRule.waitUntilCalled(object : EventDelegate {
346             @AssertCalled(count = 1)
347             override fun onAccessibilityFocused(event: AccessibilityEvent) {
348                 val node = createNodeInfo(getSourceId(event))
349                 assertThat("Text node should match text", node.text as String, equalTo("Hello "))
350             }
351         })
352     }
353 
354     private fun waitUntilTextSelectionChanged(fromIndex: Int, toIndex: Int) {
355         var eventFromIndex = 0;
356         var eventToIndex = 0;
357         do {
358             sessionRule.waitUntilCalled(object : EventDelegate {
359                 override fun onTextSelectionChanged(event: AccessibilityEvent) {
360                     eventFromIndex = event.fromIndex;
361                     eventToIndex = event.toIndex;
362                 }
363             })
364         } while (fromIndex != eventFromIndex || toIndex != eventToIndex)
365     }
366 
367     private fun waitUntilTextTraversed(fromIndex: Int, toIndex: Int,
368             expectedNode: Int? = null): Int {
369         var nodeId: Int = AccessibilityNodeProvider.HOST_VIEW_ID
370         sessionRule.waitUntilCalled(object : EventDelegate {
371             @AssertCalled(count = 1)
372             override fun onTextTraversal(event: AccessibilityEvent) {
373               nodeId = getSourceId(event)
374               if (expectedNode != null) {
375                 assertThat("Node matches", nodeId, equalTo(expectedNode))
376               }
377               assertThat("fromIndex matches", event.fromIndex, equalTo(fromIndex))
378               assertThat("toIndex matches", event.toIndex, equalTo(toIndex))
379             }
380         })
381         return nodeId
382     }
383 
384     private fun waitUntilClick(checked: Boolean) {
385         sessionRule.waitUntilCalled(object : EventDelegate {
386             @AssertCalled(count = 1)
387             override fun onClicked(event: AccessibilityEvent) {
388                 var nodeId = getSourceId(event)
389                 var node = createNodeInfo(nodeId)
390                 assertThat("Event's checked state matches", event.isChecked, equalTo(checked))
391                 assertThat("Checkbox node has correct checked state", node.isChecked, equalTo(checked))
392             }
393         })
394     }
395 
396     private fun waitUntilSelect(selected: Boolean) {
397         sessionRule.waitUntilCalled(object : EventDelegate {
398             @AssertCalled(count = 1)
399             override fun onSelected(event: AccessibilityEvent) {
400                 var nodeId = getSourceId(event)
401                 var node = createNodeInfo(nodeId)
402                 assertThat("Selectable node has correct selected state", node.isSelected, equalTo(selected))
403             }
404         })
405     }
406 
407     private fun setSelectionArguments(start: Int, end: Int): Bundle {
408         val arguments = Bundle(2)
409         arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, start)
410         arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, end)
411         return arguments
412     }
413 
414     private fun moveByGranularityArguments(granularity: Int, extendSelection: Boolean = false): Bundle {
415         val arguments = Bundle(2)
416         arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT, granularity)
417         arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, extendSelection)
418         return arguments
419     }
420 
421     @Test fun testClipboard() {
422         var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID;
423         loadTestPage("test-clipboard")
424         waitForInitialFocus()
425 
426         mainSession.evaluateJS("document.querySelector('input').focus()")
427 
428         sessionRule.waitUntilCalled(object : EventDelegate {
429             @AssertCalled(count = 1)
430             override fun onFocused(event: AccessibilityEvent) {
431                 nodeId = getSourceId(event)
432                 val node = createNodeInfo(nodeId)
433                 assertThat("Focused EditBox", node.className.toString(),
434                         equalTo("android.widget.EditText"))
435             }
436 
437             @AssertCalled(count = 1)
438             override fun onTextSelectionChanged(event: AccessibilityEvent) {
439                 assertThat("fromIndex should be at start", event.fromIndex, equalTo(0))
440                 assertThat("toIndex should be at start", event.toIndex, equalTo(0))
441             }
442         })
443 
444         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_SET_SELECTION, setSelectionArguments(5, 11))
445         waitUntilTextSelectionChanged(5, 11)
446 
447         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_COPY, null)
448 
449         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_SET_SELECTION, setSelectionArguments(11, 11))
450         waitUntilTextSelectionChanged(11, 11)
451 
452         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_PASTE, null)
453         sessionRule.waitUntilCalled(object : EventDelegate {
454             @AssertCalled(count = 1)
455             override fun onTextChanged(event: AccessibilityEvent) {
456                 assertThat("text should be pasted", event.text[0].toString(), equalTo("hello cruel cruel world"))
457             }
458         })
459 
460         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_SET_SELECTION, setSelectionArguments(17, 23))
461         waitUntilTextSelectionChanged(17, 23)
462 
463         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_PASTE, null)
464         sessionRule.waitUntilCalled(object : EventDelegate {
465             @AssertCalled
466             override fun onTextChanged(event: AccessibilityEvent) {
467                 assertThat("text should be pasted", event.text[0].toString(), equalTo("hello cruel cruel cruel"))
468             }
469         })
470 
471         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_SET_SELECTION, setSelectionArguments(0, 0))
472         waitUntilTextSelectionChanged(0, 0)
473 
474         provider.performAction(nodeId,
475                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY,
476                 moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD, true))
477         waitUntilTextSelectionChanged(0, 5)
478 
479         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_CUT, null)
480         sessionRule.waitUntilCalled(object : EventDelegate {
481             @AssertCalled
482             override fun onTextChanged(event: AccessibilityEvent) {
483                 assertThat("text should be cut", event.text[0].toString(), equalTo(" cruel cruel cruel"))
484             }
485         })
486     }
487 
488     @Test fun testMoveByCharacter() {
489         var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID
490         sessionRule.session.loadTestPath(LOREM_IPSUM_HTML_PATH)
491         waitForInitialFocus(true)
492 
493         sessionRule.waitUntilCalled(object : EventDelegate {
494             @AssertCalled(count = 1)
495             override fun onAccessibilityFocused(event: AccessibilityEvent) {
496                 nodeId = getSourceId(event)
497                 val node = createNodeInfo(nodeId)
498                 assertThat("Accessibility focus on first text leaf", node.text as String, startsWith("Lorem ipsum"))
499             }
500         })
501 
502         provider.performAction(nodeId,
503                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY,
504                 moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER))
505         waitUntilTextTraversed(0, 1, nodeId) // "L"
506 
507         provider.performAction(nodeId,
508                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY,
509                 moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER))
510         waitUntilTextTraversed(1, 2, nodeId) // "o"
511 
512         provider.performAction(nodeId,
513                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
514                 moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER))
515         waitUntilTextTraversed(0, 1, nodeId) // "L"
516     }
517 
518     @Test fun testMoveByWord() {
519         var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID
520         sessionRule.session.loadTestPath(LOREM_IPSUM_HTML_PATH)
521         waitForInitialFocus(true)
522 
523         sessionRule.waitUntilCalled(object : EventDelegate {
524             @AssertCalled(count = 1)
525             override fun onAccessibilityFocused(event: AccessibilityEvent) {
526                 nodeId = getSourceId(event)
527                 val node = createNodeInfo(nodeId)
528                 assertThat("Accessibility focus on first text leaf", node.text as String, startsWith("Lorem ipsum"))
529             }
530         })
531 
532         provider.performAction(nodeId,
533                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY,
534                 moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD))
535         waitUntilTextTraversed(0, 5, nodeId) // "Lorem"
536 
537         provider.performAction(nodeId,
538                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY,
539                 moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD))
540         waitUntilTextTraversed(6, 11, nodeId) // "ipsum"
541 
542         provider.performAction(nodeId,
543                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
544                 moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD))
545         waitUntilTextTraversed(0, 5, nodeId) // "Lorem"
546     }
547 
548     @Test fun testMoveByLine() {
549         var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID
550         sessionRule.session.loadTestPath(LOREM_IPSUM_HTML_PATH)
551         waitForInitialFocus(true)
552 
553         sessionRule.waitUntilCalled(object : EventDelegate {
554             @AssertCalled(count = 1)
555             override fun onAccessibilityFocused(event: AccessibilityEvent) {
556                 nodeId = getSourceId(event)
557                 val node = createNodeInfo(nodeId)
558                 assertThat("Accessibility focus on first text leaf", node.text as String, startsWith("Lorem ipsum"))
559             }
560         })
561 
562         provider.performAction(nodeId,
563                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY,
564                 moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE))
565         waitUntilTextTraversed(0, 18, nodeId) // "Lorem ipsum dolor "
566 
567         provider.performAction(nodeId,
568                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY,
569                 moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE))
570         waitUntilTextTraversed(18, 28, nodeId) // "sit amet, "
571 
572         provider.performAction(nodeId,
573                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
574                 moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE))
575         waitUntilTextTraversed(0, 18, nodeId) // "Lorem ipsum dolor "
576     }
577 
578     @Test fun testMoveByCharacterAtEdges() {
579         var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID
580         sessionRule.session.loadTestPath(LOREM_IPSUM_HTML_PATH)
581         waitForInitialFocus()
582 
583         // Move to the first link containing "anim id".
584         val bundle = Bundle()
585         bundle.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "LINK")
586         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, bundle)
587         sessionRule.waitUntilCalled(object : EventDelegate {
588             @AssertCalled(count = 1)
589             override fun onAccessibilityFocused(event: AccessibilityEvent) {
590                 nodeId = getSourceId(event)
591                 val node = createNodeInfo(nodeId)
592                 assertThat("Accessibility focus on link", node.contentDescription as String, startsWith("anim id"))
593             }
594         })
595 
596         var success: Boolean
597         // Navigate forward through "anim id" character by character.
598         for (start in 0..6) {
599             success = provider.performAction(nodeId,
600                     AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY,
601                     moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER))
602             assertThat("Next char should succeed", success, equalTo(true))
603             waitUntilTextTraversed(start, start + 1, nodeId)
604         }
605 
606         // Try to navigate forward past end.
607         success = provider.performAction(nodeId,
608                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY,
609                 moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER))
610         assertThat("Next char should fail at end", success, equalTo(false))
611 
612         // We're already on "d". Navigate backward through "anim i".
613         for (start in 5 downTo 0) {
614             success = provider.performAction(nodeId,
615                     AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
616                     moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER))
617             assertThat("Prev char should succeed", success, equalTo(true))
618             waitUntilTextTraversed(start, start + 1, nodeId)
619         }
620 
621         // Try to navigate backward past start.
622         success = provider.performAction(nodeId,
623                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
624                 moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER))
625         assertThat("Prev char should fail at start", success, equalTo(false))
626     }
627 
628     @Test fun testMoveByWordAtEdges() {
629         var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID
630         sessionRule.session.loadTestPath(LOREM_IPSUM_HTML_PATH)
631         waitForInitialFocus()
632 
633         // Move to the first link containing "anim id".
634         val bundle = Bundle()
635         bundle.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "LINK")
636         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, bundle)
637         sessionRule.waitUntilCalled(object : EventDelegate {
638             @AssertCalled(count = 1)
639             override fun onAccessibilityFocused(event: AccessibilityEvent) {
640                 nodeId = getSourceId(event)
641                 val node = createNodeInfo(nodeId)
642                 assertThat("Accessibility focus on link", node.contentDescription as String, startsWith("anim id"))
643             }
644         })
645 
646         var success: Boolean
647         success = provider.performAction(nodeId,
648                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY,
649                 moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD))
650         assertThat("Next word should succeed", success, equalTo(true))
651         waitUntilTextTraversed(0, 4, nodeId) // "anim"
652 
653         success = provider.performAction(nodeId,
654                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY,
655                 moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD))
656         assertThat("Next word should succeed", success, equalTo(true))
657         waitUntilTextTraversed(5, 7, nodeId) // "id"
658 
659         // Try to navigate forward past end.
660         success = provider.performAction(nodeId,
661                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY,
662                 moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD))
663         assertThat("Next word should fail at end", success, equalTo(false))
664 
665         success = provider.performAction(nodeId,
666                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
667                 moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD))
668         assertThat("Prev word should succeed", success, equalTo(true))
669         waitUntilTextTraversed(0, 4, nodeId) // "anim"
670 
671         // Try to navigate backward past start.
672         success = provider.performAction(nodeId,
673                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
674                 moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD))
675         assertThat("Prev word should fail at start", success, equalTo(false))
676     }
677 
678     @Test fun testMoveAtEndOfTextTrailingWhitespace() {
679         var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID
680         sessionRule.session.loadTestPath(LOREM_IPSUM_HTML_PATH)
681         waitForInitialFocus(true)
682 
683         sessionRule.waitUntilCalled(object : EventDelegate {
684             @AssertCalled(count = 1)
685             override fun onAccessibilityFocused(event: AccessibilityEvent) {
686                 nodeId = getSourceId(event)
687                 val node = createNodeInfo(nodeId)
688                 assertThat("Accessibility focus on first text leaf", node.text as String, startsWith("Lorem ipsum"))
689             }
690         })
691 
692         // Initial move backward to move to last word.
693         var success = provider.performAction(nodeId,
694                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
695                 moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD))
696         assertThat("Prev word should succeed", success, equalTo(true))
697         waitUntilTextTraversed(418, 424, nodeId) // "mollit"
698 
699         // Try to move forward past last word.
700         success = provider.performAction(nodeId,
701                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY,
702                 moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD))
703         assertThat("Next word should fail at last word", success, equalTo(false))
704 
705         // Move forward by character (onto trailing space).
706         success = provider.performAction(nodeId,
707                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY,
708                 moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER))
709         assertThat("Next char should succeed", success, equalTo(true))
710         waitUntilTextTraversed(424, 425, nodeId) // " "
711 
712         // Try to move forward past last character.
713         success = provider.performAction(nodeId,
714                 AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY,
715                 moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER))
716         assertThat("Next char should fail at last char", success, equalTo(false))
717     }
718 
719     @Test fun testHeadings() {
720         var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID;
721         loadTestPage("test-headings")
722         waitForInitialFocus()
723 
724         val bundle = Bundle()
725         bundle.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "HEADING")
726 
727         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, bundle)
728         sessionRule.waitUntilCalled(object : EventDelegate {
729             @AssertCalled(count = 1)
730             override fun onAccessibilityFocused(event: AccessibilityEvent) {
731                 nodeId = getSourceId(event)
732                 val node = createNodeInfo(nodeId)
733                 assertThat("Accessibility focus on first heading", node.contentDescription as String, startsWith("Fried cheese"))
734                 if (Build.VERSION.SDK_INT >= 19) {
735                     assertThat("First heading is level 1",
736                             node.extras.getCharSequence("AccessibilityNodeInfo.roleDescription")!!.toString(),
737                             equalTo("heading level 1"))
738                 }
739             }
740         })
741 
742         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, bundle)
743         sessionRule.waitUntilCalled(object : EventDelegate {
744             @AssertCalled(count = 1)
745             override fun onAccessibilityFocused(event: AccessibilityEvent) {
746                 nodeId = getSourceId(event)
747                 val node = createNodeInfo(nodeId)
748                 assertThat("Accessibility focus on second heading", node.contentDescription as String, startsWith("Popcorn shrimp"))
749                 if (Build.VERSION.SDK_INT >= 19) {
750                     assertThat("Second heading is level 2",
751                             node.extras.getCharSequence("AccessibilityNodeInfo.roleDescription")!!.toString(),
752                             equalTo("heading level 2"))
753                 }
754             }
755         })
756 
757         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, bundle)
758         sessionRule.waitUntilCalled(object : EventDelegate {
759             @AssertCalled(count = 1)
760             override fun onAccessibilityFocused(event: AccessibilityEvent) {
761                 nodeId = getSourceId(event)
762                 val node = createNodeInfo(nodeId)
763                 assertThat("Accessibility focus on second heading", node.contentDescription as String, startsWith("Chicken fingers"))
764                 if (Build.VERSION.SDK_INT >= 19) {
765                     assertThat("Third heading is level 3",
766                             node.extras.getCharSequence("AccessibilityNodeInfo.roleDescription")!!.toString(),
767                             equalTo("heading level 3"))
768                 }
769             }
770         })
771     }
772 
773     @Test fun testCheckbox() {
774         var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID;
775         loadTestPage("test-checkbox")
776         waitForInitialFocus(true)
777 
778         sessionRule.waitUntilCalled(object : EventDelegate {
779             @AssertCalled(count = 1)
780             override fun onAccessibilityFocused(event: AccessibilityEvent) {
781                 nodeId = getSourceId(event)
782                 var node = createNodeInfo(nodeId)
783                 assertThat("Checkbox node is checkable", node.isCheckable, equalTo(true))
784                 assertThat("Checkbox node is clickable", node.isClickable, equalTo(true))
785                 assertThat("Checkbox node is focusable", node.isFocusable, equalTo(true))
786                 assertThat("Checkbox node is not checked", node.isChecked, equalTo(false))
787                 assertThat("Checkbox node has correct role", node.text.toString(), equalTo("many option"))
788                 if (Build.VERSION.SDK_INT >= 19) {
789                     assertThat("Hint has description", node.extras.getString("AccessibilityNodeInfo.hint"),
790                             equalTo("description"))
791                 }
792 
793             }
794         })
795 
796         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_CLICK, null)
797         waitUntilClick(true)
798 
799         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_CLICK, null)
800         waitUntilClick(false)
801     }
802 
803     @Test fun testExpandable() {
804         var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID;
805         loadTestPage("test-expandable")
806         waitForInitialFocus(true)
807 
808         sessionRule.waitUntilCalled(object : EventDelegate {
809             @AssertCalled(count = 1)
810             override fun onAccessibilityFocused(event: AccessibilityEvent) {
811                 nodeId = getSourceId(event)
812                 if (Build.VERSION.SDK_INT >= 21) {
813                     val node = createNodeInfo(nodeId)
814                     assertThat("button is expandable", node.actionList, hasItem(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND))
815                     assertThat("button is not collapsable", node.actionList, not(hasItem(AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE)))
816                 }
817             }
818         })
819 
820         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_EXPAND, null)
821         sessionRule.waitUntilCalled(object : EventDelegate {
822             @AssertCalled(count = 1)
823             override fun onClicked(event: AccessibilityEvent) {
824                 assertThat("Clicked event is from same node", getSourceId(event), equalTo(nodeId))
825                 if (Build.VERSION.SDK_INT >= 21) {
826                     val node = createNodeInfo(nodeId)
827                     assertThat("button is collapsable", node.actionList, hasItem(AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE))
828                     assertThat("button is not expandable", node.actionList, not(hasItem(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND)))
829                 }
830             }
831         })
832 
833         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_COLLAPSE, null)
834         sessionRule.waitUntilCalled(object : EventDelegate {
835             @AssertCalled(count = 1)
836             override fun onClicked(event: AccessibilityEvent) {
837                 assertThat("Clicked event is from same node", getSourceId(event), equalTo(nodeId))
838                 if (Build.VERSION.SDK_INT >= 21) {
839                     val node = createNodeInfo(nodeId)
840                     assertThat("button is expandable", node.actionList, hasItem(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND))
841                     assertThat("button is not collapsable", node.actionList, not(hasItem(AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE)))
842                 }
843             }
844         })
845     }
846 
847     @Test fun testSelectable() {
848         var nodeId = View.NO_ID
849         loadTestPage("test-selectable")
850         waitForInitialFocus(true)
851 
852         sessionRule.waitUntilCalled(object : EventDelegate {
853             @AssertCalled(count = 1)
854             override fun onAccessibilityFocused(event: AccessibilityEvent) {
855                 nodeId = getSourceId(event)
856                 var node = createNodeInfo(nodeId)
857                 assertThat("Selectable node is clickable", node.isClickable, equalTo(true))
858                 assertThat("Selectable node is not selected", node.isSelected, equalTo(false))
859                 assertThat("Selectable node has correct text", node.text.toString(), equalTo("1"))
860             }
861         })
862 
863         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_CLICK, null)
864         waitUntilSelect(true)
865 
866         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_CLICK, null)
867         waitUntilSelect(false)
868 
869         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_SELECT, null)
870         waitUntilSelect(true)
871 
872         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_SELECT, null)
873         waitUntilSelect(false)
874     }
875 
876     @Test fun testMutation() {
877         loadTestPage("test-mutation")
878         waitForInitialFocus()
879 
880         val rootNode = createNodeInfo(View.NO_ID)
881         assertThat("Document has 1 child", rootNode.childCount, equalTo(1))
882 
883         assertThat("Section has 1 child",
884                 createNodeInfo(rootNode.getChildId(0)).childCount, equalTo(1))
885         mainSession.evaluateJS("document.querySelector('#to_show').style.display = 'none';")
886         sessionRule.waitUntilCalled(object : EventDelegate {
887             @AssertCalled(count = 0)
888             override fun onAnnouncement(event: AccessibilityEvent) { }
889 
890             @AssertCalled(count = 1)
891             override fun onWinContentChanged(event: AccessibilityEvent) { }
892         })
893 
894         assertThat("Section has no children",
895                 createNodeInfo(rootNode.getChildId(0)).childCount, equalTo(0))
896     }
897 
898     @Test fun testLiveRegion() {
899         loadTestPage("test-live-region")
900         waitForInitialFocus()
901 
902         mainSession.evaluateJS("document.querySelector('#to_change').textContent = 'Hello';")
903         sessionRule.waitUntilCalled(object : EventDelegate {
904             @AssertCalled(count = 1)
905             override fun onAnnouncement(event: AccessibilityEvent) {
906                 assertThat("Announcement is correct", event.text[0].toString(), equalTo("Hello"))
907             }
908 
909             @AssertCalled(count = 1)
910             override fun onWinContentChanged(event: AccessibilityEvent) { }
911         })
912     }
913 
914     @Test fun testLiveRegionDescendant() {
915         loadTestPage("test-live-region-descendant")
916         waitForInitialFocus()
917 
918         mainSession.evaluateJS("document.querySelector('#to_show').style.display = 'none';")
919         sessionRule.waitUntilCalled(object : EventDelegate {
920             @AssertCalled(count = 0)
921             override fun onAnnouncement(event: AccessibilityEvent) { }
922 
923             @AssertCalled(count = 1)
924             override fun onWinContentChanged(event: AccessibilityEvent) { }
925         })
926 
927         mainSession.evaluateJS("document.querySelector('#to_show').style.display = 'block';")
928         sessionRule.waitUntilCalled(object : EventDelegate {
929             @AssertCalled(count = 1)
930             override fun onAnnouncement(event: AccessibilityEvent) {
931                 assertThat("Announcement is correct", event.text[0].toString(), equalTo("I will be shown"))
932             }
933 
934             @AssertCalled(count = 1)
935             override fun onWinContentChanged(event: AccessibilityEvent) { }
936         })
937     }
938 
939     @Test fun testLiveRegionAtomic() {
940         loadTestPage("test-live-region-atomic")
941         waitForInitialFocus()
942 
943         mainSession.evaluateJS("document.querySelector('p').textContent = '4pm';")
944         sessionRule.waitUntilCalled(object : EventDelegate {
945             @AssertCalled(count = 1)
946             override fun onAnnouncement(event: AccessibilityEvent) {
947                 assertThat("Announcement is correct", event.text[0].toString(), equalTo("The time is 4pm"))
948             }
949 
950             @AssertCalled(count = 1)
951             override fun onWinContentChanged(event: AccessibilityEvent) { }
952         })
953 
954         mainSession.evaluateJS("document.querySelector('#container').removeAttribute('aria-atomic');" +
955                 "document.querySelector('p').textContent = '5pm';")
956         sessionRule.waitUntilCalled(object : EventDelegate {
957             @AssertCalled(count = 1)
958             override fun onAnnouncement(event: AccessibilityEvent) {
959                 assertThat("Announcement is correct", event.text[0].toString(), equalTo("5pm"))
960             }
961 
962             @AssertCalled(count = 1)
963             override fun onWinContentChanged(event: AccessibilityEvent) { }
964         })
965     }
966 
967     @Test fun testLiveRegionImage() {
968         loadTestPage("test-live-region-image")
969         waitForInitialFocus()
970 
971         mainSession.evaluateJS("document.querySelector('img').alt = 'sad';")
972         sessionRule.waitUntilCalled(object : EventDelegate {
973             @AssertCalled(count = 1)
974             override fun onAnnouncement(event: AccessibilityEvent) {
975                 assertThat("Announcement is correct", event.text[0].toString(), equalTo("This picture is sad"))
976             }
977         })
978     }
979 
980     @Test fun testLiveRegionImageLabeledBy() {
981         loadTestPage("test-live-region-image-labeled-by")
982         waitForInitialFocus()
983 
984         mainSession.evaluateJS("document.querySelector('img').setAttribute('aria-labelledby', 'l2');")
985         sessionRule.waitUntilCalled(object : EventDelegate {
986             @AssertCalled(count = 1)
987             override fun onAnnouncement(event: AccessibilityEvent) {
988                 assertThat("Announcement is correct", event.text[0].toString(), equalTo("Goodbye"))
989             }
990         })
991     }
992 
993     private fun screenContainsNode(nodeId: Int): Boolean {
994         var node = createNodeInfo(nodeId)
995         var nodeBounds = Rect()
996         node.getBoundsInScreen(nodeBounds)
997         return screenRect.contains(nodeBounds)
998     }
999 
1000     @Ignore // Bug 1506276 - We need to reliably wait for APZC here, and it's not trivial.
1001     @Test fun testScroll() {
1002         var nodeId = View.NO_ID
1003         loadTestPage("test-scroll.html")
1004 
1005         sessionRule.waitUntilCalled(object : EventDelegate {
1006             @AssertCalled
1007             override fun onWinStateChanged(event: AccessibilityEvent) { }
1008 
1009             @AssertCalled(count = 1)
1010             @Suppress("deprecation")
1011             override fun onFocused(event: AccessibilityEvent) {
1012                 nodeId = getSourceId(event)
1013                 var node = createNodeInfo(nodeId)
1014                 var nodeBounds = Rect()
1015                 node.getBoundsInParent(nodeBounds)
1016                 assertThat("Default root node bounds are correct", nodeBounds, equalTo(screenRect))
1017             }
1018         })
1019 
1020         provider.performAction(View.NO_ID, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, null)
1021         sessionRule.waitUntilCalled(object : EventDelegate {
1022             @AssertCalled(count = 1, order = [1])
1023             override fun onAccessibilityFocused(event: AccessibilityEvent) {
1024                 nodeId = getSourceId(event)
1025                 assertThat("Focused node is onscreen", screenContainsNode(nodeId), equalTo(true))
1026             }
1027 
1028             @AssertCalled(count = 1, order = [2])
1029             override fun onScrolled(event: AccessibilityEvent) {
1030                 assertThat("View is scrolled for focused node to be onscreen", event.scrollY, greaterThan(0))
1031                 assertThat("View is not scrolled to the end", event.scrollY, lessThan(event.maxScrollY))
1032             }
1033 
1034             @AssertCalled(count = 1, order = [3])
1035             override fun onWinContentChanged(event: AccessibilityEvent) {
1036                 assertThat("Focused node is onscreen", screenContainsNode(nodeId), equalTo(true))
1037             }
1038         })
1039 
1040         SystemClock.sleep(100);
1041         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_SCROLL_FORWARD, null)
1042         sessionRule.waitUntilCalled(object : EventDelegate {
1043             @AssertCalled(count = 1, order = [1])
1044             override fun onScrolled(event: AccessibilityEvent) {
1045                 assertThat("View is scrolled to the end", event.scrollY.toDouble(), closeTo(event.maxScrollY.toDouble(), 1.0))
1046             }
1047 
1048             @AssertCalled(count = 1, order = [2])
1049             override fun onWinContentChanged(event: AccessibilityEvent) {
1050                 assertThat("Focused node is still onscreen", screenContainsNode(nodeId), equalTo(true))
1051             }
1052         })
1053 
1054         SystemClock.sleep(100)
1055         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD, null)
1056         sessionRule.waitUntilCalled(object : EventDelegate {
1057             @AssertCalled(count = 1, order = [1])
1058             override fun onScrolled(event: AccessibilityEvent) {
1059                 assertThat("View is scrolled to the beginning", event.scrollY, equalTo(0))
1060             }
1061 
1062             @AssertCalled(count = 1, order = [2])
1063             override fun onWinContentChanged(event: AccessibilityEvent) {
1064                 assertThat("Focused node is offscreen", screenContainsNode(nodeId), equalTo(false))
1065             }
1066         })
1067 
1068         SystemClock.sleep(100)
1069         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, null)
1070         sessionRule.waitUntilCalled(object : EventDelegate {
1071             @AssertCalled(count = 1, order = [1])
1072             override fun onAccessibilityFocused(event: AccessibilityEvent) {
1073                 nodeId = getSourceId(event)
1074                 assertThat("Focused node is onscreen", screenContainsNode(nodeId), equalTo(true))
1075             }
1076 
1077             @AssertCalled(count = 1, order = [2])
1078             override fun onScrolled(event: AccessibilityEvent) {
1079                 assertThat("View is scrolled to the end", event.scrollY.toDouble(), closeTo(event.maxScrollY.toDouble(), 1.0))
1080             }
1081 
1082             @AssertCalled(count = 1, order = [3])
1083             override fun onWinContentChanged(event: AccessibilityEvent) {
1084                 assertThat("Focused node is onscreen", screenContainsNode(nodeId), equalTo(true))
1085             }
1086         })
1087     }
1088 
1089     @Setting(key = Setting.Key.FULL_ACCESSIBILITY_TREE, value = "true")
1090     @Test fun autoFill() {
1091         // Wait for the accessibility nodes to populate.
1092         mainSession.loadTestPath(FORMS_HTML_PATH)
1093         waitForInitialFocus()
1094 
1095         val autoFills = mapOf(
1096                 "#user1" to "bar", "#pass1" to "baz", "#user2" to "bar", "#pass2" to "baz") +
1097                 if (Build.VERSION.SDK_INT >= 19) mapOf(
1098                         "#email1" to "a@b.c", "#number1" to "24", "#tel1" to "42")
1099                 else mapOf(
1100                         "#email1" to "bar", "#number1" to "", "#tel1" to "bar")
1101 
1102         // Set up promises to monitor the values changing.
1103         val promises = autoFills.flatMap { entry ->
1104             // Repeat each test with both the top document and the iframe document.
1105             arrayOf("document", "document.querySelector('#iframe').contentDocument").map { doc ->
1106                 mainSession.evaluatePromiseJS("""new Promise(resolve =>
1107                     $doc.querySelector('${entry.key}').addEventListener(
1108                         'input', event => {
1109                           let eventInterface =
1110                             event instanceof $doc.defaultView.InputEvent ? "InputEvent" :
1111                             event instanceof $doc.defaultView.UIEvent ? "UIEvent" :
1112                             event instanceof $doc.defaultView.Event ? "Event" : "Unknown";
1113                           resolve([event.target.value, '${entry.value}', eventInterface]);
1114                         }, { once: true }))""")
1115             }
1116         }
1117 
1118         // Perform auto-fill and return number of auto-fills performed.
1119         fun autoFillChild(id: Int, child: AccessibilityNodeInfo) {
1120             // Seal the node info instance so we can perform actions on it.
1121             if (child.childCount > 0) {
1122                 for (i in 0 until child.childCount) {
1123                     val childId = child.getChildId(i)
1124                     autoFillChild(childId, createNodeInfo(childId))
1125                 }
1126             }
1127 
1128             if (EditText::class.java.name == child.className) {
1129                 assertThat("Input should be enabled", child.isEnabled, equalTo(true))
1130                 assertThat("Input should be focusable", child.isFocusable, equalTo(true))
1131                 if (Build.VERSION.SDK_INT >= 19) {
1132                     assertThat("Password type should match", child.isPassword, equalTo(
1133                             child.inputType == InputType.TYPE_CLASS_TEXT or
1134                                     InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD))
1135                 }
1136 
1137                 val args = Bundle(1)
1138                 val value = if (child.isPassword) "baz" else
1139                     if (Build.VERSION.SDK_INT < 19) "bar" else
1140                         when (child.inputType) {
1141                             InputType.TYPE_CLASS_TEXT or
1142                                     InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS -> "a@b.c"
1143                             InputType.TYPE_CLASS_NUMBER -> "24"
1144                             InputType.TYPE_CLASS_PHONE -> "42"
1145                             else -> "bar"
1146                         }
1147 
1148                 val ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE = if (Build.VERSION.SDK_INT >= 21)
1149                     AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE else
1150                     "ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE"
1151                 val ACTION_SET_TEXT = if (Build.VERSION.SDK_INT >= 21)
1152                     AccessibilityNodeInfo.ACTION_SET_TEXT else 0x200000
1153 
1154                 args.putCharSequence(ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, value)
1155                 assertThat("Can perform auto-fill",
1156                            provider.performAction(id, ACTION_SET_TEXT, args), equalTo(true))
1157             }
1158         }
1159 
1160         autoFillChild(View.NO_ID, createNodeInfo(View.NO_ID))
1161 
1162         // Wait on the promises and check for correct values.
1163         for ((actual, expected, eventInterface) in promises.map { it.value.asJSList<String>() }) {
1164             assertThat("Auto-filled value must match", actual, equalTo(expected))
1165             assertThat("input event should be dispatched with InputEvent interface", eventInterface, equalTo("InputEvent"))
1166         }
1167     }
1168 
1169     @Setting(key = Setting.Key.FULL_ACCESSIBILITY_TREE, value = "true")
1170     @Test fun autoFill_navigation() {
1171         // Fails with BFCache in the parent.
1172         // https://bugzilla.mozilla.org/show_bug.cgi?id=1715480
1173         sessionRule.setPrefsUntilTestEnd(mapOf(
1174                 "fission.bfcacheInParent" to false))
1175         // disable test on debug for frequently failing #Bug 1505353
1176         assumeThat(sessionRule.env.isDebugBuild, equalTo(false))
1177         fun countAutoFillNodes(cond: (AccessibilityNodeInfo) -> Boolean =
1178                                        { it.className == "android.widget.EditText" },
1179                                id: Int = View.NO_ID): Int {
1180             val info = createNodeInfo(id)
1181             return (if (cond(info) && info.className != "android.webkit.WebView" ) 1 else 0) + (if (info.childCount > 0)
1182                 (0 until info.childCount).sumBy {
1183                     countAutoFillNodes(cond, info.getChildId(it))
1184                 } else 0)
1185         }
1186 
1187         // Wait for the accessibility nodes to populate.
1188         mainSession.loadTestPath(FORMS_HTML_PATH)
1189         waitForInitialFocus()
1190 
1191         assertThat("Initial auto-fill count should match",
1192                    countAutoFillNodes(), equalTo(18))
1193         assertThat("Password auto-fill count should match",
1194                    countAutoFillNodes({ it.isPassword }), equalTo(4))
1195 
1196         // Now wait for the nodes to clear.
1197         mainSession.loadTestPath(HELLO_HTML_PATH)
1198         waitForInitialFocus()
1199         assertThat("Should not have auto-fill fields",
1200                    countAutoFillNodes(), equalTo(0))
1201 
1202         // Now wait for the nodes to reappear.
1203         mainSession.goBack()
1204         waitForInitialFocus()
1205         assertThat("Should have auto-fill fields again",
1206                    countAutoFillNodes(), equalTo(18))
1207         assertThat("Should not have focused field",
1208                    countAutoFillNodes({ it.isFocused }), equalTo(0))
1209 
1210         mainSession.evaluateJS("document.querySelector('#pass1').focus()")
1211         sessionRule.waitUntilCalled(object : EventDelegate {
1212             @AssertCalled
1213             override fun onFocused(event: AccessibilityEvent) {
1214             }
1215         })
1216         assertThat("Should have one focused field",
1217                    countAutoFillNodes({ it.isFocused }), equalTo(1))
1218 
1219         mainSession.evaluateJS("document.querySelector('#pass1').blur()")
1220         sessionRule.waitUntilCalled(object : EventDelegate {
1221             @AssertCalled
1222             override fun onFocused(event: AccessibilityEvent) {
1223             }
1224         })
1225         assertThat("Should not have focused field",
1226                    countAutoFillNodes({ it.isFocused }), equalTo(0))
1227     }
1228 
1229     @Setting(key = Setting.Key.FULL_ACCESSIBILITY_TREE, value = "true")
1230     @Test fun testTree() {
1231         loadTestPage("test-tree")
1232         waitForInitialFocus()
1233 
1234         val rootNode = createNodeInfo(View.NO_ID)
1235         assertThat("Document has 3 children", rootNode.childCount, equalTo(3))
1236 
1237         val labelNode = createNodeInfo(rootNode.getChildId(0))
1238         assertThat("First node is a label", labelNode.className.toString(), equalTo("android.view.View"))
1239         assertThat("Label has text", labelNode.text.toString(), equalTo("Name:"))
1240 
1241         val entryNode = createNodeInfo(rootNode.getChildId(1))
1242         assertThat("Second node is an entry", entryNode.className.toString(), equalTo("android.widget.EditText"))
1243         assertThat("Entry has vieIdwResourceName of 'name'", entryNode.viewIdResourceName, equalTo("name"))
1244         assertThat("Entry value is text", entryNode.text.toString(), equalTo("Julie"))
1245         if (Build.VERSION.SDK_INT >= 19) {
1246             assertThat("Entry hint is label",
1247                     entryNode.extras.getString("AccessibilityNodeInfo.hint"),
1248                     equalTo("Name:"))
1249             assertThat("Entry input type is correct", entryNode.inputType,
1250                     equalTo(InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT))
1251         }
1252 
1253         val buttonNode = createNodeInfo(rootNode.getChildId(2))
1254         assertThat("Last node is a button", buttonNode.className.toString(), equalTo("android.widget.Button"))
1255         // The child text leaf is pruned, so this button is childless.
1256         assertThat("Button has a single text leaf", buttonNode.childCount, equalTo(0))
1257         assertThat("Button has correct text", buttonNode.text.toString(), equalTo("Submit"))
1258     }
1259 
1260     @Setting(key = Setting.Key.FULL_ACCESSIBILITY_TREE, value = "true")
1261     @Test fun testCollection() {
1262         loadTestPage("test-collection")
1263         waitForInitialFocus()
1264 
1265         val rootNode = createNodeInfo(View.NO_ID)
1266         assertThat("Document has 2 children", rootNode.childCount, equalTo(2))
1267 
1268         val firstList = createNodeInfo(rootNode.getChildId(0))
1269         assertThat("First list has 2 children", firstList.childCount, equalTo(2))
1270         assertThat("List is a ListView", firstList.className.toString(), equalTo("android.widget.ListView"))
1271         if (Build.VERSION.SDK_INT >= 19) {
1272             assertThat("First list should have collectionInfo", firstList.collectionInfo, notNullValue())
1273             assertThat("First list has 2 rowCount", firstList.collectionInfo.rowCount, equalTo(2))
1274             assertThat("First list should not be hierarchical", firstList.collectionInfo.isHierarchical, equalTo(false))
1275         }
1276 
1277         val firstListFirstItem = createNodeInfo(firstList.getChildId(0))
1278         if (Build.VERSION.SDK_INT >= 19) {
1279             assertThat("Item has collectionItemInfo", firstListFirstItem.collectionItemInfo, notNullValue())
1280             assertThat("Item has collectionItemInfo", firstListFirstItem.collectionItemInfo.rowIndex, equalTo(1))
1281         }
1282 
1283         val secondList = createNodeInfo(rootNode.getChildId(1))
1284         assertThat("Second list has 1 child", secondList.childCount, equalTo(1))
1285         if (Build.VERSION.SDK_INT >= 19) {
1286             assertThat("Second list should have collectionInfo", secondList.collectionInfo, notNullValue())
1287             assertThat("Second list has 2 rowCount", secondList.collectionInfo.rowCount, equalTo(1))
1288             assertThat("Second list should be hierarchical", secondList.collectionInfo.isHierarchical, equalTo(true))
1289         }
1290     }
1291 
1292     @Setting(key = Setting.Key.FULL_ACCESSIBILITY_TREE, value = "true")
1293     @Test fun testRange() {
1294         loadTestPage("test-range")
1295         waitForInitialFocus()
1296 
1297         val rootNode = createNodeInfo(View.NO_ID)
1298         assertThat("Document has 3 children", rootNode.childCount, equalTo(3))
1299 
1300         val firstRange = createNodeInfo(rootNode.getChildId(0))
1301         assertThat("Range has right label", firstRange.text.toString(), equalTo("Rating"))
1302         assertThat("Range is SeekBar", firstRange.className.toString(), equalTo("android.widget.SeekBar"))
1303         if (Build.VERSION.SDK_INT >= 19) {
1304             assertThat("'Rating' has rangeInfo", firstRange.rangeInfo, notNullValue())
1305             assertThat("'Rating' has correct value", firstRange.rangeInfo.current, equalTo(4f))
1306             assertThat("'Rating' has correct max", firstRange.rangeInfo.max, equalTo(10f))
1307             assertThat("'Rating' has correct min", firstRange.rangeInfo.min, equalTo(1f))
1308             assertThat("'Rating' has correct range type", firstRange.rangeInfo.type, equalTo(AccessibilityNodeInfo.RangeInfo.RANGE_TYPE_INT))
1309         }
1310 
1311         val secondRange = createNodeInfo(rootNode.getChildId(1))
1312         assertThat("Range has right label", secondRange.text.toString(), equalTo("Stars"))
1313         if (Build.VERSION.SDK_INT >= 19) {
1314             assertThat("'Rating' has rangeInfo", secondRange.rangeInfo, notNullValue())
1315             assertThat("'Rating' has correct value", secondRange.rangeInfo.current, equalTo(4.5f))
1316             assertThat("'Rating' has correct max", secondRange.rangeInfo.max, equalTo(5f))
1317             assertThat("'Rating' has correct min", secondRange.rangeInfo.min, equalTo(1f))
1318             assertThat("'Rating' has correct range type", secondRange.rangeInfo.type, equalTo(AccessibilityNodeInfo.RangeInfo.RANGE_TYPE_FLOAT))
1319         }
1320 
1321         val thirdRange = createNodeInfo(rootNode.getChildId(2))
1322         assertThat("Range has right label", thirdRange.text.toString(), equalTo("Percent"))
1323         if (Build.VERSION.SDK_INT >= 19) {
1324             assertThat("'Rating' has rangeInfo", thirdRange.rangeInfo, notNullValue())
1325             assertThat("'Rating' has correct value", thirdRange.rangeInfo.current, equalTo(0.83f))
1326             assertThat("'Rating' has correct max", thirdRange.rangeInfo.max, equalTo(1f))
1327             assertThat("'Rating' has correct min", thirdRange.rangeInfo.min, equalTo(0f))
1328             assertThat("'Rating' has correct range type", thirdRange.rangeInfo.type, equalTo(AccessibilityNodeInfo.RangeInfo.RANGE_TYPE_PERCENT))
1329         }
1330     }
1331 
1332     @Test fun testLinksMovingByDefault() {
1333         loadTestPage("test-links")
1334         waitForInitialFocus()
1335         var nodeId = View.NO_ID;
1336 
1337         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, null)
1338         sessionRule.waitUntilCalled(object : EventDelegate {
1339             @AssertCalled(count = 1)
1340             override fun onAccessibilityFocused(event: AccessibilityEvent) {
1341                 nodeId = getSourceId(event)
1342                 val node = createNodeInfo(nodeId)
1343                 assertThat("Accessibility focus on a with href",
1344                     node.contentDescription as String, startsWith("a with href"))
1345                 if (Build.VERSION.SDK_INT >= 19) {
1346                     assertThat("a with href is a link",
1347                             node.extras.getCharSequence("AccessibilityNodeInfo.roleDescription")!!.toString(),
1348                             equalTo("link"))
1349                 }
1350             }
1351         })
1352 
1353         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, null)
1354         sessionRule.waitUntilCalled(object : EventDelegate {
1355             @AssertCalled(count = 1)
1356             override fun onAccessibilityFocused(event: AccessibilityEvent) {
1357                 nodeId = getSourceId(event)
1358                 val node = createNodeInfo(nodeId)
1359                 assertThat("Accessibility focus on a with no attributes",
1360                     node.text as String, startsWith("a with no attributes"))
1361                 if (Build.VERSION.SDK_INT >= 19) {
1362                     assertThat("a with no attributes is not a link",
1363                             node.extras.getCharSequence("AccessibilityNodeInfo.roleDescription")!!.toString(),
1364                             equalTo(""))
1365                 }
1366             }
1367         })
1368 
1369         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, null)
1370         sessionRule.waitUntilCalled(object : EventDelegate {
1371             @AssertCalled(count = 1)
1372             override fun onAccessibilityFocused(event: AccessibilityEvent) {
1373                 nodeId = getSourceId(event)
1374                 val node = createNodeInfo(nodeId)
1375                 assertThat("Accessibility focus on a with name",
1376                     node.text as String, startsWith("a with name"))
1377                 if (Build.VERSION.SDK_INT >= 19) {
1378                     assertThat("a with name is not a link",
1379                             node.extras.getCharSequence("AccessibilityNodeInfo.roleDescription")!!.toString(),
1380                             equalTo(""))
1381                 }
1382             }
1383         })
1384 
1385         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, null)
1386         sessionRule.waitUntilCalled(object : EventDelegate {
1387             @AssertCalled(count = 1)
1388             override fun onAccessibilityFocused(event: AccessibilityEvent) {
1389                 nodeId = getSourceId(event)
1390                 val node = createNodeInfo(nodeId)
1391                 assertThat("Accessibility focus on a with onclick",
1392                     node.contentDescription as String, startsWith("a with onclick"))
1393                 if (Build.VERSION.SDK_INT >= 19) {
1394                     assertThat("a with onclick is a link",
1395                             node.extras.getCharSequence("AccessibilityNodeInfo.roleDescription")!!.toString(),
1396                             equalTo("link"))
1397                 }
1398             }
1399         })
1400 
1401         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, null)
1402         sessionRule.waitUntilCalled(object : EventDelegate {
1403             @AssertCalled(count = 1)
1404             override fun onAccessibilityFocused(event: AccessibilityEvent) {
1405                 nodeId = getSourceId(event)
1406                 val node = createNodeInfo(nodeId)
1407                 assertThat("Accessibility focus on span with role link",
1408                     node.contentDescription as String, startsWith("span with role link"))
1409                 if (Build.VERSION.SDK_INT >= 19) {
1410                     assertThat("span with role link is a link",
1411                             node.extras.getCharSequence("AccessibilityNodeInfo.roleDescription")!!.toString(),
1412                             equalTo("link"))
1413                 }
1414             }
1415         })
1416     }
1417 
1418     @Test fun testLinksMovingByLink() {
1419         loadTestPage("test-links")
1420         waitForInitialFocus()
1421         var nodeId = View.NO_ID;
1422 
1423         val bundle = Bundle()
1424         bundle.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "LINK")
1425 
1426         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, bundle)
1427         sessionRule.waitUntilCalled(object : EventDelegate {
1428             @AssertCalled(count = 1)
1429             override fun onAccessibilityFocused(event: AccessibilityEvent) {
1430                 nodeId = getSourceId(event)
1431                 val node = createNodeInfo(nodeId)
1432                 assertThat("Accessibility focus on a with href",
1433                     node.contentDescription as String, startsWith("a with href"))
1434             }
1435         })
1436 
1437         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, bundle)
1438         sessionRule.waitUntilCalled(object : EventDelegate {
1439             @AssertCalled(count = 1)
1440             override fun onAccessibilityFocused(event: AccessibilityEvent) {
1441                 nodeId = getSourceId(event)
1442                 val node = createNodeInfo(nodeId)
1443                 assertThat("Accessibility focus on a with onclick",
1444                     node.contentDescription as String, startsWith("a with onclick"))
1445             }
1446         })
1447 
1448         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, bundle)
1449         sessionRule.waitUntilCalled(object : EventDelegate {
1450             @AssertCalled(count = 1)
1451             override fun onAccessibilityFocused(event: AccessibilityEvent) {
1452                 nodeId = getSourceId(event)
1453                 val node = createNodeInfo(nodeId)
1454                 assertThat("Accessibility focus on span with role link",
1455                     node.contentDescription as String, startsWith("span with role link"))
1456             }
1457         })
1458     }
1459 
1460     @Test fun testAriaComboBoxesMovingByDefault() {
1461         loadTestPage("test-aria-comboboxes")
1462         waitForInitialFocus()
1463         var nodeId = View.NO_ID;
1464 
1465         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, null)
1466         sessionRule.waitUntilCalled(object : EventDelegate {
1467             @AssertCalled(count = 1)
1468             override fun onAccessibilityFocused(event: AccessibilityEvent) {
1469                 nodeId = getSourceId(event)
1470                 val node = createNodeInfo(nodeId)
1471                 assertThat("Accessibility focus is EditBox",
1472                         node.className.toString(),
1473                         equalTo("android.widget.EditText"))
1474                 if (Build.VERSION.SDK_INT >= 19) {
1475                     assertThat("Accessibility focus on ARIA 1.0 combobox",
1476                             node.extras.getString("AccessibilityNodeInfo.hint"),
1477                             equalTo("ARIA 1.0 combobox"))
1478                 }
1479             }
1480         })
1481 
1482         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, null)
1483         sessionRule.waitUntilCalled(object : EventDelegate {
1484             @AssertCalled(count = 1)
1485             override fun onAccessibilityFocused(event: AccessibilityEvent) {
1486                 nodeId = getSourceId(event)
1487                 val node = createNodeInfo(nodeId)
1488                 assertThat("Accessibility focus is EditBox",
1489                         node.className.toString(),
1490                         equalTo("android.widget.EditText"))
1491                 if (Build.VERSION.SDK_INT >= 19) {
1492                     assertThat("Accessibility focus on ARIA 1.1 combobox",
1493                             node.extras.getString("AccessibilityNodeInfo.hint"),
1494                             equalTo("ARIA 1.1 combobox"))
1495                 }
1496             }
1497         })
1498     }
1499 
1500     @Test fun testAriaComboBoxesMovingByControl() {
1501         loadTestPage("test-aria-comboboxes")
1502         waitForInitialFocus()
1503         var nodeId = View.NO_ID;
1504 
1505         val bundle = Bundle()
1506         bundle.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "CONTROL")
1507 
1508         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, bundle)
1509         sessionRule.waitUntilCalled(object : EventDelegate {
1510             @AssertCalled(count = 1)
1511             override fun onAccessibilityFocused(event: AccessibilityEvent) {
1512                 nodeId = getSourceId(event)
1513                 val node = createNodeInfo(nodeId)
1514                 assertThat("Accessibility focus is EditBox",
1515                         node.className.toString(),
1516                         equalTo("android.widget.EditText"))
1517                 if (Build.VERSION.SDK_INT >= 19) {
1518                     assertThat("Accessibility focus on ARIA 1.0 combobox",
1519                             node.extras.getString("AccessibilityNodeInfo.hint"),
1520                             equalTo("ARIA 1.0 combobox"))
1521                 }
1522             }
1523         })
1524 
1525         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, bundle)
1526         sessionRule.waitUntilCalled(object : EventDelegate {
1527             @AssertCalled(count = 1)
1528             override fun onAccessibilityFocused(event: AccessibilityEvent) {
1529                 nodeId = getSourceId(event)
1530                 val node = createNodeInfo(nodeId)
1531                 assertThat("Accessibility focus is EditBox",
1532                         node.className.toString(),
1533                         equalTo("android.widget.EditText"))
1534                 if (Build.VERSION.SDK_INT >= 19) {
1535                     assertThat("Accessibility focus on ARIA 1.1 combobox",
1536                             node.extras.getString("AccessibilityNodeInfo.hint"),
1537                             equalTo("ARIA 1.1 combobox"))
1538                 }
1539             }
1540         })
1541     }
1542 
1543     @Test fun testAccessibilityFocusBoundaries() {
1544         loadTestPage("test-links")
1545         waitForInitialFocus()
1546         var nodeId = View.NO_ID
1547         var performedAction: Boolean
1548 
1549         performedAction = provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, null)
1550         assertThat("Successfully moved a11y focus to first node", performedAction, equalTo(true))
1551         sessionRule.waitUntilCalled(object : EventDelegate {
1552             @AssertCalled(count = 1)
1553             override fun onAccessibilityFocused(event: AccessibilityEvent) {
1554                 nodeId = getSourceId(event)
1555                 val node = createNodeInfo(nodeId)
1556                 assertThat("Accessibility focus on a with href",
1557                         node.contentDescription as String, startsWith("a with href"))
1558             }
1559         })
1560 
1561         performedAction = provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT, null)
1562         assertThat("Successfully moved a11y focus past first node", performedAction, equalTo(true))
1563         sessionRule.waitUntilCalled(object : EventDelegate {
1564             @AssertCalled(count = 1)
1565             override fun onAccessibilityFocused(event: AccessibilityEvent) {
1566                 assertThat("Accessibility focus on web view", getSourceId(event), equalTo(View.NO_ID))
1567             }
1568         })
1569 
1570         performedAction = provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, null)
1571         assertThat("Successfully moved a11y focus to second node", performedAction, equalTo(true))
1572         sessionRule.waitUntilCalled(object : EventDelegate {
1573             @AssertCalled(count = 1)
1574             override fun onAccessibilityFocused(event: AccessibilityEvent) {
1575                 nodeId = getSourceId(event)
1576                 val node = createNodeInfo(nodeId)
1577                 assertThat("Accessibility focus on a with no attributes",
1578                         node.text as String, startsWith("a with no attributes"))
1579             }
1580         })
1581 
1582         // hide first and last link
1583         mainSession.evaluateJS("document.querySelectorAll('body > :first-child, body > :last-child').forEach(e => e.style.display = 'none');")
1584         sessionRule.waitUntilCalled(object : EventDelegate {
1585             @AssertCalled(count = 1)
1586             override fun onWinContentChanged(event: AccessibilityEvent) { }
1587         })
1588 
1589         performedAction = provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT, null)
1590         assertThat("Successfully moved a11y focus past first visible node", performedAction, equalTo(true))
1591         sessionRule.waitUntilCalled(object : EventDelegate {
1592             @AssertCalled(count = 1)
1593             override fun onAccessibilityFocused(event: AccessibilityEvent) {
1594                 assertThat("Accessibility focus on web view", getSourceId(event), equalTo(View.NO_ID))
1595             }
1596         })
1597 
1598 
1599         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, null)
1600         sessionRule.waitUntilCalled(object : EventDelegate {
1601             @AssertCalled(count = 1)
1602             override fun onAccessibilityFocused(event: AccessibilityEvent) {
1603                 nodeId = getSourceId(event)
1604                 val node = createNodeInfo(nodeId)
1605                 assertThat("Accessibility focus on a with name",
1606                         node.text as String, startsWith("a with name"))
1607                 if (Build.VERSION.SDK_INT >= 19) {
1608                     assertThat("a with name is not a link",
1609                             node.extras.getCharSequence("AccessibilityNodeInfo.roleDescription")!!.toString(),
1610                             equalTo(""))
1611                 }
1612             }
1613         })
1614 
1615         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, null)
1616         sessionRule.waitUntilCalled(object : EventDelegate {
1617             @AssertCalled(count = 1)
1618             override fun onAccessibilityFocused(event: AccessibilityEvent) {
1619                 nodeId = getSourceId(event)
1620                 val node = createNodeInfo(nodeId)
1621                 assertThat("Accessibility focus on a with onclick",
1622                         node.contentDescription as String, startsWith("a with onclick"))
1623                 if (Build.VERSION.SDK_INT >= 19) {
1624                     assertThat("a with onclick is a link",
1625                             node.extras.getCharSequence("AccessibilityNodeInfo.roleDescription")!!.toString(),
1626                             equalTo("link"))
1627                 }
1628             }
1629         })
1630 
1631         performedAction = provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, null)
1632         assertThat("Should fail to move a11y focus to last hidden node", performedAction, equalTo(false))
1633 
1634         // show last link
1635         mainSession.evaluateJS("document.querySelector('body > :last-child').style.display = 'initial';")
1636         sessionRule.waitUntilCalled(object : EventDelegate {
1637             @AssertCalled(count = 1)
1638             override fun onWinContentChanged(event: AccessibilityEvent) { }
1639         })
1640 
1641         provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, null)
1642         sessionRule.waitUntilCalled(object : EventDelegate {
1643             @AssertCalled(count = 1)
1644             override fun onAccessibilityFocused(event: AccessibilityEvent) {
1645                 nodeId = getSourceId(event)
1646                 val node = createNodeInfo(nodeId)
1647                 assertThat("Accessibility focus on span with role link",
1648                         node.contentDescription as String, startsWith("span with role link"))
1649                 if (Build.VERSION.SDK_INT >= 19) {
1650                     assertThat("span with role link is a link",
1651                             node.extras.getCharSequence("AccessibilityNodeInfo.roleDescription")!!.toString(),
1652                             equalTo("link"))
1653                 }
1654             }
1655         })
1656 
1657         performedAction = provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, null)
1658         assertThat("Should fail to move a11y focus beyond last node", performedAction, equalTo(false))
1659 
1660         performedAction = provider.performAction(View.NO_ID, AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT, null)
1661         assertThat("Should fail to move a11y focus before web content", performedAction, equalTo(false))
1662     }
1663 
1664     @Test fun testTextEntry() {
1665         loadTestPage("test-text-entry-node")
1666         waitForInitialFocus()
1667 
1668         mainSession.evaluateJS("document.querySelector('input[aria-label=Name]').focus()")
1669         sessionRule.waitUntilCalled(object : EventDelegate {
1670             @AssertCalled(count = 1)
1671             override fun onFocused(event: AccessibilityEvent) {}
1672         })
1673 
1674         mainSession.evaluateJS("document.querySelector('input[aria-label=Name]').value = 'Tobiasas'")
1675 
1676         sessionRule.waitUntilCalled(object : EventDelegate {
1677             @AssertCalled(count = 1)
1678             override fun onTextChanged(event: AccessibilityEvent) {}
1679 
1680             @AssertCalled(count = 1)
1681             override fun onTextSelectionChanged(event: AccessibilityEvent) {}
1682 
1683             // Don't fire a11y focus for collapsed caret changes.
1684             // This will interfere with on screen keyboards and throw a11y focus
1685             // back and fourth.
1686             @AssertCalled(count = 0)
1687             override fun onAccessibilityFocused(event: AccessibilityEvent) {}
1688         })
1689     }
1690 
1691 }
1692