notifications: add local notifications

Present local notifications when receiving incoming calls or
messages.

Change-Id: Ib650474c466678778bed26b564485316ed964a2f
Reviewed-by: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
diff --git a/Ring/Ring.xcodeproj/project.pbxproj b/Ring/Ring.xcodeproj/project.pbxproj
index 3ff8e2f..1591ff5 100644
--- a/Ring/Ring.xcodeproj/project.pbxproj
+++ b/Ring/Ring.xcodeproj/project.pbxproj
@@ -104,6 +104,7 @@
 		0E2D5F551F9145F200D574BF /* LinkNewDeviceCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0E2D5F541F9145F200D574BF /* LinkNewDeviceCell.xib */; };
 		0E403F811F7D797300C80BC2 /* MessageCellGenerated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E403F801F7D797300C80BC2 /* MessageCellGenerated.swift */; };
 		0E403F831F7D79B000C80BC2 /* MessageCellGenerated.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0E403F821F7D79B000C80BC2 /* MessageCellGenerated.xib */; };
+		0E44B62F202B9DE40060F71B /* LocalNotificationsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E44B62E202B9DE40060F71B /* LocalNotificationsHelper.swift */; };
 		0E48F9D31FDF150700D6CC08 /* ContactRequestManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E48F9D21FDF150700D6CC08 /* ContactRequestManager.swift */; };
 		0E4909611FE97A94005CAA50 /* ActiveLabel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E4909601FE97A94005CAA50 /* ActiveLabel.framework */; };
 		0E49096A1FEAB156005CAA50 /* CallsAdapter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0E4909691FEAB156005CAA50 /* CallsAdapter.mm */; };
@@ -126,6 +127,7 @@
 		0EB1A5CF1F8EBE03009923E2 /* DeviceCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0EB1A5CE1F8EBE03009923E2 /* DeviceCell.xib */; };
 		0EB1A5D11F8EBE23009923E2 /* DeviceCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB1A5D01F8EBE23009923E2 /* DeviceCell.swift */; };
 		0EB479951FA28A7300106AFD /* ButtonTransparentBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB479941FA28A7300106AFD /* ButtonTransparentBackground.swift */; };
+		0EBCAA4E202E60F000E2A545 /* default.wav in Resources */ = {isa = PBXBuildFile; fileRef = 0EBCAA4D202E60F000E2A545 /* default.wav */; };
 		0ED2B6FA1F96A075001572F0 /* LinkNewDeviceViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0ED2B6F91F96A075001572F0 /* LinkNewDeviceViewController.storyboard */; };
 		0ED2B6FC1F96A158001572F0 /* LinkNewDeviceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED2B6FB1F96A158001572F0 /* LinkNewDeviceViewController.swift */; };
 		0ED2B6FE1F96A16C001572F0 /* LinkNewDeviceViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED2B6FD1F96A16C001572F0 /* LinkNewDeviceViewModel.swift */; };
