• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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