summaryrefslogtreecommitdiffstats
path: root/content/browser/accessibility
diff options
context:
space:
mode:
authoraboxhall@chromium.org <aboxhall@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-06-13 22:57:01 +0000
committeraboxhall@chromium.org <aboxhall@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-06-13 22:57:01 +0000
commit7f2d9f6bf46e1f16b6ebb997f2b42c77a9070620 (patch)
treed352d7539ee4d4002131151bd8b9a8c9aeaae388 /content/browser/accessibility
parent4dbecc37e310947f2ff7c3ada1dc9b934158d2b7 (diff)
downloadchromium_src-7f2d9f6bf46e1f16b6ebb997f2b42c77a9070620.zip
chromium_src-7f2d9f6bf46e1f16b6ebb997f2b42c77a9070620.tar.gz
chromium_src-7f2d9f6bf46e1f16b6ebb997f2b42c77a9070620.tar.bz2
Content side of changes to enable native accessibility on android.
This code has been written collaboratively by dmazzoni and aboxhall; see https://codereview.chromium.org/15741009/ for the first-pass review on all code changes. BUG=242953 TBR=piman Review URL: https://chromiumcodereview.appspot.com/16661006 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@206193 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content/browser/accessibility')
-rw-r--r--content/browser/accessibility/accessibility_tree_formatter.cc13
-rw-r--r--content/browser/accessibility/accessibility_tree_formatter.h5
-rw-r--r--content/browser/accessibility/accessibility_tree_formatter_android.cc170
-rw-r--r--content/browser/accessibility/browser_accessibility.cc24
-rw-r--r--content/browser/accessibility/browser_accessibility.h8
-rw-r--r--content/browser/accessibility/browser_accessibility_android.cc534
-rw-r--r--content/browser/accessibility/browser_accessibility_android.h99
-rw-r--r--content/browser/accessibility/browser_accessibility_manager.cc23
-rw-r--r--content/browser/accessibility/browser_accessibility_manager.h8
-rw-r--r--content/browser/accessibility/browser_accessibility_manager_android.cc185
-rw-r--r--content/browser/accessibility/browser_accessibility_manager_android.h85
11 files changed, 1139 insertions, 15 deletions
diff --git a/content/browser/accessibility/accessibility_tree_formatter.cc b/content/browser/accessibility/accessibility_tree_formatter.cc
index 99a5aa5..4cf30ea 100644
--- a/content/browser/accessibility/accessibility_tree_formatter.cc
+++ b/content/browser/accessibility/accessibility_tree_formatter.cc
@@ -63,8 +63,12 @@ void AccessibilityTreeFormatter::FormatAccessibilityTree(
void AccessibilityTreeFormatter::RecursiveBuildAccessibilityTree(
const BrowserAccessibility& node, DictionaryValue* dict) {
AddProperties(node, dict);
+
ListValue* children = new ListValue;
dict->Set(kChildrenDictAttr, children);
+ if (!IncludeChildren(node))
+ return;
+
for (size_t i = 0; i < node.children().size(); ++i) {
BrowserAccessibility* child_node = node.children()[i];
DictionaryValue* child_dict = new DictionaryValue;
@@ -89,7 +93,14 @@ void AccessibilityTreeFormatter::RecursiveFormatAccessibilityTree(
}
}
-#if (!defined(OS_WIN) && !defined(OS_MACOSX))
+#if !defined(OS_ANDROID)
+bool AccessibilityTreeFormatter::IncludeChildren(
+ const BrowserAccessibility& node) {
+ return true;
+}
+#endif
+
+#if (!defined(OS_WIN) && !defined(OS_MACOSX) && !defined(OS_ANDROID))
void AccessibilityTreeFormatter::AddProperties(const BrowserAccessibility& node,
DictionaryValue* dict) {
dict->SetInteger("id", node.renderer_id());
diff --git a/content/browser/accessibility/accessibility_tree_formatter.h b/content/browser/accessibility/accessibility_tree_formatter.h
index 7b334fe..5678b46 100644
--- a/content/browser/accessibility/accessibility_tree_formatter.h
+++ b/content/browser/accessibility/accessibility_tree_formatter.h
@@ -112,6 +112,11 @@ class CONTENT_EXPORT AccessibilityTreeFormatter {
// into the given dict.
void AddProperties(const BrowserAccessibility& node, DictionaryValue* dict);
+ // Returns true by default; can be overridden by the platform to
+ // prune some children from the tree when they wouldn't be exposed
+ // natively on that platform.
+ virtual bool IncludeChildren(const BrowserAccessibility& node);
+
string16 FormatCoordinates(const char* name,
const char* x_name,
const char* y_name,
diff --git a/content/browser/accessibility/accessibility_tree_formatter_android.cc b/content/browser/accessibility/accessibility_tree_formatter_android.cc
new file mode 100644
index 0000000..a5d6a33
--- /dev/null
+++ b/content/browser/accessibility/accessibility_tree_formatter_android.cc
@@ -0,0 +1,170 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/accessibility/accessibility_tree_formatter.h"
+
+#include <string>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/files/file_path.h"
+#include "base/json/json_writer.h"
+#include "base/string_util.h"
+#include "base/stringprintf.h"
+#include "base/utf_string_conversions.h"
+#include "content/browser/accessibility/browser_accessibility_android.h"
+#include "content/common/accessibility_node_data.h"
+
+using base::StringPrintf;
+
+namespace content {
+
+namespace {
+const char* BOOL_ATTRIBUTES[] = {
+ "checkable",
+ "checked",
+ "clickable",
+ "disabled",
+ "editable_text",
+ "focusable",
+ "focused",
+ "invisible",
+ "password",
+ "scrollable",
+ "selected"
+};
+
+const char* STRING_ATTRIBUTES[] = {
+ "name"
+};
+
+const char* INT_ATTRIBUTES[] = {
+ "item_index",
+ "item_count"
+};
+}
+
+void AccessibilityTreeFormatter::Initialize() {
+}
+
+void AccessibilityTreeFormatter::AddProperties(
+ const BrowserAccessibility& node, DictionaryValue* dict) {
+ const BrowserAccessibilityAndroid* android_node =
+ static_cast<const BrowserAccessibilityAndroid*>(&node);
+ JNIEnv* env = base::android::AttachCurrentThread();
+
+ // Class name.
+ dict->SetString("class", base::android::ConvertJavaStringToUTF8(
+ android_node->GetClassNameJNI(env, NULL)));
+
+ // Bool attributes.
+ dict->SetBoolean("focusable",
+ android_node->IsFocusableJNI(env, NULL));
+ dict->SetBoolean("focused",
+ android_node->IsFocusedJNI(env, NULL));
+ dict->SetBoolean("clickable",
+ android_node->GetClickableJNI(env, NULL));
+ dict->SetBoolean("editable_text",
+ android_node->IsEditableTextJNI(env, NULL));
+ dict->SetBoolean("checkable",
+ android_node->IsCheckableJNI(env, NULL));
+ dict->SetBoolean("checked",
+ android_node->IsCheckedJNI(env, NULL));
+ dict->SetBoolean("disabled",
+ !android_node->IsEnabledJNI(env, NULL));
+ dict->SetBoolean("scrollable",
+ android_node->IsScrollableJNI(env, NULL));
+ dict->SetBoolean("password",
+ android_node->IsPasswordJNI(env, NULL));
+ dict->SetBoolean("selected",
+ android_node->IsSelectedJNI(env, NULL));
+ dict->SetBoolean("invisible",
+ !android_node->IsVisibleJNI(env, NULL));
+
+ // String attributes.
+ dict->SetString("name", base::android::ConvertJavaStringToUTF8(
+ android_node->GetNameJNI(env, NULL)));
+
+ // Int attributes.
+ dict->SetInteger("item_index",
+ android_node->GetItemIndexJNI(env, NULL));
+ dict->SetInteger("item_count",
+ android_node->GetItemCountJNI(env, NULL));
+}
+
+bool AccessibilityTreeFormatter::IncludeChildren(
+ const BrowserAccessibility& node) {
+ const BrowserAccessibilityAndroid* android_node =
+ static_cast<const BrowserAccessibilityAndroid*>(&node);
+ JNIEnv* env = base::android::AttachCurrentThread();
+
+ return 0 != android_node->GetChildCountJNI(env, NULL);
+}
+
+string16 AccessibilityTreeFormatter::ToString(const DictionaryValue& dict,
+ const string16& indent) {
+ string16 line;
+
+ string16 class_value;
+ dict.GetString("class", &class_value);
+ WriteAttribute(true, UTF16ToUTF8(class_value), &line);
+
+ for (unsigned i = 0; i < arraysize(BOOL_ATTRIBUTES); i++) {
+ const char* attribute_name = BOOL_ATTRIBUTES[i];
+ bool value;
+ if (dict.GetBoolean(attribute_name, &value) && value)
+ WriteAttribute(true, attribute_name, &line);
+ }
+
+ for (unsigned i = 0; i < arraysize(STRING_ATTRIBUTES); i++) {
+ const char* attribute_name = STRING_ATTRIBUTES[i];
+ std::string value;
+ if (!dict.GetString(attribute_name, &value) || value.empty())
+ continue;
+ WriteAttribute(true,
+ StringPrintf("%s='%s'", attribute_name, value.c_str()),
+ &line);
+ }
+
+ for (unsigned i = 0; i < arraysize(INT_ATTRIBUTES); i++) {
+ const char* attribute_name = INT_ATTRIBUTES[i];
+ int value;
+ if (!dict.GetInteger(attribute_name, &value) || value == 0)
+ continue;
+ WriteAttribute(true,
+ StringPrintf("%s=%d", attribute_name, value),
+ &line);
+ }
+
+ return indent + line + ASCIIToUTF16("\n");
+}
+
+// static
+const base::FilePath::StringType
+AccessibilityTreeFormatter::GetActualFileSuffix() {
+ return FILE_PATH_LITERAL("-actual-android.txt");
+}
+
+// static
+const base::FilePath::StringType
+AccessibilityTreeFormatter::GetExpectedFileSuffix() {
+ return FILE_PATH_LITERAL("-expected-android.txt");
+}
+
+// static
+const std::string AccessibilityTreeFormatter::GetAllowEmptyString() {
+ return "@ANDROID-ALLOW-EMPTY:";
+}
+
+// static
+const std::string AccessibilityTreeFormatter::GetAllowString() {
+ return "@ANDROID-ALLOW:";
+}
+
+// static
+const std::string AccessibilityTreeFormatter::GetDenyString() {
+ return "@ANDROID-DENY:";
+}
+
+} // namespace content
diff --git a/content/browser/accessibility/browser_accessibility.cc b/content/browser/accessibility/browser_accessibility.cc
index f2f040b..ae7efe2 100644
--- a/content/browser/accessibility/browser_accessibility.cc
+++ b/content/browser/accessibility/browser_accessibility.cc
@@ -19,7 +19,8 @@ typedef AccessibilityNodeData::StringAttribute StringAttribute;
#if !defined(OS_MACOSX) && \
!defined(OS_WIN) && \
- !defined(TOOLKIT_GTK)
+ !defined(TOOLKIT_GTK) && \
+ !defined(OS_ANDROID)
// We have subclassess of BrowserAccessibility on Mac, Linux/GTK,
// and Win. For any other platform, instantiate the base class.
// static
@@ -112,7 +113,7 @@ bool BrowserAccessibility::IsDescendantOf(
return false;
}
-BrowserAccessibility* BrowserAccessibility::GetChild(uint32 child_index) {
+BrowserAccessibility* BrowserAccessibility::GetChild(uint32 child_index) const {
DCHECK(child_index < children_.size());
return children_[child_index];
}
@@ -134,7 +135,7 @@ BrowserAccessibility* BrowserAccessibility::GetNextSibling() {
return NULL;
}
-gfx::Rect BrowserAccessibility::GetLocalBoundsRect() {
+gfx::Rect BrowserAccessibility::GetLocalBoundsRect() const {
gfx::Rect bounds = location_;
// Walk up the parent chain. Every time we encounter a Web Area, offset
@@ -151,6 +152,14 @@ gfx::Rect BrowserAccessibility::GetLocalBoundsRect() {
bounds.Offset(parent->location().x(), parent->location().y());
need_to_offset_web_area = false;
}
+
+ // On some platforms, we don't want to take the root scroll offsets
+ // into account.
+ if (parent->role() == AccessibilityNodeData::ROLE_ROOT_WEB_AREA &&
+ !manager()->UseRootScrollOffsetsWhenComputingBounds()) {
+ break;
+ }
+
if (parent->role() == AccessibilityNodeData::ROLE_WEB_AREA ||
parent->role() == AccessibilityNodeData::ROLE_ROOT_WEB_AREA) {
int sx = 0;
@@ -167,7 +176,7 @@ gfx::Rect BrowserAccessibility::GetLocalBoundsRect() {
return bounds;
}
-gfx::Rect BrowserAccessibility::GetGlobalBoundsRect() {
+gfx::Rect BrowserAccessibility::GetGlobalBoundsRect() const {
gfx::Rect bounds = GetLocalBoundsRect();
// Adjust the bounds by the top left corner of the containing view's bounds
@@ -303,6 +312,13 @@ bool BrowserAccessibility::HasState(
}
bool BrowserAccessibility::IsEditableText() const {
+ // These roles don't have readonly set, but they're not editable text.
+ if (role_ == AccessibilityNodeData::ROLE_SCROLLAREA ||
+ role_ == AccessibilityNodeData::ROLE_COLUMN ||
+ role_ == AccessibilityNodeData::ROLE_TABLE_HEADER_CONTAINER) {
+ return false;
+ }
+
// Note: STATE_READONLY being false means it's either a text control,
// or contenteditable. We also check for editable text roles to cover
// another element that has role=textbox set on it.
diff --git a/content/browser/accessibility/browser_accessibility.h b/content/browser/accessibility/browser_accessibility.h
index d70124a..8fda68f 100644
--- a/content/browser/accessibility/browser_accessibility.h
+++ b/content/browser/accessibility/browser_accessibility.h
@@ -88,13 +88,13 @@ class CONTENT_EXPORT BrowserAccessibility {
bool IsDescendantOf(BrowserAccessibility* ancestor);
// Returns the parent of this object, or NULL if it's the root.
- BrowserAccessibility* parent() { return parent_; }
+ BrowserAccessibility* parent() const { return parent_; }
// Returns the number of children of this object.
uint32 child_count() const { return children_.size(); }
// Return a pointer to the child with the given index.
- BrowserAccessibility* GetChild(uint32 child_index);
+ BrowserAccessibility* GetChild(uint32 child_index) const;
// Return the previous sibling of this object, or NULL if it's the first
// child of its parent.
@@ -106,10 +106,10 @@ class CONTENT_EXPORT BrowserAccessibility {
// Returns the bounds of this object in coordinates relative to the
// top-left corner of the overall web area.
- gfx::Rect GetLocalBoundsRect();
+ gfx::Rect GetLocalBoundsRect() const;
// Returns the bounds of this object in screen coordinates.
- gfx::Rect GetGlobalBoundsRect();
+ gfx::Rect GetGlobalBoundsRect() const;
// Returns the deepest descendant that contains the specified point
// (in global screen coordinates).
diff --git a/content/browser/accessibility/browser_accessibility_android.cc b/content/browser/accessibility/browser_accessibility_android.cc
new file mode 100644
index 0000000..511d67e
--- /dev/null
+++ b/content/browser/accessibility/browser_accessibility_android.cc
@@ -0,0 +1,534 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/accessibility/browser_accessibility_android.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_registrar.h"
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/utf_string_conversions.h"
+#include "content/browser/accessibility/browser_accessibility_manager_android.h"
+#include "content/common/accessibility_messages.h"
+#include "content/common/accessibility_node_data.h"
+
+using base::android::ScopedJavaLocalRef;
+
+namespace content {
+
+// static
+BrowserAccessibility* BrowserAccessibility::Create() {
+ return new BrowserAccessibilityAndroid();
+}
+
+BrowserAccessibilityAndroid::BrowserAccessibilityAndroid() {
+ first_time_ = true;
+}
+
+bool BrowserAccessibilityAndroid::IsNative() const {
+ return true;
+}
+
+//
+// Actions, called from Java.
+//
+
+void BrowserAccessibilityAndroid::FocusJNI(JNIEnv* env, jobject obj) {
+ manager_->SetFocus(this, true);
+}
+
+void BrowserAccessibilityAndroid::ClickJNI(JNIEnv* env, jobject obj) {
+ manager_->DoDefaultAction(*this);
+}
+
+//
+// Const accessors, called from Java.
+//
+
+ScopedJavaLocalRef<jstring>
+BrowserAccessibilityAndroid::GetNameJNI(JNIEnv* env, jobject obj) const {
+ return base::android::ConvertUTF16ToJavaString(env, ComputeName());
+}
+
+ScopedJavaLocalRef<jobject>
+BrowserAccessibilityAndroid::GetAbsoluteRectJNI(
+ JNIEnv* env, jobject obj) const {
+ gfx::Rect rect = GetLocalBoundsRect();
+
+ // TODO(aboxhall): replace with non-stub implementation
+ return ScopedJavaLocalRef<jobject>(env, NULL);
+}
+
+ScopedJavaLocalRef<jobject>
+BrowserAccessibilityAndroid::GetRectInParentJNI(
+ JNIEnv* env, jobject obj) const {
+ gfx::Rect rect = GetLocalBoundsRect();
+ if (parent()) {
+ gfx::Rect parent_rect = parent()->GetLocalBoundsRect();
+ rect.Offset(-parent_rect.OffsetFromOrigin());
+ }
+
+ // TODO(aboxhall): replace with non-stub implementation
+ return ScopedJavaLocalRef<jobject>(env, NULL);
+}
+
+jboolean
+BrowserAccessibilityAndroid::IsFocusableJNI(JNIEnv* env, jobject obj) const {
+ return static_cast<jboolean>(IsFocusable());
+}
+
+jboolean
+BrowserAccessibilityAndroid::IsEditableTextJNI(JNIEnv* env, jobject obj) const {
+ return IsEditableText();
+}
+
+jint BrowserAccessibilityAndroid::GetParentJNI(JNIEnv* env, jobject obj) const {
+ return static_cast<jint>(parent()->renderer_id());
+}
+
+jint
+BrowserAccessibilityAndroid::GetChildCountJNI(JNIEnv* env, jobject obj) const {
+ if (IsLeaf())
+ return 0;
+ else
+ return static_cast<jint>(child_count());
+}
+
+jint BrowserAccessibilityAndroid::GetChildIdAtJNI(JNIEnv* env,
+ jobject obj,
+ jint child_index) const {
+ return static_cast<jint>(GetChild(child_index)->renderer_id());
+}
+
+jboolean
+BrowserAccessibilityAndroid::IsCheckableJNI(JNIEnv* env, jobject obj) const {
+ bool checkable = false;
+ bool is_aria_pressed_defined;
+ bool is_mixed;
+ GetAriaTristate("aria-pressed", &is_aria_pressed_defined, &is_mixed);
+ if (role() == AccessibilityNodeData::ROLE_CHECKBOX ||
+ role() == AccessibilityNodeData::ROLE_RADIO_BUTTON ||
+ is_aria_pressed_defined) {
+ checkable = true;
+ }
+ if (HasState(AccessibilityNodeData::STATE_CHECKED))
+ checkable = true;
+ return static_cast<jboolean>(checkable);
+}
+
+jboolean
+BrowserAccessibilityAndroid::IsCheckedJNI(JNIEnv* env, jobject obj) const {
+ return static_cast<jboolean>(
+ HasState(AccessibilityNodeData::STATE_CHECKED));
+}
+
+base::android::ScopedJavaLocalRef<jstring>
+BrowserAccessibilityAndroid::GetClassNameJNI(JNIEnv* env, jobject obj) const {
+ const char* class_name = NULL;
+
+ switch(role()) {
+ case AccessibilityNodeData::ROLE_EDITABLE_TEXT:
+ case AccessibilityNodeData::ROLE_SPIN_BUTTON:
+ case AccessibilityNodeData::ROLE_TEXTAREA:
+ case AccessibilityNodeData::ROLE_TEXT_FIELD:
+ class_name = "android.widget.EditText";
+ break;
+ case AccessibilityNodeData::ROLE_SLIDER:
+ class_name = "android.widget.SeekBar";
+ break;
+ case AccessibilityNodeData::ROLE_COMBO_BOX:
+ class_name = "android.widget.Spinner";
+ break;
+ case AccessibilityNodeData::ROLE_BUTTON:
+ case AccessibilityNodeData::ROLE_MENU_BUTTON:
+ case AccessibilityNodeData::ROLE_POPUP_BUTTON:
+ class_name = "android.widget.Button";
+ break;
+ case AccessibilityNodeData::ROLE_CHECKBOX:
+ class_name = "android.widget.CheckBox";
+ break;
+ case AccessibilityNodeData::ROLE_RADIO_BUTTON:
+ class_name = "android.widget.RadioButton";
+ break;
+ case AccessibilityNodeData::ROLE_TOGGLE_BUTTON:
+ class_name = "android.widget.ToggleButton";
+ break;
+ case AccessibilityNodeData::ROLE_CANVAS:
+ case AccessibilityNodeData::ROLE_IMAGE:
+ class_name = "android.widget.Image";
+ break;
+ case AccessibilityNodeData::ROLE_PROGRESS_INDICATOR:
+ class_name = "android.widget.ProgressBar";
+ break;
+ case AccessibilityNodeData::ROLE_TAB_LIST:
+ class_name = "android.widget.TabWidget";
+ break;
+ case AccessibilityNodeData::ROLE_GRID:
+ case AccessibilityNodeData::ROLE_TABLE:
+ class_name = "android.widget.GridView";
+ break;
+ case AccessibilityNodeData::ROLE_LIST:
+ case AccessibilityNodeData::ROLE_LISTBOX:
+ class_name = "android.widget.ListView";
+ break;
+ default:
+ class_name = "android.view.View";
+ break;
+ }
+
+ return base::android::ConvertUTF8ToJavaString(env, class_name);
+}
+
+jboolean
+BrowserAccessibilityAndroid::IsEnabledJNI(JNIEnv* env, jobject obj) const {
+ return static_cast<jboolean>(
+ !HasState(AccessibilityNodeData::STATE_UNAVAILABLE));
+}
+
+jboolean
+BrowserAccessibilityAndroid::IsFocusedJNI(JNIEnv* env, jobject obj) const {
+ return manager()->GetFocus(manager()->GetRoot()) == this;
+}
+
+jboolean
+BrowserAccessibilityAndroid::IsPasswordJNI(JNIEnv* env, jobject obj) const {
+ return static_cast<jboolean>(
+ HasState(AccessibilityNodeData::STATE_PROTECTED));
+}
+
+jboolean
+BrowserAccessibilityAndroid::IsScrollableJNI(JNIEnv* env, jobject obj) const {
+ int dummy;
+ bool scrollable = GetIntAttribute(
+ AccessibilityNodeData::ATTR_SCROLL_X_MAX, &dummy);
+ return static_cast<jboolean>(scrollable);
+}
+
+jboolean
+BrowserAccessibilityAndroid::IsSelectedJNI(JNIEnv* env, jobject obj) const {
+ return static_cast<jboolean>(
+ HasState(AccessibilityNodeData::STATE_SELECTED));
+}
+
+jboolean
+BrowserAccessibilityAndroid::IsVisibleJNI(JNIEnv* env, jobject obj) const {
+ return static_cast<jboolean>(
+ !HasState(AccessibilityNodeData::STATE_INVISIBLE));
+}
+
+base::android::ScopedJavaLocalRef<jstring>
+BrowserAccessibilityAndroid::GetAriaLiveJNI(JNIEnv* env, jobject obj) const {
+ return base::android::ConvertUTF16ToJavaString(env, GetAriaLive());
+}
+
+jint BrowserAccessibilityAndroid::GetItemIndexJNI(
+ JNIEnv* env, jobject obj) const {
+ int index = 0;
+ switch(role()) {
+ case AccessibilityNodeData::ROLE_LIST_ITEM:
+ case AccessibilityNodeData::ROLE_LISTBOX_OPTION:
+ index = index_in_parent();
+ break;
+ case AccessibilityNodeData::ROLE_SLIDER:
+ case AccessibilityNodeData::ROLE_PROGRESS_INDICATOR: {
+ float value_for_range;
+ if (GetFloatAttribute(
+ AccessibilityNodeData::ATTR_VALUE_FOR_RANGE, &value_for_range)) {
+ index = static_cast<int>(value_for_range);
+ }
+ break;
+ }
+ }
+ return static_cast<jint>(index);
+}
+
+jint
+BrowserAccessibilityAndroid::GetItemCountJNI(JNIEnv* env, jobject obj) const {
+ int count = 0;
+ switch(role()) {
+ case AccessibilityNodeData::ROLE_LIST:
+ case AccessibilityNodeData::ROLE_LISTBOX:
+ count = child_count();
+ break;
+ case AccessibilityNodeData::ROLE_SLIDER:
+ case AccessibilityNodeData::ROLE_PROGRESS_INDICATOR: {
+ float max_value_for_range;
+ if (GetFloatAttribute(AccessibilityNodeData::ATTR_MAX_VALUE_FOR_RANGE,
+ &max_value_for_range)) {
+ count = static_cast<int>(max_value_for_range);
+ }
+ break;
+ }
+ }
+ return static_cast<jint>(count);
+}
+
+jint
+BrowserAccessibilityAndroid::GetScrollXJNI(JNIEnv* env, jobject obj) const {
+ int value = 0;
+ GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_X, &value);
+ return static_cast<jint>(value);
+}
+
+jint
+BrowserAccessibilityAndroid::GetScrollYJNI(JNIEnv* env, jobject obj) const {
+ int value = 0;
+ GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_Y, &value);
+ return static_cast<jint>(value);
+}
+
+jint
+BrowserAccessibilityAndroid::GetMaxScrollXJNI(JNIEnv* env, jobject obj) const {
+ int value = 0;
+ GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_X_MAX, &value);
+ return static_cast<jint>(value);
+}
+
+jint
+BrowserAccessibilityAndroid::GetMaxScrollYJNI(JNIEnv* env, jobject obj) const {
+ int value = 0;
+ GetIntAttribute(AccessibilityNodeData::ATTR_SCROLL_Y_MAX, &value);
+ return static_cast<jint>(value);
+}
+
+jboolean
+BrowserAccessibilityAndroid::GetClickableJNI(JNIEnv* env, jobject obj) const {
+ return (IsLeaf() && !ComputeName().empty());
+}
+
+jint BrowserAccessibilityAndroid::GetSelectionStartJNI(JNIEnv* env, jobject obj)
+ const {
+ int sel_start = 0;
+ GetIntAttribute(AccessibilityNodeData::ATTR_TEXT_SEL_START, &sel_start);
+ return sel_start;
+}
+
+jint BrowserAccessibilityAndroid::GetSelectionEndJNI(JNIEnv* env, jobject obj)
+ const {
+ int sel_end = 0;
+ GetIntAttribute(AccessibilityNodeData::ATTR_TEXT_SEL_END, &sel_end);
+ return sel_end;
+}
+
+jint BrowserAccessibilityAndroid::GetEditableTextLengthJNI(
+ JNIEnv* env, jobject obj) const {
+ return value().length();
+}
+
+int BrowserAccessibilityAndroid::GetTextChangeFromIndexJNI(
+ JNIEnv* env, jobject obj) const {
+ size_t index = 0;
+ while (index < old_value_.length() &&
+ index < new_value_.length() &&
+ old_value_[index] == new_value_[index]) {
+ index++;
+ }
+ return index;
+}
+
+jint BrowserAccessibilityAndroid::GetTextChangeAddedCountJNI(
+ JNIEnv* env, jobject obj) const {
+ size_t old_len = old_value_.length();
+ size_t new_len = new_value_.length();
+ size_t left = 0;
+ while (left < old_len &&
+ left < new_len &&
+ old_value_[left] == new_value_[left]) {
+ left++;
+ }
+ size_t right = 0;
+ while (right < old_len &&
+ right < new_len &&
+ old_value_[old_len - right - 1] == new_value_[new_len - right - 1]) {
+ right++;
+ }
+ return (new_len - left - right);
+}
+
+jint BrowserAccessibilityAndroid::GetTextChangeRemovedCountJNI(
+ JNIEnv* env, jobject obj) const {
+ size_t old_len = old_value_.length();
+ size_t new_len = new_value_.length();
+ size_t left = 0;
+ while (left < old_len &&
+ left < new_len &&
+ old_value_[left] == new_value_[left]) {
+ left++;
+ }
+ size_t right = 0;
+ while (right < old_len &&
+ right < new_len &&
+ old_value_[old_len - right - 1] == new_value_[new_len - right - 1]) {
+ right++;
+ }
+ return (old_len - left - right);
+}
+
+base::android::ScopedJavaLocalRef<jstring>
+BrowserAccessibilityAndroid::GetTextChangeBeforeTextJNI(
+ JNIEnv* env, jobject obj) const {
+ return base::android::ConvertUTF16ToJavaString(env, old_value_);
+}
+
+string16 BrowserAccessibilityAndroid::ComputeName() const {
+ if (IsIframe() ||
+ role() == AccessibilityNodeData::ROLE_WEB_AREA) {
+ return string16();
+ }
+
+ string16 description;
+ GetStringAttribute(AccessibilityNodeData::ATTR_DESCRIPTION,
+ &description);
+
+ string16 text;
+ if (!name().empty())
+ text = name();
+ else if (!description.empty())
+ text = description;
+ else if (!value().empty())
+ text = value();
+
+ if (text.empty() && HasOnlyStaticTextChildren()) {
+ for (uint32 i = 0; i < child_count(); i++) {
+ BrowserAccessibility* child = GetChild(i);
+ text += static_cast<BrowserAccessibilityAndroid*>(child)->ComputeName();
+ }
+ }
+
+ switch(role()) {
+ case AccessibilityNodeData::ROLE_IMAGE_MAP_LINK:
+ case AccessibilityNodeData::ROLE_LINK:
+ case AccessibilityNodeData::ROLE_WEBCORE_LINK:
+ if (!text.empty())
+ text += ASCIIToUTF16(" ");
+ text += ASCIIToUTF16("Link");
+ break;
+ case AccessibilityNodeData::ROLE_HEADING:
+ // Only append "heading" if this node already has text.
+ if (!text.empty())
+ text += ASCIIToUTF16(" Heading");
+ break;
+ }
+
+ return text;
+}
+
+string16 BrowserAccessibilityAndroid::GetAriaLive() const {
+ string16 aria_live;
+ if (GetStringAttribute(AccessibilityNodeData::ATTR_CONTAINER_LIVE_STATUS,
+ &aria_live)) {
+ return aria_live;
+ }
+ return string16();
+}
+
+bool BrowserAccessibilityAndroid::HasFocusableChild() const {
+ for (uint32 i = 0; i < child_count(); i++) {
+ BrowserAccessibility* child = GetChild(i);
+ if (child->HasState(AccessibilityNodeData::STATE_FOCUSABLE))
+ return true;
+ if (static_cast<BrowserAccessibilityAndroid*>(child)->HasFocusableChild())
+ return true;
+ }
+ return false;
+}
+
+bool BrowserAccessibilityAndroid::HasOnlyStaticTextChildren() const {
+ for (uint32 i = 0; i < child_count(); i++) {
+ BrowserAccessibility* child = GetChild(i);
+ if (child->role() != AccessibilityNodeData::ROLE_STATIC_TEXT)
+ return false;
+ }
+ return true;
+}
+
+bool BrowserAccessibilityAndroid::IsIframe() const {
+ string16 html_tag;
+ GetStringAttribute(AccessibilityNodeData::ATTR_HTML_TAG, &html_tag);
+ return html_tag == ASCIIToUTF16("iframe");
+}
+
+bool BrowserAccessibilityAndroid::IsFocusable() const {
+ bool focusable = HasState(AccessibilityNodeData::STATE_FOCUSABLE);
+ if (IsIframe() ||
+ role() == AccessibilityNodeData::ROLE_WEB_AREA) {
+ focusable = false;
+ }
+ return focusable;
+}
+
+bool BrowserAccessibilityAndroid::IsLeaf() const {
+ if (child_count() == 0)
+ return true;
+
+ // Iframes are always allowed to contain children.
+ if (IsIframe() ||
+ role() == AccessibilityNodeData::ROLE_ROOT_WEB_AREA ||
+ role() == AccessibilityNodeData::ROLE_WEB_AREA) {
+ return false;
+ }
+
+ // If it has a focusable child, we definitely can't leave out children.
+ if (HasFocusableChild())
+ return false;
+
+ // Headings with text can drop their children.
+ string16 name = ComputeName();
+ if (role() == AccessibilityNodeData::ROLE_HEADING && !name.empty())
+ return true;
+
+ // Focusable nodes with text can drop their children.
+ if (HasState(AccessibilityNodeData::STATE_FOCUSABLE) && !name.empty())
+ return true;
+
+ // Nodes with only static text as children can drop their children.
+ if (HasOnlyStaticTextChildren())
+ return true;
+
+ return false;
+}
+
+void BrowserAccessibilityAndroid::PostInitialize() {
+ BrowserAccessibility::PostInitialize();
+
+ if (IsEditableText()) {
+ if (value_ != new_value_) {
+ old_value_ = new_value_;
+ new_value_ = value_;
+ }
+ }
+
+ if (role_ == AccessibilityNodeData::ROLE_ALERT && first_time_)
+ manager_->NotifyAccessibilityEvent(AccessibilityNotificationAlert, this);
+
+ string16 live;
+ if (GetStringAttribute(AccessibilityNodeData::ATTR_CONTAINER_LIVE_STATUS,
+ &live)) {
+ NotifyLiveRegionUpdate(live);
+ }
+
+ first_time_ = false;
+}
+
+void BrowserAccessibilityAndroid::NotifyLiveRegionUpdate(string16& aria_live) {
+ if (!EqualsASCII(aria_live, aria_strings::kAriaLivePolite) &&
+ !EqualsASCII(aria_live, aria_strings::kAriaLiveAssertive))
+ return;
+
+ string16 text = ComputeName();
+ if (cached_text_ != text) {
+ if (!text.empty()) {
+ manager_->NotifyAccessibilityEvent(AccessibilityNotificationObjectShow,
+ this);
+ }
+ cached_text_ = text;
+ }
+}
+
+bool RegisterBrowserAccessibility(JNIEnv* env) {
+ // TODO(aboxhall): replace with non-stub implementation
+ return false;
+}
+
+} // namespace content
diff --git a/content/browser/accessibility/browser_accessibility_android.h b/content/browser/accessibility/browser_accessibility_android.h
new file mode 100644
index 0000000..2de8c75
--- /dev/null
+++ b/content/browser/accessibility/browser_accessibility_android.h
@@ -0,0 +1,99 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_ANDROID_H_
+#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_ANDROID_H_
+
+#include "base/android/scoped_java_ref.h"
+#include "content/browser/accessibility/browser_accessibility.h"
+
+namespace content {
+
+class BrowserAccessibilityAndroid : public BrowserAccessibility {
+ public:
+ // Overrides from BrowserAccessibility.
+ virtual void PostInitialize() OVERRIDE;
+ virtual bool IsNative() const OVERRIDE;
+
+ // --------------------------------------------------------------------------
+ // Methods called from Java via JNI
+ // --------------------------------------------------------------------------
+
+ // Actions
+ void FocusJNI(JNIEnv* env, jobject obj);
+ void ClickJNI(JNIEnv* env, jobject obj);
+
+ // Const accessors
+ jboolean GetClickableJNI(JNIEnv* env, jobject obj) const;
+ jboolean IsFocusedJNI(JNIEnv* env, jobject obj) const;
+ jboolean IsEditableTextJNI(JNIEnv* env, jobject obj) const;
+ base::android::ScopedJavaLocalRef<jstring> GetNameJNI(
+ JNIEnv* env, jobject obj) const;
+ base::android::ScopedJavaLocalRef<jobject> GetAbsoluteRectJNI(
+ JNIEnv* env, jobject obj) const;
+ base::android::ScopedJavaLocalRef<jobject> GetRectInParentJNI(
+ JNIEnv* env, jobject obj) const;
+ jboolean IsFocusableJNI(JNIEnv* env, jobject obj) const;
+ jint GetParentJNI(JNIEnv* env, jobject obj) const;
+ jint GetChildCountJNI(JNIEnv* env, jobject obj) const;
+ jint GetChildIdAtJNI(
+ JNIEnv* env, jobject obj, jint child_index) const;
+ jboolean IsCheckableJNI(JNIEnv* env, jobject obj) const;
+ jboolean IsCheckedJNI(JNIEnv* env, jobject obj) const;
+ base::android::ScopedJavaLocalRef<jstring> GetClassNameJNI(
+ JNIEnv* env, jobject obj) const;
+ jboolean IsEnabledJNI(JNIEnv* env, jobject obj) const;
+ jboolean IsPasswordJNI(JNIEnv* env, jobject obj) const;
+ jboolean IsScrollableJNI(JNIEnv* env, jobject obj) const;
+ jboolean IsSelectedJNI(JNIEnv* env, jobject obj) const;
+ jboolean IsVisibleJNI(JNIEnv* env, jobject obj) const;
+ jint GetItemIndexJNI(JNIEnv* env, jobject obj) const;
+ jint GetItemCountJNI(JNIEnv* env, jobject obj) const;
+ jint GetScrollXJNI(JNIEnv* env, jobject obj) const;
+ jint GetScrollYJNI(JNIEnv* env, jobject obj) const;
+ jint GetMaxScrollXJNI(JNIEnv* env, jobject obj) const;
+ jint GetMaxScrollYJNI(JNIEnv* env, jobject obj) const;
+ base::android::ScopedJavaLocalRef<jstring> GetAriaLiveJNI(
+ JNIEnv* env, jobject obj) const;
+ jint GetSelectionStartJNI(JNIEnv* env, jobject obj) const;
+ jint GetSelectionEndJNI(JNIEnv* env, jobject obj) const;
+ jint GetEditableTextLengthJNI(JNIEnv* env, jobject obj) const;
+ jint GetTextChangeFromIndexJNI(JNIEnv* env, jobject obj) const;
+ jint GetTextChangeAddedCountJNI(JNIEnv* env, jobject obj) const;
+ jint GetTextChangeRemovedCountJNI(JNIEnv* env, jobject obj) const;
+ base::android::ScopedJavaLocalRef<jstring> GetTextChangeBeforeTextJNI(
+ JNIEnv* env, jobject obj) const;
+
+ private:
+ // This gives BrowserAccessibility::Create access to the class constructor.
+ friend class BrowserAccessibility;
+
+ // Allow BrowserAccessibilityManagerAndroid to call these private methods.
+ friend class BrowserAccessibilityManagerAndroid;
+
+ BrowserAccessibilityAndroid();
+
+ string16 ComputeName() const;
+ string16 GetAriaLive() const;
+ bool IsFocusable() const;
+ bool HasFocusableChild() const;
+ bool HasOnlyStaticTextChildren() const;
+ bool IsIframe() const;
+ bool IsLeaf() const;
+
+ void NotifyLiveRegionUpdate(string16& aria_live);
+
+ string16 cached_text_;
+ bool first_time_;
+ string16 old_value_;
+ string16 new_value_;
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityAndroid);
+};
+
+bool RegisterBrowserAccessibility(JNIEnv* env);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_ANDROID_H_
diff --git a/content/browser/accessibility/browser_accessibility_manager.cc b/content/browser/accessibility/browser_accessibility_manager.cc
index 498d853..3856a20 100644
--- a/content/browser/accessibility/browser_accessibility_manager.cc
+++ b/content/browser/accessibility/browser_accessibility_manager.cc
@@ -6,7 +6,6 @@
#include "base/logging.h"
#include "content/browser/accessibility/browser_accessibility.h"
-#include "content/browser/accessibility/browser_accessibility_state_impl.h"
#include "content/common/accessibility_messages.h"
namespace content {
@@ -17,7 +16,8 @@ BrowserAccessibility* BrowserAccessibilityFactory::Create() {
#if !defined(OS_MACOSX) && \
!defined(OS_WIN) && \
- !defined(TOOLKIT_GTK)
+ !defined(TOOLKIT_GTK) && \
+ !defined(OS_ANDROID) \
// We have subclassess of BrowserAccessibilityManager on Mac, Linux/GTK,
// and Win. For any other platform, instantiate the base class.
// static
@@ -91,6 +91,10 @@ bool BrowserAccessibilityManager::IsOSKAllowed(const gfx::Rect& bounds) {
return bounds.Contains(touch_point);
}
+bool BrowserAccessibilityManager::UseRootScrollOffsetsWhenComputingBounds() {
+ return true;
+}
+
void BrowserAccessibilityManager::RemoveNode(BrowserAccessibility* node) {
if (node == focus_)
SetFocus(root_, false);
@@ -158,6 +162,11 @@ void BrowserAccessibilityManager::SetFocus(
delegate_->SetAccessibilityFocus(node->renderer_id());
}
+void BrowserAccessibilityManager::SetRoot(BrowserAccessibility* node) {
+ root_ = node;
+ NotifyRootChanged();
+}
+
void BrowserAccessibilityManager::DoDefaultAction(
const BrowserAccessibility& node) {
if (delegate_)
@@ -372,14 +381,16 @@ bool BrowserAccessibilityManager::UpdateNode(const AccessibilityNodeData& src) {
if (root_)
root_->Destroy();
if (focus_ == root_)
- focus_ = instance;
- root_ = instance;
+ SetFocus(instance, false);
+ SetRoot(instance);
}
// Keep track of what node is focused.
- if ((src.state >> AccessibilityNodeData::STATE_FOCUSED) & 1)
+ if (src.role != AccessibilityNodeData::ROLE_ROOT_WEB_AREA &&
+ src.role != AccessibilityNodeData::ROLE_WEB_AREA &&
+ (src.state >> AccessibilityNodeData::STATE_FOCUSED & 1)) {
SetFocus(instance, false);
-
+ }
return success;
}
diff --git a/content/browser/accessibility/browser_accessibility_manager.h b/content/browser/accessibility/browser_accessibility_manager.h
index 356f976..2b9761e 100644
--- a/content/browser/accessibility/browser_accessibility_manager.h
+++ b/content/browser/accessibility/browser_accessibility_manager.h
@@ -137,6 +137,10 @@ class CONTENT_EXPORT BrowserAccessibilityManager {
// focus event on a text box?
bool IsOSKAllowed(const gfx::Rect& bounds);
+ // True by default, but some platforms want to treat the root
+ // scroll offsets separately.
+ virtual bool UseRootScrollOffsetsWhenComputingBounds();
+
// For testing only: update the given nodes as if they were
// received from the renderer process in OnAccessibilityNotifications.
// Takes up to 7 nodes at once so tests don't need to create a vector
@@ -158,6 +162,8 @@ class CONTENT_EXPORT BrowserAccessibilityManager {
virtual void AddNodeToMap(BrowserAccessibility* node);
+ virtual void NotifyRootChanged() {}
+
private:
// The following states keep track of whether or not the
// on-screen keyboard is allowed to be shown.
@@ -189,6 +195,8 @@ class CONTENT_EXPORT BrowserAccessibilityManager {
// process. Returns true on success, false on fatal error.
bool UpdateNode(const AccessibilityNodeData& src);
+ void SetRoot(BrowserAccessibility* root);
+
BrowserAccessibility* CreateNode(
BrowserAccessibility* parent,
int32 renderer_id,
diff --git a/content/browser/accessibility/browser_accessibility_manager_android.cc b/content/browser/accessibility/browser_accessibility_manager_android.cc
new file mode 100644
index 0000000..5b57b48
--- /dev/null
+++ b/content/browser/accessibility/browser_accessibility_manager_android.cc
@@ -0,0 +1,185 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/accessibility/browser_accessibility_manager_android.h"
+
+#include <cmath>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "content/browser/accessibility/browser_accessibility_android.h"
+#include "content/common/accessibility_messages.h"
+
+using base::android::AttachCurrentThread;
+using base::android::ScopedJavaLocalRef;
+
+namespace {
+
+// Restricts |val| to the range [min, max].
+int Clamp(int val, int min, int max) {
+ return std::min(std::max(val, min), max);
+}
+
+} // anonymous namespace
+
+namespace content {
+
+namespace aria_strings {
+ const char kAriaLivePolite[] = "polite";
+ const char kAriaLiveAssertive[] = "assertive";
+}
+
+// static
+BrowserAccessibilityManager* BrowserAccessibilityManager::Create(
+ const AccessibilityNodeData& src,
+ BrowserAccessibilityDelegate* delegate,
+ BrowserAccessibilityFactory* factory) {
+ return new BrowserAccessibilityManagerAndroid(ScopedJavaLocalRef<jobject>(),
+ src, delegate, factory);
+}
+
+BrowserAccessibilityManagerAndroid::BrowserAccessibilityManagerAndroid(
+ ScopedJavaLocalRef<jobject> content_view_core,
+ const AccessibilityNodeData& src,
+ BrowserAccessibilityDelegate* delegate,
+ BrowserAccessibilityFactory* factory)
+ : BrowserAccessibilityManager(src, delegate, factory) {
+ if (content_view_core.is_null())
+ return;
+
+ // TODO(aboxhall): set up Java references
+}
+
+BrowserAccessibilityManagerAndroid::~BrowserAccessibilityManagerAndroid() {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
+ if (obj.is_null())
+ return;
+
+ // TODO(aboxhall): tear down Java references
+}
+
+// static
+AccessibilityNodeData BrowserAccessibilityManagerAndroid::GetEmptyDocument() {
+ AccessibilityNodeData empty_document;
+ empty_document.id = 0;
+ empty_document.role = AccessibilityNodeData::ROLE_ROOT_WEB_AREA;
+ empty_document.state = 1 << AccessibilityNodeData::STATE_READONLY;
+ return empty_document;
+}
+
+void BrowserAccessibilityManagerAndroid::NotifyAccessibilityEvent(
+ int type,
+ BrowserAccessibility* node) {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
+ if (obj.is_null())
+ return;
+
+ // TODO(aboxhall): call into appropriate Java method for each type of
+ // notification
+}
+
+jint BrowserAccessibilityManagerAndroid::GetRootId(JNIEnv* env, jobject obj) {
+ return static_cast<jint>(root_->renderer_id());
+}
+
+jint BrowserAccessibilityManagerAndroid::HitTest(
+ JNIEnv* env, jobject obj, jint x, jint y) {
+ BrowserAccessibilityAndroid* result =
+ static_cast<BrowserAccessibilityAndroid*>(
+ root_->BrowserAccessibilityForPoint(gfx::Point(x, y)));
+
+ if (!result)
+ return root_->renderer_id();
+
+ if (result->IsFocusable())
+ return result->renderer_id();
+
+ // Examine the children of |result| to find the nearest accessibility focus
+ // candidate
+ BrowserAccessibility* nearest_node = FuzzyHitTest(x, y, result);
+ if (nearest_node)
+ return nearest_node->renderer_id();
+
+ return root_->renderer_id();
+}
+
+BrowserAccessibility* BrowserAccessibilityManagerAndroid::FuzzyHitTest(
+ int x, int y, BrowserAccessibility* start_node) {
+ BrowserAccessibility* nearest_node = NULL;
+ int min_distance = INT_MAX;
+ FuzzyHitTestImpl(x, y, start_node, &nearest_node, &min_distance);
+ return nearest_node;
+}
+
+// static
+void BrowserAccessibilityManagerAndroid::FuzzyHitTestImpl(
+ int x, int y, BrowserAccessibility* start_node,
+ BrowserAccessibility** nearest_candidate, int* nearest_distance) {
+ BrowserAccessibilityAndroid* node =
+ static_cast<BrowserAccessibilityAndroid*>(start_node);
+ int distance = CalculateDistanceSquared(x, y, node);
+
+ if (node->IsFocusable()) {
+ if (distance < *nearest_distance) {
+ *nearest_candidate = node;
+ *nearest_distance = distance;
+ }
+ // Don't examine any more children of focusable node
+ // TODO(aboxhall): what about focusable children?
+ return;
+ }
+
+ if (!node->ComputeName().empty()) {
+ if (distance < *nearest_distance) {
+ *nearest_candidate = node;
+ *nearest_distance = distance;
+ }
+ return;
+ }
+
+ if (!node->IsLeaf()) {
+ for (uint32 i = 0; i < node->child_count(); i++) {
+ BrowserAccessibility* child = node->GetChild(i);
+ FuzzyHitTestImpl(x, y, child, nearest_candidate, nearest_distance);
+ }
+ }
+}
+
+// static
+int BrowserAccessibilityManagerAndroid::CalculateDistanceSquared(
+ int x, int y, BrowserAccessibility* node) {
+ gfx::Rect node_bounds = node->GetLocalBoundsRect();
+ int nearest_x = Clamp(x, node_bounds.x(), node_bounds.right());
+ int nearest_y = Clamp(y, node_bounds.y(), node_bounds.bottom());
+ int dx = std::abs(x - nearest_x);
+ int dy = std::abs(y - nearest_y);
+ return dx * dx + dy * dy;
+}
+
+jint BrowserAccessibilityManagerAndroid::GetNativeNodeById(
+ JNIEnv* env, jobject obj, jint id) {
+ return reinterpret_cast<jint>(GetFromRendererID(id));
+}
+
+void BrowserAccessibilityManagerAndroid::NotifyRootChanged() {
+ // TODO(aboxhall): non-stub implementation
+}
+
+bool
+BrowserAccessibilityManagerAndroid::UseRootScrollOffsetsWhenComputingBounds() {
+ // The Java layer handles the root scroll offset.
+ return false;
+}
+
+bool RegisterBrowserAccessibilityManager(JNIEnv* env) {
+ // TODO(aboxhall): non-stub implementation
+ return false;
+}
+
+} // namespace content
diff --git a/content/browser/accessibility/browser_accessibility_manager_android.h b/content/browser/accessibility/browser_accessibility_manager_android.h
new file mode 100644
index 0000000..0ca04e5
--- /dev/null
+++ b/content/browser/accessibility/browser_accessibility_manager_android.h
@@ -0,0 +1,85 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_ANDROID_H_
+#define CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_ANDROID_H_
+
+#include "base/android/scoped_java_ref.h"
+#include "content/browser/accessibility/browser_accessibility_manager.h"
+#include "content/browser/android/content_view_core_impl.h"
+
+namespace content {
+
+namespace aria_strings {
+ extern const char kAriaLivePolite[];
+ extern const char kAriaLiveAssertive[];
+}
+
+class CONTENT_EXPORT BrowserAccessibilityManagerAndroid
+ : public BrowserAccessibilityManager {
+ public:
+ BrowserAccessibilityManagerAndroid(
+ base::android::ScopedJavaLocalRef<jobject> content_view_core,
+ const AccessibilityNodeData& src,
+ BrowserAccessibilityDelegate* delegate,
+ BrowserAccessibilityFactory* factory = new BrowserAccessibilityFactory());
+
+ virtual ~BrowserAccessibilityManagerAndroid();
+
+ static AccessibilityNodeData GetEmptyDocument();
+
+ // Implementation of BrowserAccessibilityManager.
+ virtual void NotifyAccessibilityEvent(int type,
+ BrowserAccessibility* node) OVERRIDE;
+
+ // --------------------------------------------------------------------------
+ // Methods called from Java via JNI
+ // --------------------------------------------------------------------------
+
+ // Tree methods.
+ jint GetRootId(JNIEnv* env, jobject obj);
+ jint HitTest(JNIEnv* env, jobject obj, jint x, jint y);
+
+ // Gets a temporary pointer to a specific node, only valid in this scope.
+ // May return 0 if that node id is no longer valid.
+ jint GetNativeNodeById(JNIEnv* env, jobject obj, jint id);
+
+ protected:
+ virtual void NotifyRootChanged() OVERRIDE;
+
+ virtual bool UseRootScrollOffsetsWhenComputingBounds() OVERRIDE;
+
+ private:
+ // This gives BrowserAccessibilityManager::Create access to the class
+ // constructor.
+ friend class BrowserAccessibilityManager;
+
+ // A weak reference to the Java BrowserAccessibilityManager object.
+ // This avoids adding another reference to BrowserAccessibilityManager and
+ // preventing garbage collection.
+ // Premature garbage collection is prevented by the long-lived reference in
+ // ContentViewCore.
+ JavaObjectWeakGlobalRef java_ref_;
+
+ // Searches through the children of start_node to find the nearest
+ // accessibility focus candidate for a touch which has not landed directly on
+ // an accessibility focus candidate.
+ BrowserAccessibility* FuzzyHitTest(
+ int x, int y, BrowserAccessibility* start_node);
+
+ static void FuzzyHitTestImpl(int x, int y, BrowserAccessibility* start_node,
+ BrowserAccessibility** nearest_candidate, int* min_distance);
+
+ // Calculates the distance from the point (x, y) to the nearest point on the
+ // edge of |node|.
+ static int CalculateDistanceSquared(int x, int y, BrowserAccessibility* node);
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityManagerAndroid);
+};
+
+bool RegisterBrowserAccessibilityManager(JNIEnv* env);
+
+}
+
+#endif // CONTENT_BROWSER_ACCESSIBILITY_BROWSER_ACCESSIBILITY_MANAGER_ANDROID_H_