- add sources.
[platform/framework/web/crosswalk.git] / src / content / browser / accessibility / browser_accessibility_manager_android.cc
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.
4
5 #include "content/browser/accessibility/browser_accessibility_manager_android.h"
6
7 #include <cmath>
8
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"
17
18 using base::android::AttachCurrentThread;
19 using base::android::ScopedJavaLocalRef;
20
21 namespace {
22
23 // These are enums from android.view.accessibility.AccessibilityEvent in Java:
24 enum {
25   ANDROID_ACCESSIBILITY_EVENT_TYPE_VIEW_TEXT_CHANGED = 16,
26   ANDROID_ACCESSIBILITY_EVENT_TYPE_VIEW_TEXT_SELECTION_CHANGED = 8192
27 };
28
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);
32 }
33
34 }  // anonymous namespace
35
36 namespace content {
37
38 namespace aria_strings {
39   const char kAriaLivePolite[] = "polite";
40   const char kAriaLiveAssertive[] = "assertive";
41 }
42
43 // static
44 BrowserAccessibilityManager* BrowserAccessibilityManager::Create(
45     const AccessibilityNodeData& src,
46     BrowserAccessibilityDelegate* delegate,
47     BrowserAccessibilityFactory* factory) {
48   return new BrowserAccessibilityManagerAndroid(ScopedJavaLocalRef<jobject>(),
49                                                 src, delegate, factory);
50 }
51
52 BrowserAccessibilityManagerAndroid*
53 BrowserAccessibilityManager::ToBrowserAccessibilityManagerAndroid() {
54   return static_cast<BrowserAccessibilityManagerAndroid*>(this);
55 }
56
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);
64 }
65
66 BrowserAccessibilityManagerAndroid::~BrowserAccessibilityManagerAndroid() {
67   JNIEnv* env = AttachCurrentThread();
68   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
69   if (obj.is_null())
70     return;
71
72   Java_BrowserAccessibilityManager_onNativeObjectDestroyed(env, obj.obj());
73 }
74
75 // static
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;
82 }
83
84 void BrowserAccessibilityManagerAndroid::SetContentViewCore(
85     ScopedJavaLocalRef<jobject> content_view_core) {
86   if (content_view_core.is_null())
87     return;
88
89   JNIEnv* env = AttachCurrentThread();
90   java_ref_ = JavaObjectWeakGlobalRef(
91       env, Java_BrowserAccessibilityManager_create(
92           env, reinterpret_cast<jint>(this), content_view_core.obj()).obj());
93 }
94
95 void BrowserAccessibilityManagerAndroid::NotifyAccessibilityEvent(
96     WebKit::WebAXEvent event_type,
97     BrowserAccessibility* node) {
98   JNIEnv* env = AttachCurrentThread();
99   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
100   if (obj.is_null())
101     return;
102
103   // Always send AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED to notify
104   // the Android system that the accessibility hierarchy rooted at this
105   // node has changed.
106   Java_BrowserAccessibilityManager_handleContentChanged(
107       env, obj.obj(), node->renderer_id());
108
109   switch (event_type) {
110     case WebKit::WebAXEventLoadComplete:
111       Java_BrowserAccessibilityManager_handlePageLoaded(
112           env, obj.obj(), focus_->renderer_id());
113       break;
114     case WebKit::WebAXEventFocus:
115       Java_BrowserAccessibilityManager_handleFocusChanged(
116           env, obj.obj(), node->renderer_id());
117       break;
118     case WebKit::WebAXEventCheckedStateChanged:
119       Java_BrowserAccessibilityManager_handleCheckStateChanged(
120           env, obj.obj(), node->renderer_id());
121       break;
122     case WebKit::WebAXEventScrolledToAnchor:
123       Java_BrowserAccessibilityManager_handleScrolledToAnchor(
124           env, obj.obj(), node->renderer_id());
125       break;
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.
131       // Speak its text.
132       BrowserAccessibilityAndroid* android_node =
133           static_cast<BrowserAccessibilityAndroid*>(node);
134       Java_BrowserAccessibilityManager_announceLiveRegionText(
135           env, obj.obj(),
136           base::android::ConvertUTF16ToJavaString(
137               env, android_node->GetText()).obj());
138       break;
139     }
140     case WebKit::WebAXEventSelectedTextChanged:
141       Java_BrowserAccessibilityManager_handleTextSelectionChanged(
142           env, obj.obj(), node->renderer_id());
143       break;
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());
150       }
151       break;
152     default:
153       // There are some notifications that aren't meaningful on Android.
154       // It's okay to skip them.
155       break;
156   }
157 }
158
159 jint BrowserAccessibilityManagerAndroid::GetRootId(JNIEnv* env, jobject obj) {
160   return static_cast<jint>(root_->renderer_id());
161 }
162
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)));
168
169   if (!result)
170     return root_->renderer_id();
171
172   if (result->IsFocusable())
173     return result->renderer_id();
174
175   // Examine the children of |result| to find the nearest accessibility focus
176   // candidate
177   BrowserAccessibility* nearest_node = FuzzyHitTest(x, y, result);
178   if (nearest_node)
179     return nearest_node->renderer_id();
180
181   return root_->renderer_id();
182 }
183
184 jboolean BrowserAccessibilityManagerAndroid::PopulateAccessibilityNodeInfo(
185     JNIEnv* env, jobject obj, jobject info, jint id) {
186   BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
187       GetFromRendererID(id));
188   if (!node)
189     return false;
190
191   if (node->parent()) {
192     Java_BrowserAccessibilityManager_setAccessibilityNodeInfoParent(
193         env, obj, info, node->parent()->renderer_id());
194   }
195   for (unsigned i = 0; i < node->PlatformChildCount(); ++i) {
196     Java_BrowserAccessibilityManager_addAccessibilityNodeInfoChild(
197         env, obj, info, node->children()[i]->renderer_id());
198   }
199   Java_BrowserAccessibilityManager_setAccessibilityNodeInfoBooleanAttributes(
200       env, obj, info,
201       id,
202       node->IsCheckable(),
203       node->IsChecked(),
204       node->IsClickable(),
205       node->IsEnabled(),
206       node->IsFocusable(),
207       node->IsFocused(),
208       node->IsPassword(),
209       node->IsScrollable(),
210       node->IsSelected(),
211       node->IsVisibleToUser());
212   Java_BrowserAccessibilityManager_setAccessibilityNodeInfoStringAttributes(
213       env, obj, info,
214       base::android::ConvertUTF8ToJavaString(env, node->GetClassName()).obj(),
215       base::android::ConvertUTF16ToJavaString(env, node->GetText()).obj());
216
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());
222   }
223   bool is_root = node->parent() == NULL;
224   Java_BrowserAccessibilityManager_setAccessibilityNodeInfoLocation(
225       env, obj, info,
226       absolute_rect.x(), absolute_rect.y(),
227       parent_relative_rect.x(), parent_relative_rect.y(),
228       absolute_rect.width(), absolute_rect.height(),
229       is_root);
230
231   return true;
232 }
233
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));
238   if (!node)
239     return false;
240
241   Java_BrowserAccessibilityManager_setAccessibilityEventBooleanAttributes(
242       env, obj, event,
243       node->IsChecked(),
244       node->IsEnabled(),
245       node->IsPassword(),
246       node->IsScrollable());
247   Java_BrowserAccessibilityManager_setAccessibilityEventClassName(
248       env, obj, event,
249       base::android::ConvertUTF8ToJavaString(env, node->GetClassName()).obj());
250   Java_BrowserAccessibilityManager_setAccessibilityEventListAttributes(
251       env, obj, event,
252       node->GetItemIndex(),
253       node->GetItemCount());
254   Java_BrowserAccessibilityManager_setAccessibilityEventScrollAttributes(
255       env, obj, event,
256       node->GetScrollX(),
257       node->GetScrollY(),
258       node->GetMaxScrollX(),
259       node->GetMaxScrollY());
260
261   switch (event_type) {
262     case ANDROID_ACCESSIBILITY_EVENT_TYPE_VIEW_TEXT_CHANGED:
263       Java_BrowserAccessibilityManager_setAccessibilityEventTextChangedAttrs(
264           env, obj, event,
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());
271       break;
272     case ANDROID_ACCESSIBILITY_EVENT_TYPE_VIEW_TEXT_SELECTION_CHANGED:
273       Java_BrowserAccessibilityManager_setAccessibilityEventSelectionAttrs(
274           env, obj, event,
275           node->GetSelectionStart(),
276           node->GetSelectionEnd(),
277           node->GetEditableTextLength(),
278           base::android::ConvertUTF16ToJavaString(env, node->GetText()).obj());
279       break;
280     default:
281       break;
282   }
283
284   return true;
285 }
286
287 void BrowserAccessibilityManagerAndroid::Click(
288     JNIEnv* env, jobject obj, jint id) {
289   BrowserAccessibility* node = GetFromRendererID(id);
290   if (node)
291     DoDefaultAction(*node);
292 }
293
294 void BrowserAccessibilityManagerAndroid::Focus(
295     JNIEnv* env, jobject obj, jint id) {
296   BrowserAccessibility* node = GetFromRendererID(id);
297   if (node)
298     SetFocus(node, true);
299 }
300
301 void BrowserAccessibilityManagerAndroid::Blur(JNIEnv* env, jobject obj) {
302   SetFocus(root_, true);
303 }
304
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);
310   return nearest_node;
311 }
312
313 // static
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);
320
321   if (node->IsFocusable()) {
322     if (distance < *nearest_distance) {
323       *nearest_candidate = node;
324       *nearest_distance = distance;
325     }
326     // Don't examine any more children of focusable node
327     // TODO(aboxhall): what about focusable children?
328     return;
329   }
330
331   if (!node->GetText().empty()) {
332     if (distance < *nearest_distance) {
333       *nearest_candidate = node;
334       *nearest_distance = distance;
335     }
336     return;
337   }
338
339   for (uint32 i = 0; i < node->PlatformChildCount(); i++) {
340     BrowserAccessibility* child = node->PlatformGetChild(i);
341     FuzzyHitTestImpl(x, y, child, nearest_candidate, nearest_distance);
342   }
343 }
344
345 // static
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;
354 }
355
356 void BrowserAccessibilityManagerAndroid::NotifyRootChanged() {
357   JNIEnv* env = AttachCurrentThread();
358   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
359   if (obj.is_null())
360     return;
361
362   Java_BrowserAccessibilityManager_handleNavigate(env, obj.obj());
363 }
364
365 bool
366 BrowserAccessibilityManagerAndroid::UseRootScrollOffsetsWhenComputingBounds() {
367   // The Java layer handles the root scroll offset.
368   return false;
369 }
370
371 bool RegisterBrowserAccessibilityManager(JNIEnv* env) {
372   return RegisterNativesImpl(env);
373 }
374
375 }  // namespace content