Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / content / browser / accessibility / browser_accessibility_manager_android.cc
blob703d800a204d678dc28fd36c75775484267d8ea4
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 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 ui::AXTreeUpdate BrowserAccessibilityManagerAndroid::GetEmptyDocument() {
90 ui::AXNodeData empty_document;
91 empty_document.id = 0;
92 empty_document.role = ui::AX_ROLE_ROOT_WEB_AREA;
93 empty_document.state = 1 << ui::AX_STATE_READ_ONLY;
95 ui::AXTreeUpdate update;
96 update.nodes.push_back(empty_document);
97 return update;
100 void BrowserAccessibilityManagerAndroid::SetContentViewCore(
101 ScopedJavaLocalRef<jobject> content_view_core) {
102 if (content_view_core.is_null())
103 return;
105 JNIEnv* env = AttachCurrentThread();
106 java_ref_ = JavaObjectWeakGlobalRef(
107 env, Java_BrowserAccessibilityManager_create(
108 env, reinterpret_cast<intptr_t>(this),
109 content_view_core.obj()).obj());
112 void BrowserAccessibilityManagerAndroid::NotifyAccessibilityEvent(
113 ui::AXEvent event_type,
114 BrowserAccessibility* node) {
115 JNIEnv* env = AttachCurrentThread();
116 ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
117 if (obj.is_null())
118 return;
120 BrowserAccessibilityAndroid* android_node =
121 static_cast<BrowserAccessibilityAndroid*>(node);
123 if (event_type == ui::AX_EVENT_HIDE)
124 return;
126 if (event_type == ui::AX_EVENT_TREE_CHANGED)
127 return;
129 if (event_type == ui::AX_EVENT_HOVER) {
130 HandleHoverEvent(node);
131 return;
134 // Always send AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED to notify
135 // the Android system that the accessibility hierarchy rooted at this
136 // node has changed.
137 Java_BrowserAccessibilityManager_handleContentChanged(
138 env, obj.obj(), node->GetId());
140 switch (event_type) {
141 case ui::AX_EVENT_LOAD_COMPLETE:
142 Java_BrowserAccessibilityManager_handlePageLoaded(
143 env, obj.obj(), focus_->id());
144 break;
145 case ui::AX_EVENT_FOCUS:
146 Java_BrowserAccessibilityManager_handleFocusChanged(
147 env, obj.obj(), node->GetId());
148 break;
149 case ui::AX_EVENT_CHECKED_STATE_CHANGED:
150 Java_BrowserAccessibilityManager_handleCheckStateChanged(
151 env, obj.obj(), node->GetId());
152 break;
153 case ui::AX_EVENT_SCROLL_POSITION_CHANGED:
154 Java_BrowserAccessibilityManager_handleScrollPositionChanged(
155 env, obj.obj(), node->GetId());
156 break;
157 case ui::AX_EVENT_SCROLLED_TO_ANCHOR:
158 Java_BrowserAccessibilityManager_handleScrolledToAnchor(
159 env, obj.obj(), node->GetId());
160 break;
161 case ui::AX_EVENT_ALERT:
162 // An alert is a special case of live region. Fall through to the
163 // next case to handle it.
164 case ui::AX_EVENT_SHOW: {
165 // This event is fired when an object appears in a live region.
166 // Speak its text.
167 Java_BrowserAccessibilityManager_announceLiveRegionText(
168 env, obj.obj(),
169 base::android::ConvertUTF16ToJavaString(
170 env, android_node->GetText()).obj());
171 break;
173 case ui::AX_EVENT_TEXT_SELECTION_CHANGED:
174 Java_BrowserAccessibilityManager_handleTextSelectionChanged(
175 env, obj.obj(), node->GetId());
176 break;
177 case ui::AX_EVENT_TEXT_CHANGED:
178 case ui::AX_EVENT_VALUE_CHANGED:
179 if (node->IsEditableText() && GetFocus(GetRoot()) == node) {
180 Java_BrowserAccessibilityManager_handleEditableTextChanged(
181 env, obj.obj(), node->GetId());
182 } else if (android_node->IsSlider()) {
183 Java_BrowserAccessibilityManager_handleSliderChanged(
184 env, obj.obj(), node->GetId());
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->IsCheckable(),
258 node->IsChecked(),
259 node->IsClickable(),
260 node->IsEnabled(),
261 node->IsFocusable(),
262 node->IsFocused(),
263 node->IsPassword(),
264 node->IsScrollable(),
265 node->IsSelected(),
266 node->IsVisibleToUser());
267 Java_BrowserAccessibilityManager_addAccessibilityNodeInfoActions(
268 env, obj, info,
270 node->CanScrollForward(),
271 node->CanScrollBackward(),
272 node->CanScrollUp(),
273 node->CanScrollDown(),
274 node->CanScrollLeft(),
275 node->CanScrollRight(),
276 node->IsClickable(),
277 node->IsEditableText(),
278 node->IsEnabled(),
279 node->IsFocusable(),
280 node->IsFocused());
281 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoClassName(
282 env, obj, info,
283 base::android::ConvertUTF8ToJavaString(env, node->GetClassName()).obj());
284 if (!node->IsPassword() ||
285 Java_BrowserAccessibilityManager_shouldExposePasswordText(env, obj)) {
286 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoContentDescription(
287 env, obj, info,
288 base::android::ConvertUTF16ToJavaString(env, node->GetText()).obj(),
289 node->IsLink());
291 base::string16 element_id;
292 if (node->GetHtmlAttribute("id", &element_id)) {
293 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoViewIdResourceName(
294 env, obj, info,
295 base::android::ConvertUTF16ToJavaString(env, element_id).obj());
298 gfx::Rect absolute_rect = node->GetLocalBoundsRect();
299 gfx::Rect parent_relative_rect = absolute_rect;
300 if (node->GetParent()) {
301 gfx::Rect parent_rect = node->GetParent()->GetLocalBoundsRect();
302 parent_relative_rect.Offset(-parent_rect.OffsetFromOrigin());
304 bool is_root = node->GetParent() == NULL;
305 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoLocation(
306 env, obj, info,
308 absolute_rect.x(), absolute_rect.y(),
309 parent_relative_rect.x(), parent_relative_rect.y(),
310 absolute_rect.width(), absolute_rect.height(),
311 is_root);
313 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoLollipopAttributes(
314 env, obj, info,
315 node->CanOpenPopup(),
316 node->IsContentInvalid(),
317 node->IsDismissable(),
318 node->IsMultiLine(),
319 node->AndroidInputType(),
320 node->AndroidLiveRegionType());
321 if (node->IsCollection()) {
322 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoCollectionInfo(
323 env, obj, info,
324 node->RowCount(),
325 node->ColumnCount(),
326 node->IsHierarchical());
328 if (node->IsCollectionItem() || node->IsHeading()) {
329 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoCollectionItemInfo(
330 env, obj, info,
331 node->RowIndex(),
332 node->RowSpan(),
333 node->ColumnIndex(),
334 node->ColumnSpan(),
335 node->IsHeading());
337 if (node->IsRangeType()) {
338 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoRangeInfo(
339 env, obj, info,
340 node->AndroidRangeType(),
341 node->RangeMin(),
342 node->RangeMax(),
343 node->RangeCurrentValue());
346 return true;
349 jboolean BrowserAccessibilityManagerAndroid::PopulateAccessibilityEvent(
350 JNIEnv* env, jobject obj, jobject event, jint id, jint event_type) {
351 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
352 GetFromID(id));
353 if (!node)
354 return false;
356 Java_BrowserAccessibilityManager_setAccessibilityEventBooleanAttributes(
357 env, obj, event,
358 node->IsChecked(),
359 node->IsEnabled(),
360 node->IsPassword(),
361 node->IsScrollable());
362 Java_BrowserAccessibilityManager_setAccessibilityEventClassName(
363 env, obj, event,
364 base::android::ConvertUTF8ToJavaString(env, node->GetClassName()).obj());
365 Java_BrowserAccessibilityManager_setAccessibilityEventListAttributes(
366 env, obj, event,
367 node->GetItemIndex(),
368 node->GetItemCount());
369 Java_BrowserAccessibilityManager_setAccessibilityEventScrollAttributes(
370 env, obj, event,
371 node->GetScrollX(),
372 node->GetScrollY(),
373 node->GetMaxScrollX(),
374 node->GetMaxScrollY());
376 switch (event_type) {
377 case ANDROID_ACCESSIBILITY_EVENT_TEXT_CHANGED: {
378 base::string16 before_text, text;
379 if (!node->IsPassword() ||
380 Java_BrowserAccessibilityManager_shouldExposePasswordText(env, obj)) {
381 before_text = node->GetTextChangeBeforeText();
382 text = node->GetText();
384 Java_BrowserAccessibilityManager_setAccessibilityEventTextChangedAttrs(
385 env, obj, event,
386 node->GetTextChangeFromIndex(),
387 node->GetTextChangeAddedCount(),
388 node->GetTextChangeRemovedCount(),
389 base::android::ConvertUTF16ToJavaString(
390 env, before_text).obj(),
391 base::android::ConvertUTF16ToJavaString(env, text).obj());
392 break;
394 case ANDROID_ACCESSIBILITY_EVENT_TEXT_SELECTION_CHANGED: {
395 base::string16 text;
396 if (!node->IsPassword() ||
397 Java_BrowserAccessibilityManager_shouldExposePasswordText(env, obj)) {
398 text = node->GetText();
400 Java_BrowserAccessibilityManager_setAccessibilityEventSelectionAttrs(
401 env, obj, event,
402 node->GetSelectionStart(),
403 node->GetSelectionEnd(),
404 node->GetEditableTextLength(),
405 base::android::ConvertUTF16ToJavaString(env, text).obj());
406 break;
408 default:
409 break;
412 Java_BrowserAccessibilityManager_setAccessibilityEventLollipopAttributes(
413 env, obj, event,
414 node->CanOpenPopup(),
415 node->IsContentInvalid(),
416 node->IsDismissable(),
417 node->IsMultiLine(),
418 node->AndroidInputType(),
419 node->AndroidLiveRegionType());
420 if (node->IsCollection()) {
421 Java_BrowserAccessibilityManager_setAccessibilityEventCollectionInfo(
422 env, obj, event,
423 node->RowCount(),
424 node->ColumnCount(),
425 node->IsHierarchical());
427 if (node->IsHeading()) {
428 Java_BrowserAccessibilityManager_setAccessibilityEventHeadingFlag(
429 env, obj, event, true);
431 if (node->IsCollectionItem()) {
432 Java_BrowserAccessibilityManager_setAccessibilityEventCollectionItemInfo(
433 env, obj, event,
434 node->RowIndex(),
435 node->RowSpan(),
436 node->ColumnIndex(),
437 node->ColumnSpan());
439 if (node->IsRangeType()) {
440 Java_BrowserAccessibilityManager_setAccessibilityEventRangeInfo(
441 env, obj, event,
442 node->AndroidRangeType(),
443 node->RangeMin(),
444 node->RangeMax(),
445 node->RangeCurrentValue());
448 return true;
451 void BrowserAccessibilityManagerAndroid::Click(
452 JNIEnv* env, jobject obj, jint id) {
453 BrowserAccessibility* node = GetFromID(id);
454 if (node)
455 DoDefaultAction(*node);
458 void BrowserAccessibilityManagerAndroid::Focus(
459 JNIEnv* env, jobject obj, jint id) {
460 BrowserAccessibility* node = GetFromID(id);
461 if (node)
462 SetFocus(node, true);
465 void BrowserAccessibilityManagerAndroid::Blur(JNIEnv* env, jobject obj) {
466 SetFocus(GetRoot(), true);
469 void BrowserAccessibilityManagerAndroid::ScrollToMakeNodeVisible(
470 JNIEnv* env, jobject obj, jint id) {
471 BrowserAccessibility* node = GetFromID(id);
472 if (node)
473 ScrollToMakeVisible(*node, gfx::Rect(node->GetLocation().size()));
476 void BrowserAccessibilityManagerAndroid::SetTextFieldValue(
477 JNIEnv* env, jobject obj, jint id, jstring value) {
478 BrowserAccessibility* node = GetFromID(id);
479 if (node) {
480 BrowserAccessibilityManager::SetValue(
481 *node, base::android::ConvertJavaStringToUTF16(env, value));
485 void BrowserAccessibilityManagerAndroid::SetSelection(
486 JNIEnv* env, jobject obj, jint id, jint start, jint end) {
487 BrowserAccessibility* node = GetFromID(id);
488 if (node)
489 SetTextSelection(*node, start, end);
492 jboolean BrowserAccessibilityManagerAndroid::AdjustSlider(
493 JNIEnv* env, jobject obj, jint id, jboolean increment) {
494 BrowserAccessibility* node = GetFromID(id);
495 if (!node)
496 return false;
498 BrowserAccessibilityAndroid* android_node =
499 static_cast<BrowserAccessibilityAndroid*>(node);
501 if (!android_node->IsSlider() || !android_node->IsEnabled())
502 return false;
504 float value = node->GetFloatAttribute(ui::AX_ATTR_VALUE_FOR_RANGE);
505 float min = node->GetFloatAttribute(ui::AX_ATTR_MIN_VALUE_FOR_RANGE);
506 float max = node->GetFloatAttribute(ui::AX_ATTR_MAX_VALUE_FOR_RANGE);
507 if (max <= min)
508 return false;
510 // To behave similarly to an Android SeekBar, move by an increment of
511 // approximately 20%.
512 float original_value = value;
513 float delta = (max - min) / 5.0f;
514 value += (increment ? delta : -delta);
515 value = std::max(std::min(value, max), min);
516 if (value != original_value) {
517 BrowserAccessibilityManager::SetValue(
518 *node, base::UTF8ToUTF16(base::DoubleToString(value)));
519 return true;
521 return false;
524 void BrowserAccessibilityManagerAndroid::HandleHoverEvent(
525 BrowserAccessibility* node) {
526 JNIEnv* env = AttachCurrentThread();
527 ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
528 if (obj.is_null())
529 return;
531 BrowserAccessibilityAndroid* ancestor =
532 static_cast<BrowserAccessibilityAndroid*>(node->GetParent());
533 while (ancestor && ancestor != GetRoot()) {
534 if (ancestor->PlatformIsLeaf() ||
535 (ancestor->IsFocusable() && !ancestor->HasFocusableChild())) {
536 node = ancestor;
537 // Don't break - we want the highest ancestor that's focusable or a
538 // leaf node.
540 ancestor = static_cast<BrowserAccessibilityAndroid*>(ancestor->GetParent());
543 Java_BrowserAccessibilityManager_handleHover(
544 env, obj.obj(), node->GetId());
547 jint BrowserAccessibilityManagerAndroid::FindElementType(
548 JNIEnv* env, jobject obj, jint start_id, jstring element_type_str,
549 jboolean forwards) {
550 BrowserAccessibility* node = GetFromID(start_id);
551 if (!node)
552 return 0;
554 AndroidHtmlElementType element_type = HtmlElementTypeFromString(
555 base::android::ConvertJavaStringToUTF16(env, element_type_str));
557 node = forwards ? NextInTreeOrder(node) : PreviousInTreeOrder(node);
558 while (node) {
559 switch(element_type) {
560 case HTML_ELEMENT_TYPE_SECTION:
561 if (node->GetRole() == ui::AX_ROLE_ARTICLE ||
562 node->GetRole() == ui::AX_ROLE_APPLICATION ||
563 node->GetRole() == ui::AX_ROLE_BANNER ||
564 node->GetRole() == ui::AX_ROLE_COMPLEMENTARY ||
565 node->GetRole() == ui::AX_ROLE_CONTENT_INFO ||
566 node->GetRole() == ui::AX_ROLE_HEADING ||
567 node->GetRole() == ui::AX_ROLE_MAIN ||
568 node->GetRole() == ui::AX_ROLE_NAVIGATION ||
569 node->GetRole() == ui::AX_ROLE_SEARCH ||
570 node->GetRole() == ui::AX_ROLE_REGION) {
571 return node->GetId();
573 break;
574 case HTML_ELEMENT_TYPE_LIST:
575 if (node->GetRole() == ui::AX_ROLE_LIST ||
576 node->GetRole() == ui::AX_ROLE_GRID ||
577 node->GetRole() == ui::AX_ROLE_TABLE ||
578 node->GetRole() == ui::AX_ROLE_TREE) {
579 return node->GetId();
581 break;
582 case HTML_ELEMENT_TYPE_CONTROL:
583 if (static_cast<BrowserAccessibilityAndroid*>(node)->IsFocusable())
584 return node->GetId();
585 break;
586 case HTML_ELEMENT_TYPE_ANY:
587 // In theory, the API says that an accessibility service could
588 // jump to an element by element name, like 'H1' or 'P'. This isn't
589 // currently used by any accessibility service, and we think it's
590 // better to keep them high-level like 'SECTION' or 'CONTROL', so we
591 // just fall back on linear navigation when we don't recognize the
592 // element type.
593 if (static_cast<BrowserAccessibilityAndroid*>(node)->IsClickable())
594 return node->GetId();
595 break;
598 node = forwards ? NextInTreeOrder(node) : PreviousInTreeOrder(node);
601 return 0;
604 jboolean BrowserAccessibilityManagerAndroid::NextAtGranularity(
605 JNIEnv* env, jobject obj, jint granularity, jboolean extend_selection,
606 jint id, jint cursor_index) {
607 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
608 GetFromID(id));
609 if (!node)
610 return false;
612 jint start_index = -1;
613 int end_index = -1;
614 if (NextAtGranularity(granularity, cursor_index, node,
615 &start_index, &end_index)) {
616 base::string16 text;
617 if (!node->IsPassword() ||
618 Java_BrowserAccessibilityManager_shouldExposePasswordText(env, obj)) {
619 text = node->GetText();
621 Java_BrowserAccessibilityManager_finishGranularityMove(
622 env, obj, base::android::ConvertUTF16ToJavaString(
623 env, text).obj(),
624 extend_selection, start_index, end_index, true);
625 return true;
627 return false;
630 jboolean BrowserAccessibilityManagerAndroid::PreviousAtGranularity(
631 JNIEnv* env, jobject obj, jint granularity, jboolean extend_selection,
632 jint id, jint cursor_index) {
633 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
634 GetFromID(id));
635 if (!node)
636 return false;
638 jint start_index = -1;
639 int end_index = -1;
640 if (PreviousAtGranularity(granularity, cursor_index, node,
641 &start_index, &end_index)) {
642 Java_BrowserAccessibilityManager_finishGranularityMove(
643 env, obj, base::android::ConvertUTF16ToJavaString(
644 env, node->GetText()).obj(),
645 extend_selection, start_index, end_index, false);
646 return true;
648 return false;
651 bool BrowserAccessibilityManagerAndroid::NextAtGranularity(
652 int32 granularity, int32 cursor_index,
653 BrowserAccessibilityAndroid* node, int32* start_index, int32* end_index) {
654 switch (granularity) {
655 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_CHARACTER: {
656 base::string16 text = node->GetText();
657 if (cursor_index >= static_cast<int32>(text.length()))
658 return false;
659 base::i18n::UTF16CharIterator iter(text.data(), text.size());
660 while (!iter.end() && iter.array_pos() <= cursor_index)
661 iter.Advance();
662 *start_index = iter.array_pos();
663 *end_index = iter.array_pos();
664 break;
666 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_WORD:
667 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_LINE: {
668 std::vector<int32> starts;
669 std::vector<int32> ends;
670 node->GetGranularityBoundaries(granularity, &starts, &ends, 0);
671 if (starts.size() == 0)
672 return false;
674 size_t index = 0;
675 while (index < starts.size() - 1 && starts[index] < cursor_index)
676 index++;
678 if (starts[index] < cursor_index)
679 return false;
681 *start_index = starts[index];
682 *end_index = ends[index];
683 break;
685 default:
686 NOTREACHED();
689 return true;
692 bool BrowserAccessibilityManagerAndroid::PreviousAtGranularity(
693 int32 granularity, int32 cursor_index,
694 BrowserAccessibilityAndroid* node, int32* start_index, int32* end_index) {
695 switch (granularity) {
696 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_CHARACTER: {
697 if (cursor_index <= 0)
698 return false;
699 base::string16 text = node->GetText();
700 base::i18n::UTF16CharIterator iter(text.data(), text.size());
701 int previous_index = 0;
702 while (!iter.end() && iter.array_pos() < cursor_index) {
703 previous_index = iter.array_pos();
704 iter.Advance();
706 *start_index = previous_index;
707 *end_index = previous_index;
708 break;
710 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_WORD:
711 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_LINE: {
712 std::vector<int32> starts;
713 std::vector<int32> ends;
714 node->GetGranularityBoundaries(granularity, &starts, &ends, 0);
715 if (starts.size() == 0)
716 return false;
718 size_t index = starts.size() - 1;
719 while (index > 0 && starts[index] >= cursor_index)
720 index--;
722 if (starts[index] >= cursor_index)
723 return false;
725 *start_index = starts[index];
726 *end_index = ends[index];
727 break;
729 default:
730 NOTREACHED();
733 return true;
736 void BrowserAccessibilityManagerAndroid::SetAccessibilityFocus(
737 JNIEnv* env, jobject obj, jint id) {
738 if (delegate_)
739 delegate_->AccessibilitySetAccessibilityFocus(id);
742 bool BrowserAccessibilityManagerAndroid::IsSlider(
743 JNIEnv* env, jobject obj, jint id) {
744 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
745 GetFromID(id));
746 if (!node)
747 return false;
749 return node->GetRole() == ui::AX_ROLE_SLIDER;
752 bool BrowserAccessibilityManagerAndroid::Scroll(
753 JNIEnv* env, jobject obj, jint id, int direction) {
754 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
755 GetFromID(id));
756 if (!node)
757 return false;
759 return node->Scroll(direction);
762 void BrowserAccessibilityManagerAndroid::OnAtomicUpdateFinished(
763 ui::AXTree* tree,
764 bool root_changed,
765 const std::vector<ui::AXTreeDelegate::Change>& changes) {
766 if (root_changed) {
767 JNIEnv* env = AttachCurrentThread();
768 ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
769 if (obj.is_null())
770 return;
772 Java_BrowserAccessibilityManager_handleNavigate(env, obj.obj());
776 bool
777 BrowserAccessibilityManagerAndroid::UseRootScrollOffsetsWhenComputingBounds() {
778 // The Java layer handles the root scroll offset.
779 return false;
782 bool RegisterBrowserAccessibilityManager(JNIEnv* env) {
783 return RegisterNativesImpl(env);
786 } // namespace content