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