CB-12882: (ios): adds support for permissions checks for captureVideo and captureImage methods
diff --git a/package.json b/package.json
index ea4a23b..6d45d64 100644
--- a/package.json
+++ b/package.json
@@ -47,6 +47,9 @@
"license": "Apache-2.0",
"engines": {
"cordovaDependencies": {
+ ">=1.4.4": {
+ "cordova-ios": ">=4.0.0"
+ },
"2.0.0": {
"cordova": ">100"
}
diff --git a/src/ios/CDVCapture.h b/src/ios/CDVCapture.h
index 2cd8db8..c7433a4 100644
--- a/src/ios/CDVCapture.h
+++ b/src/ios/CDVCapture.h
@@ -28,6 +28,7 @@
CAPTURE_APPLICATION_BUSY = 1,
CAPTURE_INVALID_ARGUMENT = 2,
CAPTURE_NO_MEDIA_FILES = 3,
+ CAPTURE_PERMISSION_DENIED = 4,
CAPTURE_NOT_SUPPORTED = 20
};
typedef NSUInteger CDVCaptureError;
diff --git a/src/ios/CDVCapture.m b/src/ios/CDVCapture.m
index 53e1b2b..7ab0832 100644
--- a/src/ios/CDVCapture.m
+++ b/src/ios/CDVCapture.m
@@ -58,17 +58,17 @@
- (BOOL)prefersStatusBarHidden {
return YES;
}
-
+
- (UIViewController*)childViewControllerForStatusBarHidden {
return nil;
}
-
+
- (void)viewWillAppear:(BOOL)animated {
SEL sel = NSSelectorFromString(@"setNeedsStatusBarAppearanceUpdate");
if ([self respondsToSelector:sel]) {
[self performSelector:sel withObject:nil afterDelay:0];
}
-
+
[super viewWillAppear:animated];
}
@@ -141,6 +141,7 @@
pickerController = [[CDVImagePicker alloc] init];
}
+ [self showAlertIfAccessProhibited];
pickerController.delegate = self;
pickerController.sourceType = UIImagePickerControllerSourceTypeCamera;
pickerController.allowsEditing = NO;
@@ -247,6 +248,8 @@
[self.commandDelegate sendPluginResult:result callbackId:callbackId];
pickerController = nil;
} else {
+ [self showAlertIfAccessProhibited];
+
pickerController.delegate = self;
pickerController.sourceType = UIImagePickerControllerSourceTypeCamera;
pickerController.allowsEditing = NO;
@@ -292,6 +295,47 @@
return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:fileArray];
}
+- (void)showAlertIfAccessProhibited
+{
+ if (![self hasCameraAccess]) {
+ [self showPermissionsAlert];
+ }
+}
+
+- (BOOL)hasCameraAccess
+{
+ AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
+
+ return status != AVAuthorizationStatusDenied && status != AVAuthorizationStatusRestricted;
+}
+
+- (void)showPermissionsAlert
+{
+ __weak CDVCapture* weakSelf = self;
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [[[UIAlertView alloc] initWithTitle:[[NSBundle mainBundle]
+ objectForInfoDictionaryKey:@"CFBundleDisplayName"]
+ message:NSLocalizedString(@"Access to the camera has been prohibited; please enable it in the Settings app to continue.", nil)
+ delegate:weakSelf
+ cancelButtonTitle:NSLocalizedString(@"OK", nil)
+ otherButtonTitles:NSLocalizedString(@"Settings", nil), nil] show];
+ });
+}
+
+- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
+{
+ if (buttonIndex == 1) {
+ [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
+ }
+
+ CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageToErrorObject:CAPTURE_PERMISSION_DENIED];
+
+ [[pickerController presentingViewController] dismissViewControllerAnimated:YES completion:nil];
+ [self.commandDelegate sendPluginResult:result callbackId:pickerController.callbackId];
+ pickerController = nil;
+ self.inUse = NO;
+}
+
- (void)getMediaModes:(CDVInvokedUrlCommand*)command
{
// NSString* callbackId = [command argumentAtIndex:0];
@@ -337,7 +381,7 @@
movieArray ? (NSObject*) movieArray:[NSNull null], @"video",
audioArray ? (NSObject*) audioArray:[NSNull null], @"audio",
nil];
-
+
NSData* jsonData = [NSJSONSerialization dataWithJSONObject:modes options:0 error:nil];
NSString* jsonStr = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
@@ -608,7 +652,7 @@
if ([self respondsToSelector:@selector(edgesForExtendedLayout)]) {
self.edgesForExtendedLayout = UIRectEdgeNone;
}
-
+
// create view and display
CGRect viewRect = [[UIScreen mainScreen] applicationFrame];
UIView* tmp = [[UIView alloc] initWithFrame:viewRect];
@@ -737,7 +781,7 @@
{
UIInterfaceOrientationMask orientation = UIInterfaceOrientationMaskPortrait;
UIInterfaceOrientationMask supported = [captureCommand.viewController supportedInterfaceOrientations];
-
+
orientation = orientation | (supported & UIInterfaceOrientationMaskPortraitUpsideDown);
return orientation;
}
@@ -746,7 +790,7 @@
{
NSUInteger orientation = UIInterfaceOrientationMaskPortrait; // must support portrait
NSUInteger supported = [captureCommand.viewController supportedInterfaceOrientations];
-
+
orientation = orientation | (supported & UIInterfaceOrientationMaskPortraitUpsideDown);
return orientation;
}
@@ -773,7 +817,7 @@
__block NSError* error = nil;
__weak CDVAudioRecorderViewController* weakSelf = self;
-
+
void (^startRecording)(void) = ^{
[weakSelf.avSession setCategory:AVAudioSessionCategoryRecord error:&error];
[weakSelf.avSession setActive:YES error:&error];
@@ -794,7 +838,7 @@
}
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
};
-
+
SEL rrpSel = NSSelectorFromString(@"requestRecordPermission:");
if ([self.avSession respondsToSelector:rrpSel])
{
diff --git a/tests/tests.js b/tests/tests.js
index fd2f60b..9434fc1 100644
--- a/tests/tests.js
+++ b/tests/tests.js
@@ -243,6 +243,28 @@
navigator.device.capture.captureVideo(captureVideoWin, captureVideoFail, options);
}
+ function permissionWasNotAllowed() {
+ log('Media has been captured. Have you forgotten to disallow camera for this app?');
+ }
+
+ function catchPermissionError(error) {
+ if (CaptureError.CAPTURE_PERMISSION_DENIED === error.code) {
+ log('Sucess: permission error has been detected!');
+ } else {
+ log('Error: another error with code: ' + error.code);
+ }
+ }
+
+ function getVideoPermissionError() {
+ var options = { limit: 1, duration: 10 };
+ navigator.device.capture.captureVideo(permissionWasNotAllowed, catchPermissionError, options);
+ }
+
+ function getImagePermissionError() {
+ var options = { limit: 1 };
+ navigator.device.capture.captureImage(permissionWasNotAllowed, catchPermissionError, options);
+ }
+
function resolveMediaFileURL(mediaFile, callback) {
resolveLocalFileSystemURL(mediaFile.localURL, function (entry) {
log("Resolved by URL: " + mediaFile.localURL);
@@ -300,7 +322,11 @@
'<p/> <div id="video"></div>' +
'Expected result: Record 10 second video. Status box will update with video file that you can play.' +
'<p/> <div id="video_and_resolve"></div>' +
- 'Expected result: Record 5 second video. Status box will show that URL was resolved and video will get added at the bottom of the status box for playback.';
+ 'Expected result: Record 5 second video. Status box will show that URL was resolved and video will get added at the bottom of the status box for playback.' +
+ '<p/> <div id="prohibited_camera_video"></div>' +
+ 'Expected result (iOS only): camera picker and alert with message that camera access is prohibited are shown. The alert has 2 buttons: OK and Settings. By click on "OK" camera is hidden, by pressing Settings it shows privacy settings for the app' +
+ '<p/> <div id="prohibited_camera_image"></div>' +
+ 'Expected result (iOS only): camera picker and alert with message that camera access is prohibited are shown. The alert has 2 buttons: OK and Settings. By click on "OK" camera is hidden, by pressing Settings it shows privacy settings for the app';
createActionButton('Capture 10 sec of audio and play', function () {
getAudio();
@@ -321,4 +347,12 @@
createActionButton('Capture 5 sec of video and resolve', function () {
resolveVideo();
}, 'video_and_resolve');
+
+ createActionButton('Disable access to Camera and click to capture video', function() {
+ getVideoPermissionError();
+ }, 'prohibited_camera_video');
+
+ createActionButton('Disable access to Camera and click to capture image', function() {
+ getImagePermissionError();
+ }, 'prohibited_camera_image');
};