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 5 package org.chromium.content.browser.accessibility; 6 7 import android.content.Context; 8 import android.graphics.Rect; 9 import android.os.Build; 10 import android.os.Bundle; 11 import android.text.SpannableString; 12 import android.text.style.URLSpan; 13 import android.view.MotionEvent; 14 import android.view.View; 15 import android.view.ViewGroup; 16 import android.view.ViewParent; 17 import android.view.accessibility.AccessibilityEvent; 18 import android.view.accessibility.AccessibilityManager; 19 import android.view.accessibility.AccessibilityNodeInfo; 20 import android.view.accessibility.AccessibilityNodeProvider; 21 22 import org.chromium.base.CalledByNative; 23 import org.chromium.base.JNINamespace; 24 import org.chromium.content.browser.ContentViewCore; 25 import org.chromium.content.browser.RenderCoordinates; 26 27 import java.util.ArrayList; 28 import java.util.List; 29 import 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") 40 public 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 create(long nativeBrowserAccessibilityManagerAndroid, ContentViewCore contentViewCore)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 BrowserAccessibilityManager(long nativeBrowserAccessibilityManagerAndroid, ContentViewCore contentViewCore)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 onNativeObjectDestroyed()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 */ getAccessibilityNodeProvider()108 public AccessibilityNodeProvider getAccessibilityNodeProvider() { 109 return null; 110 } 111 112 /** 113 * @see AccessibilityNodeProvider#createAccessibilityNodeInfo(int) 114 */ createAccessibilityNodeInfo(int virtualViewId)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 */ findAccessibilityNodeInfosByText(String text, int virtualViewId)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 */ performAction(int virtualViewId, int action, Bundle arguments)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 */ onHoverEvent(MotionEvent event)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 int cssY = (int) (mRenderCoordinates.fromPixToLocalCss(y)); 253 254 // This sends an IPC to the render process to do the hit testing. 255 // The response is handled by handleHover. 256 nativeHitTest(mNativeObj, cssX, cssY); 257 return true; 258 } 259 260 /** 261 * Called by ContentViewCore to notify us when the frame info is initialized, 262 * the first time, since until that point, we can't use mRenderCoordinates to transform 263 * web coordinates to screen coordinates. 264 */ notifyFrameInfoInitialized()265 public void notifyFrameInfoInitialized() { 266 // Invalidate the container view, since the chrome accessibility tree is now 267 // ready and listed as the child of the container view. 268 mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); 269 270 // (Re-) focus focused element, since we weren't able to create an 271 // AccessibilityNodeInfo for this element before. 272 if (mAccessibilityFocusId != View.NO_ID) { 273 sendAccessibilityEvent(mAccessibilityFocusId, 274 AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED); 275 } 276 } 277 jumpToElementType(String elementType, boolean forwards)278 private boolean jumpToElementType(String elementType, boolean forwards) { 279 int id = nativeFindElementType(mNativeObj, mAccessibilityFocusId, elementType, forwards); 280 if (id == 0) 281 return false; 282 283 mAccessibilityFocusId = id; 284 sendAccessibilityEvent(id, AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED); 285 return true; 286 } 287 sendAccessibilityEvent(int virtualViewId, int eventType)288 private void sendAccessibilityEvent(int virtualViewId, int eventType) { 289 // If we don't have any frame info, then the virtual hierarchy 290 // doesn't exist in the view of the Android framework, so should 291 // never send any events. 292 if (!mAccessibilityManager.isEnabled() || mNativeObj == 0 293 || !isFrameInfoInitialized()) { 294 return; 295 } 296 297 // This is currently needed if we want Android to draw the yellow box around 298 // the item that has accessibility focus. In practice, this doesn't seem to slow 299 // things down, because it's only called when the accessibility focus moves. 300 // TODO(dmazzoni): remove this if/when Android framework fixes bug. 301 mView.postInvalidate(); 302 303 // The container view is indicated by a virtualViewId of NO_ID; post these events directly 304 // since there's no web-specific information to attach. 305 if (virtualViewId == View.NO_ID) { 306 mView.sendAccessibilityEvent(eventType); 307 return; 308 } 309 310 final AccessibilityEvent event = AccessibilityEvent.obtain(eventType); 311 event.setPackageName(mContentViewCore.getContext().getPackageName()); 312 event.setSource(mView, virtualViewId); 313 if (!nativePopulateAccessibilityEvent(mNativeObj, event, virtualViewId, eventType)) { 314 event.recycle(); 315 return; 316 } 317 318 mView.requestSendAccessibilityEvent(mView, event); 319 } 320 getOrCreateBundleForAccessibilityEvent(AccessibilityEvent event)321 private Bundle getOrCreateBundleForAccessibilityEvent(AccessibilityEvent event) { 322 Bundle bundle = (Bundle) event.getParcelableData(); 323 if (bundle == null) { 324 bundle = new Bundle(); 325 event.setParcelableData(bundle); 326 } 327 return bundle; 328 } 329 createNodeForHost(int rootId)330 private AccessibilityNodeInfo createNodeForHost(int rootId) { 331 // Since we don't want the parent to be focusable, but we can't remove 332 // actions from a node, copy over the necessary fields. 333 final AccessibilityNodeInfo result = AccessibilityNodeInfo.obtain(mView); 334 final AccessibilityNodeInfo source = AccessibilityNodeInfo.obtain(mView); 335 mView.onInitializeAccessibilityNodeInfo(source); 336 337 // Copy over parent and screen bounds. 338 Rect rect = new Rect(); 339 source.getBoundsInParent(rect); 340 result.setBoundsInParent(rect); 341 source.getBoundsInScreen(rect); 342 result.setBoundsInScreen(rect); 343 344 // Set up the parent view, if applicable. 345 final ViewParent parent = mView.getParentForAccessibility(); 346 if (parent instanceof View) { 347 result.setParent((View) parent); 348 } 349 350 // Populate the minimum required fields. 351 result.setVisibleToUser(source.isVisibleToUser()); 352 result.setEnabled(source.isEnabled()); 353 result.setPackageName(source.getPackageName()); 354 result.setClassName(source.getClassName()); 355 356 // Add the Chrome root node. 357 if (isFrameInfoInitialized()) { 358 result.addChild(mView, rootId); 359 } 360 361 return result; 362 } 363 isFrameInfoInitialized()364 private boolean isFrameInfoInitialized() { 365 return mRenderCoordinates.getContentWidthCss() != 0.0 || 366 mRenderCoordinates.getContentHeightCss() != 0.0; 367 } 368 369 @CalledByNative handlePageLoaded(int id)370 private void handlePageLoaded(int id) { 371 if (mUserHasTouchExplored) return; 372 373 if (mContentViewCore.shouldSetAccessibilityFocusOnPageLoad()) { 374 mAccessibilityFocusId = id; 375 sendAccessibilityEvent(id, AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED); 376 } 377 } 378 379 @CalledByNative handleFocusChanged(int id)380 private void handleFocusChanged(int id) { 381 sendAccessibilityEvent(id, AccessibilityEvent.TYPE_VIEW_FOCUSED); 382 383 // Update accessibility focus if not already set to this node. 384 if (mAccessibilityFocusId != id) { 385 sendAccessibilityEvent(id, AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED); 386 mAccessibilityFocusId = id; 387 } 388 } 389 390 @CalledByNative handleCheckStateChanged(int id)391 private void handleCheckStateChanged(int id) { 392 sendAccessibilityEvent(id, AccessibilityEvent.TYPE_VIEW_CLICKED); 393 } 394 395 @CalledByNative handleTextSelectionChanged(int id)396 private void handleTextSelectionChanged(int id) { 397 sendAccessibilityEvent(id, AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED); 398 } 399 400 @CalledByNative handleEditableTextChanged(int id)401 private void handleEditableTextChanged(int id) { 402 sendAccessibilityEvent(id, AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); 403 } 404 405 @CalledByNative handleContentChanged(int id)406 private void handleContentChanged(int id) { 407 int rootId = nativeGetRootId(mNativeObj); 408 if (rootId != mCurrentRootId) { 409 mCurrentRootId = rootId; 410 mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); 411 } else { 412 sendAccessibilityEvent(id, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); 413 } 414 } 415 416 @CalledByNative handleNavigate()417 private void handleNavigate() { 418 mAccessibilityFocusId = View.NO_ID; 419 mUserHasTouchExplored = false; 420 // Invalidate the host, since its child is now gone. 421 mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); 422 } 423 424 @CalledByNative handleScrollPositionChanged(int id)425 private void handleScrollPositionChanged(int id) { 426 sendAccessibilityEvent(id, AccessibilityEvent.TYPE_VIEW_SCROLLED); 427 } 428 429 @CalledByNative handleScrolledToAnchor(int id)430 private void handleScrolledToAnchor(int id) { 431 if (mAccessibilityFocusId == id) { 432 return; 433 } 434 435 mAccessibilityFocusId = id; 436 sendAccessibilityEvent(id, AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED); 437 } 438 439 @CalledByNative handleHover(int id)440 private void handleHover(int id) { 441 if (mLastHoverId == id) return; 442 443 // Always send the ENTER and then the EXIT event, to match a standard Android View. 444 sendAccessibilityEvent(id, AccessibilityEvent.TYPE_VIEW_HOVER_ENTER); 445 sendAccessibilityEvent(mLastHoverId, AccessibilityEvent.TYPE_VIEW_HOVER_EXIT); 446 mLastHoverId = id; 447 } 448 449 @CalledByNative announceLiveRegionText(String text)450 private void announceLiveRegionText(String text) { 451 mView.announceForAccessibility(text); 452 } 453 454 @CalledByNative setAccessibilityNodeInfoParent(AccessibilityNodeInfo node, int parentId)455 private void setAccessibilityNodeInfoParent(AccessibilityNodeInfo node, int parentId) { 456 node.setParent(mView, parentId); 457 } 458 459 @CalledByNative addAccessibilityNodeInfoChild(AccessibilityNodeInfo node, int childId)460 private void addAccessibilityNodeInfoChild(AccessibilityNodeInfo node, int childId) { 461 node.addChild(mView, childId); 462 } 463 464 @CalledByNative 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)465 private void setAccessibilityNodeInfoBooleanAttributes(AccessibilityNodeInfo node, 466 int virtualViewId, boolean checkable, boolean checked, boolean clickable, 467 boolean enabled, boolean focusable, boolean focused, boolean password, 468 boolean scrollable, boolean selected, boolean visibleToUser) { 469 node.setCheckable(checkable); 470 node.setChecked(checked); 471 node.setClickable(clickable); 472 node.setEnabled(enabled); 473 node.setFocusable(focusable); 474 node.setFocused(focused); 475 node.setPassword(password); 476 node.setScrollable(scrollable); 477 node.setSelected(selected); 478 node.setVisibleToUser(visibleToUser); 479 480 node.addAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT); 481 node.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT); 482 483 if (focusable) { 484 if (focused) { 485 node.addAction(AccessibilityNodeInfo.ACTION_CLEAR_FOCUS); 486 } else { 487 node.addAction(AccessibilityNodeInfo.ACTION_FOCUS); 488 } 489 } 490 491 if (mAccessibilityFocusId == virtualViewId) { 492 node.setAccessibilityFocused(true); 493 node.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS); 494 } else { 495 node.setAccessibilityFocused(false); 496 node.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS); 497 } 498 499 if (clickable) { 500 node.addAction(AccessibilityNodeInfo.ACTION_CLICK); 501 } 502 } 503 504 @CalledByNative setAccessibilityNodeInfoClassName(AccessibilityNodeInfo node, String className)505 private void setAccessibilityNodeInfoClassName(AccessibilityNodeInfo node, 506 String className) { 507 node.setClassName(className); 508 } 509 510 @CalledByNative setAccessibilityNodeInfoContentDescription( AccessibilityNodeInfo node, String contentDescription, boolean annotateAsLink)511 private void setAccessibilityNodeInfoContentDescription( 512 AccessibilityNodeInfo node, String contentDescription, boolean annotateAsLink) { 513 if (annotateAsLink) { 514 SpannableString spannable = new SpannableString(contentDescription); 515 spannable.setSpan(new URLSpan(""), 0, spannable.length(), 0); 516 node.setContentDescription(spannable); 517 } else { 518 node.setContentDescription(contentDescription); 519 } 520 } 521 522 @CalledByNative setAccessibilityNodeInfoLocation(AccessibilityNodeInfo node, int absoluteLeft, int absoluteTop, int parentRelativeLeft, int parentRelativeTop, int width, int height, boolean isRootNode)523 private void setAccessibilityNodeInfoLocation(AccessibilityNodeInfo node, 524 int absoluteLeft, int absoluteTop, int parentRelativeLeft, int parentRelativeTop, 525 int width, int height, boolean isRootNode) { 526 // First set the bounds in parent. 527 Rect boundsInParent = new Rect(parentRelativeLeft, parentRelativeTop, 528 parentRelativeLeft + width, parentRelativeTop + height); 529 if (isRootNode) { 530 // Offset of the web content relative to the View. 531 boundsInParent.offset(0, (int) mRenderCoordinates.getContentOffsetYPix()); 532 } 533 node.setBoundsInParent(boundsInParent); 534 535 // Now set the absolute rect, which requires several transformations. 536 Rect rect = new Rect(absoluteLeft, absoluteTop, absoluteLeft + width, absoluteTop + height); 537 538 // Offset by the scroll position. 539 rect.offset(-(int) mRenderCoordinates.getScrollX(), 540 -(int) mRenderCoordinates.getScrollY()); 541 542 // Convert CSS (web) pixels to Android View pixels 543 rect.left = (int) mRenderCoordinates.fromLocalCssToPix(rect.left); 544 rect.top = (int) mRenderCoordinates.fromLocalCssToPix(rect.top); 545 rect.bottom = (int) mRenderCoordinates.fromLocalCssToPix(rect.bottom); 546 rect.right = (int) mRenderCoordinates.fromLocalCssToPix(rect.right); 547 548 // Offset by the location of the web content within the view. 549 rect.offset(0, 550 (int) mRenderCoordinates.getContentOffsetYPix()); 551 552 // Finally offset by the location of the view within the screen. 553 final int[] viewLocation = new int[2]; 554 mView.getLocationOnScreen(viewLocation); 555 rect.offset(viewLocation[0], viewLocation[1]); 556 557 node.setBoundsInScreen(rect); 558 } 559 560 @CalledByNative setAccessibilityNodeInfoKitKatAttributes(AccessibilityNodeInfo node, boolean canOpenPopup, boolean contentInvalid, boolean dismissable, boolean multiLine, int inputType, int liveRegion)561 protected void setAccessibilityNodeInfoKitKatAttributes(AccessibilityNodeInfo node, 562 boolean canOpenPopup, 563 boolean contentInvalid, 564 boolean dismissable, 565 boolean multiLine, 566 int inputType, 567 int liveRegion) { 568 // Requires KitKat or higher. 569 } 570 571 @CalledByNative setAccessibilityNodeInfoCollectionInfo(AccessibilityNodeInfo node, int rowCount, int columnCount, boolean hierarchical)572 protected void setAccessibilityNodeInfoCollectionInfo(AccessibilityNodeInfo node, 573 int rowCount, int columnCount, boolean hierarchical) { 574 // Requires KitKat or higher. 575 } 576 577 @CalledByNative setAccessibilityNodeInfoCollectionItemInfo(AccessibilityNodeInfo node, int rowIndex, int rowSpan, int columnIndex, int columnSpan, boolean heading)578 protected void setAccessibilityNodeInfoCollectionItemInfo(AccessibilityNodeInfo node, 579 int rowIndex, int rowSpan, int columnIndex, int columnSpan, boolean heading) { 580 // Requires KitKat or higher. 581 } 582 583 @CalledByNative setAccessibilityNodeInfoRangeInfo(AccessibilityNodeInfo node, int rangeType, float min, float max, float current)584 protected void setAccessibilityNodeInfoRangeInfo(AccessibilityNodeInfo node, 585 int rangeType, float min, float max, float current) { 586 // Requires KitKat or higher. 587 } 588 589 @CalledByNative setAccessibilityEventBooleanAttributes(AccessibilityEvent event, boolean checked, boolean enabled, boolean password, boolean scrollable)590 private void setAccessibilityEventBooleanAttributes(AccessibilityEvent event, 591 boolean checked, boolean enabled, boolean password, boolean scrollable) { 592 event.setChecked(checked); 593 event.setEnabled(enabled); 594 event.setPassword(password); 595 event.setScrollable(scrollable); 596 } 597 598 @CalledByNative setAccessibilityEventClassName(AccessibilityEvent event, String className)599 private void setAccessibilityEventClassName(AccessibilityEvent event, String className) { 600 event.setClassName(className); 601 } 602 603 @CalledByNative setAccessibilityEventListAttributes(AccessibilityEvent event, int currentItemIndex, int itemCount)604 private void setAccessibilityEventListAttributes(AccessibilityEvent event, 605 int currentItemIndex, int itemCount) { 606 event.setCurrentItemIndex(currentItemIndex); 607 event.setItemCount(itemCount); 608 } 609 610 @CalledByNative setAccessibilityEventScrollAttributes(AccessibilityEvent event, int scrollX, int scrollY, int maxScrollX, int maxScrollY)611 private void setAccessibilityEventScrollAttributes(AccessibilityEvent event, 612 int scrollX, int scrollY, int maxScrollX, int maxScrollY) { 613 event.setScrollX(scrollX); 614 event.setScrollY(scrollY); 615 event.setMaxScrollX(maxScrollX); 616 event.setMaxScrollY(maxScrollY); 617 } 618 619 @CalledByNative setAccessibilityEventTextChangedAttrs(AccessibilityEvent event, int fromIndex, int addedCount, int removedCount, String beforeText, String text)620 private void setAccessibilityEventTextChangedAttrs(AccessibilityEvent event, 621 int fromIndex, int addedCount, int removedCount, String beforeText, String text) { 622 event.setFromIndex(fromIndex); 623 event.setAddedCount(addedCount); 624 event.setRemovedCount(removedCount); 625 event.setBeforeText(beforeText); 626 event.getText().add(text); 627 } 628 629 @CalledByNative setAccessibilityEventSelectionAttrs(AccessibilityEvent event, int fromIndex, int addedCount, int itemCount, String text)630 private void setAccessibilityEventSelectionAttrs(AccessibilityEvent event, 631 int fromIndex, int addedCount, int itemCount, String text) { 632 event.setFromIndex(fromIndex); 633 event.setAddedCount(addedCount); 634 event.setItemCount(itemCount); 635 event.getText().add(text); 636 } 637 638 @CalledByNative setAccessibilityEventKitKatAttributes(AccessibilityEvent event, boolean canOpenPopup, boolean contentInvalid, boolean dismissable, boolean multiLine, int inputType, int liveRegion)639 protected void setAccessibilityEventKitKatAttributes(AccessibilityEvent event, 640 boolean canOpenPopup, 641 boolean contentInvalid, 642 boolean dismissable, 643 boolean multiLine, 644 int inputType, 645 int liveRegion) { 646 // Backwards compatibility for KitKat AccessibilityNodeInfo fields. 647 Bundle bundle = getOrCreateBundleForAccessibilityEvent(event); 648 bundle.putBoolean("AccessibilityNodeInfo.canOpenPopup", canOpenPopup); 649 bundle.putBoolean("AccessibilityNodeInfo.contentInvalid", contentInvalid); 650 bundle.putBoolean("AccessibilityNodeInfo.dismissable", dismissable); 651 bundle.putBoolean("AccessibilityNodeInfo.multiLine", multiLine); 652 bundle.putInt("AccessibilityNodeInfo.inputType", inputType); 653 bundle.putInt("AccessibilityNodeInfo.liveRegion", liveRegion); 654 } 655 656 @CalledByNative setAccessibilityEventCollectionInfo(AccessibilityEvent event, int rowCount, int columnCount, boolean hierarchical)657 protected void setAccessibilityEventCollectionInfo(AccessibilityEvent event, 658 int rowCount, int columnCount, boolean hierarchical) { 659 // Backwards compatibility for KitKat AccessibilityNodeInfo fields. 660 Bundle bundle = getOrCreateBundleForAccessibilityEvent(event); 661 bundle.putInt("AccessibilityNodeInfo.CollectionInfo.rowCount", rowCount); 662 bundle.putInt("AccessibilityNodeInfo.CollectionInfo.columnCount", columnCount); 663 bundle.putBoolean("AccessibilityNodeInfo.CollectionInfo.hierarchical", hierarchical); 664 } 665 666 @CalledByNative setAccessibilityEventHeadingFlag(AccessibilityEvent event, boolean heading)667 protected void setAccessibilityEventHeadingFlag(AccessibilityEvent event, 668 boolean heading) { 669 // Backwards compatibility for KitKat AccessibilityNodeInfo fields. 670 Bundle bundle = getOrCreateBundleForAccessibilityEvent(event); 671 bundle.putBoolean("AccessibilityNodeInfo.CollectionItemInfo.heading", heading); 672 } 673 674 @CalledByNative setAccessibilityEventCollectionItemInfo(AccessibilityEvent event, int rowIndex, int rowSpan, int columnIndex, int columnSpan)675 protected void setAccessibilityEventCollectionItemInfo(AccessibilityEvent event, 676 int rowIndex, int rowSpan, int columnIndex, int columnSpan) { 677 // Backwards compatibility for KitKat AccessibilityNodeInfo fields. 678 Bundle bundle = getOrCreateBundleForAccessibilityEvent(event); 679 bundle.putInt("AccessibilityNodeInfo.CollectionItemInfo.rowIndex", rowIndex); 680 bundle.putInt("AccessibilityNodeInfo.CollectionItemInfo.rowSpan", rowSpan); 681 bundle.putInt("AccessibilityNodeInfo.CollectionItemInfo.columnIndex", columnIndex); 682 bundle.putInt("AccessibilityNodeInfo.CollectionItemInfo.columnSpan", columnSpan); 683 } 684 685 @CalledByNative setAccessibilityEventRangeInfo(AccessibilityEvent event, int rangeType, float min, float max, float current)686 protected void setAccessibilityEventRangeInfo(AccessibilityEvent event, 687 int rangeType, float min, float max, float current) { 688 // Backwards compatibility for KitKat AccessibilityNodeInfo fields. 689 Bundle bundle = getOrCreateBundleForAccessibilityEvent(event); 690 bundle.putInt("AccessibilityNodeInfo.RangeInfo.type", rangeType); 691 bundle.putFloat("AccessibilityNodeInfo.RangeInfo.min", min); 692 bundle.putFloat("AccessibilityNodeInfo.RangeInfo.max", max); 693 bundle.putFloat("AccessibilityNodeInfo.RangeInfo.current", current); 694 } 695 nativeGetRootId(long nativeBrowserAccessibilityManagerAndroid)696 private native int nativeGetRootId(long nativeBrowserAccessibilityManagerAndroid); nativeIsNodeValid(long nativeBrowserAccessibilityManagerAndroid, int id)697 private native boolean nativeIsNodeValid(long nativeBrowserAccessibilityManagerAndroid, int id); nativeHitTest(long nativeBrowserAccessibilityManagerAndroid, int x, int y)698 private native void nativeHitTest(long nativeBrowserAccessibilityManagerAndroid, int x, int y); nativePopulateAccessibilityNodeInfo( long nativeBrowserAccessibilityManagerAndroid, AccessibilityNodeInfo info, int id)699 private native boolean nativePopulateAccessibilityNodeInfo( 700 long nativeBrowserAccessibilityManagerAndroid, AccessibilityNodeInfo info, int id); nativePopulateAccessibilityEvent( long nativeBrowserAccessibilityManagerAndroid, AccessibilityEvent event, int id, int eventType)701 private native boolean nativePopulateAccessibilityEvent( 702 long nativeBrowserAccessibilityManagerAndroid, AccessibilityEvent event, int id, 703 int eventType); nativeClick(long nativeBrowserAccessibilityManagerAndroid, int id)704 private native void nativeClick(long nativeBrowserAccessibilityManagerAndroid, int id); nativeFocus(long nativeBrowserAccessibilityManagerAndroid, int id)705 private native void nativeFocus(long nativeBrowserAccessibilityManagerAndroid, int id); nativeBlur(long nativeBrowserAccessibilityManagerAndroid)706 private native void nativeBlur(long nativeBrowserAccessibilityManagerAndroid); nativeScrollToMakeNodeVisible( long nativeBrowserAccessibilityManagerAndroid, int id)707 private native void nativeScrollToMakeNodeVisible( 708 long nativeBrowserAccessibilityManagerAndroid, int id); nativeFindElementType(long nativeBrowserAccessibilityManagerAndroid, int startId, String elementType, boolean forwards)709 private native int nativeFindElementType(long nativeBrowserAccessibilityManagerAndroid, 710 int startId, String elementType, boolean forwards); 711 } 712