@@ -372,6 +374,7 @@
 		0E2D5F541F9145F200D574BF /* LinkNewDeviceCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LinkNewDeviceCell.xib; sourceTree = "<group>"; };
 		0E403F801F7D797300C80BC2 /* MessageCellGenerated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCellGenerated.swift; sourceTree = "<group>"; };
 		0E403F821F7D79B000C80BC2 /* MessageCellGenerated.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MessageCellGenerated.xib; sourceTree = "<group>"; };
+		0E44B62E202B9DE40060F71B /* LocalNotificationsHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotificationsHelper.swift; sourceTree = "<group>"; };
 		0E48F9D21FDF150700D6CC08 /* ContactRequestManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactRequestManager.swift; sourceTree = "<group>"; };
 		0E4909601FE97A94005CAA50 /* ActiveLabel.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ActiveLabel.framework; path = Carthage/Build/iOS/ActiveLabel.framework; sourceTree = "<group>"; };
 		0E4909681FEAB156005CAA50 /* CallsAdapter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CallsAdapter.h; sourceTree = "<group>"; };
@@ -396,6 +399,7 @@
 		0EB1A5CE1F8EBE03009923E2 /* DeviceCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = DeviceCell.xib; sourceTree = "<group>"; };
 		0EB1A5D01F8EBE23009923E2 /* DeviceCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceCell.swift; sourceTree = "<group>"; };
 		0EB479941FA28A7300106AFD /* ButtonTransparentBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonTransparentBackground.swift; sourceTree = "<group>"; };
+		0EBCAA4D202E60F000E2A545 /* default.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = default.wav; sourceTree = "<group>"; };
 		0ED2B6F91F96A075001572F0 /* LinkNewDeviceViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LinkNewDeviceViewController.storyboard; sourceTree = "<group>"; };
 		0ED2B6FB1F96A158001572F0 /* LinkNewDeviceViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkNewDeviceViewController.swift; sourceTree = "<group>"; };
 		0ED2B6FD1F96A16C001572F0 /* LinkNewDeviceViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkNewDeviceViewModel.swift; sourceTree = "<group>"; };
@@ -852,6 +856,7 @@
 		043999F51D1C2D9D00E99CD9 /* Ring */ = {
 			isa = PBXGroup;
 			children = (
+				0E44B62D202B9DC40060F71B /* Helpers */,
 				0E63F1F3202907090001F248 /* Ring.entitlements */,
 				0E0FF1A81FC38409003898C2 /* Database */,
 				02E1A0261DDE4C2E00D75B59 /* Services */,
@@ -983,6 +988,14 @@
 			path = DBHelpers;
 			sourceTree = "<group>";
 		};
+		0E44B62D202B9DC40060F71B /* Helpers */ = {
+			isa = PBXGroup;
+			children = (
+				0E44B62E202B9DE40060F71B /* LocalNotificationsHelper.swift */,
+			);
+			path = Helpers;
+			sourceTree = "<group>";
+		};
 		0E4909711FEAC822005CAA50 /* Calls */ = {
 			isa = PBXGroup;
 			children = (
@@ -1290,6 +1303,7 @@
 		1ABE07C61F0D86B300D36361 /* Resources */ = {
 			isa = PBXGroup;
 			children = (
+				0EBCAA4D202E60F000E2A545 /* default.wav */,
 				1ABE07DA1F0D915100D36361 /* Localizable.strings */,
 				04399A021D1C2D9D00E99CD9 /* Images.xcassets */,
 			);
@@ -1522,6 +1536,7 @@
 			files = (
 				1A2D18FD1F292DAD00B2C785 /* ConversationCell.xib in Resources */,
 				1ABE07DC1F0D915100D36361 /* Localizable.strings in Resources */,
+				0EBCAA4E202E60F000E2A545 /* default.wav in Resources */,
 				1A2D18E61F29197100B2C785 /* MessageAccessoryView.xib in Resources */,
 				1A2D18F81F292D7200B2C785 /* MessageCellSent.xib in Resources */,
 				1A2D18F61F292D7200B2C785 /* MessageCellReceived.xib in Resources */,
@@ -1715,6 +1730,7 @@
 				621231FB1F8D6FEE009B86F0 /* MessageCell.swift in Sources */,
 				56AC650E1E85694D00EA1AA9 /* DesignableTextField.swift in Sources */,
 				1A2D189A1F2642C000B2C785 /* NotificationCenter+Ring.swift in Sources */,
+				0E44B62F202B9DE40060F71B /* LocalNotificationsHelper.swift in Sources */,
 				1A2D18FC1F292DAD00B2C785 /* ConversationCell.swift in Sources */,
 				0E48F9D31FDF150700D6CC08 /* ContactRequestManager.swift in Sources */,
 				0E7CF4DD20165BFB00CD967D /* ButtonsContainerViewModel.swift in Sources */,
diff --git a/Ring/Ring/AppDelegate.swift b/Ring/Ring/AppDelegate.swift
index ab7c1a8..89d0743 100644
--- a/Ring/Ring/AppDelegate.swift
+++ b/Ring/Ring/AppDelegate.swift
@@ -28,7 +28,7 @@
 import PushKit
 
 @UIApplicationMain
-class AppDelegate: UIResponder, UIApplicationDelegate {
+class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
     var window: UIWindow?
     private let daemonService = DaemonService(dRingAdaptor: DRingAdapter())
     private let accountService = AccountsService(withAccountAdapter: AccountAdapter())
@@ -70,7 +70,9 @@
         self.window = UIWindow(frame: UIScreen.main.bounds)
 
         UserDefaults.standard.setValue(false, forKey: "_UIConstraintBasedLayoutLogUnsatisfiable")
-
+        if #available(iOS 10.0, *) {
+            UNUserNotificationCenter.current().delegate = self
+        }
         // initialize log format
         let console = ConsoleDestination()
         console.format = "$Dyyyy-MM-dd HH:mm:ss.SSS$d $C$L$c: $M"
@@ -101,7 +103,7 @@
 
         // load accounts during splashscreen
         // and ask the AppCoordinator to handle the first screen once loading is finished
-        self.conversationManager = ConversationsManager(with: self.conversationsService, accountsService: self.accountService)
+        self.conversationManager = ConversationsManager(with: self.conversationsService, accountsService: self.accountService, nameService: self.nameService)
         self.startDB()
         self.accountService.loadAccounts().subscribe { [unowned self] (_) in
             guard let currentAccount = self.accountService.currentAccount else {
@@ -134,6 +136,7 @@
         NotificationCenter.default.addObserver(self, selector: #selector(unregisterVoipNotifications),
                                                name: NSNotification.Name(rawValue: NotificationName.disablePushNotifications.rawValue),
                                                object: nil)
+        self.clearBadgeNumber()
         return true
     }
 
@@ -150,6 +153,10 @@
         self.stopDaemon()
     }
 
+    func applicationDidBecomeActive(_ application: UIApplication) {
+        self.clearBadgeNumber()
+    }
+
     // MARK: - Ring Daemon
     fileprivate func startDaemon() {
 
@@ -200,7 +207,7 @@
        self.accountService.setPushNotificationToken(token: "")
     }
 
-    func requestNotificationAuthorization() {
+    private func requestNotificationAuthorization() {
         let application = UIApplication.shared
         if #available(iOS 10.0, *) {
             UNUserNotificationCenter.current().delegate = application.delegate as? UNUserNotificationCenterDelegate
@@ -211,6 +218,50 @@
             application.registerUserNotificationSettings(settings)
         }
     }
+
+    private func clearBadgeNumber() {
+        UIApplication.shared.applicationIconBadgeNumber = 0
+        if #available(iOS 10.0, *) {
+            let center = UNUserNotificationCenter.current()
+            center.removeAllDeliveredNotifications()
+            center.removeAllPendingNotificationRequests()
+        } else {
+            UIApplication.shared.cancelAllLocalNotifications()
+        }
+    }
+
+    @available(iOS 10.0, *)
+    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
+        let data = response.notification.request.content.userInfo
+        self.handleNotificationActions(data: data, responseIdentifier: response.actionIdentifier)
+        completionHandler()
+    }
+
+    func handleNotificationActions(data: [AnyHashable: Any], responseIdentifier: String) {
+        guard let callID = data[NotificationUserInfoKeys.callID.rawValue] as? String else {
+            return
+        }
+        switch responseIdentifier {
+        case CallAcition.accept.rawValue:
+            NotificationCenter.default.post(name: NSNotification.Name(NotificationName.answerCallFromNotifications.rawValue),
+                                            object: nil,
+                                            userInfo: data)
+        case CallAcition.refuse.rawValue:
+            self.callService.refuse(callId: callID)
+                .subscribe({_ in
+                    print("Call ignored")
+                }).disposed(by: self.disposeBag)
+        default:
+            print("Other Action")
+        }
+    }
+
+    func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, for notification: UILocalNotification, completionHandler: @escaping () -> Void) {
+        if let identifier = identifier, let data = notification.userInfo {
+            self.handleNotificationActions(data: data, responseIdentifier: identifier)
+        }
+        completionHandler()
+    }
 }
 
 extension AppDelegate: PKPushRegistryDelegate {
diff --git a/Ring/Ring/Features/Conversations/ConversationsCoordinator.swift b/Ring/Ring/Features/Conversations/ConversationsCoordinator.swift
index 9ec07ba..612ecf3 100644
--- a/Ring/Ring/Features/Conversations/ConversationsCoordinator.swift
+++ b/Ring/Ring/Features/Conversations/ConversationsCoordinator.swift
@@ -65,6 +65,15 @@
         }).disposed(by: self.disposeBag)
         self.navigationViewController.viewModel = ChatTabBarItemViewModel(with: self.injectionBag)
         self.callbackPlaceCall()
