Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
e7389cc
[ci] migrate to Apple Silicon
fkorotkov May 10, 2022
c71b646
Fix RunnerTest config for google_maps_flutter
stuartmorgan-g May 11, 2022
eea37d5
changelog
stuartmorgan-g May 11, 2022
49e16fc
Move some tasks back to Intel
stuartmorgan-g May 11, 2022
e31c427
Log unexpeted FlutterErrors in tests instead of throwing
stuartmorgan-g May 11, 2022
7f6506c
Add temp debug NSLog statements
stuartmorgan-g May 11, 2022
225df85
Remove failed logging, add some details
stuartmorgan-g May 12, 2022
91ea52d
Don't assert for overfullfillment
fkorotkov May 12, 2022
d65a77f
Fullfill only when initialized
fkorotkov May 12, 2022
1ddf2a7
Move linting back to ARM
stuartmorgan-g May 13, 2022
001e214
Log event for debugging
fkorotkov May 13, 2022
5ceab37
Remove NSLog
stuartmorgan-g May 13, 2022
d68340a
Remove duplicate details from error
stuartmorgan-g May 13, 2022
6b47358
video_player changelog
stuartmorgan-g May 13, 2022
9caf835
iPhone 12
fkorotkov May 15, 2022
b490760
Reverted iPhone version
fkorotkov May 15, 2022
7fffeb9
Try Xcode 13.4 and macOS 12.4
fkorotkov May 16, 2022
4e0b259
Revert "Try Xcode 13.4 and macOS 12.4"
fkorotkov May 16, 2022
9713866
Revert "Revert "Try Xcode 13.4 and macOS 12.4""
fkorotkov May 16, 2022
60eb2f5
Try experimental config
fkorotkov May 17, 2022
d9d7875
Revert "Try experimental config"
fkorotkov May 18, 2022
a3771c4
Use only one simulator
fkorotkov May 18, 2022
71e2cb5
Set timeout to not waste resources in case of a hanging
fkorotkov May 18, 2022
d44df7c
[tools] Fix `publish` flag calculation (#5694)
stuartmorgan-g May 14, 2022
8ee7bd8
[camera]handle iOS camera access permission (#5215)
hellohuanlin May 14, 2022
2ccc39e
[image_picker] Fix 'messages.g.h' file not found (#5635)
co-kevin May 16, 2022
29241ad
[ci] Manually roll Flutter master (#5765)
stuartmorgan-g May 16, 2022
1fd508f
Roll Flutter from 036cae36697a to bf7a32628eef (49 revisions) (#5768)
engine-flutter-autoroll May 17, 2022
33934de
Roll Flutter from bf7a32628eef to bb9bbc601345 (1 revision) (#5769)
engine-flutter-autoroll May 17, 2022
63a6039
Roll Flutter from bb9bbc601345 to fd312f1ccff9 (1 revision) (#5770)
engine-flutter-autoroll May 17, 2022
c1c1332
Roll Flutter from fd312f1ccff9 to c248854d176b (1 revision) (#5771)
engine-flutter-autoroll May 17, 2022
f586229
[in_app_purchase] fixed a memory leak error (#5358)
yimao009 May 17, 2022
2656002
[local_auth] Windows support. (#4806)
azchohfi May 17, 2022
9cdd4b5
[google_sign_in_platform_interface] Add availability to mock models (…
Hwan-seok May 17, 2022
74042c0
Update cirrus secret. (#5774)
godofredoc May 17, 2022
18283a7
Add more Android plugin owners (#5624)
stuartmorgan-g May 17, 2022
66e56f0
[video_player]: Bump exoplayer from 2.17.0 to 2.17.1 in /packages/vid…
dependabot[bot] May 17, 2022
9da2a72
[tools] Validate example READMEs (#5775)
stuartmorgan-g May 18, 2022
c62a7ed
[webview_flutter] Initial v4.0 platform interface implementation (#5…
mvanbeusekom May 18, 2022
bd40099
[camera] Request access permission for audio (#5766)
hellohuanlin May 18, 2022
2e81edb
Roll Flutter from c248854d176b to 1994027986cf (1 revision) (#5777)
engine-flutter-autoroll May 18, 2022
f3d955a
[ci/tools] Add iOS/macOS analysis to catch deprecated code (#5778)
stuartmorgan-g May 18, 2022
72c1752
Add more CODEOWNERS (#5779)
stuartmorgan-g May 18, 2022
bf35c1b
[tools] Add `update-release-info` (#5643)
stuartmorgan-g May 18, 2022
dfa3e84
[local_auth] Adds federated Windows support (#5776)
azchohfi May 19, 2022
b0c19f0
[google_sign_in] Upgrade to GoogleSignIn 6.2, support arm64 simulator…
jmagman May 19, 2022
9d1311d
Roll Flutter from 1994027986cf to a4a8e73bce15 (31 revisions) (#5782)
engine-flutter-autoroll May 19, 2022
afc26ec
[ci] Updates iOS deprecation check to iOS 13 (#5786)
stuartmorgan-g May 19, 2022
216b216
[various] Set minimum Flutter versions to 2.8 (#5792)
stuartmorgan-g May 19, 2022
87cd4e7
[google_maps_flutter_web] Remove custom analysis file. (#5791)
ditman May 20, 2022
82cbfc8
[path_provider] Fix integration tests on macOS (#5773)
stuartmorgan-g May 20, 2022
0fcac82
iPhone 12
fkorotkov May 15, 2022
fc971fb
Use only one simulator
fkorotkov May 18, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
[camera] Request access permission for audio (#5766)
  • Loading branch information
hellohuanlin authored and fkorotkov committed May 20, 2022
commit bd4009923fd5080dc98683d7f3f08c4ea41b8a58
4 changes: 4 additions & 0 deletions packages/camera/camera/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.9.6

* Adds audio access permission handling logic on iOS to fix an issue with `prepareForVideoRecording` not awaiting for the audio permission request result.

## 0.9.5+1

* Suppresses warnings for pre-iOS-11 codepaths.
Expand Down
8 changes: 7 additions & 1 deletion packages/camera/camera/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,16 @@ Here is a list of all permission error codes that can be thrown:

- `CameraAccessDenied`: Thrown when user denies the camera access permission.

- `CameraAccessDeniedWithoutPrompt`: iOS only for now. Thrown when user has previously denied the permission. iOS does not allow prompting alert dialog a second time. Users will have to go to Settings > Privacy in order to enable camera access.
- `CameraAccessDeniedWithoutPrompt`: iOS only for now. Thrown when user has previously denied the permission. iOS does not allow prompting alert dialog a second time. Users will have to go to Settings > Privacy > Camera in order to enable camera access.

- `CameraAccessRestricted`: iOS only for now. Thrown when camera access is restricted and users cannot grant permission (parental control).

- `AudioAccessDenied`: Thrown when user denies the audio access permission.

- `AudioAccessDeniedWithoutPrompt`: iOS only for now. Thrown when user has previously denied the permission. iOS does not allow prompting alert dialog a second time. Users will have to go to Settings > Privacy > Microphone in order to enable audio access.

- `AudioAccessRestricted`: iOS only for now. Thrown when audio access is restricted and users cannot grant permission (parental control).

- `cameraPermission`: Android and Web only. A legacy error code for all kinds of camera permission errors.

### Example
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ @interface CameraPermissionTests : XCTestCase

@implementation CameraPermissionTests

#pragma mark - camera permissions

- (void)testRequestCameraPermission_completeWithoutErrorIfPrevoiuslyAuthorized {
XCTestExpectation *expectation =
[self expectationWithDescription:
Expand Down Expand Up @@ -120,4 +122,110 @@ - (void)testRequestCameraPermission_completeWithErrorIfUserDenyAccess {
[self waitForExpectationsWithTimeout:1 handler:nil];
}

#pragma mark - audio permissions

- (void)testRequestAudioPermission_completeWithoutErrorIfPrevoiuslyAuthorized {
XCTestExpectation *expectation =
[self expectationWithDescription:
@"Must copmlete without error if audio access was previously authorized."];

id mockDevice = OCMClassMock([AVCaptureDevice class]);
OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeAudio])
.andReturn(AVAuthorizationStatusAuthorized);

FLTRequestAudioPermissionWithCompletionHandler(^(FlutterError *error) {
if (error == nil) {
[expectation fulfill];
}
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}
- (void)testRequestAudioPermission_completeWithErrorIfPreviouslyDenied {
XCTestExpectation *expectation =
[self expectationWithDescription:
@"Must complete with error if audio access was previously denied."];
FlutterError *expectedError =
[FlutterError errorWithCode:@"AudioAccessDeniedWithoutPrompt"
message:@"User has previously denied the audio access request. Go to "
@"Settings to enable audio access."
details:nil];

id mockDevice = OCMClassMock([AVCaptureDevice class]);
OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeAudio])
.andReturn(AVAuthorizationStatusDenied);
FLTRequestAudioPermissionWithCompletionHandler(^(FlutterError *error) {
if ([error isEqual:expectedError]) {
[expectation fulfill];
}
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}

- (void)testRequestAudioPermission_completeWithErrorIfRestricted {
XCTestExpectation *expectation =
[self expectationWithDescription:@"Must complete with error if audio access is restricted."];
FlutterError *expectedError = [FlutterError errorWithCode:@"AudioAccessRestricted"
message:@"Audio access is restricted. "
details:nil];

id mockDevice = OCMClassMock([AVCaptureDevice class]);
OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeAudio])
.andReturn(AVAuthorizationStatusRestricted);

FLTRequestAudioPermissionWithCompletionHandler(^(FlutterError *error) {
if ([error isEqual:expectedError]) {
[expectation fulfill];
}
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}

- (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);
// Mimic user choosing "allow" in permission dialog.
OCMStub([mockDevice requestAccessForMediaType:AVMediaTypeAudio
completionHandler:[OCMArg checkWithBlock:^BOOL(void (^block)(BOOL)) {
block(YES);
return YES;
}]]);

FLTRequestAudioPermissionWithCompletionHandler(^(FlutterError *error) {
if (error == nil) {
[grantedExpectation fulfill];
}
});
[self waitForExpectationsWithTimeout:1 handler:nil];
}

- (void)testRequestAudioPermission_completeWithErrorIfUserDenyAccess {
XCTestExpectation *expectation =
[self expectationWithDescription:@"Must complete with error if user choose to deny access"];
FlutterError *expectedError = [FlutterError errorWithCode:@"AudioAccessDenied"
message:@"User denied the audio access request."
details:nil];

id mockDevice = OCMClassMock([AVCaptureDevice class]);
OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeAudio])
.andReturn(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) {
if ([error isEqual:expectedError]) {
[expectation fulfill];
}
});

[self waitForExpectationsWithTimeout:1 handler:nil];
}

@end
11 changes: 11 additions & 0 deletions packages/camera/camera/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,17 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
// iOS only
showInSnackBar('Camera access is restricted.');
break;
case 'AudioAccessDenied':
showInSnackBar('You have denied audio access.');
break;
case 'AudioAccessDeniedWithoutPrompt':
// iOS only
showInSnackBar('Please go to Settings app to enable audio access.');
break;
case 'AudioAccessRestricted':
// iOS only
showInSnackBar('Audio access is restricted.');
break;
case 'cameraPermission':
// Android & web only
showInSnackBar('Unknown permission error.');
Expand Down
12 changes: 12 additions & 0 deletions packages/camera/camera/ios/Classes/CameraPermissionUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,15 @@ typedef void (^FLTCameraPermissionRequestCompletionHandler)(FlutterError *);
/// called on an arbitrary dispatch queue.
extern void FLTRequestCameraPermissionWithCompletionHandler(
FLTCameraPermissionRequestCompletionHandler handler);

/// Requests audio access permission.
///
/// If it is the first time requesting audio access, a permission dialog will show up on the
/// screen. Otherwise AVFoundation simply returns the user's previous choice, and in this case the
/// user will have to update the choice in Settings app.
///
/// @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);
92 changes: 70 additions & 22 deletions packages/camera/camera/ios/Classes/CameraPermissionUtils.m
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,83 @@
@import AVFoundation;
#import "CameraPermissionUtils.h"

void FLTRequestCameraPermissionWithCompletionHandler(
FLTCameraPermissionRequestCompletionHandler handler) {
switch ([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]) {
void FLTRequestPermission(BOOL forAudio, FLTCameraPermissionRequestCompletionHandler handler) {
AVMediaType mediaType;
if (forAudio) {
mediaType = AVMediaTypeAudio;
} else {
mediaType = AVMediaTypeVideo;
}

switch ([AVCaptureDevice authorizationStatusForMediaType:mediaType]) {
case AVAuthorizationStatusAuthorized:
handler(nil);
break;
case AVAuthorizationStatusDenied:
handler([FlutterError errorWithCode:@"CameraAccessDeniedWithoutPrompt"
message:@"User has previously denied the camera access request. "
@"Go to Settings to enable camera access."
details:nil]);
case AVAuthorizationStatusDenied: {
FlutterError *flutterError;
if (forAudio) {
flutterError =
[FlutterError errorWithCode:@"AudioAccessDeniedWithoutPrompt"
message:@"User has previously denied the audio access request. "
@"Go to Settings to enable audio access."
details:nil];
} else {
flutterError =
[FlutterError errorWithCode:@"CameraAccessDeniedWithoutPrompt"
message:@"User has previously denied the camera access request. "
@"Go to Settings to enable camera access."
details:nil];
}
handler(flutterError);
break;
case AVAuthorizationStatusRestricted:
handler([FlutterError errorWithCode:@"CameraAccessRestricted"
message:@"Camera access is restricted. "
details:nil]);
}
case AVAuthorizationStatusRestricted: {
FlutterError *flutterError;
if (forAudio) {
flutterError = [FlutterError errorWithCode:@"AudioAccessRestricted"
message:@"Audio access is restricted. "
details:nil];
} else {
flutterError = [FlutterError errorWithCode:@"CameraAccessRestricted"
message:@"Camera access is restricted. "
details:nil];
}
handler(flutterError);
break;
}
case AVAuthorizationStatusNotDetermined: {
[AVCaptureDevice
requestAccessForMediaType:AVMediaTypeVideo
completionHandler:^(BOOL granted) {
// handler can be invoked on an arbitrary dispatch queue.
handler(granted ? nil
: [FlutterError
errorWithCode:@"CameraAccessDenied"
message:@"User denied the camera access request."
details:nil]);
}];
[AVCaptureDevice 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);
}
}];
break;
}
}
}

void FLTRequestCameraPermissionWithCompletionHandler(
FLTCameraPermissionRequestCompletionHandler handler) {
FLTRequestPermission(/*forAudio*/ NO, handler);
}

void FLTRequestAudioPermissionWithCompletionHandler(
FLTCameraPermissionRequestCompletionHandler handler) {
FLTRequestPermission(/*forAudio*/ YES, handler);
}
38 changes: 29 additions & 9 deletions packages/camera/camera/ios/Classes/CameraPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,7 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call
[result sendNotImplemented];
}
} else if ([@"create" isEqualToString:call.method]) {
FLTRequestCameraPermissionWithCompletionHandler(^(FlutterError *error) {
// Create FLTCam only if granted camera access.
if (error) {
[result sendFlutterError:error];
} else {
[self createCameraOnSessionQueueWithCreateMethodCall:call result:result];
}
});
[self handleCreateMethodCall:call result:result];
} else if ([@"startImageStream" isEqualToString:call.method]) {
[_camera startImageStreamWithMessenger:_messenger];
[result sendSuccess];
Expand Down Expand Up @@ -194,7 +187,7 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call
[_camera close];
[result sendSuccess];
} else if ([@"prepareForVideoRecording" isEqualToString:call.method]) {
[_camera setUpCaptureSessionForAudio];
[self.camera setUpCaptureSessionForAudio];
[result sendSuccess];
} else if ([@"startVideoRecording" isEqualToString:call.method]) {
[_camera startVideoRecordingWithResult:result];
Expand Down Expand Up @@ -258,6 +251,33 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call
}
}

- (void)handleCreateMethodCall:(FlutterMethodCall *)call
result:(FLTThreadSafeFlutterResult *)result {
// Create FLTCam only if granted camera access (and audio access if audio is enabled)
FLTRequestCameraPermissionWithCompletionHandler(^(FlutterError *error) {
if (error) {
[result sendFlutterError:error];
} else {
// Request audio permission on `create` call with `enableAudio` argument instead of the
// `prepareForVideoRecording` call. This is because `prepareForVideoRecording` call is
// optional, and used as a workaround to fix a missing frame issue on iOS.
BOOL audioEnabled = [call.arguments[@"enableAudio"] boolValue];
if (audioEnabled) {
// Setup audio capture session only if granted audio access.
FLTRequestAudioPermissionWithCompletionHandler(^(FlutterError *error) {
if (error) {
[result sendFlutterError:error];
} else {
[self createCameraOnSessionQueueWithCreateMethodCall:call result:result];
}
});
} else {
[self createCameraOnSessionQueueWithCreateMethodCall:call result:result];
}
}
});
}

- (void)createCameraOnSessionQueueWithCreateMethodCall:(FlutterMethodCall *)createMethodCall
result:(FLTThreadSafeFlutterResult *)result {
dispatch_async(self.captureSessionQueue, ^{
Expand Down
2 changes: 1 addition & 1 deletion packages/camera/camera/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: A Flutter plugin for controlling the camera. Supports previewing
Dart.
repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
version: 0.9.5+1
version: 0.9.6

environment:
sdk: ">=2.14.0 <3.0.0"
Expand Down