From debd556e096c30c84356b6b612f53e0a837d80bf Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Mon, 12 Feb 2018 17:18:37 -0800 Subject: [PATCH] Fix notification % escaping, debug UI Consolidated the notifications debug UI (and fixed it) to make testing this a bit easier. // FREEBIE --- Signal.xcodeproj/project.pbxproj | 10 +- .../DebugUI/DebugUICalling.swift | 46 +------ .../DebugUI/DebugUINotifications.swift | 117 ++++++++++++++++++ .../DebugUI/DebugUITableViewController.m | 2 + Signal/src/environment/NotificationsManager.m | 7 +- SignalMessaging/utils/DisplayableText.swift | 11 ++ 6 files changed, 150 insertions(+), 43 deletions(-) create mode 100644 Signal/src/ViewControllers/DebugUI/DebugUINotifications.swift diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 9c0202b95..01cd92888 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -282,6 +282,7 @@ 4574A5D61DD6704700C6B692 /* CallService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4574A5D51DD6704700C6B692 /* CallService.swift */; }; 4579431E1E7C8CE9008ED0C0 /* Pastelog.m in Sources */ = {isa = PBXBuildFile; fileRef = 4579431D1E7C8CE9008ED0C0 /* Pastelog.m */; }; 45794E861E00620000066731 /* CallUIAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45794E851E00620000066731 /* CallUIAdapter.swift */; }; + 457C87B82032645C008D52D6 /* DebugUINotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 457C87B72032645C008D52D6 /* DebugUINotifications.swift */; }; 45847E871E4283C30080EAB3 /* Intents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 45847E861E4283C30080EAB3 /* Intents.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 4585C4681ED8F8D200896AEA /* SafetyNumberConfirmationAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4585C4671ED8F8D200896AEA /* SafetyNumberConfirmationAlert.swift */; }; 458967111DC117CC00E9DD21 /* AccountManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 458967101DC117CC00E9DD21 /* AccountManagerTest.swift */; }; @@ -818,6 +819,7 @@ 4579431C1E7C8CE9008ED0C0 /* Pastelog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Pastelog.h; sourceTree = ""; }; 4579431D1E7C8CE9008ED0C0 /* Pastelog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Pastelog.m; sourceTree = ""; }; 45794E851E00620000066731 /* CallUIAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CallUIAdapter.swift; path = UserInterface/CallUIAdapter.swift; sourceTree = ""; }; + 457C87B72032645C008D52D6 /* DebugUINotifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugUINotifications.swift; sourceTree = ""; }; 45847E861E4283C30080EAB3 /* Intents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Intents.framework; path = System/Library/Frameworks/Intents.framework; sourceTree = SDKROOT; }; 45855F351D9498A40084F340 /* OWSContactAvatarBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSContactAvatarBuilder.h; sourceTree = ""; }; 45855F361D9498A40084F340 /* OWSContactAvatarBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactAvatarBuilder.m; sourceTree = ""; }; @@ -1464,6 +1466,7 @@ 34D8C0241ED3673300188D7C /* DebugUIMessages.m */, 341F2C0D1F2B8AE700D07D6B /* DebugUIMisc.h */, 341F2C0E1F2B8AE700D07D6B /* DebugUIMisc.m */, + 457C87B72032645C008D52D6 /* DebugUINotifications.swift */, 34E3EF0E1EFC2684007F6822 /* DebugUIPage.h */, 34E3EF0F1EFC2684007F6822 /* DebugUIPage.m */, 4556FA671F54AA9500AF40DD /* DebugUIProfile.swift */, @@ -2933,6 +2936,7 @@ 34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */, 34D1F0B71F87F8850066283D /* OWSGenericAttachmentView.m in Sources */, 34B3F8801E8DF1700035BE1A /* InviteFlow.swift in Sources */, + 457C87B82032645C008D52D6 /* DebugUINotifications.swift in Sources */, 34B3F8871E8DF1700035BE1A /* NotificationSettingsViewController.m in Sources */, 347E0B7B2003CD7500BC2F76 /* OWSBackupImportViewController.m in Sources */, 458E38371D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m in Sources */, @@ -3152,7 +3156,11 @@ "DEBUG=1", "$(inherited)", ); - "GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = "DEBUG=1 $(inherited) SSK_BUILDING_FOR_TESTS=1"; + "GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = ( + "DEBUG=1", + "$(inherited)", + "SSK_BUILDING_FOR_TESTS=1", + ); GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES; GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; diff --git a/Signal/src/ViewControllers/DebugUI/DebugUICalling.swift b/Signal/src/ViewControllers/DebugUI/DebugUICalling.swift index 0b8875c13..eb78d85a0 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUICalling.swift +++ b/Signal/src/ViewControllers/DebugUI/DebugUICalling.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // import Foundation @@ -8,13 +8,8 @@ import SignalMessaging class DebugUICalling: DebugUIPage { - let TAG = "[DebugUICalling]" - // MARK: Dependencies - var notificationsAdapter: CallNotificationsAdapter { - return SignalApp.shared().callService.notificationsAdapter - } var messageSender: MessageSender { return Environment.current().messageSender } @@ -32,30 +27,15 @@ class DebugUICalling: DebugUIPage { } let sectionItems = [ - OWSTableItem(title:"⚠️ Missed Call") { - self.delayedDispatchWithFakeCall(thread: thread) { call in - self.notificationsAdapter.presentMissedCall(call, callerName: thread.name()) - } - }, - OWSTableItem(title:"⚠️ New Safety Number (rejected)") { - self.delayedDispatchWithFakeCall(thread: thread) { call in - self.notificationsAdapter.presentMissedCallBecauseOfNewIdentity(call: call, callerName: thread.name()) - } - }, - OWSTableItem(title:"⚠️ No Longer Verified (rejected)") { - self.delayedDispatchWithFakeCall(thread: thread) { call in - self.notificationsAdapter.presentMissedCallBecauseOfNoLongerVerifiedIdentity(call: call, callerName: thread.name()) - } - }, OWSTableItem(title:"Send 'hangup' for old call") { let kFakeCallId = UInt64(12345) let hangupMessage = OWSCallHangupMessage(callId: kFakeCallId) let callMessage = OWSOutgoingCallMessage(thread: thread, hangupMessage: hangupMessage) self.messageSender.sendPromise(message: callMessage).then { - Logger.debug("\(self.TAG) Successfully sent hangup call message to \(thread.contactIdentifier())") + Logger.debug("\(self.logTag) Successfully sent hangup call message to \(thread.contactIdentifier())") }.catch { error in - Logger.error("\(self.TAG) failed to send hangup call message to \(thread.contactIdentifier()) with error: \(error)") + Logger.error("\(self.logTag) failed to send hangup call message to \(thread.contactIdentifier()) with error: \(error)") } }, OWSTableItem(title:"Send 'busy' for old call") { @@ -64,27 +44,13 @@ class DebugUICalling: DebugUIPage { let callMessage = OWSOutgoingCallMessage(thread: thread, busyMessage: busyMessage) self.messageSender.sendPromise(message: callMessage).then { - Logger.debug("\(self.TAG) Successfully sent busy call message to \(thread.contactIdentifier())") + Logger.debug("\(self.logTag) Successfully sent busy call message to \(thread.contactIdentifier())") }.catch { error in - Logger.error("\(self.TAG) failed to send busy call message to \(thread.contactIdentifier()) with error: \(error)") + Logger.error("\(self.logTag) failed to send busy call message to \(thread.contactIdentifier()) with error: \(error)") } } ] - return OWSTableSection(title: "Call 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 delayedDispatchWithFakeCall(thread: TSContactThread, callBlock: @escaping (SignalCall) -> Void) { - let call = SignalCall.incomingCall(localId: UUID(), remotePhoneNumber: thread.contactIdentifier(), signalingId: 0) - - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + kNotificationDelay) { - callBlock(call) - } + return OWSTableSection(title: "Call Debug", items: sectionItems) } } diff --git a/Signal/src/ViewControllers/DebugUI/DebugUINotifications.swift b/Signal/src/ViewControllers/DebugUI/DebugUINotifications.swift new file mode 100644 index 000000000..f9d810acf --- /dev/null +++ b/Signal/src/ViewControllers/DebugUI/DebugUINotifications.swift @@ -0,0 +1,117 @@ +// +// 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") { + Logger.info("\(self.logTag) scheduling notification for incoming message.") + self.delayedNotificationDispatch { + Logger.info("\(self.logTag) dispatching") + TSStorageManager.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("\(self.logTag) notifying user of incoming message") + self.notificationsManager.notifyUser(for: incomingMessage, in: thread, contactsManager: self.contactsManager, transaction: transaction) + }) + } + } + ] + + if let contactThread = thread as? TSContactThread { + sectionItems += [ + OWSTableItem(title:"Call Missed") { + self.delayedNotificationDispatchWithFakeCall(thread: contactThread) { call in + self.notificationsAdapter.presentMissedCall(call, callerName: thread.name()) + } + }, + OWSTableItem(title:"Call Rejected: New Safety Number") { + self.delayedNotificationDispatchWithFakeCall(thread: contactThread) { call in + self.notificationsAdapter.presentMissedCallBecauseOfNewIdentity(call: call, callerName: thread.name()) + } + }, + OWSTableItem(title:"Call Rejected: No Longer Verified") { + self.delayedNotificationDispatchWithFakeCall(thread: contactThread) { call in + self.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) + } + } +} diff --git a/Signal/src/ViewControllers/DebugUI/DebugUITableViewController.m b/Signal/src/ViewControllers/DebugUI/DebugUITableViewController.m index 5213f74f2..007a443e0 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUITableViewController.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUITableViewController.m @@ -85,6 +85,8 @@ NS_ASSUME_NONNULL_BEGIN [subsectionItems addObject:[self itemForSubsection:[DebugUICalling new] viewController:viewController thread:thread]]; } + [subsectionItems + addObject:[self itemForSubsection:[DebugUINotifications new] viewController:viewController thread:thread]]; [subsectionItems addObject:[self itemForSubsection:[DebugUIProfile new] viewController:viewController thread:thread]]; [subsectionItems addObject:[self itemForSubsection:[DebugUIStress new] viewController:viewController thread:thread]]; diff --git a/Signal/src/environment/NotificationsManager.m b/Signal/src/environment/NotificationsManager.m index 90f76ab31..538909270 100644 --- a/Signal/src/environment/NotificationsManager.m +++ b/Signal/src/environment/NotificationsManager.m @@ -312,9 +312,12 @@ NSString *const kNotificationsManagerNewMesssageSoundName = @"NewMessage.aifc"; [NSString stringWithFormat:NSLocalizedString(@"APN_MESSAGE_IN_GROUP_DETAILED", nil), senderName, threadName, - messageDescription]; + [DisplayableText filterNotificationText:messageDescription]]; + } else { - notification.alertBody = [NSString stringWithFormat:@"%@: %@", senderName, messageDescription]; + notification.alertBody = [NSString stringWithFormat:@"%@: %@", + senderName, + [DisplayableText filterNotificationText:messageDescription]]; } break; } diff --git a/SignalMessaging/utils/DisplayableText.swift b/SignalMessaging/utils/DisplayableText.swift index aca28be14..cdfc21bbb 100644 --- a/SignalMessaging/utils/DisplayableText.swift +++ b/SignalMessaging/utils/DisplayableText.swift @@ -215,6 +215,17 @@ extension String { return text.ows_stripped() } + @objc + public class func filterNotificationText(_ text: String?) -> String? { + guard let text = self.filterText(text) else { + return nil + } + + // Notifications strip anything that looks lik a printf formatting character, + // so literal "%" must be escaped in order to appear in notification text. + return text.replacingOccurrences(of: "%", with: "%%") + } + private class func hasExcessiveDiacriticals(text: String) -> Bool { // discard any zalgo style text, by detecting maximum number of glyphs per character for char in text.enumerated() {