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

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

Powered by Google App Engine
This is Rietveld 408576698