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