Commit ef7d1fe3 authored by Hiroki Sato's avatar Hiroki Sato Committed by Commit Bot

Dispatch event when TalkBack state changed

This CL adds a new accessibility private API where extensions will be
able to listen a ChromeEvent which is dispatched when TalkBack state in
ARC accessibility gets changed.

Android side change is http://ag/10260750.

Bug: 981964
Test: unit_tests --gtest_filter="ArcAccessibilityHelperBridgeTest.*"
Change-Id: I9012e9761d3995bd686c2651ecc9921ed8fc15e3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2037187Reviewed-by: default avatarAkihiro Ota <akihiroota@chromium.org>
Reviewed-by: default avatarIstiaque Ahmed <lazyboy@chromium.org>
Reviewed-by: default avatarJorge Lucangeli Obes <jorgelo@chromium.org>
Reviewed-by: default avatarDavid Tseng <dtseng@chromium.org>
Commit-Queue: Hiroki Sato <hirokisato@chromium.org>
Cr-Commit-Position: refs/heads/master@{#741688}
parent e4582459
......@@ -46,16 +46,6 @@ using ash::ArcNotificationSurfaceManager;
namespace {
exo::Surface* GetArcSurface(const aura::Window* window) {
if (!window)
return nullptr;
exo::Surface* arc_surface = exo::Surface::AsSurface(window);
if (!arc_surface)
arc_surface = exo::GetShellMainSurface(window);
return arc_surface;
}
void DispatchFocusChange(arc::mojom::AccessibilityNodeInfoData* node_data,
Profile* profile) {
chromeos::AccessibilityManager* accessibility_manager =
......@@ -421,6 +411,12 @@ void ArcAccessibilityHelperBridge::OnNotificationStateChanged(
}
}
void ArcAccessibilityHelperBridge::OnToggleNativeChromeVoxArcSupport(
bool enabled) {
native_chromevox_enabled_ = enabled;
DispatchCustomSpokenFeedbackToggled(!enabled);
}
void ArcAccessibilityHelperBridge::OnAction(
const ui::AXActionData& data) const {
DCHECK(data.target_node_id);
......@@ -524,6 +520,26 @@ void ArcAccessibilityHelperBridge::OnNotificationSurfaceAdded(
}
}
void ArcAccessibilityHelperBridge::OnWindowActivated(
ActivationReason reason,
aura::Window* gained_active,
aura::Window* lost_active) {
if (gained_active == lost_active)
return;
UpdateWindowProperties(gained_active);
// Transitioning with ARC and non-ARC window may need to dispatch
// ToggleNativeChromeVoxArcSupport event.
// - When non-ChromeVox ARC window becomes inactive, dispatch |true|.
// - When non-ChromeVox ARC window becomes active, dispatch |false|.
bool lost_arc = arc::IsArcAppWindow(lost_active);
bool gained_arc = arc::IsArcAppWindow(gained_active);
bool talkback_enabled = !native_chromevox_enabled_;
if (talkback_enabled && lost_arc != gained_arc)
DispatchCustomSpokenFeedbackToggled(gained_arc);
}
void ArcAccessibilityHelperBridge::InvokeUpdateEnabledFeatureForTesting() {
UpdateEnabledFeature();
}
......@@ -577,16 +593,6 @@ void ArcAccessibilityHelperBridge::UpdateCaptionSettings() const {
instance->SetCaptionStyle(std::move(caption_style));
}
void ArcAccessibilityHelperBridge::OnWindowActivated(
ActivationReason reason,
aura::Window* gained_active,
aura::Window* lost_active) {
if (gained_active == lost_active)
return;
UpdateWindowProperties(gained_active);
}
void ArcAccessibilityHelperBridge::OnActionResult(const ui::AXActionData& data,
bool result) const {
AXTreeSourceArc* tree_source = GetFromTreeId(data.target_tree_id);
......@@ -691,15 +697,12 @@ void ArcAccessibilityHelperBridge::UpdateEnabledFeature() {
void ArcAccessibilityHelperBridge::UpdateWindowProperties(
aura::Window* window) {
if (!window)
return;
if (!GetArcSurface(window))
if (!arc::IsArcAppWindow(window))
return;
// First, do a lookup for the task id associated with this app. There should
// always be a valid entry.
int32_t task_id = arc::GetWindowTaskId(window);
if (task_id == kNoTaskId)
return;
// Do a lookup for the tree source. A tree source may not exist because the
// app isn't whitelisted Android side or no data has been received for the
......@@ -870,6 +873,20 @@ void ArcAccessibilityHelperBridge::HandleFilterTypeAllEvent(
}
}
void ArcAccessibilityHelperBridge::DispatchCustomSpokenFeedbackToggled(
bool enabled) const {
std::unique_ptr<base::ListValue> event_args(
extensions::api::accessibility_private::OnCustomSpokenFeedbackToggled::
Create(enabled));
std::unique_ptr<extensions::Event> event(new extensions::Event(
extensions::events::
ACCESSIBILITY_PRIVATE_ON_CUSTOM_SPOKEN_FEEDBACK_TOGGLED,
extensions::api::accessibility_private::OnCustomSpokenFeedbackToggled::
kEventName,
std::move(event_args)));
GetEventRouter()->BroadcastEvent(std::move(event));
}
AXTreeSourceArc* ArcAccessibilityHelperBridge::CreateFromKey(TreeKey key) {
auto tree = std::make_unique<AXTreeSourceArc>(this);
auto* tree_ptr = tree.get();
......
......@@ -91,6 +91,7 @@ class ArcAccessibilityHelperBridge
void OnNotificationStateChanged(
const std::string& notification_key,
mojom::AccessibilityNotificationStateType state) override;
void OnToggleNativeChromeVoxArcSupport(bool enabled) override;
// AXTreeSourceArc::Delegate overrides.
void OnAction(const ui::AXActionData& data) const override;
......@@ -107,6 +108,11 @@ class ArcAccessibilityHelperBridge
void OnNotificationSurfaceRemoved(
ash::ArcNotificationSurface* surface) override {}
// wm::ActivationChangeObserver overrides.
void OnWindowActivated(ActivationReason reason,
aura::Window* gained_active,
aura::Window* lost_active) override;
void InvokeUpdateEnabledFeatureForTesting();
enum class TreeKeyType {
......@@ -132,11 +138,6 @@ class ArcAccessibilityHelperBridge
std::unique_ptr<PrefChangeRegistrar> pref_change_registrar_;
void UpdateCaptionSettings() const;
// wm::ActivationChangeObserver overrides.
void OnWindowActivated(ActivationReason reason,
aura::Window* gained_active,
aura::Window* lost_active) override;
void OnActionResult(const ui::AXActionData& data, bool result) const;
void OnGetTextLocationDataResult(
const ui::AXActionData& data,
......@@ -155,6 +156,8 @@ class ArcAccessibilityHelperBridge
void HandleFilterTypeFocusEvent(mojom::AccessibilityEventDataPtr event_data);
void HandleFilterTypeAllEvent(mojom::AccessibilityEventDataPtr event_data);
void DispatchCustomSpokenFeedbackToggled(bool enabled) const;
AXTreeSourceArc* CreateFromKey(TreeKey);
AXTreeSourceArc* GetFromKey(const TreeKey&);
AXTreeSourceArc* GetFromTreeId(ui::AXTreeID tree_id) const;
......@@ -174,6 +177,8 @@ class ArcAccessibilityHelperBridge
// Set of task id where TalkBack is enabled. ChromeOS native accessibility
// support should be disabled for these tasks.
std::set<int32_t> talkback_enabled_task_ids_;
// True if native ChromeVox support is enabled.
bool native_chromevox_enabled_ = true;
DISALLOW_COPY_AND_ASSIGN(ArcAccessibilityHelperBridge);
};
......
......@@ -8,6 +8,7 @@
#include <unordered_map>
#include <utility>
#include "ash/public/cpp/app_types.h"
#include "ash/system/message_center/arc/arc_notification_constants.h"
#include "ash/system/message_center/arc/arc_notification_content_view.h"
#include "ash/system/message_center/arc/arc_notification_surface.h"
......@@ -17,6 +18,7 @@
#include "ash/system/message_center/arc/mock_arc_notification_surface.h"
#include "base/command_line.h"
#include "base/observer_list.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/common/extensions/api/accessibility_private.h"
......@@ -31,14 +33,18 @@
#include "components/language/core/browser/pref_names.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/test_event_router.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/window.h"
#include "ui/display/display.h"
#include "ui/display/manager/managed_display_info.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/public/activation_change_observer.h"
#include "ui/wm/public/wm_public_export.h"
using ash::ArcNotificationItem;
using ash::ArcNotificationSurface;
......@@ -57,7 +63,9 @@ constexpr char kNotificationKey[] = "unit.test.notification";
class ArcAccessibilityHelperBridgeTest : public ChromeViewsTestBase {
public:
class TestArcAccessibilityHelperBridge : public ArcAccessibilityHelperBridge {
class TestArcAccessibilityHelperBridge
: public ArcAccessibilityHelperBridge,
public extensions::TestEventRouter::EventObserver {
public:
TestArcAccessibilityHelperBridge(content::BrowserContext* browser_context,
ArcBridgeService* arc_bridge_service)
......@@ -66,6 +74,9 @@ class ArcAccessibilityHelperBridgeTest : public ChromeViewsTestBase {
event_router_(
extensions::CreateAndUseTestEventRouter(browser_context)) {
window_->Init(ui::LAYER_NOT_DRAWN);
window_->SetProperty(aura::client::kAppType,
static_cast<int>(ash::AppType::ARC_APP));
event_router_->AddEventObserver(this);
}
~TestArcAccessibilityHelperBridge() override { window_.reset(); }
......@@ -87,13 +98,23 @@ class ArcAccessibilityHelperBridgeTest : public ChromeViewsTestBase {
filter_type_for_test_ = filter_type;
}
// TestEventRouter::EventObserver
void OnBroadcastEvent(const extensions::Event& event) override {
last_event = event.DeepCopy();
}
void OnDispatchEventToExtension(const std::string& extension_id,
const extensions::Event& event) override {}
std::unique_ptr<aura::Window> window_;
std::unique_ptr<extensions::Event> last_event;
private:
aura::Window* GetActiveWindow() override { return window_.get(); }
extensions::EventRouter* GetEventRouter() const override {
return event_router_;
}
std::unique_ptr<aura::Window> window_;
extensions::TestEventRouter* const event_router_;
arc::mojom::AccessibilityFilterType filter_type_for_test_ =
arc::mojom::AccessibilityFilterType::ALL;
......@@ -302,9 +323,13 @@ TEST_F(ArcAccessibilityHelperBridgeTest, TaskAndAXTreeLifecycle) {
}
TEST_F(ArcAccessibilityHelperBridgeTest, EventAnnouncement) {
const char* const event_name = extensions::api::accessibility_private::
OnAnnounceForAccessibility::kEventName;
TestArcAccessibilityHelperBridge* helper_bridge =
accessibility_helper_bridge();
std::vector<std::string> text({"Str"});
const std::string announce_text = "announcement text.";
std::vector<std::string> text({announce_text});
auto event = arc::mojom::AccessibilityEventData::New();
event->event_type = arc::mojom::AccessibilityEventType::ANNOUNCEMENT;
event->eventText =
......@@ -312,9 +337,67 @@ TEST_F(ArcAccessibilityHelperBridgeTest, EventAnnouncement) {
helper_bridge->OnAccessibilityEvent(event.Clone());
ASSERT_EQ(1, helper_bridge->GetEventCount(
extensions::api::accessibility_private::
OnAnnounceForAccessibility::kEventName));
ASSERT_EQ(1, helper_bridge->GetEventCount(event_name));
ASSERT_EQ(event_name, helper_bridge->last_event->event_name);
base::Value::ConstListView arg =
helper_bridge->last_event->event_args->GetList()[0].GetList();
ASSERT_EQ(1U, arg.size());
ASSERT_EQ(announce_text, arg[0].GetString());
}
TEST_F(ArcAccessibilityHelperBridgeTest, ToggleTalkBack) {
const char* const event_name = extensions::api::accessibility_private::
OnCustomSpokenFeedbackToggled::kEventName;
TestArcAccessibilityHelperBridge* helper_bridge =
accessibility_helper_bridge();
helper_bridge->SetActiveWindowId("org.chromium.arc.1");
ASSERT_EQ(0, helper_bridge->GetEventCount(event_name));
// Enable TalkBack.
std::unique_ptr<aura::WindowTracker> window_tracker =
std::make_unique<aura::WindowTracker>();
window_tracker->Add(helper_bridge->window_.get());
helper_bridge->OnSetNativeChromeVoxArcSupportProcessed(
std::move(window_tracker), false, true);
helper_bridge->OnToggleNativeChromeVoxArcSupport(false);
ASSERT_EQ(1, helper_bridge->GetEventCount(event_name));
ASSERT_EQ(event_name, helper_bridge->last_event->event_name);
ASSERT_TRUE(helper_bridge->last_event->event_args->GetList()[0].GetBool());
std::unique_ptr<aura::Window> non_arc_window =
std::make_unique<aura::Window>(nullptr);
non_arc_window->Init(ui::LAYER_NOT_DRAWN);
// Switch to non-ARC window.
helper_bridge->OnWindowActivated(
wm::ActivationChangeObserver::ActivationReason::INPUT_EVENT,
non_arc_window.get(), helper_bridge->window_.get());
ASSERT_EQ(2, helper_bridge->GetEventCount(event_name));
ASSERT_EQ(event_name, helper_bridge->last_event->event_name);
ASSERT_FALSE(helper_bridge->last_event->event_args->GetList()[0].GetBool());
// Switch back to ARC.
helper_bridge->OnWindowActivated(
wm::ActivationChangeObserver::ActivationReason::INPUT_EVENT,
helper_bridge->window_.get(), non_arc_window.get());
ASSERT_EQ(3, helper_bridge->GetEventCount(event_name));
ASSERT_EQ(event_name, helper_bridge->last_event->event_name);
ASSERT_TRUE(helper_bridge->last_event->event_args->GetList()[0].GetBool());
// Disable TalkBack.
window_tracker.reset(new aura::WindowTracker());
window_tracker->Add(helper_bridge->window_.get());
helper_bridge->OnSetNativeChromeVoxArcSupportProcessed(
std::move(window_tracker), true, true);
helper_bridge->OnToggleNativeChromeVoxArcSupport(true);
ASSERT_EQ(4, helper_bridge->GetEventCount(event_name));
ASSERT_EQ(event_name, helper_bridge->last_event->event_name);
ASSERT_FALSE(helper_bridge->last_event->event_args->GetList()[0].GetBool());
}
// Accessibility event and surface creation/removal are sent in different
......
......@@ -435,14 +435,14 @@
{
"name": "onSelectToSpeakStateChangeRequested",
"type": "function",
"description": "Called when Chrome OS wants to change the Select-to-Speak state, between selecting with the mouse, speaking, and inactive.",
"description": "Fired when Chrome OS wants to change the Select-to-Speak state, between selecting with the mouse, speaking, and inactive.",
"parameters": [],
"platforms": ["chromeos"]
},
{
"name": "onSwitchAccessCommand",
"type": "function",
"description": "Called when Chrome OS has received a key event corresponding to a Switch Access command.",
"description": "Fired when Chrome OS has received a key event corresponding to a Switch Access command.",
"parameters": [
{
"name": "command",
......@@ -454,7 +454,7 @@
{
"name": "onAnnounceForAccessibility",
"type": "function",
"description": "Called when an internal component within accessibility wants to force speech output for an accessibility extension. Do not use without approval from accessibility owners.",
"description": "Fired when an internal component within accessibility wants to force speech output for an accessibility extension. Do not use without approval from accessibility owners.",
"parameters": [
{
"name": "announceText",
......@@ -468,7 +468,7 @@
{
"name": "findScrollableBoundsForPoint",
"type": "function",
"description": "Called when an internal component within accessibility wants to find the nearest scrolling container at a given screen coordinate. Used in Automatic Clicks.",
"description": "Fired when an internal component within accessibility wants to find the nearest scrolling container at a given screen coordinate. Used in Automatic Clicks.",
"parameters": [
{
"name": "x",
......@@ -482,6 +482,19 @@
}
],
"platforms": ["chromeos"]
},
{
"name": "onCustomSpokenFeedbackToggled",
"type": "function",
"description": "Fired when a custom spoken feedback on the active window gets enabled or disabled. Called from ARC++ accessibility.",
"parameters": [
{
"name": "enabled",
"type": "boolean",
"description": "True if the active window implements custom spoken feedback features."
}
],
"platforms": ["chromeos"]
}
]
}
......
......@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Next MinVersion: 18
// Next MinVersion: 19
module arc.mojom;
......@@ -412,7 +412,7 @@ union AccessibilityWindowKey {
// Interface for Android communicating to Chrome.
// Deprecated method IDs: 0
// Next method ID: 3
// Next method ID: 4
interface AccessibilityHelperHost {
// OnAccessibilityEvent is called when a converted Android accessibility event
// is sent from Android.
......@@ -422,6 +422,10 @@ interface AccessibilityHelperHost {
// changed, e.g. surface for a notification is created.
[MinVersion=10] OnNotificationStateChanged@2(
string notification_key, AccessibilityNotificationStateType state);
// OnToggleNativeChromeVoxArcSupport is called when state of ChromeVox native
// ARC support gets changed.
[MinVersion=18] OnToggleNativeChromeVoxArcSupport@3(bool enabled);
};
// Interface for communicating to Android.
......
......@@ -471,6 +471,7 @@ enum HistogramValue {
AUTOFILL_ASSISTANT_PRIVATE_ON_STATUS_MESSAGE_CHANGED = 449,
BLUETOOTH_PRIVATE_ON_DEVICE_ADDRESS_CHANGED = 450,
PASSWORDS_PRIVATE_ON_ACCOUNT_STORAGE_OPT_IN_STATE_CHANGED = 451,
ACCESSIBILITY_PRIVATE_ON_CUSTOM_SPOKEN_FEEDBACK_TOGGLED = 452,
// Last entry: Add new entries above, then run:
// python tools/metrics/histograms/update_extension_histograms.py
ENUM_BOUNDARY
......
......@@ -312,21 +312,21 @@ chrome.accessibilityPrivate.onTwoFingerTouchStart;
chrome.accessibilityPrivate.onTwoFingerTouchStop;
/**
* Called when Chrome OS wants to change the Select-to-Speak state, between
* Fired when Chrome OS wants to change the Select-to-Speak state, between
* selecting with the mouse, speaking, and inactive.
* @type {!ChromeEvent}
*/
chrome.accessibilityPrivate.onSelectToSpeakStateChangeRequested;
/**
* Called when Chrome OS has received a key event corresponding to a Switch
* Fired when Chrome OS has received a key event corresponding to a Switch
* Access command.
* @type {!ChromeEvent}
*/
chrome.accessibilityPrivate.onSwitchAccessCommand;
/**
* Called when an internal component within accessibility wants to force speech
* Fired when an internal component within accessibility wants to force speech
* output for an accessibility extension. Do not use without approval from
* accessibility owners.
* @type {!ChromeEvent}
......@@ -334,9 +334,16 @@ chrome.accessibilityPrivate.onSwitchAccessCommand;
chrome.accessibilityPrivate.onAnnounceForAccessibility;
/**
* Called when an internal component within accessibility wants to find the
* Fired when an internal component within accessibility wants to find the
* nearest scrolling container at a given screen coordinate. Used in Automatic
* Clicks.
* @type {!ChromeEvent}
*/
chrome.accessibilityPrivate.findScrollableBoundsForPoint;
/**
* Fired when a custom spoken feedback on the active window gets enabled or
* disabled. Called from ARC++ accessibility.
* @type {!ChromeEvent}
*/
chrome.accessibilityPrivate.onCustomSpokenFeedbackToggled;
......@@ -20768,6 +20768,8 @@ to ensure that the crash string is shown properly on the user-facing crash UI.
<int value="450" label="BLUETOOTH_PRIVATE_ON_DEVICE_ADDRESS_CHANGED"/>
<int value="451"
label="PASSWORDS_PRIVATE_ON_ACCOUNT_STORAGE_OPT_IN_STATE_CHANGED"/>
<int value="452"
label="ACCESSIBILITY_PRIVATE_ON_CUSTOM_SPOKEN_FEEDBACK_TOGGLED"/>
</enum>
<enum name="ExtensionFileWriteResult">
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