Upstream version 11.39.250.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 enum AndroidHtmlElementType {
30   HTML_ELEMENT_TYPE_SECTION,
31   HTML_ELEMENT_TYPE_LIST,
32   HTML_ELEMENT_TYPE_CONTROL,
33   HTML_ELEMENT_TYPE_ANY
34 };
35
36 // These are special unofficial strings sent from TalkBack/BrailleBack
37 // to jump to certain categories of web elements.
38 AndroidHtmlElementType HtmlElementTypeFromString(base::string16 element_type) {
39   if (element_type == base::ASCIIToUTF16("SECTION"))
40     return HTML_ELEMENT_TYPE_SECTION;
41   else if (element_type == base::ASCIIToUTF16("LIST"))
42     return HTML_ELEMENT_TYPE_LIST;
43   else if (element_type == base::ASCIIToUTF16("CONTROL"))
44     return HTML_ELEMENT_TYPE_CONTROL;
45   else
46     return HTML_ELEMENT_TYPE_ANY;
47 }
48
49 }  // anonymous namespace
50
51 namespace content {
52
53 namespace aria_strings {
54   const char kAriaLivePolite[] = "polite";
55   const char kAriaLiveAssertive[] = "assertive";
56 }
57
58 // static
59 BrowserAccessibilityManager* BrowserAccessibilityManager::Create(
60     const ui::AXTreeUpdate& initial_tree,
61     BrowserAccessibilityDelegate* delegate,
62     BrowserAccessibilityFactory* factory) {
63   return new BrowserAccessibilityManagerAndroid(
64       ScopedJavaLocalRef<jobject>(), initial_tree, delegate, factory);
65 }
66
67 BrowserAccessibilityManagerAndroid*
68 BrowserAccessibilityManager::ToBrowserAccessibilityManagerAndroid() {
69   return static_cast<BrowserAccessibilityManagerAndroid*>(this);
70 }
71
72 BrowserAccessibilityManagerAndroid::BrowserAccessibilityManagerAndroid(
73     ScopedJavaLocalRef<jobject> content_view_core,
74     const ui::AXTreeUpdate& initial_tree,
75     BrowserAccessibilityDelegate* delegate,
76     BrowserAccessibilityFactory* factory)
77     : BrowserAccessibilityManager(initial_tree, delegate, factory) {
78   SetContentViewCore(content_view_core);
79 }
80
81 BrowserAccessibilityManagerAndroid::~BrowserAccessibilityManagerAndroid() {
82   JNIEnv* env = AttachCurrentThread();
83   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
84   if (obj.is_null())
85     return;
86
87   Java_BrowserAccessibilityManager_onNativeObjectDestroyed(env, obj.obj());
88 }
89
90 // static
91 ui::AXTreeUpdate BrowserAccessibilityManagerAndroid::GetEmptyDocument() {
92   ui::AXNodeData empty_document;
93   empty_document.id = 0;
94   empty_document.role = ui::AX_ROLE_ROOT_WEB_AREA;
95   empty_document.state = 1 << ui::AX_STATE_READ_ONLY;
96
97   ui::AXTreeUpdate update;
98   update.nodes.push_back(empty_document);
99   return update;
100 }
101
102 void BrowserAccessibilityManagerAndroid::SetContentViewCore(
103     ScopedJavaLocalRef<jobject> content_view_core) {
104   if (content_view_core.is_null())
105     return;
106
107   JNIEnv* env = AttachCurrentThread();
108   java_ref_ = JavaObjectWeakGlobalRef(
109       env, Java_BrowserAccessibilityManager_create(
110           env, reinterpret_cast<intptr_t>(this),
111           content_view_core.obj()).obj());
112 }
113
114 void BrowserAccessibilityManagerAndroid::NotifyAccessibilityEvent(
115     ui::AXEvent event_type,
116     BrowserAccessibility* node) {
117   JNIEnv* env = AttachCurrentThread();
118   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
119   if (obj.is_null())
120     return;
121
122   if (event_type == ui::AX_EVENT_HIDE)
123     return;
124
125   if (event_type == ui::AX_EVENT_HOVER) {
126     HandleHoverEvent(node);
127     return;
128   }
129
130   // Always send AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED to notify
131   // the Android system that the accessibility hierarchy rooted at this
132   // node has changed.
133   Java_BrowserAccessibilityManager_handleContentChanged(
134       env, obj.obj(), node->GetId());
135
136   switch (event_type) {
137     case ui::AX_EVENT_LOAD_COMPLETE:
138       Java_BrowserAccessibilityManager_handlePageLoaded(
139           env, obj.obj(), focus_->id());
140       break;
141     case ui::AX_EVENT_FOCUS:
142       Java_BrowserAccessibilityManager_handleFocusChanged(
143           env, obj.obj(), node->GetId());
144       break;
145     case ui::AX_EVENT_CHECKED_STATE_CHANGED:
146       Java_BrowserAccessibilityManager_handleCheckStateChanged(
147           env, obj.obj(), node->GetId());
148       break;
149     case ui::AX_EVENT_SCROLL_POSITION_CHANGED:
150       Java_BrowserAccessibilityManager_handleScrollPositionChanged(
151           env, obj.obj(), node->GetId());
152       break;
153     case ui::AX_EVENT_SCROLLED_TO_ANCHOR:
154       Java_BrowserAccessibilityManager_handleScrolledToAnchor(
155           env, obj.obj(), node->GetId());
156       break;
157     case ui::AX_EVENT_ALERT:
158       // An alert is a special case of live region. Fall through to the
159       // next case to handle it.
160     case ui::AX_EVENT_SHOW: {
161       // This event is fired when an object appears in a live region.
162       // Speak its text.
163       BrowserAccessibilityAndroid* android_node =
164           static_cast<BrowserAccessibilityAndroid*>(node);
165       Java_BrowserAccessibilityManager_announceLiveRegionText(
166           env, obj.obj(),
167           base::android::ConvertUTF16ToJavaString(
168               env, android_node->GetText()).obj());
169       break;
170     }
171     case ui::AX_EVENT_TEXT_SELECTION_CHANGED:
172       Java_BrowserAccessibilityManager_handleTextSelectionChanged(
173           env, obj.obj(), node->GetId());
174       break;
175     case ui::AX_EVENT_CHILDREN_CHANGED:
176     case ui::AX_EVENT_TEXT_CHANGED:
177     case ui::AX_EVENT_VALUE_CHANGED:
178       if (node->IsEditableText()) {
179         Java_BrowserAccessibilityManager_handleEditableTextChanged(
180             env, obj.obj(), node->GetId());
181       }
182       break;
183     default:
184       // There are some notifications that aren't meaningful on Android.
185       // It's okay to skip them.
186       break;
187   }
188 }
189
190 jint BrowserAccessibilityManagerAndroid::GetRootId(JNIEnv* env, jobject obj) {
191   return static_cast<jint>(GetRoot()->GetId());
192 }
193
194 jboolean BrowserAccessibilityManagerAndroid::IsNodeValid(
195     JNIEnv* env, jobject obj, jint id) {
196   return GetFromID(id) != NULL;
197 }
198
199 void BrowserAccessibilityManagerAndroid::HitTest(
200     JNIEnv* env, jobject obj, jint x, jint y) {
201   if (delegate())
202     delegate()->AccessibilityHitTest(gfx::Point(x, y));
203 }
204
205 jboolean BrowserAccessibilityManagerAndroid::PopulateAccessibilityNodeInfo(
206     JNIEnv* env, jobject obj, jobject info, jint id) {
207   BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
208       GetFromID(id));
209   if (!node)
210     return false;
211
212   if (node->GetParent()) {
213     Java_BrowserAccessibilityManager_setAccessibilityNodeInfoParent(
214         env, obj, info, node->GetParent()->GetId());
215   }
216   for (unsigned i = 0; i < node->PlatformChildCount(); ++i) {
217     Java_BrowserAccessibilityManager_addAccessibilityNodeInfoChild(
218         env, obj, info, node->InternalGetChild(i)->GetId());
219   }
220   Java_BrowserAccessibilityManager_setAccessibilityNodeInfoBooleanAttributes(
221       env, obj, info,
222       id,
223       node->IsCheckable(),
224       node->IsChecked(),
225       node->IsClickable(),
226       node->IsEnabled(),
227       node->IsFocusable(),
228       node->IsFocused(),
229       node->IsPassword(),
230       node->IsScrollable(),
231       node->IsSelected(),
232       node->IsVisibleToUser());
233   Java_BrowserAccessibilityManager_setAccessibilityNodeInfoClassName(
234       env, obj, info,
235       base::android::ConvertUTF8ToJavaString(env, node->GetClassName()).obj());
236   Java_BrowserAccessibilityManager_setAccessibilityNodeInfoContentDescription(
237       env, obj, info,
238       base::android::ConvertUTF16ToJavaString(env, node->GetText()).obj(),
239       node->IsLink());
240
241   gfx::Rect absolute_rect = node->GetLocalBoundsRect();
242   gfx::Rect parent_relative_rect = absolute_rect;
243   if (node->GetParent()) {
244     gfx::Rect parent_rect = node->GetParent()->GetLocalBoundsRect();
245     parent_relative_rect.Offset(-parent_rect.OffsetFromOrigin());
246   }
247   bool is_root = node->GetParent() == NULL;
248   Java_BrowserAccessibilityManager_setAccessibilityNodeInfoLocation(
249       env, obj, info,
250       id,
251       absolute_rect.x(), absolute_rect.y(),
252       parent_relative_rect.x(), parent_relative_rect.y(),
253       absolute_rect.width(), absolute_rect.height(),
254       is_root);
255
256   // New KitKat APIs
257   Java_BrowserAccessibilityManager_setAccessibilityNodeInfoKitKatAttributes(
258       env, obj, info,
259       node->CanOpenPopup(),
260       node->IsContentInvalid(),
261       node->IsDismissable(),
262       node->IsMultiLine(),
263       node->AndroidInputType(),
264       node->AndroidLiveRegionType());
265   if (node->IsCollection()) {
266     Java_BrowserAccessibilityManager_setAccessibilityNodeInfoCollectionInfo(
267         env, obj, info,
268         node->RowCount(),
269         node->ColumnCount(),
270         node->IsHierarchical());
271   }
272   if (node->IsCollectionItem() || node->IsHeading()) {
273     Java_BrowserAccessibilityManager_setAccessibilityNodeInfoCollectionItemInfo(
274         env, obj, info,
275         node->RowIndex(),
276         node->RowSpan(),
277         node->ColumnIndex(),
278         node->ColumnSpan(),
279         node->IsHeading());
280   }
281   if (node->IsRangeType()) {
282     Java_BrowserAccessibilityManager_setAccessibilityNodeInfoRangeInfo(
283         env, obj, info,
284         node->AndroidRangeType(),
285         node->RangeMin(),
286         node->RangeMax(),
287         node->RangeCurrentValue());
288   }
289
290   return true;
291 }
292
293 jboolean BrowserAccessibilityManagerAndroid::PopulateAccessibilityEvent(
294     JNIEnv* env, jobject obj, jobject event, jint id, jint event_type) {
295   BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
296       GetFromID(id));
297   if (!node)
298     return false;
299
300   Java_BrowserAccessibilityManager_setAccessibilityEventBooleanAttributes(
301       env, obj, event,
302       node->IsChecked(),
303       node->IsEnabled(),
304       node->IsPassword(),
305       node->IsScrollable());
306   Java_BrowserAccessibilityManager_setAccessibilityEventClassName(
307       env, obj, event,
308       base::android::ConvertUTF8ToJavaString(env, node->GetClassName()).obj());
309   Java_BrowserAccessibilityManager_setAccessibilityEventListAttributes(
310       env, obj, event,
311       node->GetItemIndex(),
312       node->GetItemCount());
313   Java_BrowserAccessibilityManager_setAccessibilityEventScrollAttributes(
314       env, obj, event,
315       node->GetScrollX(),
316       node->GetScrollY(),
317       node->GetMaxScrollX(),
318       node->GetMaxScrollY());
319
320   switch (event_type) {
321     case ANDROID_ACCESSIBILITY_EVENT_TYPE_VIEW_TEXT_CHANGED:
322       Java_BrowserAccessibilityManager_setAccessibilityEventTextChangedAttrs(
323           env, obj, event,
324           node->GetTextChangeFromIndex(),
325           node->GetTextChangeAddedCount(),
326           node->GetTextChangeRemovedCount(),
327           base::android::ConvertUTF16ToJavaString(
328               env, node->GetTextChangeBeforeText()).obj(),
329           base::android::ConvertUTF16ToJavaString(env, node->GetText()).obj());
330       break;
331     case ANDROID_ACCESSIBILITY_EVENT_TYPE_VIEW_TEXT_SELECTION_CHANGED:
332       Java_BrowserAccessibilityManager_setAccessibilityEventSelectionAttrs(
333           env, obj, event,
334           node->GetSelectionStart(),
335           node->GetSelectionEnd(),
336           node->GetEditableTextLength(),
337           base::android::ConvertUTF16ToJavaString(env, node->GetText()).obj());
338       break;
339     default:
340       break;
341   }
342
343   // Backwards-compatible fallback for new KitKat APIs.
344   Java_BrowserAccessibilityManager_setAccessibilityEventKitKatAttributes(
345       env, obj, event,
346       node->CanOpenPopup(),
347       node->IsContentInvalid(),
348       node->IsDismissable(),
349       node->IsMultiLine(),
350       node->AndroidInputType(),
351       node->AndroidLiveRegionType());
352   if (node->IsCollection()) {
353     Java_BrowserAccessibilityManager_setAccessibilityEventCollectionInfo(
354         env, obj, event,
355         node->RowCount(),
356         node->ColumnCount(),
357         node->IsHierarchical());
358   }
359   if (node->IsHeading()) {
360     Java_BrowserAccessibilityManager_setAccessibilityEventHeadingFlag(
361         env, obj, event, true);
362   }
363   if (node->IsCollectionItem()) {
364     Java_BrowserAccessibilityManager_setAccessibilityEventCollectionItemInfo(
365         env, obj, event,
366         node->RowIndex(),
367         node->RowSpan(),
368         node->ColumnIndex(),
369         node->ColumnSpan());
370   }
371   if (node->IsRangeType()) {
372     Java_BrowserAccessibilityManager_setAccessibilityEventRangeInfo(
373         env, obj, event,
374         node->AndroidRangeType(),
375         node->RangeMin(),
376         node->RangeMax(),
377         node->RangeCurrentValue());
378   }
379
380   return true;
381 }
382
383 void BrowserAccessibilityManagerAndroid::Click(
384     JNIEnv* env, jobject obj, jint id) {
385   BrowserAccessibility* node = GetFromID(id);
386   if (node)
387     DoDefaultAction(*node);
388 }
389
390 void BrowserAccessibilityManagerAndroid::Focus(
391     JNIEnv* env, jobject obj, jint id) {
392   BrowserAccessibility* node = GetFromID(id);
393   if (node)
394     SetFocus(node, true);
395 }
396
397 void BrowserAccessibilityManagerAndroid::Blur(JNIEnv* env, jobject obj) {
398   SetFocus(GetRoot(), true);
399 }
400
401 void BrowserAccessibilityManagerAndroid::ScrollToMakeNodeVisible(
402     JNIEnv* env, jobject obj, jint id) {
403   BrowserAccessibility* node = GetFromID(id);
404   if (node)
405     ScrollToMakeVisible(*node, gfx::Rect(node->GetLocation().size()));
406 }
407
408 void BrowserAccessibilityManagerAndroid::HandleHoverEvent(
409     BrowserAccessibility* node) {
410   JNIEnv* env = AttachCurrentThread();
411   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
412   if (obj.is_null())
413     return;
414
415   BrowserAccessibilityAndroid* ancestor =
416       static_cast<BrowserAccessibilityAndroid*>(node->GetParent());
417   while (ancestor) {
418     if (ancestor->PlatformIsLeaf() ||
419         (ancestor->IsFocusable() && !ancestor->HasFocusableChild())) {
420       node = ancestor;
421       // Don't break - we want the highest ancestor that's focusable or a
422       // leaf node.
423     }
424     ancestor = static_cast<BrowserAccessibilityAndroid*>(ancestor->GetParent());
425   }
426
427   Java_BrowserAccessibilityManager_handleHover(
428       env, obj.obj(), node->GetId());
429 }
430
431 jint BrowserAccessibilityManagerAndroid::FindElementType(
432     JNIEnv* env, jobject obj, jint start_id, jstring element_type_str,
433     jboolean forwards) {
434   BrowserAccessibility* node = GetFromID(start_id);
435   if (!node)
436     return 0;
437
438   AndroidHtmlElementType element_type = HtmlElementTypeFromString(
439       base::android::ConvertJavaStringToUTF16(env, element_type_str));
440
441   node = forwards ? NextInTreeOrder(node) : PreviousInTreeOrder(node);
442   while (node) {
443     switch(element_type) {
444       case HTML_ELEMENT_TYPE_SECTION:
445         if (node->GetRole() == ui::AX_ROLE_ARTICLE ||
446             node->GetRole() == ui::AX_ROLE_APPLICATION ||
447             node->GetRole() == ui::AX_ROLE_BANNER ||
448             node->GetRole() == ui::AX_ROLE_COMPLEMENTARY ||
449             node->GetRole() == ui::AX_ROLE_CONTENT_INFO ||
450             node->GetRole() == ui::AX_ROLE_HEADING ||
451             node->GetRole() == ui::AX_ROLE_MAIN ||
452             node->GetRole() == ui::AX_ROLE_NAVIGATION ||
453             node->GetRole() == ui::AX_ROLE_SEARCH ||
454             node->GetRole() == ui::AX_ROLE_REGION) {
455           return node->GetId();
456         }
457         break;
458       case HTML_ELEMENT_TYPE_LIST:
459         if (node->GetRole() == ui::AX_ROLE_LIST ||
460             node->GetRole() == ui::AX_ROLE_GRID ||
461             node->GetRole() == ui::AX_ROLE_TABLE ||
462             node->GetRole() == ui::AX_ROLE_TREE) {
463           return node->GetId();
464         }
465         break;
466       case HTML_ELEMENT_TYPE_CONTROL:
467         if (static_cast<BrowserAccessibilityAndroid*>(node)->IsFocusable())
468           return node->GetId();
469         break;
470       case HTML_ELEMENT_TYPE_ANY:
471         // In theory, the API says that an accessibility service could
472         // jump to an element by element name, like 'H1' or 'P'. This isn't
473         // currently used by any accessibility service, and we think it's
474         // better to keep them high-level like 'SECTION' or 'CONTROL', so we
475         // just fall back on linear navigation when we don't recognize the
476         // element type.
477         if (static_cast<BrowserAccessibilityAndroid*>(node)->IsClickable())
478           return node->GetId();
479         break;
480     }
481
482     node = forwards ? NextInTreeOrder(node) : PreviousInTreeOrder(node);
483   }
484
485   return 0;
486 }
487
488 void BrowserAccessibilityManagerAndroid::OnRootChanged(ui::AXNode* new_root) {
489   JNIEnv* env = AttachCurrentThread();
490   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
491   if (obj.is_null())
492     return;
493
494   Java_BrowserAccessibilityManager_handleNavigate(env, obj.obj());
495 }
496
497 bool
498 BrowserAccessibilityManagerAndroid::UseRootScrollOffsetsWhenComputingBounds() {
499   // The Java layer handles the root scroll offset.
500   return false;
501 }
502
503 bool RegisterBrowserAccessibilityManager(JNIEnv* env) {
504   return RegisterNativesImpl(env);
505 }
506
507 }  // namespace content