+        NotificationCenter.default.addObserver(self, selector: #selector(self.incomingCall(_:)), name: NSNotification.Name(NotificationName.answerCallFromNotifications.rawValue), object: nil)
+    }
+
+    @objc func incomingCall(_ notification: NSNotification) {
+        guard let callid = notification.userInfo?[NotificationUserInfoKeys.callID.rawValue] as? String,
+            let call = self.callService.call(callID: callid) else {
+                return
+        }
+        self.answerIncomingCall(call: call)
     }
 
     func start () {
@@ -78,7 +87,7 @@
         self.present(viewController: conversationViewController, withStyle: .show, withAnimation: true, withStateable: conversationViewController.viewModel)
     }
 
-    private func answerIncomingCall(call: CallModel) {
+     func answerIncomingCall(call: CallModel) {
         let callViewController = CallViewController.instantiate(with: self.injectionBag)
         callViewController.viewModel.call = call
         callViewController.viewModel.answerCall()
@@ -88,19 +97,27 @@
     }
 
     private func showCallAlert(call: CallModel) {
-        let alertStyle = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad) ? UIAlertControllerStyle.alert : UIAlertControllerStyle.actionSheet
-        let alert = UIAlertController(title: L10n.Alerts.incomingCallAllertTitle + "\(call.displayName)", message: nil, preferredStyle: alertStyle)
-        alert.addAction(UIAlertAction(title: L10n.Alerts.incomingCallButtonAccept, style: UIAlertActionStyle.default, handler: { (_) in
-            self.answerIncomingCall(call: call)
-            alert.dismiss(animated: true, completion: nil)}))
-        alert.addAction(UIAlertAction(title: L10n.Alerts.incomingCallButtonIgnore, style: UIAlertActionStyle.default, handler: { (_) in
-            self.injectionBag.callService.refuse(callId: call.callId)
-                .subscribe({_ in
-                    print("Call ignored")
-                }).disposed(by: self.disposeBag)
-            alert.dismiss(animated: true, completion: nil)
-        }))
+        if UIApplication.shared.applicationState != .active && !call.callId.isEmpty {
+            var data = [String: String]()
+            data [NotificationUserInfoKeys.name.rawValue] = call.participantRingId
+            data [NotificationUserInfoKeys.callID.rawValue] = call.callId
+            let helper = LocalNotificationsHelper()
+            helper.presentCallNotification(data: data, callService: self.callService)
+        } else {
+            let alertStyle = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad) ? UIAlertControllerStyle.alert : UIAlertControllerStyle.actionSheet
+            let alert = UIAlertController(title: L10n.Alerts.incomingCallAllertTitle + "\(call.displayName)", message: nil, preferredStyle: alertStyle)
+            alert.addAction(UIAlertAction(title: L10n.Alerts.incomingCallButtonAccept, style: UIAlertActionStyle.default, handler: { (_) in
+                self.answerIncomingCall(call: call)
+                alert.dismiss(animated: true, completion: nil)}))
+            alert.addAction(UIAlertAction(title: L10n.Alerts.incomingCallButtonIgnore, style: UIAlertActionStyle.default, handler: { (_) in
+                self.injectionBag.callService.refuse(callId: call.callId)
+                    .subscribe({_ in
+                        print("Call ignored")
+                    }).disposed(by: self.disposeBag)
+                alert.dismiss(animated: true, completion: nil)
+            }))
 
-        self.present(viewController: alert, withStyle: .present, withAnimation: true)
+            self.present(viewController: alert, withStyle: .present, withAnimation: true)
+        }
     }
 }
