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, ); }; };
|
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 */; };
|
346129961FD1E30000532771 /* OWSDatabaseMigration.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129941FD1E30000532771 /* OWSDatabaseMigration.m */; };
|
||||||
346129991FD1E4DA00532771 /* SignalApp.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129971FD1E4D900532771 /* SignalApp.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, ); }; };
|
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 */; };
|
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, ); }; };
|
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 */; };
|
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, ); }; };
|
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 */; };
|
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 */; };
|
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, ); }; };
|
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 */; };
|
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 */; };
|
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, ); }; };
|
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 */; };
|
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 */; };
|
451F8A341FD710C3005CB9DA /* ConversationSearcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 451777C71FD61554001225FF /* ConversationSearcher.swift */; };
|
||||||
451F8A351FD710DE005CB9DA /* Searcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45360B8C1F9521F800FA666C /* Searcher.swift */; };
|
451F8A351FD710DE005CB9DA /* Searcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45360B8C1F9521F800FA666C /* Searcher.swift */; };
|
||||||
451F8A3B1FD71297005CB9DA /* UIUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B97940261832BD2400BD66CB /* UIUtil.m */; };
|
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, ); }; };
|
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 */; };
|
4CC1ECFB211A553000CC13BE /* AppUpdateNag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC1ECFA211A553000CC13BE /* AppUpdateNag.swift */; };
|
||||||
4CEB78C92178EBAB00F315D2 /* OWSSessionResetJobRecord.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CEB78C82178EBAB00F315D2 /* OWSSessionResetJobRecord.m */; };
|
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 */; };
|
70377AAB1918450100CAF501 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70377AAA1918450100CAF501 /* MobileCoreServices.framework */; };
|
||||||
768A1A2B17FC9CD300E00ED8 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 768A1A2A17FC9CD300E00ED8 /* libz.dylib */; };
|
768A1A2B17FC9CD300E00ED8 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 768A1A2A17FC9CD300E00ED8 /* libz.dylib */; };
|
||||||
76C87F19181EFCE600C4ACAB /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76C87F18181EFCE600C4ACAB /* MediaPlayer.framework */; };
|
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 */; };
|
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 */; };
|
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 */; };
|
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 */; };
|
B660F6D41C29868000687D6E /* whisperFake.cer in Resources */ = {isa = PBXBuildFile; fileRef = B660F69F1C29868000687D6E /* whisperFake.cer */; };
|
||||||
B660F6DB1C29868000687D6E /* FunctionalUtilTest.m in Sources */ = {isa = PBXBuildFile; fileRef = B660F6AD1C29868000687D6E /* FunctionalUtilTest.m */; };
|
B660F6DB1C29868000687D6E /* FunctionalUtilTest.m in Sources */ = {isa = PBXBuildFile; fileRef = B660F6AD1C29868000687D6E /* FunctionalUtilTest.m */; };
|
||||||
B660F6E01C29868000687D6E /* UtilTest.m in Sources */ = {isa = PBXBuildFile; fileRef = B660F6B41C29868000687D6E /* UtilTest.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 */; };
|
B67EBF5D19194AC60084CCFD /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = B67EBF5C19194AC60084CCFD /* Settings.bundle */; };
|
||||||
B69CD25119773E79005CE69A /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B69CD25019773E79005CE69A /* XCTest.framework */; };
|
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, ); }; };
|
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 */; };
|
B6F509971AA53F760068F56A /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B6F509951AA53F760068F56A /* Localizable.strings */; };
|
||||||
B6FE7EB71ADD62FA00A6D22F /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6FE7EB61ADD62FA00A6D22F /* PushKit.framework */; };
|
B6FE7EB71ADD62FA00A6D22F /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6FE7EB61ADD62FA00A6D22F /* PushKit.framework */; };
|
||||||
B90418E6183E9DD40038554A /* DateUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B90418E5183E9DD40038554A /* DateUtil.m */; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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; };
|
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; };
|
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>"; };
|
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>"; };
|
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; };
|
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 */,
|
346129411FD1D74B00532771 /* Environment.m */,
|
||||||
346129921FD1E30000532771 /* migrations */,
|
346129921FD1E30000532771 /* migrations */,
|
||||||
347850671FD9B78A007B8332 /* NoopCallMessageHandler.swift */,
|
347850671FD9B78A007B8332 /* NoopCallMessageHandler.swift */,
|
||||||
347850681FD9B78A007B8332 /* NoopNotificationsManager.swift */,
|
|
||||||
45F170AB1E2F0351003FC1F2 /* OWSAudioSession.swift */,
|
45F170AB1E2F0351003FC1F2 /* OWSAudioSession.swift */,
|
||||||
34074F60203D0CBE004596AE /* OWSSounds.h */,
|
34074F60203D0CBE004596AE /* OWSSounds.h */,
|
||||||
34074F5F203D0CBD004596AE /* OWSSounds.m */,
|
34074F5F203D0CBD004596AE /* OWSSounds.m */,
|
||||||
|
@ -1800,13 +1790,6 @@
|
||||||
path = Backup;
|
path = Backup;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
3496957521A301A300DCFE74 /* New Group */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
);
|
|
||||||
path = "New Group";
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
34B3F8331E8DF1700035BE1A /* ViewControllers */ = {
|
34B3F8331E8DF1700035BE1A /* ViewControllers */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -2040,8 +2023,9 @@
|
||||||
450DF2071E0DD29E003D14BE /* Notifications */ = {
|
450DF2071E0DD29E003D14BE /* Notifications */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
451A13B01E13DED2000A50FD /* NotificationsAdapter.swift */,
|
451A13B01E13DED2000A50FD /* AppNotifications.swift */,
|
||||||
450DF2081E0DD2C6003D14BE /* UserNotificationsAdaptee.swift */,
|
450DF2081E0DD2C6003D14BE /* UserNotificationsAdaptee.swift */,
|
||||||
|
4CFE6B6B21F92BA700006701 /* LegacyNotificationsAdaptee.swift */,
|
||||||
);
|
);
|
||||||
name = Notifications;
|
name = Notifications;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -2168,7 +2152,6 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
45FBC57A1DF8575700E9B410 /* CallKit */,
|
45FBC57A1DF8575700E9B410 /* CallKit */,
|
||||||
45E2E91E1E13EE3500457AA0 /* OWSCallNotificationsAdaptee.h */,
|
|
||||||
45794E851E00620000066731 /* CallUIAdapter.swift */,
|
45794E851E00620000066731 /* CallUIAdapter.swift */,
|
||||||
45F659811E1BE77000444429 /* NonCallKitCallUIAdaptee.swift */,
|
45F659811E1BE77000444429 /* NonCallKitCallUIAdaptee.swift */,
|
||||||
);
|
);
|
||||||
|
@ -2279,8 +2262,6 @@
|
||||||
children = (
|
children = (
|
||||||
34D99CE3217509C1000AFB39 /* AppEnvironment.swift */,
|
34D99CE3217509C1000AFB39 /* AppEnvironment.swift */,
|
||||||
4505C2BD1E648E6E00CEBF41 /* ExperienceUpgrades */,
|
4505C2BD1E648E6E00CEBF41 /* ExperienceUpgrades */,
|
||||||
3461299A1FD1EA9E00532771 /* NotificationsManager.h */,
|
|
||||||
3461299B1FD1EA9E00532771 /* NotificationsManager.m */,
|
|
||||||
4539B5851F79348F007141FF /* PushRegistrationManager.swift */,
|
4539B5851F79348F007141FF /* PushRegistrationManager.swift */,
|
||||||
346129981FD1E4DA00532771 /* SignalApp.h */,
|
346129981FD1E4DA00532771 /* SignalApp.h */,
|
||||||
346129971FD1E4D900532771 /* SignalApp.m */,
|
346129971FD1E4D900532771 /* SignalApp.m */,
|
||||||
|
@ -2293,8 +2274,6 @@
|
||||||
children = (
|
children = (
|
||||||
3430FE171F7751D4000EC51B /* GiphyAPI.swift */,
|
3430FE171F7751D4000EC51B /* GiphyAPI.swift */,
|
||||||
34D1F0511F7E8EA30066283D /* GiphyDownloader.swift */,
|
34D1F0511F7E8EA30066283D /* GiphyDownloader.swift */,
|
||||||
B6B9ECFA198B31BA00C620D3 /* PushManager.h */,
|
|
||||||
B6B9ECFB198B31BA00C620D3 /* PushManager.m */,
|
|
||||||
);
|
);
|
||||||
path = network;
|
path = network;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -2302,7 +2281,6 @@
|
||||||
76EB04C818170B33006006FC /* util */ = {
|
76EB04C818170B33006006FC /* util */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
3496957521A301A300DCFE74 /* New Group */,
|
|
||||||
4CC1ECFA211A553000CC13BE /* AppUpdateNag.swift */,
|
4CC1ECFA211A553000CC13BE /* AppUpdateNag.swift */,
|
||||||
B90418E4183E9DD40038554A /* DateUtil.h */,
|
B90418E4183E9DD40038554A /* DateUtil.h */,
|
||||||
3496956121A301A100DCFE74 /* Backup */,
|
3496956121A301A100DCFE74 /* Backup */,
|
||||||
|
@ -2415,7 +2393,6 @@
|
||||||
B660F6751C29867F00687D6E /* contact */,
|
B660F6751C29867F00687D6E /* contact */,
|
||||||
34843B29214FE295004DED45 /* mocks */,
|
34843B29214FE295004DED45 /* mocks */,
|
||||||
458E38381D6699110094BD24 /* Models */,
|
458E38381D6699110094BD24 /* Models */,
|
||||||
B660F69B1C29868000687D6E /* push */,
|
|
||||||
34843B2321432293004DED45 /* SignalBaseTest.h */,
|
34843B2321432293004DED45 /* SignalBaseTest.h */,
|
||||||
34843B2221432292004DED45 /* SignalBaseTest.m */,
|
34843B2221432292004DED45 /* SignalBaseTest.m */,
|
||||||
4589670F1DC117CC00E9DD21 /* SignalTests-Bridging-Header.h */,
|
4589670F1DC117CC00E9DD21 /* SignalTests-Bridging-Header.h */,
|
||||||
|
@ -2445,14 +2422,6 @@
|
||||||
path = contact;
|
path = contact;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
B660F69B1C29868000687D6E /* push */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
B660F69C1C29868000687D6E /* PushManagerTest.m */,
|
|
||||||
);
|
|
||||||
path = push;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
B660F69D1C29868000687D6E /* Supporting Files */ = {
|
B660F69D1C29868000687D6E /* Supporting Files */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -3317,7 +3286,6 @@
|
||||||
342950882124CB0A0000B063 /* OWSSearchBar.m in Sources */,
|
342950882124CB0A0000B063 /* OWSSearchBar.m in Sources */,
|
||||||
342950822124C9750000B063 /* OWSTextField.m in Sources */,
|
342950822124C9750000B063 /* OWSTextField.m in Sources */,
|
||||||
34AC0A13211B39EA00997B47 /* DisappearingTimerConfigurationView.swift in Sources */,
|
34AC0A13211B39EA00997B47 /* DisappearingTimerConfigurationView.swift in Sources */,
|
||||||
3478506C1FD9B78A007B8332 /* NoopNotificationsManager.swift in Sources */,
|
|
||||||
4CA46F4D219CFDAA0038ABDE /* GalleryRailView.swift in Sources */,
|
4CA46F4D219CFDAA0038ABDE /* GalleryRailView.swift in Sources */,
|
||||||
34480B621FD0A98800BC14EF /* UIColor+OWS.m in Sources */,
|
34480B621FD0A98800BC14EF /* UIColor+OWS.m in Sources */,
|
||||||
4C20B2B720CA0034001BAC90 /* ThreadViewModel.swift in Sources */,
|
4C20B2B720CA0034001BAC90 /* ThreadViewModel.swift in Sources */,
|
||||||
|
@ -3441,7 +3409,7 @@
|
||||||
454A84042059C787008B8C75 /* MediaTileViewController.swift in Sources */,
|
454A84042059C787008B8C75 /* MediaTileViewController.swift in Sources */,
|
||||||
340FC8B4204DAC8D007AEB0F /* OWSBackupSettingsViewController.m in Sources */,
|
340FC8B4204DAC8D007AEB0F /* OWSBackupSettingsViewController.m in Sources */,
|
||||||
34D1F0871F8678AA0066283D /* ConversationViewItem.m in Sources */,
|
34D1F0871F8678AA0066283D /* ConversationViewItem.m in Sources */,
|
||||||
451A13B11E13DED2000A50FD /* NotificationsAdapter.swift in Sources */,
|
451A13B11E13DED2000A50FD /* AppNotifications.swift in Sources */,
|
||||||
34D99CE4217509C2000AFB39 /* AppEnvironment.swift in Sources */,
|
34D99CE4217509C2000AFB39 /* AppEnvironment.swift in Sources */,
|
||||||
348570A820F67575004FF32B /* OWSMessageHeaderView.m in Sources */,
|
348570A820F67575004FF32B /* OWSMessageHeaderView.m in Sources */,
|
||||||
450DF2091E0DD2C6003D14BE /* UserNotificationsAdaptee.swift in Sources */,
|
450DF2091E0DD2C6003D14BE /* UserNotificationsAdaptee.swift in Sources */,
|
||||||
|
@ -3460,7 +3428,6 @@
|
||||||
4585C4681ED8F8D200896AEA /* SafetyNumberConfirmationAlert.swift in Sources */,
|
4585C4681ED8F8D200896AEA /* SafetyNumberConfirmationAlert.swift in Sources */,
|
||||||
4C20B2B920CA10DE001BAC90 /* ConversationSearchViewController.swift in Sources */,
|
4C20B2B920CA10DE001BAC90 /* ConversationSearchViewController.swift in Sources */,
|
||||||
450D19131F85236600970622 /* RemoteVideoView.m in Sources */,
|
450D19131F85236600970622 /* RemoteVideoView.m in Sources */,
|
||||||
B6B9ECFC198B31BA00C620D3 /* PushManager.m in Sources */,
|
|
||||||
34129B8621EF877A005457A8 /* LinkPreviewView.swift in Sources */,
|
34129B8621EF877A005457A8 /* LinkPreviewView.swift in Sources */,
|
||||||
34386A54207D271D009F5D9C /* NeverClearView.swift in Sources */,
|
34386A54207D271D009F5D9C /* NeverClearView.swift in Sources */,
|
||||||
45DF5DF21DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift in Sources */,
|
45DF5DF21DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift in Sources */,
|
||||||
|
@ -3542,6 +3509,7 @@
|
||||||
45D308AD2049A439000189E4 /* PinEntryView.m in Sources */,
|
45D308AD2049A439000189E4 /* PinEntryView.m in Sources */,
|
||||||
340FC8B1204DAC8D007AEB0F /* BlockListViewController.m in Sources */,
|
340FC8B1204DAC8D007AEB0F /* BlockListViewController.m in Sources */,
|
||||||
45B5360E206DD8BB00D61655 /* UIResponder+OWS.swift in Sources */,
|
45B5360E206DD8BB00D61655 /* UIResponder+OWS.swift in Sources */,
|
||||||
|
4CFE6B6C21F92BA700006701 /* LegacyNotificationsAdaptee.swift in Sources */,
|
||||||
3441FD9F21A3604F00BB9542 /* BackupRestoreViewController.swift in Sources */,
|
3441FD9F21A3604F00BB9542 /* BackupRestoreViewController.swift in Sources */,
|
||||||
45F659821E1BE77000444429 /* NonCallKitCallUIAdaptee.swift in Sources */,
|
45F659821E1BE77000444429 /* NonCallKitCallUIAdaptee.swift in Sources */,
|
||||||
4C5250D221E7BD7D00CE3D95 /* PhoneNumberValidator.swift in Sources */,
|
4C5250D221E7BD7D00CE3D95 /* PhoneNumberValidator.swift in Sources */,
|
||||||
|
@ -3586,7 +3554,6 @@
|
||||||
340FC8B9204DAC8D007AEB0F /* UpdateGroupViewController.m in Sources */,
|
340FC8B9204DAC8D007AEB0F /* UpdateGroupViewController.m in Sources */,
|
||||||
4574A5D61DD6704700C6B692 /* CallService.swift in Sources */,
|
4574A5D61DD6704700C6B692 /* CallService.swift in Sources */,
|
||||||
340FC8A8204DAC8D007AEB0F /* CodeVerificationViewController.m in Sources */,
|
340FC8A8204DAC8D007AEB0F /* CodeVerificationViewController.m in Sources */,
|
||||||
3461299C1FD1EA9E00532771 /* NotificationsManager.m in Sources */,
|
|
||||||
4521C3C01F59F3BA00B4C582 /* TextFieldHelper.swift in Sources */,
|
4521C3C01F59F3BA00B4C582 /* TextFieldHelper.swift in Sources */,
|
||||||
34D2CCDF206939B400CB1A14 /* DebugUIMessagesAction.m in Sources */,
|
34D2CCDF206939B400CB1A14 /* DebugUIMessagesAction.m in Sources */,
|
||||||
340FC8AC204DAC8D007AEB0F /* PrivacySettingsTableViewController.m in Sources */,
|
340FC8AC204DAC8D007AEB0F /* PrivacySettingsTableViewController.m in Sources */,
|
||||||
|
@ -3641,7 +3608,6 @@
|
||||||
4C5250D421E7C51900CE3D95 /* PhoneNumberValidatorTest.swift in Sources */,
|
4C5250D421E7C51900CE3D95 /* PhoneNumberValidatorTest.swift in Sources */,
|
||||||
452D1AF12081059C00A67F7F /* StringAdditionsTest.swift in Sources */,
|
452D1AF12081059C00A67F7F /* StringAdditionsTest.swift in Sources */,
|
||||||
4C4BC6C32102D697004040C9 /* ContactDiscoveryOperationTest.swift in Sources */,
|
4C4BC6C32102D697004040C9 /* ContactDiscoveryOperationTest.swift in Sources */,
|
||||||
B660F6D21C29868000687D6E /* PushManagerTest.m in Sources */,
|
|
||||||
455AC69E1F4F8B0300134004 /* ImageCacheTest.swift in Sources */,
|
455AC69E1F4F8B0300134004 /* ImageCacheTest.swift in Sources */,
|
||||||
34E8A8D12085238A00B272B1 /* ProtoParsingTest.m in Sources */,
|
34E8A8D12085238A00B272B1 /* ProtoParsingTest.m in Sources */,
|
||||||
);
|
);
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
#import "OWSOrphanDataCleaner.h"
|
#import "OWSOrphanDataCleaner.h"
|
||||||
#import "OWSScreenLockUI.h"
|
#import "OWSScreenLockUI.h"
|
||||||
#import "Pastelog.h"
|
#import "Pastelog.h"
|
||||||
#import "PushManager.h"
|
|
||||||
#import "RegistrationViewController.h"
|
#import "RegistrationViewController.h"
|
||||||
#import "Signal-Swift.h"
|
#import "Signal-Swift.h"
|
||||||
#import "SignalApp.h"
|
#import "SignalApp.h"
|
||||||
|
@ -61,7 +60,7 @@ static NSString *const kURLHostVerifyPrefix = @"verify";
|
||||||
|
|
||||||
static NSTimeInterval launchStartedAt;
|
static NSTimeInterval launchStartedAt;
|
||||||
|
|
||||||
@interface AppDelegate ()
|
@interface AppDelegate () <UNUserNotificationCenterDelegate>
|
||||||
|
|
||||||
@property (nonatomic) BOOL hasInitialRootViewController;
|
@property (nonatomic) BOOL hasInitialRootViewController;
|
||||||
@property (nonatomic) BOOL areVersionMigrationsComplete;
|
@property (nonatomic) BOOL areVersionMigrationsComplete;
|
||||||
|
@ -175,6 +174,11 @@ static NSTimeInterval launchStartedAt;
|
||||||
return AppEnvironment.shared.backup;
|
return AppEnvironment.shared.backup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (OWSNotificationPresenter *)notificationPresenter
|
||||||
|
{
|
||||||
|
return AppEnvironment.shared.notificationPresenter;
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark -
|
#pragma mark -
|
||||||
|
|
||||||
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
||||||
|
@ -285,6 +289,14 @@ static NSTimeInterval launchStartedAt;
|
||||||
mainWindow.rootViewController = [LoadingViewController new];
|
mainWindow.rootViewController = [LoadingViewController new];
|
||||||
[mainWindow makeKeyAndVisible];
|
[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
|
// Accept push notification when app is not open
|
||||||
NSDictionary *remoteNotif = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
|
NSDictionary *remoteNotif = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
|
||||||
if (remoteNotif) {
|
if (remoteNotif) {
|
||||||
|
@ -580,8 +592,8 @@ static NSTimeInterval launchStartedAt;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
OWSLogInfo(@"registered user notification settings");
|
OWSLogInfo(@"registered legacy notification settings");
|
||||||
[self.pushRegistrationManager didRegisterUserNotificationSettings];
|
[self.notificationPresenter didRegisterLegacyNotificationSettings];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)application:(UIApplication *)application
|
- (BOOL)application:(UIApplication *)application
|
||||||
|
@ -648,11 +660,6 @@ static NSTimeInterval launchStartedAt;
|
||||||
[self handleActivation];
|
[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.
|
// Clear all notifications whenever we become active.
|
||||||
// When opening the app from a notification,
|
// When opening the app from a notification,
|
||||||
// AppDelegate.didReceiveLocalNotification will always
|
// AppDelegate.didReceiveLocalNotification will always
|
||||||
|
@ -733,8 +740,7 @@ static NSTimeInterval launchStartedAt;
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
[self.socketManager requestSocketOpen];
|
[self.socketManager requestSocketOpen];
|
||||||
[Environment.shared.contactsManager fetchSystemContactsOnceIfAlreadyAuthorized];
|
[Environment.shared.contactsManager fetchSystemContactsOnceIfAlreadyAuthorized];
|
||||||
// This will fetch new messages, if we're using domain fronting.
|
[[AppEnvironment.shared.messageFetcherJob run] retainUntilComplete];
|
||||||
[[PushManager sharedManager] applicationDidBecomeActive];
|
|
||||||
|
|
||||||
if (![UIApplication sharedApplication].isRegisteredForRemoteNotifications) {
|
if (![UIApplication sharedApplication].isRegisteredForRemoteNotifications) {
|
||||||
OWSLogInfo(@"Retrying to register for remote notifications since user hasn't registered yet.");
|
OWSLogInfo(@"Retrying to register for remote notifications since user hasn't registered yet.");
|
||||||
|
@ -1111,8 +1117,9 @@ static NSTimeInterval launchStartedAt;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// It is safe to continue even if the app isn't ready.
|
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
|
||||||
[[PushManager sharedManager] application:application didReceiveRemoteNotification:userInfo];
|
[[AppEnvironment.shared.messageFetcherJob run] retainUntilComplete];
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)application:(UIApplication *)application
|
- (void)application:(UIApplication *)application
|
||||||
|
@ -1129,10 +1136,12 @@ static NSTimeInterval launchStartedAt;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// It is safe to continue even if the app isn't ready.
|
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
|
||||||
[[PushManager sharedManager] application:application
|
[[AppEnvironment.shared.messageFetcherJob run] retainUntilComplete];
|
||||||
didReceiveRemoteNotification:userInfo
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 20 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
||||||
fetchCompletionHandler:completionHandler];
|
completionHandler(UIBackgroundFetchResultNewData);
|
||||||
|
});
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
|
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
|
||||||
|
@ -1150,7 +1159,12 @@ static NSTimeInterval launchStartedAt;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[PushManager sharedManager] application:application didReceiveLocalNotification:notification];
|
[LegacyNotificationActionHandler.shared
|
||||||
|
handleNotificationResponseWithActionIdentifier:LegacyNotificationActionHandler.kDefaultActionIdentifier
|
||||||
|
notification:notification
|
||||||
|
responseInfo:@{}
|
||||||
|
completionHandler:^{
|
||||||
|
}];
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1180,9 +1194,9 @@ static NSTimeInterval launchStartedAt;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[PushManager sharedManager] application:application
|
[LegacyNotificationActionHandler.shared handleNotificationResponseWithActionIdentifier:identifier
|
||||||
handleActionWithIdentifier:identifier
|
notification:notification
|
||||||
forLocalNotification:notification
|
responseInfo:@{}
|
||||||
completionHandler:completionHandler];
|
completionHandler:completionHandler];
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
@ -1216,10 +1230,9 @@ static NSTimeInterval launchStartedAt;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[PushManager sharedManager] application:application
|
[LegacyNotificationActionHandler.shared handleNotificationResponseWithActionIdentifier:identifier
|
||||||
handleActionWithIdentifier:identifier
|
notification:notification
|
||||||
forLocalNotification:notification
|
responseInfo:responseInfo
|
||||||
withResponseInfo:responseInfo
|
|
||||||
completionHandler:completionHandler];
|
completionHandler:completionHandler];
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
@ -1243,6 +1256,7 @@ static NSTimeInterval launchStartedAt;
|
||||||
job = nil;
|
job = nil;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
[job retainUntilComplete];
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1301,7 +1315,7 @@ static NSTimeInterval launchStartedAt;
|
||||||
// Fetch messages as soon as possible after launching. In particular, when
|
// Fetch messages as soon as possible after launching. In particular, when
|
||||||
// launching from the background, without this, we end up waiting some extra
|
// launching from the background, without this, we end up waiting some extra
|
||||||
// seconds before receiving an actionable push notification.
|
// 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.
|
// This should happen at any launch, background or foreground.
|
||||||
__unused AnyPromise *pushTokenpromise =
|
__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
|
@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
|
import Foundation
|
||||||
|
@ -28,8 +28,7 @@ public class MessageFetcherJob: NSObject {
|
||||||
return SSKEnvironment.shared.messageReceiver
|
return SSKEnvironment.shared.messageReceiver
|
||||||
}
|
}
|
||||||
|
|
||||||
private
|
private var signalService: OWSSignalService {
|
||||||
var signalService: OWSSignalService {
|
|
||||||
return OWSSignalService.sharedInstance()
|
return OWSSignalService.sharedInstance()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,11 +15,6 @@ public class AccountManager: NSObject {
|
||||||
|
|
||||||
// MARK: - Dependencies
|
// 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 {
|
var profileManager: OWSProfileManager {
|
||||||
return OWSProfileManager.shared()
|
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>
|
#import <Foundation/Foundation.h>
|
||||||
|
@ -20,7 +20,6 @@
|
||||||
#import "HomeViewController.h"
|
#import "HomeViewController.h"
|
||||||
#import "MediaDetailViewController.h"
|
#import "MediaDetailViewController.h"
|
||||||
#import "NotificationSettingsViewController.h"
|
#import "NotificationSettingsViewController.h"
|
||||||
#import "NotificationsManager.h"
|
|
||||||
#import "OWSAddToContactViewController.h"
|
#import "OWSAddToContactViewController.h"
|
||||||
#import "OWSAnyTouchGestureRecognizer.h"
|
#import "OWSAnyTouchGestureRecognizer.h"
|
||||||
#import "OWSAudioMessageView.h"
|
#import "OWSAudioMessageView.h"
|
||||||
|
@ -30,7 +29,6 @@
|
||||||
#import "OWSBezierPathView.h"
|
#import "OWSBezierPathView.h"
|
||||||
#import "OWSBubbleShapeView.h"
|
#import "OWSBubbleShapeView.h"
|
||||||
#import "OWSBubbleView.h"
|
#import "OWSBubbleView.h"
|
||||||
#import "OWSCallNotificationsAdaptee.h"
|
|
||||||
#import "OWSDatabaseMigration.h"
|
#import "OWSDatabaseMigration.h"
|
||||||
#import "OWSMessageBubbleView.h"
|
#import "OWSMessageBubbleView.h"
|
||||||
#import "OWSMessageCell.h"
|
#import "OWSMessageCell.h"
|
||||||
|
@ -42,7 +40,6 @@
|
||||||
#import "PinEntryView.h"
|
#import "PinEntryView.h"
|
||||||
#import "PrivacySettingsTableViewController.h"
|
#import "PrivacySettingsTableViewController.h"
|
||||||
#import "ProfileViewController.h"
|
#import "ProfileViewController.h"
|
||||||
#import "PushManager.h"
|
|
||||||
#import "RegistrationViewController.h"
|
#import "RegistrationViewController.h"
|
||||||
#import "RemoteVideoView.h"
|
#import "RemoteVideoView.h"
|
||||||
#import "SignalApp.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 Foundation
|
||||||
import UserNotifications
|
import UserNotifications
|
||||||
|
import PromiseKit
|
||||||
|
|
||||||
@available(iOS 10.0, *)
|
@available(iOS 10.0, *)
|
||||||
struct AppNotifications {
|
class UserNotificationConfig {
|
||||||
enum Category {
|
|
||||||
case missedCall,
|
|
||||||
missedCallFromNoLongerVerifiedIdentity
|
|
||||||
|
|
||||||
// Don't forget to update this! We use it to register categories.
|
class var allNotificationCategories: Set<UNNotificationCategory> {
|
||||||
static let allValues = [ missedCall, missedCallFromNoLongerVerifiedIdentity ]
|
let categories = AppNotificationCategory.allCases.map { notificationCategory($0) }
|
||||||
}
|
|
||||||
|
|
||||||
enum Action {
|
|
||||||
case callBack,
|
|
||||||
showThread
|
|
||||||
}
|
|
||||||
|
|
||||||
static var allCategories: Set<UNNotificationCategory> {
|
|
||||||
let categories = Category.allValues.map { category($0) }
|
|
||||||
return Set(categories)
|
return Set(categories)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func category(_ type: Category) -> UNNotificationCategory {
|
class func notificationActions(for category: AppNotificationCategory) -> [UNNotificationAction] {
|
||||||
switch type {
|
return category.actions.map { notificationAction($0) }
|
||||||
case .missedCall:
|
}
|
||||||
return UNNotificationCategory(identifier: "org.whispersystems.signal.AppNotifications.Category.missedCall",
|
|
||||||
actions: [ action(.callBack) ],
|
|
||||||
intentIdentifiers: [],
|
|
||||||
options: [])
|
|
||||||
|
|
||||||
case .missedCallFromNoLongerVerifiedIdentity:
|
class func notificationCategory(_ category: AppNotificationCategory) -> UNNotificationCategory {
|
||||||
return UNNotificationCategory(identifier: "org.whispersystems.signal.AppNotifications.Category.missedCallFromNoLongerVerifiedIdentity",
|
return UNNotificationCategory(identifier: category.identifier,
|
||||||
actions: [ action(.showThread) ],
|
actions: notificationActions(for: category),
|
||||||
intentIdentifiers: [],
|
intentIdentifiers: [],
|
||||||
options: [])
|
options: [])
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static func action(_ type: Action) -> UNNotificationAction {
|
class func notificationAction(_ action: AppNotificationAction) -> UNNotificationAction {
|
||||||
switch type {
|
switch action {
|
||||||
|
case .answerCall:
|
||||||
|
return UNNotificationAction(identifier: action.identifier,
|
||||||
|
title: CallStrings.answerCallButtonTitle,
|
||||||
|
options: [.foreground])
|
||||||
case .callBack:
|
case .callBack:
|
||||||
return UNNotificationAction(identifier: "org.whispersystems.signal.AppNotifications.Action.callBack",
|
return UNNotificationAction(identifier: action.identifier,
|
||||||
title: CallStrings.callBackButtonTitle,
|
title: CallStrings.callBackButtonTitle,
|
||||||
options: .authenticationRequired)
|
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:
|
case .showThread:
|
||||||
return UNNotificationAction(identifier: "org.whispersystems.signal.AppNotifications.Action.showThread",
|
return UNNotificationAction(identifier: action.identifier,
|
||||||
title: CallStrings.showThreadButtonTitle,
|
title: CallStrings.showThreadButtonTitle,
|
||||||
options: .authenticationRequired)
|
options: [.foreground])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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, *)
|
@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()
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
if granted {
|
||||||
Logger.debug("succeeded.")
|
Logger.debug("succeeded.")
|
||||||
} else if error != nil {
|
} else if error != nil {
|
||||||
Logger.error("failed with error: \(error!)")
|
Logger.error("failed with error: \(error!)")
|
||||||
} else {
|
} else {
|
||||||
|
owsFailDebug("error was unexpectedly nil")
|
||||||
Logger.error("failed without error.")
|
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
|
func notify(category: AppNotificationCategory, body: String, userInfo: [AnyHashable: Any], sound: OWSSound?) {
|
||||||
|
AssertIsOnMainThread()
|
||||||
public func presentIncomingCall(_ call: SignalCall, callerName: String) {
|
notify(category: category, body: body, userInfo: userInfo, sound: sound, replacingIdentifier: nil)
|
||||||
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.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func presentMissedCall(_ call: SignalCall, callerName: String) {
|
func notify(category: AppNotificationCategory, body: String, userInfo: [AnyHashable: Any], sound: OWSSound?, replacingIdentifier: String?) {
|
||||||
Logger.debug("")
|
AssertIsOnMainThread()
|
||||||
|
|
||||||
let content = UNMutableNotificationContent()
|
let content = UNMutableNotificationContent()
|
||||||
// TODO group by thread identifier
|
content.categoryIdentifier = category.identifier
|
||||||
// content.threadIdentifier = threadId
|
content.userInfo = userInfo
|
||||||
|
content.sound = sound?.notificationSound
|
||||||
|
|
||||||
let notificationBody = { () -> String in
|
var notificationIdentifier: String = UUID().uuidString
|
||||||
switch previewType {
|
if let replacingIdentifier = replacingIdentifier {
|
||||||
case .noNameNoPreview:
|
notificationIdentifier = replacingIdentifier
|
||||||
return CallStrings.missedCallNotificationBodyWithoutCallerName
|
Logger.debug("replacing notification with identifier: \(notificationIdentifier)")
|
||||||
case .nameNoPreview, .namePreview:
|
cancelNotification(identifier: notificationIdentifier)
|
||||||
return (Environment.shared.preferences.isCallKitPrivacyEnabled()
|
|
||||||
? CallStrings.missedCallNotificationBodyWithoutCallerName
|
|
||||||
: String(format: CallStrings.missedCallNotificationBodyWithCallerName, callerName))
|
|
||||||
}}()
|
|
||||||
|
|
||||||
content.body = notificationBody
|
|
||||||
content.sound = UNNotificationSound.default()
|
|
||||||
content.categoryIdentifier = AppNotifications.category(.missedCall).identifier
|
|
||||||
|
|
||||||
let request = UNNotificationRequest.init(identifier: call.localId.uuidString, content: content, trigger: nil)
|
|
||||||
|
|
||||||
center.add(request)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func presentMissedCallBecauseOfNoLongerVerifiedIdentity(call: SignalCall, callerName: String) {
|
let trigger: UNNotificationTrigger?
|
||||||
Logger.debug("")
|
let checkForCancel = category == .incomingMessage
|
||||||
|
if checkForCancel && hasReceivedSyncMessageRecently {
|
||||||
let content = UNMutableNotificationContent()
|
assert(userInfo[AppNotificationUserInfoKey.threadId] != nil)
|
||||||
// TODO group by thread identifier
|
trigger = UNTimeIntervalNotificationTrigger(timeInterval: kNotificationDelayForRemoteRead, repeats: false)
|
||||||
// content.threadIdentifier = threadId
|
} else {
|
||||||
|
trigger = nil
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func presentMissedCallBecauseOfNewIdentity(call: SignalCall, callerName: String) {
|
if shouldPresentNotification(category: category, userInfo: userInfo) {
|
||||||
Logger.debug("")
|
content.body = body
|
||||||
|
} else {
|
||||||
|
// Play sound and vibrate, but without a `body` no banner will show.
|
||||||
|
Logger.debug("supressing notification body")
|
||||||
|
}
|
||||||
|
|
||||||
let content = UNMutableNotificationContent()
|
let request = UNNotificationRequest(identifier: notificationIdentifier, content: content, trigger: trigger)
|
||||||
// TODO group by thread identifier
|
|
||||||
// content.threadIdentifier = threadId
|
|
||||||
|
|
||||||
let notificationBody = { () -> String in
|
Logger.debug("presenting notification with identifier: \(notificationIdentifier)")
|
||||||
switch previewType {
|
notificationCenter.add(request)
|
||||||
case .noNameNoPreview:
|
notifications[notificationIdentifier] = request
|
||||||
return CallStrings.missedCallWithIdentityChangeNotificationBodyWithoutCallerName
|
}
|
||||||
case .nameNoPreview, .namePreview:
|
|
||||||
return (Environment.shared.preferences.isCallKitPrivacyEnabled()
|
|
||||||
? CallStrings.missedCallWithIdentityChangeNotificationBodyWithoutCallerName
|
|
||||||
: String(format: CallStrings.missedCallWithIdentityChangeNotificationBodyWithCallerName, callerName))
|
|
||||||
}}()
|
|
||||||
|
|
||||||
content.body = notificationBody
|
func cancelNotification(identifier: String) {
|
||||||
content.sound = UNNotificationSound.default()
|
AssertIsOnMainThread()
|
||||||
content.categoryIdentifier = AppNotifications.category(.missedCall).identifier
|
notifications.removeValue(forKey: identifier)
|
||||||
|
notificationCenter.removeDeliveredNotifications(withIdentifiers: [identifier])
|
||||||
|
notificationCenter.removePendingNotificationRequests(withIdentifiers: [identifier])
|
||||||
|
}
|
||||||
|
|
||||||
let request = UNNotificationRequest.init(identifier: call.localId.uuidString, content: content, trigger: nil)
|
func cancelNotification(_ notification: UNNotificationRequest) {
|
||||||
|
AssertIsOnMainThread()
|
||||||
|
cancelNotification(identifier: notification.identifier)
|
||||||
|
}
|
||||||
|
|
||||||
center.add(request)
|
func cancelNotifications(threadId: String) {
|
||||||
|
AssertIsOnMainThread()
|
||||||
|
for notification in notifications.values {
|
||||||
|
guard let notificationThreadId = notification.content.userInfo[AppNotificationUserInfoKey.threadId] as? String else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
guard notificationThreadId == threadId else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelNotification(notification)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func clearAllNotifications() {
|
||||||
|
AssertIsOnMainThread()
|
||||||
|
notificationCenter.removeAllPendingNotificationRequests()
|
||||||
|
notificationCenter.removeAllDeliveredNotifications()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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"
|
#import "AdvancedSettingsTableViewController.h"
|
||||||
|
@ -7,7 +7,6 @@
|
||||||
#import "DomainFrontingCountryViewController.h"
|
#import "DomainFrontingCountryViewController.h"
|
||||||
#import "OWSCountryMetadata.h"
|
#import "OWSCountryMetadata.h"
|
||||||
#import "Pastelog.h"
|
#import "Pastelog.h"
|
||||||
#import "PushManager.h"
|
|
||||||
#import "Signal-Swift.h"
|
#import "Signal-Swift.h"
|
||||||
#import "TSAccountManager.h"
|
#import "TSAccountManager.h"
|
||||||
#import <PromiseKit/AnyPromise.h>
|
#import <PromiseKit/AnyPromise.h>
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
#import "OWSNavigationController.h"
|
#import "OWSNavigationController.h"
|
||||||
#import "PrivacySettingsTableViewController.h"
|
#import "PrivacySettingsTableViewController.h"
|
||||||
#import "ProfileViewController.h"
|
#import "ProfileViewController.h"
|
||||||
#import "PushManager.h"
|
|
||||||
#import "RegistrationUtils.h"
|
#import "RegistrationUtils.h"
|
||||||
#import "Signal-Swift.h"
|
#import "Signal-Swift.h"
|
||||||
#import <SignalMessaging/Environment.h>
|
#import <SignalMessaging/Environment.h>
|
||||||
|
|
|
@ -143,9 +143,7 @@ const CGFloat kMaxTextViewHeight = 98;
|
||||||
[self.attachmentButton autoSetDimensionsToSize:CGSizeMake(40, kMinTextViewHeight)];
|
[self.attachmentButton autoSetDimensionsToSize:CGSizeMake(40, kMinTextViewHeight)];
|
||||||
|
|
||||||
_sendButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
_sendButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||||
[self.sendButton
|
[self.sendButton setTitle:MessageStrings.sendButton forState:UIControlStateNormal];
|
||||||
setTitle:NSLocalizedString(@"SEND_BUTTON_TITLE", @"Label for the send button in the conversation view.")
|
|
||||||
forState:UIControlStateNormal];
|
|
||||||
[self.sendButton setTitleColor:UIColor.ows_signalBlueColor forState:UIControlStateNormal];
|
[self.sendButton setTitleColor:UIColor.ows_signalBlueColor forState:UIControlStateNormal];
|
||||||
self.sendButton.titleLabel.textAlignment = NSTextAlignmentCenter;
|
self.sendButton.titleLabel.textAlignment = NSTextAlignmentCenter;
|
||||||
self.sendButton.titleLabel.font = [UIFont ows_mediumFontWithSize:17.f];
|
self.sendButton.titleLabel.font = [UIFont ows_mediumFontWithSize:17.f];
|
||||||
|
|
|
@ -11,8 +11,8 @@ class DebugUINotifications: DebugUIPage {
|
||||||
|
|
||||||
// MARK: Dependencies
|
// MARK: Dependencies
|
||||||
|
|
||||||
var notificationsAdapter: NotificationsAdapter {
|
var notificationPresenter: NotificationPresenter {
|
||||||
return AppEnvironment.shared.notificationsAdapter
|
return AppEnvironment.shared.notificationPresenter
|
||||||
}
|
}
|
||||||
var messageSender: MessageSender {
|
var messageSender: MessageSender {
|
||||||
return SSKEnvironment.shared.messageSender
|
return SSKEnvironment.shared.messageSender
|
||||||
|
@ -135,28 +135,28 @@ class DebugUINotifications: DebugUIPage {
|
||||||
func notifyForIncomingCall(thread: TSContactThread) -> Guarantee<Void> {
|
func notifyForIncomingCall(thread: TSContactThread) -> Guarantee<Void> {
|
||||||
Logger.info("⚠️ will present notification after delay")
|
Logger.info("⚠️ will present notification after delay")
|
||||||
return delayedNotificationDispatchWithFakeCall(thread: thread) { call in
|
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> {
|
func notifyForMissedCall(thread: TSContactThread) -> Guarantee<Void> {
|
||||||
Logger.info("⚠️ will present notification after delay")
|
Logger.info("⚠️ will present notification after delay")
|
||||||
return delayedNotificationDispatchWithFakeCall(thread: thread) { call in
|
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> {
|
func notifyForMissedCallBecauseOfNewIdentity(thread: TSContactThread) -> Guarantee<Void> {
|
||||||
Logger.info("⚠️ will present notification after delay")
|
Logger.info("⚠️ will present notification after delay")
|
||||||
return delayedNotificationDispatchWithFakeCall(thread: thread) { call in
|
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> {
|
func notifyForMissedCallBecauseOfNoLongerVerifiedIdentity(thread: TSContactThread) -> Guarantee<Void> {
|
||||||
Logger.info("⚠️ will present notification after delay")
|
Logger.info("⚠️ will present notification after delay")
|
||||||
return delayedNotificationDispatchWithFakeCall(thread: thread) { call in
|
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 }
|
factory.threadCreator = { _ in return thread }
|
||||||
let incomingMessage = factory.create(transaction: transaction)
|
let incomingMessage = factory.create(transaction: transaction)
|
||||||
|
|
||||||
self.notificationsAdapter.notifyUser(for: incomingMessage,
|
self.notificationPresenter.notifyUser(for: incomingMessage,
|
||||||
in: thread,
|
in: thread,
|
||||||
contactsManager: self.contactsManager,
|
contactsManager: self.contactsManager,
|
||||||
transaction: transaction)
|
transaction: transaction)
|
||||||
|
@ -184,7 +184,7 @@ class DebugUINotifications: DebugUIPage {
|
||||||
failedMessageType: TSErrorMessageType.invalidMessage)
|
failedMessageType: TSErrorMessageType.invalidMessage)
|
||||||
|
|
||||||
self.readWrite { transaction in
|
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
|
self.readWrite { transaction in
|
||||||
let errorMessage = TSErrorMessage.corruptedMessageInUnknownThread()
|
let errorMessage = TSErrorMessage.corruptedMessageInUnknownThread()
|
||||||
|
|
||||||
self.notificationsAdapter.notifyUser(forThreadlessErrorMessage: errorMessage,
|
self.notificationPresenter.notifyUser(forThreadlessErrorMessage: errorMessage,
|
||||||
transaction: transaction)
|
transaction: transaction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
#import "OWSNavigationController.h"
|
#import "OWSNavigationController.h"
|
||||||
#import "OWSPrimaryStorage.h"
|
#import "OWSPrimaryStorage.h"
|
||||||
#import "ProfileViewController.h"
|
#import "ProfileViewController.h"
|
||||||
#import "PushManager.h"
|
|
||||||
#import "RegistrationUtils.h"
|
#import "RegistrationUtils.h"
|
||||||
#import "Signal-Swift.h"
|
#import "Signal-Swift.h"
|
||||||
#import "SignalApp.h"
|
#import "SignalApp.h"
|
||||||
|
@ -943,10 +942,10 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations
|
||||||
{
|
{
|
||||||
OWSAssertIsOnMainThread();
|
OWSAssertIsOnMainThread();
|
||||||
OWSLogInfo(@"beggining refreshing.");
|
OWSLogInfo(@"beggining refreshing.");
|
||||||
[AppEnvironment.shared.messageFetcherJob run].ensure(^{
|
[[AppEnvironment.shared.messageFetcherJob run].ensure(^{
|
||||||
OWSLogInfo(@"ending refreshing.");
|
OWSLogInfo(@"ending refreshing.");
|
||||||
[refreshControl endRefreshing];
|
[refreshControl endRefreshing];
|
||||||
});
|
}) retainUntilComplete];
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Edit Actions
|
#pragma mark - Edit Actions
|
||||||
|
|
|
@ -401,8 +401,8 @@ private class SignalCallData: NSObject {
|
||||||
return AppEnvironment.shared.accountManager
|
return AppEnvironment.shared.accountManager
|
||||||
}
|
}
|
||||||
|
|
||||||
private var notificationsAdapter: NotificationsAdapter {
|
private var notificationPresenter: NotificationPresenter {
|
||||||
return AppEnvironment.shared.notificationsAdapter
|
return AppEnvironment.shared.notificationPresenter
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Notifications
|
// 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?")
|
Logger.warn("ending current call in. Did user toggle callkit preference while in a call?")
|
||||||
self.terminateCall()
|
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
|
// MARK: - Service Actions
|
||||||
|
@ -691,11 +691,11 @@ private class SignalCallData: NSObject {
|
||||||
switch untrustedIdentity!.verificationState {
|
switch untrustedIdentity!.verificationState {
|
||||||
case .verified:
|
case .verified:
|
||||||
owsFailDebug("shouldn't have missed a call due to untrusted identity if the identity is 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:
|
case .default:
|
||||||
self.notificationsAdapter.presentMissedCallBecauseOfNewIdentity(call: newCall, callerName: callerName)
|
self.notificationPresenter.presentMissedCallBecauseOfNewIdentity(call: newCall, callerName: callerName)
|
||||||
case .noLongerVerified:
|
case .noLongerVerified:
|
||||||
self.notificationsAdapter.presentMissedCallBecauseOfNoLongerVerifiedIdentity(call: newCall, callerName: callerName)
|
self.notificationPresenter.presentMissedCallBecauseOfNoLongerVerifiedIdentity(call: newCall, callerName: callerName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MJK TODO remove this timestamp param
|
// MJK TODO remove this timestamp param
|
||||||
|
|
|
@ -11,17 +11,17 @@ import SignalMessaging
|
||||||
*/
|
*/
|
||||||
class NonCallKitCallUIAdaptee: NSObject, CallUIAdaptee {
|
class NonCallKitCallUIAdaptee: NSObject, CallUIAdaptee {
|
||||||
|
|
||||||
let notificationsAdapter: NotificationsAdapter
|
let notificationPresenter: NotificationPresenter
|
||||||
let callService: CallService
|
let callService: CallService
|
||||||
|
|
||||||
// Starting/Stopping incoming call ringing is our apps responsibility for the non CallKit interface.
|
// Starting/Stopping incoming call ringing is our apps responsibility for the non CallKit interface.
|
||||||
let hasManualRinger = true
|
let hasManualRinger = true
|
||||||
|
|
||||||
required init(callService: CallService, notificationsAdapter: NotificationsAdapter) {
|
required init(callService: CallService, notificationPresenter: NotificationPresenter) {
|
||||||
AssertIsOnMainThread()
|
AssertIsOnMainThread()
|
||||||
|
|
||||||
self.callService = callService
|
self.callService = callService
|
||||||
self.notificationsAdapter = notificationsAdapter
|
self.notificationPresenter = notificationPresenter
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
@ -59,14 +59,14 @@ class NonCallKitCallUIAdaptee: NSObject, CallUIAdaptee {
|
||||||
if UIApplication.shared.applicationState == .active {
|
if UIApplication.shared.applicationState == .active {
|
||||||
Logger.debug("skipping notification since app is already active.")
|
Logger.debug("skipping notification since app is already active.")
|
||||||
} else {
|
} else {
|
||||||
notificationsAdapter.presentIncomingCall(call, callerName: callerName)
|
notificationPresenter.presentIncomingCall(call, callerName: callerName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func reportMissedCall(_ call: SignalCall, callerName: String) {
|
func reportMissedCall(_ call: SignalCall, callerName: String) {
|
||||||
AssertIsOnMainThread()
|
AssertIsOnMainThread()
|
||||||
|
|
||||||
notificationsAdapter.presentMissedCall(call, callerName: callerName)
|
notificationPresenter.presentMissedCall(call, callerName: callerName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func answerCall(localId: UUID) {
|
func answerCall(localId: UUID) {
|
||||||
|
|
|
@ -20,7 +20,7 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
|
||||||
|
|
||||||
private let callManager: CallKitCallManager
|
private let callManager: CallKitCallManager
|
||||||
internal let callService: CallService
|
internal let callService: CallService
|
||||||
internal let notificationsAdapter: NotificationsAdapter
|
internal let notificationPresenter: NotificationPresenter
|
||||||
internal let contactsManager: OWSContactsManager
|
internal let contactsManager: OWSContactsManager
|
||||||
private let showNamesOnCallScreen: Bool
|
private let showNamesOnCallScreen: Bool
|
||||||
private let provider: CXProvider
|
private let provider: CXProvider
|
||||||
|
@ -76,7 +76,7 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
|
||||||
return providerConfiguration
|
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()
|
AssertIsOnMainThread()
|
||||||
|
|
||||||
Logger.debug("")
|
Logger.debug("")
|
||||||
|
@ -84,7 +84,7 @@ final class CallKitCallUIAdaptee: NSObject, CallUIAdaptee, CXProviderDelegate {
|
||||||
self.callManager = CallKitCallManager(showNamesOnCallScreen: showNamesOnCallScreen)
|
self.callManager = CallKitCallManager(showNamesOnCallScreen: showNamesOnCallScreen)
|
||||||
self.callService = callService
|
self.callService = callService
|
||||||
self.contactsManager = contactsManager
|
self.contactsManager = contactsManager
|
||||||
self.notificationsAdapter = notificationsAdapter
|
self.notificationPresenter = notificationPresenter
|
||||||
|
|
||||||
self.provider = type(of: self).sharedProvider(useSystemCallLog: useSystemCallLog)
|
self.provider = type(of: self).sharedProvider(useSystemCallLog: useSystemCallLog)
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import SignalMessaging
|
||||||
import WebRTC
|
import WebRTC
|
||||||
|
|
||||||
protocol CallUIAdaptee {
|
protocol CallUIAdaptee {
|
||||||
var notificationsAdapter: NotificationsAdapter { get }
|
var notificationPresenter: NotificationPresenter { get }
|
||||||
var callService: CallService { get }
|
var callService: CallService { get }
|
||||||
var hasManualRinger: Bool { get }
|
var hasManualRinger: Bool { get }
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ extension CallUIAdaptee {
|
||||||
internal func reportMissedCall(_ call: SignalCall, callerName: String) {
|
internal func reportMissedCall(_ call: SignalCall, callerName: String) {
|
||||||
AssertIsOnMainThread()
|
AssertIsOnMainThread()
|
||||||
|
|
||||||
notificationsAdapter.presentMissedCall(call, callerName: callerName)
|
notificationPresenter.presentMissedCall(call, callerName: callerName)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal func startAndShowOutgoingCall(recipientId: String, hasLocalVideo: Bool) {
|
internal func startAndShowOutgoingCall(recipientId: String, hasLocalVideo: Bool) {
|
||||||
|
@ -88,7 +88,7 @@ extension CallUIAdaptee {
|
||||||
internal let audioService: CallAudioService
|
internal let audioService: CallAudioService
|
||||||
internal let callService: CallService
|
internal let callService: CallService
|
||||||
|
|
||||||
public required init(callService: CallService, contactsManager: OWSContactsManager, notificationsAdapter: NotificationsAdapter) {
|
public required init(callService: CallService, contactsManager: OWSContactsManager, notificationPresenter: NotificationPresenter) {
|
||||||
AssertIsOnMainThread()
|
AssertIsOnMainThread()
|
||||||
|
|
||||||
self.contactsManager = contactsManager
|
self.contactsManager = contactsManager
|
||||||
|
@ -99,13 +99,13 @@ extension CallUIAdaptee {
|
||||||
// e.g. you can't receive calls in the call screen.
|
// e.g. you can't receive calls in the call screen.
|
||||||
// So we use the non-CallKit call UI.
|
// So we use the non-CallKit call UI.
|
||||||
Logger.info("choosing non-callkit adaptee for simulator.")
|
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, *) {
|
} else if #available(iOS 11, *) {
|
||||||
Logger.info("choosing callkit adaptee for iOS11+")
|
Logger.info("choosing callkit adaptee for iOS11+")
|
||||||
let showNames = Environment.shared.preferences.notificationPreviewType() != .noNameNoPreview
|
let showNames = Environment.shared.preferences.notificationPreviewType() != .noNameNoPreview
|
||||||
let useSystemCallLog = Environment.shared.preferences.isSystemCallLogEnabled()
|
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() {
|
} else if #available(iOS 10.0, *), Environment.shared.preferences.isCallKitEnabled() {
|
||||||
Logger.info("choosing callkit adaptee for iOS10")
|
Logger.info("choosing callkit adaptee for iOS10")
|
||||||
let hideNames = Environment.shared.preferences.isCallKitPrivacyEnabled() || Environment.shared.preferences.notificationPreviewType() == .noNameNoPreview
|
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
|
// All CallKit calls use the system call log on iOS10
|
||||||
let useSystemCallLog = true
|
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 {
|
} else {
|
||||||
Logger.info("choosing non-callkit adaptee")
|
Logger.info("choosing non-callkit adaptee")
|
||||||
adaptee = NonCallKitCallUIAdaptee(callService: callService, notificationsAdapter: notificationsAdapter)
|
adaptee = NonCallKitCallUIAdaptee(callService: callService, notificationPresenter: notificationPresenter)
|
||||||
}
|
}
|
||||||
|
|
||||||
audioService = CallAudioService(handleRinging: adaptee.hasManualRinger)
|
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
|
public var accountManager: AccountManager
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
public var notificationsAdapter: NotificationsAdapter
|
public var notificationPresenter: NotificationPresenter
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
public var pushRegistrationManager: PushRegistrationManager
|
public var pushRegistrationManager: PushRegistrationManager
|
||||||
|
|
||||||
@objc
|
|
||||||
public var pushManager: PushManager
|
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
public var sessionResetJobQueue: SessionResetJobQueue
|
public var sessionResetJobQueue: SessionResetJobQueue
|
||||||
|
|
||||||
|
@ -64,9 +61,8 @@ import SignalMessaging
|
||||||
self.outboundCallInitiator = OutboundCallInitiator()
|
self.outboundCallInitiator = OutboundCallInitiator()
|
||||||
self.messageFetcherJob = MessageFetcherJob()
|
self.messageFetcherJob = MessageFetcherJob()
|
||||||
self.accountManager = AccountManager()
|
self.accountManager = AccountManager()
|
||||||
self.notificationsAdapter = NotificationsAdapter()
|
self.notificationPresenter = NotificationPresenter()
|
||||||
self.pushRegistrationManager = PushRegistrationManager()
|
self.pushRegistrationManager = PushRegistrationManager()
|
||||||
self.pushManager = PushManager()
|
|
||||||
self.sessionResetJobQueue = SessionResetJobQueue()
|
self.sessionResetJobQueue = SessionResetJobQueue()
|
||||||
self.backup = OWSBackup()
|
self.backup = OWSBackup()
|
||||||
self.backupLazyRestore = BackupLazyRestore()
|
self.backupLazyRestore = BackupLazyRestore()
|
||||||
|
@ -81,7 +77,7 @@ import SignalMessaging
|
||||||
callService.createCallUIAdapter()
|
callService.createCallUIAdapter()
|
||||||
|
|
||||||
// Hang certain singletons on SSKEnvironment too.
|
// Hang certain singletons on SSKEnvironment too.
|
||||||
SSKEnvironment.shared.notificationsManager = notificationsAdapter
|
SSKEnvironment.shared.notificationsManager = notificationPresenter
|
||||||
SSKEnvironment.shared.callMessageHandler = callMessageHandler
|
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
|
import Foundation
|
||||||
|
@ -21,8 +21,12 @@ public enum PushRegistrationError: Error {
|
||||||
|
|
||||||
// MARK: - Dependencies
|
// MARK: - Dependencies
|
||||||
|
|
||||||
private var pushManager: PushManager {
|
private var messageFetcherJob: MessageFetcherJob {
|
||||||
return PushManager.shared()
|
return AppEnvironment.shared.messageFetcherJob
|
||||||
|
}
|
||||||
|
|
||||||
|
private var notificationPresenter: NotificationPresenter {
|
||||||
|
return AppEnvironment.shared.notificationPresenter
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Singleton class
|
// MARK: - Singleton class
|
||||||
|
@ -40,9 +44,6 @@ public enum PushRegistrationError: Error {
|
||||||
SwiftSingletons.register(self)
|
SwiftSingletons.register(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var userNotificationSettingsPromise: Promise<Void>?
|
|
||||||
private var userNotificationSettingsResolver: Resolver<Void>?
|
|
||||||
|
|
||||||
private var vanillaTokenPromise: Promise<Data>?
|
private var vanillaTokenPromise: Promise<Data>?
|
||||||
private var vanillaTokenResolver: Resolver<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
|
// MARK: Vanilla push token
|
||||||
|
|
||||||
// Vanilla push token is obtained from the system via AppDelegate
|
// 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) {
|
public func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) {
|
||||||
Logger.info("")
|
Logger.info("")
|
||||||
assert(type == .voIP)
|
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) {
|
public func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {
|
||||||
|
@ -138,26 +126,11 @@ public enum PushRegistrationError: Error {
|
||||||
// MARK: helpers
|
// MARK: helpers
|
||||||
|
|
||||||
// User notification settings must be registered *before* AppDelegate will
|
// User notification settings must be registered *before* AppDelegate will
|
||||||
// return any requested push tokens. We don't consider the notifications settings registration
|
// return any requested push tokens.
|
||||||
// *complete* until AppDelegate#didRegisterUserNotificationSettings is called.
|
|
||||||
private func registerUserNotificationSettings() -> Promise<Void> {
|
private func registerUserNotificationSettings() -> Promise<Void> {
|
||||||
AssertIsOnMainThread()
|
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")
|
Logger.info("registering user notification settings")
|
||||||
|
return notificationPresenter.registerNotificationSettings()
|
||||||
UIApplication.shared.registerUserNotificationSettings(self.pushManager.userNotificationSettings)
|
|
||||||
|
|
||||||
return promise
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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
|
import Foundation
|
||||||
|
@ -26,6 +26,15 @@ import Foundation
|
||||||
@objc public class MessageStrings: NSObject {
|
@objc public class MessageStrings: NSObject {
|
||||||
@objc
|
@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.")
|
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 {
|
@objc public class CallStrings: NSObject {
|
||||||
|
@ -47,6 +56,10 @@ import Foundation
|
||||||
static public let callBackButtonTitle = NSLocalizedString("CALLBACK_BUTTON_TITLE", comment: "notification action")
|
static public let callBackButtonTitle = NSLocalizedString("CALLBACK_BUTTON_TITLE", comment: "notification action")
|
||||||
@objc
|
@objc
|
||||||
static public let showThreadButtonTitle = NSLocalizedString("SHOW_THREAD_BUTTON_TITLE", comment: "notification action")
|
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
|
// MARK: Missed Call Notification
|
||||||
@objc
|
@objc
|
||||||
|
@ -59,6 +72,12 @@ import Foundation
|
||||||
static public let missedCallWithIdentityChangeNotificationBodyWithoutCallerName = NSLocalizedString("MISSED_CALL_WITH_CHANGED_IDENTITY_BODY_WITHOUT_CALLER_NAME", comment: "notification title")
|
static public let missedCallWithIdentityChangeNotificationBodyWithoutCallerName = NSLocalizedString("MISSED_CALL_WITH_CHANGED_IDENTITY_BODY_WITHOUT_CALLER_NAME", comment: "notification title")
|
||||||
@objc
|
@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}}")
|
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 {
|
@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.
|
// Anything used by Swift outside of the framework must be imported.
|
||||||
#import <Reachability/Reachability.h>
|
#import <Reachability/Reachability.h>
|
||||||
#import <SignalServiceKit/ContactsManagerProtocol.h>
|
#import <SignalServiceKit/ContactsManagerProtocol.h>
|
||||||
|
#import <SignalServiceKit/NotificationsProtocol.h>
|
||||||
#import <SignalServiceKit/OWSFileSystem.h>
|
#import <SignalServiceKit/OWSFileSystem.h>
|
||||||
#import <SignalServiceKit/OWSOperation.h>
|
#import <SignalServiceKit/OWSOperation.h>
|
||||||
#import <SignalServiceKit/OWSSyncManagerProtocol.h>
|
#import <SignalServiceKit/OWSSyncManagerProtocol.h>
|
||||||
|
|
|
@ -241,9 +241,12 @@ public class IncomingMessageFactory: NSObject, Factory {
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
public func create(transaction: YapDatabaseReadWriteTransaction) -> TSIncomingMessage {
|
public func create(transaction: YapDatabaseReadWriteTransaction) -> TSIncomingMessage {
|
||||||
|
|
||||||
|
let thread = threadCreator(transaction)
|
||||||
|
|
||||||
let item = TSIncomingMessage(incomingMessageWithTimestamp: timestampBuilder(),
|
let item = TSIncomingMessage(incomingMessageWithTimestamp: timestampBuilder(),
|
||||||
in: threadCreator(transaction),
|
in: thread,
|
||||||
authorId: authorIdBuilder(),
|
authorId: authorIdBuilder(thread),
|
||||||
sourceDeviceId: sourceDeviceIdBuilder(),
|
sourceDeviceId: sourceDeviceIdBuilder(),
|
||||||
messageBody: messageBodyBuilder(),
|
messageBody: messageBodyBuilder(),
|
||||||
attachmentIds: attachmentIdsBuilder(),
|
attachmentIds: attachmentIdsBuilder(),
|
||||||
|
@ -279,9 +282,17 @@ public class IncomingMessageFactory: NSObject, Factory {
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
public var authorIdBuilder: () -> String = {
|
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
|
return CommonGenerator.contactId
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
public var sourceDeviceIdBuilder: () -> UInt32 = {
|
public var sourceDeviceIdBuilder: () -> UInt32 = {
|
||||||
|
@ -344,7 +355,6 @@ class GroupThreadFactory: NSObject, Factory {
|
||||||
|
|
||||||
(0..<messageCount).forEach { _ in
|
(0..<messageCount).forEach { _ in
|
||||||
if [true, false].ows_randomElement()! {
|
if [true, false].ows_randomElement()! {
|
||||||
incomingMessageFactory.authorIdBuilder = { thread.recipientIdentifiers.ows_randomElement()! }
|
|
||||||
_ = incomingMessageFactory.create(transaction: transaction)
|
_ = incomingMessageFactory.create(transaction: transaction)
|
||||||
} else {
|
} else {
|
||||||
_ = outgoingMessageFactory.create(transaction: transaction)
|
_ = 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"
|
#import "MockSSKEnvironment.h"
|
||||||
|
@ -13,7 +13,6 @@
|
||||||
#import "OWSFakeContactsUpdater.h"
|
#import "OWSFakeContactsUpdater.h"
|
||||||
#import "OWSFakeMessageSender.h"
|
#import "OWSFakeMessageSender.h"
|
||||||
#import "OWSFakeNetworkManager.h"
|
#import "OWSFakeNetworkManager.h"
|
||||||
#import "OWSFakeNotificationsManager.h"
|
|
||||||
#import "OWSFakeProfileManager.h"
|
#import "OWSFakeProfileManager.h"
|
||||||
#import "OWSIdentityManager.h"
|
#import "OWSIdentityManager.h"
|
||||||
#import "OWSMessageDecrypter.h"
|
#import "OWSMessageDecrypter.h"
|
||||||
|
@ -109,7 +108,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
}
|
}
|
||||||
|
|
||||||
self.callMessageHandler = [OWSFakeCallMessageHandler new];
|
self.callMessageHandler = [OWSFakeCallMessageHandler new];
|
||||||
self.notificationsManager = [OWSFakeNotificationsManager new];
|
self.notificationsManager = [NoopNotificationsManager new];
|
||||||
return self;
|
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
|
@objc
|
||||||
public class NoopNotificationsManager: NSObject, NotificationsProtocol {
|
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