From 8c42b89cdff3ef051b8a984a6b66eef975a1c7ae Mon Sep 17 00:00:00 2001 From: Rutger Vromans Date: Mon, 7 Mar 2022 23:51:10 +0100 Subject: [PATCH] Access on/off labels accessibility setting for switches on iOS (flutter/engine#30764) --- engine/src/flutter/lib/ui/window.dart | 8 +++ .../lib/ui/window/platform_configuration.h | 1 + engine/src/flutter/lib/web_ui/lib/window.dart | 5 ++ .../framework/Source/FlutterViewController.mm | 50 ++++++++++++++----- .../Source/FlutterViewControllerTest.mm | 47 +++++++++++++++-- .../Source/FlutterViewController_Internal.h | 2 + 6 files changed, 95 insertions(+), 18 deletions(-) diff --git a/engine/src/flutter/lib/ui/window.dart b/engine/src/flutter/lib/ui/window.dart index 7fd11a9be7..54d3c606ce 100644 --- a/engine/src/flutter/lib/ui/window.dart +++ b/engine/src/flutter/lib/ui/window.dart @@ -771,6 +771,7 @@ class AccessibilityFeatures { static const int _kBoldTextIndex = 1 << 3; static const int _kReduceMotionIndex = 1 << 4; static const int _kHighContrastIndex = 1 << 5; + static const int _kOnOffSwitchLabelsIndex = 1 << 6; // A bitfield which represents each enabled feature. final int _index; @@ -803,6 +804,11 @@ class AccessibilityFeatures { /// Only supported on iOS. bool get highContrast => _kHighContrastIndex & _index != 0; + /// The platform is requesting to show on/off labels inside switches. + /// + /// Only supported on iOS. + bool get onOffSwitchLabels => _kOnOffSwitchLabelsIndex & _index != 0; + @override String toString() { final List features = []; @@ -818,6 +824,8 @@ class AccessibilityFeatures { features.add('reduceMotion'); if (highContrast) features.add('highContrast'); + if (onOffSwitchLabels) + features.add('onOffSwitchLabels'); return 'AccessibilityFeatures$features'; } diff --git a/engine/src/flutter/lib/ui/window/platform_configuration.h b/engine/src/flutter/lib/ui/window/platform_configuration.h index d42f8639a9..d112b2dcc0 100644 --- a/engine/src/flutter/lib/ui/window/platform_configuration.h +++ b/engine/src/flutter/lib/ui/window/platform_configuration.h @@ -35,6 +35,7 @@ enum class AccessibilityFeatureFlag : int32_t { kBoldText = 1 << 3, kReduceMotion = 1 << 4, kHighContrast = 1 << 5, + kOnOffSwitchLabels = 1 << 6, }; //-------------------------------------------------------------------------- diff --git a/engine/src/flutter/lib/web_ui/lib/window.dart b/engine/src/flutter/lib/web_ui/lib/window.dart index b60ab11807..a5dc10f17f 100644 --- a/engine/src/flutter/lib/web_ui/lib/window.dart +++ b/engine/src/flutter/lib/web_ui/lib/window.dart @@ -148,6 +148,7 @@ class AccessibilityFeatures { static const int _kBoldTextIndex = 1 << 3; static const int _kReduceMotionIndex = 1 << 4; static const int _kHighContrastIndex = 1 << 5; + static const int _kOnOffSwitchLabelsIndex = 1 << 6; // A bitfield which represents each enabled feature. final int _index; @@ -158,6 +159,7 @@ class AccessibilityFeatures { bool get boldText => _kBoldTextIndex & _index != 0; bool get reduceMotion => _kReduceMotionIndex & _index != 0; bool get highContrast => _kHighContrastIndex & _index != 0; + bool get onOffSwitchLabels => _kOnOffSwitchLabelsIndex & _index != 0; @override String toString() { @@ -180,6 +182,9 @@ class AccessibilityFeatures { if (highContrast) { features.add('highContrast'); } + if (onOffSwitchLabels) { + features.add('onOffSwitchLabels'); + } return 'AccessibilityFeatures$features'; } diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 08f44786fb..c53a8778d7 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -331,6 +331,13 @@ typedef enum UIAccessibilityContrast : NSInteger { name:UIAccessibilityDarkerSystemColorsStatusDidChangeNotification object:nil]; + if (@available(iOS 13.0, *)) { + [center addObserver:self + selector:@selector(onAccessibilityStatusChanged:) + name:UIAccessibilityOnOffSwitchLabelsDidChangeNotification + object:nil]; + } + [center addObserver:self selector:@selector(onUserSettingsChanged:) name:UIContentSizeCategoryDidChangeNotification @@ -1417,19 +1424,7 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch) return; } auto platformView = [_engine.get() platformView]; - int32_t flags = 0; - if (UIAccessibilityIsInvertColorsEnabled()) { - flags |= static_cast(flutter::AccessibilityFeatureFlag::kInvertColors); - } - if (UIAccessibilityIsReduceMotionEnabled()) { - flags |= static_cast(flutter::AccessibilityFeatureFlag::kReduceMotion); - } - if (UIAccessibilityIsBoldTextEnabled()) { - flags |= static_cast(flutter::AccessibilityFeatureFlag::kBoldText); - } - if (UIAccessibilityDarkerSystemColorsEnabled()) { - flags |= static_cast(flutter::AccessibilityFeatureFlag::kHighContrast); - } + int32_t flags = [self accessibilityFlags]; #if TARGET_OS_SIMULATOR // There doesn't appear to be any way to determine whether the accessibility // inspector is enabled on the simulator. We conservatively always turn on the @@ -1447,6 +1442,35 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch) #endif } +- (int32_t)accessibilityFlags { + int32_t flags = 0; + if (UIAccessibilityIsInvertColorsEnabled()) { + flags |= static_cast(flutter::AccessibilityFeatureFlag::kInvertColors); + } + if (UIAccessibilityIsReduceMotionEnabled()) { + flags |= static_cast(flutter::AccessibilityFeatureFlag::kReduceMotion); + } + if (UIAccessibilityIsBoldTextEnabled()) { + flags |= static_cast(flutter::AccessibilityFeatureFlag::kBoldText); + } + if (UIAccessibilityDarkerSystemColorsEnabled()) { + flags |= static_cast(flutter::AccessibilityFeatureFlag::kHighContrast); + } + if ([FlutterViewController accessibilityIsOnOffSwitchLabelsEnabled]) { + flags |= static_cast(flutter::AccessibilityFeatureFlag::kOnOffSwitchLabels); + } + + return flags; +} + ++ (BOOL)accessibilityIsOnOffSwitchLabelsEnabled { + if (@available(iOS 13, *)) { + return UIAccessibilityIsOnOffSwitchLabelsEnabled(); + } else { + return NO; + } +} + #pragma mark - Set user settings - (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection { diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm index a91101c368..d6f3e0f9a1 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm @@ -6,6 +6,7 @@ #import #include "flutter/fml/platform/darwin/message_loop_darwin.h" +#import "flutter/lib/ui/window/platform_configuration.h" #import "flutter/lib/ui/window/viewport_metrics.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" @@ -25,10 +26,6 @@ FLUTTER_ASSERT_ARC userData:(nullable void*)userData; @end -namespace flutter { -class PointerDataPacket {}; -} - /// Sometimes we have to use a custom mock to avoid retain cycles in OCMock. /// Used for testing low memory notification. @interface FlutterEnginePartialMock : FlutterEngine @@ -617,6 +614,46 @@ typedef enum UIAccessibilityContrast : NSInteger { [mockTraitCollection stopMocking]; } +- (void)testItReportsAccessibilityOnOffSwitchLabelsFlagNotSet { + if (@available(iOS 13, *)) { + // noop + } else { + return; + } + + // Setup test. + FlutterViewController* viewController = + [[FlutterViewController alloc] initWithEngine:self.mockEngine nibName:nil bundle:nil]; + id partialMockViewController = OCMPartialMock(viewController); + OCMStub([partialMockViewController accessibilityIsOnOffSwitchLabelsEnabled]).andReturn(NO); + + // Exercise behavior under test. + int32_t flags = [partialMockViewController accessibilityFlags]; + + // Verify behavior. + XCTAssert((flags & (int32_t)flutter::AccessibilityFeatureFlag::kOnOffSwitchLabels) == 0); +} + +- (void)testItReportsAccessibilityOnOffSwitchLabelsFlagSet { + if (@available(iOS 13, *)) { + // noop + } else { + return; + } + + // Setup test. + FlutterViewController* viewController = + [[FlutterViewController alloc] initWithEngine:self.mockEngine nibName:nil bundle:nil]; + id partialMockViewController = OCMPartialMock(viewController); + OCMStub([partialMockViewController accessibilityIsOnOffSwitchLabelsEnabled]).andReturn(YES); + + // Exercise behavior under test. + int32_t flags = [partialMockViewController accessibilityFlags]; + + // Verify behavior. + XCTAssert((flags & (int32_t)flutter::AccessibilityFeatureFlag::kOnOffSwitchLabels) != 0); +} + - (void)testPerformOrientationUpdateForcesOrientationChange { [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait currentOrientation:UIInterfaceOrientationLandscapeLeft @@ -1052,7 +1089,7 @@ typedef enum UIAccessibilityContrast : NSInteger { [vc scrollEvent:mockPanGestureRecognizer]; [[[self.mockEngine verify] ignoringNonObjectArgs] - dispatchPointerDataPacket:std::make_unique()]; + dispatchPointerDataPacket:std::make_unique(0)]; } @end diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h index 9e069b4678..1b4af46a68 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h @@ -29,6 +29,7 @@ extern NSNotificationName const FlutterViewControllerShowHomeIndicator; @interface FlutterViewController () +@property(class, nonatomic, readonly) BOOL accessibilityIsOnOffSwitchLabelsEnabled; @property(nonatomic, readonly) BOOL isPresentingViewController; @property(nonatomic, readonly) BOOL isVoiceOverRunning; @property(nonatomic, retain) FlutterKeyboardManager* keyboardManager; @@ -45,6 +46,7 @@ extern NSNotificationName const FlutterViewControllerShowHomeIndicator; nextAction:(void (^)())nextAction API_AVAILABLE(ios(13.4)); - (void)addInternalPlugins; - (void)deregisterNotifications; +- (int32_t)accessibilityFlags; @end #endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERVIEWCONTROLLER_INTERNAL_H_