Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 4bef5f8

Browse files
authored
Enables semantics when voice control is turned on (#24640)
1 parent 8ef7b63 commit 4bef5f8

7 files changed

Lines changed: 103 additions & 1 deletion

File tree

ci/licenses_golden/licenses_flutter

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,6 +1036,7 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterView.mm
10361036
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm
10371037
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm
10381038
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h
1039+
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterViewTest.mm
10391040
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/IOKit.h
10401041
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.h
10411042
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm

shell/platform/darwin/ios/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ source_set("ios_test_flutter_mrc") {
186186
"framework/Source/FlutterEngineTest_mrc.mm",
187187
"framework/Source/FlutterPlatformPluginTest.mm",
188188
"framework/Source/FlutterPlatformViewsTest.mm",
189+
"framework/Source/FlutterViewTest.mm",
189190
"framework/Source/accessibility_bridge_test.mm",
190191
]
191192
deps = [

shell/platform/darwin/ios/framework/Source/FlutterEngine.mm

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -761,14 +761,20 @@ - (void)showAutocorrectionPromptRectForStart:(NSUInteger)start
761761
arguments:@[ @(client), @(start), @(end) ]];
762762
}
763763

764-
#pragma mark - Screenshot Delegate
764+
#pragma mark - FlutterViewEngineDelegate
765765

766766
- (flutter::Rasterizer::Screenshot)takeScreenshot:(flutter::Rasterizer::ScreenshotType)type
767767
asBase64Encoded:(BOOL)base64Encode {
768768
FML_DCHECK(_shell) << "Cannot takeScreenshot without a shell";
769769
return _shell->Screenshot(type, base64Encode);
770770
}
771771

772+
- (void)flutterViewAccessibilityDidCall {
773+
if (self.viewController.view.accessibilityElements == nil) {
774+
[self ensureSemanticsEnabled];
775+
}
776+
}
777+
772778
- (NSObject<FlutterBinaryMessenger>*)binaryMessenger {
773779
return _binaryMessenger;
774780
}

shell/platform/darwin/ios/framework/Source/FlutterEngineTest_mrc.mm

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,18 @@
1212

1313
FLUTTER_ASSERT_NOT_ARC
1414

15+
@interface FlutterEngineSpy : FlutterEngine
16+
@property(nonatomic) BOOL ensureSemanticsEnabledCalled;
17+
@end
18+
19+
@implementation FlutterEngineSpy
20+
21+
- (void)ensureSemanticsEnabled {
22+
_ensureSemanticsEnabledCalled = YES;
23+
}
24+
25+
@end
26+
1527
@interface FlutterEngineTest_mrc : XCTestCase
1628
@end
1729

@@ -41,4 +53,11 @@ - (void)testSpawnsShareGpuContext {
4153
[spawn release];
4254
}
4355

56+
- (void)testEnableSemanticsWhenFlutterViewAccessibilityDidCall {
57+
FlutterEngineSpy* engine = [[FlutterEngineSpy alloc] initWithName:@"foobar"];
58+
engine.ensureSemanticsEnabledCalled = NO;
59+
[engine flutterViewAccessibilityDidCall];
60+
XCTAssertTrue(engine.ensureSemanticsEnabledCalled);
61+
}
62+
4463
@end

shell/platform/darwin/ios/framework/Source/FlutterView.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@
2121
asBase64Encoded:(BOOL)base64Encode;
2222

2323
- (std::shared_ptr<flutter::FlutterPlatformViewsController>&)platformViewsController;
24+
25+
/**
26+
* A callback that is called when iOS queries accessibility information of the Flutter view.
27+
*
28+
* This is useful to predict the current iOS accessibility status. For example, there is
29+
* no API to listen whether voice control is turned on or off. The Flutter engine uses
30+
* this callback to enable semantics in order to catch the case that voice control is
31+
* on.
32+
*/
33+
- (void)flutterViewAccessibilityDidCall;
2434
@end
2535

2636
@interface FlutterView : UIView

shell/platform/darwin/ios/framework/Source/FlutterView.mm

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,17 @@ - (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context {
130130
CGContextRestoreGState(context);
131131
}
132132

133+
- (BOOL)isAccessibilityElement {
134+
// iOS does not provide an API to query whether the voice control
135+
// is turned on or off. It is likely at least one of the assitive
136+
// technologies is turned on if this method is called. If we do
137+
// not catch it in notification center, we will catch it here.
138+
//
139+
// TODO(chunhtai): Remove this workaround once iOS provides an
140+
// API to query whether voice control is enabled.
141+
// https://github.com/flutter/flutter/issues/76808.
142+
[_delegate flutterViewAccessibilityDidCall];
143+
return NO;
144+
}
145+
133146
@end
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#import <XCTest/XCTest.h>
6+
7+
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h"
8+
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h"
9+
10+
@interface FakeDelegate : NSObject <FlutterViewEngineDelegate>
11+
@property(nonatomic) BOOL callbackCalled;
12+
@end
13+
14+
@implementation FakeDelegate {
15+
std::shared_ptr<flutter::FlutterPlatformViewsController> _platformViewsController;
16+
}
17+
18+
- (instancetype)init {
19+
_callbackCalled = NO;
20+
_platformViewsController = std::shared_ptr<flutter::FlutterPlatformViewsController>(nullptr);
21+
return self;
22+
}
23+
24+
- (flutter::Rasterizer::Screenshot)takeScreenshot:(flutter::Rasterizer::ScreenshotType)type
25+
asBase64Encoded:(BOOL)base64Encode {
26+
return {};
27+
}
28+
29+
- (std::shared_ptr<flutter::FlutterPlatformViewsController>&)platformViewsController {
30+
return _platformViewsController;
31+
}
32+
33+
- (void)flutterViewAccessibilityDidCall {
34+
_callbackCalled = YES;
35+
}
36+
37+
@end
38+
39+
@interface FlutterViewTest : XCTestCase
40+
@end
41+
42+
@implementation FlutterViewTest
43+
44+
- (void)testFlutterViewEnableSemanticsWhenIsAccessibilityElementIsCalled {
45+
FakeDelegate* delegate = [[FakeDelegate alloc] init];
46+
FlutterView* view = [[FlutterView alloc] initWithDelegate:delegate opaque:NO];
47+
delegate.callbackCalled = NO;
48+
XCTAssertFalse(view.isAccessibilityElement);
49+
XCTAssertTrue(delegate.callbackCalled);
50+
}
51+
52+
@end

0 commit comments

Comments
 (0)