Add ICU message format support
[chromium-blink-merge.git] / content / browser / accessibility / browser_accessibility_manager_android.cc
blob28dd40f052562f40abfeb1d686fa6bd8e4963406
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"
7 #include <cmath>
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;
23 namespace {
25 enum AndroidHtmlElementType {
26 HTML_ELEMENT_TYPE_SECTION,
27 HTML_ELEMENT_TYPE_LIST,
28 HTML_ELEMENT_TYPE_CONTROL,
29 HTML_ELEMENT_TYPE_ANY
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;
41 else
42 return HTML_ELEMENT_TYPE_ANY;
45 } // anonymous namespace
47 namespace content {
49 namespace aria_strings {
50 const char kAriaLivePolite[] = "polite";
51 const char kAriaLiveAssertive[] = "assertive";
54 // static
55 BrowserAccessibilityManager* BrowserAccessibilityManager::Create(
56 const SimpleAXTreeUpdate& 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 SimpleAXTreeUpdate& 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);
82 if (obj.is_null())
83 return;
85 Java_BrowserAccessibilityManager_onNativeObjectDestroyed(env, obj.obj());
88 // static
89 SimpleAXTreeUpdate
90 BrowserAccessibilityManagerAndroid::GetEmptyDocument() {
91 ui::AXNodeData empty_document;
92 empty_document.id = 0;
93 empty_document.role = ui::AX_ROLE_ROOT_WEB_AREA;
94 empty_document.state = 1 << ui::AX_STATE_READ_ONLY;
96 SimpleAXTreeUpdate update;
97 update.nodes.push_back(empty_document);
98 return update;
101 void BrowserAccessibilityManagerAndroid::SetContentViewCore(
102 ScopedJavaLocalRef<jobject> content_view_core) {
103 if (content_view_core.is_null())
104 return;
106 JNIEnv* env = AttachCurrentThread();
107 java_ref_ = JavaObjectWeakGlobalRef(
108 env, Java_BrowserAccessibilityManager_create(
109 env, reinterpret_cast<intptr_t>(this),
110 content_view_core.obj()).obj());
113 void BrowserAccessibilityManagerAndroid::NotifyAccessibilityEvent(
114 ui::AXEvent event_type,
115 BrowserAccessibility* node) {
116 JNIEnv* env = AttachCurrentThread();
117 ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
118 if (obj.is_null())
119 return;
121 BrowserAccessibilityAndroid* android_node =
122 static_cast<BrowserAccessibilityAndroid*>(node);
124 if (event_type == ui::AX_EVENT_HIDE)
125 return;
127 if (event_type == ui::AX_EVENT_TREE_CHANGED)
128 return;
130 if (event_type == ui::AX_EVENT_HOVER) {
131 HandleHoverEvent(node);
132 return;
135 // Always send AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED to notify
136 // the Android system that the accessibility hierarchy rooted at this
137 // node has changed.
138 Java_BrowserAccessibilityManager_handleContentChanged(
139 env, obj.obj(), node->GetId());
141 switch (event_type) {
142 case ui::AX_EVENT_LOAD_COMPLETE:
143 Java_BrowserAccessibilityManager_handlePageLoaded(
144 env, obj.obj(), focus_->id());
145 break;
146 case ui::AX_EVENT_FOCUS:
147 Java_BrowserAccessibilityManager_handleFocusChanged(
148 env, obj.obj(), node->GetId());
149 break;
150 case ui::AX_EVENT_CHECKED_STATE_CHANGED:
151 Java_BrowserAccessibilityManager_handleCheckStateChanged(
152 env, obj.obj(), node->GetId());
153 break;
154 case ui::AX_EVENT_SCROLL_POSITION_CHANGED:
155 Java_BrowserAccessibilityManager_handleScrollPositionChanged(
156 env, obj.obj(), node->GetId());
157 break;
158 case ui::AX_EVENT_SCROLLED_TO_ANCHOR:
159 Java_BrowserAccessibilityManager_handleScrolledToAnchor(
160 env, obj.obj(), node->GetId());
161 break;
162 case ui::AX_EVENT_ALERT:
163 // An alert is a special case of live region. Fall through to the
164 // next case to handle it.
165 case ui::AX_EVENT_SHOW: {
166 // This event is fired when an object appears in a live region.
167 // Speak its text.
168 Java_BrowserAccessibilityManager_announceLiveRegionText(
169 env, obj.obj(),
170 base::android::ConvertUTF16ToJavaString(
171 env, android_node->GetText()).obj());
172 break;
174 case ui::AX_EVENT_TEXT_SELECTION_CHANGED:
175 Java_BrowserAccessibilityManager_handleTextSelectionChanged(
176 env, obj.obj(), node->GetId());
177 break;
178 case ui::AX_EVENT_TEXT_CHANGED:
179 case ui::AX_EVENT_VALUE_CHANGED:
180 if (node->IsEditableText() && GetFocus(GetRoot()) == node) {
181 Java_BrowserAccessibilityManager_handleEditableTextChanged(
182 env, obj.obj(), node->GetId());
183 } else if (android_node->IsSlider()) {
184 Java_BrowserAccessibilityManager_handleSliderChanged(
185 env, obj.obj(), node->GetId());
187 break;
188 default:
189 // There are some notifications that aren't meaningful on Android.
190 // It's okay to skip them.
191 break;
195 jint BrowserAccessibilityManagerAndroid::GetRootId(JNIEnv* env, jobject obj) {
196 if (GetRoot())
197 return static_cast<jint>(GetRoot()->GetId());
198 else
199 return -1;
202 jboolean BrowserAccessibilityManagerAndroid::IsNodeValid(
203 JNIEnv* env, jobject obj, jint id) {
204 return GetFromID(id) != NULL;
207 void BrowserAccessibilityManagerAndroid::HitTest(
208 JNIEnv* env, jobject obj, jint x, jint y) {
209 if (delegate())
210 delegate()->AccessibilityHitTest(gfx::Point(x, y));
213 jboolean BrowserAccessibilityManagerAndroid::IsEditableText(
214 JNIEnv* env, jobject obj, jint id) {
215 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
216 GetFromID(id));
217 if (!node)
218 return false;
220 return node->IsEditableText();
223 jint BrowserAccessibilityManagerAndroid::GetEditableTextSelectionStart(
224 JNIEnv* env, jobject obj, jint id) {
225 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
226 GetFromID(id));
227 if (!node)
228 return false;
230 return node->GetIntAttribute(ui::AX_ATTR_TEXT_SEL_START);
233 jint BrowserAccessibilityManagerAndroid::GetEditableTextSelectionEnd(
234 JNIEnv* env, jobject obj, jint id) {
235 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
236 GetFromID(id));
237 if (!node)
238 return false;
240 return node->GetIntAttribute(ui::AX_ATTR_TEXT_SEL_END);
243 jboolean BrowserAccessibilityManagerAndroid::PopulateAccessibilityNodeInfo(
244 JNIEnv* env, jobject obj, jobject info, jint id) {
245 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
246 GetFromID(id));
247 if (!node)
248 return false;
250 if (node->GetParent()) {
251 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoParent(
252 env, obj, info, node->GetParent()->GetId());
254 for (unsigned i = 0; i < node->PlatformChildCount(); ++i) {
255 Java_BrowserAccessibilityManager_addAccessibilityNodeInfoChild(
256 env, obj, info, node->InternalGetChild(i)->GetId());
258 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoBooleanAttributes(
259 env, obj, info,
261 node->IsCheckable(),
262 node->IsChecked(),
263 node->IsClickable(),
264 node->IsEnabled(),
265 node->IsFocusable(),
266 node->IsFocused(),
267 node->IsPassword(),
268 node->IsScrollable(),
269 node->IsSelected(),
270 node->IsVisibleToUser());
271 Java_BrowserAccessibilityManager_addAccessibilityNodeInfoActions(
272 env, obj, info,
274 node->CanScrollForward(),
275 node->CanScrollBackward(),
276 node->CanScrollUp(),
277 node->CanScrollDown(),
278 node->CanScrollLeft(),
279 node->CanScrollRight(),
280 node->IsClickable(),
281 node->IsEditableText(),
282 node->IsEnabled(),
283 node->IsFocusable(),
284 node->IsFocused());
285 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoClassName(
286 env, obj, info,
287 base::android::ConvertUTF8ToJavaString(env, node->GetClassName()).obj());
288 if (!node->IsPassword() ||
289 Java_BrowserAccessibilityManager_shouldExposePasswordText(env, obj)) {
290 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoContentDescription(
291 env, obj, info,
292 base::android::ConvertUTF16ToJavaString(env, node->GetText()).obj(),
293 node->IsLink());
295 base::string16 element_id;
296 if (node->GetHtmlAttribute("id", &element_id)) {
297 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoViewIdResourceName(
298 env, obj, info,
299 base::android::ConvertUTF16ToJavaString(env, element_id).obj());
302 gfx::Rect absolute_rect = node->GetLocalBoundsRect();
303 gfx::Rect parent_relative_rect = absolute_rect;
304 if (node->GetParent()) {
305 gfx::Rect parent_rect = node->GetParent()->GetLocalBoundsRect();
306 parent_relative_rect.Offset(-parent_rect.OffsetFromOrigin());
308 bool is_root = node->GetParent() == NULL;
309 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoLocation(
310 env, obj, info,
312 absolute_rect.x(), absolute_rect.y(),
313 parent_relative_rect.x(), parent_relative_rect.y(),
314 absolute_rect.width(), absolute_rect.height(),
315 is_root);
317 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoLollipopAttributes(
318 env, obj, info,
319 node->CanOpenPopup(),
320 node->IsContentInvalid(),
321 node->IsDismissable(),
322 node->IsMultiLine(),
323 node->AndroidInputType(),
324 node->AndroidLiveRegionType());
325 if (node->IsCollection()) {
326 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoCollectionInfo(
327 env, obj, info,
328 node->RowCount(),
329 node->ColumnCount(),
330 node->IsHierarchical());
332 if (node->IsCollectionItem() || node->IsHeading()) {
333 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoCollectionItemInfo(
334 env, obj, info,
335 node->RowIndex(),
336 node->RowSpan(),
337 node->ColumnIndex(),
338 node->ColumnSpan(),
339 node->IsHeading());
341 if (node->IsRangeType()) {
342 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoRangeInfo(
343 env, obj, info,
344 node->AndroidRangeType(),
345 node->RangeMin(),
346 node->RangeMax(),
347 node->RangeCurrentValue());
350 return true;
353 jboolean BrowserAccessibilityManagerAndroid::PopulateAccessibilityEvent(
354 JNIEnv* env, jobject obj, jobject event, jint id, jint event_type) {
355 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
356 GetFromID(id));
357 if (!node)
358 return false;
360 Java_BrowserAccessibilityManager_setAccessibilityEventBooleanAttributes(
361 env, obj, event,
362 node->IsChecked(),
363 node->IsEnabled(),
364 node->IsPassword(),
365 node->IsScrollable());
366 Java_BrowserAccessibilityManager_setAccessibilityEventClassName(
367 env, obj, event,
368 base::android::ConvertUTF8ToJavaString(env, node->GetClassName()).obj());
369 Java_BrowserAccessibilityManager_setAccessibilityEventListAttributes(
370 env, obj, event,
371 node->GetItemIndex(),
372 node->GetItemCount());
373 Java_BrowserAccessibilityManager_setAccessibilityEventScrollAttributes(
374 env, obj, event,
375 node->GetScrollX(),
376 node->GetScrollY(),
377 node->GetMaxScrollX(),
378 node->GetMaxScrollY());
380 switch (event_type) {
381 case ANDROID_ACCESSIBILITY_EVENT_TEXT_CHANGED: {
382 base::string16 before_text, text;
383 if (!node->IsPassword() ||
384 Java_BrowserAccessibilityManager_shouldExposePasswordText(env, obj)) {
385 before_text = node->GetTextChangeBeforeText();
386 text = node->GetText();
388 Java_BrowserAccessibilityManager_setAccessibilityEventTextChangedAttrs(
389 env, obj, event,
390 node->GetTextChangeFromIndex(),
391 node->GetTextChangeAddedCount(),
392 node->GetTextChangeRemovedCount(),
393 base::android::ConvertUTF16ToJavaString(
394 env, before_text).obj(),
395 base::android::ConvertUTF16ToJavaString(env, text).obj());
396 break;
398 case ANDROID_ACCESSIBILITY_EVENT_TEXT_SELECTION_CHANGED: {
399 base::string16 text;
400 if (!node->IsPassword() ||
401 Java_BrowserAccessibilityManager_shouldExposePasswordText(env, obj)) {
402 text = node->GetText();
404 Java_BrowserAccessibilityManager_setAccessibilityEventSelectionAttrs(
405 env, obj, event,
406 node->GetSelectionStart(),
407 node->GetSelectionEnd(),
408 node->GetEditableTextLength(),
409 base::android::ConvertUTF16ToJavaString(env, text).obj());
410 break;
412 default:
413 break;
416 Java_BrowserAccessibilityManager_setAccessibilityEventLollipopAttributes(
417 env, obj, event,
418 node->CanOpenPopup(),
419 node->IsContentInvalid(),
420 node->IsDismissable(),
421 node->IsMultiLine(),
422 node->AndroidInputType(),
423 node->AndroidLiveRegionType());
424 if (node->IsCollection()) {
425 Java_BrowserAccessibilityManager_setAccessibilityEventCollectionInfo(
426 env, obj, event,
427 node->RowCount(),
428 node->ColumnCount(),
429 node->IsHierarchical());
431 if (node->IsHeading()) {
432 Java_BrowserAccessibilityManager_setAccessibilityEventHeadingFlag(
433 env, obj, event, true);
435 if (node->IsCollectionItem()) {
436 Java_BrowserAccessibilityManager_setAccessibilityEventCollectionItemInfo(
437 env, obj, event,
438 node->RowIndex(),
439 node->RowSpan(),
440 node->ColumnIndex(),
441 node->ColumnSpan());
443 if (node->IsRangeType()) {
444 Java_BrowserAccessibilityManager_setAccessibilityEventRangeInfo(
445 env, obj, event,
446 node->AndroidRangeType(),
447 node->RangeMin(),
448 node->RangeMax(),
449 node->RangeCurrentValue());
452 return true;
455 void BrowserAccessibilityManagerAndroid::Click(
456 JNIEnv* env, jobject obj, jint id) {
457 BrowserAccessibility* node = GetFromID(id);
458 if (node)
459 DoDefaultAction(*node);
462 void BrowserAccessibilityManagerAndroid::Focus(
463 JNIEnv* env, jobject obj, jint id) {
464 BrowserAccessibility* node = GetFromID(id);
465 if (node)
466 SetFocus(node, true);
469 void BrowserAccessibilityManagerAndroid::Blur(JNIEnv* env, jobject obj) {
470 SetFocus(GetRoot(), true);
473 void BrowserAccessibilityManagerAndroid::ScrollToMakeNodeVisible(
474 JNIEnv* env, jobject obj, jint id) {
475 BrowserAccessibility* node = GetFromID(id);
476 if (node)
477 ScrollToMakeVisible(*node, gfx::Rect(node->GetLocation().size()));
480 void BrowserAccessibilityManagerAndroid::SetTextFieldValue(
481 JNIEnv* env, jobject obj, jint id, jstring value) {
482 BrowserAccessibility* node = GetFromID(id);
483 if (node) {
484 BrowserAccessibilityManager::SetValue(
485 *node, base::android::ConvertJavaStringToUTF16(env, value));
489 void BrowserAccessibilityManagerAndroid::SetSelection(
490 JNIEnv* env, jobject obj, jint id, jint start, jint end) {
491 BrowserAccessibility* node = GetFromID(id);
492 if (node)
493 SetTextSelection(*node, start, end);
496 jboolean BrowserAccessibilityManagerAndroid::AdjustSlider(
497 JNIEnv* env, jobject obj, jint id, jboolean increment) {
498 BrowserAccessibility* node = GetFromID(id);
499 if (!node)
500 return false;
502 BrowserAccessibilityAndroid* android_node =
503 static_cast<BrowserAccessibilityAndroid*>(node);
505 if (!android_node->IsSlider() || !android_node->IsEnabled())
506 return false;
508 float value = node->GetFloatAttribute(ui::AX_ATTR_VALUE_FOR_RANGE);
509 float min = node->GetFloatAttribute(ui::AX_ATTR_MIN_VALUE_FOR_RANGE);
510 float max = node->GetFloatAttribute(ui::AX_ATTR_MAX_VALUE_FOR_RANGE);
511 if (max <= min)
512 return false;
514 // To behave similarly to an Android SeekBar, move by an increment of
515 // approximately 20%.
516 float original_value = value;
517 float delta = (max - min) / 5.0f;
518 value += (increment ? delta : -delta);
519 value = std::max(std::min(value, max), min);
520 if (value != original_value) {
521 BrowserAccessibilityManager::SetValue(
522 *node, base::UTF8ToUTF16(base::DoubleToString(value)));
523 return true;
525 return false;
528 void BrowserAccessibilityManagerAndroid::HandleHoverEvent(
529 BrowserAccessibility* node) {
530 JNIEnv* env = AttachCurrentThread();
531 ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
532 if (obj.is_null())
533 return;
535 BrowserAccessibilityAndroid* ancestor =
536 static_cast<BrowserAccessibilityAndroid*>(node->GetParent());
537 while (ancestor && ancestor != GetRoot()) {
538 if (ancestor->PlatformIsLeaf() ||
539 (ancestor->IsFocusable() && !ancestor->HasFocusableChild())) {
540 node = ancestor;
541 // Don't break - we want the highest ancestor that's focusable or a
542 // leaf node.
544 ancestor = static_cast<BrowserAccessibilityAndroid*>(ancestor->GetParent());
547 Java_BrowserAccessibilityManager_handleHover(
548 env, obj.obj(), node->GetId());
551 jint BrowserAccessibilityManagerAndroid::FindElementType(
552 JNIEnv* env, jobject obj, jint start_id, jstring element_type_str,
553 jboolean forwards) {
554 BrowserAccessibility* node = GetFromID(start_id);
555 if (!node)
556 return 0;
558 AndroidHtmlElementType element_type = HtmlElementTypeFromString(
559 base::android::ConvertJavaStringToUTF16(env, element_type_str));
561 node = forwards ? NextInTreeOrder(node) : PreviousInTreeOrder(node);
562 while (node) {
563 switch(element_type) {
564 case HTML_ELEMENT_TYPE_SECTION:
565 if (node->GetRole() == ui::AX_ROLE_ARTICLE ||
566 node->GetRole() == ui::AX_ROLE_APPLICATION ||
567 node->GetRole() == ui::AX_ROLE_BANNER ||
568 node->GetRole() == ui::AX_ROLE_COMPLEMENTARY ||
569 node->GetRole() == ui::AX_ROLE_CONTENT_INFO ||
570 node->GetRole() == ui::AX_ROLE_HEADING ||
571 node->GetRole() == ui::AX_ROLE_MAIN ||
572 node->GetRole() == ui::AX_ROLE_NAVIGATION ||
573 node->GetRole() == ui::AX_ROLE_SEARCH ||
574 node->GetRole() == ui::AX_ROLE_REGION) {
575 return node->GetId();
577 break;
578 case HTML_ELEMENT_TYPE_LIST:
579 if (node->GetRole() == ui::AX_ROLE_LIST ||
580 node->GetRole() == ui::AX_ROLE_GRID ||
581 node->GetRole() == ui::AX_ROLE_TABLE ||
582 node->GetRole() == ui::AX_ROLE_TREE) {
583 return node->GetId();
585 break;
586 case HTML_ELEMENT_TYPE_CONTROL:
587 if (static_cast<BrowserAccessibilityAndroid*>(node)->IsFocusable())
588 return node->GetId();
589 break;
590 case HTML_ELEMENT_TYPE_ANY:
591 // In theory, the API says that an accessibility service could
592 // jump to an element by element name, like 'H1' or 'P'. This isn't
593 // currently used by any accessibility service, and we think it's
594 // better to keep them high-level like 'SECTION' or 'CONTROL', so we
595 // just fall back on linear navigation when we don't recognize the
596 // element type.
597 if (static_cast<BrowserAccessibilityAndroid*>(node)->IsClickable())
598 return node->GetId();
599 break;
602 node = forwards ? NextInTreeOrder(node) : PreviousInTreeOrder(node);
605 return 0;
608 jboolean BrowserAccessibilityManagerAndroid::NextAtGranularity(
609 JNIEnv* env, jobject obj, jint granularity, jboolean extend_selection,
610 jint id, jint cursor_index) {
611 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
612 GetFromID(id));
613 if (!node)
614 return false;
616 jint start_index = -1;
617 int end_index = -1;
618 if (NextAtGranularity(granularity, cursor_index, node,
619 &start_index, &end_index)) {
620 base::string16 text;
621 if (!node->IsPassword() ||
622 Java_BrowserAccessibilityManager_shouldExposePasswordText(env, obj)) {
623 text = node->GetText();
625 Java_BrowserAccessibilityManager_finishGranularityMove(
626 env, obj, base::android::ConvertUTF16ToJavaString(
627 env, text).obj(),
628 extend_selection, start_index, end_index, true);
629 return true;
631 return false;
634 jboolean BrowserAccessibilityManagerAndroid::PreviousAtGranularity(
635 JNIEnv* env, jobject obj, jint granularity, jboolean extend_selection,
636 jint id, jint cursor_index) {
637 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
638 GetFromID(id));
639 if (!node)
640 return false;
642 jint start_index = -1;
643 int end_index = -1;
644 if (PreviousAtGranularity(granularity, cursor_index, node,
645 &start_index, &end_index)) {
646 Java_BrowserAccessibilityManager_finishGranularityMove(
647 env, obj, base::android::ConvertUTF16ToJavaString(
648 env, node->GetText()).obj(),
649 extend_selection, start_index, end_index, false);
650 return true;
652 return false;
655 bool BrowserAccessibilityManagerAndroid::NextAtGranularity(
656 int32 granularity, int32 cursor_index,
657 BrowserAccessibilityAndroid* node, int32* start_index, int32* end_index) {
658 switch (granularity) {
659 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_CHARACTER: {
660 base::string16 text = node->GetText();
661 if (cursor_index >= static_cast<int32>(text.length()))
662 return false;
663 base::i18n::UTF16CharIterator iter(text.data(), text.size());
664 while (!iter.end() && iter.array_pos() <= cursor_index)
665 iter.Advance();
666 *start_index = iter.array_pos();
667 *end_index = iter.array_pos();
668 break;
670 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_WORD:
671 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_LINE: {
672 std::vector<int32> starts;
673 std::vector<int32> ends;
674 node->GetGranularityBoundaries(granularity, &starts, &ends, 0);
675 if (starts.size() == 0)
676 return false;
678 size_t index = 0;
679 while (index < starts.size() - 1 && starts[index] < cursor_index)
680 index++;
682 if (starts[index] < cursor_index)
683 return false;
685 *start_index = starts[index];
686 *end_index = ends[index];
687 break;
689 default:
690 NOTREACHED();
693 return true;
696 bool BrowserAccessibilityManagerAndroid::PreviousAtGranularity(
697 int32 granularity, int32 cursor_index,
698 BrowserAccessibilityAndroid* node, int32* start_index, int32* end_index) {
699 switch (granularity) {
700 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_CHARACTER: {
701 if (cursor_index <= 0)
702 return false;
703 base::string16 text = node->GetText();
704 base::i18n::UTF16CharIterator iter(text.data(), text.size());
705 int previous_index = 0;
706 while (!iter.end() && iter.array_pos() < cursor_index) {
707 previous_index = iter.array_pos();
708 iter.Advance();
710 *start_index = previous_index;
711 *end_index = previous_index;
712 break;
714 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_WORD:
715 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_LINE: {
716 std::vector<int32> starts;
717 std::vector<int32> ends;
718 node->GetGranularityBoundaries(granularity, &starts, &ends, 0);
719 if (starts.size() == 0)
720 return false;
722 size_t index = starts.size() - 1;
723 while (index > 0 && starts[index] >= cursor_index)
724 index--;
726 if (starts[index] >= cursor_index)
727 return false;
729 *start_index = starts[index];
730 *end_index = ends[index];
731 break;
733 default:
734 NOTREACHED();
737 return true;
740 void BrowserAccessibilityManagerAndroid::SetAccessibilityFocus(
741 JNIEnv* env, jobject obj, jint id) {
742 if (delegate_)
743 delegate_->AccessibilitySetAccessibilityFocus(id);
746 bool BrowserAccessibilityManagerAndroid::IsSlider(
747 JNIEnv* env, jobject obj, jint id) {
748 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
749 GetFromID(id));
750 if (!node)
751 return false;
753 return node->GetRole() == ui::AX_ROLE_SLIDER;
756 bool BrowserAccessibilityManagerAndroid::Scroll(
757 JNIEnv* env, jobject obj, jint id, int direction) {
758 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
759 GetFromID(id));
760 if (!node)
761 return false;
763 return node->Scroll(direction);
766 void BrowserAccessibilityManagerAndroid::OnAtomicUpdateFinished(
767 ui::AXTree* tree,
768 bool root_changed,
769 const std::vector<ui::AXTreeDelegate::Change>& changes) {
770 if (root_changed) {
771 JNIEnv* env = AttachCurrentThread();
772 ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
773 if (obj.is_null())
774 return;
776 Java_BrowserAccessibilityManager_handleNavigate(env, obj.obj());
780 bool
781 BrowserAccessibilityManagerAndroid::UseRootScrollOffsetsWhenComputingBounds() {
782 // The Java layer handles the root scroll offset.
783 return false;
786 bool RegisterBrowserAccessibilityManager(JNIEnv* env) {
787 return RegisterNativesImpl(env);
790 } // namespace content