// // Copyright (c) 2018 Open Whisper Systems. All rights reserved. // import Foundation import SignalServiceKit import SignalMessaging class DebugUINotifications: DebugUIPage { // MARK: Dependencies var notificationsManager: NotificationsManager { return SignalApp.shared().notificationsManager } var notificationsAdapter: CallNotificationsAdapter { return SignalApp.shared().callService.notificationsAdapter } var messageSender: MessageSender { return Environment.current().messageSender } var contactsManager: OWSContactsManager { return Environment.current().contactsManager } // MARK: Overrides override func name() -> String { return "Notifications" } override func section(thread aThread: TSThread?) -> OWSTableSection? { guard let thread = aThread else { owsFail("\(logTag) Notifications must specify thread.") return nil } var sectionItems = [ OWSTableItem(title: "Last Incoming Message") { [weak self] in guard let strongSelf = self else { return } Logger.info("\(strongSelf.logTag) scheduling notification for incoming message.") strongSelf.delayedNotificationDispatch { Logger.info("\(strongSelf.logTag) dispatching") OWSPrimaryStorage.shared().newDatabaseConnection().read { (transaction) in guard let viewTransaction = transaction.ext(TSMessageDatabaseViewExtensionName) as? YapDatabaseViewTransaction else { owsFail("unable to build view transaction") return } guard let threadId = thread.uniqueId else { owsFail("thread had no uniqueId") return } guard let incomingMessage = viewTransaction.lastObject(inGroup: threadId) as? TSIncomingMessage else { owsFail("last message was not an incoming message.") return } Logger.info("\(strongSelf.logTag) notifying user of incoming message") strongSelf.notificationsManager.notifyUser(for: incomingMessage, in: thread, contactsManager: strongSelf.contactsManager, transaction: transaction) } } } ] if let contactThread = thread as? TSContactThread { sectionItems += [ OWSTableItem(title: "Call Missed") { [weak self] in guard let strongSelf = self else { return } strongSelf.delayedNotificationDispatchWithFakeCall(thread: contactThread) { call in strongSelf.notificationsAdapter.presentMissedCall(call, callerName: thread.name()) } }, OWSTableItem(title: "Call Rejected: New Safety Number") { [weak self] in guard let strongSelf = self else { return } strongSelf.delayedNotificationDispatchWithFakeCall(thread: contactThread) { call in strongSelf.notificationsAdapter.presentMissedCallBecauseOfNewIdentity(call: call, callerName: thread.name()) } }, OWSTableItem(title: "Call Rejected: No Longer Verified") { [weak self] in guard let strongSelf = self else { return } strongSelf.delayedNotificationDispatchWithFakeCall(thread: contactThread) { call in strongSelf.notificationsAdapter.presentMissedCallBecauseOfNoLongerVerifiedIdentity(call: call, callerName: thread.name()) } } ] } return OWSTableSection(title: "Notifications have delay: \(kNotificationDelay)s", items: sectionItems) } // MARK: Helpers // After enqueing the notification you may want to background the app or lock the screen before it triggers, so // we give a little delay. let kNotificationDelay: TimeInterval = 5 func delayedNotificationDispatch(block: @escaping () -> Void) { // Notifications won't sound if the app is suspended. let taskIdentifier = UIApplication.shared.beginBackgroundTask(expirationHandler: nil) DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + kNotificationDelay) { block() // We don't want to endBackgroundTask until *after* the notifications manager is done, // but it dispatches async without a completion handler, so we just wait a while extra. // This is fragile, but it's only for debug UI. DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2.0) { UIApplication.shared.endBackgroundTask(taskIdentifier) } } } func delayedNotificationDispatchWithFakeCall(thread: TSContactThread, callBlock: @escaping (SignalCall) -> Void) { let call = SignalCall.incomingCall(localId: UUID(), remotePhoneNumber: thread.contactIdentifier(), signalingId: 0) delayedNotificationDispatch { callBlock(call) } } }