Disable PDF tests that are failing after combining PDF plugin into chrome.
[chromium-blink-merge.git] / content / browser / accessibility / browser_accessibility_manager_android.cc
blobef7cd01e824d23db54d0a72d3f7ae4345588cb1c
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 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);
81 if (obj.is_null())
82 return;
84 Java_BrowserAccessibilityManager_onNativeObjectDestroyed(env, obj.obj());
87 // static
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);
96 return update;
99 void BrowserAccessibilityManagerAndroid::SetContentViewCore(
100 ScopedJavaLocalRef<jobject> content_view_core) {
101 if (content_view_core.is_null())
102 return;
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);
116 if (obj.is_null())
117 return;
119 BrowserAccessibilityAndroid* android_node =
120 static_cast<BrowserAccessibilityAndroid*>(node);
122 if (event_type == ui::AX_EVENT_HIDE)
123 return;
125 if (event_type == ui::AX_EVENT_TREE_CHANGED)
126 return;
128 if (event_type == ui::AX_EVENT_HOVER) {
129 HandleHoverEvent(node);
130 return;
133 // Always send AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED to notify
134 // the Android system that the accessibility hierarchy rooted at this
135 // node has changed.
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());
143 break;
144 case ui::AX_EVENT_FOCUS:
145 Java_BrowserAccessibilityManager_handleFocusChanged(
146 env, obj.obj(), node->GetId());
147 break;
148 case ui::AX_EVENT_CHECKED_STATE_CHANGED:
149 Java_BrowserAccessibilityManager_handleCheckStateChanged(
150 env, obj.obj(), node->GetId());
151 break;
152 case ui::AX_EVENT_SCROLL_POSITION_CHANGED:
153 Java_BrowserAccessibilityManager_handleScrollPositionChanged(
154 env, obj.obj(), node->GetId());
155 break;
156 case ui::AX_EVENT_SCROLLED_TO_ANCHOR:
157 Java_BrowserAccessibilityManager_handleScrolledToAnchor(
158 env, obj.obj(), node->GetId());
159 break;
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.
165 // Speak its text.
166 Java_BrowserAccessibilityManager_announceLiveRegionText(
167 env, obj.obj(),
168 base::android::ConvertUTF16ToJavaString(
169 env, android_node->GetText()).obj());
170 break;
172 case ui::AX_EVENT_TEXT_SELECTION_CHANGED:
173 Java_BrowserAccessibilityManager_handleTextSelectionChanged(
174 env, obj.obj(), node->GetId());
175 break;
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());
186 break;
187 default:
188 // There are some notifications that aren't meaningful on Android.
189 // It's okay to skip them.
190 break;
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) {
205 if (delegate())
206 delegate()->AccessibilityHitTest(gfx::Point(x, y));
209 jboolean BrowserAccessibilityManagerAndroid::IsEditableText(
210 JNIEnv* env, jobject obj, jint id) {
211 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
212 GetFromID(id));
213 if (!node)
214 return false;
216 return node->IsEditableText();
219 jint BrowserAccessibilityManagerAndroid::GetEditableTextSelectionStart(
220 JNIEnv* env, jobject obj, jint id) {
221 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
222 GetFromID(id));
223 if (!node)
224 return false;
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*>(
232 GetFromID(id));
233 if (!node)
234 return false;
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*>(
242 GetFromID(id));
243 if (!node)
244 return false;
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(
255 env, obj, info,
257 node->CanScrollForward(),
258 node->CanScrollBackward(),
259 node->IsCheckable(),
260 node->IsChecked(),
261 node->IsClickable(),
262 node->IsEditableText(),
263 node->IsEnabled(),
264 node->IsFocusable(),
265 node->IsFocused(),
266 node->IsPassword(),
267 node->IsScrollable(),
268 node->IsSelected(),
269 node->IsVisibleToUser());
270 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoClassName(
271 env, obj, info,
272 base::android::ConvertUTF8ToJavaString(env, node->GetClassName()).obj());
273 if (!node->IsPassword() ||
274 Java_BrowserAccessibilityManager_shouldExposePasswordText(env, obj)) {
275 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoContentDescription(
276 env, obj, info,
277 base::android::ConvertUTF16ToJavaString(env, node->GetText()).obj(),
278 node->IsLink());
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(
289 env, obj, info,
291 absolute_rect.x(), absolute_rect.y(),
292 parent_relative_rect.x(), parent_relative_rect.y(),
293 absolute_rect.width(), absolute_rect.height(),
294 is_root);
296 // New KitKat APIs
297 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoKitKatAttributes(
298 env, obj, info,
299 node->CanOpenPopup(),
300 node->IsContentInvalid(),
301 node->IsDismissable(),
302 node->IsMultiLine(),
303 node->AndroidInputType(),
304 node->AndroidLiveRegionType());
305 if (node->IsCollection()) {
306 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoCollectionInfo(
307 env, obj, info,
308 node->RowCount(),
309 node->ColumnCount(),
310 node->IsHierarchical());
312 if (node->IsCollectionItem() || node->IsHeading()) {
313 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoCollectionItemInfo(
314 env, obj, info,
315 node->RowIndex(),
316 node->RowSpan(),
317 node->ColumnIndex(),
318 node->ColumnSpan(),
319 node->IsHeading());
321 if (node->IsRangeType()) {
322 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoRangeInfo(
323 env, obj, info,
324 node->AndroidRangeType(),
325 node->RangeMin(),
326 node->RangeMax(),
327 node->RangeCurrentValue());
330 return true;
333 jboolean BrowserAccessibilityManagerAndroid::PopulateAccessibilityEvent(
334 JNIEnv* env, jobject obj, jobject event, jint id, jint event_type) {
335 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
336 GetFromID(id));
337 if (!node)
338 return false;
340 Java_BrowserAccessibilityManager_setAccessibilityEventBooleanAttributes(
341 env, obj, event,
342 node->IsChecked(),
343 node->IsEnabled(),
344 node->IsPassword(),
345 node->IsScrollable());
346 Java_BrowserAccessibilityManager_setAccessibilityEventClassName(
347 env, obj, event,
348 base::android::ConvertUTF8ToJavaString(env, node->GetClassName()).obj());
349 Java_BrowserAccessibilityManager_setAccessibilityEventListAttributes(
350 env, obj, event,
351 node->GetItemIndex(),
352 node->GetItemCount());
353 Java_BrowserAccessibilityManager_setAccessibilityEventScrollAttributes(
354 env, obj, event,
355 node->GetScrollX(),
356 node->GetScrollY(),
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(
369 env, obj, event,
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());
376 break;
378 case ANDROID_ACCESSIBILITY_EVENT_TEXT_SELECTION_CHANGED: {
379 base::string16 text;
380 if (!node->IsPassword() ||
381 Java_BrowserAccessibilityManager_shouldExposePasswordText(env, obj)) {
382 text = node->GetText();
384 Java_BrowserAccessibilityManager_setAccessibilityEventSelectionAttrs(
385 env, obj, event,
386 node->GetSelectionStart(),
387 node->GetSelectionEnd(),
388 node->GetEditableTextLength(),
389 base::android::ConvertUTF16ToJavaString(env, text).obj());
390 break;
392 default:
393 break;
396 // Backwards-compatible fallback for new KitKat APIs.
397 Java_BrowserAccessibilityManager_setAccessibilityEventKitKatAttributes(
398 env, obj, event,
399 node->CanOpenPopup(),
400 node->IsContentInvalid(),
401 node->IsDismissable(),
402 node->IsMultiLine(),
403 node->AndroidInputType(),
404 node->AndroidLiveRegionType());
405 if (node->IsCollection()) {
406 Java_BrowserAccessibilityManager_setAccessibilityEventCollectionInfo(
407 env, obj, event,
408 node->RowCount(),
409 node->ColumnCount(),
410 node->IsHierarchical());
412 if (node->IsHeading()) {
413 Java_BrowserAccessibilityManager_setAccessibilityEventHeadingFlag(
414 env, obj, event, true);
416 if (node->IsCollectionItem()) {
417 Java_BrowserAccessibilityManager_setAccessibilityEventCollectionItemInfo(
418 env, obj, event,
419 node->RowIndex(),
420 node->RowSpan(),
421 node->ColumnIndex(),
422 node->ColumnSpan());
424 if (node->IsRangeType()) {
425 Java_BrowserAccessibilityManager_setAccessibilityEventRangeInfo(
426 env, obj, event,
427 node->AndroidRangeType(),
428 node->RangeMin(),
429 node->RangeMax(),
430 node->RangeCurrentValue());
433 return true;
436 void BrowserAccessibilityManagerAndroid::Click(
437 JNIEnv* env, jobject obj, jint id) {
438 BrowserAccessibility* node = GetFromID(id);
439 if (node)
440 DoDefaultAction(*node);
443 void BrowserAccessibilityManagerAndroid::Focus(
444 JNIEnv* env, jobject obj, jint id) {
445 BrowserAccessibility* node = GetFromID(id);
446 if (node)
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);
457 if (node)
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);
464 if (node) {
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);
473 if (node)
474 SetTextSelection(*node, start, end);
477 jboolean BrowserAccessibilityManagerAndroid::AdjustSlider(
478 JNIEnv* env, jobject obj, jint id, jboolean increment) {
479 BrowserAccessibility* node = GetFromID(id);
480 if (!node)
481 return false;
483 BrowserAccessibilityAndroid* android_node =
484 static_cast<BrowserAccessibilityAndroid*>(node);
486 if (!android_node->IsSlider() || !android_node->IsEnabled())
487 return false;
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);
492 if (max <= min)
493 return false;
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)));
504 return true;
506 return false;
509 void BrowserAccessibilityManagerAndroid::HandleHoverEvent(
510 BrowserAccessibility* node) {
511 JNIEnv* env = AttachCurrentThread();
512 ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
513 if (obj.is_null())
514 return;
516 BrowserAccessibilityAndroid* ancestor =
517 static_cast<BrowserAccessibilityAndroid*>(node->GetParent());
518 while (ancestor && ancestor != GetRoot()) {
519 if (ancestor->PlatformIsLeaf() ||
520 (ancestor->IsFocusable() && !ancestor->HasFocusableChild())) {
521 node = ancestor;
522 // Don't break - we want the highest ancestor that's focusable or a
523 // leaf node.
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,
534 jboolean forwards) {
535 BrowserAccessibility* node = GetFromID(start_id);
536 if (!node)
537 return 0;
539 AndroidHtmlElementType element_type = HtmlElementTypeFromString(
540 base::android::ConvertJavaStringToUTF16(env, element_type_str));
542 node = forwards ? NextInTreeOrder(node) : PreviousInTreeOrder(node);
543 while (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();
558 break;
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();
566 break;
567 case HTML_ELEMENT_TYPE_CONTROL:
568 if (static_cast<BrowserAccessibilityAndroid*>(node)->IsFocusable())
569 return node->GetId();
570 break;
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
577 // element type.
578 if (static_cast<BrowserAccessibilityAndroid*>(node)->IsClickable())
579 return node->GetId();
580 break;
583 node = forwards ? NextInTreeOrder(node) : PreviousInTreeOrder(node);
586 return 0;
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*>(
593 GetFromID(id));
594 if (!node)
595 return false;
597 jint start_index = -1;
598 int end_index = -1;
599 if (NextAtGranularity(granularity, cursor_index, node,
600 &start_index, &end_index)) {
601 base::string16 text;
602 if (!node->IsPassword() ||
603 Java_BrowserAccessibilityManager_shouldExposePasswordText(env, obj)) {
604 text = node->GetText();
606 Java_BrowserAccessibilityManager_finishGranularityMove(
607 env, obj, base::android::ConvertUTF16ToJavaString(
608 env, text).obj(),
609 extend_selection, start_index, end_index, true);
610 return true;
612 return false;
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*>(
619 GetFromID(id));
620 if (!node)
621 return false;
623 jint start_index = -1;
624 int end_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);
631 return true;
633 return 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()))
643 return false;
644 base::i18n::UTF16CharIterator iter(text.data(), text.size());
645 while (!iter.end() && iter.array_pos() <= cursor_index)
646 iter.Advance();
647 *start_index = iter.array_pos();
648 *end_index = iter.array_pos();
649 break;
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)
657 return false;
659 size_t index = 0;
660 while (index < starts.size() - 1 && starts[index] < cursor_index)
661 index++;
663 if (starts[index] < cursor_index)
664 return false;
666 *start_index = starts[index];
667 *end_index = ends[index];
668 break;
670 default:
671 NOTREACHED();
674 return true;
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)
683 return false;
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();
689 iter.Advance();
691 *start_index = previous_index;
692 *end_index = previous_index;
693 break;
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)
701 return false;
703 size_t index = starts.size() - 1;
704 while (index > 0 && starts[index] >= cursor_index)
705 index--;
707 if (starts[index] >= cursor_index)
708 return false;
710 *start_index = starts[index];
711 *end_index = ends[index];
712 break;
714 default:
715 NOTREACHED();
718 return true;
721 void BrowserAccessibilityManagerAndroid::SetAccessibilityFocus(
722 JNIEnv* env, jobject obj, jint id) {
723 if (delegate_)
724 delegate_->AccessibilitySetAccessibilityFocus(id);
727 void BrowserAccessibilityManagerAndroid::OnAtomicUpdateFinished(
728 bool root_changed,
729 const std::vector<ui::AXTreeDelegate::Change>& changes) {
730 if (root_changed) {
731 JNIEnv* env = AttachCurrentThread();
732 ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
733 if (obj.is_null())
734 return;
736 Java_BrowserAccessibilityManager_handleNavigate(env, obj.obj());
740 bool
741 BrowserAccessibilityManagerAndroid::UseRootScrollOffsetsWhenComputingBounds() {
742 // The Java layer handles the root scroll offset.
743 return false;
746 bool RegisterBrowserAccessibilityManager(JNIEnv* env) {
747 return RegisterNativesImpl(env);
750 } // namespace content