Merge remote-tracking branch 'refs/remotes/origin/master'

Conflicts:
	Linphone/Core/CoreContext.swift
	Linphone/Localizable.xcstrings
	Linphone/UI/Assistant/Viewmodel/AccountLoginViewModel.swift
This commit is contained in:
Benoit Martins 2024-03-14 15:29:31 +01:00
commit bf4e4042d3
19 changed files with 756 additions and 85 deletions

View file

@ -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 = "<group>"; };
660D8A702B517D260092694D /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
662B69D82B25DE18007118BF /* TelecomManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelecomManager.swift; sourceTree = "<group>"; };
662B69DA2B25DE25007118BF /* ProviderDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderDelegate.swift; sourceTree = "<group>"; };
667E5D802B8E444D00EBCFC4 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
6691CA7D2B839C2D00B2A7B8 /* NotificationService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
66C491F82B24D25A00CEA16D /* ConfigExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigExtension.swift; sourceTree = "<group>"; };
66C491FA2B24D32600CEA16D /* CoreExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreExtension.swift; sourceTree = "<group>"; };
66C491FC2B24D36500CEA16D /* AudioRouteUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRouteUtils.swift; sourceTree = "<group>"; };
@ -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 = "<group>";
};
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 = "<group>";
@ -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 = (

View file

@ -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()
}
}

View file

@ -6,6 +6,8 @@
<true/>
<key>ITSEncryptionExportComplianceCode</key>
<string>b5cb085f-772a-4a4f-8c77-5d1332b1f93f</string>
<key>NSSupportsSuddenTermination</key>
<false/>
<key>UIAppFonts</key>
<array>
<string>NotoSans-Light.ttf</string>

View file

@ -13,9 +13,12 @@
<string>group.belledonne-communications.linphone</string>
<string>group.org.linphone.phone.linphoneExtension</string>
<string>group.org.linphone.phone.msgNotification</string>
<string>group.org.linphone.phone.logs</string>
</array>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)org.linphone.phone</string>
</array>
</dict>
</plist>

View file

@ -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()
}
}
}
}

View file

@ -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"
}
}

View file

@ -18,6 +18,7 @@
<entry name="conference_factory_uri" overwrite="true">sip:conference-factory@sip.linphone.org</entry>
<entry name="audio_video_conference_factory_uri" overwrite="true">sip:videoconference-factory@sip.linphone.org</entry>
<entry name="push_notification_allowed" overwrite="true">1</entry>
<entry name="remote_push_notification_allowed" overwrite="true">1</entry>
<entry name="cpim_in_basic_chat_rooms_enabled" overwrite="true">1</entry>
<entry name="rtp_bundle" overwrite="true">1</entry>
<entry name="lime_server_url" overwrite="true">https://lime.linphone.org/lime-server/lime-server.php</entry>

View file

@ -24,6 +24,7 @@ size=vga
tunnel=disabled
auto_start=1
record_aware=1
disable_chat_feature=0
[tunnel]
host=

View file

@ -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

View file

@ -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

View file

@ -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() {

View file

@ -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: "")

View file

@ -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 ?? ""

View file

@ -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
}
}
}
}

View file

@ -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|

View file

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CLIENT_ID</key>
<string>221368768663-b8e48em01it3pt04vp1k0ddrgrcrju65.apps.googleusercontent.com</string>
<key>REVERSED_CLIENT_ID</key>
<string>com.googleusercontent.apps.221368768663-b8e48em01it3pt04vp1k0ddrgrcrju65</string>
<key>API_KEY</key>
<string>AIzaSyDJTtlRCM7IqdVUU2dSIYq2YIsTz6bqnkI</string>
<key>GCM_SENDER_ID</key>
<string>221368768663</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>org.linphone.phone.msgNotificationService</string>
<key>PROJECT_ID</key>
<string>linphone-iphone</string>
<key>STORAGE_BUCKET</key>
<string>linphone-iphone.appspot.com</string>
<key>IS_ADS_ENABLED</key>
<false></false>
<key>IS_ANALYTICS_ENABLED</key>
<false></false>
<key>IS_APPINVITE_ENABLED</key>
<true></true>
<key>IS_GCM_ENABLED</key>
<true></true>
<key>IS_SIGNIN_ENABLED</key>
<true></true>
<key>GOOGLE_APP_ID</key>
<string>1:221368768663:ios:ccf2c32eadcd3a0f9431d2</string>
<key>DATABASE_URL</key>
<string>https://linphone-iphone.firebaseio.com</string>
</dict>
</plist>

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>msgNotificationService</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.usernotifications.service</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).NotificationService</string>
</dict>
</dict>
</plist>

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
// 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

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.org.linphone.phone.msgNotification</string>
</array>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)org.linphone.phone</string>
</array>
</dict>
</plist>