Contact usRequest a demo

Notifications

If the SDK is configured to use notifications, there are currently two possible types of notification visible to the user:

  • There’s a new message

  • There’s an incoming call

If notifications are disabled, the user’s device token is deleted from the Collaboration Server to ensure they don’t receive any notifications. The mobile SDK doesn’t provide a way for users to manage their devices. This must be done outside the SDK.

Incoming call notifications

The SDK uses a special type of notification, PushKit, to trigger the incoming call dialog. PushKit notifications use the Apple Push Notification service (APNs). You must configure the Collaboration Server to send PushKit notifications.

The incoming call dialog is a native iOS user interface managed with the CallKit framework. It behaves the way users are used to from other applications. It isn’t possible to change this UI’s appearance or behavior, except for the icon that appears on the provider call button.

You can customize the icon by adding an icon to your application’s resource. Prepare the icon as described in the Apple documentation.

You must also specify the name of the icon in the Unblu client configuration. This is a static variable that you should specify as early as possible, for example, in the AppDelegate function.

Listing 1. Specifying the name of the icon for the incoming call dialog
class AppDelegate {
    //...
    override func application(_:didFinishLaunchingWithOptions:) {
            UnbluClientConfiguration.callKitProviderIconResourceName = "ProviderIcon"
    }
}

If your application isn’t running when there’s an incoming call, the handler that displays the incoming call control UI works before initializing the Unblu API.

Incoming call
Figure 1. iOS incoming call dialog

Voice greeting

If the iPhone is locked when a user answers a call, they usually unlock the screen quickly using biometric authentication. For video calls, the app then launches automatically.

If the call is an audio call, or if there’s a delay of a few seconds before the user unlocks their iPhone, the behavior is different. After the user answers the call, iOS loads the native call control UI instead of launching the app. If your app requires authentication before running UnbluView, the audio stream isn’t established at this point.

To provide a better user experience in such cases, you can set up a voice greeting that’s played to the user once they accept the call. For example, the greeting could guide the user to access the call: "Please tap the button to go to the application where the call is waiting for you."

Voice greetings are synthesized from the text you provide in the text property com.unblu.mobiledevice.deviceAudioGreeting on the Unblu Collaboration Server.

Biometric authentication after receiving a call

If your app uses biometric authentication, for example FaceID, when the app starts after receiving a call, authentication must start when the app is in an active state. This is because when the incoming call UI appears, the app starts but is in the background. After the user accepts the call, the app transitions from the background state to the active state and the authentication process can begin.

If the Unblu API runs before the user unlocks the phone, the API starts in the background, which can lead to problems.

To detect this situation and wait for the app to become fully initialized, use something like the code snippet below:

Listing 2. Code to detect biometric authentication before launching the Unblu API
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) {
    [weak self] success, authenticationError in
    if success {
        DispatchQueue.main.async {
            // Launch unblu API here
        }
    }
}

App Store review

During the review of your Unblu-enabled iOS app, Apple may request a video recording related to the CallKit and VoIP functions. This is because iOS requests permission to access the microphone, camera and so on during the first call.

Past experience suggests that the video must meet the following requirements:

  • The recording must show a physical device running iOS, not a simulator.

  • The video must show the process of receiving the first call after installing the application.

  • The process of requesting access must be visible in the recording.

  • After you accept the call, go to the Home screen to show the microphone indicators.

  • Finally, return to the call and end it.

Encrypted notifications

The SDK provides three versions of notification encryption:

  • Encrypted: All notifications are encrypted. The SDK decrypts the notifications automatically.

    If you opt for Encrypted notifications, you should use the notification service extension; see the related known issue for more information.

  • EncryptedService: All notifications are encrypted. The notifications are decrypted in the notification service extension. You must add the notification service extension yourself.

  • NotEncrypted: Notifications aren’t encrypted. This is a legacy option.

You must specify the version you want to use by setting UnbluClientConfiguration.unbluPushNotificationVersion:

Listing 3. Specifying the encryption version for iOS notifications
var config = UnbluClientConfiguration(...)
config.unbluPushNotificationVersion = .Encrypted

Encrypted notifications are decrypted with a key stored in Keychain Access. This key is automatically updated from the Collaboration Server over a protected HTTPS connection.

The diagram below provides an overview of how notifications with different versions of encryption are processed.

Notification flow for different versions of encryption
Figure 2. Notification flow for different versions of encryption

Notification service extension

This is a special module that modifies the content of a remote notification before it’s delivered to the user. Add it to the application manually:

  1. First, follow the instructions in the Apple article Modifying content in newly delivered notifications.

  2. To save the secret key and access it from the extension, add the keychain group group.com.unblu.coreSdk.shared to the Keychain Sharing section for the app and the extension.

  3. Add UnbluCoreSDK.framework (Do not Embed) to Framework and Libraries.

  4. Configure the Unblu SDK to use the service extension:

    var config = UnbluClientConfiguration(...)
    config.unbluPushNotificationVersion = .EncryptedService
  5. Replace the code in the NotificationService class with the code below:

    class NotificationService: UNNotificationServiceExtension {
    
        var contentHandler: ((UNNotificationContent) -> Void)?
        var bestAttemptContent: UNMutableNotificationContent?
    
        override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
    
            self.contentHandler = contentHandler
            bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
            if let bestAttemptContent = bestAttemptContent {
                if  let dictionary = decryptBody(userInfo: bestAttemptContent.userInfo)
                {
                    guard let body =  dictionary["text"] as? String else {
                        contentHandler(bestAttemptContent)
                        return
                    }
                    guard let title = dictionary["title"] as? String  else {
                        contentHandler(bestAttemptContent)
                        return
                    }
                    bestAttemptContent.userInfo = dictionary as [AnyHashable : Any]
                    bestAttemptContent.body = body
    
                    bestAttemptContent.title = title
                    bestAttemptContent.sound = .default
    
                } else {
                    bestAttemptContent.body = ""
                    bestAttemptContent.title = ""
                }
                contentHandler(bestAttemptContent)
            }
        }
    
        func decryptBody(userInfo: [AnyHashable : Any]) -> [String : Any?]? {
            if let encryptedData = userInfo[UnbluEncryptedNotificationServiceHelper.KEY_ENCRYPTED_DATA] {
                guard let dictionary = UnbluEncryptedNotificationServiceHelper.decode(encryptedData as! String)  else {
                    return nil
                }
                return dictionary
            }
            return nil
        }
    
        override func serviceExtensionTimeWillExpire() {
            // Called just before the extension is terminated by the system.
            // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload is used.
            if let contentHandler = contentHandler, let bestAttemptContent =  bestAttemptContent {
                contentHandler(bestAttemptContent)
            }
        }
    }

See also