Add long running gmail memory benchmark for background tab.
[chromium-blink-merge.git] / content / browser / accessibility / browser_accessibility_manager_android.cc
blob3cab6ea3665f15731ad7605ef5e890b26972f093
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 if (GetRoot())
196 return static_cast<jint>(GetRoot()->GetId());
197 else
198 return -1;
201 jboolean BrowserAccessibilityManagerAndroid::IsNodeValid(
202 JNIEnv* env, jobject obj, jint id) {
203 return GetFromID(id) != NULL;
206 void BrowserAccessibilityManagerAndroid::HitTest(
207 JNIEnv* env, jobject obj, jint x, jint y) {
208 if (delegate())
209 delegate()->AccessibilityHitTest(gfx::Point(x, y));
212 jboolean BrowserAccessibilityManagerAndroid::IsEditableText(
213 JNIEnv* env, jobject obj, jint id) {
214 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
215 GetFromID(id));
216 if (!node)
217 return false;
219 return node->IsEditableText();
222 jint BrowserAccessibilityManagerAndroid::GetEditableTextSelectionStart(
223 JNIEnv* env, jobject obj, jint id) {
224 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
225 GetFromID(id));
226 if (!node)
227 return false;
229 return node->GetIntAttribute(ui::AX_ATTR_TEXT_SEL_START);
232 jint BrowserAccessibilityManagerAndroid::GetEditableTextSelectionEnd(
233 JNIEnv* env, jobject obj, jint id) {
234 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
235 GetFromID(id));
236 if (!node)
237 return false;
239 return node->GetIntAttribute(ui::AX_ATTR_TEXT_SEL_END);
242 jboolean BrowserAccessibilityManagerAndroid::PopulateAccessibilityNodeInfo(
243 JNIEnv* env, jobject obj, jobject info, jint id) {
244 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
245 GetFromID(id));
246 if (!node)
247 return false;
249 if (node->GetParent()) {
250 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoParent(
251 env, obj, info, node->GetParent()->GetId());
253 for (unsigned i = 0; i < node->PlatformChildCount(); ++i) {
254 Java_BrowserAccessibilityManager_addAccessibilityNodeInfoChild(
255 env, obj, info, node->InternalGetChild(i)->GetId());
257 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoBooleanAttributes(
258 env, obj, info,
260 node->IsCheckable(),
261 node->IsChecked(),
262 node->IsClickable(),
263 node->IsEnabled(),
264 node->IsFocusable(),
265 node->IsFocused(),
266 node->IsPassword(),
267 node->IsScrollable(),
268 node->IsSelected(),
269 node->IsVisibleToUser());
270 Java_BrowserAccessibilityManager_addAccessibilityNodeInfoActions(
271 env, obj, info,
273 node->CanScrollForward(),
274 node->CanScrollBackward(),
275 node->CanScrollUp(),
276 node->CanScrollDown(),
277 node->CanScrollLeft(),
278 node->CanScrollRight(),
279 node->IsClickable(),
280 node->IsEditableText(),
281 node->IsEnabled(),
282 node->IsFocusable(),
283 node->IsFocused());
284 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoClassName(
285 env, obj, info,
286 base::android::ConvertUTF8ToJavaString(env, node->GetClassName()).obj());
287 if (!node->IsPassword() ||
288 Java_BrowserAccessibilityManager_shouldExposePasswordText(env, obj)) {
289 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoContentDescription(
290 env, obj, info,
291 base::android::ConvertUTF16ToJavaString(env, node->GetText()).obj(),
292 node->IsLink());
294 base::string16 element_id;
295 if (node->GetHtmlAttribute("id", &element_id)) {
296 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoViewIdResourceName(
297 env, obj, info,
298 base::android::ConvertUTF16ToJavaString(env, element_id).obj());
301 gfx::Rect absolute_rect = node->GetLocalBoundsRect();
302 gfx::Rect parent_relative_rect = absolute_rect;
303 if (node->GetParent()) {
304 gfx::Rect parent_rect = node->GetParent()->GetLocalBoundsRect();
305 parent_relative_rect.Offset(-parent_rect.OffsetFromOrigin());
307 bool is_root = node->GetParent() == NULL;
308 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoLocation(
309 env, obj, info,
311 absolute_rect.x(), absolute_rect.y(),
312 parent_relative_rect.x(), parent_relative_rect.y(),
313 absolute_rect.width(), absolute_rect.height(),
314 is_root);
316 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoLollipopAttributes(
317 env, obj, info,
318 node->CanOpenPopup(),
319 node->IsContentInvalid(),
320 node->IsDismissable(),
321 node->IsMultiLine(),
322 node->AndroidInputType(),
323 node->AndroidLiveRegionType());
324 if (node->IsCollection()) {
325 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoCollectionInfo(
326 env, obj, info,
327 node->RowCount(),
328 node->ColumnCount(),
329 node->IsHierarchical());
331 if (node->IsCollectionItem() || node->IsHeading()) {
332 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoCollectionItemInfo(
333 env, obj, info,
334 node->RowIndex(),
335 node->RowSpan(),
336 node->ColumnIndex(),
337 node->ColumnSpan(),
338 node->IsHeading());
340 if (node->IsRangeType()) {
341 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoRangeInfo(
342 env, obj, info,
343 node->AndroidRangeType(),
344 node->RangeMin(),
345 node->RangeMax(),
346 node->RangeCurrentValue());
349 return true;
352 jboolean BrowserAccessibilityManagerAndroid::PopulateAccessibilityEvent(
353 JNIEnv* env, jobject obj, jobject event, jint id, jint event_type) {
354 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
355 GetFromID(id));
356 if (!node)
357 return false;
359 Java_BrowserAccessibilityManager_setAccessibilityEventBooleanAttributes(
360 env, obj, event,
361 node->IsChecked(),
362 node->IsEnabled(),
363 node->IsPassword(),
364 node->IsScrollable());
365 Java_BrowserAccessibilityManager_setAccessibilityEventClassName(
366 env, obj, event,
367 base::android::ConvertUTF8ToJavaString(env, node->GetClassName()).obj());
368 Java_BrowserAccessibilityManager_setAccessibilityEventListAttributes(
369 env, obj, event,
370 node->GetItemIndex(),
371 node->GetItemCount());
372 Java_BrowserAccessibilityManager_setAccessibilityEventScrollAttributes(
373 env, obj, event,
374 node->GetScrollX(),
375 node->GetScrollY(),
376 node->GetMaxScrollX(),
377 node->GetMaxScrollY());
379 switch (event_type) {
380 case ANDROID_ACCESSIBILITY_EVENT_TEXT_CHANGED: {
381 base::string16 before_text, text;
382 if (!node->IsPassword() ||
383 Java_BrowserAccessibilityManager_shouldExposePasswordText(env, obj)) {
384 before_text = node->GetTextChangeBeforeText();
385 text = node->GetText();
387 Java_BrowserAccessibilityManager_setAccessibilityEventTextChangedAttrs(
388 env, obj, event,
389 node->GetTextChangeFromIndex(),
390 node->GetTextChangeAddedCount(),
391 node->GetTextChangeRemovedCount(),
392 base::android::ConvertUTF16ToJavaString(
393 env, before_text).obj(),
394 base::android::ConvertUTF16ToJavaString(env, text).obj());
395 break;
397 case ANDROID_ACCESSIBILITY_EVENT_TEXT_SELECTION_CHANGED: {
398 base::string16 text;
399 if (!node->IsPassword() ||
400 Java_BrowserAccessibilityManager_shouldExposePasswordText(env, obj)) {
401 text = node->GetText();
403 Java_BrowserAccessibilityManager_setAccessibilityEventSelectionAttrs(
404 env, obj, event,
405 node->GetSelectionStart(),
406 node->GetSelectionEnd(),
407 node->GetEditableTextLength(),
408 base::android::ConvertUTF16ToJavaString(env, text).obj());
409 break;
411 default:
412 break;
415 Java_BrowserAccessibilityManager_setAccessibilityEventLollipopAttributes(
416 env, obj, event,
417 node->CanOpenPopup(),
418 node->IsContentInvalid(),
419 node->IsDismissable(),
420 node->IsMultiLine(),
421 node->AndroidInputType(),
422 node->AndroidLiveRegionType());
423 if (node->IsCollection()) {
424 Java_BrowserAccessibilityManager_setAccessibilityEventCollectionInfo(
425 env, obj, event,
426 node->RowCount(),
427 node->ColumnCount(),
428 node->IsHierarchical());
430 if (node->IsHeading()) {
431 Java_BrowserAccessibilityManager_setAccessibilityEventHeadingFlag(
432 env, obj, event, true);
434 if (node->IsCollectionItem()) {
435 Java_BrowserAccessibilityManager_setAccessibilityEventCollectionItemInfo(
436 env, obj, event,
437 node->RowIndex(),
438 node->RowSpan(),
439 node->ColumnIndex(),
440 node->ColumnSpan());
442 if (node->IsRangeType()) {
443 Java_BrowserAccessibilityManager_setAccessibilityEventRangeInfo(
444 env, obj, event,
445 node->AndroidRangeType(),
446 node->RangeMin(),
447 node->RangeMax(),
448 node->RangeCurrentValue());
451 return true;
454 void BrowserAccessibilityManagerAndroid::Click(
455 JNIEnv* env, jobject obj, jint id) {
456 BrowserAccessibility* node = GetFromID(id);
457 if (node)
458 DoDefaultAction(*node);
461 void BrowserAccessibilityManagerAndroid::Focus(
462 JNIEnv* env, jobject obj, jint id) {
463 BrowserAccessibility* node = GetFromID(id);
464 if (node)
465 SetFocus(node, true);
468 void BrowserAccessibilityManagerAndroid::Blur(JNIEnv* env, jobject obj) {
469 SetFocus(GetRoot(), true);
472 void BrowserAccessibilityManagerAndroid::ScrollToMakeNodeVisible(
473 JNIEnv* env, jobject obj, jint id) {
474 BrowserAccessibility* node = GetFromID(id);
475 if (node)
476 ScrollToMakeVisible(*node, gfx::Rect(node->GetLocation().size()));
479 void BrowserAccessibilityManagerAndroid::SetTextFieldValue(
480 JNIEnv* env, jobject obj, jint id, jstring value) {
481 BrowserAccessibility* node = GetFromID(id);
482 if (node) {
483 BrowserAccessibilityManager::SetValue(
484 *node, base::android::ConvertJavaStringToUTF16(env, value));
488 void BrowserAccessibilityManagerAndroid::SetSelection(
489 JNIEnv* env, jobject obj, jint id, jint start, jint end) {
490 BrowserAccessibility* node = GetFromID(id);
491 if (node)
492 SetTextSelection(*node, start, end);
495 jboolean BrowserAccessibilityManagerAndroid::AdjustSlider(
496 JNIEnv* env, jobject obj, jint id, jboolean increment) {
497 BrowserAccessibility* node = GetFromID(id);
498 if (!node)
499 return false;
501 BrowserAccessibilityAndroid* android_node =
502 static_cast<BrowserAccessibilityAndroid*>(node);
504 if (!android_node->IsSlider() || !android_node->IsEnabled())
505 return false;
507 float value = node->GetFloatAttribute(ui::AX_ATTR_VALUE_FOR_RANGE);
508 float min = node->GetFloatAttribute(ui::AX_ATTR_MIN_VALUE_FOR_RANGE);
509 float max = node->GetFloatAttribute(ui::AX_ATTR_MAX_VALUE_FOR_RANGE);
510 if (max <= min)
511 return false;
513 // To behave similarly to an Android SeekBar, move by an increment of
514 // approximately 20%.
515 float original_value = value;
516 float delta = (max - min) / 5.0f;
517 value += (increment ? delta : -delta);
518 value = std::max(std::min(value, max), min);
519 if (value != original_value) {
520 BrowserAccessibilityManager::SetValue(
521 *node, base::UTF8ToUTF16(base::DoubleToString(value)));
522 return true;
524 return false;
527 void BrowserAccessibilityManagerAndroid::HandleHoverEvent(
528 BrowserAccessibility* node) {
529 JNIEnv* env = AttachCurrentThread();
530 ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
531 if (obj.is_null())
532 return;
534 BrowserAccessibilityAndroid* ancestor =
535 static_cast<BrowserAccessibilityAndroid*>(node->GetParent());
536 while (ancestor && ancestor != GetRoot()) {
537 if (ancestor->PlatformIsLeaf() ||
538 (ancestor->IsFocusable() && !ancestor->HasFocusableChild())) {
539 node = ancestor;
540 // Don't break - we want the highest ancestor that's focusable or a
541 // leaf node.
543 ancestor = static_cast<BrowserAccessibilityAndroid*>(ancestor->GetParent());
546 Java_BrowserAccessibilityManager_handleHover(
547 env, obj.obj(), node->GetId());
550 jint BrowserAccessibilityManagerAndroid::FindElementType(
551 JNIEnv* env, jobject obj, jint start_id, jstring element_type_str,
552 jboolean forwards) {
553 BrowserAccessibility* node = GetFromID(start_id);
554 if (!node)
555 return 0;
557 AndroidHtmlElementType element_type = HtmlElementTypeFromString(
558 base::android::ConvertJavaStringToUTF16(env, element_type_str));
560 node = forwards ? NextInTreeOrder(node) : PreviousInTreeOrder(node);
561 while (node) {
562 switch(element_type) {
563 case HTML_ELEMENT_TYPE_SECTION:
564 if (node->GetRole() == ui::AX_ROLE_ARTICLE ||
565 node->GetRole() == ui::AX_ROLE_APPLICATION ||
566 node->GetRole() == ui::AX_ROLE_BANNER ||
567 node->GetRole() == ui::AX_ROLE_COMPLEMENTARY ||
568 node->GetRole() == ui::AX_ROLE_CONTENT_INFO ||
569 node->GetRole() == ui::AX_ROLE_HEADING ||
570 node->GetRole() == ui::AX_ROLE_MAIN ||
571 node->GetRole() == ui::AX_ROLE_NAVIGATION ||
572 node->GetRole() == ui::AX_ROLE_SEARCH ||
573 node->GetRole() == ui::AX_ROLE_REGION) {
574 return node->GetId();
576 break;
577 case HTML_ELEMENT_TYPE_LIST:
578 if (node->GetRole() == ui::AX_ROLE_LIST ||
579 node->GetRole() == ui::AX_ROLE_GRID ||
580 node->GetRole() == ui::AX_ROLE_TABLE ||
581 node->GetRole() == ui::AX_ROLE_TREE) {
582 return node->GetId();
584 break;
585 case HTML_ELEMENT_TYPE_CONTROL:
586 if (static_cast<BrowserAccessibilityAndroid*>(node)->IsFocusable())
587 return node->GetId();
588 break;
589 case HTML_ELEMENT_TYPE_ANY:
590 // In theory, the API says that an accessibility service could
591 // jump to an element by element name, like 'H1' or 'P'. This isn't
592 // currently used by any accessibility service, and we think it's
593 // better to keep them high-level like 'SECTION' or 'CONTROL', so we
594 // just fall back on linear navigation when we don't recognize the
595 // element type.
596 if (static_cast<BrowserAccessibilityAndroid*>(node)->IsClickable())
597 return node->GetId();
598 break;
601 node = forwards ? NextInTreeOrder(node) : PreviousInTreeOrder(node);
604 return 0;
607 jboolean BrowserAccessibilityManagerAndroid::NextAtGranularity(
608 JNIEnv* env, jobject obj, jint granularity, jboolean extend_selection,
609 jint id, jint cursor_index) {
610 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
611 GetFromID(id));
612 if (!node)
613 return false;
615 jint start_index = -1;
616 int end_index = -1;
617 if (NextAtGranularity(granularity, cursor_index, node,
618 &start_index, &end_index)) {
619 base::string16 text;
620 if (!node->IsPassword() ||
621 Java_BrowserAccessibilityManager_shouldExposePasswordText(env, obj)) {
622 text = node->GetText();
624 Java_BrowserAccessibilityManager_finishGranularityMove(
625 env, obj, base::android::ConvertUTF16ToJavaString(
626 env, text).obj(),
627 extend_selection, start_index, end_index, true);
628 return true;
630 return false;
633 jboolean BrowserAccessibilityManagerAndroid::PreviousAtGranularity(
634 JNIEnv* env, jobject obj, jint granularity, jboolean extend_selection,
635 jint id, jint cursor_index) {
636 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
637 GetFromID(id));
638 if (!node)
639 return false;
641 jint start_index = -1;
642 int end_index = -1;
643 if (PreviousAtGranularity(granularity, cursor_index, node,
644 &start_index, &end_index)) {
645 Java_BrowserAccessibilityManager_finishGranularityMove(
646 env, obj, base::android::ConvertUTF16ToJavaString(
647 env, node->GetText()).obj(),
648 extend_selection, start_index, end_index, false);
649 return true;
651 return false;
654 bool BrowserAccessibilityManagerAndroid::NextAtGranularity(
655 int32 granularity, int32 cursor_index,
656 BrowserAccessibilityAndroid* node, int32* start_index, int32* end_index) {
657 switch (granularity) {
658 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_CHARACTER: {
659 base::string16 text = node->GetText();
660 if (cursor_index >= static_cast<int32>(text.length()))
661 return false;
662 base::i18n::UTF16CharIterator iter(text.data(), text.size());
663 while (!iter.end() && iter.array_pos() <= cursor_index)
664 iter.Advance();
665 *start_index = iter.array_pos();
666 *end_index = iter.array_pos();
667 break;
669 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_WORD:
670 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_LINE: {
671 std::vector<int32> starts;
672 std::vector<int32> ends;
673 node->GetGranularityBoundaries(granularity, &starts, &ends, 0);
674 if (starts.size() == 0)
675 return false;
677 size_t index = 0;
678 while (index < starts.size() - 1 && starts[index] < cursor_index)
679 index++;
681 if (starts[index] < cursor_index)
682 return false;
684 *start_index = starts[index];
685 *end_index = ends[index];
686 break;
688 default:
689 NOTREACHED();
692 return true;
695 bool BrowserAccessibilityManagerAndroid::PreviousAtGranularity(
696 int32 granularity, int32 cursor_index,
697 BrowserAccessibilityAndroid* node, int32* start_index, int32* end_index) {
698 switch (granularity) {
699 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_CHARACTER: {
700 if (cursor_index <= 0)
701 return false;
702 base::string16 text = node->GetText();
703 base::i18n::UTF16CharIterator iter(text.data(), text.size());
704 int previous_index = 0;
705 while (!iter.end() && iter.array_pos() < cursor_index) {
706 previous_index = iter.array_pos();
707 iter.Advance();
709 *start_index = previous_index;
710 *end_index = previous_index;
711 break;
713 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_WORD:
714 case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_LINE: {
715 std::vector<int32> starts;
716 std::vector<int32> ends;
717 node->GetGranularityBoundaries(granularity, &starts, &ends, 0);
718 if (starts.size() == 0)
719 return false;
721 size_t index = starts.size() - 1;
722 while (index > 0 && starts[index] >= cursor_index)
723 index--;
725 if (starts[index] >= cursor_index)
726 return false;
728 *start_index = starts[index];
729 *end_index = ends[index];
730 break;
732 default:
733 NOTREACHED();
736 return true;
739 void BrowserAccessibilityManagerAndroid::SetAccessibilityFocus(
740 JNIEnv* env, jobject obj, jint id) {
741 if (delegate_)
742 delegate_->AccessibilitySetAccessibilityFocus(id);
745 bool BrowserAccessibilityManagerAndroid::IsSlider(
746 JNIEnv* env, jobject obj, jint id) {
747 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
748 GetFromID(id));
749 if (!node)
750 return false;
752 return node->GetRole() == ui::AX_ROLE_SLIDER;
755 bool BrowserAccessibilityManagerAndroid::Scroll(
756 JNIEnv* env, jobject obj, jint id, int direction) {
757 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
758 GetFromID(id));
759 if (!node)
760 return false;
762 return node->Scroll(direction);
765 void BrowserAccessibilityManagerAndroid::OnAtomicUpdateFinished(
766 ui::AXTree* tree,
767 bool root_changed,
768 const std::vector<ui::AXTreeDelegate::Change>& changes) {
769 if (root_changed) {
770 JNIEnv* env = AttachCurrentThread();
771 ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
772 if (obj.is_null())
773 return;
775 Java_BrowserAccessibilityManager_handleNavigate(env, obj.obj());
779 bool
780 BrowserAccessibilityManagerAndroid::UseRootScrollOffsetsWhenComputingBounds() {
781 // The Java layer handles the root scroll offset.
782 return false;
785 bool RegisterBrowserAccessibilityManager(JNIEnv* env) {
786 return RegisterNativesImpl(env);
789 } // namespace content