• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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].
Clamp(int val,int min,int 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
Create(const AccessibilityNodeData & src,BrowserAccessibilityDelegate * delegate,BrowserAccessibilityFactory * factory)44 BrowserAccessibilityManager* BrowserAccessibilityManager::Create(
45     const AccessibilityNodeData& src,
46     BrowserAccessibilityDelegate* delegate,
47     BrowserAccessibilityFactory* factory) {
48   return new BrowserAccessibilityManagerAndroid(ScopedJavaLocalRef<jobject>(),
49                                                 src, delegate, factory);
50 }
51 
52 BrowserAccessibilityManagerAndroid*
ToBrowserAccessibilityManagerAndroid()53 BrowserAccessibilityManager::ToBrowserAccessibilityManagerAndroid() {
54   return static_cast<BrowserAccessibilityManagerAndroid*>(this);
55 }
56 
BrowserAccessibilityManagerAndroid(ScopedJavaLocalRef<jobject> content_view_core,const AccessibilityNodeData & src,BrowserAccessibilityDelegate * delegate,BrowserAccessibilityFactory * factory)57 BrowserAccessibilityManagerAndroid::BrowserAccessibilityManagerAndroid(
58     ScopedJavaLocalRef<jobject> content_view_core,
59     const AccessibilityNodeData& src,
60     BrowserAccessibilityDelegate* delegate,
61     BrowserAccessibilityFactory* factory)
62     : BrowserAccessibilityManager(src, delegate, factory) {
63   SetContentViewCore(content_view_core);
64 }
65 
~BrowserAccessibilityManagerAndroid()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
GetEmptyDocument()76 AccessibilityNodeData BrowserAccessibilityManagerAndroid::GetEmptyDocument() {
77   AccessibilityNodeData empty_document;
78   empty_document.id = 0;
79   empty_document.role = blink::WebAXRoleRootWebArea;
80   empty_document.state = 1 << blink::WebAXStateReadonly;
81   return empty_document;
82 }
83 
SetContentViewCore(ScopedJavaLocalRef<jobject> content_view_core)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 
NotifyAccessibilityEvent(blink::WebAXEvent event_type,BrowserAccessibility * node)96 void BrowserAccessibilityManagerAndroid::NotifyAccessibilityEvent(
97     blink::WebAXEvent 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 == blink::WebAXEventHide)
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 blink::WebAXEventLoadComplete:
115       Java_BrowserAccessibilityManager_handlePageLoaded(
116           env, obj.obj(), focus_->renderer_id());
117       break;
118     case blink::WebAXEventFocus:
119       Java_BrowserAccessibilityManager_handleFocusChanged(
120           env, obj.obj(), node->renderer_id());
121       break;
122     case blink::WebAXEventCheckedStateChanged:
123       Java_BrowserAccessibilityManager_handleCheckStateChanged(
124           env, obj.obj(), node->renderer_id());
125       break;
126     case blink::WebAXEventScrolledToAnchor:
127       Java_BrowserAccessibilityManager_handleScrolledToAnchor(
128           env, obj.obj(), node->renderer_id());
129       break;
130     case blink::WebAXEventAlert:
131       // An alert is a special case of live region. Fall through to the
132       // next case to handle it.
133     case blink::WebAXEventShow: {
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 blink::WebAXEventSelectedTextChanged:
145       Java_BrowserAccessibilityManager_handleTextSelectionChanged(
146           env, obj.obj(), node->renderer_id());
147       break;
148     case blink::WebAXEventChildrenChanged:
149     case blink::WebAXEventTextChanged:
150     case blink::WebAXEventValueChanged:
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 
GetRootId(JNIEnv * env,jobject obj)163 jint BrowserAccessibilityManagerAndroid::GetRootId(JNIEnv* env, jobject obj) {
164   return static_cast<jint>(root_->renderer_id());
165 }
166 
IsNodeValid(JNIEnv * env,jobject obj,jint id)167 jboolean BrowserAccessibilityManagerAndroid::IsNodeValid(
168     JNIEnv* env, jobject obj, jint id) {
169   return GetFromRendererID(id) != NULL;
170 }
171 
HitTest(JNIEnv * env,jobject obj,jint x,jint y)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 
PopulateAccessibilityNodeInfo(JNIEnv * env,jobject obj,jobject info,jint id)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 
PopulateAccessibilityEvent(JNIEnv * env,jobject obj,jobject event,jint id,jint event_type)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 
Click(JNIEnv * env,jobject obj,jint id)364 void BrowserAccessibilityManagerAndroid::Click(
365     JNIEnv* env, jobject obj, jint id) {
366   BrowserAccessibility* node = GetFromRendererID(id);
367   if (node)
368     DoDefaultAction(*node);
369 }
370 
Focus(JNIEnv * env,jobject obj,jint id)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 
Blur(JNIEnv * env,jobject obj)378 void BrowserAccessibilityManagerAndroid::Blur(JNIEnv* env, jobject obj) {
379   SetFocus(root_, true);
380 }
381 
FuzzyHitTest(int x,int y,BrowserAccessibility * start_node)382 BrowserAccessibility* BrowserAccessibilityManagerAndroid::FuzzyHitTest(
383     int x, int y, BrowserAccessibility* start_node) {
384   BrowserAccessibility* nearest_node = NULL;
385   int min_distance = INT_MAX;
386   FuzzyHitTestImpl(x, y, start_node, &nearest_node, &min_distance);
387   return nearest_node;
388 }
389 
390 // static
FuzzyHitTestImpl(int x,int y,BrowserAccessibility * start_node,BrowserAccessibility ** nearest_candidate,int * nearest_distance)391 void BrowserAccessibilityManagerAndroid::FuzzyHitTestImpl(
392     int x, int y, BrowserAccessibility* start_node,
393     BrowserAccessibility** nearest_candidate, int* nearest_distance) {
394   BrowserAccessibilityAndroid* node =
395       static_cast<BrowserAccessibilityAndroid*>(start_node);
396   int distance = CalculateDistanceSquared(x, y, node);
397 
398   if (node->IsFocusable()) {
399     if (distance < *nearest_distance) {
400       *nearest_candidate = node;
401       *nearest_distance = distance;
402     }
403     // Don't examine any more children of focusable node
404     // TODO(aboxhall): what about focusable children?
405     return;
406   }
407 
408   if (!node->GetText().empty()) {
409     if (distance < *nearest_distance) {
410       *nearest_candidate = node;
411       *nearest_distance = distance;
412     }
413     return;
414   }
415 
416   for (uint32 i = 0; i < node->PlatformChildCount(); i++) {
417     BrowserAccessibility* child = node->PlatformGetChild(i);
418     FuzzyHitTestImpl(x, y, child, nearest_candidate, nearest_distance);
419   }
420 }
421 
422 // static
CalculateDistanceSquared(int x,int y,BrowserAccessibility * node)423 int BrowserAccessibilityManagerAndroid::CalculateDistanceSquared(
424     int x, int y, BrowserAccessibility* node) {
425   gfx::Rect node_bounds = node->GetLocalBoundsRect();
426   int nearest_x = Clamp(x, node_bounds.x(), node_bounds.right());
427   int nearest_y = Clamp(y, node_bounds.y(), node_bounds.bottom());
428   int dx = std::abs(x - nearest_x);
429   int dy = std::abs(y - nearest_y);
430   return dx * dx + dy * dy;
431 }
432 
NotifyRootChanged()433 void BrowserAccessibilityManagerAndroid::NotifyRootChanged() {
434   JNIEnv* env = AttachCurrentThread();
435   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
436   if (obj.is_null())
437     return;
438 
439   Java_BrowserAccessibilityManager_handleNavigate(env, obj.obj());
440 }
441 
442 bool
UseRootScrollOffsetsWhenComputingBounds()443 BrowserAccessibilityManagerAndroid::UseRootScrollOffsetsWhenComputingBounds() {
444   // The Java layer handles the root scroll offset.
445   return false;
446 }
447 
RegisterBrowserAccessibilityManager(JNIEnv * env)448 bool RegisterBrowserAccessibilityManager(JNIEnv* env) {
449   return RegisterNativesImpl(env);
450 }
451 
452 }  // namespace content
453