In app notifications for iOS10+

Extract shared notification presention/response

Implement adapters which use that logic for modern UNUserNotification and
legacy UINotifications
This commit is contained in:
Michael Kirk 2019-01-18 10:54:09 -07:00
parent 312384201c
commit 1bfe691895
33 changed files with 1395 additions and 1606 deletions

View File

@ -85,7 +85,6 @@
346129951FD1E30000532771 /* OWSDatabaseMigration.h in Headers */ = {isa = PBXBuildFile; fileRef = 346129931FD1E30000532771 /* OWSDatabaseMigration.h */; settings = {ATTRIBUTES = (Public, ); }; };
346129961FD1E30000532771 /* OWSDatabaseMigration.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129941FD1E30000532771 /* OWSDatabaseMigration.m */; };
346129991FD1E4DA00532771 /* SignalApp.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129971FD1E4D900532771 /* SignalApp.m */; };
3461299C1FD1EA9E00532771 /* NotificationsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 3461299B1FD1EA9E00532771 /* NotificationsManager.m */; };
346129A51FD1F09100532771 /* OWSContactsManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 346129A21FD1F09100532771 /* OWSContactsManager.h */; settings = {ATTRIBUTES = (Public, ); }; };
346129A61FD1F09100532771 /* OWSContactsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129A31FD1F09100532771 /* OWSContactsManager.m */; };
346129A91FD1F0E000532771 /* OWSFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = 346129A81FD1F0DF00532771 /* OWSFormat.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -137,7 +136,6 @@
347850691FD9B78A007B8332 /* AppSetup.m in Sources */ = {isa = PBXBuildFile; fileRef = 347850651FD9B789007B8332 /* AppSetup.m */; };
3478506A1FD9B78A007B8332 /* AppSetup.h in Headers */ = {isa = PBXBuildFile; fileRef = 347850661FD9B789007B8332 /* AppSetup.h */; settings = {ATTRIBUTES = (Public, ); }; };
3478506B1FD9B78A007B8332 /* NoopCallMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 347850671FD9B78A007B8332 /* NoopCallMessageHandler.swift */; };
3478506C1FD9B78A007B8332 /* NoopNotificationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 347850681FD9B78A007B8332 /* NoopNotificationsManager.swift */; };
347850711FDAEB17007B8332 /* OWSUserProfile.m in Sources */ = {isa = PBXBuildFile; fileRef = 3478506F1FDAEB16007B8332 /* OWSUserProfile.m */; };
347850721FDAEB17007B8332 /* OWSUserProfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 347850701FDAEB16007B8332 /* OWSUserProfile.h */; settings = {ATTRIBUTES = (Public, ); }; };
34843B2421432293004DED45 /* SignalBaseTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 34843B2221432292004DED45 /* SignalBaseTest.m */; };
@ -321,7 +319,7 @@
45194F931FD7215C00333B2C /* OWSContactOffersInteraction.m in Sources */ = {isa = PBXBuildFile; fileRef = 34C42D631F4734ED0072EC04 /* OWSContactOffersInteraction.m */; };
45194F941FD7216000333B2C /* TSUnreadIndicatorInteraction.h in Headers */ = {isa = PBXBuildFile; fileRef = 34C42D641F4734ED0072EC04 /* TSUnreadIndicatorInteraction.h */; settings = {ATTRIBUTES = (Public, ); }; };
45194F951FD7216600333B2C /* TSUnreadIndicatorInteraction.m in Sources */ = {isa = PBXBuildFile; fileRef = 34C42D651F4734ED0072EC04 /* TSUnreadIndicatorInteraction.m */; };
451A13B11E13DED2000A50FD /* NotificationsAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 451A13B01E13DED2000A50FD /* NotificationsAdapter.swift */; };
451A13B11E13DED2000A50FD /* AppNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 451A13B01E13DED2000A50FD /* AppNotifications.swift */; };
451F8A341FD710C3005CB9DA /* ConversationSearcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 451777C71FD61554001225FF /* ConversationSearcher.swift */; };
451F8A351FD710DE005CB9DA /* Searcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45360B8C1F9521F800FA666C /* Searcher.swift */; };
451F8A3B1FD71297005CB9DA /* UIUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B97940261832BD2400BD66CB /* UIUtil.m */; };
@ -474,6 +472,7 @@
4CC1ECF9211A47CE00CC13BE /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4CC1ECF8211A47CD00CC13BE /* StoreKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
4CC1ECFB211A553000CC13BE /* AppUpdateNag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC1ECFA211A553000CC13BE /* AppUpdateNag.swift */; };
4CEB78C92178EBAB00F315D2 /* OWSSessionResetJobRecord.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CEB78C82178EBAB00F315D2 /* OWSSessionResetJobRecord.m */; };
4CFE6B6C21F92BA700006701 /* LegacyNotificationsAdaptee.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFE6B6B21F92BA700006701 /* LegacyNotificationsAdaptee.swift */; };
70377AAB1918450100CAF501 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70377AAA1918450100CAF501 /* MobileCoreServices.framework */; };
768A1A2B17FC9CD300E00ED8 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 768A1A2A17FC9CD300E00ED8 /* libz.dylib */; };
76C87F19181EFCE600C4ACAB /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76C87F18181EFCE600C4ACAB /* MediaPlayer.framework */; };
@ -510,7 +509,6 @@
B633C5C41A1D190B0059AC12 /* mute_on@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B633C5421A1D190B0059AC12 /* mute_on@2x.png */; };
B633C5CE1A1D190B0059AC12 /* quit@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B633C54C1A1D190B0059AC12 /* quit@2x.png */; };
B633C5D21A1D190B0059AC12 /* savephoto@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B633C5501A1D190B0059AC12 /* savephoto@2x.png */; };
B660F6D21C29868000687D6E /* PushManagerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = B660F69C1C29868000687D6E /* PushManagerTest.m */; };
B660F6D41C29868000687D6E /* whisperFake.cer in Resources */ = {isa = PBXBuildFile; fileRef = B660F69F1C29868000687D6E /* whisperFake.cer */; };
B660F6DB1C29868000687D6E /* FunctionalUtilTest.m in Sources */ = {isa = PBXBuildFile; fileRef = B660F6AD1C29868000687D6E /* FunctionalUtilTest.m */; };
B660F6E01C29868000687D6E /* UtilTest.m in Sources */ = {isa = PBXBuildFile; fileRef = B660F6B41C29868000687D6E /* UtilTest.m */; };
@ -518,7 +516,6 @@
B67EBF5D19194AC60084CCFD /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = B67EBF5C19194AC60084CCFD /* Settings.bundle */; };
B69CD25119773E79005CE69A /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B69CD25019773E79005CE69A /* XCTest.framework */; };
B6B226971BE4B7D200860F4D /* ContactsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6B226961BE4B7D200860F4D /* ContactsUI.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
B6B9ECFC198B31BA00C620D3 /* PushManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B6B9ECFB198B31BA00C620D3 /* PushManager.m */; };
B6F509971AA53F760068F56A /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B6F509951AA53F760068F56A /* Localizable.strings */; };
B6FE7EB71ADD62FA00A6D22F /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6FE7EB61ADD62FA00A6D22F /* PushKit.framework */; };
B90418E6183E9DD40038554A /* DateUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B90418E5183E9DD40038554A /* DateUtil.m */; };
@ -739,8 +736,6 @@
346129941FD1E30000532771 /* OWSDatabaseMigration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSDatabaseMigration.m; sourceTree = "<group>"; };
346129971FD1E4D900532771 /* SignalApp.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SignalApp.m; sourceTree = "<group>"; };
346129981FD1E4DA00532771 /* SignalApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SignalApp.h; sourceTree = "<group>"; };
3461299A1FD1EA9E00532771 /* NotificationsManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NotificationsManager.h; sourceTree = "<group>"; };
3461299B1FD1EA9E00532771 /* NotificationsManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NotificationsManager.m; sourceTree = "<group>"; };
346129A21FD1F09100532771 /* OWSContactsManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSContactsManager.h; sourceTree = "<group>"; };
346129A31FD1F09100532771 /* OWSContactsManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactsManager.m; sourceTree = "<group>"; };
346129A81FD1F0DF00532771 /* OWSFormat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSFormat.h; sourceTree = "<group>"; };
@ -794,7 +789,6 @@
347850651FD9B789007B8332 /* AppSetup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppSetup.m; sourceTree = "<group>"; };
347850661FD9B789007B8332 /* AppSetup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppSetup.h; sourceTree = "<group>"; };
347850671FD9B78A007B8332 /* NoopCallMessageHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoopCallMessageHandler.swift; sourceTree = "<group>"; };
347850681FD9B78A007B8332 /* NoopNotificationsManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoopNotificationsManager.swift; sourceTree = "<group>"; };
3478506F1FDAEB16007B8332 /* OWSUserProfile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSUserProfile.m; sourceTree = "<group>"; };
347850701FDAEB16007B8332 /* OWSUserProfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSUserProfile.h; sourceTree = "<group>"; };
34843B2221432292004DED45 /* SignalBaseTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SignalBaseTest.m; sourceTree = "<group>"; };
@ -1028,7 +1022,7 @@
451166BF1FD86B98000739BA /* AccountManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountManager.swift; sourceTree = "<group>"; };
451764291DE939FD00EDB8B9 /* ContactCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactCell.swift; sourceTree = "<group>"; };
451777C71FD61554001225FF /* ConversationSearcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConversationSearcher.swift; sourceTree = "<group>"; };
451A13B01E13DED2000A50FD /* NotificationsAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; name = NotificationsAdapter.swift; path = UserInterface/Notifications/NotificationsAdapter.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
451A13B01E13DED2000A50FD /* AppNotifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; name = AppNotifications.swift; path = UserInterface/Notifications/AppNotifications.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
452037CF1EE84975004E4CDF /* DebugUISessionState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUISessionState.h; sourceTree = "<group>"; };
452037D01EE84975004E4CDF /* DebugUISessionState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUISessionState.m; sourceTree = "<group>"; };
4520D8D41D417D8E00123472 /* Photos.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Photos.framework; path = System/Library/Frameworks/Photos.framework; sourceTree = SDKROOT; };
@ -1144,7 +1138,6 @@
45DF5DF11DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompareSafetyNumbersActivity.swift; sourceTree = "<group>"; };
45E282DE1D08E67800ADD4C8 /* gl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = gl; path = translations/gl.lproj/Localizable.strings; sourceTree = "<group>"; };
45E282DF1D08E6CC00ADD4C8 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = translations/id.lproj/Localizable.strings; sourceTree = "<group>"; };
45E2E91E1E13EE3500457AA0 /* OWSCallNotificationsAdaptee.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; name = OWSCallNotificationsAdaptee.h; path = UserInterface/OWSCallNotificationsAdaptee.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
45E5A6981F61E6DD001E4A8A /* MarqueeLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarqueeLabel.swift; sourceTree = "<group>"; };
45E7A6A61E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayableTextFilterTest.swift; sourceTree = "<group>"; };
45F170AB1E2F0351003FC1F2 /* OWSAudioSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSAudioSession.swift; sourceTree = "<group>"; };
@ -1197,6 +1190,7 @@
4CC1ECFA211A553000CC13BE /* AppUpdateNag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUpdateNag.swift; sourceTree = "<group>"; };
4CEB78C72178EBAB00F315D2 /* OWSSessionResetJobRecord.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OWSSessionResetJobRecord.h; sourceTree = "<group>"; };
4CEB78C82178EBAB00F315D2 /* OWSSessionResetJobRecord.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OWSSessionResetJobRecord.m; sourceTree = "<group>"; };
4CFE6B6B21F92BA700006701 /* LegacyNotificationsAdaptee.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LegacyNotificationsAdaptee.swift; path = UserInterface/Notifications/LegacyNotificationsAdaptee.swift; sourceTree = "<group>"; };
4CFF4C0920F55BBA005DA313 /* MenuActionsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuActionsViewController.swift; sourceTree = "<group>"; };
69349DE607F5BA6036C9AC60 /* Pods-SignalShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalShareExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SignalShareExtension/Pods-SignalShareExtension.debug.xcconfig"; sourceTree = "<group>"; };
70377AAA1918450100CAF501 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; };
@ -1244,7 +1238,6 @@
B646D10F1AA54626004133BA /* fil */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fil; path = translations/fil.lproj/Localizable.strings; sourceTree = "<group>"; };
B646D1141AA54674004133BA /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = translations/hu.lproj/Localizable.strings; sourceTree = "<group>"; };
B657DDC91911A40500F45B0C /* Signal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = Signal.entitlements; sourceTree = "<group>"; };
B660F69C1C29868000687D6E /* PushManagerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PushManagerTest.m; sourceTree = "<group>"; };
B660F69E1C29868000687D6E /* SignalTests-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "SignalTests-Info.plist"; sourceTree = "<group>"; };
B660F69F1C29868000687D6E /* whisperFake.cer */ = {isa = PBXFileReference; lastKnownFileType = file; path = whisperFake.cer; sourceTree = "<group>"; };
B660F6A01C29868000687D6E /* TestUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestUtil.h; sourceTree = "<group>"; };
@ -1269,8 +1262,6 @@
B69C2D1B1AA5448300A640C2 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = translations/cs.lproj/Localizable.strings; sourceTree = "<group>"; };
B69CD25019773E79005CE69A /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
B6B226961BE4B7D200860F4D /* ContactsUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ContactsUI.framework; path = System/Library/Frameworks/ContactsUI.framework; sourceTree = SDKROOT; };
B6B9ECFA198B31BA00C620D3 /* PushManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = PushManager.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
B6B9ECFB198B31BA00C620D3 /* PushManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = PushManager.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
B6BC3D0C1AA544B100C2907F /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = translations/da.lproj/Localizable.strings; sourceTree = "<group>"; };
B6F509961AA53F760068F56A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = translations/en.lproj/Localizable.strings; sourceTree = "<group>"; };
B6FE7EB61ADD62FA00A6D22F /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = System/Library/Frameworks/PushKit.framework; sourceTree = SDKROOT; };
@ -1641,7 +1632,6 @@
346129411FD1D74B00532771 /* Environment.m */,
346129921FD1E30000532771 /* migrations */,
347850671FD9B78A007B8332 /* NoopCallMessageHandler.swift */,
347850681FD9B78A007B8332 /* NoopNotificationsManager.swift */,
45F170AB1E2F0351003FC1F2 /* OWSAudioSession.swift */,
34074F60203D0CBE004596AE /* OWSSounds.h */,
34074F5F203D0CBD004596AE /* OWSSounds.m */,
@ -1800,13 +1790,6 @@
path = Backup;
sourceTree = "<group>";
};
3496957521A301A300DCFE74 /* New Group */ = {
isa = PBXGroup;
children = (
);
path = "New Group";
sourceTree = "<group>";
};
34B3F8331E8DF1700035BE1A /* ViewControllers */ = {
isa = PBXGroup;
children = (
@ -2040,8 +2023,9 @@
450DF2071E0DD29E003D14BE /* Notifications */ = {
isa = PBXGroup;
children = (
451A13B01E13DED2000A50FD /* NotificationsAdapter.swift */,
451A13B01E13DED2000A50FD /* AppNotifications.swift */,
450DF2081E0DD2C6003D14BE /* UserNotificationsAdaptee.swift */,
4CFE6B6B21F92BA700006701 /* LegacyNotificationsAdaptee.swift */,
);
name = Notifications;
sourceTree = "<group>";
@ -2168,7 +2152,6 @@
isa = PBXGroup;
children = (
45FBC57A1DF8575700E9B410 /* CallKit */,
45E2E91E1E13EE3500457AA0 /* OWSCallNotificationsAdaptee.h */,
45794E851E00620000066731 /* CallUIAdapter.swift */,
45F659811E1BE77000444429 /* NonCallKitCallUIAdaptee.swift */,
);
@ -2279,8 +2262,6 @@
children = (
34D99CE3217509C1000AFB39 /* AppEnvironment.swift */,
4505C2BD1E648E6E00CEBF41 /* ExperienceUpgrades */,
3461299A1FD1EA9E00532771 /* NotificationsManager.h */,
3461299B1FD1EA9E00532771 /* NotificationsManager.m */,
4539B5851F79348F007141FF /* PushRegistrationManager.swift */,
346129981FD1E4DA00532771 /* SignalApp.h */,
346129971FD1E4D900532771 /* SignalApp.m */,
@ -2293,8 +2274,6 @@
children = (
3430FE171F7751D4000EC51B /* GiphyAPI.swift */,
34D1F0511F7E8EA30066283D /* GiphyDownloader.swift */,
B6B9ECFA198B31BA00C620D3 /* PushManager.h */,
B6B9ECFB198B31BA00C620D3 /* PushManager.m */,
);
path = network;
sourceTree = "<group>";
@ -2302,7 +2281,6 @@
76EB04C818170B33006006FC /* util */ = {
isa = PBXGroup;
children = (
3496957521A301A300DCFE74 /* New Group */,
4CC1ECFA211A553000CC13BE /* AppUpdateNag.swift */,
B90418E4183E9DD40038554A /* DateUtil.h */,
3496956121A301A100DCFE74 /* Backup */,
@ -2415,7 +2393,6 @@
B660F6751C29867F00687D6E /* contact */,
34843B29214FE295004DED45 /* mocks */,
458E38381D6699110094BD24 /* Models */,
B660F69B1C29868000687D6E /* push */,
34843B2321432293004DED45 /* SignalBaseTest.h */,
34843B2221432292004DED45 /* SignalBaseTest.m */,
4589670F1DC117CC00E9DD21 /* SignalTests-Bridging-Header.h */,
@ -2445,14 +2422,6 @@
path = contact;
sourceTree = "<group>";
};
B660F69B1C29868000687D6E /* push */ = {
isa = PBXGroup;
children = (
B660F69C1C29868000687D6E /* PushManagerTest.m */,
);
path = push;
sourceTree = "<group>";
};
B660F69D1C29868000687D6E /* Supporting Files */ = {
isa = PBXGroup;
children = (
@ -3317,7 +3286,6 @@
342950882124CB0A0000B063 /* OWSSearchBar.m in Sources */,
342950822124C9750000B063 /* OWSTextField.m in Sources */,
34AC0A13211B39EA00997B47 /* DisappearingTimerConfigurationView.swift in Sources */,
3478506C1FD9B78A007B8332 /* NoopNotificationsManager.swift in Sources */,
4CA46F4D219CFDAA0038ABDE /* GalleryRailView.swift in Sources */,
34480B621FD0A98800BC14EF /* UIColor+OWS.m in Sources */,
4C20B2B720CA0034001BAC90 /* ThreadViewModel.swift in Sources */,
@ -3441,7 +3409,7 @@
454A84042059C787008B8C75 /* MediaTileViewController.swift in Sources */,
340FC8B4204DAC8D007AEB0F /* OWSBackupSettingsViewController.m in Sources */,
34D1F0871F8678AA0066283D /* ConversationViewItem.m in Sources */,
451A13B11E13DED2000A50FD /* NotificationsAdapter.swift in Sources */,
451A13B11E13DED2000A50FD /* AppNotifications.swift in Sources */,
34D99CE4217509C2000AFB39 /* AppEnvironment.swift in Sources */,
348570A820F67575004FF32B /* OWSMessageHeaderView.m in Sources */,
450DF2091E0DD2C6003D14BE /* UserNotificationsAdaptee.swift in Sources */,
@ -3460,7 +3428,6 @@
4585C4681ED8F8D200896AEA /* SafetyNumberConfirmationAlert.swift in Sources */,
4C20B2B920CA10DE001BAC90 /* ConversationSearchViewController.swift in Sources */,
450D19131F85236600970622 /* RemoteVideoView.m in Sources */,
B6B9ECFC198B31BA00C620D3 /* PushManager.m in Sources */,
34129B8621EF877A005457A8 /* LinkPreviewView.swift in Sources */,
34386A54207D271D009F5D9C /* NeverClearView.swift in Sources */,
45DF5DF21DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift in Sources */,
@ -3542,6 +3509,7 @@
45D308AD2049A439000189E4 /* PinEntryView.m in Sources */,
340FC8B1204DAC8D007AEB0F /* BlockListViewController.m in Sources */,
45B5360E206DD8BB00D61655 /* UIResponder+OWS.swift in Sources */,
4CFE6B6C21F92BA700006701 /* LegacyNotificationsAdaptee.swift in Sources */,
3441FD9F21A3604F00BB9542 /* BackupRestoreViewController.swift in Sources */,
45F659821E1BE77000444429 /* NonCallKitCallUIAdaptee.swift in Sources */,
4C5250D221E7BD7D00CE3D95 /* PhoneNumberValidator.swift in Sources */,
@ -3586,7 +3554,6 @@
340FC8B9204DAC8D007AEB0F /* UpdateGroupViewController.m in Sources */,
4574A5D61DD6704700C6B692 /* CallService.swift in Sources */,
340FC8A8204DAC8D007AEB0F /* CodeVerificationViewController.m in Sources */,
3461299C1FD1EA9E00532771 /* NotificationsManager.m in Sources */,
4521C3C01F59F3BA00B4C582 /* TextFieldHelper.swift in Sources */,
34D2CCDF206939B400CB1A14 /* DebugUIMessagesAction.m in Sources */,
340FC8AC204DAC8D007AEB0F /* PrivacySettingsTableViewController.m in Sources */,
@ -3641,7 +3608,6 @@
4C5250D421E7C51900CE3D95 /* PhoneNumberValidatorTest.swift in Sources */,
452D1AF12081059C00A67F7F /* StringAdditionsTest.swift in Sources */,
4C4BC6C32102D697004040C9 /* ContactDiscoveryOperationTest.swift in Sources */,
B660F6D21C29868000687D6E /* PushManagerTest.m in Sources */,
455AC69E1F4F8B0300134004 /* ImageCacheTest.swift in Sources */,
34E8A8D12085238A00B272B1 /* ProtoParsingTest.m in Sources */,
);

View File

@ -12,7 +12,6 @@
#import "OWSOrphanDataCleaner.h"
#import "OWSScreenLockUI.h"
#import "Pastelog.h"
#import "PushManager.h"
#import "RegistrationViewController.h"
#import "Signal-Swift.h"
#import "SignalApp.h"
@ -61,7 +60,7 @@ static NSString *const kURLHostVerifyPrefix = @"verify";
static NSTimeInterval launchStartedAt;
@interface AppDelegate ()
@interface AppDelegate () <UNUserNotificationCenterDelegate>
@property (nonatomic) BOOL hasInitialRootViewController;
@property (nonatomic) BOOL areVersionMigrationsComplete;
@ -175,6 +174,11 @@ static NSTimeInterval launchStartedAt;
return AppEnvironment.shared.backup;
}
- (OWSNotificationPresenter *)notificationPresenter
{
return AppEnvironment.shared.notificationPresenter;
}
#pragma mark -
- (void)applicationDidEnterBackground:(UIApplication *)application {
@ -285,6 +289,14 @@ static NSTimeInterval launchStartedAt;
mainWindow.rootViewController = [LoadingViewController new];
[mainWindow makeKeyAndVisible];
if (@available(iOS 11, *)) {
// This must happen in appDidFinishLaunching or earlier to ensure we don't
// miss notifications.
// Setting the delegate also seems to prevent us from getting the legacy notification
// notification callbacks upon launch e.g. 'didReceiveLocalNotification'
UNUserNotificationCenter.currentNotificationCenter.delegate = self;
}
// Accept push notification when app is not open
NSDictionary *remoteNotif = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
if (remoteNotif) {
@ -580,8 +592,8 @@ static NSTimeInterval launchStartedAt;
return;
}
OWSLogInfo(@"registered user notification settings");
[self.pushRegistrationManager didRegisterUserNotificationSettings];
OWSLogInfo(@"registered legacy notification settings");
[self.notificationPresenter didRegisterLegacyNotificationSettings];
}
- (BOOL)application:(UIApplication *)application
@ -648,11 +660,6 @@ static NSTimeInterval launchStartedAt;
[self handleActivation];
}];
// There is a sequence of actions a user can take where we present a conversation from a notification
// multiple times, producing an undesirable "stack" of multiple conversation view controllers.
// So we ensure that we only present conversations once per activate.
[PushManager sharedManager].hasPresentedConversationSinceLastDeactivation = NO;
// Clear all notifications whenever we become active.
// When opening the app from a notification,
// AppDelegate.didReceiveLocalNotification will always
@ -733,8 +740,7 @@ static NSTimeInterval launchStartedAt;
dispatch_async(dispatch_get_main_queue(), ^{
[self.socketManager requestSocketOpen];
[Environment.shared.contactsManager fetchSystemContactsOnceIfAlreadyAuthorized];
// This will fetch new messages, if we're using domain fronting.
[[PushManager sharedManager] applicationDidBecomeActive];
[[AppEnvironment.shared.messageFetcherJob run] retainUntilComplete];
if (![UIApplication sharedApplication].isRegisteredForRemoteNotifications) {
OWSLogInfo(@"Retrying to register for remote notifications since user hasn't registered yet.");
@ -1111,8 +1117,9 @@ static NSTimeInterval launchStartedAt;
return;
}
// It is safe to continue even if the app isn't ready.
[[PushManager sharedManager] application:application didReceiveRemoteNotification:userInfo];
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
[[AppEnvironment.shared.messageFetcherJob run] retainUntilComplete];
}];
}
- (void)application:(UIApplication *)application
@ -1129,10 +1136,12 @@ static NSTimeInterval launchStartedAt;
return;
}
// It is safe to continue even if the app isn't ready.
[[PushManager sharedManager] application:application
didReceiveRemoteNotification:userInfo
fetchCompletionHandler:completionHandler];
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
[[AppEnvironment.shared.messageFetcherJob run] retainUntilComplete];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 20 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
completionHandler(UIBackgroundFetchResultNewData);
});
}];
}
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
@ -1150,7 +1159,12 @@ static NSTimeInterval launchStartedAt;
return;
}
[[PushManager sharedManager] application:application didReceiveLocalNotification:notification];
[LegacyNotificationActionHandler.shared
handleNotificationResponseWithActionIdentifier:LegacyNotificationActionHandler.kDefaultActionIdentifier
notification:notification
responseInfo:@{}
completionHandler:^{
}];
}];
}
@ -1180,10 +1194,10 @@ static NSTimeInterval launchStartedAt;
return;
}
[[PushManager sharedManager] application:application
handleActionWithIdentifier:identifier
forLocalNotification:notification
completionHandler:completionHandler];
[LegacyNotificationActionHandler.shared handleNotificationResponseWithActionIdentifier:identifier
notification:notification
responseInfo:@{}
completionHandler:completionHandler];
}];
}
@ -1216,11 +1230,10 @@ static NSTimeInterval launchStartedAt;
return;
}
[[PushManager sharedManager] application:application
handleActionWithIdentifier:identifier
forLocalNotification:notification
withResponseInfo:responseInfo
completionHandler:completionHandler];
[LegacyNotificationActionHandler.shared handleNotificationResponseWithActionIdentifier:identifier
notification:notification
responseInfo:responseInfo
completionHandler:completionHandler];
}];
}
@ -1243,6 +1256,7 @@ static NSTimeInterval launchStartedAt;
job = nil;
});
});
[job retainUntilComplete];
}];
}
@ -1301,7 +1315,7 @@ static NSTimeInterval launchStartedAt;
// Fetch messages as soon as possible after launching. In particular, when
// launching from the background, without this, we end up waiting some extra
// seconds before receiving an actionable push notification.
__unused AnyPromise *messagePromise = [AppEnvironment.shared.messageFetcherJob run];
[[AppEnvironment.shared.messageFetcherJob run] retainUntilComplete];
// This should happen at any launch, background or foreground.
__unused AnyPromise *pushTokenpromise =
@ -1481,4 +1495,52 @@ static NSTimeInterval launchStartedAt;
}
}
#pragma mark - UNUserNotificationsDelegate
// The method will be called on the delegate only if the application is in the foreground. If the method is not
// implemented or the handler is not called in a timely manner then the notification will not be presented. The
// application can choose to have the notification presented as a sound, badge, alert and/or in the notification list.
// This decision should be based on whether the information in the notification is otherwise visible to the user.
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
willPresentNotification:(UNNotification *)notification
withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler
__IOS_AVAILABLE(10.0)__TVOS_AVAILABLE(10.0)__WATCHOS_AVAILABLE(3.0)__OSX_AVAILABLE(10.14)
{
OWSLogInfo(@"");
[AppReadiness runNowOrWhenAppDidBecomeReady:^() {
// TODO move this into adaptee ?
// we need to respect the in-app notification sound settings, either here
// or maybe it's simpler to do that when building the notification. e.g. if the app
// is in the forground when the notification was sent, we could just *not* add the sound.
UNNotificationPresentationOptions options = UNNotificationPresentationOptionAlert
| UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound;
completionHandler(options);
}];
}
// The method will be called on the delegate when the user responded to the notification by opening the application,
// dismissing the notification or choosing a UNNotificationAction. The delegate must be set before the application
// returns from application:didFinishLaunchingWithOptions:.
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
withCompletionHandler:(void (^)(void))completionHandler __IOS_AVAILABLE(10.0)__WATCHOS_AVAILABLE(3.0)
__OSX_AVAILABLE(10.14)__TVOS_PROHIBITED
{
OWSLogInfo(@"");
[AppReadiness runNowOrWhenAppDidBecomeReady:^() {
[UserNotificationActionHandler.shared handleNotificationResponse:response completionHandler:completionHandler];
}];
}
// The method will be called on the delegate when the application is launched in response to the user's request to view
// in-app notification settings. Add UNAuthorizationOptionProvidesAppNotificationSettings as an option in
// requestAuthorizationWithOptions:completionHandler: to add a button to inline notification settings view and the
// notification settings view in Settings. The notification will be nil when opened from Settings.
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
openSettingsForNotification:(nullable UNNotification *)notification __IOS_AVAILABLE(12.0)
__OSX_AVAILABLE(10.14)__WATCHOS_PROHIBITED __TVOS_PROHIBITED
{
OWSLogInfo(@"");
}
@end

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import Foundation
@ -28,8 +28,7 @@ public class MessageFetcherJob: NSObject {
return SSKEnvironment.shared.messageReceiver
}
private
var signalService: OWSSignalService {
private var signalService: OWSSignalService {
return OWSSignalService.sharedInstance()
}

View File

@ -15,11 +15,6 @@ public class AccountManager: NSObject {
// MARK: - Dependencies
var pushManager: PushManager {
// dependency injection hack since PushManager has *alot* of dependencies, and would induce a cycle.
return PushManager.shared()
}
var profileManager: OWSProfileManager {
return OWSProfileManager.shared()
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import <Foundation/Foundation.h>
@ -20,7 +20,6 @@
#import "HomeViewController.h"
#import "MediaDetailViewController.h"
#import "NotificationSettingsViewController.h"
#import "NotificationsManager.h"
#import "OWSAddToContactViewController.h"
#import "OWSAnyTouchGestureRecognizer.h"
#import "OWSAudioMessageView.h"
@ -30,7 +29,6 @@
#import "OWSBezierPathView.h"
#import "OWSBubbleShapeView.h"
#import "OWSBubbleView.h"
#import "OWSCallNotificationsAdaptee.h"
#import "OWSDatabaseMigration.h"
#import "OWSMessageBubbleView.h"
#import "OWSMessageCell.h"
@ -42,7 +40,6 @@
#import "PinEntryView.h"
#import "PrivacySettingsTableViewController.h"
#import "ProfileViewController.h"
#import "PushManager.h"
#import "RegistrationViewController.h"
#import "RemoteVideoView.h"
#import "SignalApp.h"

View File

@ -0,0 +1,672 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import Foundation
import PromiseKit
/// There are two primary components in our system notification integration:
///
/// 1. The `NotificationPresenter` shows system notifications to the user.
/// 2. The `NotificationActionHandler` handles the users interactions with these
/// notifications.
///
/// The NotificationPresenter is driven by the adapter pattern to provide a unified interface to
/// presenting notifications on iOS9, which uses UINotifications vs iOS10+ which supports
/// UNUserNotifications.
///
/// The `NotificationActionHandler`s also need slightly different integrations for UINotifications
/// vs. UNUserNotifications, but because they are integrated at separate system defined callbacks,
/// there is no need for an Adapter, and instead the appropriate NotificationActionHandler is
/// wired directly into the appropriate callback point.
enum AppNotificationCategory: CaseIterable {
case incomingMessage
case incomingMessageFromNoLongerVerifiedIdentity
case errorMessage
case threadlessErrorMessage
case incomingCall
case missedCall
case missedCallFromNoLongerVerifiedIdentity
}
enum AppNotificationAction: CaseIterable {
case answerCall
case callBack
case declineCall
case markAsRead
case reply
case showThread
}
struct AppNotificationUserInfoKey {
static let threadId = "Signal.AppNotificationsUserInfoKey.threadId"
static let callBackNumber = "Signal.AppNotificationsUserInfoKey.callBackNumber"
static let localCallId = "Signal.AppNotificationsUserInfoKey.localCallId"
}
extension AppNotificationCategory {
var identifier: String {
switch self {
case .incomingMessage:
return "Signal.AppNotificationCategory.incomingMessage"
case .incomingMessageFromNoLongerVerifiedIdentity:
return "Signal.AppNotificationCategory.incomingMessageFromNoLongerVerifiedIdentity"
case .errorMessage:
return "Signal.AppNotificationCategory.errorMessage"
case .threadlessErrorMessage:
return "Signal.AppNotificationCategory.threadlessErrorMessage"
case .incomingCall:
return "Signal.AppNotificationCategory.incomingCall"
case .missedCall:
return "Signal.AppNotificationCategory.missedCall"
case .missedCallFromNoLongerVerifiedIdentity:
return "Signal.AppNotificationCategory.missedCallFromNoLongerVerifiedIdentity"
}
}
var actions: [AppNotificationAction] {
switch self {
case .incomingMessage:
return [.markAsRead, .reply]
case .incomingMessageFromNoLongerVerifiedIdentity:
return [.markAsRead, .showThread]
case .errorMessage:
return [.showThread]
case .threadlessErrorMessage:
return []
case .incomingCall:
return [.answerCall, .declineCall]
case .missedCall:
return [.callBack, .showThread]
case .missedCallFromNoLongerVerifiedIdentity:
return [.showThread]
}
}
}
extension AppNotificationAction {
var identifier: String {
switch self {
case .answerCall:
return "Signal.AppNotifications.Action.answerCall"
case .callBack:
return "Signal.AppNotifications.Action.callBack"
case .declineCall:
return "Signal.AppNotifications.Action.declineCall"
case .markAsRead:
return "Signal.AppNotifications.Action.markAsRead"
case .reply:
return "Signal.AppNotifications.Action.reply"
case .showThread:
return "Signal.AppNotifications.Action.showThread"
}
}
}
// Delay notification of incoming messages when it's likely to be read by a linked device to
// avoid notifying a user on their phone while a conversation is actively happening on desktop.
let kNotificationDelayForRemoteRead: TimeInterval = 5
protocol NotificationPresenterAdaptee: class {
func registerNotificationSettings() -> Promise<Void>
func notify(category: AppNotificationCategory, body: String, userInfo: [AnyHashable: Any], sound: OWSSound?)
func notify(category: AppNotificationCategory, body: String, userInfo: [AnyHashable: Any], sound: OWSSound?, replacingIdentifier: String?)
func cancelNotifications(threadId: String)
func clearAllNotifications()
var shouldPlaySoundForNotification: Bool { get }
var hasReceivedSyncMessageRecently: Bool { get }
}
extension NotificationPresenterAdaptee {
var hasReceivedSyncMessageRecently: Bool {
return OWSDeviceManager.shared().hasReceivedSyncMessage(inLastSeconds: 60)
}
}
@objc(OWSNotificationPresenter)
public class NotificationPresenter: NSObject, NotificationsProtocol {
private let adaptee: NotificationPresenterAdaptee
@objc
public override init() {
if #available(iOS 10, *) {
self.adaptee = UserNotificationPresenterAdaptee()
} else {
self.adaptee = LegacyNotificationPresenterAdaptee()
}
super.init()
AppReadiness.runNowOrWhenAppDidBecomeReady {
NotificationCenter.default.addObserver(self, selector: #selector(self.handleMessageRead), name: .incomingMessageMarkedAsRead, object: nil)
}
SwiftSingletons.register(self)
}
// MARK: - Dependencies
var identityManager: OWSIdentityManager {
return OWSIdentityManager.shared()
}
var previewType: NotificationType {
return Environment.shared.preferences.notificationPreviewType()
}
// MARK: -
// It is not safe to assume push token requests will be acknowledged until the user has
// registered their notification settings.
//
// e.g. in the case that Background Fetch is disabled, token requests will be ignored until
// we register user notification settings.
//
// For modern UNUserNotificationSettings, the registration takes a callback, so "waiting" for
// notification settings registration is straight forward, however for legacy UIUserNotification
// settings, the settings request is confirmed in the AppDelegate, where we call this method
// to inform the adaptee it's safe to proceed.
@objc
public func didRegisterLegacyNotificationSettings() {
guard let legacyAdaptee = adaptee as? LegacyNotificationPresenterAdaptee else {
owsFailDebug("unexpected notifications adaptee: \(adaptee)")
return
}
legacyAdaptee.didRegisterUserNotificationSettings()
}
@objc
func handleMessageRead(notification: Notification) {
AssertIsOnMainThread()
switch notification.object {
case let incomingMessage as TSIncomingMessage:
Logger.debug("canceled notification for message: \(incomingMessage)")
cancelNotifications(threadId: incomingMessage.uniqueThreadId)
default:
break
}
}
// MARK: - Presenting Notifications
func registerNotificationSettings() -> Promise<Void> {
return adaptee.registerNotificationSettings()
}
func presentIncomingCall(_ call: SignalCall, callerName: String) {
let alertMessage: String
switch previewType {
case .noNameNoPreview:
alertMessage = CallStrings.incomingCallWithoutCallerNameNotification
case .nameNoPreview, .namePreview:
alertMessage = String(format: CallStrings.incomingCallNotificationFormat, callerName)
}
let notificationBody = "☎️".rtlSafeAppend(" ").rtlSafeAppend(alertMessage)
let remotePhoneNumber = call.remotePhoneNumber
let thread = TSContactThread.getOrCreateThread(contactId: remotePhoneNumber)
guard let threadId = thread.uniqueId else {
owsFailDebug("threadId was unexpectedly nil")
return
}
let userInfo = [
AppNotificationUserInfoKey.threadId: threadId,
AppNotificationUserInfoKey.localCallId: call.localId.uuidString
]
let sound = OWSSound.defaultiOSIncomingRingtone
DispatchQueue.main.async {
self.adaptee.notify(category: .incomingCall,
body: notificationBody,
userInfo: userInfo,
sound: sound,
replacingIdentifier: call.localId.uuidString)
}
}
func presentMissedCall(_ call: SignalCall, callerName: String) {
let notificationBody: String
switch previewType {
case .noNameNoPreview:
notificationBody = CallStrings.missedCallNotificationBodyWithoutCallerName
case .nameNoPreview, .namePreview:
notificationBody = String(format: CallStrings.missedCallNotificationBodyWithCallerName, callerName)
}
let remotePhoneNumber = call.remotePhoneNumber
let thread = TSContactThread.getOrCreateThread(contactId: remotePhoneNumber)
guard let threadId = thread.uniqueId else {
owsFailDebug("threadId was unexpectedly nil")
return
}
let sound: OWSSound?
if shouldPlaySoundForNotification {
sound = OWSSounds.notificationSound(for: thread)
} else {
sound = nil
}
let userInfo = [
AppNotificationUserInfoKey.threadId: threadId,
AppNotificationUserInfoKey.localCallId: call.localId.uuidString
]
DispatchQueue.main.async {
self.adaptee.notify(category: .missedCall,
body: notificationBody,
userInfo: userInfo,
sound: sound,
replacingIdentifier: call.localId.uuidString)
}
}
public func presentMissedCallBecauseOfNoLongerVerifiedIdentity(call: SignalCall, callerName: String) {
let notificationBody: String
switch previewType {
case .noNameNoPreview:
notificationBody = CallStrings.missedCallWithIdentityChangeNotificationBodyWithoutCallerName
case .nameNoPreview, .namePreview:
notificationBody = String(format: CallStrings.missedCallWithIdentityChangeNotificationBodyWithCallerName, callerName)
}
let remotePhoneNumber = call.remotePhoneNumber
let thread = TSContactThread.getOrCreateThread(contactId: remotePhoneNumber)
guard let threadId = thread.uniqueId else {
owsFailDebug("threadId was unexpectedly nil")
return
}
let sound: OWSSound?
if shouldPlaySoundForNotification {
sound = OWSSounds.notificationSound(for: thread)
} else {
sound = nil
}
let userInfo = [
AppNotificationUserInfoKey.threadId: threadId
]
DispatchQueue.main.async {
self.adaptee.notify(category: .missedCallFromNoLongerVerifiedIdentity,
body: notificationBody,
userInfo: userInfo,
sound: sound,
replacingIdentifier: call.localId.uuidString)
}
}
public func presentMissedCallBecauseOfNewIdentity(call: SignalCall, callerName: String) {
let notificationBody: String
switch previewType {
case .noNameNoPreview:
notificationBody = CallStrings.missedCallWithIdentityChangeNotificationBodyWithoutCallerName
case .nameNoPreview, .namePreview:
notificationBody = String(format: CallStrings.missedCallWithIdentityChangeNotificationBodyWithCallerName, callerName)
}
let remotePhoneNumber = call.remotePhoneNumber
let thread = TSContactThread.getOrCreateThread(contactId: remotePhoneNumber)
guard let threadId = thread.uniqueId else {
owsFailDebug("threadId was unexpectedly nil")
return
}
let sound: OWSSound?
if shouldPlaySoundForNotification {
sound = OWSSounds.notificationSound(for: thread)
} else {
sound = nil
}
let userInfo = [
AppNotificationUserInfoKey.threadId: threadId,
AppNotificationUserInfoKey.callBackNumber: remotePhoneNumber
]
DispatchQueue.main.async {
self.adaptee.notify(category: .missedCall,
body: notificationBody,
userInfo: userInfo,
sound: sound,
replacingIdentifier: call.localId.uuidString)
}
}
// MJK TODO DI contactsManager
public func notifyUser(for incomingMessage: TSIncomingMessage, in thread: TSThread, contactsManager: ContactsManagerProtocol, transaction: YapDatabaseReadTransaction) {
guard !thread.isMuted else {
return
}
// While batch processing, some of the necessary changes have not been commited.
let rawMessageText = incomingMessage.previewText(with: transaction)
// iOS strips anything that looks like a printf formatting character from
// the notification body, so if we want to dispay a literal "%" in a notification
// it must be escaped.
// see https://developer.apple.com/documentation/uikit/uilocalnotification/1616646-alertbody
// for more details.
let messageText = DisplayableText.filterNotificationText(rawMessageText)
let senderName = contactsManager.displayName(forPhoneIdentifier: incomingMessage.authorId)
let notificationBody: String
switch previewType {
case .noNameNoPreview:
notificationBody = NSLocalizedString("APN_Message", comment: "")
case .nameNoPreview:
switch thread {
case is TSContactThread:
// TODO - should this be a format string? seems weird we're hardcoding in a ":"
let fromText = NSLocalizedString("APN_MESSAGE_FROM", comment: "")
notificationBody = String(format: "%@: %@", fromText, senderName)
case is TSGroupThread:
var groupName = thread.name()
if groupName.count < 1 {
groupName = MessageStrings.newGroupDefaultTitle
}
// TODO - should this be a format string? seems weird we're hardcoding in the quotes
let fromText = NSLocalizedString("APN_MESSAGE_IN_GROUP", comment: "")
notificationBody = String(format: "%@ \"%@\"", fromText, groupName)
default:
owsFailDebug("unexpected thread: \(thread)")
return
}
case .namePreview:
switch thread {
case is TSContactThread:
notificationBody = String(format: "%@: %@", senderName, messageText ?? "")
case is TSGroupThread:
var groupName = thread.name()
if groupName.count < 1 {
groupName = MessageStrings.newGroupDefaultTitle
}
let threadName = String(format: "\"%@\"", groupName)
let bodyFormat = NSLocalizedString("APN_MESSAGE_IN_GROUP_DETAILED", comment: "")
notificationBody = String(format: bodyFormat, senderName, threadName, messageText ?? "")
default:
owsFailDebug("unexpected thread: \(thread)")
return
}
}
let sound: OWSSound?
if shouldPlaySoundForNotification {
sound = OWSSounds.notificationSound(for: thread)
} else {
sound = nil
}
guard let threadId = thread.uniqueId else {
owsFailDebug("threadId was unexpectedly nil")
return
}
// Don't reply from lockscreen if anyone in this conversation is
// "no longer verified".
var category = AppNotificationCategory.incomingMessage
for recipientId in thread.recipientIdentifiers {
if self.identityManager.verificationState(forRecipientId: recipientId) == .noLongerVerified {
category = AppNotificationCategory.incomingMessageFromNoLongerVerifiedIdentity
break
}
}
let userInfo = [
AppNotificationUserInfoKey.threadId: threadId
]
DispatchQueue.main.async {
self.adaptee.notify(category: category, body: notificationBody, userInfo: userInfo, sound: sound)
}
}
public func notifyForFailedSend(inThread thread: TSThread) {
let notificationFormat = NSLocalizedString("NOTIFICATION_SEND_FAILED", comment: "subsequent notification body when replying from notification fails")
let notificationBody = String(format: notificationFormat, thread.name())
let sound: OWSSound?
if shouldPlaySoundForNotification {
sound = OWSSounds.notificationSound(for: thread)
} else {
sound = nil
}
guard let threadId = thread.uniqueId else {
owsFailDebug("threadId was unexpectedly nil")
return
}
let userInfo = [
AppNotificationUserInfoKey.threadId: threadId
]
DispatchQueue.main.async {
self.adaptee.notify(category: .errorMessage, body: notificationBody, userInfo: userInfo, sound: sound)
}
}
public func notifyUser(for errorMessage: TSErrorMessage, thread: TSThread, transaction: YapDatabaseReadWriteTransaction) {
let messageText = errorMessage.previewText(with: transaction)
let authorName = thread.name()
let notificationBody: String
switch self.previewType {
case .namePreview, .nameNoPreview:
// TODO better format string, seems weird to hardcode ":"
notificationBody = authorName.rtlSafeAppend(":").rtlSafeAppend(" ").rtlSafeAppend(messageText)
case .noNameNoPreview:
notificationBody = messageText
}
let sound: OWSSound?
if shouldPlaySoundForNotification {
sound = OWSSounds.notificationSound(for: thread)
} else {
sound = nil
}
guard let threadId = thread.uniqueId else {
owsFailDebug("threadId was unexpectedly nil")
return
}
let userInfo = [
AppNotificationUserInfoKey.threadId: threadId
]
transaction.addCompletionQueue(DispatchQueue.main) {
self.adaptee.notify(category: .errorMessage, body: notificationBody, userInfo: userInfo, sound: sound)
}
}
public func notifyUser(forThreadlessErrorMessage errorMessage: TSErrorMessage, transaction: YapDatabaseReadWriteTransaction) {
let notificationBody = errorMessage.previewText(with: transaction)
let sound: OWSSound?
if shouldPlaySoundForNotification {
sound = OWSSounds.globalNotificationSound()
} else {
sound = nil
}
transaction.addCompletionQueue(DispatchQueue.main) {
self.adaptee.notify(category: .threadlessErrorMessage, body: notificationBody, userInfo: [:], sound: sound)
}
}
public func cancelNotifications(threadId: String) {
self.adaptee.cancelNotifications(threadId: threadId)
}
public func clearAllNotifications() {
adaptee.clearAllNotifications()
}
// TODO rename to something like 'shouldThrottle' or 'requestAudioUsage'
var shouldPlaySoundForNotification: Bool {
return adaptee.shouldPlaySoundForNotification
}
}
class NotificationActionHandler {
static let shared: NotificationActionHandler = NotificationActionHandler()
// MARK: - Dependencies
var signalApp: SignalApp {
return SignalApp.shared()
}
var messageSender: MessageSender {
return SSKEnvironment.shared.messageSender
}
var callUIAdapter: CallUIAdapter {
return AppEnvironment.shared.callService.callUIAdapter
}
var notificationPresenter: NotificationPresenter {
return AppEnvironment.shared.notificationPresenter
}
var dbConnection: YapDatabaseConnection {
return OWSPrimaryStorage.shared().dbReadWriteConnection
}
// MARK: -
func answerCall(userInfo: [AnyHashable: Any]) throws -> Promise<Void> {
guard let localCallIdString = userInfo[AppNotificationUserInfoKey.localCallId] as? String else {
throw NotificationError.failDebug("localCallIdString was unexpectedly nil")
}
guard let localCallId = UUID(uuidString: localCallIdString) else {
throw NotificationError.failDebug("unable to build localCallId. localCallIdString: \(localCallIdString)")
}
callUIAdapter.answerCall(localId: localCallId)
return Promise.value(())
}
func callBack(userInfo: [AnyHashable: Any]) throws -> Promise<Void> {
guard let recipientId = userInfo[AppNotificationUserInfoKey.callBackNumber] as? String else {
throw NotificationError.failDebug("recipientId was unexpectedly nil")
}
callUIAdapter.startAndShowOutgoingCall(recipientId: recipientId, hasLocalVideo: false)
return Promise.value(())
}
func declineCall(userInfo: [AnyHashable: Any]) throws -> Promise<Void> {
guard let localCallIdString = userInfo[AppNotificationUserInfoKey.localCallId] as? String else {
throw NotificationError.failDebug("localCallIdString was unexpectedly nil")
}
guard let localCallId = UUID(uuidString: localCallIdString) else {
throw NotificationError.failDebug("unable to build localCallId. localCallIdString: \(localCallIdString)")
}
callUIAdapter.declineCall(localId: localCallId)
return Promise.value(())
}
func markAsRead(userInfo: [AnyHashable: Any]) throws -> Promise<Void> {
guard let threadId = userInfo[AppNotificationUserInfoKey.threadId] as? String else {
throw NotificationError.failDebug("threadId was unexpectedly nil")
}
guard let thread = TSThread.fetch(uniqueId: threadId) else {
throw NotificationError.failDebug("unable to find thread with id: \(threadId)")
}
return Promise { resolver in
self.dbConnection.asyncReadWrite({ transaction in
thread.markAllAsRead(with: transaction)
},
completionBlock: {
self.notificationPresenter.cancelNotifications(threadId: threadId)
resolver.fulfill(())
})
}
}
func reply(userInfo: [AnyHashable: Any], replyText: String) throws -> Promise<Void> {
guard let threadId = userInfo[AppNotificationUserInfoKey.threadId] as? String else {
throw NotificationError.failDebug("threadId was unexpectedly nil")
}
guard let thread = TSThread.fetch(uniqueId: threadId) else {
throw NotificationError.failDebug("unable to find thread with id: \(threadId)")
}
return ThreadUtil.sendMessageNonDurably(text: replyText,
thread: thread,
quotedReplyModel: nil,
messageSender: messageSender).recover { error in
Logger.warn("Failed to send reply message from notification with error: \(error)")
self.notificationPresenter.notifyForFailedSend(inThread: thread)
}
}
func showThread(userInfo: [AnyHashable: Any]) throws -> Promise<Void> {
guard let threadId = userInfo[AppNotificationUserInfoKey.threadId] as? String else {
throw NotificationError.failDebug("threadId was unexpectedly nil")
}
// If this happens when the the app is not, visible we skip the animation so the thread
// can be visible to the user immediately upon opening the app, rather than having to watch
// it animate in from the homescreen.
let shouldAnimate = UIApplication.shared.applicationState == .active
signalApp.presentConversation(forThreadId: threadId, animated: shouldAnimate)
return Promise.value(())
}
}
extension ThreadUtil {
class func sendMessageNonDurably(text: String, thread: TSThread, quotedReplyModel: OWSQuotedReplyModel?, messageSender: MessageSender) -> Promise<Void> {
return Promise { resolver in
self.sendMessageNonDurably(withText: text,
in: thread,
quotedReplyModel: quotedReplyModel,
messageSender: messageSender,
success: resolver.fulfill,
failure: resolver.reject)
}
}
}
extension OWSSound {
var filename: String? {
return OWSSounds.filename(for: self)
}
}
enum NotificationError: Error {
case assertionError(description: String)
}
extension NotificationError {
static func failDebug(_ description: String) -> NotificationError {
owsFailDebug(description)
return NotificationError.assertionError(description: description)
}
}

View File

@ -0,0 +1,290 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import Foundation
import PromiseKit
struct LegacyNotificationConfig {
static var allNotificationCategories: Set<UIUserNotificationCategory> {
let categories = AppNotificationCategory.allCases.map { notificationCategory($0) }
return Set(categories)
}
static func notificationActions(for category: AppNotificationCategory) -> [UIUserNotificationAction] {
return category.actions.map { notificationAction($0) }
}
static func notificationAction(_ action: AppNotificationAction) -> UIUserNotificationAction {
switch action {
case .answerCall:
let mutableAction = UIMutableUserNotificationAction()
mutableAction.identifier = action.identifier
mutableAction.title = CallStrings.answerCallButtonTitle
mutableAction.activationMode = .foreground
mutableAction.isDestructive = false
mutableAction.isAuthenticationRequired = false
return mutableAction
case .callBack:
let mutableAction = UIMutableUserNotificationAction()
mutableAction.identifier = action.identifier
mutableAction.title = CallStrings.callBackButtonTitle
mutableAction.activationMode = .foreground
mutableAction.isDestructive = false
mutableAction.isAuthenticationRequired = true
return mutableAction
case .declineCall:
let mutableAction = UIMutableUserNotificationAction()
mutableAction.identifier = action.identifier
mutableAction.title = CallStrings.declineCallButtonTitle
mutableAction.activationMode = .background
mutableAction.isDestructive = false
mutableAction.isAuthenticationRequired = false
return mutableAction
case .markAsRead:
let mutableAction = UIMutableUserNotificationAction()
mutableAction.identifier = action.identifier
mutableAction.title = MessageStrings.markAsReadNotificationAction
mutableAction.activationMode = .background
mutableAction.isDestructive = false
mutableAction.isAuthenticationRequired = false
return mutableAction
case .reply:
let mutableAction = UIMutableUserNotificationAction()
mutableAction.identifier = action.identifier
mutableAction.title = MessageStrings.replyNotificationAction
mutableAction.activationMode = .background
mutableAction.isDestructive = false
mutableAction.isAuthenticationRequired = false
mutableAction.behavior = .textInput
return mutableAction
case .showThread:
let mutableAction = UIMutableUserNotificationAction()
mutableAction.identifier = action.identifier
mutableAction.title = CallStrings.showThreadButtonTitle
mutableAction.activationMode = .foreground
mutableAction.isDestructive = false
mutableAction.isAuthenticationRequired = true
return mutableAction
}
}
static func action(identifier: String) -> AppNotificationAction? {
return AppNotificationAction.allCases.first { notificationAction($0).identifier == identifier }
}
static func notificationActions(category: AppNotificationCategory) -> [UIUserNotificationAction] {
return category.actions.map { notificationAction($0) }
}
static func notificationCategory(_ category: AppNotificationCategory) -> UIUserNotificationCategory {
let notificationCategory = UIMutableUserNotificationCategory()
notificationCategory.identifier = category.identifier
let actions = notificationActions(category: category)
notificationCategory.setActions(actions, for: .minimal)
notificationCategory.setActions(actions, for: .default)
return notificationCategory
}
}
class LegacyNotificationPresenterAdaptee {
private var notifications: [String: UILocalNotification] = [:]
private var userNotificationSettingsPromise: Promise<Void>?
private var userNotificationSettingsResolver: Resolver<Void>?
// Notification registration is confirmed via AppDelegate
// Before this occurs, it is not safe to assume push token requests will be acknowledged.
//
// e.g. in the case that Background Fetch is disabled, token requests will be ignored until
// we register user notification settings.
@objc
public func didRegisterUserNotificationSettings() {
AssertIsOnMainThread()
guard let userNotificationSettingsResolver = self.userNotificationSettingsResolver else {
owsFailDebug("promise completion in \(#function) unexpectedly nil")
return
}
userNotificationSettingsResolver.fulfill(())
}
}
extension LegacyNotificationPresenterAdaptee: NotificationPresenterAdaptee {
func registerNotificationSettings() -> Promise<Void> {
AssertIsOnMainThread()
Logger.debug("")
guard self.userNotificationSettingsPromise == nil else {
let promise = self.userNotificationSettingsPromise!
Logger.info("already registered user notification settings")
return promise
}
let (promise, resolver) = Promise<Void>.pending()
self.userNotificationSettingsPromise = promise
self.userNotificationSettingsResolver = resolver
let settings = UIUserNotificationSettings(types: [.alert, .sound, .badge],
categories: LegacyNotificationConfig.allNotificationCategories)
UIApplication.shared.registerUserNotificationSettings(settings)
return promise
}
func notify(category: AppNotificationCategory, body: String, userInfo: [AnyHashable: Any], sound: OWSSound?) {
AssertIsOnMainThread()
notify(category: category, body: body, userInfo: userInfo, sound: sound, replacingIdentifier: nil)
}
func notify(category: AppNotificationCategory, body: String, userInfo: [AnyHashable: Any], sound: OWSSound?, replacingIdentifier: String?) {
AssertIsOnMainThread()
guard UIApplication.shared.applicationState != .active else {
Logger.info("skipping notification; app is in foreground")
return
}
let notification = UILocalNotification()
notification.category = category.identifier
notification.alertBody = body
notification.userInfo = userInfo
notification.soundName = sound?.filename
var notificationIdentifier: String = UUID().uuidString
if let replacingIdentifier = replacingIdentifier {
notificationIdentifier = replacingIdentifier
Logger.debug("replacing notification with identifier: \(notificationIdentifier)")
cancelNotification(identifier: notificationIdentifier)
}
let checkForCancel = category == .incomingMessage
if checkForCancel && hasReceivedSyncMessageRecently {
assert(userInfo[AppNotificationUserInfoKey.threadId] != nil)
notification.fireDate = Date(timeIntervalSinceNow: kNotificationDelayForRemoteRead)
notification.timeZone = NSTimeZone.local
}
Logger.debug("presenting notification with identifier: \(notificationIdentifier)")
UIApplication.shared.scheduleLocalNotification(notification)
notifications[notificationIdentifier] = notification
}
func cancelNotification(_ notification: UILocalNotification) {
AssertIsOnMainThread()
UIApplication.shared.cancelLocalNotification(notification)
}
func cancelNotification(identifier: String) {
AssertIsOnMainThread()
guard let notification = notifications.removeValue(forKey: identifier) else {
Logger.debug("no notification to cancel with identifier: \(identifier)")
return
}
cancelNotification(notification)
}
func cancelNotifications(threadId: String) {
AssertIsOnMainThread()
for notification in notifications.values {
guard let notificationThreadId = notification.userInfo?[AppNotificationUserInfoKey.threadId] as? String else {
continue
}
guard notificationThreadId == threadId else {
continue
}
cancelNotification(notification)
}
}
func clearAllNotifications() {
AssertIsOnMainThread()
for (_, notification) in notifications {
cancelNotification(notification)
}
}
// FIXME: Accomodate 'playSoundsInForeground' preference
// FIXME: debounce
var shouldPlaySoundForNotification: Bool {
return true
}
}
@objc
public class LegacyNotificationActionHandler: NSObject {
@objc
public static let kDefaultActionIdentifier = "LegacyNotificationActionHandler.kDefaultActionIdentifier"
// TODO move this to environment?
@objc
static let shared: LegacyNotificationActionHandler = LegacyNotificationActionHandler()
var actionHandler: NotificationActionHandler {
return NotificationActionHandler.shared
}
@objc
func handleNotificationResponse(actionIdentifier: String,
notification: UILocalNotification,
responseInfo: [AnyHashable: Any],
completionHandler: @escaping () -> Void) {
firstly {
try handleNotificationResponse(actionIdentifier: actionIdentifier, notification: notification, responseInfo: responseInfo)
}.done {
completionHandler()
}.catch { error in
completionHandler()
owsFailDebug("error: \(error)")
Logger.error("error: \(error)")
}.retainUntilComplete()
}
func handleNotificationResponse(actionIdentifier: String,
notification: UILocalNotification,
responseInfo: [AnyHashable: Any]) throws -> Promise<Void> {
assert(AppReadiness.isAppReady())
let userInfo = notification.userInfo ?? [:]
switch actionIdentifier {
case type(of: self).kDefaultActionIdentifier:
Logger.debug("default action")
return try actionHandler.showThread(userInfo: userInfo)
default:
// proceed
break
}
guard let action = LegacyNotificationConfig.action(identifier: actionIdentifier) else {
throw NotificationError.failDebug("unable to find action for actionIdentifier: \(actionIdentifier)")
}
switch action {
case .answerCall:
return try actionHandler.answerCall(userInfo: userInfo)
case .callBack:
return try actionHandler.callBack(userInfo: userInfo)
case .declineCall:
return try actionHandler.declineCall(userInfo: userInfo)
case .markAsRead:
return try actionHandler.markAsRead(userInfo: userInfo)
case .reply:
guard let replyText = responseInfo[UIUserNotificationActionResponseTypedTextKey] as? String else {
throw NotificationError.failDebug("replyText was unexpectedly nil")
}
return try actionHandler.reply(userInfo: userInfo, replyText: replyText)
case .showThread:
return try actionHandler.showThread(userInfo: userInfo)
}
}
}

View File

@ -1,61 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import Foundation
@objc
public protocol NotificationsAdaptee: NotificationsProtocol, OWSCallNotificationsAdaptee { }
extension NotificationsManager: NotificationsAdaptee { }
/**
* Present call related notifications to the user.
*/
@objc(OWSNotificationsAdapter)
public class NotificationsAdapter: NSObject, NotificationsProtocol {
private let adaptee: NotificationsAdaptee
@objc public override init() {
self.adaptee = NotificationsManager()
super.init()
SwiftSingletons.register(self)
}
func presentIncomingCall(_ call: SignalCall, callerName: String) {
Logger.debug("")
adaptee.presentIncomingCall(call, callerName: callerName)
}
func presentMissedCall(_ call: SignalCall, callerName: String) {
Logger.debug("")
adaptee.presentMissedCall(call, callerName: callerName)
}
public func presentMissedCallBecauseOfNoLongerVerifiedIdentity(call: SignalCall, callerName: String) {
adaptee.presentMissedCallBecauseOfNoLongerVerifiedIdentity(call: call, callerName: callerName)
}
public func presentMissedCallBecauseOfNewIdentity(call: SignalCall, callerName: String) {
adaptee.presentMissedCallBecauseOfNewIdentity(call: call, callerName: callerName)
}
// MJK TODO DI contactsManager
public func notifyUser(for incomingMessage: TSIncomingMessage, in thread: TSThread, contactsManager: ContactsManagerProtocol, transaction: YapDatabaseReadTransaction) {
adaptee.notifyUser(for: incomingMessage, in: thread, contactsManager: contactsManager, transaction: transaction)
}
public func notifyUser(for error: TSErrorMessage, thread: TSThread, transaction: YapDatabaseReadWriteTransaction) {
adaptee.notifyUser(for: error, thread: thread, transaction: transaction)
}
public func notifyUser(forThreadlessErrorMessage error: TSErrorMessage, transaction: YapDatabaseReadWriteTransaction) {
adaptee.notifyUser(forThreadlessErrorMessage: error, transaction: transaction)
}
public func clearAllNotifications() {
adaptee.clearAllNotifications()
}
}

View File

@ -1,185 +1,288 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
/**
* TODO This is currently unused code. I started implenting new notifications as UserNotifications rather than the deprecated
* LocalNotifications before I realized we can't mix and match. Registering notifications for one clobbers the other.
* So, for now iOS10 continues to use LocalNotifications until we can port all the NotificationsManager stuff here.
*/
import Foundation
import UserNotifications
import PromiseKit
@available(iOS 10.0, *)
struct AppNotifications {
enum Category {
case missedCall,
missedCallFromNoLongerVerifiedIdentity
class UserNotificationConfig {
// Don't forget to update this! We use it to register categories.
static let allValues = [ missedCall, missedCallFromNoLongerVerifiedIdentity ]
}
enum Action {
case callBack,
showThread
}
static var allCategories: Set<UNNotificationCategory> {
let categories = Category.allValues.map { category($0) }
class var allNotificationCategories: Set<UNNotificationCategory> {
let categories = AppNotificationCategory.allCases.map { notificationCategory($0) }
return Set(categories)
}
static func category(_ type: Category) -> UNNotificationCategory {
switch type {
case .missedCall:
return UNNotificationCategory(identifier: "org.whispersystems.signal.AppNotifications.Category.missedCall",
actions: [ action(.callBack) ],
intentIdentifiers: [],
options: [])
class func notificationActions(for category: AppNotificationCategory) -> [UNNotificationAction] {
return category.actions.map { notificationAction($0) }
}
case .missedCallFromNoLongerVerifiedIdentity:
return UNNotificationCategory(identifier: "org.whispersystems.signal.AppNotifications.Category.missedCallFromNoLongerVerifiedIdentity",
actions: [ action(.showThread) ],
intentIdentifiers: [],
options: [])
class func notificationCategory(_ category: AppNotificationCategory) -> UNNotificationCategory {
return UNNotificationCategory(identifier: category.identifier,
actions: notificationActions(for: category),
intentIdentifiers: [],
options: [])
}
class func notificationAction(_ action: AppNotificationAction) -> UNNotificationAction {
switch action {
case .answerCall:
return UNNotificationAction(identifier: action.identifier,
title: CallStrings.answerCallButtonTitle,
options: [.foreground])
case .callBack:
return UNNotificationAction(identifier: action.identifier,
title: CallStrings.callBackButtonTitle,
options: [.foreground])
case .declineCall:
return UNNotificationAction(identifier: action.identifier,
title: CallStrings.declineCallButtonTitle,
options: [])
case .markAsRead:
return UNNotificationAction(identifier: action.identifier,
title: MessageStrings.markAsReadNotificationAction,
options: [])
case .reply:
return UNTextInputNotificationAction(identifier: action.identifier,
title: MessageStrings.replyNotificationAction,
options: [],
textInputButtonTitle: MessageStrings.sendButton,
textInputPlaceholder: "")
case .showThread:
return UNNotificationAction(identifier: action.identifier,
title: CallStrings.showThreadButtonTitle,
options: [.foreground])
}
}
static func action(_ type: Action) -> UNNotificationAction {
switch type {
case .callBack:
return UNNotificationAction(identifier: "org.whispersystems.signal.AppNotifications.Action.callBack",
title: CallStrings.callBackButtonTitle,
options: .authenticationRequired)
case .showThread:
return UNNotificationAction(identifier: "org.whispersystems.signal.AppNotifications.Action.showThread",
title: CallStrings.showThreadButtonTitle,
options: .authenticationRequired)
}
class func action(identifier: String) -> AppNotificationAction? {
return AppNotificationAction.allCases.first { notificationAction($0).identifier == identifier }
}
}
@available(iOS 10.0, *)
class UserNotificationPresenterAdaptee: NSObject, UNUserNotificationCenterDelegate {
private let notificationCenter: UNUserNotificationCenter
private var notifications: [String: UNNotificationRequest] = [:]
override init() {
self.notificationCenter = UNUserNotificationCenter.current()
super.init()
notificationCenter.delegate = self
SwiftSingletons.register(self)
}
}
@available(iOS 10.0, *)
class UserNotificationsAdaptee: NSObject, OWSCallNotificationsAdaptee, UNUserNotificationCenterDelegate {
extension UserNotificationPresenterAdaptee: NotificationPresenterAdaptee {
private let center: UNUserNotificationCenter
func registerNotificationSettings() -> Promise<Void> {
return Promise { resolver in
notificationCenter.requestAuthorization(options: [.badge, .sound, .alert]) { (granted, error) in
self.notificationCenter.setNotificationCategories(UserNotificationConfig.allNotificationCategories)
var previewType: NotificationType {
return Environment.shared.preferences.notificationPreviewType()
}
if granted {
Logger.debug("succeeded.")
} else if error != nil {
Logger.error("failed with error: \(error!)")
} else {
owsFailDebug("error was unexpectedly nil")
Logger.error("failed without error.")
}
override init() {
self.center = UNUserNotificationCenter.current()
super.init()
SwiftSingletons.register(self)
center.delegate = self
// FIXME TODO only do this after user has registered.
// maybe the PushManager needs a reference to the NotificationsAdapter.
requestAuthorization()
center.setNotificationCategories(AppNotifications.allCategories)
}
func requestAuthorization() {
center.requestAuthorization(options: [.badge, .sound, .alert]) { (granted, error) in
if granted {
Logger.debug("succeeded.")
} else if error != nil {
Logger.error("failed with error: \(error!)")
} else {
Logger.error("failed without error.")
// Note that the promise is fulfilled regardless of if notification permssions were
// granted. This promise only indicates that the user has responded, so we can
// proceed with requesting push tokens and complete registration.
resolver.fulfill(())
}
}
}
// MARK: - OWSCallNotificationsAdaptee
public func presentIncomingCall(_ call: SignalCall, callerName: String) {
Logger.debug("is no-op, because it's handled with callkit.")
// TODO since CallKit doesn't currently work on the simulator,
// we could implement UNNotifications for simulator testing, or if people have opted out of callkit.
func notify(category: AppNotificationCategory, body: String, userInfo: [AnyHashable: Any], sound: OWSSound?) {
AssertIsOnMainThread()
notify(category: category, body: body, userInfo: userInfo, sound: sound, replacingIdentifier: nil)
}
public func presentMissedCall(_ call: SignalCall, callerName: String) {
Logger.debug("")
func notify(category: AppNotificationCategory, body: String, userInfo: [AnyHashable: Any], sound: OWSSound?, replacingIdentifier: String?) {
AssertIsOnMainThread()
let content = UNMutableNotificationContent()
// TODO group by thread identifier
// content.threadIdentifier = threadId
content.categoryIdentifier = category.identifier
content.userInfo = userInfo
content.sound = sound?.notificationSound
let notificationBody = { () -> String in
switch previewType {
case .noNameNoPreview:
return CallStrings.missedCallNotificationBodyWithoutCallerName
case .nameNoPreview, .namePreview:
return (Environment.shared.preferences.isCallKitPrivacyEnabled()
? CallStrings.missedCallNotificationBodyWithoutCallerName
: String(format: CallStrings.missedCallNotificationBodyWithCallerName, callerName))
}}()
var notificationIdentifier: String = UUID().uuidString
if let replacingIdentifier = replacingIdentifier {
notificationIdentifier = replacingIdentifier
Logger.debug("replacing notification with identifier: \(notificationIdentifier)")
cancelNotification(identifier: notificationIdentifier)
}
content.body = notificationBody
content.sound = UNNotificationSound.default()
content.categoryIdentifier = AppNotifications.category(.missedCall).identifier
let trigger: UNNotificationTrigger?
let checkForCancel = category == .incomingMessage
if checkForCancel && hasReceivedSyncMessageRecently {
assert(userInfo[AppNotificationUserInfoKey.threadId] != nil)
trigger = UNTimeIntervalNotificationTrigger(timeInterval: kNotificationDelayForRemoteRead, repeats: false)
} else {
trigger = nil
}
let request = UNNotificationRequest.init(identifier: call.localId.uuidString, content: content, trigger: nil)
if shouldPresentNotification(category: category, userInfo: userInfo) {
content.body = body
} else {
// Play sound and vibrate, but without a `body` no banner will show.
Logger.debug("supressing notification body")
}
center.add(request)
let request = UNNotificationRequest(identifier: notificationIdentifier, content: content, trigger: trigger)
Logger.debug("presenting notification with identifier: \(notificationIdentifier)")
notificationCenter.add(request)
notifications[notificationIdentifier] = request
}
public func presentMissedCallBecauseOfNoLongerVerifiedIdentity(call: SignalCall, callerName: String) {
Logger.debug("")
let content = UNMutableNotificationContent()
// TODO group by thread identifier
// content.threadIdentifier = threadId
let notificationBody = { () -> String in
switch previewType {
case .noNameNoPreview:
return CallStrings.missedCallWithIdentityChangeNotificationBodyWithoutCallerName
case .nameNoPreview, .namePreview:
return (Environment.shared.preferences.isCallKitPrivacyEnabled()
? CallStrings.missedCallWithIdentityChangeNotificationBodyWithoutCallerName
: String(format: CallStrings.missedCallWithIdentityChangeNotificationBodyWithCallerName, callerName))
}}()
content.body = notificationBody
content.sound = UNNotificationSound.default()
content.categoryIdentifier = AppNotifications.category(.missedCallFromNoLongerVerifiedIdentity).identifier
let request = UNNotificationRequest.init(identifier: call.localId.uuidString, content: content, trigger: nil)
center.add(request)
func cancelNotification(identifier: String) {
AssertIsOnMainThread()
notifications.removeValue(forKey: identifier)
notificationCenter.removeDeliveredNotifications(withIdentifiers: [identifier])
notificationCenter.removePendingNotificationRequests(withIdentifiers: [identifier])
}
public func presentMissedCallBecauseOfNewIdentity(call: SignalCall, callerName: String) {
Logger.debug("")
func cancelNotification(_ notification: UNNotificationRequest) {
AssertIsOnMainThread()
cancelNotification(identifier: notification.identifier)
}
let content = UNMutableNotificationContent()
// TODO group by thread identifier
// content.threadIdentifier = threadId
func cancelNotifications(threadId: String) {
AssertIsOnMainThread()
for notification in notifications.values {
guard let notificationThreadId = notification.content.userInfo[AppNotificationUserInfoKey.threadId] as? String else {
continue
}
let notificationBody = { () -> String in
switch previewType {
case .noNameNoPreview:
return CallStrings.missedCallWithIdentityChangeNotificationBodyWithoutCallerName
case .nameNoPreview, .namePreview:
return (Environment.shared.preferences.isCallKitPrivacyEnabled()
? CallStrings.missedCallWithIdentityChangeNotificationBodyWithoutCallerName
: String(format: CallStrings.missedCallWithIdentityChangeNotificationBodyWithCallerName, callerName))
}}()
guard notificationThreadId == threadId else {
continue
}
content.body = notificationBody
content.sound = UNNotificationSound.default()
content.categoryIdentifier = AppNotifications.category(.missedCall).identifier
cancelNotification(notification)
}
}
let request = UNNotificationRequest.init(identifier: call.localId.uuidString, content: content, trigger: nil)
func clearAllNotifications() {
AssertIsOnMainThread()
notificationCenter.removeAllPendingNotificationRequests()
notificationCenter.removeAllDeliveredNotifications()
}
center.add(request)
// UNUserNotification framework does it's own audio throttling
var shouldPlaySoundForNotification: Bool {
return true
}
func shouldPresentNotification(category: AppNotificationCategory, userInfo: [AnyHashable: Any]) -> Bool {
AssertIsOnMainThread()
guard UIApplication.shared.applicationState == .active else {
return true
}
guard category == .incomingMessage || category == .errorMessage else {
return true
}
guard let notificationThreadId = userInfo[AppNotificationUserInfoKey.threadId] as? String else {
owsFailDebug("threadId was unexpectedly nil")
return true
}
guard let conversationViewController = UIApplication.shared.frontmostViewController as? ConversationViewController else {
return true
}
// Show notifications for any *other* thread
return conversationViewController.thread.uniqueId != notificationThreadId
}
}
@objc
@available(iOS 10.0, *)
public class UserNotificationActionHandler: NSObject {
// TODO move this to environment?
@objc
static let shared: UserNotificationActionHandler = UserNotificationActionHandler()
var actionHandler: NotificationActionHandler {
return NotificationActionHandler.shared
}
@objc
func handleNotificationResponse( _ response: UNNotificationResponse, completionHandler: @escaping () -> Void) {
AssertIsOnMainThread()
firstly {
try handleNotificationResponse(response)
}.done {
completionHandler()
}.catch { error in
completionHandler()
owsFailDebug("error: \(error)")
Logger.error("error: \(error)")
}.retainUntilComplete()
}
func handleNotificationResponse( _ response: UNNotificationResponse) throws -> Promise<Void> {
AssertIsOnMainThread()
assert(AppReadiness.isAppReady())
let userInfo = response.notification.request.content.userInfo
switch response.actionIdentifier {
case UNNotificationDefaultActionIdentifier:
Logger.debug("default action")
return try actionHandler.showThread(userInfo: userInfo)
case UNNotificationDismissActionIdentifier:
// TODO - mark as read?
Logger.debug("dismissed notification")
return Promise.value(())
default:
// proceed
break
}
guard let action = UserNotificationConfig.action(identifier: response.actionIdentifier) else {
throw NotificationError.failDebug("unable to find action for actionIdentifier: \(response.actionIdentifier)")
}
switch action {
case .answerCall:
return try actionHandler.answerCall(userInfo: userInfo)
case .callBack:
return try actionHandler.callBack(userInfo: userInfo)
case .declineCall:
return try actionHandler.declineCall(userInfo: userInfo)
case .markAsRead:
return try actionHandler.markAsRead(userInfo: userInfo)
case .reply:
guard let textInputResponse = response as? UNTextInputNotificationResponse else {
throw NotificationError.failDebug("response had unexpected type: \(response)")
}
return try actionHandler.reply(userInfo: userInfo, replyText: textInputResponse.userText)
case .showThread:
return try actionHandler.showThread(userInfo: userInfo)
}
}
}
extension OWSSound {
@available(iOS 10.0, *)
var notificationSound: UNNotificationSound {
guard let filename = OWSSounds.filename(for: self) else {
owsFailDebug("filename was unexpectedly nil")
return UNNotificationSound.default()
}
return UNNotificationSound(named: filename)
}
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "AdvancedSettingsTableViewController.h"
@ -7,7 +7,6 @@
#import "DomainFrontingCountryViewController.h"
#import "OWSCountryMetadata.h"
#import "Pastelog.h"
#import "PushManager.h"
#import "Signal-Swift.h"
#import "TSAccountManager.h"
#import <PromiseKit/AnyPromise.h>

View File

@ -13,7 +13,6 @@
#import "OWSNavigationController.h"
#import "PrivacySettingsTableViewController.h"
#import "ProfileViewController.h"
#import "PushManager.h"
#import "RegistrationUtils.h"
#import "Signal-Swift.h"
#import <SignalMessaging/Environment.h>

View File

@ -143,9 +143,7 @@ const CGFloat kMaxTextViewHeight = 98;
[self.attachmentButton autoSetDimensionsToSize:CGSizeMake(40, kMinTextViewHeight)];
_sendButton = [UIButton buttonWithType:UIButtonTypeCustom];
[self.sendButton
setTitle:NSLocalizedString(@"SEND_BUTTON_TITLE", @"Label for the send button in the conversation view.")
forState:UIControlStateNormal];
[self.sendButton setTitle:MessageStrings.sendButton forState:UIControlStateNormal];
[self.sendButton setTitleColor:UIColor.ows_signalBlueColor forState:UIControlStateNormal];
self.sendButton.titleLabel.textAlignment = NSTextAlignmentCenter;
self.sendButton.titleLabel.font = [UIFont ows_mediumFontWithSize:17.f];

View File

@ -11,8 +11,8 @@ class DebugUINotifications: DebugUIPage {
// MARK: Dependencies
var notificationsAdapter: NotificationsAdapter {
return AppEnvironment.shared.notificationsAdapter
var notificationPresenter: NotificationPresenter {
return AppEnvironment.shared.notificationPresenter
}
var messageSender: MessageSender {
return SSKEnvironment.shared.messageSender
@ -135,28 +135,28 @@ class DebugUINotifications: DebugUIPage {
func notifyForIncomingCall(thread: TSContactThread) -> Guarantee<Void> {
Logger.info("⚠️ will present notification after delay")
return delayedNotificationDispatchWithFakeCall(thread: thread) { call in
self.notificationsAdapter.presentIncomingCall(call, callerName: thread.name())
self.notificationPresenter.presentIncomingCall(call, callerName: thread.name())
}
}
func notifyForMissedCall(thread: TSContactThread) -> Guarantee<Void> {
Logger.info("⚠️ will present notification after delay")
return delayedNotificationDispatchWithFakeCall(thread: thread) { call in
self.notificationsAdapter.presentMissedCall(call, callerName: thread.name())
self.notificationPresenter.presentMissedCall(call, callerName: thread.name())
}
}
func notifyForMissedCallBecauseOfNewIdentity(thread: TSContactThread) -> Guarantee<Void> {
Logger.info("⚠️ will present notification after delay")
return delayedNotificationDispatchWithFakeCall(thread: thread) { call in
self.notificationsAdapter.presentMissedCallBecauseOfNewIdentity(call: call, callerName: thread.name())
self.notificationPresenter.presentMissedCallBecauseOfNewIdentity(call: call, callerName: thread.name())
}
}
func notifyForMissedCallBecauseOfNoLongerVerifiedIdentity(thread: TSContactThread) -> Guarantee<Void> {
Logger.info("⚠️ will present notification after delay")
return delayedNotificationDispatchWithFakeCall(thread: thread) { call in
self.notificationsAdapter.presentMissedCallBecauseOfNoLongerVerifiedIdentity(call: call, callerName: thread.name())
self.notificationPresenter.presentMissedCallBecauseOfNoLongerVerifiedIdentity(call: call, callerName: thread.name())
}
}
@ -168,7 +168,7 @@ class DebugUINotifications: DebugUIPage {
factory.threadCreator = { _ in return thread }
let incomingMessage = factory.create(transaction: transaction)
self.notificationsAdapter.notifyUser(for: incomingMessage,
self.notificationPresenter.notifyUser(for: incomingMessage,
in: thread,
contactsManager: self.contactsManager,
transaction: transaction)
@ -184,7 +184,7 @@ class DebugUINotifications: DebugUIPage {
failedMessageType: TSErrorMessageType.invalidMessage)
self.readWrite { transaction in
self.notificationsAdapter.notifyUser(for: errorMessage, thread: thread, transaction: transaction)
self.notificationPresenter.notifyUser(for: errorMessage, thread: thread, transaction: transaction)
}
}
}
@ -195,7 +195,7 @@ class DebugUINotifications: DebugUIPage {
self.readWrite { transaction in
let errorMessage = TSErrorMessage.corruptedMessageInUnknownThread()
self.notificationsAdapter.notifyUser(forThreadlessErrorMessage: errorMessage,
self.notificationPresenter.notifyUser(forThreadlessErrorMessage: errorMessage,
transaction: transaction)
}
}

View File

@ -10,7 +10,6 @@
#import "OWSNavigationController.h"
#import "OWSPrimaryStorage.h"
#import "ProfileViewController.h"
#import "PushManager.h"
#import "RegistrationUtils.h"
#import "Signal-Swift.h"
#import "SignalApp.h"
@ -943,10 +942,10 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
{
OWSAssertIsOnMainThread();
OWSLogInfo(@"beggining refreshing.");
[AppEnvironment.shared.messageFetcherJob run].ensure(^{
[[AppEnvironment.shared.messageFetcherJob run].ensure(^{
OWSLogInfo(@"ending refreshing.");
[refreshControl endRefreshing];
});
}) retainUntilComplete];
}
#pragma mark - Edit Actions

View File

@ -401,8 +401,8 @@ private class SignalCallData: NSObject {
return AppEnvironment.shared.accountManager
}
private var notificationsAdapter: NotificationsAdapter {
return AppEnvironment.shared.notificationsAdapter
private var notificationPresenter: NotificationPresenter {
return AppEnvironment.shared.notificationPresenter
}
// MARK: - Notifications
@ -427,7 +427,7 @@ private class SignalCallData: NSObject {
Logger.warn("ending current call in. Did user toggle callkit preference while in a call?")
self.terminateCall()
}
self.callUIAdapter = CallUIAdapter(callService: self, contactsManager: self.contactsManager, notificationsAdapter: self.notificationsAdapter)
self.callUIAdapter = CallUIAdapter(callService: self, contactsManager: self.contactsManager, notificationPresenter: self.notificationPresenter)
}
// MARK: - Service Actions
@ -691,11 +691,11 @@ private class SignalCallData: NSObject {
switch untrustedIdentity!.verificationState {
case .verified:
owsFailDebug("shouldn't have missed a call due to untrusted identity if the identity is verified")
self.notificationsAdapter.presentMissedCall(newCall, callerName: callerName)
self.notificationPresenter.presentMissedCall(newCall, callerName: callerName)
case .default:
self.notificationsAdapter.presentMissedCallBecauseOfNewIdentity(call: newCall, callerName: callerName)
self.notificationPresenter.presentMissedCallBecauseOfNewIdentity(call: newCall, callerName: callerName)
case .noLongerVerified:
self.notificationsAdapter.presentMissedCallBecauseOfNoLongerVerifiedIdentity(call: newCall, callerName: callerName)
self.notificationPresenter.presentMissedCallBecauseOfNoLongerVerifiedIdentity(call: newCall, callerName: callerName)
}
// MJK TODO remove this timestamp param

View File

@ -11,17 +11,17 @@ import SignalMessaging
*/
class NonCallKitCallUIAdaptee: NSObject, CallUIAdaptee {
let notificationsAdapter: NotificationsAdapter
let notificationPresenter: NotificationPresenter
let callService: CallService
// Starting/Stopping incoming call ringing is our apps responsibility for the non CallKit interface.
let hasManualRinger = true
required init(callService: CallService, notificationsAdapter: NotificationsAdapter) {
required init(callService: CallService, notificationPresenter: NotificationPresenter) {
AssertIsOnMainThread()
self.callService = callService
self.notificationsAdapter = notificationsAdapter
self.notificationPresenter = notificationPresenter
super.init()
}
@ -59,14 +59,14 @@ class NonCallKitCallUIAdaptee: NSObject, CallUIAdaptee {
if UIApplication.shared.applicationState == .active {
Logger.debug("skipping notification since app is already active.")
} else {
notificationsAdapter.presentIncomingCall(call, callerName: callerName)
notificationPresenter.presentIncomingCall(call, callerName: callerName)
}
}
func reportMissedCall(_ call: SignalCall, callerName: String) {
AssertIsOnMainThread()
notificationsAdapter.presentMissedCall(call, callerName: callerName)
notificationPresenter.presentMissedCall(call, callerName: callerName)
}
func answerCall(localId: UUID) {

View File

@ -20,7 +20,7 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
private let callManager: CallKitCallManager
internal let callService: CallService
internal let notificationsAdapter: NotificationsAdapter
internal let notificationPresenter: NotificationPresenter
internal let contactsManager: OWSContactsManager
private let showNamesOnCallScreen: Bool
private let provider: CXProvider
@ -76,7 +76,7 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
return providerConfiguration
}
init(callService: CallService, contactsManager: OWSContactsManager, notificationsAdapter: NotificationsAdapter, showNamesOnCallScreen: Bool, useSystemCallLog: Bool) {
init(callService: CallService, contactsManager: OWSContactsManager, notificationPresenter: NotificationPresenter, showNamesOnCallScreen: Bool, useSystemCallLog: Bool) {
AssertIsOnMainThread()
Logger.debug("")
@ -84,7 +84,7 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
self.callManager = CallKitCallManager(showNamesOnCallScreen: showNamesOnCallScreen)
self.callService = callService
self.contactsManager = contactsManager
self.notificationsAdapter = notificationsAdapter
self.notificationPresenter = notificationPresenter
self.provider = type(of: self).sharedProvider(useSystemCallLog: useSystemCallLog)

View File

@ -10,7 +10,7 @@ import SignalMessaging
import WebRTC
protocol CallUIAdaptee {
var notificationsAdapter: NotificationsAdapter { get }
var notificationPresenter: NotificationPresenter { get }
var callService: CallService { get }
var hasManualRinger: Bool { get }
@ -60,7 +60,7 @@ extension CallUIAdaptee {
internal func reportMissedCall(_ call: SignalCall, callerName: String) {
AssertIsOnMainThread()
notificationsAdapter.presentMissedCall(call, callerName: callerName)
notificationPresenter.presentMissedCall(call, callerName: callerName)
}
internal func startAndShowOutgoingCall(recipientId: String, hasLocalVideo: Bool) {
@ -88,7 +88,7 @@ extension CallUIAdaptee {
internal let audioService: CallAudioService
internal let callService: CallService
public required init(callService: CallService, contactsManager: OWSContactsManager, notificationsAdapter: NotificationsAdapter) {
public required init(callService: CallService, contactsManager: OWSContactsManager, notificationPresenter: NotificationPresenter) {
AssertIsOnMainThread()
self.contactsManager = contactsManager
@ -99,13 +99,13 @@ extension CallUIAdaptee {
// e.g. you can't receive calls in the call screen.
// So we use the non-CallKit call UI.
Logger.info("choosing non-callkit adaptee for simulator.")
adaptee = NonCallKitCallUIAdaptee(callService: callService, notificationsAdapter: notificationsAdapter)
adaptee = NonCallKitCallUIAdaptee(callService: callService, notificationPresenter: notificationPresenter)
} else if #available(iOS 11, *) {
Logger.info("choosing callkit adaptee for iOS11+")
let showNames = Environment.shared.preferences.notificationPreviewType() != .noNameNoPreview
let useSystemCallLog = Environment.shared.preferences.isSystemCallLogEnabled()
adaptee = CallKitCallUIAdaptee(callService: callService, contactsManager: contactsManager, notificationsAdapter: notificationsAdapter, showNamesOnCallScreen: showNames, useSystemCallLog: useSystemCallLog)
adaptee = CallKitCallUIAdaptee(callService: callService, contactsManager: contactsManager, notificationPresenter: notificationPresenter, showNamesOnCallScreen: showNames, useSystemCallLog: useSystemCallLog)
} else if #available(iOS 10.0, *), Environment.shared.preferences.isCallKitEnabled() {
Logger.info("choosing callkit adaptee for iOS10")
let hideNames = Environment.shared.preferences.isCallKitPrivacyEnabled() || Environment.shared.preferences.notificationPreviewType() == .noNameNoPreview
@ -114,10 +114,10 @@ extension CallUIAdaptee {
// All CallKit calls use the system call log on iOS10
let useSystemCallLog = true
adaptee = CallKitCallUIAdaptee(callService: callService, contactsManager: contactsManager, notificationsAdapter: notificationsAdapter, showNamesOnCallScreen: showNames, useSystemCallLog: useSystemCallLog)
adaptee = CallKitCallUIAdaptee(callService: callService, contactsManager: contactsManager, notificationPresenter: notificationPresenter, showNamesOnCallScreen: showNames, useSystemCallLog: useSystemCallLog)
} else {
Logger.info("choosing non-callkit adaptee")
adaptee = NonCallKitCallUIAdaptee(callService: callService, notificationsAdapter: notificationsAdapter)
adaptee = NonCallKitCallUIAdaptee(callService: callService, notificationPresenter: notificationPresenter)
}
audioService = CallAudioService(handleRinging: adaptee.hasManualRinger)

View File

@ -1,26 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@class OWSRecipientIdentity;
@class SignalCall;
@protocol OWSCallNotificationsAdaptee <NSObject>
- (void)presentIncomingCall:(SignalCall *)call callerName:(NSString *)callerName;
- (void)presentMissedCall:(SignalCall *)call callerName:(NSString *)callerName;
- (void)presentMissedCallBecauseOfNewIdentity:(SignalCall *)call
callerName:(NSString *)callerName
NS_SWIFT_NAME(presentMissedCallBecauseOfNewIdentity(call:callerName:));
- (void)presentMissedCallBecauseOfNoLongerVerifiedIdentity:(SignalCall *)call
callerName:(NSString *)callerName
NS_SWIFT_NAME(presentMissedCallBecauseOfNoLongerVerifiedIdentity(call:callerName:));
@end
NS_ASSUME_NONNULL_END

View File

@ -41,14 +41,11 @@ import SignalMessaging
public var accountManager: AccountManager
@objc
public var notificationsAdapter: NotificationsAdapter
public var notificationPresenter: NotificationPresenter
@objc
public var pushRegistrationManager: PushRegistrationManager
@objc
public var pushManager: PushManager
@objc
public var sessionResetJobQueue: SessionResetJobQueue
@ -64,9 +61,8 @@ import SignalMessaging
self.outboundCallInitiator = OutboundCallInitiator()
self.messageFetcherJob = MessageFetcherJob()
self.accountManager = AccountManager()
self.notificationsAdapter = NotificationsAdapter()
self.notificationPresenter = NotificationPresenter()
self.pushRegistrationManager = PushRegistrationManager()
self.pushManager = PushManager()
self.sessionResetJobQueue = SessionResetJobQueue()
self.backup = OWSBackup()
self.backupLazyRestore = BackupLazyRestore()
@ -81,7 +77,7 @@ import SignalMessaging
callService.createCallUIAdapter()
// Hang certain singletons on SSKEnvironment too.
SSKEnvironment.shared.notificationsManager = notificationsAdapter
SSKEnvironment.shared.notificationsManager = notificationPresenter
SSKEnvironment.shared.callMessageHandler = callMessageHandler
}
}

View File

@ -1,26 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSCallNotificationsAdaptee.h"
#import <SignalServiceKit/NotificationsProtocol.h>
NS_ASSUME_NONNULL_BEGIN
@class OWSContactsManager;
@class OWSPreferences;
@class SignalCall;
@class TSCall;
@class TSContactThread;
@interface NotificationsManager : NSObject <NotificationsProtocol, OWSCallNotificationsAdaptee>
#ifdef DEBUG
+ (void)presentDebugNotification;
#endif
@end
NS_ASSUME_NONNULL_END

View File

@ -1,505 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "NotificationsManager.h"
#import "PushManager.h"
#import "Signal-Swift.h"
#import <AudioToolbox/AudioServices.h>
#import <SignalCoreKit/Threading.h>
#import <SignalMessaging/Environment.h>
#import <SignalMessaging/OWSContactsManager.h>
#import <SignalMessaging/OWSPreferences.h>
#import <SignalMessaging/OWSSounds.h>
#import <SignalServiceKit/NSString+SSK.h>
#import <SignalServiceKit/SSKEnvironment.h>
#import <SignalServiceKit/TSCall.h>
#import <SignalServiceKit/TSContactThread.h>
#import <SignalServiceKit/TSErrorMessage.h>
#import <SignalServiceKit/TSIncomingMessage.h>
#import <YapDatabase/YapDatabaseTransaction.h>
@interface NotificationsManager ()
@property (nonatomic, readonly) NSMutableDictionary<NSString *, UILocalNotification *> *currentNotifications;
@property (nonatomic, readonly) NotificationType notificationPreviewType;
@property (nonatomic, readonly) NSMutableArray<NSDate *> *notificationHistory;
@property (nonatomic, nullable) OWSAudioPlayer *audioPlayer;
@end
#pragma mark -
@implementation NotificationsManager
- (instancetype)init
{
self = [super init];
if (!self) {
return self;
}
_currentNotifications = [NSMutableDictionary new];
_notificationHistory = [NSMutableArray new];
OWSSingletonAssert();
return self;
}
#pragma mark - Signal Calls
/**
* Notify user for incoming WebRTC Call
*/
- (void)presentIncomingCall:(SignalCall *)call callerName:(NSString *)callerName
{
OWSLogDebug(@"incoming call from: %@", call.remotePhoneNumber);
UILocalNotification *notification = [UILocalNotification new];
notification.category = PushManagerCategoriesIncomingCall;
// Rather than using notification sounds, we control the ringtone and repeat vibrations with the CallAudioManager.
notification.soundName = [OWSSounds filenameForSound:OWSSound_DefaultiOSIncomingRingtone];
NSString *localCallId = call.localId.UUIDString;
notification.userInfo = @{ PushManagerUserInfoKeysLocalCallId : localCallId };
NSString *alertMessage;
switch (self.notificationPreviewType) {
case NotificationNoNameNoPreview: {
alertMessage = NSLocalizedString(@"INCOMING_CALL", @"notification body");
break;
}
case NotificationNameNoPreview:
case NotificationNamePreview: {
alertMessage =
[NSString stringWithFormat:NSLocalizedString(@"INCOMING_CALL_FROM", @"notification body"), callerName];
break;
}
}
notification.alertBody = [NSString stringWithFormat:@"☎️ %@", alertMessage];
[self presentNotification:notification identifier:localCallId];
}
/**
* Notify user for missed WebRTC Call
*/
- (void)presentMissedCall:(SignalCall *)call callerName:(NSString *)callerName
{
TSContactThread *thread = [TSContactThread getOrCreateThreadWithContactId:call.remotePhoneNumber];
OWSAssertDebug(thread != nil);
UILocalNotification *notification = [UILocalNotification new];
notification.category = PushManagerCategoriesMissedCall;
NSString *localCallId = call.localId.UUIDString;
notification.userInfo = @{
PushManagerUserInfoKeysLocalCallId : localCallId,
PushManagerUserInfoKeysCallBackSignalRecipientId : call.remotePhoneNumber,
Signal_Thread_UserInfo_Key : thread.uniqueId
};
if ([self shouldPlaySoundForNotification]) {
OWSSound sound = [OWSSounds notificationSoundForThread:thread];
notification.soundName = [OWSSounds filenameForSound:sound];
}
NSString *alertMessage;
switch (self.notificationPreviewType) {
case NotificationNoNameNoPreview: {
alertMessage = [CallStrings missedCallNotificationBodyWithoutCallerName];
break;
}
case NotificationNameNoPreview:
case NotificationNamePreview: {
alertMessage =
[NSString stringWithFormat:[CallStrings missedCallNotificationBodyWithCallerName], callerName];
break;
}
}
notification.alertBody = [NSString stringWithFormat:@"☎️ %@", alertMessage];
[self presentNotification:notification identifier:localCallId];
}
- (void)presentMissedCallBecauseOfNewIdentity:(SignalCall *)call callerName:(NSString *)callerName
{
TSContactThread *thread = [TSContactThread getOrCreateThreadWithContactId:call.remotePhoneNumber];
OWSAssertDebug(thread != nil);
UILocalNotification *notification = [UILocalNotification new];
// Use category which allows call back
notification.category = PushManagerCategoriesMissedCall;
NSString *localCallId = call.localId.UUIDString;
notification.userInfo = @{
PushManagerUserInfoKeysLocalCallId : localCallId,
PushManagerUserInfoKeysCallBackSignalRecipientId : call.remotePhoneNumber,
Signal_Thread_UserInfo_Key : thread.uniqueId
};
if ([self shouldPlaySoundForNotification]) {
OWSSound sound = [OWSSounds notificationSoundForThread:thread];
notification.soundName = [OWSSounds filenameForSound:sound];
}
NSString *alertMessage;
switch (self.notificationPreviewType) {
case NotificationNoNameNoPreview: {
alertMessage = [CallStrings missedCallWithIdentityChangeNotificationBodyWithoutCallerName];
break;
}
case NotificationNameNoPreview:
case NotificationNamePreview: {
alertMessage = [NSString
stringWithFormat:[CallStrings missedCallWithIdentityChangeNotificationBodyWithCallerName], callerName];
break;
}
}
notification.alertBody = [NSString stringWithFormat:@"☎️ %@", alertMessage];
[self presentNotification:notification identifier:localCallId];
}
- (void)presentMissedCallBecauseOfNoLongerVerifiedIdentity:(SignalCall *)call callerName:(NSString *)callerName
{
TSContactThread *thread = [TSContactThread getOrCreateThreadWithContactId:call.remotePhoneNumber];
OWSAssertDebug(thread != nil);
UILocalNotification *notification = [UILocalNotification new];
// Use category which does not allow call back
notification.category = PushManagerCategoriesMissedCallFromNoLongerVerifiedIdentity;
NSString *localCallId = call.localId.UUIDString;
notification.userInfo = @{
PushManagerUserInfoKeysLocalCallId : localCallId,
PushManagerUserInfoKeysCallBackSignalRecipientId : call.remotePhoneNumber,
Signal_Thread_UserInfo_Key : thread.uniqueId
};
if ([self shouldPlaySoundForNotification]) {
OWSSound sound = [OWSSounds notificationSoundForThread:thread];
notification.soundName = [OWSSounds filenameForSound:sound];
}
NSString *alertMessage;
switch (self.notificationPreviewType) {
case NotificationNoNameNoPreview: {
alertMessage = [CallStrings missedCallWithIdentityChangeNotificationBodyWithoutCallerName];
break;
}
case NotificationNameNoPreview:
case NotificationNamePreview: {
alertMessage = [NSString
stringWithFormat:[CallStrings missedCallWithIdentityChangeNotificationBodyWithCallerName], callerName];
break;
}
}
notification.alertBody = [NSString stringWithFormat:@"☎️ %@", alertMessage];
[self presentNotification:notification identifier:localCallId];
}
#pragma mark - Signal Messages
- (void)notifyUserForErrorMessage:(TSErrorMessage *)message
thread:(TSThread *)thread
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssertDebug(message);
if (!thread) {
OWSFailDebug(@"unexpected notification not associated with a thread: %@.", [message class]);
[self notifyUserForThreadlessErrorMessage:message transaction:transaction];
return;
}
NSString *messageText = [message previewTextWithTransaction:transaction];
[transaction
addCompletionQueue:nil
completionBlock:^() {
if (thread.isMuted) {
return;
}
BOOL shouldPlaySound = [self shouldPlaySoundForNotification];
if (([UIApplication sharedApplication].applicationState != UIApplicationStateActive) && messageText) {
UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.userInfo = @{ Signal_Thread_UserInfo_Key : thread.uniqueId };
if (shouldPlaySound) {
OWSSound sound = [OWSSounds notificationSoundForThread:thread];
notification.soundName = [OWSSounds filenameForSound:sound];
}
NSString *alertBodyString = @"";
NSString *authorName = [thread name];
switch (self.notificationPreviewType) {
case NotificationNamePreview:
case NotificationNameNoPreview:
alertBodyString = [NSString stringWithFormat:@"%@: %@", authorName, messageText];
break;
case NotificationNoNameNoPreview:
alertBodyString = messageText;
break;
}
notification.alertBody = alertBodyString;
[[PushManager sharedManager] presentNotification:notification checkForCancel:NO];
} else {
if (shouldPlaySound && [Environment.shared.preferences soundInForeground]) {
OWSSound sound = [OWSSounds notificationSoundForThread:thread];
SystemSoundID soundId = [OWSSounds systemSoundIDForSound:sound quiet:YES];
// Vibrate, respect silent switch, respect "Alert" volume, not media volume.
AudioServicesPlayAlertSound(soundId);
}
}
}];
}
- (void)notifyUserForThreadlessErrorMessage:(TSErrorMessage *)message
transaction:(YapDatabaseReadWriteTransaction *)transaction;
{
OWSAssertDebug(message);
NSString *messageText = [message previewTextWithTransaction:transaction];
[transaction
addCompletionQueue:nil
completionBlock:^() {
BOOL shouldPlaySound = [self shouldPlaySoundForNotification];
if (([UIApplication sharedApplication].applicationState != UIApplicationStateActive) && messageText) {
UILocalNotification *notification = [[UILocalNotification alloc] init];
if (shouldPlaySound) {
OWSSound sound = [OWSSounds globalNotificationSound];
notification.soundName = [OWSSounds filenameForSound:sound];
}
NSString *alertBodyString = messageText;
notification.alertBody = alertBodyString;
[[PushManager sharedManager] presentNotification:notification checkForCancel:NO];
} else {
if (shouldPlaySound && [Environment.shared.preferences soundInForeground]) {
OWSSound sound = [OWSSounds globalNotificationSound];
SystemSoundID soundId = [OWSSounds systemSoundIDForSound:sound quiet:YES];
// Vibrate, respect silent switch, respect "Alert" volume, not media volume.
AudioServicesPlayAlertSound(soundId);
}
}
}];
}
- (void)notifyUserForIncomingMessage:(TSIncomingMessage *)message
inThread:(TSThread *)thread
contactsManager:(id<ContactsManagerProtocol>)contactsManager
transaction:(YapDatabaseReadTransaction *)transaction
{
OWSAssertDebug(message);
OWSAssertDebug(thread);
OWSAssertDebug(contactsManager);
// While batch processing, some of the necessary changes have not been commited.
NSString *rawMessageText = [message previewTextWithTransaction:transaction];
// iOS strips anything that looks like a printf formatting character from
// the notification body, so if we want to dispay a literal "%" in a notification
// it must be escaped.
// see https://developer.apple.com/documentation/uikit/uilocalnotification/1616646-alertbody
// for more details.
NSString *messageText = [DisplayableText filterNotificationText:rawMessageText];
dispatch_async(dispatch_get_main_queue(), ^{
if (thread.isMuted) {
return;
}
BOOL shouldPlaySound = [self shouldPlaySoundForNotification];
NSString *senderName = [contactsManager displayNameForPhoneIdentifier:message.authorId];
NSString *groupName = [thread.name ows_stripped];
if (groupName.length < 1) {
groupName = [MessageStrings newGroupDefaultTitle];
}
if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive && messageText) {
UILocalNotification *notification = [[UILocalNotification alloc] init];
if (shouldPlaySound) {
OWSSound sound = [OWSSounds notificationSoundForThread:thread];
notification.soundName = [OWSSounds filenameForSound:sound];
}
switch (self.notificationPreviewType) {
case NotificationNamePreview: {
// Don't reply from lockscreen if anyone in this conversation is
// "no longer verified".
BOOL isNoLongerVerified = NO;
for (NSString *recipientId in thread.recipientIdentifiers) {
if ([OWSIdentityManager.sharedManager verificationStateForRecipientId:recipientId]
== OWSVerificationStateNoLongerVerified) {
isNoLongerVerified = YES;
break;
}
}
notification.category = (isNoLongerVerified ? Signal_Full_New_Message_Category_No_Longer_Verified
: Signal_Full_New_Message_Category);
notification.userInfo = @{
Signal_Thread_UserInfo_Key : thread.uniqueId,
Signal_Message_UserInfo_Key : message.uniqueId
};
if ([thread isGroupThread]) {
NSString *threadName = [NSString stringWithFormat:@"\"%@\"", groupName];
// TODO: Format parameters might change order in l10n. We should use named parameters.
notification.alertBody =
[NSString stringWithFormat:NSLocalizedString(@"APN_MESSAGE_IN_GROUP_DETAILED", nil),
senderName,
threadName,
messageText];
} else {
notification.alertBody = [NSString stringWithFormat:@"%@: %@", senderName, messageText];
}
break;
}
case NotificationNameNoPreview: {
notification.userInfo = @{ Signal_Thread_UserInfo_Key : thread.uniqueId };
if ([thread isGroupThread]) {
notification.alertBody = [NSString
stringWithFormat:@"%@ \"%@\"", NSLocalizedString(@"APN_MESSAGE_IN_GROUP", nil), groupName];
} else {
notification.alertBody = [NSString
stringWithFormat:@"%@ %@", NSLocalizedString(@"APN_MESSAGE_FROM", nil), senderName];
}
break;
}
case NotificationNoNameNoPreview:
notification.alertBody = NSLocalizedString(@"APN_Message", nil);
break;
default:
OWSLogWarn(@"unknown notification preview type: %lu", (unsigned long)self.notificationPreviewType);
notification.alertBody = NSLocalizedString(@"APN_Message", nil);
break;
}
[[PushManager sharedManager] presentNotification:notification checkForCancel:YES];
} else {
if (shouldPlaySound && [Environment.shared.preferences soundInForeground]) {
OWSSound sound = [OWSSounds notificationSoundForThread:thread];
SystemSoundID soundId = [OWSSounds systemSoundIDForSound:sound quiet:YES];
// Vibrate, respect silent switch, respect "Alert" volume, not media volume.
AudioServicesPlayAlertSound(soundId);
}
}
});
}
- (BOOL)shouldPlaySoundForNotification
{
@synchronized(self)
{
// Play no more than 2 notification sounds in a given
// five-second window.
const CGFloat kNotificationWindowSeconds = 5.f;
const NSUInteger kMaxNotificationRate = 2;
// Cull obsolete notification timestamps from the thread's notification history.
while (self.notificationHistory.count > 0) {
NSDate *notificationTimestamp = self.notificationHistory[0];
CGFloat notificationAgeSeconds = fabs(notificationTimestamp.timeIntervalSinceNow);
if (notificationAgeSeconds > kNotificationWindowSeconds) {
[self.notificationHistory removeObjectAtIndex:0];
} else {
break;
}
}
// Ignore notifications if necessary.
BOOL shouldPlaySound = self.notificationHistory.count < kMaxNotificationRate;
if (shouldPlaySound) {
// Add new notification timestamp to the thread's notification history.
NSDate *newNotificationTimestamp = [NSDate new];
[self.notificationHistory addObject:newNotificationTimestamp];
return YES;
} else {
OWSLogDebug(@"Skipping sound for notification");
return NO;
}
}
}
#pragma mark - Util
- (NotificationType)notificationPreviewType
{
OWSPreferences *prefs = Environment.shared.preferences;
return prefs.notificationPreviewType;
}
- (void)presentNotification:(UILocalNotification *)notification identifier:(NSString *)identifier
{
notification.alertBody = notification.alertBody.filterStringForDisplay;
DispatchMainThreadSafe(^{
if (UIApplication.sharedApplication.applicationState == UIApplicationStateActive) {
OWSLogWarn(@"skipping notification; app is in foreground and active.");
return;
}
// Replace any existing notification
// e.g. when an "Incoming Call" notification gets replaced with a "Missed Call" notification.
if (self.currentNotifications[identifier]) {
[self cancelNotificationWithIdentifier:identifier];
}
[[UIApplication sharedApplication] scheduleLocalNotification:notification];
OWSLogDebug(@"presenting notification with identifier: %@", identifier);
self.currentNotifications[identifier] = notification;
});
}
- (void)cancelNotificationWithIdentifier:(NSString *)identifier
{
DispatchMainThreadSafe(^{
UILocalNotification *notification = self.currentNotifications[identifier];
if (!notification) {
OWSLogWarn(@"Couldn't cancel notification because none was found with identifier: %@", identifier);
return;
}
[self.currentNotifications removeObjectForKey:identifier];
[[UIApplication sharedApplication] cancelLocalNotification:notification];
});
}
#ifdef DEBUG
+ (void)presentDebugNotification
{
OWSAssertIsOnMainThread();
UILocalNotification *notification = [UILocalNotification new];
notification.category = Signal_Full_New_Message_Category;
notification.soundName = [OWSSounds filenameForSound:OWSSound_DefaultiOSIncomingRingtone];
notification.alertBody = @"test";
[[UIApplication sharedApplication] scheduleLocalNotification:notification];
}
#endif
- (void)clearAllNotifications
{
OWSAssertIsOnMainThread();
[self.currentNotifications removeAllObjects];
}
@end

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import Foundation
@ -21,8 +21,12 @@ public enum PushRegistrationError: Error {
// MARK: - Dependencies
private var pushManager: PushManager {
return PushManager.shared()
private var messageFetcherJob: MessageFetcherJob {
return AppEnvironment.shared.messageFetcherJob
}
private var notificationPresenter: NotificationPresenter {
return AppEnvironment.shared.notificationPresenter
}
// MARK: - Singleton class
@ -40,9 +44,6 @@ public enum PushRegistrationError: Error {
SwiftSingletons.register(self)
}
private var userNotificationSettingsPromise: Promise<Void>?
private var userNotificationSettingsResolver: Resolver<Void>?
private var vanillaTokenPromise: Promise<Data>?
private var vanillaTokenResolver: Resolver<Data>?
@ -70,21 +71,6 @@ public enum PushRegistrationError: Error {
}
}
// Notification registration is confirmed via AppDelegate
// Before this occurs, it is not safe to assume push token requests will be acknowledged.
//
// e.g. in the case that Background Fetch is disabled, token requests will be ignored until
// we register user notification settings.
@objc
public func didRegisterUserNotificationSettings() {
guard let userNotificationSettingsResolver = self.userNotificationSettingsResolver else {
owsFailDebug("promise completion in \(#function) unexpectedly nil")
return
}
userNotificationSettingsResolver.fulfill(())
}
// MARK: Vanilla push token
// Vanilla push token is obtained from the system via AppDelegate
@ -114,7 +100,9 @@ public enum PushRegistrationError: Error {
public func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) {
Logger.info("")
assert(type == .voIP)
self.pushManager.application(UIApplication.shared, didReceiveRemoteNotification: payload.dictionaryPayload)
AppReadiness.runNowOrWhenAppDidBecomeReady {
(self.messageFetcherJob.run() as Promise<Void>).retainUntilComplete()
}
}
public func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {
@ -138,26 +126,11 @@ public enum PushRegistrationError: Error {
// MARK: helpers
// User notification settings must be registered *before* AppDelegate will
// return any requested push tokens. We don't consider the notifications settings registration
// *complete* until AppDelegate#didRegisterUserNotificationSettings is called.
// return any requested push tokens.
private func registerUserNotificationSettings() -> Promise<Void> {
AssertIsOnMainThread()
guard self.userNotificationSettingsPromise == nil else {
let promise = self.userNotificationSettingsPromise!
Logger.info("already registered user notification settings")
return promise
}
let (promise, resolver) = Promise<Void>.pending()
self.userNotificationSettingsPromise = promise
self.userNotificationSettingsResolver = resolver
Logger.info("registering user notification settings")
UIApplication.shared.registerUserNotificationSettings(self.pushManager.userNotificationSettings)
return promise
return notificationPresenter.registerNotificationSettings()
}
/**

View File

@ -1,75 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@class UILocalNotification;
extern NSString *const Signal_Thread_UserInfo_Key;
extern NSString *const Signal_Message_UserInfo_Key;
extern NSString *const Signal_Full_New_Message_Category;
extern NSString *const Signal_Full_New_Message_Category_No_Longer_Verified;
extern NSString *const Signal_Message_Reply_Identifier;
extern NSString *const Signal_Message_MarkAsRead_Identifier;
#pragma mark Signal Calls constants
extern NSString *const PushManagerCategoriesIncomingCall;
extern NSString *const PushManagerCategoriesMissedCall;
extern NSString *const PushManagerCategoriesMissedCallFromNoLongerVerifiedIdentity;
extern NSString *const PushManagerActionsAcceptCall;
extern NSString *const PushManagerActionsDeclineCall;
extern NSString *const PushManagerActionsCallBack;
extern NSString *const PushManagerActionsShowThread;
extern NSString *const PushManagerUserInfoKeysCallBackSignalRecipientId;
extern NSString *const PushManagerUserInfoKeysLocalCallId;
typedef void (^failedPushRegistrationBlock)(NSError *error);
typedef void (^pushTokensSuccessBlock)(NSString *pushToken, NSString *voipToken);
/**
* The Push Manager is responsible for handling received push notifications.
*/
@interface PushManager : NSObject
@property (nonatomic) BOOL hasPresentedConversationSinceLastDeactivation;
+ (PushManager *)sharedManager;
/**
* Settings required for the notification categories we use.
*/
@property (nonatomic, readonly) UIUserNotificationSettings *userNotificationSettings;
// If checkForCancel is set, the notification will be delayed for
// a moment. If a relevant cancel notification is received in that window,
// the notification will not be displayed.
- (void)presentNotification:(UILocalNotification *)notification checkForCancel:(BOOL)checkForCancel;
- (void)cancelNotificationsWithThreadId:(NSString *)threadId;
#pragma mark Push Notifications Delegate Methods
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo;
- (void)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler;
- (void)application:(UIApplication *)application
handleActionWithIdentifier:(NSString *)identifier
forLocalNotification:(UILocalNotification *)notification
completionHandler:(void (^)(void))completionHandler;
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification;
- (void)application:(UIApplication *)application
handleActionWithIdentifier:(NSString *)identifier
forLocalNotification:(UILocalNotification *)notification
withResponseInfo:(NSDictionary *)responseInfo
completionHandler:(void (^)(void))completionHandler;
- (void)applicationDidBecomeActive;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,504 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "PushManager.h"
#import "AppDelegate.h"
#import "Signal-Swift.h"
#import "SignalApp.h"
#import "ThreadUtil.h"
#import <SignalCoreKit/NSDate+OWS.h>
#import <SignalMessaging/OWSContactsManager.h>
#import <SignalServiceKit/AppReadiness.h>
#import <SignalServiceKit/NSString+SSK.h>
#import <SignalServiceKit/OWSDevice.h>
#import <SignalServiceKit/OWSMessageSender.h>
#import <SignalServiceKit/OWSReadReceiptManager.h>
#import <SignalServiceKit/OWSSignalService.h>
#import <SignalServiceKit/TSAccountManager.h>
#import <SignalServiceKit/TSIncomingMessage.h>
#import <SignalServiceKit/TSOutgoingMessage.h>
#import <SignalServiceKit/TSSocketManager.h>
NSString *const Signal_Thread_UserInfo_Key = @"Signal_Thread_Id";
NSString *const Signal_Message_UserInfo_Key = @"Signal_Message_Id";
NSString *const Signal_Full_New_Message_Category = @"Signal_Full_New_Message";
NSString *const Signal_Full_New_Message_Category_No_Longer_Verified =
@"Signal_Full_New_Message_Category_No_Longer_Verified";
NSString *const Signal_Message_Reply_Identifier = @"Signal_New_Message_Reply";
NSString *const Signal_Message_MarkAsRead_Identifier = @"Signal_Message_MarkAsRead";
@interface PushManager ()
@property (nonatomic) NSMutableArray *currentNotifications;
@property (nonatomic) UIBackgroundTaskIdentifier callBackgroundTask;
@end
@implementation PushManager
+ (instancetype)sharedManager {
OWSAssertDebug(AppEnvironment.shared.pushManager);
return AppEnvironment.shared.pushManager;
}
- (instancetype)init {
self = [super init];
if (!self) {
return self;
}
_callBackgroundTask = UIBackgroundTaskInvalid;
// TODO: consolidate notification tracking with NotificationsManager, which also maintains a list of notifications.
_currentNotifications = [NSMutableArray array];
OWSSingletonAssert();
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleMessageRead:)
name:kIncomingMessageMarkedAsReadNotification
object:nil];
return self;
}
#pragma mark - Dependencies
- (OWSMessageSender *)messageSender {
return SSKEnvironment.shared.messageSender;
}
- (OWSMessageFetcherJob *)messageFetcherJob {
return AppEnvironment.shared.messageFetcherJob;
}
- (id<NotificationsProtocol>)notificationsManager {
return SSKEnvironment.shared.notificationsManager;
}
#pragma mark -
- (CallUIAdapter *)callUIAdapter
{
return AppEnvironment.shared.callService.callUIAdapter;
}
- (void)handleMessageRead:(NSNotification *)notification
{
OWSAssertIsOnMainThread();
if ([notification.object isKindOfClass:[TSIncomingMessage class]]) {
TSIncomingMessage *message = (TSIncomingMessage *)notification.object;
OWSLogDebug(@"canceled notification for message:%@", message);
[self cancelNotificationsWithThreadId:message.uniqueThreadId];
}
}
#pragma mark Manage Incoming Push
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
OWSLogInfo(@"received remote notification");
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
[self.messageFetcherJob run];
}];
}
- (void)applicationDidBecomeActive {
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
[self.messageFetcherJob run];
}];
}
/**
* This code should in principle never be called. The only cases where it would be called are with the old-style
* "content-available:1" pushes if there is no "voip" token registered
*
*/
- (void)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
OWSLogInfo(@"received content-available push");
// If we want to re-introduce silent pushes we can remove this assert.
OWSFailDebug(@"Unexpected content-available push.");
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 20 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
completionHandler(UIBackgroundFetchResultNewData);
});
}];
}
- (void)presentOncePerActivationConversationWithThreadId:(NSString *)threadId
{
if (self.hasPresentedConversationSinceLastDeactivation) {
OWSFailDebug(@"refusing to present conversation: %@ multiple times.", threadId);
return;
}
self.hasPresentedConversationSinceLastDeactivation = YES;
// This will happen before the app is visible. By making this animated:NO, the conversation screen
// will be visible to the user immediately upon opening the app, rather than having to watch it animate
// in from the homescreen.
[SignalApp.sharedApp presentConversationForThreadId:threadId animated:NO];
}
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
OWSAssertIsOnMainThread();
OWSLogInfo(@"launched from local notification");
NSString *_Nullable threadId = notification.userInfo[Signal_Thread_UserInfo_Key];
if (threadId) {
[self presentOncePerActivationConversationWithThreadId:threadId];
} else {
OWSFailDebug(@"threadId was unexpectedly nil");
}
// We only want to receive a single local notification per launch.
[application cancelAllLocalNotifications];
[self.currentNotifications removeAllObjects];
[self.notificationsManager clearAllNotifications];
}
- (void)application:(UIApplication *)application
handleActionWithIdentifier:(NSString *)identifier
forLocalNotification:(UILocalNotification *)notification
completionHandler:(void (^)(void))completionHandler
{
OWSLogInfo(@"in %s", __FUNCTION__);
[self application:application
handleActionWithIdentifier:identifier
forLocalNotification:notification
withResponseInfo:@{}
completionHandler:completionHandler];
}
- (void)application:(UIApplication *)application
handleActionWithIdentifier:(NSString *)identifier
forLocalNotification:(UILocalNotification *)notification
withResponseInfo:(NSDictionary *)responseInfo
completionHandler:(void (^)(void))completionHandler
{
OWSLogInfo(@"handling action with identifier: %@", identifier);
if ([identifier isEqualToString:Signal_Message_Reply_Identifier]) {
NSString *threadId = notification.userInfo[Signal_Thread_UserInfo_Key];
if (threadId) {
TSThread *thread = [TSThread fetchObjectWithUniqueID:threadId];
NSString *replyText = responseInfo[UIUserNotificationActionResponseTypedTextKey];
// In line with most apps, we send a normal outgoing messgae here - not a "quoted reply".
// We use a non-durable send to delay calling the completion handler until sending completes
// in hopes our send will complete before the app gets suspended.
[ThreadUtil sendMessageNonDurablyWithText:replyText
inThread:thread
quotedReplyModel:nil
messageSender:self.messageSender
success:^{
// TODO do we really want to mark them all as read?
[self markAllInThreadAsRead:notification.userInfo completionHandler:completionHandler];
}
failure:^(NSError *_Nonnull error) {
// TODO Surface the specific error in the notification?
OWSLogError(@"Message send failed with error: %@", error);
UILocalNotification *failedSendNotif = [[UILocalNotification alloc] init];
failedSendNotif.alertBody =
[NSString stringWithFormat:NSLocalizedString(@"NOTIFICATION_SEND_FAILED", nil), [thread name]]
.filterStringForDisplay;
failedSendNotif.userInfo = @{ Signal_Thread_UserInfo_Key : thread.uniqueId };
[self presentNotification:failedSendNotif checkForCancel:NO];
completionHandler();
}];
}
} else if ([identifier isEqualToString:Signal_Message_MarkAsRead_Identifier]) {
// TODO mark all as read? Or just this one?
[self markAllInThreadAsRead:notification.userInfo completionHandler:completionHandler];
} else if ([identifier isEqualToString:PushManagerActionsAcceptCall]) {
NSString *localIdString = notification.userInfo[PushManagerUserInfoKeysLocalCallId];
if (!localIdString) {
OWSLogError(@"missing localIdString.");
return;
}
NSUUID *localId = [[NSUUID alloc] initWithUUIDString:localIdString];
if (!localId) {
OWSLogError(@"localIdString failed to parse as UUID.");
return;
}
[self.callUIAdapter answerCallWithLocalId:localId];
completionHandler();
} else if ([identifier isEqualToString:PushManagerActionsDeclineCall]) {
NSString *localIdString = notification.userInfo[PushManagerUserInfoKeysLocalCallId];
if (!localIdString) {
OWSLogError(@"missing localIdString.");
return;
}
NSUUID *localId = [[NSUUID alloc] initWithUUIDString:localIdString];
if (!localId) {
OWSLogError(@"localIdString failed to parse as UUID.");
return;
}
[self.callUIAdapter declineCallWithLocalId:localId];
completionHandler();
} else if ([identifier isEqualToString:PushManagerActionsCallBack]) {
NSString *recipientId = notification.userInfo[PushManagerUserInfoKeysCallBackSignalRecipientId];
if (!recipientId) {
OWSLogError(@"missing call back id");
return;
}
[self.callUIAdapter startAndShowOutgoingCallWithRecipientId:recipientId hasLocalVideo:NO];
completionHandler();
} else if ([identifier isEqualToString:PushManagerActionsShowThread]) {
NSString *threadId = notification.userInfo[Signal_Thread_UserInfo_Key];
if (threadId) {
[self presentOncePerActivationConversationWithThreadId:threadId];
} else {
OWSFailDebug(@"threadId was unexpectedly nil in action with identifier: %@", identifier);
}
completionHandler();
} else {
OWSFailDebug(@"Unhandled action with identifier: %@", identifier);
NSString *threadId = notification.userInfo[Signal_Thread_UserInfo_Key];
if (threadId) {
[self presentOncePerActivationConversationWithThreadId:threadId];
} else {
OWSFailDebug(@"threadId was unexpectedly nil in action with identifier: %@", identifier);
}
completionHandler();
}
}
- (void)markAllInThreadAsRead:(NSDictionary *)userInfo completionHandler:(void (^)(void))completionHandler
{
NSString *threadId = userInfo[Signal_Thread_UserInfo_Key];
if (!threadId) {
OWSFailDebug(@"missing thread id for notification.");
return;
}
TSThread *thread = [TSThread fetchObjectWithUniqueID:threadId];
[OWSPrimaryStorage.dbReadWriteConnection
asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
// TODO: I suspect we only want to mark the message in
// question as read.
[thread markAllAsReadWithTransaction:transaction];
}
completionBlock:^{
[self cancelNotificationsWithThreadId:threadId];
completionHandler();
}];
}
- (UIUserNotificationCategory *)fullNewMessageNotificationCategory {
UIMutableUserNotificationAction *action_markRead = [self markAsReadAction];
UIMutableUserNotificationAction *action_reply = [UIMutableUserNotificationAction new];
action_reply.identifier = Signal_Message_Reply_Identifier;
action_reply.title = NSLocalizedString(@"PUSH_MANAGER_REPLY", @"");
action_reply.destructive = NO;
action_reply.authenticationRequired = NO;
action_reply.behavior = UIUserNotificationActionBehaviorTextInput;
action_reply.activationMode = UIUserNotificationActivationModeBackground;
UIMutableUserNotificationCategory *messageCategory = [UIMutableUserNotificationCategory new];
messageCategory.identifier = Signal_Full_New_Message_Category;
[messageCategory setActions:@[ action_markRead, action_reply ] forContext:UIUserNotificationActionContextMinimal];
[messageCategory setActions:@[ action_markRead, action_reply ] forContext:UIUserNotificationActionContextDefault];
return messageCategory;
}
- (UIUserNotificationCategory *)fullNewMessageNoLongerVerifiedNotificationCategory
{
UIMutableUserNotificationAction *action_markRead = [self markAsReadAction];
UIMutableUserNotificationCategory *messageCategory = [UIMutableUserNotificationCategory new];
messageCategory.identifier = Signal_Full_New_Message_Category_No_Longer_Verified;
[messageCategory setActions:@[ action_markRead ] forContext:UIUserNotificationActionContextMinimal];
[messageCategory setActions:@[ action_markRead ] forContext:UIUserNotificationActionContextDefault];
return messageCategory;
}
- (UIMutableUserNotificationAction *)markAsReadAction
{
UIMutableUserNotificationAction *action = [UIMutableUserNotificationAction new];
action.identifier = Signal_Message_MarkAsRead_Identifier;
action.title = NSLocalizedString(@"PUSH_MANAGER_MARKREAD", nil);
action.destructive = NO;
action.authenticationRequired = NO;
action.activationMode = UIUserNotificationActivationModeBackground;
return action;
}
#pragma mark - Signal Calls
NSString *const PushManagerCategoriesIncomingCall = @"PushManagerCategoriesIncomingCall";
NSString *const PushManagerCategoriesMissedCall = @"PushManagerCategoriesMissedCall";
NSString *const PushManagerCategoriesMissedCallFromNoLongerVerifiedIdentity =
@"PushManagerCategoriesMissedCallFromNoLongerVerifiedIdentity";
NSString *const PushManagerActionsAcceptCall = @"PushManagerActionsAcceptCall";
NSString *const PushManagerActionsDeclineCall = @"PushManagerActionsDeclineCall";
NSString *const PushManagerActionsCallBack = @"PushManagerActionsCallBack";
NSString *const PushManagerActionsIgnoreIdentityChangeAndCallBack =
@"PushManagerActionsIgnoreIdentityChangeAndCallBack";
NSString *const PushManagerActionsShowThread = @"PushManagerActionsShowThread";
NSString *const PushManagerUserInfoKeysLocalCallId = @"PushManagerUserInfoKeysLocalCallId";
NSString *const PushManagerUserInfoKeysCallBackSignalRecipientId = @"PushManagerUserInfoKeysCallBackSignalRecipientId";
- (UIUserNotificationCategory *)signalIncomingCallCategory
{
UIMutableUserNotificationAction *acceptAction = [UIMutableUserNotificationAction new];
acceptAction.identifier = PushManagerActionsAcceptCall;
acceptAction.title = NSLocalizedString(@"ANSWER_CALL_BUTTON_TITLE", @"");
acceptAction.activationMode = UIUserNotificationActivationModeForeground;
acceptAction.destructive = NO;
acceptAction.authenticationRequired = NO;
UIMutableUserNotificationAction *declineAction = [UIMutableUserNotificationAction new];
declineAction.identifier = PushManagerActionsDeclineCall;
declineAction.title = NSLocalizedString(@"REJECT_CALL_BUTTON_TITLE", @"");
declineAction.activationMode = UIUserNotificationActivationModeBackground;
declineAction.destructive = NO;
declineAction.authenticationRequired = NO;
UIMutableUserNotificationCategory *callCategory = [UIMutableUserNotificationCategory new];
callCategory.identifier = PushManagerCategoriesIncomingCall;
[callCategory setActions:@[ acceptAction, declineAction ] forContext:UIUserNotificationActionContextMinimal];
[callCategory setActions:@[ acceptAction, declineAction ] forContext:UIUserNotificationActionContextDefault];
return callCategory;
}
- (UIUserNotificationCategory *)signalMissedCallCategory
{
UIMutableUserNotificationAction *callBackAction = [UIMutableUserNotificationAction new];
callBackAction.identifier = PushManagerActionsCallBack;
callBackAction.title = [CallStrings callBackButtonTitle];
callBackAction.activationMode = UIUserNotificationActivationModeForeground;
callBackAction.destructive = NO;
callBackAction.authenticationRequired = YES;
UIMutableUserNotificationCategory *missedCallCategory = [UIMutableUserNotificationCategory new];
missedCallCategory.identifier = PushManagerCategoriesMissedCall;
[missedCallCategory setActions:@[ callBackAction ] forContext:UIUserNotificationActionContextMinimal];
[missedCallCategory setActions:@[ callBackAction ] forContext:UIUserNotificationActionContextDefault];
return missedCallCategory;
}
- (UIUserNotificationCategory *)signalMissedCallWithNoLongerVerifiedIdentityChangeCategory
{
UIMutableUserNotificationAction *showThreadAction = [UIMutableUserNotificationAction new];
showThreadAction.identifier = PushManagerActionsShowThread;
showThreadAction.title = [CallStrings showThreadButtonTitle];
showThreadAction.activationMode = UIUserNotificationActivationModeForeground;
showThreadAction.destructive = NO;
showThreadAction.authenticationRequired = YES;
UIMutableUserNotificationCategory *rejectedCallCategory = [UIMutableUserNotificationCategory new];
rejectedCallCategory.identifier = PushManagerCategoriesMissedCallFromNoLongerVerifiedIdentity;
[rejectedCallCategory setActions:@[ showThreadAction ] forContext:UIUserNotificationActionContextMinimal];
[rejectedCallCategory setActions:@[ showThreadAction ] forContext:UIUserNotificationActionContextDefault];
return rejectedCallCategory;
}
#pragma mark Util
- (int)allNotificationTypes {
return UIUserNotificationTypeAlert | UIUserNotificationTypeSound | UIUserNotificationTypeBadge;
}
- (UIUserNotificationSettings *)userNotificationSettings
{
OWSLogDebug(@"registering user notification settings");
UIUserNotificationSettings *settings = [UIUserNotificationSettings
settingsForTypes:(UIUserNotificationType)[self allNotificationTypes]
categories:[NSSet setWithObjects:[self fullNewMessageNotificationCategory],
[self fullNewMessageNoLongerVerifiedNotificationCategory],
[self signalIncomingCallCategory],
[self signalMissedCallCategory],
[self signalMissedCallWithNoLongerVerifiedIdentityChangeCategory],
nil]];
return settings;
}
- (BOOL)applicationIsActive {
UIApplication *app = [UIApplication sharedApplication];
if (app.applicationState == UIApplicationStateActive) {
return YES;
}
return NO;
}
// TODO: consolidate notification tracking with NotificationsManager, which also maintains a list of notifications.
- (void)presentNotification:(UILocalNotification *)notification checkForCancel:(BOOL)checkForCancel
{
notification.alertBody = notification.alertBody.filterStringForDisplay;
dispatch_async(dispatch_get_main_queue(), ^{
NSString *threadId = notification.userInfo[Signal_Thread_UserInfo_Key];
if (checkForCancel && threadId != nil) {
if ([[OWSDeviceManager sharedManager] hasReceivedSyncMessageInLastSeconds:60.f]) {
// "If youve heard from desktop in last minute, wait 5 seconds."
//
// This provides a window in which we can cancel notifications
// already viewed on desktop before they are presented here.
const CGFloat kDelaySeconds = 5.f;
notification.fireDate = [NSDate dateWithTimeIntervalSinceNow:kDelaySeconds];
} else {
notification.fireDate = [NSDate new];
}
notification.timeZone = [NSTimeZone localTimeZone];
}
[[UIApplication sharedApplication] scheduleLocalNotification:notification];
[self.currentNotifications addObject:notification];
});
}
// TODO: consolidate notification tracking with NotificationsManager, which also maintains a list of notifications.
- (void)cancelNotificationsWithThreadId:(NSString *)threadId
{
dispatch_async(dispatch_get_main_queue(), ^{
NSMutableArray *toDelete = [NSMutableArray array];
[self.currentNotifications
enumerateObjectsUsingBlock:^(UILocalNotification *notif, NSUInteger idx, BOOL *stop) {
if ([notif.userInfo[Signal_Thread_UserInfo_Key] isEqualToString:threadId]) {
[[UIApplication sharedApplication] cancelLocalNotification:notif];
[toDelete addObject:notif];
}
}];
[self.currentNotifications removeObjectsInArray:toDelete];
});
}
@end

View File

@ -1,33 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "SignalBaseTest.h"
@interface PushManagerTest : SignalBaseTest
@end
@implementation PushManagerTest
- (void)setUp {
[super setUp];
// Put setup code here. This method is called before the invocation of each test method in the class.
}
- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
[super tearDown];
}
/**
* This test verifies that the enum containing the notifications types doesn't change for iOS7 support.
*/
- (void)testNotificationTypesForiOS7 {
XCTAssert(UIRemoteNotificationTypeAlert == UIUserNotificationTypeAlert, @"iOS 7 <-> 8 compatibility");
XCTAssert(UIRemoteNotificationTypeSound == UIUserNotificationTypeSound, @"iOS 7 <-> 8 compatibility");
XCTAssert(UIRemoteNotificationTypeBadge == UIUserNotificationTypeBadge, @"iOS 7 <-> 8 compatibility");
}
@end

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import Foundation
@ -26,6 +26,15 @@ import Foundation
@objc public class MessageStrings: NSObject {
@objc
static public let newGroupDefaultTitle = NSLocalizedString("NEW_GROUP_DEFAULT_TITLE", comment: "Used in place of the group name when a group has not yet been named.")
@objc
static public let replyNotificationAction = NSLocalizedString("PUSH_MANAGER_REPLY", comment: "Notification action button title")
@objc
static public let markAsReadNotificationAction = NSLocalizedString("PUSH_MANAGER_MARKREAD", comment: "Notification action button title")
@objc
static public let sendButton = NSLocalizedString("SEND_BUTTON_TITLE", comment: "Label for the button to send a message")
}
@objc public class CallStrings: NSObject {
@ -47,6 +56,10 @@ import Foundation
static public let callBackButtonTitle = NSLocalizedString("CALLBACK_BUTTON_TITLE", comment: "notification action")
@objc
static public let showThreadButtonTitle = NSLocalizedString("SHOW_THREAD_BUTTON_TITLE", comment: "notification action")
@objc
static public let answerCallButtonTitle = NSLocalizedString("ANSWER_CALL_BUTTON_TITLE", comment: "notification action")
@objc
static public let declineCallButtonTitle = NSLocalizedString("REJECT_CALL_BUTTON_TITLE", comment: "")
// MARK: Missed Call Notification
@objc
@ -59,6 +72,12 @@ import Foundation
static public let missedCallWithIdentityChangeNotificationBodyWithoutCallerName = NSLocalizedString("MISSED_CALL_WITH_CHANGED_IDENTITY_BODY_WITHOUT_CALLER_NAME", comment: "notification title")
@objc
static public let missedCallWithIdentityChangeNotificationBodyWithCallerName = NSLocalizedString("MISSED_CALL_WITH_CHANGED_IDENTITY_BODY_WITH_CALLER_NAME", comment: "notification title. Embeds {{caller's name or phone number}}")
@objc
static public let incomingCallWithoutCallerNameNotification = NSLocalizedString("INCOMING_CALL", comment: "notification body, does not include the callers name")
@objc
static public let incomingCallNotificationFormat = NSLocalizedString("INCOMING_CALL_FROM", comment: "notification body, embeds {{caller name or number}}")
}
@objc public class MediaStrings: NSObject {

View File

@ -1,10 +1,11 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
// Anything used by Swift outside of the framework must be imported.
#import <Reachability/Reachability.h>
#import <SignalServiceKit/ContactsManagerProtocol.h>
#import <SignalServiceKit/NotificationsProtocol.h>
#import <SignalServiceKit/OWSFileSystem.h>
#import <SignalServiceKit/OWSOperation.h>
#import <SignalServiceKit/OWSSyncManagerProtocol.h>

View File

@ -241,9 +241,12 @@ public class IncomingMessageFactory: NSObject, Factory {
@objc
public func create(transaction: YapDatabaseReadWriteTransaction) -> TSIncomingMessage {
let thread = threadCreator(transaction)
let item = TSIncomingMessage(incomingMessageWithTimestamp: timestampBuilder(),
in: threadCreator(transaction),
authorId: authorIdBuilder(),
in: thread,
authorId: authorIdBuilder(thread),
sourceDeviceId: sourceDeviceIdBuilder(),
messageBody: messageBodyBuilder(),
attachmentIds: attachmentIdsBuilder(),
@ -279,8 +282,16 @@ public class IncomingMessageFactory: NSObject, Factory {
}
@objc
public var authorIdBuilder: () -> String = {
return CommonGenerator.contactId
public var authorIdBuilder: (TSThread) -> String = { thread in
switch thread {
case let contactThread as TSContactThread:
return contactThread.contactIdentifier()
case let groupThread as TSGroupThread:
return groupThread.recipientIdentifiers.ows_randomElement() ?? CommonGenerator.contactId
default:
owsFailDebug("unexpected thread type")
return CommonGenerator.contactId
}
}
@objc
@ -344,7 +355,6 @@ class GroupThreadFactory: NSObject, Factory {
(0..<messageCount).forEach { _ in
if [true, false].ows_randomElement()! {
incomingMessageFactory.authorIdBuilder = { thread.recipientIdentifiers.ows_randomElement()! }
_ = incomingMessageFactory.create(transaction: transaction)
} else {
_ = outgoingMessageFactory.create(transaction: transaction)

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "MockSSKEnvironment.h"
@ -13,7 +13,6 @@
#import "OWSFakeContactsUpdater.h"
#import "OWSFakeMessageSender.h"
#import "OWSFakeNetworkManager.h"
#import "OWSFakeNotificationsManager.h"
#import "OWSFakeProfileManager.h"
#import "OWSIdentityManager.h"
#import "OWSMessageDecrypter.h"
@ -109,7 +108,7 @@ NS_ASSUME_NONNULL_BEGIN
}
self.callMessageHandler = [OWSFakeCallMessageHandler new];
self.notificationsManager = [OWSFakeNotificationsManager new];
self.notificationsManager = [NoopNotificationsManager new];
return self;
}

View File

@ -1,9 +1,7 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import SignalServiceKit
@objc
public class NoopNotificationsManager: NSObject, NotificationsProtocol {

View File

@ -1,17 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "NotificationsProtocol.h"
NS_ASSUME_NONNULL_BEGIN
#ifdef DEBUG
@interface OWSFakeNotificationsManager : NSObject <NotificationsProtocol>
@end
#endif
NS_ASSUME_NONNULL_END

View File

@ -1,39 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSFakeNotificationsManager.h"
NS_ASSUME_NONNULL_BEGIN
#ifdef DEBUG
@implementation OWSFakeNotificationsManager
- (void)notifyUserForIncomingMessage:(TSIncomingMessage *)incomingMessage
inThread:(TSThread *)thread
contactsManager:(id<ContactsManagerProtocol>)contactsManager
transaction:(YapDatabaseReadTransaction *)transaction {
OWSLogInfo(@"");
}
- (void)notifyUserForErrorMessage:(TSErrorMessage *)error
thread:(TSThread *)thread
transaction:(YapDatabaseReadWriteTransaction *)transaction {
OWSLogInfo(@"");
}
- (void)notifyUserForThreadlessErrorMessage:(TSErrorMessage *)error
transaction:(YapDatabaseReadWriteTransaction *)transaction {
OWSLogInfo(@"");
}
- (void)clearAllNotifications {
OWSLogInfo(@"");
}
@end
#endif
NS_ASSUME_NONNULL_END