Skip to content

Commit

Permalink
[camera] Convert iOS Obj-C->Dart calls to Pigeon (#6568)
Browse files Browse the repository at this point in the history
Converts all of the Obj-C -> Dart calls to Pigeon, using the new suffix-based Pigeon API instantiation feature.

This required decentralizing some threading code slightly: since method channel calls only involve one method (due to the lack of strong types), a wrapper that automatically did thread bouncing was feasible, but with Pigeon it's not since a wrapper would have to duplicate the entire API surface, and that's more work than just doing the dispatches at the call site.

Part of flutter/flutter#117905
  • Loading branch information
stuartmorgan committed Apr 19, 2024
1 parent 5d15437 commit 88a3a56
Show file tree
Hide file tree
Showing 24 changed files with 1,133 additions and 609 deletions.
4 changes: 4 additions & 0 deletions packages/camera/camera_avfoundation/CHANGELOG.md
@@ -1,3 +1,7 @@
## 0.9.15+4

* Converts host-to-Dart communcation to Pigeon.

## 0.9.15+3

* Moves `pigeon` to `dev_dependencies`.
Expand Down
Expand Up @@ -29,7 +29,6 @@
E071CF7227B3061B006EF3BA /* FLTCamPhotoCaptureTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */; };
E071CF7427B31DE4006EF3BA /* FLTCamSampleBufferTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E071CF7327B31DE4006EF3BA /* FLTCamSampleBufferTests.m */; };
E0B0D2BB27DFF2AF00E71E4B /* CameraPermissionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0B0D2BA27DFF2AF00E71E4B /* CameraPermissionTests.m */; };
E0C6E2002770F01A00EA6AA3 /* ThreadSafeMethodChannelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0C6E1FD2770F01A00EA6AA3 /* ThreadSafeMethodChannelTests.m */; };
E0C6E2012770F01A00EA6AA3 /* ThreadSafeTextureRegistryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0C6E1FE2770F01A00EA6AA3 /* ThreadSafeTextureRegistryTests.m */; };
E0C6E2022770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0C6E1FF2770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m */; };
E0CDBAC227CD9729002561D9 /* CameraTestUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = E0CDBAC127CD9729002561D9 /* CameraTestUtils.m */; };
Expand Down Expand Up @@ -96,7 +95,6 @@
E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTCamPhotoCaptureTests.m; sourceTree = "<group>"; };
E071CF7327B31DE4006EF3BA /* FLTCamSampleBufferTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLTCamSampleBufferTests.m; sourceTree = "<group>"; };
E0B0D2BA27DFF2AF00E71E4B /* CameraPermissionTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CameraPermissionTests.m; sourceTree = "<group>"; };
E0C6E1FD2770F01A00EA6AA3 /* ThreadSafeMethodChannelTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreadSafeMethodChannelTests.m; sourceTree = "<group>"; };
E0C6E1FE2770F01A00EA6AA3 /* ThreadSafeTextureRegistryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreadSafeTextureRegistryTests.m; sourceTree = "<group>"; };
E0C6E1FF2770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreadSafeEventChannelTests.m; sourceTree = "<group>"; };
E0CDBAC027CD9729002561D9 /* CameraTestUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CameraTestUtils.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -134,7 +132,6 @@
03BB766C2665316900CE5A93 /* Info.plist */,
033B94BD269C40A200B4DF97 /* CameraMethodChannelTests.m */,
E0C6E1FF2770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m */,
E0C6E1FD2770F01A00EA6AA3 /* ThreadSafeMethodChannelTests.m */,
E0C6E1FE2770F01A00EA6AA3 /* ThreadSafeTextureRegistryTests.m */,
E04F108527A87CA600573D0C /* FLTSavePhotoDelegateTests.m */,
E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */,
Expand Down Expand Up @@ -454,7 +451,6 @@
E0C6E2022770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m in Sources */,
E0C6E2012770F01A00EA6AA3 /* ThreadSafeTextureRegistryTests.m in Sources */,
E0B0D2BB27DFF2AF00E71E4B /* CameraPermissionTests.m in Sources */,
E0C6E2002770F01A00EA6AA3 /* ThreadSafeMethodChannelTests.m in Sources */,
E01EE4A82799F3A5008C1950 /* QueueUtilsTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
Expand Up @@ -37,7 +37,7 @@ - (void)testAutoFocusWithContinuousModeSupported_ShouldSetContinuousAutoFocus {
[[_mockDevice reject] setFocusMode:AVCaptureFocusModeAutoFocus];

// Run test
[_camera applyFocusMode:FLTFocusModeAuto onDevice:_mockDevice];
[_camera applyFocusMode:FCPPlatformFocusModeAuto onDevice:_mockDevice];

// Expect setFocusMode:AVCaptureFocusModeContinuousAutoFocus
OCMVerify([_mockDevice setFocusMode:AVCaptureFocusModeContinuousAutoFocus]);
Expand All @@ -54,7 +54,7 @@ - (void)testAutoFocusWithContinuousModeNotSupported_ShouldSetAutoFocus {
[[_mockDevice reject] setFocusMode:AVCaptureFocusModeContinuousAutoFocus];

// Run test
[_camera applyFocusMode:FLTFocusModeAuto onDevice:_mockDevice];
[_camera applyFocusMode:FCPPlatformFocusModeAuto onDevice:_mockDevice];

// Expect setFocusMode:AVCaptureFocusModeAutoFocus
OCMVerify([_mockDevice setFocusMode:AVCaptureFocusModeAutoFocus]);
Expand All @@ -72,7 +72,7 @@ - (void)testAutoFocusWithNoModeSupported_ShouldSetNothing {
[[_mockDevice reject] setFocusMode:AVCaptureFocusModeAutoFocus];

// Run test
[_camera applyFocusMode:FLTFocusModeAuto onDevice:_mockDevice];
[_camera applyFocusMode:FCPPlatformFocusModeAuto onDevice:_mockDevice];
}

- (void)testLockedFocusWithModeSupported_ShouldSetModeAutoFocus {
Expand All @@ -85,7 +85,7 @@ - (void)testLockedFocusWithModeSupported_ShouldSetModeAutoFocus {
[[_mockDevice reject] setFocusMode:AVCaptureFocusModeContinuousAutoFocus];

// Run test
[_camera applyFocusMode:FLTFocusModeLocked onDevice:_mockDevice];
[_camera applyFocusMode:FCPPlatformFocusModeLocked onDevice:_mockDevice];

// Expect setFocusMode:AVCaptureFocusModeAutoFocus
OCMVerify([_mockDevice setFocusMode:AVCaptureFocusModeAutoFocus]);
Expand All @@ -102,7 +102,7 @@ - (void)testLockedFocusWithModeNotSupported_ShouldSetNothing {
[[_mockDevice reject] setFocusMode:AVCaptureFocusModeAutoFocus];

// Run test
[_camera applyFocusMode:FLTFocusModeLocked onDevice:_mockDevice];
[_camera applyFocusMode:FCPPlatformFocusModeLocked onDevice:_mockDevice];
}

- (void)testSetFocusPointWithResult_SetsFocusPointOfInterest {
Expand Down
Expand Up @@ -9,44 +9,87 @@

#import <OCMock/OCMock.h>

@interface StubGlobalEventApi : FCPCameraGlobalEventApi
@property(nonatomic) BOOL called;
@property(nonatomic) FCPPlatformDeviceOrientation lastOrientation;
@end

@implementation StubGlobalEventApi
- (void)deviceOrientationChangedOrientation:(FCPPlatformDeviceOrientation)orientation
completion:(void (^)(FlutterError *_Nullable))completion {
self.called = YES;
self.lastOrientation = orientation;
completion(nil);
}

- (FlutterBinaryMessengerConnection)setMessageHandlerOnChannel:(nonnull NSString *)channel
binaryMessageHandler:
(nullable FlutterBinaryMessageHandler)handler {
return 0;
}

@end

#pragma mark -

@interface CameraOrientationTests : XCTestCase
@end

@implementation CameraOrientationTests

// Ensure that the given queue and then the main queue have both cycled, to wait for any pending
// async events that may have been bounced between them.
- (void)waitForRoundTripWithQueue:(dispatch_queue_t)queue {
XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"Queue flush"];
dispatch_async(queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
[expectation fulfill];
});
});
[self waitForExpectations:@[ expectation ]];
}

- (void)sendOrientation:(UIDeviceOrientation)orientation toCamera:(CameraPlugin *)cameraPlugin {
[cameraPlugin orientationChanged:[self createMockNotificationForOrientation:orientation]];
[self waitForRoundTripWithQueue:cameraPlugin.captureSessionQueue];
}

- (void)testOrientationNotifications {
id mockMessenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger));
CameraPlugin *cameraPlugin = [[CameraPlugin alloc] initWithRegistry:nil messenger:mockMessenger];

[mockMessenger setExpectationOrderMatters:YES];

[self rotate:UIDeviceOrientationPortraitUpsideDown
expectedChannelOrientation:@"portraitDown"
cameraPlugin:cameraPlugin
messenger:mockMessenger];
[self rotate:UIDeviceOrientationPortrait
expectedChannelOrientation:@"portraitUp"
cameraPlugin:cameraPlugin
messenger:mockMessenger];
[self rotate:UIDeviceOrientationLandscapeLeft
expectedChannelOrientation:@"landscapeLeft"
cameraPlugin:cameraPlugin
messenger:mockMessenger];
[self rotate:UIDeviceOrientationLandscapeRight
expectedChannelOrientation:@"landscapeRight"
cameraPlugin:cameraPlugin
messenger:mockMessenger];

OCMReject([mockMessenger sendOnChannel:[OCMArg any] message:[OCMArg any]]);

// No notification when flat.
[cameraPlugin
orientationChanged:[self createMockNotificationForOrientation:UIDeviceOrientationFaceUp]];
// No notification when facedown.
[cameraPlugin
orientationChanged:[self createMockNotificationForOrientation:UIDeviceOrientationFaceDown]];

OCMVerifyAll(mockMessenger);
StubGlobalEventApi *eventAPI = [[StubGlobalEventApi alloc] init];
CameraPlugin *cameraPlugin = [[CameraPlugin alloc] initWithRegistry:nil
messenger:nil
globalAPI:eventAPI];

[self sendOrientation:UIDeviceOrientationPortraitUpsideDown toCamera:cameraPlugin];
XCTAssertEqual(eventAPI.lastOrientation, FCPPlatformDeviceOrientationPortraitDown);
[self sendOrientation:UIDeviceOrientationPortrait toCamera:cameraPlugin];
XCTAssertEqual(eventAPI.lastOrientation, FCPPlatformDeviceOrientationPortraitUp);
[self sendOrientation:UIDeviceOrientationLandscapeLeft toCamera:cameraPlugin];
XCTAssertEqual(eventAPI.lastOrientation, FCPPlatformDeviceOrientationLandscapeLeft);
[self sendOrientation:UIDeviceOrientationLandscapeRight toCamera:cameraPlugin];
XCTAssertEqual(eventAPI.lastOrientation, FCPPlatformDeviceOrientationLandscapeRight);
}

- (void)testOrientationNotificationsNotCalledForFaceUp {
StubGlobalEventApi *eventAPI = [[StubGlobalEventApi alloc] init];
CameraPlugin *cameraPlugin = [[CameraPlugin alloc] initWithRegistry:nil
messenger:nil
globalAPI:eventAPI];

[self sendOrientation:UIDeviceOrientationFaceUp toCamera:cameraPlugin];

XCTAssertFalse(eventAPI.called);
}

- (void)testOrientationNotificationsNotCalledForFaceDown {
StubGlobalEventApi *eventAPI = [[StubGlobalEventApi alloc] init];
CameraPlugin *cameraPlugin = [[CameraPlugin alloc] initWithRegistry:nil
messenger:nil
globalAPI:eventAPI];

[self sendOrientation:UIDeviceOrientationFaceDown toCamera:cameraPlugin];

XCTAssertFalse(eventAPI.called);
}

- (void)testOrientationUpdateMustBeOnCaptureSessionQueue {
Expand All @@ -71,40 +114,20 @@ - (void)testOrientationUpdateMustBeOnCaptureSessionQueue {
[self waitForExpectationsWithTimeout:1 handler:nil];
}

- (void)rotate:(UIDeviceOrientation)deviceOrientation
expectedChannelOrientation:(NSString *)channelOrientation
cameraPlugin:(CameraPlugin *)cameraPlugin
messenger:(NSObject<FlutterBinaryMessenger> *)messenger {
XCTestExpectation *orientationExpectation = [self expectationWithDescription:channelOrientation];

OCMExpect([messenger
sendOnChannel:[OCMArg any]
message:[OCMArg checkWithBlock:^BOOL(NSData *data) {
NSObject<FlutterMethodCodec> *codec = [FlutterStandardMethodCodec sharedInstance];
FlutterMethodCall *methodCall = [codec decodeMethodCall:data];
[orientationExpectation fulfill];
return
[methodCall.method isEqualToString:@"orientation_changed"] &&
[methodCall.arguments isEqualToDictionary:@{@"orientation" : channelOrientation}];
}]]);

[cameraPlugin orientationChanged:[self createMockNotificationForOrientation:deviceOrientation]];
[self waitForExpectationsWithTimeout:30.0 handler:nil];
}

- (void)testOrientationChanged_noRetainCycle {
dispatch_queue_t captureSessionQueue = dispatch_queue_create("capture_session_queue", NULL);
FLTCam *mockCam = OCMClassMock([FLTCam class]);
FLTThreadSafeMethodChannel *mockChannel = OCMClassMock([FLTThreadSafeMethodChannel class]);
StubGlobalEventApi *stubAPI = [[StubGlobalEventApi alloc] init];

__weak CameraPlugin *weakCamera;

@autoreleasepool {
CameraPlugin *camera = [[CameraPlugin alloc] initWithRegistry:nil messenger:nil];
CameraPlugin *camera = [[CameraPlugin alloc] initWithRegistry:nil
messenger:nil
globalAPI:stubAPI];
weakCamera = camera;
camera.captureSessionQueue = captureSessionQueue;
camera.camera = mockCam;
camera.deviceEventMethodChannel = mockChannel;

[camera orientationChanged:
[self createMockNotificationForOrientation:UIDeviceOrientationLandscapeLeft]];
Expand All @@ -118,11 +141,11 @@ - (void)testOrientationChanged_noRetainCycle {
[self expectationWithDescription:@"Dispatched to capture session queue"];
dispatch_async(captureSessionQueue, ^{
OCMVerify(never(), [mockCam setDeviceOrientation:UIDeviceOrientationLandscapeLeft]);
OCMVerify(never(), [mockChannel invokeMethod:@"orientation_changed" arguments:OCMOCK_ANY]);
XCTAssertFalse(stubAPI.called);
[expectation fulfill];
});

[self waitForExpectationsWithTimeout:1 handler:nil];
[self waitForExpectationsWithTimeout:30 handler:nil];
}

- (NSNotification *)createMockNotificationForOrientation:(UIDeviceOrientation)deviceOrientation {
Expand Down
Expand Up @@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

@import camera_avfoundation;
@import camera_avfoundation.Test;
@import AVFoundation;
@import XCTest;
Expand Down Expand Up @@ -31,30 +32,16 @@ - (void)testFLTGetAVCaptureFlashModeForFLTFlashMode {

#pragma mark - exposure mode tests

- (void)testFLTGetStringForFLTExposureMode {
XCTAssertEqualObjects(@"auto", FLTGetStringForFLTExposureMode(FLTExposureModeAuto));
XCTAssertEqualObjects(@"locked", FLTGetStringForFLTExposureMode(FLTExposureModeLocked));
XCTAssertNil(FLTGetStringForFLTExposureMode(-1));
}

- (void)testFLTGetFLTExposureModeForString {
XCTAssertEqual(FLTExposureModeAuto, FLTGetFLTExposureModeForString(@"auto"));
XCTAssertEqual(FLTExposureModeLocked, FLTGetFLTExposureModeForString(@"locked"));
XCTAssertEqual(FLTExposureModeInvalid, FLTGetFLTExposureModeForString(@"unknown"));
- (void)testFCPGetExposureModeForString {
XCTAssertEqual(FCPPlatformExposureModeAuto, FCPGetExposureModeForString(@"auto"));
XCTAssertEqual(FCPPlatformExposureModeLocked, FCPGetExposureModeForString(@"locked"));
}

#pragma mark - focus mode tests

- (void)testFLTGetStringForFLTFocusMode {
XCTAssertEqualObjects(@"auto", FLTGetStringForFLTFocusMode(FLTFocusModeAuto));
XCTAssertEqualObjects(@"locked", FLTGetStringForFLTFocusMode(FLTFocusModeLocked));
XCTAssertNil(FLTGetStringForFLTFocusMode(-1));
}

- (void)testFLTGetFLTFocusModeForString {
XCTAssertEqual(FLTFocusModeAuto, FLTGetFLTFocusModeForString(@"auto"));
XCTAssertEqual(FLTFocusModeLocked, FLTGetFLTFocusModeForString(@"locked"));
XCTAssertEqual(FLTFocusModeInvalid, FLTGetFLTFocusModeForString(@"unknown"));
XCTAssertEqual(FCPPlatformFocusModeAuto, FCPGetFocusModeForString(@"auto"));
XCTAssertEqual(FCPPlatformFocusModeLocked, FCPGetFocusModeForString(@"locked"));
}

#pragma mark - resolution preset tests
Expand Down Expand Up @@ -93,15 +80,17 @@ - (void)testFLTGetUIDeviceOrientationForString {
}

- (void)testFLTGetStringForUIDeviceOrientation {
XCTAssertEqualObjects(@"portraitDown",
FLTGetStringForUIDeviceOrientation(UIDeviceOrientationPortraitUpsideDown));
XCTAssertEqualObjects(@"landscapeLeft",
FLTGetStringForUIDeviceOrientation(UIDeviceOrientationLandscapeLeft));
XCTAssertEqualObjects(@"landscapeRight",
FLTGetStringForUIDeviceOrientation(UIDeviceOrientationLandscapeRight));
XCTAssertEqualObjects(@"portraitUp",
FLTGetStringForUIDeviceOrientation(UIDeviceOrientationPortrait));
XCTAssertEqualObjects(@"portraitUp", FLTGetStringForUIDeviceOrientation(-1));
XCTAssertEqual(
FCPPlatformDeviceOrientationPortraitDown,
FCPGetPigeonDeviceOrientationForOrientation(UIDeviceOrientationPortraitUpsideDown));
XCTAssertEqual(FCPPlatformDeviceOrientationLandscapeLeft,
FCPGetPigeonDeviceOrientationForOrientation(UIDeviceOrientationLandscapeLeft));
XCTAssertEqual(FCPPlatformDeviceOrientationLandscapeRight,
FCPGetPigeonDeviceOrientationForOrientation(UIDeviceOrientationLandscapeRight));
XCTAssertEqual(FCPPlatformDeviceOrientationPortraitUp,
FCPGetPigeonDeviceOrientationForOrientation(UIDeviceOrientationPortrait));
XCTAssertEqual(FCPPlatformDeviceOrientationPortraitUp,
FCPGetPigeonDeviceOrientationForOrientation(-1));
}

#pragma mark - file format tests
Expand Down

This file was deleted.

0 comments on commit 88a3a56

Please sign in to comment.