From 0babbd5fd42c93038ad591007f8f1d29b7fe05cc Mon Sep 17 00:00:00 2001 From: Marcin Chudy Date: Wed, 4 Dec 2024 15:20:58 +0100 Subject: [PATCH 1/7] Refactor permissions logic and remove OCMock from tests --- .../ios/RunnerTests/CameraPermissionTests.m | 163 +++++++++--------- .../camera_avfoundation/CameraPlugin.m | 15 +- ...onUtils.m => FLTCameraPermissionManager.m} | 38 ++-- .../FLTPermissionService.m | 17 ++ .../include/CameraPlugin.modulemap | 3 +- ...onUtils.h => FLTCameraPermissionManager.h} | 23 ++- .../FLTPermissionService.h | 19 ++ 7 files changed, 177 insertions(+), 101 deletions(-) rename packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/{CameraPermissionUtils.m => FLTCameraPermissionManager.m} (75%) create mode 100644 packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTPermissionService.m rename packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/{CameraPermissionUtils.h => FLTCameraPermissionManager.h} (71%) create mode 100644 packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTPermissionService.h diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPermissionTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPermissionTests.m index 02a610affaa5..3a1e8bc88dda 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPermissionTests.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPermissionTests.m @@ -8,31 +8,68 @@ #endif @import AVFoundation; @import XCTest; -#import #import "CameraTestUtils.h" +#import "FLTPermissionService.h" +#import "FLTCameraPermissionManager.h" -@interface CameraPermissionTests : XCTestCase +@interface MockPermissionService : NSObject +@property (nonatomic, assign) AVAuthorizationStatus cameraAuthorizationStatusStub; +@property (nonatomic, assign) AVAuthorizationStatus audioAuthorizationStatusStub; + +@property (nonatomic, assign) BOOL cameraGrantAccessStub; +@property (nonatomic, assign) BOOL audioGrantAccessStub; +@end +@implementation MockPermissionService +- (AVAuthorizationStatus)authorizationStatusForMediaType:(AVMediaType)mediaType { + if (mediaType == AVMediaTypeVideo) { + return self.cameraAuthorizationStatusStub; + } else if (mediaType == AVMediaTypeAudio) { + return self.audioAuthorizationStatusStub; + } + @throw [NSException exceptionWithName:@"UnexpectedMediaType" + reason:@"Unexpected media type was used" + userInfo:nil]; +} + +- (void)requestAccessForMediaType:(AVMediaType)mediaType + completionHandler:(void (^)(BOOL granted))handler { + if (mediaType == AVMediaTypeVideo) { + handler(self.cameraGrantAccessStub); + } else if (mediaType == AVMediaTypeAudio) { + handler(self.audioGrantAccessStub); + } +} +@end + +@interface CameraPermissionTests : XCTestCase +@property (nonatomic, strong) FLTCameraPermissionManager *permissionManager; +@property (nonatomic, strong) MockPermissionService *mockService; @end @implementation CameraPermissionTests +- (void)setUp { + [super setUp]; + self.mockService = [[MockPermissionService alloc] init]; + self.permissionManager = [[FLTCameraPermissionManager alloc] + initWithPermissionService:self.mockService]; +} + #pragma mark - camera permissions -- (void)testRequestCameraPermission_completeWithoutErrorIfPrevoiuslyAuthorized { +- (void)testRequestCameraPermission_completeWithoutErrorIfPreviouslyAuthorized { XCTestExpectation *expectation = [self expectationWithDescription: @"Must copmlete without error if camera access was previously authorized."]; - id mockDevice = OCMClassMock([AVCaptureDevice class]); - OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeVideo]) - .andReturn(AVAuthorizationStatusAuthorized); + self.mockService.cameraAuthorizationStatusStub = AVAuthorizationStatusAuthorized; - FLTRequestCameraPermissionWithCompletionHandler(^(FlutterError *error) { + [self.permissionManager requestCameraPermissionWithCompletionHandler:^(FlutterError *error) { if (error == nil) { [expectation fulfill]; } - }); + }]; [self waitForExpectationsWithTimeout:1 handler:nil]; } - (void)testRequestCameraPermission_completeWithErrorIfPreviouslyDenied { @@ -45,14 +82,13 @@ - (void)testRequestCameraPermission_completeWithErrorIfPreviouslyDenied { @"Settings to enable camera access." details:nil]; - id mockDevice = OCMClassMock([AVCaptureDevice class]); - OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeVideo]) - .andReturn(AVAuthorizationStatusDenied); - FLTRequestCameraPermissionWithCompletionHandler(^(FlutterError *error) { + self.mockService.cameraAuthorizationStatusStub = AVAuthorizationStatusDenied; + + [self.permissionManager requestCameraPermissionWithCompletionHandler:^(FlutterError *error) { if ([error isEqual:expectedError]) { [expectation fulfill]; } - }); + }]; [self waitForExpectationsWithTimeout:1 handler:nil]; } @@ -63,15 +99,13 @@ - (void)testRequestCameraPermission_completeWithErrorIfRestricted { message:@"Camera access is restricted. " details:nil]; - id mockDevice = OCMClassMock([AVCaptureDevice class]); - OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeVideo]) - .andReturn(AVAuthorizationStatusRestricted); + self.mockService.cameraAuthorizationStatusStub = AVAuthorizationStatusRestricted; - FLTRequestCameraPermissionWithCompletionHandler(^(FlutterError *error) { + [self.permissionManager requestCameraPermissionWithCompletionHandler:^(FlutterError *error) { if ([error isEqual:expectedError]) { [expectation fulfill]; } - }); + }]; [self waitForExpectationsWithTimeout:1 handler:nil]; } @@ -79,21 +113,16 @@ - (void)testRequestCameraPermission_completeWithoutErrorIfUserGrantAccess { XCTestExpectation *grantedExpectation = [self expectationWithDescription:@"Must complete without error if user choose to grant access"]; - id mockDevice = OCMClassMock([AVCaptureDevice class]); - OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeVideo]) - .andReturn(AVAuthorizationStatusNotDetermined); + self.mockService.cameraAuthorizationStatusStub = AVAuthorizationStatusNotDetermined; + // Mimic user choosing "allow" in permission dialog. - OCMStub([mockDevice requestAccessForMediaType:AVMediaTypeVideo - completionHandler:[OCMArg checkWithBlock:^BOOL(void (^block)(BOOL)) { - block(YES); - return YES; - }]]); + self.mockService.cameraGrantAccessStub = YES; - FLTRequestCameraPermissionWithCompletionHandler(^(FlutterError *error) { - if (error == nil) { - [grantedExpectation fulfill]; - } - }); + [self.permissionManager requestCameraPermissionWithCompletionHandler:^(FlutterError *error) { + if (error == nil) { + [grantedExpectation fulfill]; + } + }]; [self waitForExpectationsWithTimeout:1 handler:nil]; } @@ -105,21 +134,16 @@ - (void)testRequestCameraPermission_completeWithErrorIfUserDenyAccess { message:@"User denied the camera access request." details:nil]; - id mockDevice = OCMClassMock([AVCaptureDevice class]); - OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeVideo]) - .andReturn(AVAuthorizationStatusNotDetermined); + self.mockService.cameraAuthorizationStatusStub = AVAuthorizationStatusNotDetermined; // Mimic user choosing "deny" in permission dialog. - OCMStub([mockDevice requestAccessForMediaType:AVMediaTypeVideo - completionHandler:[OCMArg checkWithBlock:^BOOL(void (^block)(BOOL)) { - block(NO); - return YES; - }]]); - FLTRequestCameraPermissionWithCompletionHandler(^(FlutterError *error) { + self.mockService.cameraGrantAccessStub = NO; + + [self.permissionManager requestCameraPermissionWithCompletionHandler:^(FlutterError *error) { if ([error isEqual:expectedError]) { [expectation fulfill]; } - }); + }]; [self waitForExpectationsWithTimeout:1 handler:nil]; } @@ -131,17 +155,16 @@ - (void)testRequestAudioPermission_completeWithoutErrorIfPrevoiuslyAuthorized { [self expectationWithDescription: @"Must copmlete without error if audio access was previously authorized."]; - id mockDevice = OCMClassMock([AVCaptureDevice class]); - OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeAudio]) - .andReturn(AVAuthorizationStatusAuthorized); + self.mockService.audioAuthorizationStatusStub = AVAuthorizationStatusAuthorized; - FLTRequestAudioPermissionWithCompletionHandler(^(FlutterError *error) { + [self.permissionManager requestAudioPermissionWithCompletionHandler:^(FlutterError *error) { if (error == nil) { [expectation fulfill]; } - }); + }]; [self waitForExpectationsWithTimeout:1 handler:nil]; } + - (void)testRequestAudioPermission_completeWithErrorIfPreviouslyDenied { XCTestExpectation *expectation = [self expectationWithDescription: @@ -152,14 +175,13 @@ - (void)testRequestAudioPermission_completeWithErrorIfPreviouslyDenied { @"Settings to enable audio access." details:nil]; - id mockDevice = OCMClassMock([AVCaptureDevice class]); - OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeAudio]) - .andReturn(AVAuthorizationStatusDenied); - FLTRequestAudioPermissionWithCompletionHandler(^(FlutterError *error) { + self.mockService.audioAuthorizationStatusStub = AVAuthorizationStatusDenied; + + [self.permissionManager requestAudioPermissionWithCompletionHandler:^(FlutterError *error) { if ([error isEqual:expectedError]) { [expectation fulfill]; } - }); + }]; [self waitForExpectationsWithTimeout:1 handler:nil]; } @@ -170,15 +192,13 @@ - (void)testRequestAudioPermission_completeWithErrorIfRestricted { message:@"Audio access is restricted. " details:nil]; - id mockDevice = OCMClassMock([AVCaptureDevice class]); - OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeAudio]) - .andReturn(AVAuthorizationStatusRestricted); + self.mockService.audioAuthorizationStatusStub = AVAuthorizationStatusRestricted; - FLTRequestAudioPermissionWithCompletionHandler(^(FlutterError *error) { + [self.permissionManager requestAudioPermissionWithCompletionHandler:^(FlutterError *error) { if ([error isEqual:expectedError]) { [expectation fulfill]; } - }); + }]; [self waitForExpectationsWithTimeout:1 handler:nil]; } @@ -186,21 +206,16 @@ - (void)testRequestAudioPermission_completeWithoutErrorIfUserGrantAccess { XCTestExpectation *grantedExpectation = [self expectationWithDescription:@"Must complete without error if user choose to grant access"]; - id mockDevice = OCMClassMock([AVCaptureDevice class]); - OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeAudio]) - .andReturn(AVAuthorizationStatusNotDetermined); + self.mockService.audioAuthorizationStatusStub = AVAuthorizationStatusNotDetermined; + // Mimic user choosing "allow" in permission dialog. - OCMStub([mockDevice requestAccessForMediaType:AVMediaTypeAudio - completionHandler:[OCMArg checkWithBlock:^BOOL(void (^block)(BOOL)) { - block(YES); - return YES; - }]]); + self.mockService.audioGrantAccessStub = YES; - FLTRequestAudioPermissionWithCompletionHandler(^(FlutterError *error) { + [self.permissionManager requestAudioPermissionWithCompletionHandler:^(FlutterError *error) { if (error == nil) { [grantedExpectation fulfill]; } - }); + }]; [self waitForExpectationsWithTimeout:1 handler:nil]; } @@ -211,22 +226,16 @@ - (void)testRequestAudioPermission_completeWithErrorIfUserDenyAccess { message:@"User denied the audio access request." details:nil]; - id mockDevice = OCMClassMock([AVCaptureDevice class]); - OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeAudio]) - .andReturn(AVAuthorizationStatusNotDetermined); + self.mockService.audioAuthorizationStatusStub = AVAuthorizationStatusNotDetermined; // Mimic user choosing "deny" in permission dialog. - OCMStub([mockDevice requestAccessForMediaType:AVMediaTypeAudio - completionHandler:[OCMArg checkWithBlock:^BOOL(void (^block)(BOOL)) { - block(NO); - return YES; - }]]); - FLTRequestAudioPermissionWithCompletionHandler(^(FlutterError *error) { + self.mockService.audioGrantAccessStub = NO; + + [self.permissionManager requestAudioPermissionWithCompletionHandler:^(FlutterError *error) { if ([error isEqual:expectedError]) { [expectation fulfill]; } - }); - + }]; [self waitForExpectationsWithTimeout:1 handler:nil]; } diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.m b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.m index de208fd560ef..ca004b97d605 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.m +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.m @@ -8,7 +8,7 @@ @import AVFoundation; @import Flutter; -#import "./include/camera_avfoundation/CameraPermissionUtils.h" +#import "./include/camera_avfoundation/FLTCameraPermissionManager.h" #import "./include/camera_avfoundation/CameraProperties.h" #import "./include/camera_avfoundation/FLTCam.h" #import "./include/camera_avfoundation/FLTThreadSafeEventChannel.h" @@ -25,6 +25,7 @@ @interface CameraPlugin () @property(readonly, nonatomic) id registry; @property(readonly, nonatomic) NSObject *messenger; @property(nonatomic) FCPCameraGlobalEventApi *globalEventAPI; +@property(readonly, nonatomic) FLTCameraPermissionManager *permissionManager; @end @implementation CameraPlugin @@ -52,6 +53,10 @@ - (instancetype)initWithRegistry:(NSObject *)registry _messenger = messenger; _globalEventAPI = globalAPI; _captureSessionQueue = dispatch_queue_create("io.flutter.camera.captureSessionQueue", NULL); + + id permissionService = [[FLTDefaultPermissionService alloc] init]; + _permissionManager = [[FLTCameraPermissionManager alloc] initWithPermissionService:permissionService]; + dispatch_queue_set_specific(_captureSessionQueue, FLTCaptureSessionQueueSpecific, (void *)FLTCaptureSessionQueueSpecific, NULL); @@ -145,7 +150,7 @@ - (void)createCameraWithName:(nonnull NSString *)cameraName // Create FLTCam only if granted camera access (and audio access if audio is enabled) __weak typeof(self) weakSelf = self; dispatch_async(self.captureSessionQueue, ^{ - FLTRequestCameraPermissionWithCompletionHandler(^(FlutterError *error) { + [self->_permissionManager requestCameraPermissionWithCompletionHandler:^(FlutterError *error) { typeof(self) strongSelf = weakSelf; if (!strongSelf) return; @@ -157,7 +162,7 @@ - (void)createCameraWithName:(nonnull NSString *)cameraName // optional, and used as a workaround to fix a missing frame issue on iOS. if (settings.enableAudio) { // Setup audio capture session only if granted audio access. - FLTRequestAudioPermissionWithCompletionHandler(^(FlutterError *error) { + [self->_permissionManager requestAudioPermissionWithCompletionHandler:^(FlutterError *error) { // cannot use the outter `strongSelf` typeof(self) strongSelf = weakSelf; if (!strongSelf) return; @@ -168,14 +173,14 @@ - (void)createCameraWithName:(nonnull NSString *)cameraName settings:settings completion:completion]; } - }); + }]; } else { [strongSelf createCameraOnSessionQueueWithName:cameraName settings:settings completion:completion]; } } - }); + }]; }); } diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPermissionUtils.m b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCameraPermissionManager.m similarity index 75% rename from packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPermissionUtils.m rename to packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCameraPermissionManager.m index b63a1d684e00..c3e326e5fcbd 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPermissionUtils.m +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCameraPermissionManager.m @@ -3,9 +3,29 @@ // found in the LICENSE file. @import AVFoundation; -#import "./include/camera_avfoundation/CameraPermissionUtils.h" +#import "./include/camera_avfoundation/FLTCameraPermissionManager.h" +#import "./include/camera_avfoundation/FLTPermissionService.h" -void FLTRequestPermission(BOOL forAudio, FLTCameraPermissionRequestCompletionHandler handler) { +@implementation FLTCameraPermissionManager + +- (instancetype)initWithPermissionService:(id)service { + self = [super init]; + if (self) { + _permissionService = service ?: [[FLTDefaultPermissionService alloc] init]; + } + return self; +} + +- (void)requestAudioPermissionWithCompletionHandler:(__strong FLTCameraPermissionRequestCompletionHandler)handler { + [self requestPermissionForAudio:YES handler:handler]; +} + +- (void)requestCameraPermissionWithCompletionHandler:(__strong FLTCameraPermissionRequestCompletionHandler)handler { + [self requestPermissionForAudio:NO handler:handler]; +} + +- (void)requestPermissionForAudio:(BOOL)forAudio + handler:(FLTCameraPermissionRequestCompletionHandler)handler { AVMediaType mediaType; if (forAudio) { mediaType = AVMediaTypeAudio; @@ -13,7 +33,7 @@ void FLTRequestPermission(BOOL forAudio, FLTCameraPermissionRequestCompletionHan mediaType = AVMediaTypeVideo; } - switch ([AVCaptureDevice authorizationStatusForMediaType:mediaType]) { + switch ([_permissionService authorizationStatusForMediaType:mediaType]) { case AVAuthorizationStatusAuthorized: handler(nil); break; @@ -50,7 +70,7 @@ void FLTRequestPermission(BOOL forAudio, FLTCameraPermissionRequestCompletionHan break; } case AVAuthorizationStatusNotDetermined: { - [AVCaptureDevice requestAccessForMediaType:mediaType + [_permissionService requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) { // handler can be invoked on an arbitrary dispatch queue. if (granted) { @@ -76,12 +96,4 @@ void FLTRequestPermission(BOOL forAudio, FLTCameraPermissionRequestCompletionHan } } -void FLTRequestCameraPermissionWithCompletionHandler( - FLTCameraPermissionRequestCompletionHandler handler) { - FLTRequestPermission(/*forAudio*/ NO, handler); -} - -void FLTRequestAudioPermissionWithCompletionHandler( - FLTCameraPermissionRequestCompletionHandler handler) { - FLTRequestPermission(/*forAudio*/ YES, handler); -} +@end diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTPermissionService.m b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTPermissionService.m new file mode 100644 index 000000000000..151524e4ff11 --- /dev/null +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTPermissionService.m @@ -0,0 +1,17 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "./include/camera_avfoundation/FLTPermissionService.h" + +@implementation FLTDefaultPermissionService +- (AVAuthorizationStatus)authorizationStatusForMediaType:(AVMediaType)mediaType { + return [AVCaptureDevice authorizationStatusForMediaType:mediaType]; +} + +- (void)requestAccessForMediaType:(AVMediaType)mediaType + completionHandler:(void (^)(BOOL granted))handler { + [AVCaptureDevice requestAccessForMediaType:mediaType + completionHandler:handler]; +} +@end diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/CameraPlugin.modulemap b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/CameraPlugin.modulemap index bc864d174927..c2487e24704f 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/CameraPlugin.modulemap +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/CameraPlugin.modulemap @@ -6,12 +6,13 @@ framework module camera_avfoundation { explicit module Test { header "CameraPlugin_Test.h" - header "CameraPermissionUtils.h" header "CameraProperties.h" header "FLTCam.h" header "FLTCam_Test.h" header "FLTSavePhotoDelegate_Test.h" header "FLTThreadSafeEventChannel.h" + header "FLTPermissionService.h" + header "FLTCameraPermissionManager.h" header "QueueUtils.h" } } diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/CameraPermissionUtils.h b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCameraPermissionManager.h similarity index 71% rename from packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/CameraPermissionUtils.h rename to packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCameraPermissionManager.h index 5cbbab055f34..a7e859aebdde 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/CameraPermissionUtils.h +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCameraPermissionManager.h @@ -5,7 +5,16 @@ @import Foundation; #import -typedef void (^FLTCameraPermissionRequestCompletionHandler)(FlutterError *); +#import "FLTPermissionService.h" + +NS_ASSUME_NONNULL_BEGIN + +typedef void (^FLTCameraPermissionRequestCompletionHandler)(FlutterError *_Nullable); + +@interface FLTCameraPermissionManager : NSObject +@property (nonatomic, strong) id permissionService; + +- (instancetype)initWithPermissionService:(id)service; /// Requests camera access permission. /// @@ -16,8 +25,8 @@ typedef void (^FLTCameraPermissionRequestCompletionHandler)(FlutterError *); /// @param handler if access permission is (or was previously) granted, completion handler will be /// called without error; Otherwise completion handler will be called with error. Handler can be /// called on an arbitrary dispatch queue. -extern void FLTRequestCameraPermissionWithCompletionHandler( - FLTCameraPermissionRequestCompletionHandler handler); +- (void)requestCameraPermissionWithCompletionHandler:( + FLTCameraPermissionRequestCompletionHandler)handler; /// Requests audio access permission. /// @@ -28,5 +37,9 @@ extern void FLTRequestCameraPermissionWithCompletionHandler( /// @param handler if access permission is (or was previously) granted, completion handler will be /// called without error; Otherwise completion handler will be called with error. Handler can be /// called on an arbitrary dispatch queue. -extern void FLTRequestAudioPermissionWithCompletionHandler( - FLTCameraPermissionRequestCompletionHandler handler); +- (void)requestAudioPermissionWithCompletionHandler:( + FLTCameraPermissionRequestCompletionHandler)handler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTPermissionService.h b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTPermissionService.h new file mode 100644 index 000000000000..af31e415e995 --- /dev/null +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTPermissionService.h @@ -0,0 +1,19 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +@import AVFoundation; +@import Foundation; + +NS_ASSUME_NONNULL_BEGIN + +@protocol FLTPermissionService +- (AVAuthorizationStatus)authorizationStatusForMediaType:(AVMediaType)mediaType; +- (void)requestAccessForMediaType:(AVMediaType)mediaType + completionHandler:(void (^)(BOOL granted))handler; +@end + +@interface FLTDefaultPermissionService : NSObject +@end + +NS_ASSUME_NONNULL_END From ab78a677228d047dbc207dce4432cdc7254d1a8f Mon Sep 17 00:00:00 2001 From: Marcin Chudy Date: Fri, 27 Dec 2024 11:15:32 +0100 Subject: [PATCH 2/7] Update tests file --- .../ios/RunnerTests/CameraPermissionTests.m | 129 +++++++++++------- 1 file changed, 80 insertions(+), 49 deletions(-) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPermissionTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPermissionTests.m index 3a1e8bc88dda..a2490ba0d774 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPermissionTests.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPermissionTests.m @@ -8,52 +8,41 @@ #endif @import AVFoundation; @import XCTest; + #import "CameraTestUtils.h" -#import "FLTPermissionService.h" #import "FLTCameraPermissionManager.h" +#import "FLTPermissionService.h" @interface MockPermissionService : NSObject -@property (nonatomic, assign) AVAuthorizationStatus cameraAuthorizationStatusStub; -@property (nonatomic, assign) AVAuthorizationStatus audioAuthorizationStatusStub; - -@property (nonatomic, assign) BOOL cameraGrantAccessStub; -@property (nonatomic, assign) BOOL audioGrantAccessStub; +@property(nonatomic, copy) AVAuthorizationStatus (^authorizationStatusStub)(AVMediaType mediaType); +@property(nonatomic, copy) void (^requestAccessStub)(AVMediaType mediaType, void (^handler)(BOOL)); @end @implementation MockPermissionService - (AVAuthorizationStatus)authorizationStatusForMediaType:(AVMediaType)mediaType { - if (mediaType == AVMediaTypeVideo) { - return self.cameraAuthorizationStatusStub; - } else if (mediaType == AVMediaTypeAudio) { - return self.audioAuthorizationStatusStub; - } - @throw [NSException exceptionWithName:@"UnexpectedMediaType" - reason:@"Unexpected media type was used" - userInfo:nil]; + return self.authorizationStatusStub ? self.authorizationStatusStub(mediaType) + : AVAuthorizationStatusNotDetermined; } -- (void)requestAccessForMediaType:(AVMediaType)mediaType - completionHandler:(void (^)(BOOL granted))handler { - if (mediaType == AVMediaTypeVideo) { - handler(self.cameraGrantAccessStub); - } else if (mediaType == AVMediaTypeAudio) { - handler(self.audioGrantAccessStub); +- (void)requestAccessForMediaType:(AVMediaType)mediaType completionHandler:(void (^)(BOOL))handler { + if (self.requestAccessStub) { + self.requestAccessStub(mediaType, handler); } } @end -@interface CameraPermissionTests : XCTestCase -@property (nonatomic, strong) FLTCameraPermissionManager *permissionManager; -@property (nonatomic, strong) MockPermissionService *mockService; +@interface FLTCameraPermissionManagerTests : XCTestCase +@property(nonatomic, strong) FLTCameraPermissionManager *permissionManager; +@property(nonatomic, strong) MockPermissionService *mockService; @end -@implementation CameraPermissionTests +@implementation FLTCameraPermissionManagerTests - (void)setUp { - [super setUp]; - self.mockService = [[MockPermissionService alloc] init]; - self.permissionManager = [[FLTCameraPermissionManager alloc] - initWithPermissionService:self.mockService]; + [super setUp]; + self.mockService = [[MockPermissionService alloc] init]; + self.permissionManager = + [[FLTCameraPermissionManager alloc] initWithPermissionService:self.mockService]; } #pragma mark - camera permissions @@ -63,7 +52,10 @@ - (void)testRequestCameraPermission_completeWithoutErrorIfPreviouslyAuthorized { [self expectationWithDescription: @"Must copmlete without error if camera access was previously authorized."]; - self.mockService.cameraAuthorizationStatusStub = AVAuthorizationStatusAuthorized; + self.mockService.authorizationStatusStub = ^AVAuthorizationStatus(AVMediaType mediaType) { + XCTAssertEqualObjects(mediaType, AVMediaTypeVideo); + return AVAuthorizationStatusAuthorized; + }; [self.permissionManager requestCameraPermissionWithCompletionHandler:^(FlutterError *error) { if (error == nil) { @@ -82,8 +74,11 @@ - (void)testRequestCameraPermission_completeWithErrorIfPreviouslyDenied { @"Settings to enable camera access." details:nil]; - self.mockService.cameraAuthorizationStatusStub = AVAuthorizationStatusDenied; - + self.mockService.authorizationStatusStub = ^AVAuthorizationStatus(AVMediaType mediaType) { + XCTAssertEqualObjects(mediaType, AVMediaTypeVideo); + return AVAuthorizationStatusDenied; + }; + [self.permissionManager requestCameraPermissionWithCompletionHandler:^(FlutterError *error) { if ([error isEqual:expectedError]) { [expectation fulfill]; @@ -99,7 +94,10 @@ - (void)testRequestCameraPermission_completeWithErrorIfRestricted { message:@"Camera access is restricted. " details:nil]; - self.mockService.cameraAuthorizationStatusStub = AVAuthorizationStatusRestricted; + self.mockService.authorizationStatusStub = ^AVAuthorizationStatus(AVMediaType mediaType) { + XCTAssertEqualObjects(mediaType, AVMediaTypeVideo); + return AVAuthorizationStatusRestricted; + }; [self.permissionManager requestCameraPermissionWithCompletionHandler:^(FlutterError *error) { if ([error isEqual:expectedError]) { @@ -113,16 +111,22 @@ - (void)testRequestCameraPermission_completeWithoutErrorIfUserGrantAccess { XCTestExpectation *grantedExpectation = [self expectationWithDescription:@"Must complete without error if user choose to grant access"]; - self.mockService.cameraAuthorizationStatusStub = AVAuthorizationStatusNotDetermined; + self.mockService.authorizationStatusStub = ^AVAuthorizationStatus(AVMediaType mediaType) { + XCTAssertEqualObjects(mediaType, AVMediaTypeVideo); + return AVAuthorizationStatusNotDetermined; + }; // Mimic user choosing "allow" in permission dialog. - self.mockService.cameraGrantAccessStub = YES; + self.mockService.requestAccessStub = ^(AVMediaType mediaType, void (^handler)(BOOL)) { + XCTAssertEqualObjects(mediaType, AVMediaTypeVideo); + handler(YES); + }; [self.permissionManager requestCameraPermissionWithCompletionHandler:^(FlutterError *error) { - if (error == nil) { - [grantedExpectation fulfill]; - } - }]; + if (error == nil) { + [grantedExpectation fulfill]; + } + }]; [self waitForExpectationsWithTimeout:1 handler:nil]; } @@ -134,16 +138,22 @@ - (void)testRequestCameraPermission_completeWithErrorIfUserDenyAccess { message:@"User denied the camera access request." details:nil]; - self.mockService.cameraAuthorizationStatusStub = AVAuthorizationStatusNotDetermined; + self.mockService.authorizationStatusStub = ^AVAuthorizationStatus(AVMediaType mediaType) { + XCTAssertEqualObjects(mediaType, AVMediaTypeVideo); + return AVAuthorizationStatusNotDetermined; + }; - // Mimic user choosing "deny" in permission dialog. - self.mockService.cameraGrantAccessStub = NO; + // Mimic user choosing "allow" in permission dialog. + self.mockService.requestAccessStub = ^(AVMediaType mediaType, void (^handler)(BOOL)) { + XCTAssertEqualObjects(mediaType, AVMediaTypeVideo); + handler(NO); + }; [self.permissionManager requestCameraPermissionWithCompletionHandler:^(FlutterError *error) { if ([error isEqual:expectedError]) { [expectation fulfill]; } - }]; + }]; [self waitForExpectationsWithTimeout:1 handler:nil]; } @@ -155,7 +165,10 @@ - (void)testRequestAudioPermission_completeWithoutErrorIfPrevoiuslyAuthorized { [self expectationWithDescription: @"Must copmlete without error if audio access was previously authorized."]; - self.mockService.audioAuthorizationStatusStub = AVAuthorizationStatusAuthorized; + self.mockService.authorizationStatusStub = ^AVAuthorizationStatus(AVMediaType mediaType) { + XCTAssertEqualObjects(mediaType, AVMediaTypeAudio); + return AVAuthorizationStatusAuthorized; + }; [self.permissionManager requestAudioPermissionWithCompletionHandler:^(FlutterError *error) { if (error == nil) { @@ -175,7 +188,10 @@ - (void)testRequestAudioPermission_completeWithErrorIfPreviouslyDenied { @"Settings to enable audio access." details:nil]; - self.mockService.audioAuthorizationStatusStub = AVAuthorizationStatusDenied; + self.mockService.authorizationStatusStub = ^AVAuthorizationStatus(AVMediaType mediaType) { + XCTAssertEqualObjects(mediaType, AVMediaTypeAudio); + return AVAuthorizationStatusDenied; + }; [self.permissionManager requestAudioPermissionWithCompletionHandler:^(FlutterError *error) { if ([error isEqual:expectedError]) { @@ -192,7 +208,10 @@ - (void)testRequestAudioPermission_completeWithErrorIfRestricted { message:@"Audio access is restricted. " details:nil]; - self.mockService.audioAuthorizationStatusStub = AVAuthorizationStatusRestricted; + self.mockService.authorizationStatusStub = ^AVAuthorizationStatus(AVMediaType mediaType) { + XCTAssertEqualObjects(mediaType, AVMediaTypeAudio); + return AVAuthorizationStatusRestricted; + }; [self.permissionManager requestAudioPermissionWithCompletionHandler:^(FlutterError *error) { if ([error isEqual:expectedError]) { @@ -206,10 +225,16 @@ - (void)testRequestAudioPermission_completeWithoutErrorIfUserGrantAccess { XCTestExpectation *grantedExpectation = [self expectationWithDescription:@"Must complete without error if user choose to grant access"]; - self.mockService.audioAuthorizationStatusStub = AVAuthorizationStatusNotDetermined; + self.mockService.authorizationStatusStub = ^AVAuthorizationStatus(AVMediaType mediaType) { + XCTAssertEqualObjects(mediaType, AVMediaTypeAudio); + return AVAuthorizationStatusNotDetermined; + }; // Mimic user choosing "allow" in permission dialog. - self.mockService.audioGrantAccessStub = YES; + self.mockService.requestAccessStub = ^(AVMediaType mediaType, void (^handler)(BOOL)) { + XCTAssertEqualObjects(mediaType, AVMediaTypeAudio); + handler(YES); + }; [self.permissionManager requestAudioPermissionWithCompletionHandler:^(FlutterError *error) { if (error == nil) { @@ -226,10 +251,16 @@ - (void)testRequestAudioPermission_completeWithErrorIfUserDenyAccess { message:@"User denied the audio access request." details:nil]; - self.mockService.audioAuthorizationStatusStub = AVAuthorizationStatusNotDetermined; + self.mockService.authorizationStatusStub = ^AVAuthorizationStatus(AVMediaType mediaType) { + XCTAssertEqualObjects(mediaType, AVMediaTypeAudio); + return AVAuthorizationStatusNotDetermined; + }; // Mimic user choosing "deny" in permission dialog. - self.mockService.audioGrantAccessStub = NO; + self.mockService.requestAccessStub = ^(AVMediaType mediaType, void (^handler)(BOOL)) { + XCTAssertEqualObjects(mediaType, AVMediaTypeAudio); + handler(NO); + }; [self.permissionManager requestAudioPermissionWithCompletionHandler:^(FlutterError *error) { if ([error isEqual:expectedError]) { From 5dbee5acc94983a7607d4c7b3f6e458074de5d84 Mon Sep 17 00:00:00 2001 From: Marcin Chudy Date: Fri, 27 Dec 2024 14:31:10 +0100 Subject: [PATCH 3/7] Fix formatting --- .../camera_avfoundation/CameraPlugin.m | 34 +++++++------- .../FLTCameraPermissionManager.m | 46 ++++++++++--------- .../FLTPermissionService.m | 7 ++- .../FLTCameraPermissionManager.h | 10 ++-- .../FLTPermissionService.h | 2 +- .../include/camera_avfoundation/QueueUtils.h | 2 +- 6 files changed, 52 insertions(+), 49 deletions(-) diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.m b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.m index ca004b97d605..dda74726c871 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.m +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.m @@ -8,9 +8,9 @@ @import AVFoundation; @import Flutter; -#import "./include/camera_avfoundation/FLTCameraPermissionManager.h" #import "./include/camera_avfoundation/CameraProperties.h" #import "./include/camera_avfoundation/FLTCam.h" +#import "./include/camera_avfoundation/FLTCameraPermissionManager.h" #import "./include/camera_avfoundation/FLTThreadSafeEventChannel.h" #import "./include/camera_avfoundation/QueueUtils.h" #import "./include/camera_avfoundation/messages.g.h" @@ -53,10 +53,11 @@ - (instancetype)initWithRegistry:(NSObject *)registry _messenger = messenger; _globalEventAPI = globalAPI; _captureSessionQueue = dispatch_queue_create("io.flutter.camera.captureSessionQueue", NULL); - + id permissionService = [[FLTDefaultPermissionService alloc] init]; - _permissionManager = [[FLTCameraPermissionManager alloc] initWithPermissionService:permissionService]; - + _permissionManager = + [[FLTCameraPermissionManager alloc] initWithPermissionService:permissionService]; + dispatch_queue_set_specific(_captureSessionQueue, FLTCaptureSessionQueueSpecific, (void *)FLTCaptureSessionQueueSpecific, NULL); @@ -162,18 +163,19 @@ - (void)createCameraWithName:(nonnull NSString *)cameraName // optional, and used as a workaround to fix a missing frame issue on iOS. if (settings.enableAudio) { // Setup audio capture session only if granted audio access. - [self->_permissionManager requestAudioPermissionWithCompletionHandler:^(FlutterError *error) { - // cannot use the outter `strongSelf` - typeof(self) strongSelf = weakSelf; - if (!strongSelf) return; - if (error) { - completion(nil, error); - } else { - [strongSelf createCameraOnSessionQueueWithName:cameraName - settings:settings - completion:completion]; - } - }]; + [self->_permissionManager + requestAudioPermissionWithCompletionHandler:^(FlutterError *error) { + // cannot use the outter `strongSelf` + typeof(self) strongSelf = weakSelf; + if (!strongSelf) return; + if (error) { + completion(nil, error); + } else { + [strongSelf createCameraOnSessionQueueWithName:cameraName + settings:settings + completion:completion]; + } + }]; } else { [strongSelf createCameraOnSessionQueueWithName:cameraName settings:settings diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCameraPermissionManager.m b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCameraPermissionManager.m index c3e326e5fcbd..f4291400ab0b 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCameraPermissionManager.m +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCameraPermissionManager.m @@ -16,11 +16,13 @@ - (instancetype)initWithPermissionService:(id)service { return self; } -- (void)requestAudioPermissionWithCompletionHandler:(__strong FLTCameraPermissionRequestCompletionHandler)handler { +- (void)requestAudioPermissionWithCompletionHandler: + (__strong FLTCameraPermissionRequestCompletionHandler)handler { [self requestPermissionForAudio:YES handler:handler]; } -- (void)requestCameraPermissionWithCompletionHandler:(__strong FLTCameraPermissionRequestCompletionHandler)handler { +- (void)requestCameraPermissionWithCompletionHandler: + (__strong FLTCameraPermissionRequestCompletionHandler)handler { [self requestPermissionForAudio:NO handler:handler]; } @@ -71,26 +73,26 @@ - (void)requestPermissionForAudio:(BOOL)forAudio } case AVAuthorizationStatusNotDetermined: { [_permissionService requestAccessForMediaType:mediaType - completionHandler:^(BOOL granted) { - // handler can be invoked on an arbitrary dispatch queue. - if (granted) { - handler(nil); - } else { - FlutterError *flutterError; - if (forAudio) { - flutterError = [FlutterError - errorWithCode:@"AudioAccessDenied" - message:@"User denied the audio access request." - details:nil]; - } else { - flutterError = [FlutterError - errorWithCode:@"CameraAccessDenied" - message:@"User denied the camera access request." - details:nil]; - } - handler(flutterError); - } - }]; + completionHandler:^(BOOL granted) { + // handler can be invoked on an arbitrary dispatch queue. + if (granted) { + handler(nil); + } else { + FlutterError *flutterError; + if (forAudio) { + flutterError = [FlutterError + errorWithCode:@"AudioAccessDenied" + message:@"User denied the audio access request." + details:nil]; + } else { + flutterError = [FlutterError + errorWithCode:@"CameraAccessDenied" + message:@"User denied the camera access request." + details:nil]; + } + handler(flutterError); + } + }]; break; } } diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTPermissionService.m b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTPermissionService.m index 151524e4ff11..8fdccddf4419 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTPermissionService.m +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTPermissionService.m @@ -6,12 +6,11 @@ @implementation FLTDefaultPermissionService - (AVAuthorizationStatus)authorizationStatusForMediaType:(AVMediaType)mediaType { - return [AVCaptureDevice authorizationStatusForMediaType:mediaType]; + return [AVCaptureDevice authorizationStatusForMediaType:mediaType]; } - (void)requestAccessForMediaType:(AVMediaType)mediaType - completionHandler:(void (^)(BOOL granted))handler { - [AVCaptureDevice requestAccessForMediaType:mediaType - completionHandler:handler]; + completionHandler:(void (^)(BOOL granted))handler { + [AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:handler]; } @end diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCameraPermissionManager.h b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCameraPermissionManager.h index a7e859aebdde..6bd24bae12fe 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCameraPermissionManager.h +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCameraPermissionManager.h @@ -12,7 +12,7 @@ NS_ASSUME_NONNULL_BEGIN typedef void (^FLTCameraPermissionRequestCompletionHandler)(FlutterError *_Nullable); @interface FLTCameraPermissionManager : NSObject -@property (nonatomic, strong) id permissionService; +@property(nonatomic, strong) id permissionService; - (instancetype)initWithPermissionService:(id)service; @@ -25,8 +25,8 @@ typedef void (^FLTCameraPermissionRequestCompletionHandler)(FlutterError *_Nulla /// @param handler if access permission is (or was previously) granted, completion handler will be /// called without error; Otherwise completion handler will be called with error. Handler can be /// called on an arbitrary dispatch queue. -- (void)requestCameraPermissionWithCompletionHandler:( - FLTCameraPermissionRequestCompletionHandler)handler; +- (void)requestCameraPermissionWithCompletionHandler: + (FLTCameraPermissionRequestCompletionHandler)handler; /// Requests audio access permission. /// @@ -37,8 +37,8 @@ typedef void (^FLTCameraPermissionRequestCompletionHandler)(FlutterError *_Nulla /// @param handler if access permission is (or was previously) granted, completion handler will be /// called without error; Otherwise completion handler will be called with error. Handler can be /// called on an arbitrary dispatch queue. -- (void)requestAudioPermissionWithCompletionHandler:( - FLTCameraPermissionRequestCompletionHandler)handler; +- (void)requestAudioPermissionWithCompletionHandler: + (FLTCameraPermissionRequestCompletionHandler)handler; @end diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTPermissionService.h b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTPermissionService.h index af31e415e995..7ab7cdfbbd8c 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTPermissionService.h +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTPermissionService.h @@ -10,7 +10,7 @@ NS_ASSUME_NONNULL_BEGIN @protocol FLTPermissionService - (AVAuthorizationStatus)authorizationStatusForMediaType:(AVMediaType)mediaType; - (void)requestAccessForMediaType:(AVMediaType)mediaType - completionHandler:(void (^)(BOOL granted))handler; + completionHandler:(void (^)(BOOL granted))handler; @end @interface FLTDefaultPermissionService : NSObject diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/QueueUtils.h b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/QueueUtils.h index a7e22da716d0..e230a53508fa 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/QueueUtils.h +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/QueueUtils.h @@ -7,7 +7,7 @@ NS_ASSUME_NONNULL_BEGIN /// Queue-specific context data to be associated with the capture session queue. -extern const char* FLTCaptureSessionQueueSpecific; +extern const char *FLTCaptureSessionQueueSpecific; /// Ensures the given block to be run on the main queue. /// If caller site is already on the main queue, the block will be run From 34089f721c9039c35ff16419e80bcfaff9e0e5bf Mon Sep 17 00:00:00 2001 From: Marcin Chudy Date: Sat, 28 Dec 2024 16:11:58 +0100 Subject: [PATCH 4/7] Change naming and fix comment --- .../example/ios/RunnerTests/CameraPermissionTests.m | 6 +++--- .../Sources/camera_avfoundation/CameraPlugin.m | 2 +- .../camera_avfoundation/FLTCameraPermissionManager.m | 4 ++-- .../{FLTPermissionService.m => FLTPermissionServicing.m} | 2 +- .../camera_avfoundation/include/CameraPlugin.modulemap | 2 +- .../camera_avfoundation/FLTCameraPermissionManager.h | 6 +++--- .../{FLTPermissionService.h => FLTPermissionServicing.h} | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) rename packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/{FLTPermissionService.m => FLTPermissionServicing.m} (89%) rename packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/{FLTPermissionService.h => FLTPermissionServicing.h} (90%) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPermissionTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPermissionTests.m index a2490ba0d774..36d5e9b25d95 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPermissionTests.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPermissionTests.m @@ -11,9 +11,9 @@ #import "CameraTestUtils.h" #import "FLTCameraPermissionManager.h" -#import "FLTPermissionService.h" +#import "FLTPermissionServicing.h" -@interface MockPermissionService : NSObject +@interface MockPermissionService : NSObject @property(nonatomic, copy) AVAuthorizationStatus (^authorizationStatusStub)(AVMediaType mediaType); @property(nonatomic, copy) void (^requestAccessStub)(AVMediaType mediaType, void (^handler)(BOOL)); @end @@ -143,7 +143,7 @@ - (void)testRequestCameraPermission_completeWithErrorIfUserDenyAccess { return AVAuthorizationStatusNotDetermined; }; - // Mimic user choosing "allow" in permission dialog. + // Mimic user choosing "deny" in permission dialog. self.mockService.requestAccessStub = ^(AVMediaType mediaType, void (^handler)(BOOL)) { XCTAssertEqualObjects(mediaType, AVMediaTypeVideo); handler(NO); diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.m b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.m index dda74726c871..63a49025231a 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.m +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.m @@ -54,7 +54,7 @@ - (instancetype)initWithRegistry:(NSObject *)registry _globalEventAPI = globalAPI; _captureSessionQueue = dispatch_queue_create("io.flutter.camera.captureSessionQueue", NULL); - id permissionService = [[FLTDefaultPermissionService alloc] init]; + id permissionService = [[FLTDefaultPermissionService alloc] init]; _permissionManager = [[FLTCameraPermissionManager alloc] initWithPermissionService:permissionService]; diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCameraPermissionManager.m b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCameraPermissionManager.m index f4291400ab0b..013326a0de32 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCameraPermissionManager.m +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCameraPermissionManager.m @@ -4,11 +4,11 @@ @import AVFoundation; #import "./include/camera_avfoundation/FLTCameraPermissionManager.h" -#import "./include/camera_avfoundation/FLTPermissionService.h" +#import "./include/camera_avfoundation/FLTPermissionServicing.h" @implementation FLTCameraPermissionManager -- (instancetype)initWithPermissionService:(id)service { +- (instancetype)initWithPermissionService:(id)service { self = [super init]; if (self) { _permissionService = service ?: [[FLTDefaultPermissionService alloc] init]; diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTPermissionService.m b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTPermissionServicing.m similarity index 89% rename from packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTPermissionService.m rename to packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTPermissionServicing.m index 8fdccddf4419..4596b879b436 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTPermissionService.m +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTPermissionServicing.m @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import "./include/camera_avfoundation/FLTPermissionService.h" +#import "./include/camera_avfoundation/FLTPermissionServicing.h" @implementation FLTDefaultPermissionService - (AVAuthorizationStatus)authorizationStatusForMediaType:(AVMediaType)mediaType { diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/CameraPlugin.modulemap b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/CameraPlugin.modulemap index c2487e24704f..57d858f894fa 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/CameraPlugin.modulemap +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/CameraPlugin.modulemap @@ -11,7 +11,7 @@ framework module camera_avfoundation { header "FLTCam_Test.h" header "FLTSavePhotoDelegate_Test.h" header "FLTThreadSafeEventChannel.h" - header "FLTPermissionService.h" + header "FLTPermissionServicing.h" header "FLTCameraPermissionManager.h" header "QueueUtils.h" } diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCameraPermissionManager.h b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCameraPermissionManager.h index 6bd24bae12fe..d431e3f3b3f7 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCameraPermissionManager.h +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCameraPermissionManager.h @@ -5,16 +5,16 @@ @import Foundation; #import -#import "FLTPermissionService.h" +#import "FLTPermissionServicing.h" NS_ASSUME_NONNULL_BEGIN typedef void (^FLTCameraPermissionRequestCompletionHandler)(FlutterError *_Nullable); @interface FLTCameraPermissionManager : NSObject -@property(nonatomic, strong) id permissionService; +@property(nonatomic, strong) id permissionService; -- (instancetype)initWithPermissionService:(id)service; +- (instancetype)initWithPermissionService:(id)service; /// Requests camera access permission. /// diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTPermissionService.h b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTPermissionServicing.h similarity index 90% rename from packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTPermissionService.h rename to packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTPermissionServicing.h index 7ab7cdfbbd8c..2e65568baf83 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTPermissionService.h +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTPermissionServicing.h @@ -7,13 +7,13 @@ NS_ASSUME_NONNULL_BEGIN -@protocol FLTPermissionService +@protocol FLTPermissionServicing - (AVAuthorizationStatus)authorizationStatusForMediaType:(AVMediaType)mediaType; - (void)requestAccessForMediaType:(AVMediaType)mediaType completionHandler:(void (^)(BOOL granted))handler; @end -@interface FLTDefaultPermissionService : NSObject +@interface FLTDefaultPermissionService : NSObject @end NS_ASSUME_NONNULL_END From 27d62d4993370e7a7eea4cb1e3bd02fdd6d8378c Mon Sep 17 00:00:00 2001 From: Marcin Chudy Date: Sat, 28 Dec 2024 16:14:35 +0100 Subject: [PATCH 5/7] Update version --- packages/camera/camera_avfoundation/CHANGELOG.md | 3 ++- packages/camera/camera_avfoundation/pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera_avfoundation/CHANGELOG.md b/packages/camera/camera_avfoundation/CHANGELOG.md index 34de3bf88e46..f0f4a302ae5a 100644 --- a/packages/camera/camera_avfoundation/CHANGELOG.md +++ b/packages/camera/camera_avfoundation/CHANGELOG.md @@ -1,6 +1,7 @@ -## NEXT +## 0.9.17+6 * Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. +* Removes OCMock usage from permission tests ## 0.9.17+5 diff --git a/packages/camera/camera_avfoundation/pubspec.yaml b/packages/camera/camera_avfoundation/pubspec.yaml index 8ba11828e97d..507011775bfd 100644 --- a/packages/camera/camera_avfoundation/pubspec.yaml +++ b/packages/camera/camera_avfoundation/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_avfoundation description: iOS implementation of the camera plugin. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_avfoundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.9.17+5 +version: 0.9.17+6 environment: sdk: ^3.4.0 From 8cb2172358510f10cd80d2f95a44a963699c17fe Mon Sep 17 00:00:00 2001 From: Marcin Chudy Date: Sat, 28 Dec 2024 16:35:45 +0100 Subject: [PATCH 6/7] Fix formatting --- .../include/camera_avfoundation/QueueUtils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/QueueUtils.h b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/QueueUtils.h index e230a53508fa..a7e22da716d0 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/QueueUtils.h +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/QueueUtils.h @@ -7,7 +7,7 @@ NS_ASSUME_NONNULL_BEGIN /// Queue-specific context data to be associated with the capture session queue. -extern const char *FLTCaptureSessionQueueSpecific; +extern const char* FLTCaptureSessionQueueSpecific; /// Ensures the given block to be run on the main queue. /// If caller site is already on the main queue, the block will be run From b37709420a1a9276a1621786bc110996b2bc198f Mon Sep 17 00:00:00 2001 From: Marcin Chudy Date: Sat, 28 Dec 2024 16:42:45 +0100 Subject: [PATCH 7/7] Fix imports --- .../example/ios/RunnerTests/CameraPermissionTests.m | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPermissionTests.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPermissionTests.m index 36d5e9b25d95..ec7530381023 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPermissionTests.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPermissionTests.m @@ -10,8 +10,6 @@ @import XCTest; #import "CameraTestUtils.h" -#import "FLTCameraPermissionManager.h" -#import "FLTPermissionServicing.h" @interface MockPermissionService : NSObject @property(nonatomic, copy) AVAuthorizationStatus (^authorizationStatusStub)(AVMediaType mediaType);