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 // Restricts |val| to the range [min, max].
30 int Clamp(int val
, int min
, int max
) {
31 return std::min(std::max(val
, min
), max
);
34 } // anonymous namespace
38 namespace aria_strings
{
39 const char kAriaLivePolite
[] = "polite";
40 const char kAriaLiveAssertive
[] = "assertive";
44 BrowserAccessibilityManager
* BrowserAccessibilityManager::Create(
45 const ui::AXNodeData
& src
,
46 BrowserAccessibilityDelegate
* delegate
,
47 BrowserAccessibilityFactory
* factory
) {
48 return new BrowserAccessibilityManagerAndroid(ScopedJavaLocalRef
<jobject
>(),
49 src
, delegate
, factory
);
52 BrowserAccessibilityManagerAndroid
*
53 BrowserAccessibilityManager::ToBrowserAccessibilityManagerAndroid() {
54 return static_cast<BrowserAccessibilityManagerAndroid
*>(this);
57 BrowserAccessibilityManagerAndroid::BrowserAccessibilityManagerAndroid(
58 ScopedJavaLocalRef
<jobject
> content_view_core
,
59 const ui::AXNodeData
& src
,
60 BrowserAccessibilityDelegate
* delegate
,
61 BrowserAccessibilityFactory
* factory
)
62 : BrowserAccessibilityManager(src
, delegate
, factory
) {
63 SetContentViewCore(content_view_core
);
66 BrowserAccessibilityManagerAndroid::~BrowserAccessibilityManagerAndroid() {
67 JNIEnv
* env
= AttachCurrentThread();
68 ScopedJavaLocalRef
<jobject
> obj
= java_ref_
.get(env
);
72 Java_BrowserAccessibilityManager_onNativeObjectDestroyed(env
, obj
.obj());
76 ui::AXNodeData
BrowserAccessibilityManagerAndroid::GetEmptyDocument() {
77 ui::AXNodeData empty_document
;
78 empty_document
.id
= 0;
79 empty_document
.role
= ui::AX_ROLE_ROOT_WEB_AREA
;
80 empty_document
.state
= 1 << ui::AX_STATE_READONLY
;
81 return empty_document
;
84 void BrowserAccessibilityManagerAndroid::SetContentViewCore(
85 ScopedJavaLocalRef
<jobject
> content_view_core
) {
86 if (content_view_core
.is_null())
89 JNIEnv
* env
= AttachCurrentThread();
90 java_ref_
= JavaObjectWeakGlobalRef(
91 env
, Java_BrowserAccessibilityManager_create(
92 env
, reinterpret_cast<intptr_t>(this),
93 content_view_core
.obj()).obj());
96 void BrowserAccessibilityManagerAndroid::NotifyAccessibilityEvent(
97 ui::AXEvent event_type
,
98 BrowserAccessibility
* node
) {
99 JNIEnv
* env
= AttachCurrentThread();
100 ScopedJavaLocalRef
<jobject
> obj
= java_ref_
.get(env
);
104 if (event_type
== ui::AX_EVENT_HIDE
)
107 // Always send AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED to notify
108 // the Android system that the accessibility hierarchy rooted at this
110 Java_BrowserAccessibilityManager_handleContentChanged(
111 env
, obj
.obj(), node
->renderer_id());
113 switch (event_type
) {
114 case ui::AX_EVENT_LOAD_COMPLETE
:
115 Java_BrowserAccessibilityManager_handlePageLoaded(
116 env
, obj
.obj(), focus_
->renderer_id());
118 case ui::AX_EVENT_FOCUS
:
119 Java_BrowserAccessibilityManager_handleFocusChanged(
120 env
, obj
.obj(), node
->renderer_id());
122 case ui::AX_EVENT_CHECKED_STATE_CHANGED
:
123 Java_BrowserAccessibilityManager_handleCheckStateChanged(
124 env
, obj
.obj(), node
->renderer_id());
126 case ui::AX_EVENT_SCROLLED_TO_ANCHOR
:
127 Java_BrowserAccessibilityManager_handleScrolledToAnchor(
128 env
, obj
.obj(), node
->renderer_id());
130 case ui::AX_EVENT_ALERT
:
131 // An alert is a special case of live region. Fall through to the
132 // next case to handle it.
133 case ui::AX_EVENT_SHOW
: {
134 // This event is fired when an object appears in a live region.
136 BrowserAccessibilityAndroid
* android_node
=
137 static_cast<BrowserAccessibilityAndroid
*>(node
);
138 Java_BrowserAccessibilityManager_announceLiveRegionText(
140 base::android::ConvertUTF16ToJavaString(
141 env
, android_node
->GetText()).obj());
144 case ui::AX_EVENT_SELECTED_TEXT_CHANGED
:
145 Java_BrowserAccessibilityManager_handleTextSelectionChanged(
146 env
, obj
.obj(), node
->renderer_id());
148 case ui::AX_EVENT_CHILDREN_CHANGED
:
149 case ui::AX_EVENT_TEXT_CHANGED
:
150 case ui::AX_EVENT_VALUE_CHANGED
:
151 if (node
->IsEditableText()) {
152 Java_BrowserAccessibilityManager_handleEditableTextChanged(
153 env
, obj
.obj(), node
->renderer_id());
157 // There are some notifications that aren't meaningful on Android.
158 // It's okay to skip them.
163 jint
BrowserAccessibilityManagerAndroid::GetRootId(JNIEnv
* env
, jobject obj
) {
164 return static_cast<jint
>(root_
->renderer_id());
167 jboolean
BrowserAccessibilityManagerAndroid::IsNodeValid(
168 JNIEnv
* env
, jobject obj
, jint id
) {
169 return GetFromRendererID(id
) != NULL
;
172 jint
BrowserAccessibilityManagerAndroid::HitTest(
173 JNIEnv
* env
, jobject obj
, jint x
, jint y
) {
174 BrowserAccessibilityAndroid
* result
=
175 static_cast<BrowserAccessibilityAndroid
*>(
176 root_
->BrowserAccessibilityForPoint(gfx::Point(x
, y
)));
179 return root_
->renderer_id();
181 if (result
->IsFocusable())
182 return result
->renderer_id();
184 // Examine the children of |result| to find the nearest accessibility focus
186 BrowserAccessibility
* nearest_node
= FuzzyHitTest(x
, y
, result
);
188 return nearest_node
->renderer_id();
190 return root_
->renderer_id();
193 jboolean
BrowserAccessibilityManagerAndroid::PopulateAccessibilityNodeInfo(
194 JNIEnv
* env
, jobject obj
, jobject info
, jint id
) {
195 BrowserAccessibilityAndroid
* node
= static_cast<BrowserAccessibilityAndroid
*>(
196 GetFromRendererID(id
));
200 if (node
->parent()) {
201 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoParent(
202 env
, obj
, info
, node
->parent()->renderer_id());
204 for (unsigned i
= 0; i
< node
->PlatformChildCount(); ++i
) {
205 Java_BrowserAccessibilityManager_addAccessibilityNodeInfoChild(
206 env
, obj
, info
, node
->children()[i
]->renderer_id());
208 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoBooleanAttributes(
218 node
->IsScrollable(),
220 node
->IsVisibleToUser());
221 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoStringAttributes(
223 base::android::ConvertUTF8ToJavaString(env
, node
->GetClassName()).obj(),
224 base::android::ConvertUTF16ToJavaString(env
, node
->GetText()).obj());
226 gfx::Rect absolute_rect
= node
->GetLocalBoundsRect();
227 gfx::Rect parent_relative_rect
= absolute_rect
;
228 if (node
->parent()) {
229 gfx::Rect parent_rect
= node
->parent()->GetLocalBoundsRect();
230 parent_relative_rect
.Offset(-parent_rect
.OffsetFromOrigin());
232 bool is_root
= node
->parent() == NULL
;
233 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoLocation(
235 absolute_rect
.x(), absolute_rect
.y(),
236 parent_relative_rect
.x(), parent_relative_rect
.y(),
237 absolute_rect
.width(), absolute_rect
.height(),
241 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoKitKatAttributes(
243 node
->CanOpenPopup(),
244 node
->IsContentInvalid(),
245 node
->IsDismissable(),
247 node
->AndroidInputType(),
248 node
->AndroidLiveRegionType());
249 if (node
->IsCollection()) {
250 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoCollectionInfo(
254 node
->IsHierarchical());
256 if (node
->IsCollectionItem() || node
->IsHeading()) {
257 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoCollectionItemInfo(
265 if (node
->IsRangeType()) {
266 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoRangeInfo(
268 node
->AndroidRangeType(),
271 node
->RangeCurrentValue());
277 jboolean
BrowserAccessibilityManagerAndroid::PopulateAccessibilityEvent(
278 JNIEnv
* env
, jobject obj
, jobject event
, jint id
, jint event_type
) {
279 BrowserAccessibilityAndroid
* node
= static_cast<BrowserAccessibilityAndroid
*>(
280 GetFromRendererID(id
));
284 Java_BrowserAccessibilityManager_setAccessibilityEventBooleanAttributes(
289 node
->IsScrollable());
290 Java_BrowserAccessibilityManager_setAccessibilityEventClassName(
292 base::android::ConvertUTF8ToJavaString(env
, node
->GetClassName()).obj());
293 Java_BrowserAccessibilityManager_setAccessibilityEventListAttributes(
295 node
->GetItemIndex(),
296 node
->GetItemCount());
297 Java_BrowserAccessibilityManager_setAccessibilityEventScrollAttributes(
301 node
->GetMaxScrollX(),
302 node
->GetMaxScrollY());
304 switch (event_type
) {
305 case ANDROID_ACCESSIBILITY_EVENT_TYPE_VIEW_TEXT_CHANGED
:
306 Java_BrowserAccessibilityManager_setAccessibilityEventTextChangedAttrs(
308 node
->GetTextChangeFromIndex(),
309 node
->GetTextChangeAddedCount(),
310 node
->GetTextChangeRemovedCount(),
311 base::android::ConvertUTF16ToJavaString(
312 env
, node
->GetTextChangeBeforeText()).obj(),
313 base::android::ConvertUTF16ToJavaString(env
, node
->GetText()).obj());
315 case ANDROID_ACCESSIBILITY_EVENT_TYPE_VIEW_TEXT_SELECTION_CHANGED
:
316 Java_BrowserAccessibilityManager_setAccessibilityEventSelectionAttrs(
318 node
->GetSelectionStart(),
319 node
->GetSelectionEnd(),
320 node
->GetEditableTextLength(),
321 base::android::ConvertUTF16ToJavaString(env
, node
->GetText()).obj());
327 // Backwards-compatible fallback for new KitKat APIs.
328 Java_BrowserAccessibilityManager_setAccessibilityEventKitKatAttributes(
330 node
->CanOpenPopup(),
331 node
->IsContentInvalid(),
332 node
->IsDismissable(),
334 node
->AndroidInputType(),
335 node
->AndroidLiveRegionType());
336 if (node
->IsCollection()) {
337 Java_BrowserAccessibilityManager_setAccessibilityEventCollectionInfo(
341 node
->IsHierarchical());
343 if (node
->IsCollectionItem() || node
->IsHeading()) {
344 Java_BrowserAccessibilityManager_setAccessibilityEventCollectionItemInfo(
352 if (node
->IsRangeType()) {
353 Java_BrowserAccessibilityManager_setAccessibilityEventRangeInfo(
355 node
->AndroidRangeType(),
358 node
->RangeCurrentValue());
364 void BrowserAccessibilityManagerAndroid::Click(
365 JNIEnv
* env
, jobject obj
, jint id
) {
366 BrowserAccessibility
* node
= GetFromRendererID(id
);
368 DoDefaultAction(*node
);
371 void BrowserAccessibilityManagerAndroid::Focus(
372 JNIEnv
* env
, jobject obj
, jint id
) {
373 BrowserAccessibility
* node
= GetFromRendererID(id
);
375 SetFocus(node
, true);
378 void BrowserAccessibilityManagerAndroid::Blur(JNIEnv
* env
, jobject obj
) {
379 SetFocus(root_
, true);
382 BrowserAccessibility
* BrowserAccessibilityManagerAndroid::FuzzyHitTest(
383 int x
, int y
, BrowserAccessibility
* start_node
) {
384 BrowserAccessibility
* nearest_node
= NULL
;
385 int min_distance
= INT_MAX
;
386 FuzzyHitTestImpl(x
, y
, start_node
, &nearest_node
, &min_distance
);
391 void BrowserAccessibilityManagerAndroid::FuzzyHitTestImpl(
392 int x
, int y
, BrowserAccessibility
* start_node
,
393 BrowserAccessibility
** nearest_candidate
, int* nearest_distance
) {
394 BrowserAccessibilityAndroid
* node
=
395 static_cast<BrowserAccessibilityAndroid
*>(start_node
);
396 int distance
= CalculateDistanceSquared(x
, y
, node
);
398 if (node
->IsFocusable()) {
399 if (distance
< *nearest_distance
) {
400 *nearest_candidate
= node
;
401 *nearest_distance
= distance
;
403 // Don't examine any more children of focusable node
404 // TODO(aboxhall): what about focusable children?
408 if (!node
->GetText().empty()) {
409 if (distance
< *nearest_distance
) {
410 *nearest_candidate
= node
;
411 *nearest_distance
= distance
;
416 for (uint32 i
= 0; i
< node
->PlatformChildCount(); i
++) {
417 BrowserAccessibility
* child
= node
->PlatformGetChild(i
);
418 FuzzyHitTestImpl(x
, y
, child
, nearest_candidate
, nearest_distance
);
423 int BrowserAccessibilityManagerAndroid::CalculateDistanceSquared(
424 int x
, int y
, BrowserAccessibility
* node
) {
425 gfx::Rect node_bounds
= node
->GetLocalBoundsRect();
426 int nearest_x
= Clamp(x
, node_bounds
.x(), node_bounds
.right());
427 int nearest_y
= Clamp(y
, node_bounds
.y(), node_bounds
.bottom());
428 int dx
= std::abs(x
- nearest_x
);
429 int dy
= std::abs(y
- nearest_y
);
430 return dx
* dx
+ dy
* dy
;
433 void BrowserAccessibilityManagerAndroid::NotifyRootChanged() {
434 JNIEnv
* env
= AttachCurrentThread();
435 ScopedJavaLocalRef
<jobject
> obj
= java_ref_
.get(env
);
439 Java_BrowserAccessibilityManager_handleNavigate(env
, obj
.obj());
443 BrowserAccessibilityManagerAndroid::UseRootScrollOffsetsWhenComputingBounds() {
444 // The Java layer handles the root scroll offset.
448 bool RegisterBrowserAccessibilityManager(JNIEnv
* env
) {
449 return RegisterNativesImpl(env
);
452 } // namespace content