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.
5 #include "content/browser/accessibility/browser_accessibility_manager_android.h"
9 #include "base/android/jni_android.h"
10 #include "base/android/jni_string.h"
11 #include "base/i18n/char_iterator.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/values.h"
15 #include "content/browser/accessibility/browser_accessibility_android.h"
16 #include "content/common/accessibility_messages.h"
17 #include "jni/BrowserAccessibilityManager_jni.h"
18 #include "ui/accessibility/ax_text_utils.h"
20 using base::android::AttachCurrentThread
;
21 using base::android::ScopedJavaLocalRef
;
25 enum AndroidHtmlElementType
{
26 HTML_ELEMENT_TYPE_SECTION
,
27 HTML_ELEMENT_TYPE_LIST
,
28 HTML_ELEMENT_TYPE_CONTROL
,
32 // These are special unofficial strings sent from TalkBack/BrailleBack
33 // to jump to certain categories of web elements.
34 AndroidHtmlElementType
HtmlElementTypeFromString(base::string16 element_type
) {
35 if (element_type
== base::ASCIIToUTF16("SECTION"))
36 return HTML_ELEMENT_TYPE_SECTION
;
37 else if (element_type
== base::ASCIIToUTF16("LIST"))
38 return HTML_ELEMENT_TYPE_LIST
;
39 else if (element_type
== base::ASCIIToUTF16("CONTROL"))
40 return HTML_ELEMENT_TYPE_CONTROL
;
42 return HTML_ELEMENT_TYPE_ANY
;
45 } // anonymous namespace
49 namespace aria_strings
{
50 const char kAriaLivePolite
[] = "polite";
51 const char kAriaLiveAssertive
[] = "assertive";
55 BrowserAccessibilityManager
* BrowserAccessibilityManager::Create(
56 const ui::AXTreeUpdate
& initial_tree
,
57 BrowserAccessibilityDelegate
* delegate
,
58 BrowserAccessibilityFactory
* factory
) {
59 return new BrowserAccessibilityManagerAndroid(
60 ScopedJavaLocalRef
<jobject
>(), initial_tree
, delegate
, factory
);
63 BrowserAccessibilityManagerAndroid
*
64 BrowserAccessibilityManager::ToBrowserAccessibilityManagerAndroid() {
65 return static_cast<BrowserAccessibilityManagerAndroid
*>(this);
68 BrowserAccessibilityManagerAndroid::BrowserAccessibilityManagerAndroid(
69 ScopedJavaLocalRef
<jobject
> content_view_core
,
70 const ui::AXTreeUpdate
& initial_tree
,
71 BrowserAccessibilityDelegate
* delegate
,
72 BrowserAccessibilityFactory
* factory
)
73 : BrowserAccessibilityManager(delegate
, factory
),
74 prune_tree_for_screen_reader_(true) {
75 Initialize(initial_tree
);
76 SetContentViewCore(content_view_core
);
79 BrowserAccessibilityManagerAndroid::~BrowserAccessibilityManagerAndroid() {
80 JNIEnv
* env
= AttachCurrentThread();
81 ScopedJavaLocalRef
<jobject
> obj
= java_ref_
.get(env
);
85 Java_BrowserAccessibilityManager_onNativeObjectDestroyed(env
, obj
.obj());
89 ui::AXTreeUpdate
BrowserAccessibilityManagerAndroid::GetEmptyDocument() {
90 ui::AXNodeData empty_document
;
91 empty_document
.id
= 0;
92 empty_document
.role
= ui::AX_ROLE_ROOT_WEB_AREA
;
93 empty_document
.state
= 1 << ui::AX_STATE_READ_ONLY
;
95 ui::AXTreeUpdate update
;
96 update
.nodes
.push_back(empty_document
);
100 void BrowserAccessibilityManagerAndroid::SetContentViewCore(
101 ScopedJavaLocalRef
<jobject
> content_view_core
) {
102 if (content_view_core
.is_null())
105 JNIEnv
* env
= AttachCurrentThread();
106 java_ref_
= JavaObjectWeakGlobalRef(
107 env
, Java_BrowserAccessibilityManager_create(
108 env
, reinterpret_cast<intptr_t>(this),
109 content_view_core
.obj()).obj());
112 void BrowserAccessibilityManagerAndroid::NotifyAccessibilityEvent(
113 ui::AXEvent event_type
,
114 BrowserAccessibility
* node
) {
115 JNIEnv
* env
= AttachCurrentThread();
116 ScopedJavaLocalRef
<jobject
> obj
= java_ref_
.get(env
);
120 BrowserAccessibilityAndroid
* android_node
=
121 static_cast<BrowserAccessibilityAndroid
*>(node
);
123 if (event_type
== ui::AX_EVENT_HIDE
)
126 if (event_type
== ui::AX_EVENT_TREE_CHANGED
)
129 if (event_type
== ui::AX_EVENT_HOVER
) {
130 HandleHoverEvent(node
);
134 // Always send AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED to notify
135 // the Android system that the accessibility hierarchy rooted at this
137 Java_BrowserAccessibilityManager_handleContentChanged(
138 env
, obj
.obj(), node
->GetId());
140 switch (event_type
) {
141 case ui::AX_EVENT_LOAD_COMPLETE
:
142 Java_BrowserAccessibilityManager_handlePageLoaded(
143 env
, obj
.obj(), focus_
->id());
145 case ui::AX_EVENT_FOCUS
:
146 Java_BrowserAccessibilityManager_handleFocusChanged(
147 env
, obj
.obj(), node
->GetId());
149 case ui::AX_EVENT_CHECKED_STATE_CHANGED
:
150 Java_BrowserAccessibilityManager_handleCheckStateChanged(
151 env
, obj
.obj(), node
->GetId());
153 case ui::AX_EVENT_SCROLL_POSITION_CHANGED
:
154 Java_BrowserAccessibilityManager_handleScrollPositionChanged(
155 env
, obj
.obj(), node
->GetId());
157 case ui::AX_EVENT_SCROLLED_TO_ANCHOR
:
158 Java_BrowserAccessibilityManager_handleScrolledToAnchor(
159 env
, obj
.obj(), node
->GetId());
161 case ui::AX_EVENT_ALERT
:
162 // An alert is a special case of live region. Fall through to the
163 // next case to handle it.
164 case ui::AX_EVENT_SHOW
: {
165 // This event is fired when an object appears in a live region.
167 Java_BrowserAccessibilityManager_announceLiveRegionText(
169 base::android::ConvertUTF16ToJavaString(
170 env
, android_node
->GetText()).obj());
173 case ui::AX_EVENT_TEXT_SELECTION_CHANGED
:
174 Java_BrowserAccessibilityManager_handleTextSelectionChanged(
175 env
, obj
.obj(), node
->GetId());
177 case ui::AX_EVENT_TEXT_CHANGED
:
178 case ui::AX_EVENT_VALUE_CHANGED
:
179 if (node
->IsEditableText() && GetFocus(GetRoot()) == node
) {
180 Java_BrowserAccessibilityManager_handleEditableTextChanged(
181 env
, obj
.obj(), node
->GetId());
182 } else if (android_node
->IsSlider()) {
183 Java_BrowserAccessibilityManager_handleSliderChanged(
184 env
, obj
.obj(), node
->GetId());
188 // There are some notifications that aren't meaningful on Android.
189 // It's okay to skip them.
194 jint
BrowserAccessibilityManagerAndroid::GetRootId(JNIEnv
* env
, jobject obj
) {
196 return static_cast<jint
>(GetRoot()->GetId());
201 jboolean
BrowserAccessibilityManagerAndroid::IsNodeValid(
202 JNIEnv
* env
, jobject obj
, jint id
) {
203 return GetFromID(id
) != NULL
;
206 void BrowserAccessibilityManagerAndroid::HitTest(
207 JNIEnv
* env
, jobject obj
, jint x
, jint y
) {
209 delegate()->AccessibilityHitTest(gfx::Point(x
, y
));
212 jboolean
BrowserAccessibilityManagerAndroid::IsEditableText(
213 JNIEnv
* env
, jobject obj
, jint id
) {
214 BrowserAccessibilityAndroid
* node
= static_cast<BrowserAccessibilityAndroid
*>(
219 return node
->IsEditableText();
222 jint
BrowserAccessibilityManagerAndroid::GetEditableTextSelectionStart(
223 JNIEnv
* env
, jobject obj
, jint id
) {
224 BrowserAccessibilityAndroid
* node
= static_cast<BrowserAccessibilityAndroid
*>(
229 return node
->GetIntAttribute(ui::AX_ATTR_TEXT_SEL_START
);
232 jint
BrowserAccessibilityManagerAndroid::GetEditableTextSelectionEnd(
233 JNIEnv
* env
, jobject obj
, jint id
) {
234 BrowserAccessibilityAndroid
* node
= static_cast<BrowserAccessibilityAndroid
*>(
239 return node
->GetIntAttribute(ui::AX_ATTR_TEXT_SEL_END
);
242 jboolean
BrowserAccessibilityManagerAndroid::PopulateAccessibilityNodeInfo(
243 JNIEnv
* env
, jobject obj
, jobject info
, jint id
) {
244 BrowserAccessibilityAndroid
* node
= static_cast<BrowserAccessibilityAndroid
*>(
249 if (node
->GetParent()) {
250 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoParent(
251 env
, obj
, info
, node
->GetParent()->GetId());
253 for (unsigned i
= 0; i
< node
->PlatformChildCount(); ++i
) {
254 Java_BrowserAccessibilityManager_addAccessibilityNodeInfoChild(
255 env
, obj
, info
, node
->InternalGetChild(i
)->GetId());
257 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoBooleanAttributes(
267 node
->IsScrollable(),
269 node
->IsVisibleToUser());
270 Java_BrowserAccessibilityManager_addAccessibilityNodeInfoActions(
273 node
->CanScrollForward(),
274 node
->CanScrollBackward(),
276 node
->CanScrollDown(),
277 node
->CanScrollLeft(),
278 node
->CanScrollRight(),
280 node
->IsEditableText(),
284 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoClassName(
286 base::android::ConvertUTF8ToJavaString(env
, node
->GetClassName()).obj());
287 if (!node
->IsPassword() ||
288 Java_BrowserAccessibilityManager_shouldExposePasswordText(env
, obj
)) {
289 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoContentDescription(
291 base::android::ConvertUTF16ToJavaString(env
, node
->GetText()).obj(),
294 base::string16 element_id
;
295 if (node
->GetHtmlAttribute("id", &element_id
)) {
296 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoViewIdResourceName(
298 base::android::ConvertUTF16ToJavaString(env
, element_id
).obj());
301 gfx::Rect absolute_rect
= node
->GetLocalBoundsRect();
302 gfx::Rect parent_relative_rect
= absolute_rect
;
303 if (node
->GetParent()) {
304 gfx::Rect parent_rect
= node
->GetParent()->GetLocalBoundsRect();
305 parent_relative_rect
.Offset(-parent_rect
.OffsetFromOrigin());
307 bool is_root
= node
->GetParent() == NULL
;
308 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoLocation(
311 absolute_rect
.x(), absolute_rect
.y(),
312 parent_relative_rect
.x(), parent_relative_rect
.y(),
313 absolute_rect
.width(), absolute_rect
.height(),
316 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoLollipopAttributes(
318 node
->CanOpenPopup(),
319 node
->IsContentInvalid(),
320 node
->IsDismissable(),
322 node
->AndroidInputType(),
323 node
->AndroidLiveRegionType());
324 if (node
->IsCollection()) {
325 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoCollectionInfo(
329 node
->IsHierarchical());
331 if (node
->IsCollectionItem() || node
->IsHeading()) {
332 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoCollectionItemInfo(
340 if (node
->IsRangeType()) {
341 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoRangeInfo(
343 node
->AndroidRangeType(),
346 node
->RangeCurrentValue());
352 jboolean
BrowserAccessibilityManagerAndroid::PopulateAccessibilityEvent(
353 JNIEnv
* env
, jobject obj
, jobject event
, jint id
, jint event_type
) {
354 BrowserAccessibilityAndroid
* node
= static_cast<BrowserAccessibilityAndroid
*>(
359 Java_BrowserAccessibilityManager_setAccessibilityEventBooleanAttributes(
364 node
->IsScrollable());
365 Java_BrowserAccessibilityManager_setAccessibilityEventClassName(
367 base::android::ConvertUTF8ToJavaString(env
, node
->GetClassName()).obj());
368 Java_BrowserAccessibilityManager_setAccessibilityEventListAttributes(
370 node
->GetItemIndex(),
371 node
->GetItemCount());
372 Java_BrowserAccessibilityManager_setAccessibilityEventScrollAttributes(
376 node
->GetMaxScrollX(),
377 node
->GetMaxScrollY());
379 switch (event_type
) {
380 case ANDROID_ACCESSIBILITY_EVENT_TEXT_CHANGED
: {
381 base::string16 before_text
, text
;
382 if (!node
->IsPassword() ||
383 Java_BrowserAccessibilityManager_shouldExposePasswordText(env
, obj
)) {
384 before_text
= node
->GetTextChangeBeforeText();
385 text
= node
->GetText();
387 Java_BrowserAccessibilityManager_setAccessibilityEventTextChangedAttrs(
389 node
->GetTextChangeFromIndex(),
390 node
->GetTextChangeAddedCount(),
391 node
->GetTextChangeRemovedCount(),
392 base::android::ConvertUTF16ToJavaString(
393 env
, before_text
).obj(),
394 base::android::ConvertUTF16ToJavaString(env
, text
).obj());
397 case ANDROID_ACCESSIBILITY_EVENT_TEXT_SELECTION_CHANGED
: {
399 if (!node
->IsPassword() ||
400 Java_BrowserAccessibilityManager_shouldExposePasswordText(env
, obj
)) {
401 text
= node
->GetText();
403 Java_BrowserAccessibilityManager_setAccessibilityEventSelectionAttrs(
405 node
->GetSelectionStart(),
406 node
->GetSelectionEnd(),
407 node
->GetEditableTextLength(),
408 base::android::ConvertUTF16ToJavaString(env
, text
).obj());
415 Java_BrowserAccessibilityManager_setAccessibilityEventLollipopAttributes(
417 node
->CanOpenPopup(),
418 node
->IsContentInvalid(),
419 node
->IsDismissable(),
421 node
->AndroidInputType(),
422 node
->AndroidLiveRegionType());
423 if (node
->IsCollection()) {
424 Java_BrowserAccessibilityManager_setAccessibilityEventCollectionInfo(
428 node
->IsHierarchical());
430 if (node
->IsHeading()) {
431 Java_BrowserAccessibilityManager_setAccessibilityEventHeadingFlag(
432 env
, obj
, event
, true);
434 if (node
->IsCollectionItem()) {
435 Java_BrowserAccessibilityManager_setAccessibilityEventCollectionItemInfo(
442 if (node
->IsRangeType()) {
443 Java_BrowserAccessibilityManager_setAccessibilityEventRangeInfo(
445 node
->AndroidRangeType(),
448 node
->RangeCurrentValue());
454 void BrowserAccessibilityManagerAndroid::Click(
455 JNIEnv
* env
, jobject obj
, jint id
) {
456 BrowserAccessibility
* node
= GetFromID(id
);
458 DoDefaultAction(*node
);
461 void BrowserAccessibilityManagerAndroid::Focus(
462 JNIEnv
* env
, jobject obj
, jint id
) {
463 BrowserAccessibility
* node
= GetFromID(id
);
465 SetFocus(node
, true);
468 void BrowserAccessibilityManagerAndroid::Blur(JNIEnv
* env
, jobject obj
) {
469 SetFocus(GetRoot(), true);
472 void BrowserAccessibilityManagerAndroid::ScrollToMakeNodeVisible(
473 JNIEnv
* env
, jobject obj
, jint id
) {
474 BrowserAccessibility
* node
= GetFromID(id
);
476 ScrollToMakeVisible(*node
, gfx::Rect(node
->GetLocation().size()));
479 void BrowserAccessibilityManagerAndroid::SetTextFieldValue(
480 JNIEnv
* env
, jobject obj
, jint id
, jstring value
) {
481 BrowserAccessibility
* node
= GetFromID(id
);
483 BrowserAccessibilityManager::SetValue(
484 *node
, base::android::ConvertJavaStringToUTF16(env
, value
));
488 void BrowserAccessibilityManagerAndroid::SetSelection(
489 JNIEnv
* env
, jobject obj
, jint id
, jint start
, jint end
) {
490 BrowserAccessibility
* node
= GetFromID(id
);
492 SetTextSelection(*node
, start
, end
);
495 jboolean
BrowserAccessibilityManagerAndroid::AdjustSlider(
496 JNIEnv
* env
, jobject obj
, jint id
, jboolean increment
) {
497 BrowserAccessibility
* node
= GetFromID(id
);
501 BrowserAccessibilityAndroid
* android_node
=
502 static_cast<BrowserAccessibilityAndroid
*>(node
);
504 if (!android_node
->IsSlider() || !android_node
->IsEnabled())
507 float value
= node
->GetFloatAttribute(ui::AX_ATTR_VALUE_FOR_RANGE
);
508 float min
= node
->GetFloatAttribute(ui::AX_ATTR_MIN_VALUE_FOR_RANGE
);
509 float max
= node
->GetFloatAttribute(ui::AX_ATTR_MAX_VALUE_FOR_RANGE
);
513 // To behave similarly to an Android SeekBar, move by an increment of
514 // approximately 20%.
515 float original_value
= value
;
516 float delta
= (max
- min
) / 5.0f
;
517 value
+= (increment
? delta
: -delta
);
518 value
= std::max(std::min(value
, max
), min
);
519 if (value
!= original_value
) {
520 BrowserAccessibilityManager::SetValue(
521 *node
, base::UTF8ToUTF16(base::DoubleToString(value
)));
527 void BrowserAccessibilityManagerAndroid::HandleHoverEvent(
528 BrowserAccessibility
* node
) {
529 JNIEnv
* env
= AttachCurrentThread();
530 ScopedJavaLocalRef
<jobject
> obj
= java_ref_
.get(env
);
534 BrowserAccessibilityAndroid
* ancestor
=
535 static_cast<BrowserAccessibilityAndroid
*>(node
->GetParent());
536 while (ancestor
&& ancestor
!= GetRoot()) {
537 if (ancestor
->PlatformIsLeaf() ||
538 (ancestor
->IsFocusable() && !ancestor
->HasFocusableChild())) {
540 // Don't break - we want the highest ancestor that's focusable or a
543 ancestor
= static_cast<BrowserAccessibilityAndroid
*>(ancestor
->GetParent());
546 Java_BrowserAccessibilityManager_handleHover(
547 env
, obj
.obj(), node
->GetId());
550 jint
BrowserAccessibilityManagerAndroid::FindElementType(
551 JNIEnv
* env
, jobject obj
, jint start_id
, jstring element_type_str
,
553 BrowserAccessibility
* node
= GetFromID(start_id
);
557 AndroidHtmlElementType element_type
= HtmlElementTypeFromString(
558 base::android::ConvertJavaStringToUTF16(env
, element_type_str
));
560 node
= forwards
? NextInTreeOrder(node
) : PreviousInTreeOrder(node
);
562 switch(element_type
) {
563 case HTML_ELEMENT_TYPE_SECTION
:
564 if (node
->GetRole() == ui::AX_ROLE_ARTICLE
||
565 node
->GetRole() == ui::AX_ROLE_APPLICATION
||
566 node
->GetRole() == ui::AX_ROLE_BANNER
||
567 node
->GetRole() == ui::AX_ROLE_COMPLEMENTARY
||
568 node
->GetRole() == ui::AX_ROLE_CONTENT_INFO
||
569 node
->GetRole() == ui::AX_ROLE_HEADING
||
570 node
->GetRole() == ui::AX_ROLE_MAIN
||
571 node
->GetRole() == ui::AX_ROLE_NAVIGATION
||
572 node
->GetRole() == ui::AX_ROLE_SEARCH
||
573 node
->GetRole() == ui::AX_ROLE_REGION
) {
574 return node
->GetId();
577 case HTML_ELEMENT_TYPE_LIST
:
578 if (node
->GetRole() == ui::AX_ROLE_LIST
||
579 node
->GetRole() == ui::AX_ROLE_GRID
||
580 node
->GetRole() == ui::AX_ROLE_TABLE
||
581 node
->GetRole() == ui::AX_ROLE_TREE
) {
582 return node
->GetId();
585 case HTML_ELEMENT_TYPE_CONTROL
:
586 if (static_cast<BrowserAccessibilityAndroid
*>(node
)->IsFocusable())
587 return node
->GetId();
589 case HTML_ELEMENT_TYPE_ANY
:
590 // In theory, the API says that an accessibility service could
591 // jump to an element by element name, like 'H1' or 'P'. This isn't
592 // currently used by any accessibility service, and we think it's
593 // better to keep them high-level like 'SECTION' or 'CONTROL', so we
594 // just fall back on linear navigation when we don't recognize the
596 if (static_cast<BrowserAccessibilityAndroid
*>(node
)->IsClickable())
597 return node
->GetId();
601 node
= forwards
? NextInTreeOrder(node
) : PreviousInTreeOrder(node
);
607 jboolean
BrowserAccessibilityManagerAndroid::NextAtGranularity(
608 JNIEnv
* env
, jobject obj
, jint granularity
, jboolean extend_selection
,
609 jint id
, jint cursor_index
) {
610 BrowserAccessibilityAndroid
* node
= static_cast<BrowserAccessibilityAndroid
*>(
615 jint start_index
= -1;
617 if (NextAtGranularity(granularity
, cursor_index
, node
,
618 &start_index
, &end_index
)) {
620 if (!node
->IsPassword() ||
621 Java_BrowserAccessibilityManager_shouldExposePasswordText(env
, obj
)) {
622 text
= node
->GetText();
624 Java_BrowserAccessibilityManager_finishGranularityMove(
625 env
, obj
, base::android::ConvertUTF16ToJavaString(
627 extend_selection
, start_index
, end_index
, true);
633 jboolean
BrowserAccessibilityManagerAndroid::PreviousAtGranularity(
634 JNIEnv
* env
, jobject obj
, jint granularity
, jboolean extend_selection
,
635 jint id
, jint cursor_index
) {
636 BrowserAccessibilityAndroid
* node
= static_cast<BrowserAccessibilityAndroid
*>(
641 jint start_index
= -1;
643 if (PreviousAtGranularity(granularity
, cursor_index
, node
,
644 &start_index
, &end_index
)) {
645 Java_BrowserAccessibilityManager_finishGranularityMove(
646 env
, obj
, base::android::ConvertUTF16ToJavaString(
647 env
, node
->GetText()).obj(),
648 extend_selection
, start_index
, end_index
, false);
654 bool BrowserAccessibilityManagerAndroid::NextAtGranularity(
655 int32 granularity
, int32 cursor_index
,
656 BrowserAccessibilityAndroid
* node
, int32
* start_index
, int32
* end_index
) {
657 switch (granularity
) {
658 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_CHARACTER
: {
659 base::string16 text
= node
->GetText();
660 if (cursor_index
>= static_cast<int32
>(text
.length()))
662 base::i18n::UTF16CharIterator
iter(text
.data(), text
.size());
663 while (!iter
.end() && iter
.array_pos() <= cursor_index
)
665 *start_index
= iter
.array_pos();
666 *end_index
= iter
.array_pos();
669 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_WORD
:
670 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_LINE
: {
671 std::vector
<int32
> starts
;
672 std::vector
<int32
> ends
;
673 node
->GetGranularityBoundaries(granularity
, &starts
, &ends
, 0);
674 if (starts
.size() == 0)
678 while (index
< starts
.size() - 1 && starts
[index
] < cursor_index
)
681 if (starts
[index
] < cursor_index
)
684 *start_index
= starts
[index
];
685 *end_index
= ends
[index
];
695 bool BrowserAccessibilityManagerAndroid::PreviousAtGranularity(
696 int32 granularity
, int32 cursor_index
,
697 BrowserAccessibilityAndroid
* node
, int32
* start_index
, int32
* end_index
) {
698 switch (granularity
) {
699 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_CHARACTER
: {
700 if (cursor_index
<= 0)
702 base::string16 text
= node
->GetText();
703 base::i18n::UTF16CharIterator
iter(text
.data(), text
.size());
704 int previous_index
= 0;
705 while (!iter
.end() && iter
.array_pos() < cursor_index
) {
706 previous_index
= iter
.array_pos();
709 *start_index
= previous_index
;
710 *end_index
= previous_index
;
713 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_WORD
:
714 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_LINE
: {
715 std::vector
<int32
> starts
;
716 std::vector
<int32
> ends
;
717 node
->GetGranularityBoundaries(granularity
, &starts
, &ends
, 0);
718 if (starts
.size() == 0)
721 size_t index
= starts
.size() - 1;
722 while (index
> 0 && starts
[index
] >= cursor_index
)
725 if (starts
[index
] >= cursor_index
)
728 *start_index
= starts
[index
];
729 *end_index
= ends
[index
];
739 void BrowserAccessibilityManagerAndroid::SetAccessibilityFocus(
740 JNIEnv
* env
, jobject obj
, jint id
) {
742 delegate_
->AccessibilitySetAccessibilityFocus(id
);
745 bool BrowserAccessibilityManagerAndroid::IsSlider(
746 JNIEnv
* env
, jobject obj
, jint id
) {
747 BrowserAccessibilityAndroid
* node
= static_cast<BrowserAccessibilityAndroid
*>(
752 return node
->GetRole() == ui::AX_ROLE_SLIDER
;
755 bool BrowserAccessibilityManagerAndroid::Scroll(
756 JNIEnv
* env
, jobject obj
, jint id
, int direction
) {
757 BrowserAccessibilityAndroid
* node
= static_cast<BrowserAccessibilityAndroid
*>(
762 return node
->Scroll(direction
);
765 void BrowserAccessibilityManagerAndroid::OnAtomicUpdateFinished(
768 const std::vector
<ui::AXTreeDelegate::Change
>& changes
) {
770 JNIEnv
* env
= AttachCurrentThread();
771 ScopedJavaLocalRef
<jobject
> obj
= java_ref_
.get(env
);
775 Java_BrowserAccessibilityManager_handleNavigate(env
, obj
.obj());
780 BrowserAccessibilityManagerAndroid::UseRootScrollOffsetsWhenComputingBounds() {
781 // The Java layer handles the root scroll offset.
785 bool RegisterBrowserAccessibilityManager(JNIEnv
* env
) {
786 return RegisterNativesImpl(env
);
789 } // namespace content