mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
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:
parent
312384201c
commit
1bfe691895
33 changed files with 1395 additions and 1606 deletions
|
@ -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 */,
|
||||
);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
672
Signal/src/UserInterface/Notifications/AppNotifications.swift
Normal file
672
Signal/src/UserInterface/Notifications/AppNotifications.swift
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
|
@ -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 you’ve 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
|
|
@ -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
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in a new issue