From version < 51.6 >
edited by Simon Morlat
on 2020/06/25 17:03
To version < 51.7 >
edited by Simon Morlat
on 2020/06/25 17:06
< >
Change comment: There is no comment for this version



Page properties
... ... @@ -316,12 +316,134 @@
316 316  
317 317  When pressed, the main application is launched and can display the conversation that relates to the received message. The app extension and the main app have a shared filesystem and messaging framework, with synchronisation mechanisms.
318 318  
319 -This solution is presented with details in the section "Advertising instant messages with a Notification Service Extension". It is the one implemented in the Linphone application.
319 +This solution is presented with details in the below section "Advertising instant messages with a Notification Service Extension". It is the one implemented in the Linphone application.
320 320  )))
321 321  
322 322  === Advertising instant messages with a Notification Service Extension ===
323 323  
324 +This section describes a step by step procedure to implement state of art receiving of instant messages in a liblinphone based application, thanks to [[Notification Service Extension>>]].
324 324  
326 +==== Create the UNNotificationServiceExtension App Extension ====
327 +
328 +A **Remote** push notification cannot resume an application to perform tasks in background. However it can do so with an app extension. An app extension is a process that is linked to the app but it doesn't share the same container (not the same process, not the same file system). The first step is then to create a UNNotificationServiceExtension.
329 +
330 +Its goal is to catch the **Remote** push notification and modify the **user notification** before it is displayed. For this we need to connect to the server, get the new message then we can display the user notification.
331 +
332 +In Xcode, in the “capabilities” menu, you must add the **App Group** capability for both the app and the app extension with the same App Group identifier: the //AppGroupId//. This identifier is used to reach the file system that is shared between the main App and the UNNotificationServiceExtension.
333 +
334 +==== Create a Shared Core in app ====
335 +
336 +Since version 4.4, liblinphone has a new concept of **Shared Linphone Core** to be able to manage a LinphoneCore in both the app and the app extension. The goal of the Shared Cores is to be able to create several Cores at the same time with the same configuration while making sure that only one Shared Core can write to the configuration files and the databases at the same time.
337 +
338 +There are two types of Shared Cores:
339 + - The **Main Shared Core** is the Core owned by the application. It can stop the **Executor Shared Core** when it needs to start. It is the one that has the priority.
340 + - The **Executor Shared Core** is the Core of the app extension. It can't start if another Shared Core is already running and it can be stopped by the **Main Shared Core **at any time.
341 +
342 +The mutual exclusion between Shared Cores is handled by the liblinphone.
343 +
344 +(% class="box infomessage" %)
345 +(((
346 +The liblinphone methods for creating the Shared Core exists in C and Swift. A newly written application will prefer to use Swift, while an already existing application written in Objective-C can use C.
347 +)))
348 +
349 +The Main Shared Core is to be created as follows:
350 +
351 +1. Use// linphone_config_new_for_shared_core(app_group_id, config_filename, factory_path)// taking your //AppGroupId// and a config filename (i.e. linphonerc). It computes the path in the shared memory for your config file. The factory config path works as for a normal LinphoneCore. It returns a **LinphoneConfig** object suitable to instantiate a Shared Core.
352 +1. Use //linphone_factory_create_shared_core_with_config(factory, config, system_context, app_group_id, main_core)// to create the Shared Core. Set //main_core=TRUE// as we are creating a Main Shared Core for the app.
353 +
354 +Note: linphone_factory_create_shared_core (factory, config_filename, factory_config_path, system_context, app_group_id, main_core) combines the two steps in a single function.
355 +
356 +== 3 - Stop the Main Shared Core when going to background ==
357 +
358 +The app extension will need to start an **Executor Shared Core** to get the new messages. To allow that, you need to stop the **Main Shared Core** when the app goes in background. And you need to start it again when the app goes in foreground.
359 +
360 +- In (void)applicationDidEnterBackground: (UIApplication *)application You need to call linphone_core_stop()
361 +
362 +- In (void)applicationWillEnterForeground: (UIApplication *)application You need to call linphone_core_start()
363 +
364 +== 4 - Migrate all the configuration files to the shared file system ==
365 +
366 +You have created a **Main Shared Core** instead of the usual **Core**. Now all the configuration files need to be moved to the **shared file system**in order to still have the account configured, the messages received in database, the list of the call history, etc.
367 +
368 +For this we use these three functions:
369 +
370 +- const char *linphone_factory_get_config_dir(LinphoneFactory *factory, void *context) that leads to /Library/Application Support/linphone/
371 +
372 +- const char *linphone_factory_get_data_dir(LinphoneFactory *factory, void *context) that leads to /Library/Preferences/linphone/
373 +
374 +- const char *linphone_factory_get_download_dir(LinphoneFactory *factory, void *context) that leads to /Library/Caches/
375 +
376 +These functions return paths to the different iOS application configuration directories. It can be used to migrate files to the shared memory because of the context parameter. If context = NULL, the returned path is in the application file system. If context = AppGroupId, the path leads to the file system shared between the app and the app extension.
377 +
378 +== 5 - Set up the app extension to get the message ==
379 +
380 +The function didReceive() of the **NotificationService** app extension will be called when a **Remote** push notification is received. It has ~~30 seconds to get the message before the user notification will be displayed.
381 +
382 +A Shared Linphone Core is required to reach the msg. This time you want to create an **Executor Shared Core** as described in step 2 but by setting main_core=FALSE. The main app can stop the Executor Shared Core if it is running when the user put the application in foreground.
383 +
384 +Then you can call:
385 +
386 +- LinphonePushNotificationMessage *linphone_core_get_new_message_from_callid(lc, call_id) : to get the message from the //call_id// which comes from the push notification payload.
387 +
388 +- LinphoneChatRoom *linphone_core_get_new_chat_room_from_conf_addr(lc , chat_room_addr) : to get the new chat room from the //chat_room_addr// which comes from the push notification payload. This allows the app extension to tell its user when he has been added to a chat room.
389 +
391 +\\- Don't call linphone_core_start() yourself. The functions above will start the Shared Core if needed.
392 +\\- It is your responsibility to call linphone_core_stop() (if the Core is not started, it won't do anything). This allows the app extension to perform some actions using the Shared Core after receiving the message but it is better to stop the Shared Core as fast as possible to allow the other instance of NotificationService extension to process. One instance of the app extension is launched for every push (i.e. message) received. The synchronization is done in the Linphone-sdk.
393 +\\- These functions work when the application is in background AND in foreground. So, you can remove your code in the main application that handle messages notifications. The app extension will display ALL the messages to the user.
394 +
395 +== 6 - Adding actions in user notifications: UNNotificationContentExtension ==
396 +
397 +We have implemented the UNNotificationContentExtension app extension in addition of the UNNotificationServiceExtension. We use it to **reply** and **mark as seen** new messages.
398 +
399 +The user notifications displayed by the NotificationService extension needs a NotificationContent extension to add a **NotificationCategory**as well as some **actions** attached to this category. The categories and actions declared in the main app won't work when the app is in background.
400 +
401 +We have implemented the actions **reply** and **mark as seen**. That requires to start an Linphone Core. If the app is in foreground, the NotificationContent extension won't be able to start a Shared Core so the app will handle the actions.
402 +
403 +So, for the notifications launched by the NotificationService extension, you need to declare the notification categories in BOTH the NotificationContent extension and the app for the case the NotificationContent extension can’t do the processing because the app is in foreground.
404 +
405 +== 7 - Update Contact URI parameters ==
406 +
407 +In order to get push notification, the iOS app must send its push token(s) to Flexisip. Flexisip can then send a push notification request to the APNs server.
408 +
409 +These push paramers are sent in the Contact URI parameter in the REGISTER.
410 +
411 +We have updated the names and the syntax of the push parameters to follow the RFC 8599 about push notifications. Here are the new parameters we use :
412 +
413 +* "pn-prid=“ token [ “:” service ] *( “&” token “:” service )
414 +* "pn-param=" TeamId ”.” BundleId ”.” service *( “&” service )
415 +* "pn-provider=apns"
416 +* service = "voip" / "remote"
417 +* token = <voip push token or remote push token>
418 +
419 +Example with voip and remote push token:
420 +
421 +* pn-prid=00fc13adff78512:voip&c11292f7b74733d:remote
422 +*
423 +* pn-provider=apns
424 +
425 +Example only with remote push token:
426 +
427 +* pn-prid=c11292f7b74733d:remote
428 +*
429 +* pn-provider=apns
430 +
431 +The "voip" service is PushKit. You must provide the token provided by the PushKit API if you wan't to use PushKit.
432 +
433 +The "remote" service is the "basic" push notification service. This is the service that we now have to use for instant messaging due to iOS13 push policy.
434 +
435 +These parameters must be added manually to the proxy config contact URI using linphone_proxy_config_set_contact_uri_parameters().
436 +
437 += Notes =
438 +
439 +== iOS < 12 UNNotificationServiceExtension limitation ==
440 +
441 +It appears that in iOS < 12, if the UNNotificationServiceExtension takes more than 1 second to start, it is killed by the system. Once it has successfully started once, the process is kept in cache and works every time. It is removed from the cache after ~~30 minutes without any push notification.
442 +
443 +When the app extension fails to start, the push notification can't be caught to retrieve the message. We used the "loc-key" argument of the push notification JSON payload to put a key. This key is an entry in our translation files. So, when the app extension fails to start, we will display a localized message similar to "You have received a new message".
444 +
445 +This limitation is not documented by Apple and we haven't been able to reproduce it in iOS versions >= 12.
446 +
325 325  = Handling liblinphone log =
326 326  
327 327  In order to see liblinphone logs in your IOS app (for example in your Xcode console) follow these steps :