diff --git a/Linphone.xcodeproj/project.pbxproj b/Linphone.xcodeproj/project.pbxproj index 329a09722..7a2f7f197 100644 --- a/Linphone.xcodeproj/project.pbxproj +++ b/Linphone.xcodeproj/project.pbxproj @@ -7,14 +7,22 @@ objects = { /* Begin PBXBuildFile section */ + 660AAF7F2B839272004C0FA6 /* msgNotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 660AAF7B2B839271004C0FA6 /* msgNotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 660D8A712B517D260092694D /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 660D8A702B517D260092694D /* GoogleService-Info.plist */; }; 662B69D92B25DE18007118BF /* TelecomManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662B69D82B25DE18007118BF /* TelecomManager.swift */; }; 662B69DB2B25DE25007118BF /* ProviderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662B69DA2B25DE25007118BF /* ProviderDelegate.swift */; }; + 667E5D7F2B8E430C00EBCFC4 /* linphonerc-factory in Resources */ = {isa = PBXBuildFile; fileRef = D732A90B2B0376F500DB42BA /* linphonerc-factory */; }; + 667E5D812B8E444E00EBCFC4 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 667E5D802B8E444D00EBCFC4 /* GoogleService-Info.plist */; }; + 6691CA7E2B839C2D00B2A7B8 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6691CA7D2B839C2D00B2A7B8 /* NotificationService.swift */; }; 66C491F92B24D25B00CEA16D /* ConfigExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66C491F82B24D25A00CEA16D /* ConfigExtension.swift */; }; 66C491FB2B24D32600CEA16D /* CoreExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66C491FA2B24D32600CEA16D /* CoreExtension.swift */; }; 66C491FD2B24D36500CEA16D /* AudioRouteUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66C491FC2B24D36500CEA16D /* AudioRouteUtils.swift */; }; 66C491FF2B24D4AC00CEA16D /* FileUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66C491FE2B24D4AC00CEA16D /* FileUtils.swift */; }; 66C492012B24DB6900CEA16D /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66C492002B24DB6900CEA16D /* Log.swift */; }; + 66FBFC482B83B8CC00BC6AB1 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66C492002B24DB6900CEA16D /* Log.swift */; }; + 66FBFC492B83BD2400BC6AB1 /* ConfigExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66C491F82B24D25A00CEA16D /* ConfigExtension.swift */; }; + 66FBFC4A2B83BD3300BC6AB1 /* FileUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66C491FE2B24D4AC00CEA16D /* FileUtils.swift */; }; + 66FBFC4B2B83BD7B00BC6AB1 /* CoreExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66C491FA2B24D32600CEA16D /* CoreExtension.swift */; }; D706BA822ADD72D100278F45 /* DeviceRotationViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D706BA812ADD72D100278F45 /* DeviceRotationViewModifier.swift */; }; D70959F12B8DF3EC0014AC0B /* ConversationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D70959F02B8DF3EC0014AC0B /* ConversationModel.swift */; }; D70A26EE2B7CF60B006CC8FC /* ConversationsListBottomSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D70A26ED2B7CF60B006CC8FC /* ConversationsListBottomSheet.swift */; }; @@ -112,10 +120,38 @@ D7FB55112AD447FD00A5AB15 /* RegisterFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7FB55102AD447FD00A5AB15 /* RegisterFragment.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 660AAF7D2B839272004C0FA6 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D719ABAB2ABC67BF00B41C10 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 660AAF7A2B839271004C0FA6; + remoteInfo = msgNotificationService; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 660AAF802B839272004C0FA6 /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 660AAF7F2B839272004C0FA6 /* msgNotificationService.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ + 660AAF7B2B839271004C0FA6 /* msgNotificationService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = msgNotificationService.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 660AAF842B8392E0004C0FA6 /* msgNotificationService.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = msgNotificationService.entitlements; sourceTree = ""; }; 660D8A702B517D260092694D /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 662B69D82B25DE18007118BF /* TelecomManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelecomManager.swift; sourceTree = ""; }; 662B69DA2B25DE25007118BF /* ProviderDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderDelegate.swift; sourceTree = ""; }; + 667E5D802B8E444D00EBCFC4 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 6691CA7D2B839C2D00B2A7B8 /* NotificationService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; 66C491F82B24D25A00CEA16D /* ConfigExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigExtension.swift; sourceTree = ""; }; 66C491FA2B24D32600CEA16D /* CoreExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreExtension.swift; sourceTree = ""; }; 66C491FC2B24D36500CEA16D /* AudioRouteUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRouteUtils.swift; sourceTree = ""; }; @@ -222,6 +258,13 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 660AAF782B839271004C0FA6 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; D719ABB02ABC67BF00B41C10 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -232,6 +275,16 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 660AAF7C2B839272004C0FA6 /* msgNotificationService */ = { + isa = PBXGroup; + children = ( + 667E5D802B8E444D00EBCFC4 /* GoogleService-Info.plist */, + 6691CA7D2B839C2D00B2A7B8 /* NotificationService.swift */, + 660AAF842B8392E0004C0FA6 /* msgNotificationService.entitlements */, + ); + path = msgNotificationService; + sourceTree = ""; + }; 662B69D72B25DDF6007118BF /* TelecomManager */ = { isa = PBXGroup; children = ( @@ -294,6 +347,7 @@ children = ( 660D8A702B517D260092694D /* GoogleService-Info.plist */, D719ABB52ABC67BF00B41C10 /* Linphone */, + 660AAF7C2B839272004C0FA6 /* msgNotificationService */, D719ABB42ABC67BF00B41C10 /* Products */, A31AF2AB8C6A3D7B7EA3B424 /* Pods */, ); @@ -303,6 +357,7 @@ isa = PBXGroup; children = ( D719ABB32ABC67BF00B41C10 /* Linphone.app */, + 660AAF7B2B839271004C0FA6 /* msgNotificationService.appex */, ); name = Products; sourceTree = ""; @@ -619,12 +674,30 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 660AAF7A2B839271004C0FA6 /* msgNotificationService */ = { + isa = PBXNativeTarget; + buildConfigurationList = 660AAF832B839272004C0FA6 /* Build configuration list for PBXNativeTarget "msgNotificationService" */; + buildPhases = ( + 660AAF772B839271004C0FA6 /* Sources */, + 660AAF782B839271004C0FA6 /* Frameworks */, + 660AAF792B839271004C0FA6 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = msgNotificationService; + productName = msgNotificationService; + productReference = 660AAF7B2B839271004C0FA6 /* msgNotificationService.appex */; + productType = "com.apple.product-type.app-extension"; + }; D719ABB22ABC67BF00B41C10 /* Linphone */ = { isa = PBXNativeTarget; buildConfigurationList = D719ABC22ABC67BF00B41C10 /* Build configuration list for PBXNativeTarget "Linphone" */; buildPhases = ( D719ABAF2ABC67BF00B41C10 /* Sources */, D719ABB02ABC67BF00B41C10 /* Frameworks */, + 660AAF802B839272004C0FA6 /* Embed Foundation Extensions */, D719ABB12ABC67BF00B41C10 /* Resources */, D7FB55122AD53FE200A5AB15 /* Run Script */, 66BF2D4B2B558A3100A5F2E3 /* Crashlytics */, @@ -632,6 +705,7 @@ buildRules = ( ); dependencies = ( + 660AAF7E2B839272004C0FA6 /* PBXTargetDependency */, ); name = Linphone; productName = Linphone; @@ -648,6 +722,10 @@ LastSwiftUpdateCheck = 1430; LastUpgradeCheck = 1430; TargetAttributes = { + 660AAF7A2B839271004C0FA6 = { + CreatedOnToolsVersion = 15.0.1; + LastSwiftMigration = 1500; + }; D719ABB22ABC67BF00B41C10 = { CreatedOnToolsVersion = 14.3.1; }; @@ -667,11 +745,21 @@ projectRoot = ""; targets = ( D719ABB22ABC67BF00B41C10 /* Linphone */, + 660AAF7A2B839271004C0FA6 /* msgNotificationService */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 660AAF792B839271004C0FA6 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 667E5D7F2B8E430C00EBCFC4 /* linphonerc-factory in Resources */, + 667E5D812B8E444E00EBCFC4 /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D719ABB12ABC67BF00B41C10 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -741,6 +829,18 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 660AAF772B839271004C0FA6 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 66FBFC482B83B8CC00BC6AB1 /* Log.swift in Sources */, + 6691CA7E2B839C2D00B2A7B8 /* NotificationService.swift in Sources */, + 66FBFC492B83BD2400BC6AB1 /* ConfigExtension.swift in Sources */, + 66FBFC4A2B83BD3300BC6AB1 /* FileUtils.swift in Sources */, + 66FBFC4B2B83BD7B00BC6AB1 /* CoreExtension.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D719ABAF2ABC67BF00B41C10 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -839,7 +939,95 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 660AAF7E2B839272004C0FA6 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 660AAF7A2B839271004C0FA6 /* msgNotificationService */; + targetProxy = 660AAF7D2B839272004C0FA6 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin XCBuildConfiguration section */ + 660AAF812B839272004C0FA6 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = msgNotificationService/msgNotificationService.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = Z2V957B3D6; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "DEBUG=1", + ); + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = msgNotificationService/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = msgNotificationService; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + OTHER_SWIFT_FLAGS = "$(inherited)"; + PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone.msgNotificationService; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 660AAF822B839272004C0FA6 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = msgNotificationService/msgNotificationService.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = Z2V957B3D6; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = msgNotificationService/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = msgNotificationService; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + OTHER_SWIFT_FLAGS = "$(inherited)"; + PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone.msgNotificationService; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; D719ABC02ABC67BF00B41C10 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -958,6 +1146,7 @@ D719ABC32ABC67BF00B41C10 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; @@ -1013,6 +1202,7 @@ D719ABC42ABC67BF00B41C10 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; @@ -1064,6 +1254,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 660AAF832B839272004C0FA6 /* Build configuration list for PBXNativeTarget "msgNotificationService" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 660AAF812B839272004C0FA6 /* Debug */, + 660AAF822B839272004C0FA6 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; D719ABAE2ABC67BF00B41C10 /* Build configuration list for PBXProject "Linphone" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Linphone/Core/CoreContext.swift b/Linphone/Core/CoreContext.swift index 676c6caed..bde91d495 100644 --- a/Linphone/Core/CoreContext.swift +++ b/Linphone/Core/CoreContext.swift @@ -26,6 +26,10 @@ import Combine import UniformTypeIdentifiers import Network +#if USE_CRASHLYTICS +import Firebase +#endif + final class CoreContext: ObservableObject { static let shared = CoreContext() @@ -66,36 +70,35 @@ final class CoreContext: ObservableObject { } func initialiseCore() throws { - +#if USE_CRASHLYTICS + FirebaseApp.configure() +#endif coreQueue.async { - LoggingService.Instance.logLevel = LogLevel.Debug - let configDir = Factory.Instance.getConfigDir(context: nil) - - Factory.Instance.logCollectionPath = configDir + Factory.Instance.logCollectionPath = Factory.Instance.getConfigDir(context: nil) Factory.Instance.enableLogCollection(state: LogCollectionState.Enabled) - Log.info("Initialising core") - let url = NSURL(fileURLWithPath: configDir) - if let pathComponent = url.appendingPathComponent("linphonerc") { - let filePath = pathComponent.path - let fileManager = FileManager.default - if !fileManager.fileExists(atPath: filePath) { - let path = Bundle.main.path(forResource: "linphonerc-default", ofType: nil) - if path != nil { - try? FileManager.default.copyItem(at: NSURL(fileURLWithPath: path!) as URL, to: pathComponent) + Log.info("Checking if linphonerc file exists already. If not, creating one as a copy of linphonerc-default") + if let rcDir = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Config.appGroupName)? + .appendingPathComponent("Library/Preferences/linphone") { + let rcFileUrl = rcDir.appendingPathComponent("linphonerc") + if !FileManager.default.fileExists(atPath: rcFileUrl.path) { + do { + try FileManager.default.createDirectory(at: rcDir, withIntermediateDirectories: true) + if let pathToDefaultConfig = Bundle.main.path(forResource: "linphonerc-default", ofType: nil) { + try FileManager.default.copyItem(at: URL(fileURLWithPath: pathToDefaultConfig), to: rcFileUrl) + Log.info("Successfully copied linphonerc-default configuration") + } + } catch let error { + Log.error("Failed to copy default linphonerc file: \(error.localizedDescription)") } + } else { + Log.info("Found existing linphonerc file, skip copying of linphonerc-default configuration") } } - let config = try? Factory.Instance.createConfigWithFactory( - path: "\(configDir)/linphonerc", - factoryPath: Bundle.main.path(forResource: "linphonerc-factory", ofType: nil) - ) - - if config != nil { - self.mCore = try? Factory.Instance.createCoreWithConfig(config: config!, systemContext: nil) - } + Log.info("Initialising core") + self.mCore = try? Factory.Instance.createSharedCoreWithConfig(config: Config.get(), systemContext: nil, appGroupId: Config.appGroupName, mainCore: true) linphone_core_set_push_registry_dispatch_queue(self.mCore.getCobject, Unmanaged.passUnretained(coreQueue).toOpaque()) self.mCore.autoIterateEnabled = false @@ -169,9 +172,6 @@ final class CoreContext: ObservableObject { if cbVal.state == .Ok { self.loggingInProgress = false self.loggedIn = true - if self.mCore.consolidatedPresence != ConsolidatedPresence.Online { - self.onForeground() - } } else if cbVal.state == .Progress { self.loggingInProgress = true } else { @@ -198,8 +198,11 @@ final class CoreContext: ObservableObject { }) self.mCoreSuscriptions.insert(self.mCore.publisher?.onAccountRegistrationStateChanged?.postOnCoreQueue { (cbVal: (core: Core, account: Account, state: RegistrationState, message: String)) in - // If registration failed, remove account from core - if cbVal.state != .Ok && cbVal.state != .Progress { + if cbVal.state == .Ok { + if self.mCore.consolidatedPresence != ConsolidatedPresence.Online { + self.updatePresence(core: self.mCore, presence: ConsolidatedPresence.Online) + } + } else if cbVal.state != .Ok && cbVal.state != .Progress { // If registration failed, remove account from core let params = cbVal.account.params let clonedParams = params?.clone() clonedParams?.registerEnabled = false @@ -266,27 +269,35 @@ final class CoreContext: ObservableObject { try? self.mCore.start() } } - func onForeground() { - coreQueue.async { - // We can't rely on defaultAccount?.params?.isPublishEnabled - // as it will be modified by the SDK when changing the presence status - if self.mCore.config!.getBool(section: "app", key: "publish_presence", defaultValue: true) { - Log.info("App is in foreground, PUBLISHING presence as Online") - self.mCore.consolidatedPresence = ConsolidatedPresence.Online - } + + func updatePresence(core : Core, presence : ConsolidatedPresence) { + if core.config!.getBool(section: "app", key: "publish_presence", defaultValue: true) { + core.consolidatedPresence = presence } } - func onBackground() { + func onEnterForeground() { coreQueue.async { // We can't rely on defaultAccount?.params?.isPublishEnabled // as it will be modified by the SDK when changing the presence status - if self.mCore.config!.getBool(section: "app", key: "publish_presence", defaultValue: true) { - Log.info("App is in background, un-PUBLISHING presence info") - // We don't use ConsolidatedPresence.Busy but Offline to do an unsubscribe, - // Flexisip will handle the Busy status depending on other devices - self.mCore.consolidatedPresence = ConsolidatedPresence.Offline - } + + Log.info("App is in foreground, PUBLISHING presence as Online") + self.updatePresence(core: self.mCore, presence: ConsolidatedPresence.Online) + try? self.mCore.start() + } + } + + func onEnterBackground() { + coreQueue.async { + // We can't rely on defaultAccount?.params?.isPublishEnabled + // as it will be modified by the SDK when changing the presence status + Log.info("App is in background, un-PUBLISHING presence info") + + // We don't use ConsolidatedPresence.Busy but Offline to do an unsubscribe, + // Flexisip will handle the Busy status depending on other devices + self.updatePresence(core: self.mCore, presence: ConsolidatedPresence.Offline) + // self.mCore.iterate() + self.mCore.stop() } } diff --git a/Linphone/Info.plist b/Linphone/Info.plist index 0cc800b37..1639aabd3 100644 --- a/Linphone/Info.plist +++ b/Linphone/Info.plist @@ -6,6 +6,8 @@ ITSEncryptionExportComplianceCode b5cb085f-772a-4a4f-8c77-5d1332b1f93f + NSSupportsSuddenTermination + UIAppFonts NotoSans-Light.ttf diff --git a/Linphone/Linphone.entitlements b/Linphone/Linphone.entitlements index f88b0a974..5ac776606 100644 --- a/Linphone/Linphone.entitlements +++ b/Linphone/Linphone.entitlements @@ -13,9 +13,12 @@ group.belledonne-communications.linphone group.org.linphone.phone.linphoneExtension group.org.linphone.phone.msgNotification - group.org.linphone.phone.logs com.apple.security.files.user-selected.read-only + keychain-access-groups + + $(AppIdentifierPrefix)org.linphone.phone + diff --git a/Linphone/LinphoneApp.swift b/Linphone/LinphoneApp.swift index 06435c86a..6f62860e6 100644 --- a/Linphone/LinphoneApp.swift +++ b/Linphone/LinphoneApp.swift @@ -18,23 +18,54 @@ */ import SwiftUI -#if USE_CRASHLYTICS -import Firebase -#endif +import linphonesw + +let accountTokenNotification = Notification.Name("AccountCreationTokenReceived") class AppDelegate: NSObject, UIApplicationDelegate { - func application(_ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { -#if USE_CRASHLYTICS - FirebaseApp.configure() -#endif - return true + + func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + let tokenStr = deviceToken.map { String(format: "%02.2hhx", $0) }.joined() + Log.info("Received remote push token : \(tokenStr)") + CoreContext.shared.doOnCoreQueue { core in + Log.info("Forwarding remote push token to core") + core.didRegisterForRemotePushWithStringifiedToken(deviceTokenStr: tokenStr + ":remote") + } } + + func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { + Log.error("Failed to register for push notifications : \(error.localizedDescription)") + } + + func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + Log.info("Received background push notification, payload = \(userInfo.description)") + /* + let creationToken = (userInfo["customPayload"] as? NSDictionary)?["token"] as? String + if let creationToken = creationToken { + NotificationCenter.default.post(name: accountTokenNotification, object: nil, userInfo: ["token": creationToken]) + } + completionHandler(UIBackgroundFetchResult.newData)*/ + } + + func applicationWillTerminate(_ application: UIApplication) { + Log.info("IOS applicationWillTerminate") + CoreContext.shared.doOnCoreQueue(synchronous: true) { core in + Log.info("applicationWillTerminate - Stopping linphone core") + MagicSearchSingleton.shared.destroyMagicSearch() + if core.globalState != GlobalState.Off { + core.stop() + } else { + Log.info("applicationWillTerminate - Core already stopped") + } + } + } + } @main struct LinphoneApp: App { + @Environment(\.scenePhase) var scenePhase @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate @ObservedObject private var coreContext = CoreContext.shared @ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared @@ -96,6 +127,15 @@ struct LinphoneApp: App { conversationViewModel = ConversationViewModel() } } + }.onChange(of: scenePhase) { newPhase in + if newPhase == .active { + Log.info("Entering foreground") + coreContext.onEnterForeground() + } else if newPhase == .inactive { + } else if newPhase == .background { + Log.info("Entering background") + coreContext.onEnterBackground() + } } } } diff --git a/Linphone/Localizable.xcstrings b/Linphone/Localizable.xcstrings index 6bb5ae918..b251f51a8 100644 --- a/Linphone/Localizable.xcstrings +++ b/Linphone/Localizable.xcstrings @@ -340,6 +340,23 @@ }, "First name*" : { + }, + "GC_MSG" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "You have been added to a chat room" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vous avez été ajouté à une conversation" + } + } + } }, "Hang up call" : { @@ -355,6 +372,23 @@ }, "I understand" : { + }, + "IM_MSG" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "You have received a message" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vous avez reçu un message" + } + } + } }, "Incoming call" : { @@ -379,6 +413,9 @@ }, "Job title" : { + }, + "Key" : { + "extractionState" : "manual" }, "Last name" : { @@ -656,4 +693,4 @@ } }, "version" : "1.0" -} \ No newline at end of file +} diff --git a/Linphone/Ressources/assistant_linphone_default_values b/Linphone/Ressources/assistant_linphone_default_values index f3723d7a9..c1e26073c 100644 --- a/Linphone/Ressources/assistant_linphone_default_values +++ b/Linphone/Ressources/assistant_linphone_default_values @@ -18,6 +18,7 @@ sip:conference-factory@sip.linphone.org sip:videoconference-factory@sip.linphone.org 1 + 1 1 1 https://lime.linphone.org/lime-server/lime-server.php diff --git a/Linphone/Ressources/linphonerc-default b/Linphone/Ressources/linphonerc-default index ea5356429..d0fb013ee 100644 --- a/Linphone/Ressources/linphonerc-default +++ b/Linphone/Ressources/linphonerc-default @@ -24,6 +24,7 @@ size=vga tunnel=disabled auto_start=1 record_aware=1 +disable_chat_feature=0 [tunnel] host= diff --git a/Linphone/Ressources/linphonerc-factory b/Linphone/Ressources/linphonerc-factory index 0abf8269d..eb16b25f2 100644 --- a/Linphone/Ressources/linphonerc-factory +++ b/Linphone/Ressources/linphonerc-factory @@ -45,6 +45,7 @@ store_friends=0 activation_code_length=4 prefer_basic_chat_room=1 record_aware=1 +disable_chat_feature=0 [account_creator] backend=1 diff --git a/Linphone/UI/Assistant/Viewmodel/AccountLoginViewModel.swift b/Linphone/UI/Assistant/Viewmodel/AccountLoginViewModel.swift index 2d6f9bffb..fbae5ce2d 100644 --- a/Linphone/UI/Assistant/Viewmodel/AccountLoginViewModel.swift +++ b/Linphone/UI/Assistant/Viewmodel/AccountLoginViewModel.swift @@ -90,7 +90,7 @@ class AccountLoginViewModel: ObservableObject { // And we ensure the account will start the registration process accountParams.registerEnabled = true accountParams.pushNotificationAllowed = true - accountParams.remotePushNotificationAllowed = false + accountParams.remotePushNotificationAllowed = true #if DEBUG let pushEnvironment = ".dev" #else diff --git a/Linphone/UI/Main/ContentView.swift b/Linphone/UI/Main/ContentView.swift index eaa9ed2f9..bce817c6e 100644 --- a/Linphone/UI/Main/ContentView.swift +++ b/Linphone/UI/Main/ContentView.swift @@ -894,25 +894,6 @@ struct ContentView: View { } orientation = newOrientation } - .onChange(of: scenePhase) { newPhase in - if newPhase == .active { - coreContext.onForeground() - /* - if !isShowStartCallFragment { - contactsManager.fetchContacts() - DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) { - historyListViewModel.computeCallLogsList() - } - } - */ - print("Active") - } else if newPhase == .inactive { - print("Inactive") - } else if newPhase == .background { - coreContext.onBackground() - print("Background") - } - } } func openMenu() { diff --git a/Linphone/Utils/Extensions/ConfigExtension.swift b/Linphone/Utils/Extensions/ConfigExtension.swift index 9d83cd891..b7b308c0a 100644 --- a/Linphone/Utils/Extensions/ConfigExtension.swift +++ b/Linphone/Utils/Extensions/ConfigExtension.swift @@ -36,7 +36,8 @@ extension Config { public static func get() -> Config { if _instance == nil { - let factoryPath = FileUtil.bundleFilePath(Core.runsInsideExtension() ? "linphonerc-factory-appex" : "linphonerc-factory-app")! + let factoryPath = FileUtil.bundleFilePath("linphonerc-factory")! + let configDir = Factory.Instance.getConfigDir(context: nil) _instance = Config.newForSharedCore(appGroupId: Config.appGroupName, configFilename: "linphonerc", factoryConfigFilename: factoryPath)! } return _instance! @@ -46,7 +47,7 @@ extension Config { return hasEntry(section: section, key: key) == 1 ? getString(section: section, key: key, defaultString: "") : nil } - static let appGroupName = "group.org.linphone.phone.logs" + static let appGroupName = "group.org.linphone.phone.msgNotification" // Needs to be the same name in App Group (capabilities in ALL targets - app & extensions - content + service), can't be stored in the Config itself the Config needs this value to get created static let teamID = Config.get().getString(section: "app", key: "team_id", defaultString: "") static let earlymediaContentExtCatIdentifier = Config.get().getString(section: "app", key: "extension_category", defaultString: "") diff --git a/Linphone/Utils/MagicSearchSingleton.swift b/Linphone/Utils/MagicSearchSingleton.swift index 4a58ca4c8..8b7352d08 100644 --- a/Linphone/Utils/MagicSearchSingleton.swift +++ b/Linphone/Utils/MagicSearchSingleton.swift @@ -43,6 +43,10 @@ final class MagicSearchSingleton: ObservableObject { var searchSubscription: AnyCancellable? + func destroyMagicSearch() { + magicSearch = nil + } + private init() { coreContext.doOnCoreQueue { core in self.domainDefaultAccount = core.defaultAccount?.params?.domain ?? "" diff --git a/Linphone/Utils/PermissionManager.swift b/Linphone/Utils/PermissionManager.swift index bad8532a5..e20bc2d51 100644 --- a/Linphone/Utils/PermissionManager.swift +++ b/Linphone/Utils/PermissionManager.swift @@ -20,25 +20,41 @@ import Foundation import Photos import Contacts +import UserNotifications +import SwiftUI class PermissionManager: ObservableObject { static let shared = PermissionManager() + @Published var pushPermissionGranted = false @Published var photoLibraryPermissionGranted = false @Published var cameraPermissionGranted = false - @Published var contactsPermissionGranted = false + @Published var contactsPermissionGranted = false @Published var microphonePermissionGranted = false private init() {} func getPermissions() { + pushNotificationRequestPermission() microphoneRequestPermission() photoLibraryRequestPermission() cameraRequestPermission() contactsRequestPermission() } + func pushNotificationRequestPermission() { + let options: UNAuthorizationOptions = [.alert, .sound, .badge] + UNUserNotificationCenter.current().requestAuthorization(options: options) { (granted, error) in + if let error = error { + Log.error("Unexpected error when asking for Push permission : \(error.localizedDescription)") + } + DispatchQueue.main.async { + self.pushPermissionGranted = granted + } + } + } + func microphoneRequestPermission() { AVAudioSession.sharedInstance().requestRecordPermission({ granted in DispatchQueue.main.async { @@ -62,13 +78,13 @@ class PermissionManager: ObservableObject { } }) } - - func contactsRequestPermission() { - let store = CNContactStore() - store.requestAccess(for: .contacts) { success, _ in - DispatchQueue.main.async { - self.contactsPermissionGranted = success - } - } - } + + func contactsRequestPermission() { + let store = CNContactStore() + store.requestAccess(for: .contacts) { success, _ in + DispatchQueue.main.async { + self.contactsPermissionGranted = success + } + } + } } diff --git a/Podfile b/Podfile index 696a25e90..cc53ba162 100644 --- a/Podfile +++ b/Podfile @@ -30,6 +30,15 @@ target 'Linphone' do end +target 'msgNotificationService' do + # Uncomment the next line if you're using Swift or would like to use dynamic frameworks + use_frameworks! + + # Pods for messagesNotification + basic_pods + +end + post_install do |installer| app_project = Xcodeproj::Project.open(Dir.glob("*.xcodeproj")[0]) app_project.native_targets.each do |target| diff --git a/msgNotificationService/GoogleService-Info.plist b/msgNotificationService/GoogleService-Info.plist new file mode 100644 index 000000000..b493c102e --- /dev/null +++ b/msgNotificationService/GoogleService-Info.plist @@ -0,0 +1,36 @@ + + + + + CLIENT_ID + 221368768663-b8e48em01it3pt04vp1k0ddrgrcrju65.apps.googleusercontent.com + REVERSED_CLIENT_ID + com.googleusercontent.apps.221368768663-b8e48em01it3pt04vp1k0ddrgrcrju65 + API_KEY + AIzaSyDJTtlRCM7IqdVUU2dSIYq2YIsTz6bqnkI + GCM_SENDER_ID + 221368768663 + PLIST_VERSION + 1 + BUNDLE_ID + org.linphone.phone.msgNotificationService + PROJECT_ID + linphone-iphone + STORAGE_BUCKET + linphone-iphone.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:221368768663:ios:ccf2c32eadcd3a0f9431d2 + DATABASE_URL + https://linphone-iphone.firebaseio.com + + \ No newline at end of file diff --git a/msgNotificationService/Info.plist b/msgNotificationService/Info.plist new file mode 100644 index 000000000..59a92df5e --- /dev/null +++ b/msgNotificationService/Info.plist @@ -0,0 +1,31 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + msgNotificationService + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSExtension + + NSExtensionPointIdentifier + com.apple.usernotifications.service + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).NotificationService + + + diff --git a/msgNotificationService/NotificationService.swift b/msgNotificationService/NotificationService.swift new file mode 100644 index 000000000..ef3f6439e --- /dev/null +++ b/msgNotificationService/NotificationService.swift @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-iphone + * + * 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, see . + */ + +// swiftlint:disable identifier_name + +import UserNotifications +import linphonesw +#if USE_CRASHLYTICS +import Firebase +#endif + +var LINPHONE_DUMMY_SUBJECT = "dummy subject" + +extension String { + func getDisplayNameFromSipAddress(lc: Core) -> String? { + Log.info("looking for display name for \(self)") + + let defaults = UserDefaults.init(suiteName: Config.appGroupName) + let addressBook = defaults?.dictionary(forKey: "addressBook") + + if addressBook == nil { + Log.info("address book not found in userDefaults") + return nil + } + + var usePrefix = true + if let account = lc.defaultAccount, let params = account.params { + usePrefix = params.useInternationalPrefixForCallsAndChats + } + + if let simpleAddr = lc.interpretUrl(url: self, applyInternationalPrefix: usePrefix) { + simpleAddr.clean() + let nomalSipaddr = simpleAddr.asString() + if let displayName = addressBook?[nomalSipaddr] as? String { + Log.info("display name for \(self): \(displayName)") + return displayName + } + } + + Log.info("display name for \(self) not found in userDefaults") + return nil + } +} + +struct MsgData: Codable { + var from: String? + var body: String? + var subtitle: String? + var callId: String? + var localAddr: String? + var peerAddr: String? +} + +class NotificationService: UNNotificationServiceExtension { + + var contentHandler: ((UNNotificationContent) -> Void)? + var bestAttemptContent: UNMutableNotificationContent? + + var lc: Core? + + override init() { + super.init() +#if USE_CRASHLYTICS + FirebaseApp.configure() +#endif + } + + override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { + + self.contentHandler = contentHandler + bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) + + LoggingService.Instance.logLevel = LogLevel.Debug + Factory.Instance.logCollectionPath = Factory.Instance.getConfigDir(context: nil) + Factory.Instance.enableLogCollection(state: LogCollectionState.Enabled) + Log.info("[msgNotificationService] start msgNotificationService extension") + /* + if (VFSUtil.vfsEnabled(groupName: Config.appGroupName) && !VFSUtil.activateVFS()) { + VFSUtil.log("[VFS] Error unable to activate.", .error) + } + */ + if let bestAttemptContent = bestAttemptContent { + createCore() + + if !lc!.config!.getBool(section: "app", key: "disable_chat_feature", defaultValue: true) { + Log.info("received push payload : \(bestAttemptContent.userInfo.debugDescription)") + + /* + let defaults = UserDefaults.init(suiteName: Config.appGroupName) + if let chatroomsPushStatus = defaults?.dictionary(forKey: "chatroomsPushStatus") { + let aps = bestAttemptContent.userInfo["aps"] as? NSDictionary + let alert = aps?["alert"] as? NSDictionary + let fromAddresses = alert?["loc-args"] as? [String] + + if let from = fromAddresses?.first { + if ((chatroomsPushStatus[from] as? String) == "disabled") { + NotificationService.log.message(message: "message comes from a muted chatroom, ignore it") + contentHandler(UNNotificationContent()) + } + } + } + */ + if let chatRoomInviteAddr = bestAttemptContent.userInfo["chat-room-addr"] as? String, !chatRoomInviteAddr.isEmpty { + Log.info("fetch chat room for invite, addr: \(chatRoomInviteAddr)") + let chatRoom = lc!.getNewChatRoomFromConfAddr(chatRoomAddr: chatRoomInviteAddr) + + if let chatRoom = chatRoom { + stopCore() + Log.info("chat room invite received") + bestAttemptContent.title = NSLocalizedString("GC_MSG", comment: "") + if chatRoom.hasCapability(mask: ChatRoom.Capabilities.OneToOne.rawValue) { + if chatRoom.peerAddress?.displayName?.isEmpty != true { + bestAttemptContent.body = chatRoom.peerAddress!.displayName! + } else { + bestAttemptContent.body = chatRoom.peerAddress!.username! + } + } else { + bestAttemptContent.body = chatRoom.subject! + } + contentHandler(bestAttemptContent) + return + } + } else if let callId = bestAttemptContent.userInfo["call-id"] as? String { + Log.info("fetch msg for callid ["+callId+"]") + let message = lc!.getNewMessageFromCallid(callId: callId) + + if let message = message { + let msgData = parseMessage(message: message) + + // Extension only upates app's badge when main shared core is Off = extension's core is On. + // Otherwise, the app will update the badge. + if lc?.globalState == GlobalState.On, let badge = updateBadge() as NSNumber? { + bestAttemptContent.badge = badge + } + + stopCore() + + bestAttemptContent.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "msg.caf")) + bestAttemptContent.title = NSLocalizedString("Message received", comment: "") + if let subtitle = msgData?.subtitle { + bestAttemptContent.subtitle = subtitle + } + if let body = msgData?.body { + bestAttemptContent.body = body + } + + bestAttemptContent.categoryIdentifier = "msg_cat" + + bestAttemptContent.userInfo.updateValue(msgData?.callId as Any, forKey: "CallId") + bestAttemptContent.userInfo.updateValue(msgData?.from as Any, forKey: "from") + bestAttemptContent.userInfo.updateValue(msgData?.peerAddr as Any, forKey: "peer_addr") + bestAttemptContent.userInfo.updateValue(msgData?.localAddr as Any, forKey: "local_addr") + + if message.reactionContent != " " { + contentHandler(bestAttemptContent) + } else { + contentHandler(UNNotificationContent()) + } + + return + } else { + Log.info("Message not found for callid ["+callId+"]") + } + } + } + serviceExtensionTimeWillExpire() + } + + } + + override func serviceExtensionTimeWillExpire() { + // Called just before the extension will be terminated by the system. + // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. + Log.warn("serviceExtensionTimeWillExpire") + stopCore() + if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { + NSLog("[msgNotificationService] serviceExtensionTimeWillExpire") + bestAttemptContent.categoryIdentifier = "app_active" + + if let chatRoomInviteAddr = bestAttemptContent.userInfo["chat-room-addr"] as? String, !chatRoomInviteAddr.isEmpty { + bestAttemptContent.title = NSLocalizedString("GC_MSG", comment: "") + bestAttemptContent.body = "" + bestAttemptContent.sound = UNNotificationSound(named: UNNotificationSoundName("msg.caf")) // TODO : temporary fix, to be removed after flexisip release + } else { + bestAttemptContent.title = NSLocalizedString("Message received", comment: "") + bestAttemptContent.body = NSLocalizedString("IM_MSG", comment: "") + } + contentHandler(bestAttemptContent) + } + } + + func parseMessage(message: PushNotificationMessage) -> MsgData? { + + var content = "" + if message.isConferenceInvitationNew { + content = NSLocalizedString("📅 You are invited to a meeting", comment: "") + } else if message.isConferenceInvitationUpdate { + content = NSLocalizedString("📅 Meeting has been modified", comment: "") + } else if message.isConferenceInvitationCancellation { + content = NSLocalizedString("📅 Meeting has been cancelled", comment: "") + } else { + content = message.isText ? message.textContent! : "🗻" + } + + let fromAddr = message.fromAddr?.username + let callId = message.callId + let localUri = message.localAddr?.asStringUriOnly() + let peerUri = message.peerAddr?.asStringUriOnly() + let reactionContent = message.reactionContent + let from: String + if let fromDisplayName = message.fromAddr?.asStringUriOnly().getDisplayNameFromSipAddress(lc: lc!) { + from = fromDisplayName + } else { + from = fromAddr! + } + + var msgData = MsgData(from: fromAddr, body: "", subtitle: "", callId: callId, localAddr: localUri, peerAddr: peerUri) + + if let showMsg = lc!.config?.getBool(section: "app", key: "show_msg_in_notif", defaultValue: true), showMsg == true { + if let subject = message.subject as String?, !subject.isEmpty { + msgData.subtitle = subject + if reactionContent == nil { + msgData.body = from + " : " + content + } else { + msgData.body = from + NSLocalizedString(" has reacted by ", comment: "") + reactionContent! + NSLocalizedString(" to: ", comment: "") + content + } + } else { + msgData.subtitle = from + msgData.body = content + } + } else { + if let subject = message.subject as String?, !subject.isEmpty { + msgData.body = subject + " : " + from + } else { + msgData.body = from + } + } + + Log.info("received msg size : \(content.count) \n") + return msgData + } + + func createCore() { + Log.info("[msgNotificationService] create core") + + lc = try? Factory.Instance.createSharedCoreWithConfig(config: Config.get(), systemContext: nil, appGroupId: Config.appGroupName, mainCore: false) + } + + func stopCore() { + Log.info("stop core") + if let lc = lc { + lc.stop() + } + } + + func updateBadge() -> Int { + var count = 0 + count += lc!.unreadChatMessageCount + count += lc!.missedCallsCount + count += lc!.callsNb + Log.info("badge: \(count)\n") + + return count + } + +} + +// swiftlint:enable identifier_name diff --git a/msgNotificationService/msgNotificationService.entitlements b/msgNotificationService/msgNotificationService.entitlements new file mode 100644 index 000000000..b63a67474 --- /dev/null +++ b/msgNotificationService/msgNotificationService.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.application-groups + + group.org.linphone.phone.msgNotification + + keychain-access-groups + + $(AppIdentifierPrefix)org.linphone.phone + + +