diff --git a/Ring/Ring/Helpers/LocalNotificationsHelper.swift b/Ring/Ring/Helpers/LocalNotificationsHelper.swift
new file mode 100644
index 0000000..d75e9f5
--- /dev/null
+++ b/Ring/Ring/Helpers/LocalNotificationsHelper.swift
@@ -0,0 +1,199 @@
+/*
+ *  Copyright (C) 2018 Savoir-faire Linux Inc.
+ *
+ *  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+import Foundation
+import RxSwift
+
+enum NotificationUserInfoKeys: String {
+    case callID
+    case name
+    case messageContent
+}
+
+enum NotificationCallTitle: String {
+    case incomingCall = "Incoming Call"
+    case missedCall = "Missed Call"
+}
+
+enum CallAcition: String {
+    case accept = "ACCEPT_ACTION"
+    case refuse = "REFUSE_ACTION"
+
+    func title() -> String {
+        switch self {
+        case .accept:
+            return "ACCEPT"
+        case .refuse:
+            return "REFUSE"
+        }
+    }
+}
+
+class LocalNotificationsHelper {
+    let disposeBag = DisposeBag()
+    let callCategory = "CALL_CATEGORY"
+    var timer: Timer?
+
+    init() {
+        self.createCallCategory()
+    }
+
+    func presentMessageNotification(data: [String: String]) {
+        guard let title = data [NotificationUserInfoKeys.name.rawValue],
+            let body = data [NotificationUserInfoKeys.messageContent.rawValue] else {
+                return
+        }
+        if #available(iOS 10.0, *) {
+            let content = UNMutableNotificationContent()
+            content.title = title
+            content.body = body
+            content.badge = UIApplication.shared.applicationIconBadgeNumber + 1 as NSNumber
+            let notificationTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.01, repeats: false)
+            let identifier = Int64(arc4random_uniform(10000000))
+            let notificationRequest = UNNotificationRequest(identifier: "\(identifier)", content: content, trigger: notificationTrigger)
+            UNUserNotificationCenter.current().add(notificationRequest) { (error) in
+                if let error = error {
+                    print("Unable to Add Notification Request (\(error), \(error.localizedDescription))")
+                }
+            }
+        } else {
+            let notification = UILocalNotification()
+            notification.alertTitle = title
+            notification.alertBody = body
+            notification.applicationIconBadgeNumber = UIApplication.shared.applicationIconBadgeNumber + 1
+            UIApplication.shared.scheduleLocalNotification(notification)
+        }
+    }
+
+    func createCallCategory() {
+        if #available(iOS 10.0, *) {
+            let acceptAction = UNNotificationAction(identifier: CallAcition.accept.rawValue,
+                                                    title: CallAcition.accept.title(),
+                                                    options: [.foreground])
+            let refuseAction = UNNotificationAction(identifier: CallAcition.refuse.rawValue,
+                                                    title: CallAcition.refuse.title(),
+                                                    options: [])
+
+            let callCategory = UNNotificationCategory(identifier: self.callCategory,
+                                                      actions: [acceptAction, refuseAction],
+                                                      intentIdentifiers: [], options: [])
+            UNUserNotificationCenter.current().setNotificationCategories([callCategory])
+        } else {
+            let notificationTypes: UIUserNotificationType = (UIApplication.shared.currentUserNotificationSettings?.types)!
+            let acceptAction = UIMutableUserNotificationAction()
+            acceptAction.identifier = CallAcition.accept.rawValue
+            acceptAction.title = CallAcition.accept.title()
+            acceptAction.activationMode = UIUserNotificationActivationMode.foreground
+            let refuseAction = UIMutableUserNotificationAction()
+            refuseAction.identifier = CallAcition.refuse.rawValue
+            refuseAction.title = CallAcition.refuse.title()
+            refuseAction.activationMode = UIUserNotificationActivationMode.background
+            let callCategory = UIMutableUserNotificationCategory()
+            callCategory.identifier = self.callCategory
+            // A. Set actions for the default context
+            callCategory.setActions([acceptAction, refuseAction],
+                                    for: UIUserNotificationActionContext.default)
+            // B. Set actions for the minimal context
+            callCategory.setActions([acceptAction, refuseAction],
+                                    for: UIUserNotificationActionContext.minimal)
+            guard let categoriesForSettings: Set<UIUserNotificationCategory> = NSSet(objects: callCategory) as? Set<UIUserNotificationCategory> else {
+                return
+            }
+            let newNotificationSettings = UIUserNotificationSettings(types: notificationTypes, categories: categoriesForSettings)
+            UIApplication.shared.registerUserNotificationSettings(newNotificationSettings)
+        }
+    }
+
+    @objc func cancelCall(timer: Timer!) {
+        guard let info = timer.userInfo as? [String: String],
+            let callID = info[NotificationUserInfoKeys.callID.rawValue] else {
+                self.timer?.invalidate()
+                self.timer = nil
+                return
+        }
+        var data = [String: String]()
+        data[NotificationUserInfoKeys.callID.rawValue] = callID
+        NotificationCenter.default.post(name: NSNotification.Name(NotificationName.refuseCallFromNotifications.rawValue), object: nil, userInfo: data)
+        self.timer?.invalidate()
+        self.timer = nil
+    }
+
+    func presentCallNotification(data: [String: String], callService: CallsService) {
+        let title = NotificationCallTitle.incomingCall.rawValue
+        guard let name = data [NotificationUserInfoKeys.name.rawValue],
+            let callID = data [NotificationUserInfoKeys.callID.rawValue] else {
+                return
+        }
+        timer = Timer.scheduledTimer(timeInterval: 10,
+                                     target: self,
+                                     selector: #selector(cancelCall),
+                                     userInfo: [NotificationUserInfoKeys.callID.rawValue: callID],
+                                     repeats: false)
+        if #available(iOS 10.0, *) {
+            let content = UNMutableNotificationContent()
+            content.title = title
+            content.body = name
+            content.userInfo = data
+            content.categoryIdentifier = self.callCategory
+            content.sound = UNNotificationSound(named: "defaul.wav")
+            let notificationTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.01, repeats: false)
+            let notificationRequest = UNNotificationRequest(identifier: callID, content: content, trigger: notificationTrigger)
+            UNUserNotificationCenter.current().add(notificationRequest) { (error) in
+                if let error = error {
+                    print("Unable to Add Notification Request (\(error), \(error.localizedDescription))")
+                }
+            }
+            callService.currentCall.filter({ call in
+                return call.callId == callID && (call.state == .over || call.state == .failure)
+            }).single()
+                .subscribe(onNext: { _ in
+                    let content = UNMutableNotificationContent()
+                    content.title = NotificationCallTitle.missedCall.rawValue
+                    content.body = name
+                    content.badge = UIApplication.shared.applicationIconBadgeNumber + 1 as NSNumber
+                    let notificationTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.01, repeats: false)
+                    let notificationRequest = UNNotificationRequest(identifier: callID, content: content, trigger: notificationTrigger)
+                    UNUserNotificationCenter.current().add(notificationRequest) { (error) in
+                        if let error = error {
+                            print("Unable to Add Notification Request (\(error), \(error.localizedDescription))")
+                        }
+                    }
+                }).disposed(by: self.disposeBag)
+        } else {
+            let notification = UILocalNotification()
+            notification.userInfo = data
+            notification.alertTitle = title
+            notification.alertBody = name
+            notification.category = self.callCategory
+            notification.applicationIconBadgeNumber = UIApplication.shared.applicationIconBadgeNumber + 1
+            UIApplication.shared.scheduleLocalNotification(notification)
+            callService.currentCall.filter({ call in
+                return call.callId == callID && (call.state == .over || call.state == .failure)
+            }).single()
+                .subscribe(onNext: { _ in
+                    let notification = UILocalNotification()
+                    notification.userInfo = data
+                    notification.alertTitle = NotificationCallTitle.missedCall.rawValue
+                    notification.alertBody = name
+                    notification.applicationIconBadgeNumber = UIApplication.shared.applicationIconBadgeNumber + 1
+                    UIApplication.shared.scheduleLocalNotification(notification)
+                }).disposed(by: self.disposeBag)
+        }
+    }
+}
diff --git a/Ring/Ring/Resources/default.wav b/Ring/Ring/Resources/default.wav
new file mode 100644
index 0000000..f68346e
--- /dev/null
+++ b/Ring/Ring/Resources/default.wav
Binary files differ
diff --git a/Ring/Ring/Services/AccountsService.swift b/Ring/Ring/Services/AccountsService.swift
index a32504d..2898916 100644
--- a/Ring/Ring/Services/AccountsService.swift
+++ b/Ring/Ring/Services/AccountsService.swift
@@ -36,6 +36,8 @@
 enum NotificationName: String {
     case enablePushNotifications
     case disablePushNotifications
+    case answerCallFromNotifications
+    case refuseCallFromNotifications
 }
 
 class AccountsService: AccountAdapterDelegate {
diff --git a/Ring/Ring/Services/CallsService.swift b/Ring/Ring/Services/CallsService.swift
index 1188056..0a2488c 100644
--- a/Ring/Ring/Services/CallsService.swift
+++ b/Ring/Ring/Services/CallsService.swift
@@ -69,6 +69,28 @@
         self.responseStream.disposed(by: disposeBag)
         self.sharedResponseStream = responseStream.share()
         CallsAdapter.delegate = self
+        NotificationCenter.default.addObserver(self, selector: #selector(self.refuseUnansweredCall(_:)),
+                                               name: NSNotification.Name(rawValue: NotificationName.refuseCallFromNotifications.rawValue),
+                                               object: nil)
+    }
+    
+    @objc func refuseUnansweredCall(_ notification: NSNotification) {
+        guard let callid = notification.userInfo?[NotificationUserInfoKeys.callID.rawValue] as? String else {
+            return
+        }
+        guard let call = self.call(callID: callid) else {
+            return
+        }
+
+        if call.state == .incoming {
+            self.refuse(callId: callid).subscribe({_ in
+                print("Call ignored")
+            }).disposed(by: self.disposeBag)
+        }
+    }
+
+    func call(callID: String) -> CallModel? {
+        return self.calls[callID]
     }
 
     func accept(call: CallModel?) -> Completable {
diff --git a/Ring/Ring/Services/ConversationsManager.swift b/Ring/Ring/Services/ConversationsManager.swift
index bd9a2a2..a10548d 100644
--- a/Ring/Ring/Services/ConversationsManager.swift
+++ b/Ring/Ring/Services/ConversationsManager.swift
@@ -25,12 +25,15 @@
 
     let conversationService: ConversationsService
     let accountsService: AccountsService
-    let disposeBag = DisposeBag()
+    let nameService: NameService
+    private let disposeBag = DisposeBag()
     fileprivate let textPlainMIMEType = "text/plain"
+    private let notificationHandler = LocalNotificationsHelper()
 
-    init(with conversationService: ConversationsService, accountsService: AccountsService) {
+    init(with conversationService: ConversationsService, accountsService: AccountsService, nameService: NameService) {
         self.conversationService = conversationService
         self.accountsService = accountsService
+        self.nameService = nameService
         MessagesAdapter.delegate = self
         self.accountsService
             .sharedResponseStream
@@ -66,6 +69,27 @@
             return
         }
 
+        if UIApplication.shared.applicationState != .active {
+            var data = [String: String]()
+            data [NotificationUserInfoKeys.messageContent.rawValue] = content
+            self.nameService.usernameLookupStatus.single()
+                .filter({ lookupNameResponse in
+                    return lookupNameResponse.address != nil &&
+                        lookupNameResponse.address == senderAccount
+                })
+                .subscribe(onNext: { [weak self] lookupNameResponse in
+                    if let name = lookupNameResponse.name, !name.isEmpty {
+                        data [NotificationUserInfoKeys.name.rawValue] = name
+                        self?.notificationHandler.presentMessageNotification(data: data)
+                    } else if let address = lookupNameResponse.address {
+                       data [NotificationUserInfoKeys.name.rawValue] = address
+                       self?.notificationHandler.presentMessageNotification(data: data)
+                    }
+                }).disposed(by: self.disposeBag)
+
+            self.nameService.lookupAddress(withAccount: "", nameserver: "", address: senderAccount)
+        }
+
         guard let currentAccount = self.accountsService.currentAccount else {
             return
         }
@@ -107,5 +131,4 @@
                                                       from: accountId,
                                                       to: uri)
     }
-
 }
diff --git a/Ring/Ring/Services/ConversationsService.swift b/Ring/Ring/Services/ConversationsService.swift
index d35129d..623f4cd 100644
--- a/Ring/Ring/Services/ConversationsService.swift
+++ b/Ring/Ring/Services/ConversationsService.swift
@@ -237,6 +237,10 @@
         })
     }
 
+    func getProfile(uri: String) -> Observable<Profile> {
+       return self.dbManager.profileObservable(for: uri, createIfNotExists: false)
+    }
+
     func deleteConversation(conversation: ConversationModel) {
         self.dbManager.removeConversationBetween(accountUri: conversation.accountUri, and: conversation.recipientRingId)
             .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background))