BrowserAccessibilityManager.java revision 34680572440d7894ef8dafce81d8039ed80726a2
1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package org.chromium.content.browser.accessibility;
6
7import android.content.Context;
8import android.graphics.Rect;
9import android.os.Build;
10import android.os.Bundle;
11import android.text.SpannableString;
12import android.text.style.URLSpan;
13import android.view.MotionEvent;
14import android.view.View;
15import android.view.ViewGroup;
16import android.view.ViewParent;
17import android.view.accessibility.AccessibilityEvent;
18import android.view.accessibility.AccessibilityManager;
19import android.view.accessibility.AccessibilityNodeInfo;
20import android.view.accessibility.AccessibilityNodeProvider;
21
22import org.chromium.base.CalledByNative;
23import org.chromium.base.JNINamespace;
24import org.chromium.content.browser.ContentViewCore;
25import org.chromium.content.browser.RenderCoordinates;
26
27import java.util.ArrayList;
28import java.util.List;
29import java.util.Locale;
30
31/**
32 * Native accessibility for a {@link ContentViewCore}.
33 *
34 * This class is safe to load on ICS and can be used to run tests, but
35 * only the subclass, JellyBeanBrowserAccessibilityManager, actually
36 * has a AccessibilityNodeProvider implementation needed for native
37 * accessibility.
38 */
39@JNINamespace("content")
40public class BrowserAccessibilityManager {
41    private static final String TAG = "BrowserAccessibilityManager";
42
43    private ContentViewCore mContentViewCore;
44    private final AccessibilityManager mAccessibilityManager;
45    private final RenderCoordinates mRenderCoordinates;
46    private long mNativeObj;
47    private int mAccessibilityFocusId;
48    private Rect mAccessibilityFocusRect;
49    private boolean mIsHovering;
50    private int mLastHoverId = View.NO_ID;
51    private int mCurrentRootId;
52    private final int[] mTempLocation = new int[2];
53    private final ViewGroup mView;
54    private boolean mUserHasTouchExplored;
55    private boolean mPendingScrollToMakeNodeVisible;
56    private boolean mNotifyFrameInfoInitializedCalled;
57
58    /**
59     * Create a BrowserAccessibilityManager object, which is owned by the C++
60     * BrowserAccessibilityManagerAndroid instance, and connects to the content view.
61     * @param nativeBrowserAccessibilityManagerAndroid A pointer to the counterpart native
62     *     C++ object that owns this object.
63     * @param contentViewCore The content view that this object provides accessibility for.
64     */
65    @CalledByNative
66    private static BrowserAccessibilityManager create(long nativeBrowserAccessibilityManagerAndroid,
67            ContentViewCore contentViewCore) {
68        // A bug in the KitKat framework prevents us from using these new APIs.
69        // http://crbug.com/348088/
70        // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
71        //     return new KitKatBrowserAccessibilityManager(
72        //             nativeBrowserAccessibilityManagerAndroid, contentViewCore);
73
74        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
75            return new JellyBeanBrowserAccessibilityManager(
76                    nativeBrowserAccessibilityManagerAndroid, contentViewCore);
77        } else {
78            return new BrowserAccessibilityManager(
79                    nativeBrowserAccessibilityManagerAndroid, contentViewCore);
80        }
81    }
82
83    protected BrowserAccessibilityManager(long nativeBrowserAccessibilityManagerAndroid,
84            ContentViewCore contentViewCore) {
85        mNativeObj = nativeBrowserAccessibilityManagerAndroid;
86        mContentViewCore = contentViewCore;
87        mContentViewCore.setBrowserAccessibilityManager(this);
88        mAccessibilityFocusId = View.NO_ID;
89        mIsHovering = false;
90        mCurrentRootId = View.NO_ID;
91        mView = mContentViewCore.getContainerView();
92        mRenderCoordinates = mContentViewCore.getRenderCoordinates();
93        mAccessibilityManager =
94            (AccessibilityManager) mContentViewCore.getContext()
95            .getSystemService(Context.ACCESSIBILITY_SERVICE);
96    }
97
98    @CalledByNative
99    private void onNativeObjectDestroyed() {
100        if (mContentViewCore.getBrowserAccessibilityManager() == this) {
101            mContentViewCore.setBrowserAccessibilityManager(null);
102        }
103        mNativeObj = 0;
104        mContentViewCore = null;
105    }
106
107    /**
108     * @return An AccessibilityNodeProvider on JellyBean, and null on previous versions.
109     */
110    public AccessibilityNodeProvider getAccessibilityNodeProvider() {
111        return null;
112    }
113
114    /**
115     * @see AccessibilityNodeProvider#createAccessibilityNodeInfo(int)
116     */
117    protected AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
118        if (!mAccessibilityManager.isEnabled() || mNativeObj == 0) {
119            return null;
120        }
121
122        int rootId = nativeGetRootId(mNativeObj);
123
124        if (virtualViewId == View.NO_ID) {
125            return createNodeForHost(rootId);
126        }
127
128        if (!isFrameInfoInitialized()) {
129            return null;
130        }
131
132        final AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(mView);
133        info.setPackageName(mContentViewCore.getContext().getPackageName());
134        info.setSource(mView, virtualViewId);
135
136        if (virtualViewId == rootId) {
137            info.setParent(mView);
138        }
139
140        if (nativePopulateAccessibilityNodeInfo(mNativeObj, info, virtualViewId)) {
141            return info;
142        } else {
143            info.recycle();
144            return null;
145        }
146    }
147
148    /**
149     * @see AccessibilityNodeProvider#findAccessibilityNodeInfosByText(String, int)
150     */
151    protected List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String text,
152            int virtualViewId) {
153        return new ArrayList<AccessibilityNodeInfo>();
154    }
155
156    /**
157     * @see AccessibilityNodeProvider#performAction(int, int, Bundle)
158     */
159    protected boolean performAction(int virtualViewId, int action, Bundle arguments) {
160        // We don't support any actions on the host view or nodes
161        // that are not (any longer) in the tree.
162        if (!mAccessibilityManager.isEnabled() || mNativeObj == 0
163                || !nativeIsNodeValid(mNativeObj, virtualViewId)) {
164            return false;
165        }
166
167        switch (action) {
168            case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
169                if (!moveAccessibilityFocusToId(virtualViewId)) return true;
170
171                if (!mIsHovering) {
172                    nativeScrollToMakeNodeVisible(
173                            mNativeObj, mAccessibilityFocusId);
174                } else {
175                    mPendingScrollToMakeNodeVisible = true;
176                }
177                return true;
178            case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
179                if (mAccessibilityFocusId == virtualViewId) {
180                    sendAccessibilityEvent(mAccessibilityFocusId,
181                            AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
182                    mAccessibilityFocusId = View.NO_ID;
183                    mAccessibilityFocusRect = null;
184                }
185                return true;
186            case AccessibilityNodeInfo.ACTION_CLICK:
187                nativeClick(mNativeObj, virtualViewId);
188                sendAccessibilityEvent(virtualViewId,
189                        AccessibilityEvent.TYPE_VIEW_CLICKED);
190                return true;
191            case AccessibilityNodeInfo.ACTION_FOCUS:
192                nativeFocus(mNativeObj, virtualViewId);
193                return true;
194            case AccessibilityNodeInfo.ACTION_CLEAR_FOCUS:
195                nativeBlur(mNativeObj);
196                return true;
197
198            case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT: {
199                if (arguments == null)
200                    return false;
201                String elementType = arguments.getString(
202                    AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING);
203                if (elementType == null)
204                    return false;
205                elementType = elementType.toUpperCase(Locale.US);
206                return jumpToElementType(elementType, true);
207            }
208            case AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT: {
209                if (arguments == null)
210                    return false;
211                String elementType = arguments.getString(
212                    AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING);
213                if (elementType == null)
214                    return false;
215                elementType = elementType.toUpperCase(Locale.US);
216                return jumpToElementType(elementType, false);
217            }
218
219            default:
220                break;
221        }
222        return false;
223    }
224
225    /**
226     * @see View#onHoverEvent(MotionEvent)
227     */
228    public boolean onHoverEvent(MotionEvent event) {
229        if (!mAccessibilityManager.isEnabled() || mNativeObj == 0) {
230            return false;
231        }
232
233        if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) {
234            mIsHovering = false;
235            if (mPendingScrollToMakeNodeVisible) {
236                nativeScrollToMakeNodeVisible(
237                        mNativeObj, mAccessibilityFocusId);
238            }
239            mPendingScrollToMakeNodeVisible = false;
240            return true;
241        }
242
243        mIsHovering = true;
244        mUserHasTouchExplored = true;
245        float x = event.getX();
246        float y = event.getY();
247
248        // Convert to CSS coordinates.
249        int cssX = (int) (mRenderCoordinates.fromPixToLocalCss(x));
250        int cssY = (int) (mRenderCoordinates.fromPixToLocalCss(y));
251
252        // This sends an IPC to the render process to do the hit testing.
253        // The response is handled by handleHover.
254        nativeHitTest(mNativeObj, cssX, cssY);
255        return true;
256    }
257
258    /**
259     * Called by ContentViewCore to notify us when the frame info is initialized,
260     * the first time, since until that point, we can't use mRenderCoordinates to transform
261     * web coordinates to screen coordinates.
262     */
263    public void notifyFrameInfoInitialized() {
264        if (mNotifyFrameInfoInitializedCalled)
265            return;
266
267        mNotifyFrameInfoInitializedCalled = true;
268
269        // Invalidate the container view, since the chrome accessibility tree is now
270        // ready and listed as the child of the container view.
271        mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
272
273        // (Re-) focus focused element, since we weren't able to create an
274        // AccessibilityNodeInfo for this element before.
275        if (mAccessibilityFocusId != View.NO_ID) {
276            moveAccessibilityFocusToIdAndRefocusIfNeeded(mAccessibilityFocusId);
277        }
278    }
279
280    private boolean jumpToElementType(String elementType, boolean forwards) {
281        int id = nativeFindElementType(mNativeObj, mAccessibilityFocusId, elementType, forwards);
282        if (id == 0)
283            return false;
284
285        moveAccessibilityFocusToId(id);
286        return true;
287    }
288
289    private boolean moveAccessibilityFocusToId(int newAccessibilityFocusId) {
290        if (newAccessibilityFocusId == mAccessibilityFocusId)
291            return false;
292
293        mAccessibilityFocusId = newAccessibilityFocusId;
294        mAccessibilityFocusRect = null;
295        sendAccessibilityEvent(mAccessibilityFocusId,
296                AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
297        return true;
298    }
299
300    private void moveAccessibilityFocusToIdAndRefocusIfNeeded(int newAccessibilityFocusId) {
301        // Work around a bug in the Android framework where it doesn't fully update the object
302        // with accessibility focus even if you send it a WINDOW_CONTENT_CHANGED. To work around
303        // this, clear focus and then set focus again.
304        if (newAccessibilityFocusId == mAccessibilityFocusId) {
305            sendAccessibilityEvent(newAccessibilityFocusId,
306                    AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
307            mAccessibilityFocusId = View.NO_ID;
308        }
309
310        moveAccessibilityFocusToId(newAccessibilityFocusId);
311    }
312
313    private void sendAccessibilityEvent(int virtualViewId, int eventType) {
314        // If we don't have any frame info, then the virtual hierarchy
315        // doesn't exist in the view of the Android framework, so should
316        // never send any events.
317        if (!mAccessibilityManager.isEnabled() || mNativeObj == 0
318                || !isFrameInfoInitialized()) {
319            return;
320        }
321
322        // This is currently needed if we want Android to draw the yellow box around
323        // the item that has accessibility focus. In practice, this doesn't seem to slow
324        // things down, because it's only called when the accessibility focus moves.
325        // TODO(dmazzoni): remove this if/when Android framework fixes bug.
326        mView.postInvalidate();
327
328        // The container view is indicated by a virtualViewId of NO_ID; post these events directly
329        // since there's no web-specific information to attach.
330        if (virtualViewId == View.NO_ID) {
331            mView.sendAccessibilityEvent(eventType);
332            return;
333        }
334
335        final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
336        event.setPackageName(mContentViewCore.getContext().getPackageName());
337        event.setSource(mView, virtualViewId);
338        if (!nativePopulateAccessibilityEvent(mNativeObj, event, virtualViewId, eventType)) {
339            event.recycle();
340            return;
341        }
342
343        mView.requestSendAccessibilityEvent(mView, event);
344    }
345
346    private Bundle getOrCreateBundleForAccessibilityEvent(AccessibilityEvent event) {
347        Bundle bundle = (Bundle) event.getParcelableData();
348        if (bundle == null) {
349            bundle = new Bundle();
350            event.setParcelableData(bundle);
351        }
352        return bundle;
353    }
354
355    private AccessibilityNodeInfo createNodeForHost(int rootId) {
356        // Since we don't want the parent to be focusable, but we can't remove
357        // actions from a node, copy over the necessary fields.
358        final AccessibilityNodeInfo result = AccessibilityNodeInfo.obtain(mView);
359        final AccessibilityNodeInfo source = AccessibilityNodeInfo.obtain(mView);
360        mView.onInitializeAccessibilityNodeInfo(source);
361
362        // Copy over parent and screen bounds.
363        Rect rect = new Rect();
364        source.getBoundsInParent(rect);
365        result.setBoundsInParent(rect);
366        source.getBoundsInScreen(rect);
367        result.setBoundsInScreen(rect);
368
369        // Set up the parent view, if applicable.
370        final ViewParent parent = mView.getParentForAccessibility();
371        if (parent instanceof View) {
372            result.setParent((View) parent);
373        }
374
375        // Populate the minimum required fields.
376        result.setVisibleToUser(source.isVisibleToUser());
377        result.setEnabled(source.isEnabled());
378        result.setPackageName(source.getPackageName());
379        result.setClassName(source.getClassName());
380
381        // Add the Chrome root node.
382        if (isFrameInfoInitialized()) {
383            result.addChild(mView, rootId);
384        }
385
386        return result;
387    }
388
389    /**
390     * Returns whether or not the frame info is initialized, meaning we can safely
391     * convert web coordinates to screen coordinates. When this is first initialized,
392     * notifyFrameInfoInitialized is called - but we shouldn't check whether or not
393     * that method was called as a way to determine if frame info is valid because
394     * notifyFrameInfoInitialized might not be called at all if mRenderCoordinates
395     * gets initialized first.
396     */
397    private boolean isFrameInfoInitialized() {
398        return mRenderCoordinates.getContentWidthCss() != 0.0 ||
399               mRenderCoordinates.getContentHeightCss() != 0.0;
400    }
401
402    @CalledByNative
403    private void handlePageLoaded(int id) {
404        if (mUserHasTouchExplored) return;
405
406        if (mContentViewCore.shouldSetAccessibilityFocusOnPageLoad()) {
407            moveAccessibilityFocusToIdAndRefocusIfNeeded(id);
408        }
409    }
410
411    @CalledByNative
412    private void handleFocusChanged(int id) {
413        sendAccessibilityEvent(id, AccessibilityEvent.TYPE_VIEW_FOCUSED);
414        moveAccessibilityFocusToId(id);
415    }
416
417    @CalledByNative
418    private void handleCheckStateChanged(int id) {
419        sendAccessibilityEvent(id, AccessibilityEvent.TYPE_VIEW_CLICKED);
420    }
421
422    @CalledByNative
423    private void handleTextSelectionChanged(int id) {
424        sendAccessibilityEvent(id, AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
425    }
426
427    @CalledByNative
428    private void handleEditableTextChanged(int id) {
429        sendAccessibilityEvent(id, AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
430    }
431
432    @CalledByNative
433    private void handleContentChanged(int id) {
434        int rootId = nativeGetRootId(mNativeObj);
435        if (rootId != mCurrentRootId) {
436            mCurrentRootId = rootId;
437            mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
438        } else {
439            sendAccessibilityEvent(id, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
440        }
441    }
442
443    @CalledByNative
444    private void handleNavigate() {
445        mAccessibilityFocusId = View.NO_ID;
446        mAccessibilityFocusRect = null;
447        mUserHasTouchExplored = false;
448        // Invalidate the host, since its child is now gone.
449        mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
450    }
451
452    @CalledByNative
453    private void handleScrollPositionChanged(int id) {
454        sendAccessibilityEvent(id, AccessibilityEvent.TYPE_VIEW_SCROLLED);
455    }
456
457    @CalledByNative
458    private void handleScrolledToAnchor(int id) {
459        moveAccessibilityFocusToId(id);
460    }
461
462    @CalledByNative
463    private void handleHover(int id) {
464        if (mLastHoverId == id) return;
465
466        // Always send the ENTER and then the EXIT event, to match a standard Android View.
467        sendAccessibilityEvent(id, AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
468        sendAccessibilityEvent(mLastHoverId, AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
469        mLastHoverId = id;
470    }
471
472    @CalledByNative
473    private void announceLiveRegionText(String text) {
474        mView.announceForAccessibility(text);
475    }
476
477    @CalledByNative
478    private void setAccessibilityNodeInfoParent(AccessibilityNodeInfo node, int parentId) {
479        node.setParent(mView, parentId);
480    }
481
482    @CalledByNative
483    private void addAccessibilityNodeInfoChild(AccessibilityNodeInfo node, int childId) {
484        node.addChild(mView, childId);
485    }
486
487    @CalledByNative
488    private void setAccessibilityNodeInfoBooleanAttributes(AccessibilityNodeInfo node,
489            int virtualViewId, boolean checkable, boolean checked, boolean clickable,
490            boolean enabled, boolean focusable, boolean focused, boolean password,
491            boolean scrollable, boolean selected, boolean visibleToUser) {
492        node.setCheckable(checkable);
493        node.setChecked(checked);
494        node.setClickable(clickable);
495        node.setEnabled(enabled);
496        node.setFocusable(focusable);
497        node.setFocused(focused);
498        node.setPassword(password);
499        node.setScrollable(scrollable);
500        node.setSelected(selected);
501        node.setVisibleToUser(visibleToUser);
502
503        node.addAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT);
504        node.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT);
505
506        if (focusable) {
507            if (focused) {
508                node.addAction(AccessibilityNodeInfo.ACTION_CLEAR_FOCUS);
509            } else {
510                node.addAction(AccessibilityNodeInfo.ACTION_FOCUS);
511            }
512        }
513
514        if (mAccessibilityFocusId == virtualViewId) {
515            node.setAccessibilityFocused(true);
516            node.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
517        } else {
518            node.setAccessibilityFocused(false);
519            node.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
520        }
521
522        if (clickable) {
523            node.addAction(AccessibilityNodeInfo.ACTION_CLICK);
524        }
525    }
526
527    @CalledByNative
528    private void setAccessibilityNodeInfoClassName(AccessibilityNodeInfo node,
529            String className) {
530        node.setClassName(className);
531    }
532
533    @CalledByNative
534    private void setAccessibilityNodeInfoContentDescription(
535            AccessibilityNodeInfo node, String contentDescription, boolean annotateAsLink) {
536        if (annotateAsLink) {
537            SpannableString spannable = new SpannableString(contentDescription);
538            spannable.setSpan(new URLSpan(""), 0, spannable.length(), 0);
539            node.setContentDescription(spannable);
540        } else {
541            node.setContentDescription(contentDescription);
542        }
543    }
544
545    @CalledByNative
546    private void setAccessibilityNodeInfoLocation(AccessibilityNodeInfo node,
547            final int virtualViewId,
548            int absoluteLeft, int absoluteTop, int parentRelativeLeft, int parentRelativeTop,
549            int width, int height, boolean isRootNode) {
550        // First set the bounds in parent.
551        Rect boundsInParent = new Rect(parentRelativeLeft, parentRelativeTop,
552                parentRelativeLeft + width, parentRelativeTop + height);
553        if (isRootNode) {
554            // Offset of the web content relative to the View.
555            boundsInParent.offset(0, (int) mRenderCoordinates.getContentOffsetYPix());
556        }
557        node.setBoundsInParent(boundsInParent);
558
559        // Now set the absolute rect, which requires several transformations.
560        Rect rect = new Rect(absoluteLeft, absoluteTop, absoluteLeft + width, absoluteTop + height);
561
562        // Offset by the scroll position.
563        rect.offset(-(int) mRenderCoordinates.getScrollX(),
564                    -(int) mRenderCoordinates.getScrollY());
565
566        // Convert CSS (web) pixels to Android View pixels
567        rect.left = (int) mRenderCoordinates.fromLocalCssToPix(rect.left);
568        rect.top = (int) mRenderCoordinates.fromLocalCssToPix(rect.top);
569        rect.bottom = (int) mRenderCoordinates.fromLocalCssToPix(rect.bottom);
570        rect.right = (int) mRenderCoordinates.fromLocalCssToPix(rect.right);
571
572        // Offset by the location of the web content within the view.
573        rect.offset(0,
574                    (int) mRenderCoordinates.getContentOffsetYPix());
575
576        // Finally offset by the location of the view within the screen.
577        final int[] viewLocation = new int[2];
578        mView.getLocationOnScreen(viewLocation);
579        rect.offset(viewLocation[0], viewLocation[1]);
580
581        node.setBoundsInScreen(rect);
582
583        // Work around a bug in the Android framework where if the object with accessibility
584        // focus moves, the accessibility focus rect is not updated - both the visual highlight,
585        // and the location on the screen that's clicked if you double-tap. To work around this,
586        // when we know the object with accessibility focus moved, move focus away and then
587        // move focus right back to it, which tricks Android into updating its bounds.
588        if (virtualViewId == mAccessibilityFocusId && virtualViewId != mCurrentRootId) {
589            if (mAccessibilityFocusRect == null) {
590                mAccessibilityFocusRect = rect;
591            } else if (!mAccessibilityFocusRect.equals(rect)) {
592                mAccessibilityFocusRect = rect;
593                moveAccessibilityFocusToIdAndRefocusIfNeeded(virtualViewId);
594            }
595        }
596    }
597
598    @CalledByNative
599    protected void setAccessibilityNodeInfoKitKatAttributes(AccessibilityNodeInfo node,
600            boolean canOpenPopup,
601            boolean contentInvalid,
602            boolean dismissable,
603            boolean multiLine,
604            int inputType,
605            int liveRegion) {
606        // Requires KitKat or higher.
607    }
608
609    @CalledByNative
610    protected void setAccessibilityNodeInfoCollectionInfo(AccessibilityNodeInfo node,
611            int rowCount, int columnCount, boolean hierarchical) {
612        // Requires KitKat or higher.
613    }
614
615    @CalledByNative
616    protected void setAccessibilityNodeInfoCollectionItemInfo(AccessibilityNodeInfo node,
617            int rowIndex, int rowSpan, int columnIndex, int columnSpan, boolean heading) {
618        // Requires KitKat or higher.
619    }
620
621    @CalledByNative
622    protected void setAccessibilityNodeInfoRangeInfo(AccessibilityNodeInfo node,
623            int rangeType, float min, float max, float current) {
624        // Requires KitKat or higher.
625    }
626
627    @CalledByNative
628    private void setAccessibilityEventBooleanAttributes(AccessibilityEvent event,
629            boolean checked, boolean enabled, boolean password, boolean scrollable) {
630        event.setChecked(checked);
631        event.setEnabled(enabled);
632        event.setPassword(password);
633        event.setScrollable(scrollable);
634    }
635
636    @CalledByNative
637    private void setAccessibilityEventClassName(AccessibilityEvent event, String className) {
638        event.setClassName(className);
639    }
640
641    @CalledByNative
642    private void setAccessibilityEventListAttributes(AccessibilityEvent event,
643            int currentItemIndex, int itemCount) {
644        event.setCurrentItemIndex(currentItemIndex);
645        event.setItemCount(itemCount);
646    }
647
648    @CalledByNative
649    private void setAccessibilityEventScrollAttributes(AccessibilityEvent event,
650            int scrollX, int scrollY, int maxScrollX, int maxScrollY) {
651        event.setScrollX(scrollX);
652        event.setScrollY(scrollY);
653        event.setMaxScrollX(maxScrollX);
654        event.setMaxScrollY(maxScrollY);
655    }
656
657    @CalledByNative
658    private void setAccessibilityEventTextChangedAttrs(AccessibilityEvent event,
659            int fromIndex, int addedCount, int removedCount, String beforeText, String text) {
660        event.setFromIndex(fromIndex);
661        event.setAddedCount(addedCount);
662        event.setRemovedCount(removedCount);
663        event.setBeforeText(beforeText);
664        event.getText().add(text);
665    }
666
667    @CalledByNative
668    private void setAccessibilityEventSelectionAttrs(AccessibilityEvent event,
669            int fromIndex, int addedCount, int itemCount, String text) {
670        event.setFromIndex(fromIndex);
671        event.setAddedCount(addedCount);
672        event.setItemCount(itemCount);
673        event.getText().add(text);
674    }
675
676    @CalledByNative
677    protected void setAccessibilityEventKitKatAttributes(AccessibilityEvent event,
678            boolean canOpenPopup,
679            boolean contentInvalid,
680            boolean dismissable,
681            boolean multiLine,
682            int inputType,
683            int liveRegion) {
684        // Backwards compatibility for KitKat AccessibilityNodeInfo fields.
685        Bundle bundle = getOrCreateBundleForAccessibilityEvent(event);
686        bundle.putBoolean("AccessibilityNodeInfo.canOpenPopup", canOpenPopup);
687        bundle.putBoolean("AccessibilityNodeInfo.contentInvalid", contentInvalid);
688        bundle.putBoolean("AccessibilityNodeInfo.dismissable", dismissable);
689        bundle.putBoolean("AccessibilityNodeInfo.multiLine", multiLine);
690        bundle.putInt("AccessibilityNodeInfo.inputType", inputType);
691        bundle.putInt("AccessibilityNodeInfo.liveRegion", liveRegion);
692    }
693
694    @CalledByNative
695    protected void setAccessibilityEventCollectionInfo(AccessibilityEvent event,
696            int rowCount, int columnCount, boolean hierarchical) {
697        // Backwards compatibility for KitKat AccessibilityNodeInfo fields.
698        Bundle bundle = getOrCreateBundleForAccessibilityEvent(event);
699        bundle.putInt("AccessibilityNodeInfo.CollectionInfo.rowCount", rowCount);
700        bundle.putInt("AccessibilityNodeInfo.CollectionInfo.columnCount", columnCount);
701        bundle.putBoolean("AccessibilityNodeInfo.CollectionInfo.hierarchical", hierarchical);
702    }
703
704    @CalledByNative
705    protected void setAccessibilityEventCollectionItemInfo(AccessibilityEvent event,
706            int rowIndex, int rowSpan, int columnIndex, int columnSpan, boolean heading) {
707        // Backwards compatibility for KitKat AccessibilityNodeInfo fields.
708        Bundle bundle = getOrCreateBundleForAccessibilityEvent(event);
709        bundle.putInt("AccessibilityNodeInfo.CollectionItemInfo.rowIndex", rowIndex);
710        bundle.putInt("AccessibilityNodeInfo.CollectionItemInfo.rowSpan", rowSpan);
711        bundle.putInt("AccessibilityNodeInfo.CollectionItemInfo.columnIndex", columnIndex);
712        bundle.putInt("AccessibilityNodeInfo.CollectionItemInfo.columnSpan", columnSpan);
713        bundle.putBoolean("AccessibilityNodeInfo.CollectionItemInfo.heading", heading);
714    }
715
716    @CalledByNative
717    protected void setAccessibilityEventRangeInfo(AccessibilityEvent event,
718            int rangeType, float min, float max, float current) {
719        // Backwards compatibility for KitKat AccessibilityNodeInfo fields.
720        Bundle bundle = getOrCreateBundleForAccessibilityEvent(event);
721        bundle.putInt("AccessibilityNodeInfo.RangeInfo.type", rangeType);
722        bundle.putFloat("AccessibilityNodeInfo.RangeInfo.min", min);
723        bundle.putFloat("AccessibilityNodeInfo.RangeInfo.max", max);
724        bundle.putFloat("AccessibilityNodeInfo.RangeInfo.current", current);
725    }
726
727    private native int nativeGetRootId(long nativeBrowserAccessibilityManagerAndroid);
728    private native boolean nativeIsNodeValid(long nativeBrowserAccessibilityManagerAndroid, int id);
729    private native void nativeHitTest(long nativeBrowserAccessibilityManagerAndroid, int x, int y);
730    private native boolean nativePopulateAccessibilityNodeInfo(
731        long nativeBrowserAccessibilityManagerAndroid, AccessibilityNodeInfo info, int id);
732    private native boolean nativePopulateAccessibilityEvent(
733        long nativeBrowserAccessibilityManagerAndroid, AccessibilityEvent event, int id,
734        int eventType);
735    private native void nativeClick(long nativeBrowserAccessibilityManagerAndroid, int id);
736    private native void nativeFocus(long nativeBrowserAccessibilityManagerAndroid, int id);
737    private native void nativeBlur(long nativeBrowserAccessibilityManagerAndroid);
738    private native void nativeScrollToMakeNodeVisible(
739            long nativeBrowserAccessibilityManagerAndroid, int id);
740    private native int nativeFindElementType(long nativeBrowserAccessibilityManagerAndroid,
741            int startId, String elementType, boolean forwards);
742}
743