From ddfe6e79b75e246ef10c1e5d219989ae03d9930e Mon Sep 17 00:00:00 2001 From: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com> Date: Wed, 20 Dec 2017 11:29:59 -0500 Subject: [PATCH] call: add call service MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch introduce call service and adapter to manage call state. Author: Silbino Gonçalves Matado <silbino.gmatado@savoirfairelinux.com> Change-Id: I8f69aad4f9c91c2001ef27cb78df631fd31bf291 Reviewed-by: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> --- Ring/Ring.xcodeproj/project.pbxproj | 18 ++ Ring/Ring/Bridging/CallsAdapter.h | 40 ++++ Ring/Ring/Bridging/CallsAdapter.mm | 186 +++++++++++++++++ Ring/Ring/Bridging/Ring-Bridging-Header.h | 1 + Ring/Ring/Models/CallModel.swift | 144 +++++++++++++ Ring/Ring/Services/CallsAdapterDelegate.swift | 30 +++ Ring/Ring/Services/CallsService.swift | 197 ++++++++++++++++++ 7 files changed, 616 insertions(+) create mode 100644 Ring/Ring/Bridging/CallsAdapter.h create mode 100644 Ring/Ring/Bridging/CallsAdapter.mm create mode 100644 Ring/Ring/Models/CallModel.swift create mode 100644 Ring/Ring/Services/CallsAdapterDelegate.swift create mode 100644 Ring/Ring/Services/CallsService.swift diff --git a/Ring/Ring.xcodeproj/project.pbxproj b/Ring/Ring.xcodeproj/project.pbxproj index ac2a49126..45b26bb1c 100644 --- a/Ring/Ring.xcodeproj/project.pbxproj +++ b/Ring/Ring.xcodeproj/project.pbxproj @@ -106,6 +106,10 @@ 0E403F831F7D79B000C80BC2 /* MessageCellGenerated.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0E403F821F7D79B000C80BC2 /* MessageCellGenerated.xib */; }; 0E48F9D31FDF150700D6CC08 /* ContactRequestManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E48F9D21FDF150700D6CC08 /* ContactRequestManager.swift */; }; 0E4909611FE97A94005CAA50 /* ActiveLabel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E4909601FE97A94005CAA50 /* ActiveLabel.framework */; }; + 0E49096A1FEAB156005CAA50 /* CallsAdapter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0E4909691FEAB156005CAA50 /* CallsAdapter.mm */; }; + 0E49096C1FEAB225005CAA50 /* CallsAdapterDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E49096B1FEAB225005CAA50 /* CallsAdapterDelegate.swift */; }; + 0E49096E1FEAC0DE005CAA50 /* CallsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E49096D1FEAC0DE005CAA50 /* CallsService.swift */; }; + 0E4909701FEAC1C6005CAA50 /* CallModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E49096F1FEAC1C6005CAA50 /* CallModel.swift */; }; 0E6949791FA7E71C0029B60A /* BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6949781FA7E71C0029B60A /* BaseViewController.swift */; }; 0E983E6E1FC77C3E0082103E /* ConversationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E983E6D1FC77C3E0082103E /* ConversationModel.swift */; }; 0E9D84491FA7DA6A00C561EB /* ChatTabBarItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9D84481FA7DA6A00C561EB /* ChatTabBarItemViewModel.swift */; }; @@ -356,6 +360,11 @@ 0E403F821F7D79B000C80BC2 /* MessageCellGenerated.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MessageCellGenerated.xib; sourceTree = "<group>"; }; 0E48F9D21FDF150700D6CC08 /* ContactRequestManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactRequestManager.swift; sourceTree = "<group>"; }; 0E4909601FE97A94005CAA50 /* ActiveLabel.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ActiveLabel.framework; path = Carthage/Build/iOS/ActiveLabel.framework; sourceTree = "<group>"; }; + 0E4909681FEAB156005CAA50 /* CallsAdapter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CallsAdapter.h; sourceTree = "<group>"; }; + 0E4909691FEAB156005CAA50 /* CallsAdapter.mm */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; path = CallsAdapter.mm; sourceTree = "<group>"; }; + 0E49096B1FEAB225005CAA50 /* CallsAdapterDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallsAdapterDelegate.swift; sourceTree = "<group>"; }; + 0E49096D1FEAC0DE005CAA50 /* CallsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallsService.swift; sourceTree = "<group>"; }; + 0E49096F1FEAC1C6005CAA50 /* CallModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallModel.swift; sourceTree = "<group>"; }; 0E6949781FA7E71C0029B60A /* BaseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseViewController.swift; sourceTree = "<group>"; }; 0E983E6D1FC77C3E0082103E /* ConversationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationModel.swift; sourceTree = "<group>"; }; 0E9D84481FA7DA6A00C561EB /* ChatTabBarItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTabBarItemViewModel.swift; sourceTree = "<group>"; }; @@ -704,6 +713,8 @@ 62DFAB2D1F9FF0D0002D6F9C /* NetworkService.swift */, 0EF78DE21FD0AE3000FC6966 /* ConversationsManager.swift */, 0E48F9D21FDF150700D6CC08 /* ContactRequestManager.swift */, + 0E49096B1FEAB225005CAA50 /* CallsAdapterDelegate.swift */, + 0E49096D1FEAC0DE005CAA50 /* CallsService.swift */, ); path = Services; sourceTree = "<group>"; @@ -737,6 +748,8 @@ 563AEC731EA6627F003A5641 /* NameRegistration */, 62A88D351F6C2E5F00F8AB18 /* PresenceAdapter.h */, 62A88D381F6C323500F8AB18 /* PresenceAdapter.mm */, + 0E4909681FEAB156005CAA50 /* CallsAdapter.h */, + 0E4909691FEAB156005CAA50 /* CallsAdapter.mm */, ); path = Bridging; sourceTree = "<group>"; @@ -1137,6 +1150,7 @@ 1A2D18BD1F29180700B2C785 /* ContactModel.swift */, 1A2D18BF1F29180700B2C785 /* DeviceModel.swift */, 0E983E6D1FC77C3E0082103E /* ConversationModel.swift */, + 0E49096F1FEAC1C6005CAA50 /* CallModel.swift */, ); path = Models; sourceTree = "<group>"; @@ -1581,6 +1595,7 @@ 1A2D18C51F29180700B2C785 /* ContactModel.swift in Sources */, 1A2D18F71F292D7200B2C785 /* MessageCellSent.swift in Sources */, 04399AAC1D1C304300E99CD9 /* AccountAdapter.mm in Sources */, + 0E49096C1FEAB225005CAA50 /* CallsAdapterDelegate.swift in Sources */, 1AABA7461F0FE9C000739605 /* UIColor+Ring.swift in Sources */, 1A5DC0201F355DCF0075E8EF /* ContactsService.swift in Sources */, 1A2D18C71F29180700B2C785 /* DeviceModel.swift in Sources */, @@ -1591,8 +1606,10 @@ 02B22E091DF7585F000358C9 /* DaemonService.swift in Sources */, 5CE66F761FBF769B00EE9291 /* InitialLoadingViewController.swift in Sources */, 56BBC99F1ED714CB00CDAF8B /* MessagesAdapter.mm in Sources */, + 0E49096A1FEAB156005CAA50 /* CallsAdapter.mm in Sources */, 1A2D18A61F27F7A400B2C785 /* UIViewController+Rx.swift in Sources */, 1A5DC0241F3564360075E8EF /* ContactRequestModel.swift in Sources */, + 0E4909701FEAC1C6005CAA50 /* CallModel.swift in Sources */, 1A5DC03F1F35678D0075E8EF /* ContactRequestsViewController.swift in Sources */, 1A20418B1F1EA58A00C08435 /* ViewModelBased.swift in Sources */, 1A5DC01E1F355DA70075E8EF /* ContactsAdapterDelegate.swift in Sources */, @@ -1673,6 +1690,7 @@ 04399AAD1D1C304300E99CD9 /* DRingAdapter.mm in Sources */, 1A2D18D11F29182500B2C785 /* ConversationViewController.swift in Sources */, 0EE1B5501F75AD4700BA98EE /* VCardUtils.swift in Sources */, + 0E49096E1FEAC0DE005CAA50 /* CallsService.swift in Sources */, 0273C2FF1E0C438F00CF00BA /* AccountAdapterDelegate.swift in Sources */, 1A2D19011F29353A00B2C785 /* MeDetailViewModel.swift in Sources */, 1A2D18A41F27EF5200B2C785 /* AppCoordinator.swift in Sources */, diff --git a/Ring/Ring/Bridging/CallsAdapter.h b/Ring/Ring/Bridging/CallsAdapter.h new file mode 100644 index 000000000..eb82cec7c --- /dev/null +++ b/Ring/Ring/Bridging/CallsAdapter.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017 Savoir-faire Linux Inc. + * + * Author: Silbino Gonçalves Matado <silbino.gmatado@savoirfairelinux.com> + * Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#import <Foundation/Foundation.h> + +@protocol CallsAdapterDelegate; + +@interface CallsAdapter : NSObject + +@property (class, nonatomic, weak) id <CallsAdapterDelegate> delegate; + +- (BOOL)acceptCallWithId:(NSString*)callId; +- (BOOL)refuseCallWithId:(NSString*)callId; +- (BOOL)hangUpCallWithId:(NSString*)callId; +- (BOOL)holdCallWithId:(NSString*)callId; +- (BOOL)unholdCallWithId:(NSString*)callId; + +- (NSString*)placeCallWithAccountId:(NSString*)accountId toRingId:(NSString*)ringId; +- (NSDictionary<NSString*,NSString*>*)callDetailsWithCallId:(NSString*)callId; +- (NSArray<NSString*>*)calls; + +@end diff --git a/Ring/Ring/Bridging/CallsAdapter.mm b/Ring/Ring/Bridging/CallsAdapter.mm new file mode 100644 index 000000000..fda358220 --- /dev/null +++ b/Ring/Ring/Bridging/CallsAdapter.mm @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2017 Savoir-faire Linux Inc. + * + * Author: Silbino Gonçalves Matado <silbino.gmatado@savoirfairelinux.com> + * Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#import "CallsAdapter.h" +#import "Utils.h" +#import "dring/callmanager_interface.h" +#import "Ring-Swift.h" + +using namespace DRing; + +@implementation CallsAdapter +/// Static delegate that will receive the propagated daemon events +static id <CallsAdapterDelegate> _delegate; + +#pragma mark Init + +- (id)init { + if (self = [super init]) { + [self registerCallHandler]; + } + return self; +} + +#pragma mark - + +#pragma mark Callbacks registration + +- (void)registerCallHandler { + + std::map<std::string, std::shared_ptr<CallbackWrapperBase>> callHandlers; + + //State changed signal + callHandlers.insert(exportable_callback<CallSignal::StateChange>([&](const std::string& callId, + const std::string& state, + int errorCode) { + if (CallsAdapter.delegate) { + NSString* callIdString = [NSString stringWithUTF8String:callId.c_str()]; + NSString* stateString = [NSString stringWithUTF8String:state.c_str()]; + [CallsAdapter.delegate didChangeCallStateWithCallId:callIdString + state:stateString + stateCode:errorCode]; + } + })); + + //Incoming message signal + callHandlers.insert(exportable_callback<CallSignal::IncomingMessage>([&](const std::string& callId, + const std::string& fromURI, + const std::map<std::string, + std::string>& message) { + + if (CallsAdapter.delegate) { + NSString* callIdString = [NSString stringWithUTF8String:callId.c_str()]; + NSString* fromURIString = [NSString stringWithUTF8String:fromURI.c_str()]; + NSDictionary* messageDict = [Utils mapToDictionnary:message]; + [CallsAdapter.delegate didReceiveMessageWithCallId:callIdString + fromURI:fromURIString + message:messageDict]; + } + })); + + //Incoming call signal + callHandlers.insert(exportable_callback<CallSignal::IncomingCall>([&](const std::string& accountId, + const std::string& callId, + const std::string& fromURI) { + if (CallsAdapter.delegate) { + NSString* accountIdString = [NSString stringWithUTF8String:accountId.c_str()]; + NSString* callIdString = [NSString stringWithUTF8String:callId.c_str()]; + NSString* fromURIString = [NSString stringWithUTF8String:fromURI.c_str()]; + [CallsAdapter.delegate receivingCallWithAccountId:accountIdString + callId:callIdString + fromURI:fromURIString]; + } + })); + + //New outgoing call signal + callHandlers.insert(exportable_callback<CallSignal::NewCallCreated>([&](const std::string& accountId, + const std::string& callId, + const std::string& toURI) { + if (CallsAdapter.delegate) { + NSString* accountIdString = [NSString stringWithUTF8String:accountId.c_str()]; + NSString* callIdString = [NSString stringWithUTF8String:callId.c_str()]; + NSString* toURIString = [NSString stringWithUTF8String:toURI.c_str()]; + [CallsAdapter.delegate newCallStartedWithAccountId: accountIdString + callId: callIdString + toURI: toURIString]; + + } + })); + + //Peer place call on hold signal + callHandlers.insert(exportable_callback<CallSignal::PeerHold>([&](const std::string& callId, + bool holding) { + if (CallsAdapter.delegate) { + NSString* callIdString = [NSString stringWithUTF8String: callId.c_str()]; + [CallsAdapter.delegate callPlacedOnHoldWithCallId:callIdString holding:holding]; + } + })); + + callHandlers.insert(exportable_callback<CallSignal::VideoMuted>([&](const std::string& callId, + bool muted) { + if (CallsAdapter.delegate) { + NSString* callIdString = [NSString stringWithUTF8String:callId.c_str()]; + [CallsAdapter.delegate muteVideoWithCall: callIdString mute: muted]; + } + })); + + callHandlers.insert(exportable_callback<CallSignal::AudioMuted>([&](const std::string& callId, + bool muted) { + if (CallsAdapter.delegate) { + NSString* callIdString = [NSString stringWithUTF8String:callId.c_str()]; + [CallsAdapter.delegate muteAudioWithCall: callIdString mute: muted]; + + } + })); + + registerCallHandlers(callHandlers); +} + +#pragma mark - + +- (BOOL)acceptCallWithId:(NSString*)callId { + return accept(std::string([callId UTF8String])); +} + +- (BOOL)refuseCallWithId:(NSString*)callId { + return refuse(std::string([callId UTF8String])); +} + +- (BOOL)hangUpCallWithId:(NSString*)callId { + return hangUp(std::string([callId UTF8String])); +} + +- (BOOL)holdCallWithId:(NSString*)callId { + return hold(std::string([callId UTF8String])); +} + +- (BOOL)unholdCallWithId:(NSString*)callId { + return unhold(std::string([callId UTF8String])); +} + +- (NSString*)placeCallWithAccountId:(NSString*)accountId toRingId:(NSString*)ringId { + std::string callId = placeCall(std::string([accountId UTF8String]), std::string([ringId UTF8String])); + return [NSString stringWithUTF8String:callId.c_str()]; +} + +- (NSDictionary<NSString*,NSString*>*)callDetailsWithCallId:(NSString*)callId { + std::map<std::string, std::string> callDetails = getCallDetails(std::string([callId UTF8String])); + return [Utils mapToDictionnary:callDetails]; +} + +- (NSArray<NSString*>*)calls { + std::vector<std::string> calls = getCallList(); + return [Utils vectorToArray:calls]; +} + +#pragma mark AccountAdapterDelegate + ++ (id <CallsAdapterDelegate>)delegate { + return _delegate; +} + ++ (void) setDelegate:(id<CallsAdapterDelegate>)delegate { + _delegate = delegate; +} + +#pragma mark - + +@end diff --git a/Ring/Ring/Bridging/Ring-Bridging-Header.h b/Ring/Ring/Bridging/Ring-Bridging-Header.h index 174232018..54365de06 100644 --- a/Ring/Ring/Bridging/Ring-Bridging-Header.h +++ b/Ring/Ring/Bridging/Ring-Bridging-Header.h @@ -35,3 +35,4 @@ #import "PresenceAdapter.h" #import <CommonCrypto/CommonCrypto.h> #import <Contacts/Contacts.h> +#import "CallsAdapter.h" diff --git a/Ring/Ring/Models/CallModel.swift b/Ring/Ring/Models/CallModel.swift new file mode 100644 index 000000000..86fedfd87 --- /dev/null +++ b/Ring/Ring/Models/CallModel.swift @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2017 Savoir-faire Linux Inc. + * + * Author: Silbino Gonçalves Matado <silbino.gmatado@savoirfairelinux.com> + * Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +enum CallState: String { + case incoming = "INCOMING" + case connecting = "CONNECTING" + case ringing = "RINGING" + case current = "CURRENT" + case hungup = "HUNGUP" + case busy = "BUSY" + case failure = "FAILURE" + case hold = "HOLD" + case unhold = "UNHOLD" + case inactive = "INACTIVE" + case over = "OVER" + case unknown = "UNKNOWN" +} + +enum CallType: Int { + case incoming = 0 + case outgoing + case missed +} + +enum CallDetailKey: String { + case callTypeKey = "CALL_TYPE" + case peerNumberKey = "PEER_NUMBER" + case registeredNameKey = "REGISTERED_NAME" + case displayNameKey = "DISPLAY_NAME" + case timeStampStartKey = "TIMESTAMP_START" + case accountIdKey = "ACCOUNTID" + case peerHoldingKey = "PEER_HOLDING" + case audioMutedKey = "AUDIO_MUTED" + case videoMutedKey = "VIDEO_MUTED" + case videoSourceKey = "VIDEO_SOURCE" +} + +class CallModel { + + var callId: String = "" + var dateReceived: Date = Date() + var participantRingId: String = "" + var displayName: String = "" + var registeredName: String = "" + var accountId: String = "" + var audioMuted: Bool = false + var videoMuted: Bool = false + + var stateValue = CallState.unknown.rawValue + var callTypeValue = CallType.missed.rawValue + + var state: CallState { + get { + if let state = CallState(rawValue: stateValue) { + return state + } + return CallState.unknown + } + set { + stateValue = newValue.rawValue + } + } + + var callType: CallType { + get { + if let type = CallType(rawValue: callTypeValue) { + return type + } + return CallType.missed + } + set { + callTypeValue = newValue.rawValue + } + } + + init(withCallId callId: String, callDetails dictionary: [String: String]) { + self.callId = callId + + if let fromRingId = dictionary[CallDetailKey.peerNumberKey.rawValue]?.components(separatedBy: "@").first { + self.participantRingId = fromRingId + } + + if let accountId = dictionary[CallDetailKey.accountIdKey.rawValue] { + self.accountId = accountId + } + + if let callType = dictionary[CallDetailKey.callTypeKey.rawValue] { + if let callTypeInt = Int(callType) { + self.callType = CallType(rawValue: callTypeInt)! + } else { + self.callType = .missed + } + } + + self.update(withDictionary: dictionary) + } + + func update(withDictionary dictionary: [String: String]) { + + self.dateReceived = Date() + + if let displayName = dictionary[CallDetailKey.displayNameKey.rawValue] { + self.displayName = displayName + } + + if let registeredName = dictionary[CallDetailKey.registeredNameKey.rawValue] { + self.registeredName = registeredName + } + + if let videoMuted = dictionary[CallDetailKey.videoMutedKey.rawValue]?.toBool() { + self.videoMuted = videoMuted + } + + if let audioMuted = dictionary[CallDetailKey.audioMutedKey.rawValue]?.toBool() { + self.audioMuted = audioMuted + } + + if let participantRingId = dictionary[CallDetailKey.peerNumberKey.rawValue]?.components(separatedBy: "@").first { + self.participantRingId = participantRingId + } + + if let accountId = dictionary[CallDetailKey.accountIdKey.rawValue] { + self.accountId = accountId + } + } +} diff --git a/Ring/Ring/Services/CallsAdapterDelegate.swift b/Ring/Ring/Services/CallsAdapterDelegate.swift new file mode 100644 index 000000000..1267fba25 --- /dev/null +++ b/Ring/Ring/Services/CallsAdapterDelegate.swift @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 Savoir-faire Linux Inc. + * + * Author: Silbino Gonçalves Matado <silbino.gmatado@savoirfairelinux.com> + * Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +@objc protocol CallsAdapterDelegate { + func didChangeCallState(withCallId callId: String, state: String, stateCode: NSInteger) + func didReceiveMessage(withCallId callId: String, fromURI uri: String, message: [String: String]) + func receivingCall(withAccountId accountId: String, callId: String, fromURI uri: String) + func newCallStarted(withAccountId accountId: String, callId: String, toURI uri: String) + func callPlacedOnHold(withCallId callId: String, holding: Bool) + func muteAudio(call callId: String, mute: Bool) + func muteVideo(call callId: String, mute: Bool) +} diff --git a/Ring/Ring/Services/CallsService.swift b/Ring/Ring/Services/CallsService.swift new file mode 100644 index 000000000..18fc8c44c --- /dev/null +++ b/Ring/Ring/Services/CallsService.swift @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2017 Savoir-faire Linux Inc. + * + * Author: Silbino Gonçalves Matado <silbino.gmatado@savoirfairelinux.com> + * Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import RxSwift +import SwiftyBeaver +import Contacts + +enum CallServiceError: Error { + case acceptCallFailed + case refuseCallFailed + case hangUpCallFailed + case holdCallFailed + case unholdCallFailed + case placeCallFailed +} + +class CallsService: CallsAdapterDelegate { + + fileprivate let disposeBag = DisposeBag() + fileprivate let callsAdapter: CallsAdapter + fileprivate let log = SwiftyBeaver.self + + fileprivate var calls = [String: CallModel]() + fileprivate let ringVCardMIMEType = "x-ring/ring.profile.vcard" + + let currentCall = ReplaySubject<CallModel>.create(bufferSize: 1) + let newcall = Variable<CallModel>(CallModel(withCallId: "", callDetails: [:])) + + init(withCallsAdapter callsAdapter: CallsAdapter) { + self.callsAdapter = callsAdapter + CallsAdapter.delegate = self + } + + func accept(callId: String) -> Completable { + return Completable.create(subscribe: { completable in + let success = self.callsAdapter.acceptCall(withId: callId) + if success { + completable(.completed) + } else { + completable(.error(CallServiceError.acceptCallFailed)) + } + return Disposables.create { } + }) + } + + func refuse(callId: String) -> Completable { + return Completable.create(subscribe: { completable in + let success = self.callsAdapter.refuseCall(withId: callId) + if success { + completable(.completed) + } else { + completable(.error(CallServiceError.refuseCallFailed)) + } + return Disposables.create { } + }) + } + + func hangUp(callId: String) -> Completable { + return Completable.create(subscribe: { completable in + let success = self.callsAdapter.hangUpCall(withId: callId) + if success { + completable(.completed) + } else { + completable(.error(CallServiceError.hangUpCallFailed)) + } + return Disposables.create { } + }) + } + + func hold(callId: String) -> Completable { + return Completable.create(subscribe: { completable in + let success = self.callsAdapter.holdCall(withId: callId) + if success { + completable(.completed) + } else { + completable(.error(CallServiceError.holdCallFailed)) + } + return Disposables.create { } + }) + } + + func unhold(callId: String) -> Completable { + return Completable.create(subscribe: { completable in + let success = self.callsAdapter.unholdCall(withId: callId) + if success { + completable(.completed) + } else { + completable(.error(CallServiceError.unholdCallFailed)) + } + return Disposables.create { } + }) + } + + func placeCall(withAccount account: AccountModel, toRingId ringId: String) -> Single<CallModel> { + + //Create and emit the call + let call = CallModel(withCallId: ringId, callDetails: [String: String]()) + call.state = .connecting + + return Single<CallModel>.create(subscribe: { single in + if let callId = self.callsAdapter.placeCall(withAccountId: account.id, + toRingId: "ring:\(ringId)"), + let callDictionary = self.callsAdapter.callDetails(withCallId: callId) { + call.update(withDictionary: callDictionary) + call.callId = callId + self.currentCall.onNext(call) + self.calls[callId] = call + single(.success(call)) + } else { + single(.error(CallServiceError.placeCallFailed)) + } + return Disposables.create { } + }) + } + + // MARK: CallsAdapterDelegate + + func didChangeCallState(withCallId callId: String, state: String, stateCode: NSInteger) { + + if let callDictionary = self.callsAdapter.callDetails(withCallId: callId) { + + //Add or update new call + var call = self.calls[callId] + if call == nil { + call = CallModel(withCallId: callId, callDetails: callDictionary) + } else { + call?.update(withDictionary: callDictionary) + } + + //Update the call + call?.state = CallState(rawValue: state)! + + //Emit the call to the observers + self.currentCall.onNext(call!) + + //Remove from the cache if the call is over + if call?.state == .over { + // TODO save history + self.calls[callId] = nil + } + } + } + + func didReceiveMessage(withCallId callId: String, fromURI uri: String, message: [String: String]) { + + } + + func receivingCall(withAccountId accountId: String, callId: String, fromURI uri: String) { + if let callDictionary = self.callsAdapter.callDetails(withCallId: callId) { + + //Add or update new call + var call = self.calls[callId] + if call == nil { + call = CallModel(withCallId: callId, callDetails: callDictionary) + } else { + call?.update(withDictionary: callDictionary) + } + //Emit the call to the observers + self.newcall.value = call! + } + + } + + func newCallStarted(withAccountId accountId: String, callId: String, toURI uri: String) { + + } + + func callPlacedOnHold(withCallId callId: String, holding: Bool) { + + } + + func muteAudio(call callId: String, mute: Bool) { + + } + + func muteVideo(call callId: String, mute: Bool) { + + } +} -- GitLab