Home | History | Annotate | Download | only in accessibility
      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 #include "content/common/accessibility_node_data.h"
     11 
     12 namespace content {
     13 
     14 // static
     15 BrowserAccessibility* BrowserAccessibility::Create() {
     16   return new BrowserAccessibilityAndroid();
     17 }
     18 
     19 BrowserAccessibilityAndroid::BrowserAccessibilityAndroid() {
     20   first_time_ = true;
     21 }
     22 
     23 bool BrowserAccessibilityAndroid::IsNative() const {
     24   return true;
     25 }
     26 
     27 bool BrowserAccessibilityAndroid::IsLeaf() const {
     28   if (child_count() == 0)
     29     return true;
     30 
     31   // Iframes are always allowed to contain children.
     32   if (IsIframe() ||
     33       role() == AccessibilityNodeData::ROLE_ROOT_WEB_AREA ||
     34       role() == AccessibilityNodeData::ROLE_WEB_AREA) {
     35     return false;
     36   }
     37 
     38   // If it has a focusable child, we definitely can't leave out children.
     39   if (HasFocusableChild())
     40     return false;
     41 
     42   // Headings with text can drop their children.
     43   string16 name = GetText();
     44   if (role() == AccessibilityNodeData::ROLE_HEADING && !name.empty())
     45     return true;
     46 
     47   // Focusable nodes with text can drop their children.
     48   if (HasState(AccessibilityNodeData::STATE_FOCUSABLE) && !name.empty())
     49     return true;
     50 
     51   // Nodes with only static text as children can drop their children.
     52   if (HasOnlyStaticTextChildren())
     53     return true;
     54 
     55   return false;
     56 }
     57 
     58 bool BrowserAccessibilityAndroid::IsCheckable() const {
     59   bool checkable = false;
     60   bool is_aria_pressed_defined;
     61   bool is_mixed;
     62   GetAriaTristate("aria-pressed", &is_aria_pressed_defined, &is_mixed);
     63   if (role() == AccessibilityNodeData::ROLE_CHECKBOX ||
     64       role() == AccessibilityNodeData::ROLE_RADIO_BUTTON ||
     65       is_aria_pressed_defined) {
     66     checkable = true;
     67   }
     68   if (HasState(AccessibilityNodeData::STATE_CHECKED))
     69     checkable = true;
     70   return checkable;
     71 }
     72 
     73 bool BrowserAccessibilityAndroid::IsChecked() const {
     74   return HasState(AccessibilityNodeData::STATE_CHECKED);
     75 }
     76 
     77 bool BrowserAccessibilityAndroid::IsClickable() const {
     78   return (IsLeaf() && !GetText().empty());
     79 }
     80 
     81 bool BrowserAccessibilityAndroid::IsEnabled() const {
     82   return !HasState(AccessibilityNodeData::STATE_UNAVAILABLE);
     83 }
     84 
     85 bool BrowserAccessibilityAndroid::IsFocusable() const {
     86   bool focusable = HasState(AccessibilityNodeData::STATE_FOCUSABLE);
     87   if (IsIframe() ||
     88       role() == AccessibilityNodeData::ROLE_WEB_AREA) {
     89     focusable = false;
     90   }
     91   return focusable;
     92 }
     93 
     94 bool BrowserAccessibilityAndroid::IsFocused() const {
     95   return manager()->GetFocus(manager()->GetRoot()) == this;
     96 }
     97 
     98 bool BrowserAccessibilityAndroid::IsPassword() const {
     99   return HasState(AccessibilityNodeData::STATE_PROTECTED);
    100 }
    101 
    102 bool BrowserAccessibilityAndroid::IsScrollable() const {
    103   int dummy;
    104   return GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_X_MAX, &dummy);
    105 }
    106 
    107 bool BrowserAccessibilityAndroid::IsSelected() const {
    108   return HasState(AccessibilityNodeData::STATE_SELECTED);
    109 }
    110 
    111 bool BrowserAccessibilityAndroid::IsVisibleToUser() const {
    112   return !HasState(AccessibilityNodeData::STATE_INVISIBLE);
    113 }
    114 
    115 const char* BrowserAccessibilityAndroid::GetClassName() const {
    116   const char* class_name = NULL;
    117 
    118   switch(role()) {
    119     case AccessibilityNodeData::ROLE_EDITABLE_TEXT:
    120     case AccessibilityNodeData::ROLE_SPIN_BUTTON:
    121     case AccessibilityNodeData::ROLE_TEXTAREA:
    122     case AccessibilityNodeData::ROLE_TEXT_FIELD:
    123       class_name = "android.widget.EditText";
    124       break;
    125     case AccessibilityNodeData::ROLE_SLIDER:
    126       class_name = "android.widget.SeekBar";
    127       break;
    128     case AccessibilityNodeData::ROLE_COMBO_BOX:
    129       class_name = "android.widget.Spinner";
    130       break;
    131     case AccessibilityNodeData::ROLE_BUTTON:
    132     case AccessibilityNodeData::ROLE_MENU_BUTTON:
    133     case AccessibilityNodeData::ROLE_POPUP_BUTTON:
    134       class_name = "android.widget.Button";
    135       break;
    136     case AccessibilityNodeData::ROLE_CHECKBOX:
    137       class_name = "android.widget.CheckBox";
    138       break;
    139     case AccessibilityNodeData::ROLE_RADIO_BUTTON:
    140       class_name = "android.widget.RadioButton";
    141       break;
    142     case AccessibilityNodeData::ROLE_TOGGLE_BUTTON:
    143       class_name = "android.widget.ToggleButton";
    144       break;
    145     case AccessibilityNodeData::ROLE_CANVAS:
    146     case AccessibilityNodeData::ROLE_IMAGE:
    147       class_name = "android.widget.Image";
    148       break;
    149     case AccessibilityNodeData::ROLE_PROGRESS_INDICATOR:
    150       class_name = "android.widget.ProgressBar";
    151       break;
    152     case AccessibilityNodeData::ROLE_TAB_LIST:
    153       class_name = "android.widget.TabWidget";
    154       break;
    155     case AccessibilityNodeData::ROLE_GRID:
    156     case AccessibilityNodeData::ROLE_TABLE:
    157       class_name = "android.widget.GridView";
    158       break;
    159     case AccessibilityNodeData::ROLE_LIST:
    160     case AccessibilityNodeData::ROLE_LISTBOX:
    161       class_name = "android.widget.ListView";
    162       break;
    163     default:
    164       class_name = "android.view.View";
    165       break;
    166   }
    167 
    168   return class_name;
    169 }
    170 
    171 string16 BrowserAccessibilityAndroid::GetText() const {
    172   if (IsIframe() ||
    173       role() == AccessibilityNodeData::ROLE_WEB_AREA) {
    174     return string16();
    175   }
    176 
    177   string16 description;
    178   GetStringAttribute(AccessibilityNodeData::ATTR_DESCRIPTION, &description);
    179 
    180   string16 text;
    181   if (!name().empty())
    182     text = name();
    183   else if (!description.empty())
    184     text = description;
    185   else if (!value().empty())
    186     text = value();
    187 
    188   if (text.empty() && HasOnlyStaticTextChildren()) {
    189     for (uint32 i = 0; i < child_count(); i++) {
    190       BrowserAccessibility* child = GetChild(i);
    191       text += static_cast<BrowserAccessibilityAndroid*>(child)->GetText();
    192     }
    193   }
    194 
    195   switch(role()) {
    196     case AccessibilityNodeData::ROLE_IMAGE_MAP_LINK:
    197     case AccessibilityNodeData::ROLE_LINK:
    198     case AccessibilityNodeData::ROLE_WEBCORE_LINK:
    199       if (!text.empty())
    200         text += ASCIIToUTF16(" ");
    201       text += ASCIIToUTF16("Link");
    202       break;
    203     case AccessibilityNodeData::ROLE_HEADING:
    204       // Only append "heading" if this node already has text.
    205       if (!text.empty())
    206         text += ASCIIToUTF16(" Heading");
    207       break;
    208   }
    209 
    210   return text;
    211 }
    212 
    213 int BrowserAccessibilityAndroid::GetItemIndex() const {
    214   int index = 0;
    215   switch(role()) {
    216     case AccessibilityNodeData::ROLE_LIST_ITEM:
    217     case AccessibilityNodeData::ROLE_LISTBOX_OPTION:
    218       index = index_in_parent();
    219       break;
    220     case AccessibilityNodeData::ROLE_SLIDER:
    221     case AccessibilityNodeData::ROLE_PROGRESS_INDICATOR: {
    222       float value_for_range;
    223       if (GetFloatAttribute(
    224               AccessibilityNodeData::ATTR_VALUE_FOR_RANGE, &value_for_range)) {
    225         index = static_cast<int>(value_for_range);
    226       }
    227       break;
    228     }
    229   }
    230   return index;
    231 }
    232 
    233 int BrowserAccessibilityAndroid::GetItemCount() const {
    234   int count = 0;
    235   switch(role()) {
    236     case AccessibilityNodeData::ROLE_LIST:
    237     case AccessibilityNodeData::ROLE_LISTBOX:
    238       count = child_count();
    239       break;
    240     case AccessibilityNodeData::ROLE_SLIDER:
    241     case AccessibilityNodeData::ROLE_PROGRESS_INDICATOR: {
    242       float max_value_for_range;
    243       if (GetFloatAttribute(AccessibilityNodeData::ATTR_MAX_VALUE_FOR_RANGE,
    244                             &max_value_for_range)) {
    245         count = static_cast<int>(max_value_for_range);
    246       }
    247       break;
    248     }
    249   }
    250   return count;
    251 }
    252 
    253 int BrowserAccessibilityAndroid::GetScrollX() const {
    254   int value = 0;
    255   GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_X, &value);
    256   return value;
    257 }
    258 
    259 int BrowserAccessibilityAndroid::GetScrollY() const {
    260   int value = 0;
    261   GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_Y, &value);
    262   return value;
    263 }
    264 
    265 int BrowserAccessibilityAndroid::GetMaxScrollX() const {
    266   int value = 0;
    267   GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_X_MAX, &value);
    268   return value;
    269 }
    270 
    271 int BrowserAccessibilityAndroid::GetMaxScrollY() const {
    272   int value = 0;
    273   GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_Y_MAX, &value);
    274   return value;
    275 }
    276 
    277 int BrowserAccessibilityAndroid::GetTextChangeFromIndex() const {
    278   size_t index = 0;
    279   while (index < old_value_.length() &&
    280          index < new_value_.length() &&
    281          old_value_[index] == new_value_[index]) {
    282     index++;
    283   }
    284   return index;
    285 }
    286 
    287 int BrowserAccessibilityAndroid::GetTextChangeAddedCount() const {
    288   size_t old_len = old_value_.length();
    289   size_t new_len = new_value_.length();
    290   size_t left = 0;
    291   while (left < old_len &&
    292          left < new_len &&
    293          old_value_[left] == new_value_[left]) {
    294     left++;
    295   }
    296   size_t right = 0;
    297   while (right < old_len &&
    298          right < new_len &&
    299          old_value_[old_len - right - 1] == new_value_[new_len - right - 1]) {
    300     right++;
    301   }
    302   return (new_len - left - right);
    303 }
    304 
    305 int BrowserAccessibilityAndroid::GetTextChangeRemovedCount() const {
    306   size_t old_len = old_value_.length();
    307   size_t new_len = new_value_.length();
    308   size_t left = 0;
    309   while (left < old_len &&
    310          left < new_len &&
    311          old_value_[left] == new_value_[left]) {
    312     left++;
    313   }
    314   size_t right = 0;
    315   while (right < old_len &&
    316          right < new_len &&
    317          old_value_[old_len - right - 1] == new_value_[new_len - right - 1]) {
    318     right++;
    319   }
    320   return (old_len - left - right);
    321 }
    322 
    323 string16 BrowserAccessibilityAndroid::GetTextChangeBeforeText() const {
    324   return old_value_;
    325 }
    326 
    327 int BrowserAccessibilityAndroid::GetSelectionStart() const {
    328   int sel_start = 0;
    329   GetIntAttribute(AccessibilityNodeData::ATTR_TEXT_SEL_START, &sel_start);
    330   return sel_start;
    331 }
    332 
    333 int BrowserAccessibilityAndroid::GetSelectionEnd() const {
    334   int sel_end = 0;
    335   GetIntAttribute(AccessibilityNodeData::ATTR_TEXT_SEL_END, &sel_end);
    336   return sel_end;
    337 }
    338 
    339 int BrowserAccessibilityAndroid::GetEditableTextLength() const {
    340   return value().length();
    341 }
    342 
    343 bool BrowserAccessibilityAndroid::HasFocusableChild() const {
    344   for (uint32 i = 0; i < child_count(); i++) {
    345     BrowserAccessibility* child = GetChild(i);
    346     if (child->HasState(AccessibilityNodeData::STATE_FOCUSABLE))
    347       return true;
    348     if (static_cast<BrowserAccessibilityAndroid*>(child)->HasFocusableChild())
    349       return true;
    350   }
    351   return false;
    352 }
    353 
    354 bool BrowserAccessibilityAndroid::HasOnlyStaticTextChildren() const {
    355   for (uint32 i = 0; i < child_count(); i++) {
    356     BrowserAccessibility* child = GetChild(i);
    357     if (child->role() != AccessibilityNodeData::ROLE_STATIC_TEXT)
    358       return false;
    359   }
    360   return true;
    361 }
    362 
    363 bool BrowserAccessibilityAndroid::IsIframe() const {
    364   string16 html_tag;
    365   GetStringAttribute(AccessibilityNodeData::ATTR_HTML_TAG, &html_tag);
    366   return html_tag == ASCIIToUTF16("iframe");
    367 }
    368 
    369 void BrowserAccessibilityAndroid::PostInitialize() {
    370   BrowserAccessibility::PostInitialize();
    371 
    372   if (IsEditableText()) {
    373     if (value_ != new_value_) {
    374       old_value_ = new_value_;
    375       new_value_ = value_;
    376     }
    377   }
    378 
    379   if (role_ == AccessibilityNodeData::ROLE_ALERT && first_time_)
    380     manager_->NotifyAccessibilityEvent(AccessibilityNotificationAlert, this);
    381 
    382   string16 live;
    383   if (GetStringAttribute(AccessibilityNodeData::ATTR_CONTAINER_LIVE_STATUS,
    384                          &live)) {
    385     NotifyLiveRegionUpdate(live);
    386   }
    387 
    388   first_time_ = false;
    389 }
    390 
    391 void BrowserAccessibilityAndroid::NotifyLiveRegionUpdate(string16& aria_live) {
    392   if (!EqualsASCII(aria_live, aria_strings::kAriaLivePolite) &&
    393       !EqualsASCII(aria_live, aria_strings::kAriaLiveAssertive))
    394     return;
    395 
    396   string16 text = GetText();
    397   if (cached_text_ != text) {
    398     if (!text.empty()) {
    399       manager_->NotifyAccessibilityEvent(AccessibilityNotificationObjectShow,
    400                                          this);
    401     }
    402     cached_text_ = text;
    403   }
    404 }
    405 
    406 }  // namespace content
    407