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 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 create(long nativeBrowserAccessibilityManagerAndroid, ContentViewCore contentViewCore)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 BrowserAccessibilityManager(long nativeBrowserAccessibilityManagerAndroid, ContentViewCore contentViewCore)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 onNativeObjectDestroyed()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 */ getAccessibilityNodeProvider()110 public AccessibilityNodeProvider getAccessibilityNodeProvider() { 111 return null; 112 } 113 114 /** 115 * @see AccessibilityNodeProvider#createAccessibilityNodeInfo(int) 116 */ createAccessibilityNodeInfo(int virtualViewId)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 */ findAccessibilityNodeInfosByText(String text, int virtualViewId)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 */ performAction(int virtualViewId, int action, Bundle arguments)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 */ onHoverEvent(MotionEvent event)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 */ notifyFrameInfoInitialized()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 jumpToElementType(String elementType, boolean forwards)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 moveAccessibilityFocusToId(int newAccessibilityFocusId)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 moveAccessibilityFocusToIdAndRefocusIfNeeded(int newAccessibilityFocusId)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 sendAccessibilityEvent(int virtualViewId, int eventType)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 getOrCreateBundleForAccessibilityEvent(AccessibilityEvent event)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 createNodeForHost(int rootId)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 */ isFrameInfoInitialized()397 private boolean isFrameInfoInitialized() { 398 return mRenderCoordinates.getContentWidthCss() != 0.0 || 399 mRenderCoordinates.getContentHeightCss() != 0.0; 400 } 401 402 @CalledByNative handlePageLoaded(int id)403 private void handlePageLoaded(int id) { 404 if (mUserHasTouchExplored) return; 405 406 if (mContentViewCore.shouldSetAccessibilityFocusOnPageLoad()) { 407 moveAccessibilityFocusToIdAndRefocusIfNeeded(id); 408 } 409 } 410 411 @CalledByNative handleFocusChanged(int id)412 private void handleFocusChanged(int id) { 413 sendAccessibilityEvent(id, AccessibilityEvent.TYPE_VIEW_FOCUSED); 414 moveAccessibilityFocusToId(id); 415 } 416 417 @CalledByNative handleCheckStateChanged(int id)418 private void handleCheckStateChanged(int id) { 419 sendAccessibilityEvent(id, AccessibilityEvent.TYPE_VIEW_CLICKED); 420 } 421 422 @CalledByNative handleTextSelectionChanged(int id)423 private void handleTextSelectionChanged(int id) { 424 sendAccessibilityEvent(id, AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED); 425 } 426 427 @CalledByNative handleEditableTextChanged(int id)428 private void handleEditableTextChanged(int id) { 429 sendAccessibilityEvent(id, AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); 430 } 431 432 @CalledByNative handleContentChanged(int id)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 handleNavigate()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 handleScrollPositionChanged(int id)453 private void handleScrollPositionChanged(int id) { 454 sendAccessibilityEvent(id, AccessibilityEvent.TYPE_VIEW_SCROLLED); 455 } 456 457 @CalledByNative handleScrolledToAnchor(int id)458 private void handleScrolledToAnchor(int id) { 459 moveAccessibilityFocusToId(id); 460 } 461 462 @CalledByNative handleHover(int id)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 announceLiveRegionText(String text)473 private void announceLiveRegionText(String text) { 474 mView.announceForAccessibility(text); 475 } 476 477 @CalledByNative setAccessibilityNodeInfoParent(AccessibilityNodeInfo node, int parentId)478 private void setAccessibilityNodeInfoParent(AccessibilityNodeInfo node, int parentId) { 479 node.setParent(mView, parentId); 480 } 481 482 @CalledByNative addAccessibilityNodeInfoChild(AccessibilityNodeInfo node, int childId)483 private void addAccessibilityNodeInfoChild(AccessibilityNodeInfo node, int childId) { 484 node.addChild(mView, childId); 485 } 486 487 @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)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 setAccessibilityNodeInfoClassName(AccessibilityNodeInfo node, String className)528 private void setAccessibilityNodeInfoClassName(AccessibilityNodeInfo node, 529 String className) { 530 node.setClassName(className); 531 } 532 533 @CalledByNative setAccessibilityNodeInfoContentDescription( AccessibilityNodeInfo node, String contentDescription, boolean annotateAsLink)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 setAccessibilityNodeInfoLocation(AccessibilityNodeInfo node, final int virtualViewId, int absoluteLeft, int absoluteTop, int parentRelativeLeft, int parentRelativeTop, int width, int height, boolean isRootNode)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 setAccessibilityNodeInfoKitKatAttributes(AccessibilityNodeInfo node, boolean canOpenPopup, boolean contentInvalid, boolean dismissable, boolean multiLine, int inputType, int liveRegion)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 setAccessibilityNodeInfoCollectionInfo(AccessibilityNodeInfo node, int rowCount, int columnCount, boolean hierarchical)610 protected void setAccessibilityNodeInfoCollectionInfo(AccessibilityNodeInfo node, 611 int rowCount, int columnCount, boolean hierarchical) { 612 // Requires KitKat or higher. 613 } 614 615 @CalledByNative setAccessibilityNodeInfoCollectionItemInfo(AccessibilityNodeInfo node, int rowIndex, int rowSpan, int columnIndex, int columnSpan, boolean heading)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 setAccessibilityNodeInfoRangeInfo(AccessibilityNodeInfo node, int rangeType, float min, float max, float current)622 protected void setAccessibilityNodeInfoRangeInfo(AccessibilityNodeInfo node, 623 int rangeType, float min, float max, float current) { 624 // Requires KitKat or higher. 625 } 626 627 @CalledByNative setAccessibilityEventBooleanAttributes(AccessibilityEvent event, boolean checked, boolean enabled, boolean password, boolean scrollable)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 setAccessibilityEventClassName(AccessibilityEvent event, String className)637 private void setAccessibilityEventClassName(AccessibilityEvent event, String className) { 638 event.setClassName(className); 639 } 640 641 @CalledByNative setAccessibilityEventListAttributes(AccessibilityEvent event, int currentItemIndex, int itemCount)642 private void setAccessibilityEventListAttributes(AccessibilityEvent event, 643 int currentItemIndex, int itemCount) { 644 event.setCurrentItemIndex(currentItemIndex); 645 event.setItemCount(itemCount); 646 } 647 648 @CalledByNative setAccessibilityEventScrollAttributes(AccessibilityEvent event, int scrollX, int scrollY, int maxScrollX, int maxScrollY)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 setAccessibilityEventTextChangedAttrs(AccessibilityEvent event, int fromIndex, int addedCount, int removedCount, String beforeText, String text)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 setAccessibilityEventSelectionAttrs(AccessibilityEvent event, int fromIndex, int addedCount, int itemCount, String text)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 setAccessibilityEventKitKatAttributes(AccessibilityEvent event, boolean canOpenPopup, boolean contentInvalid, boolean dismissable, boolean multiLine, int inputType, int liveRegion)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 setAccessibilityEventCollectionInfo(AccessibilityEvent event, int rowCount, int columnCount, boolean hierarchical)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 setAccessibilityEventHeadingFlag(AccessibilityEvent event, boolean heading)705 protected void setAccessibilityEventHeadingFlag(AccessibilityEvent event, 706 boolean heading) { 707 // Backwards compatibility for KitKat AccessibilityNodeInfo fields. 708 Bundle bundle = getOrCreateBundleForAccessibilityEvent(event); 709 bundle.putBoolean("AccessibilityNodeInfo.CollectionItemInfo.heading", heading); 710 } 711 712 @CalledByNative setAccessibilityEventCollectionItemInfo(AccessibilityEvent event, int rowIndex, int rowSpan, int columnIndex, int columnSpan)713 protected void setAccessibilityEventCollectionItemInfo(AccessibilityEvent event, 714 int rowIndex, int rowSpan, int columnIndex, int columnSpan) { 715 // Backwards compatibility for KitKat AccessibilityNodeInfo fields. 716 Bundle bundle = getOrCreateBundleForAccessibilityEvent(event); 717 bundle.putInt("AccessibilityNodeInfo.CollectionItemInfo.rowIndex", rowIndex); 718 bundle.putInt("AccessibilityNodeInfo.CollectionItemInfo.rowSpan", rowSpan); 719 bundle.putInt("AccessibilityNodeInfo.CollectionItemInfo.columnIndex", columnIndex); 720 bundle.putInt("AccessibilityNodeInfo.CollectionItemInfo.columnSpan", columnSpan); 721 } 722 723 @CalledByNative setAccessibilityEventRangeInfo(AccessibilityEvent event, int rangeType, float min, float max, float current)724 protected void setAccessibilityEventRangeInfo(AccessibilityEvent event, 725 int rangeType, float min, float max, float current) { 726 // Backwards compatibility for KitKat AccessibilityNodeInfo fields. 727 Bundle bundle = getOrCreateBundleForAccessibilityEvent(event); 728 bundle.putInt("AccessibilityNodeInfo.RangeInfo.type", rangeType); 729 bundle.putFloat("AccessibilityNodeInfo.RangeInfo.min", min); 730 bundle.putFloat("AccessibilityNodeInfo.RangeInfo.max", max); 731 bundle.putFloat("AccessibilityNodeInfo.RangeInfo.current", current); 732 } 733 nativeGetRootId(long nativeBrowserAccessibilityManagerAndroid)734 private native int nativeGetRootId(long nativeBrowserAccessibilityManagerAndroid); nativeIsNodeValid(long nativeBrowserAccessibilityManagerAndroid, int id)735 private native boolean nativeIsNodeValid(long nativeBrowserAccessibilityManagerAndroid, int id); nativeHitTest(long nativeBrowserAccessibilityManagerAndroid, int x, int y)736 private native void nativeHitTest(long nativeBrowserAccessibilityManagerAndroid, int x, int y); nativePopulateAccessibilityNodeInfo( long nativeBrowserAccessibilityManagerAndroid, AccessibilityNodeInfo info, int id)737 private native boolean nativePopulateAccessibilityNodeInfo( 738 long nativeBrowserAccessibilityManagerAndroid, AccessibilityNodeInfo info, int id); nativePopulateAccessibilityEvent( long nativeBrowserAccessibilityManagerAndroid, AccessibilityEvent event, int id, int eventType)739 private native boolean nativePopulateAccessibilityEvent( 740 long nativeBrowserAccessibilityManagerAndroid, AccessibilityEvent event, int id, 741 int eventType); nativeClick(long nativeBrowserAccessibilityManagerAndroid, int id)742 private native void nativeClick(long nativeBrowserAccessibilityManagerAndroid, int id); nativeFocus(long nativeBrowserAccessibilityManagerAndroid, int id)743 private native void nativeFocus(long nativeBrowserAccessibilityManagerAndroid, int id); nativeBlur(long nativeBrowserAccessibilityManagerAndroid)744 private native void nativeBlur(long nativeBrowserAccessibilityManagerAndroid); nativeScrollToMakeNodeVisible( long nativeBrowserAccessibilityManagerAndroid, int id)745 private native void nativeScrollToMakeNodeVisible( 746 long nativeBrowserAccessibilityManagerAndroid, int id); nativeFindElementType(long nativeBrowserAccessibilityManagerAndroid, int startId, String elementType, boolean forwards)747 private native int nativeFindElementType(long nativeBrowserAccessibilityManagerAndroid, 748 int startId, String elementType, boolean forwards); 749 } 750