// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.content.browser.accessibility; import android.content.Context; import android.graphics.Rect; import android.os.Build; import android.os.Bundle; import android.text.SpannableString; import android.text.style.URLSpan; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeProvider; import org.chromium.base.CalledByNative; import org.chromium.base.JNINamespace; import org.chromium.content.browser.ContentViewCore; import org.chromium.content.browser.RenderCoordinates; import java.util.ArrayList; import java.util.List; import java.util.Locale; /** * Native accessibility for a {@link ContentViewCore}. * * This class is safe to load on ICS and can be used to run tests, but * only the subclass, JellyBeanBrowserAccessibilityManager, actually * has a AccessibilityNodeProvider implementation needed for native * accessibility. */ @JNINamespace("content") public class BrowserAccessibilityManager { private static final String TAG = "BrowserAccessibilityManager"; private ContentViewCore mContentViewCore; private final AccessibilityManager mAccessibilityManager; private final RenderCoordinates mRenderCoordinates; private long mNativeObj; private int mAccessibilityFocusId; private Rect mAccessibilityFocusRect; private boolean mIsHovering; private int mLastHoverId = View.NO_ID; private int mCurrentRootId; private final int[] mTempLocation = new int[2]; private final ViewGroup mView; private boolean mUserHasTouchExplored; private boolean mPendingScrollToMakeNodeVisible; private boolean mNotifyFrameInfoInitializedCalled; /** * Create a BrowserAccessibilityManager object, which is owned by the C++ * BrowserAccessibilityManagerAndroid instance, and connects to the content view. * @param nativeBrowserAccessibilityManagerAndroid A pointer to the counterpart native * C++ object that owns this object. * @param contentViewCore The content view that this object provides accessibility for. */ @CalledByNative private static BrowserAccessibilityManager create(long nativeBrowserAccessibilityManagerAndroid, ContentViewCore contentViewCore) { // A bug in the KitKat framework prevents us from using these new APIs. // http://crbug.com/348088/ // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // return new KitKatBrowserAccessibilityManager( // nativeBrowserAccessibilityManagerAndroid, contentViewCore); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { return new JellyBeanBrowserAccessibilityManager( nativeBrowserAccessibilityManagerAndroid, contentViewCore); } else { return new BrowserAccessibilityManager( nativeBrowserAccessibilityManagerAndroid, contentViewCore); } } protected BrowserAccessibilityManager(long nativeBrowserAccessibilityManagerAndroid, ContentViewCore contentViewCore) { mNativeObj = nativeBrowserAccessibilityManagerAndroid; mContentViewCore = contentViewCore; mContentViewCore.setBrowserAccessibilityManager(this); mAccessibilityFocusId = View.NO_ID; mIsHovering = false; mCurrentRootId = View.NO_ID; mView = mContentViewCore.getContainerView(); mRenderCoordinates = mContentViewCore.getRenderCoordinates(); mAccessibilityManager = (AccessibilityManager) mContentViewCore.getContext() .getSystemService(Context.ACCESSIBILITY_SERVICE); } @CalledByNative private void onNativeObjectDestroyed() { if (mContentViewCore.getBrowserAccessibilityManager() == this) { mContentViewCore.setBrowserAccessibilityManager(null); } mNativeObj = 0; mContentViewCore = null; } /** * @return An AccessibilityNodeProvider on JellyBean, and null on previous versions. */ public AccessibilityNodeProvider getAccessibilityNodeProvider() { return null; } /** * @see AccessibilityNodeProvider#createAccessibilityNodeInfo(int) */ protected AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) { if (!mAccessibilityManager.isEnabled() || mNativeObj == 0) { return null; } int rootId = nativeGetRootId(mNativeObj); if (virtualViewId == View.NO_ID) { return createNodeForHost(rootId); } if (!isFrameInfoInitialized()) { return null; } final AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(mView); info.setPackageName(mContentViewCore.getContext().getPackageName()); info.setSource(mView, virtualViewId); if (virtualViewId == rootId) { info.setParent(mView); } if (nativePopulateAccessibilityNodeInfo(mNativeObj, info, virtualViewId)) { return info; } else { info.recycle(); return null; } } /** * @see AccessibilityNodeProvider#findAccessibilityNodeInfosByText(String, int) */ protected List findAccessibilityNodeInfosByText(String text, int virtualViewId) { return new ArrayList(); } /** * @see AccessibilityNodeProvider#performAction(int, int, Bundle) */ protected boolean performAction(int virtualViewId, int action, Bundle arguments) { // We don't support any actions on the host view or nodes // that are not (any longer) in the tree. if (!mAccessibilityManager.isEnabled() || mNativeObj == 0 || !nativeIsNodeValid(mNativeObj, virtualViewId)) { return false; } switch (action) { case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: if (!moveAccessibilityFocusToId(virtualViewId)) return true; if (!mIsHovering) { nativeScrollToMakeNodeVisible( mNativeObj, mAccessibilityFocusId); } else { mPendingScrollToMakeNodeVisible = true; } return true; case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: if (mAccessibilityFocusId == virtualViewId) { sendAccessibilityEvent(mAccessibilityFocusId, AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED); mAccessibilityFocusId = View.NO_ID; mAccessibilityFocusRect = null; } return true; case AccessibilityNodeInfo.ACTION_CLICK: nativeClick(mNativeObj, virtualViewId); sendAccessibilityEvent(virtualViewId, AccessibilityEvent.TYPE_VIEW_CLICKED); return true; case AccessibilityNodeInfo.ACTION_FOCUS: nativeFocus(mNativeObj, virtualViewId); return true; case AccessibilityNodeInfo.ACTION_CLEAR_FOCUS: nativeBlur(mNativeObj); return true; case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT: { if (arguments == null) return false; String elementType = arguments.getString( AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING); if (elementType == null) return false; elementType = elementType.toUpperCase(Locale.US); return jumpToElementType(elementType, true); } case AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT: { if (arguments == null) return false; String elementType = arguments.getString( AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING); if (elementType == null) return false; elementType = elementType.toUpperCase(Locale.US); return jumpToElementType(elementType, false); } default: break; } return false; } /** * @see View#onHoverEvent(MotionEvent) */ public boolean onHoverEvent(MotionEvent event) { if (!mAccessibilityManager.isEnabled() || mNativeObj == 0) { return false; } if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) { mIsHovering = false; if (mPendingScrollToMakeNodeVisible) { nativeScrollToMakeNodeVisible( mNativeObj, mAccessibilityFocusId); } mPendingScrollToMakeNodeVisible = false; return true; } mIsHovering = true; mUserHasTouchExplored = true; float x = event.getX(); float y = event.getY(); // Convert to CSS coordinates. int cssX = (int) (mRenderCoordinates.fromPixToLocalCss(x)); int cssY = (int) (mRenderCoordinates.fromPixToLocalCss(y)); // This sends an IPC to the render process to do the hit testing. // The response is handled by handleHover. nativeHitTest(mNativeObj, cssX, cssY); return true; } /** * Called by ContentViewCore to notify us when the frame info is initialized, * the first time, since until that point, we can't use mRenderCoordinates to transform * web coordinates to screen coordinates. */ public void notifyFrameInfoInitialized() { if (mNotifyFrameInfoInitializedCalled) return; mNotifyFrameInfoInitializedCalled = true; // Invalidate the container view, since the chrome accessibility tree is now // ready and listed as the child of the container view. mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); // (Re-) focus focused element, since we weren't able to create an // AccessibilityNodeInfo for this element before. if (mAccessibilityFocusId != View.NO_ID) { moveAccessibilityFocusToIdAndRefocusIfNeeded(mAccessibilityFocusId); } } private boolean jumpToElementType(String elementType, boolean forwards) { int id = nativeFindElementType(mNativeObj, mAccessibilityFocusId, elementType, forwards); if (id == 0) return false; moveAccessibilityFocusToId(id); return true; } private boolean moveAccessibilityFocusToId(int newAccessibilityFocusId) { if (newAccessibilityFocusId == mAccessibilityFocusId) return false; mAccessibilityFocusId = newAccessibilityFocusId; mAccessibilityFocusRect = null; sendAccessibilityEvent(mAccessibilityFocusId, AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED); return true; } private void moveAccessibilityFocusToIdAndRefocusIfNeeded(int newAccessibilityFocusId) { // Work around a bug in the Android framework where it doesn't fully update the object // with accessibility focus even if you send it a WINDOW_CONTENT_CHANGED. To work around // this, clear focus and then set focus again. if (newAccessibilityFocusId == mAccessibilityFocusId) { sendAccessibilityEvent(newAccessibilityFocusId, AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED); mAccessibilityFocusId = View.NO_ID; } moveAccessibilityFocusToId(newAccessibilityFocusId); } private void sendAccessibilityEvent(int virtualViewId, int eventType) { // If we don't have any frame info, then the virtual hierarchy // doesn't exist in the view of the Android framework, so should // never send any events. if (!mAccessibilityManager.isEnabled() || mNativeObj == 0 || !isFrameInfoInitialized()) { return; } // This is currently needed if we want Android to draw the yellow box around // the item that has accessibility focus. In practice, this doesn't seem to slow // things down, because it's only called when the accessibility focus moves. // TODO(dmazzoni): remove this if/when Android framework fixes bug. mView.postInvalidate(); // The container view is indicated by a virtualViewId of NO_ID; post these events directly // since there's no web-specific information to attach. if (virtualViewId == View.NO_ID) { mView.sendAccessibilityEvent(eventType); return; } final AccessibilityEvent event = AccessibilityEvent.obtain(eventType); event.setPackageName(mContentViewCore.getContext().getPackageName()); event.setSource(mView, virtualViewId); if (!nativePopulateAccessibilityEvent(mNativeObj, event, virtualViewId, eventType)) { event.recycle(); return; } mView.requestSendAccessibilityEvent(mView, event); } private Bundle getOrCreateBundleForAccessibilityEvent(AccessibilityEvent event) { Bundle bundle = (Bundle) event.getParcelableData(); if (bundle == null) { bundle = new Bundle(); event.setParcelableData(bundle); } return bundle; } private AccessibilityNodeInfo createNodeForHost(int rootId) { // Since we don't want the parent to be focusable, but we can't remove // actions from a node, copy over the necessary fields. final AccessibilityNodeInfo result = AccessibilityNodeInfo.obtain(mView); final AccessibilityNodeInfo source = AccessibilityNodeInfo.obtain(mView); mView.onInitializeAccessibilityNodeInfo(source); // Copy over parent and screen bounds. Rect rect = new Rect(); source.getBoundsInParent(rect); result.setBoundsInParent(rect); source.getBoundsInScreen(rect); result.setBoundsInScreen(rect); // Set up the parent view, if applicable. final ViewParent parent = mView.getParentForAccessibility(); if (parent instanceof View) { result.setParent((View) parent); } // Populate the minimum required fields. result.setVisibleToUser(source.isVisibleToUser()); result.setEnabled(source.isEnabled()); result.setPackageName(source.getPackageName()); result.setClassName(source.getClassName()); // Add the Chrome root node. if (isFrameInfoInitialized()) { result.addChild(mView, rootId); } return result; } /** * Returns whether or not the frame info is initialized, meaning we can safely * convert web coordinates to screen coordinates. When this is first initialized, * notifyFrameInfoInitialized is called - but we shouldn't check whether or not * that method was called as a way to determine if frame info is valid because * notifyFrameInfoInitialized might not be called at all if mRenderCoordinates * gets initialized first. */ private boolean isFrameInfoInitialized() { return mRenderCoordinates.getContentWidthCss() != 0.0 || mRenderCoordinates.getContentHeightCss() != 0.0; } @CalledByNative private void handlePageLoaded(int id) { if (mUserHasTouchExplored) return; if (mContentViewCore.shouldSetAccessibilityFocusOnPageLoad()) { moveAccessibilityFocusToIdAndRefocusIfNeeded(id); } } @CalledByNative private void handleFocusChanged(int id) { sendAccessibilityEvent(id, AccessibilityEvent.TYPE_VIEW_FOCUSED); moveAccessibilityFocusToId(id); } @CalledByNative private void handleCheckStateChanged(int id) { sendAccessibilityEvent(id, AccessibilityEvent.TYPE_VIEW_CLICKED); } @CalledByNative private void handleTextSelectionChanged(int id) { sendAccessibilityEvent(id, AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED); } @CalledByNative private void handleEditableTextChanged(int id) { sendAccessibilityEvent(id, AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); } @CalledByNative private void handleContentChanged(int id) { int rootId = nativeGetRootId(mNativeObj); if (rootId != mCurrentRootId) { mCurrentRootId = rootId; mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); } else { sendAccessibilityEvent(id, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); } } @CalledByNative private void handleNavigate() { mAccessibilityFocusId = View.NO_ID; mAccessibilityFocusRect = null; mUserHasTouchExplored = false; // Invalidate the host, since its child is now gone. mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); } @CalledByNative private void handleScrollPositionChanged(int id) { sendAccessibilityEvent(id, AccessibilityEvent.TYPE_VIEW_SCROLLED); } @CalledByNative private void handleScrolledToAnchor(int id) { moveAccessibilityFocusToId(id); } @CalledByNative private void handleHover(int id) { if (mLastHoverId == id) return; // Always send the ENTER and then the EXIT event, to match a standard Android View. sendAccessibilityEvent(id, AccessibilityEvent.TYPE_VIEW_HOVER_ENTER); sendAccessibilityEvent(mLastHoverId, AccessibilityEvent.TYPE_VIEW_HOVER_EXIT); mLastHoverId = id; } @CalledByNative private void announceLiveRegionText(String text) { mView.announceForAccessibility(text); } @CalledByNative private void setAccessibilityNodeInfoParent(AccessibilityNodeInfo node, int parentId) { node.setParent(mView, parentId); } @CalledByNative private void addAccessibilityNodeInfoChild(AccessibilityNodeInfo node, int childId) { node.addChild(mView, childId); } @CalledByNative private void setAccessibilityNodeInfoBooleanAttributes(AccessibilityNodeInfo node, int virtualViewId, boolean checkable, boolean checked, boolean clickable, boolean enabled, boolean focusable, boolean focused, boolean password, boolean scrollable, boolean selected, boolean visibleToUser) { node.setCheckable(checkable); node.setChecked(checked); node.setClickable(clickable); node.setEnabled(enabled); node.setFocusable(focusable); node.setFocused(focused); node.setPassword(password); node.setScrollable(scrollable); node.setSelected(selected); node.setVisibleToUser(visibleToUser); node.addAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT); node.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT); if (focusable) { if (focused) { node.addAction(AccessibilityNodeInfo.ACTION_CLEAR_FOCUS); } else { node.addAction(AccessibilityNodeInfo.ACTION_FOCUS); } } if (mAccessibilityFocusId == virtualViewId) { node.setAccessibilityFocused(true); node.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS); } else { node.setAccessibilityFocused(false); node.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS); } if (clickable) { node.addAction(AccessibilityNodeInfo.ACTION_CLICK); } } @CalledByNative private void setAccessibilityNodeInfoClassName(AccessibilityNodeInfo node, String className) { node.setClassName(className); } @CalledByNative private void setAccessibilityNodeInfoContentDescription( AccessibilityNodeInfo node, String contentDescription, boolean annotateAsLink) { if (annotateAsLink) { SpannableString spannable = new SpannableString(contentDescription); spannable.setSpan(new URLSpan(""), 0, spannable.length(), 0); node.setContentDescription(spannable); } else { node.setContentDescription(contentDescription); } } @CalledByNative private void setAccessibilityNodeInfoLocation(AccessibilityNodeInfo node, final int virtualViewId, int absoluteLeft, int absoluteTop, int parentRelativeLeft, int parentRelativeTop, int width, int height, boolean isRootNode) { // First set the bounds in parent. Rect boundsInParent = new Rect(parentRelativeLeft, parentRelativeTop, parentRelativeLeft + width, parentRelativeTop + height); if (isRootNode) { // Offset of the web content relative to the View. boundsInParent.offset(0, (int) mRenderCoordinates.getContentOffsetYPix()); } node.setBoundsInParent(boundsInParent); // Now set the absolute rect, which requires several transformations. Rect rect = new Rect(absoluteLeft, absoluteTop, absoluteLeft + width, absoluteTop + height); // Offset by the scroll position. rect.offset(-(int) mRenderCoordinates.getScrollX(), -(int) mRenderCoordinates.getScrollY()); // Convert CSS (web) pixels to Android View pixels rect.left = (int) mRenderCoordinates.fromLocalCssToPix(rect.left); rect.top = (int) mRenderCoordinates.fromLocalCssToPix(rect.top); rect.bottom = (int) mRenderCoordinates.fromLocalCssToPix(rect.bottom); rect.right = (int) mRenderCoordinates.fromLocalCssToPix(rect.right); // Offset by the location of the web content within the view. rect.offset(0, (int) mRenderCoordinates.getContentOffsetYPix()); // Finally offset by the location of the view within the screen. final int[] viewLocation = new int[2]; mView.getLocationOnScreen(viewLocation); rect.offset(viewLocation[0], viewLocation[1]); node.setBoundsInScreen(rect); // Work around a bug in the Android framework where if the object with accessibility // focus moves, the accessibility focus rect is not updated - both the visual highlight, // and the location on the screen that's clicked if you double-tap. To work around this, // when we know the object with accessibility focus moved, move focus away and then // move focus right back to it, which tricks Android into updating its bounds. if (virtualViewId == mAccessibilityFocusId && virtualViewId != mCurrentRootId) { if (mAccessibilityFocusRect == null) { mAccessibilityFocusRect = rect; } else if (!mAccessibilityFocusRect.equals(rect)) { mAccessibilityFocusRect = rect; moveAccessibilityFocusToIdAndRefocusIfNeeded(virtualViewId); } } } @CalledByNative protected void setAccessibilityNodeInfoKitKatAttributes(AccessibilityNodeInfo node, boolean canOpenPopup, boolean contentInvalid, boolean dismissable, boolean multiLine, int inputType, int liveRegion) { // Requires KitKat or higher. } @CalledByNative protected void setAccessibilityNodeInfoCollectionInfo(AccessibilityNodeInfo node, int rowCount, int columnCount, boolean hierarchical) { // Requires KitKat or higher. } @CalledByNative protected void setAccessibilityNodeInfoCollectionItemInfo(AccessibilityNodeInfo node, int rowIndex, int rowSpan, int columnIndex, int columnSpan, boolean heading) { // Requires KitKat or higher. } @CalledByNative protected void setAccessibilityNodeInfoRangeInfo(AccessibilityNodeInfo node, int rangeType, float min, float max, float current) { // Requires KitKat or higher. } @CalledByNative private void setAccessibilityEventBooleanAttributes(AccessibilityEvent event, boolean checked, boolean enabled, boolean password, boolean scrollable) { event.setChecked(checked); event.setEnabled(enabled); event.setPassword(password); event.setScrollable(scrollable); } @CalledByNative private void setAccessibilityEventClassName(AccessibilityEvent event, String className) { event.setClassName(className); } @CalledByNative private void setAccessibilityEventListAttributes(AccessibilityEvent event, int currentItemIndex, int itemCount) { event.setCurrentItemIndex(currentItemIndex); event.setItemCount(itemCount); } @CalledByNative private void setAccessibilityEventScrollAttributes(AccessibilityEvent event, int scrollX, int scrollY, int maxScrollX, int maxScrollY) { event.setScrollX(scrollX); event.setScrollY(scrollY); event.setMaxScrollX(maxScrollX); event.setMaxScrollY(maxScrollY); } @CalledByNative private void setAccessibilityEventTextChangedAttrs(AccessibilityEvent event, int fromIndex, int addedCount, int removedCount, String beforeText, String text) { event.setFromIndex(fromIndex); event.setAddedCount(addedCount); event.setRemovedCount(removedCount); event.setBeforeText(beforeText); event.getText().add(text); } @CalledByNative private void setAccessibilityEventSelectionAttrs(AccessibilityEvent event, int fromIndex, int addedCount, int itemCount, String text) { event.setFromIndex(fromIndex); event.setAddedCount(addedCount); event.setItemCount(itemCount); event.getText().add(text); } @CalledByNative protected void setAccessibilityEventKitKatAttributes(AccessibilityEvent event, boolean canOpenPopup, boolean contentInvalid, boolean dismissable, boolean multiLine, int inputType, int liveRegion) { // Backwards compatibility for KitKat AccessibilityNodeInfo fields. Bundle bundle = getOrCreateBundleForAccessibilityEvent(event); bundle.putBoolean("AccessibilityNodeInfo.canOpenPopup", canOpenPopup); bundle.putBoolean("AccessibilityNodeInfo.contentInvalid", contentInvalid); bundle.putBoolean("AccessibilityNodeInfo.dismissable", dismissable); bundle.putBoolean("AccessibilityNodeInfo.multiLine", multiLine); bundle.putInt("AccessibilityNodeInfo.inputType", inputType); bundle.putInt("AccessibilityNodeInfo.liveRegion", liveRegion); } @CalledByNative protected void setAccessibilityEventCollectionInfo(AccessibilityEvent event, int rowCount, int columnCount, boolean hierarchical) { // Backwards compatibility for KitKat AccessibilityNodeInfo fields. Bundle bundle = getOrCreateBundleForAccessibilityEvent(event); bundle.putInt("AccessibilityNodeInfo.CollectionInfo.rowCount", rowCount); bundle.putInt("AccessibilityNodeInfo.CollectionInfo.columnCount", columnCount); bundle.putBoolean("AccessibilityNodeInfo.CollectionInfo.hierarchical", hierarchical); } @CalledByNative protected void setAccessibilityEventHeadingFlag(AccessibilityEvent event, boolean heading) { // Backwards compatibility for KitKat AccessibilityNodeInfo fields. Bundle bundle = getOrCreateBundleForAccessibilityEvent(event); bundle.putBoolean("AccessibilityNodeInfo.CollectionItemInfo.heading", heading); } @CalledByNative protected void setAccessibilityEventCollectionItemInfo(AccessibilityEvent event, int rowIndex, int rowSpan, int columnIndex, int columnSpan) { // Backwards compatibility for KitKat AccessibilityNodeInfo fields. Bundle bundle = getOrCreateBundleForAccessibilityEvent(event); bundle.putInt("AccessibilityNodeInfo.CollectionItemInfo.rowIndex", rowIndex); bundle.putInt("AccessibilityNodeInfo.CollectionItemInfo.rowSpan", rowSpan); bundle.putInt("AccessibilityNodeInfo.CollectionItemInfo.columnIndex", columnIndex); bundle.putInt("AccessibilityNodeInfo.CollectionItemInfo.columnSpan", columnSpan); } @CalledByNative protected void setAccessibilityEventRangeInfo(AccessibilityEvent event, int rangeType, float min, float max, float current) { // Backwards compatibility for KitKat AccessibilityNodeInfo fields. Bundle bundle = getOrCreateBundleForAccessibilityEvent(event); bundle.putInt("AccessibilityNodeInfo.RangeInfo.type", rangeType); bundle.putFloat("AccessibilityNodeInfo.RangeInfo.min", min); bundle.putFloat("AccessibilityNodeInfo.RangeInfo.max", max); bundle.putFloat("AccessibilityNodeInfo.RangeInfo.current", current); } private native int nativeGetRootId(long nativeBrowserAccessibilityManagerAndroid); private native boolean nativeIsNodeValid(long nativeBrowserAccessibilityManagerAndroid, int id); private native void nativeHitTest(long nativeBrowserAccessibilityManagerAndroid, int x, int y); private native boolean nativePopulateAccessibilityNodeInfo( long nativeBrowserAccessibilityManagerAndroid, AccessibilityNodeInfo info, int id); private native boolean nativePopulateAccessibilityEvent( long nativeBrowserAccessibilityManagerAndroid, AccessibilityEvent event, int id, int eventType); private native void nativeClick(long nativeBrowserAccessibilityManagerAndroid, int id); private native void nativeFocus(long nativeBrowserAccessibilityManagerAndroid, int id); private native void nativeBlur(long nativeBrowserAccessibilityManagerAndroid); private native void nativeScrollToMakeNodeVisible( long nativeBrowserAccessibilityManagerAndroid, int id); private native int nativeFindElementType(long nativeBrowserAccessibilityManagerAndroid, int startId, String elementType, boolean forwards); }