Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / content / browser / accessibility / browser_accessibility_manager_android.cc
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.
4
5 #include "content/browser/accessibility/browser_accessibility_manager_android.h"
6
7 #include <cmath>
8
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"
19
20 using base::android::AttachCurrentThread;
21 using base::android::ScopedJavaLocalRef;
22
23 namespace {
24
25 enum AndroidHtmlElementType {
26   HTML_ELEMENT_TYPE_SECTION,
27   HTML_ELEMENT_TYPE_LIST,
28   HTML_ELEMENT_TYPE_CONTROL,
29   HTML_ELEMENT_TYPE_ANY
30 };
31
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;
43 }
44
45 }  // anonymous namespace
46
47 namespace content {
48
49 namespace aria_strings {
50   const char kAriaLivePolite[] = "polite";
51   const char kAriaLiveAssertive[] = "assertive";
52 }
53
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);
61 }
62
63 BrowserAccessibilityManagerAndroid*
64 BrowserAccessibilityManager::ToBrowserAccessibilityManagerAndroid() {
65   return static_cast<BrowserAccessibilityManagerAndroid*>(this);
66 }
67
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);
76 }
77
78 BrowserAccessibilityManagerAndroid::~BrowserAccessibilityManagerAndroid() {
79   JNIEnv* env = AttachCurrentThread();
80   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
81   if (obj.is_null())
82     return;
83
84   Java_BrowserAccessibilityManager_onNativeObjectDestroyed(env, obj.obj());
85 }
86
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;
93
94   ui::AXTreeUpdate update;
95   update.nodes.push_back(empty_document);
96   return update;
97 }
98
99 void BrowserAccessibilityManagerAndroid::SetContentViewCore(
100     ScopedJavaLocalRef<jobject> content_view_core) {
101   if (content_view_core.is_null())
102     return;
103
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());
109 }
110
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;
118
119   BrowserAccessibilityAndroid* android_node =
120       static_cast<BrowserAccessibilityAndroid*>(node);
121
122   if (event_type == ui::AX_EVENT_HIDE)
123     return;
124
125   if (event_type == ui::AX_EVENT_TREE_CHANGED)
126     return;
127
128   if (event_type == ui::AX_EVENT_HOVER) {
129     HandleHoverEvent(node);
130     return;
131   }
132
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());
138
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;
171     }
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());
185       }
186       break;
187     default:
188       // There are some notifications that aren't meaningful on Android.
189       // It's okay to skip them.
190       break;
191   }
192 }
193
194 jint BrowserAccessibilityManagerAndroid::GetRootId(JNIEnv* env, jobject obj) {
195   return static_cast<jint>(GetRoot()->GetId());
196 }
197
198 jboolean BrowserAccessibilityManagerAndroid::IsNodeValid(
199     JNIEnv* env, jobject obj, jint id) {
200   return GetFromID(id) != NULL;
201 }
202
203 void BrowserAccessibilityManagerAndroid::HitTest(
204     JNIEnv* env, jobject obj, jint x, jint y) {
205   if (delegate())
206     delegate()->AccessibilityHitTest(gfx::Point(x, y));
207 }
208
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;
215
216   return node->IsEditableText();
217 }
218
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;
225
226   return node->GetIntAttribute(ui::AX_ATTR_TEXT_SEL_START);
227 }
228
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;
235
236   return node->GetIntAttribute(ui::AX_ATTR_TEXT_SEL_END);
237 }
238
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;
245
246   if (node->GetParent()) {
247     Java_BrowserAccessibilityManager_setAccessibilityNodeInfoParent(
248         env, obj, info, node->GetParent()->GetId());
249   }
250   for (unsigned i = 0; i < node->PlatformChildCount(); ++i) {
251     Java_BrowserAccessibilityManager_addAccessibilityNodeInfoChild(
252         env, obj, info, node->InternalGetChild(i)->GetId());
253   }
254   Java_BrowserAccessibilityManager_setAccessibilityNodeInfoBooleanAttributes(
255       env, obj, info,
256       id,
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());
279   }
280
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());
286   }
287   bool is_root = node->GetParent() == NULL;
288   Java_BrowserAccessibilityManager_setAccessibilityNodeInfoLocation(
289       env, obj, info,
290       id,
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);
295
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());
311   }
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());
320   }
321   if (node->IsRangeType()) {
322     Java_BrowserAccessibilityManager_setAccessibilityNodeInfoRangeInfo(
323         env, obj, info,
324         node->AndroidRangeType(),
325         node->RangeMin(),
326         node->RangeMax(),
327         node->RangeCurrentValue());
328   }
329
330   return true;
331 }
332
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;
339
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());
359
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();
367       }
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;
377     }
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();
383       }
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;
391     }
392     default:
393       break;
394   }
395
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());
411   }
412   if (node->IsHeading()) {
413     Java_BrowserAccessibilityManager_setAccessibilityEventHeadingFlag(
414         env, obj, event, true);
415   }
416   if (node->IsCollectionItem()) {
417     Java_BrowserAccessibilityManager_setAccessibilityEventCollectionItemInfo(
418         env, obj, event,
419         node->RowIndex(),
420         node->RowSpan(),
421         node->ColumnIndex(),
422         node->ColumnSpan());
423   }
424   if (node->IsRangeType()) {
425     Java_BrowserAccessibilityManager_setAccessibilityEventRangeInfo(
426         env, obj, event,
427         node->AndroidRangeType(),
428         node->RangeMin(),
429         node->RangeMax(),
430         node->RangeCurrentValue());
431   }
432
433   return true;
434 }
435
436 void BrowserAccessibilityManagerAndroid::Click(
437     JNIEnv* env, jobject obj, jint id) {
438   BrowserAccessibility* node = GetFromID(id);
439   if (node)
440     DoDefaultAction(*node);
441 }
442
443 void BrowserAccessibilityManagerAndroid::Focus(
444     JNIEnv* env, jobject obj, jint id) {
445   BrowserAccessibility* node = GetFromID(id);
446   if (node)
447     SetFocus(node, true);
448 }
449
450 void BrowserAccessibilityManagerAndroid::Blur(JNIEnv* env, jobject obj) {
451   SetFocus(GetRoot(), true);
452 }
453
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()));
459 }
460
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));
467   }
468 }
469
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);
475 }
476
477 jboolean BrowserAccessibilityManagerAndroid::AdjustSlider(
478     JNIEnv* env, jobject obj, jint id, jboolean increment) {
479   BrowserAccessibility* node = GetFromID(id);
480   if (!node)
481     return false;
482
483   BrowserAccessibilityAndroid* android_node =
484       static_cast<BrowserAccessibilityAndroid*>(node);
485
486   if (!android_node->IsSlider() || !android_node->IsEnabled())
487     return false;
488
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;
494
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;
505   }
506   return false;
507 }
508
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;
515
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.
524     }
525     ancestor = static_cast<BrowserAccessibilityAndroid*>(ancestor->GetParent());
526   }
527
528   Java_BrowserAccessibilityManager_handleHover(
529       env, obj.obj(), node->GetId());
530 }
531
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;
538
539   AndroidHtmlElementType element_type = HtmlElementTypeFromString(
540       base::android::ConvertJavaStringToUTF16(env, element_type_str));
541
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();
557         }
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();
565         }
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;
581     }
582
583     node = forwards ? NextInTreeOrder(node) : PreviousInTreeOrder(node);
584   }
585
586   return 0;
587 }
588
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;
596
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();
605     }
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;
611   }
612   return false;
613 }
614
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;
622
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;
632   }
633   return false;
634 }
635
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;
650     }
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;
658
659       size_t index = 0;
660       while (index < starts.size() - 1 && starts[index] < cursor_index)
661         index++;
662
663       if (starts[index] < cursor_index)
664         return false;
665
666       *start_index = starts[index];
667       *end_index = ends[index];
668       break;
669     }
670     default:
671       NOTREACHED();
672   }
673
674   return true;
675 }
676
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();
690       }
691       *start_index = previous_index;
692       *end_index = previous_index;
693       break;
694     }
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;
702
703       size_t index = starts.size() - 1;
704       while (index > 0 && starts[index] >= cursor_index)
705         index--;
706
707       if (starts[index] >= cursor_index)
708         return false;
709
710       *start_index = starts[index];
711       *end_index = ends[index];
712       break;
713     }
714     default:
715       NOTREACHED();
716   }
717
718   return true;
719 }
720
721 void BrowserAccessibilityManagerAndroid::SetAccessibilityFocus(
722     JNIEnv* env, jobject obj, jint id) {
723   if (delegate_)
724     delegate_->AccessibilitySetAccessibilityFocus(id);
725 }
726
727 void BrowserAccessibilityManagerAndroid::OnRootChanged(ui::AXNode* new_root) {
728   JNIEnv* env = AttachCurrentThread();
729   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
730   if (obj.is_null())
731     return;
732
733   Java_BrowserAccessibilityManager_handleNavigate(env, obj.obj());
734 }
735
736 bool
737 BrowserAccessibilityManagerAndroid::UseRootScrollOffsetsWhenComputingBounds() {
738   // The Java layer handles the root scroll offset.
739   return false;
740 }
741
742 bool RegisterBrowserAccessibilityManager(JNIEnv* env) {
743   return RegisterNativesImpl(env);
744 }
745
746 }  // namespace content