• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/strings/utf_string_conversions.h"
8 #include "content/browser/accessibility/browser_accessibility_manager_android.h"
9 #include "content/common/accessibility_messages.h"
10 
11 namespace {
12 
13 // These are enums from android.text.InputType in Java:
14 enum {
15   ANDROID_TEXT_INPUTTYPE_TYPE_NULL = 0,
16   ANDROID_TEXT_INPUTTYPE_TYPE_DATETIME = 0x4,
17   ANDROID_TEXT_INPUTTYPE_TYPE_DATETIME_DATE = 0x14,
18   ANDROID_TEXT_INPUTTYPE_TYPE_DATETIME_TIME = 0x24,
19   ANDROID_TEXT_INPUTTYPE_TYPE_NUMBER = 0x2,
20   ANDROID_TEXT_INPUTTYPE_TYPE_PHONE = 0x3,
21   ANDROID_TEXT_INPUTTYPE_TYPE_TEXT = 0x1,
22   ANDROID_TEXT_INPUTTYPE_TYPE_TEXT_URI = 0x11,
23   ANDROID_TEXT_INPUTTYPE_TYPE_TEXT_WEB_EDIT_TEXT = 0xa1,
24   ANDROID_TEXT_INPUTTYPE_TYPE_TEXT_WEB_EMAIL = 0xd1,
25   ANDROID_TEXT_INPUTTYPE_TYPE_TEXT_WEB_PASSWORD = 0xe1
26 };
27 
28 // These are enums from android.view.View in Java:
29 enum {
30   ANDROID_VIEW_VIEW_ACCESSIBILITY_LIVE_REGION_NONE = 0,
31   ANDROID_VIEW_VIEW_ACCESSIBILITY_LIVE_REGION_POLITE = 1,
32   ANDROID_VIEW_VIEW_ACCESSIBILITY_LIVE_REGION_ASSERTIVE = 2
33 };
34 
35 // These are enums from
36 // android.view.accessibility.AccessibilityNodeInfo.RangeInfo in Java:
37 enum {
38   ANDROID_VIEW_ACCESSIBILITY_RANGE_TYPE_FLOAT = 1
39 };
40 
41 }  // namespace
42 
43 namespace content {
44 
45 // static
Create()46 BrowserAccessibility* BrowserAccessibility::Create() {
47   return new BrowserAccessibilityAndroid();
48 }
49 
BrowserAccessibilityAndroid()50 BrowserAccessibilityAndroid::BrowserAccessibilityAndroid() {
51   first_time_ = true;
52 }
53 
IsNative() const54 bool BrowserAccessibilityAndroid::IsNative() const {
55   return true;
56 }
57 
PlatformIsLeaf() const58 bool BrowserAccessibilityAndroid::PlatformIsLeaf() const {
59   if (InternalChildCount() == 0)
60     return true;
61 
62   // Iframes are always allowed to contain children.
63   if (IsIframe() ||
64       GetRole() == ui::AX_ROLE_ROOT_WEB_AREA ||
65       GetRole() == ui::AX_ROLE_WEB_AREA) {
66     return false;
67   }
68 
69   // If it has a focusable child, we definitely can't leave out children.
70   if (HasFocusableChild())
71     return false;
72 
73   // Headings with text can drop their children.
74   base::string16 name = GetText();
75   if (GetRole() == ui::AX_ROLE_HEADING && !name.empty())
76     return true;
77 
78   // Focusable nodes with text can drop their children.
79   if (HasState(ui::AX_STATE_FOCUSABLE) && !name.empty())
80     return true;
81 
82   // Nodes with only static text as children can drop their children.
83   if (HasOnlyStaticTextChildren())
84     return true;
85 
86   return BrowserAccessibility::PlatformIsLeaf();
87 }
88 
IsCheckable() const89 bool BrowserAccessibilityAndroid::IsCheckable() const {
90   bool checkable = false;
91   bool is_aria_pressed_defined;
92   bool is_mixed;
93   GetAriaTristate("aria-pressed", &is_aria_pressed_defined, &is_mixed);
94   if (GetRole() == ui::AX_ROLE_CHECK_BOX ||
95       GetRole() == ui::AX_ROLE_RADIO_BUTTON ||
96       is_aria_pressed_defined) {
97     checkable = true;
98   }
99   if (HasState(ui::AX_STATE_CHECKED))
100     checkable = true;
101   return checkable;
102 }
103 
IsChecked() const104 bool BrowserAccessibilityAndroid::IsChecked() const {
105   return HasState(ui::AX_STATE_CHECKED);
106 }
107 
IsClickable() const108 bool BrowserAccessibilityAndroid::IsClickable() const {
109   return (PlatformIsLeaf() && !GetText().empty());
110 }
111 
IsCollection() const112 bool BrowserAccessibilityAndroid::IsCollection() const {
113   return (GetRole() == ui::AX_ROLE_GRID ||
114           GetRole() == ui::AX_ROLE_LIST ||
115           GetRole() == ui::AX_ROLE_LIST_BOX ||
116           GetRole() == ui::AX_ROLE_TABLE ||
117           GetRole() == ui::AX_ROLE_TREE);
118 }
119 
IsCollectionItem() const120 bool BrowserAccessibilityAndroid::IsCollectionItem() const {
121   return (GetRole() == ui::AX_ROLE_CELL ||
122           GetRole() == ui::AX_ROLE_COLUMN_HEADER ||
123           GetRole() == ui::AX_ROLE_DESCRIPTION_LIST_TERM ||
124           GetRole() == ui::AX_ROLE_LIST_BOX_OPTION ||
125           GetRole() == ui::AX_ROLE_LIST_ITEM ||
126           GetRole() == ui::AX_ROLE_ROW_HEADER ||
127           GetRole() == ui::AX_ROLE_TREE_ITEM);
128 }
129 
IsContentInvalid() const130 bool BrowserAccessibilityAndroid::IsContentInvalid() const {
131   std::string invalid;
132   return GetHtmlAttribute("aria-invalid", &invalid);
133 }
134 
IsDismissable() const135 bool BrowserAccessibilityAndroid::IsDismissable() const {
136   return false;  // No concept of "dismissable" on the web currently.
137 }
138 
IsEnabled() const139 bool BrowserAccessibilityAndroid::IsEnabled() const {
140   return HasState(ui::AX_STATE_ENABLED);
141 }
142 
IsFocusable() const143 bool BrowserAccessibilityAndroid::IsFocusable() const {
144   bool focusable = HasState(ui::AX_STATE_FOCUSABLE);
145   if (IsIframe() ||
146       GetRole() == ui::AX_ROLE_WEB_AREA) {
147     focusable = false;
148   }
149   return focusable;
150 }
151 
IsFocused() const152 bool BrowserAccessibilityAndroid::IsFocused() const {
153   return manager()->GetFocus(manager()->GetRoot()) == this;
154 }
155 
IsHeading() const156 bool BrowserAccessibilityAndroid::IsHeading() const {
157   return (GetRole() == ui::AX_ROLE_COLUMN_HEADER ||
158           GetRole() == ui::AX_ROLE_HEADING ||
159           GetRole() == ui::AX_ROLE_ROW_HEADER);
160 }
161 
IsHierarchical() const162 bool BrowserAccessibilityAndroid::IsHierarchical() const {
163   return (GetRole() == ui::AX_ROLE_LIST ||
164           GetRole() == ui::AX_ROLE_TREE);
165 }
166 
IsLink() const167 bool BrowserAccessibilityAndroid::IsLink() const {
168   return GetRole() == ui::AX_ROLE_LINK ||
169          GetRole() == ui::AX_ROLE_IMAGE_MAP_LINK;
170 }
171 
IsMultiLine() const172 bool BrowserAccessibilityAndroid::IsMultiLine() const {
173   return GetRole() == ui::AX_ROLE_TEXT_AREA;
174 }
175 
IsPassword() const176 bool BrowserAccessibilityAndroid::IsPassword() const {
177   return HasState(ui::AX_STATE_PROTECTED);
178 }
179 
IsRangeType() const180 bool BrowserAccessibilityAndroid::IsRangeType() const {
181   return (GetRole() == ui::AX_ROLE_PROGRESS_INDICATOR ||
182           GetRole() == ui::AX_ROLE_SCROLL_BAR ||
183           GetRole() == ui::AX_ROLE_SLIDER);
184 }
185 
IsScrollable() const186 bool BrowserAccessibilityAndroid::IsScrollable() const {
187   int dummy;
188   return GetIntAttribute(ui::AX_ATTR_SCROLL_X_MAX, &dummy);
189 }
190 
IsSelected() const191 bool BrowserAccessibilityAndroid::IsSelected() const {
192   return HasState(ui::AX_STATE_SELECTED);
193 }
194 
IsVisibleToUser() const195 bool BrowserAccessibilityAndroid::IsVisibleToUser() const {
196   return !HasState(ui::AX_STATE_INVISIBLE);
197 }
198 
CanOpenPopup() const199 bool BrowserAccessibilityAndroid::CanOpenPopup() const {
200   return HasState(ui::AX_STATE_HASPOPUP);
201 }
202 
GetClassName() const203 const char* BrowserAccessibilityAndroid::GetClassName() const {
204   const char* class_name = NULL;
205 
206   switch(GetRole()) {
207     case ui::AX_ROLE_EDITABLE_TEXT:
208     case ui::AX_ROLE_SPIN_BUTTON:
209     case ui::AX_ROLE_TEXT_AREA:
210     case ui::AX_ROLE_TEXT_FIELD:
211       class_name = "android.widget.EditText";
212       break;
213     case ui::AX_ROLE_SLIDER:
214       class_name = "android.widget.SeekBar";
215       break;
216     case ui::AX_ROLE_COMBO_BOX:
217       class_name = "android.widget.Spinner";
218       break;
219     case ui::AX_ROLE_BUTTON:
220     case ui::AX_ROLE_MENU_BUTTON:
221     case ui::AX_ROLE_POP_UP_BUTTON:
222       class_name = "android.widget.Button";
223       break;
224     case ui::AX_ROLE_CHECK_BOX:
225       class_name = "android.widget.CheckBox";
226       break;
227     case ui::AX_ROLE_RADIO_BUTTON:
228       class_name = "android.widget.RadioButton";
229       break;
230     case ui::AX_ROLE_TOGGLE_BUTTON:
231       class_name = "android.widget.ToggleButton";
232       break;
233     case ui::AX_ROLE_CANVAS:
234     case ui::AX_ROLE_IMAGE:
235       class_name = "android.widget.Image";
236       break;
237     case ui::AX_ROLE_PROGRESS_INDICATOR:
238       class_name = "android.widget.ProgressBar";
239       break;
240     case ui::AX_ROLE_TAB_LIST:
241       class_name = "android.widget.TabWidget";
242       break;
243     case ui::AX_ROLE_GRID:
244     case ui::AX_ROLE_TABLE:
245       class_name = "android.widget.GridView";
246       break;
247     case ui::AX_ROLE_LIST:
248     case ui::AX_ROLE_LIST_BOX:
249       class_name = "android.widget.ListView";
250       break;
251     case ui::AX_ROLE_DIALOG:
252       class_name = "android.app.Dialog";
253       break;
254     case ui::AX_ROLE_ROOT_WEB_AREA:
255       class_name = "android.webkit.WebView";
256       break;
257     default:
258       class_name = "android.view.View";
259       break;
260   }
261 
262   return class_name;
263 }
264 
GetText() const265 base::string16 BrowserAccessibilityAndroid::GetText() const {
266   if (IsIframe() ||
267       GetRole() == ui::AX_ROLE_WEB_AREA) {
268     return base::string16();
269   }
270 
271   // See comment in browser_accessibility_win.cc for details.
272   // The difference here is that we can only expose one accessible
273   // name on Android, not 2 or 3 like on Windows or Mac.
274 
275   // First, always return the |value| attribute if this is an
276   // accessible text.
277   if (!value().empty() &&
278       (GetRole() == ui::AX_ROLE_EDITABLE_TEXT ||
279        GetRole() == ui::AX_ROLE_TEXT_AREA ||
280        GetRole() == ui::AX_ROLE_TEXT_FIELD ||
281        HasState(ui::AX_STATE_EDITABLE))) {
282     return base::UTF8ToUTF16(value());
283   }
284 
285   // If there's no text value, the basic rule is: prefer description
286   // (aria-labelledby or aria-label), then help (title), then name
287   // (inner text), then value (control value).  However, if
288   // title_elem_id is set, that means there's a label element
289   // supplying the name and then name takes precedence over help.
290   // TODO(dmazzoni): clean this up by providing more granular labels in
291   // Blink, making the platform-specific mapping to accessible text simpler.
292   base::string16 description = GetString16Attribute(ui::AX_ATTR_DESCRIPTION);
293   base::string16 help = GetString16Attribute(ui::AX_ATTR_HELP);
294   int title_elem_id = GetIntAttribute(
295       ui::AX_ATTR_TITLE_UI_ELEMENT);
296   base::string16 text;
297   if (!description.empty())
298     text = description;
299   else if (title_elem_id && !name().empty())
300     text = base::UTF8ToUTF16(name());
301   else if (!help.empty())
302     text = help;
303   else if (!name().empty())
304     text = base::UTF8ToUTF16(name());
305   else if (!value().empty())
306     text = base::UTF8ToUTF16(value());
307 
308   // This is called from PlatformIsLeaf, so don't call PlatformChildCount
309   // from within this!
310   if (text.empty() && HasOnlyStaticTextChildren()) {
311     for (uint32 i = 0; i < InternalChildCount(); i++) {
312       BrowserAccessibility* child = InternalGetChild(i);
313       text += static_cast<BrowserAccessibilityAndroid*>(child)->GetText();
314     }
315   }
316 
317   if (text.empty() && IsLink()) {
318     base::string16 url = GetString16Attribute(ui::AX_ATTR_URL);
319     // Given a url like http://foo.com/bar/baz.png, just return the
320     // base name, e.g., "baz".
321     int trailing_slashes = 0;
322     while (url.size() - trailing_slashes > 0 &&
323            url[url.size() - trailing_slashes - 1] == '/') {
324       trailing_slashes++;
325     }
326     if (trailing_slashes)
327       url = url.substr(0, url.size() - trailing_slashes);
328     size_t slash_index = url.rfind('/');
329     if (slash_index != std::string::npos)
330       url = url.substr(slash_index + 1);
331     size_t dot_index = url.rfind('.');
332     if (dot_index != std::string::npos)
333       url = url.substr(0, dot_index);
334     text = url;
335   }
336 
337   return text;
338 }
339 
GetItemIndex() const340 int BrowserAccessibilityAndroid::GetItemIndex() const {
341   int index = 0;
342   switch(GetRole()) {
343     case ui::AX_ROLE_LIST_ITEM:
344     case ui::AX_ROLE_LIST_BOX_OPTION:
345     case ui::AX_ROLE_TREE_ITEM:
346       index = GetIndexInParent();
347       break;
348     case ui::AX_ROLE_SLIDER:
349     case ui::AX_ROLE_PROGRESS_INDICATOR: {
350       float value_for_range;
351       if (GetFloatAttribute(
352               ui::AX_ATTR_VALUE_FOR_RANGE, &value_for_range)) {
353         index = static_cast<int>(value_for_range);
354       }
355       break;
356     }
357   }
358   return index;
359 }
360 
GetItemCount() const361 int BrowserAccessibilityAndroid::GetItemCount() const {
362   int count = 0;
363   switch(GetRole()) {
364     case ui::AX_ROLE_LIST:
365     case ui::AX_ROLE_LIST_BOX:
366       count = PlatformChildCount();
367       break;
368     case ui::AX_ROLE_SLIDER:
369     case ui::AX_ROLE_PROGRESS_INDICATOR: {
370       float max_value_for_range;
371       if (GetFloatAttribute(ui::AX_ATTR_MAX_VALUE_FOR_RANGE,
372                             &max_value_for_range)) {
373         count = static_cast<int>(max_value_for_range);
374       }
375       break;
376     }
377   }
378   return count;
379 }
380 
GetScrollX() const381 int BrowserAccessibilityAndroid::GetScrollX() const {
382   int value = 0;
383   GetIntAttribute(ui::AX_ATTR_SCROLL_X, &value);
384   return value;
385 }
386 
GetScrollY() const387 int BrowserAccessibilityAndroid::GetScrollY() const {
388   int value = 0;
389   GetIntAttribute(ui::AX_ATTR_SCROLL_Y, &value);
390   return value;
391 }
392 
GetMaxScrollX() const393 int BrowserAccessibilityAndroid::GetMaxScrollX() const {
394   int value = 0;
395   GetIntAttribute(ui::AX_ATTR_SCROLL_X_MAX, &value);
396   return value;
397 }
398 
GetMaxScrollY() const399 int BrowserAccessibilityAndroid::GetMaxScrollY() const {
400   int value = 0;
401   GetIntAttribute(ui::AX_ATTR_SCROLL_Y_MAX, &value);
402   return value;
403 }
404 
GetTextChangeFromIndex() const405 int BrowserAccessibilityAndroid::GetTextChangeFromIndex() const {
406   size_t index = 0;
407   while (index < old_value_.length() &&
408          index < new_value_.length() &&
409          old_value_[index] == new_value_[index]) {
410     index++;
411   }
412   return index;
413 }
414 
GetTextChangeAddedCount() const415 int BrowserAccessibilityAndroid::GetTextChangeAddedCount() const {
416   size_t old_len = old_value_.length();
417   size_t new_len = new_value_.length();
418   size_t left = 0;
419   while (left < old_len &&
420          left < new_len &&
421          old_value_[left] == new_value_[left]) {
422     left++;
423   }
424   size_t right = 0;
425   while (right < old_len &&
426          right < new_len &&
427          old_value_[old_len - right - 1] == new_value_[new_len - right - 1]) {
428     right++;
429   }
430   return (new_len - left - right);
431 }
432 
GetTextChangeRemovedCount() const433 int BrowserAccessibilityAndroid::GetTextChangeRemovedCount() const {
434   size_t old_len = old_value_.length();
435   size_t new_len = new_value_.length();
436   size_t left = 0;
437   while (left < old_len &&
438          left < new_len &&
439          old_value_[left] == new_value_[left]) {
440     left++;
441   }
442   size_t right = 0;
443   while (right < old_len &&
444          right < new_len &&
445          old_value_[old_len - right - 1] == new_value_[new_len - right - 1]) {
446     right++;
447   }
448   return (old_len - left - right);
449 }
450 
GetTextChangeBeforeText() const451 base::string16 BrowserAccessibilityAndroid::GetTextChangeBeforeText() const {
452   return old_value_;
453 }
454 
GetSelectionStart() const455 int BrowserAccessibilityAndroid::GetSelectionStart() const {
456   int sel_start = 0;
457   GetIntAttribute(ui::AX_ATTR_TEXT_SEL_START, &sel_start);
458   return sel_start;
459 }
460 
GetSelectionEnd() const461 int BrowserAccessibilityAndroid::GetSelectionEnd() const {
462   int sel_end = 0;
463   GetIntAttribute(ui::AX_ATTR_TEXT_SEL_END, &sel_end);
464   return sel_end;
465 }
466 
GetEditableTextLength() const467 int BrowserAccessibilityAndroid::GetEditableTextLength() const {
468   return value().length();
469 }
470 
AndroidInputType() const471 int BrowserAccessibilityAndroid::AndroidInputType() const {
472   std::string html_tag = GetStringAttribute(
473       ui::AX_ATTR_HTML_TAG);
474   if (html_tag != "input")
475     return ANDROID_TEXT_INPUTTYPE_TYPE_NULL;
476 
477   std::string type;
478   if (!GetHtmlAttribute("type", &type))
479     return ANDROID_TEXT_INPUTTYPE_TYPE_TEXT;
480 
481   if (type == "" || type == "text" || type == "search")
482     return ANDROID_TEXT_INPUTTYPE_TYPE_TEXT;
483   else if (type == "date")
484     return ANDROID_TEXT_INPUTTYPE_TYPE_DATETIME_DATE;
485   else if (type == "datetime" || type == "datetime-local")
486     return ANDROID_TEXT_INPUTTYPE_TYPE_DATETIME;
487   else if (type == "email")
488     return ANDROID_TEXT_INPUTTYPE_TYPE_TEXT_WEB_EMAIL;
489   else if (type == "month")
490     return ANDROID_TEXT_INPUTTYPE_TYPE_DATETIME_DATE;
491   else if (type == "number")
492     return ANDROID_TEXT_INPUTTYPE_TYPE_NUMBER;
493   else if (type == "password")
494     return ANDROID_TEXT_INPUTTYPE_TYPE_TEXT_WEB_PASSWORD;
495   else if (type == "tel")
496     return ANDROID_TEXT_INPUTTYPE_TYPE_PHONE;
497   else if (type == "time")
498     return ANDROID_TEXT_INPUTTYPE_TYPE_DATETIME_TIME;
499   else if (type == "url")
500     return ANDROID_TEXT_INPUTTYPE_TYPE_TEXT_URI;
501   else if (type == "week")
502     return ANDROID_TEXT_INPUTTYPE_TYPE_DATETIME;
503 
504   return ANDROID_TEXT_INPUTTYPE_TYPE_NULL;
505 }
506 
AndroidLiveRegionType() const507 int BrowserAccessibilityAndroid::AndroidLiveRegionType() const {
508   std::string live = GetStringAttribute(
509       ui::AX_ATTR_LIVE_STATUS);
510   if (live == "polite")
511     return ANDROID_VIEW_VIEW_ACCESSIBILITY_LIVE_REGION_POLITE;
512   else if (live == "assertive")
513     return ANDROID_VIEW_VIEW_ACCESSIBILITY_LIVE_REGION_ASSERTIVE;
514   return ANDROID_VIEW_VIEW_ACCESSIBILITY_LIVE_REGION_NONE;
515 }
516 
AndroidRangeType() const517 int BrowserAccessibilityAndroid::AndroidRangeType() const {
518   return ANDROID_VIEW_ACCESSIBILITY_RANGE_TYPE_FLOAT;
519 }
520 
RowCount() const521 int BrowserAccessibilityAndroid::RowCount() const {
522   if (GetRole() == ui::AX_ROLE_GRID ||
523       GetRole() == ui::AX_ROLE_TABLE) {
524     return CountChildrenWithRole(ui::AX_ROLE_ROW);
525   }
526 
527   if (GetRole() == ui::AX_ROLE_LIST ||
528       GetRole() == ui::AX_ROLE_LIST_BOX ||
529       GetRole() == ui::AX_ROLE_TREE) {
530     return PlatformChildCount();
531   }
532 
533   return 0;
534 }
535 
ColumnCount() const536 int BrowserAccessibilityAndroid::ColumnCount() const {
537   if (GetRole() == ui::AX_ROLE_GRID ||
538       GetRole() == ui::AX_ROLE_TABLE) {
539     return CountChildrenWithRole(ui::AX_ROLE_COLUMN);
540   }
541   return 0;
542 }
543 
RowIndex() const544 int BrowserAccessibilityAndroid::RowIndex() const {
545   if (GetRole() == ui::AX_ROLE_LIST_ITEM ||
546       GetRole() == ui::AX_ROLE_LIST_BOX_OPTION ||
547       GetRole() == ui::AX_ROLE_TREE_ITEM) {
548     return GetIndexInParent();
549   }
550 
551   return GetIntAttribute(ui::AX_ATTR_TABLE_CELL_ROW_INDEX);
552 }
553 
RowSpan() const554 int BrowserAccessibilityAndroid::RowSpan() const {
555   return GetIntAttribute(ui::AX_ATTR_TABLE_CELL_ROW_SPAN);
556 }
557 
ColumnIndex() const558 int BrowserAccessibilityAndroid::ColumnIndex() const {
559   return GetIntAttribute(ui::AX_ATTR_TABLE_CELL_COLUMN_INDEX);
560 }
561 
ColumnSpan() const562 int BrowserAccessibilityAndroid::ColumnSpan() const {
563   return GetIntAttribute(ui::AX_ATTR_TABLE_CELL_COLUMN_SPAN);
564 }
565 
RangeMin() const566 float BrowserAccessibilityAndroid::RangeMin() const {
567   return GetFloatAttribute(ui::AX_ATTR_MIN_VALUE_FOR_RANGE);
568 }
569 
RangeMax() const570 float BrowserAccessibilityAndroid::RangeMax() const {
571   return GetFloatAttribute(ui::AX_ATTR_MAX_VALUE_FOR_RANGE);
572 }
573 
RangeCurrentValue() const574 float BrowserAccessibilityAndroid::RangeCurrentValue() const {
575   return GetFloatAttribute(ui::AX_ATTR_VALUE_FOR_RANGE);
576 }
577 
HasFocusableChild() const578 bool BrowserAccessibilityAndroid::HasFocusableChild() const {
579   // This is called from PlatformIsLeaf, so don't call PlatformChildCount
580   // from within this!
581   for (uint32 i = 0; i < InternalChildCount(); i++) {
582     BrowserAccessibility* child = InternalGetChild(i);
583     if (child->HasState(ui::AX_STATE_FOCUSABLE))
584       return true;
585     if (static_cast<BrowserAccessibilityAndroid*>(child)->HasFocusableChild())
586       return true;
587   }
588   return false;
589 }
590 
HasOnlyStaticTextChildren() const591 bool BrowserAccessibilityAndroid::HasOnlyStaticTextChildren() const {
592   // This is called from PlatformIsLeaf, so don't call PlatformChildCount
593   // from within this!
594   for (uint32 i = 0; i < InternalChildCount(); i++) {
595     BrowserAccessibility* child = InternalGetChild(i);
596     if (child->GetRole() != ui::AX_ROLE_STATIC_TEXT)
597       return false;
598   }
599   return true;
600 }
601 
IsIframe() const602 bool BrowserAccessibilityAndroid::IsIframe() const {
603   base::string16 html_tag = GetString16Attribute(
604       ui::AX_ATTR_HTML_TAG);
605   return html_tag == base::ASCIIToUTF16("iframe");
606 }
607 
OnDataChanged()608 void BrowserAccessibilityAndroid::OnDataChanged() {
609   BrowserAccessibility::OnDataChanged();
610 
611   if (IsEditableText()) {
612     if (base::UTF8ToUTF16(value()) != new_value_) {
613       old_value_ = new_value_;
614       new_value_ = base::UTF8ToUTF16(value());
615     }
616   }
617 
618   if (GetRole() == ui::AX_ROLE_ALERT && first_time_)
619     manager()->NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, this);
620 
621   base::string16 live;
622   if (GetString16Attribute(
623       ui::AX_ATTR_CONTAINER_LIVE_STATUS, &live)) {
624     NotifyLiveRegionUpdate(live);
625   }
626 
627   first_time_ = false;
628 }
629 
NotifyLiveRegionUpdate(base::string16 & aria_live)630 void BrowserAccessibilityAndroid::NotifyLiveRegionUpdate(
631     base::string16& aria_live) {
632   if (!EqualsASCII(aria_live, aria_strings::kAriaLivePolite) &&
633       !EqualsASCII(aria_live, aria_strings::kAriaLiveAssertive))
634     return;
635 
636   base::string16 text = GetText();
637   if (cached_text_ != text) {
638     if (!text.empty()) {
639       manager()->NotifyAccessibilityEvent(ui::AX_EVENT_SHOW,
640                                          this);
641     }
642     cached_text_ = text;
643   }
644 }
645 
CountChildrenWithRole(ui::AXRole role) const646 int BrowserAccessibilityAndroid::CountChildrenWithRole(ui::AXRole role) const {
647   int count = 0;
648   for (uint32 i = 0; i < PlatformChildCount(); i++) {
649     if (PlatformGetChild(i)->GetRole() == role)
650       count++;
651   }
652   return count;
653 }
654 
655 }  // namespace content
656