Skip to content

Commit

Permalink
[Linux][A11y] report disabled animations and high contrast accessibil…
Browse files Browse the repository at this point in the history
…ity features (flutter#33313)

* FlEngine: add fl_engine_update_accessibility_features()
* FlSettings: add API for high-contrast & animations
* FlEngine: allow passing mock messenger at construction time
* FlSettingsPlugin: report accessibility features
  • Loading branch information
jpnurmi authored Jun 26, 2022
1 parent ee56813 commit 00b7292
Show file tree
Hide file tree
Showing 14 changed files with 294 additions and 15 deletions.
42 changes: 40 additions & 2 deletions shell/platform/linux/fl_engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ G_DEFINE_TYPE_WITH_CODE(
G_IMPLEMENT_INTERFACE(fl_plugin_registry_get_type(),
fl_engine_plugin_registry_iface_init))

enum { PROP_0, PROP_BINARY_MESSENGER, PROP_LAST };

// Parse a locale into its components.
static void parse_locale(const gchar* locale,
gchar** language,
Expand Down Expand Up @@ -351,6 +353,22 @@ static void fl_engine_plugin_registry_iface_init(
iface->get_registrar_for_plugin = fl_engine_get_registrar_for_plugin;
}

static void fl_engine_set_property(GObject* object,
guint prop_id,
const GValue* value,
GParamSpec* pspec) {
FlEngine* self = FL_ENGINE(object);
switch (prop_id) {
case PROP_BINARY_MESSENGER:
g_set_object(&self->binary_messenger,
FL_BINARY_MESSENGER(g_value_get_object(value)));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}

static void fl_engine_dispose(GObject* object) {
FlEngine* self = FL_ENGINE(object);

Expand Down Expand Up @@ -397,6 +415,15 @@ static void fl_engine_dispose(GObject* object) {

static void fl_engine_class_init(FlEngineClass* klass) {
G_OBJECT_CLASS(klass)->dispose = fl_engine_dispose;
G_OBJECT_CLASS(klass)->set_property = fl_engine_set_property;

g_object_class_install_property(
G_OBJECT_CLASS(klass), PROP_BINARY_MESSENGER,
g_param_spec_object(
"binary-messenger", "messenger", "Binary messenger",
fl_binary_messenger_get_type(),
static_cast<GParamFlags>(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS)));
}

static void fl_engine_init(FlEngine* self) {
Expand All @@ -406,7 +433,6 @@ static void fl_engine_init(FlEngine* self) {
FlutterEngineGetProcAddresses(&self->embedder_api);

self->texture_registrar = fl_texture_registrar_new(self);
self->binary_messenger = fl_binary_messenger_new(self);
}

FlEngine* fl_engine_new(FlDartProject* project, FlRenderer* renderer) {
Expand All @@ -416,6 +442,7 @@ FlEngine* fl_engine_new(FlDartProject* project, FlRenderer* renderer) {
FlEngine* self = FL_ENGINE(g_object_new(fl_engine_get_type(), nullptr));
self->project = FL_DART_PROJECT(g_object_ref(project));
self->renderer = FL_RENDERER(g_object_ref(renderer));
self->binary_messenger = fl_binary_messenger_new(self);
return self;
}

Expand Down Expand Up @@ -522,7 +549,7 @@ gboolean fl_engine_start(FlEngine* self, GError** error) {
setup_locales(self);

g_autoptr(FlSettings) settings = fl_settings_new();
self->settings_plugin = fl_settings_plugin_new(self->binary_messenger);
self->settings_plugin = fl_settings_plugin_new(self);
fl_settings_plugin_start(self->settings_plugin, settings);

result = self->embedder_api.UpdateSemanticsEnabled(self->engine, TRUE);
Expand Down Expand Up @@ -844,3 +871,14 @@ G_MODULE_EXPORT FlTextureRegistrar* fl_engine_get_texture_registrar(
g_return_val_if_fail(FL_IS_ENGINE(self), nullptr);
return self->texture_registrar;
}

void fl_engine_update_accessibility_features(FlEngine* self, int32_t flags) {
g_return_if_fail(FL_IS_ENGINE(self));

if (self->engine == nullptr) {
return;
}

self->embedder_api.UpdateAccessibilityFeatures(
self->engine, static_cast<FlutterAccessibilityFeature>(flags));
}
9 changes: 9 additions & 0 deletions shell/platform/linux/fl_engine_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,15 @@ gboolean fl_engine_register_external_texture(FlEngine* engine,
gboolean fl_engine_unregister_external_texture(FlEngine* engine,
int64_t texture_id);

/**
* fl_engine_update_accessibility_features:
* @engine: an #FlEngine.
* @flags: the features to enable in the accessibility tree.
*
* Tells the Flutter engine to update the flags on the accessibility tree.
*/
void fl_engine_update_accessibility_features(FlEngine* engine, int32_t flags);

G_END_DECLS

#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_ENGINE_PRIVATE_H_
10 changes: 10 additions & 0 deletions shell/platform/linux/fl_gnome_settings.cc
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ static FlColorScheme fl_gnome_settings_get_color_scheme(FlSettings* settings) {
return color_scheme;
}

static gboolean fl_gnome_settings_get_enable_animations(FlSettings* settings) {
return true;
}

static gboolean fl_gnome_settings_get_high_contrast(FlSettings* settings) {
return false;
}

static gdouble fl_gnome_settings_get_text_scaling_factor(FlSettings* settings) {
FlGnomeSettings* self = FL_GNOME_SETTINGS(settings);

Expand Down Expand Up @@ -133,6 +141,8 @@ static void fl_gnome_settings_class_init(FlGnomeSettingsClass* klass) {
static void fl_gnome_settings_iface_init(FlSettingsInterface* iface) {
iface->get_clock_format = fl_gnome_settings_get_clock_format;
iface->get_color_scheme = fl_gnome_settings_get_color_scheme;
iface->get_enable_animations = fl_gnome_settings_get_enable_animations;
iface->get_high_contrast = fl_gnome_settings_get_high_contrast;
iface->get_text_scaling_factor = fl_gnome_settings_get_text_scaling_factor;
}

Expand Down
10 changes: 10 additions & 0 deletions shell/platform/linux/fl_gnome_settings_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,16 @@ TEST_F(FlGnomeSettingsTest, GtkTheme) {
EXPECT_EQ(fl_settings_get_color_scheme(settings), FL_COLOR_SCHEME_DARK);
}

TEST_F(FlGnomeSettingsTest, EnableAnimations) {
g_autoptr(FlSettings) settings = fl_gnome_settings_new();
EXPECT_TRUE(fl_settings_get_enable_animations(settings));
}

TEST_F(FlGnomeSettingsTest, HighContrast) {
g_autoptr(FlSettings) settings = fl_gnome_settings_new();
EXPECT_FALSE(fl_settings_get_high_contrast(settings));
}

TEST_F(FlGnomeSettingsTest, TextScalingFactor) {
g_autoptr(GSettings) interface_settings =
create_settings("ubuntu-20.04", "org.gnome.desktop.interface");
Expand Down
8 changes: 8 additions & 0 deletions shell/platform/linux/fl_settings.cc
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ FlColorScheme fl_settings_get_color_scheme(FlSettings* self) {
return FL_SETTINGS_GET_IFACE(self)->get_color_scheme(self);
}

gboolean fl_settings_get_enable_animations(FlSettings* self) {
return FL_SETTINGS_GET_IFACE(self)->get_enable_animations(self);
}

gboolean fl_settings_get_high_contrast(FlSettings* self) {
return FL_SETTINGS_GET_IFACE(self)->get_high_contrast(self);
}

gdouble fl_settings_get_text_scaling_factor(FlSettings* self) {
return FL_SETTINGS_GET_IFACE(self)->get_text_scaling_factor(self);
}
Expand Down
27 changes: 27 additions & 0 deletions shell/platform/linux/fl_settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ struct _FlSettingsInterface {
GTypeInterface parent;
FlClockFormat (*get_clock_format)(FlSettings* settings);
FlColorScheme (*get_color_scheme)(FlSettings* settings);
gboolean (*get_enable_animations)(FlSettings* settings);
gboolean (*get_high_contrast)(FlSettings* settings);
gdouble (*get_text_scaling_factor)(FlSettings* settings);
};

Expand Down Expand Up @@ -79,6 +81,31 @@ FlClockFormat fl_settings_get_clock_format(FlSettings* settings);
*/
FlColorScheme fl_settings_get_color_scheme(FlSettings* settings);

/**
* fl_settings_get_enable_animations:
* @settings: an #FlSettings.
*
* Whether animations should be enabled.
*
* This corresponds to `org.gnome.desktop.interface.enable-animations` in GNOME.
*
* Returns: %TRUE if animations are enabled.
*/
gboolean fl_settings_get_enable_animations(FlSettings* settings);

/**
* fl_settings_get_high_contrast:
* @settings: an #FlSettings.
*
* Whether to use high contrast theme.
*
* This corresponds to `org.gnome.desktop.a11y.interface.high-contrast` in
* GNOME.
*
* Returns: %TRUE if high contrast is used.
*/
gboolean fl_settings_get_high_contrast(FlSettings* settings);

/**
* fl_settings_get_text_scaling_factor:
* @settings: an #FlSettings.
Expand Down
31 changes: 28 additions & 3 deletions shell/platform/linux/fl_settings_plugin.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@

#include <gmodule.h>

#include "flutter/shell/platform/embedder/embedder.h"
#include "flutter/shell/platform/linux/fl_engine_private.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_basic_message_channel.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h"

static constexpr char kChannelName[] = "flutter/settings";
Expand All @@ -20,7 +23,7 @@ struct _FlSettingsPlugin {
GObject parent_instance;

FlBasicMessageChannel* channel;

FlEngine* engine;
FlSettings* settings;
};

Expand Down Expand Up @@ -54,6 +57,17 @@ static void update_settings(FlSettingsPlugin* self) {
fl_value_new_string(to_platform_brightness(color_scheme)));
fl_basic_message_channel_send(self->channel, message, nullptr, nullptr,
nullptr);

if (self->engine != nullptr) {
int32_t flags = 0;
if (!fl_settings_get_enable_animations(self->settings)) {
flags |= kFlutterAccessibilityFeatureDisableAnimations;
}
if (fl_settings_get_high_contrast(self->settings)) {
flags |= kFlutterAccessibilityFeatureHighContrast;
}
fl_engine_update_accessibility_features(self->engine, flags);
}
}

static void fl_settings_plugin_dispose(GObject* object) {
Expand All @@ -62,6 +76,12 @@ static void fl_settings_plugin_dispose(GObject* object) {
g_clear_object(&self->channel);
g_clear_object(&self->settings);

if (self->engine != nullptr) {
g_object_remove_weak_pointer(G_OBJECT(self),
reinterpret_cast<gpointer*>(&(self->engine)));
self->engine = nullptr;
}

G_OBJECT_CLASS(fl_settings_plugin_parent_class)->dispose(object);
}

Expand All @@ -71,12 +91,17 @@ static void fl_settings_plugin_class_init(FlSettingsPluginClass* klass) {

static void fl_settings_plugin_init(FlSettingsPlugin* self) {}

FlSettingsPlugin* fl_settings_plugin_new(FlBinaryMessenger* messenger) {
g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr);
FlSettingsPlugin* fl_settings_plugin_new(FlEngine* engine) {
g_return_val_if_fail(FL_IS_ENGINE(engine), nullptr);

FlSettingsPlugin* self =
FL_SETTINGS_PLUGIN(g_object_new(fl_settings_plugin_get_type(), nullptr));

self->engine = engine;
g_object_add_weak_pointer(G_OBJECT(self),
reinterpret_cast<gpointer*>(&(self->engine)));

FlBinaryMessenger* messenger = fl_engine_get_binary_messenger(engine);
g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new();
self->channel = fl_basic_message_channel_new(messenger, kChannelName,
FL_MESSAGE_CODEC(codec));
Expand Down
6 changes: 3 additions & 3 deletions shell/platform/linux/fl_settings_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_SETTINGS_PLUGIN_H_

#include "flutter/shell/platform/linux/fl_settings.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_engine.h"

G_BEGIN_DECLS

Expand All @@ -25,13 +25,13 @@ G_DECLARE_FINAL_TYPE(FlSettingsPlugin,

/**
* fl_settings_plugin_new:
* @messenger: an #FlBinaryMessenger
* @messenger: an #FlEngine
*
* Creates a new plugin that sends user settings to the Flutter engine.
*
* Returns: a new #FlSettingsPlugin
*/
FlSettingsPlugin* fl_settings_plugin_new(FlBinaryMessenger* messenger);
FlSettingsPlugin* fl_settings_plugin_new(FlEngine* engine);

/**
* fl_settings_plugin_start:
Expand Down
69 changes: 66 additions & 3 deletions shell/platform/linux/fl_settings_plugin_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
// found in the LICENSE file.

#include "flutter/shell/platform/linux/fl_settings_plugin.h"
#include "flutter/shell/platform/embedder/embedder.h"
#include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h"
#include "flutter/shell/platform/linux/fl_engine_private.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_value.h"
Expand Down Expand Up @@ -38,7 +41,9 @@ TEST(FlSettingsPluginTest, AlwaysUse24HourFormat) {
::testing::NiceMock<flutter::testing::MockSettings> settings;
::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;

g_autoptr(FlSettingsPlugin) plugin = fl_settings_plugin_new(messenger);
g_autoptr(FlEngine) engine = FL_ENGINE(g_object_new(
fl_engine_get_type(), "binary-messenger", messenger, nullptr));
g_autoptr(FlSettingsPlugin) plugin = fl_settings_plugin_new(engine);

g_autoptr(FlValue) use_12h = fl_value_new_bool(false);
g_autoptr(FlValue) use_24h = fl_value_new_bool(true);
Expand All @@ -61,7 +66,9 @@ TEST(FlSettingsPluginTest, PlatformBrightness) {
::testing::NiceMock<flutter::testing::MockSettings> settings;
::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;

g_autoptr(FlSettingsPlugin) plugin = fl_settings_plugin_new(messenger);
g_autoptr(FlEngine) engine = FL_ENGINE(g_object_new(
fl_engine_get_type(), "binary-messenger", messenger, nullptr));
g_autoptr(FlSettingsPlugin) plugin = fl_settings_plugin_new(engine);

g_autoptr(FlValue) light = fl_value_new_string("light");
g_autoptr(FlValue) dark = fl_value_new_string("dark");
Expand All @@ -84,7 +91,9 @@ TEST(FlSettingsPluginTest, TextScaleFactor) {
::testing::NiceMock<flutter::testing::MockSettings> settings;
::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;

g_autoptr(FlSettingsPlugin) plugin = fl_settings_plugin_new(messenger);
g_autoptr(FlEngine) engine = FL_ENGINE(g_object_new(
fl_engine_get_type(), "binary-messenger", messenger, nullptr));
g_autoptr(FlSettingsPlugin) plugin = fl_settings_plugin_new(engine);

g_autoptr(FlValue) one = fl_value_new_float(1.0);
g_autoptr(FlValue) two = fl_value_new_float(2.0);
Expand All @@ -102,3 +111,57 @@ TEST(FlSettingsPluginTest, TextScaleFactor) {

fl_settings_emit_changed(settings);
}

// MOCK_ENGINE_PROC is leaky by design
// NOLINTBEGIN(clang-analyzer-core.StackAddressEscape)
TEST(FlSettingsPluginTest, AccessibilityFeatures) {
g_autoptr(FlEngine) engine = make_mock_engine();
FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine);

std::vector<FlutterAccessibilityFeature> calls;
embedder_api->UpdateAccessibilityFeatures = MOCK_ENGINE_PROC(
UpdateAccessibilityFeatures,
([&calls](auto engine, FlutterAccessibilityFeature features) {
calls.push_back(features);
return kSuccess;
}));

g_autoptr(FlSettingsPlugin) plugin = fl_settings_plugin_new(engine);

::testing::NiceMock<flutter::testing::MockSettings> settings;

EXPECT_CALL(settings, fl_settings_get_enable_animations(
::testing::Eq<FlSettings*>(settings)))
.WillOnce(::testing::Return(false))
.WillOnce(::testing::Return(true))
.WillOnce(::testing::Return(false))
.WillOnce(::testing::Return(true));

EXPECT_CALL(settings, fl_settings_get_high_contrast(
::testing::Eq<FlSettings*>(settings)))
.WillOnce(::testing::Return(true))
.WillOnce(::testing::Return(false))
.WillOnce(::testing::Return(false))
.WillOnce(::testing::Return(true));

fl_settings_plugin_start(plugin, settings);
EXPECT_THAT(calls, ::testing::SizeIs(1));
EXPECT_EQ(calls.back(), static_cast<FlutterAccessibilityFeature>(
kFlutterAccessibilityFeatureDisableAnimations |
kFlutterAccessibilityFeatureHighContrast));

fl_settings_emit_changed(settings);
EXPECT_THAT(calls, ::testing::SizeIs(2));
EXPECT_EQ(calls.back(), static_cast<FlutterAccessibilityFeature>(0));

fl_settings_emit_changed(settings);
EXPECT_THAT(calls, ::testing::SizeIs(3));
EXPECT_EQ(calls.back(), static_cast<FlutterAccessibilityFeature>(
kFlutterAccessibilityFeatureDisableAnimations));

fl_settings_emit_changed(settings);
EXPECT_THAT(calls, ::testing::SizeIs(4));
EXPECT_EQ(calls.back(), static_cast<FlutterAccessibilityFeature>(
kFlutterAccessibilityFeatureHighContrast));
}
// NOLINTEND(clang-analyzer-core.StackAddressEscape)
Loading

0 comments on commit 00b7292

Please sign in to comment.