Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(196)

Side by Side Diff: content/browser/accessibility/browser_accessibility_android.cc

Issue 15741009: Native Android accessibility. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebase Created 7 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 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/android/jni_android.h"
8 #include "base/android/jni_registrar.h"
9 #include "base/android/jni_string.h"
10 #include "base/utf_string_conversions.h"
11 #include "content/browser/accessibility/browser_accessibility_manager_android.h"
12 #include "content/common/accessibility_messages.h"
13 #include "content/common/accessibility_node_data.h"
14 #include "jni/BrowserAccessibility_jni.h"
15
16 namespace content {
17
18 // static
19 BrowserAccessibility* BrowserAccessibility::Create() {
20 return new BrowserAccessibilityAndroid();
21 }
22
23 BrowserAccessibilityAndroid::BrowserAccessibilityAndroid() {
24 first_time_ = true;
25 }
26
27 bool BrowserAccessibilityAndroid::IsNative() const {
28 return true;
29 }
30
31 //
32 // Actions, called from Java.
33 //
34
35 void BrowserAccessibilityAndroid::FocusJNI(JNIEnv* env, jobject obj) {
36 manager_->SetFocus(this, true);
37 }
38
39 void BrowserAccessibilityAndroid::ClickJNI(JNIEnv* env, jobject obj) {
40 manager_->DoDefaultAction(*this);
41 }
42
43 //
44 // Const accessors, called from Java.
45 //
46
47 ScopedJavaLocalRef<jstring>
48 BrowserAccessibilityAndroid::GetNameJNI(JNIEnv* env, jobject obj) const {
49 return base::android::ConvertUTF16ToJavaString(env, ComputeName());
50 }
51
52 ScopedJavaLocalRef<jobject>
53 BrowserAccessibilityAndroid::GetAbsoluteRectJNI(JNIEnv* env, jobject obj) const {
54 gfx::Rect rect = GetLocalBoundsRect();
55
56 return Java_BrowserAccessibility_createRect(
57 env,
58 static_cast<int>(rect.x()),
59 static_cast<int>(rect.y()),
60 static_cast<int>(rect.right()),
61 static_cast<int>(rect.bottom()));
62 }
63
64 ScopedJavaLocalRef<jobject>
65 BrowserAccessibilityAndroid::GetRectInParentJNI(JNIEnv* env, jobject obj) const {
66 gfx::Rect rect = GetLocalBoundsRect();
67 if (parent()) {
68 gfx::Rect parent_rect = parent()->GetLocalBoundsRect();
69 rect.Offset(-parent_rect.OffsetFromOrigin());
70 }
71 return Java_BrowserAccessibility_createRect(
72 env,
73 static_cast<int>(rect.x()),
74 static_cast<int>(rect.y()),
75 static_cast<int>(rect.right()),
76 static_cast<int>(rect.bottom()));
77 }
78
79 jboolean
80 BrowserAccessibilityAndroid::IsFocusableJNI(JNIEnv* env, jobject obj) const {
81 return static_cast<jboolean>(IsFocusable());
82 }
83
84 jboolean
85 BrowserAccessibilityAndroid::IsEditableTextJNI(JNIEnv* env, jobject obj) const {
86 return IsEditableText();
87 }
88
89 jint BrowserAccessibilityAndroid::GetParentJNI(JNIEnv* env, jobject obj) const {
90 return static_cast<jint>(parent()->renderer_id());
91 }
92
93 jint
94 BrowserAccessibilityAndroid::GetChildCountJNI(JNIEnv* env, jobject obj) const {
95 if (IsLeaf())
96 return 0;
97 else
98 return static_cast<jint>(child_count());
99 }
100
101 jint BrowserAccessibilityAndroid::GetChildIdAtJNI(JNIEnv* env,
102 jobject obj,
103 jint child_index) const {
104 return static_cast<jint>(GetChild(child_index)->renderer_id());
105 }
106
107 jboolean
108 BrowserAccessibilityAndroid::IsCheckableJNI(JNIEnv* env, jobject obj) const {
109 bool checkable = false;
110 bool is_aria_pressed_defined;
111 bool is_mixed;
112 GetAriaTristate("aria-pressed", &is_aria_pressed_defined, &is_mixed);
113 if (role() == AccessibilityNodeData::ROLE_CHECKBOX ||
114 role() == AccessibilityNodeData::ROLE_RADIO_BUTTON ||
115 is_aria_pressed_defined) {
116 checkable = true;
117 }
118 if (HasState(AccessibilityNodeData::STATE_CHECKED))
119 checkable = true;
120 return static_cast<jboolean>(checkable);
121 }
122
123 jboolean
124 BrowserAccessibilityAndroid::IsCheckedJNI(JNIEnv* env, jobject obj) const {
125 return static_cast<jboolean>(
126 HasState(AccessibilityNodeData::STATE_CHECKED));
127 }
128
129 base::android::ScopedJavaLocalRef<jstring>
130 BrowserAccessibilityAndroid::GetClassNameJNI(JNIEnv* env, jobject obj) const {
131 const char* class_name = NULL;
132
133 switch(role()) {
134 case AccessibilityNodeData::ROLE_EDITABLE_TEXT:
135 case AccessibilityNodeData::ROLE_SPIN_BUTTON:
136 case AccessibilityNodeData::ROLE_TEXTAREA:
137 case AccessibilityNodeData::ROLE_TEXT_FIELD:
138 class_name = "android.widget.EditText";
139 break;
140 case AccessibilityNodeData::ROLE_SLIDER:
141 class_name = "android.widget.SeekBar";
142 break;
143 case AccessibilityNodeData::ROLE_COMBO_BOX:
144 class_name = "android.widget.Spinner";
145 break;
146 case AccessibilityNodeData::ROLE_BUTTON:
147 case AccessibilityNodeData::ROLE_MENU_BUTTON:
148 case AccessibilityNodeData::ROLE_POPUP_BUTTON:
149 class_name = "android.widget.Button";
150 break;
151 case AccessibilityNodeData::ROLE_CHECKBOX:
152 class_name = "android.widget.CheckBox";
153 break;
154 case AccessibilityNodeData::ROLE_RADIO_BUTTON:
155 class_name = "android.widget.RadioButton";
156 break;
157 case AccessibilityNodeData::ROLE_TOGGLE_BUTTON:
158 class_name = "android.widget.ToggleButton";
159 break;
160 case AccessibilityNodeData::ROLE_CANVAS:
161 case AccessibilityNodeData::ROLE_IMAGE:
162 class_name = "android.widget.Image";
163 break;
164 case AccessibilityNodeData::ROLE_PROGRESS_INDICATOR:
165 class_name = "android.widget.ProgressBar";
166 break;
167 case AccessibilityNodeData::ROLE_TAB_LIST:
168 class_name = "android.widget.TabWidget";
169 break;
170 case AccessibilityNodeData::ROLE_GRID:
171 case AccessibilityNodeData::ROLE_TABLE:
172 class_name = "android.widget.GridView";
173 break;
174 case AccessibilityNodeData::ROLE_LIST:
175 case AccessibilityNodeData::ROLE_LISTBOX:
176 class_name = "android.widget.ListView";
177 break;
178 default:
179 class_name = "android.view.View";
180 break;
181 }
182
183 return base::android::ConvertUTF8ToJavaString(env, class_name);
184 }
185
186 jboolean
187 BrowserAccessibilityAndroid::IsEnabledJNI(JNIEnv* env, jobject obj) const {
188 return static_cast<jboolean>(
189 !HasState(AccessibilityNodeData::STATE_UNAVAILABLE));
190 }
191
192 jboolean
193 BrowserAccessibilityAndroid::IsFocusedJNI(JNIEnv* env, jobject obj) const {
194 return manager()->GetFocus(manager()->GetRoot()) == this;
195 }
196
197 jboolean
198 BrowserAccessibilityAndroid::IsPasswordJNI(JNIEnv* env, jobject obj) const {
199 return static_cast<jboolean>(
200 HasState(AccessibilityNodeData::STATE_PROTECTED));
201 }
202
203 jboolean
204 BrowserAccessibilityAndroid::IsScrollableJNI(JNIEnv* env, jobject obj) const {
205 int dummy;
206 bool scrollable = GetIntAttribute(
207 AccessibilityNodeData::ATTR_SCROLL_X_MAX, &dummy);
208 return static_cast<jboolean>(scrollable);
209 }
210
211 jboolean
212 BrowserAccessibilityAndroid::IsSelectedJNI(JNIEnv* env, jobject obj) const {
213 return static_cast<jboolean>(
214 HasState(AccessibilityNodeData::STATE_SELECTED));
215 }
216
217 jboolean
218 BrowserAccessibilityAndroid::IsVisibleJNI(JNIEnv* env, jobject obj) const {
219 return static_cast<jboolean>(
220 !HasState(AccessibilityNodeData::STATE_INVISIBLE));
221 }
222
223 base::android::ScopedJavaLocalRef<jstring>
224 BrowserAccessibilityAndroid::GetAriaLiveJNI(JNIEnv* env, jobject obj) const {
225 return base::android::ConvertUTF16ToJavaString(env, GetAriaLive());
226 }
227
228 jint BrowserAccessibilityAndroid::GetItemIndexJNI(JNIEnv* env, jobject obj) cons t {
229 int index = 0;
230 switch(role()) {
231 case AccessibilityNodeData::ROLE_LIST_ITEM:
232 case AccessibilityNodeData::ROLE_LISTBOX_OPTION:
233 index = index_in_parent();
234 break;
235 case AccessibilityNodeData::ROLE_SLIDER:
236 case AccessibilityNodeData::ROLE_PROGRESS_INDICATOR: {
237 float value_for_range;
238 if (GetFloatAttribute(
239 AccessibilityNodeData::ATTR_VALUE_FOR_RANGE, &value_for_range)) {
240 index = static_cast<int>(value_for_range);
241 }
242 break;
243 }
244 }
245 return static_cast<jint>(index);
246 }
247
248 jint
249 BrowserAccessibilityAndroid::GetItemCountJNI(JNIEnv* env, jobject obj) const {
250 int count = 0;
251 switch(role()) {
252 case AccessibilityNodeData::ROLE_LIST:
253 case AccessibilityNodeData::ROLE_LISTBOX:
254 count = child_count();
255 break;
256 case AccessibilityNodeData::ROLE_SLIDER:
257 case AccessibilityNodeData::ROLE_PROGRESS_INDICATOR: {
258 float max_value_for_range;
259 if (GetFloatAttribute(AccessibilityNodeData::ATTR_MAX_VALUE_FOR_RANGE,
260 &max_value_for_range)) {
261 count = static_cast<int>(max_value_for_range);
262 }
263 break;
264 }
265 }
266 return static_cast<jint>(count);
267 }
268
269 jint
270 BrowserAccessibilityAndroid::GetScrollXJNI(JNIEnv* env, jobject obj) const {
271 int value = 0;
272 GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_X, &value);
273 return static_cast<jint>(value);
274 }
275
276 jint
277 BrowserAccessibilityAndroid::GetScrollYJNI(JNIEnv* env, jobject obj) const {
278 int value = 0;
279 GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_Y, &value);
280 return static_cast<jint>(value);
281 }
282
283 jint
284 BrowserAccessibilityAndroid::GetMaxScrollXJNI(JNIEnv* env, jobject obj) const {
285 int value = 0;
286 GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_X_MAX, &value);
287 return static_cast<jint>(value);
288 }
289
290 jint
291 BrowserAccessibilityAndroid::GetMaxScrollYJNI(JNIEnv* env, jobject obj) const {
292 int value = 0;
293 GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_Y_MAX, &value);
294 return static_cast<jint>(value);
295 }
296
297 jboolean
298 BrowserAccessibilityAndroid::GetClickableJNI(JNIEnv* env, jobject obj) const {
299 return (IsLeaf() && !ComputeName().empty());
300 }
301
302 jint BrowserAccessibilityAndroid::GetSelectionStartJNI(JNIEnv* env, jobject obj)
303 const {
304 int sel_start = 0;
305 GetIntAttribute(AccessibilityNodeData::ATTR_TEXT_SEL_START, &sel_start);
306 return sel_start;
307 }
308
309 jint BrowserAccessibilityAndroid::GetSelectionEndJNI(JNIEnv* env, jobject obj)
310 const {
311 int sel_end = 0;
312 GetIntAttribute(AccessibilityNodeData::ATTR_TEXT_SEL_END, &sel_end);
313 return sel_end;
314 }
315
316 jint BrowserAccessibilityAndroid::GetEditableTextLengthJNI(
317 JNIEnv* env, jobject obj) const {
318 return value().length();
319 }
320
321 int BrowserAccessibilityAndroid::GetTextChangeFromIndexJNI(
322 JNIEnv* env, jobject obj) const {
323 size_t index = 0;
324 while (index < old_value_.length() &&
325 index < new_value_.length() &&
326 old_value_[index] == new_value_[index]) {
327 index++;
328 }
329 return index;
330 }
331
332 jint BrowserAccessibilityAndroid::GetTextChangeAddedCountJNI(
333 JNIEnv* env, jobject obj) const {
334 size_t old_len = old_value_.length();
335 size_t new_len = new_value_.length();
336 size_t left = 0;
337 while (left < old_len &&
338 left < new_len &&
339 old_value_[left] == new_value_[left]) {
340 left++;
341 }
342 size_t right = 0;
343 while (right < old_len &&
344 right < new_len &&
345 old_value_[old_len - right - 1] == new_value_[new_len - right - 1]) {
346 right++;
347 }
348 return (new_len - left - right);
349 }
350
351 jint BrowserAccessibilityAndroid::GetTextChangeRemovedCountJNI(
352 JNIEnv* env, jobject obj) const {
353 size_t old_len = old_value_.length();
354 size_t new_len = new_value_.length();
355 size_t left = 0;
356 while (left < old_len &&
357 left < new_len &&
358 old_value_[left] == new_value_[left]) {
359 left++;
360 }
361 size_t right = 0;
362 while (right < old_len &&
363 right < new_len &&
364 old_value_[old_len - right - 1] == new_value_[new_len - right - 1]) {
365 right++;
366 }
367 return (old_len - left - right);
368 }
369
370 base::android::ScopedJavaLocalRef<jstring>
371 BrowserAccessibilityAndroid::GetTextChangeBeforeTextJNI(
372 JNIEnv* env, jobject obj) const {
373 return base::android::ConvertUTF16ToJavaString(env, old_value_);
374 }
375
376 string16 BrowserAccessibilityAndroid::ComputeName() const {
377 if (IsIframe() ||
378 role() == AccessibilityNodeData::ROLE_WEB_AREA) {
379 return string16();
380 }
381
382 string16 description;
383 GetStringAttribute(AccessibilityNodeData::ATTR_DESCRIPTION,
384 &description);
385
386 string16 text;
387 if (!name().empty())
388 text = name();
389 else if (!description.empty())
390 text = description;
391 else if (!value().empty())
392 text = value();
393
394 if (text.empty() && HasOnlyStaticTextChildren()) {
395 for (uint32 i = 0; i < child_count(); i++) {
396 BrowserAccessibility* child = GetChild(i);
397 text += static_cast<BrowserAccessibilityAndroid*>(child)->ComputeName();
398 }
399 }
400
401 switch(role()) {
402 case AccessibilityNodeData::ROLE_IMAGE_MAP_LINK:
403 case AccessibilityNodeData::ROLE_LINK:
404 case AccessibilityNodeData::ROLE_WEBCORE_LINK:
405 if (!text.empty())
406 text += ASCIIToUTF16(" ");
407 text += ASCIIToUTF16("Link");
408 break;
409 case AccessibilityNodeData::ROLE_HEADING:
410 // Only append "heading" if this node already has text.
411 if (!text.empty())
412 text += ASCIIToUTF16(" Heading");
413 break;
414 }
415
416 return text;
417 }
418
419 string16 BrowserAccessibilityAndroid::GetAriaLive() const {
420 string16 aria_live;
421 if (GetStringAttribute(AccessibilityNodeData::ATTR_CONTAINER_LIVE_STATUS,
422 &aria_live)) {
423 return aria_live;
424 }
425 return string16();
426 }
427
428 bool BrowserAccessibilityAndroid::HasFocusableChild() const {
429 for (uint32 i = 0; i < child_count(); i++) {
430 BrowserAccessibility* child = GetChild(i);
431 if (child->HasState(AccessibilityNodeData::STATE_FOCUSABLE))
432 return true;
433 if (static_cast<BrowserAccessibilityAndroid*>(child)->HasFocusableChild())
434 return true;
435 }
436 return false;
437 }
438
439 bool BrowserAccessibilityAndroid::HasOnlyStaticTextChildren() const {
440 for (uint32 i = 0; i < child_count(); i++) {
441 BrowserAccessibility* child = GetChild(i);
442 if (child->role() != AccessibilityNodeData::ROLE_STATIC_TEXT)
443 return false;
444 }
445 return true;
446 }
447
448 bool BrowserAccessibilityAndroid::IsIframe() const {
449 string16 html_tag;
450 GetStringAttribute(AccessibilityNodeData::ATTR_HTML_TAG, &html_tag);
451 return html_tag == ASCIIToUTF16("iframe");
452 }
453
454 bool BrowserAccessibilityAndroid::IsFocusable() const {
455 bool focusable = HasState(AccessibilityNodeData::STATE_FOCUSABLE);
456 if (IsIframe() ||
457 role() == AccessibilityNodeData::ROLE_WEB_AREA) {
458 focusable = false;
459 }
460 return focusable;
461 }
462
463 bool BrowserAccessibilityAndroid::IsLeaf() const {
464 if (child_count() == 0)
465 return true;
466
467 // Iframes are always allowed to contain children.
468 if (IsIframe() ||
469 role() == AccessibilityNodeData::ROLE_ROOT_WEB_AREA ||
470 role() == AccessibilityNodeData::ROLE_WEB_AREA) {
471 return false;
472 }
473
474 // If it has a focusable child, we definitely can't leave out children.
475 if (HasFocusableChild())
476 return false;
477
478 // Headings with text can drop their children.
479 string16 name = ComputeName();
480 if (role() == AccessibilityNodeData::ROLE_HEADING && !name.empty())
481 return true;
482
483 // Focusable nodes with text can drop their children.
484 if (HasState(AccessibilityNodeData::STATE_FOCUSABLE) && !name.empty())
485 return true;
486
487 // Nodes with only static text as children can drop their children.
488 if (HasOnlyStaticTextChildren())
489 return true;
490
491 return false;
492 }
493
494 void BrowserAccessibilityAndroid::PostInitialize() {
495 BrowserAccessibility::PostInitialize();
496
497 if (IsEditableText()) {
498 if (value_ != new_value_) {
499 old_value_ = new_value_;
500 new_value_ = value_;
501 }
502 }
503
504 if (role_ == AccessibilityNodeData::ROLE_ALERT && first_time_)
505 manager_->NotifyAccessibilityEvent(AccessibilityNotificationAlert, this);
506
507 string16 live;
508 if (GetStringAttribute(AccessibilityNodeData::ATTR_CONTAINER_LIVE_STATUS,
509 &live)) {
510 NotifyLiveRegionUpdate(live);
511 }
512
513 first_time_ = false;
514 }
515
516 void BrowserAccessibilityAndroid::NotifyLiveRegionUpdate(string16& aria_live) {
517 if (!EqualsASCII(aria_live, aria_strings::kAriaLivePolite) &&
518 !EqualsASCII(aria_live, aria_strings::kAriaLiveAssertive))
519 return;
520
521 string16 text = ComputeName();
522 if (cached_text_ != text) {
523 if (!text.empty()) {
524 manager_->NotifyAccessibilityEvent(AccessibilityNotificationObjectShow,
525 this);
526 }
527 cached_text_ = text;
528 }
529 }
530
531 bool RegisterBrowserAccessibility(JNIEnv* env) {
532 return RegisterNativesImpl(env);
533 }
534
535 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698