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_READ_ONLY;
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 void BrowserAccessibilityManagerAndroid::ScrollToMakeNodeVisible(
383 JNIEnv* env, jobject obj, jint id) {
384 BrowserAccessibility* node = GetFromRendererID(id);
386 ScrollToMakeVisible(*node, gfx::Rect(node->location().size()));
389 BrowserAccessibility* BrowserAccessibilityManagerAndroid::FuzzyHitTest(
390 int x, int y, BrowserAccessibility* start_node) {
391 BrowserAccessibility* nearest_node = NULL;
392 int min_distance = INT_MAX;
393 FuzzyHitTestImpl(x, y, start_node, &nearest_node, &min_distance);
398 void BrowserAccessibilityManagerAndroid::FuzzyHitTestImpl(
399 int x, int y, BrowserAccessibility* start_node,
400 BrowserAccessibility** nearest_candidate, int* nearest_distance) {
401 BrowserAccessibilityAndroid* node =
402 static_cast<BrowserAccessibilityAndroid*>(start_node);
403 int distance = CalculateDistanceSquared(x, y, node);
405 if (node->IsFocusable()) {
406 if (distance < *nearest_distance) {
407 *nearest_candidate = node;
408 *nearest_distance = distance;
410 // Don't examine any more children of focusable node
411 // TODO(aboxhall): what about focusable children?
415 if (!node->GetText().empty()) {
416 if (distance < *nearest_distance) {
417 *nearest_candidate = node;
418 *nearest_distance = distance;
423 for (uint32 i = 0; i < node->PlatformChildCount(); i++) {
424 BrowserAccessibility* child = node->PlatformGetChild(i);
425 FuzzyHitTestImpl(x, y, child, nearest_candidate, nearest_distance);
430 int BrowserAccessibilityManagerAndroid::CalculateDistanceSquared(
431 int x, int y, BrowserAccessibility* node) {
432 gfx::Rect node_bounds = node->GetLocalBoundsRect();
433 int nearest_x = Clamp(x, node_bounds.x(), node_bounds.right());
434 int nearest_y = Clamp(y, node_bounds.y(), node_bounds.bottom());
435 int dx = std::abs(x - nearest_x);
436 int dy = std::abs(y - nearest_y);
437 return dx * dx + dy * dy;
440 void BrowserAccessibilityManagerAndroid::NotifyRootChanged() {
441 JNIEnv* env = AttachCurrentThread();
442 ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
446 Java_BrowserAccessibilityManager_handleNavigate(env, obj.obj());
450 BrowserAccessibilityManagerAndroid::UseRootScrollOffsetsWhenComputingBounds() {
451 // The Java layer handles the root scroll offset.
455 bool RegisterBrowserAccessibilityManager(JNIEnv* env) {
456 return RegisterNativesImpl(env);
459 } // namespace content