Upstream version 5.34.104.0
[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 ui::AXNodeData& 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 ui::AXNodeData& 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 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;
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<intptr_t>(this),
93           content_view_core.obj()).obj());
94 }
95
96 void BrowserAccessibilityManagerAndroid::NotifyAccessibilityEvent(
97     ui::AXEvent event_type,
98     BrowserAccessibility* node) {
99   JNIEnv* env = AttachCurrentThread();
100   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
101   if (obj.is_null())
102     return;
103
104   if (event_type == ui::AX_EVENT_HIDE)
105     return;
106
107   // Always send AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED to notify
108   // the Android system that the accessibility hierarchy rooted at this
109   // node has changed.
110   Java_BrowserAccessibilityManager_handleContentChanged(
111       env, obj.obj(), node->renderer_id());
112
113   switch (event_type) {
114     case ui::AX_EVENT_LOAD_COMPLETE:
115       Java_BrowserAccessibilityManager_handlePageLoaded(
116           env, obj.obj(), focus_->renderer_id());
117       break;
118     case ui::AX_EVENT_FOCUS:
119       Java_BrowserAccessibilityManager_handleFocusChanged(
120           env, obj.obj(), node->renderer_id());
121       break;
122     case ui::AX_EVENT_CHECKED_STATE_CHANGED:
123       Java_BrowserAccessibilityManager_handleCheckStateChanged(
124           env, obj.obj(), node->renderer_id());
125       break;
126     case ui::AX_EVENT_SCROLLED_TO_ANCHOR:
127       Java_BrowserAccessibilityManager_handleScrolledToAnchor(
128           env, obj.obj(), node->renderer_id());
129       break;
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.
135       // Speak its text.
136       BrowserAccessibilityAndroid* android_node =
137           static_cast<BrowserAccessibilityAndroid*>(node);
138       Java_BrowserAccessibilityManager_announceLiveRegionText(
139           env, obj.obj(),
140           base::android::ConvertUTF16ToJavaString(
141               env, android_node->GetText()).obj());
142       break;
143     }
144     case ui::AX_EVENT_SELECTED_TEXT_CHANGED:
145       Java_BrowserAccessibilityManager_handleTextSelectionChanged(
146           env, obj.obj(), node->renderer_id());
147       break;
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());
154       }
155       break;
156     default:
157       // There are some notifications that aren't meaningful on Android.
158       // It's okay to skip them.
159       break;
160   }
161 }
162
163 jint BrowserAccessibilityManagerAndroid::GetRootId(JNIEnv* env, jobject obj) {
164   return static_cast<jint>(root_->renderer_id());
165 }
166
167 jboolean BrowserAccessibilityManagerAndroid::IsNodeValid(
168     JNIEnv* env, jobject obj, jint id) {
169   return GetFromRendererID(id) != NULL;
170 }
171
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)));
177
178   if (!result)
179     return root_->renderer_id();
180
181   if (result->IsFocusable())
182     return result->renderer_id();
183
184   // Examine the children of |result| to find the nearest accessibility focus
185   // candidate
186   BrowserAccessibility* nearest_node = FuzzyHitTest(x, y, result);
187   if (nearest_node)
188     return nearest_node->renderer_id();
189
190   return root_->renderer_id();
191 }
192
193 jboolean BrowserAccessibilityManagerAndroid::PopulateAccessibilityNodeInfo(
194     JNIEnv* env, jobject obj, jobject info, jint id) {
195   BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
196       GetFromRendererID(id));
197   if (!node)
198     return false;
199
200   if (node->parent()) {
201     Java_BrowserAccessibilityManager_setAccessibilityNodeInfoParent(
202         env, obj, info, node->parent()->renderer_id());
203   }
204   for (unsigned i = 0; i < node->PlatformChildCount(); ++i) {
205     Java_BrowserAccessibilityManager_addAccessibilityNodeInfoChild(
206         env, obj, info, node->children()[i]->renderer_id());
207   }
208   Java_BrowserAccessibilityManager_setAccessibilityNodeInfoBooleanAttributes(
209       env, obj, info,
210       id,
211       node->IsCheckable(),
212       node->IsChecked(),
213       node->IsClickable(),
214       node->IsEnabled(),
215       node->IsFocusable(),
216       node->IsFocused(),
217       node->IsPassword(),
218       node->IsScrollable(),
219       node->IsSelected(),
220       node->IsVisibleToUser());
221   Java_BrowserAccessibilityManager_setAccessibilityNodeInfoStringAttributes(
222       env, obj, info,
223       base::android::ConvertUTF8ToJavaString(env, node->GetClassName()).obj(),
224       base::android::ConvertUTF16ToJavaString(env, node->GetText()).obj());
225
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());
231   }
232   bool is_root = node->parent() == NULL;
233   Java_BrowserAccessibilityManager_setAccessibilityNodeInfoLocation(
234       env, obj, info,
235       absolute_rect.x(), absolute_rect.y(),
236       parent_relative_rect.x(), parent_relative_rect.y(),
237       absolute_rect.width(), absolute_rect.height(),
238       is_root);
239
240   // New KitKat APIs
241   Java_BrowserAccessibilityManager_setAccessibilityNodeInfoKitKatAttributes(
242       env, obj, info,
243       node->CanOpenPopup(),
244       node->IsContentInvalid(),
245       node->IsDismissable(),
246       node->IsMultiLine(),
247       node->AndroidInputType(),
248       node->AndroidLiveRegionType());
249   if (node->IsCollection()) {
250     Java_BrowserAccessibilityManager_setAccessibilityNodeInfoCollectionInfo(
251         env, obj, info,
252         node->RowCount(),
253         node->ColumnCount(),
254         node->IsHierarchical());
255   }
256   if (node->IsCollectionItem() || node->IsHeading()) {
257     Java_BrowserAccessibilityManager_setAccessibilityNodeInfoCollectionItemInfo(
258         env, obj, info,
259         node->RowIndex(),
260         node->RowSpan(),
261         node->ColumnIndex(),
262         node->ColumnSpan(),
263         node->IsHeading());
264   }
265   if (node->IsRangeType()) {
266     Java_BrowserAccessibilityManager_setAccessibilityNodeInfoRangeInfo(
267         env, obj, info,
268         node->AndroidRangeType(),
269         node->RangeMin(),
270         node->RangeMax(),
271         node->RangeCurrentValue());
272   }
273
274   return true;
275 }
276
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));
281   if (!node)
282     return false;
283
284   Java_BrowserAccessibilityManager_setAccessibilityEventBooleanAttributes(
285       env, obj, event,
286       node->IsChecked(),
287       node->IsEnabled(),
288       node->IsPassword(),
289       node->IsScrollable());
290   Java_BrowserAccessibilityManager_setAccessibilityEventClassName(
291       env, obj, event,
292       base::android::ConvertUTF8ToJavaString(env, node->GetClassName()).obj());
293   Java_BrowserAccessibilityManager_setAccessibilityEventListAttributes(
294       env, obj, event,
295       node->GetItemIndex(),
296       node->GetItemCount());
297   Java_BrowserAccessibilityManager_setAccessibilityEventScrollAttributes(
298       env, obj, event,
299       node->GetScrollX(),
300       node->GetScrollY(),
301       node->GetMaxScrollX(),
302       node->GetMaxScrollY());
303
304   switch (event_type) {
305     case ANDROID_ACCESSIBILITY_EVENT_TYPE_VIEW_TEXT_CHANGED:
306       Java_BrowserAccessibilityManager_setAccessibilityEventTextChangedAttrs(
307           env, obj, event,
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());
314       break;
315     case ANDROID_ACCESSIBILITY_EVENT_TYPE_VIEW_TEXT_SELECTION_CHANGED:
316       Java_BrowserAccessibilityManager_setAccessibilityEventSelectionAttrs(
317           env, obj, event,
318           node->GetSelectionStart(),
319           node->GetSelectionEnd(),
320           node->GetEditableTextLength(),
321           base::android::ConvertUTF16ToJavaString(env, node->GetText()).obj());
322       break;
323     default:
324       break;
325   }
326
327   // Backwards-compatible fallback for new KitKat APIs.
328   Java_BrowserAccessibilityManager_setAccessibilityEventKitKatAttributes(
329       env, obj, event,
330       node->CanOpenPopup(),
331       node->IsContentInvalid(),
332       node->IsDismissable(),
333       node->IsMultiLine(),
334       node->AndroidInputType(),
335       node->AndroidLiveRegionType());
336   if (node->IsCollection()) {
337     Java_BrowserAccessibilityManager_setAccessibilityEventCollectionInfo(
338         env, obj, event,
339         node->RowCount(),
340         node->ColumnCount(),
341         node->IsHierarchical());
342   }
343   if (node->IsCollectionItem() || node->IsHeading()) {
344     Java_BrowserAccessibilityManager_setAccessibilityEventCollectionItemInfo(
345         env, obj, event,
346         node->RowIndex(),
347         node->RowSpan(),
348         node->ColumnIndex(),
349         node->ColumnSpan(),
350         node->IsHeading());
351   }
352   if (node->IsRangeType()) {
353     Java_BrowserAccessibilityManager_setAccessibilityEventRangeInfo(
354         env, obj, event,
355         node->AndroidRangeType(),
356         node->RangeMin(),
357         node->RangeMax(),
358         node->RangeCurrentValue());
359   }
360
361   return true;
362 }
363
364 void BrowserAccessibilityManagerAndroid::Click(
365     JNIEnv* env, jobject obj, jint id) {
366   BrowserAccessibility* node = GetFromRendererID(id);
367   if (node)
368     DoDefaultAction(*node);
369 }
370
371 void BrowserAccessibilityManagerAndroid::Focus(
372     JNIEnv* env, jobject obj, jint id) {
373   BrowserAccessibility* node = GetFromRendererID(id);
374   if (node)
375     SetFocus(node, true);
376 }
377
378 void BrowserAccessibilityManagerAndroid::Blur(JNIEnv* env, jobject obj) {
379   SetFocus(root_, true);
380 }
381
382 void BrowserAccessibilityManagerAndroid::ScrollToMakeNodeVisible(
383     JNIEnv* env, jobject obj, jint id) {
384   BrowserAccessibility* node = GetFromRendererID(id);
385   if (node)
386     ScrollToMakeVisible(*node, gfx::Rect(node->location().size()));
387 }
388
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);
394   return nearest_node;
395 }
396
397 // static
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);
404
405   if (node->IsFocusable()) {
406     if (distance < *nearest_distance) {
407       *nearest_candidate = node;
408       *nearest_distance = distance;
409     }
410     // Don't examine any more children of focusable node
411     // TODO(aboxhall): what about focusable children?
412     return;
413   }
414
415   if (!node->GetText().empty()) {
416     if (distance < *nearest_distance) {
417       *nearest_candidate = node;
418       *nearest_distance = distance;
419     }
420     return;
421   }
422
423   for (uint32 i = 0; i < node->PlatformChildCount(); i++) {
424     BrowserAccessibility* child = node->PlatformGetChild(i);
425     FuzzyHitTestImpl(x, y, child, nearest_candidate, nearest_distance);
426   }
427 }
428
429 // static
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;
438 }
439
440 void BrowserAccessibilityManagerAndroid::NotifyRootChanged() {
441   JNIEnv* env = AttachCurrentThread();
442   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
443   if (obj.is_null())
444     return;
445
446   Java_BrowserAccessibilityManager_handleNavigate(env, obj.obj());
447 }
448
449 bool
450 BrowserAccessibilityManagerAndroid::UseRootScrollOffsetsWhenComputingBounds() {
451   // The Java layer handles the root scroll offset.
452   return false;
453 }
454
455 bool RegisterBrowserAccessibilityManager(JNIEnv* env) {
456   return RegisterNativesImpl(env);
457 }
458
459 }  // namespace content