Cast: Stop logging kVideoFrameSentToEncoder and rename a couple events.
[chromium-blink-merge.git] / content / browser / accessibility / browser_accessibility_manager_android.cc
blob21c29c0aab53296e749b8bc4654e689a9031755c
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 // Restricts |val| to the range [min, max].
37 int Clamp(int val, int min, int max) {
38 return std::min(std::max(val, min), max);
41 // These are special unofficial strings sent from TalkBack/BrailleBack
42 // to jump to certain categories of web elements.
43 AndroidHtmlElementType HtmlElementTypeFromString(base::string16 element_type) {
44 if (element_type == base::ASCIIToUTF16("SECTION"))
45 return HTML_ELEMENT_TYPE_SECTION;
46 else if (element_type == base::ASCIIToUTF16("LIST"))
47 return HTML_ELEMENT_TYPE_LIST;
48 else if (element_type == base::ASCIIToUTF16("CONTROL"))
49 return HTML_ELEMENT_TYPE_CONTROL;
50 else
51 return HTML_ELEMENT_TYPE_ANY;
54 } // anonymous namespace
56 namespace content {
58 namespace aria_strings {
59 const char kAriaLivePolite[] = "polite";
60 const char kAriaLiveAssertive[] = "assertive";
63 // static
64 BrowserAccessibilityManager* BrowserAccessibilityManager::Create(
65 const ui::AXTreeUpdate& initial_tree,
66 BrowserAccessibilityDelegate* delegate,
67 BrowserAccessibilityFactory* factory) {
68 return new BrowserAccessibilityManagerAndroid(
69 ScopedJavaLocalRef<jobject>(), initial_tree, delegate, factory);
72 BrowserAccessibilityManagerAndroid*
73 BrowserAccessibilityManager::ToBrowserAccessibilityManagerAndroid() {
74 return static_cast<BrowserAccessibilityManagerAndroid*>(this);
77 BrowserAccessibilityManagerAndroid::BrowserAccessibilityManagerAndroid(
78 ScopedJavaLocalRef<jobject> content_view_core,
79 const ui::AXTreeUpdate& initial_tree,
80 BrowserAccessibilityDelegate* delegate,
81 BrowserAccessibilityFactory* factory)
82 : BrowserAccessibilityManager(initial_tree, delegate, factory) {
83 SetContentViewCore(content_view_core);
86 BrowserAccessibilityManagerAndroid::~BrowserAccessibilityManagerAndroid() {
87 JNIEnv* env = AttachCurrentThread();
88 ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
89 if (obj.is_null())
90 return;
92 Java_BrowserAccessibilityManager_onNativeObjectDestroyed(env, obj.obj());
95 // static
96 ui::AXTreeUpdate BrowserAccessibilityManagerAndroid::GetEmptyDocument() {
97 ui::AXNodeData empty_document;
98 empty_document.id = 0;
99 empty_document.role = ui::AX_ROLE_ROOT_WEB_AREA;
100 empty_document.state = 1 << ui::AX_STATE_READ_ONLY;
102 ui::AXTreeUpdate update;
103 update.nodes.push_back(empty_document);
104 return update;
107 void BrowserAccessibilityManagerAndroid::SetContentViewCore(
108 ScopedJavaLocalRef<jobject> content_view_core) {
109 if (content_view_core.is_null())
110 return;
112 JNIEnv* env = AttachCurrentThread();
113 java_ref_ = JavaObjectWeakGlobalRef(
114 env, Java_BrowserAccessibilityManager_create(
115 env, reinterpret_cast<intptr_t>(this),
116 content_view_core.obj()).obj());
119 void BrowserAccessibilityManagerAndroid::NotifyAccessibilityEvent(
120 ui::AXEvent event_type,
121 BrowserAccessibility* node) {
122 JNIEnv* env = AttachCurrentThread();
123 ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
124 if (obj.is_null())
125 return;
127 if (event_type == ui::AX_EVENT_HIDE)
128 return;
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());
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;
171 case ui::AX_EVENT_SELECTED_TEXT_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());
182 break;
183 default:
184 // There are some notifications that aren't meaningful on Android.
185 // It's okay to skip them.
186 break;
190 jint BrowserAccessibilityManagerAndroid::GetRootId(JNIEnv* env, jobject obj) {
191 return static_cast<jint>(GetRoot()->GetId());
194 jboolean BrowserAccessibilityManagerAndroid::IsNodeValid(
195 JNIEnv* env, jobject obj, jint id) {
196 return GetFromID(id) != NULL;
199 jint BrowserAccessibilityManagerAndroid::HitTest(
200 JNIEnv* env, jobject obj, jint x, jint y) {
201 BrowserAccessibilityAndroid* result =
202 static_cast<BrowserAccessibilityAndroid*>(
203 GetRoot()->BrowserAccessibilityForPoint(gfx::Point(x, y)));
205 if (!result)
206 return GetRoot()->GetId();
208 if (result->IsFocusable())
209 return result->GetId();
211 // Examine the children of |result| to find the nearest accessibility focus
212 // candidate
213 BrowserAccessibility* nearest_node = FuzzyHitTest(x, y, result);
214 if (nearest_node)
215 return nearest_node->GetId();
217 return GetRoot()->GetId();
220 jboolean BrowserAccessibilityManagerAndroid::PopulateAccessibilityNodeInfo(
221 JNIEnv* env, jobject obj, jobject info, jint id) {
222 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
223 GetFromID(id));
224 if (!node)
225 return false;
227 if (node->GetParent()) {
228 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoParent(
229 env, obj, info, node->GetParent()->GetId());
231 for (unsigned i = 0; i < node->PlatformChildCount(); ++i) {
232 Java_BrowserAccessibilityManager_addAccessibilityNodeInfoChild(
233 env, obj, info, node->InternalGetChild(i)->GetId());
235 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoBooleanAttributes(
236 env, obj, info,
238 node->IsCheckable(),
239 node->IsChecked(),
240 node->IsClickable(),
241 node->IsEnabled(),
242 node->IsFocusable(),
243 node->IsFocused(),
244 node->IsPassword(),
245 node->IsScrollable(),
246 node->IsSelected(),
247 node->IsVisibleToUser());
248 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoClassName(
249 env, obj, info,
250 base::android::ConvertUTF8ToJavaString(env, node->GetClassName()).obj());
251 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoContentDescription(
252 env, obj, info,
253 base::android::ConvertUTF16ToJavaString(env, node->GetText()).obj(),
254 node->IsLink());
256 gfx::Rect absolute_rect = node->GetLocalBoundsRect();
257 gfx::Rect parent_relative_rect = absolute_rect;
258 if (node->GetParent()) {
259 gfx::Rect parent_rect = node->GetParent()->GetLocalBoundsRect();
260 parent_relative_rect.Offset(-parent_rect.OffsetFromOrigin());
262 bool is_root = node->GetParent() == NULL;
263 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoLocation(
264 env, obj, info,
265 absolute_rect.x(), absolute_rect.y(),
266 parent_relative_rect.x(), parent_relative_rect.y(),
267 absolute_rect.width(), absolute_rect.height(),
268 is_root);
270 // New KitKat APIs
271 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoKitKatAttributes(
272 env, obj, info,
273 node->CanOpenPopup(),
274 node->IsContentInvalid(),
275 node->IsDismissable(),
276 node->IsMultiLine(),
277 node->AndroidInputType(),
278 node->AndroidLiveRegionType());
279 if (node->IsCollection()) {
280 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoCollectionInfo(
281 env, obj, info,
282 node->RowCount(),
283 node->ColumnCount(),
284 node->IsHierarchical());
286 if (node->IsCollectionItem() || node->IsHeading()) {
287 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoCollectionItemInfo(
288 env, obj, info,
289 node->RowIndex(),
290 node->RowSpan(),
291 node->ColumnIndex(),
292 node->ColumnSpan(),
293 node->IsHeading());
295 if (node->IsRangeType()) {
296 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoRangeInfo(
297 env, obj, info,
298 node->AndroidRangeType(),
299 node->RangeMin(),
300 node->RangeMax(),
301 node->RangeCurrentValue());
304 return true;
307 jboolean BrowserAccessibilityManagerAndroid::PopulateAccessibilityEvent(
308 JNIEnv* env, jobject obj, jobject event, jint id, jint event_type) {
309 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
310 GetFromID(id));
311 if (!node)
312 return false;
314 Java_BrowserAccessibilityManager_setAccessibilityEventBooleanAttributes(
315 env, obj, event,
316 node->IsChecked(),
317 node->IsEnabled(),
318 node->IsPassword(),
319 node->IsScrollable());
320 Java_BrowserAccessibilityManager_setAccessibilityEventClassName(
321 env, obj, event,
322 base::android::ConvertUTF8ToJavaString(env, node->GetClassName()).obj());
323 Java_BrowserAccessibilityManager_setAccessibilityEventListAttributes(
324 env, obj, event,
325 node->GetItemIndex(),
326 node->GetItemCount());
327 Java_BrowserAccessibilityManager_setAccessibilityEventScrollAttributes(
328 env, obj, event,
329 node->GetScrollX(),
330 node->GetScrollY(),
331 node->GetMaxScrollX(),
332 node->GetMaxScrollY());
334 switch (event_type) {
335 case ANDROID_ACCESSIBILITY_EVENT_TYPE_VIEW_TEXT_CHANGED:
336 Java_BrowserAccessibilityManager_setAccessibilityEventTextChangedAttrs(
337 env, obj, event,
338 node->GetTextChangeFromIndex(),
339 node->GetTextChangeAddedCount(),
340 node->GetTextChangeRemovedCount(),
341 base::android::ConvertUTF16ToJavaString(
342 env, node->GetTextChangeBeforeText()).obj(),
343 base::android::ConvertUTF16ToJavaString(env, node->GetText()).obj());
344 break;
345 case ANDROID_ACCESSIBILITY_EVENT_TYPE_VIEW_TEXT_SELECTION_CHANGED:
346 Java_BrowserAccessibilityManager_setAccessibilityEventSelectionAttrs(
347 env, obj, event,
348 node->GetSelectionStart(),
349 node->GetSelectionEnd(),
350 node->GetEditableTextLength(),
351 base::android::ConvertUTF16ToJavaString(env, node->GetText()).obj());
352 break;
353 default:
354 break;
357 // Backwards-compatible fallback for new KitKat APIs.
358 Java_BrowserAccessibilityManager_setAccessibilityEventKitKatAttributes(
359 env, obj, event,
360 node->CanOpenPopup(),
361 node->IsContentInvalid(),
362 node->IsDismissable(),
363 node->IsMultiLine(),
364 node->AndroidInputType(),
365 node->AndroidLiveRegionType());
366 if (node->IsCollection()) {
367 Java_BrowserAccessibilityManager_setAccessibilityEventCollectionInfo(
368 env, obj, event,
369 node->RowCount(),
370 node->ColumnCount(),
371 node->IsHierarchical());
373 if (node->IsCollectionItem() || node->IsHeading()) {
374 Java_BrowserAccessibilityManager_setAccessibilityEventCollectionItemInfo(
375 env, obj, event,
376 node->RowIndex(),
377 node->RowSpan(),
378 node->ColumnIndex(),
379 node->ColumnSpan(),
380 node->IsHeading());
382 if (node->IsRangeType()) {
383 Java_BrowserAccessibilityManager_setAccessibilityEventRangeInfo(
384 env, obj, event,
385 node->AndroidRangeType(),
386 node->RangeMin(),
387 node->RangeMax(),
388 node->RangeCurrentValue());
391 return true;
394 void BrowserAccessibilityManagerAndroid::Click(
395 JNIEnv* env, jobject obj, jint id) {
396 BrowserAccessibility* node = GetFromID(id);
397 if (node)
398 DoDefaultAction(*node);
401 void BrowserAccessibilityManagerAndroid::Focus(
402 JNIEnv* env, jobject obj, jint id) {
403 BrowserAccessibility* node = GetFromID(id);
404 if (node)
405 SetFocus(node, true);
408 void BrowserAccessibilityManagerAndroid::Blur(JNIEnv* env, jobject obj) {
409 SetFocus(GetRoot(), true);
412 void BrowserAccessibilityManagerAndroid::ScrollToMakeNodeVisible(
413 JNIEnv* env, jobject obj, jint id) {
414 BrowserAccessibility* node = GetFromID(id);
415 if (node)
416 ScrollToMakeVisible(*node, gfx::Rect(node->GetLocation().size()));
419 BrowserAccessibility* BrowserAccessibilityManagerAndroid::FuzzyHitTest(
420 int x, int y, BrowserAccessibility* start_node) {
421 BrowserAccessibility* nearest_node = NULL;
422 int min_distance = INT_MAX;
423 FuzzyHitTestImpl(x, y, start_node, &nearest_node, &min_distance);
424 return nearest_node;
427 // static
428 void BrowserAccessibilityManagerAndroid::FuzzyHitTestImpl(
429 int x, int y, BrowserAccessibility* start_node,
430 BrowserAccessibility** nearest_candidate, int* nearest_distance) {
431 BrowserAccessibilityAndroid* node =
432 static_cast<BrowserAccessibilityAndroid*>(start_node);
433 int distance = CalculateDistanceSquared(x, y, node);
435 if (node->IsFocusable()) {
436 if (distance < *nearest_distance) {
437 *nearest_candidate = node;
438 *nearest_distance = distance;
440 // Don't examine any more children of focusable node
441 // TODO(aboxhall): what about focusable children?
442 return;
445 if (!node->GetText().empty()) {
446 if (distance < *nearest_distance) {
447 *nearest_candidate = node;
448 *nearest_distance = distance;
450 return;
453 for (uint32 i = 0; i < node->PlatformChildCount(); i++) {
454 BrowserAccessibility* child = node->PlatformGetChild(i);
455 FuzzyHitTestImpl(x, y, child, nearest_candidate, nearest_distance);
459 // static
460 int BrowserAccessibilityManagerAndroid::CalculateDistanceSquared(
461 int x, int y, BrowserAccessibility* node) {
462 gfx::Rect node_bounds = node->GetLocalBoundsRect();
463 int nearest_x = Clamp(x, node_bounds.x(), node_bounds.right());
464 int nearest_y = Clamp(y, node_bounds.y(), node_bounds.bottom());
465 int dx = std::abs(x - nearest_x);
466 int dy = std::abs(y - nearest_y);
467 return dx * dx + dy * dy;
470 jint BrowserAccessibilityManagerAndroid::FindElementType(
471 JNIEnv* env, jobject obj, jint start_id, jstring element_type_str,
472 jboolean forwards) {
473 BrowserAccessibility* node = GetFromID(start_id);
474 if (!node)
475 return 0;
477 AndroidHtmlElementType element_type = HtmlElementTypeFromString(
478 base::android::ConvertJavaStringToUTF16(env, element_type_str));
480 node = forwards ? NextInTreeOrder(node) : PreviousInTreeOrder(node);
481 while (node) {
482 switch(element_type) {
483 case HTML_ELEMENT_TYPE_SECTION:
484 if (node->GetRole() == ui::AX_ROLE_ARTICLE ||
485 node->GetRole() == ui::AX_ROLE_APPLICATION ||
486 node->GetRole() == ui::AX_ROLE_BANNER ||
487 node->GetRole() == ui::AX_ROLE_COMPLEMENTARY ||
488 node->GetRole() == ui::AX_ROLE_CONTENT_INFO ||
489 node->GetRole() == ui::AX_ROLE_HEADING ||
490 node->GetRole() == ui::AX_ROLE_MAIN ||
491 node->GetRole() == ui::AX_ROLE_NAVIGATION ||
492 node->GetRole() == ui::AX_ROLE_SEARCH ||
493 node->GetRole() == ui::AX_ROLE_REGION) {
494 return node->GetId();
496 break;
497 case HTML_ELEMENT_TYPE_LIST:
498 if (node->GetRole() == ui::AX_ROLE_LIST ||
499 node->GetRole() == ui::AX_ROLE_GRID ||
500 node->GetRole() == ui::AX_ROLE_TABLE ||
501 node->GetRole() == ui::AX_ROLE_TREE) {
502 return node->GetId();
504 break;
505 case HTML_ELEMENT_TYPE_CONTROL:
506 if (static_cast<BrowserAccessibilityAndroid*>(node)->IsFocusable())
507 return node->GetId();
508 break;
509 case HTML_ELEMENT_TYPE_ANY:
510 // In theory, the API says that an accessibility service could
511 // jump to an element by element name, like 'H1' or 'P'. This isn't
512 // currently used by any accessibility service, and we think it's
513 // better to keep them high-level like 'SECTION' or 'CONTROL', so we
514 // just fall back on linear navigation when we don't recognize the
515 // element type.
516 if (static_cast<BrowserAccessibilityAndroid*>(node)->IsClickable())
517 return node->GetId();
518 break;
521 node = forwards ? NextInTreeOrder(node) : PreviousInTreeOrder(node);
524 return 0;
527 void BrowserAccessibilityManagerAndroid::OnRootChanged(ui::AXNode* new_root) {
528 JNIEnv* env = AttachCurrentThread();
529 ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
530 if (obj.is_null())
531 return;
533 Java_BrowserAccessibilityManager_handleNavigate(env, obj.obj());
536 bool
537 BrowserAccessibilityManagerAndroid::UseRootScrollOffsetsWhenComputingBounds() {
538 // The Java layer handles the root scroll offset.
539 return false;
542 bool RegisterBrowserAccessibilityManager(JNIEnv* env) {
543 return RegisterNativesImpl(env);
546 } // namespace content