diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 9ac095887..f882b863b 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -373,7 +373,7 @@ E1370BE518A0686C00826894 /* r.caf in Resources */ = {isa = PBXBuildFile; fileRef = E18AB40C18A05754001A532A /* r.caf */; }; E1370BE618A0686C00826894 /* sonarping.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = E18AB40D18A05754001A532A /* sonarping.mp3 */; }; E32B0699162419B7046BC643 /* libPods-Signal.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7DB8EE72F8522189E3E2CB45 /* libPods-Signal.a */; }; - EF764C351DB67CC5000D9A87 /* UIViewController+CameraPermissions.m in Sources */ = {isa = PBXBuildFile; fileRef = EF764C341DB67CC5000D9A87 /* UIViewController+CameraPermissions.m */; }; + EF764C351DB67CC5000D9A87 /* UIViewController+Permissions.m in Sources */ = {isa = PBXBuildFile; fileRef = EF764C341DB67CC5000D9A87 /* UIViewController+Permissions.m */; }; FC3BD9881A30A790005B96BB /* Social.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC3BD9871A30A790005B96BB /* Social.framework */; }; FC5CDF391A3393DD00B47253 /* error_white@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = FC5CDF371A3393DD00B47253 /* error_white@2x.png */; }; FC5CDF3A1A3393DD00B47253 /* warning_white@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = FC5CDF381A3393DD00B47253 /* warning_white@2x.png */; }; @@ -870,8 +870,8 @@ E18AB40D18A05754001A532A /* sonarping.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = sonarping.mp3; sourceTree = ""; }; E1A0AD8B16E13FDD0071E604 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; E85DB184824BA9DC302EC8B3 /* Pods-SignalTests.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalTests.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SignalTests/Pods-SignalTests.app store release.xcconfig"; sourceTree = ""; }; - EF764C331DB67CC5000D9A87 /* UIViewController+CameraPermissions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIViewController+CameraPermissions.h"; path = "util/UIViewController+CameraPermissions.h"; sourceTree = ""; }; - EF764C341DB67CC5000D9A87 /* UIViewController+CameraPermissions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIViewController+CameraPermissions.m"; path = "util/UIViewController+CameraPermissions.m"; sourceTree = ""; }; + EF764C331DB67CC5000D9A87 /* UIViewController+Permissions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIViewController+Permissions.h"; path = "util/UIViewController+Permissions.h"; sourceTree = ""; }; + EF764C341DB67CC5000D9A87 /* UIViewController+Permissions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIViewController+Permissions.m"; path = "util/UIViewController+Permissions.m"; sourceTree = ""; }; FC3BD9871A30A790005B96BB /* Social.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Social.framework; path = System/Library/Frameworks/Social.framework; sourceTree = SDKROOT; }; FC5CDF371A3393DD00B47253 /* error_white@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "error_white@2x.png"; sourceTree = ""; }; FC5CDF381A3393DD00B47253 /* warning_white@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "warning_white@2x.png"; sourceTree = ""; }; @@ -1766,24 +1766,24 @@ FCFA64B11A24F29E0007FB87 /* UI Categories */ = { isa = PBXGroup; children = ( + 45638BDE1F3DDB2200128435 /* MessageSender+Promise.swift */, + 450449371F45EE7D002D1ADA /* NSString+OWS.h */, + 450449381F45EE7D002D1ADA /* NSString+OWS.m */, + 45C0DC1A1E68FE9000E04C47 /* UIApplication+OWS.swift */, FCFA64B21A24F3880007FB87 /* UIColor+OWS.h */, FCFA64B31A24F3880007FB87 /* UIColor+OWS.m */, + 45BB93371E688E14001E3939 /* UIDevice+featureSupport.swift */, FCFA64B51A24F6730007FB87 /* UIFont+OWS.h */, FCFA64B61A24F6730007FB87 /* UIFont+OWS.m */, B68112E81A4D9EC400BA82FF /* UIImage+OWS.h */, B68112E91A4D9EC400BA82FF /* UIImage+OWS.m */, + 45C0DC1D1E69011F00E04C47 /* UIStoryboard+OWS.swift */, 34535D801E256BE9008A4747 /* UIView+OWS.h */, 34535D811E256BE9008A4747 /* UIView+OWS.m */, - EF764C331DB67CC5000D9A87 /* UIViewController+CameraPermissions.h */, - EF764C341DB67CC5000D9A87 /* UIViewController+CameraPermissions.m */, 344F2F651E57A932000D9322 /* UIViewController+OWS.h */, 344F2F661E57A932000D9322 /* UIViewController+OWS.m */, - 45BB93371E688E14001E3939 /* UIDevice+featureSupport.swift */, - 45C0DC1A1E68FE9000E04C47 /* UIApplication+OWS.swift */, - 45C0DC1D1E69011F00E04C47 /* UIStoryboard+OWS.swift */, - 45638BDE1F3DDB2200128435 /* MessageSender+Promise.swift */, - 450449371F45EE7D002D1ADA /* NSString+OWS.h */, - 450449381F45EE7D002D1ADA /* NSString+OWS.m */, + EF764C331DB67CC5000D9A87 /* UIViewController+Permissions.h */, + EF764C341DB67CC5000D9A87 /* UIViewController+Permissions.m */, ); name = "UI Categories"; path = ..; @@ -2213,7 +2213,7 @@ 4505C2BF1E648EA300CEBF41 /* ExperienceUpgrade.swift in Sources */, 34CCAF381F0C0599004084F4 /* AppUpdateNag.m in Sources */, 45387B041E36D650005D00B3 /* OWS102MoveLoggingPreferenceToUserDefaults.m in Sources */, - EF764C351DB67CC5000D9A87 /* UIViewController+CameraPermissions.m in Sources */, + EF764C351DB67CC5000D9A87 /* UIViewController+Permissions.m in Sources */, 45CD81EF1DC030E7004C9430 /* AccountManager.swift in Sources */, 45794E861E00620000066731 /* CallUIAdapter.swift in Sources */, 4585C4681ED8F8D200896AEA /* SafetyNumberConfirmationAlert.swift in Sources */, diff --git a/Signal/src/Signal-Bridging-Header.h b/Signal/src/Signal-Bridging-Header.h index 1e2d2ecc1..5abd45338 100644 --- a/Signal/src/Signal-Bridging-Header.h +++ b/Signal/src/Signal-Bridging-Header.h @@ -39,6 +39,7 @@ #import "UIImage+OWS.h" #import "UIUtil.h" #import "UIView+OWS.h" +#import "UIViewController+Permissions.h" #import "ViewControllerUtils.h" #import #import diff --git a/Signal/src/ViewControllers/AttachmentSharing.m b/Signal/src/ViewControllers/AttachmentSharing.m index cf4f3f458..824f4f558 100644 --- a/Signal/src/ViewControllers/AttachmentSharing.m +++ b/Signal/src/ViewControllers/AttachmentSharing.m @@ -4,8 +4,8 @@ #import "AttachmentSharing.h" #import "TSAttachmentStream.h" -#import "Threading.h" #import "UIUtil.h" +#import @implementation AttachmentSharing diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 0a7a864ec..4cf4b489f 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -48,8 +48,8 @@ #import "ThreadUtil.h" #import "UIFont+OWS.h" #import "UIUtil.h" -#import "UIViewController+CameraPermissions.h" #import "UIViewController+OWS.h" +#import "UIViewController+Permissions.h" #import "ViewControllerUtils.h" #import #import @@ -2473,7 +2473,10 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) { - (void)takePictureOrVideo { - [self ows_askForCameraPermissions:^{ + [self ows_askForCameraPermissions:^(BOOL granted) { + if (!granted) { + return; + } UIImagePickerController *picker = [[UIImagePickerController alloc] init]; picker.sourceType = UIImagePickerControllerSourceTypeCamera; picker.mediaTypes = @[ (__bridge NSString *)kUTTypeImage, (__bridge NSString *)kUTTypeMovie ]; @@ -3048,27 +3051,25 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) { self.voiceMessageUUID = voiceMessageUUID; __weak typeof(self) weakSelf = self; - [[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted) { - dispatch_async(dispatch_get_main_queue(), ^{ - __strong typeof(self) strongSelf = weakSelf; - if (!strongSelf) { - return; - } + [self ows_askForMicrophonePermissions:^(BOOL granted) { + __strong typeof(self) strongSelf = weakSelf; + if (!strongSelf) { + return; + } - if (strongSelf.voiceMessageUUID != voiceMessageUUID) { - // This voice message recording has been cancelled - // before recording could begin. - return; - } + if (strongSelf.voiceMessageUUID != voiceMessageUUID) { + // This voice message recording has been cancelled + // before recording could begin. + return; + } - if (granted) { - [strongSelf startRecordingVoiceMemo]; - } else { - DDLogInfo(@"%@ we do not have recording permission.", self.logTag); - [strongSelf cancelVoiceMemo]; - [OWSAlerts showNoMicrophonePermissionAlert]; - } - }); + if (granted) { + [strongSelf startRecordingVoiceMemo]; + } else { + DDLogInfo(@"%@ we do not have recording permission.", self.logTag); + [strongSelf cancelVoiceMemo]; + [OWSAlerts showNoMicrophonePermissionAlert]; + } }]; } diff --git a/Signal/src/ViewControllers/FingerprintViewScanController.m b/Signal/src/ViewControllers/FingerprintViewScanController.m index 594ee46c9..64ad54225 100644 --- a/Signal/src/ViewControllers/FingerprintViewScanController.m +++ b/Signal/src/ViewControllers/FingerprintViewScanController.m @@ -11,7 +11,7 @@ #import "UIFont+OWS.h" #import "UIUtil.h" #import "UIView+OWS.h" -#import "UIViewController+CameraPermissions.h" +#import "UIViewController+Permissions.h" #import #import #import @@ -118,19 +118,19 @@ NS_ASSUME_NONNULL_BEGIN { [super viewDidAppear:animated]; - [self ows_askForCameraPermissions:^{ + [self ows_askForCameraPermissions:^(BOOL granted) { + if (granted) { + // Camera stops capturing when "sharing" while in capture mode. + // Also, it's less obvious whats being "shared" at this point, + // so just disable sharing when in capture mode. - // Camera stops capturing when "sharing" while in capture mode. - // Also, it's less obvious whats being "shared" at this point, - // so just disable sharing when in capture mode. + DDLogInfo(@"%@ Showing Scanner", self.logTag); - DDLogInfo(@"%@ Showing Scanner", self.logTag); - - [self.qrScanningController startCapture]; - } - failureCallback:^{ + [self.qrScanningController startCapture]; + } else { [self.navigationController popViewControllerAnimated:YES]; - }]; + } + }]; } #pragma mark - OWSQRScannerDelegate diff --git a/Signal/src/ViewControllers/OWSLinkedDevicesTableViewController.m b/Signal/src/ViewControllers/OWSLinkedDevicesTableViewController.m index 5ea8c9ede..ebd4490e5 100644 --- a/Signal/src/ViewControllers/OWSLinkedDevicesTableViewController.m +++ b/Signal/src/ViewControllers/OWSLinkedDevicesTableViewController.m @@ -6,7 +6,7 @@ #import "OWSDeviceTableViewCell.h" #import "OWSLinkDeviceViewController.h" #import "Signal-Swift.h" -#import "UIViewController+CameraPermissions.h" +#import "UIViewController+Permissions.h" #import #import #import @@ -256,7 +256,10 @@ int const OWSLinkedDevicesTableViewControllerSectionAddDevice = 1; if (indexPath.section == OWSLinkedDevicesTableViewControllerSectionAddDevice) { - [self ows_askForCameraPermissions:^{ + [self ows_askForCameraPermissions:^(BOOL granted) { + if (!granted) { + return; + } [self performSegueWithIdentifier:@"LinkDeviceSegue" sender:self]; }]; } diff --git a/Signal/src/call/CallService.swift b/Signal/src/call/CallService.swift index cf7016cd4..8223d4549 100644 --- a/Signal/src/call/CallService.swift +++ b/Signal/src/call/CallService.swift @@ -154,7 +154,7 @@ protocol CallServiceObserver: class { Logger.debug("\(self.TAG) .call setter: \(oldValue?.identifiersForLogs as Optional) -> \(call?.identifiersForLogs as Optional)") for observer in observers { - observer.value?.didUpdateCall(call:call) + observer.value?.didUpdateCall(call: call) } } } @@ -227,13 +227,13 @@ protocol CallServiceObserver: class { self.createCallUIAdapter() NotificationCenter.default.addObserver(self, - selector:#selector(didEnterBackground), - name:NSNotification.Name.UIApplicationDidEnterBackground, - object:nil) + selector: #selector(didEnterBackground), + name: NSNotification.Name.UIApplicationDidEnterBackground, + object: nil) NotificationCenter.default.addObserver(self, - selector:#selector(didBecomeActive), - name:NSNotification.Name.UIApplicationDidBecomeActive, - object:nil) + selector: #selector(didBecomeActive), + name: NSNotification.Name.UIApplicationDidBecomeActive, + object: nil) } deinit { @@ -275,8 +275,8 @@ protocol CallServiceObserver: class { let errorDescription = "\(TAG) call was unexpectedly already set." Logger.error(errorDescription) call.state = .localFailure - OWSProdError(OWSAnalyticsEvents.callServiceCallAlreadySet(), file:#file, function:#function, line:#line) - return Promise(error: CallError.assertionError(description:errorDescription)) + OWSProdError(OWSAnalyticsEvents.callServiceCallAlreadySet(), file: #file, function: #function, line: #line) + return Promise(error: CallError.assertionError(description: errorDescription)) } self.call = call @@ -292,14 +292,14 @@ protocol CallServiceObserver: class { Logger.debug("\(self.TAG) got ice servers:\(iceServers) for call: \(call.identifiersForLogs)") guard self.call == call else { - throw CallError.obsoleteCall(description:"obsolete call in \(#function)") + throw CallError.obsoleteCall(description: "obsolete call in \(#function)") } guard self.peerConnectionClient == nil else { let errorDescription = "\(self.TAG) peerconnection was unexpectedly already set." Logger.error(errorDescription) - OWSProdError(OWSAnalyticsEvents.callServicePeerConnectionAlreadySet(), file:#file, function:#function, line:#line) - throw CallError.assertionError(description:errorDescription) + OWSProdError(OWSAnalyticsEvents.callServicePeerConnectionAlreadySet(), file: #file, function: #function, line: #line) + throw CallError.assertionError(description: errorDescription) } let useTurnOnly = Environment.getCurrent().preferences.doCallsHideIPAddress() @@ -312,11 +312,11 @@ protocol CallServiceObserver: class { return peerConnectionClient.createOffer() }.then { (sessionDescription: HardenedRTCSessionDescription) -> Promise in guard self.call == call else { - throw CallError.obsoleteCall(description:"obsolete call in \(#function)") + throw CallError.obsoleteCall(description: "obsolete call in \(#function)") } guard let peerConnectionClient = self.peerConnectionClient else { owsFail("Missing peerConnectionClient in \(#function)") - throw CallError.obsoleteCall(description:"Missing peerConnectionClient in \(#function)") + throw CallError.obsoleteCall(description: "Missing peerConnectionClient in \(#function)") } return peerConnectionClient.setLocalSessionDescription(sessionDescription).then { @@ -326,7 +326,7 @@ protocol CallServiceObserver: class { } }.then { guard self.call == call else { - throw CallError.obsoleteCall(description:"obsolete call in \(#function)") + throw CallError.obsoleteCall(description: "obsolete call in \(#function)") } // For outgoing calls, wait until call offer is sent before we send any ICE updates, to ensure message ordering for @@ -340,7 +340,7 @@ protocol CallServiceObserver: class { // Don't let the outgoing call ring forever. We don't support inbound ringing forever anyway. let timeout: Promise = after(interval: connectingTimeoutSeconds).then { () -> Void in // rejecting a promise by throwing is safely a no-op if the promise has already been fulfilled - OWSProdInfo(OWSAnalyticsEvents.callServiceErrorTimeoutWhileConnectingOutgoing(), file:#file, function:#function, line:#line) + OWSProdInfo(OWSAnalyticsEvents.callServiceErrorTimeoutWhileConnectingOutgoing(), file: #file, function: #function, line: #line) throw CallError.timeout(description: "timed out waiting to receive call answer") } @@ -353,10 +353,10 @@ protocol CallServiceObserver: class { Logger.error("\(self.TAG) placing call \(call.identifiersForLogs) failed with error: \(error)") if let callError = error as? CallError { - OWSProdInfo(OWSAnalyticsEvents.callServiceErrorOutgoingConnectionFailedInternal(), file:#file, function:#function, line:#line) + OWSProdInfo(OWSAnalyticsEvents.callServiceErrorOutgoingConnectionFailedInternal(), file: #file, function: #function, line: #line) self.handleFailedCall(failedCall: call, error: callError) } else { - OWSProdInfo(OWSAnalyticsEvents.callServiceErrorOutgoingConnectionFailedExternal(), file:#file, function:#function, line:#line) + OWSProdInfo(OWSAnalyticsEvents.callServiceErrorOutgoingConnectionFailedExternal(), file: #file, function: #function, line: #line) let externalError = CallError.externalError(underlyingError: error) self.handleFailedCall(failedCall: call, error: externalError) } @@ -378,8 +378,8 @@ protocol CallServiceObserver: class { } guard let fulfillReadyToSendIceUpdatesPromise = self.fulfillReadyToSendIceUpdatesPromise else { - OWSProdError(OWSAnalyticsEvents.callServiceMissingFulfillReadyToSendIceUpdatesPromise(), file:#file, function:#function, line:#line) - self.handleFailedCall(failedCall: call, error: CallError.assertionError(description:"failed to create fulfillReadyToSendIceUpdatesPromise")) + OWSProdError(OWSAnalyticsEvents.callServiceMissingFulfillReadyToSendIceUpdatesPromise(), file: #file, function: #function, line: #line) + self.handleFailedCall(failedCall: call, error: CallError.assertionError(description: "failed to create fulfillReadyToSendIceUpdatesPromise")) return } @@ -417,8 +417,8 @@ protocol CallServiceObserver: class { } guard let peerConnectionClient = self.peerConnectionClient else { - OWSProdError(OWSAnalyticsEvents.callServicePeerConnectionMissing(), file:#file, function:#function, line:#line) - handleFailedCall(failedCall: call, error: CallError.assertionError(description:"peerConnectionClient was unexpectedly nil in \(#function)")) + OWSProdError(OWSAnalyticsEvents.callServicePeerConnectionMissing(), file: #file, function: #function, line: #line) + handleFailedCall(failedCall: call, error: CallError.assertionError(description: "peerConnectionClient was unexpectedly nil in \(#function)")) return } @@ -427,10 +427,10 @@ protocol CallServiceObserver: class { Logger.debug("\(self.TAG) successfully set remote description") }.catch { error in if let callError = error as? CallError { - OWSProdInfo(OWSAnalyticsEvents.callServiceErrorHandleReceivedErrorInternal(), file:#file, function:#function, line:#line) + OWSProdInfo(OWSAnalyticsEvents.callServiceErrorHandleReceivedErrorInternal(), file: #file, function: #function, line: #line) self.handleFailedCall(failedCall: call, error: callError) } else { - OWSProdInfo(OWSAnalyticsEvents.callServiceErrorHandleReceivedErrorExternal(), file:#file, function:#function, line:#line) + OWSProdInfo(OWSAnalyticsEvents.callServiceErrorHandleReceivedErrorExternal(), file: #file, function: #function, line: #line) let externalError = CallError.externalError(underlyingError: error) self.handleFailedCall(failedCall: call, error: externalError) } @@ -567,7 +567,7 @@ protocol CallServiceObserver: class { case .idle, .dialing, .remoteRinging: // If both users are trying to call each other at the same time, // both should see busy. - handleRemoteBusy(thread:existingCall.thread, callId:existingCall.signalingId) + handleRemoteBusy(thread: existingCall.thread, callId: existingCall.signalingId) case .answering, .localRinging, .connected, .localFailure, .localHangup, .remoteHangup, .remoteBusy: // If one user calls another while the other has a "vestigial" call with // that same user, fail the old call. @@ -644,7 +644,7 @@ protocol CallServiceObserver: class { let timeout: Promise = after(interval: connectingTimeoutSeconds).then { () -> Void in // rejecting a promise by throwing is safely a no-op if the promise has already been fulfilled - OWSProdInfo(OWSAnalyticsEvents.callServiceErrorTimeoutWhileConnectingIncoming(), file:#file, function:#function, line:#line) + OWSProdInfo(OWSAnalyticsEvents.callServiceErrorTimeoutWhileConnectingIncoming(), file: #file, function: #function, line: #line) throw CallError.timeout(description: "timed out waiting for call to connect") } @@ -663,10 +663,10 @@ protocol CallServiceObserver: class { return } if let callError = error as? CallError { - OWSProdInfo(OWSAnalyticsEvents.callServiceErrorIncomingConnectionFailedInternal(), file:#file, function:#function, line:#line) + OWSProdInfo(OWSAnalyticsEvents.callServiceErrorIncomingConnectionFailedInternal(), file: #file, function: #function, line: #line) self.handleFailedCall(failedCall: newCall, error: callError) } else { - OWSProdInfo(OWSAnalyticsEvents.callServiceErrorIncomingConnectionFailedExternal(), file:#file, function:#function, line:#line) + OWSProdInfo(OWSAnalyticsEvents.callServiceErrorIncomingConnectionFailedExternal(), file: #file, function: #function, line: #line) let externalError = CallError.externalError(underlyingError: error) self.handleFailedCall(failedCall: newCall, error: externalError) } @@ -706,7 +706,7 @@ protocol CallServiceObserver: class { peerConnectionClient.addRemoteIceCandidate(RTCIceCandidate(sdp: sdp, sdpMLineIndex: lineIndex, sdpMid: mid)) }.catch { error in - OWSProdInfo(OWSAnalyticsEvents.callServiceErrorHandleRemoteAddedIceCandidate(), file:#file, function:#function, line:#line) + OWSProdInfo(OWSAnalyticsEvents.callServiceErrorHandleRemoteAddedIceCandidate(), file: #file, function: #function, line: #line) Logger.error("\(self.TAG) in \(#function) waitForPeerConnectionClient failed with error: \(error)") }.retainUntilComplete() } @@ -719,8 +719,8 @@ protocol CallServiceObserver: class { AssertIsOnMainThread() guard let call = self.call else { - OWSProdError(OWSAnalyticsEvents.callServiceCallMissing(), file:#file, function:#function, line:#line) - self.handleFailedCurrentCall(error: CallError.assertionError(description:"ignoring local ice candidate, since there is no current call.")) + OWSProdError(OWSAnalyticsEvents.callServiceCallMissing(), file: #file, function: #function, line: #line) + self.handleFailedCurrentCall(error: CallError.assertionError(description: "ignoring local ice candidate, since there is no current call.")) return } @@ -735,8 +735,8 @@ protocol CallServiceObserver: class { guard call.state != .idle else { // This will only be called for the current peerConnectionClient, so // fail the current call. - OWSProdError(OWSAnalyticsEvents.callServiceCallUnexpectedlyIdle(), file:#file, function:#function, line:#line) - self.handleFailedCurrentCall(error: CallError.assertionError(description:"ignoring local ice candidate, since call is now idle.")) + OWSProdError(OWSAnalyticsEvents.callServiceCallUnexpectedlyIdle(), file: #file, function: #function, line: #line) + self.handleFailedCurrentCall(error: CallError.assertionError(description: "ignoring local ice candidate, since call is now idle.")) return } @@ -756,7 +756,7 @@ protocol CallServiceObserver: class { return } }.catch { error in - OWSProdInfo(OWSAnalyticsEvents.callServiceErrorHandleLocalAddedIceCandidate(), file:#file, function:#function, line:#line) + OWSProdInfo(OWSAnalyticsEvents.callServiceErrorHandleLocalAddedIceCandidate(), file: #file, function: #function, line: #line) Logger.error("\(self.TAG) in \(#function) waitUntilReadyToSendIceUpdates failed with error: \(error)") }.retainUntilComplete() } @@ -773,8 +773,8 @@ protocol CallServiceObserver: class { guard let call = self.call else { // This will only be called for the current peerConnectionClient, so // fail the current call. - OWSProdError(OWSAnalyticsEvents.callServiceCallMissing(), file:#file, function:#function, line:#line) - handleFailedCurrentCall(error: CallError.assertionError(description:"\(TAG) ignoring \(#function) since there is no current call.")) + OWSProdError(OWSAnalyticsEvents.callServiceCallMissing(), file: #file, function: #function, line: #line) + handleFailedCurrentCall(error: CallError.assertionError(description: "\(TAG) ignoring \(#function) since there is no current call.")) return } @@ -848,16 +848,16 @@ protocol CallServiceObserver: class { guard let call = self.call else { // This should never happen; return to a known good state. owsFail("\(TAG) call was unexpectedly nil in \(#function)") - OWSProdError(OWSAnalyticsEvents.callServiceCallMissing(), file:#file, function:#function, line:#line) - handleFailedCurrentCall(error: CallError.assertionError(description:"\(TAG) call was unexpectedly nil in \(#function)")) + OWSProdError(OWSAnalyticsEvents.callServiceCallMissing(), file: #file, function: #function, line: #line) + handleFailedCurrentCall(error: CallError.assertionError(description: "\(TAG) call was unexpectedly nil in \(#function)")) return } guard call.localId == localId else { // This should never happen; return to a known good state. owsFail("\(TAG) callLocalId:\(localId) doesn't match current calls: \(call.localId)") - OWSProdError(OWSAnalyticsEvents.callServiceCallIdMismatch(), file:#file, function:#function, line:#line) - handleFailedCurrentCall(error: CallError.assertionError(description:"\(TAG) callLocalId:\(localId) doesn't match current calls: \(call.localId)")) + OWSProdError(OWSAnalyticsEvents.callServiceCallIdMismatch(), file: #file, function: #function, line: #line) + handleFailedCurrentCall(error: CallError.assertionError(description: "\(TAG) callLocalId:\(localId) doesn't match current calls: \(call.localId)")) return } @@ -873,8 +873,8 @@ protocol CallServiceObserver: class { Logger.debug("\(TAG) in \(#function)") guard let currentCall = self.call else { - OWSProdError(OWSAnalyticsEvents.callServiceCallMissing(), file:#file, function:#function, line:#line) - handleFailedCall(failedCall: call, error: CallError.assertionError(description:"\(TAG) ignoring \(#function) since there is no current call")) + OWSProdError(OWSAnalyticsEvents.callServiceCallMissing(), file: #file, function: #function, line: #line) + handleFailedCall(failedCall: call, error: CallError.assertionError(description: "\(TAG) ignoring \(#function) since there is no current call")) return } @@ -886,8 +886,8 @@ protocol CallServiceObserver: class { } guard let peerConnectionClient = self.peerConnectionClient else { - OWSProdError(OWSAnalyticsEvents.callServicePeerConnectionMissing(), file:#file, function:#function, line:#line) - handleFailedCall(failedCall: call, error: CallError.assertionError(description:"\(TAG) missing peerconnection client in \(#function)")) + OWSProdError(OWSAnalyticsEvents.callServicePeerConnectionMissing(), file: #file, function: #function, line: #line) + handleFailedCall(failedCall: call, error: CallError.assertionError(description: "\(TAG) missing peerconnection client in \(#function)")) return } @@ -898,7 +898,7 @@ protocol CallServiceObserver: class { call.callRecord = callRecord let message = DataChannelMessage.forConnected(callId: call.signalingId) - peerConnectionClient.sendDataChannelMessage(data: message.asData(), description:"connected", isCritical:true) + peerConnectionClient.sendDataChannelMessage(data: message.asData(), description: "connected", isCritical: true) handleConnectedCall(call) } @@ -912,8 +912,8 @@ protocol CallServiceObserver: class { AssertIsOnMainThread() guard let peerConnectionClient = self.peerConnectionClient else { - OWSProdError(OWSAnalyticsEvents.callServicePeerConnectionMissing(), file:#file, function:#function, line:#line) - handleFailedCall(failedCall: call, error: CallError.assertionError(description:"\(TAG) peerConnectionClient unexpectedly nil in \(#function)")) + OWSProdError(OWSAnalyticsEvents.callServicePeerConnectionMissing(), file: #file, function: #function, line: #line) + handleFailedCall(failedCall: call, error: CallError.assertionError(description: "\(TAG) peerConnectionClient unexpectedly nil in \(#function)")) return } @@ -943,16 +943,16 @@ protocol CallServiceObserver: class { guard let call = self.call else { // This should never happen; return to a known good state. owsFail("\(TAG) call was unexpectedly nil in \(#function)") - OWSProdError(OWSAnalyticsEvents.callServiceCallMissing(), file:#file, function:#function, line:#line) - handleFailedCurrentCall(error: CallError.assertionError(description:"\(TAG) call was unexpectedly nil in \(#function)")) + OWSProdError(OWSAnalyticsEvents.callServiceCallMissing(), file: #file, function: #function, line: #line) + handleFailedCurrentCall(error: CallError.assertionError(description: "\(TAG) call was unexpectedly nil in \(#function)")) return } guard call.localId == localId else { // This should never happen; return to a known good state. owsFail("\(TAG) callLocalId:\(localId) doesn't match current calls: \(call.localId)") - OWSProdError(OWSAnalyticsEvents.callServiceCallIdMismatch(), file:#file, function:#function, line:#line) - handleFailedCurrentCall(error: CallError.assertionError(description:"\(TAG) callLocalId:\(localId) doesn't match current calls: \(call.localId)")) + OWSProdError(OWSAnalyticsEvents.callServiceCallIdMismatch(), file: #file, function: #function, line: #line) + handleFailedCurrentCall(error: CallError.assertionError(description: "\(TAG) callLocalId:\(localId) doesn't match current calls: \(call.localId)")) return } @@ -991,14 +991,14 @@ protocol CallServiceObserver: class { AssertIsOnMainThread() guard let currentCall = self.call else { - OWSProdError(OWSAnalyticsEvents.callServiceCallMissing(), file:#file, function:#function, line:#line) - handleFailedCall(failedCall: call, error: CallError.assertionError(description:"\(TAG) ignoring \(#function) since there is no current call")) + OWSProdError(OWSAnalyticsEvents.callServiceCallMissing(), file: #file, function: #function, line: #line) + handleFailedCall(failedCall: call, error: CallError.assertionError(description: "\(TAG) ignoring \(#function) since there is no current call")) return } guard call == currentCall else { - OWSProdError(OWSAnalyticsEvents.callServiceCallMismatch(), file:#file, function:#function, line:#line) - handleFailedCall(failedCall: call, error: CallError.assertionError(description:"\(TAG) ignoring \(#function) for call other than current call")) + OWSProdError(OWSAnalyticsEvents.callServiceCallMismatch(), file: #file, function: #function, line: #line) + handleFailedCall(failedCall: call, error: CallError.assertionError(description: "\(TAG) ignoring \(#function) for call other than current call")) return } @@ -1013,7 +1013,7 @@ protocol CallServiceObserver: class { if let peerConnectionClient = self.peerConnectionClient { // If the call is connected, we can send the hangup via the data channel for faster hangup. let message = DataChannelMessage.forHangup(callId: call.signalingId) - peerConnectionClient.sendDataChannelMessage(data: message.asData(), description:"hangup", isCritical:true) + peerConnectionClient.sendDataChannelMessage(data: message.asData(), description: "hangup", isCritical: true) } else { Logger.info("\(TAG) ending call before peer connection created. Device offline or quick hangup.") } @@ -1024,7 +1024,7 @@ protocol CallServiceObserver: class { let sendPromise = self.messageSender.sendPromise(message: callMessage).then { Logger.debug("\(self.TAG) successfully sent hangup call message to \(call.thread.contactIdentifier())") }.catch { error in - OWSProdInfo(OWSAnalyticsEvents.callServiceErrorHandleLocalHungupCall(), file:#file, function:#function, line:#line) + OWSProdInfo(OWSAnalyticsEvents.callServiceErrorHandleLocalHungupCall(), file: #file, function: #function, line: #line) Logger.error("\(self.TAG) failed to send hangup call message to \(call.thread.contactIdentifier()) with error: \(error)") } sendPromise.retainUntilComplete() @@ -1103,38 +1103,39 @@ protocol CallServiceObserver: class { func setHasLocalVideo(hasLocalVideo: Bool) { AssertIsOnMainThread() - let authStatus = AVCaptureDevice.authorizationStatus(forMediaType:AVMediaTypeVideo) - switch authStatus { - case .notDetermined: - Logger.debug("\(TAG) authStatus: AVAuthorizationStatusNotDetermined") - break - case .restricted: - Logger.debug("\(TAG) authStatus: AVAuthorizationStatusRestricted") - break - case .denied: - Logger.debug("\(TAG) authStatus: AVAuthorizationStatusDenied") - break - case .authorized: - Logger.debug("\(TAG) authStatus: AVAuthorizationStatusAuthorized") - break - } - - // We don't need to worry about the user granting or remoting this permission - // during a call while the app is in the background, because changing this - // permission kills the app. - if authStatus != .authorized { - - OWSAlerts.showAlert(withTitle:NSLocalizedString("MISSING_CAMERA_PERMISSION_TITLE", comment: "Alert title when camera is not authorized"), - message:NSLocalizedString("MISSING_CAMERA_PERMISSION_MESSAGE", comment: "Alert body when camera is not authorized")) - + guard let frontmostViewController = UIApplication.shared.frontmostViewController else { + owsFail("\(TAG) could not identify frontmostViewController in \(#function)") return } + frontmostViewController.ows_ask(forCameraPermissions: { [weak self] granted in + guard let strongSelf = self else { + return + } + + if (granted) { + // Success callback; camera permissions are granted. + strongSelf.setHasLocalVideoWithCameraPermissions(hasLocalVideo: hasLocalVideo) + } else { + // Failed callback; camera permissions are _NOT_ granted. + + // We don't need to worry about the user granting or remoting this permission + // during a call while the app is in the background, because changing this + // permission kills the app. + OWSAlerts.showAlert(withTitle: NSLocalizedString("MISSING_CAMERA_PERMISSION_TITLE", comment: "Alert title when camera is not authorized"), + message: NSLocalizedString("MISSING_CAMERA_PERMISSION_MESSAGE", comment: "Alert body when camera is not authorized")) + } + }) + } + + private func setHasLocalVideoWithCameraPermissions(hasLocalVideo: Bool) { + AssertIsOnMainThread() + guard let call = self.call else { // This should never happen; return to a known good state. owsFail("\(TAG) call was unexpectedly nil in \(#function)") - OWSProdError(OWSAnalyticsEvents.callServiceCallMissing(), file:#file, function:#function, line:#line) - handleFailedCurrentCall(error: CallError.assertionError(description:"\(TAG) call unexpectedly nil in \(#function)")) + OWSProdError(OWSAnalyticsEvents.callServiceCallMissing(), file: #file, function: #function, line: #line) + handleFailedCurrentCall(error: CallError.assertionError(description: "\(TAG) call unexpectedly nil in \(#function)")) return } @@ -1153,7 +1154,7 @@ protocol CallServiceObserver: class { func handleCallKitStartVideo() { AssertIsOnMainThread() - self.setHasLocalVideo(hasLocalVideo:true) + self.setHasLocalVideo(hasLocalVideo: true) } /** @@ -1172,8 +1173,8 @@ protocol CallServiceObserver: class { guard let call = self.call else { // This should never happen; return to a known good state. owsFail("\(TAG) received data message, but there is no current call. Ignoring.") - OWSProdError(OWSAnalyticsEvents.callServiceCallMissing(), file:#file, function:#function, line:#line) - handleFailedCurrentCall(error: CallError.assertionError(description:"\(TAG) received data message, but there is no current call. Ignoring.")) + OWSProdError(OWSAnalyticsEvents.callServiceCallMissing(), file: #file, function: #function, line: #line) + handleFailedCurrentCall(error: CallError.assertionError(description: "\(TAG) received data message, but there is no current call. Ignoring.")) return } @@ -1185,8 +1186,8 @@ protocol CallServiceObserver: class { guard connected.id == call.signalingId else { // This should never happen; return to a known good state. owsFail("\(TAG) received connected message for call with id:\(connected.id) but current call has id:\(call.signalingId)") - OWSProdError(OWSAnalyticsEvents.callServiceCallIdMismatch(), file:#file, function:#function, line:#line) - handleFailedCurrentCall(error: CallError.assertionError(description:"\(TAG) received connected message for call with id:\(connected.id) but current call has id:\(call.signalingId)")) + OWSProdError(OWSAnalyticsEvents.callServiceCallIdMismatch(), file: #file, function: #function, line: #line) + handleFailedCurrentCall(error: CallError.assertionError(description: "\(TAG) received connected message for call with id:\(connected.id) but current call has id:\(call.signalingId)")) return } @@ -1201,8 +1202,8 @@ protocol CallServiceObserver: class { guard hangup.id == call.signalingId else { // This should never happen; return to a known good state. owsFail("\(TAG) received hangup message for call with id:\(hangup.id) but current call has id:\(call.signalingId)") - OWSProdError(OWSAnalyticsEvents.callServiceCallIdMismatch(), file:#file, function:#function, line:#line) - handleFailedCurrentCall(error: CallError.assertionError(description:"\(TAG) received hangup message for call with id:\(hangup.id) but current call has id:\(call.signalingId)")) + OWSProdError(OWSAnalyticsEvents.callServiceCallIdMismatch(), file: #file, function: #function, line: #line) + handleFailedCurrentCall(error: CallError.assertionError(description: "\(TAG) received hangup message for call with id:\(hangup.id) but current call has id:\(call.signalingId)")) return } @@ -1309,8 +1310,8 @@ protocol CallServiceObserver: class { } guard let readyToSendIceUpdatesPromise = self.readyToSendIceUpdatesPromise else { - OWSProdError(OWSAnalyticsEvents.callServiceCouldNotCreateReadyToSendIceUpdatesPromise(), file:#file, function:#function, line:#line) - return Promise(error: CallError.assertionError(description:"failed to create readyToSendIceUpdatesPromise")) + OWSProdError(OWSAnalyticsEvents.callServiceCouldNotCreateReadyToSendIceUpdatesPromise(), file: #file, function: #function, line: #line) + return Promise(error: CallError.assertionError(description: "failed to create readyToSendIceUpdatesPromise")) } return readyToSendIceUpdatesPromise @@ -1353,8 +1354,8 @@ protocol CallServiceObserver: class { } guard let peerConnectionClientPromise = self.peerConnectionClientPromise else { - OWSProdError(OWSAnalyticsEvents.callServiceCouldNotCreatePeerConnectionClientPromise(), file:#file, function:#function, line:#line) - return Promise(error: CallError.assertionError(description:"failed to create peerConnectionClientPromise")) + OWSProdError(OWSAnalyticsEvents.callServiceCouldNotCreatePeerConnectionClientPromise(), file: #file, function: #function, line: #line) + return Promise(error: CallError.assertionError(description: "failed to create peerConnectionClientPromise")) } return peerConnectionClientPromise @@ -1576,8 +1577,8 @@ protocol CallServiceObserver: class { self.peerConnectionClient?.setLocalVideoEnabled(enabled: shouldHaveLocalVideoTrack) - let message = DataChannelMessage.forVideoStreamingStatus(callId: call.signalingId, enabled:shouldHaveLocalVideoTrack) - peerConnectionClient.sendDataChannelMessage(data: message.asData(), description:"videoStreamingStatus") + let message = DataChannelMessage.forVideoStreamingStatus(callId: call.signalingId, enabled: shouldHaveLocalVideoTrack) + peerConnectionClient.sendDataChannelMessage(data: message.asData(), description: "videoStreamingStatus") } // MARK: - Observers @@ -1592,9 +1593,9 @@ protocol CallServiceObserver: class { let call = self.call let localVideoTrack = self.localVideoTrack let remoteVideoTrack = self.isRemoteVideoEnabled ? self.remoteVideoTrack : nil - observer.didUpdateVideoTracks(call:call, - localVideoTrack:localVideoTrack, - remoteVideoTrack:remoteVideoTrack) + observer.didUpdateVideoTracks(call: call, + localVideoTrack: localVideoTrack, + remoteVideoTrack: remoteVideoTrack) } // The observer-related methods should be invoked on the main thread. @@ -1621,9 +1622,9 @@ protocol CallServiceObserver: class { let remoteVideoTrack = self.isRemoteVideoEnabled ? self.remoteVideoTrack : nil for observer in observers { - observer.value?.didUpdateVideoTracks(call:call, - localVideoTrack:localVideoTrack, - remoteVideoTrack:remoteVideoTrack) + observer.value?.didUpdateVideoTracks(call: call, + localVideoTrack: localVideoTrack, + remoteVideoTrack: remoteVideoTrack) } } @@ -1669,7 +1670,7 @@ protocol CallServiceObserver: class { let frontmostViewController = UIApplication.shared.frontmostViewControllerIgnoringAlerts guard nil != frontmostViewController as? CallViewController else { - OWSProdError(OWSAnalyticsEvents.callServiceCallViewCouldNotPresent(), file:#file, function:#function, line:#line) + OWSProdError(OWSAnalyticsEvents.callServiceCallViewCouldNotPresent(), file: #file, function: #function, line: #line) owsFail("\(TAG) in \(#function) Call terminated due to call view presentation delay: \(frontmostViewController.debugDescription).") self.terminateCall() return diff --git a/Signal/src/call/OutboundCallInitiator.swift b/Signal/src/call/OutboundCallInitiator.swift index 3b284ff05..1138082cb 100644 --- a/Signal/src/call/OutboundCallInitiator.swift +++ b/Signal/src/call/OutboundCallInitiator.swift @@ -44,6 +44,10 @@ import Foundation owsFail("\(TAG) can't initiate call because callUIAdapter is nil") return false } + guard let frontmostViewController = UIApplication.shared.frontmostViewController else { + owsFail("\(TAG) could not identify frontmostViewController in \(#function)") + return false + } let showedAlert = SafetyNumberConfirmationAlert.presentAlertIfNecessary(recipientId: recipientId, confirmationText: CallStrings.confirmAndCallButtonTitle, @@ -59,17 +63,22 @@ import Foundation // Check for microphone permissions // Alternative way without prompting for permissions: // if AVAudioSession.sharedInstance().recordPermission() == .denied { - AVAudioSession.sharedInstance().requestRecordPermission { isGranted in - DispatchQueue.main.async { - // Here the permissions are either granted or denied - guard isGranted == true else { - Logger.warn("\(self.TAG) aborting due to missing microphone permissions.") - OWSAlerts.showNoMicrophonePermissionAlert() - return - } - callUIAdapter.startAndShowOutgoingCall(recipientId: recipientId) + frontmostViewController.ows_ask(forMicrophonePermissions: { [weak self] granted in + // Success callback; camera permissions are granted. + + guard let strongSelf = self else { + return } - } + + // Here the permissions are either granted or denied + guard granted == true else { + Logger.warn("\(strongSelf.TAG) aborting due to missing microphone permissions.") + OWSAlerts.showNoMicrophonePermissionAlert() + return + } + callUIAdapter.startAndShowOutgoingCall(recipientId: recipientId) + }) + return true } } diff --git a/Signal/src/util/UIViewController+CameraPermissions.m b/Signal/src/util/UIViewController+CameraPermissions.m deleted file mode 100644 index 0fba1dd98..000000000 --- a/Signal/src/util/UIViewController+CameraPermissions.m +++ /dev/null @@ -1,76 +0,0 @@ -// -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. -// - -#import "UIViewController+CameraPermissions.h" -#import "Signal-Swift.h" -#import "UIUtil.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@implementation UIViewController (CameraPermissions) - -- (void)ows_askForCameraPermissions:(void (^)(void))permissionsGrantedCallback -{ - [self ows_askForCameraPermissions:permissionsGrantedCallback failureCallback:nil]; -} - -- (void)ows_askForCameraPermissions:(void (^)(void))permissionsGrantedCallback - failureCallback:(nullable void (^)(void))failureCallback -{ - // Avoid nil tests below. - if (!failureCallback) { - failureCallback = ^{ - }; - } - - if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { - DDLogError(@"Camera ImagePicker source not available"); - failureCallback(); - return; - } - - AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; - if (status == AVAuthorizationStatusDenied) { - UIAlertController* alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"MISSING_CAMERA_PERMISSION_TITLE", @"Alert title") - message:NSLocalizedString(@"MISSING_CAMERA_PERMISSION_MESSAGE", @"Alert body") - preferredStyle:UIAlertControllerStyleAlert]; - - NSString *settingsTitle = NSLocalizedString(@"OPEN_SETTINGS_BUTTON", @"Button text which opens the settings app"); - UIAlertAction *openSettingsAction = [UIAlertAction actionWithTitle:settingsTitle style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { - [[UIApplication sharedApplication] openSystemSettings]; - failureCallback(); - }]; - [alert addAction:openSettingsAction]; - - UIAlertAction *dismissAction = [UIAlertAction actionWithTitle:CommonStrings.dismissButton - style:UIAlertActionStyleCancel - handler:^(UIAlertAction *action) { - failureCallback(); - }]; - [alert addAction:dismissAction]; - - [self presentViewController:alert animated:YES completion:nil]; - } else if (status == AVAuthorizationStatusAuthorized) { - permissionsGrantedCallback(); - } else if (status == AVAuthorizationStatusNotDetermined) { - [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo - completionHandler:^(BOOL granted) { - dispatch_async(dispatch_get_main_queue(), ^{ - if (granted) { - permissionsGrantedCallback(); - } else { - failureCallback(); - } - }); - }]; - } else { - DDLogError(@"Unknown AVAuthorizationStatus: %ld", (long)status); - failureCallback(); - } -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/Signal/src/util/UIViewController+Permissions.h b/Signal/src/util/UIViewController+Permissions.h new file mode 100644 index 000000000..7919ad045 --- /dev/null +++ b/Signal/src/util/UIViewController+Permissions.h @@ -0,0 +1,17 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UIViewController (Permissions) + +- (void)ows_askForCameraPermissions:(void (^)(BOOL granted))callback; + +- (void)ows_askForMicrophonePermissions:(void (^)(BOOL granted))callback; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/util/UIViewController+Permissions.m b/Signal/src/util/UIViewController+Permissions.m new file mode 100644 index 000000000..0a14fe5e9 --- /dev/null +++ b/Signal/src/util/UIViewController+Permissions.m @@ -0,0 +1,97 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import "Signal-Swift.h" +#import "UIUtil.h" +#import "UIViewController+Permissions.h" +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@implementation UIViewController (Permissions) + +- (void)ows_askForCameraPermissions:(void (^)(BOOL granted))callbackParam +{ + DDLogVerbose(@"[%@] ows_askForCameraPermissions", NSStringFromClass(self.class)); + + // Ensure callback is invoked on main thread. + void (^callback)(BOOL) = ^(BOOL granted) { + DispatchMainThreadSafe(^{ + callbackParam(granted); + }); + }; + + if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) { + DDLogError(@"Skipping camera permissions request when app is in background."); + callback(NO); + return; + } + + if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { + DDLogError(@"Camera ImagePicker source not available"); + callback(NO); + return; + } + + AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; + if (status == AVAuthorizationStatusDenied) { + UIAlertController *alert = [UIAlertController + alertControllerWithTitle:NSLocalizedString(@"MISSING_CAMERA_PERMISSION_TITLE", @"Alert title") + message:NSLocalizedString(@"MISSING_CAMERA_PERMISSION_MESSAGE", @"Alert body") + preferredStyle:UIAlertControllerStyleAlert]; + + NSString *settingsTitle + = NSLocalizedString(@"OPEN_SETTINGS_BUTTON", @"Button text which opens the settings app"); + UIAlertAction *openSettingsAction = + [UIAlertAction actionWithTitle:settingsTitle + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *_Nonnull action) { + [[UIApplication sharedApplication] openSystemSettings]; + callback(NO); + }]; + [alert addAction:openSettingsAction]; + + UIAlertAction *dismissAction = [UIAlertAction actionWithTitle:CommonStrings.dismissButton + style:UIAlertActionStyleCancel + handler:^(UIAlertAction *action) { + callback(NO); + }]; + [alert addAction:dismissAction]; + + [self presentViewController:alert animated:YES completion:nil]; + } else if (status == AVAuthorizationStatusAuthorized) { + callback(YES); + } else if (status == AVAuthorizationStatusNotDetermined) { + [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo + completionHandler:callback]; + } else { + DDLogError(@"Unknown AVAuthorizationStatus: %ld", (long)status); + callback(NO); + } +} + +- (void)ows_askForMicrophonePermissions:(void (^)(BOOL granted))callbackParam +{ + DDLogVerbose(@"[%@] ows_askForMicrophonePermissions", NSStringFromClass(self.class)); + + // Ensure callback is invoked on main thread. + void (^callback)(BOOL) = ^(BOOL granted) { + DispatchMainThreadSafe(^{ + callbackParam(granted); + }); + }; + + if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) { + DDLogError(@"Skipping microphone permissions request when app is in background."); + callback(NO); + return; + } + + [[AVAudioSession sharedInstance] requestRecordPermission:callback]; +} + +@end + +NS_ASSUME_NONNULL_END