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.
5 #include "content/browser/accessibility/browser_accessibility_manager_android.h"
9 #include "base/android/jni_android.h"
10 #include "base/android/jni_string.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "base/values.h"
14 #include "content/browser/accessibility/browser_accessibility_android.h"
15 #include "content/common/accessibility_messages.h"
16 #include "jni/BrowserAccessibilityManager_jni.h"
18 using base::android::AttachCurrentThread
;
19 using base::android::ScopedJavaLocalRef
;
23 // These are enums from android.view.accessibility.AccessibilityEvent in Java:
25 ANDROID_ACCESSIBILITY_EVENT_TYPE_VIEW_TEXT_CHANGED
= 16,
26 ANDROID_ACCESSIBILITY_EVENT_TYPE_VIEW_TEXT_SELECTION_CHANGED
= 8192
29 enum AndroidHtmlElementType
{
30 HTML_ELEMENT_TYPE_SECTION
,
31 HTML_ELEMENT_TYPE_LIST
,
32 HTML_ELEMENT_TYPE_CONTROL
,
36 // These are special unofficial strings sent from TalkBack/BrailleBack
37 // to jump to certain categories of web elements.
38 AndroidHtmlElementType
HtmlElementTypeFromString(base::string16 element_type
) {
39 if (element_type
== base::ASCIIToUTF16("SECTION"))
40 return HTML_ELEMENT_TYPE_SECTION
;
41 else if (element_type
== base::ASCIIToUTF16("LIST"))
42 return HTML_ELEMENT_TYPE_LIST
;
43 else if (element_type
== base::ASCIIToUTF16("CONTROL"))
44 return HTML_ELEMENT_TYPE_CONTROL
;
46 return HTML_ELEMENT_TYPE_ANY
;
49 } // anonymous namespace
53 namespace aria_strings
{
54 const char kAriaLivePolite
[] = "polite";
55 const char kAriaLiveAssertive
[] = "assertive";
59 BrowserAccessibilityManager
* BrowserAccessibilityManager::Create(
60 const ui::AXTreeUpdate
& initial_tree
,
61 BrowserAccessibilityDelegate
* delegate
,
62 BrowserAccessibilityFactory
* factory
) {
63 return new BrowserAccessibilityManagerAndroid(
64 ScopedJavaLocalRef
<jobject
>(), initial_tree
, delegate
, factory
);
67 BrowserAccessibilityManagerAndroid
*
68 BrowserAccessibilityManager::ToBrowserAccessibilityManagerAndroid() {
69 return static_cast<BrowserAccessibilityManagerAndroid
*>(this);
72 BrowserAccessibilityManagerAndroid::BrowserAccessibilityManagerAndroid(
73 ScopedJavaLocalRef
<jobject
> content_view_core
,
74 const ui::AXTreeUpdate
& initial_tree
,
75 BrowserAccessibilityDelegate
* delegate
,
76 BrowserAccessibilityFactory
* factory
)
77 : BrowserAccessibilityManager(delegate
, factory
) {
78 SetContentViewCore(content_view_core
);
79 Initialize(initial_tree
);
82 BrowserAccessibilityManagerAndroid::~BrowserAccessibilityManagerAndroid() {
83 JNIEnv
* env
= AttachCurrentThread();
84 ScopedJavaLocalRef
<jobject
> obj
= java_ref_
.get(env
);
88 Java_BrowserAccessibilityManager_onNativeObjectDestroyed(env
, obj
.obj());
92 ui::AXTreeUpdate
BrowserAccessibilityManagerAndroid::GetEmptyDocument() {
93 ui::AXNodeData empty_document
;
94 empty_document
.id
= 0;
95 empty_document
.role
= ui::AX_ROLE_ROOT_WEB_AREA
;
96 empty_document
.state
= 1 << ui::AX_STATE_READ_ONLY
;
98 ui::AXTreeUpdate update
;
99 update
.nodes
.push_back(empty_document
);
103 void BrowserAccessibilityManagerAndroid::SetContentViewCore(
104 ScopedJavaLocalRef
<jobject
> content_view_core
) {
105 if (content_view_core
.is_null())
108 JNIEnv
* env
= AttachCurrentThread();
109 java_ref_
= JavaObjectWeakGlobalRef(
110 env
, Java_BrowserAccessibilityManager_create(
111 env
, reinterpret_cast<intptr_t>(this),
112 content_view_core
.obj()).obj());
115 void BrowserAccessibilityManagerAndroid::NotifyAccessibilityEvent(
116 ui::AXEvent event_type
,
117 BrowserAccessibility
* node
) {
118 JNIEnv
* env
= AttachCurrentThread();
119 ScopedJavaLocalRef
<jobject
> obj
= java_ref_
.get(env
);
123 if (event_type
== ui::AX_EVENT_HIDE
)
126 if (event_type
== ui::AX_EVENT_HOVER
) {
127 HandleHoverEvent(node
);
131 // Always send AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED to notify
132 // the Android system that the accessibility hierarchy rooted at this
134 Java_BrowserAccessibilityManager_handleContentChanged(
135 env
, obj
.obj(), node
->GetId());
137 switch (event_type
) {
138 case ui::AX_EVENT_LOAD_COMPLETE
:
139 Java_BrowserAccessibilityManager_handlePageLoaded(
140 env
, obj
.obj(), focus_
->id());
142 case ui::AX_EVENT_FOCUS
:
143 Java_BrowserAccessibilityManager_handleFocusChanged(
144 env
, obj
.obj(), node
->GetId());
146 case ui::AX_EVENT_CHECKED_STATE_CHANGED
:
147 Java_BrowserAccessibilityManager_handleCheckStateChanged(
148 env
, obj
.obj(), node
->GetId());
150 case ui::AX_EVENT_SCROLL_POSITION_CHANGED
:
151 Java_BrowserAccessibilityManager_handleScrollPositionChanged(
152 env
, obj
.obj(), node
->GetId());
154 case ui::AX_EVENT_SCROLLED_TO_ANCHOR
:
155 Java_BrowserAccessibilityManager_handleScrolledToAnchor(
156 env
, obj
.obj(), node
->GetId());
158 case ui::AX_EVENT_ALERT
:
159 // An alert is a special case of live region. Fall through to the
160 // next case to handle it.
161 case ui::AX_EVENT_SHOW
: {
162 // This event is fired when an object appears in a live region.
164 BrowserAccessibilityAndroid
* android_node
=
165 static_cast<BrowserAccessibilityAndroid
*>(node
);
166 Java_BrowserAccessibilityManager_announceLiveRegionText(
168 base::android::ConvertUTF16ToJavaString(
169 env
, android_node
->GetText()).obj());
172 case ui::AX_EVENT_TEXT_SELECTION_CHANGED
:
173 Java_BrowserAccessibilityManager_handleTextSelectionChanged(
174 env
, obj
.obj(), node
->GetId());
176 case ui::AX_EVENT_CHILDREN_CHANGED
:
177 case ui::AX_EVENT_TEXT_CHANGED
:
178 case ui::AX_EVENT_VALUE_CHANGED
:
179 if (node
->IsEditableText()) {
180 Java_BrowserAccessibilityManager_handleEditableTextChanged(
181 env
, obj
.obj(), node
->GetId());
185 // There are some notifications that aren't meaningful on Android.
186 // It's okay to skip them.
191 jint
BrowserAccessibilityManagerAndroid::GetRootId(JNIEnv
* env
, jobject obj
) {
192 return static_cast<jint
>(GetRoot()->GetId());
195 jboolean
BrowserAccessibilityManagerAndroid::IsNodeValid(
196 JNIEnv
* env
, jobject obj
, jint id
) {
197 return GetFromID(id
) != NULL
;
200 void BrowserAccessibilityManagerAndroid::HitTest(
201 JNIEnv
* env
, jobject obj
, jint x
, jint y
) {
203 delegate()->AccessibilityHitTest(gfx::Point(x
, y
));
206 jboolean
BrowserAccessibilityManagerAndroid::PopulateAccessibilityNodeInfo(
207 JNIEnv
* env
, jobject obj
, jobject info
, jint id
) {
208 BrowserAccessibilityAndroid
* node
= static_cast<BrowserAccessibilityAndroid
*>(
213 if (node
->GetParent()) {
214 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoParent(
215 env
, obj
, info
, node
->GetParent()->GetId());
217 for (unsigned i
= 0; i
< node
->PlatformChildCount(); ++i
) {
218 Java_BrowserAccessibilityManager_addAccessibilityNodeInfoChild(
219 env
, obj
, info
, node
->InternalGetChild(i
)->GetId());
221 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoBooleanAttributes(
231 node
->IsScrollable(),
233 node
->IsVisibleToUser());
234 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoClassName(
236 base::android::ConvertUTF8ToJavaString(env
, node
->GetClassName()).obj());
237 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoContentDescription(
239 base::android::ConvertUTF16ToJavaString(env
, node
->GetText()).obj(),
242 gfx::Rect absolute_rect
= node
->GetLocalBoundsRect();
243 gfx::Rect parent_relative_rect
= absolute_rect
;
244 if (node
->GetParent()) {
245 gfx::Rect parent_rect
= node
->GetParent()->GetLocalBoundsRect();
246 parent_relative_rect
.Offset(-parent_rect
.OffsetFromOrigin());
248 bool is_root
= node
->GetParent() == NULL
;
249 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoLocation(
252 absolute_rect
.x(), absolute_rect
.y(),
253 parent_relative_rect
.x(), parent_relative_rect
.y(),
254 absolute_rect
.width(), absolute_rect
.height(),
258 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoKitKatAttributes(
260 node
->CanOpenPopup(),
261 node
->IsContentInvalid(),
262 node
->IsDismissable(),
264 node
->AndroidInputType(),
265 node
->AndroidLiveRegionType());
266 if (node
->IsCollection()) {
267 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoCollectionInfo(
271 node
->IsHierarchical());
273 if (node
->IsCollectionItem() || node
->IsHeading()) {
274 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoCollectionItemInfo(
282 if (node
->IsRangeType()) {
283 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoRangeInfo(
285 node
->AndroidRangeType(),
288 node
->RangeCurrentValue());
294 jboolean
BrowserAccessibilityManagerAndroid::PopulateAccessibilityEvent(
295 JNIEnv
* env
, jobject obj
, jobject event
, jint id
, jint event_type
) {
296 BrowserAccessibilityAndroid
* node
= static_cast<BrowserAccessibilityAndroid
*>(
301 Java_BrowserAccessibilityManager_setAccessibilityEventBooleanAttributes(
306 node
->IsScrollable());
307 Java_BrowserAccessibilityManager_setAccessibilityEventClassName(
309 base::android::ConvertUTF8ToJavaString(env
, node
->GetClassName()).obj());
310 Java_BrowserAccessibilityManager_setAccessibilityEventListAttributes(
312 node
->GetItemIndex(),
313 node
->GetItemCount());
314 Java_BrowserAccessibilityManager_setAccessibilityEventScrollAttributes(
318 node
->GetMaxScrollX(),
319 node
->GetMaxScrollY());
321 switch (event_type
) {
322 case ANDROID_ACCESSIBILITY_EVENT_TYPE_VIEW_TEXT_CHANGED
:
323 Java_BrowserAccessibilityManager_setAccessibilityEventTextChangedAttrs(
325 node
->GetTextChangeFromIndex(),
326 node
->GetTextChangeAddedCount(),
327 node
->GetTextChangeRemovedCount(),
328 base::android::ConvertUTF16ToJavaString(
329 env
, node
->GetTextChangeBeforeText()).obj(),
330 base::android::ConvertUTF16ToJavaString(env
, node
->GetText()).obj());
332 case ANDROID_ACCESSIBILITY_EVENT_TYPE_VIEW_TEXT_SELECTION_CHANGED
:
333 Java_BrowserAccessibilityManager_setAccessibilityEventSelectionAttrs(
335 node
->GetSelectionStart(),
336 node
->GetSelectionEnd(),
337 node
->GetEditableTextLength(),
338 base::android::ConvertUTF16ToJavaString(env
, node
->GetText()).obj());
344 // Backwards-compatible fallback for new KitKat APIs.
345 Java_BrowserAccessibilityManager_setAccessibilityEventKitKatAttributes(
347 node
->CanOpenPopup(),
348 node
->IsContentInvalid(),
349 node
->IsDismissable(),
351 node
->AndroidInputType(),
352 node
->AndroidLiveRegionType());
353 if (node
->IsCollection()) {
354 Java_BrowserAccessibilityManager_setAccessibilityEventCollectionInfo(
358 node
->IsHierarchical());
360 if (node
->IsHeading()) {
361 Java_BrowserAccessibilityManager_setAccessibilityEventHeadingFlag(
362 env
, obj
, event
, true);
364 if (node
->IsCollectionItem()) {
365 Java_BrowserAccessibilityManager_setAccessibilityEventCollectionItemInfo(
372 if (node
->IsRangeType()) {
373 Java_BrowserAccessibilityManager_setAccessibilityEventRangeInfo(
375 node
->AndroidRangeType(),
378 node
->RangeCurrentValue());
384 void BrowserAccessibilityManagerAndroid::Click(
385 JNIEnv
* env
, jobject obj
, jint id
) {
386 BrowserAccessibility
* node
= GetFromID(id
);
388 DoDefaultAction(*node
);
391 void BrowserAccessibilityManagerAndroid::Focus(
392 JNIEnv
* env
, jobject obj
, jint id
) {
393 BrowserAccessibility
* node
= GetFromID(id
);
395 SetFocus(node
, true);
398 void BrowserAccessibilityManagerAndroid::Blur(JNIEnv
* env
, jobject obj
) {
399 SetFocus(GetRoot(), true);
402 void BrowserAccessibilityManagerAndroid::ScrollToMakeNodeVisible(
403 JNIEnv
* env
, jobject obj
, jint id
) {
404 BrowserAccessibility
* node
= GetFromID(id
);
406 ScrollToMakeVisible(*node
, gfx::Rect(node
->GetLocation().size()));
409 void BrowserAccessibilityManagerAndroid::HandleHoverEvent(
410 BrowserAccessibility
* node
) {
411 JNIEnv
* env
= AttachCurrentThread();
412 ScopedJavaLocalRef
<jobject
> obj
= java_ref_
.get(env
);
416 BrowserAccessibilityAndroid
* ancestor
=
417 static_cast<BrowserAccessibilityAndroid
*>(node
->GetParent());
418 while (ancestor
&& ancestor
!= GetRoot()) {
419 if (ancestor
->PlatformIsLeaf() ||
420 (ancestor
->IsFocusable() && !ancestor
->HasFocusableChild())) {
422 // Don't break - we want the highest ancestor that's focusable or a
425 ancestor
= static_cast<BrowserAccessibilityAndroid
*>(ancestor
->GetParent());
428 Java_BrowserAccessibilityManager_handleHover(
429 env
, obj
.obj(), node
->GetId());
432 jint
BrowserAccessibilityManagerAndroid::FindElementType(
433 JNIEnv
* env
, jobject obj
, jint start_id
, jstring element_type_str
,
435 BrowserAccessibility
* node
= GetFromID(start_id
);
439 AndroidHtmlElementType element_type
= HtmlElementTypeFromString(
440 base::android::ConvertJavaStringToUTF16(env
, element_type_str
));
442 node
= forwards
? NextInTreeOrder(node
) : PreviousInTreeOrder(node
);
444 switch(element_type
) {
445 case HTML_ELEMENT_TYPE_SECTION
:
446 if (node
->GetRole() == ui::AX_ROLE_ARTICLE
||
447 node
->GetRole() == ui::AX_ROLE_APPLICATION
||
448 node
->GetRole() == ui::AX_ROLE_BANNER
||
449 node
->GetRole() == ui::AX_ROLE_COMPLEMENTARY
||
450 node
->GetRole() == ui::AX_ROLE_CONTENT_INFO
||
451 node
->GetRole() == ui::AX_ROLE_HEADING
||
452 node
->GetRole() == ui::AX_ROLE_MAIN
||
453 node
->GetRole() == ui::AX_ROLE_NAVIGATION
||
454 node
->GetRole() == ui::AX_ROLE_SEARCH
||
455 node
->GetRole() == ui::AX_ROLE_REGION
) {
456 return node
->GetId();
459 case HTML_ELEMENT_TYPE_LIST
:
460 if (node
->GetRole() == ui::AX_ROLE_LIST
||
461 node
->GetRole() == ui::AX_ROLE_GRID
||
462 node
->GetRole() == ui::AX_ROLE_TABLE
||
463 node
->GetRole() == ui::AX_ROLE_TREE
) {
464 return node
->GetId();
467 case HTML_ELEMENT_TYPE_CONTROL
:
468 if (static_cast<BrowserAccessibilityAndroid
*>(node
)->IsFocusable())
469 return node
->GetId();
471 case HTML_ELEMENT_TYPE_ANY
:
472 // In theory, the API says that an accessibility service could
473 // jump to an element by element name, like 'H1' or 'P'. This isn't
474 // currently used by any accessibility service, and we think it's
475 // better to keep them high-level like 'SECTION' or 'CONTROL', so we
476 // just fall back on linear navigation when we don't recognize the
478 if (static_cast<BrowserAccessibilityAndroid
*>(node
)->IsClickable())
479 return node
->GetId();
483 node
= forwards
? NextInTreeOrder(node
) : PreviousInTreeOrder(node
);
489 void BrowserAccessibilityManagerAndroid::OnRootChanged(ui::AXNode
* new_root
) {
490 JNIEnv
* env
= AttachCurrentThread();
491 ScopedJavaLocalRef
<jobject
> obj
= java_ref_
.get(env
);
495 Java_BrowserAccessibilityManager_handleNavigate(env
, obj
.obj());
499 BrowserAccessibilityManagerAndroid::UseRootScrollOffsetsWhenComputingBounds() {
500 // The Java layer handles the root scroll offset.
504 bool RegisterBrowserAccessibilityManager(JNIEnv
* env
) {
505 return RegisterNativesImpl(env
);
508 } // namespace content