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_CHILDREN_CHANGED
:
177 case ui::AX_EVENT_TEXT_CHANGED
:
178 case ui::AX_EVENT_VALUE_CHANGED
:
179 if (node
->IsEditableText()) {
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
) {
195 return static_cast<jint
>(GetRoot()->GetId());
198 jboolean
BrowserAccessibilityManagerAndroid::IsNodeValid(
199 JNIEnv
* env
, jobject obj
, jint id
) {
200 return GetFromID(id
) != NULL
;
203 void BrowserAccessibilityManagerAndroid::HitTest(
204 JNIEnv
* env
, jobject obj
, jint x
, jint y
) {
206 delegate()->AccessibilityHitTest(gfx::Point(x
, y
));
209 jboolean
BrowserAccessibilityManagerAndroid::IsEditableText(
210 JNIEnv
* env
, jobject obj
, jint id
) {
211 BrowserAccessibilityAndroid
* node
= static_cast<BrowserAccessibilityAndroid
*>(
216 return node
->IsEditableText();
219 jint
BrowserAccessibilityManagerAndroid::GetEditableTextSelectionStart(
220 JNIEnv
* env
, jobject obj
, jint id
) {
221 BrowserAccessibilityAndroid
* node
= static_cast<BrowserAccessibilityAndroid
*>(
226 return node
->GetIntAttribute(ui::AX_ATTR_TEXT_SEL_START
);
229 jint
BrowserAccessibilityManagerAndroid::GetEditableTextSelectionEnd(
230 JNIEnv
* env
, jobject obj
, jint id
) {
231 BrowserAccessibilityAndroid
* node
= static_cast<BrowserAccessibilityAndroid
*>(
236 return node
->GetIntAttribute(ui::AX_ATTR_TEXT_SEL_END
);
239 jboolean
BrowserAccessibilityManagerAndroid::PopulateAccessibilityNodeInfo(
240 JNIEnv
* env
, jobject obj
, jobject info
, jint id
) {
241 BrowserAccessibilityAndroid
* node
= static_cast<BrowserAccessibilityAndroid
*>(
246 if (node
->GetParent()) {
247 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoParent(
248 env
, obj
, info
, node
->GetParent()->GetId());
250 for (unsigned i
= 0; i
< node
->PlatformChildCount(); ++i
) {
251 Java_BrowserAccessibilityManager_addAccessibilityNodeInfoChild(
252 env
, obj
, info
, node
->InternalGetChild(i
)->GetId());
254 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoBooleanAttributes(
257 node
->CanScrollForward(),
258 node
->CanScrollBackward(),
262 node
->IsEditableText(),
267 node
->IsScrollable(),
269 node
->IsVisibleToUser());
270 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoClassName(
272 base::android::ConvertUTF8ToJavaString(env
, node
->GetClassName()).obj());
273 if (!node
->IsPassword() ||
274 Java_BrowserAccessibilityManager_shouldExposePasswordText(env
, obj
)) {
275 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoContentDescription(
277 base::android::ConvertUTF16ToJavaString(env
, node
->GetText()).obj(),
281 gfx::Rect absolute_rect
= node
->GetLocalBoundsRect();
282 gfx::Rect parent_relative_rect
= absolute_rect
;
283 if (node
->GetParent()) {
284 gfx::Rect parent_rect
= node
->GetParent()->GetLocalBoundsRect();
285 parent_relative_rect
.Offset(-parent_rect
.OffsetFromOrigin());
287 bool is_root
= node
->GetParent() == NULL
;
288 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoLocation(
291 absolute_rect
.x(), absolute_rect
.y(),
292 parent_relative_rect
.x(), parent_relative_rect
.y(),
293 absolute_rect
.width(), absolute_rect
.height(),
297 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoKitKatAttributes(
299 node
->CanOpenPopup(),
300 node
->IsContentInvalid(),
301 node
->IsDismissable(),
303 node
->AndroidInputType(),
304 node
->AndroidLiveRegionType());
305 if (node
->IsCollection()) {
306 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoCollectionInfo(
310 node
->IsHierarchical());
312 if (node
->IsCollectionItem() || node
->IsHeading()) {
313 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoCollectionItemInfo(
321 if (node
->IsRangeType()) {
322 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoRangeInfo(
324 node
->AndroidRangeType(),
327 node
->RangeCurrentValue());
333 jboolean
BrowserAccessibilityManagerAndroid::PopulateAccessibilityEvent(
334 JNIEnv
* env
, jobject obj
, jobject event
, jint id
, jint event_type
) {
335 BrowserAccessibilityAndroid
* node
= static_cast<BrowserAccessibilityAndroid
*>(
340 Java_BrowserAccessibilityManager_setAccessibilityEventBooleanAttributes(
345 node
->IsScrollable());
346 Java_BrowserAccessibilityManager_setAccessibilityEventClassName(
348 base::android::ConvertUTF8ToJavaString(env
, node
->GetClassName()).obj());
349 Java_BrowserAccessibilityManager_setAccessibilityEventListAttributes(
351 node
->GetItemIndex(),
352 node
->GetItemCount());
353 Java_BrowserAccessibilityManager_setAccessibilityEventScrollAttributes(
357 node
->GetMaxScrollX(),
358 node
->GetMaxScrollY());
360 switch (event_type
) {
361 case ANDROID_ACCESSIBILITY_EVENT_TEXT_CHANGED
: {
362 base::string16 before_text
, text
;
363 if (!node
->IsPassword() ||
364 Java_BrowserAccessibilityManager_shouldExposePasswordText(env
, obj
)) {
365 before_text
= node
->GetTextChangeBeforeText();
366 text
= node
->GetText();
368 Java_BrowserAccessibilityManager_setAccessibilityEventTextChangedAttrs(
370 node
->GetTextChangeFromIndex(),
371 node
->GetTextChangeAddedCount(),
372 node
->GetTextChangeRemovedCount(),
373 base::android::ConvertUTF16ToJavaString(
374 env
, before_text
).obj(),
375 base::android::ConvertUTF16ToJavaString(env
, text
).obj());
378 case ANDROID_ACCESSIBILITY_EVENT_TEXT_SELECTION_CHANGED
: {
380 if (!node
->IsPassword() ||
381 Java_BrowserAccessibilityManager_shouldExposePasswordText(env
, obj
)) {
382 text
= node
->GetText();
384 Java_BrowserAccessibilityManager_setAccessibilityEventSelectionAttrs(
386 node
->GetSelectionStart(),
387 node
->GetSelectionEnd(),
388 node
->GetEditableTextLength(),
389 base::android::ConvertUTF16ToJavaString(env
, text
).obj());
396 // Backwards-compatible fallback for new KitKat APIs.
397 Java_BrowserAccessibilityManager_setAccessibilityEventKitKatAttributes(
399 node
->CanOpenPopup(),
400 node
->IsContentInvalid(),
401 node
->IsDismissable(),
403 node
->AndroidInputType(),
404 node
->AndroidLiveRegionType());
405 if (node
->IsCollection()) {
406 Java_BrowserAccessibilityManager_setAccessibilityEventCollectionInfo(
410 node
->IsHierarchical());
412 if (node
->IsHeading()) {
413 Java_BrowserAccessibilityManager_setAccessibilityEventHeadingFlag(
414 env
, obj
, event
, true);
416 if (node
->IsCollectionItem()) {
417 Java_BrowserAccessibilityManager_setAccessibilityEventCollectionItemInfo(
424 if (node
->IsRangeType()) {
425 Java_BrowserAccessibilityManager_setAccessibilityEventRangeInfo(
427 node
->AndroidRangeType(),
430 node
->RangeCurrentValue());
436 void BrowserAccessibilityManagerAndroid::Click(
437 JNIEnv
* env
, jobject obj
, jint id
) {
438 BrowserAccessibility
* node
= GetFromID(id
);
440 DoDefaultAction(*node
);
443 void BrowserAccessibilityManagerAndroid::Focus(
444 JNIEnv
* env
, jobject obj
, jint id
) {
445 BrowserAccessibility
* node
= GetFromID(id
);
447 SetFocus(node
, true);
450 void BrowserAccessibilityManagerAndroid::Blur(JNIEnv
* env
, jobject obj
) {
451 SetFocus(GetRoot(), true);
454 void BrowserAccessibilityManagerAndroid::ScrollToMakeNodeVisible(
455 JNIEnv
* env
, jobject obj
, jint id
) {
456 BrowserAccessibility
* node
= GetFromID(id
);
458 ScrollToMakeVisible(*node
, gfx::Rect(node
->GetLocation().size()));
461 void BrowserAccessibilityManagerAndroid::SetTextFieldValue(
462 JNIEnv
* env
, jobject obj
, jint id
, jstring value
) {
463 BrowserAccessibility
* node
= GetFromID(id
);
465 BrowserAccessibilityManager::SetValue(
466 *node
, base::android::ConvertJavaStringToUTF16(env
, value
));
470 void BrowserAccessibilityManagerAndroid::SetSelection(
471 JNIEnv
* env
, jobject obj
, jint id
, jint start
, jint end
) {
472 BrowserAccessibility
* node
= GetFromID(id
);
474 SetTextSelection(*node
, start
, end
);
477 jboolean
BrowserAccessibilityManagerAndroid::AdjustSlider(
478 JNIEnv
* env
, jobject obj
, jint id
, jboolean increment
) {
479 BrowserAccessibility
* node
= GetFromID(id
);
483 BrowserAccessibilityAndroid
* android_node
=
484 static_cast<BrowserAccessibilityAndroid
*>(node
);
486 if (!android_node
->IsSlider() || !android_node
->IsEnabled())
489 float value
= node
->GetFloatAttribute(ui::AX_ATTR_VALUE_FOR_RANGE
);
490 float min
= node
->GetFloatAttribute(ui::AX_ATTR_MIN_VALUE_FOR_RANGE
);
491 float max
= node
->GetFloatAttribute(ui::AX_ATTR_MAX_VALUE_FOR_RANGE
);
495 // To behave similarly to an Android SeekBar, move by an increment of
496 // approximately 20%.
497 float original_value
= value
;
498 float delta
= (max
- min
) / 5.0f
;
499 value
+= (increment
? delta
: -delta
);
500 value
= std::max(std::min(value
, max
), min
);
501 if (value
!= original_value
) {
502 BrowserAccessibilityManager::SetValue(
503 *node
, base::UTF8ToUTF16(base::DoubleToString(value
)));
509 void BrowserAccessibilityManagerAndroid::HandleHoverEvent(
510 BrowserAccessibility
* node
) {
511 JNIEnv
* env
= AttachCurrentThread();
512 ScopedJavaLocalRef
<jobject
> obj
= java_ref_
.get(env
);
516 BrowserAccessibilityAndroid
* ancestor
=
517 static_cast<BrowserAccessibilityAndroid
*>(node
->GetParent());
518 while (ancestor
&& ancestor
!= GetRoot()) {
519 if (ancestor
->PlatformIsLeaf() ||
520 (ancestor
->IsFocusable() && !ancestor
->HasFocusableChild())) {
522 // Don't break - we want the highest ancestor that's focusable or a
525 ancestor
= static_cast<BrowserAccessibilityAndroid
*>(ancestor
->GetParent());
528 Java_BrowserAccessibilityManager_handleHover(
529 env
, obj
.obj(), node
->GetId());
532 jint
BrowserAccessibilityManagerAndroid::FindElementType(
533 JNIEnv
* env
, jobject obj
, jint start_id
, jstring element_type_str
,
535 BrowserAccessibility
* node
= GetFromID(start_id
);
539 AndroidHtmlElementType element_type
= HtmlElementTypeFromString(
540 base::android::ConvertJavaStringToUTF16(env
, element_type_str
));
542 node
= forwards
? NextInTreeOrder(node
) : PreviousInTreeOrder(node
);
544 switch(element_type
) {
545 case HTML_ELEMENT_TYPE_SECTION
:
546 if (node
->GetRole() == ui::AX_ROLE_ARTICLE
||
547 node
->GetRole() == ui::AX_ROLE_APPLICATION
||
548 node
->GetRole() == ui::AX_ROLE_BANNER
||
549 node
->GetRole() == ui::AX_ROLE_COMPLEMENTARY
||
550 node
->GetRole() == ui::AX_ROLE_CONTENT_INFO
||
551 node
->GetRole() == ui::AX_ROLE_HEADING
||
552 node
->GetRole() == ui::AX_ROLE_MAIN
||
553 node
->GetRole() == ui::AX_ROLE_NAVIGATION
||
554 node
->GetRole() == ui::AX_ROLE_SEARCH
||
555 node
->GetRole() == ui::AX_ROLE_REGION
) {
556 return node
->GetId();
559 case HTML_ELEMENT_TYPE_LIST
:
560 if (node
->GetRole() == ui::AX_ROLE_LIST
||
561 node
->GetRole() == ui::AX_ROLE_GRID
||
562 node
->GetRole() == ui::AX_ROLE_TABLE
||
563 node
->GetRole() == ui::AX_ROLE_TREE
) {
564 return node
->GetId();
567 case HTML_ELEMENT_TYPE_CONTROL
:
568 if (static_cast<BrowserAccessibilityAndroid
*>(node
)->IsFocusable())
569 return node
->GetId();
571 case HTML_ELEMENT_TYPE_ANY
:
572 // In theory, the API says that an accessibility service could
573 // jump to an element by element name, like 'H1' or 'P'. This isn't
574 // currently used by any accessibility service, and we think it's
575 // better to keep them high-level like 'SECTION' or 'CONTROL', so we
576 // just fall back on linear navigation when we don't recognize the
578 if (static_cast<BrowserAccessibilityAndroid
*>(node
)->IsClickable())
579 return node
->GetId();
583 node
= forwards
? NextInTreeOrder(node
) : PreviousInTreeOrder(node
);
589 jboolean
BrowserAccessibilityManagerAndroid::NextAtGranularity(
590 JNIEnv
* env
, jobject obj
, jint granularity
, jboolean extend_selection
,
591 jint id
, jint cursor_index
) {
592 BrowserAccessibilityAndroid
* node
= static_cast<BrowserAccessibilityAndroid
*>(
597 jint start_index
= -1;
599 if (NextAtGranularity(granularity
, cursor_index
, node
,
600 &start_index
, &end_index
)) {
602 if (!node
->IsPassword() ||
603 Java_BrowserAccessibilityManager_shouldExposePasswordText(env
, obj
)) {
604 text
= node
->GetText();
606 Java_BrowserAccessibilityManager_finishGranularityMove(
607 env
, obj
, base::android::ConvertUTF16ToJavaString(
609 extend_selection
, start_index
, end_index
, true);
615 jboolean
BrowserAccessibilityManagerAndroid::PreviousAtGranularity(
616 JNIEnv
* env
, jobject obj
, jint granularity
, jboolean extend_selection
,
617 jint id
, jint cursor_index
) {
618 BrowserAccessibilityAndroid
* node
= static_cast<BrowserAccessibilityAndroid
*>(
623 jint start_index
= -1;
625 if (PreviousAtGranularity(granularity
, cursor_index
, node
,
626 &start_index
, &end_index
)) {
627 Java_BrowserAccessibilityManager_finishGranularityMove(
628 env
, obj
, base::android::ConvertUTF16ToJavaString(
629 env
, node
->GetText()).obj(),
630 extend_selection
, start_index
, end_index
, false);
636 bool BrowserAccessibilityManagerAndroid::NextAtGranularity(
637 int32 granularity
, int32 cursor_index
,
638 BrowserAccessibilityAndroid
* node
, int32
* start_index
, int32
* end_index
) {
639 switch (granularity
) {
640 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_CHARACTER
: {
641 base::string16 text
= node
->GetText();
642 if (cursor_index
>= static_cast<int32
>(text
.length()))
644 base::i18n::UTF16CharIterator
iter(text
.data(), text
.size());
645 while (!iter
.end() && iter
.array_pos() <= cursor_index
)
647 *start_index
= iter
.array_pos();
648 *end_index
= iter
.array_pos();
651 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_WORD
:
652 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_LINE
: {
653 std::vector
<int32
> starts
;
654 std::vector
<int32
> ends
;
655 node
->GetGranularityBoundaries(granularity
, &starts
, &ends
, 0);
656 if (starts
.size() == 0)
660 while (index
< starts
.size() - 1 && starts
[index
] < cursor_index
)
663 if (starts
[index
] < cursor_index
)
666 *start_index
= starts
[index
];
667 *end_index
= ends
[index
];
677 bool BrowserAccessibilityManagerAndroid::PreviousAtGranularity(
678 int32 granularity
, int32 cursor_index
,
679 BrowserAccessibilityAndroid
* node
, int32
* start_index
, int32
* end_index
) {
680 switch (granularity
) {
681 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_CHARACTER
: {
682 if (cursor_index
<= 0)
684 base::string16 text
= node
->GetText();
685 base::i18n::UTF16CharIterator
iter(text
.data(), text
.size());
686 int previous_index
= 0;
687 while (!iter
.end() && iter
.array_pos() < cursor_index
) {
688 previous_index
= iter
.array_pos();
691 *start_index
= previous_index
;
692 *end_index
= previous_index
;
695 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_WORD
:
696 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_LINE
: {
697 std::vector
<int32
> starts
;
698 std::vector
<int32
> ends
;
699 node
->GetGranularityBoundaries(granularity
, &starts
, &ends
, 0);
700 if (starts
.size() == 0)
703 size_t index
= starts
.size() - 1;
704 while (index
> 0 && starts
[index
] >= cursor_index
)
707 if (starts
[index
] >= cursor_index
)
710 *start_index
= starts
[index
];
711 *end_index
= ends
[index
];
721 void BrowserAccessibilityManagerAndroid::SetAccessibilityFocus(
722 JNIEnv
* env
, jobject obj
, jint id
) {
724 delegate_
->AccessibilitySetAccessibilityFocus(id
);
727 void BrowserAccessibilityManagerAndroid::OnAtomicUpdateFinished(
729 const std::vector
<ui::AXTreeDelegate::Change
>& changes
) {
731 JNIEnv
* env
= AttachCurrentThread();
732 ScopedJavaLocalRef
<jobject
> obj
= java_ref_
.get(env
);
736 Java_BrowserAccessibilityManager_handleNavigate(env
, obj
.obj());
741 BrowserAccessibilityManagerAndroid::UseRootScrollOffsetsWhenComputingBounds() {
742 // The Java layer handles the root scroll offset.
746 bool RegisterBrowserAccessibilityManager(JNIEnv
* env
) {
747 return RegisterNativesImpl(env
);
750 } // namespace content