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 AccessibilityNodeData& 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 AccessibilityNodeData& 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 AccessibilityNodeData BrowserAccessibilityManagerAndroid::GetEmptyDocument() {
77 AccessibilityNodeData empty_document;
78 empty_document.id = 0;
79 empty_document.role = WebKit::WebAXRoleRootWebArea;
80 empty_document.state = 1 << WebKit::WebAXStateReadonly;
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<jint>(this), content_view_core.obj()).obj());
95 void BrowserAccessibilityManagerAndroid::NotifyAccessibilityEvent(
96 WebKit::WebAXEvent event_type,
97 BrowserAccessibility* node) {
98 JNIEnv* env = AttachCurrentThread();
99 ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
103 // Always send AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED to notify
104 // the Android system that the accessibility hierarchy rooted at this
106 Java_BrowserAccessibilityManager_handleContentChanged(
107 env, obj.obj(), node->renderer_id());
109 switch (event_type) {
110 case WebKit::WebAXEventLoadComplete:
111 Java_BrowserAccessibilityManager_handlePageLoaded(
112 env, obj.obj(), focus_->renderer_id());
114 case WebKit::WebAXEventFocus:
115 Java_BrowserAccessibilityManager_handleFocusChanged(
116 env, obj.obj(), node->renderer_id());
118 case WebKit::WebAXEventCheckedStateChanged:
119 Java_BrowserAccessibilityManager_handleCheckStateChanged(
120 env, obj.obj(), node->renderer_id());
122 case WebKit::WebAXEventScrolledToAnchor:
123 Java_BrowserAccessibilityManager_handleScrolledToAnchor(
124 env, obj.obj(), node->renderer_id());
126 case WebKit::WebAXEventAlert:
127 // An alert is a special case of live region. Fall through to the
128 // next case to handle it.
129 case WebKit::WebAXEventShow: {
130 // This event is fired when an object appears in a live region.
132 BrowserAccessibilityAndroid* android_node =
133 static_cast<BrowserAccessibilityAndroid*>(node);
134 Java_BrowserAccessibilityManager_announceLiveRegionText(
136 base::android::ConvertUTF16ToJavaString(
137 env, android_node->GetText()).obj());
140 case WebKit::WebAXEventSelectedTextChanged:
141 Java_BrowserAccessibilityManager_handleTextSelectionChanged(
142 env, obj.obj(), node->renderer_id());
144 case WebKit::WebAXEventChildrenChanged:
145 case WebKit::WebAXEventTextChanged:
146 case WebKit::WebAXEventValueChanged:
147 if (node->IsEditableText()) {
148 Java_BrowserAccessibilityManager_handleEditableTextChanged(
149 env, obj.obj(), node->renderer_id());
153 // There are some notifications that aren't meaningful on Android.
154 // It's okay to skip them.
159 jint BrowserAccessibilityManagerAndroid::GetRootId(JNIEnv* env, jobject obj) {
160 return static_cast<jint>(root_->renderer_id());
163 jint BrowserAccessibilityManagerAndroid::HitTest(
164 JNIEnv* env, jobject obj, jint x, jint y) {
165 BrowserAccessibilityAndroid* result =
166 static_cast<BrowserAccessibilityAndroid*>(
167 root_->BrowserAccessibilityForPoint(gfx::Point(x, y)));
170 return root_->renderer_id();
172 if (result->IsFocusable())
173 return result->renderer_id();
175 // Examine the children of |result| to find the nearest accessibility focus
177 BrowserAccessibility* nearest_node = FuzzyHitTest(x, y, result);
179 return nearest_node->renderer_id();
181 return root_->renderer_id();
184 jboolean BrowserAccessibilityManagerAndroid::PopulateAccessibilityNodeInfo(
185 JNIEnv* env, jobject obj, jobject info, jint id) {
186 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
187 GetFromRendererID(id));
191 if (node->parent()) {
192 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoParent(
193 env, obj, info, node->parent()->renderer_id());
195 for (unsigned i = 0; i < node->PlatformChildCount(); ++i) {
196 Java_BrowserAccessibilityManager_addAccessibilityNodeInfoChild(
197 env, obj, info, node->children()[i]->renderer_id());
199 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoBooleanAttributes(
209 node->IsScrollable(),
211 node->IsVisibleToUser());
212 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoStringAttributes(
214 base::android::ConvertUTF8ToJavaString(env, node->GetClassName()).obj(),
215 base::android::ConvertUTF16ToJavaString(env, node->GetText()).obj());
217 gfx::Rect absolute_rect = node->GetLocalBoundsRect();
218 gfx::Rect parent_relative_rect = absolute_rect;
219 if (node->parent()) {
220 gfx::Rect parent_rect = node->parent()->GetLocalBoundsRect();
221 parent_relative_rect.Offset(-parent_rect.OffsetFromOrigin());
223 bool is_root = node->parent() == NULL;
224 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoLocation(
226 absolute_rect.x(), absolute_rect.y(),
227 parent_relative_rect.x(), parent_relative_rect.y(),
228 absolute_rect.width(), absolute_rect.height(),
234 jboolean BrowserAccessibilityManagerAndroid::PopulateAccessibilityEvent(
235 JNIEnv* env, jobject obj, jobject event, jint id, jint event_type) {
236 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
237 GetFromRendererID(id));
241 Java_BrowserAccessibilityManager_setAccessibilityEventBooleanAttributes(
246 node->IsScrollable());
247 Java_BrowserAccessibilityManager_setAccessibilityEventClassName(
249 base::android::ConvertUTF8ToJavaString(env, node->GetClassName()).obj());
250 Java_BrowserAccessibilityManager_setAccessibilityEventListAttributes(
252 node->GetItemIndex(),
253 node->GetItemCount());
254 Java_BrowserAccessibilityManager_setAccessibilityEventScrollAttributes(
258 node->GetMaxScrollX(),
259 node->GetMaxScrollY());
261 switch (event_type) {
262 case ANDROID_ACCESSIBILITY_EVENT_TYPE_VIEW_TEXT_CHANGED:
263 Java_BrowserAccessibilityManager_setAccessibilityEventTextChangedAttrs(
265 node->GetTextChangeFromIndex(),
266 node->GetTextChangeAddedCount(),
267 node->GetTextChangeRemovedCount(),
268 base::android::ConvertUTF16ToJavaString(
269 env, node->GetTextChangeBeforeText()).obj(),
270 base::android::ConvertUTF16ToJavaString(env, node->GetText()).obj());
272 case ANDROID_ACCESSIBILITY_EVENT_TYPE_VIEW_TEXT_SELECTION_CHANGED:
273 Java_BrowserAccessibilityManager_setAccessibilityEventSelectionAttrs(
275 node->GetSelectionStart(),
276 node->GetSelectionEnd(),
277 node->GetEditableTextLength(),
278 base::android::ConvertUTF16ToJavaString(env, node->GetText()).obj());
287 void BrowserAccessibilityManagerAndroid::Click(
288 JNIEnv* env, jobject obj, jint id) {
289 BrowserAccessibility* node = GetFromRendererID(id);
291 DoDefaultAction(*node);
294 void BrowserAccessibilityManagerAndroid::Focus(
295 JNIEnv* env, jobject obj, jint id) {
296 BrowserAccessibility* node = GetFromRendererID(id);
298 SetFocus(node, true);
301 void BrowserAccessibilityManagerAndroid::Blur(JNIEnv* env, jobject obj) {
302 SetFocus(root_, true);
305 BrowserAccessibility* BrowserAccessibilityManagerAndroid::FuzzyHitTest(
306 int x, int y, BrowserAccessibility* start_node) {
307 BrowserAccessibility* nearest_node = NULL;
308 int min_distance = INT_MAX;
309 FuzzyHitTestImpl(x, y, start_node, &nearest_node, &min_distance);
314 void BrowserAccessibilityManagerAndroid::FuzzyHitTestImpl(
315 int x, int y, BrowserAccessibility* start_node,
316 BrowserAccessibility** nearest_candidate, int* nearest_distance) {
317 BrowserAccessibilityAndroid* node =
318 static_cast<BrowserAccessibilityAndroid*>(start_node);
319 int distance = CalculateDistanceSquared(x, y, node);
321 if (node->IsFocusable()) {
322 if (distance < *nearest_distance) {
323 *nearest_candidate = node;
324 *nearest_distance = distance;
326 // Don't examine any more children of focusable node
327 // TODO(aboxhall): what about focusable children?
331 if (!node->GetText().empty()) {
332 if (distance < *nearest_distance) {
333 *nearest_candidate = node;
334 *nearest_distance = distance;
339 for (uint32 i = 0; i < node->PlatformChildCount(); i++) {
340 BrowserAccessibility* child = node->PlatformGetChild(i);
341 FuzzyHitTestImpl(x, y, child, nearest_candidate, nearest_distance);
346 int BrowserAccessibilityManagerAndroid::CalculateDistanceSquared(
347 int x, int y, BrowserAccessibility* node) {
348 gfx::Rect node_bounds = node->GetLocalBoundsRect();
349 int nearest_x = Clamp(x, node_bounds.x(), node_bounds.right());
350 int nearest_y = Clamp(y, node_bounds.y(), node_bounds.bottom());
351 int dx = std::abs(x - nearest_x);
352 int dy = std::abs(y - nearest_y);
353 return dx * dx + dy * dy;
356 void BrowserAccessibilityManagerAndroid::NotifyRootChanged() {
357 JNIEnv* env = AttachCurrentThread();
358 ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
362 Java_BrowserAccessibilityManager_handleNavigate(env, obj.obj());
366 BrowserAccessibilityManagerAndroid::UseRootScrollOffsetsWhenComputingBounds() {
367 // The Java layer handles the root scroll offset.
371 bool RegisterBrowserAccessibilityManager(JNIEnv* env) {
372 return RegisterNativesImpl(env);
375 } // namespace content