Commit 9002b9a6 authored by Hiroki Sato's avatar Hiroki Sato Committed by Chromium LUCI CQ

arc-a11y: Support Android AutoCompleteTextView accessibility

This change adds support of Android AutoCompleteTextView for Chrome
automation, so that ChromeVox can handle it and make an announcement.

See go/arc-autocomplete-a11y for details.

This also changes ChromeVox's announcement on active descendant changed
events, so that it won't announce hints as the hints are mostly for the
focused node.

AX-Relnotes: Adding a support of Android app's autocomplete of editables.
Bug: b:150827488
Test: unit_tests

Change-Id: I2f2dd9832311a080d24f1e0771b2e97ecd455e5c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2576068
Commit-Queue: Hiroki Sato <hirokisato@chromium.org>
Reviewed-by: default avatarDavid Tseng <dtseng@chromium.org>
Reviewed-by: default avatarSara Kato <sarakato@chromium.org>
Cr-Commit-Position: refs/heads/master@{#837007}
parent 2453782c
......@@ -573,6 +573,8 @@ source_set("chromeos") {
"arc/accessibility/arc_accessibility_helper_bridge.h",
"arc/accessibility/arc_accessibility_util.cc",
"arc/accessibility/arc_accessibility_util.h",
"arc/accessibility/auto_complete_handler.cc",
"arc/accessibility/auto_complete_handler.h",
"arc/accessibility/ax_tree_source_arc.cc",
"arc/accessibility/ax_tree_source_arc.h",
"arc/accessibility/drawer_layout_handler.cc",
......@@ -3342,6 +3344,7 @@ source_set("unit_tests") {
"arc/accessibility/accessibility_node_info_data_wrapper_unittest.cc",
"arc/accessibility/arc_accessibility_helper_bridge_unittest.cc",
"arc/accessibility/arc_accessibility_util_unittest.cc",
"arc/accessibility/auto_complete_handler_unittest.cc",
"arc/accessibility/ax_tree_source_arc_unittest.cc",
"arc/accessibility/drawer_layout_handler_unittest.cc",
"arc/adbd/arc_adbd_monitor_bridge_unittest.cc",
......
......@@ -11,11 +11,9 @@
namespace arc {
using AXActionType = mojom::AccessibilityActionType;
using AXBooleanProperty = mojom::AccessibilityBooleanProperty;
using AXIntListProperty = mojom::AccessibilityIntListProperty;
using AXEventIntProperty = mojom::AccessibilityEventIntProperty;
using AXNodeInfoData = mojom::AccessibilityNodeInfoData;
using AXStringProperty = mojom::AccessibilityStringProperty;
base::Optional<ax::mojom::Event> FromContentChangeTypesToAXEvent(
const std::vector<int32_t>& arc_content_change_types) {
......@@ -151,6 +149,56 @@ base::Optional<mojom::AccessibilityActionType> ConvertToAndroidAction(
}
}
AccessibilityInfoDataWrapper* GetSelectedNodeInfoFromAdapterViewEvent(
const mojom::AccessibilityEventData& event_data,
AccessibilityInfoDataWrapper* source_node) {
if (!source_node || !source_node->IsNode())
return nullptr;
AXNodeInfoData* node_info = source_node->GetNode();
if (!node_info)
return nullptr;
AccessibilityInfoDataWrapper* selected_node = source_node;
if (!node_info->collection_item_info) {
// The event source is not an item of AdapterView. If the event source is
// AdapterView, select the child. Otherwise, this is an unrelated event.
int item_count, from_index, current_item_index;
if (!GetProperty(event_data.int_properties, AXEventIntProperty::ITEM_COUNT,
&item_count) ||
!GetProperty(event_data.int_properties, AXEventIntProperty::FROM_INDEX,
&from_index) ||
!GetProperty(event_data.int_properties,
AXEventIntProperty::CURRENT_ITEM_INDEX,
&current_item_index)) {
return nullptr;
}
int index = current_item_index - from_index;
if (index < 0)
return nullptr;
std::vector<AccessibilityInfoDataWrapper*> children;
source_node->GetChildren(&children);
if (index >= static_cast<int>(children.size()))
return nullptr;
selected_node = children[index];
}
// Sometimes a collection item is wrapped by a non-focusable node.
// Find a node with focusable property.
while (selected_node && !GetBooleanProperty(selected_node->GetNode(),
AXBooleanProperty::FOCUSABLE)) {
std::vector<AccessibilityInfoDataWrapper*> children;
selected_node->GetChildren(&children);
if (children.size() != 1)
break;
selected_node = children[0];
}
return selected_node;
}
std::string ToLiveStatusString(mojom::AccessibilityLiveRegionType type) {
switch (type) {
case mojom::AccessibilityLiveRegionType::NONE:
......
......@@ -17,7 +17,9 @@
#include "ui/accessibility/ax_enum_util.h"
namespace arc {
class AccessibilityInfoDataWrapper;
// This function is only called when EventType is WINDOW_STATE_CHANGED or
// WINDOW_CONTENT_CHANGED.
base::Optional<ax::mojom::Event> FromContentChangeTypesToAXEvent(
......@@ -32,6 +34,10 @@ ax::mojom::Event ToAXEvent(
base::Optional<mojom::AccessibilityActionType> ConvertToAndroidAction(
ax::mojom::Action action);
AccessibilityInfoDataWrapper* GetSelectedNodeInfoFromAdapterViewEvent(
const mojom::AccessibilityEventData& event_data,
AccessibilityInfoDataWrapper* source_node);
std::string ToLiveStatusString(mojom::AccessibilityLiveRegionType type);
template <class DataType, class PropType>
......
// Copyright 2020 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 "chrome/browser/chromeos/arc/accessibility/auto_complete_handler.h"
#include "chrome/browser/chromeos/arc/accessibility/accessibility_info_data_wrapper.h"
#include "chrome/browser/chromeos/arc/accessibility/arc_accessibility_util.h"
#include "chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc.h"
#include "components/arc/mojom/accessibility_helper.mojom-forward.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/platform/ax_android_constants.h"
namespace {
// The list value aria-autocomplete property.
constexpr char kAutoCompleteListAttribute[] = "list";
bool IsAutoComplete(arc::mojom::AccessibilityNodeInfoData* node) {
if (!node || !node->string_properties)
return false;
auto it = node->string_properties->find(
arc::mojom::AccessibilityStringProperty::CLASS_NAME);
if (it == node->string_properties->end())
return false;
return it->second == ui::kAXAutoCompleteTextViewClassname ||
it->second == ui::kAXMultiAutoCompleteTextViewClassname;
}
int32_t GetAnchorId(arc::mojom::AccessibilityWindowInfoData* window) {
if (!window)
return ui::AXNode::kInvalidAXID;
int32_t id;
if (arc::GetProperty(
window->int_properties,
arc::mojom::AccessibilityWindowIntProperty::ANCHOR_NODE_ID, &id)) {
return id;
}
return ui::AXNode::kInvalidAXID;
}
} // namespace
namespace arc {
AutoCompleteHandler::AutoCompleteHandler(const int32_t editable_node_id)
: anchored_node_id_(editable_node_id) {}
AutoCompleteHandler::~AutoCompleteHandler() = default;
// static
std::vector<std::pair<int32_t, std::unique_ptr<AutoCompleteHandler>>>
AutoCompleteHandler::CreateIfNecessary(
AXTreeSourceArc* tree_source,
const mojom::AccessibilityEventData& event_data) {
if (event_data.event_type !=
mojom::AccessibilityEventType::WINDOW_CONTENT_CHANGED) {
return {};
}
std::vector<std::pair<int32_t, std::unique_ptr<AutoCompleteHandler>>> results;
// Check all updated nodes under the event source.
std::vector<AccessibilityInfoDataWrapper*> to_visit;
to_visit.push_back(tree_source->GetFromId(event_data.source_id));
while (!to_visit.empty()) {
AccessibilityInfoDataWrapper* node_ptr = to_visit.back();
to_visit.pop_back();
if (!node_ptr)
continue;
const int32_t node_id = node_ptr->GetId();
if (IsAutoComplete(node_ptr->GetNode())) {
results.emplace_back(node_id,
std::make_unique<AutoCompleteHandler>(node_id));
}
node_ptr->GetChildren(&to_visit);
}
return results;
}
bool AutoCompleteHandler::PreDispatchEvent(
AXTreeSourceArc* tree_source,
const mojom::AccessibilityEventData& event_data) {
if (event_data.event_type == mojom::AccessibilityEventType::WINDOWS_CHANGED) {
// Check if a popup window anchoring the node exists.
// The first window is the main window. Look for other windows.
for (size_t i = 1; i < event_data.window_data->size(); ++i) {
if (GetAnchorId(event_data.window_data->at(i).get()) !=
anchored_node_id_) {
continue;
}
int32_t window_id = event_data.window_data->at(i)->window_id;
if (suggestion_window_id_ != window_id) {
// Anchoring window has changed.
suggestion_window_id_ = window_id;
return true;
} else {
// Nothing related changed. No need to update.
return false;
}
}
if (suggestion_window_id_.has_value()) {
// No popup window found. The window has disappeared.
suggestion_window_id_.reset();
selected_node_id_.reset();
return true;
}
return false;
} else if (event_data.event_type ==
mojom::AccessibilityEventType::VIEW_SELECTED) {
// VIEW_SELECTED event from the suggestion list is a user's selecting action
// in a candidate.
if (!suggestion_window_id_.has_value())
return false;
AccessibilityInfoDataWrapper* source_node =
tree_source->GetFromId(event_data.source_id);
if (!source_node || source_node->GetWindowId() != suggestion_window_id_)
return false;
AccessibilityInfoDataWrapper* selected_node =
GetSelectedNodeInfoFromAdapterViewEvent(event_data, source_node);
if (!selected_node || selected_node->GetId() == selected_node_id_)
return false;
selected_node_id_ = selected_node->GetId();
return true;
}
return false;
}
void AutoCompleteHandler::PostSerializeNode(ui::AXNodeData* out_data) const {
DCHECK_EQ(out_data->role, ax::mojom::Role::kTextField);
out_data->AddStringAttribute(ax::mojom::StringAttribute::kAutoComplete,
kAutoCompleteListAttribute);
if (suggestion_window_id_.has_value()) {
out_data->AddState(ax::mojom::State::kExpanded);
out_data->AddIntListAttribute(ax::mojom::IntListAttribute::kControlsIds,
{suggestion_window_id_.value()});
if (selected_node_id_.has_value()) {
out_data->AddIntAttribute(ax::mojom::IntAttribute::kActivedescendantId,
selected_node_id_.value());
}
} else {
out_data->AddState(ax::mojom::State::kCollapsed);
}
}
} // namespace arc
// Copyright 2020 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 CHROME_BROWSER_CHROMEOS_ARC_ACCESSIBILITY_AUTO_COMPLETE_HANDLER_H_
#define CHROME_BROWSER_CHROMEOS_ARC_ACCESSIBILITY_AUTO_COMPLETE_HANDLER_H_
#include <memory>
#include <utility>
#include <vector>
#include "base/optional.h"
#include "chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc.h"
namespace ui {
struct AXNodeData;
}
namespace arc {
namespace mojom {
class AccessibilityEventData;
}
class AutoCompleteHandler : public AXTreeSourceArc::Hook {
public:
static std::vector<std::pair<int32_t, std::unique_ptr<AutoCompleteHandler>>>
CreateIfNecessary(AXTreeSourceArc* tree_source,
const mojom::AccessibilityEventData& event_data);
explicit AutoCompleteHandler(const int32_t editable_node_id);
~AutoCompleteHandler() override;
// AXTreeSourceArc::Hook overrides:
bool PreDispatchEvent(
AXTreeSourceArc* tree_source,
const mojom::AccessibilityEventData& event_data) override;
void PostSerializeNode(ui::AXNodeData* out_data) const override;
private:
const int32_t anchored_node_id_;
base::Optional<int32_t> suggestion_window_id_;
base::Optional<int32_t> selected_node_id_;
};
} // namespace arc
#endif // CHROME_BROWSER_CHROMEOS_ARC_ACCESSIBILITY_AUTO_COMPLETE_HANDLER_H_
......@@ -13,6 +13,7 @@
#include "chrome/browser/chromeos/arc/accessibility/accessibility_node_info_data_wrapper.h"
#include "chrome/browser/chromeos/arc/accessibility/accessibility_window_info_data_wrapper.h"
#include "chrome/browser/chromeos/arc/accessibility/arc_accessibility_util.h"
#include "chrome/browser/chromeos/arc/accessibility/auto_complete_handler.h"
#include "chrome/browser/chromeos/arc/accessibility/drawer_layout_handler.h"
#include "components/exo/input_method_surface.h"
#include "components/exo/wm_helper.h"
......@@ -23,10 +24,7 @@
namespace arc {
using AXBooleanProperty = mojom::AccessibilityBooleanProperty;
using AXEventData = mojom::AccessibilityEventData;
using AXEventIntListProperty = mojom::AccessibilityEventIntListProperty;
using AXEventIntProperty = mojom::AccessibilityEventIntProperty;
using AXEventType = mojom::AccessibilityEventType;
using AXIntProperty = mojom::AccessibilityIntProperty;
using AXIntListProperty = mojom::AccessibilityIntListProperty;
......@@ -237,7 +235,7 @@ void AXTreeSourceArc::NotifyAccessibilityEventInternal(
return;
}
ProcessHooksOnEvent(event_data);
std::vector<int32_t> update_ids = ProcessHooksOnEvent(event_data);
// Bundle the event and send it to automation.
ExtensionMsg_AccessibilityEventBundleParams event_bundle;
......@@ -263,8 +261,6 @@ void AXTreeSourceArc::NotifyAccessibilityEventInternal(
HandleLiveRegions(&event_bundle.events);
event_bundle.updates.emplace_back();
// Force the tree, to update, so unignored fields get updated.
// On event type of WINDOW_STATE_CHANGED, update the entire tree so that
// window location is correctly calculated.
......@@ -272,11 +268,16 @@ void AXTreeSourceArc::NotifyAccessibilityEventInternal(
(event_data.event_type == AXEventType::WINDOW_STATE_CHANGED)
? *root_id_
: event_data.source_id;
event_bundle.updates[0].node_id_to_clear = node_id_to_clear;
current_tree_serializer_->InvalidateSubtree(GetFromId(node_id_to_clear));
current_tree_serializer_->SerializeChanges(GetFromId(node_id_to_clear),
&event_bundle.updates.back());
update_ids.push_back(node_id_to_clear);
for (const int32_t update_root : update_ids) {
event_bundle.updates.emplace_back();
event_bundle.updates.back().node_id_to_clear = update_root;
current_tree_serializer_->InvalidateSubtree(GetFromId(update_root));
current_tree_serializer_->SerializeChanges(GetFromId(update_root),
&event_bundle.updates.back());
}
GetAutomationEventRouter()->DispatchAccessibilityEvents(event_bundle);
}
......@@ -345,57 +346,6 @@ AXTreeSourceArc::FindFirstFocusableNodeInFullFocusMode(
return nullptr;
}
AccessibilityInfoDataWrapper*
AXTreeSourceArc::GetSelectedNodeInfoFromAdapterView(
const AXEventData& event_data) const {
AccessibilityInfoDataWrapper* source_node = GetFromId(event_data.source_id);
if (!source_node || !source_node->IsNode())
return nullptr;
AXNodeInfoData* node_info = source_node->GetNode();
if (!node_info)
return nullptr;
AccessibilityInfoDataWrapper* selected_node = source_node;
if (!node_info->collection_item_info) {
// The event source is not an item of AdapterView. If the event source is
// AdapterView, select the child. Otherwise, this is an unrelated event.
int item_count, from_index, current_item_index;
if (!GetProperty(event_data.int_properties, AXEventIntProperty::ITEM_COUNT,
&item_count) ||
!GetProperty(event_data.int_properties, AXEventIntProperty::FROM_INDEX,
&from_index) ||
!GetProperty(event_data.int_properties,
AXEventIntProperty::CURRENT_ITEM_INDEX,
&current_item_index)) {
return nullptr;
}
int index = current_item_index - from_index;
if (index < 0)
return nullptr;
std::vector<AccessibilityInfoDataWrapper*> children;
source_node->GetChildren(&children);
if (index >= static_cast<int>(children.size()))
return nullptr;
selected_node = children[index];
}
// Sometimes a collection item is wrapped by a non-focusable node.
// Find a node with focusable property.
while (selected_node && !GetBooleanProperty(selected_node->GetNode(),
AXBooleanProperty::FOCUSABLE)) {
std::vector<AccessibilityInfoDataWrapper*> children;
selected_node->GetChildren(&children);
if (children.size() != 1)
break;
selected_node = children[0];
}
return selected_node;
}
bool AXTreeSourceArc::UpdateAndroidFocusedId(const AXEventData& event_data) {
AccessibilityInfoDataWrapper* source_node = GetFromId(event_data.source_id);
if (source_node) {
......@@ -437,7 +387,7 @@ bool AXTreeSourceArc::UpdateAndroidFocusedId(const AXEventData& event_data) {
bool is_range_change = !node_info->range_info.is_null();
if (!is_range_change) {
AccessibilityInfoDataWrapper* selected_node =
GetSelectedNodeInfoFromAdapterView(event_data);
GetSelectedNodeInfoFromAdapterViewEvent(event_data, source_node);
if (!selected_node || !selected_node->IsVisibleToUser())
return false;
......@@ -499,16 +449,32 @@ bool AXTreeSourceArc::UpdateAndroidFocusedId(const AXEventData& event_data) {
return true;
}
void AXTreeSourceArc::ProcessHooksOnEvent(const AXEventData& event_data) {
std::vector<int32_t> AXTreeSourceArc::ProcessHooksOnEvent(
const AXEventData& event_data) {
base::EraseIf(hooks_, [this](const auto& it) {
return this->GetFromId(it.first) == nullptr;
});
std::vector<int32_t> serialization_needed_ids;
for (const auto& modifier : hooks_) {
if (modifier.second->PreDispatchEvent(this, event_data))
serialization_needed_ids.push_back(modifier.first);
}
// Add new hook implementations if necessary.
auto drawer_layout_hook =
DrawerLayoutHandler::CreateIfNecessary(this, event_data);
if (drawer_layout_hook.has_value())
hooks_.insert(std::move(*drawer_layout_hook));
auto auto_complete_hooks =
AutoCompleteHandler::CreateIfNecessary(this, event_data);
for (auto& modifier : auto_complete_hooks) {
if (hooks_.count(modifier.first) == 0)
hooks_.insert(std::move(modifier));
}
return serialization_needed_ids;
}
void AXTreeSourceArc::HandleLiveRegions(std::vector<ui::AXEvent>* events) {
......
......@@ -52,8 +52,20 @@ class AXTreeSourceArc : public ui::AXTreeSource<AccessibilityInfoDataWrapper*,
Hook() = default;
virtual ~Hook() = default;
// Called prior to accessibility event dispatch.
// Hook implementations can update the internal state if necessary so that
// hooks can update the serialization state in PostSerializeNode().
// Return true if re-serialization of attaching node is needed.
virtual bool PreDispatchEvent(
AXTreeSourceArc* tree_source,
const mojom::AccessibilityEventData& event_data) = 0;
// Called after the default serialization of the attaching node.
// Implementations can modify the serialization of given |out_data|.
// Hook implementations can modify the serialization of given |out_data|.
// Note that serialization is executed only when ui::AXTreeSerializer calls
// SerializeNode() from AXTreeSerializer.SerializeChanges().
// To ensure the node re-serialized, the class must return |true| on
// PreDispatchEvent() if the event is NOT coming from its ancestry.
virtual void PostSerializeNode(ui::AXNodeData* out_data) const = 0;
};
......@@ -131,9 +143,6 @@ class AXTreeSourceArc : public ui::AXTreeSource<AccessibilityInfoDataWrapper*,
AccessibilityInfoDataWrapper* FindFirstFocusableNodeInFullFocusMode(
AccessibilityInfoDataWrapper* info_data) const;
AccessibilityInfoDataWrapper* GetSelectedNodeInfoFromAdapterView(
const mojom::AccessibilityEventData& event_data) const;
// Updates android_focused_id_ from given AccessibilityEventData.
// Having this method, |android_focused_id_| is one of these:
// - input focus in Android
......@@ -144,7 +153,10 @@ class AXTreeSourceArc : public ui::AXTreeSource<AccessibilityInfoDataWrapper*,
// event to chrome automation. Otherwise, this returns true.
bool UpdateAndroidFocusedId(const mojom::AccessibilityEventData& event_data);
void ProcessHooksOnEvent(const mojom::AccessibilityEventData& event_data);
// Processes implementations of Hooks and returns a list node id that needs
// re-serialization.
std::vector<int32_t> ProcessHooksOnEvent(
const mojom::AccessibilityEventData& event_data);
// Compare previous live region and current live region, and add event to the
// given vector if there is any difference.
......
......@@ -38,6 +38,7 @@ using AXStringListProperty = mojom::AccessibilityStringListProperty;
using AXStringProperty = mojom::AccessibilityStringProperty;
using AXWindowBooleanProperty = mojom::AccessibilityWindowBooleanProperty;
using AXWindowInfoData = mojom::AccessibilityWindowInfoData;
using AXWindowIntProperty = mojom::AccessibilityWindowIntProperty;
using AXWindowIntListProperty = mojom::AccessibilityWindowIntListProperty;
using AXWindowStringProperty = mojom::AccessibilityWindowStringProperty;
......@@ -75,6 +76,12 @@ void SetProperty(AXWindowInfoData* window,
arc::SetProperty(window->boolean_properties, prop, value);
}
void SetProperty(AXWindowInfoData* window,
AXWindowIntProperty prop,
int value) {
arc::SetProperty(window->int_properties, prop, value);
}
void SetProperty(AXWindowInfoData* window,
AXWindowIntListProperty prop,
const std::vector<int>& value) {
......@@ -1407,4 +1414,119 @@ TEST_F(AXTreeSourceArcTest, ControlReceivesFocus) {
EXPECT_EQ(node->id, tree_data.focus_id);
}
TEST_F(AXTreeSourceArcTest, AutoComplete) {
auto event = AXEventData::New();
event->task_id = 1;
event->window_data = std::vector<mojom::AccessibilityWindowInfoDataPtr>();
event->window_data->push_back(AXWindowInfoData::New());
AXWindowInfoData* root_window = event->window_data->back().get();
root_window->window_id = 100;
root_window->root_node_id = 10;
event->node_data.push_back(AXNodeInfoData::New());
AXNodeInfoData* root = event->node_data.back().get();
root->id = 10;
root->window_id = 100;
SetProperty(root, AXIntListProperty::CHILD_NODE_IDS, std::vector<int>({1}));
SetProperty(root, AXBooleanProperty::IMPORTANCE, true);
event->node_data.push_back(AXNodeInfoData::New());
AXNodeInfoData* editable = event->node_data.back().get();
editable->id = 1;
editable->window_id = 100;
SetProperty(editable, AXBooleanProperty::IMPORTANCE, true);
SetProperty(editable, AXBooleanProperty::VISIBLE_TO_USER, true);
SetProperty(editable, AXBooleanProperty::EDITABLE, true);
SetProperty(editable, AXStringProperty::CLASS_NAME,
"android.widget.MultiAutoCompleteTextView");
// Check basic properties.
event->event_type = AXEventType::WINDOW_CONTENT_CHANGED;
event->source_id = root->id;
CallNotifyAccessibilityEvent(event.get());
ui::AXNodeData data;
data = GetSerializedNode(editable->id);
ASSERT_EQ(ax::mojom::Role::kTextField, data.role);
std::string attribute;
ASSERT_TRUE(data.GetStringAttribute(ax::mojom::StringAttribute::kAutoComplete,
&attribute));
EXPECT_EQ("list", attribute);
EXPECT_TRUE(data.HasState(ax::mojom::State::kCollapsed));
// Add a sub-window anchoring the editable.
event->window_data->push_back(AXWindowInfoData::New());
AXWindowInfoData* popup_window = event->window_data->back().get();
popup_window->window_id = 200;
popup_window->root_node_id = 20;
SetProperty(popup_window, AXWindowIntProperty::ANCHOR_NODE_ID, editable->id);
SetProperty(root_window, AXWindowIntListProperty::CHILD_WINDOW_IDS, {200});
event->node_data.push_back(AXNodeInfoData::New());
AXNodeInfoData* candidate_list = event->node_data.back().get();
candidate_list->id = 20;
candidate_list->window_id = 200;
SetProperty(candidate_list, AXBooleanProperty::IMPORTANCE, true);
SetProperty(candidate_list, AXBooleanProperty::VISIBLE_TO_USER, true);
SetProperty(candidate_list, AXIntListProperty::CHILD_NODE_IDS,
std::vector<int>({21}));
event->node_data.push_back(AXNodeInfoData::New());
AXNodeInfoData* list_item = event->node_data.back().get();
list_item->id = 21;
list_item->window_id = 200;
SetProperty(list_item, AXBooleanProperty::IMPORTANCE, true);
SetProperty(list_item, AXBooleanProperty::VISIBLE_TO_USER, true);
list_item->collection_item_info = AXCollectionItemInfoData::New();
// Verify the relationship of window and editable.
event->event_type = AXEventType::WINDOWS_CHANGED;
event->source_id = root->id;
CallNotifyAccessibilityEvent(event.get());
data = GetSerializedNode(editable->id);
EXPECT_TRUE(data.HasState(ax::mojom::State::kExpanded));
std::vector<int32_t> controlled_ids;
ASSERT_TRUE(data.GetIntListAttribute(
ax::mojom::IntListAttribute::kControlsIds, &controlled_ids));
ASSERT_EQ(1U, controlled_ids.size());
ASSERT_EQ(popup_window->window_id, controlled_ids[0]);
// Invoke a selection event from the list.
SetProperty(list_item, AXBooleanProperty::SELECTED, true);
// Verify that active descendant is updated.
event->event_type = AXEventType::VIEW_SELECTED;
event->source_id = list_item->id;
CallNotifyAccessibilityEvent(event.get());
data = GetSerializedNode(editable->id);
int32_t active_descendant;
ASSERT_TRUE(data.GetIntAttribute(ax::mojom::IntAttribute::kActivedescendantId,
&active_descendant));
ASSERT_EQ(list_item->id, active_descendant);
// Delete popup window.
event->node_data.pop_back();
event->node_data.pop_back();
event->window_data->pop_back();
SetProperty(root_window, AXWindowIntListProperty::CHILD_WINDOW_IDS, {});
// Verify autocomplete properties are still populated.
event->event_type = AXEventType::WINDOWS_CHANGED;
event->source_id = root->id;
CallNotifyAccessibilityEvent(event.get());
data = GetSerializedNode(editable->id);
ASSERT_TRUE(data.GetStringAttribute(ax::mojom::StringAttribute::kAutoComplete,
&attribute));
EXPECT_EQ("list", attribute);
EXPECT_TRUE(data.HasState(ax::mojom::State::kCollapsed));
}
} // namespace arc
......@@ -70,6 +70,12 @@ DrawerLayoutHandler::CreateIfNecessary(
return base::nullopt;
}
bool DrawerLayoutHandler::PreDispatchEvent(
AXTreeSourceArc* tree_source,
const mojom::AccessibilityEventData& event_data) {
return false;
}
void DrawerLayoutHandler::PostSerializeNode(ui::AXNodeData* out_data) const {
out_data->role = ax::mojom::Role::kMenu;
if (!name_.empty())
......
......@@ -32,6 +32,9 @@ class DrawerLayoutHandler : public AXTreeSourceArc::Hook {
explicit DrawerLayoutHandler(const std::string& name) : name_(name) {}
// AXTreeSourceArc::Hook overrides:
bool PreDispatchEvent(
AXTreeSourceArc* tree_source,
const mojom::AccessibilityEventData& event_data) override;
void PostSerializeNode(ui::AXNodeData* out_data) const override;
private:
......
......@@ -82,6 +82,7 @@ FocusAutomationHandler = class extends BaseAutomationHandler {
cursors.Range.fromNode(this.previousActiveDescendant_) :
ChromeVoxState.instance.currentRange;
new Output()
.withoutHints()
.withRichSpeechAndBraille(
cursors.Range.fromNode(evt.target.activeDescendant), prev,
Output.EventType.NAVIGATE)
......
......@@ -6,6 +6,8 @@
namespace ui {
const char kAXAutoCompleteTextViewClassname[] =
"android.widget.AutoCompleteTextView";
const char kAXAbsListViewClassname[] = "android.widget.AbsListView";
const char kAXButtonClassname[] = "android.widget.Button";
const char kAXCheckBoxClassname[] = "android.widget.CheckBox";
......@@ -21,6 +23,8 @@ const char kAXImageButtonClassname[] = "android.widget.ImageButton";
const char kAXImageViewClassname[] = "android.widget.ImageView";
const char kAXListViewClassname[] = "android.widget.ListView";
const char kAXMenuItemClassname[] = "android.view.MenuItem";
const char kAXMultiAutoCompleteTextViewClassname[] =
"android.widget.MultiAutoCompleteTextView";
const char kAXPagerClassname[] = "android.support.v4.view.ViewPager";
const char kAXProgressBarClassname[] = "android.widget.ProgressBar";
const char kAXRadioButtonClassname[] = "android.widget.RadioButton";
......
......@@ -12,6 +12,7 @@ namespace ui {
// Classnames.
AX_EXPORT extern const char kAXAutoCompleteTextViewClassname[];
AX_EXPORT extern const char kAXAbsListViewClassname[];
AX_EXPORT extern const char kAXButtonClassname[];
AX_EXPORT extern const char kAXCheckBoxClassname[];
......@@ -26,6 +27,7 @@ AX_EXPORT extern const char kAXImageButtonClassname[];
AX_EXPORT extern const char kAXImageViewClassname[];
AX_EXPORT extern const char kAXListViewClassname[];
AX_EXPORT extern const char kAXMenuItemClassname[];
AX_EXPORT extern const char kAXMultiAutoCompleteTextViewClassname[];
AX_EXPORT extern const char kAXPagerClassname[];
AX_EXPORT extern const char kAXProgressBarClassname[];
AX_EXPORT extern const char kAXRadioButtonClassname[];
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment