Remove PlatformFile from profile_browsertest
[chromium-blink-merge.git] / content / browser / accessibility / browser_accessibility_manager_android.cc
blobc079825b3d8089206b8f4f77cbfe7b190150c202
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::AXNodeData& src,
66 BrowserAccessibilityDelegate* delegate,
67 BrowserAccessibilityFactory* factory) {
68 return new BrowserAccessibilityManagerAndroid(ScopedJavaLocalRef<jobject>(),
69 src, 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::AXNodeData& src,
80 BrowserAccessibilityDelegate* delegate,
81 BrowserAccessibilityFactory* factory)
82 : BrowserAccessibilityManager(src, 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::AXNodeData 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;
101 return empty_document;
104 void BrowserAccessibilityManagerAndroid::SetContentViewCore(
105 ScopedJavaLocalRef<jobject> content_view_core) {
106 if (content_view_core.is_null())
107 return;
109 JNIEnv* env = AttachCurrentThread();
110 java_ref_ = JavaObjectWeakGlobalRef(
111 env, Java_BrowserAccessibilityManager_create(
112 env, reinterpret_cast<intptr_t>(this),
113 content_view_core.obj()).obj());
116 void BrowserAccessibilityManagerAndroid::NotifyAccessibilityEvent(
117 ui::AXEvent event_type,
118 BrowserAccessibility* node) {
119 JNIEnv* env = AttachCurrentThread();
120 ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
121 if (obj.is_null())
122 return;
124 if (event_type == ui::AX_EVENT_HIDE)
125 return;
127 // Always send AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED to notify
128 // the Android system that the accessibility hierarchy rooted at this
129 // node has changed.
130 Java_BrowserAccessibilityManager_handleContentChanged(
131 env, obj.obj(), node->renderer_id());
133 switch (event_type) {
134 case ui::AX_EVENT_LOAD_COMPLETE:
135 Java_BrowserAccessibilityManager_handlePageLoaded(
136 env, obj.obj(), focus_->renderer_id());
137 break;
138 case ui::AX_EVENT_FOCUS:
139 Java_BrowserAccessibilityManager_handleFocusChanged(
140 env, obj.obj(), node->renderer_id());
141 break;
142 case ui::AX_EVENT_CHECKED_STATE_CHANGED:
143 Java_BrowserAccessibilityManager_handleCheckStateChanged(
144 env, obj.obj(), node->renderer_id());
145 break;
146 case ui::AX_EVENT_SCROLL_POSITION_CHANGED:
147 Java_BrowserAccessibilityManager_handleScrollPositionChanged(
148 env, obj.obj(), node->renderer_id());
149 break;
150 case ui::AX_EVENT_SCROLLED_TO_ANCHOR:
151 Java_BrowserAccessibilityManager_handleScrolledToAnchor(
152 env, obj.obj(), node->renderer_id());
153 break;
154 case ui::AX_EVENT_ALERT:
155 // An alert is a special case of live region. Fall through to the
156 // next case to handle it.
157 case ui::AX_EVENT_SHOW: {
158 // This event is fired when an object appears in a live region.
159 // Speak its text.
160 BrowserAccessibilityAndroid* android_node =
161 static_cast<BrowserAccessibilityAndroid*>(node);
162 Java_BrowserAccessibilityManager_announceLiveRegionText(
163 env, obj.obj(),
164 base::android::ConvertUTF16ToJavaString(
165 env, android_node->GetText()).obj());
166 break;
168 case ui::AX_EVENT_SELECTED_TEXT_CHANGED:
169 Java_BrowserAccessibilityManager_handleTextSelectionChanged(
170 env, obj.obj(), node->renderer_id());
171 break;
172 case ui::AX_EVENT_CHILDREN_CHANGED:
173 case ui::AX_EVENT_TEXT_CHANGED:
174 case ui::AX_EVENT_VALUE_CHANGED:
175 if (node->IsEditableText()) {
176 Java_BrowserAccessibilityManager_handleEditableTextChanged(
177 env, obj.obj(), node->renderer_id());
179 break;
180 default:
181 // There are some notifications that aren't meaningful on Android.
182 // It's okay to skip them.
183 break;
187 jint BrowserAccessibilityManagerAndroid::GetRootId(JNIEnv* env, jobject obj) {
188 return static_cast<jint>(root_->renderer_id());
191 jboolean BrowserAccessibilityManagerAndroid::IsNodeValid(
192 JNIEnv* env, jobject obj, jint id) {
193 return GetFromRendererID(id) != NULL;
196 jint BrowserAccessibilityManagerAndroid::HitTest(
197 JNIEnv* env, jobject obj, jint x, jint y) {
198 BrowserAccessibilityAndroid* result =
199 static_cast<BrowserAccessibilityAndroid*>(
200 root_->BrowserAccessibilityForPoint(gfx::Point(x, y)));
202 if (!result)
203 return root_->renderer_id();
205 if (result->IsFocusable())
206 return result->renderer_id();
208 // Examine the children of |result| to find the nearest accessibility focus
209 // candidate
210 BrowserAccessibility* nearest_node = FuzzyHitTest(x, y, result);
211 if (nearest_node)
212 return nearest_node->renderer_id();
214 return root_->renderer_id();
217 jboolean BrowserAccessibilityManagerAndroid::PopulateAccessibilityNodeInfo(
218 JNIEnv* env, jobject obj, jobject info, jint id) {
219 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
220 GetFromRendererID(id));
221 if (!node)
222 return false;
224 if (node->parent()) {
225 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoParent(
226 env, obj, info, node->parent()->renderer_id());
228 for (unsigned i = 0; i < node->PlatformChildCount(); ++i) {
229 Java_BrowserAccessibilityManager_addAccessibilityNodeInfoChild(
230 env, obj, info, node->children()[i]->renderer_id());
232 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoBooleanAttributes(
233 env, obj, info,
235 node->IsCheckable(),
236 node->IsChecked(),
237 node->IsClickable(),
238 node->IsEnabled(),
239 node->IsFocusable(),
240 node->IsFocused(),
241 node->IsPassword(),
242 node->IsScrollable(),
243 node->IsSelected(),
244 node->IsVisibleToUser());
245 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoClassName(
246 env, obj, info,
247 base::android::ConvertUTF8ToJavaString(env, node->GetClassName()).obj());
248 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoContentDescription(
249 env, obj, info,
250 base::android::ConvertUTF16ToJavaString(env, node->GetText()).obj(),
251 node->IsLink());
253 gfx::Rect absolute_rect = node->GetLocalBoundsRect();
254 gfx::Rect parent_relative_rect = absolute_rect;
255 if (node->parent()) {
256 gfx::Rect parent_rect = node->parent()->GetLocalBoundsRect();
257 parent_relative_rect.Offset(-parent_rect.OffsetFromOrigin());
259 bool is_root = node->parent() == NULL;
260 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoLocation(
261 env, obj, info,
262 absolute_rect.x(), absolute_rect.y(),
263 parent_relative_rect.x(), parent_relative_rect.y(),
264 absolute_rect.width(), absolute_rect.height(),
265 is_root);
267 // New KitKat APIs
268 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoKitKatAttributes(
269 env, obj, info,
270 node->CanOpenPopup(),
271 node->IsContentInvalid(),
272 node->IsDismissable(),
273 node->IsMultiLine(),
274 node->AndroidInputType(),
275 node->AndroidLiveRegionType());
276 if (node->IsCollection()) {
277 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoCollectionInfo(
278 env, obj, info,
279 node->RowCount(),
280 node->ColumnCount(),
281 node->IsHierarchical());
283 if (node->IsCollectionItem() || node->IsHeading()) {
284 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoCollectionItemInfo(
285 env, obj, info,
286 node->RowIndex(),
287 node->RowSpan(),
288 node->ColumnIndex(),
289 node->ColumnSpan(),
290 node->IsHeading());
292 if (node->IsRangeType()) {
293 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoRangeInfo(
294 env, obj, info,
295 node->AndroidRangeType(),
296 node->RangeMin(),
297 node->RangeMax(),
298 node->RangeCurrentValue());
301 return true;
304 jboolean BrowserAccessibilityManagerAndroid::PopulateAccessibilityEvent(
305 JNIEnv* env, jobject obj, jobject event, jint id, jint event_type) {
306 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>(
307 GetFromRendererID(id));
308 if (!node)
309 return false;
311 Java_BrowserAccessibilityManager_setAccessibilityEventBooleanAttributes(
312 env, obj, event,
313 node->IsChecked(),
314 node->IsEnabled(),
315 node->IsPassword(),
316 node->IsScrollable());
317 Java_BrowserAccessibilityManager_setAccessibilityEventClassName(
318 env, obj, event,
319 base::android::ConvertUTF8ToJavaString(env, node->GetClassName()).obj());
320 Java_BrowserAccessibilityManager_setAccessibilityEventListAttributes(
321 env, obj, event,
322 node->GetItemIndex(),
323 node->GetItemCount());
324 Java_BrowserAccessibilityManager_setAccessibilityEventScrollAttributes(
325 env, obj, event,
326 node->GetScrollX(),
327 node->GetScrollY(),
328 node->GetMaxScrollX(),
329 node->GetMaxScrollY());
331 switch (event_type) {
332 case ANDROID_ACCESSIBILITY_EVENT_TYPE_VIEW_TEXT_CHANGED:
333 Java_BrowserAccessibilityManager_setAccessibilityEventTextChangedAttrs(
334 env, obj, event,
335 node->GetTextChangeFromIndex(),
336 node->GetTextChangeAddedCount(),
337 node->GetTextChangeRemovedCount(),
338 base::android::ConvertUTF16ToJavaString(
339 env, node->GetTextChangeBeforeText()).obj(),
340 base::android::ConvertUTF16ToJavaString(env, node->GetText()).obj());
341 break;
342 case ANDROID_ACCESSIBILITY_EVENT_TYPE_VIEW_TEXT_SELECTION_CHANGED:
343 Java_BrowserAccessibilityManager_setAccessibilityEventSelectionAttrs(
344 env, obj, event,
345 node->GetSelectionStart(),
346 node->GetSelectionEnd(),
347 node->GetEditableTextLength(),
348 base::android::ConvertUTF16ToJavaString(env, node->GetText()).obj());
349 break;
350 default:
351 break;
354 // Backwards-compatible fallback for new KitKat APIs.
355 Java_BrowserAccessibilityManager_setAccessibilityEventKitKatAttributes(
356 env, obj, event,
357 node->CanOpenPopup(),
358 node->IsContentInvalid(),
359 node->IsDismissable(),
360 node->IsMultiLine(),
361 node->AndroidInputType(),
362 node->AndroidLiveRegionType());
363 if (node->IsCollection()) {
364 Java_BrowserAccessibilityManager_setAccessibilityEventCollectionInfo(
365 env, obj, event,
366 node->RowCount(),
367 node->ColumnCount(),
368 node->IsHierarchical());
370 if (node->IsCollectionItem() || node->IsHeading()) {
371 Java_BrowserAccessibilityManager_setAccessibilityEventCollectionItemInfo(
372 env, obj, event,
373 node->RowIndex(),
374 node->RowSpan(),
375 node->ColumnIndex(),
376 node->ColumnSpan(),
377 node->IsHeading());
379 if (node->IsRangeType()) {
380 Java_BrowserAccessibilityManager_setAccessibilityEventRangeInfo(
381 env, obj, event,
382 node->AndroidRangeType(),
383 node->RangeMin(),
384 node->RangeMax(),
385 node->RangeCurrentValue());
388 return true;
391 void BrowserAccessibilityManagerAndroid::Click(
392 JNIEnv* env, jobject obj, jint id) {
393 BrowserAccessibility* node = GetFromRendererID(id);
394 if (node)
395 DoDefaultAction(*node);
398 void BrowserAccessibilityManagerAndroid::Focus(
399 JNIEnv* env, jobject obj, jint id) {
400 BrowserAccessibility* node = GetFromRendererID(id);
401 if (node)
402 SetFocus(node, true);
405 void BrowserAccessibilityManagerAndroid::Blur(JNIEnv* env, jobject obj) {
406 SetFocus(root_, true);
409 void BrowserAccessibilityManagerAndroid::ScrollToMakeNodeVisible(
410 JNIEnv* env, jobject obj, jint id) {
411 BrowserAccessibility* node = GetFromRendererID(id);
412 if (node)
413 ScrollToMakeVisible(*node, gfx::Rect(node->location().size()));
416 BrowserAccessibility* BrowserAccessibilityManagerAndroid::FuzzyHitTest(
417 int x, int y, BrowserAccessibility* start_node) {
418 BrowserAccessibility* nearest_node = NULL;
419 int min_distance = INT_MAX;
420 FuzzyHitTestImpl(x, y, start_node, &nearest_node, &min_distance);
421 return nearest_node;
424 // static
425 void BrowserAccessibilityManagerAndroid::FuzzyHitTestImpl(
426 int x, int y, BrowserAccessibility* start_node,
427 BrowserAccessibility** nearest_candidate, int* nearest_distance) {
428 BrowserAccessibilityAndroid* node =
429 static_cast<BrowserAccessibilityAndroid*>(start_node);
430 int distance = CalculateDistanceSquared(x, y, node);
432 if (node->IsFocusable()) {
433 if (distance < *nearest_distance) {
434 *nearest_candidate = node;
435 *nearest_distance = distance;
437 // Don't examine any more children of focusable node
438 // TODO(aboxhall): what about focusable children?
439 return;
442 if (!node->GetText().empty()) {
443 if (distance < *nearest_distance) {
444 *nearest_candidate = node;
445 *nearest_distance = distance;
447 return;
450 for (uint32 i = 0; i < node->PlatformChildCount(); i++) {
451 BrowserAccessibility* child = node->PlatformGetChild(i);
452 FuzzyHitTestImpl(x, y, child, nearest_candidate, nearest_distance);
456 // static
457 int BrowserAccessibilityManagerAndroid::CalculateDistanceSquared(
458 int x, int y, BrowserAccessibility* node) {
459 gfx::Rect node_bounds = node->GetLocalBoundsRect();
460 int nearest_x = Clamp(x, node_bounds.x(), node_bounds.right());
461 int nearest_y = Clamp(y, node_bounds.y(), node_bounds.bottom());
462 int dx = std::abs(x - nearest_x);
463 int dy = std::abs(y - nearest_y);
464 return dx * dx + dy * dy;
467 jint BrowserAccessibilityManagerAndroid::FindElementType(
468 JNIEnv* env, jobject obj, jint start_id, jstring element_type_str,
469 jboolean forwards) {
470 BrowserAccessibility* node = GetFromRendererID(start_id);
471 if (!node)
472 return 0;
474 AndroidHtmlElementType element_type = HtmlElementTypeFromString(
475 base::android::ConvertJavaStringToUTF16(env, element_type_str));
477 node = forwards ? NextInTreeOrder(node) : PreviousInTreeOrder(node);
478 while (node) {
479 switch(element_type) {
480 case HTML_ELEMENT_TYPE_SECTION:
481 if (node->role() == ui::AX_ROLE_ARTICLE ||
482 node->role() == ui::AX_ROLE_APPLICATION ||
483 node->role() == ui::AX_ROLE_BANNER ||
484 node->role() == ui::AX_ROLE_COMPLEMENTARY ||
485 node->role() == ui::AX_ROLE_CONTENT_INFO ||
486 node->role() == ui::AX_ROLE_HEADING ||
487 node->role() == ui::AX_ROLE_MAIN ||
488 node->role() == ui::AX_ROLE_NAVIGATION ||
489 node->role() == ui::AX_ROLE_SEARCH ||
490 node->role() == ui::AX_ROLE_REGION) {
491 return node->renderer_id();
493 break;
494 case HTML_ELEMENT_TYPE_LIST:
495 if (node->role() == ui::AX_ROLE_LIST ||
496 node->role() == ui::AX_ROLE_GRID ||
497 node->role() == ui::AX_ROLE_TABLE ||
498 node->role() == ui::AX_ROLE_TREE) {
499 return node->renderer_id();
501 break;
502 case HTML_ELEMENT_TYPE_CONTROL:
503 if (static_cast<BrowserAccessibilityAndroid*>(node)->IsFocusable())
504 return node->renderer_id();
505 break;
506 case HTML_ELEMENT_TYPE_ANY:
507 // In theory, the API says that an accessibility service could
508 // jump to an element by element name, like 'H1' or 'P'. This isn't
509 // currently used by any accessibility service, and we think it's
510 // better to keep them high-level like 'SECTION' or 'CONTROL', so we
511 // just fall back on linear navigation when we don't recognize the
512 // element type.
513 if (static_cast<BrowserAccessibilityAndroid*>(node)->IsClickable())
514 return node->renderer_id();
515 break;
518 node = forwards ? NextInTreeOrder(node) : PreviousInTreeOrder(node);
521 return 0;
524 void BrowserAccessibilityManagerAndroid::NotifyRootChanged() {
525 JNIEnv* env = AttachCurrentThread();
526 ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
527 if (obj.is_null())
528 return;
530 Java_BrowserAccessibilityManager_handleNavigate(env, obj.obj());
533 bool
534 BrowserAccessibilityManagerAndroid::UseRootScrollOffsetsWhenComputingBounds() {
535 // The Java layer handles the root scroll offset.
536 return false;
539 bool RegisterBrowserAccessibilityManager(JNIEnv* env) {
540 return RegisterNativesImpl(env);
543 } // namespace content