Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / content / browser / accessibility / browser_accessibility_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_android.h"
6
7 #include "base/i18n/break_iterator.h"
8 #include "base/strings/string_util.h"
9 #include "base/strings/stringprintf.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "content/browser/accessibility/browser_accessibility_manager_android.h"
12 #include "content/common/accessibility_messages.h"
13
14 namespace {
15
16 // These are enums from android.text.InputType in Java:
17 enum {
18   ANDROID_TEXT_INPUTTYPE_TYPE_NULL = 0,
19   ANDROID_TEXT_INPUTTYPE_TYPE_DATETIME = 0x4,
20   ANDROID_TEXT_INPUTTYPE_TYPE_DATETIME_DATE = 0x14,
21   ANDROID_TEXT_INPUTTYPE_TYPE_DATETIME_TIME = 0x24,
22   ANDROID_TEXT_INPUTTYPE_TYPE_NUMBER = 0x2,
23   ANDROID_TEXT_INPUTTYPE_TYPE_PHONE = 0x3,
24   ANDROID_TEXT_INPUTTYPE_TYPE_TEXT = 0x1,
25   ANDROID_TEXT_INPUTTYPE_TYPE_TEXT_URI = 0x11,
26   ANDROID_TEXT_INPUTTYPE_TYPE_TEXT_WEB_EDIT_TEXT = 0xa1,
27   ANDROID_TEXT_INPUTTYPE_TYPE_TEXT_WEB_EMAIL = 0xd1,
28   ANDROID_TEXT_INPUTTYPE_TYPE_TEXT_WEB_PASSWORD = 0xe1
29 };
30
31 // These are enums from android.view.View in Java:
32 enum {
33   ANDROID_VIEW_VIEW_ACCESSIBILITY_LIVE_REGION_NONE = 0,
34   ANDROID_VIEW_VIEW_ACCESSIBILITY_LIVE_REGION_POLITE = 1,
35   ANDROID_VIEW_VIEW_ACCESSIBILITY_LIVE_REGION_ASSERTIVE = 2
36 };
37
38 // These are enums from
39 // android.view.accessibility.AccessibilityNodeInfo.RangeInfo in Java:
40 enum {
41   ANDROID_VIEW_ACCESSIBILITY_RANGE_TYPE_FLOAT = 1
42 };
43
44 }  // namespace
45
46 namespace content {
47
48 // static
49 BrowserAccessibility* BrowserAccessibility::Create() {
50   return new BrowserAccessibilityAndroid();
51 }
52
53 BrowserAccessibilityAndroid::BrowserAccessibilityAndroid() {
54   first_time_ = true;
55 }
56
57 bool BrowserAccessibilityAndroid::IsNative() const {
58   return true;
59 }
60
61 void BrowserAccessibilityAndroid::OnLocationChanged() {
62   manager()->NotifyAccessibilityEvent(ui::AX_EVENT_LOCATION_CHANGED, this);
63 }
64
65 bool BrowserAccessibilityAndroid::PlatformIsLeaf() const {
66   if (InternalChildCount() == 0)
67     return true;
68
69   // Iframes are always allowed to contain children.
70   if (IsIframe() ||
71       GetRole() == ui::AX_ROLE_ROOT_WEB_AREA ||
72       GetRole() == ui::AX_ROLE_WEB_AREA) {
73     return false;
74   }
75
76   // If it has a focusable child, we definitely can't leave out children.
77   if (HasFocusableChild())
78     return false;
79
80   // Date and time controls should drop their children.
81   if (GetRole() == ui::AX_ROLE_DATE || GetRole() == ui::AX_ROLE_TIME)
82     return true;
83
84   // Headings with text can drop their children.
85   base::string16 name = GetText();
86   if (GetRole() == ui::AX_ROLE_HEADING && !name.empty())
87     return true;
88
89   // Focusable nodes with text can drop their children.
90   if (HasState(ui::AX_STATE_FOCUSABLE) && !name.empty())
91     return true;
92
93   // Nodes with only static text as children can drop their children.
94   if (HasOnlyStaticTextChildren())
95     return true;
96
97   return BrowserAccessibility::PlatformIsLeaf();
98 }
99
100 bool BrowserAccessibilityAndroid::CanScrollForward() const {
101   if (!IsSlider())
102     return false;
103
104   float value = GetFloatAttribute(ui::AX_ATTR_VALUE_FOR_RANGE);
105   float max = GetFloatAttribute(ui::AX_ATTR_MAX_VALUE_FOR_RANGE);
106   return value < max;
107 }
108
109 bool BrowserAccessibilityAndroid::CanScrollBackward() const {
110   if (!IsSlider())
111     return false;
112
113   float value = GetFloatAttribute(ui::AX_ATTR_VALUE_FOR_RANGE);
114   float min = GetFloatAttribute(ui::AX_ATTR_MIN_VALUE_FOR_RANGE);
115   return value > min;
116 }
117
118 bool BrowserAccessibilityAndroid::IsCheckable() const {
119   bool checkable = false;
120   bool is_aria_pressed_defined;
121   bool is_mixed;
122   GetAriaTristate("aria-pressed", &is_aria_pressed_defined, &is_mixed);
123   if (GetRole() == ui::AX_ROLE_CHECK_BOX ||
124       GetRole() == ui::AX_ROLE_RADIO_BUTTON ||
125       GetRole() == ui::AX_ROLE_MENU_ITEM_CHECK_BOX ||
126       GetRole() == ui::AX_ROLE_MENU_ITEM_RADIO ||
127       is_aria_pressed_defined) {
128     checkable = true;
129   }
130   if (HasState(ui::AX_STATE_CHECKED))
131     checkable = true;
132   return checkable;
133 }
134
135 bool BrowserAccessibilityAndroid::IsChecked() const {
136   return HasState(ui::AX_STATE_CHECKED);
137 }
138
139 bool BrowserAccessibilityAndroid::IsClickable() const {
140   return (PlatformIsLeaf() && !GetText().empty());
141 }
142
143 bool BrowserAccessibilityAndroid::IsCollection() const {
144   return (GetRole() == ui::AX_ROLE_GRID ||
145           GetRole() == ui::AX_ROLE_LIST ||
146           GetRole() == ui::AX_ROLE_LIST_BOX ||
147           GetRole() == ui::AX_ROLE_DESCRIPTION_LIST ||
148           GetRole() == ui::AX_ROLE_TABLE ||
149           GetRole() == ui::AX_ROLE_TREE);
150 }
151
152 bool BrowserAccessibilityAndroid::IsCollectionItem() const {
153   return (GetRole() == ui::AX_ROLE_CELL ||
154           GetRole() == ui::AX_ROLE_COLUMN_HEADER ||
155           GetRole() == ui::AX_ROLE_DESCRIPTION_LIST_TERM ||
156           GetRole() == ui::AX_ROLE_LIST_BOX_OPTION ||
157           GetRole() == ui::AX_ROLE_LIST_ITEM ||
158           GetRole() == ui::AX_ROLE_ROW_HEADER ||
159           GetRole() == ui::AX_ROLE_TREE_ITEM);
160 }
161
162 bool BrowserAccessibilityAndroid::IsContentInvalid() const {
163   std::string invalid;
164   return GetHtmlAttribute("aria-invalid", &invalid);
165 }
166
167 bool BrowserAccessibilityAndroid::IsDismissable() const {
168   return false;  // No concept of "dismissable" on the web currently.
169 }
170
171 bool BrowserAccessibilityAndroid::IsEditableText() const {
172   return (GetRole() == ui::AX_ROLE_EDITABLE_TEXT ||
173           GetRole() == ui::AX_ROLE_TEXT_AREA ||
174           GetRole() == ui::AX_ROLE_TEXT_FIELD);
175 }
176
177 bool BrowserAccessibilityAndroid::IsEnabled() const {
178   return HasState(ui::AX_STATE_ENABLED);
179 }
180
181 bool BrowserAccessibilityAndroid::IsFocusable() const {
182   bool focusable = HasState(ui::AX_STATE_FOCUSABLE);
183   if (IsIframe() ||
184       GetRole() == ui::AX_ROLE_WEB_AREA) {
185     focusable = false;
186   }
187   return focusable;
188 }
189
190 bool BrowserAccessibilityAndroid::IsFocused() const {
191   return manager()->GetFocus(manager()->GetRoot()) == this;
192 }
193
194 bool BrowserAccessibilityAndroid::IsHeading() const {
195   return (GetRole() == ui::AX_ROLE_COLUMN_HEADER ||
196           GetRole() == ui::AX_ROLE_HEADING ||
197           GetRole() == ui::AX_ROLE_ROW_HEADER);
198 }
199
200 bool BrowserAccessibilityAndroid::IsHierarchical() const {
201   return (GetRole() == ui::AX_ROLE_LIST ||
202           GetRole() == ui::AX_ROLE_DESCRIPTION_LIST ||
203           GetRole() == ui::AX_ROLE_TREE);
204 }
205
206 bool BrowserAccessibilityAndroid::IsLink() const {
207   return GetRole() == ui::AX_ROLE_LINK ||
208          GetRole() == ui::AX_ROLE_IMAGE_MAP_LINK;
209 }
210
211 bool BrowserAccessibilityAndroid::IsMultiLine() const {
212   return GetRole() == ui::AX_ROLE_TEXT_AREA;
213 }
214
215 bool BrowserAccessibilityAndroid::IsPassword() const {
216   return HasState(ui::AX_STATE_PROTECTED);
217 }
218
219 bool BrowserAccessibilityAndroid::IsRangeType() const {
220   return (GetRole() == ui::AX_ROLE_PROGRESS_INDICATOR ||
221           GetRole() == ui::AX_ROLE_SCROLL_BAR ||
222           GetRole() == ui::AX_ROLE_SLIDER);
223 }
224
225 bool BrowserAccessibilityAndroid::IsScrollable() const {
226   int dummy;
227   return GetIntAttribute(ui::AX_ATTR_SCROLL_X_MAX, &dummy);
228 }
229
230 bool BrowserAccessibilityAndroid::IsSelected() const {
231   return HasState(ui::AX_STATE_SELECTED);
232 }
233
234 bool BrowserAccessibilityAndroid::IsSlider() const {
235   return GetRole() == ui::AX_ROLE_SLIDER;
236 }
237
238 bool BrowserAccessibilityAndroid::IsVisibleToUser() const {
239   return !HasState(ui::AX_STATE_INVISIBLE);
240 }
241
242 bool BrowserAccessibilityAndroid::CanOpenPopup() const {
243   return HasState(ui::AX_STATE_HASPOPUP);
244 }
245
246 const char* BrowserAccessibilityAndroid::GetClassName() const {
247   const char* class_name = NULL;
248
249   switch(GetRole()) {
250     case ui::AX_ROLE_EDITABLE_TEXT:
251     case ui::AX_ROLE_SPIN_BUTTON:
252     case ui::AX_ROLE_TEXT_AREA:
253     case ui::AX_ROLE_TEXT_FIELD:
254       class_name = "android.widget.EditText";
255       break;
256     case ui::AX_ROLE_SLIDER:
257       class_name = "android.widget.SeekBar";
258       break;
259     case ui::AX_ROLE_COLOR_WELL:
260     case ui::AX_ROLE_COMBO_BOX:
261     case ui::AX_ROLE_DATE:
262     case ui::AX_ROLE_POP_UP_BUTTON:
263     case ui::AX_ROLE_TIME:
264       class_name = "android.widget.Spinner";
265       break;
266     case ui::AX_ROLE_BUTTON:
267     case ui::AX_ROLE_MENU_BUTTON:
268       class_name = "android.widget.Button";
269       break;
270     case ui::AX_ROLE_CHECK_BOX:
271       class_name = "android.widget.CheckBox";
272       break;
273     case ui::AX_ROLE_RADIO_BUTTON:
274       class_name = "android.widget.RadioButton";
275       break;
276     case ui::AX_ROLE_TOGGLE_BUTTON:
277       class_name = "android.widget.ToggleButton";
278       break;
279     case ui::AX_ROLE_CANVAS:
280     case ui::AX_ROLE_IMAGE:
281     case ui::AX_ROLE_SVG_ROOT:
282       class_name = "android.widget.Image";
283       break;
284     case ui::AX_ROLE_METER:
285     case ui::AX_ROLE_PROGRESS_INDICATOR:
286       class_name = "android.widget.ProgressBar";
287       break;
288     case ui::AX_ROLE_TAB_LIST:
289       class_name = "android.widget.TabWidget";
290       break;
291     case ui::AX_ROLE_GRID:
292     case ui::AX_ROLE_TABLE:
293       class_name = "android.widget.GridView";
294       break;
295     case ui::AX_ROLE_LIST:
296     case ui::AX_ROLE_LIST_BOX:
297     case ui::AX_ROLE_DESCRIPTION_LIST:
298       class_name = "android.widget.ListView";
299       break;
300     case ui::AX_ROLE_DIALOG:
301       class_name = "android.app.Dialog";
302       break;
303     case ui::AX_ROLE_ROOT_WEB_AREA:
304       class_name = "android.webkit.WebView";
305       break;
306     case ui::AX_ROLE_MENU_ITEM:
307     case ui::AX_ROLE_MENU_ITEM_CHECK_BOX:
308     case ui::AX_ROLE_MENU_ITEM_RADIO:
309       class_name = "android.view.MenuItem";
310       break;
311     default:
312       class_name = "android.view.View";
313       break;
314   }
315
316   return class_name;
317 }
318
319 base::string16 BrowserAccessibilityAndroid::GetText() const {
320   if (IsIframe() ||
321       GetRole() == ui::AX_ROLE_WEB_AREA) {
322     return base::string16();
323   }
324
325   // See comment in browser_accessibility_win.cc for details.
326   // The difference here is that we can only expose one accessible
327   // name on Android, not 2 or 3 like on Windows or Mac.
328
329   // First, always return the |value| attribute if this is an
330   // input field.
331   if (!value().empty()) {
332     if (HasState(ui::AX_STATE_EDITABLE))
333       return base::UTF8ToUTF16(value());
334
335     switch (GetRole()) {
336       case ui::AX_ROLE_COMBO_BOX:
337       case ui::AX_ROLE_EDITABLE_TEXT:
338       case ui::AX_ROLE_POP_UP_BUTTON:
339       case ui::AX_ROLE_TEXT_AREA:
340       case ui::AX_ROLE_TEXT_FIELD:
341         return base::UTF8ToUTF16(value());
342     }
343   }
344
345   // For color wells, the color is stored in separate attributes.
346   // Perhaps we could return color names in the future?
347   if (GetRole() == ui::AX_ROLE_COLOR_WELL) {
348     int red = GetIntAttribute(ui::AX_ATTR_COLOR_VALUE_RED);
349     int green = GetIntAttribute(ui::AX_ATTR_COLOR_VALUE_GREEN);
350     int blue = GetIntAttribute(ui::AX_ATTR_COLOR_VALUE_BLUE);
351     return base::UTF8ToUTF16(
352         base::StringPrintf("#%02X%02X%02X", red, green, blue));
353   }
354
355   // Always prefer visible text if this is a link. Sites sometimes add
356   // a "title" attribute to a link with more information, but we can't
357   // lose the link text.
358   if (!name().empty() && GetRole() == ui::AX_ROLE_LINK)
359     return base::UTF8ToUTF16(name());
360
361   // If there's no text value, the basic rule is: prefer description
362   // (aria-labelledby or aria-label), then help (title), then name
363   // (inner text), then value (control value).  However, if
364   // title_elem_id is set, that means there's a label element
365   // supplying the name and then name takes precedence over help.
366   // TODO(dmazzoni): clean this up by providing more granular labels in
367   // Blink, making the platform-specific mapping to accessible text simpler.
368   base::string16 description = GetString16Attribute(ui::AX_ATTR_DESCRIPTION);
369   base::string16 help = GetString16Attribute(ui::AX_ATTR_HELP);
370
371   base::string16 placeholder;
372   switch (GetRole()) {
373     case ui::AX_ROLE_DATE:
374     case ui::AX_ROLE_EDITABLE_TEXT:
375     case ui::AX_ROLE_TEXT_AREA:
376     case ui::AX_ROLE_TEXT_FIELD:
377     case ui::AX_ROLE_TIME:
378       GetHtmlAttribute("placeholder", &placeholder);
379   }
380
381   int title_elem_id = GetIntAttribute(
382       ui::AX_ATTR_TITLE_UI_ELEMENT);
383   base::string16 text;
384   if (!description.empty())
385     text = description;
386   else if (title_elem_id && !name().empty())
387     text = base::UTF8ToUTF16(name());
388   else if (!help.empty())
389     text = help;
390   else if (!name().empty())
391     text = base::UTF8ToUTF16(name());
392   else if (!placeholder.empty())
393     text = placeholder;
394   else if (!value().empty())
395     text = base::UTF8ToUTF16(value());
396
397   // This is called from PlatformIsLeaf, so don't call PlatformChildCount
398   // from within this!
399   if (text.empty() &&
400       (HasOnlyStaticTextChildren() ||
401        (IsFocusable() && HasOnlyTextAndImageChildren()))) {
402     for (uint32 i = 0; i < InternalChildCount(); i++) {
403       BrowserAccessibility* child = InternalGetChild(i);
404       text += static_cast<BrowserAccessibilityAndroid*>(child)->GetText();
405     }
406   }
407
408   if (text.empty() && (IsLink() || GetRole() == ui::AX_ROLE_IMAGE)) {
409     base::string16 url = GetString16Attribute(ui::AX_ATTR_URL);
410     // Given a url like http://foo.com/bar/baz.png, just return the
411     // base name, e.g., "baz".
412     int trailing_slashes = 0;
413     while (url.size() - trailing_slashes > 0 &&
414            url[url.size() - trailing_slashes - 1] == '/') {
415       trailing_slashes++;
416     }
417     if (trailing_slashes)
418       url = url.substr(0, url.size() - trailing_slashes);
419     size_t slash_index = url.rfind('/');
420     if (slash_index != std::string::npos)
421       url = url.substr(slash_index + 1);
422     size_t dot_index = url.rfind('.');
423     if (dot_index != std::string::npos)
424       url = url.substr(0, dot_index);
425     text = url;
426   }
427
428   return text;
429 }
430
431 int BrowserAccessibilityAndroid::GetItemIndex() const {
432   int index = 0;
433   switch(GetRole()) {
434     case ui::AX_ROLE_LIST_ITEM:
435     case ui::AX_ROLE_LIST_BOX_OPTION:
436     case ui::AX_ROLE_TREE_ITEM:
437       index = GetIndexInParent();
438       break;
439     case ui::AX_ROLE_SLIDER:
440     case ui::AX_ROLE_PROGRESS_INDICATOR: {
441       // Return a percentage here for live feedback in an AccessibilityEvent.
442       // The exact value is returned in RangeCurrentValue.
443       float min = GetFloatAttribute(ui::AX_ATTR_MIN_VALUE_FOR_RANGE);
444       float max = GetFloatAttribute(ui::AX_ATTR_MAX_VALUE_FOR_RANGE);
445       float value = GetFloatAttribute(ui::AX_ATTR_VALUE_FOR_RANGE);
446       if (max > min && value >= min && value <= max)
447         index = static_cast<int>(((value - min)) * 100 / (max - min));
448       break;
449     }
450   }
451   return index;
452 }
453
454 int BrowserAccessibilityAndroid::GetItemCount() const {
455   int count = 0;
456   switch(GetRole()) {
457     case ui::AX_ROLE_LIST:
458     case ui::AX_ROLE_LIST_BOX:
459     case ui::AX_ROLE_DESCRIPTION_LIST:
460       count = PlatformChildCount();
461       break;
462     case ui::AX_ROLE_SLIDER:
463     case ui::AX_ROLE_PROGRESS_INDICATOR:
464       // An AccessibilityEvent can only return integer information about a
465       // seek control, so we return a percentage. The real range is returned
466       // in RangeMin and RangeMax.
467       count = 100;
468       break;
469   }
470   return count;
471 }
472
473 int BrowserAccessibilityAndroid::GetScrollX() const {
474   int value = 0;
475   GetIntAttribute(ui::AX_ATTR_SCROLL_X, &value);
476   return value;
477 }
478
479 int BrowserAccessibilityAndroid::GetScrollY() const {
480   int value = 0;
481   GetIntAttribute(ui::AX_ATTR_SCROLL_Y, &value);
482   return value;
483 }
484
485 int BrowserAccessibilityAndroid::GetMaxScrollX() const {
486   int value = 0;
487   GetIntAttribute(ui::AX_ATTR_SCROLL_X_MAX, &value);
488   return value;
489 }
490
491 int BrowserAccessibilityAndroid::GetMaxScrollY() const {
492   int value = 0;
493   GetIntAttribute(ui::AX_ATTR_SCROLL_Y_MAX, &value);
494   return value;
495 }
496
497 int BrowserAccessibilityAndroid::GetTextChangeFromIndex() const {
498   size_t index = 0;
499   while (index < old_value_.length() &&
500          index < new_value_.length() &&
501          old_value_[index] == new_value_[index]) {
502     index++;
503   }
504   return index;
505 }
506
507 int BrowserAccessibilityAndroid::GetTextChangeAddedCount() const {
508   size_t old_len = old_value_.length();
509   size_t new_len = new_value_.length();
510   size_t left = 0;
511   while (left < old_len &&
512          left < new_len &&
513          old_value_[left] == new_value_[left]) {
514     left++;
515   }
516   size_t right = 0;
517   while (right < old_len &&
518          right < new_len &&
519          old_value_[old_len - right - 1] == new_value_[new_len - right - 1]) {
520     right++;
521   }
522   return (new_len - left - right);
523 }
524
525 int BrowserAccessibilityAndroid::GetTextChangeRemovedCount() const {
526   size_t old_len = old_value_.length();
527   size_t new_len = new_value_.length();
528   size_t left = 0;
529   while (left < old_len &&
530          left < new_len &&
531          old_value_[left] == new_value_[left]) {
532     left++;
533   }
534   size_t right = 0;
535   while (right < old_len &&
536          right < new_len &&
537          old_value_[old_len - right - 1] == new_value_[new_len - right - 1]) {
538     right++;
539   }
540   return (old_len - left - right);
541 }
542
543 base::string16 BrowserAccessibilityAndroid::GetTextChangeBeforeText() const {
544   return old_value_;
545 }
546
547 int BrowserAccessibilityAndroid::GetSelectionStart() const {
548   int sel_start = 0;
549   GetIntAttribute(ui::AX_ATTR_TEXT_SEL_START, &sel_start);
550   return sel_start;
551 }
552
553 int BrowserAccessibilityAndroid::GetSelectionEnd() const {
554   int sel_end = 0;
555   GetIntAttribute(ui::AX_ATTR_TEXT_SEL_END, &sel_end);
556   return sel_end;
557 }
558
559 int BrowserAccessibilityAndroid::GetEditableTextLength() const {
560   return value().length();
561 }
562
563 int BrowserAccessibilityAndroid::AndroidInputType() const {
564   std::string html_tag = GetStringAttribute(
565       ui::AX_ATTR_HTML_TAG);
566   if (html_tag != "input")
567     return ANDROID_TEXT_INPUTTYPE_TYPE_NULL;
568
569   std::string type;
570   if (!GetHtmlAttribute("type", &type))
571     return ANDROID_TEXT_INPUTTYPE_TYPE_TEXT;
572
573   if (type == "" || type == "text" || type == "search")
574     return ANDROID_TEXT_INPUTTYPE_TYPE_TEXT;
575   else if (type == "date")
576     return ANDROID_TEXT_INPUTTYPE_TYPE_DATETIME_DATE;
577   else if (type == "datetime" || type == "datetime-local")
578     return ANDROID_TEXT_INPUTTYPE_TYPE_DATETIME;
579   else if (type == "email")
580     return ANDROID_TEXT_INPUTTYPE_TYPE_TEXT_WEB_EMAIL;
581   else if (type == "month")
582     return ANDROID_TEXT_INPUTTYPE_TYPE_DATETIME_DATE;
583   else if (type == "number")
584     return ANDROID_TEXT_INPUTTYPE_TYPE_NUMBER;
585   else if (type == "password")
586     return ANDROID_TEXT_INPUTTYPE_TYPE_TEXT_WEB_PASSWORD;
587   else if (type == "tel")
588     return ANDROID_TEXT_INPUTTYPE_TYPE_PHONE;
589   else if (type == "time")
590     return ANDROID_TEXT_INPUTTYPE_TYPE_DATETIME_TIME;
591   else if (type == "url")
592     return ANDROID_TEXT_INPUTTYPE_TYPE_TEXT_URI;
593   else if (type == "week")
594     return ANDROID_TEXT_INPUTTYPE_TYPE_DATETIME;
595
596   return ANDROID_TEXT_INPUTTYPE_TYPE_NULL;
597 }
598
599 int BrowserAccessibilityAndroid::AndroidLiveRegionType() const {
600   std::string live = GetStringAttribute(
601       ui::AX_ATTR_LIVE_STATUS);
602   if (live == "polite")
603     return ANDROID_VIEW_VIEW_ACCESSIBILITY_LIVE_REGION_POLITE;
604   else if (live == "assertive")
605     return ANDROID_VIEW_VIEW_ACCESSIBILITY_LIVE_REGION_ASSERTIVE;
606   return ANDROID_VIEW_VIEW_ACCESSIBILITY_LIVE_REGION_NONE;
607 }
608
609 int BrowserAccessibilityAndroid::AndroidRangeType() const {
610   return ANDROID_VIEW_ACCESSIBILITY_RANGE_TYPE_FLOAT;
611 }
612
613 int BrowserAccessibilityAndroid::RowCount() const {
614   if (GetRole() == ui::AX_ROLE_GRID ||
615       GetRole() == ui::AX_ROLE_TABLE) {
616     return CountChildrenWithRole(ui::AX_ROLE_ROW);
617   }
618
619   if (GetRole() == ui::AX_ROLE_LIST ||
620       GetRole() == ui::AX_ROLE_LIST_BOX ||
621       GetRole() == ui::AX_ROLE_DESCRIPTION_LIST ||
622       GetRole() == ui::AX_ROLE_TREE) {
623     return PlatformChildCount();
624   }
625
626   return 0;
627 }
628
629 int BrowserAccessibilityAndroid::ColumnCount() const {
630   if (GetRole() == ui::AX_ROLE_GRID ||
631       GetRole() == ui::AX_ROLE_TABLE) {
632     return CountChildrenWithRole(ui::AX_ROLE_COLUMN);
633   }
634   return 0;
635 }
636
637 int BrowserAccessibilityAndroid::RowIndex() const {
638   if (GetRole() == ui::AX_ROLE_LIST_ITEM ||
639       GetRole() == ui::AX_ROLE_LIST_BOX_OPTION ||
640       GetRole() == ui::AX_ROLE_TREE_ITEM) {
641     return GetIndexInParent();
642   }
643
644   return GetIntAttribute(ui::AX_ATTR_TABLE_CELL_ROW_INDEX);
645 }
646
647 int BrowserAccessibilityAndroid::RowSpan() const {
648   return GetIntAttribute(ui::AX_ATTR_TABLE_CELL_ROW_SPAN);
649 }
650
651 int BrowserAccessibilityAndroid::ColumnIndex() const {
652   return GetIntAttribute(ui::AX_ATTR_TABLE_CELL_COLUMN_INDEX);
653 }
654
655 int BrowserAccessibilityAndroid::ColumnSpan() const {
656   return GetIntAttribute(ui::AX_ATTR_TABLE_CELL_COLUMN_SPAN);
657 }
658
659 float BrowserAccessibilityAndroid::RangeMin() const {
660   return GetFloatAttribute(ui::AX_ATTR_MIN_VALUE_FOR_RANGE);
661 }
662
663 float BrowserAccessibilityAndroid::RangeMax() const {
664   return GetFloatAttribute(ui::AX_ATTR_MAX_VALUE_FOR_RANGE);
665 }
666
667 float BrowserAccessibilityAndroid::RangeCurrentValue() const {
668   return GetFloatAttribute(ui::AX_ATTR_VALUE_FOR_RANGE);
669 }
670
671 void BrowserAccessibilityAndroid::GetGranularityBoundaries(
672     int granularity,
673     std::vector<int32>* starts,
674     std::vector<int32>* ends,
675     int offset) {
676   switch (granularity) {
677     case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_LINE:
678       GetLineBoundaries(starts, ends, offset);
679       break;
680     case ANDROID_ACCESSIBILITY_NODE_INFO_MOVEMENT_GRANULARITY_WORD:
681       GetWordBoundaries(starts, ends, offset);
682       break;
683     default:
684       NOTREACHED();
685   }
686 }
687
688 void BrowserAccessibilityAndroid::GetLineBoundaries(
689     std::vector<int32>* line_starts,
690     std::vector<int32>* line_ends,
691     int offset) {
692   // If this node has no children, treat it as all one line.
693   if (GetText().size() > 0 && !InternalChildCount()) {
694     line_starts->push_back(offset);
695     line_ends->push_back(offset + GetText().size());
696   }
697
698   // If this is a static text node, get the line boundaries from the
699   // inline text boxes if possible.
700   if (GetRole() == ui::AX_ROLE_STATIC_TEXT) {
701     int last_y = 0;
702     for (uint32 i = 0; i < InternalChildCount(); i++) {
703       BrowserAccessibilityAndroid* child =
704           static_cast<BrowserAccessibilityAndroid*>(InternalGetChild(i));
705       CHECK_EQ(ui::AX_ROLE_INLINE_TEXT_BOX, child->GetRole());
706       // TODO(dmazzoni): replace this with a proper API to determine
707       // if two inline text boxes are on the same line. http://crbug.com/421771
708       int y = child->GetLocation().y();
709       if (i == 0) {
710         line_starts->push_back(offset);
711       } else if (y != last_y) {
712         line_ends->push_back(offset);
713         line_starts->push_back(offset);
714       }
715       offset += child->GetText().size();
716       last_y = y;
717     }
718     line_ends->push_back(offset);
719     return;
720   }
721
722   // Otherwise, call GetLineBoundaries recursively on the children.
723   for (uint32 i = 0; i < InternalChildCount(); i++) {
724     BrowserAccessibilityAndroid* child =
725         static_cast<BrowserAccessibilityAndroid*>(InternalGetChild(i));
726     child->GetLineBoundaries(line_starts, line_ends, offset);
727     offset += child->GetText().size();
728   }
729 }
730
731 void BrowserAccessibilityAndroid::GetWordBoundaries(
732     std::vector<int32>* word_starts,
733     std::vector<int32>* word_ends,
734     int offset) {
735   if (GetRole() == ui::AX_ROLE_INLINE_TEXT_BOX) {
736     const std::vector<int32>& starts = GetIntListAttribute(
737         ui::AX_ATTR_WORD_STARTS);
738     const std::vector<int32>& ends = GetIntListAttribute(
739         ui::AX_ATTR_WORD_ENDS);
740     for (size_t i = 0; i < starts.size(); ++i) {
741       word_starts->push_back(offset + starts[i]);
742       word_ends->push_back(offset + ends[i]);
743     }
744     return;
745   }
746
747   base::string16 concatenated_text;
748   for (uint32 i = 0; i < InternalChildCount(); i++) {
749     BrowserAccessibilityAndroid* child =
750         static_cast<BrowserAccessibilityAndroid*>(InternalGetChild(i));
751     base::string16 child_text = child->GetText();
752     concatenated_text += child->GetText();
753   }
754
755   base::string16 text = GetText();
756   if (text.empty() || concatenated_text == text) {
757     // Great - this node is just the concatenation of its children, so
758     // we can get the word boundaries recursively.
759     for (uint32 i = 0; i < InternalChildCount(); i++) {
760       BrowserAccessibilityAndroid* child =
761           static_cast<BrowserAccessibilityAndroid*>(InternalGetChild(i));
762       child->GetWordBoundaries(word_starts, word_ends, offset);
763       offset += child->GetText().size();
764     }
765   } else {
766     // This node has its own accessible text that doesn't match its
767     // visible text - like alt text for an image or something with an
768     // aria-label, so split the text into words locally.
769     base::i18n::BreakIterator iter(text, base::i18n::BreakIterator::BREAK_WORD);
770     if (!iter.Init())
771       return;
772     while (iter.Advance()) {
773       if (iter.IsWord()) {
774         word_starts->push_back(iter.prev());
775         word_ends->push_back(iter.pos());
776       }
777     }
778   }
779 }
780
781 bool BrowserAccessibilityAndroid::HasFocusableChild() const {
782   // This is called from PlatformIsLeaf, so don't call PlatformChildCount
783   // from within this!
784   for (uint32 i = 0; i < InternalChildCount(); i++) {
785     BrowserAccessibility* child = InternalGetChild(i);
786     if (child->HasState(ui::AX_STATE_FOCUSABLE))
787       return true;
788     if (static_cast<BrowserAccessibilityAndroid*>(child)->HasFocusableChild())
789       return true;
790   }
791   return false;
792 }
793
794 bool BrowserAccessibilityAndroid::HasOnlyStaticTextChildren() const {
795   // This is called from PlatformIsLeaf, so don't call PlatformChildCount
796   // from within this!
797   for (uint32 i = 0; i < InternalChildCount(); i++) {
798     BrowserAccessibility* child = InternalGetChild(i);
799     if (child->GetRole() != ui::AX_ROLE_STATIC_TEXT)
800       return false;
801   }
802   return true;
803 }
804
805 bool BrowserAccessibilityAndroid::HasOnlyTextAndImageChildren() const {
806   // This is called from PlatformIsLeaf, so don't call PlatformChildCount
807   // from within this!
808   for (uint32 i = 0; i < InternalChildCount(); i++) {
809     BrowserAccessibility* child = InternalGetChild(i);
810     if (child->GetRole() != ui::AX_ROLE_STATIC_TEXT &&
811         child->GetRole() != ui::AX_ROLE_IMAGE) {
812       return false;
813     }
814   }
815   return true;
816 }
817
818 bool BrowserAccessibilityAndroid::IsIframe() const {
819   base::string16 html_tag = GetString16Attribute(
820       ui::AX_ATTR_HTML_TAG);
821   return html_tag == base::ASCIIToUTF16("iframe");
822 }
823
824 void BrowserAccessibilityAndroid::OnDataChanged() {
825   BrowserAccessibility::OnDataChanged();
826
827   if (IsEditableText()) {
828     if (base::UTF8ToUTF16(value()) != new_value_) {
829       old_value_ = new_value_;
830       new_value_ = base::UTF8ToUTF16(value());
831     }
832   }
833
834   if (GetRole() == ui::AX_ROLE_ALERT && first_time_)
835     manager()->NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, this);
836
837   base::string16 live;
838   if (GetString16Attribute(
839       ui::AX_ATTR_CONTAINER_LIVE_STATUS, &live)) {
840     NotifyLiveRegionUpdate(live);
841   }
842
843   first_time_ = false;
844 }
845
846 void BrowserAccessibilityAndroid::NotifyLiveRegionUpdate(
847     base::string16& aria_live) {
848   if (!EqualsASCII(aria_live, aria_strings::kAriaLivePolite) &&
849       !EqualsASCII(aria_live, aria_strings::kAriaLiveAssertive))
850     return;
851
852   base::string16 text = GetText();
853   if (cached_text_ != text) {
854     if (!text.empty()) {
855       manager()->NotifyAccessibilityEvent(ui::AX_EVENT_SHOW,
856                                          this);
857     }
858     cached_text_ = text;
859   }
860 }
861
862 int BrowserAccessibilityAndroid::CountChildrenWithRole(ui::AXRole role) const {
863   int count = 0;
864   for (uint32 i = 0; i < PlatformChildCount(); i++) {
865     if (PlatformGetChild(i)->GetRole() == role)
866       count++;
867   }
868   return count;
869 }
870
871 }  // namespace content