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 SetContentViewCore(content_view_core
);
75 Initialize(initial_tree
);
78 BrowserAccessibilityManagerAndroid::~BrowserAccessibilityManagerAndroid() {
79 JNIEnv
* env
= AttachCurrentThread();
80 ScopedJavaLocalRef
<jobject
> obj
= java_ref_
.get(env
);
84 Java_BrowserAccessibilityManager_onNativeObjectDestroyed(env
, obj
.obj());
88 ui::AXTreeUpdate
BrowserAccessibilityManagerAndroid::GetEmptyDocument() {
89 ui::AXNodeData empty_document
;
90 empty_document
.id
= 0;
91 empty_document
.role
= ui::AX_ROLE_ROOT_WEB_AREA
;
92 empty_document
.state
= 1 << ui::AX_STATE_READ_ONLY
;
94 ui::AXTreeUpdate update
;
95 update
.nodes
.push_back(empty_document
);
99 void BrowserAccessibilityManagerAndroid::SetContentViewCore(
100 ScopedJavaLocalRef
<jobject
> content_view_core
) {
101 if (content_view_core
.is_null())
104 JNIEnv
* env
= AttachCurrentThread();
105 java_ref_
= JavaObjectWeakGlobalRef(
106 env
, Java_BrowserAccessibilityManager_create(
107 env
, reinterpret_cast<intptr_t>(this),
108 content_view_core
.obj()).obj());
111 void BrowserAccessibilityManagerAndroid::NotifyAccessibilityEvent(
112 ui::AXEvent event_type
,
113 BrowserAccessibility
* node
) {
114 JNIEnv
* env
= AttachCurrentThread();
115 ScopedJavaLocalRef
<jobject
> obj
= java_ref_
.get(env
);
119 BrowserAccessibilityAndroid
* android_node
=
120 static_cast<BrowserAccessibilityAndroid
*>(node
);
122 if (event_type
== ui::AX_EVENT_HIDE
)
125 if (event_type
== ui::AX_EVENT_TREE_CHANGED
)
128 if (event_type
== ui::AX_EVENT_HOVER
) {
129 HandleHoverEvent(node
);
133 // Always send AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED to notify
134 // the Android system that the accessibility hierarchy rooted at this
136 Java_BrowserAccessibilityManager_handleContentChanged(
137 env
, obj
.obj(), node
->GetId());
139 switch (event_type
) {
140 case ui::AX_EVENT_LOAD_COMPLETE
:
141 Java_BrowserAccessibilityManager_handlePageLoaded(
142 env
, obj
.obj(), focus_
->id());
144 case ui::AX_EVENT_FOCUS
:
145 Java_BrowserAccessibilityManager_handleFocusChanged(
146 env
, obj
.obj(), node
->GetId());
148 case ui::AX_EVENT_CHECKED_STATE_CHANGED
:
149 Java_BrowserAccessibilityManager_handleCheckStateChanged(
150 env
, obj
.obj(), node
->GetId());
152 case ui::AX_EVENT_SCROLL_POSITION_CHANGED
:
153 Java_BrowserAccessibilityManager_handleScrollPositionChanged(
154 env
, obj
.obj(), node
->GetId());
156 case ui::AX_EVENT_SCROLLED_TO_ANCHOR
:
157 Java_BrowserAccessibilityManager_handleScrolledToAnchor(
158 env
, obj
.obj(), node
->GetId());
160 case ui::AX_EVENT_ALERT
:
161 // An alert is a special case of live region. Fall through to the
162 // next case to handle it.
163 case ui::AX_EVENT_SHOW
: {
164 // This event is fired when an object appears in a live region.
166 Java_BrowserAccessibilityManager_announceLiveRegionText(
168 base::android::ConvertUTF16ToJavaString(
169 env
, android_node
->GetText()).obj());
172 case ui::AX_EVENT_TEXT_SELECTION_CHANGED
:
173 Java_BrowserAccessibilityManager_handleTextSelectionChanged(
174 env
, obj
.obj(), node
->GetId());
176 case ui::AX_EVENT_TEXT_CHANGED
:
177 case ui::AX_EVENT_VALUE_CHANGED
:
178 if (node
->IsEditableText() && GetFocus(GetRoot()) == node
) {
179 Java_BrowserAccessibilityManager_handleEditableTextChanged(
180 env
, obj
.obj(), node
->GetId());
181 } else if (android_node
->IsSlider()) {
182 Java_BrowserAccessibilityManager_handleSliderChanged(
183 env
, obj
.obj(), node
->GetId());
187 // There are some notifications that aren't meaningful on Android.
188 // It's okay to skip them.
193 jint
BrowserAccessibilityManagerAndroid::GetRootId(JNIEnv
* env
, jobject obj
) {
194 return static_cast<jint
>(GetRoot()->GetId());
197 jboolean
BrowserAccessibilityManagerAndroid::IsNodeValid(
198 JNIEnv
* env
, jobject obj
, jint id
) {
199 return GetFromID(id
) != NULL
;
202 void BrowserAccessibilityManagerAndroid::HitTest(
203 JNIEnv
* env
, jobject obj
, jint x
, jint y
) {
205 delegate()->AccessibilityHitTest(gfx::Point(x
, y
));
208 jboolean
BrowserAccessibilityManagerAndroid::IsEditableText(
209 JNIEnv
* env
, jobject obj
, jint id
) {
210 BrowserAccessibilityAndroid
* node
= static_cast<BrowserAccessibilityAndroid
*>(
215 return node
->IsEditableText();
218 jint
BrowserAccessibilityManagerAndroid::GetEditableTextSelectionStart(
219 JNIEnv
* env
, jobject obj
, jint id
) {
220 BrowserAccessibilityAndroid
* node
= static_cast<BrowserAccessibilityAndroid
*>(
225 return node
->GetIntAttribute(ui::AX_ATTR_TEXT_SEL_START
);
228 jint
BrowserAccessibilityManagerAndroid::GetEditableTextSelectionEnd(
229 JNIEnv
* env
, jobject obj
, jint id
) {
230 BrowserAccessibilityAndroid
* node
= static_cast<BrowserAccessibilityAndroid
*>(
235 return node
->GetIntAttribute(ui::AX_ATTR_TEXT_SEL_END
);
238 jboolean
BrowserAccessibilityManagerAndroid::PopulateAccessibilityNodeInfo(
239 JNIEnv
* env
, jobject obj
, jobject info
, jint id
) {
240 BrowserAccessibilityAndroid
* node
= static_cast<BrowserAccessibilityAndroid
*>(
245 if (node
->GetParent()) {
246 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoParent(
247 env
, obj
, info
, node
->GetParent()->GetId());
249 for (unsigned i
= 0; i
< node
->PlatformChildCount(); ++i
) {
250 Java_BrowserAccessibilityManager_addAccessibilityNodeInfoChild(
251 env
, obj
, info
, node
->InternalGetChild(i
)->GetId());
253 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoBooleanAttributes(
263 node
->IsScrollable(),
265 node
->IsVisibleToUser());
266 Java_BrowserAccessibilityManager_addAccessibilityNodeInfoActions(
269 node
->CanScrollForward(),
270 node
->CanScrollBackward(),
272 node
->IsEditableText(),
276 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoClassName(
278 base::android::ConvertUTF8ToJavaString(env
, node
->GetClassName()).obj());
279 if (!node
->IsPassword() ||
280 Java_BrowserAccessibilityManager_shouldExposePasswordText(env
, obj
)) {
281 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoContentDescription(
283 base::android::ConvertUTF16ToJavaString(env
, node
->GetText()).obj(),
287 gfx::Rect absolute_rect
= node
->GetLocalBoundsRect();
288 gfx::Rect parent_relative_rect
= absolute_rect
;
289 if (node
->GetParent()) {
290 gfx::Rect parent_rect
= node
->GetParent()->GetLocalBoundsRect();
291 parent_relative_rect
.Offset(-parent_rect
.OffsetFromOrigin());
293 bool is_root
= node
->GetParent() == NULL
;
294 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoLocation(
297 absolute_rect
.x(), absolute_rect
.y(),
298 parent_relative_rect
.x(), parent_relative_rect
.y(),
299 absolute_rect
.width(), absolute_rect
.height(),
302 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoLollipopAttributes(
304 node
->CanOpenPopup(),
305 node
->IsContentInvalid(),
306 node
->IsDismissable(),
308 node
->AndroidInputType(),
309 node
->AndroidLiveRegionType());
310 if (node
->IsCollection()) {
311 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoCollectionInfo(
315 node
->IsHierarchical());
317 if (node
->IsCollectionItem() || node
->IsHeading()) {
318 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoCollectionItemInfo(
326 if (node
->IsRangeType()) {
327 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoRangeInfo(
329 node
->AndroidRangeType(),
332 node
->RangeCurrentValue());
338 jboolean
BrowserAccessibilityManagerAndroid::PopulateAccessibilityEvent(
339 JNIEnv
* env
, jobject obj
, jobject event
, jint id
, jint event_type
) {
340 BrowserAccessibilityAndroid
* node
= static_cast<BrowserAccessibilityAndroid
*>(
345 Java_BrowserAccessibilityManager_setAccessibilityEventBooleanAttributes(
350 node
->IsScrollable());
351 Java_BrowserAccessibilityManager_setAccessibilityEventClassName(
353 base::android::ConvertUTF8ToJavaString(env
, node
->GetClassName()).obj());
354 Java_BrowserAccessibilityManager_setAccessibilityEventListAttributes(
356 node
->GetItemIndex(),
357 node
->GetItemCount());
358 Java_BrowserAccessibilityManager_setAccessibilityEventScrollAttributes(
362 node
->GetMaxScrollX(),
363 node
->GetMaxScrollY());
365 switch (event_type
) {
366 case ANDROID_ACCESSIBILITY_EVENT_TEXT_CHANGED
: {
367 base::string16 before_text
, text
;
368 if (!node
->IsPassword() ||
369 Java_BrowserAccessibilityManager_shouldExposePasswordText(env
, obj
)) {
370 before_text
= node
->GetTextChangeBeforeText();
371 text
= node
->GetText();
373 Java_BrowserAccessibilityManager_setAccessibilityEventTextChangedAttrs(
375 node
->GetTextChangeFromIndex(),
376 node
->GetTextChangeAddedCount(),
377 node
->GetTextChangeRemovedCount(),
378 base::android::ConvertUTF16ToJavaString(
379 env
, before_text
).obj(),
380 base::android::ConvertUTF16ToJavaString(env
, text
).obj());
383 case ANDROID_ACCESSIBILITY_EVENT_TEXT_SELECTION_CHANGED
: {
385 if (!node
->IsPassword() ||
386 Java_BrowserAccessibilityManager_shouldExposePasswordText(env
, obj
)) {
387 text
= node
->GetText();
389 Java_BrowserAccessibilityManager_setAccessibilityEventSelectionAttrs(
391 node
->GetSelectionStart(),
392 node
->GetSelectionEnd(),
393 node
->GetEditableTextLength(),
394 base::android::ConvertUTF16ToJavaString(env
, text
).obj());
401 Java_BrowserAccessibilityManager_setAccessibilityEventLollipopAttributes(
403 node
->CanOpenPopup(),
404 node
->IsContentInvalid(),
405 node
->IsDismissable(),
407 node
->AndroidInputType(),
408 node
->AndroidLiveRegionType());
409 if (node
->IsCollection()) {
410 Java_BrowserAccessibilityManager_setAccessibilityEventCollectionInfo(
414 node
->IsHierarchical());
416 if (node
->IsHeading()) {
417 Java_BrowserAccessibilityManager_setAccessibilityEventHeadingFlag(
418 env
, obj
, event
, true);
420 if (node
->IsCollectionItem()) {
421 Java_BrowserAccessibilityManager_setAccessibilityEventCollectionItemInfo(
428 if (node
->IsRangeType()) {
429 Java_BrowserAccessibilityManager_setAccessibilityEventRangeInfo(
431 node
->AndroidRangeType(),
434 node
->RangeCurrentValue());
440 void BrowserAccessibilityManagerAndroid::Click(
441 JNIEnv
* env
, jobject obj
, jint id
) {
442 BrowserAccessibility
* node
= GetFromID(id
);
444 DoDefaultAction(*node
);
447 void BrowserAccessibilityManagerAndroid::Focus(
448 JNIEnv
* env
, jobject obj
, jint id
) {
449 BrowserAccessibility
* node
= GetFromID(id
);
451 SetFocus(node
, true);
454 void BrowserAccessibilityManagerAndroid::Blur(JNIEnv
* env
, jobject obj
) {
455 SetFocus(GetRoot(), true);
458 void BrowserAccessibilityManagerAndroid::ScrollToMakeNodeVisible(
459 JNIEnv
* env
, jobject obj
, jint id
) {
460 BrowserAccessibility
* node
= GetFromID(id
);
462 ScrollToMakeVisible(*node
, gfx::Rect(node
->GetLocation().size()));
465 void BrowserAccessibilityManagerAndroid::SetTextFieldValue(
466 JNIEnv
* env
, jobject obj
, jint id
, jstring value
) {
467 BrowserAccessibility
* node
= GetFromID(id
);
469 BrowserAccessibilityManager::SetValue(
470 *node
, base::android::ConvertJavaStringToUTF16(env
, value
));
474 void BrowserAccessibilityManagerAndroid::SetSelection(
475 JNIEnv
* env
, jobject obj
, jint id
, jint start
, jint end
) {
476 BrowserAccessibility
* node
= GetFromID(id
);
478 SetTextSelection(*node
, start
, end
);
481 jboolean
BrowserAccessibilityManagerAndroid::AdjustSlider(
482 JNIEnv
* env
, jobject obj
, jint id
, jboolean increment
) {
483 BrowserAccessibility
* node
= GetFromID(id
);
487 BrowserAccessibilityAndroid
* android_node
=
488 static_cast<BrowserAccessibilityAndroid
*>(node
);
490 if (!android_node
->IsSlider() || !android_node
->IsEnabled())
493 float value
= node
->GetFloatAttribute(ui::AX_ATTR_VALUE_FOR_RANGE
);
494 float min
= node
->GetFloatAttribute(ui::AX_ATTR_MIN_VALUE_FOR_RANGE
);
495 float max
= node
->GetFloatAttribute(ui::AX_ATTR_MAX_VALUE_FOR_RANGE
);
499 // To behave similarly to an Android SeekBar, move by an increment of
500 // approximately 20%.
501 float original_value
= value
;
502 float delta
= (max
- min
) / 5.0f
;
503 value
+= (increment
? delta
: -delta
);
504 value
= std::max(std::min(value
, max
), min
);
505 if (value
!= original_value
) {
506 BrowserAccessibilityManager::SetValue(
507 *node
, base::UTF8ToUTF16(base::DoubleToString(value
)));
513 void BrowserAccessibilityManagerAndroid::HandleHoverEvent(
514 BrowserAccessibility
* node
) {
515 JNIEnv
* env
= AttachCurrentThread();
516 ScopedJavaLocalRef
<jobject
> obj
= java_ref_
.get(env
);
520 BrowserAccessibilityAndroid
* ancestor
=
521 static_cast<BrowserAccessibilityAndroid
*>(node
->GetParent());
522 while (ancestor
&& ancestor
!= GetRoot()) {
523 if (ancestor
->PlatformIsLeaf() ||
524 (ancestor
->IsFocusable() && !ancestor
->HasFocusableChild())) {
526 // Don't break - we want the highest ancestor that's focusable or a
529 ancestor
= static_cast<BrowserAccessibilityAndroid
*>(ancestor
->GetParent());
532 Java_BrowserAccessibilityManager_handleHover(
533 env
, obj
.obj(), node
->GetId());
536 jint
BrowserAccessibilityManagerAndroid::FindElementType(
537 JNIEnv
* env
, jobject obj
, jint start_id
, jstring element_type_str
,
539 BrowserAccessibility
* node
= GetFromID(start_id
);
543 AndroidHtmlElementType element_type
= HtmlElementTypeFromString(
544 base::android::ConvertJavaStringToUTF16(env
, element_type_str
));
546 node
= forwards
? NextInTreeOrder(node
) : PreviousInTreeOrder(node
);
548 switch(element_type
) {
549 case HTML_ELEMENT_TYPE_SECTION
:
550 if (node
->GetRole() == ui::AX_ROLE_ARTICLE
||
551 node
->GetRole() == ui::AX_ROLE_APPLICATION
||
552 node
->GetRole() == ui::AX_ROLE_BANNER
||
553 node
->GetRole() == ui::AX_ROLE_COMPLEMENTARY
||
554 node
->GetRole() == ui::AX_ROLE_CONTENT_INFO
||
555 node
->GetRole() == ui::AX_ROLE_HEADING
||
556 node
->GetRole() == ui::AX_ROLE_MAIN
||
557 node
->GetRole() == ui::AX_ROLE_NAVIGATION
||
558 node
->GetRole() == ui::AX_ROLE_SEARCH
||
559 node
->GetRole() == ui::AX_ROLE_REGION
) {
560 return node
->GetId();
563 case HTML_ELEMENT_TYPE_LIST
:
564 if (node
->GetRole() == ui::AX_ROLE_LIST
||
565 node
->GetRole() == ui::AX_ROLE_GRID
||
566 node
->GetRole() == ui::AX_ROLE_TABLE
||
567 node
->GetRole() == ui::AX_ROLE_TREE
) {
568 return node
->GetId();
571 case HTML_ELEMENT_TYPE_CONTROL
:
572 if (static_cast<BrowserAccessibilityAndroid
*>(node
)->IsFocusable())
573 return node
->GetId();
575 case HTML_ELEMENT_TYPE_ANY
:
576 // In theory, the API says that an accessibility service could
577 // jump to an element by element name, like 'H1' or 'P'. This isn't
578 // currently used by any accessibility service, and we think it's
579 // better to keep them high-level like 'SECTION' or 'CONTROL', so we
580 // just fall back on linear navigation when we don't recognize the
582 if (static_cast<BrowserAccessibilityAndroid
*>(node
)->IsClickable())
583 return node
->GetId();
587 node
= forwards
? NextInTreeOrder(node
) : PreviousInTreeOrder(node
);
593 jboolean
BrowserAccessibilityManagerAndroid::NextAtGranularity(
594 JNIEnv
* env
, jobject obj
, jint granularity
, jboolean extend_selection
,
595 jint id
, jint cursor_index
) {
596 BrowserAccessibilityAndroid
* node
= static_cast<BrowserAccessibilityAndroid
*>(
601 jint start_index
= -1;
603 if (NextAtGranularity(granularity
, cursor_index
, node
,
604 &start_index
, &end_index
)) {
606 if (!node
->IsPassword() ||
607 Java_BrowserAccessibilityManager_shouldExposePasswordText(env
, obj
)) {
608 text
= node
->GetText();
610 Java_BrowserAccessibilityManager_finishGranularityMove(
611 env
, obj
, base::android::ConvertUTF16ToJavaString(
613 extend_selection
, start_index
, end_index
, true);
619 jboolean
BrowserAccessibilityManagerAndroid::PreviousAtGranularity(
620 JNIEnv
* env
, jobject obj
, jint granularity
, jboolean extend_selection
,
621 jint id
, jint cursor_index
) {
622 BrowserAccessibilityAndroid
* node
= static_cast<BrowserAccessibilityAndroid
*>(
627 jint start_index
= -1;
629 if (PreviousAtGranularity(granularity
, cursor_index
, node
,
630 &start_index
, &end_index
)) {
631 Java_BrowserAccessibilityManager_finishGranularityMove(
632 env
, obj
, base::android::ConvertUTF16ToJavaString(
633 env
, node
->GetText()).obj(),
634 extend_selection
, start_index
, end_index
, false);
640 bool BrowserAccessibilityManagerAndroid::NextAtGranularity(
641 int32 granularity
, int32 cursor_index
,
642 BrowserAccessibilityAndroid
* node
, int32
* start_index
, int32
* end_index
) {
643 switch (granularity
) {
644 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_CHARACTER
: {
645 base::string16 text
= node
->GetText();
646 if (cursor_index
>= static_cast<int32
>(text
.length()))
648 base::i18n::UTF16CharIterator
iter(text
.data(), text
.size());
649 while (!iter
.end() && iter
.array_pos() <= cursor_index
)
651 *start_index
= iter
.array_pos();
652 *end_index
= iter
.array_pos();
655 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_WORD
:
656 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_LINE
: {
657 std::vector
<int32
> starts
;
658 std::vector
<int32
> ends
;
659 node
->GetGranularityBoundaries(granularity
, &starts
, &ends
, 0);
660 if (starts
.size() == 0)
664 while (index
< starts
.size() - 1 && starts
[index
] < cursor_index
)
667 if (starts
[index
] < cursor_index
)
670 *start_index
= starts
[index
];
671 *end_index
= ends
[index
];
681 bool BrowserAccessibilityManagerAndroid::PreviousAtGranularity(
682 int32 granularity
, int32 cursor_index
,
683 BrowserAccessibilityAndroid
* node
, int32
* start_index
, int32
* end_index
) {
684 switch (granularity
) {
685 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_CHARACTER
: {
686 if (cursor_index
<= 0)
688 base::string16 text
= node
->GetText();
689 base::i18n::UTF16CharIterator
iter(text
.data(), text
.size());
690 int previous_index
= 0;
691 while (!iter
.end() && iter
.array_pos() < cursor_index
) {
692 previous_index
= iter
.array_pos();
695 *start_index
= previous_index
;
696 *end_index
= previous_index
;
699 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_WORD
:
700 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_LINE
: {
701 std::vector
<int32
> starts
;
702 std::vector
<int32
> ends
;
703 node
->GetGranularityBoundaries(granularity
, &starts
, &ends
, 0);
704 if (starts
.size() == 0)
707 size_t index
= starts
.size() - 1;
708 while (index
> 0 && starts
[index
] >= cursor_index
)
711 if (starts
[index
] >= cursor_index
)
714 *start_index
= starts
[index
];
715 *end_index
= ends
[index
];
725 void BrowserAccessibilityManagerAndroid::SetAccessibilityFocus(
726 JNIEnv
* env
, jobject obj
, jint id
) {
728 delegate_
->AccessibilitySetAccessibilityFocus(id
);
731 void BrowserAccessibilityManagerAndroid::OnAtomicUpdateFinished(
733 const std::vector
<ui::AXTreeDelegate::Change
>& changes
) {
735 JNIEnv
* env
= AttachCurrentThread();
736 ScopedJavaLocalRef
<jobject
> obj
= java_ref_
.get(env
);
740 Java_BrowserAccessibilityManager_handleNavigate(env
, obj
.obj());
745 BrowserAccessibilityManagerAndroid::UseRootScrollOffsetsWhenComputingBounds() {
746 // The Java layer handles the root scroll offset.
750 bool RegisterBrowserAccessibilityManager(JNIEnv
* env
) {
751 return RegisterNativesImpl(env
);
754 } // namespace content