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]. 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 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::BrowserAccessibilityManagerAndroid( 53 ScopedJavaLocalRef<jobject> content_view_core, 54 const AccessibilityNodeData& src, 55 BrowserAccessibilityDelegate* delegate, 56 BrowserAccessibilityFactory* factory) 57 : BrowserAccessibilityManager(src, delegate, factory) { 58 if (content_view_core.is_null()) 59 return; 60 61 JNIEnv* env = AttachCurrentThread(); 62 java_ref_ = JavaObjectWeakGlobalRef( 63 env, Java_BrowserAccessibilityManager_create( 64 env, reinterpret_cast<jint>(this), content_view_core.obj()).obj()); 65 } 66 67 BrowserAccessibilityManagerAndroid::~BrowserAccessibilityManagerAndroid() { 68 JNIEnv* env = AttachCurrentThread(); 69 ScopedJavaLocalRef<jobject> obj = java_ref_.get(env); 70 if (obj.is_null()) 71 return; 72 73 Java_BrowserAccessibilityManager_onNativeObjectDestroyed(env, obj.obj()); 74 } 75 76 // static 77 AccessibilityNodeData BrowserAccessibilityManagerAndroid::GetEmptyDocument() { 78 AccessibilityNodeData empty_document; 79 empty_document.id = 0; 80 empty_document.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA; 81 empty_document.state = 1 << AccessibilityNodeData::STATE_READONLY; 82 return empty_document; 83 } 84 85 void BrowserAccessibilityManagerAndroid::NotifyAccessibilityEvent( 86 int type, 87 BrowserAccessibility* node) { 88 JNIEnv* env = AttachCurrentThread(); 89 ScopedJavaLocalRef<jobject> obj = java_ref_.get(env); 90 if (obj.is_null()) 91 return; 92 93 // Always send AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED to notify 94 // the Android system that the accessibility hierarchy rooted at this 95 // node has changed. 96 Java_BrowserAccessibilityManager_handleContentChanged( 97 env, obj.obj(), node->renderer_id()); 98 99 switch (type) { 100 case AccessibilityNotificationLoadComplete: 101 Java_BrowserAccessibilityManager_handlePageLoaded( 102 env, obj.obj(), focus_->renderer_id()); 103 break; 104 case AccessibilityNotificationFocusChanged: 105 Java_BrowserAccessibilityManager_handleFocusChanged( 106 env, obj.obj(), node->renderer_id()); 107 break; 108 case AccessibilityNotificationCheckStateChanged: 109 Java_BrowserAccessibilityManager_handleCheckStateChanged( 110 env, obj.obj(), node->renderer_id()); 111 break; 112 case AccessibilityNotificationScrolledToAnchor: 113 Java_BrowserAccessibilityManager_handleScrolledToAnchor( 114 env, obj.obj(), node->renderer_id()); 115 break; 116 case AccessibilityNotificationAlert: 117 // An alert is a special case of live region. Fall through to the 118 // next case to handle it. 119 case AccessibilityNotificationObjectShow: { 120 // This event is fired when an object appears in a live region. 121 // Speak its text. 122 BrowserAccessibilityAndroid* android_node = 123 static_cast<BrowserAccessibilityAndroid*>(node); 124 Java_BrowserAccessibilityManager_announceLiveRegionText( 125 env, obj.obj(), 126 base::android::ConvertUTF16ToJavaString( 127 env, android_node->GetText()).obj()); 128 break; 129 } 130 case AccessibilityNotificationSelectedTextChanged: 131 Java_BrowserAccessibilityManager_handleTextSelectionChanged( 132 env, obj.obj(), node->renderer_id()); 133 break; 134 case AccessibilityNotificationChildrenChanged: 135 case AccessibilityNotificationTextChanged: 136 case AccessibilityNotificationValueChanged: 137 if (node->IsEditableText()) { 138 Java_BrowserAccessibilityManager_handleEditableTextChanged( 139 env, obj.obj(), node->renderer_id()); 140 } 141 break; 142 default: 143 // There are some notifications that aren't meaningful on Android. 144 // It's okay to skip them. 145 break; 146 } 147 } 148 149 jint BrowserAccessibilityManagerAndroid::GetRootId(JNIEnv* env, jobject obj) { 150 return static_cast<jint>(root_->renderer_id()); 151 } 152 153 jint BrowserAccessibilityManagerAndroid::HitTest( 154 JNIEnv* env, jobject obj, jint x, jint y) { 155 BrowserAccessibilityAndroid* result = 156 static_cast<BrowserAccessibilityAndroid*>( 157 root_->BrowserAccessibilityForPoint(gfx::Point(x, y))); 158 159 if (!result) 160 return root_->renderer_id(); 161 162 if (result->IsFocusable()) 163 return result->renderer_id(); 164 165 // Examine the children of |result| to find the nearest accessibility focus 166 // candidate 167 BrowserAccessibility* nearest_node = FuzzyHitTest(x, y, result); 168 if (nearest_node) 169 return nearest_node->renderer_id(); 170 171 return root_->renderer_id(); 172 } 173 174 jboolean BrowserAccessibilityManagerAndroid::PopulateAccessibilityNodeInfo( 175 JNIEnv* env, jobject obj, jobject info, jint id) { 176 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>( 177 GetFromRendererID(id)); 178 if (!node) 179 return false; 180 181 if (node->parent()) { 182 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoParent( 183 env, obj, info, node->parent()->renderer_id()); 184 } 185 if (!node->IsLeaf()) { 186 for (unsigned i = 0; i < node->child_count(); ++i) { 187 Java_BrowserAccessibilityManager_addAccessibilityNodeInfoChild( 188 env, obj, info, node->children()[i]->renderer_id()); 189 } 190 } 191 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoBooleanAttributes( 192 env, obj, info, 193 id, 194 node->IsCheckable(), 195 node->IsChecked(), 196 node->IsClickable(), 197 node->IsEnabled(), 198 node->IsFocusable(), 199 node->IsFocused(), 200 node->IsPassword(), 201 node->IsScrollable(), 202 node->IsSelected(), 203 node->IsVisibleToUser()); 204 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoStringAttributes( 205 env, obj, info, 206 base::android::ConvertUTF8ToJavaString(env, node->GetClassName()).obj(), 207 base::android::ConvertUTF16ToJavaString(env, node->GetText()).obj()); 208 209 gfx::Rect absolute_rect = node->GetLocalBoundsRect(); 210 gfx::Rect parent_relative_rect = absolute_rect; 211 if (node->parent()) { 212 gfx::Rect parent_rect = node->parent()->GetLocalBoundsRect(); 213 parent_relative_rect.Offset(-parent_rect.OffsetFromOrigin()); 214 } 215 bool is_root = node->parent() == NULL; 216 Java_BrowserAccessibilityManager_setAccessibilityNodeInfoLocation( 217 env, obj, info, 218 absolute_rect.x(), absolute_rect.y(), 219 parent_relative_rect.x(), parent_relative_rect.y(), 220 absolute_rect.width(), absolute_rect.height(), 221 is_root); 222 223 return true; 224 } 225 226 jboolean BrowserAccessibilityManagerAndroid::PopulateAccessibilityEvent( 227 JNIEnv* env, jobject obj, jobject event, jint id, jint event_type) { 228 BrowserAccessibilityAndroid* node = static_cast<BrowserAccessibilityAndroid*>( 229 GetFromRendererID(id)); 230 if (!node) 231 return false; 232 233 Java_BrowserAccessibilityManager_setAccessibilityEventBooleanAttributes( 234 env, obj, event, 235 node->IsChecked(), 236 node->IsEnabled(), 237 node->IsPassword(), 238 node->IsScrollable()); 239 Java_BrowserAccessibilityManager_setAccessibilityEventClassName( 240 env, obj, event, 241 base::android::ConvertUTF8ToJavaString(env, node->GetClassName()).obj()); 242 Java_BrowserAccessibilityManager_setAccessibilityEventListAttributes( 243 env, obj, event, 244 node->GetItemIndex(), 245 node->GetItemCount()); 246 Java_BrowserAccessibilityManager_setAccessibilityEventScrollAttributes( 247 env, obj, event, 248 node->GetScrollX(), 249 node->GetScrollY(), 250 node->GetMaxScrollX(), 251 node->GetMaxScrollY()); 252 253 switch (event_type) { 254 case ANDROID_ACCESSIBILITY_EVENT_TYPE_VIEW_TEXT_CHANGED: 255 Java_BrowserAccessibilityManager_setAccessibilityEventTextChangedAttrs( 256 env, obj, event, 257 node->GetTextChangeFromIndex(), 258 node->GetTextChangeAddedCount(), 259 node->GetTextChangeRemovedCount(), 260 base::android::ConvertUTF16ToJavaString( 261 env, node->GetTextChangeBeforeText()).obj(), 262 base::android::ConvertUTF16ToJavaString(env, node->GetText()).obj()); 263 break; 264 case ANDROID_ACCESSIBILITY_EVENT_TYPE_VIEW_TEXT_SELECTION_CHANGED: 265 Java_BrowserAccessibilityManager_setAccessibilityEventSelectionAttrs( 266 env, obj, event, 267 node->GetSelectionStart(), 268 node->GetSelectionEnd(), 269 node->GetEditableTextLength(), 270 base::android::ConvertUTF16ToJavaString(env, node->GetText()).obj()); 271 break; 272 default: 273 break; 274 } 275 276 return true; 277 } 278 279 void BrowserAccessibilityManagerAndroid::Click( 280 JNIEnv* env, jobject obj, jint id) { 281 BrowserAccessibility* node = GetFromRendererID(id); 282 if (node) 283 DoDefaultAction(*node); 284 } 285 286 void BrowserAccessibilityManagerAndroid::Focus( 287 JNIEnv* env, jobject obj, jint id) { 288 BrowserAccessibility* node = GetFromRendererID(id); 289 if (node) 290 SetFocus(node, true); 291 } 292 293 void BrowserAccessibilityManagerAndroid::Blur(JNIEnv* env, jobject obj) { 294 SetFocus(root_, true); 295 } 296 297 BrowserAccessibility* BrowserAccessibilityManagerAndroid::FuzzyHitTest( 298 int x, int y, BrowserAccessibility* start_node) { 299 BrowserAccessibility* nearest_node = NULL; 300 int min_distance = INT_MAX; 301 FuzzyHitTestImpl(x, y, start_node, &nearest_node, &min_distance); 302 return nearest_node; 303 } 304 305 // static 306 void BrowserAccessibilityManagerAndroid::FuzzyHitTestImpl( 307 int x, int y, BrowserAccessibility* start_node, 308 BrowserAccessibility** nearest_candidate, int* nearest_distance) { 309 BrowserAccessibilityAndroid* node = 310 static_cast<BrowserAccessibilityAndroid*>(start_node); 311 int distance = CalculateDistanceSquared(x, y, node); 312 313 if (node->IsFocusable()) { 314 if (distance < *nearest_distance) { 315 *nearest_candidate = node; 316 *nearest_distance = distance; 317 } 318 // Don't examine any more children of focusable node 319 // TODO(aboxhall): what about focusable children? 320 return; 321 } 322 323 if (!node->GetText().empty()) { 324 if (distance < *nearest_distance) { 325 *nearest_candidate = node; 326 *nearest_distance = distance; 327 } 328 return; 329 } 330 331 if (!node->IsLeaf()) { 332 for (uint32 i = 0; i < node->child_count(); i++) { 333 BrowserAccessibility* child = node->GetChild(i); 334 FuzzyHitTestImpl(x, y, child, nearest_candidate, nearest_distance); 335 } 336 } 337 } 338 339 // static 340 int BrowserAccessibilityManagerAndroid::CalculateDistanceSquared( 341 int x, int y, BrowserAccessibility* node) { 342 gfx::Rect node_bounds = node->GetLocalBoundsRect(); 343 int nearest_x = Clamp(x, node_bounds.x(), node_bounds.right()); 344 int nearest_y = Clamp(y, node_bounds.y(), node_bounds.bottom()); 345 int dx = std::abs(x - nearest_x); 346 int dy = std::abs(y - nearest_y); 347 return dx * dx + dy * dy; 348 } 349 350 void BrowserAccessibilityManagerAndroid::NotifyRootChanged() { 351 JNIEnv* env = AttachCurrentThread(); 352 ScopedJavaLocalRef<jobject> obj = java_ref_.get(env); 353 if (obj.is_null()) 354 return; 355 356 Java_BrowserAccessibilityManager_handleNavigate(env, obj.obj()); 357 } 358 359 bool 360 BrowserAccessibilityManagerAndroid::UseRootScrollOffsetsWhenComputingBounds() { 361 // The Java layer handles the root scroll offset. 362 return false; 363 } 364 365 bool RegisterBrowserAccessibilityManager(JNIEnv* env) { 366 return RegisterNativesImpl(env); 367 } 368 369 } // namespace content 370